All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m16s
155 lines
4.9 KiB
PL/PgSQL
155 lines
4.9 KiB
PL/PgSQL
-- 修复退款误重置资产钱包的候选记录。
|
|
-- 执行前先运行 scripts/refund_wallet_reset_audit.sql 复核输出。
|
|
-- 幂等策略:
|
|
-- 1. 新钱包无余额变动时,软删新钱包并恢复旧钱包。
|
|
-- 2. 新钱包已有余额或流水时,将旧钱包余额合并到新钱包,并把旧钱包余额清零,避免重复合并。
|
|
BEGIN;
|
|
|
|
CREATE TEMP TABLE refund_wallet_reset_targets ON COMMIT DROP AS
|
|
WITH refund_resources AS (
|
|
SELECT
|
|
r.id AS refund_id,
|
|
r.refund_no,
|
|
r.order_id,
|
|
o.order_no,
|
|
o.generation AS order_generation,
|
|
r.processed_at,
|
|
CASE
|
|
WHEN COALESCE(r.iot_card_id, o.iot_card_id) IS NOT NULL THEN 'iot_card'
|
|
ELSE 'device'
|
|
END AS resource_type,
|
|
COALESCE(r.iot_card_id, o.iot_card_id, r.device_id, o.device_id) AS resource_id
|
|
FROM tb_refund_request r
|
|
JOIN tb_order o ON o.id = r.order_id
|
|
WHERE r.deleted_at IS NULL
|
|
AND r.status = 2
|
|
AND r.asset_reset = TRUE
|
|
),
|
|
wallet_pairs AS (
|
|
SELECT
|
|
rr.*,
|
|
active_wallet.id AS active_wallet_id,
|
|
active_wallet.balance AS active_balance,
|
|
active_wallet.frozen_balance AS active_frozen_balance,
|
|
deleted_wallet.id AS deleted_wallet_id,
|
|
deleted_wallet.balance AS deleted_balance,
|
|
deleted_wallet.frozen_balance AS deleted_frozen_balance,
|
|
COALESCE(active_tx.tx_count, 0) AS active_wallet_tx_count
|
|
FROM refund_resources rr
|
|
JOIN LATERAL (
|
|
SELECT *
|
|
FROM tb_asset_wallet w
|
|
WHERE w.resource_type = rr.resource_type
|
|
AND w.resource_id = rr.resource_id
|
|
AND w.deleted_at IS NULL
|
|
ORDER BY w.created_at DESC, w.id DESC
|
|
LIMIT 1
|
|
) active_wallet ON TRUE
|
|
JOIN LATERAL (
|
|
SELECT *
|
|
FROM tb_asset_wallet w
|
|
WHERE w.resource_type = rr.resource_type
|
|
AND w.resource_id = rr.resource_id
|
|
AND w.deleted_at IS NOT NULL
|
|
AND (w.balance <> 0 OR w.frozen_balance <> 0)
|
|
ORDER BY w.deleted_at DESC, w.id DESC
|
|
LIMIT 1
|
|
) deleted_wallet ON TRUE
|
|
LEFT JOIN LATERAL (
|
|
SELECT COUNT(*) AS tx_count
|
|
FROM tb_asset_wallet_transaction t
|
|
WHERE t.asset_wallet_id = active_wallet.id
|
|
AND t.deleted_at IS NULL
|
|
) active_tx ON TRUE
|
|
)
|
|
SELECT
|
|
*,
|
|
CASE
|
|
WHEN active_wallet_tx_count = 0 AND active_balance = 0 AND active_frozen_balance = 0 THEN 'restore_old_wallet'
|
|
ELSE 'merge_old_balance'
|
|
END AS repair_mode
|
|
FROM wallet_pairs;
|
|
|
|
-- 新钱包没有后续余额变动:先软删新钱包,再恢复旧钱包,避免唯一索引冲突。
|
|
UPDATE tb_asset_wallet w
|
|
SET deleted_at = NOW(),
|
|
updated_at = NOW()
|
|
FROM refund_wallet_reset_targets t
|
|
WHERE t.repair_mode = 'restore_old_wallet'
|
|
AND w.id = t.active_wallet_id
|
|
AND w.deleted_at IS NULL;
|
|
|
|
UPDATE tb_asset_wallet w
|
|
SET deleted_at = NULL,
|
|
updated_at = NOW()
|
|
FROM refund_wallet_reset_targets t
|
|
WHERE t.repair_mode = 'restore_old_wallet'
|
|
AND w.id = t.deleted_wallet_id
|
|
AND w.deleted_at IS NOT NULL;
|
|
|
|
-- 新钱包已有后续余额变动:合并旧钱包余额,并清零旧钱包余额,保证重复执行不会二次入账。
|
|
UPDATE tb_asset_wallet w
|
|
SET balance = w.balance + t.deleted_balance,
|
|
frozen_balance = w.frozen_balance + t.deleted_frozen_balance,
|
|
version = w.version + 1,
|
|
updated_at = NOW()
|
|
FROM refund_wallet_reset_targets t
|
|
WHERE t.repair_mode = 'merge_old_balance'
|
|
AND w.id = t.active_wallet_id
|
|
AND w.deleted_at IS NULL;
|
|
|
|
UPDATE tb_asset_wallet w
|
|
SET balance = 0,
|
|
frozen_balance = 0,
|
|
updated_at = NOW()
|
|
FROM refund_wallet_reset_targets t
|
|
WHERE t.repair_mode = 'merge_old_balance'
|
|
AND w.id = t.deleted_wallet_id
|
|
AND w.deleted_at IS NOT NULL;
|
|
|
|
-- 安全恢复单次误增的资产世代;复杂场景保留给人工复核。
|
|
UPDATE tb_iot_card c
|
|
SET generation = t.order_generation,
|
|
updated_at = NOW()
|
|
FROM refund_wallet_reset_targets t
|
|
WHERE t.resource_type = 'iot_card'
|
|
AND c.id = t.resource_id
|
|
AND c.generation = t.order_generation + 1
|
|
AND NOT EXISTS (
|
|
SELECT 1
|
|
FROM tb_exchange_order e
|
|
WHERE e.deleted_at IS NULL
|
|
AND e.status = 4
|
|
AND e.old_asset_type = t.resource_type
|
|
AND e.old_asset_id = t.resource_id
|
|
AND e.created_at >= t.processed_at
|
|
);
|
|
|
|
UPDATE tb_device d
|
|
SET generation = t.order_generation,
|
|
updated_at = NOW()
|
|
FROM refund_wallet_reset_targets t
|
|
WHERE t.resource_type = 'device'
|
|
AND d.id = t.resource_id
|
|
AND d.generation = t.order_generation + 1
|
|
AND NOT EXISTS (
|
|
SELECT 1
|
|
FROM tb_exchange_order e
|
|
WHERE e.deleted_at IS NULL
|
|
AND e.status = 4
|
|
AND e.old_asset_type = t.resource_type
|
|
AND e.old_asset_id = t.resource_id
|
|
AND e.created_at >= t.processed_at
|
|
);
|
|
|
|
SELECT
|
|
repair_mode,
|
|
COUNT(*) AS affected_resources,
|
|
SUM(deleted_balance) AS restored_balance,
|
|
SUM(deleted_frozen_balance) AS restored_frozen_balance
|
|
FROM refund_wallet_reset_targets
|
|
GROUP BY repair_mode
|
|
ORDER BY repair_mode;
|
|
|
|
COMMIT;
|