fix: correct affiliate audit record sources
This commit is contained in:
85
backend/migrations/134_affiliate_ledger_audit_snapshots.sql
Normal file
85
backend/migrations/134_affiliate_ledger_audit_snapshots.sql
Normal file
@@ -0,0 +1,85 @@
|
||||
-- 邀请返利流水补充订单关联和转余额快照。
|
||||
-- 这些字段只用于审计展示;历史旧流水无法可靠反推的字段保持 NULL,避免把当前状态误展示为历史状态。
|
||||
|
||||
ALTER TABLE user_affiliate_ledger
|
||||
ADD COLUMN IF NOT EXISTS source_order_id BIGINT NULL REFERENCES payment_orders(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE user_affiliate_ledger
|
||||
ADD COLUMN IF NOT EXISTS balance_after DECIMAL(20,8) NULL;
|
||||
|
||||
ALTER TABLE user_affiliate_ledger
|
||||
ADD COLUMN IF NOT EXISTS aff_quota_after DECIMAL(20,8) NULL;
|
||||
|
||||
ALTER TABLE user_affiliate_ledger
|
||||
ADD COLUMN IF NOT EXISTS aff_frozen_quota_after DECIMAL(20,8) NULL;
|
||||
|
||||
ALTER TABLE user_affiliate_ledger
|
||||
ADD COLUMN IF NOT EXISTS aff_history_quota_after DECIMAL(20,8) NULL;
|
||||
|
||||
COMMENT ON COLUMN user_affiliate_ledger.source_order_id IS '产生该返利流水的充值订单;转余额或无法可靠回填的历史数据为 NULL';
|
||||
COMMENT ON COLUMN user_affiliate_ledger.balance_after IS '邀请返利转余额后的用户余额快照;无法取得时为 NULL';
|
||||
COMMENT ON COLUMN user_affiliate_ledger.aff_quota_after IS '邀请返利转余额后的可用返利额度快照;无法取得时为 NULL';
|
||||
COMMENT ON COLUMN user_affiliate_ledger.aff_frozen_quota_after IS '邀请返利转余额后的冻结返利额度快照;无法取得时为 NULL';
|
||||
COMMENT ON COLUMN user_affiliate_ledger.aff_history_quota_after IS '邀请返利转余额后的历史返利总额快照;无法取得时为 NULL';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_affiliate_ledger_source_order_id
|
||||
ON user_affiliate_ledger(source_order_id)
|
||||
WHERE source_order_id IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_affiliate_ledger_rebate_lookup
|
||||
ON user_affiliate_ledger(action, source_order_id, user_id, source_user_id, created_at)
|
||||
WHERE action = 'accrue';
|
||||
|
||||
-- 尽力回填 PR #2169 合并后、该迁移前已经产生的返利流水。
|
||||
-- 只有在同一订单只能匹配到一条返利流水时才回填,避免把多笔同额流水错误绑定到订单。
|
||||
WITH rebate_audits AS (
|
||||
SELECT po.id AS order_id,
|
||||
po.user_id AS invitee_user_id,
|
||||
invitee_aff.inviter_id,
|
||||
rebate_detail.rebate_amount,
|
||||
pal.created_at AS audit_created_at
|
||||
FROM payment_audit_logs pal
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT substring(
|
||||
pal.detail
|
||||
FROM '"rebateAmount"[[:space:]]*:[[:space:]]*(-?[0-9]+(\.[0-9]+)?)'
|
||||
)::numeric AS rebate_amount
|
||||
) rebate_detail
|
||||
JOIN payment_orders po ON po.id::text = pal.order_id
|
||||
JOIN user_affiliates invitee_aff ON invitee_aff.user_id = po.user_id
|
||||
WHERE pal.action = 'AFFILIATE_REBATE_APPLIED'
|
||||
AND rebate_detail.rebate_amount IS NOT NULL
|
||||
),
|
||||
ranked_matches AS (
|
||||
SELECT ual.id AS ledger_id,
|
||||
ra.order_id,
|
||||
COUNT(*) OVER (PARTITION BY ra.order_id) AS order_match_count,
|
||||
COUNT(*) OVER (PARTITION BY ual.id) AS ledger_match_count,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY ual.id
|
||||
ORDER BY ABS(EXTRACT(EPOCH FROM (ual.created_at - ra.audit_created_at))), ra.order_id
|
||||
) AS ledger_rank
|
||||
FROM rebate_audits ra
|
||||
JOIN user_affiliate_ledger ual
|
||||
ON ual.action = 'accrue'
|
||||
AND ual.source_order_id IS NULL
|
||||
AND ual.user_id = ra.inviter_id
|
||||
AND ual.source_user_id = ra.invitee_user_id
|
||||
AND ABS(ual.amount - ra.rebate_amount) < 0.00000001
|
||||
AND ual.created_at BETWEEN ra.audit_created_at - INTERVAL '10 minutes'
|
||||
AND ra.audit_created_at + INTERVAL '10 minutes'
|
||||
)
|
||||
UPDATE user_affiliate_ledger ual
|
||||
SET source_order_id = ranked_matches.order_id,
|
||||
updated_at = NOW()
|
||||
FROM ranked_matches
|
||||
WHERE ual.id = ranked_matches.ledger_id
|
||||
AND ranked_matches.order_match_count = 1
|
||||
AND ranked_matches.ledger_match_count = 1
|
||||
AND ranked_matches.ledger_rank = 1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM user_affiliate_ledger existing
|
||||
WHERE existing.source_order_id = ranked_matches.order_id
|
||||
AND existing.action = 'accrue'
|
||||
);
|
||||
@@ -127,3 +127,18 @@ func TestMigration124BackfillsLegacyOIDCSecurityFlagsSafely(t *testing.T) {
|
||||
require.Contains(t, sql, "oidc_connect_enabled")
|
||||
require.Contains(t, sql, "'false'")
|
||||
}
|
||||
|
||||
func TestMigration134AddsAffiliateLedgerAuditFieldsWithoutJSONCast(t *testing.T) {
|
||||
content, err := FS.ReadFile("134_affiliate_ledger_audit_snapshots.sql")
|
||||
require.NoError(t, err)
|
||||
|
||||
sql := string(content)
|
||||
require.Contains(t, sql, "ADD COLUMN IF NOT EXISTS source_order_id BIGINT")
|
||||
require.Contains(t, sql, "ADD COLUMN IF NOT EXISTS balance_after DECIMAL(20,8)")
|
||||
require.Contains(t, sql, "ADD COLUMN IF NOT EXISTS aff_quota_after DECIMAL(20,8)")
|
||||
require.Contains(t, sql, "substring(")
|
||||
require.Contains(t, sql, `"rebateAmount"`)
|
||||
require.Contains(t, sql, "COUNT(*) OVER (PARTITION BY ra.order_id) AS order_match_count")
|
||||
require.Contains(t, sql, "COUNT(*) OVER (PARTITION BY ual.id) AS ledger_match_count")
|
||||
require.NotContains(t, sql, "detail::jsonb")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user