134_affiliate_ledger_audit_snapshots.sql 3.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. -- 邀请返利流水补充订单关联和转余额快照。
  2. -- 这些字段只用于审计展示;历史旧流水无法可靠反推的字段保持 NULL,避免把当前状态误展示为历史状态。
  3. ALTER TABLE user_affiliate_ledger
  4. ADD COLUMN IF NOT EXISTS source_order_id BIGINT NULL REFERENCES payment_orders(id) ON DELETE SET NULL;
  5. ALTER TABLE user_affiliate_ledger
  6. ADD COLUMN IF NOT EXISTS balance_after DECIMAL(20,8) NULL;
  7. ALTER TABLE user_affiliate_ledger
  8. ADD COLUMN IF NOT EXISTS aff_quota_after DECIMAL(20,8) NULL;
  9. ALTER TABLE user_affiliate_ledger
  10. ADD COLUMN IF NOT EXISTS aff_frozen_quota_after DECIMAL(20,8) NULL;
  11. ALTER TABLE user_affiliate_ledger
  12. ADD COLUMN IF NOT EXISTS aff_history_quota_after DECIMAL(20,8) NULL;
  13. COMMENT ON COLUMN user_affiliate_ledger.source_order_id IS '产生该返利流水的充值订单;转余额或无法可靠回填的历史数据为 NULL';
  14. COMMENT ON COLUMN user_affiliate_ledger.balance_after IS '邀请返利转余额后的用户余额快照;无法取得时为 NULL';
  15. COMMENT ON COLUMN user_affiliate_ledger.aff_quota_after IS '邀请返利转余额后的可用返利额度快照;无法取得时为 NULL';
  16. COMMENT ON COLUMN user_affiliate_ledger.aff_frozen_quota_after IS '邀请返利转余额后的冻结返利额度快照;无法取得时为 NULL';
  17. COMMENT ON COLUMN user_affiliate_ledger.aff_history_quota_after IS '邀请返利转余额后的历史返利总额快照;无法取得时为 NULL';
  18. CREATE INDEX IF NOT EXISTS idx_user_affiliate_ledger_source_order_id
  19. ON user_affiliate_ledger(source_order_id)
  20. WHERE source_order_id IS NOT NULL;
  21. CREATE INDEX IF NOT EXISTS idx_user_affiliate_ledger_rebate_lookup
  22. ON user_affiliate_ledger(action, source_order_id, user_id, source_user_id, created_at)
  23. WHERE action = 'accrue';
  24. -- 尽力回填 PR #2169 合并后、该迁移前已经产生的返利流水。
  25. -- 只有在同一订单只能匹配到一条返利流水时才回填,避免把多笔同额流水错误绑定到订单。
  26. WITH rebate_audits AS (
  27. SELECT po.id AS order_id,
  28. po.user_id AS invitee_user_id,
  29. invitee_aff.inviter_id,
  30. rebate_detail.rebate_amount,
  31. pal.created_at AS audit_created_at
  32. FROM payment_audit_logs pal
  33. CROSS JOIN LATERAL (
  34. SELECT substring(
  35. pal.detail
  36. FROM '"rebateAmount"[[:space:]]*:[[:space:]]*(-?[0-9]+(\.[0-9]+)?)'
  37. )::numeric AS rebate_amount
  38. ) rebate_detail
  39. JOIN payment_orders po ON po.id::text = pal.order_id
  40. JOIN user_affiliates invitee_aff ON invitee_aff.user_id = po.user_id
  41. WHERE pal.action = 'AFFILIATE_REBATE_APPLIED'
  42. AND rebate_detail.rebate_amount IS NOT NULL
  43. ),
  44. ranked_matches AS (
  45. SELECT ual.id AS ledger_id,
  46. ra.order_id,
  47. COUNT(*) OVER (PARTITION BY ra.order_id) AS order_match_count,
  48. COUNT(*) OVER (PARTITION BY ual.id) AS ledger_match_count,
  49. ROW_NUMBER() OVER (
  50. PARTITION BY ual.id
  51. ORDER BY ABS(EXTRACT(EPOCH FROM (ual.created_at - ra.audit_created_at))), ra.order_id
  52. ) AS ledger_rank
  53. FROM rebate_audits ra
  54. JOIN user_affiliate_ledger ual
  55. ON ual.action = 'accrue'
  56. AND ual.source_order_id IS NULL
  57. AND ual.user_id = ra.inviter_id
  58. AND ual.source_user_id = ra.invitee_user_id
  59. AND ABS(ual.amount - ra.rebate_amount) < 0.00000001
  60. AND ual.created_at BETWEEN ra.audit_created_at - INTERVAL '10 minutes'
  61. AND ra.audit_created_at + INTERVAL '10 minutes'
  62. )
  63. UPDATE user_affiliate_ledger ual
  64. SET source_order_id = ranked_matches.order_id,
  65. updated_at = NOW()
  66. FROM ranked_matches
  67. WHERE ual.id = ranked_matches.ledger_id
  68. AND ranked_matches.order_match_count = 1
  69. AND ranked_matches.ledger_match_count = 1
  70. AND ranked_matches.ledger_rank = 1
  71. AND NOT EXISTS (
  72. SELECT 1
  73. FROM user_affiliate_ledger existing
  74. WHERE existing.source_order_id = ranked_matches.order_id
  75. AND existing.action = 'accrue'
  76. );