feat(收口): 补齐 8 月迭代缺口并同步 Spec 与证据链
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
- 新增六对成对迁移 000232–000237:H5 弹窗类型、退款结算标识与申请人备注、优先轮询事实字段与两个新终态、通道阈值命中留痕、手机号最近解绑人、提现资格校验留痕 - 退款:原因必填与申请人备注、来源支付与渠道流水冻结、线下处理流水号补录审计、按订单查询可选退款方式、企微审批材料补齐且新增字段缺失映射即明确失败 - 优先轮询:人工关闭、有效期到期独立周期任务、失败与过期人工重触发、事实字段与异常重试查询、资产解析端点只读投影 - 通道阈值:命中事实同事务留痕与命中记录查询;员工账单:列表筛选与详情投影;商户池:列表投影与统计周期语义;H5:弹窗类型与类别排序 - 手机号:有效关联数量与最近解绑人、短信验证码失败次数限制;导出:佣金明细十五列与报表序号列 - 时间筛选:三处新增筛选纳入统一严格解析契约,员工账单产生时间参数改名 - 同步 12 份主 Spec 需求、两端点与异步任务证据链,门禁 context-health 与 OpenSpec 校验通过
This commit is contained in:
14
migrations/000232_add_h5_popup_type.down.sql
Normal file
14
migrations/000232_add_h5_popup_type.down.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
-- 回滚 H5 运营弹窗类型的补齐。
|
||||
-- 先删取值约束再删列,与 up 的创建顺序严格倒序。
|
||||
--
|
||||
-- 不可逆说明(ENG-MIG-001 例外条件):本迁移的 down 会删除 tb_h5_popup_configuration.popup_type。
|
||||
-- 弹窗类型是运营配置事实,删除后无法由数据库自身重建;恢复方式为重新执行 up,
|
||||
-- 但既有配置会统一回落到默认值 announcement,原先的 promotion 类型只能依据
|
||||
-- tb_audit_event 中 h5_popup_configuration 的前后值快照人工复核,因此 down 只在类型事实
|
||||
-- 已无留存需求时执行。
|
||||
|
||||
ALTER TABLE tb_h5_popup_configuration
|
||||
DROP CONSTRAINT IF EXISTS chk_h5_popup_configuration_popup_type;
|
||||
|
||||
ALTER TABLE tb_h5_popup_configuration
|
||||
DROP COLUMN IF EXISTS popup_type;
|
||||
23
migrations/000232_add_h5_popup_type.up.sql
Normal file
23
migrations/000232_add_h5_popup_type.up.sql
Normal file
@@ -0,0 +1,23 @@
|
||||
-- H5 运营弹窗配置补齐「弹窗类型」。
|
||||
-- 背景:111.md §13.3 要求运营弹窗区分「套餐政策推广」与「通用公告」,并规定类别优先级
|
||||
-- 为「风险换卡提醒 > 套餐政策推广 > 通用公告」。既有 tb_h5_popup_configuration 只有
|
||||
-- 一个 priority 列,无法在数据上区分两类运营弹窗,也无法表达「类别优先于显式优先级」。
|
||||
--
|
||||
-- 设计选择:
|
||||
-- 1. 新增 popup_type,取值仅 promotion(套餐政策推广)与 announcement(通用公告),
|
||||
-- 非空且默认 announcement:既有配置在 up 时全部获得确定类型,不需要额外回填语句。
|
||||
-- 默认值同时兜住回滚期(旧代码不写该列)与手工插入路径,避免出现无类型配置。
|
||||
-- 2. 类型只决定候选排序的第一顺位类别,不新增排序冗余列:类别顺序由查询侧的显式排序
|
||||
-- 表达式(CASE)表达,MUST NOT 依赖取值字典序(announcement 字典序小于 promotion,
|
||||
-- 与业务顺序相反)。既有匹配索引继续服务启用过滤、时间窗口与同类别内排序。
|
||||
-- 3. 既有配置的显式 priority 与同类别内相对顺序不变:本迁移只加列,不改写任何既有值。
|
||||
|
||||
ALTER TABLE tb_h5_popup_configuration
|
||||
ADD COLUMN popup_type VARCHAR(20) NOT NULL DEFAULT 'announcement';
|
||||
|
||||
COMMENT ON COLUMN tb_h5_popup_configuration.popup_type IS '弹窗类型 promotion-套餐政策推广 announcement-通用公告;类别优先级为风险换卡提醒 > 套餐政策推广 > 通用公告,类别顺序由查询侧显式排序表达式表达,不依赖取值字典序';
|
||||
|
||||
-- 取值域在 Schema 层兜住:类型参与候选排序类别判定,落库非法值会让该配置在排序中
|
||||
-- 落到未定义类别,属于必须在数据库层拒绝的半配置状态。
|
||||
ALTER TABLE tb_h5_popup_configuration
|
||||
ADD CONSTRAINT chk_h5_popup_configuration_popup_type CHECK (popup_type IN ('promotion', 'announcement'));
|
||||
13
migrations/000233_extend_refund_settlement_fields.down.sql
Normal file
13
migrations/000233_extend_refund_settlement_fields.down.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
-- 回滚 000233:删除退款结算标识与申请人备注新增列。
|
||||
-- 新列在回滚前可能已写入业务事实(来源支付标识、线下补录流水号、申请人备注),
|
||||
-- 回滚会丢弃这些列及其内容;回退前如需保留,应先导出对应列再执行。
|
||||
|
||||
ALTER TABLE tb_refund_request
|
||||
DROP COLUMN IF EXISTS source_payment_no,
|
||||
DROP COLUMN IF EXISTS original_channel_trade_no,
|
||||
DROP COLUMN IF EXISTS offline_settlement_no,
|
||||
DROP COLUMN IF EXISTS offline_settled_at,
|
||||
DROP COLUMN IF EXISTS offline_settled_by;
|
||||
|
||||
ALTER TABLE tb_refund_request_attempt
|
||||
DROP COLUMN IF EXISTS remark;
|
||||
33
migrations/000233_extend_refund_settlement_fields.up.sql
Normal file
33
migrations/000233_extend_refund_settlement_fields.up.sql
Normal file
@@ -0,0 +1,33 @@
|
||||
-- 退款链路补齐结算标识与申请人备注。
|
||||
-- 背景:退款单需要可追溯「原支付单号」与「原支付渠道交易流水号」,财务需要登记线下退款处理流水号;
|
||||
-- 退款申请还需要支持可选的申请人备注,且备注必须随每次提交或重提冻结进当次审批尝试快照。
|
||||
--
|
||||
-- 设计选择:
|
||||
-- 1. 来源支付单号与原支付渠道交易流水号直接冻结在退款主表:这两项来自创建申请时的原成功支付事实,
|
||||
-- 读取时跨订单与支付单推导无法表达「冻结」语义(支付单后续改挂或缺失会让结果漂移),也不满足对账需要的稳定性。
|
||||
-- 线下订单(后台线下套餐订单、员工代收订单)没有线上支付记录,两个字段以空字符串保存,不阻断申请创建。
|
||||
-- 2. 线下退款处理流水号、登记时间与登记人为财务补录事实:允许在申请创建后由授权账号补录或更正,
|
||||
-- 每次登记写审计(操作者、时间、前后值)。补录不改变退款状态、实收金额、套餐失效与佣金回溯规则,
|
||||
-- 因此与既有渠道退款流水号(channel_refund_no,渠道原路退款结果)是两列不同语义,不得互相覆盖。
|
||||
-- 3. 申请人备注落在审批尝试表而非退款主表:主表既有 remark 列语义为「审批备注」(审批人填写、随审批结果展示),
|
||||
-- 本变更 MUST NOT 复用或改写该列。备注随每次提交或重提写入一条新的尝试记录,历史尝试快照不被改写。
|
||||
-- 4. 新列一律可空或带默认值,既有行零变更,读侧对空值按空处理。
|
||||
|
||||
ALTER TABLE tb_refund_request
|
||||
ADD COLUMN IF NOT EXISTS source_payment_no VARCHAR(64) NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS original_channel_trade_no VARCHAR(100) NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS offline_settlement_no VARCHAR(128) NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS offline_settled_at TIMESTAMPTZ,
|
||||
ADD COLUMN IF NOT EXISTS offline_settled_by BIGINT NOT NULL DEFAULT 0;
|
||||
|
||||
COMMENT ON COLUMN tb_refund_request.source_payment_no IS '冻结的来源支付单号:取创建申请时该订单原成功支付记录的支付单号;线下订单无线上支付记录时为空字符串';
|
||||
COMMENT ON COLUMN tb_refund_request.original_channel_trade_no IS '冻结的原支付渠道交易流水号:取创建申请时原成功支付记录的渠道交易流水号,列宽与来源列 tb_payment.third_party_trade_no 一致;线下订单无线上支付记录时为空字符串';
|
||||
COMMENT ON COLUMN tb_refund_request.offline_settlement_no IS '线下退款处理流水号或凭证编号,由授权账号补录或更正;与渠道原路退款流水号(channel_refund_no)语义不同,不参与资金与状态判定';
|
||||
COMMENT ON COLUMN tb_refund_request.offline_settled_at IS '线下退款处理流水号最近一次登记或更正时间,未登记时为空';
|
||||
COMMENT ON COLUMN tb_refund_request.offline_settled_by IS '线下退款处理流水号最近一次登记或更正的操作账号ID,0-未登记';
|
||||
|
||||
-- 退款审批尝试表新增申请人备注:每次提交或重提一条尝试,备注逐次冻结,历史尝试不被改写。
|
||||
ALTER TABLE tb_refund_request_attempt
|
||||
ADD COLUMN IF NOT EXISTS remark TEXT NOT NULL DEFAULT '';
|
||||
|
||||
COMMENT ON COLUMN tb_refund_request_attempt.remark IS '本次提交的申请人备注快照,随提交与重提逐次冻结,历史尝试不被改写;与退款主表 remark(审批备注)语义与展示口径不同,不得复用或互相覆盖';
|
||||
66
migrations/000234_extend_polling_priority_item.down.sql
Normal file
66
migrations/000234_extend_polling_priority_item.down.sql
Normal file
@@ -0,0 +1,66 @@
|
||||
-- 回滚卡轮询优先队列的事实字段补齐与两个新终态,与 up 严格成对,顺序与 up 的创建顺序严格倒序。
|
||||
--
|
||||
-- 不可逆说明(ENG-MIG-001 例外条件):本迁移的 down 会删除资产类型与资产ID、设备号快照、
|
||||
-- 代理归属、尝试上限、执行起止时间、下一次计划执行时间、出队时间、集成交互日志标识与
|
||||
-- 有效期起算时间等列。这些事实(尤其是「为什么出队、什么时候执行过、上游交互日志标识」)
|
||||
-- 只由本表承载,删除后无法由数据库自身重建,因此 down 只在上述事实已无留存需求时执行。
|
||||
--
|
||||
-- 守卫:存在已关闭或已过期终态的行时拒绝回滚——旧状态约束不允许这两个取值,
|
||||
-- 回滚会连带丢失「人工关闭」与「有效期到期」两个出口留下的终态事实。
|
||||
|
||||
BEGIN;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF to_regclass('tb_polling_priority_item') IS NOT NULL THEN
|
||||
-- 同一事务内先取排他锁再检查,避免检查与回滚之间写入新的已关闭/已过期事实。
|
||||
LOCK TABLE tb_polling_priority_item IN ACCESS EXCLUSIVE MODE;
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM tb_polling_priority_item
|
||||
WHERE status IN ('closed', 'expired')
|
||||
LIMIT 1
|
||||
) THEN
|
||||
RAISE EXCEPTION '存在已关闭或已过期的卡轮询优先项,拒绝回滚以避免丢失人工关闭与有效期到期终态事实,请人工核对后向前修复';
|
||||
END IF;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
DROP INDEX IF EXISTS idx_polling_priority_item_next_run;
|
||||
|
||||
ALTER TABLE tb_polling_priority_item
|
||||
DROP CONSTRAINT IF EXISTS ck_polling_priority_item_attempt_limit,
|
||||
DROP CONSTRAINT IF EXISTS ck_polling_priority_item_asset,
|
||||
DROP CONSTRAINT IF EXISTS ck_polling_priority_item_asset_type;
|
||||
|
||||
-- 恢复 000228 的状态约束与终态-结果约束原文。
|
||||
ALTER TABLE tb_polling_priority_item
|
||||
DROP CONSTRAINT IF EXISTS ck_polling_priority_item_terminal_result;
|
||||
|
||||
ALTER TABLE tb_polling_priority_item
|
||||
ADD CONSTRAINT ck_polling_priority_item_terminal_result CHECK (
|
||||
(status = 'completed' AND result = 'success')
|
||||
OR (status = 'failed' AND result = 'failed')
|
||||
OR status IN ('pending', 'processing')
|
||||
);
|
||||
|
||||
ALTER TABLE tb_polling_priority_item
|
||||
DROP CONSTRAINT IF EXISTS ck_polling_priority_item_status;
|
||||
|
||||
ALTER TABLE tb_polling_priority_item
|
||||
ADD CONSTRAINT ck_polling_priority_item_status CHECK (status IN ('pending', 'processing', 'completed', 'failed'));
|
||||
|
||||
ALTER TABLE tb_polling_priority_item
|
||||
DROP COLUMN IF EXISTS priority_effective_from,
|
||||
DROP COLUMN IF EXISTS integration_log_id,
|
||||
DROP COLUMN IF EXISTS dequeued_at,
|
||||
DROP COLUMN IF EXISTS next_run_at,
|
||||
DROP COLUMN IF EXISTS finished_at,
|
||||
DROP COLUMN IF EXISTS started_at,
|
||||
DROP COLUMN IF EXISTS attempt_limit,
|
||||
DROP COLUMN IF EXISTS agent_shop_id_snapshot,
|
||||
DROP COLUMN IF EXISTS device_no_snapshot,
|
||||
DROP COLUMN IF EXISTS asset_id,
|
||||
DROP COLUMN IF EXISTS asset_type;
|
||||
|
||||
COMMIT;
|
||||
116
migrations/000234_extend_polling_priority_item.up.sql
Normal file
116
migrations/000234_extend_polling_priority_item.up.sql
Normal file
@@ -0,0 +1,116 @@
|
||||
-- 卡轮询优先队列补齐事实字段与两个新终态。
|
||||
-- 背景:111.md §23.4/§23.6/§23.7/§23.9/§23.10 要求优先项可追溯资产、设备号、店铺与代理归属、
|
||||
-- 尝试上限、执行起止时间、下一次执行时间、出队时间、集成交互日志标识与有效期起算时间,并要求
|
||||
-- 支持「有效期到期出队」与「人工关闭出队」两个出口。既有 tb_polling_priority_item 只有执行结果、
|
||||
-- 失败原因与尝试次数,读侧无法解释「什么时候执行、下一次什么时候执行、为什么出队」,
|
||||
-- 也没有承载已关闭与已过期两个终态的取值空间。
|
||||
--
|
||||
-- 设计选择:
|
||||
-- 1. 只加列与重建受影响的 CHECK,不改活动项唯一键:活动集合仍是 pending/processing,
|
||||
-- 部分唯一索引谓词 status IN ('pending','processing') 保持原样,两个新终态不占键位,
|
||||
-- 同一卡同一任务类型出队后可再次入队或人工重触发。
|
||||
-- 2. 资产列(asset_type/asset_id)按既有设备—卡绑定回填:卡已绑定设备时资产为该设备,
|
||||
-- 独立卡回填为卡自身;设备号快照取绑定设备的虚拟号,未绑定设备时为空字符串。
|
||||
-- 两列允许空值/空串(CHECK 兼容旧代码回滚期不写这两列的插入),新代码入队必写。
|
||||
-- 3. priority_effective_from 是有效期起算时间:既有活动项在 up 中回填为迁移时刻,
|
||||
-- 使上线后首轮到期扫描不会把既有未完成项批量判为已过期;终态行留空(已出队,不再计算有效期)。
|
||||
-- 新增行取入队时间。有效期长度与尝试上限均为代码常量,本迁移不新增可维护配置项。
|
||||
-- 4. attempt_limit 默认 3 与既有代码常量一致,既有行随默认值获得尝试上限。
|
||||
-- 5. next_run_at 可空:既有行没有下次计划执行时间,读侧 MUST NOT 据此判定已过期。
|
||||
-- 为它建部分索引以支撑到期扫描与读侧筛选。
|
||||
-- 6. 状态 CHECK 重建并新增 closed-已关闭 expired-已过期 两个终态;终态与结果约束同步放宽,
|
||||
-- 使两个新终态可以保留既有执行结果与失败原因而不被约束拒绝。
|
||||
-- 7. 不使用数据库外键;新增列由应用层显式写入。
|
||||
|
||||
ALTER TABLE tb_polling_priority_item
|
||||
ADD COLUMN asset_type VARCHAR(20) NOT NULL DEFAULT '',
|
||||
ADD COLUMN asset_id BIGINT,
|
||||
ADD COLUMN device_no_snapshot VARCHAR(100) NOT NULL DEFAULT '',
|
||||
ADD COLUMN agent_shop_id_snapshot BIGINT,
|
||||
ADD COLUMN attempt_limit INT NOT NULL DEFAULT 3,
|
||||
ADD COLUMN started_at TIMESTAMPTZ,
|
||||
ADD COLUMN finished_at TIMESTAMPTZ,
|
||||
ADD COLUMN next_run_at TIMESTAMPTZ,
|
||||
ADD COLUMN dequeued_at TIMESTAMPTZ,
|
||||
ADD COLUMN integration_log_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||
ADD COLUMN priority_effective_from TIMESTAMPTZ;
|
||||
|
||||
-- 资产列回填:优先取该卡当前有效的设备绑定(同一卡存在多条绑定时取 is_current 优先、其次最新一条)。
|
||||
UPDATE tb_polling_priority_item AS i
|
||||
SET asset_type = 'device',
|
||||
asset_id = b.device_id,
|
||||
device_no_snapshot = COALESCE(d.virtual_no, '')
|
||||
FROM (
|
||||
SELECT DISTINCT ON (iot_card_id) iot_card_id, device_id
|
||||
FROM tb_device_sim_binding
|
||||
WHERE bind_status = 1 AND deleted_at IS NULL
|
||||
ORDER BY iot_card_id, is_current DESC, id DESC
|
||||
) AS b
|
||||
LEFT JOIN tb_device AS d ON d.id = b.device_id AND d.deleted_at IS NULL
|
||||
WHERE b.iot_card_id = i.card_id;
|
||||
|
||||
-- 未被上一步覆盖的行即独立卡:资产为卡自身。
|
||||
UPDATE tb_polling_priority_item
|
||||
SET asset_type = 'iot_card',
|
||||
asset_id = card_id
|
||||
WHERE asset_id IS NULL;
|
||||
|
||||
-- 代理归属回填:卡所属店铺的直接上级代理店铺(一级代理与平台卡为空)。
|
||||
UPDATE tb_polling_priority_item AS i
|
||||
SET agent_shop_id_snapshot = s.parent_id
|
||||
FROM tb_iot_card AS c
|
||||
JOIN tb_shop AS s ON s.id = c.shop_id AND s.deleted_at IS NULL
|
||||
WHERE c.id = i.card_id
|
||||
AND c.deleted_at IS NULL
|
||||
AND s.parent_id IS NOT NULL;
|
||||
|
||||
-- 既有活动项的有效期自迁移时刻起算,避免上线后被首轮到期扫描批量过期。
|
||||
UPDATE tb_polling_priority_item
|
||||
SET priority_effective_from = NOW()
|
||||
WHERE deleted_at IS NULL AND status IN ('pending', 'processing');
|
||||
|
||||
COMMENT ON COLUMN tb_polling_priority_item.asset_type IS '资产类型 iot_card-物联网卡 device-设备,与 pkg/constants.AssetTypeIotCard/AssetTypeDevice 一致;卡绑定设备时资产为设备,独立卡为卡自身';
|
||||
COMMENT ON COLUMN tb_polling_priority_item.asset_id IS '资产ID,与 asset_type 共同构成资产身份;入队时按设备—卡绑定解析';
|
||||
COMMENT ON COLUMN tb_polling_priority_item.device_no_snapshot IS '设备号快照(绑定设备的虚拟号),独立卡为空字符串';
|
||||
COMMENT ON COLUMN tb_polling_priority_item.agent_shop_id_snapshot IS '代理归属快照-卡所属店铺的直接上级代理店铺ID,一级代理与平台卡为空';
|
||||
COMMENT ON COLUMN tb_polling_priority_item.attempt_limit IS '尝试上限,入队时取代码常量(3),既有行随默认值获得上限;不新增可维护配置项';
|
||||
COMMENT ON COLUMN tb_polling_priority_item.started_at IS '最近一次执行的开始时间,只在真正发起执行后写入';
|
||||
COMMENT ON COLUMN tb_polling_priority_item.finished_at IS '最近一次执行的结束时间,与开始时间共同表达执行耗时';
|
||||
COMMENT ON COLUMN tb_polling_priority_item.next_run_at IS '下一次计划执行时间,可空;为空表示未排期,读侧 MUST NOT 据此判定已过期';
|
||||
COMMENT ON COLUMN tb_polling_priority_item.dequeued_at IS '出队时间,终态(已完成、失败出队、已关闭、已过期)写入';
|
||||
COMMENT ON COLUMN tb_polling_priority_item.integration_log_id IS '最近一次上游交互的集成交互日志标识,不复制凭证或响应原文';
|
||||
COMMENT ON COLUMN tb_polling_priority_item.priority_effective_from IS '有效期起算时间,新增行取入队时间;既有活动项在迁移时刻回填,终态行留空';
|
||||
|
||||
-- 取值域约束:允许空串/空值以兼容旧代码回滚期的插入(旧版本不写资产列);
|
||||
-- 一旦写入则必须是合法资产类型与正数资产ID。
|
||||
ALTER TABLE tb_polling_priority_item
|
||||
ADD CONSTRAINT ck_polling_priority_item_asset_type CHECK (asset_type IN ('', 'iot_card', 'device')),
|
||||
ADD CONSTRAINT ck_polling_priority_item_asset CHECK (asset_id IS NULL OR asset_id > 0),
|
||||
ADD CONSTRAINT ck_polling_priority_item_attempt_limit CHECK (attempt_limit > 0);
|
||||
|
||||
-- 状态约束重建:新增 closed-已关闭 与 expired-已过期 两个终态。
|
||||
ALTER TABLE tb_polling_priority_item
|
||||
DROP CONSTRAINT ck_polling_priority_item_status;
|
||||
|
||||
ALTER TABLE tb_polling_priority_item
|
||||
ADD CONSTRAINT ck_polling_priority_item_status CHECK (
|
||||
status IN ('pending', 'processing', 'completed', 'failed', 'closed', 'expired')
|
||||
);
|
||||
|
||||
-- 终态与结果的约束同步放宽:两个新终态保留既有执行结果与失败原因,不参与「必须带结果」的强约束。
|
||||
ALTER TABLE tb_polling_priority_item
|
||||
DROP CONSTRAINT ck_polling_priority_item_terminal_result;
|
||||
|
||||
ALTER TABLE tb_polling_priority_item
|
||||
ADD CONSTRAINT ck_polling_priority_item_terminal_result CHECK (
|
||||
(status = 'completed' AND result = 'success')
|
||||
OR (status = 'failed' AND result = 'failed')
|
||||
OR status IN ('pending', 'processing', 'closed', 'expired')
|
||||
);
|
||||
|
||||
-- 到期扫描与读侧筛选按下一次计划执行时间取活动项。
|
||||
CREATE INDEX idx_polling_priority_item_next_run
|
||||
ON tb_polling_priority_item (next_run_at)
|
||||
WHERE deleted_at IS NULL AND status IN ('pending', 'processing') AND next_run_at IS NOT NULL;
|
||||
|
||||
COMMENT ON INDEX idx_polling_priority_item_next_run IS '优先队列活动项的下一次计划执行时间部分索引,服务到期扫描与读侧筛选';
|
||||
25
migrations/000235_extend_carrier_threshold_hit.down.sql
Normal file
25
migrations/000235_extend_carrier_threshold_hit.down.sql
Normal file
@@ -0,0 +1,25 @@
|
||||
-- 回滚运营商通道阈值命中事实留痕,与 up 严格成对,顺序与 up 的创建顺序严格倒序。
|
||||
--
|
||||
-- 不可逆说明(ENG-MIG-001 例外条件):本迁移的 down 会删除命中累计流量、阈值数值与单位、
|
||||
-- 判定时间、触发来源、解锁时间与复机结果等列。命中数字证据(尤其是「按旧阈值命中」与
|
||||
-- 「周期恢复结果」)只由本表承载,tb_audit_event 与集成日志不能还原完整命中读数,
|
||||
-- 删除后无法由数据库自身重建,因此 down 只在上述事实已无留存需求时执行。
|
||||
|
||||
DROP INDEX IF EXISTS idx_carrier_traffic_threshold_lock_card_judged;
|
||||
DROP INDEX IF EXISTS idx_carrier_traffic_threshold_lock_carrier_judged;
|
||||
|
||||
ALTER TABLE tb_carrier_traffic_threshold_lock
|
||||
DROP CONSTRAINT IF EXISTS ck_carrier_traffic_threshold_lock_resume_result,
|
||||
DROP CONSTRAINT IF EXISTS ck_carrier_traffic_threshold_lock_trigger_source,
|
||||
DROP CONSTRAINT IF EXISTS ck_carrier_traffic_threshold_lock_hit_unit,
|
||||
DROP CONSTRAINT IF EXISTS ck_carrier_traffic_threshold_lock_hit_threshold,
|
||||
DROP CONSTRAINT IF EXISTS ck_carrier_traffic_threshold_lock_hit_traffic;
|
||||
|
||||
ALTER TABLE tb_carrier_traffic_threshold_lock
|
||||
DROP COLUMN IF EXISTS resume_result,
|
||||
DROP COLUMN IF EXISTS unlocked_at,
|
||||
DROP COLUMN IF EXISTS trigger_source,
|
||||
DROP COLUMN IF EXISTS judged_at,
|
||||
DROP COLUMN IF EXISTS hit_threshold_unit,
|
||||
DROP COLUMN IF EXISTS hit_threshold_value,
|
||||
DROP COLUMN IF EXISTS hit_traffic_mb;
|
||||
74
migrations/000235_extend_carrier_threshold_hit.up.sql
Normal file
74
migrations/000235_extend_carrier_threshold_hit.up.sql
Normal file
@@ -0,0 +1,74 @@
|
||||
-- 运营商通道阈值命中事实留痕。
|
||||
-- 背景:111.md 要求阈值命中可解释、可查询:停机后运维必须能看到「命中时该卡累计流量多少、
|
||||
-- 判定用的阈值是多少(含改阈值前的历史值)、属于哪个计费周期、什么时候判定、由什么触发」,
|
||||
-- 并能在周期恢复时看到解锁时间与复机结果。既有 tb_carrier_traffic_threshold_lock 只有
|
||||
-- 锁生命周期与停复机子任务状态,没有任何数字证据,改阈值后历史命中无法解释。
|
||||
--
|
||||
-- 设计选择:
|
||||
-- 1. 加列到既有锁表而不新建命中事实表:锁表本身即「一次命中一条」(一个卡一个计费周期至多一条),
|
||||
-- 新建表会与锁表 1:1 冗余并需要额外一致性约束。
|
||||
-- 2. hit_* 三列冻结命中时值:hit_traffic_mb 取与停机判定同一来源的网关累计读数换算值(MB,
|
||||
-- 不使用本地用量或套餐真流量);hit_threshold_value/hit_threshold_unit 取该次判定实际生效的
|
||||
-- 通道配置值,因此之后修改通道阈值不会改写历史命中。
|
||||
-- 3. judged_at 是判定时间:既有行以创建时间回填(历史读数与阈值不可重建),回填后置为非空。
|
||||
-- hit_traffic_mb 与 hit_threshold_value 允许为空,读侧按「无」展示而不阻断查询。
|
||||
-- 4. trigger_source 记录触发本次判定的流量观测来源(与 pkg/constants.CardObservationSource*
|
||||
-- 一致),使自动判定与重放可区分;允许空串以兼容回滚期旧代码插入。
|
||||
-- 5. unlocked_at/resume_result 记录周期恢复结果,MUST NOT 覆盖上面的停机命中事实:
|
||||
-- resumed-已发起复机 skipped-仅解锁未复机 manual-周期归属不可判定转人工;跳过原因沿用既有失败原因列。
|
||||
-- 6. 新增按通道与按卡的判定时间索引,服务命中记录查询的筛选与排序。
|
||||
-- 7. 本迁移不改动既有达量判定、停机调用、持锁拒绝复机与新周期解锁语义。
|
||||
|
||||
ALTER TABLE tb_carrier_traffic_threshold_lock
|
||||
ADD COLUMN hit_traffic_mb NUMERIC(18,2),
|
||||
ADD COLUMN hit_threshold_value NUMERIC(18,2),
|
||||
ADD COLUMN hit_threshold_unit VARCHAR(8) NOT NULL DEFAULT '',
|
||||
ADD COLUMN judged_at TIMESTAMPTZ,
|
||||
ADD COLUMN trigger_source VARCHAR(20) NOT NULL DEFAULT '',
|
||||
ADD COLUMN unlocked_at TIMESTAMPTZ,
|
||||
ADD COLUMN resume_result VARCHAR(30) NOT NULL DEFAULT '';
|
||||
|
||||
-- 历史行判定时间取创建时间;命中数值与阈值留空(历史读数不可重建,读侧按「无」展示)。
|
||||
UPDATE tb_carrier_traffic_threshold_lock
|
||||
SET judged_at = created_at
|
||||
WHERE judged_at IS NULL;
|
||||
|
||||
ALTER TABLE tb_carrier_traffic_threshold_lock
|
||||
ALTER COLUMN judged_at SET NOT NULL;
|
||||
|
||||
-- judged_at 保留默认值 NOW():本次硬规则要求「新列可空或带默认值」,而 judged_at 需要非空
|
||||
-- (判定时间是命中记录的查询与排序依据)。若不给默认值,回滚到上一版本二进制时,旧代码写入
|
||||
-- 停机锁不带该列会直接违反 NOT NULL,使达量停机事实无法落库。默认值让旧代码插入的命中事实
|
||||
-- 以插入时刻作为判定时间,与「既有行以创建时间回填」的语义自洽。
|
||||
ALTER TABLE tb_carrier_traffic_threshold_lock
|
||||
ALTER COLUMN judged_at SET DEFAULT NOW();
|
||||
|
||||
ALTER TABLE tb_carrier_traffic_threshold_lock
|
||||
ADD CONSTRAINT ck_carrier_traffic_threshold_lock_hit_traffic CHECK (hit_traffic_mb IS NULL OR hit_traffic_mb >= 0),
|
||||
ADD CONSTRAINT ck_carrier_traffic_threshold_lock_hit_threshold CHECK (hit_threshold_value IS NULL OR hit_threshold_value > 0),
|
||||
ADD CONSTRAINT ck_carrier_traffic_threshold_lock_hit_unit CHECK (hit_threshold_unit IN ('', 'MB', 'GB')),
|
||||
ADD CONSTRAINT ck_carrier_traffic_threshold_lock_trigger_source CHECK (
|
||||
trigger_source IN ('', 'polling', 'manual_sync', 'manual_override', 'carrier_callback', 'business_event')
|
||||
),
|
||||
ADD CONSTRAINT ck_carrier_traffic_threshold_lock_resume_result CHECK (
|
||||
resume_result IN ('', 'resumed', 'skipped', 'manual')
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.hit_traffic_mb IS '命中时该卡当前计费周期的累计流量读数(MB),取与停机判定同一来源的网关累计读数换算值,不使用本地用量或套餐真流量;历史行为空表示读数不可重建';
|
||||
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.hit_threshold_value IS '本次判定实际生效的通道阈值数值,冻结命中时值,改阈值后历史命中不被改写;历史行为空';
|
||||
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.hit_threshold_unit IS '本次判定阈值的单位 MB-兆字节 GB-吉字节,空字符串-历史行未记录';
|
||||
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.judged_at IS '判定时间,既有行以创建时间回填;命中记录查询与排序以此为业务时间。列默认值为 NOW(),使回滚期不写该列的旧代码插入的命中事实以插入时刻作为判定时间';
|
||||
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.trigger_source IS '触发本次判定的流量观测来源 polling-周期轮询 manual_sync-手动同步 manual_override-人工纠偏 carrier_callback-运营商回调 business_event-业务成功后的观测;空字符串-历史行未记录';
|
||||
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.unlocked_at IS '周期恢复时的解锁时间,只在跨期解锁成功时写入;不覆盖停机命中事实';
|
||||
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.resume_result IS '周期恢复的复机结果 resumed-已发起复机 skipped-仅解锁未复机 manual-周期归属不可判定转人工;空字符串-未处理或历史行,跳过原因沿用失败原因列';
|
||||
|
||||
-- 命中记录查询按判定时间倒序:分别按运营商通道与按卡提供索引,避免查询走顺序扫描。
|
||||
CREATE INDEX idx_carrier_traffic_threshold_lock_carrier_judged
|
||||
ON tb_carrier_traffic_threshold_lock (carrier_id, judged_at DESC, id DESC)
|
||||
WHERE deleted_at IS NULL;
|
||||
CREATE INDEX idx_carrier_traffic_threshold_lock_card_judged
|
||||
ON tb_carrier_traffic_threshold_lock (card_id, judged_at DESC, id DESC)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
COMMENT ON INDEX idx_carrier_traffic_threshold_lock_carrier_judged IS '通道阈值命中记录按通道与判定时间分页索引';
|
||||
COMMENT ON INDEX idx_carrier_traffic_threshold_lock_card_judged IS '通道阈值命中记录按卡与判定时间分页索引';
|
||||
@@ -0,0 +1,9 @@
|
||||
-- 回滚手机号—资产关联的最近解绑人名称快照,与 up 严格成对。
|
||||
--
|
||||
-- 不可逆说明(ENG-MIG-001 例外条件):本迁移的 down 会删除 invalidator_name_snapshot。
|
||||
-- 解绑人名称是解绑当时的账号名称快照,账号改名后无法由 invalidator 账号ID反推,
|
||||
-- 删除后只能依据 tb_audit_event 中同一解绑动作的记录人工复核,因此 down 只在
|
||||
-- 解绑人名称事实已无留存需求时执行。
|
||||
|
||||
ALTER TABLE tb_phone_asset_association
|
||||
DROP COLUMN IF EXISTS invalidator_name_snapshot;
|
||||
@@ -0,0 +1,15 @@
|
||||
-- 手机号—资产关联补齐「最近解绑人名称快照」。
|
||||
-- 背景:111.md §15.7 要求后台关联查看直接显示最近解绑人,避免运营拿着失效时间再去审计里翻账号。
|
||||
-- 既有表已有 invalidator(解绑操作人账号ID),但没有名称快照,而账号改名后历史解绑人无法复原。
|
||||
--
|
||||
-- 设计选择:
|
||||
-- 1. 快照列非空且默认空字符串:有效关系(status = 1)保持空串,MUST NOT 以创建人或最后更新人填充;
|
||||
-- 已失效关系写入解绑当时的后台账号名称。
|
||||
-- 2. 审计侧仍只写脱敏值:本列是业务投影快照,不改变 tb_audit_event 对手机号的脱敏规则,
|
||||
-- 也不把完整手机号写进审计。
|
||||
-- 3. 只读投影字段:本列不改变既有解绑、换绑、上限判定与审计规则。
|
||||
|
||||
ALTER TABLE tb_phone_asset_association
|
||||
ADD COLUMN invalidator_name_snapshot VARCHAR(100) NOT NULL DEFAULT '';
|
||||
|
||||
COMMENT ON COLUMN tb_phone_asset_association.invalidator_name_snapshot IS '最近解绑人名称快照,仅 status = 0 时写入解绑当时的后台账号名称;有效关系为空,不以创建人或更新时间填充。仅用于业务投影,审计侧仍只写脱敏值';
|
||||
@@ -0,0 +1,14 @@
|
||||
-- 回滚 000237:删除提现审批尝试的资料资格版本与校验结果留痕列。
|
||||
-- 回滚会丢弃既有尝试行上的资格校验留痕;如需保留,应先导出对应列再执行。
|
||||
|
||||
ALTER TABLE tb_commission_withdrawal_request_attempt
|
||||
DROP CONSTRAINT IF EXISTS chk_commission_withdrawal_attempt_qualification_reason;
|
||||
|
||||
ALTER TABLE tb_commission_withdrawal_request_attempt
|
||||
DROP CONSTRAINT IF EXISTS chk_commission_withdrawal_attempt_qualification_passed;
|
||||
|
||||
ALTER TABLE tb_commission_withdrawal_request_attempt
|
||||
DROP COLUMN IF EXISTS qualification_version_id,
|
||||
DROP COLUMN IF EXISTS qualification_checked_at,
|
||||
DROP COLUMN IF EXISTS qualification_passed,
|
||||
DROP COLUMN IF EXISTS qualification_failure_reason;
|
||||
@@ -0,0 +1,38 @@
|
||||
-- 提现审批尝试补齐「资料资格版本 + 当次校验结果」留痕。
|
||||
-- 背景:提现申请必须先校验申请店铺存在有效资料资格;本变更要求把本次依据的有效资料资格版本标识
|
||||
-- 与当次校验结果(校验时间、是否通过、未通过稳定原因)冻结进当次审批尝试快照,并在提现详情可查。
|
||||
--
|
||||
-- 设计选择:
|
||||
-- 1. 记录落在审批尝试表而非提现申请表:尝试记录是每次提交或重提一条的不可变快照,
|
||||
-- 资格被替换或失效后,历史尝试仍返回提交时冻结的版本与结果,不被新版本改写。
|
||||
-- 2. 未通过原因为稳定编码列(qualification_failure_reason),取值域为本变更登记的四种稳定原因
|
||||
-- (资格不存在、已失效、已停用、资料未通过审批),由 CHECK 兜住半残取值;
|
||||
-- 通过时该列为空字符串。稳定编码用于机器判定与审计,中文文案由应用层按编码映射。
|
||||
-- 3. qualification_version_id 记录当次依据的有效资格版本主键;未通过(资格不存在等)时为 0,
|
||||
-- 表示本次未依据任何有效版本。该列不建外键,关联由应用层显式校验(ENG-MODEL-001)。
|
||||
-- 4. 新列一律带默认值,既有尝试行零变更:historical 尝试的 qualification_passed 为 0 且原因为空,
|
||||
-- 读侧据此按「历史尝试无资格校验留痕」处理,MUST NOT 反推或改写历史结果。
|
||||
-- 5. 本迁移不新增企业微信模板控件:该留痕只用于本地不可变审批尝试快照与提现详情查询。
|
||||
|
||||
ALTER TABLE tb_commission_withdrawal_request_attempt
|
||||
ADD COLUMN IF NOT EXISTS qualification_version_id BIGINT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS qualification_checked_at TIMESTAMPTZ,
|
||||
ADD COLUMN IF NOT EXISTS qualification_passed SMALLINT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS qualification_failure_reason VARCHAR(64) NOT NULL DEFAULT '';
|
||||
|
||||
-- 启停类标记必须严格 0/1:判定端按 qualification_passed = 1 识别通过,其他取值会被静默视为未通过。
|
||||
ALTER TABLE tb_commission_withdrawal_request_attempt
|
||||
ADD CONSTRAINT chk_commission_withdrawal_attempt_qualification_passed
|
||||
CHECK (qualification_passed IN (0, 1));
|
||||
|
||||
-- 通过时不得携带未通过原因;未通过时的稳定原因取值域由应用层校验。
|
||||
-- qualification_passed = 0 且原因为空是合法的历史状态:早于 000237 的既有尝试行就是该形态,
|
||||
-- 读侧据此按「无资格校验留痕」处理,因此 MUST NOT 用 CHECK 把空原因一律判为非法。
|
||||
ALTER TABLE tb_commission_withdrawal_request_attempt
|
||||
ADD CONSTRAINT chk_commission_withdrawal_attempt_qualification_reason
|
||||
CHECK (qualification_passed = 0 OR qualification_failure_reason = '');
|
||||
|
||||
COMMENT ON COLUMN tb_commission_withdrawal_request_attempt.qualification_version_id IS '本次提现依据的有效提现资料资格版本ID;早于 000237 的历史尝试与资格不存在时为 0';
|
||||
COMMENT ON COLUMN tb_commission_withdrawal_request_attempt.qualification_checked_at IS '本次资料资格校验时间;早于 000237 的历史尝试为空';
|
||||
COMMENT ON COLUMN tb_commission_withdrawal_request_attempt.qualification_passed IS '本次资料资格校验是否通过 0-未通过 1-通过';
|
||||
COMMENT ON COLUMN tb_commission_withdrawal_request_attempt.qualification_failure_reason IS '未通过时的稳定原因编码 qualification_missing-资格不存在 qualification_invalidated-已失效 shop_disabled-已停用 qualification_not_approved-资料未通过审批;通过时为空字符串';
|
||||
Reference in New Issue
Block a user