Files
junhong_cmp_fiber/migrations/000234_extend_polling_priority_item.up.sql
break 5ed6b39deb
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
feat(收口): 补齐 8 月迭代缺口并同步 Spec 与证据链
- 新增六对成对迁移 000232–000237:H5 弹窗类型、退款结算标识与申请人备注、优先轮询事实字段与两个新终态、通道阈值命中留痕、手机号最近解绑人、提现资格校验留痕
- 退款:原因必填与申请人备注、来源支付与渠道流水冻结、线下处理流水号补录审计、按订单查询可选退款方式、企微审批材料补齐且新增字段缺失映射即明确失败
- 优先轮询:人工关闭、有效期到期独立周期任务、失败与过期人工重触发、事实字段与异常重试查询、资产解析端点只读投影
- 通道阈值:命中事实同事务留痕与命中记录查询;员工账单:列表筛选与详情投影;商户池:列表投影与统计周期语义;H5:弹窗类型与类别排序
- 手机号:有效关联数量与最近解绑人、短信验证码失败次数限制;导出:佣金明细十五列与报表序号列
- 时间筛选:三处新增筛选纳入统一严格解析契约,员工账单产生时间参数改名
- 同步 12 份主 Spec 需求、两端点与异步任务证据链,门禁 context-health 与 OpenSpec 校验通过
2026-09-18 15:34:29 +08:00

117 lines
7.8 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- 卡轮询优先队列补齐事实字段与两个新终态。
-- 背景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 '优先队列活动项的下一次计划执行时间部分索引,服务到期扫描与读侧筛选';