fix: 禁止过期充值单补开通 + 管理命令收回错误恢复会员

rollback_erroneous_member_restore:后台已减仍有效、充值单已过期仍显示有效。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-08 15:56:11 +08:00
parent 1c6e908f28
commit 7c3f04e7f3
2 changed files with 173 additions and 0 deletions

View File

@@ -0,0 +1,117 @@
"""
收回因 dddhq 每次 sync 错误补开通的会员权益。
两类处理:
1. 后台「减会员」日志存在但权益仍有效 → 再次 revoke删记录+标记充值单撤销)
2. 当前仍显示有效,但已无任何未过期充值单支撑 → 置为过期
用法:
python manage.py rollback_erroneous_member_restore --dry-run
python manage.py rollback_erroneous_member_restore
"""
from django.core.management.base import BaseCommand
from django.db import transaction
from jituan.services.huiyuan_bundle import huiyuan_ids_match
from jituan.services.member_recharge import (
ENTITLEMENT_REVOKED_MARKER,
expire_huiyuangoumai_record,
revoke_member_entitlement_admin,
user_has_active_czjilu_entitlement,
)
from products.models import Huiyuangoumai
from users.models import Xiugaijilu
class Command(BaseCommand):
help = '收回错误恢复的会员(后台已减仍有效 / 充值单已过期仍显示有效)'
def add_arguments(self, parser):
parser.add_argument(
'--dry-run',
action='store_true',
help='仅统计,不写库',
)
def handle(self, *args, **options):
dry_run = bool(options.get('dry_run'))
admin_revoke = 0
expire_orphan = 0
samples = []
with transaction.atomic():
# 1) 后台减会员日志 → 仍有效的再 revoke 一遍
logs = Xiugaijilu.query.filter(
huiyuan_id__isnull=False,
).exclude(huiyuan_id='').order_by('-CreateTime')
seen_pairs = set()
for log in logs:
note = (log.qitashuoming or '') + (log.huiyuan_id or '')
if '移除' not in (log.qitashuoming or '') and '减会员' not in (log.qitashuoming or ''):
continue
if not log.yonghuid or not log.huiyuan_id:
continue
pair = (log.yonghuid, str(log.huiyuan_id).strip())
if pair in seen_pairs:
continue
seen_pairs.add(pair)
active = False
for rec in Huiyuangoumai.query.filter(yonghu_id=log.yonghuid):
if not huiyuan_ids_match(rec.huiyuan_id, log.huiyuan_id):
continue
if not rec.jiance_shifou_daoqi() and rec.huiyuan_zhuangtai == 1:
active = True
break
if not active:
continue
admin_revoke += 1
samples.append(f'[后台已减仍有效] {log.yonghuid} 会员{log.huiyuan_id}')
if not dry_run:
revoke_member_entitlement_admin(log.yonghuid, log.huiyuan_id)
# 2) 仍显示有效,但没有任何未过期充值单支撑 → 过期收回
for rec in Huiyuangoumai.query.filter(huiyuan_zhuangtai=1):
if rec.jiance_shifou_daoqi():
continue
from products.models import Czjilu
from jituan.services.member_recharge import MEMBER_LEIXING, MEMBER_PAID_STATUS
paid_orders = [
o for o in Czjilu.query.filter(
yonghuid=rec.yonghu_id,
leixing=MEMBER_LEIXING,
zhuangtai=MEMBER_PAID_STATUS,
)
if huiyuan_ids_match(o.huiyuan_id, rec.huiyuan_id)
]
if not paid_orders:
continue
if user_has_active_czjilu_entitlement(
rec.yonghu_id, rec.huiyuan_id, rec.club_id,
):
continue
expire_orphan += 1
samples.append(
f'[充值单已过期仍有效] {rec.yonghu_id} 会员{rec.huiyuan_id} '
f'到期{rec.daoqi_time}',
)
if not dry_run:
expire_huiyuangoumai_record(
rec,
reason='rollback_erroneous_member_restore',
)
if dry_run:
transaction.set_rollback(True)
mode = 'DRY-RUN' if dry_run else 'APPLIED'
self.stdout.write(self.style.SUCCESS(
f'[{mode}] 后台减会员仍有效收回 {admin_revoke} 条;'
f'无有效充值单支撑过期 {expire_orphan}',
))
for line in samples[:50]:
self.stdout.write(f' {line}')
if len(samples) > 50:
self.stdout.write(f' ... 另有 {len(samples) - 50}')