chore: v1 整理 — 清理历史文件、DDL 合并、文档归档

- 清理 1155 个已删除的历史文件(废弃 prompt_logs、tmp、旧 ops 脚本)
- export/ 数据文件从 git 移除(已在 .gitignore)
- demo-miniprogram 从 tmp/ 移入 apps/,添加 CLAUDE.md 注解
- DDL 合并:完整 schema 定义填充到 db/*/schemas/(从 docs/database/ddl/ 复制)
- 39 个 v1 迁移脚本归档到 db/_archived/migrations_v1_merged/
- 4 个迁移变更类 BD_Manual 文档归档到 docs/database/_archived/
- .gitignore 补充 .vite/ 和 apps/*.zip
- settings.json 添加 effortLevel 默认配置
- scripts/ops/ 新增运维脚本入库

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Neo
2026-04-06 00:39:27 +08:00
parent 6f8f12314f
commit 779b2f6d52
1340 changed files with 9124 additions and 132087 deletions

View File

@@ -0,0 +1,24 @@
-- =============================================================================
-- 迁移biz.notes 新增 score 列(备注星星评分)
-- 日期2026-03-18
-- 关联 SPECrns1-task-performance-apiRNS1.1 任务与绩效接口)
-- =============================================================================
-- 新增列
ALTER TABLE biz.notes
ADD COLUMN score SMALLINT CHECK (score IS NULL OR (score >= 1 AND score <= 5));
COMMENT ON COLUMN biz.notes.score IS '备注星星评分1-5由助教在创建备注时可选填写不参与 AI 分析,仅存储展示';
-- =============================================================================
-- 回滚
-- =============================================================================
-- ALTER TABLE biz.notes DROP COLUMN IF EXISTS score;
-- =============================================================================
-- 验证
-- =============================================================================
-- SELECT column_name, data_type, is_nullable
-- FROM information_schema.columns
-- WHERE table_schema = 'biz' AND table_name = 'notes' AND column_name = 'score';
-- 预期1 行smallint, YES

View File

@@ -0,0 +1,14 @@
-- 迁移:业务库导入 BOARD 看板所需的 3 个 FDW 外部表
-- 日期2026-03-19
-- 前置ETL 库已执行 2026-03-19_add_board_rls_views.sql
-- 回滚DROP FOREIGN TABLE IF EXISTS fdw_etl.v_dws_assistant_project_tag, fdw_etl.v_dws_member_project_tag, fdw_etl.v_dws_member_spending_power_index;
-- 从 ETL 库 app schema 导入 3 个新视图为外部表
IMPORT FOREIGN SCHEMA app
LIMIT TO (
v_dws_assistant_project_tag,
v_dws_member_project_tag,
v_dws_member_spending_power_index
)
FROM SERVER etl_feiqiu_server
INTO fdw_etl;

View File

@@ -0,0 +1,42 @@
-- =============================================================================
-- 迁移member_retention_clue 新增 is_hidden 列(维客线索隐藏功能)
-- 日期2026-03-20
-- 关联 SPECtenant-admin-web
-- 需求16.1, 16.2
-- 说明:
-- 租户管理后台可隐藏线索使其不在小程序端展示,同时保留在管理后台可见。
-- 已有数据通过 DEFAULT false 保证兼容(所有现有线索默认可见)。
-- =============================================================================
ALTER TABLE public.member_retention_clue
ADD COLUMN IF NOT EXISTS is_hidden BOOLEAN NOT NULL DEFAULT false;
COMMENT ON COLUMN public.member_retention_clue.is_hidden
IS '是否隐藏true=管理后台保留但小程序不展示)';
-- =============================================================================
-- 回滚
-- =============================================================================
-- ALTER TABLE public.member_retention_clue DROP COLUMN IF EXISTS is_hidden;
-- =============================================================================
-- 验证
-- =============================================================================
-- 1) 字段存在性
-- 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
-- 2) 已有数据全部为 false
-- SELECT COUNT(*) FROM public.member_retention_clue WHERE is_hidden = true;
-- 预期0
-- 3) 列注释存在
-- SELECT col_description(
-- (SELECT oid FROM pg_class WHERE relname = 'member_retention_clue'),
-- (SELECT ordinal_position FROM information_schema.columns
-- WHERE table_name = 'member_retention_clue' AND column_name = 'is_hidden')
-- );
-- 预期:'是否隐藏true=管理后台保留但小程序不展示)'

View File

@@ -0,0 +1,217 @@
-- =============================================================================
-- 迁移NS4 租户管理后台 — 新建表
-- 日期2026-03-20
-- 关联 SPECtenant-admin-web
-- 需求15.1, 15.2, 15.3, 15.4
-- 说明:
-- 1. auth.tenant_admins — 租户管理员表(独立认证体系)
-- 2. biz.excel_upload_log — Excel 上传记录表
-- 3. biz.salary_adjustments — 助教奖罚明细表
-- 4. biz.stg_finance_expense — 财务支出暂存表
-- 5. biz.stg_platform_income — 团购收入暂存表
-- 6. biz.stg_recharge_commission — 充值业绩归属暂存表
-- 创建顺序excel_upload_log 先于 salary_adjustments 和 staging 表FK 依赖)
-- =============================================================================
-- 确保 schema 存在
CREATE SCHEMA IF NOT EXISTS auth;
CREATE SCHEMA IF NOT EXISTS biz;
-- ---------------------------------------------------------------------------
-- 1. auth.tenant_admins — 租户管理员表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.tenant_admins (
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, -- 所属租户
managed_site_ids BIGINT[] NOT NULL, -- 管辖门店 ID 列表
is_active BOOLEAN DEFAULT true, -- 账号状态(启用/禁用)
deleted_at TIMESTAMPTZ DEFAULT NULL, -- 软删除时间戳NULL=正常,非 NULL=已删除2026-03-22 新增,与 is_active 分离)
created_by BIGINT, -- 创建者Operator ID
created_at TIMESTAMPTZ DEFAULT NOW(), -- 创建时间
last_login_at TIMESTAMPTZ -- 最后登录时间
);
COMMENT ON TABLE auth.tenant_admins IS '租户管理员表NS4 独立认证体系,与小程序 auth.users 隔离)';
COMMENT ON COLUMN auth.tenant_admins.username IS '登录用户名,全局唯一';
COMMENT ON COLUMN auth.tenant_admins.password_hash IS 'bcrypt 密码哈希';
COMMENT ON COLUMN auth.tenant_admins.managed_site_ids IS '管辖门店 ID 数组,用于数据隔离';
COMMENT ON COLUMN auth.tenant_admins.tenant_id IS '所属租户 ID';
COMMENT ON COLUMN auth.tenant_admins.is_active IS '账号状态false=禁用,登录返回 403仅控制启用/禁用,与软删除无关)';
COMMENT ON COLUMN auth.tenant_admins.deleted_at IS '软删除时间戳NULL=正常,非 NULL=已删除。删除与禁用分离is_active 控制启用/禁用deleted_at 控制软删除2026-03-22';
CREATE INDEX IF NOT EXISTS idx_tenant_admin_tenant
ON auth.tenant_admins (tenant_id);
-- 部分索引加速列表和登录查询仅索引未删除记录2026-03-22 新增)
CREATE INDEX IF NOT EXISTS idx_tenant_admins_active_not_deleted
ON auth.tenant_admins (is_active)
WHERE deleted_at IS NULL;
-- ---------------------------------------------------------------------------
-- 2. biz.excel_upload_log — Excel 上传记录表(必须先于 salary_adjustments 和 staging 表)
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS biz.excel_upload_log (
id BIGSERIAL PRIMARY KEY,
site_id BIGINT NOT NULL, -- 门店 ID
upload_type VARCHAR(30) NOT NULL
CHECK (upload_type IN ('expense', 'platform_income', 'salary_adj', 'recharge_commission')),
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 (status IN ('pending', 'confirmed', 'failed')),
error_detail JSONB, -- 错误详情
created_at TIMESTAMPTZ DEFAULT NOW(), -- 上传时间
confirmed_at TIMESTAMPTZ -- 确认时间
);
COMMENT ON TABLE biz.excel_upload_log IS 'Excel 上传记录表NS4 租户管理后台)';
COMMENT ON COLUMN biz.excel_upload_log.upload_type IS '模板类型expense/platform_income/salary_adj/recharge_commission';
COMMENT ON COLUMN biz.excel_upload_log.uploaded_by IS '上传人auth.tenant_admins.id';
COMMENT ON COLUMN biz.excel_upload_log.status IS '批次状态pending=待确认, confirmed=已写入, failed=写入失败';
COMMENT ON COLUMN biz.excel_upload_log.error_detail IS '写入失败时的错误详情 JSON';
CREATE INDEX IF NOT EXISTS idx_excel_log_site
ON biz.excel_upload_log (site_id, created_at DESC);
-- ---------------------------------------------------------------------------
-- 3. biz.salary_adjustments — 助教奖罚明细表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS biz.salary_adjustments (
id BIGSERIAL PRIMARY KEY,
site_id BIGINT NOT NULL, -- 门店 ID
assistant_id BIGINT, -- 匹配到的助教 ID可空
assistant_name VARCHAR(100) NOT NULL, -- 助教姓名
assistant_number VARCHAR(50) NOT NULL, -- 助教编号
salary_month VARCHAR(7) NOT NULL, -- 月份 YYYY-MM
adjustment_type VARCHAR(20) NOT NULL
CHECK (adjustment_type IN ('deduction', 'bonus')),
amount NUMERIC(12,2) NOT NULL
CHECK (amount > 0), -- 金额(必须 > 0
reason VARCHAR(200) NOT NULL, -- 原因
upload_batch_id BIGINT REFERENCES biz.excel_upload_log(id),
created_at TIMESTAMPTZ DEFAULT NOW(), -- 创建时间
created_by BIGINT -- 上传人
);
COMMENT ON TABLE biz.salary_adjustments IS '助教奖罚明细表(通过 Excel 上传写入,直接进入业务库)';
COMMENT ON COLUMN biz.salary_adjustments.assistant_id IS '匹配到的助教 ID匹配失败时为 NULL';
COMMENT ON COLUMN biz.salary_adjustments.adjustment_type IS '类型deduction=扣款, bonus=奖金';
COMMENT ON COLUMN biz.salary_adjustments.upload_batch_id IS '关联 excel_upload_log.id';
CREATE INDEX IF NOT EXISTS idx_salary_adj_site_month
ON biz.salary_adjustments (site_id, salary_month);
CREATE INDEX IF NOT EXISTS idx_salary_adj_assistant_month
ON biz.salary_adjustments (assistant_id, salary_month);
-- ---------------------------------------------------------------------------
-- 4. biz.stg_finance_expense — 财务支出暂存表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS biz.stg_finance_expense (
id BIGSERIAL PRIMARY KEY,
site_id BIGINT NOT NULL, -- 门店 ID
expense_month VARCHAR(7) NOT NULL, -- 月份 YYYY-MM
category VARCHAR(50) NOT NULL, -- 支出类别
amount NUMERIC(12,2) NOT NULL, -- 金额
remark TEXT, -- 备注
upload_batch_id BIGINT REFERENCES biz.excel_upload_log(id),
synced_at TIMESTAMPTZ, -- ETL 同步时间NULL=未同步)
created_at TIMESTAMPTZ DEFAULT NOW() -- 创建时间
);
COMMENT ON TABLE biz.stg_finance_expense IS '财务支出暂存表Excel 上传 → ETL 同步到 DWS';
COMMENT ON COLUMN biz.stg_finance_expense.synced_at IS 'ETL 同步时间NULL 表示尚未同步';
-- ---------------------------------------------------------------------------
-- 5. biz.stg_platform_income — 团购收入暂存表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS 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 REFERENCES biz.excel_upload_log(id),
synced_at TIMESTAMPTZ, -- ETL 同步时间NULL=未同步)
created_at TIMESTAMPTZ DEFAULT NOW() -- 创建时间
);
COMMENT ON TABLE biz.stg_platform_income IS '团购收入暂存表Excel 上传 → ETL 同步到 DWS';
COMMENT ON COLUMN biz.stg_platform_income.synced_at IS 'ETL 同步时间NULL 表示尚未同步';
-- ---------------------------------------------------------------------------
-- 6. biz.stg_recharge_commission — 充值业绩归属暂存表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS 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 REFERENCES biz.excel_upload_log(id),
synced_at TIMESTAMPTZ, -- ETL 同步时间NULL=未同步)
created_at TIMESTAMPTZ DEFAULT NOW() -- 创建时间
);
COMMENT ON TABLE biz.stg_recharge_commission IS '充值业绩归属暂存表Excel 上传 → ETL 同步到 DWS';
COMMENT ON COLUMN biz.stg_recharge_commission.synced_at IS 'ETL 同步时间NULL 表示尚未同步';
-- =============================================================================
-- 回滚
-- =============================================================================
-- DROP TABLE IF EXISTS biz.stg_recharge_commission;
-- DROP TABLE IF EXISTS biz.stg_platform_income;
-- DROP TABLE IF EXISTS biz.stg_finance_expense;
-- DROP TABLE IF EXISTS biz.salary_adjustments;
-- DROP TABLE IF EXISTS biz.excel_upload_log;
-- DROP TABLE IF EXISTS auth.tenant_admins;
-- =============================================================================
-- 验证
-- =============================================================================
-- 1) auth.tenant_admins 表存在性
-- SELECT column_name, data_type, is_nullable
-- FROM information_schema.columns
-- WHERE table_schema = 'auth' AND table_name = 'tenant_admins'
-- ORDER BY ordinal_position;
-- 预期11 行(含 2026-03-22 新增的 deleted_at
-- 2) biz.excel_upload_log 表存在性
-- SELECT column_name, data_type
-- FROM information_schema.columns
-- WHERE table_schema = 'biz' AND table_name = 'excel_upload_log'
-- ORDER BY ordinal_position;
-- 预期12 行
-- 3) biz.salary_adjustments FK 约束
-- SELECT conname, pg_get_constraintdef(oid)
-- FROM pg_constraint
-- WHERE conrelid = 'biz.salary_adjustments'::regclass AND contype = 'f';
-- 预期1 行upload_batch_id → excel_upload_log
-- 4) 索引存在性
-- SELECT indexname FROM pg_indexes
-- WHERE schemaname IN ('auth', 'biz')
-- AND indexname IN (
-- 'idx_tenant_admin_tenant',
-- 'idx_excel_log_site',
-- 'idx_salary_adj_site_month',
-- 'idx_salary_adj_assistant_month'
-- );
-- 预期4 行
-- 5) staging 表存在性
-- SELECT table_schema, table_name
-- FROM information_schema.tables
-- WHERE table_schema = 'biz'
-- AND table_name IN ('stg_finance_expense', 'stg_platform_income', 'stg_recharge_commission');
-- 预期3 行

View File

@@ -0,0 +1,89 @@
-- =============================================================================
-- 迁移:扩展 biz.ai_conversations 和 biz.ai_messages 支持 CHAT 模块
-- 日期2026-03-20
-- 关联 SPECrns1-chat-integrationRNS1.4 CHAT 对齐与联调收尾)
-- 需求R2.3, R3.8, R3.10, R4.3, R7.5
-- 说明:
-- 1. ai_conversations 新增 context_type/context_id多入口对话复用
-- title/last_message/last_message_at历史列表展示与排序
-- 2. ai_messages 新增 reference_card引用卡片 JSON
-- 3. 两个索引:上下文对话查找 + 历史列表排序
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 1. 扩展 ai_conversations新增 5 个字段
-- ---------------------------------------------------------------------------
ALTER TABLE biz.ai_conversations
ADD COLUMN IF NOT EXISTS context_type varchar(20),
ADD COLUMN IF NOT EXISTS context_id varchar(50),
ADD COLUMN IF NOT EXISTS title varchar(200),
ADD COLUMN IF NOT EXISTS last_message text,
ADD COLUMN IF NOT EXISTS last_message_at timestamptz;
COMMENT ON COLUMN biz.ai_conversations.context_type
IS '对话关联上下文类型task任务/ customer客户/ coach助教/ general通用';
COMMENT ON COLUMN biz.ai_conversations.context_id
IS '关联上下文 IDtask 入口为 taskIdcustomer 入口为 customerIdcoach 入口为 coachIdgeneral 为 NULL';
COMMENT ON COLUMN biz.ai_conversations.title
IS '对话标题:自定义 > 上下文名称 > 首条消息前20字';
COMMENT ON COLUMN biz.ai_conversations.last_message
IS '最后一条消息内容摘要截断至100字';
COMMENT ON COLUMN biz.ai_conversations.last_message_at
IS '最后消息时间,用于历史列表排序和对话复用时限判断';
-- ---------------------------------------------------------------------------
-- 2. 扩展 ai_messages新增 reference_card 字段
-- ---------------------------------------------------------------------------
ALTER TABLE biz.ai_messages
ADD COLUMN IF NOT EXISTS reference_card jsonb;
COMMENT ON COLUMN biz.ai_messages.reference_card
IS 'referenceCard JSON{type, title, summary, data}';
-- ---------------------------------------------------------------------------
-- 3. 索引:上下文对话查找(按 context_type + context_id 查找可复用对话)
-- ---------------------------------------------------------------------------
CREATE INDEX IF NOT EXISTS idx_ai_conv_context
ON biz.ai_conversations (user_id, site_id, context_type, context_id, last_message_at DESC NULLS LAST)
WHERE context_type IS NOT NULL;
-- ---------------------------------------------------------------------------
-- 4. 索引历史列表排序优化CHAT-1: 按 last_message_at 倒序)
-- ---------------------------------------------------------------------------
CREATE INDEX IF NOT EXISTS idx_ai_conv_last_msg
ON biz.ai_conversations (user_id, site_id, last_message_at DESC NULLS LAST);
-- =============================================================================
-- 回滚
-- =============================================================================
-- DROP INDEX IF EXISTS biz.idx_ai_conv_context;
-- DROP INDEX IF EXISTS biz.idx_ai_conv_last_msg;
-- ALTER TABLE biz.ai_messages DROP COLUMN IF EXISTS reference_card;
-- ALTER TABLE biz.ai_conversations DROP COLUMN IF EXISTS last_message_at;
-- ALTER TABLE biz.ai_conversations DROP COLUMN IF EXISTS last_message;
-- ALTER TABLE biz.ai_conversations DROP COLUMN IF EXISTS title;
-- ALTER TABLE biz.ai_conversations DROP COLUMN IF EXISTS context_id;
-- ALTER TABLE biz.ai_conversations DROP COLUMN IF EXISTS context_type;
-- =============================================================================
-- 验证
-- =============================================================================
-- 1) ai_conversations 新字段存在性
-- SELECT column_name, data_type, character_maximum_length
-- FROM information_schema.columns
-- WHERE table_schema = 'biz' AND table_name = 'ai_conversations'
-- AND column_name IN ('context_type','context_id','title','last_message','last_message_at');
-- 预期5 行
-- 2) ai_messages 新字段存在性
-- SELECT column_name, data_type
-- FROM information_schema.columns
-- WHERE table_schema = 'biz' AND table_name = 'ai_messages'
-- AND column_name = 'reference_card';
-- 预期1 行jsonb
-- 3) 索引存在性
-- SELECT indexname FROM pg_indexes
-- WHERE schemaname = 'biz' AND tablename = 'ai_conversations'
-- AND indexname IN ('idx_ai_conv_context','idx_ai_conv_last_msg');
-- 预期2 行

View File

@@ -0,0 +1,74 @@
-- =============================================================================
-- 迁移:重建 RLS 视图 app.v_dws_finance_recharge_summary新增赠送卡细分字段
-- 日期2026-03-20
-- 关联 SPECgift-card-breakdown赠送卡矩阵细分数据
-- 前置ETL 库已执行 2026-03-20_add_gift_breakdown_fields.sqlDWS 表新增 6 列)
-- =============================================================================
CREATE OR REPLACE VIEW app.v_dws_finance_recharge_summary AS
SELECT id,
site_id,
tenant_id,
stat_date,
recharge_count,
recharge_total,
recharge_cash,
recharge_gift,
first_recharge_count,
first_recharge_cash,
first_recharge_gift,
first_recharge_total,
renewal_count,
renewal_cash,
renewal_gift,
renewal_total,
recharge_member_count,
new_member_count,
total_card_balance,
cash_card_balance,
gift_card_balance,
-- 赠送卡细分字段6 个新增)
gift_liquor_balance,
gift_table_fee_balance,
gift_voucher_balance,
gift_liquor_recharge,
gift_table_fee_recharge,
gift_voucher_recharge,
created_at,
updated_at
FROM dws.dws_finance_recharge_summary
WHERE site_id = (current_setting('app.current_site_id'::text))::bigint;
-- =============================================================================
-- 回滚(恢复为不含细分字段的原始视图)
-- =============================================================================
-- CREATE OR REPLACE VIEW app.v_dws_finance_recharge_summary AS
-- SELECT id, site_id, tenant_id, stat_date,
-- recharge_count, recharge_total, recharge_cash, recharge_gift,
-- first_recharge_count, first_recharge_cash, first_recharge_gift, first_recharge_total,
-- renewal_count, renewal_cash, renewal_gift, renewal_total,
-- recharge_member_count, new_member_count,
-- total_card_balance, cash_card_balance, gift_card_balance,
-- created_at, updated_at
-- FROM dws.dws_finance_recharge_summary
-- WHERE site_id = (current_setting('app.current_site_id'::text))::bigint;
-- =============================================================================
-- 验证
-- =============================================================================
-- 1. 确认视图包含新字段
-- SELECT column_name FROM information_schema.columns
-- WHERE table_schema = 'app' AND table_name = 'v_dws_finance_recharge_summary'
-- AND column_name LIKE 'gift_%'
-- ORDER BY ordinal_position;
-- 预期gift_card_balance + 6 个新字段 = 7 行
-- 2. 确认 RLS 过滤生效(需先 SET app.current_site_id
-- SET app.current_site_id = '12345';
-- SELECT COUNT(*) FROM app.v_dws_finance_recharge_summary;
-- 3. 确认新字段可查询且有默认值
-- SET app.current_site_id = '12345';
-- SELECT gift_liquor_balance, gift_table_fee_balance, gift_voucher_balance,
-- gift_liquor_recharge, gift_table_fee_recharge, gift_voucher_recharge
-- FROM app.v_dws_finance_recharge_summary LIMIT 1;

View File

@@ -0,0 +1,58 @@
-- =============================================================================
-- 迁移:刷新 FDW 外部表 v_dws_finance_recharge_summary同步赠送卡细分字段
-- 日期2026-03-20
-- 关联 SPECgift-card-breakdown赠送卡矩阵细分数据
-- 前置:
-- 1. ETL 库已执行 DDL 迁移DWS 表新增 6 列)
-- 2. ETL 库已执行 RLS 视图重建app.v_dws_finance_recharge_summary 包含 6 个新字段)
-- 幂等策略:先 DROP 外部表再 IMPORT FOREIGN SCHEMA支持重复执行不报错
-- =============================================================================
-- -----------------------------------------------------------------------------
-- 1. 删除旧的外部表IF EXISTS 保证幂等)
-- -----------------------------------------------------------------------------
DROP FOREIGN TABLE IF EXISTS fdw_etl.v_dws_finance_recharge_summary;
-- -----------------------------------------------------------------------------
-- 2. 重新导入外部表(从 ETL 库 app schema 获取最新列定义)
-- LIMIT TO 仅导入目标视图,避免影响其他外部表
-- -----------------------------------------------------------------------------
IMPORT FOREIGN SCHEMA app
LIMIT TO (v_dws_finance_recharge_summary)
FROM SERVER etl_feiqiu_server
INTO fdw_etl;
-- =============================================================================
-- 回滚(恢复为旧版外部表,需重新导入)
-- =============================================================================
-- DROP FOREIGN TABLE IF EXISTS fdw_etl.v_dws_finance_recharge_summary;
-- IMPORT FOREIGN SCHEMA app
-- LIMIT TO (v_dws_finance_recharge_summary)
-- FROM SERVER etl_feiqiu_server
-- INTO fdw_etl;
-- 注意:回滚前需先将 ETL 库的 RLS 视图恢复为不含细分字段的版本
-- =============================================================================
-- 验证 SQL
-- =============================================================================
-- 1. 确认外部表存在且包含新字段(应有 6 个 gift_* 细分字段)
-- SELECT column_name, data_type
-- FROM information_schema.columns
-- WHERE table_schema = 'fdw_etl'
-- AND table_name = 'v_dws_finance_recharge_summary'
-- AND column_name LIKE 'gift_%'
-- ORDER BY ordinal_position;
-- 预期gift_card_balance + 6 个新字段 = 7 行
-- 2. 确认外部表可查询
-- SELECT gift_liquor_balance, gift_table_fee_balance, gift_voucher_balance,
-- gift_liquor_recharge, gift_table_fee_recharge, gift_voucher_recharge
-- FROM fdw_etl.v_dws_finance_recharge_summary
-- LIMIT 1;
-- 3. 确认外部表总列数正确(原有列 + 6 个新列)
-- SELECT COUNT(*) AS column_count
-- FROM information_schema.columns
-- WHERE table_schema = 'fdw_etl'
-- AND table_name = 'v_dws_finance_recharge_summary';

View File

@@ -0,0 +1,9 @@
-- 2026-03-22 | 为 task_execution_log 添加 config JSONB 列
-- 用途:存储完整 TaskConfig JSONrerun 时还原原始参数processing_mode、lookback_hours 等)
-- 旧记录 config 为 NULLrerun 时回退到默认配置
ALTER TABLE task_execution_log
ADD COLUMN IF NOT EXISTS config JSONB DEFAULT NULL;
COMMENT ON COLUMN task_execution_log.config
IS '完整 TaskConfig JSON用于 rerun 时还原原始参数';

View File

@@ -0,0 +1,173 @@
-- =============================================================================
-- 迁移NS4.1 注册体系 — 连接器/租户/店铺/简写ID历史 四张新表
-- 日期2026-03-22
-- 关联 SPECadmin-web-enhancement
-- 需求A1.1, A1.2, A1.3, A1.4, A1.5
-- 说明:
-- 1. biz.connectors — 连接器注册表(上游 SaaS 系统)
-- 2. biz.tenants — 租户注册表(连接器下的租户)
-- 3. biz.sites — 店铺注册表(合并原 auth.site_code_mapping
-- 4. biz.site_code_history — 简写ID 变更历史
-- 创建顺序connectors → tenants → sites → site_code_historyFK 依赖链)
-- 种子数据:飞球连接器 + 朗朗桌球租户
-- 数据迁移auth.site_code_mapping → biz.sites + biz.site_code_history
-- =============================================================================
-- 确保 biz schema 存在
CREATE SCHEMA IF NOT EXISTS biz;
-- ---------------------------------------------------------------------------
-- 1. biz.connectors — 连接器注册表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS biz.connectors (
id SERIAL PRIMARY KEY,
connector_key VARCHAR(50) NOT NULL UNIQUE, -- 连接器标识(如 feiqiu
display_name VARCHAR(100) NOT NULL, -- 显示名称
is_active BOOLEAN NOT NULL DEFAULT true, -- 是否启用
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -- 创建时间
);
COMMENT ON TABLE biz.connectors IS '连接器注册表:记录本项目接入的上游 SaaS 系统';
-- ---------------------------------------------------------------------------
-- 2. biz.tenants — 租户注册表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS biz.tenants (
id SERIAL PRIMARY KEY,
connector_id INTEGER NOT NULL REFERENCES 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(), -- 更新时间
UNIQUE (connector_id, tenant_id)
);
COMMENT ON TABLE biz.tenants IS '租户注册表连接器下的租户tenant_id 来自上游系统';
-- ---------------------------------------------------------------------------
-- 3. biz.sites — 店铺注册表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS biz.sites (
id SERIAL PRIMARY KEY,
tenant_id INTEGER NOT NULL REFERENCES biz.tenants(id), -- 所属租户biz.tenants.id
site_id BIGINT NOT NULL UNIQUE, -- 上游系统店铺 ID
site_name VARCHAR(200), -- 店铺名称
site_code VARCHAR(6) UNIQUE, -- 当前生效的简写ID6位3+3格式
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() -- 更新时间
);
COMMENT ON TABLE biz.sites IS '店铺注册表:合并原 auth.site_code_mapping增加租户关联和简写ID管理';
COMMENT ON COLUMN biz.sites.site_code IS '当前生效的简写ID6位字符3+3格式全局唯一';
-- ---------------------------------------------------------------------------
-- 4. biz.site_code_history — 简写ID 变更历史
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS biz.site_code_history (
id SERIAL PRIMARY KEY,
site_id BIGINT NOT NULL, -- 店铺 IDbiz.sites.site_id
site_code VARCHAR(6) NOT NULL UNIQUE, -- 简写ID全局唯一含历史
is_current BOOLEAN NOT NULL DEFAULT false, -- 是否当前生效
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 创建时间
retired_at TIMESTAMPTZ -- 退役时间NULL=未退役)
);
COMMENT ON TABLE biz.site_code_history IS '简写ID变更历史增量记录所有使用过的简写ID全局 UNIQUE 保护已提交申请';
COMMENT ON COLUMN biz.site_code_history.is_current IS 'true=当前生效的简写ID每个 site_id 最多一条 is_current=true';
-- =============================================================================
-- 种子数据
-- =============================================================================
-- 连接器:飞球
INSERT INTO biz.connectors (connector_key, display_name)
VALUES ('feiqiu', '飞球');
-- 租户:朗朗桌球
INSERT INTO biz.tenants (connector_id, tenant_id, tenant_name)
VALUES (1, 2790683160709957, '朗朗桌球');
-- =============================================================================
-- 数据迁移auth.site_code_mapping → biz.sites
-- =============================================================================
-- 从 auth.site_code_mapping 迁移真实数据到 biz.sites
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;
-- 为已有 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;
-- =============================================================================
-- === ROLLBACK ===
-- 按依赖逆序删除
-- DROP TABLE IF EXISTS biz.site_code_history;
-- DROP TABLE IF EXISTS biz.sites;
-- DROP TABLE IF EXISTS biz.tenants;
-- DROP TABLE IF EXISTS biz.connectors;
-- =============================================================================
-- =============================================================================
-- 验证
-- =============================================================================
-- 1) biz.connectors 表存在性
-- SELECT column_name, data_type, is_nullable
-- FROM information_schema.columns
-- WHERE table_schema = 'biz' AND table_name = 'connectors'
-- ORDER BY ordinal_position;
-- 预期5 行id, connector_key, display_name, is_active, created_at
-- 2) biz.tenants 表存在性
-- SELECT column_name, data_type, is_nullable
-- FROM information_schema.columns
-- WHERE table_schema = 'biz' AND table_name = 'tenants'
-- ORDER BY ordinal_position;
-- 预期7 行id, connector_id, tenant_id, tenant_name, is_active, created_at, updated_at
-- 3) biz.sites 表存在性及字段
-- SELECT column_name, data_type, is_nullable
-- FROM information_schema.columns
-- WHERE table_schema = 'biz' AND table_name = 'sites'
-- ORDER BY ordinal_position;
-- 预期9 行id, tenant_id, site_id, site_name, site_code, site_label, is_active, created_at, updated_at
-- 4) biz.site_code_history 表存在性
-- SELECT column_name, data_type, is_nullable
-- FROM information_schema.columns
-- WHERE table_schema = 'biz' AND table_name = 'site_code_history'
-- ORDER BY ordinal_position;
-- 预期6 行id, site_id, site_code, is_current, created_at, retired_at
-- 5) 种子数据验证
-- SELECT connector_key, display_name FROM biz.connectors;
-- 预期1 行feiqiu, 飞球)
-- SELECT tenant_id, tenant_name FROM biz.tenants;
-- 预期1 行2790683160709957, 朗朗桌球)
-- 6) 数据迁移验证 — biz.sites 行数应 ≥ auth.site_code_mapping 中 tenant_id IS NOT NULL 的行数
-- SELECT COUNT(*) FROM biz.sites;
-- SELECT COUNT(*) FROM auth.site_code_mapping WHERE tenant_id IS NOT NULL;
-- 7) site_code_history 记录数应等于 biz.sites 中 site_code IS NOT NULL 的行数
-- SELECT COUNT(*) FROM biz.site_code_history;
-- SELECT COUNT(*) FROM biz.sites WHERE site_code IS NOT NULL;
-- 8) UNIQUE 约束验证
-- SELECT conname, pg_get_constraintdef(oid)
-- FROM pg_constraint
-- WHERE conrelid IN (
-- 'biz.connectors'::regclass,
-- 'biz.tenants'::regclass,
-- 'biz.sites'::regclass,
-- 'biz.site_code_history'::regclass
-- ) AND contype IN ('u', 'p')
-- ORDER BY conrelid::text, conname;

View File

@@ -0,0 +1,164 @@
-- =============================================================================
-- 迁移P14 — AI 模块改造DashScope 迁移 + 调度器完善)
-- 日期2026-03-22
-- 关联 SPECP14-ai-dashscope-migration
-- 需求14.1, 14.2, 14.3, 14.4, 14.5, 14.6, 14.7
-- 说明:
-- 1. 新建 biz.ai_run_logsAI 运行记录,含 3 个索引)
-- 2. 新建 biz.ai_trigger_jobs调度运行记录含 3 个索引,含去重部分索引)
-- 3. ai_conversations 新增 session_id 字段(百炼会话 ID
-- 4. ai_cache 新增 status 字段 + CHECK 约束
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 1. 新建 biz.ai_run_logsAI 运行记录)
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS biz.ai_run_logs (
id BIGSERIAL PRIMARY KEY,
site_id BIGINT NOT NULL,
app_type VARCHAR(30) NOT NULL,
trigger_type VARCHAR(20) NOT NULL,
member_id BIGINT,
request_prompt TEXT,
response_text TEXT,
tokens_used INTEGER DEFAULT 0,
latency_ms INTEGER,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
error_message TEXT,
session_id VARCHAR(100),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
finished_at TIMESTAMPTZ
);
COMMENT ON TABLE biz.ai_run_logs IS 'AI 运行记录:每次 DashScope Application API 调用的详细日志';
COMMENT ON COLUMN biz.ai_run_logs.app_type IS '应用类型app1_chat / app2_finance / app3_clue / ... / app8_consolidate';
COMMENT ON COLUMN biz.ai_run_logs.trigger_type IS '触发类型user / scheduled / event / forced';
COMMENT ON COLUMN biz.ai_run_logs.request_prompt IS '请求 prompt截断前 2000 字符)';
COMMENT ON COLUMN biz.ai_run_logs.status IS '状态pending / running / success / failed / timeout / budget_exceeded';
COMMENT ON COLUMN biz.ai_run_logs.session_id IS '百炼 session_id仅 App1 使用)';
CREATE INDEX IF NOT EXISTS idx_ai_run_logs_site_app
ON biz.ai_run_logs (site_id, app_type);
CREATE INDEX IF NOT EXISTS idx_ai_run_logs_created
ON biz.ai_run_logs (created_at);
CREATE INDEX IF NOT EXISTS idx_ai_run_logs_status
ON biz.ai_run_logs (status);
-- ---------------------------------------------------------------------------
-- 2. 新建 biz.ai_trigger_jobs调度运行记录
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS biz.ai_trigger_jobs (
id BIGSERIAL PRIMARY KEY,
site_id BIGINT NOT NULL,
event_type VARCHAR(30) NOT NULL,
connector_type VARCHAR(30) DEFAULT 'feiqiu',
member_id BIGINT,
payload JSONB,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
is_forced BOOLEAN DEFAULT false,
app_chain VARCHAR(100),
started_at TIMESTAMPTZ,
finished_at TIMESTAMPTZ,
error_message TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
COMMENT ON TABLE biz.ai_trigger_jobs IS '调度运行记录:每次 AI 事件触发的编排执行记录';
COMMENT ON COLUMN biz.ai_trigger_jobs.event_type IS '事件类型consumption / dws_completed / note_created / task_assigned';
COMMENT ON COLUMN biz.ai_trigger_jobs.status IS '状态pending / running / completed / failed / skipped_duplicate / budget_exceeded';
COMMENT ON COLUMN biz.ai_trigger_jobs.is_forced IS '是否强制执行(跳过去重检查)';
COMMENT ON COLUMN biz.ai_trigger_jobs.app_chain IS '调用链描述,如 app3→app8→app7';
CREATE INDEX IF NOT EXISTS idx_ai_trigger_jobs_site
ON biz.ai_trigger_jobs (site_id, event_type);
CREATE INDEX IF NOT EXISTS idx_ai_trigger_jobs_dedup
ON biz.ai_trigger_jobs (event_type, member_id, site_id, created_at)
WHERE status NOT IN ('skipped_duplicate');
CREATE INDEX IF NOT EXISTS idx_ai_trigger_jobs_status
ON biz.ai_trigger_jobs (status);
-- ---------------------------------------------------------------------------
-- 3. ai_conversations 新增 session_id 字段
-- ---------------------------------------------------------------------------
ALTER TABLE biz.ai_conversations
ADD COLUMN IF NOT EXISTS session_id VARCHAR(100);
COMMENT ON COLUMN biz.ai_conversations.session_id
IS '百炼 session_id格式 conv_{conversation_id}_{created_timestamp},仅 App1 使用';
-- ---------------------------------------------------------------------------
-- 4. ai_cache 新增 status 字段 + CHECK 约束
-- ---------------------------------------------------------------------------
ALTER TABLE biz.ai_cache
ADD COLUMN IF NOT EXISTS status VARCHAR(20) DEFAULT 'valid';
-- CHECK 约束需要先检查是否已存在IF NOT EXISTS 不适用于约束)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'chk_ai_cache_status'
AND conrelid = 'biz.ai_cache'::regclass
) THEN
ALTER TABLE biz.ai_cache
ADD CONSTRAINT chk_ai_cache_status
CHECK (status IN ('valid', 'expired', 'invalidated', 'generating'));
END IF;
END $$;
COMMENT ON COLUMN biz.ai_cache.status
IS '缓存状态valid有效/ expired已过期/ invalidated手动失效/ generating生成中';
-- =============================================================================
-- 回滚脚本(逆序执行)
-- =============================================================================
-- 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;
-- DROP TABLE IF EXISTS biz.ai_trigger_jobs;
-- DROP TABLE IF EXISTS biz.ai_run_logs;
-- =============================================================================
-- 验证 SQL
-- =============================================================================
-- 1) 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;
-- 预期14 行
-- 2) 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 行
-- 3) ai_run_logs 索引
-- SELECT indexname FROM pg_indexes
-- WHERE schemaname = 'biz' AND tablename = 'ai_run_logs';
-- 预期4 行(含 PK
-- 4) ai_trigger_jobs 索引(含去重部分索引)
-- SELECT indexname FROM pg_indexes
-- WHERE schemaname = 'biz' AND tablename = 'ai_trigger_jobs';
-- 预期4 行(含 PK
-- 5) 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)
-- 6) 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 行

View File

@@ -0,0 +1,78 @@
-- =============================================================================
-- 迁移P16 调度任务最小运行间隔 — scheduled_tasks 新增字段
-- 日期2026-03-22
-- 关联 SPECadmin-web-enhancement
-- 需求B1.1, B1.2
-- 说明:
-- 为 public.scheduled_tasks 表新增 3 个字段:
-- 1. min_run_interval_value — 最小间隔数值0=无限制,向后兼容)
-- 2. min_run_interval_unit — 间隔单位minutes/hours/days
-- 3. last_success_at — 最后一次成功执行的时间
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 1. 新增字段
-- ---------------------------------------------------------------------------
-- 最小间隔数值0=无限制,与现有行为完全一致)
ALTER TABLE scheduled_tasks
ADD COLUMN IF NOT EXISTS min_run_interval_value INTEGER NOT NULL DEFAULT 0;
-- 间隔单位minutes/hours/days
ALTER TABLE scheduled_tasks
ADD COLUMN IF NOT EXISTS min_run_interval_unit VARCHAR(20) NOT NULL DEFAULT 'minutes';
-- 最后一次成功执行的时间NULL=从未成功执行)
ALTER TABLE scheduled_tasks
ADD COLUMN IF NOT EXISTS last_success_at TIMESTAMPTZ;
-- ---------------------------------------------------------------------------
-- 2. 字段注释
-- ---------------------------------------------------------------------------
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 '最后一次成功执行的时间';
-- =============================================================================
-- === ROLLBACK ===
-- 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;
-- =============================================================================
-- =============================================================================
-- 验证
-- =============================================================================
-- 1) 新增字段存在性
-- SELECT column_name, data_type, is_nullable, column_default
-- FROM information_schema.columns
-- WHERE table_schema = 'public' AND table_name = 'scheduled_tasks'
-- AND column_name IN ('min_run_interval_value', 'min_run_interval_unit', 'last_success_at')
-- ORDER BY ordinal_position;
-- 预期3 行
-- 2) 默认值验证
-- SELECT column_name, column_default
-- FROM information_schema.columns
-- WHERE table_schema = 'public' AND table_name = 'scheduled_tasks'
-- AND column_name IN ('min_run_interval_value', 'min_run_interval_unit')
-- ORDER BY column_name;
-- 预期min_run_interval_unit → 'minutes'::character varying, min_run_interval_value → 0
-- 3) 向后兼容验证 — 已有行的新字段值应为默认值
-- SELECT id, 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
-- 4) 字段注释验证
-- SELECT a.attname, d.description
-- FROM pg_catalog.pg_attribute a
-- JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
-- JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
-- LEFT JOIN pg_catalog.pg_description d ON d.objoid = c.oid AND d.objsubid = a.attnum
-- WHERE n.nspname = 'public' AND c.relname = 'scheduled_tasks'
-- AND a.attname IN ('min_run_interval_value', 'min_run_interval_unit', 'last_success_at')
-- ORDER BY a.attnum;
-- 预期3 行,每行 description 非 NULL

View File

@@ -0,0 +1,22 @@
-- AI_CHANGELOG
-- | 日期 | Prompt | 变更 |
-- |------|--------|------|
-- | 2026-03-23 | 角色路由+页面权限守卫 | 新增 head_coach教练和 manager管理员角色及权限映射 |
-- 迁移:新增 head_coach教练和 manager管理员角色
-- 原因:小程序需要按角色区分页面入口和 tab-bar 可见性
-- 回滚DELETE FROM auth.roles WHERE code IN ('head_coach', 'manager');
-- 幂等插入code 有唯一约束,重复执行不报错
INSERT INTO auth.roles (code, name)
VALUES ('head_coach', '教练')
ON CONFLICT (code) DO NOTHING;
INSERT INTO auth.roles (code, name)
VALUES ('manager', '管理员')
ON CONFLICT (code) DO NOTHING;
-- 验证
-- 1. SELECT id, code, name FROM auth.roles ORDER BY id;
-- 2. SELECT count(*) FROM auth.roles WHERE code IN ('head_coach', 'manager');
-- 3. SELECT code, name FROM auth.roles WHERE code IN ('coach', 'staff', 'head_coach', 'manager');

View File

@@ -0,0 +1,37 @@
-- 迁移:申请审核流程增强
-- 1. users 表增加 rejection_count 字段(累计被拒绝次数)
-- 2. user_applications.status 增加 'cancelled' 值(用户主动取消)
-- 日期2026-03-23
-- 1. 增加 rejection_count 字段(默认 0
ALTER TABLE auth.users
ADD COLUMN IF NOT EXISTS rejection_count integer NOT NULL DEFAULT 0;
COMMENT ON COLUMN auth.users.rejection_count
IS '累计被管理员拒绝的申请次数,达到 3 次自动禁用账号';
-- 2. user_applications.status 检查约束更新(增加 cancelled
-- 先删除旧约束(如果存在),再添加新约束
DO $$
BEGIN
-- 尝试删除旧的 CHECK 约束
IF EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE table_schema = 'auth'
AND table_name = 'user_applications'
AND constraint_type = 'CHECK'
AND constraint_name = 'user_applications_status_check'
) THEN
ALTER TABLE auth.user_applications
DROP CONSTRAINT user_applications_status_check;
END IF;
END $$;
ALTER TABLE auth.user_applications
ADD CONSTRAINT user_applications_status_check
CHECK (status IN ('pending', 'approved', 'rejected', 'cancelled'));
-- 回滚 SQL备用
-- ALTER TABLE auth.users DROP COLUMN IF EXISTS rejection_count;
-- 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'));

View File

@@ -0,0 +1,30 @@
-- 迁移:租户管理员用户名大小写不敏感
-- 日期2026-03-23
-- 原因:登录时用户名应不区分大小写,避免 Admin/admin 被视为不同账号
-- 影响auth.tenant_admins 表
-- 回滚:见文件末尾
-- 1. 将现有用户名统一转小写(幂等:已经是小写的不受影响)
UPDATE auth.tenant_admins
SET username = LOWER(username)
WHERE username != LOWER(username);
-- 2. 创建大小写不敏感的唯一索引(替代原 UNIQUE 约束的语义)
-- 原 UNIQUE 约束仍保留(防止完全相同的用户名),此索引额外覆盖大小写变体
CREATE UNIQUE INDEX IF NOT EXISTS idx_tenant_admins_username_lower
ON auth.tenant_admins (LOWER(username))
WHERE deleted_at IS NULL;
-- ── 回滚 SQL ──
-- DROP INDEX IF EXISTS auth.idx_tenant_admins_username_lower;
-- (用户名小写化不可逆,但不影响功能)
-- ── 验证 SQL ──
-- 1. 确认索引存在
-- SELECT indexname FROM pg_indexes WHERE tablename = 'tenant_admins' AND indexname = 'idx_tenant_admins_username_lower';
-- 2. 确认无大写用户名残留
-- SELECT username FROM auth.tenant_admins WHERE username != LOWER(username);
-- 3. 测试大小写不敏感插入冲突
-- INSERT INTO auth.tenant_admins (username, password_hash, tenant_id, managed_site_ids) VALUES ('TESTDUP', 'x', 1, '{1}');
-- INSERT INTO auth.tenant_admins (username, password_hash, tenant_id, managed_site_ids) VALUES ('testdup', 'x', 1, '{1}');
-- 第二条应报 unique violation

View File

@@ -0,0 +1,95 @@
-- AI_CHANGELOG
-- | 日期 | Prompt | 变更 |
-- |------|--------|------|
-- | 2026-03-23 | 角色体系隔离+店铺管理员 | 清理小程序 RBAC 中的 site_admin/tenant_admin新增 head_coach/managertenant_admins 加 admin_type |
-- 迁移:角色体系隔离 + 店铺管理员支持
-- 原因site_admin/tenant_admin 属于租户管理后台概念,不应出现在小程序 RBAC 体系中
-- 回滚:见文件末尾
BEGIN;
-- ═══════════════════════════════════════════════════════════
-- Part A: 清理小程序 RBAC 中的 site_admin / tenant_admin
-- ═══════════════════════════════════════════════════════════
-- A1. 安全检查:确认无用户绑定这两个角色(如有则中止)
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM auth.user_site_roles usr
JOIN auth.roles r ON r.id = usr.role_id
WHERE r.code IN ('site_admin', 'tenant_admin')
) THEN
RAISE EXCEPTION '存在用户绑定了 site_admin/tenant_admin 角色,需先迁移数据';
END IF;
END $$;
-- A2. 删除 role_permissions 关联
DELETE FROM auth.role_permissions
WHERE role_id IN (SELECT id FROM auth.roles WHERE code IN ('site_admin', 'tenant_admin'));
-- A3. 删除角色记录
DELETE FROM auth.roles WHERE code IN ('site_admin', 'tenant_admin');
-- ═══════════════════════════════════════════════════════════
-- Part B: 新增 head_coach / manager 角色(幂等)
-- ═══════════════════════════════════════════════════════════
INSERT INTO auth.roles (code, name, description)
VALUES ('head_coach', '教练', '教练,可查看任务和看板')
ON CONFLICT (code) DO NOTHING;
INSERT INTO auth.roles (code, name, description)
VALUES ('manager', '管理员', '店铺管理员(小程序端),可查看所有板块')
ON CONFLICT (code) DO NOTHING;
-- B2. 为 head_coach 分配权限view_tasks, view_board
INSERT INTO auth.role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM auth.roles r, auth.permissions p
WHERE r.code = 'head_coach' AND p.code IN ('view_tasks', 'view_board')
ON CONFLICT DO NOTHING;
-- B3. 为 manager 分配全部 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 = 'manager'
ON CONFLICT DO NOTHING;
-- ═══════════════════════════════════════════════════════════
-- Part C: tenant_admins 加 admin_type 列
-- ═══════════════════════════════════════════════════════════
ALTER TABLE auth.tenant_admins
ADD COLUMN IF NOT EXISTS admin_type VARCHAR(20) NOT NULL DEFAULT 'tenant_admin';
-- C2. CHECK 约束:只允许 tenant_admin / site_admin
ALTER TABLE auth.tenant_admins
ADD CONSTRAINT chk_admin_type CHECK (admin_type IN ('tenant_admin', 'site_admin'));
COMMIT;
-- ═══════════════════════════════════════════════════════════
-- 回滚
-- ═══════════════════════════════════════════════════════════
-- BEGIN;
-- ALTER TABLE auth.tenant_admins DROP CONSTRAINT IF EXISTS chk_admin_type;
-- ALTER TABLE auth.tenant_admins DROP COLUMN IF EXISTS admin_type;
-- 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', '店铺管理员', '单店管理员,可查看所有看板和审核用户');
-- INSERT INTO auth.roles (code, name, description) VALUES ('tenant_admin', '租户管理员', '连锁管理员,可管理多店铺和所有功能');
-- -- 重新插入 role_permissionssite_admin/tenant_admin 各 5 条)
-- COMMIT;
-- ═══════════════════════════════════════════════════════════
-- 验证
-- ═══════════════════════════════════════════════════════════
-- 1. SELECT code, name FROM auth.roles ORDER BY id;
-- 期望coach, staff, head_coach, manager无 site_admin/tenant_admin
-- 2. SELECT r.code, array_agg(p.code ORDER BY p.code) FROM auth.role_permissions rp JOIN auth.roles r ON r.id = rp.role_id JOIN auth.permissions p ON p.id = rp.permission_id GROUP BY r.code;
-- 期望head_coach=[view_board, view_tasks], manager=[全部5个]
-- 3. 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, varchar, 'tenant_admin'

View File

@@ -0,0 +1,43 @@
-- P15AI 监控后台 — ai_run_logs 新增 alert_status 字段 + BRIN 索引
-- 依赖P14 迁移2026-03-22__p14_ai_module.sql
BEGIN;
-- 1. 新增 alert_status 字段
ALTER TABLE biz.ai_run_logs
ADD COLUMN IF NOT EXISTS alert_status VARCHAR(20) DEFAULT NULL;
ALTER TABLE biz.ai_run_logs
ADD CONSTRAINT chk_ai_run_logs_alert_status
CHECK (alert_status IS NULL OR alert_status IN ('pending', 'acknowledged', 'ignored'));
COMMENT ON COLUMN biz.ai_run_logs.alert_status IS
'P15 — 告警处理状态NULL/pending/acknowledged/ignored';
-- 2. 告警查询部分索引
CREATE INDEX IF NOT EXISTS idx_ai_run_logs_alert
ON biz.ai_run_logs(alert_status, created_at DESC)
WHERE status IN ('failed', 'timeout', 'circuit_open');
-- 3. BRIN 索引Dashboard 聚合优化)
CREATE INDEX IF NOT EXISTS idx_ai_run_logs_created_brin
ON biz.ai_run_logs USING BRIN (created_at)
WITH (pages_per_range = 32);
-- 4. 回填已有失败记录的 alert_status
UPDATE biz.ai_run_logs
SET alert_status = 'pending'
WHERE status IN ('failed', 'timeout', 'circuit_open')
AND alert_status IS NULL;
COMMIT;
-- ============================================================
-- P15 回滚
-- ============================================================
-- 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;

View File

@@ -0,0 +1,9 @@
-- 2026-03-23: 为 scheduled_tasks 添加 per-task-code 最小执行间隔
-- 格式: {"TASK_CODE": {"value": 30, "unit": "minutes"}, ...}
-- 空对象 {} 表示所有任务无独立间隔限制(回退到 schedule 级别的 min_run_interval_value/unit
ALTER TABLE scheduled_tasks
ADD COLUMN IF NOT EXISTS min_run_intervals JSONB NOT NULL DEFAULT '{}'::jsonb;
COMMENT ON COLUMN scheduled_tasks.min_run_intervals IS
'Per-task-code 最小执行间隔JSON 格式 {"TASK_CODE": {"value": N, "unit": "minutes|hours|days"}}';

View File

@@ -0,0 +1,47 @@
-- =============================================================================
-- 迁移trigger_jobs 新增 last_error 字段 + description 字段
-- 日期2026-03-23
-- 说明:
-- 1. last_error — 最后一次执行异常的错误信息NULL=无异常)
-- 2. description — 任务中文描述(用于管理页面展示)
-- =============================================================================
ALTER TABLE biz.trigger_jobs
ADD COLUMN IF NOT EXISTS last_error TEXT;
ALTER TABLE biz.trigger_jobs
ADD COLUMN IF NOT EXISTS description TEXT;
COMMENT ON COLUMN biz.trigger_jobs.last_error IS '最后一次执行异常的错误信息NULL=无异常)';
COMMENT ON COLUMN biz.trigger_jobs.description IS '任务中文描述(管理页面展示)';
-- 填充描述
UPDATE biz.trigger_jobs SET description = '每日凌晨4点根据客户指数为助教生成待办任务' WHERE job_name = 'task_generator';
UPDATE biz.trigger_jobs SET description = '每小时检查超时未处理的任务,标记为过期' WHERE job_name = 'task_expiry_check';
UPDATE biz.trigger_jobs SET description = 'ETL数据更新后检测客户是否已回店完成召回' WHERE job_name = 'recall_completion_check';
UPDATE biz.trigger_jobs SET description = '召回完成后,回溯检查备注是否需要重分类' WHERE job_name = 'note_reclassify_backfill';
-- =============================================================================
-- === ROLLBACK ===
-- ALTER TABLE biz.trigger_jobs DROP COLUMN IF EXISTS last_error;
-- ALTER TABLE biz.trigger_jobs DROP COLUMN IF EXISTS description;
-- =============================================================================
-- =============================================================================
-- 验证
-- =============================================================================
-- 1) 新增字段存在性
-- SELECT column_name, data_type, is_nullable
-- FROM information_schema.columns
-- WHERE table_schema = 'biz' AND table_name = 'trigger_jobs'
-- AND column_name IN ('last_error', 'description')
-- ORDER BY column_name;
-- 预期2 行
-- 2) description 已填充
-- SELECT job_name, description FROM biz.trigger_jobs ORDER BY id;
-- 预期4 行description 非 NULL
-- 3) last_error 默认 NULL
-- SELECT job_name, last_error FROM biz.trigger_jobs ORDER BY id;
-- 预期4 行last_error 全为 NULL

View File

@@ -0,0 +1,139 @@
-- P17助教客户归属与任务生成引擎 — 数据库变更
-- 依赖biz.coach_tasks 表已存在P4 创建)
-- 回滚:见文件末尾 ROLLBACK 注释块
BEGIN;
-- ═══════════════════════════════════════════════════════════
-- 1. 新增任务状态枚举值
-- ═══════════════════════════════════════════════════════════
-- 检查 coach_tasks.status 是否使用 enum 类型
-- 现有值: active, inactive, completed, abandoned
-- 新增: transferred已转移, pending_review待人工审核
-- 注意: 如果 status 是 VARCHAR 类型则无需 ALTER TYPE直接使用即可
-- 以下 DO 块兼容 enum 和 varchar 两种情况
DO $$
BEGIN
-- 尝试添加 enum 值(如果 status 列使用 enum 类型)
BEGIN
EXECUTE 'ALTER TYPE task_status ADD VALUE IF NOT EXISTS ''transferred''';
EXCEPTION WHEN undefined_object THEN
-- status 列不是 enum 类型,跳过
NULL;
END;
BEGIN
EXECUTE 'ALTER TYPE task_status ADD VALUE IF NOT EXISTS ''pending_review''';
EXCEPTION WHEN undefined_object THEN
NULL;
END;
END $$;
-- ═══════════════════════════════════════════════════════════
-- 2. coach_tasks 表新增转移追踪字段
-- ═══════════════════════════════════════════════════════════
ALTER TABLE biz.coach_tasks
ADD COLUMN IF NOT EXISTS transfer_count INTEGER NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS transferred_from BIGINT,
ADD COLUMN IF NOT EXISTS transferred_at TIMESTAMPTZ;
-- transferred_from 外键(指向自身,记录转移来源任务)
-- 使用 DO 块避免重复添加约束
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name = 'fk_coach_tasks_transferred_from'
AND table_schema = 'biz'
AND table_name = 'coach_tasks'
) THEN
ALTER TABLE biz.coach_tasks
ADD CONSTRAINT fk_coach_tasks_transferred_from
FOREIGN KEY (transferred_from) REFERENCES biz.coach_tasks(id);
END IF;
END $$;
COMMENT ON COLUMN biz.coach_tasks.transfer_count IS '该客户在此任务链上的累计转移次数';
COMMENT ON COLUMN biz.coach_tasks.transferred_from IS '转移来源任务 ID指向原助教的任务';
COMMENT ON COLUMN biz.coach_tasks.transferred_at IS '转移发生时间';
-- ═══════════════════════════════════════════════════════════
-- 3. 新增表biz.cfg_task_generator_params任务引擎参数配置
-- ═══════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS biz.cfg_task_generator_params (
id BIGSERIAL PRIMARY KEY,
site_id BIGINT, -- NULL 表示全局默认值
param_key VARCHAR(64) NOT NULL,
param_value NUMERIC NOT NULL,
description TEXT,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (site_id, param_key)
);
COMMENT ON TABLE biz.cfg_task_generator_params IS 'P17 任务引擎参数配置,支持全局默认 + 门店级覆盖';
COMMENT ON COLUMN biz.cfg_task_generator_params.site_id IS 'NULL=全局默认非NULL=门店级覆盖';
-- ═══════════════════════════════════════════════════════════
-- 4. 新增表biz.coach_task_transfer_log客户转移日志
-- ═══════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS biz.coach_task_transfer_log (
id BIGSERIAL PRIMARY KEY,
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 REFERENCES biz.coach_tasks(id),
to_task_id BIGINT REFERENCES biz.coach_tasks(id),
transfer_reason TEXT,
guard_checks JSONB, -- 三重保护检查结果快照
transfer_score NUMERIC, -- 转移候选得分
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE biz.coach_task_transfer_log IS 'P17 客户转移日志,记录每次转移的完整上下文';
-- 索引:按门店+时间查询转移日志
CREATE INDEX IF NOT EXISTS idx_transfer_log_site_created
ON biz.coach_task_transfer_log (site_id, created_at DESC);
-- 索引:按客户查询转移历史
CREATE INDEX IF NOT EXISTS idx_transfer_log_member
ON biz.coach_task_transfer_log (member_id, created_at DESC);
-- ═══════════════════════════════════════════════════════════
-- 5. 插入全局默认参数P17 第 5 节定义)
-- ═══════════════════════════════════════════════════════════
INSERT INTO biz.cfg_task_generator_params (site_id, param_key, param_value, description)
VALUES
(NULL, 'high_priority_recall_threshold', 7.0, 'max(WBI,NCI) 超过此值生成高优先召回'),
(NULL, 'priority_recall_threshold', 5.0, 'max(WBI,NCI) 超过此值生成优先召回'),
(NULL, 'rs_min_for_relationship', 1.0, 'RS ≤ 此值不生成关系构建'),
(NULL, 'rs_max_for_relationship', 6.0, 'RS ≥ 此值不生成关系构建'),
(NULL, 'consecutive_recall_fail_cycles', 3, '连续失败多少轮触发客户转移'),
(NULL, 'min_wbi_for_transfer', 5.0, 'WBI 低于此值不触发转移'),
(NULL, 'guard_assistant_coverage_ratio', 0.5, '绑定率低于此值禁用转移'),
(NULL, 'guard_new_assistant_days', 10, '新助教入驻保护天数'),
(NULL, 'transfer_score_w_rs', 0.5, '转移候选排序RS 权重'),
(NULL, 'transfer_score_w_ms', 0.3, '转移候选排序MS 权重'),
(NULL, 'transfer_score_w_ml', 0.2, '转移候选排序ML 权重'),
(NULL, 'max_transfer_count', 2, '单客户最大累计转移次数'),
(NULL, 'follow_up_visit_retention_hours', 48, '回访任务最低保留时长(小时)')
ON CONFLICT (site_id, param_key) DO NOTHING;
COMMIT;
-- ═══════════════════════════════════════════════════════════
-- ROLLBACK手动执行不在事务内
-- ═══════════════════════════════════════════════════════════
-- DELETE FROM biz.cfg_task_generator_params WHERE site_id IS NULL;
-- DROP TABLE IF EXISTS biz.coach_task_transfer_log;
-- DROP TABLE IF EXISTS biz.cfg_task_generator_params;
-- 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 值一旦添加无法直接删除,需重建类型

View File

@@ -0,0 +1,24 @@
-- P18管理后台任务引擎运营看板 — DDL 迁移
-- 日期2026-03-24
-- 依赖P17 已完成的表结构
-- 1. trigger_jobs 新增 last_stats 字段(记录最近一次执行统计)
ALTER TABLE biz.trigger_jobs
ADD COLUMN IF NOT EXISTS last_stats JSONB;
COMMENT ON COLUMN biz.trigger_jobs.last_stats
IS '最近一次执行的统计结果 JSON如 {"created":5,"replaced":2,"skipped":10,"transferred":1}';
-- 2. cfg_task_generator_params 新增 updated_by 字段(审计追溯)
ALTER TABLE biz.cfg_task_generator_params
ADD COLUMN IF NOT EXISTS updated_by BIGINT;
COMMENT ON COLUMN biz.cfg_task_generator_params.updated_by
IS '最近修改人 user_id用于审计追溯';
-- 验证
-- SELECT column_name, data_type FROM information_schema.columns
-- WHERE table_schema = 'biz' AND table_name = 'trigger_jobs' AND column_name = 'last_stats';
--
-- SELECT column_name, data_type FROM information_schema.columns
-- WHERE table_schema = 'biz' AND table_name = 'cfg_task_generator_params' AND column_name = 'updated_by';

View File

@@ -0,0 +1,23 @@
-- relationship_building 保底任务:扩大生成范围
-- 依赖biz.coach_tasks 表已存在P4 创建P17 扩展)
-- 回滚:见文件末尾 ROLLBACK 注释块
BEGIN;
-- ═══════════════════════════════════════════════════════════
-- 1. Partial unique index每个 (assistant, member) 对最多 1 条 active 的 relationship_building
-- ═══════════════════════════════════════════════════════════
CREATE UNIQUE INDEX IF NOT EXISTS idx_coach_tasks_rb_unique_active
ON biz.coach_tasks (site_id, assistant_id, member_id)
WHERE task_type = 'relationship_building' AND status = 'active';
COMMENT ON INDEX biz.idx_coach_tasks_rb_unique_active IS
'保证每个 (site_id, assistant_id, member_id) 最多 1 条 active 的 relationship_building 任务,支持 upsert';
COMMIT;
-- ═══════════════════════════════════════════════════════════
-- ROLLBACK
-- ═══════════════════════════════════════════════════════════
-- DROP INDEX IF EXISTS biz.idx_coach_tasks_rb_unique_active;

View File

@@ -0,0 +1,78 @@
-- AI_CHANGELOG
-- | 日期 | Prompt | 变更 |
-- |------|--------|------|
-- | 2026-03-27 | 权限改造 W3 | 修正角色-权限码映射coach 仅 view_tasksstaff 仅 view_board+customer+coachhead_coach/manager 全权限 |
-- 迁移:修正角色-权限码映射
-- 原因:前后端权限不一致导致"页面能进但数据全空403"
-- 目标映射:
-- coach: view_tasks
-- staff: view_board, view_board_customer, view_board_coach
-- head_coach: view_tasks, view_board, view_board_finance, view_board_customer, view_board_coach
-- manager: view_tasks, view_board, view_board_finance, view_board_customer, view_board_coach
-- 回滚:见文件末尾
BEGIN;
-- 1. 清空所有现有角色-权限关联(重建更安全,避免残留脏数据)
DELETE FROM auth.role_permissions
WHERE role_id IN (SELECT id FROM auth.roles WHERE code IN ('coach', 'staff', 'head_coach', 'manager'));
-- 2. coach → view_tasks
INSERT INTO auth.role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM auth.roles r, auth.permissions p
WHERE r.code = 'coach' AND p.code IN ('view_tasks')
ON CONFLICT DO NOTHING;
-- 3. staff → view_board, view_board_customer, view_board_coach
INSERT INTO auth.role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM auth.roles r, auth.permissions p
WHERE r.code = 'staff' AND p.code IN ('view_board', 'view_board_customer', 'view_board_coach')
ON CONFLICT DO NOTHING;
-- 4. head_coach → 全部 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 = 'head_coach'
ON CONFLICT DO NOTHING;
-- 5. manager → 全部 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 = 'manager'
ON CONFLICT DO NOTHING;
COMMIT;
-- ═══════════════════════════════════════════════════════════
-- 回滚(恢复到改造前状态)
-- ═══════════════════════════════════════════════════════════
-- BEGIN;
-- DELETE FROM auth.role_permissions WHERE role_id IN (SELECT id FROM auth.roles WHERE code IN ('coach', 'staff', 'head_coach', 'manager'));
-- -- coach: view_tasks, view_board_coach
-- INSERT INTO auth.role_permissions (role_id, permission_id) SELECT r.id, p.id FROM auth.roles r, auth.permissions p WHERE r.code = 'coach' AND p.code IN ('view_tasks', 'view_board_coach') ON CONFLICT DO NOTHING;
-- -- staff: view_board, view_tasks
-- INSERT INTO auth.role_permissions (role_id, permission_id) SELECT r.id, p.id FROM auth.roles r, auth.permissions p WHERE r.code = 'staff' AND p.code IN ('view_board', 'view_tasks') ON CONFLICT DO NOTHING;
-- -- head_coach: view_board, view_tasks
-- INSERT INTO auth.role_permissions (role_id, permission_id) SELECT r.id, p.id FROM auth.roles r, auth.permissions p WHERE r.code = 'head_coach' AND p.code IN ('view_board', 'view_tasks') ON CONFLICT DO NOTHING;
-- -- manager: 全部 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 = 'manager' ON CONFLICT DO NOTHING;
-- COMMIT;
-- ═══════════════════════════════════════════════════════════
-- 验证
-- ═══════════════════════════════════════════════════════════
-- SELECT r.code, array_agg(p.code ORDER BY p.code)
-- FROM auth.role_permissions rp
-- JOIN auth.roles r ON r.id = rp.role_id
-- JOIN auth.permissions p ON p.id = rp.permission_id
-- GROUP BY r.code ORDER BY r.code;
-- 期望:
-- coach = {view_tasks}
-- head_coach = {view_board,view_board_coach,view_board_customer,view_board_finance,view_tasks}
-- manager = {view_board,view_board_coach,view_board_customer,view_board_finance,view_tasks}
-- staff = {view_board,view_board_coach,view_board_customer}

View File

@@ -0,0 +1,18 @@
-- OS 分级分配:新增 3 条任务引擎参数
-- 依赖biz.cfg_task_generator_params 表已存在P17 创建)
BEGIN;
INSERT INTO biz.cfg_task_generator_params (site_id, param_key, param_value, description)
VALUES
(NULL, 'escalation_comanage_multiplier', 3.0, '升级到 COMANAGE 的倍数阈值(任务未完成时长 / 理想到店周期)'),
(NULL, 'escalation_pool_multiplier', 5.0, '转移到 POOL 的倍数阈值(任务未完成时长 / 理想到店周期)'),
(NULL, 'default_ideal_interval_days', 10.0, '无历史数据时的兜底到店周期(天)')
ON CONFLICT (site_id, param_key) DO NOTHING;
COMMIT;
-- ROLLBACK:
-- DELETE FROM biz.cfg_task_generator_params
-- WHERE site_id IS NULL
-- AND param_key IN ('escalation_comanage_multiplier', 'escalation_pool_multiplier', 'default_ideal_interval_days');

View File

@@ -0,0 +1,38 @@
-- 任务引擎参数调优2026-03-30
-- 更新 cfg_task_generator_params 全局默认参数
BEGIN;
UPDATE biz.cfg_task_generator_params SET param_value = 7.5
WHERE site_id IS NULL AND param_key = 'high_priority_recall_threshold';
UPDATE biz.cfg_task_generator_params SET param_value = 4.0
WHERE site_id IS NULL AND param_key = 'priority_recall_threshold';
UPDATE biz.cfg_task_generator_params SET param_value = 3.0
WHERE site_id IS NULL AND param_key = 'min_wbi_for_transfer';
UPDATE biz.cfg_task_generator_params SET param_value = 0.0
WHERE site_id IS NULL AND param_key = 'guard_assistant_coverage_ratio';
UPDATE biz.cfg_task_generator_params SET param_value = 4
WHERE site_id IS NULL AND param_key = 'max_transfer_count';
-- 新增/更新升级倍数参数
INSERT INTO biz.cfg_task_generator_params (site_id, param_key, param_value, description)
VALUES
(NULL, 'escalation_comanage_multiplier', 2.5, '升级到 COMANAGE 的倍数阈值'),
(NULL, 'escalation_pool_multiplier', 4.0, '转移到 POOL 的倍数阈值'),
(NULL, 'default_ideal_interval_days', 10.0, '无历史数据时的兜底到店周期')
ON CONFLICT (site_id, param_key) DO UPDATE SET param_value = EXCLUDED.param_value;
COMMIT;
-- ROLLBACK:
-- UPDATE biz.cfg_task_generator_params SET param_value = 7.0 WHERE site_id IS NULL AND param_key = 'high_priority_recall_threshold';
-- UPDATE biz.cfg_task_generator_params SET param_value = 5.0 WHERE site_id IS NULL AND param_key = 'priority_recall_threshold';
-- UPDATE biz.cfg_task_generator_params SET param_value = 5.0 WHERE site_id IS NULL AND param_key = 'min_wbi_for_transfer';
-- UPDATE biz.cfg_task_generator_params SET param_value = 0.5 WHERE site_id IS NULL AND param_key = 'guard_assistant_coverage_ratio';
-- UPDATE biz.cfg_task_generator_params SET param_value = 2 WHERE site_id IS NULL AND param_key = 'max_transfer_count';
-- UPDATE biz.cfg_task_generator_params SET param_value = 3.0 WHERE site_id IS NULL AND param_key = 'escalation_comanage_multiplier';
-- UPDATE biz.cfg_task_generator_params SET param_value = 5.0 WHERE site_id IS NULL AND param_key = 'escalation_pool_multiplier';

View File

@@ -0,0 +1,47 @@
-- 任务统计表2026-03-31
-- B: 按助教+月份汇总表
-- C: 关系指数表新增历史总计字段
BEGIN;
-- ═══════════════════════════════════════════════════════════
-- B: 新建 biz.dws_assistant_task_monthly按月汇总
-- ═══════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS biz.dws_assistant_task_monthly (
id BIGSERIAL PRIMARY KEY,
site_id BIGINT NOT NULL,
assistant_id BIGINT NOT NULL,
stat_month DATE NOT NULL, -- 月份第一天,如 2026-03-01
-- 创建数
recall_created INT NOT NULL DEFAULT 0,
follow_up_created INT NOT NULL DEFAULT 0,
relationship_created INT NOT NULL DEFAULT 0,
total_created INT NOT NULL DEFAULT 0,
-- 完成数
recall_completed INT NOT NULL DEFAULT 0,
follow_up_completed INT NOT NULL DEFAULT 0,
total_completed INT NOT NULL DEFAULT 0,
-- 其他状态
abandoned_count INT NOT NULL DEFAULT 0,
transferred_count INT NOT NULL DEFAULT 0,
-- 时间戳
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (site_id, assistant_id, stat_month)
);
COMMENT ON TABLE biz.dws_assistant_task_monthly IS '助教任务月度统计汇总';
COMMENT ON COLUMN biz.dws_assistant_task_monthly.stat_month IS '统计月份(月初日期)';
COMMENT ON COLUMN biz.dws_assistant_task_monthly.recall_created IS '当月创建的召回任务数high_priority + priority';
COMMENT ON COLUMN biz.dws_assistant_task_monthly.recall_completed IS '当月完成的召回任务数';
CREATE INDEX IF NOT EXISTS idx_task_monthly_site_month
ON biz.dws_assistant_task_monthly (site_id, stat_month DESC);
CREATE INDEX IF NOT EXISTS idx_task_monthly_assistant
ON biz.dws_assistant_task_monthly (assistant_id, stat_month DESC);
COMMIT;
-- ROLLBACK:
-- DROP TABLE IF EXISTS biz.dws_assistant_task_monthly;

View File

@@ -0,0 +1,13 @@
-- 迁移admin_users 表新增 roles 字段
-- 原因:登录签发 JWT 时需要携带角色信息,供管理端权限检查使用
-- 回滚ALTER TABLE admin_users DROP COLUMN IF EXISTS roles;
ALTER TABLE admin_users
ADD COLUMN IF NOT EXISTS roles text[] NOT NULL DEFAULT '{site_admin}';
COMMENT ON COLUMN admin_users.roles IS '用户角色列表,如 site_admin / tenant_admin';
-- 验证
-- 1. SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_name='admin_users' AND column_name='roles';
-- 2. SELECT id, username, roles FROM admin_users LIMIT 5;
-- 3. SELECT count(*) FROM admin_users WHERE 'site_admin' = ANY(roles);

View File

@@ -0,0 +1,33 @@
-- 迁移auth.users 新增 avatar_url 字段
-- 日期2026-03-24
-- 原因:小程序登录时获取微信头像,保存到服务器后存储相对路径
-- 影响:后端 /api/xcx/me 返回 avatar_url小程序个人页、审核页显示头像
BEGIN;
ALTER TABLE auth.users
ADD COLUMN IF NOT EXISTS avatar_url VARCHAR(500);
COMMENT ON COLUMN auth.users.avatar_url IS '用户头像相对路径(如 avatars/123.jpg由 chooseAvatar 上传后保存';
COMMIT;
-- ── 回滚 ──
-- BEGIN;
-- ALTER TABLE auth.users DROP COLUMN IF EXISTS avatar_url;
-- COMMIT;
-- ── 验证 ──
-- 1. SELECT column_name, data_type, character_maximum_length
-- FROM information_schema.columns
-- WHERE table_schema='auth' AND table_name='users' AND column_name='avatar_url';
-- 期望avatar_url, character varying, 500
--
-- 2. SELECT id, nickname, avatar_url FROM auth.users LIMIT 5;
-- 期望avatar_url 列存在,值为 NULL
--
-- 3. SELECT col_description(
-- (SELECT oid FROM pg_class WHERE relname='users' AND relnamespace=(SELECT oid FROM pg_namespace WHERE nspname='auth')),
-- (SELECT ordinal_position FROM information_schema.columns WHERE table_schema='auth' AND table_name='users' AND column_name='avatar_url')
-- );
-- 期望:包含 'chooseAvatar' 关键词

View File

@@ -0,0 +1,35 @@
-- 迁移user_site_roles 和 user_assistant_binding 软删除支持
-- 日期2026-03-24
-- 原因:租户后台"移除用户"改为软删除,保留历史记录可追溯
-- 影响:后端所有查询需加 is_removed = false 过滤
BEGIN;
-- 1. user_site_roles 加软删除字段
ALTER TABLE auth.user_site_roles
ADD COLUMN IF NOT EXISTS is_removed boolean NOT NULL DEFAULT false,
ADD COLUMN IF NOT EXISTS removed_at timestamptz;
-- 2. user_assistant_binding 加软删除字段
ALTER TABLE auth.user_assistant_binding
ADD COLUMN IF NOT EXISTS is_removed boolean NOT NULL DEFAULT false,
ADD COLUMN IF NOT EXISTS removed_at timestamptz;
-- 3. 部分索引:加速常规查询(只查未移除的记录)
CREATE INDEX IF NOT EXISTS idx_user_site_roles_active
ON auth.user_site_roles (user_id, site_id)
WHERE is_removed = false;
CREATE INDEX IF NOT EXISTS idx_user_assistant_binding_active
ON auth.user_assistant_binding (user_id, site_id)
WHERE is_removed = false;
COMMIT;
-- ── 回滚 ──
-- BEGIN;
-- DROP INDEX IF EXISTS auth.idx_user_site_roles_active;
-- DROP INDEX IF EXISTS auth.idx_user_assistant_binding_active;
-- ALTER TABLE auth.user_site_roles DROP COLUMN IF EXISTS removed_at, DROP COLUMN IF EXISTS is_removed;
-- ALTER TABLE auth.user_assistant_binding DROP COLUMN IF EXISTS removed_at, DROP COLUMN IF EXISTS is_removed;
-- COMMIT;