feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本
包含多个会话的累积代码变更: - backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔 - admin-web: ETL 状态页、任务管理、调度配置、登录优化 - miniprogram: 看板页面、聊天集成、UI 组件、导航更新 - etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强 - tenant-admin: 项目初始化 - db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8) - packages/shared: 枚举和工具函数更新 - tools: 数据库工具、报表生成、健康检查 - docs: PRD/架构/部署/合约文档更新 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
-- 迁移:补录 cfg_skill_type 缺失的 3 条 skill_id
|
||||
-- 原因:dwd_assistant_service_log 中存在 5525 条记录引用了不在 cfg_skill_type 中的 skill_id,
|
||||
-- 导致 WBI/NCI 的到店判定(settle_type=3 + BONUS EXISTS 检查)漏掉了这些到店记录。
|
||||
-- 典型案例:会员"梅"实际 5 天前到店,但 WBI 显示 16 天未到店,display_score 虚高至 9.42。
|
||||
-- 影响:WBI、NCI、关系指数、助教服务统计、工资计算等所有依赖 cfg_skill_type JOIN 的下游。
|
||||
-- 回滚:DELETE FROM dws.cfg_skill_type WHERE skill_id IN (2790683529513797, 2790683529513798, 3039912271463941);
|
||||
|
||||
BEGIN;
|
||||
|
||||
INSERT INTO dws.cfg_skill_type (skill_id, skill_name, course_type_code, course_type_name, is_active, description)
|
||||
VALUES
|
||||
(2790683529513797, '基础课', 'BASE', '基础课', TRUE,
|
||||
'基础课:飞球系统原始课程类型,与"台球基础陪打"同类'),
|
||||
(2790683529513798, '附加课', 'BONUS', '附加课', TRUE,
|
||||
'附加课:飞球系统原始课程类型,与"台球超休服务"同类'),
|
||||
(3039912271463941, '包厢课', 'BASE', '基础课', TRUE,
|
||||
'包厢课:飞球系统原始课程类型,与"包厢服务"同类')
|
||||
ON CONFLICT (skill_id) DO NOTHING;
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- 验证 SQL(执行后应返回 6 行,且无 NULL 的 course_type_code):
|
||||
-- SELECT skill_id, skill_name, course_type_code, is_active FROM dws.cfg_skill_type ORDER BY skill_id;
|
||||
--
|
||||
-- 验证受影响会员数(应 > 0):
|
||||
-- SELECT COUNT(DISTINCT asl.tenant_member_id)
|
||||
-- FROM dwd.dwd_assistant_service_log asl
|
||||
-- JOIN dws.cfg_skill_type st ON asl.skill_id = st.skill_id
|
||||
-- WHERE asl.skill_id IN (2790683529513797, 2790683529513798, 3039912271463941)
|
||||
-- AND asl.is_delete = 0;
|
||||
--
|
||||
-- 验证梅的到店记录是否被正确识别:
|
||||
-- SELECT s.order_settle_id, s.pay_time, s.settle_type,
|
||||
-- EXISTS (
|
||||
-- SELECT 1 FROM dwd.dwd_assistant_service_log asl
|
||||
-- JOIN dws.cfg_skill_type st ON asl.skill_id = st.skill_id AND st.course_type_code = 'BONUS' AND st.is_active = TRUE
|
||||
-- WHERE asl.order_settle_id = s.order_settle_id AND asl.site_id = s.site_id AND asl.tenant_member_id = s.member_id AND asl.is_delete = 0
|
||||
-- ) AS has_bonus_service
|
||||
-- FROM dwd.dwd_settlement_head s
|
||||
-- WHERE s.member_id = 2975065345119045 AND s.pay_time >= '2026-03-08'
|
||||
-- ORDER BY s.pay_time DESC;
|
||||
@@ -0,0 +1,71 @@
|
||||
-- =============================================================================
|
||||
-- 迁移:补充 ETL 字段差异分析中识别的缺失字段
|
||||
-- 日期:2026-03-26
|
||||
-- 来源:field_gap_analysis.md 深度评估
|
||||
-- 影响:5 张 ODS 表 + 6 张 DWD 表
|
||||
-- =============================================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- ── ODS 层新增列 ──
|
||||
|
||||
-- 1. member_profiles: 4 个 API 字段
|
||||
ALTER TABLE ods.member_profiles ADD COLUMN IF NOT EXISTS other_pay_money_sum numeric(18,2);
|
||||
ALTER TABLE ods.member_profiles ADD COLUMN IF NOT EXISTS last_consume_time timestamp without time zone;
|
||||
ALTER TABLE ods.member_profiles ADD COLUMN IF NOT EXISTS non_consume_day_num integer;
|
||||
ALTER TABLE ods.member_profiles ADD COLUMN IF NOT EXISTS first_consumption integer;
|
||||
|
||||
-- 2. assistant_service_records: 1 个 API 字段
|
||||
ALTER TABLE ods.assistant_service_records ADD COLUMN IF NOT EXISTS deduct_leave_seconds integer DEFAULT 0;
|
||||
|
||||
-- 3. store_goods_sales_records: 2 个 API 字段
|
||||
ALTER TABLE ods.store_goods_sales_records ADD COLUMN IF NOT EXISTS activity_amount numeric(18,2) DEFAULT 0;
|
||||
ALTER TABLE ods.store_goods_sales_records ADD COLUMN IF NOT EXISTS activity_id bigint DEFAULT 0;
|
||||
|
||||
-- 4. goods_stock_summary: 1 个 API 字段(注意 ODS 列名全小写)
|
||||
ALTER TABLE ods.goods_stock_summary ADD COLUMN IF NOT EXISTS createtime timestamp without time zone;
|
||||
|
||||
-- 5. table_fee_transactions: order_from
|
||||
ALTER TABLE ods.table_fee_transactions ADD COLUMN IF NOT EXISTS order_from integer;
|
||||
|
||||
-- 6. assistant_service_records: order_from
|
||||
ALTER TABLE ods.assistant_service_records ADD COLUMN IF NOT EXISTS order_from integer;
|
||||
|
||||
-- 7. store_goods_sales_records: order_from
|
||||
ALTER TABLE ods.store_goods_sales_records ADD COLUMN IF NOT EXISTS order_from integer;
|
||||
|
||||
-- 8. settlement_records: order_from(来自 settleList.orderFrom,展开后为 orderfrom)
|
||||
-- 注意:settlement_records 的 ODS 列名是全小写无下划线(与 API camelCase 对应)
|
||||
ALTER TABLE ods.settlement_records ADD COLUMN IF NOT EXISTS orderfrom integer;
|
||||
|
||||
-- ── DWD 层新增列 ──
|
||||
|
||||
-- 1. dim_member_ex: 4 个字段 + birthday
|
||||
ALTER TABLE dwd.dim_member_ex ADD COLUMN IF NOT EXISTS other_pay_money_sum numeric(18,2);
|
||||
ALTER TABLE dwd.dim_member_ex ADD COLUMN IF NOT EXISTS last_consume_time timestamp with time zone;
|
||||
ALTER TABLE dwd.dim_member_ex ADD COLUMN IF NOT EXISTS non_consume_day_num integer;
|
||||
ALTER TABLE dwd.dim_member_ex ADD COLUMN IF NOT EXISTS first_consumption integer;
|
||||
-- birthday 已在 dim_member 主表中存在,无需重复添加
|
||||
|
||||
-- 2. dim_member_card_account_ex: pdassisnatlevel + cxassisnatlevel 已存在于 DDL 中
|
||||
-- 确认:goodscategoryid, tableareaid, pdassisnatlevel, cxassisnatlevel 已在 DWD DDL 和 FACT_MAPPINGS 中
|
||||
|
||||
-- 3. dwd_assistant_service_log_ex: deduct_leave_seconds + order_from
|
||||
ALTER TABLE dwd.dwd_assistant_service_log_ex ADD COLUMN IF NOT EXISTS deduct_leave_seconds integer DEFAULT 0;
|
||||
ALTER TABLE dwd.dwd_assistant_service_log_ex ADD COLUMN IF NOT EXISTS order_from integer;
|
||||
|
||||
-- 4. dwd_store_goods_sale_ex: activity_amount + activity_id + order_from
|
||||
ALTER TABLE dwd.dwd_store_goods_sale_ex ADD COLUMN IF NOT EXISTS activity_amount numeric(18,2) DEFAULT 0;
|
||||
ALTER TABLE dwd.dwd_store_goods_sale_ex ADD COLUMN IF NOT EXISTS activity_id bigint DEFAULT 0;
|
||||
ALTER TABLE dwd.dwd_store_goods_sale_ex ADD COLUMN IF NOT EXISTS order_from integer;
|
||||
|
||||
-- 5. dwd_goods_stock_summary: create_time
|
||||
ALTER TABLE dwd.dwd_goods_stock_summary ADD COLUMN IF NOT EXISTS create_time timestamp with time zone;
|
||||
|
||||
-- 6. dwd_table_fee_log_ex: order_from
|
||||
ALTER TABLE dwd.dwd_table_fee_log_ex ADD COLUMN IF NOT EXISTS order_from integer;
|
||||
|
||||
-- 7. dwd_settlement_head_ex: order_from
|
||||
ALTER TABLE dwd.dwd_settlement_head_ex ADD COLUMN IF NOT EXISTS order_from integer;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,51 @@
|
||||
-- 迁移:财务日报新增支付方式拆分字段
|
||||
-- 关联:board-finance-integration SPEC T1.1
|
||||
-- 日期:2026-03-27
|
||||
-- 回滚:见文件末尾
|
||||
|
||||
-- ============================================================
|
||||
-- 1. 新增字段
|
||||
-- ============================================================
|
||||
ALTER TABLE dws.dws_finance_daily_summary
|
||||
ADD COLUMN IF NOT EXISTS cash_paper_amount NUMERIC(14,2) DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN IF NOT EXISTS scan_pay_amount NUMERIC(14,2) DEFAULT 0 NOT NULL;
|
||||
|
||||
COMMENT ON COLUMN dws.dws_finance_daily_summary.cash_paper_amount
|
||||
IS '纸币现金收款(dwd_payment.payment_method=2)';
|
||||
COMMENT ON COLUMN dws.dws_finance_daily_summary.scan_pay_amount
|
||||
IS '扫码收款/离线支付(dwd_payment.payment_method=4,含微信/支付宝)';
|
||||
|
||||
-- ============================================================
|
||||
-- 2. 更新 RLS 视图暴露新字段
|
||||
-- ============================================================
|
||||
CREATE OR REPLACE VIEW app.v_dws_finance_daily_summary AS
|
||||
SELECT id, site_id, tenant_id, stat_date,
|
||||
gross_amount, table_fee_amount, goods_amount,
|
||||
assistant_pd_amount, assistant_cx_amount,
|
||||
discount_total, discount_groupbuy, discount_vip,
|
||||
discount_gift_card, discount_manual, discount_rounding, discount_other,
|
||||
confirmed_income,
|
||||
cash_inflow_total, cash_pay_amount,
|
||||
cash_paper_amount, scan_pay_amount,
|
||||
groupbuy_pay_amount, platform_settlement_amount, platform_fee_amount,
|
||||
recharge_cash_inflow,
|
||||
card_consume_total, recharge_card_consume AS cash_card_consume, gift_card_consume,
|
||||
cash_outflow_total, cash_balance_change,
|
||||
recharge_count, recharge_total, recharge_cash, recharge_gift,
|
||||
first_recharge_count, first_recharge_amount,
|
||||
renewal_count, renewal_amount,
|
||||
order_count, member_order_count, guest_order_count, avg_order_amount,
|
||||
created_at, updated_at
|
||||
FROM dws.dws_finance_daily_summary
|
||||
WHERE site_id = (current_setting('app.current_site_id'::text))::bigint;
|
||||
|
||||
GRANT SELECT ON app.v_dws_finance_daily_summary TO app_reader;
|
||||
|
||||
-- ============================================================
|
||||
-- 回滚
|
||||
-- ============================================================
|
||||
-- DROP VIEW IF EXISTS app.v_dws_finance_daily_summary;
|
||||
-- ALTER TABLE dws.dws_finance_daily_summary
|
||||
-- DROP COLUMN IF EXISTS cash_paper_amount,
|
||||
-- DROP COLUMN IF EXISTS scan_pay_amount;
|
||||
-- 然后重建原始视图(见 docs/database/ddl/etl_feiqiu__app.sql)
|
||||
@@ -0,0 +1,106 @@
|
||||
-- 迁移:创建区域日粒度财务原子层表 dws_finance_area_daily 及 RLS 视图
|
||||
-- 关联:board-finance-dws-area-refactor SPEC T2.1
|
||||
-- 日期:2026-03-28
|
||||
-- 回滚:见文件末尾
|
||||
|
||||
-- ============================================================
|
||||
-- 1. 创建 dws_finance_area_daily 表
|
||||
-- ============================================================
|
||||
CREATE TABLE dws.dws_finance_area_daily (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
site_id BIGINT NOT NULL,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
stat_date DATE NOT NULL,
|
||||
area_code VARCHAR(20) NOT NULL,
|
||||
|
||||
-- 收入结构(4 项 + gross_amount)
|
||||
table_fee_amount NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
goods_amount NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
assistant_pd_amount NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
assistant_cx_amount NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
gross_amount NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
|
||||
-- 优惠拆分(6 项 + discount_total)
|
||||
-- 恒等式:discount_total = groupbuy + vip + manual + gift_card + rounding + other
|
||||
discount_groupbuy NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
discount_vip NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
discount_manual NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
discount_gift_card NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
discount_rounding NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
discount_other NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
discount_total NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
|
||||
-- 确认收入(gross_amount - discount_total)
|
||||
confirmed_income NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
|
||||
-- 现金流(仅 area_code='all' 时有效值,其余区域为 0)
|
||||
cash_pay_amount NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
cash_paper_amount NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
scan_pay_amount NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
groupbuy_pay_amount NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
recharge_cash_inflow NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
cash_inflow_total NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
cash_outflow_total NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
cash_balance_change NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
|
||||
-- 卡消费(仅 area_code='all' 时有效值)
|
||||
card_consume_total NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
recharge_card_consume NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
gift_card_consume NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
|
||||
-- 充值(仅 area_code='all' 时有效值)
|
||||
recharge_cash NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
first_recharge_cash NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
renewal_cash NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
|
||||
-- 订单统计
|
||||
order_count INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
-- 元数据
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
-- 唯一约束:每个站点每天每区域一行
|
||||
UNIQUE (site_id, stat_date, area_code)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE dws.dws_finance_area_daily
|
||||
IS '区域日粒度财务原子层表,按 (site_id, stat_date, area_code) 存储 9 个区域的收入/优惠/现金流预计算数据';
|
||||
|
||||
COMMENT ON COLUMN dws.dws_finance_area_daily.area_code
|
||||
IS '区域编码:all/hall/hallA/hallB/hallC/vip/snooker/mahjong/ktv';
|
||||
COMMENT ON COLUMN dws.dws_finance_area_daily.gross_amount
|
||||
IS '毛收入 = table_fee_amount + goods_amount + assistant_pd_amount + assistant_cx_amount';
|
||||
COMMENT ON COLUMN dws.dws_finance_area_daily.discount_total
|
||||
IS '优惠合计 = groupbuy + vip + manual + gift_card + rounding + other';
|
||||
COMMENT ON COLUMN dws.dws_finance_area_daily.confirmed_income
|
||||
IS '确认收入 = gross_amount - discount_total';
|
||||
COMMENT ON COLUMN dws.dws_finance_area_daily.discount_gift_card
|
||||
IS '赠送卡消费金额口径(非结算单 gift_card_amount)';
|
||||
|
||||
-- ============================================================
|
||||
-- 2. 创建 RLS 视图
|
||||
-- ============================================================
|
||||
CREATE OR REPLACE VIEW dws.v_dws_finance_area_daily AS
|
||||
SELECT *
|
||||
FROM dws.dws_finance_area_daily
|
||||
WHERE site_id = (current_setting('app.current_site_id'::text))::bigint;
|
||||
|
||||
GRANT SELECT ON dws.v_dws_finance_area_daily TO app_reader;
|
||||
|
||||
-- ============================================================
|
||||
-- 3. 创建 app schema 导出视图(后端通过 app.v_* 访问)
|
||||
-- ============================================================
|
||||
CREATE OR REPLACE VIEW app.v_dws_finance_area_daily AS
|
||||
SELECT *
|
||||
FROM dws.dws_finance_area_daily
|
||||
WHERE site_id = (current_setting('app.current_site_id'::text))::bigint;
|
||||
|
||||
GRANT SELECT ON app.v_dws_finance_area_daily TO app_reader;
|
||||
|
||||
-- ============================================================
|
||||
-- 回滚
|
||||
-- ============================================================
|
||||
-- DROP VIEW IF EXISTS app.v_dws_finance_area_daily;
|
||||
-- DROP VIEW IF EXISTS dws.v_dws_finance_area_daily;
|
||||
-- DROP TABLE IF EXISTS dws.dws_finance_area_daily;
|
||||
@@ -0,0 +1,102 @@
|
||||
-- 迁移:创建看板缓存层表 dws_finance_board_cache 及 RLS 视图
|
||||
-- 关联:board-finance-dws-area-refactor SPEC T5.1
|
||||
-- 日期:2026-03-28
|
||||
-- 回滚:见文件末尾
|
||||
|
||||
-- ============================================================
|
||||
-- 1. 创建 dws_finance_board_cache 表
|
||||
-- ============================================================
|
||||
CREATE TABLE dws.dws_finance_board_cache (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
site_id BIGINT NOT NULL,
|
||||
time_range VARCHAR(20) NOT NULL,
|
||||
area_code VARCHAR(20) NOT NULL,
|
||||
start_date DATE NOT NULL,
|
||||
end_date DATE NOT NULL,
|
||||
prev_start_date DATE,
|
||||
prev_end_date DATE,
|
||||
|
||||
-- overview 8 项核心指标
|
||||
occurrence NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
discount NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
discount_rate NUMERIC(8,4) NOT NULL DEFAULT 0,
|
||||
confirmed_revenue NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
cash_in NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
cash_out NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
cash_balance NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||||
balance_rate NUMERIC(8,4) NOT NULL DEFAULT 0,
|
||||
|
||||
-- 数据指纹(MD5,用于检测源数据变化触发缓存失效)
|
||||
data_fingerprint VARCHAR(64),
|
||||
computed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
-- 元数据
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
-- 唯一约束:每个站点每个时间范围每区域一行
|
||||
UNIQUE (site_id, time_range, area_code)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE dws.dws_finance_board_cache
|
||||
IS '看板缓存层表,缓存已完成周期的 overview 聚合结果,按 (site_id, time_range, area_code) 唯一';
|
||||
|
||||
COMMENT ON COLUMN dws.dws_finance_board_cache.time_range
|
||||
IS '时间范围:lastMonth/lastWeek/lastQuarter/quarter3/half6(仅已完成周期缓存)';
|
||||
COMMENT ON COLUMN dws.dws_finance_board_cache.area_code
|
||||
IS '区域编码:all/hall/hallA/hallB/hallC/vip/snooker/mahjong/ktv';
|
||||
COMMENT ON COLUMN dws.dws_finance_board_cache.start_date
|
||||
IS '当期起始日期';
|
||||
COMMENT ON COLUMN dws.dws_finance_board_cache.end_date
|
||||
IS '当期结束日期';
|
||||
COMMENT ON COLUMN dws.dws_finance_board_cache.prev_start_date
|
||||
IS '上期起始日期(环比用),无上期时为 NULL';
|
||||
COMMENT ON COLUMN dws.dws_finance_board_cache.prev_end_date
|
||||
IS '上期结束日期(环比用),无上期时为 NULL';
|
||||
COMMENT ON COLUMN dws.dws_finance_board_cache.occurrence
|
||||
IS '发生额(毛收入 gross_amount 的周期汇总)';
|
||||
COMMENT ON COLUMN dws.dws_finance_board_cache.discount
|
||||
IS '优惠合计(discount_total 的周期汇总)';
|
||||
COMMENT ON COLUMN dws.dws_finance_board_cache.discount_rate
|
||||
IS '优惠占比 = discount / occurrence';
|
||||
COMMENT ON COLUMN dws.dws_finance_board_cache.confirmed_revenue
|
||||
IS '确认收入 = occurrence - discount';
|
||||
COMMENT ON COLUMN dws.dws_finance_board_cache.cash_in
|
||||
IS '现金流入合计(仅 area_code=all 时有效值)';
|
||||
COMMENT ON COLUMN dws.dws_finance_board_cache.cash_out
|
||||
IS '现金流出合计(仅 area_code=all 时有效值)';
|
||||
COMMENT ON COLUMN dws.dws_finance_board_cache.cash_balance
|
||||
IS '现金余额变动 = cash_in - cash_out';
|
||||
COMMENT ON COLUMN dws.dws_finance_board_cache.balance_rate
|
||||
IS '余额变动率';
|
||||
COMMENT ON COLUMN dws.dws_finance_board_cache.data_fingerprint
|
||||
IS '源数据指纹(MD5),用于检测补录导致的数据变化';
|
||||
COMMENT ON COLUMN dws.dws_finance_board_cache.computed_at
|
||||
IS '缓存计算时间';
|
||||
|
||||
-- ============================================================
|
||||
-- 2. 创建 RLS 视图
|
||||
-- ============================================================
|
||||
CREATE OR REPLACE VIEW dws.v_dws_finance_board_cache AS
|
||||
SELECT *
|
||||
FROM dws.dws_finance_board_cache
|
||||
WHERE site_id = (current_setting('app.current_site_id'::text))::bigint;
|
||||
|
||||
GRANT SELECT ON dws.v_dws_finance_board_cache TO app_reader;
|
||||
|
||||
-- ============================================================
|
||||
-- 3. 创建 app schema 导出视图(后端通过 app.v_* 访问)
|
||||
-- ============================================================
|
||||
CREATE OR REPLACE VIEW app.v_dws_finance_board_cache AS
|
||||
SELECT *
|
||||
FROM dws.dws_finance_board_cache
|
||||
WHERE site_id = (current_setting('app.current_site_id'::text))::bigint;
|
||||
|
||||
GRANT SELECT ON app.v_dws_finance_board_cache TO app_reader;
|
||||
|
||||
-- ============================================================
|
||||
-- 回滚
|
||||
-- ============================================================
|
||||
-- DROP VIEW IF EXISTS app.v_dws_finance_board_cache;
|
||||
-- DROP VIEW IF EXISTS dws.v_dws_finance_board_cache;
|
||||
-- DROP TABLE IF EXISTS dws.dws_finance_board_cache;
|
||||
@@ -0,0 +1,13 @@
|
||||
-- 回滚脚本:board-finance-dws-area-refactor
|
||||
-- 逆序 DROP VIEW → DROP TABLE
|
||||
-- 日期:2026-03-28
|
||||
--
|
||||
-- 执行顺序:先删缓存层(依赖日粒度),再删日粒度
|
||||
|
||||
-- 1. 删除缓存层 RLS 视图和表
|
||||
DROP VIEW IF EXISTS dws.v_dws_finance_board_cache;
|
||||
DROP TABLE IF EXISTS dws.dws_finance_board_cache;
|
||||
|
||||
-- 2. 删除日粒度 RLS 视图和表
|
||||
DROP VIEW IF EXISTS dws.v_dws_finance_area_daily;
|
||||
DROP TABLE IF EXISTS dws.dws_finance_area_daily;
|
||||
@@ -0,0 +1,30 @@
|
||||
-- 关系指数表新增任务统计字段(2026-03-31)
|
||||
-- C: 历史总计字段
|
||||
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE dws.dws_member_assistant_relation_index
|
||||
ADD COLUMN IF NOT EXISTS recall_created_total INT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS recall_completed_total INT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS follow_up_created_total INT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS follow_up_completed_total INT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS total_created INT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS total_completed INT NOT NULL DEFAULT 0;
|
||||
|
||||
COMMENT ON COLUMN dws.dws_member_assistant_relation_index.recall_created_total IS '历史累计召回任务创建数';
|
||||
COMMENT ON COLUMN dws.dws_member_assistant_relation_index.recall_completed_total IS '历史累计召回任务完成数';
|
||||
COMMENT ON COLUMN dws.dws_member_assistant_relation_index.follow_up_created_total IS '历史累计回访任务创建数';
|
||||
COMMENT ON COLUMN dws.dws_member_assistant_relation_index.follow_up_completed_total IS '历史累计回访任务完成数';
|
||||
COMMENT ON COLUMN dws.dws_member_assistant_relation_index.total_created IS '历史累计任务创建总数';
|
||||
COMMENT ON COLUMN dws.dws_member_assistant_relation_index.total_completed IS '历史累计任务完成总数';
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- ROLLBACK:
|
||||
-- ALTER TABLE dws.dws_member_assistant_relation_index
|
||||
-- DROP COLUMN IF EXISTS recall_created_total,
|
||||
-- DROP COLUMN IF EXISTS recall_completed_total,
|
||||
-- DROP COLUMN IF EXISTS follow_up_created_total,
|
||||
-- DROP COLUMN IF EXISTS follow_up_completed_total,
|
||||
-- DROP COLUMN IF EXISTS total_created,
|
||||
-- DROP COLUMN IF EXISTS total_completed;
|
||||
@@ -0,0 +1,95 @@
|
||||
-- AI_CHANGELOG
|
||||
-- | 日期 | Prompt | 变更 |
|
||||
-- |------|--------|------|
|
||||
-- | 2026-03-23 | 角色体系隔离+店铺管理员 | 清理小程序 RBAC 中的 site_admin/tenant_admin;新增 head_coach/manager;tenant_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_permissions(site_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'
|
||||
@@ -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;
|
||||
78
db/zqyy_app/migrations/2026-03-27__fix_role_permissions.sql
Normal file
78
db/zqyy_app/migrations/2026-03-27__fix_role_permissions.sql
Normal file
@@ -0,0 +1,78 @@
|
||||
-- AI_CHANGELOG
|
||||
-- | 日期 | Prompt | 变更 |
|
||||
-- |------|--------|------|
|
||||
-- | 2026-03-27 | 权限改造 W3 | 修正角色-权限码映射:coach 仅 view_tasks,staff 仅 view_board+customer+coach,head_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}
|
||||
18
db/zqyy_app/migrations/2026-03-29__os_escalation_params.sql
Normal file
18
db/zqyy_app/migrations/2026-03-29__os_escalation_params.sql
Normal 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');
|
||||
@@ -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';
|
||||
47
db/zqyy_app/migrations/2026-03-31__task_stats_tables.sql
Normal file
47
db/zqyy_app/migrations/2026-03-31__task_stats_tables.sql
Normal 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;
|
||||
@@ -0,0 +1,42 @@
|
||||
-- =============================================================================
|
||||
-- 迁移:member_retention_clue 新增 is_hidden 列(维客线索隐藏功能)
|
||||
-- 日期:2026-03-20
|
||||
-- 关联 SPEC:tenant-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=管理后台保留但小程序不展示)'
|
||||
@@ -0,0 +1,217 @@
|
||||
-- =============================================================================
|
||||
-- 迁移:NS4 租户管理后台 — 新建表
|
||||
-- 日期:2026-03-20
|
||||
-- 关联 SPEC:tenant-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 行
|
||||
@@ -0,0 +1,9 @@
|
||||
-- 2026-03-22 | 为 task_execution_log 添加 config JSONB 列
|
||||
-- 用途:存储完整 TaskConfig JSON,rerun 时还原原始参数(processing_mode、lookback_hours 等)
|
||||
-- 旧记录 config 为 NULL,rerun 时回退到默认配置
|
||||
|
||||
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 时还原原始参数';
|
||||
@@ -0,0 +1,173 @@
|
||||
-- =============================================================================
|
||||
-- 迁移:NS4.1 注册体系 — 连接器/租户/店铺/简写ID历史 四张新表
|
||||
-- 日期:2026-03-22
|
||||
-- 关联 SPEC:admin-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_history(FK 依赖链)
|
||||
-- 种子数据:飞球连接器 + 朗朗桌球租户
|
||||
-- 数据迁移: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, -- 当前生效的简写ID(6位,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 '当前生效的简写ID,6位字符(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, -- 店铺 ID(biz.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;
|
||||
164
db/zqyy_app/migrations/_archived/2026-03-22__p14_ai_module.sql
Normal file
164
db/zqyy_app/migrations/_archived/2026-03-22__p14_ai_module.sql
Normal file
@@ -0,0 +1,164 @@
|
||||
-- =============================================================================
|
||||
-- 迁移:P14 — AI 模块改造(DashScope 迁移 + 调度器完善)
|
||||
-- 日期:2026-03-22
|
||||
-- 关联 SPEC:P14-ai-dashscope-migration
|
||||
-- 需求:14.1, 14.2, 14.3, 14.4, 14.5, 14.6, 14.7
|
||||
-- 说明:
|
||||
-- 1. 新建 biz.ai_run_logs(AI 运行记录,含 3 个索引)
|
||||
-- 2. 新建 biz.ai_trigger_jobs(调度运行记录,含 3 个索引,含去重部分索引)
|
||||
-- 3. ai_conversations 新增 session_id 字段(百炼会话 ID)
|
||||
-- 4. ai_cache 新增 status 字段 + CHECK 约束
|
||||
-- =============================================================================
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 1. 新建 biz.ai_run_logs(AI 运行记录)
|
||||
-- ---------------------------------------------------------------------------
|
||||
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 行
|
||||
@@ -0,0 +1,78 @@
|
||||
-- =============================================================================
|
||||
-- 迁移:P16 调度任务最小运行间隔 — scheduled_tasks 新增字段
|
||||
-- 日期:2026-03-22
|
||||
-- 关联 SPEC:admin-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
|
||||
@@ -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');
|
||||
@@ -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'));
|
||||
@@ -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
|
||||
@@ -0,0 +1,43 @@
|
||||
-- P15:AI 监控后台 — 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;
|
||||
@@ -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"}}';
|
||||
@@ -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
|
||||
@@ -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 值一旦添加无法直接删除,需重建类型
|
||||
@@ -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';
|
||||
@@ -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);
|
||||
@@ -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' 关键词
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user