-- 修复退款误重置资产钱包的候选记录。 -- 执行前先运行 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;