chore: 文档与 IDE 配置整理
- .kiro/specs/ → docs/specs/(41 个历史需求 spec 迁移,移除 .config.kiro) - CLAUDE.md 三层拆分:根文件精简 + apps/backend/CLAUDE.md + .claude/commands/ - 新增 /spec-close、/pre-change 两个工作流命令 - DDL 基线刷新(从测试库重新导出 11 个文件,dws 35→38 表,biz 18→21 表) - BD_Manual → BD_manual 命名统一(48 个文件) - 修复 3 处文档与数据库不一致(auth.users.status 默认值、scheduled_tasks 字段、RLS 视图数) - 新增 BD_manual_public_rbac_tables.md(public schema 8 张 RBAC/工作流表) - 合并 biz.trigger_jobs 文档(10→12 字段,归档独立文档) - docs/database/README.md 索引更新 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,22 +1,26 @@
|
||||
# BD_Manual:biz Schema AI 表(对话记录 + 消息 + 缓存)
|
||||
# BD_Manual:biz Schema AI 表(对话记录 + 消息 + 缓存 + 运行日志 + 调度记录)
|
||||
|
||||
> 目标库:`test_zqyy_app`(通过 `APP_DB_DSN` 连接)
|
||||
> 迁移脚本:
|
||||
> - `db/zqyy_app/migrations/2026-03-08__create_ai_tables.sql`(初始建表)
|
||||
> - `db/zqyy_app/migrations/2026-03-20__rns14_chat_module_extend.sql`(RNS1.4 CHAT 扩展)
|
||||
> 关联 SPEC:`05-miniapp-ai-integration`(P5 AI 集成层)、`rns1-chat-integration`(RNS1.4 CHAT 对齐与联调收尾)
|
||||
> - `db/zqyy_app/migrations/2026-03-22__p14_ai_module.sql`(P14 DashScope 迁移 + 调度器完善)
|
||||
> - `db/zqyy_app/migrations/2026-03-23__p15_ai_monitoring.sql`(P15 AI 监控后台 — alert_status + BRIN 索引)
|
||||
> 关联 SPEC:`05-miniapp-ai-integration`(P5 AI 集成层)、`rns1-chat-integration`(RNS1.4 CHAT 对齐与联调收尾)、`P14-ai-dashscope-migration`(P14 DashScope 迁移)、`ai-monitoring-testing`(P15 AI 监控后台)
|
||||
|
||||
---
|
||||
|
||||
## 1. 变更说明
|
||||
|
||||
### 新增表(3 张,P5 初始建表)
|
||||
### 新增表(5 张)
|
||||
|
||||
| # | 表名 | 用途 | 字段数(初始→当前) |
|
||||
|---|------|------|---------------------|
|
||||
| 1 | `biz.ai_conversations` | AI 对话记录:每次 AI 调用(用户主动或系统自动)创建一条 | 8 → 13 |
|
||||
| 2 | `biz.ai_messages` | AI 消息记录:对话中的每条消息(输入/输出/系统) | 6 → 7 |
|
||||
| 3 | `biz.ai_cache` | AI 应用缓存:各应用的结构化输出结果 | 9 |
|
||||
| # | 表名 | 用途 | 字段数(初始→当前) | 来源 |
|
||||
|---|------|------|---------------------|------|
|
||||
| 1 | `biz.ai_conversations` | AI 对话记录:每次 AI 调用(用户主动或系统自动)创建一条 | 8 → 14 | P5 |
|
||||
| 2 | `biz.ai_messages` | AI 消息记录:对话中的每条消息(输入/输出/系统) | 6 → 7 | P5 |
|
||||
| 3 | `biz.ai_cache` | AI 应用缓存:各应用的结构化输出结果 | 9 → 10 | P5 |
|
||||
| 4 | `biz.ai_run_logs` | **P14 新增** — AI 运行记录:每次 DashScope API 调用的详细日志 | 14 → 15 | P14, P15 |
|
||||
| 5 | `biz.ai_trigger_jobs` | **P14 新增** — 调度运行记录:每次 AI 事件触发的编排执行记录 | 13 | P14 |
|
||||
|
||||
### RNS1.4 CHAT 模块扩展字段(2026-03-20)
|
||||
|
||||
@@ -25,9 +29,26 @@
|
||||
| 1 | `biz.ai_conversations` | `context_type`, `context_id`, `title`, `last_message`, `last_message_at` | 多入口对话复用 + 历史列表展示与排序 |
|
||||
| 2 | `biz.ai_messages` | `reference_card` | 引用卡片 JSON(客户概览等结构化上下文数据) |
|
||||
|
||||
### P14 DashScope 迁移扩展字段(2026-03-22)
|
||||
|
||||
| # | 表名 | 新增字段 | 用途 |
|
||||
|---|------|---------|------|
|
||||
| 1 | `biz.ai_conversations` | `session_id` | 百炼 session_id(格式 `conv_{id}_{ts}`),仅 App1 使用 |
|
||||
| 2 | `biz.ai_cache` | `status` | 缓存状态:valid / expired / invalidated / generating |
|
||||
|
||||
### P15 AI 监控后台扩展(2026-03-23)
|
||||
|
||||
| # | 表名 | 变更类型 | 说明 |
|
||||
|---|------|---------|------|
|
||||
| 1 | `biz.ai_run_logs` | 新增字段 `alert_status` | 告警处理状态:NULL / pending / acknowledged / ignored |
|
||||
| 2 | `biz.ai_run_logs` | 新增约束 `chk_ai_run_logs_alert_status` | CHECK (alert_status IS NULL OR alert_status IN ('pending', 'acknowledged', 'ignored')) |
|
||||
| 3 | `biz.ai_run_logs` | 新增部分索引 `idx_ai_run_logs_alert` | (alert_status, created_at DESC) WHERE status IN ('failed', 'timeout', 'circuit_open') — 告警列表查询 |
|
||||
| 4 | `biz.ai_run_logs` | 新增 BRIN 索引 `idx_ai_run_logs_created_brin` | BRIN (created_at) WITH (pages_per_range = 32) — Dashboard 聚合优化 |
|
||||
| 5 | `biz.ai_run_logs` | 回填 | 已有 status IN ('failed','timeout','circuit_open') 的记录 alert_status 设为 'pending' |
|
||||
|
||||
### 表字段明细
|
||||
|
||||
#### biz.ai_conversations(13 字段)
|
||||
#### biz.ai_conversations(14 字段)
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
@@ -39,11 +60,12 @@
|
||||
| `source_page` | VARCHAR(100) | 可空 | 来源页面标识 |
|
||||
| `source_context` | JSONB | 可空 | 页面上下文 JSON |
|
||||
| `created_at` | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 创建时间 |
|
||||
| `context_type` | VARCHAR(20) | 可空 | **RNS1.4 新增** — 对话关联上下文类型:task(任务)/ customer(客户)/ coach(助教)/ general(通用) |
|
||||
| `context_id` | VARCHAR(50) | 可空 | **RNS1.4 新增** — 关联上下文 ID:task 入口为 taskId,customer 入口为 customerId,coach 入口为 coachId,general 为 NULL |
|
||||
| `title` | VARCHAR(200) | 可空 | **RNS1.4 新增** — 对话标题:自定义 > 上下文名称 > 首条消息前20字 |
|
||||
| `context_type` | VARCHAR(20) | 可空 | **RNS1.4 新增** — 对话关联上下文类型:task / customer / coach / general |
|
||||
| `context_id` | VARCHAR(50) | 可空 | **RNS1.4 新增** — 关联上下文 ID |
|
||||
| `title` | VARCHAR(200) | 可空 | **RNS1.4 新增** — 对话标题 |
|
||||
| `last_message` | TEXT | 可空 | **RNS1.4 新增** — 最后一条消息内容摘要(截断至100字) |
|
||||
| `last_message_at` | TIMESTAMPTZ | 可空 | **RNS1.4 新增** — 最后消息时间,用于历史列表排序和对话复用时限判断 |
|
||||
| `last_message_at` | TIMESTAMPTZ | 可空 | **RNS1.4 新增** — 最后消息时间 |
|
||||
| `session_id` | VARCHAR(100) | 可空 | **P14 新增** — 百炼 session_id,格式 `conv_{conversation_id}_{created_timestamp}`,仅 App1 使用 |
|
||||
|
||||
#### biz.ai_messages(7 字段)
|
||||
|
||||
@@ -57,7 +79,7 @@
|
||||
| `created_at` | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 创建时间 |
|
||||
| `reference_card` | JSONB | 可空 | **RNS1.4 新增** — 引用卡片 JSON:`{type, title, summary, data}`,用于展示客户概览等结构化上下文数据 |
|
||||
|
||||
#### biz.ai_cache(9 字段)
|
||||
#### biz.ai_cache(10 字段)
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
@@ -70,6 +92,7 @@
|
||||
| `triggered_by` | VARCHAR(100) | 可空 | 触发来源标识 |
|
||||
| `created_at` | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 创建时间 |
|
||||
| `expires_at` | TIMESTAMPTZ | 可空 | 可选过期时间 |
|
||||
| `status` | VARCHAR(20) | DEFAULT 'valid', CHECK | **P14 新增** — 缓存状态:valid(有效)/ expired(已过期)/ invalidated(手动失效)/ generating(生成中) |
|
||||
|
||||
### cache_type 枚举值与 target_id 约定
|
||||
|
||||
@@ -89,14 +112,62 @@
|
||||
|----|-----------|------|------|
|
||||
| `ai_conversations` | `idx_ai_conv_user_site` | INDEX | `(user_id, site_id, created_at DESC)` — 用户历史对话列表查询 |
|
||||
| `ai_conversations` | `idx_ai_conv_app_site` | INDEX | `(app_id, site_id, created_at DESC)` — 按应用查询对话 |
|
||||
| `ai_conversations` | `idx_ai_conv_context` | INDEX(条件) | **RNS1.4** — `(user_id, site_id, context_type, context_id, last_message_at DESC) WHERE context_type IS NOT NULL` |
|
||||
| `ai_conversations` | `idx_ai_conv_last_msg` | INDEX | **RNS1.4** — `(user_id, site_id, last_message_at DESC NULLS LAST)` |
|
||||
| `ai_messages` | FK `conversation_id` | FK | → `biz.ai_conversations(id)` ON DELETE CASCADE |
|
||||
| `ai_messages` | `chk_ai_msg_role` | CHECK | `role IN ('user', 'assistant', 'system')` |
|
||||
| `ai_messages` | `idx_ai_msg_conv` | INDEX | `(conversation_id, created_at)` — 对话消息列表 |
|
||||
| `ai_cache` | `chk_ai_cache_type` | CHECK | 7 个枚举值 |
|
||||
| `ai_cache` | `chk_ai_cache_status` | CHECK | **P14** — `status IN ('valid', 'expired', 'invalidated', 'generating')` |
|
||||
| `ai_cache` | `idx_ai_cache_lookup` | INDEX | `(cache_type, site_id, target_id, created_at DESC)` — 查询最新缓存 |
|
||||
| `ai_cache` | `idx_ai_cache_cleanup` | INDEX | `(cache_type, site_id, target_id, created_at)` — 清理超限记录(ASC 排序便于删除最旧) |
|
||||
| `ai_conversations` | `idx_ai_conv_context` | INDEX(条件) | **RNS1.4 新增** — `(user_id, site_id, context_type, context_id, last_message_at DESC NULLS LAST) WHERE context_type IS NOT NULL` — 上下文对话查找(多入口复用) |
|
||||
| `ai_conversations` | `idx_ai_conv_last_msg` | INDEX | **RNS1.4 新增** — `(user_id, site_id, last_message_at DESC NULLS LAST)` — 历史列表排序优化(CHAT-1 倒序) |
|
||||
| `ai_cache` | `idx_ai_cache_cleanup` | INDEX | `(cache_type, site_id, target_id, created_at)` — 清理超限记录 |
|
||||
| `ai_run_logs` | `idx_ai_run_logs_site_app` | INDEX | **P14** — `(site_id, app_type)` |
|
||||
| `ai_run_logs` | `idx_ai_run_logs_created` | INDEX | **P14** — `(created_at)` — Token 预算聚合 |
|
||||
| `ai_run_logs` | `idx_ai_run_logs_status` | INDEX | **P14** — `(status)` |
|
||||
| `ai_run_logs` | `chk_ai_run_logs_alert_status` | CHECK | **P15** — `alert_status IS NULL OR alert_status IN ('pending', 'acknowledged', 'ignored')` |
|
||||
| `ai_run_logs` | `idx_ai_run_logs_alert` | INDEX(部分) | **P15** — `(alert_status, created_at DESC) WHERE status IN ('failed', 'timeout', 'circuit_open')` — 告警列表查询优化 |
|
||||
| `ai_run_logs` | `idx_ai_run_logs_created_brin` | BRIN INDEX | **P15** — `BRIN (created_at) WITH (pages_per_range = 32)` — Dashboard 聚合查询优化,适合按时间顺序插入的表 |
|
||||
| `ai_trigger_jobs` | `idx_ai_trigger_jobs_site` | INDEX | **P14** — `(site_id, event_type)` |
|
||||
| `ai_trigger_jobs` | `idx_ai_trigger_jobs_dedup` | INDEX(条件) | **P14** — `(event_type, member_id, site_id, created_at) WHERE status NOT IN ('skipped_duplicate')` — 去重 |
|
||||
| `ai_trigger_jobs` | `idx_ai_trigger_jobs_status` | INDEX | **P14** — `(status)` |
|
||||
|
||||
#### biz.ai_run_logs(15 字段,P14 新增 + P15 扩展)
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | BIGSERIAL | PK | 自增主键 |
|
||||
| `site_id` | BIGINT | NOT NULL | 门店 ID |
|
||||
| `app_type` | VARCHAR(30) | NOT NULL | 应用类型:app1_chat / app2_finance / ... / app8_consolidate |
|
||||
| `trigger_type` | VARCHAR(20) | NOT NULL | 触发类型:user / scheduled / event / forced |
|
||||
| `member_id` | BIGINT | 可空 | 会员 ID |
|
||||
| `request_prompt` | TEXT | 可空 | 请求 prompt(截断前 2000 字符) |
|
||||
| `response_text` | TEXT | 可空 | 响应文本 |
|
||||
| `tokens_used` | INTEGER | DEFAULT 0 | 消耗 token 数 |
|
||||
| `latency_ms` | INTEGER | 可空 | 调用延迟(毫秒) |
|
||||
| `status` | VARCHAR(20) | NOT NULL DEFAULT 'pending' | 状态:pending / running / success / failed / timeout / budget_exceeded |
|
||||
| `error_message` | TEXT | 可空 | 错误信息 |
|
||||
| `session_id` | VARCHAR(100) | 可空 | 百炼 session_id(仅 App1) |
|
||||
| `alert_status` | VARCHAR(20) | DEFAULT NULL, CHECK | **P15 新增** — 告警处理状态:NULL(非告警记录)/ pending(待处理)/ acknowledged(已确认)/ ignored(已忽略)。仅 status IN ('failed','timeout','circuit_open') 的记录才设置此字段 |
|
||||
| `created_at` | TIMESTAMPTZ | NOT NULL DEFAULT now() | 创建时间 |
|
||||
| `finished_at` | TIMESTAMPTZ | 可空 | 完成时间 |
|
||||
|
||||
#### biz.ai_trigger_jobs(13 字段,P14 新增)
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | BIGSERIAL | PK | 自增主键 |
|
||||
| `site_id` | BIGINT | NOT NULL | 门店 ID |
|
||||
| `event_type` | VARCHAR(30) | NOT NULL | 事件类型:consumption / dws_completed / note_created / task_assigned |
|
||||
| `connector_type` | VARCHAR(30) | DEFAULT 'feiqiu' | 连接器类型 |
|
||||
| `member_id` | BIGINT | 可空 | 会员 ID |
|
||||
| `payload` | JSONB | 可空 | 附加数据 |
|
||||
| `status` | VARCHAR(20) | NOT NULL DEFAULT 'pending' | 状态:pending / running / completed / failed / skipped_duplicate / budget_exceeded |
|
||||
| `is_forced` | BOOLEAN | DEFAULT false | 是否强制执行(跳过去重检查) |
|
||||
| `app_chain` | VARCHAR(100) | 可空 | 调用链描述,如 `app3→app8→app7` |
|
||||
| `started_at` | TIMESTAMPTZ | 可空 | 开始时间 |
|
||||
| `finished_at` | TIMESTAMPTZ | 可空 | 完成时间 |
|
||||
| `error_message` | TEXT | 可空 | 错误信息 |
|
||||
| `created_at` | TIMESTAMPTZ | NOT NULL DEFAULT now() | 创建时间 |
|
||||
|
||||
---
|
||||
|
||||
@@ -104,19 +175,56 @@
|
||||
|
||||
| 组件 | 影响 |
|
||||
|------|------|
|
||||
| ETL 任务 | 无影响。AI 表属于 `biz` Schema,不参与 ETL 流程 |
|
||||
| 后端 API | 直接依赖。P5 AI 模块(`apps/backend/app/ai/`)将基于这三张表实现对话持久化、缓存读写、SSE 流式对话等功能 |
|
||||
| 后端 API(RNS1.4) | **直接依赖**。CHAT 模块(`apps/backend/app/routers/xcx_chat.py`、`apps/backend/app/services/chat_service.py`)依赖 `ai_conversations` 的 5 个新字段(`context_type`/`context_id`/`title`/`last_message`/`last_message_at`)实现多入口对话复用、历史列表展示与排序;依赖 `ai_messages.reference_card` 存储引用卡片 JSON |
|
||||
| ETL 任务 | **P14 新增**:DWS 任务完成后通过 `utils/ai_trigger.py` 发送 HTTP 触发事件到后端 `ai_trigger_jobs`,失败不中断 ETL 流程 |
|
||||
| 后端 API | 直接依赖。P5 AI 模块(`apps/backend/app/ai/`)基于这些表实现对话持久化、缓存读写、SSE 流式对话等功能 |
|
||||
| 后端 API(RNS1.4) | **直接依赖**。CHAT 模块依赖 `ai_conversations` 的 5 个 RNS1.4 字段实现多入口对话复用 |
|
||||
| 后端 API(P14) | **直接依赖**。`ai_run_logs` 用于 Token 预算聚合(BudgetTracker);`ai_trigger_jobs` 用于事件去重和调度记录;`ai_conversations.session_id` 用于百炼会话管理;`ai_cache.status` 用于缓存状态控制 |
|
||||
| 小程序 | 间接依赖。小程序通过后端 AI API 间接使用对话和缓存数据 |
|
||||
| 小程序(RNS1.4) | **间接依赖**。`pages/chat/chat.ts` 和 `pages/chat-history/chat-history.ts` 通过 CHAT-1/2/3/4 端点间接依赖新字段(`title`→对话标题、`lastMessage`→摘要、`timestamp`→排序、`referenceCard`→引用卡片渲染) |
|
||||
| 管理后台 | 暂无影响。后续可能增加 AI 调用统计和缓存管理界面 |
|
||||
| `member_retention_clue` | 间接关联。App8(维客线索整理)的结果同时写入 `ai_cache` 和 `member_retention_clue` 表 |
|
||||
| 现有 `biz` Schema | 兼容。P5 新增 3 张表;RNS1.4 仅在已有表上 ADD COLUMN / CREATE INDEX,不修改已有字段或约束 |
|
||||
| 管理后台 | **P15 直接依赖**。admin-web AI 监控后台(4 个页面)依赖 `ai_run_logs.alert_status` 实现告警管理(确认/忽略),依赖 BRIN 索引优化 Dashboard 聚合查询性能 |
|
||||
| `member_retention_clue` | 间接关联。App8 结果同时写入 `ai_cache` 和 `member_retention_clue` 表(P14 实现幂等 DELETE+INSERT) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 回滚策略
|
||||
|
||||
### 3d. 回滚 P15 AI 监控后台(2026-03-23 迁移)
|
||||
|
||||
按逆序 DROP 新增索引、约束和字段:
|
||||
|
||||
```sql
|
||||
BEGIN;
|
||||
DROP INDEX IF EXISTS biz.idx_ai_run_logs_created_brin;
|
||||
DROP INDEX IF EXISTS biz.idx_ai_run_logs_alert;
|
||||
ALTER TABLE biz.ai_run_logs DROP CONSTRAINT IF EXISTS chk_ai_run_logs_alert_status;
|
||||
ALTER TABLE biz.ai_run_logs DROP COLUMN IF EXISTS alert_status;
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
注意:
|
||||
- 回滚后 admin-web AI 监控后台的告警管理功能将不可用
|
||||
- Dashboard 聚合查询性能可能下降(失去 BRIN 索引)
|
||||
- 回滚不影响 P14 的核心功能(调度器、预算追踪等)
|
||||
|
||||
### 3c. 回滚 P14 DashScope 迁移(2026-03-22 迁移)
|
||||
|
||||
按逆序 DROP 新增表和字段:
|
||||
|
||||
```sql
|
||||
-- 删除 P14 新增约束和字段
|
||||
ALTER TABLE biz.ai_cache DROP CONSTRAINT IF EXISTS chk_ai_cache_status;
|
||||
ALTER TABLE biz.ai_cache DROP COLUMN IF EXISTS status;
|
||||
ALTER TABLE biz.ai_conversations DROP COLUMN IF EXISTS session_id;
|
||||
|
||||
-- 删除 P14 新增表
|
||||
DROP TABLE IF EXISTS biz.ai_trigger_jobs;
|
||||
DROP TABLE IF EXISTS biz.ai_run_logs;
|
||||
```
|
||||
|
||||
注意:
|
||||
- 回滚后 P14 AI 调度器(dispatcher)、Token 预算追踪(BudgetTracker)将无法正常工作
|
||||
- `ai_run_logs` 和 `ai_trigger_jobs` 中的数据将丢失,需先备份
|
||||
- 回滚不影响 P5 和 RNS1.4 的功能
|
||||
|
||||
### 3a. 回滚 RNS1.4 CHAT 扩展(2026-03-20 迁移)
|
||||
|
||||
按逆序 DROP 新增索引和字段:
|
||||
@@ -185,7 +293,7 @@ SELECT column_name, data_type, is_nullable
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'biz' AND table_name = 'ai_conversations'
|
||||
ORDER BY ordinal_position;
|
||||
-- 预期:返回 13 行(P5 原始 8 字段 + RNS1.4 新增 5 字段)
|
||||
-- 预期:返回 14 行(P5 原始 8 + RNS1.4 新增 5 + P14 session_id = 14)
|
||||
|
||||
-- 3. 验证 ai_messages 的外键和 CHECK 约束
|
||||
SELECT conname, contype, pg_get_constraintdef(oid) AS constraint_def
|
||||
@@ -198,7 +306,7 @@ ORDER BY conname;
|
||||
SELECT conname, pg_get_constraintdef(oid) AS constraint_def
|
||||
FROM pg_constraint
|
||||
WHERE conrelid = 'biz.ai_cache'::regclass AND contype = 'c';
|
||||
-- 预期:返回 1 行 chk_ai_cache_type,包含 7 个枚举值
|
||||
-- 预期:返回 2 行(chk_ai_cache_type 含 7 个枚举值,chk_ai_cache_status 含 4 个状态值)
|
||||
|
||||
-- 5. 验证 P5 初始索引全部存在(5 个)
|
||||
SELECT indexname
|
||||
@@ -244,3 +352,101 @@ WHERE schemaname = 'biz' AND tablename = 'ai_conversations'
|
||||
-- idx_ai_conv_context — 含 WHERE context_type IS NOT NULL 条件
|
||||
-- idx_ai_conv_last_msg — (user_id, site_id, last_message_at DESC NULLS LAST)
|
||||
```
|
||||
|
||||
### 4c. P14 DashScope 迁移验证
|
||||
|
||||
```sql
|
||||
-- 9. 验证 ai_run_logs 表存在且字段正确
|
||||
SELECT column_name, data_type, character_maximum_length
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'biz' AND table_name = 'ai_run_logs'
|
||||
ORDER BY ordinal_position;
|
||||
-- 预期:返回 15 行
|
||||
|
||||
-- 10. 验证 ai_trigger_jobs 表存在且字段正确
|
||||
SELECT column_name, data_type, character_maximum_length
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'biz' AND table_name = 'ai_trigger_jobs'
|
||||
ORDER BY ordinal_position;
|
||||
-- 预期:返回 13 行
|
||||
|
||||
-- 11. 验证 ai_run_logs 索引(含 PK)
|
||||
SELECT indexname FROM pg_indexes
|
||||
WHERE schemaname = 'biz' AND tablename = 'ai_run_logs';
|
||||
-- 预期:6 行(PK + 3 P14 索引 + 2 P15 索引)
|
||||
|
||||
-- 12. 验证 ai_trigger_jobs 索引(含去重部分索引)
|
||||
SELECT indexname FROM pg_indexes
|
||||
WHERE schemaname = 'biz' AND tablename = 'ai_trigger_jobs';
|
||||
-- 预期:4 行(PK + 3 个索引)
|
||||
|
||||
-- 13. 验证 ai_conversations.session_id 字段
|
||||
SELECT column_name, data_type, character_maximum_length
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'biz' AND table_name = 'ai_conversations'
|
||||
AND column_name = 'session_id';
|
||||
-- 预期:1 行,varchar(100)
|
||||
|
||||
-- 14. 验证 ai_cache.status 字段 + CHECK 约束
|
||||
SELECT column_name, data_type, column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'biz' AND table_name = 'ai_cache'
|
||||
AND column_name = 'status';
|
||||
-- 预期:1 行,varchar(20),default 'valid'
|
||||
|
||||
SELECT conname FROM pg_constraint
|
||||
WHERE conrelid = 'biz.ai_cache'::regclass AND conname = 'chk_ai_cache_status';
|
||||
-- 预期:1 行
|
||||
```
|
||||
|
||||
### 4d. P15 AI 监控后台验证
|
||||
|
||||
```sql
|
||||
-- 15. 验证 ai_run_logs.alert_status 字段存在
|
||||
SELECT column_name, data_type, character_maximum_length, column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'biz' AND table_name = 'ai_run_logs'
|
||||
AND column_name = 'alert_status';
|
||||
-- 预期:1 行,varchar(20),default NULL
|
||||
|
||||
-- 16. 验证 alert_status CHECK 约束
|
||||
SELECT conname, pg_get_constraintdef(oid) AS constraint_def
|
||||
FROM pg_constraint
|
||||
WHERE conrelid = 'biz.ai_run_logs'::regclass
|
||||
AND conname = 'chk_ai_run_logs_alert_status';
|
||||
-- 预期:1 行,CHECK (alert_status IS NULL OR alert_status IN ('pending', 'acknowledged', 'ignored'))
|
||||
|
||||
-- 17. 验证 P15 新增索引(部分索引 + BRIN 索引)
|
||||
SELECT indexname, indexdef
|
||||
FROM pg_indexes
|
||||
WHERE schemaname = 'biz' AND tablename = 'ai_run_logs'
|
||||
AND indexname IN ('idx_ai_run_logs_alert', 'idx_ai_run_logs_created_brin');
|
||||
-- 预期:2 行
|
||||
-- idx_ai_run_logs_alert — 含 WHERE status IN ('failed', 'timeout', 'circuit_open')
|
||||
-- idx_ai_run_logs_created_brin — USING brin
|
||||
|
||||
-- 18. 验证 ai_run_logs 总索引数(P14 3个 + P15 2个 + PK = 6)
|
||||
SELECT indexname FROM pg_indexes
|
||||
WHERE schemaname = 'biz' AND tablename = 'ai_run_logs';
|
||||
-- 预期:6 行
|
||||
|
||||
-- 19. 验证回填结果:失败/超时/熔断记录的 alert_status 应为 'pending'
|
||||
SELECT COUNT(*) AS unset_alerts
|
||||
FROM biz.ai_run_logs
|
||||
WHERE status IN ('failed', 'timeout', 'circuit_open')
|
||||
AND alert_status IS NULL;
|
||||
-- 预期:0(所有失败记录已回填为 'pending')
|
||||
```
|
||||
|
||||
### P15 admin API 查询模式说明
|
||||
|
||||
P15 admin-web AI 监控后台引入以下典型查询模式:
|
||||
|
||||
| 查询场景 | SQL 模式 | 使用索引 |
|
||||
|---------|---------|---------|
|
||||
| Dashboard 今日统计 | `SELECT COUNT(*), AVG(latency_ms) FROM ai_run_logs WHERE created_at >= 今日零点` | `idx_ai_run_logs_created_brin` |
|
||||
| Dashboard 7天趋势 | `SELECT date_trunc('day', created_at), COUNT(*) FROM ai_run_logs WHERE created_at >= 7天前 GROUP BY 1` | `idx_ai_run_logs_created_brin` |
|
||||
| 告警列表 | `SELECT * FROM ai_run_logs WHERE status IN ('failed','timeout','circuit_open') AND alert_status = 'pending' ORDER BY created_at DESC` | `idx_ai_run_logs_alert` |
|
||||
| 告警确认/忽略 | `UPDATE ai_run_logs SET alert_status = 'acknowledged' WHERE id = ?` | PK |
|
||||
| 调用记录分页 | `SELECT * FROM ai_run_logs WHERE site_id = ? AND app_type = ? ORDER BY created_at DESC LIMIT ? OFFSET ?` | `idx_ai_run_logs_site_app` |
|
||||
| Token 预算聚合 | `SELECT SUM(tokens_used) FROM ai_run_logs WHERE created_at >= 今日零点 AND status = 'success'` | `idx_ai_run_logs_created` (B-tree) |
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
### 新增角色
|
||||
- `app_reader`:只读角色(`LOGIN`),拥有 `app` Schema 的 `USAGE` + `SELECT` 权限
|
||||
|
||||
### 新增视图(39 张)
|
||||
### 新增视图(49 张)
|
||||
|
||||
**DWD 层(11 张,全部含 `site_id` 过滤):**
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
| `app.v_dws_assistant_salary_calc` | `dws.dws_assistant_salary_calc` |
|
||||
| `app.v_dws_assistant_customer_stats` | `dws.dws_assistant_customer_stats` |
|
||||
| `app.v_dws_assistant_finance_analysis` | `dws.dws_assistant_finance_analysis` |
|
||||
| `app.v_dws_assistant_order_contribution` | `dws.dws_assistant_order_contribution` |
|
||||
| `app.v_dws_finance_daily_summary` | `dws.dws_finance_daily_summary` |
|
||||
| `app.v_dws_finance_income_structure` | `dws.dws_finance_income_structure` |
|
||||
| `app.v_dws_finance_recharge_summary` | `dws.dws_finance_recharge_summary` |
|
||||
@@ -59,6 +60,9 @@
|
||||
| `app.v_dws_assistant_project_tag` | `dws.dws_assistant_project_tag` |
|
||||
| `app.v_dws_member_project_tag` | `dws.dws_member_project_tag` |
|
||||
| `app.v_dws_member_spending_power_index` | `dws.dws_member_spending_power_index` |
|
||||
| `app.v_dws_coach_area_hours` | `dws.dws_coach_area_hours` |
|
||||
| `app.v_dws_finance_area_daily` | `dws.dws_finance_area_daily` |
|
||||
| `app.v_dws_finance_board_cache` | `dws.dws_finance_board_cache` |
|
||||
|
||||
**DWS 层 — cfg_* 配置表(5 张,无 `site_id`):**
|
||||
|
||||
@@ -70,15 +74,24 @@
|
||||
| `app.v_cfg_index_parameters` | `dws.cfg_index_parameters` | 同上 |
|
||||
| `app.v_cfg_area_category` | `dws.cfg_area_category` | DISTINCT 去重到 category 级别,排除 SPECIAL/OTHER,按 sort_order 排序。用于项目类型筛选器(CONFIG-1)。2026-03-20 新增。 |
|
||||
|
||||
**快捷别名视图(7 张,简化常用查询路径):**
|
||||
|
||||
| 视图 | 源视图 | 说明 |
|
||||
|------|--------|------|
|
||||
| `app.v_assistant` | `app.v_dim_assistant` | 助教维度快捷别名 |
|
||||
| `app.v_assistant_daily` | `app.v_dws_assistant_daily_detail` | 助教日明细快捷别名 |
|
||||
| `app.v_finance_daily` | `app.v_dws_finance_daily_summary` | 财务日汇总快捷别名 |
|
||||
| `app.v_member` | `app.v_dim_member` | 会员维度快捷别名 |
|
||||
| `app.v_member_consumption` | `app.v_dws_member_consumption_summary` | 会员消费汇总快捷别名 |
|
||||
| `app.v_order_summary` | `app.v_dws_order_summary` | 订单汇总快捷别名 |
|
||||
| `app.v_site` | `dwd.dim_site` | 门店维度快捷别名 |
|
||||
|
||||
### 权限配置
|
||||
|
||||
| 角色 | Schema | 权限 |
|
||||
|------|--------|------|
|
||||
| `app_reader` | `app` | `USAGE` + `SELECT ON ALL TABLES` + `ALTER DEFAULT PRIVILEGES` |
|
||||
|
||||
### P2 预留(注释形式,暂不创建)
|
||||
- `dws.dws_assistant_order_contribution` → `app.v_dws_assistant_order_contribution`
|
||||
|
||||
> `v_dws_member_spending_power_index`、`v_dws_assistant_project_tag`、`v_dws_member_project_tag` 已于 2026-03-19 正式创建(迁移脚本 `2026-03-19_add_board_rls_views.sql`)。
|
||||
|
||||
> `v_dws_finance_recharge_summary` 已于 2026-03-20 重建,新增 6 个赠送卡细分字段(`gift_liquor_balance`、`gift_table_fee_balance`、`gift_voucher_balance`、`gift_liquor_recharge`、`gift_table_fee_recharge`、`gift_voucher_recharge`)。迁移脚本:`db/zqyy_app/migrations/2026-03-20_rebuild_rls_view_gift_breakdown.sql`。关联 SPEC:`gift-card-breakdown`。
|
||||
@@ -119,7 +132,7 @@ DROP ROLE IF EXISTS app_reader;
|
||||
-- 1. 验证 app Schema 存在
|
||||
SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'app';
|
||||
|
||||
-- 2. 验证视图数量(应为 39 张:原 35 + 2026-03-19 新增 3 + 2026-03-20 新增 1)
|
||||
-- 2. 验证视图数量(应为 49 张:11 DWD + 26 DWS + 5 cfg + 7 快捷别名)
|
||||
SELECT count(*) FROM information_schema.views WHERE table_schema = 'app';
|
||||
|
||||
-- 3. 验证 app_reader 角色存在且有 app Schema 权限
|
||||
|
||||
@@ -4,24 +4,27 @@
|
||||
> 迁移脚本:
|
||||
> - `db/zqyy_app/migrations/2026-02-25__p3_create_auth_tables.sql`(建表)
|
||||
> - `db/zqyy_app/migrations/2026-02-25__p3_seed_roles_permissions.sql`(种子数据)
|
||||
> - `db/zqyy_app/migrations/2026-03-23__add_head_coach_manager_roles.sql`(新增 head_coach、manager 角色及权限映射)
|
||||
> - `db/zqyy_app/migrations/2026-03-23__add_rejection_count_and_cancelled_status.sql`(申请审核流程增强:rejection_count + cancelled 状态)
|
||||
> 关联 SPEC:`miniapp-auth-system`(P3 小程序用户认证系统)
|
||||
|
||||
---
|
||||
|
||||
## 1. 变更说明
|
||||
|
||||
### 新增表(8 张)
|
||||
### 新增表(9 张)
|
||||
|
||||
| # | 表名 | 用途 | 主要字段 |
|
||||
|---|------|------|---------|
|
||||
| 1 | `auth.users` | 微信用户主表 | `id`(PK), `wx_openid`(UK), `wx_union_id`, `wx_avatar_url`, `nickname`, `phone`, `status`(默认 `pending`), `created_at`, `updated_at` |
|
||||
| 2 | `auth.user_applications` | 用户入驻申请表 | `id`(PK), `user_id`(FK→users), `site_code`, `site_id`, `applied_role_text`, `employee_number`, `phone`, `status`(默认 `pending`), `reviewer_id`, `review_note`, `created_at`, `reviewed_at` |
|
||||
| 3 | `auth.site_code_mapping` | 球房ID与门店映射表 | `id`(PK), `site_code`(UK), `site_id`(UK), `site_name`, `tenant_id`, `created_at` |
|
||||
| 1 | `auth.users` | 微信用户主表 | `id`(PK), `wx_openid`(UK), `wx_union_id`, `wx_avatar_url`, `nickname`, `phone`, `avatar_url`, `status`(默认 `new`), `rejection_count`(默认 0,累计被拒次数), `created_at`, `updated_at` |
|
||||
| 2 | `auth.user_applications` | 用户入驻申请表 | `id`(PK), `user_id`(FK→users), `site_code`, `site_id`, `applied_role_text`, `employee_number`, `phone`, `status`(默认 `pending`,可选值: pending/approved/rejected/cancelled), `reviewer_id`, `review_note`, `created_at`, `reviewed_at` |
|
||||
| 3 | `auth._archived_site_code_mapping` | [已废弃] 球房ID与门店映射表(NS4.1 重命名,替代方案:`biz.sites` + `biz.site_code_history`) | `id`(PK), `site_code`(UK), `site_id`(UK), `site_name`, `tenant_id`, `created_at` |
|
||||
| 4 | `auth.roles` | 角色定义表 | `id`(PK), `code`(UK), `name`, `description`, `created_at` |
|
||||
| 5 | `auth.permissions` | 权限定义表 | `id`(PK), `code`(UK), `name`, `description`, `created_at` |
|
||||
| 6 | `auth.role_permissions` | 角色-权限关联表 | `role_id`(FK→roles), `permission_id`(FK→permissions),联合主键 |
|
||||
| 7 | `auth.user_site_roles` | 用户-门店-角色关联表 | `id`(PK), `user_id`(FK→users), `site_id`, `role_id`(FK→roles), `created_at`,`(user_id, site_id, role_id)` 唯一约束 |
|
||||
| 8 | `auth.user_assistant_binding` | 用户-人员绑定表 | `id`(PK), `user_id`(FK→users), `site_id`, `assistant_id`(可空), `staff_id`(可空), `binding_type`, `created_at` |
|
||||
| 9 | `auth.tenant_admins` | 租户管理员表(详见 `BD_Manual_tenant_admin_tables.md`) | `id`(PK), `username`(UK), `password_hash`, `display_name`, `tenant_id`, `managed_site_ids`, `is_active`, `deleted_at`, `created_by`, `created_at`, `last_login_at` |
|
||||
|
||||
### 约束与索引
|
||||
|
||||
@@ -29,14 +32,15 @@
|
||||
|----|-----------|------|------|
|
||||
| `users` | `uq_users_wx_openid` | UNIQUE | 微信 openid 唯一 |
|
||||
| `users` | `ix_users_wx_openid` | INDEX | openid 查询加速 |
|
||||
| `site_code_mapping` | `uq_site_code_mapping_site_code` | UNIQUE | 球房ID 唯一 |
|
||||
| `site_code_mapping` | `uq_site_code_mapping_site_id` | UNIQUE | site_id 唯一映射 |
|
||||
| `site_code_mapping` | `ix_site_code_mapping_site_code` | INDEX | site_code 查询加速 |
|
||||
| `site_code_mapping` | `uq_site_code_mapping_site_code` | UNIQUE | 球房ID 唯一(已废弃,表已重命名为 `_archived_site_code_mapping`) |
|
||||
| `site_code_mapping` | `uq_site_code_mapping_site_id` | UNIQUE | site_id 唯一映射(已废弃) |
|
||||
| `site_code_mapping` | `ix_site_code_mapping_site_code` | INDEX | site_code 查询加速(已废弃) |
|
||||
| `roles` | `uq_roles_code` | UNIQUE | 角色 code 唯一 |
|
||||
| `permissions` | `uq_permissions_code` | UNIQUE | 权限 code 唯一 |
|
||||
| `role_permissions` | PK `(role_id, permission_id)` | PRIMARY KEY | 联合主键 |
|
||||
| `role_permissions` | `fk_role_permissions_role_id` | FK | → `auth.roles(id)` CASCADE |
|
||||
| `role_permissions` | `fk_role_permissions_permission_id` | FK | → `auth.permissions(id)` CASCADE |
|
||||
| `user_applications` | `user_applications_status_check` | CHECK | status IN ('pending', 'approved', 'rejected', 'cancelled') |
|
||||
| `user_applications` | `fk_user_applications_user_id` | FK | → `auth.users(id)` CASCADE |
|
||||
| `user_applications` | `ix_user_applications_user_id` | INDEX | user_id 查询加速 |
|
||||
| `user_applications` | `ix_user_applications_status` | INDEX | status 过滤加速 |
|
||||
@@ -65,17 +69,21 @@
|
||||
|------|------|-------------|
|
||||
| `coach` | 助教 | 球房助教,可查看任务和助教看板 |
|
||||
| `staff` | 员工 | 球房员工,可查看任务和数据看板 |
|
||||
| `site_admin` | 店铺管理员 | 单店管理员,可查看所有看板 |
|
||||
| `tenant_admin` | 租户管理员 | 租户级管理员,拥有全部权限 |
|
||||
| `head_coach` | 教练 | 主教练,负责训练助教,可查看任务和全部看板 |
|
||||
| `manager` | 管理员 | 球房管理员,可查看任务和全部看板,未来将与 staff 进一步区分权限 |
|
||||
|
||||
#### 角色-权限映射(14 条)
|
||||
> 注:`site_admin` 和 `tenant_admin` 已于 2026-03-23 从小程序 RBAC 体系中清理。租户/店铺管理员的区分通过 `auth.tenant_admins.admin_type` 列实现,不依赖 `auth.roles` 表。
|
||||
|
||||
#### 角色-权限映射(11 条)
|
||||
|
||||
| 角色 | 权限列表 | 权限数 |
|
||||
|------|---------|--------|
|
||||
| `coach` | `view_tasks`, `view_board_coach` | 2 |
|
||||
| `staff` | `view_tasks`, `view_board` | 2 |
|
||||
| `site_admin` | `view_tasks`, `view_board`, `view_board_finance`, `view_board_customer`, `view_board_coach` | 5 |
|
||||
| `tenant_admin` | `view_tasks`, `view_board`, `view_board_finance`, `view_board_customer`, `view_board_coach` | 5 |
|
||||
| `head_coach` | `view_tasks`, `view_board` | 2 |
|
||||
| `manager` | `view_tasks`, `view_board`, `view_board_finance`, `view_board_customer`, `view_board_coach` | 5 |
|
||||
|
||||
> 注:`site_admin`(5 条)和 `tenant_admin`(5 条)的权限映射已于 2026-03-23 删除,角色体系隔离后总映射从 24 条减为 11 条(head_coach 仅分配 view_tasks + view_board,非全部 5 个权限)。详见迁移脚本 `2026-03-23__cleanup_roles_add_admin_type.sql`。
|
||||
|
||||
---
|
||||
|
||||
@@ -100,10 +108,10 @@
|
||||
```sql
|
||||
-- 先删除种子数据(如需保留表结构)
|
||||
DELETE FROM auth.role_permissions
|
||||
WHERE role_id IN (SELECT id FROM auth.roles WHERE code IN ('coach', 'staff', 'site_admin', 'tenant_admin'))
|
||||
WHERE role_id IN (SELECT id FROM auth.roles WHERE code IN ('coach', 'staff', 'head_coach', 'manager'))
|
||||
AND permission_id IN (SELECT id FROM auth.permissions WHERE code IN ('view_tasks', 'view_board', 'view_board_finance', 'view_board_customer', 'view_board_coach'));
|
||||
|
||||
DELETE FROM auth.roles WHERE code IN ('coach', 'staff', 'site_admin', 'tenant_admin');
|
||||
DELETE FROM auth.roles WHERE code IN ('coach', 'staff', 'head_coach', 'manager');
|
||||
DELETE FROM auth.permissions WHERE code IN ('view_tasks', 'view_board', 'view_board_finance', 'view_board_customer', 'view_board_coach');
|
||||
|
||||
-- 删除表(按逆序,CASCADE 处理外键依赖)
|
||||
@@ -113,7 +121,7 @@ DROP TABLE IF EXISTS auth.user_applications CASCADE;
|
||||
DROP TABLE IF EXISTS auth.role_permissions CASCADE;
|
||||
DROP TABLE IF EXISTS auth.permissions CASCADE;
|
||||
DROP TABLE IF EXISTS auth.roles CASCADE;
|
||||
DROP TABLE IF EXISTS auth.site_code_mapping CASCADE;
|
||||
DROP TABLE IF EXISTS auth._archived_site_code_mapping CASCADE;
|
||||
DROP TABLE IF EXISTS auth.users CASCADE;
|
||||
```
|
||||
|
||||
@@ -124,17 +132,17 @@ DROP TABLE IF EXISTS auth.users CASCADE;
|
||||
## 4. 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 验证 auth Schema 下 8 张认证表全部存在
|
||||
-- 1. 验证 auth Schema 下 9 张认证表全部存在
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'auth'
|
||||
AND table_name IN (
|
||||
'users', 'user_applications', 'site_code_mapping',
|
||||
'users', 'user_applications', '_archived_site_code_mapping',
|
||||
'roles', 'permissions', 'role_permissions',
|
||||
'user_site_roles', 'user_assistant_binding'
|
||||
'user_site_roles', 'user_assistant_binding', 'tenant_admins'
|
||||
)
|
||||
ORDER BY table_name;
|
||||
-- 预期:返回 8 行
|
||||
-- 预期:返回 9 行
|
||||
|
||||
-- 2. 验证种子数据:5 条权限
|
||||
SELECT COUNT(*) AS perm_count
|
||||
@@ -142,28 +150,111 @@ FROM auth.permissions
|
||||
WHERE code IN ('view_tasks', 'view_board', 'view_board_finance', 'view_board_customer', 'view_board_coach');
|
||||
-- 预期:5
|
||||
|
||||
-- 3. 验证种子数据:4 条角色
|
||||
-- 3. 验证种子数据:4 条角色(site_admin/tenant_admin 已于 2026-03-23 删除)
|
||||
SELECT COUNT(*) AS role_count
|
||||
FROM auth.roles
|
||||
WHERE code IN ('coach', 'staff', 'site_admin', 'tenant_admin');
|
||||
WHERE code IN ('coach', 'staff', 'head_coach', 'manager');
|
||||
-- 预期:4
|
||||
|
||||
-- 4. 验证角色-权限映射数量
|
||||
-- 4. 验证角色-权限映射数量(11 条)
|
||||
SELECT r.code AS role_code, COUNT(rp.permission_id) AS perm_count
|
||||
FROM auth.roles r
|
||||
JOIN auth.role_permissions rp ON r.id = rp.role_id
|
||||
GROUP BY r.code
|
||||
ORDER BY r.code;
|
||||
-- 预期:coach=2, site_admin=5, staff=2, tenant_admin=5(共 14 条映射)
|
||||
-- 预期:coach=2, head_coach=2, manager=5, staff=2(共 11 条映射,无 site_admin/tenant_admin)
|
||||
|
||||
-- 5. 验证关键约束存在
|
||||
SELECT conname, contype
|
||||
FROM pg_constraint
|
||||
WHERE conrelid IN (
|
||||
'auth.users'::regclass,
|
||||
'auth.site_code_mapping'::regclass,
|
||||
'auth._archived_site_code_mapping'::regclass,
|
||||
'auth.user_site_roles'::regclass
|
||||
)
|
||||
ORDER BY conrelid::regclass::text, conname;
|
||||
-- 预期:包含 uq_users_wx_openid、uq_site_code_mapping_site_code、uq_site_code_mapping_site_id、uq_user_site_roles_user_site_role 等
|
||||
-- 预期:包含 uq_users_wx_openid、uq_site_code_mapping_site_code(已废弃表 _archived_site_code_mapping)、uq_site_code_mapping_site_id、uq_user_site_roles_user_site_role 等
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 5. 变更记录:2026-03-23 申请审核流程增强
|
||||
|
||||
### 5.1 变更说明
|
||||
|
||||
| 对象 | 变更类型 | 说明 |
|
||||
|------|---------|------|
|
||||
| `auth.users.rejection_count` | 新增字段 | `integer NOT NULL DEFAULT 0`,累计被管理员拒绝的申请次数,达到 3 次自动将 `status` 设为 `disabled` |
|
||||
| `auth.user_applications.status` CHECK 约束 | 修改 | 新增 `'cancelled'` 可选值(用户主动取消申请),约束名 `user_applications_status_check` |
|
||||
|
||||
### 5.2 业务规则
|
||||
|
||||
- 管理员拒绝申请时:`rejection_count += 1`,第 3 次自动将 `users.status` 设为 `disabled`
|
||||
- 用户主动取消(`cancelled`)不计入 `rejection_count`
|
||||
- `cancelled` 状态的申请不在管理端申请列表中显示
|
||||
- `disabled` 用户不允许重新申请,需管理员手动解除(功能待开发)
|
||||
|
||||
### 5.3 兼容性影响
|
||||
|
||||
| 组件 | 影响 |
|
||||
|------|------|
|
||||
| ETL | 无影响,不涉及 ETL 库 |
|
||||
| 后端 API | 直接依赖。`reject_application` 增加 rejection_count 累加逻辑;新增 `cancel_application` 服务和 `/api/xcx/cancel-application` 端点;`/api/xcx/me` 返回 `latestApplication` 详情 |
|
||||
| 小程序 | 间接依赖。reviewing 页展示申请详情+重新申请按钮;no-permission 页区分 rejected/disabled 状态 |
|
||||
| 管理后台 | 间接依赖。拒绝端点自动累加 rejection_count 并触发禁用;申请列表排除 cancelled |
|
||||
|
||||
### 5.4 回滚策略
|
||||
|
||||
```sql
|
||||
-- 回滚 rejection_count 字段
|
||||
ALTER TABLE auth.users DROP COLUMN IF EXISTS rejection_count;
|
||||
|
||||
-- 回滚 status CHECK 约束(恢复为不含 cancelled)
|
||||
ALTER TABLE auth.user_applications DROP CONSTRAINT IF EXISTS user_applications_status_check;
|
||||
ALTER TABLE auth.user_applications
|
||||
ADD CONSTRAINT user_applications_status_check
|
||||
CHECK (status IN ('pending', 'approved', 'rejected'));
|
||||
|
||||
-- 注意:回滚前需确认无 status='cancelled' 的记录,否则约束添加会失败
|
||||
-- UPDATE auth.user_applications SET status = 'pending' WHERE status = 'cancelled';
|
||||
```
|
||||
|
||||
### 5.5 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 验证 rejection_count 字段存在且默认值正确
|
||||
SELECT column_name, data_type, column_default, is_nullable
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'auth' AND table_name = 'users' AND column_name = 'rejection_count';
|
||||
-- 预期:integer, 0, NO
|
||||
|
||||
-- 2. 验证 CHECK 约束包含 cancelled
|
||||
SELECT conname, pg_get_constraintdef(oid) AS constraint_def
|
||||
FROM pg_constraint
|
||||
WHERE conrelid = 'auth.user_applications'::regclass
|
||||
AND conname = 'user_applications_status_check';
|
||||
-- 预期:包含 'cancelled'
|
||||
|
||||
-- 3. 验证现有数据 rejection_count 默认值
|
||||
SELECT COUNT(*) AS users_with_zero_rejection
|
||||
FROM auth.users
|
||||
WHERE rejection_count = 0;
|
||||
-- 预期:等于 users 表总行数(所有现有用户默认 0)
|
||||
|
||||
-- 4. 验证 cancelled 状态可正常写入(dry-run 验证)
|
||||
-- INSERT INTO auth.user_applications (user_id, site_code, site_id, applied_role_text, phone, status)
|
||||
-- VALUES (1, 'TEST', 1, '测试', '13800000000', 'cancelled');
|
||||
-- 预期:不报 CHECK 约束错误
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<!-- AI_CHANGELOG
|
||||
| 日期 | Prompt | 变更 |
|
||||
|------|--------|------|
|
||||
| 2026-03-23 | 角色路由+页面权限守卫 | 更新角色表从 4 条到 6 条,新增 head_coach/manager 角色及权限映射,更新验证 SQL 和回滚策略 |
|
||||
| 2026-03-23 | 申请审核流程增强 | users 新增 rejection_count 字段;user_applications.status CHECK 约束增加 cancelled;新增第 5 节变更记录 |
|
||||
| 2026-03-23 | 角色体系隔离+店铺管理员 | auth.roles 删除 site_admin/tenant_admin(小程序 RBAC 不需要);角色从 6 条减为 4 条;role_permissions 从 24 条减为 11 条(head_coach 仅 view_tasks+view_board) |
|
||||
-->
|
||||
|
||||
@@ -4,24 +4,28 @@
|
||||
> 迁移脚本:
|
||||
> - `db/zqyy_app/migrations/2026-02-27__p4_create_biz_tables.sql`(建表)
|
||||
> - `db/zqyy_app/migrations/2026-02-27__p4_seed_trigger_jobs.sql`(种子数据)
|
||||
> 关联 SPEC:`04-miniapp-core-business`(P4 小程序核心业务模块)
|
||||
> - `db/zqyy_app/migrations/2026-03-24__p17_task_engine_ownership.sql`(P17 客户归属与转移)
|
||||
> - `db/zqyy_app/migrations/2026-03-24__p18_task_engine_dashboard.sql`(P18 运营看板字段扩展)
|
||||
> 关联 SPEC:`04-miniapp-core-business`(P4)、`P17-assistant-ownership-task-engine`(P17)、`P18-admin-task-engine-dashboard`(P18)
|
||||
|
||||
---
|
||||
|
||||
## 1. 变更说明
|
||||
|
||||
### 新增表(4 张)
|
||||
### 新增表(6 张)
|
||||
|
||||
| # | 表名 | 用途 | 字段数 |
|
||||
|---|------|------|--------|
|
||||
| 1 | `biz.coach_tasks` | 助教任务表:存储任务分配、状态、有效期、置顶、放弃原因等 | 15 |
|
||||
| 2 | `biz.coach_task_history` | 任务变更历史表:记录任务关闭/新建/置顶/放弃的追溯链 | 9 |
|
||||
| 3 | `biz.notes` | 统一备注表:通过 `type` 字段区分普通备注/回访备注/放弃原因,含星星评分 | 15 |
|
||||
| 4 | `biz.trigger_jobs` | 触发器配置表:存储 cron/interval/event 三种触发方式的配置与执行状态 | 9 |
|
||||
| # | 表名 | 用途 | 字段数 | 来源 |
|
||||
|---|------|------|--------|------|
|
||||
| 1 | `biz.coach_tasks` | 助教任务表:存储任务分配、状态、有效期、置顶、放弃原因、转移追踪等 | 18 | P4+P17 |
|
||||
| 2 | `biz.coach_task_history` | 任务变更历史表:记录任务关闭/新建/置顶/放弃/转移的追溯链 | 9 | P4 |
|
||||
| 3 | `biz.notes` | 统一备注表:通过 `type` 字段区分普通备注/回访备注/放弃原因,含星星评分 | 15 | P4 |
|
||||
| 4 | `biz.trigger_jobs` | 触发器配置表:存储 cron/interval/event 三种触发方式的配置与执行状态 | 12 | P4+P18+P23 |
|
||||
| 5 | `biz.cfg_task_generator_params` | 任务引擎参数配置表:支持全局默认 + 门店级覆盖 | 7 | P17+P18 |
|
||||
| 6 | `biz.coach_task_transfer_log` | 客户转移日志表:记录每次转移的完整上下文 | 11 | P17 |
|
||||
|
||||
### 表字段明细
|
||||
|
||||
#### biz.coach_tasks(15 字段)
|
||||
#### biz.coach_tasks(18 字段)
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
@@ -30,7 +34,7 @@
|
||||
| `assistant_id` | BIGINT | NOT NULL | 助教 ID |
|
||||
| `member_id` | BIGINT | NOT NULL | 客户 ID |
|
||||
| `task_type` | VARCHAR(50) | NOT NULL | 任务类型:`high_priority_recall` / `priority_recall` / `follow_up_visit` / `relationship_building` |
|
||||
| `status` | VARCHAR(20) | NOT NULL DEFAULT 'active' | 状态:`active` / `inactive` / `completed` / `abandoned` |
|
||||
| `status` | VARCHAR(20) | NOT NULL DEFAULT 'active' | 状态:`active` / `inactive` / `completed` / `abandoned` / `transferred`(P17)/ `pending_review`(P17) |
|
||||
| `priority_score` | NUMERIC(5,2) | 可空 | 优先级分数,取 `max(WBI, NCI)` 快照 |
|
||||
| `expires_at` | TIMESTAMPTZ | 可空 | 有效期时间戳,NULL 表示无限期 |
|
||||
| `is_pinned` | BOOLEAN | DEFAULT FALSE | 是否置顶 |
|
||||
@@ -38,6 +42,9 @@
|
||||
| `completed_at` | TIMESTAMPTZ | 可空 | 完成时间 |
|
||||
| `completed_task_type` | VARCHAR(50) | 可空 | 完成时的任务类型快照 |
|
||||
| `parent_task_id` | BIGINT | FK → `biz.coach_tasks(id)`,可空 | 父任务 ID(自引用) |
|
||||
| `transfer_count` | INTEGER | NOT NULL DEFAULT 0 | 该客户在此任务链上的累计转移次数(P17 新增) |
|
||||
| `transferred_from` | BIGINT | FK → `biz.coach_tasks(id)`,可空 | 转移来源任务 ID(P17 新增) |
|
||||
| `transferred_at` | TIMESTAMPTZ | 可空 | 转移发生时间(P17 新增) |
|
||||
| `created_at` | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
|
||||
| `updated_at` | TIMESTAMPTZ | DEFAULT NOW() | 更新时间 |
|
||||
|
||||
@@ -47,7 +54,7 @@
|
||||
|------|------|------|------|
|
||||
| `id` | BIGSERIAL | PK | 自增主键 |
|
||||
| `task_id` | BIGINT | NOT NULL, FK → `biz.coach_tasks(id)` | 关联任务 |
|
||||
| `action` | VARCHAR(50) | NOT NULL | 操作类型:`created` / `type_changed` / `pinned` / `abandoned` / `cancel_abandon` / `expired` / `completed` |
|
||||
| `action` | VARCHAR(50) | NOT NULL | 操作类型:`created` / `type_changed` / `type_change_close` / `pinned` / `abandoned` / `cancel_abandon` / `expired` / `completed` / `expires_at_filled` / `transferred_out`(P17)/ `transferred_in`(P17) |
|
||||
| `old_status` | VARCHAR(20) | 可空 | 变更前状态 |
|
||||
| `new_status` | VARCHAR(20) | 可空 | 变更后状态 |
|
||||
| `old_task_type` | VARCHAR(50) | 可空 | 变更前任务类型 |
|
||||
@@ -75,20 +82,53 @@
|
||||
| `updated_at` | TIMESTAMPTZ | DEFAULT NOW() | 更新时间 |
|
||||
| `score` | SMALLINT | CHECK (1-5),可空 | 备注星星评分,助教创建备注时可选填写,不参与 AI 分析(RNS1.1 新增) |
|
||||
|
||||
#### biz.trigger_jobs(9 字段)
|
||||
#### biz.trigger_jobs(12 字段)
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | SERIAL | PK | 自增主键 |
|
||||
| `job_type` | VARCHAR(100) | NOT NULL | 任务类型标识,映射到 Python handler |
|
||||
| `job_name` | VARCHAR(100) | NOT NULL, UNIQUE | 任务名称(唯一) |
|
||||
| `job_type` | VARCHAR(100) | NOT NULL | 任务类型标识,映射到 Python handler(`_JOB_REGISTRY` 注册键) |
|
||||
| `job_name` | VARCHAR(100) | NOT NULL, UNIQUE | 任务名称(唯一标识,如 `task_generator`) |
|
||||
| `trigger_condition` | VARCHAR(20) | NOT NULL | 触发方式:`cron` / `interval` / `event` |
|
||||
| `trigger_config` | JSONB | NOT NULL | 触发配置(cron 表达式 / 间隔秒数 / 事件名) |
|
||||
| `last_run_at` | TIMESTAMPTZ | 可空 | 上次运行时间 |
|
||||
| `next_run_at` | TIMESTAMPTZ | 可空 | 下次运行时间(event 类型为 NULL) |
|
||||
| `status` | VARCHAR(20) | NOT NULL DEFAULT 'enabled' | 状态:`enabled` / `disabled` |
|
||||
| `description` | TEXT | 可空 | 任务中文描述,管理后台页面展示(P23 新增) |
|
||||
| `last_error` | TEXT | 可空 | 最后一次执行异常的错误信息,成功后清空为 NULL(P23 新增) |
|
||||
| `last_stats` | JSONB | 可空 | 最近一次执行的统计结果 JSON,如 `{"created":5,"replaced":2,"skipped":10,"transferred":1}`(P18 新增) |
|
||||
| `created_at` | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
|
||||
|
||||
#### biz.cfg_task_generator_params(7 字段,P17+P18)
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | BIGSERIAL | PK | 自增主键 |
|
||||
| `site_id` | BIGINT | 可空 | NULL=全局默认,非NULL=门店级覆盖 |
|
||||
| `param_key` | VARCHAR(64) | NOT NULL | 参数键名 |
|
||||
| `param_value` | NUMERIC | NOT NULL | 参数值 |
|
||||
| `description` | TEXT | 可空 | 参数说明 |
|
||||
| `updated_at` | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 更新时间 |
|
||||
| `updated_by` | BIGINT | 可空 | 最近修改人 user_id,用于审计追溯(P18 新增) |
|
||||
|
||||
继承链:代码默认 → 全局默认(site_id IS NULL)→ 门店覆盖(site_id = ?)
|
||||
|
||||
#### biz.coach_task_transfer_log(11 字段,P17 新增)
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | BIGSERIAL | PK | 自增主键 |
|
||||
| `site_id` | BIGINT | NOT NULL | 门店 ID |
|
||||
| `member_id` | BIGINT | NOT NULL | 客户 ID |
|
||||
| `from_assistant_id` | BIGINT | NOT NULL | 原助教 ID |
|
||||
| `to_assistant_id` | BIGINT | NOT NULL | 新助教 ID |
|
||||
| `from_task_id` | BIGINT | NOT NULL, FK → `biz.coach_tasks(id)` | 原任务 ID |
|
||||
| `to_task_id` | BIGINT | FK → `biz.coach_tasks(id)`,可空 | 新任务 ID |
|
||||
| `transfer_reason` | TEXT | 可空 | 转移原因描述 |
|
||||
| `guard_checks` | JSONB | 可空 | 三重保护检查结果快照 |
|
||||
| `transfer_score` | NUMERIC | 可空 | 转移候选得分 |
|
||||
| `created_at` | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 创建时间 |
|
||||
|
||||
### 约束与索引
|
||||
|
||||
| 表 | 约束/索引名 | 类型 | 说明 |
|
||||
@@ -96,6 +136,7 @@
|
||||
| `coach_tasks` | `idx_coach_tasks_site_assistant_member_type` | UNIQUE INDEX (partial) | `(site_id, assistant_id, member_id, task_type) WHERE status = 'active'`,保证同一组合下活跃任务最多一条 |
|
||||
| `coach_tasks` | `idx_coach_tasks_assistant_status` | INDEX | `(site_id, assistant_id, status)`,助教任务列表查询加速 |
|
||||
| `coach_tasks` | FK `parent_task_id` | FK | → `biz.coach_tasks(id)`,自引用 |
|
||||
| `coach_tasks` | FK `fk_coach_tasks_transferred_from` | FK | → `biz.coach_tasks(id)`,转移来源任务(P17 新增) |
|
||||
| `coach_task_history` | FK `task_id` | FK | → `biz.coach_tasks(id)` |
|
||||
| `notes` | `idx_notes_target` | INDEX | `(site_id, target_type, target_id)`,按目标查询备注加速 |
|
||||
| `notes` | CHECK `rating_service_willingness` | CHECK | `BETWEEN 1 AND 5` |
|
||||
@@ -103,6 +144,31 @@
|
||||
| `notes` | CHECK `score` | CHECK | `score IS NULL OR (score >= 1 AND score <= 5)`(RNS1.1 新增) |
|
||||
| `notes` | FK `task_id` | FK | → `biz.coach_tasks(id)` |
|
||||
| `trigger_jobs` | UNIQUE `job_name` | UNIQUE | 触发器名称唯一 |
|
||||
| `cfg_task_generator_params` | UNIQUE `(site_id, param_key)` | UNIQUE | 全局+门店级参数唯一约束(P17 新增) |
|
||||
| `coach_task_transfer_log` | FK `from_task_id` | FK | → `biz.coach_tasks(id)`(P17 新增) |
|
||||
| `coach_task_transfer_log` | FK `to_task_id` | FK | → `biz.coach_tasks(id)`(P17 新增) |
|
||||
| `coach_task_transfer_log` | `idx_transfer_log_site_created` | INDEX | `(site_id, created_at DESC)`(P17 新增) |
|
||||
| `coach_task_transfer_log` | `idx_transfer_log_member` | INDEX | `(member_id, created_at DESC)`(P17 新增) |
|
||||
|
||||
### P17 种子数据(13 条任务引擎参数)
|
||||
|
||||
| param_key | param_value | description |
|
||||
|-----------|-------------|-------------|
|
||||
| `high_priority_recall_threshold` | 7.0 | max(WBI,NCI) 超过此值生成高优先召回 |
|
||||
| `priority_recall_threshold` | 5.0 | max(WBI,NCI) 超过此值生成优先召回 |
|
||||
| `rs_min_for_relationship` | 1.0 | RS ≤ 此值不生成关系构建 |
|
||||
| `rs_max_for_relationship` | 6.0 | RS ≥ 此值不生成关系构建 |
|
||||
| `consecutive_recall_fail_cycles` | 3 | 连续失败多少轮触发客户转移 |
|
||||
| `min_wbi_for_transfer` | 5.0 | WBI 低于此值不触发转移 |
|
||||
| `guard_assistant_coverage_ratio` | 0.5 | 绑定率低于此值禁用转移 |
|
||||
| `guard_new_assistant_days` | 10 | 新助教入驻保护天数 |
|
||||
| `transfer_score_w_rs` | 0.5 | 转移候选排序:RS 权重 |
|
||||
| `transfer_score_w_ms` | 0.3 | 转移候选排序:MS 权重 |
|
||||
| `transfer_score_w_ml` | 0.2 | 转移候选排序:ML 权重 |
|
||||
| `max_transfer_count` | 2 | 单客户最大累计转移次数 |
|
||||
| `follow_up_visit_retention_hours` | 48 | 回访任务最低保留时长(小时) |
|
||||
|
||||
> 所有参数 `site_id IS NULL`(全局默认),门店可通过插入 `site_id = ?` 的行覆盖。
|
||||
|
||||
### 种子数据(4 条触发器配置)
|
||||
|
||||
@@ -120,13 +186,13 @@
|
||||
| 组件 | 影响 |
|
||||
|------|------|
|
||||
| ETL 任务 | 无直接影响。`biz` Schema 表不参与 ETL 流程,但任务生成器通过 FDW 只读访问 ETL 库的 WBI/NCI/RS 指数数据 |
|
||||
| 后端 API | 直接依赖。FastAPI 后端将基于这些表实现任务 CRUD(`/api/xcx/tasks`)、备注 CRUD(`/api/xcx/notes`)、触发器调度等功能 |
|
||||
| 后端 API | 直接依赖。FastAPI 后端将基于这些表实现任务 CRUD(`/api/xcx/tasks`)、备注 CRUD(`/api/xcx/notes`)、触发器调度等功能。P17 新增:`task_generator.py` 完全重写,入口改为 OS 归属对;`fdw_queries.py` 新增 4 个批量查询方法 |
|
||||
| 小程序 | 间接依赖。小程序通过后端 API 间接使用任务列表、备注功能 |
|
||||
| 管理后台 | 暂无影响。后续可能增加任务监控和触发器管理界面 |
|
||||
| 管理后台 | P18 已实施。`admin_task_engine` router 提供 9 个端点(转移日志分页+历史、待审核任务分页+重新分配+关闭、参数管理 CRUD);前端 3 个页面(TransferLog/PendingReview/TaskEngineConfig)通过 `taskEngine.ts` API 层调用 |
|
||||
| FDW 配置 | 无影响。`fdw_etl` Schema 独立于 `biz`,任务生成器和召回检测器通过 FDW 只读查询 ETL 库 |
|
||||
| `auth` Schema | 间接依赖。任务生成器通过 `auth.user_assistant_binding` 确定助教与小程序用户的映射关系 |
|
||||
| `auth` Schema | 间接依赖。P17 仍通过 `auth.user_assistant_binding` 获取 site_ids 和助教绑定信息(转移保护检查) |
|
||||
| `public` Schema | 无影响。`member_retention_clue` 表独立于本次变更 |
|
||||
| 现有 `biz` Schema | 兼容。`biz` Schema 已由 P1 迁移脚本创建,本次仅在其中新增 4 张表,不修改已有对象 |
|
||||
| 现有 `biz` Schema | 兼容。`coach_tasks` 新增 3 字段均有默认值(`transfer_count DEFAULT 0`,其余可空),不影响现有查询。新增 `transferred`/`pending_review` 状态值,7 个下游模块均使用显式 status 过滤,不会误匹配 |
|
||||
|
||||
---
|
||||
|
||||
@@ -170,6 +236,8 @@ RNS1.2(客户与助教接口)新增 3 个端点,引用了以下 biz/public
|
||||
|
||||
## 3. 回滚策略
|
||||
|
||||
### P4 回滚(原始 4 张表)
|
||||
|
||||
按逆序 `DROP TABLE IF EXISTS CASCADE`(迁移脚本末尾已包含注释形式的回滚语句):
|
||||
|
||||
```sql
|
||||
@@ -194,6 +262,32 @@ DROP TABLE IF EXISTS biz.coach_task_history CASCADE;
|
||||
DROP TABLE IF EXISTS biz.coach_tasks CASCADE;
|
||||
```
|
||||
|
||||
### P17 回滚(增量变更)
|
||||
|
||||
```sql
|
||||
-- 1. 删除 P17 种子数据
|
||||
DELETE FROM biz.cfg_task_generator_params WHERE site_id IS NULL;
|
||||
|
||||
-- 2. 删除 P17 新增表
|
||||
DROP TABLE IF EXISTS biz.coach_task_transfer_log;
|
||||
DROP TABLE IF EXISTS biz.cfg_task_generator_params;
|
||||
|
||||
-- 3. 删除 P17 新增字段
|
||||
ALTER TABLE biz.coach_tasks DROP COLUMN IF EXISTS transfer_count;
|
||||
ALTER TABLE biz.coach_tasks DROP COLUMN IF EXISTS transferred_from;
|
||||
ALTER TABLE biz.coach_tasks DROP COLUMN IF EXISTS transferred_at;
|
||||
|
||||
-- 注意:enum 值(transferred/pending_review)一旦添加无法直接删除,需重建类型
|
||||
```
|
||||
|
||||
### P18 回滚(字段扩展)
|
||||
|
||||
```sql
|
||||
-- 删除 P18 新增字段
|
||||
ALTER TABLE biz.trigger_jobs DROP COLUMN IF EXISTS last_stats;
|
||||
ALTER TABLE biz.cfg_task_generator_params DROP COLUMN IF EXISTS updated_by;
|
||||
```
|
||||
|
||||
注意:
|
||||
- `CASCADE` 会级联删除依赖对象(外键引用的子表数据)
|
||||
- 如果表中已有业务数据,需先备份再执行回滚
|
||||
@@ -204,52 +298,90 @@ DROP TABLE IF EXISTS biz.coach_tasks CASCADE;
|
||||
## 4. 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 验证 biz Schema 下 4 张业务表全部存在
|
||||
-- 1. 验证 biz Schema 下 6 张业务表全部存在
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'biz'
|
||||
AND table_name IN ('coach_tasks', 'coach_task_history', 'notes', 'trigger_jobs')
|
||||
AND table_name IN ('coach_tasks', 'coach_task_history', 'notes', 'trigger_jobs',
|
||||
'cfg_task_generator_params', 'coach_task_transfer_log')
|
||||
ORDER BY table_name;
|
||||
-- 预期:返回 4 行(coach_task_history, coach_tasks, notes, trigger_jobs)
|
||||
-- 预期:返回 6 行
|
||||
|
||||
-- 2. 验证 coach_tasks 表字段数量和关键字段
|
||||
-- 2. 验证 coach_tasks 表字段数量(P4 原 15 + P17 新增 3 = 18)
|
||||
SELECT column_name, data_type, is_nullable, column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'biz' AND table_name = 'coach_tasks'
|
||||
ORDER BY ordinal_position;
|
||||
-- 预期:返回 15 行,包含 id/site_id/assistant_id/member_id/task_type/status 等
|
||||
-- 预期:返回 18 行,包含 transfer_count/transferred_from/transferred_at
|
||||
|
||||
-- 3. 验证部分唯一索引存在
|
||||
-- 3. 验证 P17 新增字段的默认值和约束
|
||||
SELECT column_name, data_type, is_nullable, column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'biz' AND table_name = 'coach_tasks'
|
||||
AND column_name IN ('transfer_count', 'transferred_from', 'transferred_at');
|
||||
-- 预期:transfer_count: integer, NOT NULL, DEFAULT 0
|
||||
-- transferred_from: bigint, YES (nullable)
|
||||
-- transferred_at: timestamp with time zone, YES (nullable)
|
||||
|
||||
-- 4. 验证 cfg_task_generator_params 表结构
|
||||
SELECT column_name, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'biz' AND table_name = 'cfg_task_generator_params'
|
||||
ORDER BY ordinal_position;
|
||||
-- 预期:6 行(id, site_id, param_key, param_value, description, updated_at)
|
||||
|
||||
-- 5. 验证 P17 种子数据(13 条全局默认参数)
|
||||
SELECT param_key, param_value, description
|
||||
FROM biz.cfg_task_generator_params
|
||||
WHERE site_id IS NULL
|
||||
ORDER BY param_key;
|
||||
-- 预期:返回 13 行
|
||||
|
||||
-- 6. 验证 coach_task_transfer_log 表结构
|
||||
SELECT column_name, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'biz' AND table_name = 'coach_task_transfer_log'
|
||||
ORDER BY ordinal_position;
|
||||
-- 预期:11 行(id, site_id, member_id, from_assistant_id, to_assistant_id,
|
||||
-- from_task_id, to_task_id, transfer_reason, guard_checks, transfer_score, created_at)
|
||||
|
||||
-- 7. 验证 P17 新增索引
|
||||
SELECT indexname
|
||||
FROM pg_indexes
|
||||
WHERE schemaname = 'biz'
|
||||
AND indexname IN ('idx_transfer_log_site_created', 'idx_transfer_log_member')
|
||||
ORDER BY indexname;
|
||||
-- 预期:返回 2 行
|
||||
|
||||
-- 8. 验证 transferred_from 外键约束
|
||||
SELECT conname
|
||||
FROM pg_constraint
|
||||
WHERE conrelid = 'biz.coach_tasks'::regclass
|
||||
AND conname = 'fk_coach_tasks_transferred_from';
|
||||
-- 预期:返回 1 行
|
||||
|
||||
-- 9. 验证部分唯一索引存在(P4 原有)
|
||||
SELECT indexname, indexdef
|
||||
FROM pg_indexes
|
||||
WHERE schemaname = 'biz' AND indexname = 'idx_coach_tasks_site_assistant_member_type';
|
||||
-- 预期:返回 1 行,indexdef 包含 "WHERE ((status)::text = 'active'::text)"
|
||||
|
||||
-- 4. 验证 notes 表的 CHECK 约束(评分 1-5)
|
||||
SELECT conname, pg_get_constraintdef(oid) AS constraint_def
|
||||
FROM pg_constraint
|
||||
WHERE conrelid = 'biz.notes'::regclass AND contype = 'c';
|
||||
-- 预期:返回 2 行,分别约束 rating_service_willingness 和 rating_revisit_likelihood 在 1-5 范围
|
||||
|
||||
-- 5. 验证种子数据:4 条触发器配置
|
||||
SELECT job_name, job_type, trigger_condition,
|
||||
trigger_config->>'cron_expression' AS cron,
|
||||
trigger_config->>'interval_seconds' AS interval_sec,
|
||||
trigger_config->>'event_name' AS event
|
||||
-- 10. 验证种子数据:4 条触发器配置(P4 原有)
|
||||
SELECT job_name, job_type, trigger_condition
|
||||
FROM biz.trigger_jobs
|
||||
WHERE job_name IN ('task_generator', 'task_expiry_check', 'recall_completion_check', 'note_reclassify_backfill')
|
||||
ORDER BY job_name;
|
||||
-- 预期:返回 4 行
|
||||
-- note_reclassify_backfill | event | recall_completed
|
||||
-- recall_completion_check | event | etl_data_updated
|
||||
-- task_expiry_check | interval| interval_seconds=3600
|
||||
-- task_generator | cron | 0 4 * * *
|
||||
|
||||
-- 6. 验证查询索引存在
|
||||
SELECT indexname
|
||||
FROM pg_indexes
|
||||
WHERE schemaname = 'biz'
|
||||
AND indexname IN ('idx_coach_tasks_assistant_status', 'idx_notes_target')
|
||||
ORDER BY indexname;
|
||||
-- 预期:返回 2 行
|
||||
-- 11. 验证 P18 新增字段:trigger_jobs.last_stats
|
||||
SELECT column_name, data_type, is_nullable
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'biz' AND table_name = 'trigger_jobs' AND column_name = 'last_stats';
|
||||
-- 预期:返回 1 行,data_type = 'jsonb',is_nullable = 'YES'
|
||||
|
||||
-- 12. 验证 P18 新增字段:cfg_task_generator_params.updated_by
|
||||
SELECT column_name, data_type, is_nullable
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'biz' AND table_name = 'cfg_task_generator_params' AND column_name = 'updated_by';
|
||||
-- 预期:返回 1 行,data_type = 'bigint',is_nullable = 'YES'
|
||||
```
|
||||
|
||||
@@ -34,9 +34,11 @@
|
||||
|
||||
### 导入的外部表
|
||||
|
||||
通过 `IMPORT FOREIGN SCHEMA app` 批量导入,外部表与 ETL 库 `app` Schema 中的 RLS 视图一一对应(共 38 张,2026-03-19 新增 3 张 BOARD 看板视图):
|
||||
通过 `IMPORT FOREIGN SCHEMA app` 批量导入,外部表与 ETL 库 `app` Schema 中的 RLS 视图一一对应(共 46 张,含 11 DWD + 23 DWS + 5 cfg + 7 快捷别名):
|
||||
- 11 张 DWD 视图:`v_dim_member`、`v_dim_assistant`、`v_dim_member_card_account`、`v_dim_table`、`v_dwd_settlement_head`、`v_dwd_table_fee_log`、`v_dwd_assistant_service_log`、`v_dwd_recharge_order`、`v_dwd_store_goods_sale`、`v_dim_staff`、`v_dim_staff_ex`
|
||||
- 27 张 DWS 视图(含 4 张 cfg_* 配置表):`v_dws_member_consumption_summary`、`v_dws_member_visit_detail` 等
|
||||
- 23 张 DWS 视图(含 `v_dws_assistant_project_tag`、`v_dws_member_project_tag`、`v_dws_member_spending_power_index`、`v_dws_assistant_order_contribution` 等)
|
||||
- 5 张 cfg_* 配置表视图(`v_cfg_performance_tier`、`v_cfg_assistant_level_price`、`v_cfg_bonus_rules`、`v_cfg_index_parameters`、`v_cfg_area_category`)
|
||||
- 7 张快捷别名视图(`v_assistant`、`v_assistant_daily`、`v_finance_daily`、`v_member`、`v_member_consumption`、`v_order_summary`、`v_site`)
|
||||
|
||||
### 权限配置
|
||||
|
||||
|
||||
@@ -217,6 +217,7 @@ SELECT n.nspname AS schema_name, d.defaclacl AS default_acl
|
||||
| 项目 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| `source` 列缺失 | ⚠️ 待同步 | 业务库侧已有 `source VARCHAR(20) NOT NULL DEFAULT 'manual'`(2026-02-27),FDW 外部表定义未包含。当前无 ETL 任务需要此字段,但未来如需读取线索来源需先更新外部表 |
|
||||
| `is_hidden` 列缺失 | ⚠️ 待同步 | 业务库侧已有 `is_hidden BOOLEAN NOT NULL DEFAULT false`(2026-03-20,NS4 迁移),FDW 外部表定义未包含。如 ETL 任务需要过滤隐藏线索需先更新外部表 |
|
||||
| DWS 任务消费 | 📋 待规划 | 原 `member_birthday_manual` 的 DWS 消费逻辑已移除。维客线索的 DWS 聚合任务尚未规划 |
|
||||
|
||||
### source 列同步方法(备用)
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
| zqyy_app / test_zqyy_app | public | member_retention_clue | 新建 | 维客线索表 |
|
||||
| zqyy_app / test_zqyy_app | public | member_retention_clue.source | 新增列 | 2026-02-27 补齐线索来源字段 |
|
||||
| zqyy_app / test_zqyy_app | public | member_retention_clue.category | 约束变更 | 2026-03-08 枚举对齐:`客户基础信息` → `客户基础` |
|
||||
| zqyy_app / test_zqyy_app | public | member_retention_clue.is_hidden | 新增列 | 2026-03-20 NS4 租户管理后台:隐藏/显示控制 |
|
||||
|
||||
### 表结构
|
||||
|
||||
@@ -29,6 +30,7 @@
|
||||
| recorded_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 记录时间 |
|
||||
| site_id | BIGINT | NOT NULL | 门店 ID(多门店隔离) |
|
||||
| source | VARCHAR(20) | NOT NULL DEFAULT 'manual' | 线索来源(2026-02-27 新增) |
|
||||
| is_hidden | BOOLEAN | NOT NULL DEFAULT false | 是否隐藏(true=管理后台保留但小程序不展示)(2026-03-20 新增) |
|
||||
|
||||
### category 枚举值
|
||||
|
||||
@@ -65,6 +67,7 @@
|
||||
|
||||
- **后端 API**:`POST /api/member-birthday` 废弃,替换为 `POST /api/retention-clue`、`GET /api/retention-clue/{member_id}`、`DELETE /api/retention-clue/{clue_id}`
|
||||
- **source 字段**(2026-02-27):`POST /api/retention-clue` 接受可选 `source` 参数,默认 `manual`;`GET` 返回中包含 `source` 字段。已有数据自动填充 `DEFAULT 'manual'`,向后兼容
|
||||
- **is_hidden 字段**(2026-03-20 NS4):租户管理后台线索隐藏/显示控制。`DEFAULT false` 保证已有数据兼容。小程序端线索查询已追加 `WHERE is_hidden = false` 条件,隐藏线索仅在管理后台可见。租户管理后台路由 `tenant_clues.py` 提供 `PATCH /api/tenant/clues/{id}/visibility` 端点切换状态
|
||||
- **ETL Connector**:DWS 任务移除 FDW 读取 `member_birthday_manual` 的逻辑,生日仅从 `dim_member.birthday`(API 来源)读取
|
||||
- **FDW**:`fdw_app.member_birthday_manual` 外部表需在 ETL 库侧同步更新为 `fdw_app.member_retention_clue`(含 `source` 列)
|
||||
- **小程序**:助教端调用新 API 提交维客线索
|
||||
@@ -102,13 +105,13 @@ SELECT table_name FROM information_schema.tables
|
||||
WHERE table_schema = 'public' AND table_name = 'member_retention_clue';
|
||||
-- 预期:1 行
|
||||
|
||||
-- 3. 确认列结构完整(10 列)
|
||||
-- 3. 确认列结构完整(11 列)
|
||||
SELECT column_name, data_type, is_nullable
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = 'member_retention_clue'
|
||||
ORDER BY ordinal_position;
|
||||
-- 预期:id, member_id, category, summary, detail,
|
||||
-- recorded_by_assistant_id, recorded_by_name, recorded_at, site_id, source
|
||||
-- recorded_by_assistant_id, recorded_by_name, recorded_at, site_id, source, is_hidden
|
||||
|
||||
-- 4. 确认 CHECK 约束
|
||||
SELECT conname FROM pg_constraint
|
||||
@@ -143,6 +146,23 @@ SELECT col_description(
|
||||
-- 9. 确认已有数据的 source 分布
|
||||
SELECT source, COUNT(*) FROM member_retention_clue GROUP BY source;
|
||||
-- 预期:全部为 'manual'(或空表)
|
||||
|
||||
-- 10. 确认 is_hidden 列存在且默认值正确(2026-03-20)
|
||||
SELECT column_name, data_type, column_default, is_nullable
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'member_retention_clue'
|
||||
AND column_name = 'is_hidden';
|
||||
-- 预期:1 行,boolean, false, NO
|
||||
|
||||
-- 11. 确认已有数据的 is_hidden 分布(2026-03-20)
|
||||
SELECT is_hidden, COUNT(*) FROM member_retention_clue GROUP BY is_hidden;
|
||||
-- 预期:全部为 false(DEFAULT false 保证兼容)
|
||||
|
||||
-- 12. 确认列结构完整(11 列,含 is_hidden)
|
||||
SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = 'member_retention_clue';
|
||||
-- 预期:11
|
||||
```
|
||||
|
||||
## 关联文件
|
||||
@@ -150,9 +170,11 @@ SELECT source, COUNT(*) FROM member_retention_clue GROUP BY source;
|
||||
- 迁移脚本(建表):`db/zqyy_app/migrations/2026-02-26__refactor_birthday_to_retention_clue.sql`
|
||||
- 迁移脚本(source 列):`db/zqyy_app/migrations/2026-02-27__add_source_to_retention_clue.sql`
|
||||
- 迁移脚本(category 枚举对齐):`db/zqyy_app/migrations/2026-03-08__align_retention_clue_category_enum.sql`
|
||||
- 迁移脚本(is_hidden 列):`db/zqyy_app/migrations/2026-03-xx__ns4_member_clue_is_hidden.sql`
|
||||
- FDW 反向映射(生产):`db/fdw/setup_fdw_reverse.sql`
|
||||
- FDW 反向映射(测试):`db/fdw/setup_fdw_reverse_test.sql`
|
||||
- 后端路由:`apps/backend/app/routers/member_retention_clue.py`
|
||||
- 后端路由(租户管理后台):`apps/backend/app/routers/tenant_clues.py`
|
||||
- 后端模型:`apps/backend/app/schemas/member_retention_clue.py`
|
||||
- H5 原型:`docs/h5_ui/pages/customer-detail.html`、`docs/h5_ui/pages/task-detail.html`
|
||||
- 旧表文档(已归档):`docs/database/_archived/BD_Manual_member_birthday_manual.md`
|
||||
|
||||
53
docs/database/BD_manual_auth_users_avatar.md
Normal file
53
docs/database/BD_manual_auth_users_avatar.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# BD 手册:auth.users.avatar_url 字段
|
||||
|
||||
## 概述
|
||||
|
||||
`auth.users` 表新增 `avatar_url` 字段,存储用户头像的相对路径。
|
||||
|
||||
## 字段定义
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `avatar_url` | `VARCHAR(500)` | `NULL` | 头像相对路径,格式 `avatars/{user_id}.jpg` |
|
||||
|
||||
## 数据流
|
||||
|
||||
1. 小程序端通过 `<button open-type="chooseAvatar">` 获取微信头像临时路径
|
||||
2. 通过 `wx.uploadFile` 上传到 `POST /api/xcx/avatar/upload`
|
||||
3. 后端保存文件到 `AVATAR_EXPORT_PATH/{user_id}.jpg`(覆盖式,幂等)
|
||||
4. 数据库 `avatar_url` 更新为 `avatars/{user_id}.jpg`(相对路径)
|
||||
5. 小程序通过 `GET /api/xcx/avatar/{user_id}` 获取头像文件
|
||||
|
||||
## 关联接口
|
||||
|
||||
| 接口 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| `/api/xcx/avatar/upload` | POST | 上传头像,更新 avatar_url |
|
||||
| `/api/xcx/avatar/{user_id}` | GET | 获取头像文件(FileResponse) |
|
||||
| `/api/xcx/me` | GET | 返回 avatar_url 字段 |
|
||||
|
||||
## 设计决策
|
||||
|
||||
- 审核表 `auth.user_applications` 不冗余 `avatar_url`,通过 JOIN `auth.users` 获取(头像可能更新)
|
||||
- 文件命名按 `user_id`,覆盖式保存(幂等,无历史版本)
|
||||
- 文件大小限制 2MB,空文件拒绝
|
||||
|
||||
## 迁移脚本
|
||||
|
||||
`db/zqyy_app/migrations/20260324_add_avatar_url_to_users.sql`
|
||||
|
||||
## 环境变量
|
||||
|
||||
`AVATAR_EXPORT_PATH` — 头像文件存储目录,缺失时后端报 500 错误
|
||||
|
||||
## 回滚
|
||||
|
||||
```sql
|
||||
ALTER TABLE auth.users DROP COLUMN IF EXISTS avatar_url;
|
||||
```
|
||||
|
||||
## 验证
|
||||
|
||||
```sql
|
||||
SELECT id, avatar_url FROM auth.users WHERE avatar_url IS NOT NULL LIMIT 5;
|
||||
```
|
||||
254
docs/database/BD_manual_biz_registry_tables.md
Normal file
254
docs/database/BD_manual_biz_registry_tables.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# BD 手册:biz 注册体系表(NS4.1 registry)
|
||||
|
||||
## 概述
|
||||
|
||||
NS4.1 注册体系新增 4 张表,全部位于 `biz` Schema,建立「连接器 → 租户 → 店铺」三级注册体系。合并原 `auth.site_code_mapping`,统一管理上游 SaaS 系统、租户和店铺的关系,并为简写ID 提供归属和变更历史。
|
||||
|
||||
所有表位于 `zqyy_app` / `test_zqyy_app` 数据库。
|
||||
|
||||
## 变更原因
|
||||
|
||||
- NS4.1 注册体系设计,建立项目级「连接器 → 租户 → 店铺」三级结构
|
||||
- 合并原 `auth.site_code_mapping` 到 `biz.sites`,增加租户关联和简写ID 变更历史管理
|
||||
- 数据来源:种子数据从 `auth.site_code_mapping` 迁移,ETL 增量同步从 `dwd.dim_site`
|
||||
|
||||
## 变更说明
|
||||
|
||||
| 库 | Schema | 表 | 变更类型 | 说明 |
|
||||
|----|--------|---|---------|------|
|
||||
| zqyy_app | biz | connectors | 新建 | 连接器注册表 |
|
||||
| zqyy_app | biz | tenants | 新建 | 租户注册表 |
|
||||
| zqyy_app | biz | sites | 新建 | 店铺注册表(合并原 auth.site_code_mapping) |
|
||||
| zqyy_app | biz | site_code_history | 新建 | 简写ID 变更历史表 |
|
||||
| zqyy_app | auth | site_code_mapping | 废弃重命名 | → `auth._archived_site_code_mapping` |
|
||||
|
||||
---
|
||||
|
||||
## 1. biz.connectors — 连接器注册表
|
||||
|
||||
记录本项目接入的上游 SaaS 系统。当前仅有飞球(feiqiu)一个连接器,预留多连接器扩展能力。
|
||||
|
||||
### 表结构
|
||||
|
||||
| 列名 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | SERIAL | PRIMARY KEY | 自增主键 |
|
||||
| connector_key | VARCHAR(50) | UNIQUE NOT NULL | 连接器标识(如 `'feiqiu'`) |
|
||||
| display_name | VARCHAR(100) | NOT NULL | 显示名称 |
|
||||
| is_active | BOOLEAN | NOT NULL DEFAULT true | 是否启用 |
|
||||
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 创建时间 |
|
||||
|
||||
### 约束与索引
|
||||
|
||||
| 名称 | 类型 | 列 | 说明 |
|
||||
|------|------|---|------|
|
||||
| connectors_pkey | PRIMARY KEY | id | 主键 |
|
||||
| connectors_connector_key_key | UNIQUE | connector_key | 连接器标识唯一 |
|
||||
|
||||
### 种子数据
|
||||
|
||||
```sql
|
||||
INSERT INTO biz.connectors (connector_key, display_name) VALUES ('feiqiu', '飞球');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. biz.tenants — 租户注册表
|
||||
|
||||
连接器下的租户,`tenant_id` 来自上游系统。同一连接器下 `tenant_id` 唯一。
|
||||
|
||||
### 表结构
|
||||
|
||||
| 列名 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | SERIAL | PRIMARY KEY | 自增主键 |
|
||||
| connector_id | INTEGER | NOT NULL FK → biz.connectors(id) | 所属连接器 |
|
||||
| tenant_id | BIGINT | NOT NULL | 上游系统租户 ID |
|
||||
| tenant_name | VARCHAR(200) | — | 租户名称 |
|
||||
| is_active | BOOLEAN | NOT NULL DEFAULT true | 是否启用 |
|
||||
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 创建时间 |
|
||||
| updated_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 更新时间 |
|
||||
|
||||
### 约束与索引
|
||||
|
||||
| 名称 | 类型 | 列 | 说明 |
|
||||
|------|------|---|------|
|
||||
| tenants_pkey | PRIMARY KEY | id | 主键 |
|
||||
| tenants_connector_id_tenant_id_key | UNIQUE | (connector_id, tenant_id) | 同一连接器下租户唯一 |
|
||||
| tenants_connector_id_fkey | FOREIGN KEY | connector_id → biz.connectors(id) | 外键关联连接器 |
|
||||
|
||||
### 种子数据
|
||||
|
||||
```sql
|
||||
INSERT INTO biz.tenants (connector_id, tenant_id, tenant_name)
|
||||
VALUES (1, 2790683160709957, '朗朗桌球');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. biz.sites — 店铺注册表
|
||||
|
||||
合并原 `auth.site_code_mapping`,增加租户关联和简写ID 管理。`site_id` 来自上游系统,全局唯一。`site_code` 为当前生效的简写ID(6 位字符,3+3 格式),全局唯一。
|
||||
|
||||
### 表结构
|
||||
|
||||
| 列名 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | SERIAL | PRIMARY KEY | 自增主键 |
|
||||
| tenant_id | INTEGER | NOT NULL FK → biz.tenants(id) | 所属租户 |
|
||||
| site_id | BIGINT | NOT NULL UNIQUE | 上游系统门店 ID |
|
||||
| site_name | VARCHAR(200) | — | 门店名称 |
|
||||
| site_code | VARCHAR(6) | UNIQUE | 当前生效的简写ID(3+3 格式,如 `LLQ001`) |
|
||||
| site_label | VARCHAR(50) | — | 门店标签 |
|
||||
| is_active | BOOLEAN | NOT NULL DEFAULT true | 是否启用 |
|
||||
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 创建时间 |
|
||||
| updated_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 更新时间 |
|
||||
|
||||
### 约束与索引
|
||||
|
||||
| 名称 | 类型 | 列 | 说明 |
|
||||
|------|------|---|------|
|
||||
| sites_pkey | PRIMARY KEY | id | 主键 |
|
||||
| sites_site_id_key | UNIQUE | site_id | 上游门店 ID 唯一 |
|
||||
| sites_site_code_key | UNIQUE | site_code | 简写ID 全局唯一 |
|
||||
| sites_tenant_id_fkey | FOREIGN KEY | tenant_id → biz.tenants(id) | 外键关联租户 |
|
||||
|
||||
### 数据迁移
|
||||
|
||||
```sql
|
||||
-- 从 auth.site_code_mapping 迁移真实数据
|
||||
INSERT INTO biz.sites (tenant_id, site_id, site_name, site_code)
|
||||
SELECT t.id, scm.site_id, scm.site_name, scm.site_code
|
||||
FROM auth.site_code_mapping scm
|
||||
JOIN biz.tenants t ON t.tenant_id = scm.tenant_id
|
||||
WHERE scm.tenant_id IS NOT NULL;
|
||||
|
||||
-- ETL 增量同步补充:通过 FDW 读取 dwd.dim_site(scd2_is_current=1),
|
||||
-- 补充 auth.site_code_mapping 中没有但 dwd.dim_site 中有的店铺
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. biz.site_code_history — 简写ID 变更历史表
|
||||
|
||||
增量记录所有使用过的简写ID。`site_code` 全局唯一(含历史),确保已退役的 code 不会被重新分配。每个 `site_id` 最多一条 `is_current=true` 记录。
|
||||
|
||||
### 表结构
|
||||
|
||||
| 列名 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | SERIAL | PRIMARY KEY | 自增主键 |
|
||||
| site_id | BIGINT | NOT NULL | 关联 biz.sites.site_id |
|
||||
| site_code | VARCHAR(6) | NOT NULL UNIQUE | 简写ID(全局唯一,含历史) |
|
||||
| is_current | BOOLEAN | NOT NULL DEFAULT false | true=当前生效,每个 site_id 最多一条 |
|
||||
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 创建时间 |
|
||||
| retired_at | TIMESTAMPTZ | — | 退役时间(is_current=false 时设置) |
|
||||
|
||||
### 约束与索引
|
||||
|
||||
| 名称 | 类型 | 列 | 说明 |
|
||||
|------|------|---|------|
|
||||
| site_code_history_pkey | PRIMARY KEY | id | 主键 |
|
||||
| site_code_history_site_code_key | UNIQUE | site_code | 简写ID 全局唯一(含历史) |
|
||||
|
||||
### 初始数据
|
||||
|
||||
```sql
|
||||
-- 为已有 site_code 的店铺创建历史记录
|
||||
INSERT INTO biz.site_code_history (site_id, site_code, is_current)
|
||||
SELECT site_id, site_code, true
|
||||
FROM biz.sites
|
||||
WHERE site_code IS NOT NULL;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 兼容性
|
||||
|
||||
| 组件 | 影响 |
|
||||
|------|------|
|
||||
| 后端 API | 全部切换到 `biz.sites` + `biz.site_code_history`。新增 `admin_registry` 路由模块(租户列表、店铺列表、简写ID 管理)。`admin_tenant_admins` 路由中 `tenant_id` 从 `biz.tenants` 选择。`tenant_users` 路由中 site_code 查询从 `auth.site_code_mapping` 切换到 `biz.sites` |
|
||||
| ETL | 无直接影响。店铺同步通过 FDW 只读访问 ETL 库 `dwd.dim_site`,写入 `biz.sites`。ETL 流程本身不变 |
|
||||
| 小程序 | 无需改动。用户申请时的 site_code 验证由后端 API 透明切换到 `biz.sites` + `biz.site_code_history` |
|
||||
| 管理后台(admin-web) | 新增注册体系 API 调用(`src/api/registry.ts`),租户管理员创建流程从 `biz.tenants`/`biz.sites` 选择 |
|
||||
| 原 auth.site_code_mapping | 迁移完成后重命名为 `auth._archived_site_code_mapping`,保留供回滚 |
|
||||
|
||||
## 回滚策略
|
||||
|
||||
### 完整回滚(逆序 DROP + 恢复原表)
|
||||
|
||||
```sql
|
||||
BEGIN;
|
||||
-- 1. 逆序删除注册体系表
|
||||
DROP TABLE IF EXISTS biz.site_code_history CASCADE;
|
||||
DROP TABLE IF EXISTS biz.sites CASCADE;
|
||||
DROP TABLE IF EXISTS biz.tenants CASCADE;
|
||||
DROP TABLE IF EXISTS biz.connectors CASCADE;
|
||||
|
||||
-- 2. 恢复原表(如已重命名)
|
||||
ALTER TABLE IF EXISTS auth._archived_site_code_mapping
|
||||
RENAME TO site_code_mapping;
|
||||
COMMENT ON TABLE auth.site_code_mapping IS '店铺简写ID 映射表(已恢复)';
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
注意:
|
||||
- 回滚前需确认后端代码已切换回 `auth.site_code_mapping` 查询
|
||||
- `CASCADE` 会级联删除依赖对象
|
||||
- 如果 `biz.sites` 中已有新增店铺(ETL 同步补充的),回滚后这些数据将丢失
|
||||
|
||||
## 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 验证 biz.sites 中已有 site_code 的店铺数量
|
||||
SELECT COUNT(*) FROM biz.sites WHERE site_code IS NOT NULL;
|
||||
-- 预期:与原 auth.site_code_mapping 中有 site_code 的行数一致
|
||||
|
||||
-- 2. 验证 sites 与 site_code_history 的一致性
|
||||
SELECT s.site_id, s.site_code, h.is_current
|
||||
FROM biz.sites s
|
||||
LEFT JOIN biz.site_code_history h
|
||||
ON h.site_id = s.site_id AND h.site_code = s.site_code
|
||||
WHERE s.site_code IS NOT NULL;
|
||||
-- 预期:所有行的 h.is_current = true(每个有 code 的店铺在历史表中有对应的当前记录)
|
||||
|
||||
-- 3. 验证三级注册体系关联完整性
|
||||
SELECT c.connector_key, t.tenant_name, COUNT(s.id) AS site_count
|
||||
FROM biz.connectors c
|
||||
JOIN biz.tenants t ON t.connector_id = c.id
|
||||
LEFT JOIN biz.sites s ON s.tenant_id = t.id
|
||||
GROUP BY c.connector_key, t.tenant_name;
|
||||
-- 预期:至少 1 行(feiqiu / 朗朗桌球 / N),site_count > 0
|
||||
|
||||
-- 4. 验证 4 张注册体系表全部存在
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'biz'
|
||||
AND table_name IN ('connectors', 'tenants', 'sites', 'site_code_history')
|
||||
ORDER BY table_name;
|
||||
-- 预期:返回 4 行
|
||||
|
||||
-- 5. 验证 site_code 全局唯一性(sites + history 无冲突)
|
||||
SELECT site_code, COUNT(*) AS cnt
|
||||
FROM biz.site_code_history
|
||||
GROUP BY site_code
|
||||
HAVING COUNT(*) > 1;
|
||||
-- 预期:返回 0 行(每个 site_code 在历史表中最多出现一次)
|
||||
|
||||
-- 6. 验证种子数据
|
||||
SELECT connector_key, display_name FROM biz.connectors WHERE connector_key = 'feiqiu';
|
||||
-- 预期:1 行(feiqiu / 飞球)
|
||||
SELECT tenant_id, tenant_name FROM biz.tenants WHERE tenant_id = 2790683160709957;
|
||||
-- 预期:1 行(2790683160709957 / 朗朗桌球)
|
||||
```
|
||||
|
||||
## 关联文件
|
||||
|
||||
- DDL 基线(biz):`docs/database/ddl/zqyy_app__biz.sql`
|
||||
- 迁移脚本:`db/zqyy_app/migrations/2026-03-22__ns41_registry_tables.sql`
|
||||
- 后端路由:`apps/backend/app/routers/admin_registry.py`
|
||||
- 后端 Schema:`apps/backend/app/schemas/admin_registry.py`
|
||||
- 管理员路由:`apps/backend/app/routers/admin_tenant_admins.py`
|
||||
- 前端 API:`apps/admin-web/src/api/registry.ts`
|
||||
- Spec:`.kiro/specs/admin-web-enhancement/`
|
||||
- PRD:`docs/prd/Neo_Specs/NS4.1-tenant-admin-redesign.md`
|
||||
76
docs/database/BD_manual_dws_assistant_task_monthly.md
Normal file
76
docs/database/BD_manual_dws_assistant_task_monthly.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# BD 手册:biz.dws_assistant_task_monthly
|
||||
|
||||
> 助教任务月度统计汇总表
|
||||
|
||||
## 基本信息
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| Schema | biz |
|
||||
| 表名 | dws_assistant_task_monthly |
|
||||
| 所属库 | zqyy_app |
|
||||
| 创建日期 | 2026-03-31 |
|
||||
| 写入方 | `task_generator._update_task_stats()` |
|
||||
| 读取方 | 看板 / 绩效报表(待接入) |
|
||||
| 幂等策略 | UPSERT(`ON CONFLICT (site_id, assistant_id, stat_month) DO UPDATE`) |
|
||||
|
||||
## 用途
|
||||
|
||||
按助教 + 自然月维度汇总任务引擎的创建/完成/放弃/转移数据。每次 `task_generator.run()` 执行时,对当前门店的所有助教重新计算当月统计并 upsert。
|
||||
|
||||
## 字段定义
|
||||
|
||||
| 字段名 | 类型 | 可空 | 默认值 | 说明 |
|
||||
|--------|------|------|--------|------|
|
||||
| id | BIGSERIAL | NOT NULL | 自增 | 主键 |
|
||||
| site_id | BIGINT | NOT NULL | — | 门店 ID |
|
||||
| assistant_id | BIGINT | NOT NULL | — | 助教 ID |
|
||||
| stat_month | DATE | NOT NULL | — | 统计月份(月初日期,如 2026-03-01) |
|
||||
| recall_created | INT | NOT NULL | 0 | 当月创建的召回任务数(high_priority + priority) |
|
||||
| follow_up_created | INT | NOT NULL | 0 | 当月创建的回访任务数 |
|
||||
| relationship_created | INT | NOT NULL | 0 | 当月创建的关系构建任务数 |
|
||||
| total_created | INT | NOT NULL | 0 | 当月创建的任务总数 |
|
||||
| recall_completed | INT | NOT NULL | 0 | 当月完成的召回任务数 |
|
||||
| follow_up_completed | INT | NOT NULL | 0 | 当月完成的回访任务数 |
|
||||
| total_completed | INT | NOT NULL | 0 | 当月完成的任务总数 |
|
||||
| abandoned_count | INT | NOT NULL | 0 | 当月放弃的任务数 |
|
||||
| transferred_count | INT | NOT NULL | 0 | 当月转移的任务数 |
|
||||
| updated_at | TIMESTAMPTZ | NOT NULL | NOW() | 最后更新时间 |
|
||||
|
||||
## 约束与索引
|
||||
|
||||
| 名称 | 类型 | 字段 |
|
||||
|------|------|------|
|
||||
| PK | PRIMARY KEY | id |
|
||||
| UQ | UNIQUE | (site_id, assistant_id, stat_month) |
|
||||
| idx_task_monthly_site_month | INDEX | (site_id, stat_month DESC) |
|
||||
| idx_task_monthly_assistant | INDEX | (assistant_id, stat_month DESC) |
|
||||
|
||||
## 数据来源
|
||||
|
||||
统计数据从 `biz.coach_tasks` 表聚合:
|
||||
- 按 `assigned_assistant_id` + `DATE_TRUNC('month', created_at)` 分组
|
||||
- `recall_created` = COUNT WHERE task_type IN ('high_priority_recall', 'priority_recall')
|
||||
- `follow_up_created` = COUNT WHERE task_type = 'follow_up_visit'
|
||||
- `relationship_created` = COUNT WHERE task_type = 'relationship_building'
|
||||
- `*_completed` = COUNT WHERE status = 'completed' AND 对应 task_type
|
||||
- `abandoned_count` = COUNT WHERE status = 'abandoned'
|
||||
- `transferred_count` = COUNT WHERE status = 'transferred'
|
||||
|
||||
## 兼容性
|
||||
|
||||
- ETL:无直接依赖(表由后端 task_generator 写入)
|
||||
- 后端 API:待接入看板/绩效接口
|
||||
- 小程序:无直接依赖
|
||||
|
||||
## 回滚策略
|
||||
|
||||
```sql
|
||||
DROP TABLE IF EXISTS biz.dws_assistant_task_monthly;
|
||||
```
|
||||
|
||||
## 关联变更
|
||||
|
||||
- 迁移脚本:`db/zqyy_app/migrations/2026-03-31__task_stats_tables.sql`
|
||||
- 审计记录:`docs/audit/changes/2026-03-31__task-engine-overhaul.md`
|
||||
- 关联表:`biz.coach_tasks`(数据源)、`dws.dws_member_assistant_relation_index`(C 层历史总计字段)
|
||||
83
docs/database/BD_manual_fdw_finance_area.md
Normal file
83
docs/database/BD_manual_fdw_finance_area.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# BD_Manual:FDW 财务区域查询映射(fdw_finance_area)
|
||||
|
||||
> 目标库:后端通过 `get_etl_readonly_connection(site_id)` 直连 ETL 库
|
||||
> 关联 SPEC:board-finance-dws-area-refactor
|
||||
> 日期:2026-03-28
|
||||
|
||||
---
|
||||
|
||||
## 1. 变更说明
|
||||
|
||||
### 新增 RLS 视图(ETL 库 app Schema 自动导出)
|
||||
|
||||
本次新增的两张 DWS 表各有一个 RLS 视图,后端通过直连 ETL 库的 `app.v_*` 视图访问(与 RNS1.2 以来的模式一致,不使用 `fdw_etl.*` 外部表)。
|
||||
|
||||
| 视图 | 源表 | 用途 |
|
||||
|------|------|------|
|
||||
| `dws.v_dws_finance_area_daily` | `dws.dws_finance_area_daily` | 区域日粒度财务数据(overview/revenue 板块) |
|
||||
| `dws.v_dws_finance_board_cache` | `dws.dws_finance_board_cache` | 已完成周期缓存(overview 8 项指标) |
|
||||
|
||||
### 后端查询函数
|
||||
|
||||
| 函数 | 视图 | 用途 |
|
||||
|------|------|------|
|
||||
| `get_finance_overview_area()` | `v_dws_finance_area_daily` | 按 area_code 聚合 overview 8 项指标 |
|
||||
| `get_finance_revenue_area()` | `v_dws_finance_area_daily` | 按 area_code 聚合 revenue 板块数据 |
|
||||
| `get_finance_board_cache()` | `v_dws_finance_board_cache` | 查询缓存 |
|
||||
| `set_finance_board_cache()` | `dws.dws_finance_board_cache` | 写入/更新缓存 |
|
||||
|
||||
### RLS 隔离
|
||||
|
||||
所有查询通过 `SET LOCAL app.current_site_id = :site_id` 设置门店隔离参数,RLS 视图的 `WHERE site_id = current_setting('app.current_site_id')::bigint` 自动过滤。
|
||||
|
||||
---
|
||||
|
||||
## 2. 兼容性影响
|
||||
|
||||
| 组件 | 影响 |
|
||||
|------|------|
|
||||
| ETL 任务 | 无影响。FDW 配置不修改 ETL 库 |
|
||||
| 后端 API | `fdw_queries.py` 新增 4 个函数,`board_service.py` 改为调用新函数 |
|
||||
| 小程序 | 无直接影响(API 签名不变) |
|
||||
| 现有 FDW 外部表 | 无影响。新视图通过直连 ETL 库访问,不经过 `fdw_etl` Schema |
|
||||
|
||||
---
|
||||
|
||||
## 3. 回滚策略
|
||||
|
||||
后端回滚:
|
||||
1. 恢复 `board_service.py` 到旧逻辑(从 `dws_finance_daily_summary` 取数)
|
||||
2. 移除 `fdw_queries.py` 中的 4 个新函数
|
||||
|
||||
数据库回滚:
|
||||
```sql
|
||||
DROP VIEW IF EXISTS dws.v_dws_finance_board_cache;
|
||||
DROP TABLE IF EXISTS dws.dws_finance_board_cache;
|
||||
DROP VIEW IF EXISTS dws.v_dws_finance_area_daily;
|
||||
DROP TABLE IF EXISTS dws.dws_finance_area_daily;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 验证 RLS 视图可访问(需先设置 site_id)
|
||||
SET LOCAL app.current_site_id = '1';
|
||||
SELECT COUNT(*) FROM dws.v_dws_finance_area_daily;
|
||||
|
||||
-- 2. 验证缓存视图可访问
|
||||
SET LOCAL app.current_site_id = '1';
|
||||
SELECT COUNT(*) FROM dws.v_dws_finance_board_cache;
|
||||
|
||||
-- 3. 验证 app_reader 角色有 SELECT 权限
|
||||
SELECT has_table_privilege('app_reader', 'dws.v_dws_finance_area_daily', 'SELECT') AS daily_ok,
|
||||
has_table_privilege('app_reader', 'dws.v_dws_finance_board_cache', 'SELECT') AS cache_ok;
|
||||
|
||||
-- 4. 验证区域日粒度数据完整性(每天 9 行)
|
||||
SET LOCAL app.current_site_id = '1';
|
||||
SELECT stat_date, COUNT(*) AS cnt
|
||||
FROM dws.v_dws_finance_area_daily
|
||||
GROUP BY stat_date
|
||||
HAVING COUNT(*) != 9;
|
||||
```
|
||||
74
docs/database/BD_manual_idx_coach_tasks_rb_unique_active.md
Normal file
74
docs/database/BD_manual_idx_coach_tasks_rb_unique_active.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# BD 手册:idx_coach_tasks_rb_unique_active
|
||||
|
||||
| 字段 | 值 |
|
||||
|------|-----|
|
||||
| 数据库 | zqyy_app |
|
||||
| Schema | biz |
|
||||
| 表 | coach_tasks |
|
||||
| 索引名 | idx_coach_tasks_rb_unique_active |
|
||||
| 类型 | UNIQUE (partial) |
|
||||
| 创建日期 | 2026-03-25 |
|
||||
| 迁移脚本 | `db/zqyy_app/migrations/2026-03-25__relationship_building_baseline.sql` |
|
||||
|
||||
## 索引定义
|
||||
|
||||
```sql
|
||||
CREATE UNIQUE INDEX idx_coach_tasks_rb_unique_active
|
||||
ON biz.coach_tasks (site_id, assistant_id, member_id)
|
||||
WHERE task_type = 'relationship_building' AND status = 'active';
|
||||
```
|
||||
|
||||
## 用途
|
||||
|
||||
保证每个 `(site_id, assistant_id, member_id)` 组合最多存在 1 条 `status = 'active'` 的 `relationship_building` 任务。
|
||||
|
||||
支持 `_generate_baseline_relationship_tasks()` 中的 upsert 操作:
|
||||
```sql
|
||||
INSERT INTO biz.coach_tasks (...)
|
||||
VALUES (...)
|
||||
ON CONFLICT (site_id, assistant_id, member_id)
|
||||
WHERE task_type = 'relationship_building' AND status = 'active'
|
||||
DO NOTHING
|
||||
```
|
||||
|
||||
## 业务背景
|
||||
|
||||
保底 relationship_building 任务:对每个助教,所有确切发生过服务关系(`session_count > 0`)的客户都生成一条 relationship_building 任务。partial unique index 确保幂等性——重复运行不会产生重复任务。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- 写入方:`task_generator._generate_baseline_relationship_tasks()` — upsert 依赖此索引
|
||||
- 读取方:`task_manager.get_task_list_v2()` — 查询不直接使用此索引,但受益于去重保证
|
||||
- 不影响其他 task_type 的任务(partial index 仅覆盖 `relationship_building` + `active`)
|
||||
|
||||
## 回滚
|
||||
|
||||
```sql
|
||||
DROP INDEX IF EXISTS biz.idx_coach_tasks_rb_unique_active;
|
||||
```
|
||||
|
||||
回滚后 `_generate_baseline_relationship_tasks()` 的 `ON CONFLICT` 子句会报错(无匹配索引),需同步回滚代码。
|
||||
|
||||
## 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 确认索引存在
|
||||
SELECT indexname, indexdef FROM pg_indexes
|
||||
WHERE indexname = 'idx_coach_tasks_rb_unique_active';
|
||||
|
||||
-- 2. 确认无重复
|
||||
SELECT site_id, assistant_id, member_id, COUNT(*)
|
||||
FROM biz.coach_tasks
|
||||
WHERE task_type = 'relationship_building' AND status = 'active'
|
||||
GROUP BY site_id, assistant_id, member_id
|
||||
HAVING COUNT(*) > 1;
|
||||
-- 应返回 0 行
|
||||
|
||||
-- 3. 测试 upsert 幂等性(dry run)
|
||||
EXPLAIN INSERT INTO biz.coach_tasks
|
||||
(site_id, assistant_id, member_id, task_type, status, priority_score)
|
||||
VALUES (1, 1, 1, 'relationship_building', 'active', 0)
|
||||
ON CONFLICT (site_id, assistant_id, member_id)
|
||||
WHERE task_type = 'relationship_building' AND status = 'active'
|
||||
DO NOTHING;
|
||||
```
|
||||
230
docs/database/BD_manual_public_rbac_tables.md
Normal file
230
docs/database/BD_manual_public_rbac_tables.md
Normal file
@@ -0,0 +1,230 @@
|
||||
# BD 手册:public Schema — RBAC 与工作流(8 表)
|
||||
|
||||
> 目标库:`zqyy_app`(通过 `APP_DB_DSN` 连接)
|
||||
> DDL 位置:`docs/database/ddl/zqyy_app__public.sql`
|
||||
> 关联文档:`BD_manual_scheduled_tasks.md`(调度相关 3 表)、`BD_manual_member_retention_clue.md`(维客线索表)
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
`public` Schema 共 12 张表,本文档覆盖其中 RBAC(角色权限)+ 工作流(任务审批)共 8 张表。其余 4 张表已有独立文档:
|
||||
|
||||
| 表 | 文档 |
|
||||
|----|------|
|
||||
| `scheduled_tasks`、`task_queue`、`task_execution_log` | `BD_manual_scheduled_tasks.md` |
|
||||
| `member_retention_clue` | `BD_manual_member_retention_clue.md` |
|
||||
|
||||
### 表关系
|
||||
|
||||
```
|
||||
admin_users(管理后台登录账户,roles 数组字段)
|
||||
|
||||
users ──┬── user_roles ── roles ── role_permissions ── permissions
|
||||
│ (用户-角色映射) (角色) (角色-权限映射) (权限)
|
||||
│
|
||||
├── tasks(任务指派,creator_id / assignee_id → users)
|
||||
│ │
|
||||
└── approvals(审批记录,approver_id → users,task_id → tasks)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 表结构
|
||||
|
||||
### public.admin_users(9 字段)
|
||||
|
||||
管理后台(admin-web / tenant-admin)登录账户。通过 JWT `aud=admin` 或 `aud=tenant-admin` 认证。
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | SERIAL | PK | 自增主键 |
|
||||
| `username` | VARCHAR(64) | NOT NULL, UNIQUE | 登录用户名 |
|
||||
| `password_hash` | VARCHAR(256) | NOT NULL | bcrypt 密码哈希 |
|
||||
| `display_name` | VARCHAR(128) | 可空 | 显示名称 |
|
||||
| `site_id` | BIGINT | NOT NULL | 所属门店 ID |
|
||||
| `is_active` | BOOLEAN | DEFAULT true | 是否启用 |
|
||||
| `roles` | TEXT[] | NOT NULL, DEFAULT '{site_admin}' | 角色数组(`site_admin` / `tenant_admin` 等) |
|
||||
| `created_at` | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
|
||||
| `updated_at` | TIMESTAMPTZ | DEFAULT NOW() | 更新时间 |
|
||||
|
||||
> 注意:`admin_users.roles` 是 TEXT 数组字段(快捷权限标记),与 `roles` 表的 RBAC 体系并行使用。admin-web 当前主要依赖此数组字段做权限判断。
|
||||
|
||||
### public.users(8 字段)
|
||||
|
||||
小程序端用户(C 端会员),通过微信 code → JWT `aud=miniapp` 认证。
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | BIGSERIAL | PK | 自增主键 |
|
||||
| `wx_openid` | TEXT | UNIQUE,可空 | 微信 OpenID(登录唯一标识) |
|
||||
| `mobile` | TEXT | 可空 | 手机号 |
|
||||
| `nickname` | TEXT | 可空 | 用户昵称 |
|
||||
| `status` | INTEGER | DEFAULT 1 | 状态(1=正常) |
|
||||
| `site_id` | BIGINT | NOT NULL | 所属门店 ID |
|
||||
| `created_at` | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
|
||||
| `updated_at` | TIMESTAMPTZ | DEFAULT NOW() | 更新时间 |
|
||||
|
||||
### public.roles(5 字段)
|
||||
|
||||
角色定义表,按门店隔离。
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | SERIAL | PK | 自增主键 |
|
||||
| `name` | TEXT | NOT NULL, UNIQUE | 角色名称(全局唯一) |
|
||||
| `description` | TEXT | 可空 | 角色描述 |
|
||||
| `site_id` | BIGINT | NOT NULL | 所属门店 ID |
|
||||
| `created_at` | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
|
||||
|
||||
### public.permissions(4 字段)
|
||||
|
||||
权限定义表(资源+动作组合)。
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | SERIAL | PK | 自增主键 |
|
||||
| `resource` | TEXT | NOT NULL | 资源标识(如 `etl`、`users`) |
|
||||
| `action` | TEXT | NOT NULL | 操作标识(如 `read`、`write`、`execute`) |
|
||||
| `description` | TEXT | 可空 | 权限描述 |
|
||||
|
||||
### public.role_permissions(2 字段)
|
||||
|
||||
角色-权限关联表(多对多)。
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `role_id` | INTEGER | PK, FK → roles(id) ON DELETE CASCADE | 角色 ID |
|
||||
| `permission_id` | INTEGER | PK, FK → permissions(id) ON DELETE CASCADE | 权限 ID |
|
||||
|
||||
### public.user_roles(3 字段)
|
||||
|
||||
用户-角色关联表(多对多),按门店隔离。
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `user_id` | BIGINT | PK, FK → users(id) ON DELETE CASCADE | 用户 ID |
|
||||
| `role_id` | INTEGER | PK, FK → roles(id) ON DELETE CASCADE | 角色 ID |
|
||||
| `site_id` | BIGINT | NOT NULL | 门店 ID |
|
||||
|
||||
### public.tasks(9 字段)
|
||||
|
||||
任务指派表,用于门店内任务管理。
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | BIGSERIAL | PK | 自增主键 |
|
||||
| `title` | TEXT | NOT NULL | 任务标题 |
|
||||
| `description` | TEXT | 可空 | 任务描述 |
|
||||
| `status` | TEXT | DEFAULT 'pending' | 状态:`pending` / `in_progress` / `completed` |
|
||||
| `assignee_id` | BIGINT | FK → users(id),可空 | 负责人 ID |
|
||||
| `creator_id` | BIGINT | FK → users(id),可空 | 创建人 ID |
|
||||
| `site_id` | BIGINT | NOT NULL | 门店 ID |
|
||||
| `created_at` | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
|
||||
| `updated_at` | TIMESTAMPTZ | DEFAULT NOW() | 更新时间 |
|
||||
|
||||
### public.approvals(7 字段)
|
||||
|
||||
审批记录表,关联到任务。
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | BIGSERIAL | PK | 自增主键 |
|
||||
| `task_id` | BIGINT | FK → tasks(id) ON DELETE CASCADE,可空 | 关联任务 ID |
|
||||
| `approver_id` | BIGINT | FK → users(id),可空 | 审批人 ID |
|
||||
| `status` | TEXT | DEFAULT 'pending' | 状态:`pending` / `approved` / `rejected` |
|
||||
| `comment` | TEXT | 可空 | 审批意见 |
|
||||
| `site_id` | BIGINT | NOT NULL | 门店 ID |
|
||||
| `created_at` | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 约束与索引
|
||||
|
||||
| 表 | 约束/索引名 | 类型 | 说明 |
|
||||
|----|-----------|------|------|
|
||||
| `admin_users` | `admin_users_pkey` | PK | `(id)` |
|
||||
| `admin_users` | `admin_users_username_key` | UNIQUE | `(username)` |
|
||||
| `admin_users` | `idx_admin_users_site` | INDEX | `(site_id)` |
|
||||
| `users` | `users_pkey` | PK | `(id)` |
|
||||
| `users` | `users_wx_openid_key` | UNIQUE | `(wx_openid)` |
|
||||
| `users` | `idx_users_site_id` | INDEX | `(site_id)` |
|
||||
| `users` | `idx_users_mobile` | INDEX | `(mobile)` |
|
||||
| `roles` | `roles_pkey` | PK | `(id)` |
|
||||
| `roles` | `roles_name_key` | UNIQUE | `(name)` |
|
||||
| `roles` | `idx_roles_site_id` | INDEX | `(site_id)` |
|
||||
| `permissions` | `permissions_pkey` | PK | `(id)` |
|
||||
| `permissions` | `permissions_resource_action_key` | UNIQUE | `(resource, action)` |
|
||||
| `role_permissions` | `role_permissions_pkey` | PK | `(role_id, permission_id)` |
|
||||
| `role_permissions` | FK `role_id` | FK | → roles(id) ON DELETE CASCADE |
|
||||
| `role_permissions` | FK `permission_id` | FK | → permissions(id) ON DELETE CASCADE |
|
||||
| `user_roles` | `user_roles_pkey` | PK | `(user_id, role_id)` |
|
||||
| `user_roles` | FK `user_id` | FK | → users(id) ON DELETE CASCADE |
|
||||
| `user_roles` | FK `role_id` | FK | → roles(id) ON DELETE CASCADE |
|
||||
| `user_roles` | `idx_user_roles_site_id` | INDEX | `(site_id)` |
|
||||
| `tasks` | `tasks_pkey` | PK | `(id)` |
|
||||
| `tasks` | FK `assignee_id` | FK | → users(id) |
|
||||
| `tasks` | FK `creator_id` | FK | → users(id) |
|
||||
| `tasks` | `idx_tasks_site_id` | INDEX | `(site_id)` |
|
||||
| `tasks` | `idx_tasks_status` | INDEX | `(status)` |
|
||||
| `tasks` | `idx_tasks_assignee_id` | INDEX | `(assignee_id)` |
|
||||
| `approvals` | `approvals_pkey` | PK | `(id)` |
|
||||
| `approvals` | FK `task_id` | FK | → tasks(id) ON DELETE CASCADE |
|
||||
| `approvals` | FK `approver_id` | FK | → users(id) |
|
||||
| `approvals` | `idx_approvals_site_id` | INDEX | `(site_id)` |
|
||||
| `approvals` | `idx_approvals_task_id` | INDEX | `(task_id)` |
|
||||
|
||||
---
|
||||
|
||||
## 4. 兼容性影响
|
||||
|
||||
| 组件 | 影响 |
|
||||
|------|------|
|
||||
| admin-web | 直接依赖。登录用 `admin_users`,权限判断用 `admin_users.roles` 数组 |
|
||||
| tenant-admin | 直接依赖。登录也用 `admin_users`(`aud=tenant-admin`),按 `site_id` 隔离 |
|
||||
| 后端 API | `admin_users` 用于 JWT 签发和验证;RBAC 表(roles/permissions/user_roles)用于细粒度权限控制 |
|
||||
| 小程序 | 通过 `users` 表完成微信登录注册和用户信息管理 |
|
||||
| ETL | 无直接影响 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 验证 8 张表存在
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name IN ('admin_users', 'users', 'roles', 'permissions',
|
||||
'role_permissions', 'user_roles', 'tasks', 'approvals')
|
||||
ORDER BY table_name;
|
||||
-- 预期:8 行
|
||||
|
||||
-- 2. 验证 admin_users 字段数量
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = 'admin_users';
|
||||
-- 预期:9
|
||||
|
||||
-- 3. 验证 admin_users.roles 默认值
|
||||
SELECT column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = 'admin_users' AND column_name = 'roles';
|
||||
-- 预期:'{site_admin}'::text[]
|
||||
|
||||
-- 4. 验证外键关系
|
||||
SELECT tc.table_name, tc.constraint_name, ccu.table_name AS references_table
|
||||
FROM information_schema.table_constraints tc
|
||||
JOIN information_schema.constraint_column_usage ccu ON tc.constraint_name = ccu.constraint_name
|
||||
WHERE tc.table_schema = 'public'
|
||||
AND tc.constraint_type = 'FOREIGN KEY'
|
||||
AND tc.table_name IN ('approvals', 'tasks', 'role_permissions', 'user_roles')
|
||||
ORDER BY tc.table_name;
|
||||
-- 预期:7 行外键
|
||||
|
||||
-- 5. 验证 permissions 唯一约束
|
||||
SELECT constraint_name
|
||||
FROM information_schema.table_constraints
|
||||
WHERE table_schema = 'public' AND table_name = 'permissions' AND constraint_type = 'UNIQUE';
|
||||
-- 预期:permissions_resource_action_key
|
||||
```
|
||||
134
docs/database/BD_manual_scheduled_tasks.md
Normal file
134
docs/database/BD_manual_scheduled_tasks.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# BD 手册:scheduled_tasks 新增字段(P16 最小运行间隔)
|
||||
|
||||
## 概述
|
||||
|
||||
P16 为 `public.scheduled_tasks` 表新增 3 个字段,支持调度任务最小运行间隔机制。调度器轮询时检查并发状态和间隔约束,避免任务重复执行。
|
||||
|
||||
所有变更位于 `zqyy_app` / `test_zqyy_app` 数据库,`public` Schema。
|
||||
|
||||
## 变更原因
|
||||
|
||||
- P16 调度任务最小运行间隔需求:管理员可为每个任务配置最小间隔,任务即使调度到期也不会在间隔内重复执行
|
||||
- 新增 `last_success_at` 字段区分"最后执行时间"和"最后成功时间",支持更精确的间隔计算
|
||||
|
||||
## 变更说明
|
||||
|
||||
| 库 | Schema | 表 | 变更类型 | 说明 |
|
||||
|----|--------|---|---------|------|
|
||||
| zqyy_app | public | scheduled_tasks | 新增字段 ×4 | min_run_interval_value, min_run_interval_unit, last_success_at, min_run_intervals |
|
||||
|
||||
---
|
||||
|
||||
## 新增字段明细
|
||||
|
||||
| 列名 | 类型 | 约束 | 默认值 | 说明 |
|
||||
|------|------|------|--------|------|
|
||||
| min_run_interval_value | INTEGER | NOT NULL | 0 | 最小间隔数值(0=无限制,与现有行为完全一致) |
|
||||
| min_run_interval_unit | VARCHAR(20) | NOT NULL | 'minutes' | 间隔单位:`minutes` / `hours` / `days` |
|
||||
| last_success_at | TIMESTAMPTZ | — | NULL | 最后一次成功执行的时间 |
|
||||
| min_run_intervals | JSONB | NOT NULL | '{}' | 每任务子代码级别的最小运行间隔配置(key=task_code, value=interval_seconds) |
|
||||
|
||||
### DDL
|
||||
|
||||
```sql
|
||||
ALTER TABLE scheduled_tasks ADD COLUMN min_run_interval_value INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE scheduled_tasks ADD COLUMN min_run_interval_unit VARCHAR(20) NOT NULL DEFAULT 'minutes';
|
||||
ALTER TABLE scheduled_tasks ADD COLUMN last_success_at TIMESTAMPTZ;
|
||||
|
||||
COMMENT ON COLUMN scheduled_tasks.min_run_interval_value IS '最小间隔数值(0=无限制)';
|
||||
COMMENT ON COLUMN scheduled_tasks.min_run_interval_unit IS '间隔单位:minutes/hours/days';
|
||||
COMMENT ON COLUMN scheduled_tasks.last_success_at IS '最后一次成功执行的时间';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 业务逻辑
|
||||
|
||||
调度器(`scheduler.py`)轮询时对每个到期任务执行以下检查:
|
||||
|
||||
1. **并发检查**:`last_status = 'running'` → 跳过本次入队,日志记录 `skipped_concurrent`
|
||||
2. **间隔检查**:`min_run_interval_value > 0` 且 `now() - last_run_at < min_interval_seconds` → 跳过本次执行,推进 `next_run_at`,日志记录 `skipped_interval`
|
||||
3. **首次执行**:`last_run_at IS NULL`(从未执行)→ 跳过间隔检查,正常执行
|
||||
4. **强制执行**:`force=true` 参数绕过所有检查(并发 + 间隔),直接入队
|
||||
|
||||
### 间隔转换
|
||||
|
||||
```python
|
||||
def _convert_interval_to_seconds(value: int, unit: str) -> int:
|
||||
multipliers = {"minutes": 60, "hours": 3600, "days": 86400}
|
||||
return value * multipliers.get(unit, 60)
|
||||
```
|
||||
|
||||
### last_success_at 更新规则
|
||||
|
||||
- 任务成功完成时:`last_status='completed'`, `last_success_at=NOW()`
|
||||
- 任务失败时:`last_status='failed'`,`last_success_at` 不变
|
||||
|
||||
---
|
||||
|
||||
## 兼容性
|
||||
|
||||
| 组件 | 影响 |
|
||||
|------|------|
|
||||
| 后端 API | `POST /api/schedules` 和 `PUT /api/schedules/{id}` 请求体新增 `min_run_interval_value`、`min_run_interval_unit`。`GET /api/schedules` 响应新增 3 个字段。`POST /api/schedules/{id}/run` 新增 `force` 查询参数 |
|
||||
| 前端 ScheduleTab | 创建/编辑表单新增「最小运行间隔」配置行。列表新增「最小间隔」和「上次成功」列。手动执行确认框新增「强制执行」Checkbox |
|
||||
| ETL | 无影响。ETL 任务调度由 `scheduled_tasks` 表驱动,新增字段默认值 0 表示无限制,向后兼容 |
|
||||
| 小程序 | 无影响 |
|
||||
| 调度器 | `scheduler.py` 的 `check_and_enqueue()` 新增并发检查和间隔检查逻辑 |
|
||||
|
||||
## 回滚策略
|
||||
|
||||
```sql
|
||||
BEGIN;
|
||||
ALTER TABLE scheduled_tasks DROP COLUMN IF EXISTS min_run_interval_value;
|
||||
ALTER TABLE scheduled_tasks DROP COLUMN IF EXISTS min_run_interval_unit;
|
||||
ALTER TABLE scheduled_tasks DROP COLUMN IF EXISTS last_success_at;
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
注意:
|
||||
- 回滚后所有任务恢复为无间隔限制的行为
|
||||
- 回滚前需确认后端代码已移除对这 3 个字段的引用
|
||||
- `last_success_at` 数据丢失不可恢复
|
||||
|
||||
## 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 验证新增字段存在且类型正确
|
||||
SELECT column_name, data_type, column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'scheduled_tasks'
|
||||
AND column_name IN ('min_run_interval_value', 'min_run_interval_unit', 'last_success_at');
|
||||
-- 预期:3 行
|
||||
-- min_run_interval_value | integer | 0
|
||||
-- min_run_interval_unit | character varying | 'minutes'::character varying
|
||||
-- last_success_at | timestamp with time zone | NULL
|
||||
|
||||
-- 2. 验证现有任务的新字段默认值
|
||||
SELECT id, name, min_run_interval_value, min_run_interval_unit, last_success_at
|
||||
FROM scheduled_tasks
|
||||
LIMIT 5;
|
||||
-- 预期:min_run_interval_value=0, min_run_interval_unit='minutes', last_success_at=NULL(除非已手动配置)
|
||||
|
||||
-- 3. 验证已配置间隔的任务数量
|
||||
SELECT COUNT(*) FROM scheduled_tasks WHERE min_run_interval_value > 0;
|
||||
-- 预期:≥ 0(初始状态为 0,配置后递增)
|
||||
|
||||
-- 4. 验证字段注释
|
||||
SELECT col_description(
|
||||
(SELECT oid FROM pg_class WHERE relname = 'scheduled_tasks'),
|
||||
(SELECT attnum FROM pg_attribute WHERE attrelid = 'scheduled_tasks'::regclass AND attname = 'min_run_interval_value')
|
||||
);
|
||||
-- 预期:'最小间隔数值(0=无限制)'
|
||||
```
|
||||
|
||||
## 关联文件
|
||||
|
||||
- DDL 基线(public):`docs/database/ddl/zqyy_app__public.sql`
|
||||
- 迁移脚本:`db/zqyy_app/migrations/2026-03-22__p16_min_run_interval.sql`
|
||||
- 调度器逻辑:`apps/backend/app/services/scheduler.py`
|
||||
- 后端路由:`apps/backend/app/routers/schedules.py`
|
||||
- 后端 Schema:`apps/backend/app/schemas/schedules.py`
|
||||
- 前端组件:`apps/admin-web/src/components/ScheduleTab.tsx`
|
||||
- Spec:`.kiro/specs/admin-web-enhancement/`
|
||||
- PRD:`docs/prd/specs/P16-task-min-run-interval.md`
|
||||
105
docs/database/BD_manual_soft_delete_user_site_roles.md
Normal file
105
docs/database/BD_manual_soft_delete_user_site_roles.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# BD 手册:user_site_roles / user_assistant_binding 软删除
|
||||
|
||||
## 变更日期
|
||||
2026-03-24
|
||||
|
||||
## 变更说明
|
||||
|
||||
在 `auth.user_site_roles` 和 `auth.user_assistant_binding` 两张表上新增软删除字段,替代原有的物理删除(`DELETE`)。
|
||||
|
||||
### 新增字段
|
||||
|
||||
| 表 | 字段 | 类型 | 默认值 | 说明 |
|
||||
|----|------|------|--------|------|
|
||||
| `auth.user_site_roles` | `is_removed` | `boolean` | `false` | 软删除标记 |
|
||||
| `auth.user_site_roles` | `removed_at` | `timestamptz` | `NULL` | 移除时间戳 |
|
||||
| `auth.user_assistant_binding` | `is_removed` | `boolean` | `false` | 软删除标记 |
|
||||
| `auth.user_assistant_binding` | `removed_at` | `timestamptz` | `NULL` | 移除时间戳 |
|
||||
|
||||
### 新增索引
|
||||
|
||||
| 索引名 | 表 | 类型 | 说明 |
|
||||
|--------|-----|------|------|
|
||||
| `ix_user_site_roles_active` | `user_site_roles` | 部分索引 `WHERE is_removed = false` | 加速活跃记录查询 |
|
||||
| `ix_user_assistant_binding_active` | `user_assistant_binding` | 部分索引 `WHERE is_removed = false` | 加速活跃记录查询 |
|
||||
|
||||
## 兼容性影响
|
||||
|
||||
### 后端 API(已同步修改)
|
||||
所有查询 `user_site_roles` 和 `user_assistant_binding` 的位置均已添加 `AND is_removed = false` 过滤:
|
||||
- `xcx_auth.py`:登录、me 接口、切换门店、刷新令牌、获取门店列表、dev 调试接口
|
||||
- `tenant_users.py`:用户列表、编辑用户、更新绑定、移除用户
|
||||
- `role.py`:权限查询、门店列表、角色检查
|
||||
- `task_manager.py`:获取助教 ID
|
||||
- `task_generator.py`:门店助教规模检查、入驻时间保护
|
||||
- `performance_service.py`:助教信息查询
|
||||
|
||||
### ETL
|
||||
无直接影响。ETL 不写入这两张表。
|
||||
|
||||
### 小程序
|
||||
无代码改动。被移除的用户在小程序端会因角色查询返回空而进入已有的无权限路由。
|
||||
|
||||
### 租户管理后台
|
||||
`remove_user` 操作从 `DELETE` 改为 `UPDATE SET is_removed = true, removed_at = now()`。
|
||||
|
||||
## 查询规则(强制)
|
||||
|
||||
所有读取 `user_site_roles` 或 `user_assistant_binding` 的 SELECT 查询,必须包含 `AND is_removed = false`(或等效的 JOIN 条件 `AND xxx.is_removed = false`)。
|
||||
|
||||
例外:
|
||||
- 管理后台需要查看已移除记录的场景(如审计日志)
|
||||
- dev 调试接口中的物理删除操作(仅开发模式)
|
||||
|
||||
## 回滚策略
|
||||
|
||||
```sql
|
||||
-- 1. 恢复所有被软删除的记录
|
||||
UPDATE auth.user_site_roles SET is_removed = false, removed_at = NULL WHERE is_removed = true;
|
||||
UPDATE auth.user_assistant_binding SET is_removed = false, removed_at = NULL WHERE is_removed = true;
|
||||
|
||||
-- 2. 删除索引
|
||||
DROP INDEX IF EXISTS auth.ix_user_site_roles_active;
|
||||
DROP INDEX IF EXISTS auth.ix_user_assistant_binding_active;
|
||||
|
||||
-- 3. 删除字段
|
||||
ALTER TABLE auth.user_site_roles DROP COLUMN IF EXISTS removed_at;
|
||||
ALTER TABLE auth.user_site_roles DROP COLUMN IF EXISTS is_removed;
|
||||
ALTER TABLE auth.user_assistant_binding DROP COLUMN IF EXISTS removed_at;
|
||||
ALTER TABLE auth.user_assistant_binding DROP COLUMN IF EXISTS is_removed;
|
||||
```
|
||||
|
||||
注意:回滚后需同步还原后端代码中所有 `AND is_removed = false` 过滤条件,并将 `remove_user` 恢复为 `DELETE`。
|
||||
|
||||
## 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 确认字段存在且默认值正确
|
||||
SELECT column_name, data_type, column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'auth'
|
||||
AND table_name IN ('user_site_roles', 'user_assistant_binding')
|
||||
AND column_name IN ('is_removed', 'removed_at')
|
||||
ORDER BY table_name, column_name;
|
||||
|
||||
-- 2. 确认部分索引存在
|
||||
SELECT indexname, indexdef
|
||||
FROM pg_indexes
|
||||
WHERE schemaname = 'auth'
|
||||
AND indexname IN ('ix_user_site_roles_active', 'ix_user_assistant_binding_active');
|
||||
|
||||
-- 3. 确认现有数据未被误标记(所有记录 is_removed 应为 false)
|
||||
SELECT 'user_site_roles' AS tbl, COUNT(*) AS total,
|
||||
COUNT(*) FILTER (WHERE is_removed = true) AS removed
|
||||
FROM auth.user_site_roles
|
||||
UNION ALL
|
||||
SELECT 'user_assistant_binding', COUNT(*),
|
||||
COUNT(*) FILTER (WHERE is_removed = true)
|
||||
FROM auth.user_assistant_binding;
|
||||
|
||||
-- 4. 确认活跃记录查询走部分索引(EXPLAIN 检查)
|
||||
EXPLAIN SELECT * FROM auth.user_site_roles WHERE user_id = 1 AND site_id = 1 AND is_removed = false;
|
||||
```
|
||||
|
||||
## 迁移脚本
|
||||
`db/zqyy_app/migrations/20260324_soft_delete_user_site_roles_and_binding.sql`
|
||||
544
docs/database/BD_manual_tenant_admin_tables.md
Normal file
544
docs/database/BD_manual_tenant_admin_tables.md
Normal file
@@ -0,0 +1,544 @@
|
||||
# BD 手册:租户管理后台表(NS4 tenant-admin-web)
|
||||
|
||||
## 概述
|
||||
|
||||
NS4 租户管理后台新增 6 张表,分布在 `auth` 和 `biz` 两个 Schema 中。`auth.tenant_admins` 为租户管理员认证表,与小程序 `auth.users`(微信登录)完全隔离;`biz` Schema 下 5 张表支撑 Excel 数据上传功能(上传日志、助教奖罚、3 张 staging 暂存表)。
|
||||
|
||||
所有表位于 `zqyy_app` / `test_zqyy_app` 数据库。
|
||||
|
||||
## 变更说明
|
||||
|
||||
| 库 | Schema | 表 | 变更类型 | 说明 |
|
||||
|----|--------|---|---------|------|
|
||||
| zqyy_app | auth | tenant_admins | 新建 | 租户管理员认证表 |
|
||||
| zqyy_app | biz | excel_upload_log | 新建 | Excel 上传记录表 |
|
||||
| zqyy_app | biz | salary_adjustments | 新建 | 助教奖罚明细表 |
|
||||
| zqyy_app | biz | stg_finance_expense | 新建 | 财务支出暂存表 |
|
||||
| zqyy_app | biz | stg_platform_income | 新建 | 团购收入暂存表 |
|
||||
| zqyy_app | biz | stg_recharge_commission | 新建 | 充值业绩归属暂存表 |
|
||||
|
||||
---
|
||||
|
||||
## 1. auth.tenant_admins — 租户管理员表
|
||||
|
||||
独立于小程序 `auth.users`,使用用户名+密码登录,JWT `aud=tenant-admin` 隔离。
|
||||
|
||||
### 表结构
|
||||
|
||||
| 列名 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | BIGSERIAL | PRIMARY KEY | 自增主键 |
|
||||
| username | VARCHAR(50) | UNIQUE NOT NULL | 登录用户名 |
|
||||
| password_hash | VARCHAR(255) | NOT NULL | bcrypt 哈希密码 |
|
||||
| display_name | VARCHAR(100) | — | 显示名称 |
|
||||
| tenant_id | BIGINT | NOT NULL | 所属租户 ID |
|
||||
| managed_site_ids | BIGINT[] | NOT NULL | 管辖门店 ID 列表(数据隔离依据) |
|
||||
| admin_type | VARCHAR(20) | NOT NULL DEFAULT 'tenant_admin' | 管理员类型:tenant_admin(租户管理员)/ site_admin(店铺管理员),CHECK 约束 |
|
||||
| is_active | BOOLEAN | DEFAULT true | 账号状态(false=禁用,登录返回 403,仅控制启用/禁用,与软删除无关) |
|
||||
| deleted_at | TIMESTAMPTZ | DEFAULT NULL | 软删除时间戳:NULL=正常,非 NULL=已删除(与 is_active 分离) |
|
||||
| created_by | BIGINT | — | 创建者(管理员 ID) |
|
||||
| created_at | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
|
||||
| last_login_at | TIMESTAMPTZ | — | 最后登录时间 |
|
||||
|
||||
### 约束与索引
|
||||
|
||||
| 名称 | 类型 | 列 | 说明 |
|
||||
|------|------|---|------|
|
||||
| tenant_admins_pkey | PRIMARY KEY | id | 主键 |
|
||||
| tenant_admins_username_key | UNIQUE | username | 用户名唯一 |
|
||||
| chk_admin_type | CHECK | admin_type | admin_type IN ('tenant_admin', 'site_admin') |
|
||||
| idx_tenant_admin_tenant | INDEX (btree) | tenant_id | 按租户查询 |
|
||||
| idx_tenant_admins_active_not_deleted | INDEX (btree, partial) | is_active WHERE deleted_at IS NULL | 加速列表和登录查询(仅索引未删除记录) |
|
||||
| idx_tenant_admins_username_lower | UNIQUE (partial) | LOWER(username) WHERE deleted_at IS NULL | 大小写不敏感唯一约束(2026-03-23) |
|
||||
|
||||
---
|
||||
|
||||
## 2. biz.excel_upload_log — Excel 上传记录表
|
||||
|
||||
记录每次 Excel 上传的批次信息,支撑上传→校验→冲突→确认的完整流程。
|
||||
|
||||
### 表结构
|
||||
|
||||
| 列名 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | BIGSERIAL | PRIMARY KEY | 自增主键 |
|
||||
| site_id | BIGINT | NOT NULL | 门店 ID |
|
||||
| upload_type | VARCHAR(30) | NOT NULL, CHECK | 模板类型(见枚举) |
|
||||
| file_name | VARCHAR(255) | NOT NULL | 原始文件名 |
|
||||
| uploaded_by | BIGINT | NOT NULL | 上传人(管理员 ID) |
|
||||
| row_count | INTEGER | DEFAULT 0 | 数据行数 |
|
||||
| conflict_count | INTEGER | DEFAULT 0 | 冲突行数 |
|
||||
| resolved_count | INTEGER | DEFAULT 0 | 已解决冲突数 |
|
||||
| status | VARCHAR(20) | NOT NULL, CHECK | 批次状态(见枚举) |
|
||||
| error_detail | JSONB | — | 错误详情 / 临时缓存上传数据 |
|
||||
| created_at | TIMESTAMPTZ | DEFAULT NOW() | 上传时间 |
|
||||
| confirmed_at | TIMESTAMPTZ | — | 确认写入时间 |
|
||||
|
||||
### upload_type 枚举值
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| expense | 财务支出 |
|
||||
| platform_income | 团购收入 |
|
||||
| salary_adj | 助教奖罚 |
|
||||
| recharge_commission | 充值业绩归属 |
|
||||
|
||||
### status 枚举值
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| pending | 待确认(已上传校验通过,等待用户确认写入) |
|
||||
| confirmed | 已确认(数据已写入目标表) |
|
||||
| failed | 失败(写入过程出错,已回滚) |
|
||||
|
||||
### 约束与索引
|
||||
|
||||
| 名称 | 类型 | 列 | 说明 |
|
||||
|------|------|---|------|
|
||||
| excel_upload_log_pkey | PRIMARY KEY | id | 主键 |
|
||||
| CHECK (upload_type) | CHECK | upload_type | 限制模板类型枚举 |
|
||||
| CHECK (status) | CHECK | status | 限制状态枚举 |
|
||||
| idx_excel_log_site | INDEX (btree) | (site_id, created_at DESC) | 按门店+时间查询 |
|
||||
|
||||
---
|
||||
|
||||
## 3. biz.salary_adjustments — 助教奖罚明细表
|
||||
|
||||
直接写入 biz Schema(非 staging),记录助教扣款/奖金明细。
|
||||
|
||||
### 表结构
|
||||
|
||||
| 列名 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | BIGSERIAL | PRIMARY KEY | 自增主键 |
|
||||
| site_id | BIGINT | NOT NULL | 门店 ID |
|
||||
| assistant_id | BIGINT | — | 匹配到的助教 ID(可空,人员匹配失败时为 NULL) |
|
||||
| assistant_name | VARCHAR(100) | NOT NULL | 助教姓名(Excel 原始值) |
|
||||
| assistant_number | VARCHAR(50) | NOT NULL | 助教编号(Excel 原始值) |
|
||||
| salary_month | VARCHAR(7) | NOT NULL | 月份(格式 YYYY-MM) |
|
||||
| adjustment_type | VARCHAR(20) | NOT NULL, CHECK | 类型:deduction(扣款)/ bonus(奖金) |
|
||||
| amount | NUMERIC(12,2) | NOT NULL, CHECK (> 0) | 金额(正数) |
|
||||
| reason | VARCHAR(200) | NOT NULL | 原因说明 |
|
||||
| upload_batch_id | BIGINT | FK → excel_upload_log(id) | 上传批次 |
|
||||
| created_at | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
|
||||
| created_by | BIGINT | — | 上传人(管理员 ID) |
|
||||
|
||||
### adjustment_type 枚举值
|
||||
|
||||
| 值 | Excel 中文值 | 说明 |
|
||||
|----|-------------|------|
|
||||
| deduction | 扣款 | 扣款 |
|
||||
| bonus | 奖金 | 奖金 |
|
||||
|
||||
### 约束与索引
|
||||
|
||||
| 名称 | 类型 | 列 | 说明 |
|
||||
|------|------|---|------|
|
||||
| salary_adjustments_pkey | PRIMARY KEY | id | 主键 |
|
||||
| CHECK (adjustment_type) | CHECK | adjustment_type | 限制类型枚举 |
|
||||
| CHECK (amount) | CHECK | amount | 金额必须 > 0 |
|
||||
| salary_adjustments_upload_batch_id_fkey | FOREIGN KEY | upload_batch_id → excel_upload_log(id) | 关联上传批次 |
|
||||
| idx_salary_adj_site_month | INDEX (btree) | (site_id, salary_month) | 按门店+月份查询 |
|
||||
| idx_salary_adj_assistant_month | INDEX (btree) | (assistant_id, salary_month) | 按助教+月份查询 |
|
||||
|
||||
---
|
||||
|
||||
## 4. biz.stg_finance_expense — 财务支出暂存表
|
||||
|
||||
通过 Excel 上传写入,等待 ETL 同步到正式表。
|
||||
|
||||
### 表结构
|
||||
|
||||
| 列名 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | BIGSERIAL | PRIMARY KEY | 自增主键 |
|
||||
| site_id | BIGINT | NOT NULL | 门店 ID |
|
||||
| expense_month | VARCHAR(7) | NOT NULL | 月份(格式 YYYY-MM) |
|
||||
| category | VARCHAR(50) | NOT NULL | 支出类别(8 值枚举) |
|
||||
| amount | NUMERIC(12,2) | NOT NULL | 金额 |
|
||||
| remark | TEXT | — | 备注 |
|
||||
| upload_batch_id | BIGINT | FK → excel_upload_log(id) | 上传批次 |
|
||||
| synced_at | TIMESTAMPTZ | — | ETL 同步时间(NULL=未同步) |
|
||||
| created_at | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
|
||||
|
||||
### 冲突检测主键
|
||||
|
||||
`(site_id, expense_month, category)` — 同门店同月份同类别视为冲突。
|
||||
|
||||
### 约束与索引
|
||||
|
||||
| 名称 | 类型 | 列 | 说明 |
|
||||
|------|------|---|------|
|
||||
| stg_finance_expense_pkey | PRIMARY KEY | id | 主键 |
|
||||
| stg_finance_expense_upload_batch_id_fkey | FOREIGN KEY | upload_batch_id → excel_upload_log(id) | 关联上传批次 |
|
||||
|
||||
---
|
||||
|
||||
## 5. biz.stg_platform_income — 团购收入暂存表
|
||||
|
||||
### 表结构
|
||||
|
||||
| 列名 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | BIGSERIAL | PRIMARY KEY | 自增主键 |
|
||||
| site_id | BIGINT | NOT NULL | 门店 ID |
|
||||
| income_month | VARCHAR(7) | NOT NULL | 月份(格式 YYYY-MM) |
|
||||
| platform_name | VARCHAR(100) | NOT NULL | 平台名称 |
|
||||
| amount | NUMERIC(12,2) | NOT NULL | 收入金额 |
|
||||
| remark | TEXT | — | 备注 |
|
||||
| upload_batch_id | BIGINT | FK → excel_upload_log(id) | 上传批次 |
|
||||
| synced_at | TIMESTAMPTZ | — | ETL 同步时间(NULL=未同步) |
|
||||
| created_at | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
|
||||
|
||||
### 冲突检测主键
|
||||
|
||||
`(site_id, income_month, platform_name)` — 同门店同月份同平台视为冲突。
|
||||
|
||||
### 约束与索引
|
||||
|
||||
| 名称 | 类型 | 列 | 说明 |
|
||||
|------|------|---|------|
|
||||
| stg_platform_income_pkey | PRIMARY KEY | id | 主键 |
|
||||
| stg_platform_income_upload_batch_id_fkey | FOREIGN KEY | upload_batch_id → excel_upload_log(id) | 关联上传批次 |
|
||||
|
||||
---
|
||||
|
||||
## 6. biz.stg_recharge_commission — 充值业绩归属暂存表
|
||||
|
||||
### 表结构
|
||||
|
||||
| 列名 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | BIGSERIAL | PRIMARY KEY | 自增主键 |
|
||||
| site_id | BIGINT | NOT NULL | 门店 ID |
|
||||
| recharge_date | DATE | NOT NULL | 充值日期 |
|
||||
| member_name | VARCHAR(100) | NOT NULL | 会员名称 |
|
||||
| recharge_amount | NUMERIC(12,2) | NOT NULL | 充值金额 |
|
||||
| assigned_assistant | VARCHAR(100) | NOT NULL | 归属助教 |
|
||||
| reward_amount | NUMERIC(12,2) | NOT NULL | 奖励金额 |
|
||||
| upload_batch_id | BIGINT | FK → excel_upload_log(id) | 上传批次 |
|
||||
| synced_at | TIMESTAMPTZ | — | ETL 同步时间(NULL=未同步) |
|
||||
| created_at | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
|
||||
|
||||
### 冲突检测主键
|
||||
|
||||
`(site_id, recharge_date, member_name, assigned_assistant)` — 同门店同日期同会员同助教视为冲突。
|
||||
|
||||
### 约束与索引
|
||||
|
||||
| 名称 | 类型 | 列 | 说明 |
|
||||
|------|------|---|------|
|
||||
| stg_recharge_commission_pkey | PRIMARY KEY | id | 主键 |
|
||||
| stg_recharge_commission_upload_batch_id_fkey | FOREIGN KEY | upload_batch_id → excel_upload_log(id) | 关联上传批次 |
|
||||
|
||||
---
|
||||
|
||||
## 兼容性
|
||||
|
||||
- **后端 API**:5 个新路由模块(tenant_auth / tenant_users / tenant_excel / tenant_clues / admin_tenant_admins)全部依赖上述表
|
||||
- **ETL**:3 张 staging 表(stg_finance_expense / stg_platform_income / stg_recharge_commission)的 `synced_at` 字段供 ETL 同步标记,ETL 读取 `synced_at IS NULL` 的行进行同步
|
||||
- **小程序**:无直接影响(租户管理后台独立认证体系)
|
||||
- **管理后台(admin-web)**:新增租户管理员 CRUD 页面,调用 admin_tenant_admins 路由
|
||||
|
||||
## 回滚策略
|
||||
|
||||
### 完整回滚(按依赖顺序)
|
||||
|
||||
```sql
|
||||
BEGIN;
|
||||
-- 先删除有外键依赖的表
|
||||
DROP TABLE IF EXISTS biz.salary_adjustments CASCADE;
|
||||
DROP TABLE IF EXISTS biz.stg_finance_expense CASCADE;
|
||||
DROP TABLE IF EXISTS biz.stg_platform_income CASCADE;
|
||||
DROP TABLE IF EXISTS biz.stg_recharge_commission CASCADE;
|
||||
-- 再删除被引用的表
|
||||
DROP TABLE IF EXISTS biz.excel_upload_log CASCADE;
|
||||
-- 最后删除认证表
|
||||
DROP TABLE IF EXISTS auth.tenant_admins CASCADE;
|
||||
-- 清理序列(CASCADE 已处理,此处为显式确认)
|
||||
DROP SEQUENCE IF EXISTS biz.excel_upload_log_id_seq;
|
||||
DROP SEQUENCE IF EXISTS biz.salary_adjustments_id_seq;
|
||||
DROP SEQUENCE IF EXISTS biz.stg_finance_expense_id_seq;
|
||||
DROP SEQUENCE IF EXISTS biz.stg_platform_income_id_seq;
|
||||
DROP SEQUENCE IF EXISTS biz.stg_recharge_commission_id_seq;
|
||||
DROP SEQUENCE IF EXISTS auth.tenant_admins_id_seq;
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
## 验证步骤
|
||||
|
||||
```sql
|
||||
-- 1. 确认 auth.tenant_admins 表存在且列完整(12 列,含 admin_type + deleted_at)
|
||||
SELECT column_name, data_type, is_nullable, column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'auth' AND table_name = 'tenant_admins'
|
||||
ORDER BY ordinal_position;
|
||||
-- 预期:12 行(id, username, password_hash, display_name, tenant_id,
|
||||
-- managed_site_ids, admin_type, is_active, deleted_at, created_by, created_at, last_login_at)
|
||||
|
||||
-- 2. 确认 tenant_admins 唯一约束
|
||||
SELECT conname, contype FROM pg_constraint
|
||||
WHERE conrelid = 'auth.tenant_admins'::regclass;
|
||||
-- 预期:包含 tenant_admins_pkey (p) 和 tenant_admins_username_key (u)
|
||||
|
||||
-- 3. 确认 biz.excel_upload_log 表存在且 CHECK 约束正确
|
||||
SELECT conname, pg_get_constraintdef(oid) FROM pg_constraint
|
||||
WHERE conrelid = 'biz.excel_upload_log'::regclass AND contype = 'c';
|
||||
-- 预期:2 行(upload_type CHECK 和 status CHECK)
|
||||
|
||||
-- 4. 确认 biz.salary_adjustments 外键和 CHECK 约束
|
||||
SELECT conname, contype, pg_get_constraintdef(oid) FROM pg_constraint
|
||||
WHERE conrelid = 'biz.salary_adjustments'::regclass AND contype IN ('c', 'f');
|
||||
-- 预期:3 行(adjustment_type CHECK, amount CHECK, upload_batch_id FK)
|
||||
|
||||
-- 5. 确认 3 张 staging 表的外键
|
||||
SELECT c.conname, c.conrelid::regclass AS table_name
|
||||
FROM pg_constraint c
|
||||
WHERE c.confrelid = 'biz.excel_upload_log'::regclass AND c.contype = 'f';
|
||||
-- 预期:4 行(salary_adjustments + 3 张 staging 表各 1 个 FK)
|
||||
|
||||
-- 6. 确认索引
|
||||
SELECT schemaname, tablename, indexname FROM pg_indexes
|
||||
WHERE tablename IN ('tenant_admins', 'excel_upload_log', 'salary_adjustments',
|
||||
'stg_finance_expense', 'stg_platform_income', 'stg_recharge_commission')
|
||||
ORDER BY tablename, indexname;
|
||||
-- 预期:tenant_admins 3 个(pkey + idx_tenant_admin_tenant + idx_tenant_admins_active_not_deleted)
|
||||
-- excel_upload_log 2 个(pkey + idx_excel_log_site)
|
||||
-- salary_adjustments 3 个(pkey + idx_salary_adj_site_month + idx_salary_adj_assistant_month)
|
||||
-- stg_* 各 1 个(pkey)
|
||||
|
||||
-- 7. 确认 6 张表均可查询
|
||||
SELECT 'auth.tenant_admins' AS tbl, COUNT(*) FROM auth.tenant_admins
|
||||
UNION ALL SELECT 'biz.excel_upload_log', COUNT(*) FROM biz.excel_upload_log
|
||||
UNION ALL SELECT 'biz.salary_adjustments', COUNT(*) FROM biz.salary_adjustments
|
||||
UNION ALL SELECT 'biz.stg_finance_expense', COUNT(*) FROM biz.stg_finance_expense
|
||||
UNION ALL SELECT 'biz.stg_platform_income', COUNT(*) FROM biz.stg_platform_income
|
||||
UNION ALL SELECT 'biz.stg_recharge_commission', COUNT(*) FROM biz.stg_recharge_commission;
|
||||
-- 预期:6 行,各表行数 ≥ 0
|
||||
```
|
||||
|
||||
## NS4.1 变更补充(2026-03-22)
|
||||
|
||||
### auth.tenant_admins 行为变更
|
||||
|
||||
NS4.1 对 `auth.tenant_admins` 表做了以下调整:
|
||||
|
||||
#### deleted_at 软删除字段(已合并入主 DDL)
|
||||
|
||||
`deleted_at` 字段已合并到主迁移脚本 `2026-03-20__ns4_tenant_admin_tables.sql` 中,不再需要独立的 ALTER TABLE 迁移。字段语义:
|
||||
- `NULL` = 正常记录
|
||||
- 非 `NULL` = 已删除(时间戳记录删除时间)
|
||||
- 与 `is_active` 分离:`is_active` 控制启用/禁用,`deleted_at` 控制软删除
|
||||
- 部分索引 `idx_tenant_admins_active_not_deleted` 仅索引 `deleted_at IS NULL` 的记录
|
||||
|
||||
#### 软删除逻辑
|
||||
|
||||
`DELETE /api/admin/tenant-admins/{id}` 端点实际执行软删除:将 `is_active` 设置为 `false`,不物理删除行。已禁用的管理员再次删除返回 409。
|
||||
|
||||
```sql
|
||||
-- 软删除
|
||||
UPDATE auth.tenant_admins SET is_active = false WHERE id = :id AND is_active = true;
|
||||
```
|
||||
|
||||
#### tenant_id 来源变更
|
||||
|
||||
创建管理员时,`tenant_id` 不再是自由输入,而是从 `biz.tenants` 表中选择。后端校验 `tenant_id` 在 `biz.tenants` 中存在且 `is_active=true`。
|
||||
|
||||
```sql
|
||||
-- 创建时校验
|
||||
SELECT id FROM biz.tenants WHERE id = :tenant_id AND is_active = true;
|
||||
```
|
||||
|
||||
#### username 可编辑
|
||||
|
||||
`PATCH /api/admin/tenant-admins/{id}` 端点支持修改 `username`。修改时校验全局唯一性(大小写不敏感),冲突返回 409 Conflict。
|
||||
|
||||
```sql
|
||||
-- 唯一性校验(大小写不敏感)
|
||||
SELECT id FROM auth.tenant_admins WHERE LOWER(username) = LOWER(:new_username) AND id != :current_id AND deleted_at IS NULL;
|
||||
```
|
||||
|
||||
#### 列表过滤
|
||||
|
||||
`GET /api/admin/tenant-admins` 端点默认只返回 `is_active=true` 的记录。新增 `include_inactive` 查询参数,设为 `true` 时返回所有记录(含已禁用)。
|
||||
|
||||
列表查询 JOIN `biz.tenants` 获取 `tenant_name` 字段。
|
||||
|
||||
---
|
||||
|
||||
## 关联文件
|
||||
|
||||
- DDL 基线(auth):`docs/database/ddl/zqyy_app__auth.sql`
|
||||
- DDL 基线(biz):`docs/database/ddl/zqyy_app__biz.sql`
|
||||
- 迁移脚本:`db/zqyy_app/migrations/2026-03-20__ns4_tenant_admin_tables.sql`
|
||||
- 迁移脚本:`db/zqyy_app/migrations/2026-03-23__case_insensitive_username.sql`
|
||||
- 迁移脚本:`db/zqyy_app/migrations/2026-03-23__cleanup_roles_add_admin_type.sql`
|
||||
- 后端路由:`apps/backend/app/routers/tenant_auth.py`、`tenant_users.py`、`tenant_excel.py`、`tenant_clues.py`、`admin_tenant_admins.py`、`tenant_site_admins.py`
|
||||
- 后端 Schema:`apps/backend/app/schemas/tenant_excel.py`、`admin_tenant_admins.py`
|
||||
- 认证模块:`apps/backend/app/auth/tenant_admins.py`
|
||||
- Spec:`.kiro/specs/tenant-admin-web/`
|
||||
|
||||
---
|
||||
|
||||
## NS4.2 变更补充(2026-03-23)
|
||||
|
||||
### auth.tenant_admins 用户名大小写不敏感
|
||||
|
||||
#### 变更说明
|
||||
|
||||
登录、创建、编辑管理员时,用户名统一转小写存储,查询使用 `LOWER()` 比较。避免 `Admin` 和 `admin` 被视为不同账号。
|
||||
|
||||
#### 新增索引
|
||||
|
||||
| 名称 | 类型 | 列 | 说明 |
|
||||
|------|------|---|------|
|
||||
| idx_tenant_admins_username_lower | UNIQUE (partial) | LOWER(username) WHERE deleted_at IS NULL | 大小写不敏感唯一约束 |
|
||||
|
||||
#### 代码变更
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `tenant_auth.py` | 登录查询 `WHERE LOWER(username) = LOWER(%s)` |
|
||||
| `admin_tenant_admins.py` | 创建 `INSERT ... VALUES (LOWER(%s), ...)`;编辑 `SET username = LOWER(%s)`;唯一性校验 `LOWER()` 比较 |
|
||||
|
||||
#### 兼容性
|
||||
|
||||
- 后端 API:登录和 CRUD 接口透明兼容,无需前端改动
|
||||
- ETL:无影响
|
||||
- 小程序:无影响(独立认证体系)
|
||||
- 管理后台(admin-web):无需改动,后端统一处理
|
||||
|
||||
#### 回滚策略
|
||||
|
||||
```sql
|
||||
-- 1. 删除函数索引
|
||||
DROP INDEX IF EXISTS auth.idx_tenant_admins_username_lower;
|
||||
-- 2. 代码回滚:恢复 WHERE username = %s(不带 LOWER)
|
||||
-- 注意:已小写化的用户名不可逆,但不影响功能
|
||||
```
|
||||
|
||||
#### 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 确认索引存在
|
||||
SELECT indexname FROM pg_indexes
|
||||
WHERE tablename = 'tenant_admins' AND indexname = 'idx_tenant_admins_username_lower';
|
||||
-- 预期:1 行
|
||||
|
||||
-- 2. 确认无大写用户名残留
|
||||
SELECT username FROM auth.tenant_admins WHERE username != LOWER(username);
|
||||
-- 预期:0 行
|
||||
|
||||
-- 3. 确认大小写不敏感登录可用
|
||||
SELECT id FROM auth.tenant_admins WHERE LOWER(username) = LOWER('Admin') AND deleted_at IS NULL;
|
||||
-- 预期:与 SELECT ... WHERE LOWER(username) = 'admin' 结果一致
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## NS4.3 变更补充(2026-03-23)
|
||||
|
||||
### 角色体系隔离 + 店铺管理员支持
|
||||
|
||||
#### 变更说明
|
||||
|
||||
| 对象 | 变更类型 | 说明 |
|
||||
|------|---------|------|
|
||||
| `auth.tenant_admins.admin_type` | 新增字段 | `VARCHAR(20) NOT NULL DEFAULT 'tenant_admin'`,区分租户管理员(`tenant_admin`)和店铺管理员(`site_admin`) |
|
||||
| `chk_admin_type` | 新增约束 | `CHECK (admin_type IN ('tenant_admin', 'site_admin'))` |
|
||||
| `auth.roles` — `site_admin` / `tenant_admin` | 删除 | 小程序 RBAC 体系不需要这两个角色,租户/店铺管理员的区分通过 `admin_type` 列实现 |
|
||||
| `auth.role_permissions` | 删除关联 | 删除 `site_admin` / `tenant_admin` 对应的 10 条权限映射 |
|
||||
| `auth.roles` — `head_coach` / `manager` | 新增 | 小程序端新增教练和管理员角色 |
|
||||
|
||||
#### 业务规则
|
||||
|
||||
- 租户管理员(`admin_type='tenant_admin'`):可管理所有管辖门店,可创建/编辑/删除店铺管理员
|
||||
- 店铺管理员(`admin_type='site_admin'`):仅可管理分配的门店,不可创建其他管理员
|
||||
- 登录后界面一致,仅数据范围不同(由 `managed_site_ids` 控制)
|
||||
- 用户名格式:`{第一个管辖店铺的 site_code} + 最长 50 字符`
|
||||
- 只有 `admin_type='tenant_admin'` 的管理员可以访问 `/api/tenant/site-admins/*` 端点
|
||||
|
||||
#### 新增后端端点(店铺管理员 CRUD)
|
||||
|
||||
| 方法 | 路径 | 说明 | 权限 |
|
||||
|------|------|------|------|
|
||||
| GET | `/api/tenant/site-admins` | 列出店铺管理员 | 仅 tenant_admin |
|
||||
| POST | `/api/tenant/site-admins` | 创建店铺管理员 | 仅 tenant_admin |
|
||||
| PATCH | `/api/tenant/site-admins/{id}` | 编辑店铺管理员 | 仅 tenant_admin |
|
||||
| DELETE | `/api/tenant/site-admins/{id}` | 删除店铺管理员(软删除) | 仅 tenant_admin |
|
||||
| POST | `/api/tenant/site-admins/{id}/reset-password` | 重置密码 | 仅 tenant_admin |
|
||||
|
||||
#### 代码变更
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `tenant_auth.py` | 登录查询加入 `admin_type` 列,JWT 签发包含 `admin_type` |
|
||||
| `tenant_admins.py` | `CurrentTenantAdmin` dataclass 加入 `admin_type` 字段 |
|
||||
| `tenant_site_admins.py` | 新增路由模块,5 个 CRUD 端点 |
|
||||
| `main.py` | 注册 `tenant_site_admins` 路由 |
|
||||
| `admin_tenant_admins.py` | SQL 查询加入 `ta.admin_type` 列 |
|
||||
| `xcx_auth.py` | `dev-switch-role` 硬编码更新(删除 site_admin/tenant_admin,新增 head_coach/manager) |
|
||||
|
||||
#### 兼容性
|
||||
|
||||
| 组件 | 影响 |
|
||||
|------|------|
|
||||
| 后端 API | 直接依赖。JWT 新增 `admin_type` 字段;新增 5 个店铺管理员端点;admin-web 列表接口返回 `admin_type` |
|
||||
| 租户管理后台(tenant-admin) | 直接依赖。菜单根据 `adminType` 动态显示;新增店铺管理员管理页面 |
|
||||
| 管理后台(admin-web) | 间接依赖。租户管理员列表新增"类型"列显示 |
|
||||
| 小程序 | 间接依赖。`auth.roles` 删除 site_admin/tenant_admin,新增 head_coach/manager |
|
||||
| ETL | 无影响 |
|
||||
|
||||
#### 回滚策略
|
||||
|
||||
```sql
|
||||
BEGIN;
|
||||
-- 1. 回滚 admin_type 列
|
||||
ALTER TABLE auth.tenant_admins DROP CONSTRAINT IF EXISTS chk_admin_type;
|
||||
ALTER TABLE auth.tenant_admins DROP COLUMN IF EXISTS admin_type;
|
||||
|
||||
-- 2. 回滚角色变更(恢复 site_admin/tenant_admin,删除 head_coach/manager)
|
||||
DELETE FROM auth.role_permissions
|
||||
WHERE role_id IN (SELECT id FROM auth.roles WHERE code IN ('head_coach', 'manager'));
|
||||
DELETE FROM auth.roles WHERE code IN ('head_coach', 'manager');
|
||||
|
||||
INSERT INTO auth.roles (code, name, description)
|
||||
VALUES ('site_admin', '店铺管理员', '单店管理员,可查看所有看板和审核用户'),
|
||||
('tenant_admin', '租户管理员', '连锁管理员,可管理多店铺和所有功能');
|
||||
|
||||
-- 恢复 site_admin/tenant_admin 的权限映射(各 5 条)
|
||||
INSERT INTO auth.role_permissions (role_id, permission_id)
|
||||
SELECT r.id, p.id
|
||||
FROM auth.roles r, auth.permissions p
|
||||
WHERE r.code IN ('site_admin', 'tenant_admin');
|
||||
|
||||
-- 3. 代码回滚:移除 tenant_site_admins 路由;JWT 移除 admin_type;恢复 dev-switch-role 硬编码
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
#### 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 确认 admin_type 列存在且默认值正确
|
||||
SELECT column_name, data_type, column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'auth' AND table_name = 'tenant_admins' AND column_name = 'admin_type';
|
||||
-- 预期:admin_type, character varying, 'tenant_admin'::character varying
|
||||
|
||||
-- 2. 确认 CHECK 约束
|
||||
SELECT conname, pg_get_constraintdef(oid)
|
||||
FROM pg_constraint
|
||||
WHERE conrelid = 'auth.tenant_admins'::regclass AND conname = 'chk_admin_type';
|
||||
-- 预期:chk_admin_type, CHECK ((admin_type)::text = ANY (...))
|
||||
|
||||
-- 3. 确认角色体系(4 条,无 site_admin/tenant_admin)
|
||||
SELECT code, name FROM auth.roles ORDER BY id;
|
||||
-- 预期:coach, staff, head_coach, manager
|
||||
|
||||
-- 4. 确认角色-权限映射(11 条)
|
||||
SELECT r.code, COUNT(rp.permission_id) AS perm_count
|
||||
FROM auth.roles r
|
||||
JOIN auth.role_permissions rp ON r.id = rp.role_id
|
||||
GROUP BY r.code ORDER BY r.code;
|
||||
-- 预期:coach=2, head_coach=2, manager=5, staff=2(共 11 条,head_coach 仅 view_tasks+view_board)
|
||||
```
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
## DDL 基线(`ddl/` 子目录)
|
||||
|
||||
从测试库自动导出的完整 DDL,按 schema 分文件。重新生成:`python scripts/ops/gen_consolidated_ddl.py`
|
||||
从测试库自动导出的完整 DDL,按 schema 分文件。重新生成:`PYTHONUTF8=1 python tools/db/gen_consolidated_ddl.py`
|
||||
|
||||
最近一次刷新:**2026-04-05**(合并了截至 2026-03-31 的全部迁移)。
|
||||
|
||||
| 文件 | 数据库 | Schema | 内容 |
|
||||
|------|--------|--------|------|
|
||||
@@ -10,12 +12,13 @@
|
||||
| `etl_feiqiu__ods.sql` | etl_feiqiu | ods | 原始数据层(23 表) |
|
||||
| `etl_feiqiu__dwd.sql` | etl_feiqiu | dwd | 明细数据层(42 表) |
|
||||
| `etl_feiqiu__core.sql` | etl_feiqiu | core | 跨门店标准化(7 表) |
|
||||
| `etl_feiqiu__dws.sql` | etl_feiqiu | dws | 汇总数据层(36 表 + 物化视图) |
|
||||
| `etl_feiqiu__dws.sql` | etl_feiqiu | dws | 汇总数据层(38 表) |
|
||||
| `etl_feiqiu__app.sql` | etl_feiqiu | app | RLS 视图层(仅视图,无表) |
|
||||
| `zqyy_app__public.sql` | zqyy_app | public | 小程序业务表(12 表) |
|
||||
| `zqyy_app__auth.sql` | zqyy_app | auth | 用户认证与权限(8 表) |
|
||||
| `zqyy_app__biz.sql` | zqyy_app | biz | 核心业务表(任务/备注/触发器/AI,7 表) |
|
||||
| `fdw.sql` | — | — | FDW 正向跨库映射配置(etl→app) |
|
||||
| `zqyy_app__auth.sql` | zqyy_app | auth | 用户认证与权限(9 表) |
|
||||
| `zqyy_app__biz.sql` | zqyy_app | biz | 核心业务表(21 表) |
|
||||
| `fdw.sql` | — | — | FDW 正向映射(zqyy_app → etl_feiqiu.app) |
|
||||
| `fdw_reverse.sql` | — | — | FDW 反向映射(etl_feiqiu → zqyy_app) |
|
||||
|
||||
## 业务库文档(BD_Manual — zqyy_app / 跨模块)
|
||||
|
||||
@@ -23,31 +26,39 @@
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `BD_Manual_auth_tables.md` | zqyy_app auth schema 表结构 |
|
||||
| `BD_Manual_biz_tables.md` | zqyy_app biz schema 表结构 |
|
||||
| `BD_Manual_ai_tables.md` | zqyy_app AI 相关表 |
|
||||
| `BD_Manual_auth_biz_schemas.md` | auth + biz schema 建库说明 |
|
||||
| `BD_Manual_app_schema_rls_views.md` | app schema RLS 视图 |
|
||||
| `BD_Manual_fdw_etl_setup.md` | FDW 跨库映射配置 |
|
||||
| `BD_Manual_member_retention_clue.md` | zqyy_app 维客线索表 |
|
||||
| `BD_manual_public_rbac_tables.md` | zqyy_app public schema RBAC 与工作流(admin_users/users/roles/permissions/user_roles/tasks/approvals,8 表) |
|
||||
| `BD_manual_scheduled_tasks.md` | zqyy_app public schema 调度任务表(scheduled_tasks/task_queue/task_execution_log) |
|
||||
| `BD_manual_auth_tables.md` | zqyy_app auth schema 表结构(9 表) |
|
||||
| `BD_manual_auth_biz_schemas.md` | auth + biz schema 建库说明 |
|
||||
| `BD_manual_biz_tables.md` | zqyy_app biz schema 核心业务表(coach_tasks/notes/trigger_jobs,12 字段) |
|
||||
| `BD_manual_biz_registry_tables.md` | zqyy_app biz schema 注册体系(connectors/tenants/sites/site_code_history) |
|
||||
| `BD_manual_ai_tables.md` | zqyy_app biz schema AI 相关表(ai_conversations/ai_messages/ai_cache/ai_run_logs/ai_trigger_jobs) |
|
||||
| `BD_manual_tenant_admin_tables.md` | zqyy_app auth/biz 租户管理后台表(tenant_admins/excel_upload_log/salary_adjustments/stg_*) |
|
||||
| `BD_manual_member_retention_clue.md` | zqyy_app 维客线索表 |
|
||||
| `BD_manual_app_schema_rls_views.md` | ETL app schema RLS 视图(49 视图) |
|
||||
| `BD_manual_fdw_etl_setup.md` | FDW 正向跨库映射配置(zqyy_app → etl_feiqiu) |
|
||||
| `BD_manual_fdw_reverse_retention_clue.md` | FDW 反向映射(etl_feiqiu → zqyy_app 维客线索) |
|
||||
|
||||
## 归档(`_archived/` 子目录)
|
||||
|
||||
已吸收进 DDL 基线的迁移变更记录,仅供历史参考:
|
||||
- 迁移变更类 BD_Manual(加列、改约束、删表、FDW 变更、tenant_id 类型变更等)
|
||||
- 迁移变更类 BD_Manual(加列、改约束、删表、FDW 变更、tenant_id 类型变更、tenant_admins 软删除等)
|
||||
- `etl_feiqiu_schema_migration.md`(旧迁移汇总)
|
||||
- `zqyy_app_admin_web_tables.md`(建表记录)
|
||||
|
||||
## 注意事项
|
||||
## 文档分工
|
||||
|
||||
- `fdw.sql` 仅包含正向映射(etl_feiqiu → zqyy_app),反向映射(zqyy_app → etl_feiqiu)的可执行脚本在 `db/fdw/setup_fdw_reverse*.sql`
|
||||
- DDL 基线最近一次从测试库导出日期:2026-03-15(合并了 2026-02-27 至 2026-03-09 的全部迁移)
|
||||
- ETL 专属文档(ODS→DWD 映射、DWS 表文档):`apps/etl/connectors/feiqiu/docs/database/`
|
||||
| 位置 | 管辖范围 |
|
||||
|------|---------|
|
||||
| `docs/database/`(本目录) | 业务库(zqyy_app) BD_Manual + 跨模块(FDW/RLS) + DDL 基线 |
|
||||
| `apps/etl/connectors/feiqiu/docs/database/` | ETL 专属(ODS/DWD/DWS 表级文档、API 映射) |
|
||||
| `db/` | 运行时资产(迁移脚本/FDW 配置/建库脚本) |
|
||||
| `tools/db/` | 数据库工具(DDL 生成/验证/一致性检查) |
|
||||
|
||||
## 相关资源
|
||||
|
||||
- 种子数据:已合并进各 DDL 文件末尾(不再单独维护)
|
||||
- FDW 配置(可执行):`db/fdw/`(含正向 + 反向 + 测试环境版本)
|
||||
- DDL 生成脚本:`scripts/ops/gen_consolidated_ddl.py`
|
||||
- 迁移脚本(活跃):`db/etl_feiqiu/migrations/`、`db/zqyy_app/migrations/`(当前已清空,1.0 基线已统一)
|
||||
- DDL 生成脚本:`tools/db/gen_consolidated_ddl.py`
|
||||
- 迁移脚本(活跃):`db/etl_feiqiu/migrations/`(11 个)、`db/zqyy_app/migrations/`(8 个)
|
||||
- 迁移脚本归档:`db/_archived/ddl_baseline_2026-02-22/`
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# BD 手册 — auth.tenant_admins 新增 deleted_at 软删除字段
|
||||
|
||||
## 变更日期
|
||||
2026-03-22
|
||||
|
||||
## 变更说明
|
||||
|
||||
### 新增字段
|
||||
| 字段 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `deleted_at` | `TIMESTAMPTZ` | `NULL` | 软删除时间戳:NULL=正常,非 NULL=已删除 |
|
||||
|
||||
### 新增索引
|
||||
| 索引名 | 说明 |
|
||||
|--------|------|
|
||||
| `idx_tenant_admins_active_not_deleted` | 部分索引 `(is_active) WHERE deleted_at IS NULL`,加速列表和登录查询 |
|
||||
|
||||
### 语义变更
|
||||
- 删除与禁用分离:`is_active` 仅控制启用/禁用,`deleted_at` 控制软删除
|
||||
- DELETE 接口改为设置 `deleted_at = NOW()`,不再检查 `is_active` 状态
|
||||
- 所有查询(列表、登录、编辑、重置密码)默认过滤 `deleted_at IS NULL`
|
||||
- 用户名唯一性校验仅在未删除记录中生效
|
||||
|
||||
## 兼容性影响
|
||||
|
||||
| 模块 | 影响 |
|
||||
|------|------|
|
||||
| 后端 DELETE 接口 | 改为设置 `deleted_at`,不再返回 409(已禁用) |
|
||||
| 后端列表接口 | 新增 `deleted_at IS NULL` 过滤,已删除记录不再出现 |
|
||||
| 租户登录接口 | 新增 `deleted_at IS NULL` 过滤,已删除账号无法登录 |
|
||||
| 编辑/重置密码接口 | WHERE 条件加 `deleted_at IS NULL`,已删除记录返回 404 |
|
||||
| 前端 | 无需改动,删除按钮已存在 |
|
||||
| ETL | 无影响(不涉及 tenant_admins 表) |
|
||||
|
||||
## 回滚策略
|
||||
```sql
|
||||
DROP INDEX IF EXISTS auth.idx_tenant_admins_active_not_deleted;
|
||||
ALTER TABLE auth.tenant_admins DROP COLUMN IF EXISTS deleted_at;
|
||||
```
|
||||
回滚后需同步还原后端代码中的 DELETE 接口逻辑。
|
||||
|
||||
## 验证 SQL
|
||||
```sql
|
||||
-- 1) 字段存在性
|
||||
SELECT column_name, data_type, is_nullable, column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'auth' AND table_name = 'tenant_admins' AND column_name = 'deleted_at';
|
||||
-- 预期:1 行
|
||||
|
||||
-- 2) 索引存在性
|
||||
SELECT indexname FROM pg_indexes
|
||||
WHERE schemaname = 'auth' AND tablename = 'tenant_admins'
|
||||
AND indexname = 'idx_tenant_admins_active_not_deleted';
|
||||
-- 预期:1 行
|
||||
|
||||
-- 3) 现有数据不受影响
|
||||
SELECT COUNT(*) FROM auth.tenant_admins WHERE deleted_at IS NOT NULL;
|
||||
-- 预期:0
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- etl_feiqiu / app(RLS 视图层)
|
||||
-- 生成日期:2026-03-15
|
||||
-- 生成日期:2026-04-05
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
@@ -39,6 +39,17 @@ SELECT id,
|
||||
FROM dws.dws_assistant_daily_detail d;
|
||||
;
|
||||
|
||||
CREATE OR REPLACE VIEW app.v_cfg_area_category AS
|
||||
SELECT DISTINCT category_code,
|
||||
category_name,
|
||||
display_name,
|
||||
short_name,
|
||||
sort_order
|
||||
FROM dws.cfg_area_category
|
||||
WHERE ((is_active = true) AND ((category_code)::text <> ALL ((ARRAY['SPECIAL'::character varying, 'OTHER'::character varying])::text[])))
|
||||
ORDER BY sort_order;
|
||||
;
|
||||
|
||||
CREATE OR REPLACE VIEW app.v_cfg_assistant_level_price AS
|
||||
SELECT price_id,
|
||||
level_code,
|
||||
@@ -104,19 +115,6 @@ SELECT tier_id,
|
||||
FROM dws.cfg_performance_tier;
|
||||
;
|
||||
|
||||
CREATE OR REPLACE VIEW app.v_cfg_area_category AS
|
||||
SELECT DISTINCT
|
||||
category_code,
|
||||
category_name,
|
||||
display_name,
|
||||
short_name,
|
||||
sort_order
|
||||
FROM dws.cfg_area_category
|
||||
WHERE is_active = TRUE
|
||||
AND category_code NOT IN ('SPECIAL', 'OTHER')
|
||||
ORDER BY sort_order;
|
||||
;
|
||||
|
||||
CREATE OR REPLACE VIEW app.v_dim_assistant AS
|
||||
SELECT assistant_id,
|
||||
user_id,
|
||||
@@ -606,6 +604,26 @@ SELECT contribution_id,
|
||||
WHERE (site_id = (current_setting('app.current_site_id'::text))::bigint);
|
||||
;
|
||||
|
||||
CREATE OR REPLACE VIEW app.v_dws_assistant_project_tag AS
|
||||
SELECT id,
|
||||
site_id,
|
||||
tenant_id,
|
||||
assistant_id,
|
||||
time_window,
|
||||
category_code,
|
||||
category_name,
|
||||
short_name,
|
||||
duration_seconds,
|
||||
total_seconds,
|
||||
percentage,
|
||||
is_tagged,
|
||||
computed_at,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM dws.dws_assistant_project_tag
|
||||
WHERE (site_id = (current_setting('app.current_site_id'::text))::bigint);
|
||||
;
|
||||
|
||||
CREATE OR REPLACE VIEW app.v_dws_assistant_recharge_commission AS
|
||||
SELECT id,
|
||||
site_id,
|
||||
@@ -671,6 +689,92 @@ SELECT id,
|
||||
WHERE (site_id = (current_setting('app.current_site_id'::text))::bigint);
|
||||
;
|
||||
|
||||
CREATE OR REPLACE VIEW app.v_dws_coach_area_hours AS
|
||||
SELECT id,
|
||||
site_id,
|
||||
tenant_id,
|
||||
stat_month,
|
||||
assistant_id,
|
||||
area_code,
|
||||
base_hours,
|
||||
bonus_hours,
|
||||
room_hours,
|
||||
effective_hours,
|
||||
trashed_hours,
|
||||
base_service_count,
|
||||
bonus_service_count,
|
||||
room_service_count,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM dws.dws_coach_area_hours
|
||||
WHERE (site_id = (current_setting('app.current_site_id'::text))::bigint);
|
||||
;
|
||||
|
||||
CREATE OR REPLACE VIEW app.v_dws_finance_area_daily AS
|
||||
SELECT id,
|
||||
site_id,
|
||||
tenant_id,
|
||||
stat_date,
|
||||
area_code,
|
||||
table_fee_amount,
|
||||
goods_amount,
|
||||
assistant_pd_amount,
|
||||
assistant_cx_amount,
|
||||
gross_amount,
|
||||
discount_groupbuy,
|
||||
discount_vip,
|
||||
discount_manual,
|
||||
discount_gift_card,
|
||||
discount_rounding,
|
||||
discount_other,
|
||||
discount_total,
|
||||
confirmed_income,
|
||||
cash_pay_amount,
|
||||
cash_paper_amount,
|
||||
scan_pay_amount,
|
||||
groupbuy_pay_amount,
|
||||
recharge_cash_inflow,
|
||||
cash_inflow_total,
|
||||
cash_outflow_total,
|
||||
cash_balance_change,
|
||||
card_consume_total,
|
||||
recharge_card_consume,
|
||||
gift_card_consume,
|
||||
recharge_cash,
|
||||
first_recharge_cash,
|
||||
renewal_cash,
|
||||
order_count,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM dws.dws_finance_area_daily
|
||||
WHERE (site_id = (current_setting('app.current_site_id'::text))::bigint);
|
||||
;
|
||||
|
||||
CREATE OR REPLACE VIEW app.v_dws_finance_board_cache AS
|
||||
SELECT id,
|
||||
site_id,
|
||||
time_range,
|
||||
area_code,
|
||||
start_date,
|
||||
end_date,
|
||||
prev_start_date,
|
||||
prev_end_date,
|
||||
occurrence,
|
||||
discount,
|
||||
discount_rate,
|
||||
confirmed_revenue,
|
||||
cash_in,
|
||||
cash_out,
|
||||
cash_balance,
|
||||
balance_rate,
|
||||
data_fingerprint,
|
||||
computed_at,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM dws.dws_finance_board_cache
|
||||
WHERE (site_id = (current_setting('app.current_site_id'::text))::bigint);
|
||||
;
|
||||
|
||||
CREATE OR REPLACE VIEW app.v_dws_finance_daily_summary AS
|
||||
SELECT id,
|
||||
site_id,
|
||||
@@ -691,6 +795,8 @@ SELECT id,
|
||||
confirmed_income,
|
||||
cash_inflow_total,
|
||||
cash_pay_amount,
|
||||
cash_paper_amount,
|
||||
scan_pay_amount,
|
||||
groupbuy_pay_amount,
|
||||
platform_settlement_amount,
|
||||
platform_fee_amount,
|
||||
@@ -963,7 +1069,54 @@ SELECT newconv_id,
|
||||
WHERE (site_id = (current_setting('app.current_site_id'::text))::bigint);
|
||||
;
|
||||
|
||||
CREATE OR REPLACE VIEW app.v_dws_member_project_tag AS
|
||||
SELECT id,
|
||||
site_id,
|
||||
tenant_id,
|
||||
member_id,
|
||||
time_window,
|
||||
category_code,
|
||||
category_name,
|
||||
short_name,
|
||||
duration_seconds,
|
||||
total_seconds,
|
||||
percentage,
|
||||
is_tagged,
|
||||
computed_at,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM dws.dws_member_project_tag
|
||||
WHERE (site_id = (current_setting('app.current_site_id'::text))::bigint);
|
||||
;
|
||||
|
||||
CREATE OR REPLACE VIEW app.v_dws_member_spending_power_index AS
|
||||
SELECT spi_id,
|
||||
site_id,
|
||||
member_id,
|
||||
spend_30,
|
||||
spend_90,
|
||||
recharge_90,
|
||||
orders_30,
|
||||
orders_90,
|
||||
visit_days_30,
|
||||
visit_days_90,
|
||||
avg_ticket_90,
|
||||
active_weeks_90,
|
||||
daily_spend_ewma_90,
|
||||
score_level_raw,
|
||||
score_speed_raw,
|
||||
score_stability_raw,
|
||||
score_level_display,
|
||||
score_speed_display,
|
||||
score_stability_display,
|
||||
raw_score,
|
||||
display_score,
|
||||
calc_time,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM dws.dws_member_spending_power_index
|
||||
WHERE (site_id = (current_setting('app.current_site_id'::text))::bigint);
|
||||
;
|
||||
|
||||
CREATE OR REPLACE VIEW app.v_dws_member_visit_detail AS
|
||||
SELECT id,
|
||||
@@ -1182,34 +1335,3 @@ SELECT site_id,
|
||||
FROM core.dim_site s;
|
||||
;
|
||||
|
||||
|
||||
-- 2026-03-19 新增:BOARD 看板所需的 3 个 RLS 视图
|
||||
-- 迁移脚本:db/etl_feiqiu/migrations/2026-03-19_add_board_rls_views.sql
|
||||
|
||||
CREATE OR REPLACE VIEW app.v_dws_assistant_project_tag AS
|
||||
SELECT id, site_id, tenant_id, assistant_id, time_window,
|
||||
category_code, category_name, short_name,
|
||||
duration_seconds, total_seconds, percentage, is_tagged,
|
||||
computed_at, created_at, updated_at
|
||||
FROM dws.dws_assistant_project_tag
|
||||
WHERE site_id = current_setting('app.current_site_id')::bigint;
|
||||
|
||||
CREATE OR REPLACE VIEW app.v_dws_member_project_tag AS
|
||||
SELECT id, site_id, tenant_id, member_id, time_window,
|
||||
category_code, category_name, short_name,
|
||||
duration_seconds, total_seconds, percentage, is_tagged,
|
||||
computed_at, created_at, updated_at
|
||||
FROM dws.dws_member_project_tag
|
||||
WHERE site_id = current_setting('app.current_site_id')::bigint;
|
||||
|
||||
CREATE OR REPLACE VIEW app.v_dws_member_spending_power_index AS
|
||||
SELECT spi_id, site_id, member_id,
|
||||
spend_30, spend_90, recharge_90,
|
||||
orders_30, orders_90, visit_days_30, visit_days_90,
|
||||
avg_ticket_90, active_weeks_90, daily_spend_ewma_90,
|
||||
score_level_raw, score_speed_raw, score_stability_raw,
|
||||
score_level_display, score_speed_display, score_stability_display,
|
||||
raw_score, display_score,
|
||||
calc_time, created_at, updated_at
|
||||
FROM dws.dws_member_spending_power_index
|
||||
WHERE site_id = current_setting('app.current_site_id')::bigint;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- etl_feiqiu / core(跨门店标准化维度/事实)
|
||||
-- 生成日期:2026-03-15
|
||||
-- 生成日期:2026-04-05
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- etl_feiqiu / dwd(明细数据层)
|
||||
-- 生成日期:2026-03-15
|
||||
-- 生成日期:2026-04-05
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
@@ -284,7 +284,11 @@ CREATE TABLE dwd.dim_member_ex (
|
||||
scd2_start_time timestamp with time zone NOT NULL,
|
||||
scd2_end_time timestamp with time zone,
|
||||
scd2_is_current integer,
|
||||
scd2_version integer
|
||||
scd2_version integer,
|
||||
other_pay_money_sum numeric(18,2),
|
||||
last_consume_time timestamp with time zone,
|
||||
non_consume_day_num integer,
|
||||
first_consumption integer
|
||||
);
|
||||
|
||||
CREATE TABLE dwd.dim_site (
|
||||
@@ -617,7 +621,9 @@ CREATE TABLE dwd.dwd_assistant_service_log_ex (
|
||||
composite_grade_time timestamp with time zone,
|
||||
assistant_team_name text,
|
||||
operator_id bigint,
|
||||
operator_name text
|
||||
operator_name text,
|
||||
deduct_leave_seconds integer DEFAULT 0,
|
||||
order_from integer
|
||||
);
|
||||
|
||||
CREATE TABLE dwd.dwd_goods_stock_movement (
|
||||
@@ -660,7 +666,8 @@ CREATE TABLE dwd.dwd_goods_stock_summary (
|
||||
current_stock numeric(18,4),
|
||||
site_id bigint,
|
||||
tenant_id bigint,
|
||||
fetched_at timestamp with time zone NOT NULL
|
||||
fetched_at timestamp with time zone NOT NULL,
|
||||
create_time timestamp with time zone
|
||||
);
|
||||
|
||||
CREATE TABLE dwd.dwd_groupbuy_redemption (
|
||||
@@ -986,7 +993,8 @@ CREATE TABLE dwd.dwd_settlement_head_ex (
|
||||
salesman_name character varying(100),
|
||||
order_remark character varying(255),
|
||||
operator_id bigint,
|
||||
salesman_user_id bigint
|
||||
salesman_user_id bigint,
|
||||
order_from integer
|
||||
);
|
||||
|
||||
CREATE TABLE dwd.dwd_store_goods_sale (
|
||||
@@ -1045,7 +1053,10 @@ CREATE TABLE dwd.dwd_store_goods_sale_ex (
|
||||
push_money numeric(18,2),
|
||||
is_single_order integer,
|
||||
sales_type integer,
|
||||
operator_id bigint
|
||||
operator_id bigint,
|
||||
activity_amount numeric(18,2) DEFAULT 0,
|
||||
activity_id bigint DEFAULT 0,
|
||||
order_from integer
|
||||
);
|
||||
|
||||
CREATE TABLE dwd.dwd_table_fee_adjust (
|
||||
@@ -1128,7 +1139,8 @@ CREATE TABLE dwd.dwd_table_fee_log_ex (
|
||||
operator_id bigint,
|
||||
salesman_user_id bigint,
|
||||
salesman_org_id bigint,
|
||||
order_consumption_type integer
|
||||
order_consumption_type integer,
|
||||
order_from integer
|
||||
);
|
||||
|
||||
-- 约束(主键 / 唯一 / 外键)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- etl_feiqiu / dws(汇总数据层)
|
||||
-- 生成日期:2026-03-15
|
||||
-- 生成日期:2026-04-05
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
@@ -21,6 +21,9 @@ CREATE SEQUENCE IF NOT EXISTS dws.dws_assistant_order_contribution_contribution_
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_assistant_project_tag_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_assistant_recharge_commission_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_assistant_salary_calc_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_coach_area_hours_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_finance_area_daily_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_finance_board_cache_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_finance_daily_summary_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_finance_discount_detail_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_finance_expense_summary_id_seq AS bigint;
|
||||
@@ -364,6 +367,86 @@ CREATE TABLE dws.dws_assistant_salary_calc (
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE dws.dws_coach_area_hours (
|
||||
id bigint DEFAULT nextval('dws.dws_coach_area_hours_id_seq'::regclass) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
tenant_id bigint NOT NULL,
|
||||
stat_month date NOT NULL,
|
||||
assistant_id bigint NOT NULL,
|
||||
area_code character varying(20) NOT NULL,
|
||||
base_hours numeric(10,2) DEFAULT 0 NOT NULL,
|
||||
bonus_hours numeric(10,2) DEFAULT 0 NOT NULL,
|
||||
room_hours numeric(10,2) DEFAULT 0 NOT NULL,
|
||||
effective_hours numeric(10,2) DEFAULT 0 NOT NULL,
|
||||
trashed_hours numeric(10,2) DEFAULT 0 NOT NULL,
|
||||
base_service_count integer DEFAULT 0 NOT NULL,
|
||||
bonus_service_count integer DEFAULT 0 NOT NULL,
|
||||
room_service_count integer DEFAULT 0 NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE dws.dws_finance_area_daily (
|
||||
id bigint DEFAULT nextval('dws.dws_finance_area_daily_id_seq'::regclass) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
tenant_id bigint NOT NULL,
|
||||
stat_date date NOT NULL,
|
||||
area_code character varying(20) NOT NULL,
|
||||
table_fee_amount numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
goods_amount numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
assistant_pd_amount numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
assistant_cx_amount numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
gross_amount numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
discount_groupbuy numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
discount_vip numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
discount_manual numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
discount_gift_card numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
discount_rounding numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
discount_other numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
discount_total numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
confirmed_income numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
cash_pay_amount numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
cash_paper_amount numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
scan_pay_amount numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
groupbuy_pay_amount numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
recharge_cash_inflow numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
cash_inflow_total numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
cash_outflow_total numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
cash_balance_change numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
card_consume_total numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
recharge_card_consume numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
gift_card_consume numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
recharge_cash numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
first_recharge_cash numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
renewal_cash numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
order_count integer DEFAULT 0 NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE dws.dws_finance_board_cache (
|
||||
id bigint DEFAULT nextval('dws.dws_finance_board_cache_id_seq'::regclass) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
time_range character varying(20) NOT NULL,
|
||||
area_code character varying(20) NOT NULL,
|
||||
start_date date NOT NULL,
|
||||
end_date date NOT NULL,
|
||||
prev_start_date date,
|
||||
prev_end_date date,
|
||||
occurrence numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
discount numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
discount_rate numeric(8,4) DEFAULT 0 NOT NULL,
|
||||
confirmed_revenue numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
cash_in numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
cash_out numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
cash_balance numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
balance_rate numeric(8,4) DEFAULT 0 NOT NULL,
|
||||
data_fingerprint character varying(64),
|
||||
computed_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE dws.dws_finance_daily_summary (
|
||||
id bigint DEFAULT nextval('dws.dws_finance_daily_summary_id_seq'::regclass) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
@@ -406,7 +489,9 @@ CREATE TABLE dws.dws_finance_daily_summary (
|
||||
guest_order_count integer DEFAULT 0 NOT NULL,
|
||||
avg_order_amount numeric(12,2) DEFAULT 0 NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
cash_paper_amount numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
scan_pay_amount numeric(14,2) DEFAULT 0 NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE dws.dws_finance_discount_detail (
|
||||
@@ -481,14 +566,14 @@ CREATE TABLE dws.dws_finance_recharge_summary (
|
||||
total_card_balance numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
cash_card_balance numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
gift_card_balance numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
gift_liquor_balance numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
gift_table_fee_balance numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
gift_voucher_balance numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
gift_liquor_recharge numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
gift_table_fee_recharge numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
gift_voucher_recharge numeric(14,2) DEFAULT 0 NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
gift_voucher_recharge numeric(14,2) DEFAULT 0 NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE dws.dws_goods_stock_daily_summary (
|
||||
@@ -631,7 +716,13 @@ CREATE TABLE dws.dws_member_assistant_relation_index (
|
||||
ml_display numeric(4,2) DEFAULT 0 NOT NULL,
|
||||
calc_time timestamp with time zone DEFAULT now() NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
recall_created_total integer DEFAULT 0 NOT NULL,
|
||||
recall_completed_total integer DEFAULT 0 NOT NULL,
|
||||
follow_up_created_total integer DEFAULT 0 NOT NULL,
|
||||
follow_up_completed_total integer DEFAULT 0 NOT NULL,
|
||||
total_created integer DEFAULT 0 NOT NULL,
|
||||
total_completed integer DEFAULT 0 NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE dws.dws_member_consumption_summary (
|
||||
@@ -976,6 +1067,12 @@ ALTER TABLE dws.dws_assistant_project_tag ADD CONSTRAINT uk_dws_assistant_projec
|
||||
ALTER TABLE dws.dws_assistant_recharge_commission ADD CONSTRAINT dws_assistant_recharge_commission_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE dws.dws_assistant_salary_calc ADD CONSTRAINT dws_assistant_salary_calc_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE dws.dws_assistant_salary_calc ADD CONSTRAINT uk_dws_assistant_salary UNIQUE (site_id, assistant_id, salary_month, assistant_level_code);
|
||||
ALTER TABLE dws.dws_coach_area_hours ADD CONSTRAINT dws_coach_area_hours_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE dws.dws_coach_area_hours ADD CONSTRAINT dws_coach_area_hours_site_id_stat_month_assistant_id_area_c_key UNIQUE (site_id, stat_month, assistant_id, area_code);
|
||||
ALTER TABLE dws.dws_finance_area_daily ADD CONSTRAINT dws_finance_area_daily_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE dws.dws_finance_area_daily ADD CONSTRAINT dws_finance_area_daily_site_id_stat_date_area_code_key UNIQUE (site_id, stat_date, area_code);
|
||||
ALTER TABLE dws.dws_finance_board_cache ADD CONSTRAINT dws_finance_board_cache_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE dws.dws_finance_board_cache ADD CONSTRAINT dws_finance_board_cache_site_id_time_range_area_code_key UNIQUE (site_id, time_range, area_code);
|
||||
ALTER TABLE dws.dws_finance_daily_summary ADD CONSTRAINT dws_finance_daily_summary_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE dws.dws_finance_daily_summary ADD CONSTRAINT uk_dws_finance_daily UNIQUE (site_id, stat_date);
|
||||
ALTER TABLE dws.dws_finance_discount_detail ADD CONSTRAINT dws_finance_discount_detail_pkey PRIMARY KEY (id);
|
||||
@@ -1099,6 +1196,92 @@ CREATE INDEX idx_mv_finance_daily_l3 ON dws.mv_dws_finance_daily_summary_l3 USIN
|
||||
CREATE INDEX idx_mv_finance_daily_l4 ON dws.mv_dws_finance_daily_summary_l4 USING btree (site_id, stat_date);
|
||||
|
||||
-- 视图
|
||||
CREATE OR REPLACE VIEW dws.v_dws_coach_area_hours AS
|
||||
SELECT id,
|
||||
site_id,
|
||||
tenant_id,
|
||||
stat_month,
|
||||
assistant_id,
|
||||
area_code,
|
||||
base_hours,
|
||||
bonus_hours,
|
||||
room_hours,
|
||||
effective_hours,
|
||||
trashed_hours,
|
||||
base_service_count,
|
||||
bonus_service_count,
|
||||
room_service_count,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM dws.dws_coach_area_hours
|
||||
WHERE (site_id = (current_setting('app.current_site_id'::text))::bigint);
|
||||
;
|
||||
|
||||
CREATE OR REPLACE VIEW dws.v_dws_finance_area_daily AS
|
||||
SELECT id,
|
||||
site_id,
|
||||
tenant_id,
|
||||
stat_date,
|
||||
area_code,
|
||||
table_fee_amount,
|
||||
goods_amount,
|
||||
assistant_pd_amount,
|
||||
assistant_cx_amount,
|
||||
gross_amount,
|
||||
discount_groupbuy,
|
||||
discount_vip,
|
||||
discount_manual,
|
||||
discount_gift_card,
|
||||
discount_rounding,
|
||||
discount_other,
|
||||
discount_total,
|
||||
confirmed_income,
|
||||
cash_pay_amount,
|
||||
cash_paper_amount,
|
||||
scan_pay_amount,
|
||||
groupbuy_pay_amount,
|
||||
recharge_cash_inflow,
|
||||
cash_inflow_total,
|
||||
cash_outflow_total,
|
||||
cash_balance_change,
|
||||
card_consume_total,
|
||||
recharge_card_consume,
|
||||
gift_card_consume,
|
||||
recharge_cash,
|
||||
first_recharge_cash,
|
||||
renewal_cash,
|
||||
order_count,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM dws.dws_finance_area_daily
|
||||
WHERE (site_id = (current_setting('app.current_site_id'::text))::bigint);
|
||||
;
|
||||
|
||||
CREATE OR REPLACE VIEW dws.v_dws_finance_board_cache AS
|
||||
SELECT id,
|
||||
site_id,
|
||||
time_range,
|
||||
area_code,
|
||||
start_date,
|
||||
end_date,
|
||||
prev_start_date,
|
||||
prev_end_date,
|
||||
occurrence,
|
||||
discount,
|
||||
discount_rate,
|
||||
confirmed_revenue,
|
||||
cash_in,
|
||||
cash_out,
|
||||
cash_balance,
|
||||
balance_rate,
|
||||
data_fingerprint,
|
||||
computed_at,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM dws.dws_finance_board_cache
|
||||
WHERE (site_id = (current_setting('app.current_site_id'::text))::bigint);
|
||||
;
|
||||
|
||||
CREATE OR REPLACE VIEW dws.v_member_recall_priority AS
|
||||
SELECT dws_member_winback_index.site_id,
|
||||
dws_member_winback_index.tenant_id,
|
||||
@@ -1510,625 +1693,3 @@ CREATE INDEX idx_mv_finance_daily_l2 ON dws.mv_dws_finance_daily_summary_l2 USIN
|
||||
CREATE INDEX idx_mv_finance_daily_l3 ON dws.mv_dws_finance_daily_summary_l3 USING btree (site_id, stat_date);
|
||||
CREATE INDEX idx_mv_finance_daily_l4 ON dws.mv_dws_finance_daily_summary_l4 USING btree (site_id, stat_date);
|
||||
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 种子数据:DWS 配置表初始数据(绩效档位、等级定价、奖金规则、区域分类、技能映射)
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
-- DWS 配置表初始数据
|
||||
-- 版本: v4.0
|
||||
-- 创建日期: 2026-02-01
|
||||
-- 更新日期: 2026-02-21
|
||||
-- AI_CHANGELOG [2026-02-21] 取消全文注释,数据已写入 test_etl_feiqiu;
|
||||
-- 新增 2025-01-01~2026-02-28 统一提成档位(基础课18元/小时,打赏课40%);
|
||||
-- 新增 GUARANTEE 保底奖金规则(按等级:初级12000/中级16000/高级18000/星级23000);
|
||||
-- 历史分档口径截止日期调整为 2024-12-31
|
||||
-- 描述: 初始化配置表数据,包含绩效档位、等级定价、奖金规则、区域分类、技能映射
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
-- 1. cfg_performance_tier - 绩效档位配置(含历史口径)
|
||||
-- 数据来源:DWS 数据库处理需求.md
|
||||
-- 三段时间线:
|
||||
-- 2000-01-01 ~ 2024-12-31: 旧方案(6档阶梯抽成)
|
||||
-- 2025-01-01 ~ 2026-02-28: 统一提成(不分档,基础课18元/小时,打赏课40%)
|
||||
-- 2026-03-01 ~ 9999-12-31: 新方案(5档阶梯抽成)
|
||||
-- =============================================================================
|
||||
TRUNCATE TABLE dws.cfg_performance_tier RESTART IDENTITY CASCADE;
|
||||
|
||||
INSERT INTO dws.cfg_performance_tier (
|
||||
tier_code, tier_name, tier_level,
|
||||
min_hours, max_hours,
|
||||
base_deduction, bonus_deduction_ratio, vacation_days, vacation_unlimited,
|
||||
is_new_hire_tier, effective_from, effective_to, description
|
||||
) VALUES
|
||||
-- 旧方案(至2024-12-31)
|
||||
('T0', '0档-淘汰压力', 0,
|
||||
0, 100,
|
||||
28.00, 0.50, 3, FALSE,
|
||||
FALSE, '2000-01-01', '2024-12-31',
|
||||
'旧方案:H<100,专业课抽成28元/小时,打赏课抽成50%,休假3天'),
|
||||
('T1', '1档-及格档', 1,
|
||||
100, 130,
|
||||
18.00, 0.40, 4, FALSE,
|
||||
FALSE, '2000-01-01', '2024-12-31',
|
||||
'旧方案:100≤H<130,专业课抽成18元/小时,打赏课抽成40%,休假4天'),
|
||||
('T2', '2档-良好档', 2,
|
||||
130, 160,
|
||||
15.00, 0.38, 4, FALSE,
|
||||
FALSE, '2000-01-01', '2024-12-31',
|
||||
'旧方案:130≤H<160,专业课抽成15元/小时,打赏课抽成38%,休假4天'),
|
||||
('T3', '3档-优秀档', 3,
|
||||
160, 190,
|
||||
13.00, 0.35, 5, FALSE,
|
||||
FALSE, '2000-01-01', '2024-12-31',
|
||||
'旧方案:160≤H<190,专业课抽成13元/小时,打赏课抽成35%,休假5天'),
|
||||
('T4', '4档-卓越加速档', 4,
|
||||
190, 220,
|
||||
10.00, 0.33, 6, FALSE,
|
||||
FALSE, '2000-01-01', '2024-12-31',
|
||||
'旧方案:190≤H<220,专业课抽成10元/小时,打赏课抽成33%,休假6天'),
|
||||
('T5', '5档-冠军加速档', 5,
|
||||
220, NULL,
|
||||
8.00, 0.30, 0, TRUE,
|
||||
FALSE, '2000-01-01', '2024-12-31',
|
||||
'旧方案:H≥220,专业课抽成8元/小时,打赏课抽成30%,休假自由'),
|
||||
|
||||
-- 2025-01-01 ~ 2026-02-28: 统一提成(不分档,所有助教统一规则)
|
||||
-- CHANGE 2026-02-21 | 新增统一提成档位,基础课球房提成18元/小时,打赏课球房提成40%
|
||||
('T0', '统一档', 0,
|
||||
0, NULL,
|
||||
18.00, 0.40, 0, FALSE,
|
||||
FALSE, '2025-01-01', '2026-02-28',
|
||||
'2025-01-01~2026-02-28统一规则:基础课球房提成18元/小时,打赏课球房提成40%,不分档位'),
|
||||
|
||||
-- 新方案(2026-03-01起)
|
||||
('T0', '0档-淘汰压力', 0,
|
||||
0, 120,
|
||||
28.00, 0.50, 3, FALSE,
|
||||
FALSE, '2026-03-01', '9999-12-31',
|
||||
'新方案:H<120,专业课抽成28元/小时,打赏课抽成50%,休假3天'),
|
||||
('T1', '1档-及格档', 1,
|
||||
120, 150,
|
||||
18.00, 0.40, 4, FALSE,
|
||||
FALSE, '2026-03-01', '9999-12-31',
|
||||
'新方案:120≤H<150,专业课抽成18元/小时,打赏课抽成40%,休假4天'),
|
||||
('T2', '2档-良好档', 2,
|
||||
150, 180,
|
||||
13.00, 0.35, 5, FALSE,
|
||||
FALSE, '2026-03-01', '9999-12-31',
|
||||
'新方案:150≤H<180,专业课抽成13元/小时,打赏课抽成35%,休假5天'),
|
||||
('T3', '3档-优秀档', 3,
|
||||
180, 210,
|
||||
10.00, 0.30, 6, FALSE,
|
||||
FALSE, '2026-03-01', '9999-12-31',
|
||||
'新方案:180≤H<210,专业课抽成10元/小时,打赏课抽成30%,休假6天'),
|
||||
('T4', '4档-销冠竞争', 4,
|
||||
210, NULL,
|
||||
8.00, 0.25, 0, TRUE,
|
||||
FALSE, '2026-03-01', '9999-12-31',
|
||||
'新方案:H≥210,专业课抽成8元/小时,打赏课抽成25%,休假自由');
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 2. cfg_assistant_level_price - 助教等级定价
|
||||
-- 说明:
|
||||
-- - level_code 来自 dim_assistant.assistant_level
|
||||
-- - 8=助教管理, 10=初级, 20=中级, 30=高级, 40=星级
|
||||
-- - 价格为客户支付价格(对外价格),助教收入=客户支付-档位抽成
|
||||
-- - 包厢课基础课统一138元/小时(不随等级变化)
|
||||
-- =============================================================================
|
||||
TRUNCATE TABLE dws.cfg_assistant_level_price RESTART IDENTITY CASCADE;
|
||||
|
||||
INSERT INTO dws.cfg_assistant_level_price (
|
||||
level_code, level_name,
|
||||
base_course_price, bonus_course_price,
|
||||
effective_from, effective_to, description
|
||||
) VALUES
|
||||
(10, '初级',
|
||||
98.00, 190.00,
|
||||
'2000-01-01', '9999-12-31',
|
||||
'初级助教:基础课98元/时,附加课190元/时(客户支付价格)'),
|
||||
(20, '中级',
|
||||
108.00, 190.00,
|
||||
'2000-01-01', '9999-12-31',
|
||||
'中级助教:基础课108元/时,附加课190元/时(客户支付价格)'),
|
||||
(30, '高级',
|
||||
118.00, 190.00,
|
||||
'2000-01-01', '9999-12-31',
|
||||
'高级助教:基础课118元/时,附加课190元/时(客户支付价格)'),
|
||||
(40, '星级',
|
||||
138.00, 190.00,
|
||||
'2000-01-01', '9999-12-31',
|
||||
'星级助教:基础课138元/时,附加课190元/时(客户支付价格)'),
|
||||
(8, '助教管理',
|
||||
98.00, 190.00,
|
||||
'2000-01-01', '9999-12-31',
|
||||
'助教管理:不参与客户服务计费,默认按初级价格');
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 3. cfg_bonus_rules - 奖金规则配置
|
||||
-- 说明:
|
||||
-- - SPRINT: 冲刺奖金(历史口径,至2024-12-31)
|
||||
-- - GUARANTEE: 保底月薪线(2025-01-01~2026-02-28,按等级区分)
|
||||
-- * 保底规则:总课时达标 + 打赏课≥10小时 → 触发保底月薪线
|
||||
-- * 保底含义:实发 = MAX(课时收入+奖金, 保底金额),非额外奖金
|
||||
-- * rule_code 中 LV10/LV20/LV30/LV40 对应 level_code
|
||||
-- - TOP_RANK: Top3排名奖金(2026-03-01起)
|
||||
-- CHANGE 2026-02-21 | 新增 GUARANTEE 保底奖金规则
|
||||
-- =============================================================================
|
||||
TRUNCATE TABLE dws.cfg_bonus_rules RESTART IDENTITY CASCADE;
|
||||
|
||||
INSERT INTO dws.cfg_bonus_rules (
|
||||
rule_type, rule_code, rule_name,
|
||||
threshold_hours, rank_position, bonus_amount,
|
||||
is_cumulative, priority,
|
||||
effective_from, effective_to, description
|
||||
) VALUES
|
||||
-- 冲刺奖金(历史口径,至2024-12-31)
|
||||
('SPRINT', 'SPRINT_190', '冲刺奖金190',
|
||||
190.00, NULL, 300.00,
|
||||
FALSE, 1,
|
||||
'2000-01-01', '2024-12-31',
|
||||
'历史口径:业绩≥190小时,获得300元冲刺奖金(不累计)'),
|
||||
('SPRINT', 'SPRINT_220', '冲刺奖金220',
|
||||
220.00, NULL, 800.00,
|
||||
FALSE, 2,
|
||||
'2000-01-01', '2024-12-31',
|
||||
'历史口径:业绩≥220小时,获得800元冲刺奖金(覆盖190档)'),
|
||||
|
||||
-- 保底奖金(2025-01-01 ~ 2026-02-28)
|
||||
-- 按助教等级区分,需同时满足总课时和打赏课最低时数(≥10小时)
|
||||
('GUARANTEE', 'GUAR_LV10', '初级保底奖金',
|
||||
130.00, NULL, 12000.00,
|
||||
FALSE, 10,
|
||||
'2025-01-01', '2026-02-28',
|
||||
'初级保底:完成130小时课程(含≥10小时打赏课),保底月薪线12000元(实发=MAX(课时收入+奖金, 12000))'),
|
||||
('GUARANTEE', 'GUAR_LV20', '中级保底奖金',
|
||||
150.00, NULL, 16000.00,
|
||||
FALSE, 20,
|
||||
'2025-01-01', '2026-02-28',
|
||||
'中级保底:完成150小时课程(含≥10小时打赏课),保底月薪线16000元(实发=MAX(课时收入+奖金, 16000))'),
|
||||
('GUARANTEE', 'GUAR_LV30', '高级保底奖金',
|
||||
160.00, NULL, 18000.00,
|
||||
FALSE, 30,
|
||||
'2025-01-01', '2026-02-28',
|
||||
'高级保底:完成160小时课程(含≥10小时打赏课),保底月薪线18000元(实发=MAX(课时收入+奖金, 18000))'),
|
||||
('GUARANTEE', 'GUAR_LV40', '星级保底奖金',
|
||||
170.00, NULL, 23000.00,
|
||||
FALSE, 40,
|
||||
'2025-01-01', '2026-02-28',
|
||||
'星级保底:完成170小时课程(含≥10小时打赏课),保底月薪线23000元(实发=MAX(课时收入+奖金, 23000))'),
|
||||
|
||||
-- Top排名奖金(2026-03-01起)
|
||||
('TOP_RANK', 'TOP_1', 'Top1排名奖金',
|
||||
NULL, 1, 1000.00,
|
||||
FALSE, 0,
|
||||
'2026-03-01', '9999-12-31',
|
||||
'月度排名第一,获得1000元(并列都算)'),
|
||||
('TOP_RANK', 'TOP_2', 'Top2排名奖金',
|
||||
NULL, 2, 600.00,
|
||||
FALSE, 0,
|
||||
'2026-03-01', '9999-12-31',
|
||||
'月度排名第二,获得600元(并列都算)'),
|
||||
('TOP_RANK', 'TOP_3', 'Top3排名奖金',
|
||||
NULL, 3, 400.00,
|
||||
FALSE, 0,
|
||||
'2026-03-01', '9999-12-31',
|
||||
'月度排名第三,获得400元(并列都算)');
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 4. cfg_area_category - 台区分类映射(纯台桌级精确映射)
|
||||
-- 说明:
|
||||
-- - 每台桌一行精确映射,source_area_name=区域, source_table_name=台桌名
|
||||
-- - 不使用 LIKE 模糊匹配,仅 EXACT + DEFAULT 兜底
|
||||
-- - 数据来源: 用户提供的完整台桌清单(2026-03-09)
|
||||
-- =============================================================================
|
||||
TRUNCATE TABLE dws.cfg_area_category RESTART IDENTITY CASCADE;
|
||||
|
||||
INSERT INTO dws.cfg_area_category (
|
||||
source_area_name, source_table_name, category_code, category_name,
|
||||
display_name, short_name,
|
||||
match_type, match_priority, is_active, description, sort_order
|
||||
) VALUES
|
||||
-- ============ BILLIARD 🎱 中式/追分 (sort_order=10) ============
|
||||
-- A区(18台)
|
||||
('A区', 'A1', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'A区-A1', 10),
|
||||
('A区', 'A2', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'A区-A2', 10),
|
||||
('A区', 'A3', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'A区-A3', 10),
|
||||
('A区', 'A4', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'A区-A4', 10),
|
||||
('A区', 'A5', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'A区-A5', 10),
|
||||
('A区', 'A6', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'A区-A6', 10),
|
||||
('A区', 'A7', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'A区-A7', 10),
|
||||
('A区', 'A8', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'A区-A8', 10),
|
||||
('A区', 'A9', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'A区-A9', 10),
|
||||
('A区', 'A10', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'A区-A10', 10),
|
||||
('A区', 'A11', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'A区-A11', 10),
|
||||
('A区', 'A12', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'A区-A12', 10),
|
||||
('A区', 'A13', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'A区-A13', 10),
|
||||
('A区', 'A14', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'A区-A14', 10),
|
||||
('A区', 'A15', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'A区-A15', 10),
|
||||
('A区', 'A16', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'A区-A16', 10),
|
||||
('A区', 'A17', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'A区-A17', 10),
|
||||
('A区', 'A18', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'A区-A18', 10),
|
||||
-- B区(15台)
|
||||
('B区', 'B1', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'B区-B1', 10),
|
||||
('B区', 'B2', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'B区-B2', 10),
|
||||
('B区', 'B3', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'B区-B3', 10),
|
||||
('B区', 'B4', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'B区-B4', 10),
|
||||
('B区', 'B5', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'B区-B5', 10),
|
||||
('B区', 'B6', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'B区-B6', 10),
|
||||
('B区', 'B7', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'B区-B7', 10),
|
||||
('B区', 'B8', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'B区-B8', 10),
|
||||
('B区', 'B9', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'B区-B9', 10),
|
||||
('B区', 'B10', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'B区-B10', 10),
|
||||
('B区', 'B11', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'B区-B11', 10),
|
||||
('B区', 'B12', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'B区-B12', 10),
|
||||
('B区', 'B13', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'B区-B13', 10),
|
||||
('B区', 'B14', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'B区-B14', 10),
|
||||
('B区', 'B15', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'B区-B15', 10),
|
||||
-- C区(6台)
|
||||
('C区', 'C1', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'C区-C1', 10),
|
||||
('C区', 'C2', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'C区-C2', 10),
|
||||
('C区', 'C3', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'C区-C3', 10),
|
||||
('C区', 'C4', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'C区-C4', 10),
|
||||
('C区', 'C5', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'C区-C5', 10),
|
||||
('C区', 'C6', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'C区-C6', 10),
|
||||
-- VIP包厢 BILLIARD(3台)
|
||||
('VIP包厢', 'VIP1', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'VIP包厢-VIP1', 10),
|
||||
('VIP包厢', 'VIP2', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'VIP包厢-VIP2', 10),
|
||||
('VIP包厢', 'VIP3', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'VIP包厢-VIP3', 10),
|
||||
-- TV台(1台)
|
||||
('TV台', 'TV', 'BILLIARD', '🎱 中式/追分', '🎱 中式/追分', '🎱', 'EXACT', 10, TRUE, 'TV台-TV', 10),
|
||||
-- ============ SNOOKER 斯诺克 ============
|
||||
('VIP包厢', 'VIP5', 'SNOOKER', '斯诺克', '斯诺克', '斯', 'EXACT', 10, TRUE, 'VIP包厢-VIP5→斯诺克', 20),
|
||||
('斯诺克区', 'S1', 'SNOOKER', '斯诺克', '斯诺克', '斯', 'EXACT', 10, TRUE, '斯诺克区-S1', 20),
|
||||
('斯诺克区', 'S2', 'SNOOKER', '斯诺克', '斯诺克', '斯', 'EXACT', 10, TRUE, '斯诺克区-S2', 20),
|
||||
('斯诺克区', 'S3', 'SNOOKER', '斯诺克', '斯诺克', '斯', 'EXACT', 10, TRUE, '斯诺克区-S3', 20),
|
||||
('斯诺克区', 'S4', 'SNOOKER', '斯诺克', '斯诺克', '斯', 'EXACT', 10, TRUE, '斯诺克区-S4', 20),
|
||||
-- ============ MAHJONG 🀄 麻将/棋牌 ============
|
||||
('666', '董事办', 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'EXACT', 10, TRUE, '666-董事办', 30),
|
||||
('666', '666', 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'EXACT', 10, TRUE, '666-666', 30),
|
||||
('麻将房', 'M1', 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'EXACT', 10, TRUE, '麻将房-M1', 30),
|
||||
('麻将房', 'M2', 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'EXACT', 10, TRUE, '麻将房-M2', 30),
|
||||
('麻将房', 'M3', 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'EXACT', 10, TRUE, '麻将房-M3', 30),
|
||||
('麻将房', 'M4', 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'EXACT', 10, TRUE, '麻将房-M4', 30),
|
||||
('麻将房', 'M5', 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'EXACT', 10, TRUE, '麻将房-M5', 30),
|
||||
('M7', 'M7', 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'EXACT', 10, TRUE, 'M7-M7', 30),
|
||||
('M7', '大包麻将房', 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'EXACT', 10, TRUE, 'M7-大包麻将房', 30),
|
||||
('M8', 'M8', 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'EXACT', 10, TRUE, 'M8-M8', 30),
|
||||
('发财', '发财', 'MAHJONG', '🀄 麻将/棋牌', '🀄 麻将/棋牌', '🀄', 'EXACT', 10, TRUE, '发财-发财', 30),
|
||||
-- ============ KTV 🎤 团建/K歌 ============
|
||||
('K包', '常乐', 'KTV', '🎤 团建/K歌', '🎤 团建/K歌', '🎤', 'EXACT', 10, TRUE, 'K包-常乐', 40),
|
||||
('K包', '幸会(纯k)', 'KTV', '🎤 团建/K歌', '🎤 团建/K歌', '🎤', 'EXACT', 10, TRUE, 'K包-幸会(纯k)', 40),
|
||||
('K包', '虚拟188', 'KTV', '🎤 团建/K歌', '🎤 团建/K歌', '🎤', 'EXACT', 10, TRUE, 'K包-虚拟188', 40),
|
||||
('K包', '888', 'KTV', '🎤 团建/K歌', '🎤 团建/K歌', '🎤', 'EXACT', 10, TRUE, 'K包-888', 40),
|
||||
('k包活动区', '大包', 'KTV', '🎤 团建/K歌', '🎤 团建/K歌', '🎤', 'EXACT', 10, TRUE, 'k包活动区-大包', 40),
|
||||
('k包活动区', '小包', 'KTV', '🎤 团建/K歌', '🎤 团建/K歌', '🎤', 'EXACT', 10, TRUE, 'k包活动区-小包', 40),
|
||||
('幸会158', '纯k', 'KTV', '🎤 团建/K歌', '🎤 团建/K歌', '🎤', 'EXACT', 10, TRUE, '幸会158-纯k', 40),
|
||||
-- ============ SPECIAL 补时长/虚拟台 ============
|
||||
('补时长', '补时长', 'SPECIAL', '补时长', '补时长', '补', 'EXACT', 10, TRUE, '补时长-补时长', 900),
|
||||
('补时长', '补时长2', 'SPECIAL', '补时长', '补时长', '补', 'EXACT', 10, TRUE, '补时长-补时长2', 900),
|
||||
('补时长', '补时长3', 'SPECIAL', '补时长', '补时长', '补', 'EXACT', 10, TRUE, '补时长-补时长3', 900),
|
||||
('补时长', '补时长4', 'SPECIAL', '补时长', '补时长', '补', 'EXACT', 10, TRUE, '补时长-补时长4', 900),
|
||||
('补时长', '补时长5', 'SPECIAL', '补时长', '补时长', '补', 'EXACT', 10, TRUE, '补时长-补时长5', 900),
|
||||
('补时长', '补时长6', 'SPECIAL', '补时长', '补时长', '补', 'EXACT', 10, TRUE, '补时长-补时长6', 900),
|
||||
('补时长', '补时长7', 'SPECIAL', '补时长', '补时长', '补', 'EXACT', 10, TRUE, '补时长-补时长7', 900),
|
||||
('虚拟台', '虚拟台1号', 'SPECIAL', '补时长', '补时长', '补', 'EXACT', 10, TRUE, '虚拟台-虚拟台1号', 900),
|
||||
-- ============ OTHER 兜底 ============
|
||||
('DEFAULT', NULL, 'OTHER', '其他', '其他', '他', 'DEFAULT', 999, TRUE, '兜底规则:无法匹配的归入其他', 999);
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 5. cfg_skill_type - 技能→课程类型映射
|
||||
-- 说明:
|
||||
-- - 将 skill_id 映射到课程类型
|
||||
-- - 基础课/陪打: skill_id = 2791903611396869
|
||||
-- - 附加课/超休: skill_id = 2807440316432197
|
||||
-- - 避免依赖 skill_name 文本匹配
|
||||
-- =============================================================================
|
||||
TRUNCATE TABLE dws.cfg_skill_type RESTART IDENTITY CASCADE;
|
||||
|
||||
INSERT INTO dws.cfg_skill_type (
|
||||
skill_id, skill_name,
|
||||
course_type_code, course_type_name,
|
||||
is_active, description
|
||||
) VALUES
|
||||
(2791903611396869, '台球基础陪打',
|
||||
'BASE', '基础课',
|
||||
TRUE, '基础课:陪打服务,按助教等级计价'),
|
||||
(2807440316432197, '台球超休服务',
|
||||
'BONUS', '附加课',
|
||||
TRUE, '附加课:超休/激励课,固定190元/小时'),
|
||||
(2807440316432198, '包厢服务',
|
||||
'BASE', '基础课',
|
||||
TRUE, '包厢服务:归入基础课统计,统一按138元/小时计价');
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 6~8. 优惠类型/支出类型/平台类型 — 作为代码常量使用,不单独建表
|
||||
-- =============================================================================
|
||||
-- 优惠类型: GROUPBUY/VIP/GIFT_CARD/MANUAL/ROUNDING/BIG_CUSTOMER/OTHER
|
||||
-- 支出类型: RENT/UTILITY/PROPERTY/SALARY/REIMBURSE/PLATFORM_FEE/OTHER
|
||||
-- 平台类型: MEITUAN/DOUYIN/DIANPING/OTHER
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 验证数据插入
|
||||
-- =============================================================================
|
||||
DO $
|
||||
DECLARE
|
||||
v_tier_count INTEGER;
|
||||
v_price_count INTEGER;
|
||||
v_bonus_count INTEGER;
|
||||
v_area_count INTEGER;
|
||||
v_skill_count INTEGER;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO v_tier_count FROM dws.cfg_performance_tier;
|
||||
SELECT COUNT(*) INTO v_price_count FROM dws.cfg_assistant_level_price;
|
||||
SELECT COUNT(*) INTO v_bonus_count FROM dws.cfg_bonus_rules;
|
||||
SELECT COUNT(*) INTO v_area_count FROM dws.cfg_area_category;
|
||||
SELECT COUNT(*) INTO v_skill_count FROM dws.cfg_skill_type;
|
||||
|
||||
RAISE NOTICE '配置数据初始化完成:';
|
||||
RAISE NOTICE ' - cfg_performance_tier: % 条', v_tier_count;
|
||||
RAISE NOTICE ' - cfg_assistant_level_price: % 条', v_price_count;
|
||||
RAISE NOTICE ' - cfg_bonus_rules: % 条', v_bonus_count;
|
||||
RAISE NOTICE ' - cfg_area_category: % 条', v_area_count;
|
||||
RAISE NOTICE ' - cfg_skill_type: % 条', v_skill_count;
|
||||
END;
|
||||
$;
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 种子数据:指数算法参数(NCI/WBI/RS/OS/MS/ML/SPI)
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
-- 指数算法参数初始化脚本
|
||||
-- 版本: v3.0
|
||||
-- 创建日期: 2026-02-13
|
||||
-- 描述: 仅保留 RS / OS / MS / ML / NCI / WBI 指数参数(已移除 RECALL / INTIMACY)
|
||||
-- AI_CHANGELOG [2026-02-13] 移除 RECALL/INTIMACY 参数及 ML 废弃参数(source_mode/recharge_attribute_hours)
|
||||
-- =============================================================================
|
||||
|
||||
-- 清理旧版指数参数
|
||||
DELETE FROM dws.cfg_index_parameters WHERE index_type IN ('RECALL', 'INTIMACY');
|
||||
-- 清理 ML 已废弃参数
|
||||
DELETE FROM dws.cfg_index_parameters WHERE index_type = 'ML' AND param_name IN ('source_mode', 'recharge_attribute_hours');
|
||||
|
||||
INSERT INTO dws.cfg_index_parameters
|
||||
(index_type, param_name, param_value, description, effective_from)
|
||||
VALUES
|
||||
('NCI', 'active_new_penalty', 0.200000, 'active-new suppression multiplier', DATE '2026-02-06'),
|
||||
('NCI', 'active_new_recency_days', 7.000000, 'active-new recency window (days)', DATE '2026-02-06'),
|
||||
('NCI', 'active_new_visit_threshold_14d', 2.000000, 'active-new threshold in 14d visits', DATE '2026-02-06'),
|
||||
('NCI', 'amount_base_M0', 300.000000, 'spend log base M0', DATE '2026-02-06'),
|
||||
('NCI', 'balance_base_B0', 500.000000, 'balance log base B0', DATE '2026-02-06'),
|
||||
('NCI', 'compression_mode', 0.000000, 'compression mode', DATE '2026-02-06'),
|
||||
('NCI', 'enable_stop_high_balance_exception', 0.000000, 'enable high-balance STOP exception', DATE '2026-02-06'),
|
||||
('NCI', 'ewma_alpha', 0.200000, 'EWMA alpha', DATE '2026-02-06'),
|
||||
('NCI', 'h_recharge', 7.000000, 'recharge decay half-life (days)', DATE '2026-02-06'),
|
||||
('NCI', 'high_balance_threshold', 1000.000000, 'high-balance threshold', DATE '2026-02-06'),
|
||||
('NCI', 'lookback_days_recency', 60.000000, 'recency lookback window (days)', DATE '2026-02-06'),
|
||||
('NCI', 'new_days_threshold', 30.000000, 'new member days threshold', DATE '2026-02-06'),
|
||||
('NCI', 'new_recharge_max_visits', 10.000000, 'max visits for new-recharge grouping', DATE '2026-02-06'),
|
||||
('NCI', 'new_visit_threshold', 2.000000, 'new member visit threshold', DATE '2026-02-06'),
|
||||
('NCI', 'no_touch_days_new', 3.000000, 'no-touch threshold (days)', DATE '2026-02-06'),
|
||||
('NCI', 'percentile_lower', 5.000000, 'lower percentile', DATE '2026-02-06'),
|
||||
('NCI', 'percentile_upper', 95.000000, 'upper percentile', DATE '2026-02-06'),
|
||||
('NCI', 'recharge_recent_days', 14.000000, 'recent recharge window (days)', DATE '2026-02-06'),
|
||||
('NCI', 'salvage_end', 60.000000, 'salvage decay end day', DATE '2026-02-06'),
|
||||
('NCI', 'salvage_start', 30.000000, 'salvage decay start day', DATE '2026-02-06'),
|
||||
('NCI', 't2_target_days', 7.000000, 'second-visit target window (days)', DATE '2026-02-06'),
|
||||
('NCI', 'use_smoothing', 1.000000, 'enable smoothing', DATE '2026-02-06'),
|
||||
('NCI', 'value_w_bal', 0.800000, 'value weight for balance', DATE '2026-02-06'),
|
||||
('NCI', 'value_w_spend', 1.000000, 'value weight for spend', DATE '2026-02-06'),
|
||||
('NCI', 'visit_lookback_days', 180.000000, 'visit history lookback (days)', DATE '2026-02-06'),
|
||||
('NCI', 'w_need', 1.600000, 'need weight', DATE '2026-02-06'),
|
||||
('NCI', 'w_re', 0.800000, 'recharge pressure weight', DATE '2026-02-06'),
|
||||
('NCI', 'w_value', 1.000000, 'value weight', DATE '2026-02-06'),
|
||||
('NCI', 'w_welcome', 1.000000, 'welcome-stage weight', DATE '2026-02-06'),
|
||||
('NCI', 'welcome_window_days', 3.000000, 'welcome outreach window for first touch (days)', DATE '2026-02-06'),
|
||||
('WBI', 'amount_base_M0', 300.000000, 'spend log base M0', DATE '2026-02-06'),
|
||||
('WBI', 'balance_base_B0', 500.000000, 'balance log base B0', DATE '2026-02-06'),
|
||||
('WBI', 'compression_mode', 0.000000, 'compression mode', DATE '2026-02-06'),
|
||||
('WBI', 'enable_stop_high_balance_exception', 0.000000, 'enable high-balance STOP exception', DATE '2026-02-06'),
|
||||
('WBI', 'ewma_alpha', 0.200000, 'EWMA alpha', DATE '2026-02-06'),
|
||||
('WBI', 'h_recharge', 7.000000, 'recharge decay half-life (days)', DATE '2026-02-06'),
|
||||
('WBI', 'high_balance_threshold', 1000.000000, 'high-balance threshold', DATE '2026-02-06'),
|
||||
('WBI', 'lookback_days_recency', 60.000000, 'recency lookback window (days)', DATE '2026-02-06'),
|
||||
('WBI', 'new_days_threshold', 30.000000, 'new member days threshold', DATE '2026-02-06'),
|
||||
('WBI', 'new_recharge_max_visits', 10.000000, 'max visits for new-recharge grouping', DATE '2026-02-06'),
|
||||
('WBI', 'new_visit_threshold', 2.000000, 'new member visit threshold', DATE '2026-02-06'),
|
||||
('WBI', 'overdue_alpha', 2.000000, 'overdue fallback alpha', DATE '2026-02-06'),
|
||||
('WBI', 'overdue_weight_blend_min_samples', 8.000000, 'minimum samples to fully trust weighted overdue CDF', DATE '2026-02-07'),
|
||||
('WBI', 'overdue_weight_halflife_days', 30.000000, 'overdue weighted-CDF interval half-life (days)', DATE '2026-02-07'),
|
||||
('WBI', 'percentile_lower', 5.000000, 'lower percentile', DATE '2026-02-06'),
|
||||
('WBI', 'percentile_upper', 95.000000, 'upper percentile', DATE '2026-02-06'),
|
||||
('WBI', 'recency_gate_days', 14.000000, 'recency suppression gate center (days)', DATE '2026-02-06'),
|
||||
('WBI', 'recency_gate_slope_days', 3.000000, 'recency suppression slope (days)', DATE '2026-02-06'),
|
||||
('WBI', 'recency_hard_floor_days', 14.000000, 'hard floor for winback recency (days)', DATE '2026-02-06'),
|
||||
('WBI', 'recharge_recent_days', 14.000000, 'recent recharge window (days)', DATE '2026-02-06'),
|
||||
('WBI', 'use_smoothing', 1.000000, 'enable smoothing', DATE '2026-02-06'),
|
||||
('WBI', 'value_w_bal', 1.000000, 'value weight for balance', DATE '2026-02-06'),
|
||||
('WBI', 'value_w_spend', 1.000000, 'value weight for spend', DATE '2026-02-06'),
|
||||
('WBI', 'visit_lookback_days', 180.000000, 'visit history lookback (days)', DATE '2026-02-06'),
|
||||
('WBI', 'w_drop', 1.000000, 'drop weight', DATE '2026-02-06'),
|
||||
('WBI', 'w_over', 2.000000, 'overdue weight', DATE '2026-02-06'),
|
||||
('WBI', 'w_re', 0.400000, 'recharge pressure weight', DATE '2026-02-06'),
|
||||
('WBI', 'w_value', 1.200000, 'value weight', DATE '2026-02-06')
|
||||
ON CONFLICT (index_type, param_name, effective_from) DO UPDATE SET
|
||||
param_value = EXCLUDED.param_value,
|
||||
description = EXCLUDED.description,
|
||||
updated_at = NOW();
|
||||
|
||||
-- =============================================================================
|
||||
-- 关系指数(RS/OS/MS/ML)参数
|
||||
-- 生效时间:北京时间 2026-01-01(按数据库日期管理)
|
||||
-- =============================================================================
|
||||
|
||||
INSERT INTO dws.cfg_index_parameters
|
||||
(index_type, param_name, param_value, description, effective_from)
|
||||
VALUES
|
||||
-- RS(关系强度)
|
||||
('RS', 'lookback_days', 60.000000, '服务行为回溯窗口(天)', DATE '2026-01-01'),
|
||||
('RS', 'session_merge_hours', 4.000000, '会话合并阈值(小时)', DATE '2026-01-01'),
|
||||
('RS', 'incentive_weight', 1.500000, '激励课权重', DATE '2026-01-01'),
|
||||
('RS', 'halflife_session', 14.000000, '会话半衰期(天)', DATE '2026-01-01'),
|
||||
('RS', 'halflife_last', 10.000000, '最近一次服务半衰期(天)', DATE '2026-01-01'),
|
||||
('RS', 'weight_f', 1.000000, '频次项权重', DATE '2026-01-01'),
|
||||
('RS', 'weight_d', 0.700000, '时长项权重', DATE '2026-01-01'),
|
||||
('RS', 'gate_alpha', 0.600000, '最近服务门控指数', DATE '2026-01-01'),
|
||||
('RS', 'percentile_lower', 5.000000, '展示分下分位', DATE '2026-01-01'),
|
||||
('RS', 'percentile_upper', 95.000000, '展示分上分位', DATE '2026-01-01'),
|
||||
('RS', 'compression_mode', 1.000000, '压缩模式:0=none,1=log1p,2=asinh', DATE '2026-01-01'),
|
||||
('RS', 'use_smoothing', 1.000000, '是否启用分位平滑', DATE '2026-01-01'),
|
||||
('RS', 'ewma_alpha', 0.200000, 'EWMA平滑系数', DATE '2026-01-01'),
|
||||
|
||||
-- OS(归属份额)
|
||||
('OS', 'min_rs_raw_for_ownership', 0.050000, '参与归属计算的最小RS_raw', DATE '2026-01-01'),
|
||||
('OS', 'min_total_rs_raw', 0.100000, '形成稳定归属的最小sum_rs', DATE '2026-01-01'),
|
||||
('OS', 'ownership_main_threshold', 0.600000, '主责阈值', DATE '2026-01-01'),
|
||||
('OS', 'ownership_comanage_threshold', 0.350000, '共管阈值', DATE '2026-01-01'),
|
||||
('OS', 'ownership_gap_threshold', 0.150000, '主责与次席份额差阈值', DATE '2026-01-01'),
|
||||
('OS', 'eps', 0.000001, '数值稳定项', DATE '2026-01-01'),
|
||||
|
||||
-- MS(升温动量)
|
||||
('MS', 'lookback_days', 60.000000, '服务行为回溯窗口(天)', DATE '2026-01-01'),
|
||||
('MS', 'session_merge_hours', 4.000000, '会话合并阈值(小时)', DATE '2026-01-01'),
|
||||
('MS', 'incentive_weight', 1.500000, '激励课权重', DATE '2026-01-01'),
|
||||
('MS', 'halflife_short', 7.000000, '短期半衰期(天)', DATE '2026-01-01'),
|
||||
('MS', 'halflife_long', 30.000000, '长期半衰期(天)', DATE '2026-01-01'),
|
||||
('MS', 'eps', 0.000001, '数值稳定项', DATE '2026-01-01'),
|
||||
('MS', 'percentile_lower', 5.000000, '展示分下分位', DATE '2026-01-01'),
|
||||
('MS', 'percentile_upper', 95.000000, '展示分上分位', DATE '2026-01-01'),
|
||||
('MS', 'compression_mode', 1.000000, '压缩模式:0=none,1=log1p,2=asinh', DATE '2026-01-01'),
|
||||
('MS', 'use_smoothing', 1.000000, '是否启用分位平滑', DATE '2026-01-01'),
|
||||
('MS', 'ewma_alpha', 0.200000, 'EWMA平滑系数', DATE '2026-01-01'),
|
||||
|
||||
-- ML(付费关联)
|
||||
('ML', 'lookback_days', 60.000000, '充值行为回溯窗口(天)', DATE '2026-01-01'),
|
||||
('ML', 'amount_base', 500.000000, '金额压缩基准', DATE '2026-01-01'),
|
||||
('ML', 'halflife_recharge', 21.000000, '充值半衰期(天)', DATE '2026-01-01'),
|
||||
('ML', 'percentile_lower', 5.000000, '展示分下分位', DATE '2026-01-01'),
|
||||
('ML', 'percentile_upper', 95.000000, '展示分上分位', DATE '2026-01-01'),
|
||||
('ML', 'compression_mode', 1.000000, '压缩模式:0=none,1=log1p,2=asinh', DATE '2026-01-01'),
|
||||
('ML', 'use_smoothing', 1.000000, '是否启用分位平滑', DATE '2026-01-01'),
|
||||
('ML', 'ewma_alpha', 0.200000, 'EWMA平滑系数', DATE '2026-01-01')
|
||||
ON CONFLICT (index_type, param_name, effective_from) DO UPDATE SET
|
||||
param_value = EXCLUDED.param_value,
|
||||
description = EXCLUDED.description,
|
||||
updated_at = NOW();
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- SPI(消费力指数)参数
|
||||
-- 生效时间:北京时间 2026-02-23
|
||||
-- =============================================================================
|
||||
|
||||
INSERT INTO dws.cfg_index_parameters
|
||||
(index_type, param_name, param_value, description, effective_from)
|
||||
VALUES
|
||||
-- 窗口参数
|
||||
('SPI', 'spend_window_short_days', 30.000000, '短期消费窗口(天)', DATE '2026-02-23'),
|
||||
('SPI', 'spend_window_long_days', 90.000000, '长期消费窗口(天)', DATE '2026-02-23'),
|
||||
('SPI', 'ewma_alpha_daily_spend', 0.300000, '日消费 EWMA 平滑系数', DATE '2026-02-23'),
|
||||
-- 金额压缩基数(基于典型台球门店消费水平的初始默认值)
|
||||
('SPI', 'amount_base_spend_30', 500.000000, '30天消费额压缩基数', DATE '2026-02-23'),
|
||||
('SPI', 'amount_base_spend_90', 1500.000000, '90天消费额压缩基数', DATE '2026-02-23'),
|
||||
('SPI', 'amount_base_ticket_90', 200.000000, '90天客单价压缩基数', DATE '2026-02-23'),
|
||||
('SPI', 'amount_base_recharge_90', 1000.000000, '90天充值额压缩基数', DATE '2026-02-23'),
|
||||
('SPI', 'amount_base_speed_abs', 100.000000, '绝对速度压缩基数', DATE '2026-02-23'),
|
||||
('SPI', 'amount_base_ewma_90', 50.000000, '日消费EWMA压缩基数', DATE '2026-02-23'),
|
||||
-- Level 子分权重
|
||||
('SPI', 'w_level_spend_30', 0.300000, 'Level子分:30天消费权重', DATE '2026-02-23'),
|
||||
('SPI', 'w_level_spend_90', 0.350000, 'Level子分:90天消费权重', DATE '2026-02-23'),
|
||||
('SPI', 'w_level_ticket_90', 0.200000, 'Level子分:90天客单权重', DATE '2026-02-23'),
|
||||
('SPI', 'w_level_recharge_90', 0.150000, 'Level子分:90天充值权重', DATE '2026-02-23'),
|
||||
-- Speed 子分权重
|
||||
('SPI', 'w_speed_abs', 0.500000, 'Speed子分:绝对速度权重', DATE '2026-02-23'),
|
||||
('SPI', 'w_speed_rel', 0.300000, 'Speed子分:相对速度权重', DATE '2026-02-23'),
|
||||
('SPI', 'w_speed_ewma', 0.200000, 'Speed子分:EWMA速度权重', DATE '2026-02-23'),
|
||||
-- 总分权重
|
||||
('SPI', 'weight_level', 0.600000, 'SPI总分:Level子分权重', DATE '2026-02-23'),
|
||||
('SPI', 'weight_speed', 0.300000, 'SPI总分:Speed子分权重', DATE '2026-02-23'),
|
||||
('SPI', 'weight_stability', 0.100000, 'SPI总分:Stability子分权重', DATE '2026-02-23'),
|
||||
-- 稳定性参数
|
||||
('SPI', 'stability_window_days', 90.000000, '稳定性计算窗口(天)', DATE '2026-02-23'),
|
||||
('SPI', 'use_stability', 1.000000, '是否启用稳定性子分:0=关闭,1=启用', DATE '2026-02-23'),
|
||||
-- 映射与平滑
|
||||
('SPI', 'percentile_lower', 5.000000, '展示分下分位', DATE '2026-02-23'),
|
||||
('SPI', 'percentile_upper', 95.000000, '展示分上分位', DATE '2026-02-23'),
|
||||
('SPI', 'compression_mode', 1.000000, '压缩模式:0=none,1=log1p,2=asinh', DATE '2026-02-23'),
|
||||
('SPI', 'use_smoothing', 1.000000, '是否启用分位平滑', DATE '2026-02-23'),
|
||||
('SPI', 'ewma_alpha', 0.200000, 'EWMA平滑系数', DATE '2026-02-23'),
|
||||
-- 速度计算
|
||||
('SPI', 'speed_epsilon', 0.000001, '速度计算防除零小量', DATE '2026-02-23')
|
||||
ON CONFLICT (index_type, param_name, effective_from) DO UPDATE SET
|
||||
param_value = EXCLUDED.param_value,
|
||||
description = EXCLUDED.description,
|
||||
updated_at = NOW();
|
||||
|
||||
-- =============================================================================
|
||||
-- 验证
|
||||
-- =============================================================================
|
||||
DO $
|
||||
DECLARE
|
||||
rs_count INTEGER;
|
||||
os_count INTEGER;
|
||||
ms_count INTEGER;
|
||||
ml_count INTEGER;
|
||||
nci_count INTEGER;
|
||||
wbi_count INTEGER;
|
||||
spi_count INTEGER;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO rs_count
|
||||
FROM dws.cfg_index_parameters
|
||||
WHERE index_type = 'RS';
|
||||
|
||||
SELECT COUNT(*) INTO os_count
|
||||
FROM dws.cfg_index_parameters
|
||||
WHERE index_type = 'OS';
|
||||
|
||||
SELECT COUNT(*) INTO ms_count
|
||||
FROM dws.cfg_index_parameters
|
||||
WHERE index_type = 'MS';
|
||||
|
||||
SELECT COUNT(*) INTO ml_count
|
||||
FROM dws.cfg_index_parameters
|
||||
WHERE index_type = 'ML';
|
||||
|
||||
SELECT COUNT(*) INTO nci_count
|
||||
FROM dws.cfg_index_parameters
|
||||
WHERE index_type = 'NCI';
|
||||
|
||||
SELECT COUNT(*) INTO wbi_count
|
||||
FROM dws.cfg_index_parameters
|
||||
WHERE index_type = 'WBI';
|
||||
|
||||
SELECT COUNT(*) INTO spi_count
|
||||
FROM dws.cfg_index_parameters
|
||||
WHERE index_type = 'SPI';
|
||||
|
||||
RAISE NOTICE 'RS 参数数量: %', rs_count;
|
||||
RAISE NOTICE 'OS 参数数量: %', os_count;
|
||||
RAISE NOTICE 'MS 参数数量: %', ms_count;
|
||||
RAISE NOTICE 'ML 参数数量: %', ml_count;
|
||||
RAISE NOTICE '新客转化参数数量: %', nci_count;
|
||||
RAISE NOTICE '唤回指数参数数量: %', wbi_count;
|
||||
RAISE NOTICE 'SPI 消费力指数参数数量: %', spi_count;
|
||||
END $;
|
||||
|
||||
SELECT
|
||||
index_type,
|
||||
param_name,
|
||||
param_value,
|
||||
description,
|
||||
effective_from
|
||||
FROM dws.cfg_index_parameters
|
||||
ORDER BY index_type, param_name, effective_from;
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- etl_feiqiu / meta(ETL 调度元数据)
|
||||
-- 生成日期:2026-03-15
|
||||
-- 生成日期:2026-04-05
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
@@ -75,105 +75,3 @@ ALTER TABLE meta.etl_run ADD CONSTRAINT etl_run_pkey PRIMARY KEY (run_id);
|
||||
ALTER TABLE meta.etl_task ADD CONSTRAINT etl_task_pkey PRIMARY KEY (task_id);
|
||||
ALTER TABLE meta.etl_task ADD CONSTRAINT etl_task_task_code_store_id_key UNIQUE (task_code, store_id);
|
||||
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 种子数据:ODS 任务注册
|
||||
-- =============================================================================
|
||||
|
||||
WITH target_store AS (
|
||||
SELECT 2790685415443269::bigint AS store_id -- TODO: 替换为实际 store_id
|
||||
),
|
||||
task_codes AS (
|
||||
SELECT unnest(ARRAY[
|
||||
'ODS_ASSISTANT_ACCOUNT',
|
||||
'ODS_ASSISTANT_LEDGER',
|
||||
'ODS_SETTLEMENT_RECORDS',
|
||||
'ODS_TABLE_USE',
|
||||
'ODS_PAYMENT',
|
||||
'ODS_REFUND',
|
||||
'ODS_PLATFORM_COUPON',
|
||||
'ODS_MEMBER',
|
||||
'ODS_MEMBER_CARD',
|
||||
'ODS_MEMBER_BALANCE',
|
||||
'ODS_RECHARGE_SETTLE',
|
||||
'ODS_GROUP_PACKAGE',
|
||||
'ODS_GROUP_BUY_REDEMPTION',
|
||||
'ODS_INVENTORY_STOCK',
|
||||
'ODS_INVENTORY_CHANGE',
|
||||
'ODS_TABLES',
|
||||
'ODS_GOODS_CATEGORY',
|
||||
'ODS_STORE_GOODS',
|
||||
'ODS_STORE_GOODS_SALES',
|
||||
'ODS_TABLE_FEE_DISCOUNT',
|
||||
'ODS_TENANT_GOODS'
|
||||
]) AS task_code
|
||||
)
|
||||
INSERT INTO meta.etl_task (task_code, store_id, enabled)
|
||||
SELECT t.task_code, s.store_id, TRUE
|
||||
FROM task_codes t CROSS JOIN target_store s
|
||||
ON CONFLICT (task_code, store_id) DO UPDATE
|
||||
SET enabled = EXCLUDED.enabled;
|
||||
|
||||
-- =============================================================================
|
||||
-- 种子数据:调度任务注册
|
||||
-- =============================================================================
|
||||
|
||||
WITH target_store AS (
|
||||
SELECT 2790685415443269::bigint AS store_id -- TODO: 替换为实际 store_id
|
||||
),
|
||||
task_codes AS (
|
||||
SELECT unnest(ARRAY[
|
||||
'ASSISTANTS',
|
||||
'COUPON_USAGE',
|
||||
'CHECK_CUTOFF',
|
||||
'DWD_LOAD_FROM_ODS',
|
||||
'DWD_QUALITY_CHECK',
|
||||
'INIT_DWD_SCHEMA',
|
||||
'INIT_DWS_SCHEMA',
|
||||
'INIT_ODS_SCHEMA',
|
||||
'INVENTORY_CHANGE',
|
||||
'LEDGER',
|
||||
'MANUAL_INGEST',
|
||||
'MEMBERS',
|
||||
'MEMBERS_DWD',
|
||||
'ODS_JSON_ARCHIVE',
|
||||
'ORDERS',
|
||||
'PACKAGES_DEF',
|
||||
'PAYMENTS',
|
||||
'PAYMENTS_DWD',
|
||||
'PRODUCTS',
|
||||
'REFUNDS',
|
||||
'TABLE_DISCOUNT',
|
||||
'TABLES',
|
||||
'TICKET_DWD',
|
||||
'TOPUPS',
|
||||
'DWS_BUILD_ORDER_SUMMARY',
|
||||
'DWS_ASSISTANT_DAILY',
|
||||
'DWS_ASSISTANT_MONTHLY',
|
||||
'DWS_ASSISTANT_CUSTOMER',
|
||||
'DWS_ASSISTANT_SALARY',
|
||||
'DWS_ASSISTANT_FINANCE',
|
||||
'DWS_MEMBER_CONSUMPTION',
|
||||
'DWS_MEMBER_VISIT',
|
||||
'DWS_FINANCE_DAILY',
|
||||
'DWS_FINANCE_RECHARGE',
|
||||
'DWS_FINANCE_INCOME_STRUCTURE',
|
||||
'DWS_FINANCE_DISCOUNT_DETAIL',
|
||||
'DWS_GOODS_STOCK_DAILY',
|
||||
'DWS_GOODS_STOCK_WEEKLY',
|
||||
'DWS_GOODS_STOCK_MONTHLY',
|
||||
'DWS_WINBACK_INDEX',
|
||||
'DWS_NEWCONV_INDEX',
|
||||
'DWS_RELATION_INDEX',
|
||||
'DWS_ASSISTANT_PROJECT_TAG',
|
||||
'DWS_MEMBER_PROJECT_TAG',
|
||||
'DWS_ML_MANUAL_IMPORT'
|
||||
]) AS task_code
|
||||
)
|
||||
INSERT INTO meta.etl_task (task_code, store_id, enabled)
|
||||
SELECT t.task_code, s.store_id, TRUE
|
||||
FROM task_codes t CROSS JOIN target_store s
|
||||
ON CONFLICT (task_code, store_id) DO UPDATE
|
||||
SET enabled = EXCLUDED.enabled,
|
||||
updated_at = now();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- etl_feiqiu / ods(原始数据层)
|
||||
-- 生成日期:2026-03-15
|
||||
-- 生成日期:2026-04-05
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
@@ -148,7 +148,9 @@ CREATE TABLE ods.assistant_service_records (
|
||||
content_hash text NOT NULL,
|
||||
source_file text,
|
||||
source_endpoint text,
|
||||
fetched_at timestamp with time zone DEFAULT now()
|
||||
fetched_at timestamp with time zone DEFAULT now(),
|
||||
deduct_leave_seconds integer DEFAULT 0,
|
||||
order_from integer
|
||||
);
|
||||
|
||||
CREATE TABLE ods.goods_stock_movements (
|
||||
@@ -198,7 +200,8 @@ CREATE TABLE ods.goods_stock_summary (
|
||||
source_endpoint text,
|
||||
fetched_at timestamp with time zone DEFAULT now(),
|
||||
payload jsonb NOT NULL,
|
||||
siteid bigint
|
||||
siteid bigint,
|
||||
createtime timestamp without time zone
|
||||
);
|
||||
|
||||
CREATE TABLE ods.group_buy_package_details (
|
||||
@@ -394,7 +397,11 @@ CREATE TABLE ods.member_profiles (
|
||||
source_endpoint text,
|
||||
fetched_at timestamp with time zone DEFAULT now(),
|
||||
payload jsonb NOT NULL,
|
||||
birthday date
|
||||
birthday date,
|
||||
other_pay_money_sum numeric(18,2),
|
||||
last_consume_time timestamp without time zone,
|
||||
non_consume_day_num integer,
|
||||
first_consumption integer
|
||||
);
|
||||
|
||||
CREATE TABLE ods.member_stored_value_cards (
|
||||
@@ -719,7 +726,8 @@ CREATE TABLE ods.settlement_records (
|
||||
source_file text,
|
||||
source_endpoint text,
|
||||
fetched_at timestamp with time zone DEFAULT now(),
|
||||
payload jsonb NOT NULL
|
||||
payload jsonb NOT NULL,
|
||||
orderfrom integer
|
||||
);
|
||||
|
||||
CREATE TABLE ods.site_tables_master (
|
||||
@@ -939,7 +947,10 @@ CREATE TABLE ods.store_goods_sales_records (
|
||||
content_hash text NOT NULL,
|
||||
source_file text,
|
||||
source_endpoint text,
|
||||
fetched_at timestamp with time zone DEFAULT now()
|
||||
fetched_at timestamp with time zone DEFAULT now(),
|
||||
activity_amount numeric(18,2) DEFAULT 0,
|
||||
activity_id bigint DEFAULT 0,
|
||||
order_from integer
|
||||
);
|
||||
|
||||
CREATE TABLE ods.table_fee_discount_records (
|
||||
@@ -1025,7 +1036,8 @@ CREATE TABLE ods.table_fee_transactions (
|
||||
content_hash text NOT NULL,
|
||||
source_file text,
|
||||
source_endpoint text,
|
||||
fetched_at timestamp with time zone DEFAULT now()
|
||||
fetched_at timestamp with time zone DEFAULT now(),
|
||||
order_from integer
|
||||
);
|
||||
|
||||
CREATE TABLE ods.tenant_goods_master (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- FDW 跨库映射(在 zqyy_app 中执行)
|
||||
-- 生成日期:2026-03-15
|
||||
-- 生成日期:2026-04-05
|
||||
-- 来源:db/fdw/setup_fdw.sql
|
||||
-- =============================================================================
|
||||
|
||||
|
||||
86
docs/database/ddl/fdw_reverse.sql
Normal file
86
docs/database/ddl/fdw_reverse.sql
Normal file
@@ -0,0 +1,86 @@
|
||||
-- =============================================================================
|
||||
-- FDW 反向映射配置(生产环境)— 在 etl_feiqiu 数据库中执行
|
||||
-- 用途:通过 postgres_fdw 将 zqyy_app.member_retention_clue 只读映射到 etl_feiqiu,
|
||||
-- 使 ETL DWS 任务无需直接连接业务库即可读取维客线索数据。
|
||||
-- 方向:etl_feiqiu → zqyy_app(与 setup_fdw.sql 的 zqyy_app → etl_feiqiu 方向相反)
|
||||
-- 前提:zqyy_app 数据库已部署 member_retention_clue 表
|
||||
-- 测试环境版本:setup_fdw_reverse_test.sql(指向 test_zqyy_app)
|
||||
-- CHANGE 2026-02-26 | member_birthday_manual → member_retention_clue(维客线索重构)
|
||||
-- =============================================================================
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 1. 安装 postgres_fdw 扩展(如已安装则跳过)
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE EXTENSION IF NOT EXISTS postgres_fdw;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 2. 创建外部服务器(指向 zqyy_app 业务库)
|
||||
-- 部署时按实际环境替换 host / port
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE SERVER IF NOT EXISTS zqyy_app_server
|
||||
FOREIGN DATA WRAPPER postgres_fdw
|
||||
OPTIONS (host 'localhost', dbname 'zqyy_app', port '5432');
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 3. 创建用户映射(只读角色)
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE USER MAPPING IF NOT EXISTS FOR etl_user
|
||||
SERVER zqyy_app_server
|
||||
OPTIONS (user 'app_reader', password '***');
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4. 创建目标 schema(存放来自业务库的外部表)
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE SCHEMA IF NOT EXISTS fdw_app;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 5. 创建外部表:member_retention_clue
|
||||
-- 映射 zqyy_app.public.member_retention_clue,ETL 侧只读
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE FOREIGN TABLE IF NOT EXISTS fdw_app.member_retention_clue (
|
||||
id BIGINT,
|
||||
member_id BIGINT,
|
||||
category VARCHAR(20),
|
||||
summary VARCHAR(200),
|
||||
detail TEXT,
|
||||
recorded_by_assistant_id BIGINT,
|
||||
recorded_by_name VARCHAR(50),
|
||||
recorded_at TIMESTAMPTZ,
|
||||
site_id BIGINT
|
||||
) SERVER zqyy_app_server
|
||||
OPTIONS (schema_name 'public', table_name 'member_retention_clue');
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 6. 授权:允许 etl_user 访问 fdw_app schema 及其外部表
|
||||
-- -----------------------------------------------------------------------------
|
||||
GRANT USAGE ON SCHEMA fdw_app TO etl_user;
|
||||
GRANT SELECT ON ALL TABLES IN SCHEMA fdw_app TO etl_user;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA fdw_app GRANT SELECT ON TABLES TO etl_user;
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 回滚脚本(按逆序执行)
|
||||
-- =============================================================================
|
||||
-- ALTER DEFAULT PRIVILEGES IN SCHEMA fdw_app REVOKE SELECT ON TABLES FROM etl_user;
|
||||
-- REVOKE SELECT ON ALL TABLES IN SCHEMA fdw_app FROM etl_user;
|
||||
-- REVOKE USAGE ON SCHEMA fdw_app FROM etl_user;
|
||||
-- DROP FOREIGN TABLE IF EXISTS fdw_app.member_retention_clue;
|
||||
-- DROP SCHEMA IF EXISTS fdw_app CASCADE;
|
||||
-- DROP USER MAPPING IF EXISTS FOR etl_user SERVER zqyy_app_server;
|
||||
-- DROP SERVER IF EXISTS zqyy_app_server CASCADE;
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 验证 SQL
|
||||
-- =============================================================================
|
||||
-- 1. 确认外部服务器存在
|
||||
-- SELECT srvname, srvoptions FROM pg_foreign_server
|
||||
-- WHERE srvname = 'zqyy_app_server';
|
||||
|
||||
-- 2. 确认外部表列结构完整(9 列)
|
||||
-- SELECT column_name, data_type FROM information_schema.columns
|
||||
-- WHERE table_schema = 'fdw_app' AND table_name = 'member_retention_clue'
|
||||
-- ORDER BY ordinal_position;
|
||||
|
||||
-- 3. 确认外部表可读取
|
||||
-- SELECT COUNT(*) FROM fdw_app.member_retention_clue;
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- zqyy_app / auth(用户认证与权限)
|
||||
-- 生成日期:2026-03-15
|
||||
-- 生成日期:2026-04-05
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
@@ -10,12 +10,22 @@ CREATE SCHEMA IF NOT EXISTS auth;
|
||||
CREATE SEQUENCE IF NOT EXISTS auth.permissions_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS auth.roles_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS auth.site_code_mapping_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS auth.tenant_admins_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS auth.user_applications_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS auth.user_assistant_binding_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS auth.user_site_roles_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS auth.users_id_seq AS integer;
|
||||
|
||||
-- 表
|
||||
CREATE TABLE auth._archived_site_code_mapping (
|
||||
id integer DEFAULT nextval('auth.site_code_mapping_id_seq'::regclass) NOT NULL,
|
||||
site_code character varying(10) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
site_name character varying(200),
|
||||
tenant_id bigint,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE auth.permissions (
|
||||
id integer DEFAULT nextval('auth.permissions_id_seq'::regclass) NOT NULL,
|
||||
code character varying(100) NOT NULL,
|
||||
@@ -37,13 +47,19 @@ CREATE TABLE auth.roles (
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE auth.site_code_mapping (
|
||||
id integer DEFAULT nextval('auth.site_code_mapping_id_seq'::regclass) NOT NULL,
|
||||
site_code character varying(10) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
site_name character varying(200),
|
||||
tenant_id bigint,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
CREATE TABLE auth.tenant_admins (
|
||||
id bigint DEFAULT nextval('auth.tenant_admins_id_seq'::regclass) NOT NULL,
|
||||
username character varying(50) NOT NULL,
|
||||
password_hash character varying(255) NOT NULL,
|
||||
display_name character varying(100),
|
||||
tenant_id bigint NOT NULL,
|
||||
managed_site_ids _int8 NOT NULL,
|
||||
is_active boolean DEFAULT true,
|
||||
created_by bigint,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
last_login_at timestamp with time zone,
|
||||
deleted_at timestamp with time zone,
|
||||
admin_type character varying(20) DEFAULT 'tenant_admin'::character varying NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE auth.user_applications (
|
||||
@@ -68,7 +84,9 @@ CREATE TABLE auth.user_assistant_binding (
|
||||
assistant_id bigint,
|
||||
staff_id bigint,
|
||||
binding_type character varying(20) NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
is_removed boolean DEFAULT false NOT NULL,
|
||||
removed_at timestamp with time zone
|
||||
);
|
||||
|
||||
CREATE TABLE auth.user_site_roles (
|
||||
@@ -76,7 +94,9 @@ CREATE TABLE auth.user_site_roles (
|
||||
user_id integer NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
role_id integer NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
is_removed boolean DEFAULT false NOT NULL,
|
||||
removed_at timestamp with time zone
|
||||
);
|
||||
|
||||
CREATE TABLE auth.users (
|
||||
@@ -88,10 +108,17 @@ CREATE TABLE auth.users (
|
||||
phone character varying(20),
|
||||
status character varying(20) DEFAULT 'new'::character varying NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
rejection_count integer DEFAULT 0 NOT NULL,
|
||||
avatar_url character varying(500)
|
||||
);
|
||||
|
||||
-- 约束(主键 / 唯一 / 外键)
|
||||
ALTER TABLE auth._archived_site_code_mapping ADD CONSTRAINT site_code_mapping_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE auth._archived_site_code_mapping ADD CONSTRAINT site_code_mapping_site_code_key UNIQUE (site_code);
|
||||
ALTER TABLE auth._archived_site_code_mapping ADD CONSTRAINT site_code_mapping_site_id_key UNIQUE (site_id);
|
||||
ALTER TABLE auth._archived_site_code_mapping ADD CONSTRAINT uq_site_code_mapping_site_code UNIQUE (site_code);
|
||||
ALTER TABLE auth._archived_site_code_mapping ADD CONSTRAINT uq_site_code_mapping_site_id UNIQUE (site_id);
|
||||
ALTER TABLE auth.permissions ADD CONSTRAINT permissions_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE auth.permissions ADD CONSTRAINT permissions_code_key UNIQUE (code);
|
||||
ALTER TABLE auth.permissions ADD CONSTRAINT uq_permissions_code UNIQUE (code);
|
||||
@@ -103,11 +130,8 @@ ALTER TABLE auth.role_permissions ADD CONSTRAINT role_permissions_pkey PRIMARY K
|
||||
ALTER TABLE auth.roles ADD CONSTRAINT roles_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE auth.roles ADD CONSTRAINT roles_code_key UNIQUE (code);
|
||||
ALTER TABLE auth.roles ADD CONSTRAINT uq_roles_code UNIQUE (code);
|
||||
ALTER TABLE auth.site_code_mapping ADD CONSTRAINT site_code_mapping_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE auth.site_code_mapping ADD CONSTRAINT site_code_mapping_site_code_key UNIQUE (site_code);
|
||||
ALTER TABLE auth.site_code_mapping ADD CONSTRAINT site_code_mapping_site_id_key UNIQUE (site_id);
|
||||
ALTER TABLE auth.site_code_mapping ADD CONSTRAINT uq_site_code_mapping_site_code UNIQUE (site_code);
|
||||
ALTER TABLE auth.site_code_mapping ADD CONSTRAINT uq_site_code_mapping_site_id UNIQUE (site_id);
|
||||
ALTER TABLE auth.tenant_admins ADD CONSTRAINT tenant_admins_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE auth.tenant_admins ADD CONSTRAINT tenant_admins_username_key UNIQUE (username);
|
||||
ALTER TABLE auth.user_applications ADD CONSTRAINT fk_user_applications_user_id FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.user_applications ADD CONSTRAINT user_applications_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.user_applications ADD CONSTRAINT user_applications_pkey PRIMARY KEY (id);
|
||||
@@ -126,60 +150,14 @@ ALTER TABLE auth.users ADD CONSTRAINT uq_users_wx_openid UNIQUE (wx_openid);
|
||||
ALTER TABLE auth.users ADD CONSTRAINT users_wx_openid_key UNIQUE (wx_openid);
|
||||
|
||||
-- 索引
|
||||
CREATE INDEX ix_site_code_mapping_site_code ON auth.site_code_mapping USING btree (site_code);
|
||||
CREATE INDEX ix_site_code_mapping_site_code ON auth._archived_site_code_mapping USING btree (site_code);
|
||||
CREATE INDEX idx_tenant_admin_tenant ON auth.tenant_admins USING btree (tenant_id);
|
||||
CREATE INDEX idx_tenant_admins_active_not_deleted ON auth.tenant_admins USING btree (is_active) WHERE (deleted_at IS NULL);
|
||||
CREATE INDEX ix_user_applications_status ON auth.user_applications USING btree (status);
|
||||
CREATE INDEX ix_user_applications_user_id ON auth.user_applications USING btree (user_id);
|
||||
CREATE INDEX idx_user_assistant_binding_active ON auth.user_assistant_binding USING btree (user_id, site_id) WHERE (is_removed = false);
|
||||
CREATE INDEX idx_user_site_roles_active ON auth.user_site_roles USING btree (user_id, site_id) WHERE (is_removed = false);
|
||||
CREATE INDEX ix_user_site_roles_user_site ON auth.user_site_roles USING btree (user_id, site_id);
|
||||
CREATE INDEX ix_users_status ON auth.users USING btree (status);
|
||||
CREATE INDEX ix_users_wx_openid ON auth.users USING btree (wx_openid);
|
||||
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 种子数据:权限列表(5 条)
|
||||
-- =============================================================================
|
||||
|
||||
INSERT INTO auth.permissions (code, name, description) VALUES
|
||||
('view_tasks', '查看任务', '允许查看任务列表和任务详情'),
|
||||
('view_board', '查看看板', '允许查看数据看板概览'),
|
||||
('view_board_finance', '查看财务看板', '允许查看财务相关的数据看板'),
|
||||
('view_board_customer', '查看客户看板', '允许查看客户相关的数据看板'),
|
||||
('view_board_coach', '查看助教看板', '允许查看助教相关的数据看板')
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
-- =============================================================================
|
||||
-- 种子数据:默认角色(4 条)
|
||||
-- =============================================================================
|
||||
|
||||
INSERT INTO auth.roles (code, name, description) VALUES
|
||||
('coach', '助教', '球房助教,可查看任务和助教看板'),
|
||||
('staff', '员工', '球房员工,可查看任务和数据看板'),
|
||||
('site_admin', '店铺管理员', '单店管理员,可查看所有看板'),
|
||||
('tenant_admin', '租户管理员', '租户级管理员,拥有全部权限')
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
-- =============================================================================
|
||||
-- 种子数据:角色-权限映射(14 条)
|
||||
-- =============================================================================
|
||||
|
||||
INSERT INTO auth.role_permissions (role_id, permission_id)
|
||||
SELECT r.id, p.id
|
||||
FROM auth.roles r
|
||||
CROSS JOIN auth.permissions p
|
||||
WHERE (r.code, p.code) IN (
|
||||
('coach', 'view_tasks'),
|
||||
('coach', 'view_board_coach'),
|
||||
('staff', 'view_tasks'),
|
||||
('staff', 'view_board'),
|
||||
('site_admin', 'view_tasks'),
|
||||
('site_admin', 'view_board'),
|
||||
('site_admin', 'view_board_finance'),
|
||||
('site_admin', 'view_board_customer'),
|
||||
('site_admin', 'view_board_coach'),
|
||||
('tenant_admin', 'view_tasks'),
|
||||
('tenant_admin', 'view_board'),
|
||||
('tenant_admin', 'view_board_finance'),
|
||||
('tenant_admin', 'view_board_customer'),
|
||||
('tenant_admin', 'view_board_coach')
|
||||
)
|
||||
ON CONFLICT (role_id, permission_id) DO NOTHING;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- zqyy_app / biz(核心业务表(任务/备注/触发器))
|
||||
-- 生成日期:2026-03-20
|
||||
-- 生成日期:2026-04-05
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
@@ -10,9 +10,23 @@ CREATE SCHEMA IF NOT EXISTS biz;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.ai_cache_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.ai_conversations_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.ai_messages_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.ai_run_logs_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.ai_trigger_jobs_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.cfg_task_generator_params_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.coach_task_history_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.coach_task_transfer_log_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.coach_tasks_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.connectors_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.dws_assistant_task_monthly_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.excel_upload_log_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.notes_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.salary_adjustments_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.site_code_history_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.sites_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.stg_finance_expense_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.stg_platform_income_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.stg_recharge_commission_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.tenants_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS biz.trigger_jobs_id_seq AS integer;
|
||||
|
||||
-- 表
|
||||
@@ -25,7 +39,8 @@ CREATE TABLE biz.ai_cache (
|
||||
score integer,
|
||||
triggered_by character varying(100),
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
expires_at timestamp with time zone
|
||||
expires_at timestamp with time zone,
|
||||
status character varying(20) DEFAULT 'valid'::character varying
|
||||
);
|
||||
|
||||
CREATE TABLE biz.ai_conversations (
|
||||
@@ -36,12 +51,13 @@ CREATE TABLE biz.ai_conversations (
|
||||
site_id bigint NOT NULL,
|
||||
source_page character varying(100),
|
||||
source_context jsonb,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
context_type character varying(20),
|
||||
context_id character varying(50),
|
||||
title character varying(200),
|
||||
last_message text,
|
||||
last_message_at timestamp with time zone,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
session_id character varying(100)
|
||||
);
|
||||
|
||||
CREATE TABLE biz.ai_messages (
|
||||
@@ -50,10 +66,54 @@ CREATE TABLE biz.ai_messages (
|
||||
role character varying(10) NOT NULL,
|
||||
content text NOT NULL,
|
||||
tokens_used integer,
|
||||
reference_card jsonb,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
reference_card jsonb
|
||||
);
|
||||
|
||||
CREATE TABLE biz.ai_run_logs (
|
||||
id bigint DEFAULT nextval('biz.ai_run_logs_id_seq'::regclass) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
app_type character varying(30) NOT NULL,
|
||||
trigger_type character varying(20) NOT NULL,
|
||||
member_id bigint,
|
||||
request_prompt text,
|
||||
response_text text,
|
||||
tokens_used integer DEFAULT 0,
|
||||
latency_ms integer,
|
||||
status character varying(20) DEFAULT 'pending'::character varying NOT NULL,
|
||||
error_message text,
|
||||
session_id character varying(100),
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
finished_at timestamp with time zone,
|
||||
alert_status character varying(20) DEFAULT NULL::character varying
|
||||
);
|
||||
|
||||
CREATE TABLE biz.ai_trigger_jobs (
|
||||
id bigint DEFAULT nextval('biz.ai_trigger_jobs_id_seq'::regclass) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
event_type character varying(30) NOT NULL,
|
||||
connector_type character varying(30) DEFAULT 'feiqiu'::character varying,
|
||||
member_id bigint,
|
||||
payload jsonb,
|
||||
status character varying(20) DEFAULT 'pending'::character varying NOT NULL,
|
||||
is_forced boolean DEFAULT false,
|
||||
app_chain character varying(100),
|
||||
started_at timestamp with time zone,
|
||||
finished_at timestamp with time zone,
|
||||
error_message text,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE biz.cfg_task_generator_params (
|
||||
id bigint DEFAULT nextval('biz.cfg_task_generator_params_id_seq'::regclass) NOT NULL,
|
||||
site_id bigint,
|
||||
param_key character varying(64) NOT NULL,
|
||||
param_value numeric NOT NULL,
|
||||
description text,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_by bigint
|
||||
);
|
||||
|
||||
CREATE TABLE biz.coach_task_history (
|
||||
id bigint DEFAULT nextval('biz.coach_task_history_id_seq'::regclass) NOT NULL,
|
||||
task_id bigint NOT NULL,
|
||||
@@ -66,6 +126,20 @@ CREATE TABLE biz.coach_task_history (
|
||||
created_at timestamp with time zone DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE biz.coach_task_transfer_log (
|
||||
id bigint DEFAULT nextval('biz.coach_task_transfer_log_id_seq'::regclass) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
member_id bigint NOT NULL,
|
||||
from_assistant_id bigint NOT NULL,
|
||||
to_assistant_id bigint NOT NULL,
|
||||
from_task_id bigint NOT NULL,
|
||||
to_task_id bigint,
|
||||
transfer_reason text,
|
||||
guard_checks jsonb,
|
||||
transfer_score numeric,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE biz.coach_tasks (
|
||||
id bigint DEFAULT nextval('biz.coach_tasks_id_seq'::regclass) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
@@ -81,7 +155,50 @@ CREATE TABLE biz.coach_tasks (
|
||||
completed_task_type character varying(50),
|
||||
parent_task_id bigint,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now()
|
||||
updated_at timestamp with time zone DEFAULT now(),
|
||||
transfer_count integer DEFAULT 0 NOT NULL,
|
||||
transferred_from bigint,
|
||||
transferred_at timestamp with time zone
|
||||
);
|
||||
|
||||
CREATE TABLE biz.connectors (
|
||||
id integer DEFAULT nextval('biz.connectors_id_seq'::regclass) NOT NULL,
|
||||
connector_key character varying(50) NOT NULL,
|
||||
display_name character varying(100) NOT NULL,
|
||||
is_active boolean DEFAULT true NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE biz.dws_assistant_task_monthly (
|
||||
id bigint DEFAULT nextval('biz.dws_assistant_task_monthly_id_seq'::regclass) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
assistant_id bigint NOT NULL,
|
||||
stat_month date NOT NULL,
|
||||
recall_created integer DEFAULT 0 NOT NULL,
|
||||
follow_up_created integer DEFAULT 0 NOT NULL,
|
||||
relationship_created integer DEFAULT 0 NOT NULL,
|
||||
total_created integer DEFAULT 0 NOT NULL,
|
||||
recall_completed integer DEFAULT 0 NOT NULL,
|
||||
follow_up_completed integer DEFAULT 0 NOT NULL,
|
||||
total_completed integer DEFAULT 0 NOT NULL,
|
||||
abandoned_count integer DEFAULT 0 NOT NULL,
|
||||
transferred_count integer DEFAULT 0 NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE biz.excel_upload_log (
|
||||
id bigint DEFAULT nextval('biz.excel_upload_log_id_seq'::regclass) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
upload_type character varying(30) NOT NULL,
|
||||
file_name character varying(255) NOT NULL,
|
||||
uploaded_by bigint NOT NULL,
|
||||
row_count integer DEFAULT 0,
|
||||
conflict_count integer DEFAULT 0,
|
||||
resolved_count integer DEFAULT 0,
|
||||
status character varying(20) NOT NULL,
|
||||
error_detail jsonb,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
confirmed_at timestamp with time zone
|
||||
);
|
||||
|
||||
CREATE TABLE biz.notes (
|
||||
@@ -102,6 +219,89 @@ CREATE TABLE biz.notes (
|
||||
score smallint
|
||||
);
|
||||
|
||||
CREATE TABLE biz.salary_adjustments (
|
||||
id bigint DEFAULT nextval('biz.salary_adjustments_id_seq'::regclass) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
assistant_id bigint,
|
||||
assistant_name character varying(100) NOT NULL,
|
||||
assistant_number character varying(50) NOT NULL,
|
||||
salary_month character varying(7) NOT NULL,
|
||||
adjustment_type character varying(20) NOT NULL,
|
||||
amount numeric(12,2) NOT NULL,
|
||||
reason character varying(200) NOT NULL,
|
||||
upload_batch_id bigint,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
created_by bigint
|
||||
);
|
||||
|
||||
CREATE TABLE biz.site_code_history (
|
||||
id integer DEFAULT nextval('biz.site_code_history_id_seq'::regclass) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
site_code character varying(6) NOT NULL,
|
||||
is_current boolean DEFAULT false NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
retired_at timestamp with time zone
|
||||
);
|
||||
|
||||
CREATE TABLE biz.sites (
|
||||
id integer DEFAULT nextval('biz.sites_id_seq'::regclass) NOT NULL,
|
||||
tenant_id integer NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
site_name character varying(200),
|
||||
site_code character varying(6),
|
||||
site_label character varying(50),
|
||||
is_active boolean DEFAULT true NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE biz.stg_finance_expense (
|
||||
id bigint DEFAULT nextval('biz.stg_finance_expense_id_seq'::regclass) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
expense_month character varying(7) NOT NULL,
|
||||
category character varying(50) NOT NULL,
|
||||
amount numeric(12,2) NOT NULL,
|
||||
remark text,
|
||||
upload_batch_id bigint,
|
||||
synced_at timestamp with time zone,
|
||||
created_at timestamp with time zone DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE biz.stg_platform_income (
|
||||
id bigint DEFAULT nextval('biz.stg_platform_income_id_seq'::regclass) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
income_month character varying(7) NOT NULL,
|
||||
platform_name character varying(100) NOT NULL,
|
||||
amount numeric(12,2) NOT NULL,
|
||||
remark text,
|
||||
upload_batch_id bigint,
|
||||
synced_at timestamp with time zone,
|
||||
created_at timestamp with time zone DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE biz.stg_recharge_commission (
|
||||
id bigint DEFAULT nextval('biz.stg_recharge_commission_id_seq'::regclass) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
recharge_date date NOT NULL,
|
||||
member_name character varying(100) NOT NULL,
|
||||
recharge_amount numeric(12,2) NOT NULL,
|
||||
assigned_assistant character varying(100) NOT NULL,
|
||||
reward_amount numeric(12,2) NOT NULL,
|
||||
upload_batch_id bigint,
|
||||
synced_at timestamp with time zone,
|
||||
created_at timestamp with time zone DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE biz.tenants (
|
||||
id integer DEFAULT nextval('biz.tenants_id_seq'::regclass) NOT NULL,
|
||||
connector_id integer NOT NULL,
|
||||
tenant_id bigint NOT NULL,
|
||||
tenant_name character varying(200),
|
||||
is_active boolean DEFAULT true NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE biz.trigger_jobs (
|
||||
id integer DEFAULT nextval('biz.trigger_jobs_id_seq'::regclass) NOT NULL,
|
||||
job_type character varying(100) NOT NULL,
|
||||
@@ -111,7 +311,10 @@ CREATE TABLE biz.trigger_jobs (
|
||||
last_run_at timestamp with time zone,
|
||||
next_run_at timestamp with time zone,
|
||||
status character varying(20) DEFAULT 'enabled'::character varying NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now()
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
last_error text,
|
||||
description text,
|
||||
last_stats jsonb
|
||||
);
|
||||
|
||||
-- 约束(主键 / 唯一 / 外键)
|
||||
@@ -119,12 +322,42 @@ ALTER TABLE biz.ai_cache ADD CONSTRAINT ai_cache_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.ai_conversations ADD CONSTRAINT ai_conversations_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.ai_messages ADD CONSTRAINT ai_messages_conversation_id_fkey FOREIGN KEY (conversation_id) REFERENCES biz.ai_conversations(id) ON DELETE CASCADE;
|
||||
ALTER TABLE biz.ai_messages ADD CONSTRAINT ai_messages_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.ai_run_logs ADD CONSTRAINT ai_run_logs_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.ai_trigger_jobs ADD CONSTRAINT ai_trigger_jobs_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.cfg_task_generator_params ADD CONSTRAINT cfg_task_generator_params_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.cfg_task_generator_params ADD CONSTRAINT cfg_task_generator_params_site_id_param_key_key UNIQUE (site_id, param_key);
|
||||
ALTER TABLE biz.coach_task_history ADD CONSTRAINT coach_task_history_task_id_fkey FOREIGN KEY (task_id) REFERENCES biz.coach_tasks(id);
|
||||
ALTER TABLE biz.coach_task_history ADD CONSTRAINT coach_task_history_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.coach_task_transfer_log ADD CONSTRAINT coach_task_transfer_log_from_task_id_fkey FOREIGN KEY (from_task_id) REFERENCES biz.coach_tasks(id);
|
||||
ALTER TABLE biz.coach_task_transfer_log ADD CONSTRAINT coach_task_transfer_log_to_task_id_fkey FOREIGN KEY (to_task_id) REFERENCES biz.coach_tasks(id);
|
||||
ALTER TABLE biz.coach_task_transfer_log ADD CONSTRAINT coach_task_transfer_log_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.coach_tasks ADD CONSTRAINT coach_tasks_parent_task_id_fkey FOREIGN KEY (parent_task_id) REFERENCES biz.coach_tasks(id);
|
||||
ALTER TABLE biz.coach_tasks ADD CONSTRAINT fk_coach_tasks_transferred_from FOREIGN KEY (transferred_from) REFERENCES biz.coach_tasks(id);
|
||||
ALTER TABLE biz.coach_tasks ADD CONSTRAINT coach_tasks_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.connectors ADD CONSTRAINT connectors_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.connectors ADD CONSTRAINT connectors_connector_key_key UNIQUE (connector_key);
|
||||
ALTER TABLE biz.dws_assistant_task_monthly ADD CONSTRAINT dws_assistant_task_monthly_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.dws_assistant_task_monthly ADD CONSTRAINT dws_assistant_task_monthly_site_id_assistant_id_stat_month_key UNIQUE (site_id, assistant_id, stat_month);
|
||||
ALTER TABLE biz.excel_upload_log ADD CONSTRAINT excel_upload_log_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.notes ADD CONSTRAINT notes_task_id_fkey FOREIGN KEY (task_id) REFERENCES biz.coach_tasks(id);
|
||||
ALTER TABLE biz.notes ADD CONSTRAINT notes_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.salary_adjustments ADD CONSTRAINT salary_adjustments_upload_batch_id_fkey FOREIGN KEY (upload_batch_id) REFERENCES biz.excel_upload_log(id);
|
||||
ALTER TABLE biz.salary_adjustments ADD CONSTRAINT salary_adjustments_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.site_code_history ADD CONSTRAINT site_code_history_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.site_code_history ADD CONSTRAINT site_code_history_site_code_key UNIQUE (site_code);
|
||||
ALTER TABLE biz.sites ADD CONSTRAINT sites_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES biz.tenants(id);
|
||||
ALTER TABLE biz.sites ADD CONSTRAINT sites_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.sites ADD CONSTRAINT sites_site_code_key UNIQUE (site_code);
|
||||
ALTER TABLE biz.sites ADD CONSTRAINT sites_site_id_key UNIQUE (site_id);
|
||||
ALTER TABLE biz.stg_finance_expense ADD CONSTRAINT stg_finance_expense_upload_batch_id_fkey FOREIGN KEY (upload_batch_id) REFERENCES biz.excel_upload_log(id);
|
||||
ALTER TABLE biz.stg_finance_expense ADD CONSTRAINT stg_finance_expense_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.stg_platform_income ADD CONSTRAINT stg_platform_income_upload_batch_id_fkey FOREIGN KEY (upload_batch_id) REFERENCES biz.excel_upload_log(id);
|
||||
ALTER TABLE biz.stg_platform_income ADD CONSTRAINT stg_platform_income_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.stg_recharge_commission ADD CONSTRAINT stg_recharge_commission_upload_batch_id_fkey FOREIGN KEY (upload_batch_id) REFERENCES biz.excel_upload_log(id);
|
||||
ALTER TABLE biz.stg_recharge_commission ADD CONSTRAINT stg_recharge_commission_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.tenants ADD CONSTRAINT tenants_connector_id_fkey FOREIGN KEY (connector_id) REFERENCES biz.connectors(id);
|
||||
ALTER TABLE biz.tenants ADD CONSTRAINT tenants_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.tenants ADD CONSTRAINT tenants_connector_id_tenant_id_key UNIQUE (connector_id, tenant_id);
|
||||
ALTER TABLE biz.trigger_jobs ADD CONSTRAINT trigger_jobs_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE biz.trigger_jobs ADD CONSTRAINT trigger_jobs_job_name_key UNIQUE (job_name);
|
||||
|
||||
@@ -132,32 +365,26 @@ ALTER TABLE biz.trigger_jobs ADD CONSTRAINT trigger_jobs_job_name_key UNIQUE (jo
|
||||
CREATE INDEX idx_ai_cache_cleanup ON biz.ai_cache USING btree (cache_type, site_id, target_id, created_at);
|
||||
CREATE INDEX idx_ai_cache_lookup ON biz.ai_cache USING btree (cache_type, site_id, target_id, created_at DESC);
|
||||
CREATE INDEX idx_ai_conv_app_site ON biz.ai_conversations USING btree (app_id, site_id, created_at DESC);
|
||||
CREATE INDEX idx_ai_conv_user_site ON biz.ai_conversations USING btree (user_id, site_id, created_at DESC);
|
||||
CREATE INDEX idx_ai_conv_context ON biz.ai_conversations USING btree (user_id, site_id, context_type, context_id, last_message_at DESC NULLS LAST) WHERE (context_type IS NOT NULL);
|
||||
CREATE INDEX idx_ai_conv_last_msg ON biz.ai_conversations USING btree (user_id, site_id, last_message_at DESC NULLS LAST);
|
||||
CREATE INDEX idx_ai_conv_user_site ON biz.ai_conversations USING btree (user_id, site_id, created_at DESC);
|
||||
CREATE INDEX idx_ai_msg_conv ON biz.ai_messages USING btree (conversation_id, created_at);
|
||||
CREATE INDEX idx_ai_run_logs_alert ON biz.ai_run_logs USING btree (alert_status, created_at DESC) WHERE ((status)::text = ANY ((ARRAY['failed'::character varying, 'timeout'::character varying, 'circuit_open'::character varying])::text[]));
|
||||
CREATE INDEX idx_ai_run_logs_created ON biz.ai_run_logs USING btree (created_at);
|
||||
CREATE INDEX idx_ai_run_logs_created_brin ON biz.ai_run_logs USING brin (created_at) WITH (pages_per_range='32');
|
||||
CREATE INDEX idx_ai_run_logs_site_app ON biz.ai_run_logs USING btree (site_id, app_type);
|
||||
CREATE INDEX idx_ai_run_logs_status ON biz.ai_run_logs USING btree (status);
|
||||
CREATE INDEX idx_ai_trigger_jobs_dedup ON biz.ai_trigger_jobs USING btree (event_type, member_id, site_id, created_at) WHERE ((status)::text <> 'skipped_duplicate'::text);
|
||||
CREATE INDEX idx_ai_trigger_jobs_site ON biz.ai_trigger_jobs USING btree (site_id, event_type);
|
||||
CREATE INDEX idx_ai_trigger_jobs_status ON biz.ai_trigger_jobs USING btree (status);
|
||||
CREATE INDEX idx_transfer_log_member ON biz.coach_task_transfer_log USING btree (member_id, created_at DESC);
|
||||
CREATE INDEX idx_transfer_log_site_created ON biz.coach_task_transfer_log USING btree (site_id, created_at DESC);
|
||||
CREATE INDEX idx_coach_tasks_assistant_status ON biz.coach_tasks USING btree (site_id, assistant_id, status);
|
||||
CREATE UNIQUE INDEX idx_coach_tasks_site_assistant_member_type ON biz.coach_tasks USING btree (site_id, assistant_id, member_id, task_type) WHERE ((status)::text = 'active'::text);
|
||||
CREATE INDEX idx_task_monthly_assistant ON biz.dws_assistant_task_monthly USING btree (assistant_id, stat_month DESC);
|
||||
CREATE INDEX idx_task_monthly_site_month ON biz.dws_assistant_task_monthly USING btree (site_id, stat_month DESC);
|
||||
CREATE INDEX idx_excel_log_site ON biz.excel_upload_log USING btree (site_id, created_at DESC);
|
||||
CREATE INDEX idx_notes_target ON biz.notes USING btree (site_id, target_type, target_id);
|
||||
CREATE INDEX idx_salary_adj_assistant_month ON biz.salary_adjustments USING btree (assistant_id, salary_month);
|
||||
CREATE INDEX idx_salary_adj_site_month ON biz.salary_adjustments USING btree (site_id, salary_month);
|
||||
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 种子数据:触发器配置(4 条)
|
||||
-- =============================================================================
|
||||
|
||||
INSERT INTO biz.trigger_jobs (job_type, job_name, trigger_condition, trigger_config, next_run_at)
|
||||
VALUES
|
||||
('task_generator', 'task_generator', 'cron',
|
||||
'{"cron_expression": "0 7 * * *"}',
|
||||
(CURRENT_DATE + 1) + INTERVAL '7 hours'),
|
||||
('task_expiry_check', 'task_expiry_check', 'interval',
|
||||
'{"interval_seconds": 3600}',
|
||||
NOW() + INTERVAL '1 hour'),
|
||||
('recall_completion_check', 'recall_completion_check', 'event',
|
||||
'{"event_name": "etl_data_updated"}',
|
||||
NULL),
|
||||
('note_reclassify_backfill', 'note_reclassify_backfill', 'event',
|
||||
'{"event_name": "recall_completed"}',
|
||||
NULL)
|
||||
ON CONFLICT (job_name) DO NOTHING;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- zqyy_app / public(小程序业务表)
|
||||
-- 生成日期:2026-03-15
|
||||
-- 生成日期:2026-04-05
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
@@ -24,7 +24,8 @@ CREATE TABLE public.admin_users (
|
||||
site_id bigint NOT NULL,
|
||||
is_active boolean DEFAULT true,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now()
|
||||
updated_at timestamp with time zone DEFAULT now(),
|
||||
roles _text DEFAULT '{site_admin}'::text[] NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE public.approvals (
|
||||
@@ -47,7 +48,8 @@ CREATE TABLE public.member_retention_clue (
|
||||
recorded_by_name character varying(50),
|
||||
recorded_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
source character varying(20) DEFAULT 'manual'::character varying NOT NULL
|
||||
source character varying(20) DEFAULT 'manual'::character varying NOT NULL,
|
||||
is_hidden boolean DEFAULT false NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE public.permissions (
|
||||
@@ -83,7 +85,11 @@ CREATE TABLE public.scheduled_tasks (
|
||||
run_count integer DEFAULT 0,
|
||||
last_status character varying(20),
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now()
|
||||
updated_at timestamp with time zone DEFAULT now(),
|
||||
min_run_interval_value integer DEFAULT 0 NOT NULL,
|
||||
min_run_interval_unit character varying(20) DEFAULT 'minutes'::character varying NOT NULL,
|
||||
last_success_at timestamp with time zone,
|
||||
min_run_intervals jsonb DEFAULT '{}'::jsonb NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE public.task_execution_log (
|
||||
@@ -101,7 +107,8 @@ CREATE TABLE public.task_execution_log (
|
||||
error_log text,
|
||||
summary jsonb,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
schedule_id uuid
|
||||
schedule_id uuid,
|
||||
config jsonb
|
||||
);
|
||||
|
||||
CREATE TABLE public.task_queue (
|
||||
@@ -198,20 +205,3 @@ CREATE INDEX idx_user_roles_site_id ON public.user_roles USING btree (site_id);
|
||||
CREATE INDEX idx_users_mobile ON public.users USING btree (mobile);
|
||||
CREATE INDEX idx_users_site_id ON public.users USING btree (site_id);
|
||||
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 种子数据:Web 管理后台默认管理员账号
|
||||
-- 默认密码:admin123(bcrypt hash,cost=12)
|
||||
-- 生产环境部署后务必立即修改密码
|
||||
-- =============================================================================
|
||||
|
||||
INSERT INTO admin_users (username, password_hash, display_name, site_id, is_active)
|
||||
VALUES (
|
||||
'admin',
|
||||
'$2b$12$2MTWlJKL0HTgHIkv5Rmpie2pQ9PkeJu0iciLbzPEpPcA94ZakIQzq',
|
||||
'默认管理员',
|
||||
1,
|
||||
TRUE
|
||||
)
|
||||
ON CONFLICT (username) DO NOTHING;
|
||||
|
||||
Reference in New Issue
Block a user