fix: 停止每次拉单补回已删会员;后台移除标记充值单撤销

ddhq 不再全量 sync 充值单;jian_huiyuan 标记权益已撤销防复活;clumber 只读 huiyuangoumai。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-08 15:49:07 +08:00
parent 38fc52241f
commit cec7d69199
4 changed files with 76 additions and 33 deletions

View File

@@ -673,11 +673,10 @@ class KefuUpdateDashouView(APIView):
return Response({'code': 403, 'msg': '无权限给打手减会员'})
if not huiyuan_id:
return Response({'code': 400, 'msg': '缺少会员ID'})
try:
record = Huiyuangoumai.query.get(yonghu_id=dashou_id, huiyuan_id=huiyuan_id)
except Huiyuangoumai.DoesNotExist:
return Response({'code': 404, 'msg': '该打手未购买此会员'})
record.delete()
from jituan.services.member_recharge import revoke_member_entitlement_admin
removed, err = revoke_member_entitlement_admin(dashou_id, huiyuan_id)
if err:
return Response({'code': 404, 'msg': err})
write_xiugai_log(
yonghuid=dashou_id,
xiugaiid=kefu.user.Phone,

View File

@@ -157,6 +157,8 @@ def _czjilu_grants_huiyuan_access(yonghu_id, required_huiyuan_id, club_id=None):
leixing=MEMBER_LEIXING,
zhuangtai=MEMBER_PAID_STATUS,
).order_by('-CreateTime')[:30]:
if _czjilu_entitlement_revoked(order):
continue
if not huiyuan_ids_match(order.huiyuan_id, req):
continue
cid = getattr(order, 'club_id', None) or club_id or CLUB_ID_DEFAULT
@@ -357,28 +359,15 @@ def _build_clumber_from_paid_czjilu(yonghu_id):
def ensure_grab_pool_clumber(yonghu_id, club_id=None):
"""
ddhq / dashouxinxi 展示专用】每次请求:先同步已付会员权益,再生成 clumber体验=正式)。
提现校验不得使用此函数
dddhq / dashouxinxi:只读 huiyuangoumai 生成 clumber体验=正式)。
不在每次请求全量 sync 充值单(后台移除后会被补回)
"""
from jituan.services.member_recharge import sync_member_entitlements_for_user
from jituan.services.member_recharge import repair_recent_paid_entitlements_if_missing
sync_member_entitlements_for_user(yonghu_id, limit=30)
items = _build_user_clumber_core(yonghu_id)
extra = _build_clumber_from_paid_czjilu(yonghu_id)
if extra:
seen_cores = set()
for it in items:
hid = normalize_huiyuan_id(it.get('huiyuanid') or it.get('huiyuan_id'))
if hid:
seen_cores.add(hid.lstrip('0') or '0')
for it in extra:
hid = normalize_huiyuan_id(it.get('huiyuanid') or it.get('huiyuan_id'))
if not hid:
continue
core = hid.lstrip('0') or '0'
if core not in seen_cores:
items.append(it)
seen_cores.add(core)
if not items:
repair_recent_paid_entitlements_if_missing(yonghu_id, limit=3)
items = _build_user_clumber_core(yonghu_id)
return bool(items), items

View File

@@ -48,6 +48,7 @@ MEMBER_PAID_STATUS = 3
MEMBER_PENDING_STATUS = 9
MEMBER_FAILED_STATUS = 8
MEMBER_CANCELLED_STATUS = 2
ENTITLEMENT_REVOKED_MARKER = '权益已撤销'
MEMBER_JIFEN_CAP = 10
MEMBER_JIFEN_TRIAL_GRANT = 5
MEMBER_JIFEN_FORMAL_GRANT = 5
@@ -629,10 +630,16 @@ def _add_first_buy_jifen(yonghuid):
dashou.save(update_fields=['jifen'])
def _czjilu_entitlement_revoked(order):
return ENTITLEMENT_REVOKED_MARKER in (getattr(order, 'shuoming', None) or '')
def repair_member_entitlement_if_paid(dingdan_id):
order = Czjilu.query.filter(dingdan_id=dingdan_id, leixing=MEMBER_LEIXING).first()
if not order or order.zhuangtai != MEMBER_PAID_STATUS or not order.huiyuan_id:
return False
if _czjilu_entitlement_revoked(order):
return False
from jituan.services.huiyuan_bundle import huiyuan_ids_match
for rec in Huiyuangoumai.query.filter(yonghu_id=order.yonghuid):
@@ -684,24 +691,74 @@ def repair_member_entitlement_if_paid(dingdan_id):
def sync_member_entitlements_for_user(yonghu_id, limit=15):
"""读取打手资料/抢单池前,补写已支付但未落库的会员权益(幂等)。"""
"""支付回调偶发失败时补权益;禁止在 dddhq 每次请求全量扫单(会覆盖后台移除)。"""
return repair_recent_paid_entitlements_if_missing(yonghu_id, limit=limit)
def repair_recent_paid_entitlements_if_missing(yonghu_id, limit=3):
"""仅当用户当前无任何有效会员时,尝试补最近几笔已付单。"""
from jituan.services.huiyuan_bundle import _membership_record_active
if not yonghu_id:
return 0
has_active = any(
_membership_record_active(rec)
for rec in Huiyuangoumai.query.filter(yonghu_id=yonghu_id)
)
if has_active:
return 0
orders = Czjilu.query.filter(
yonghuid=yonghu_id,
leixing=MEMBER_LEIXING,
zhuangtai=MEMBER_PAID_STATUS,
).order_by('-CreateTime')[: max(1, int(limit or 15))]
).order_by('-CreateTime')[: max(1, int(limit or 3))]
fixed = 0
for order in orders:
if _czjilu_entitlement_revoked(order):
continue
try:
if repair_member_entitlement_if_paid(order.dingdan_id):
fixed += 1
except Exception as exc:
logger.error('同步会员权益失败 dingdan=%s err=%s', order.dingdan_id, exc)
logger.error('会员权益失败 dingdan=%s err=%s', order.dingdan_id, exc)
return fixed
def revoke_member_entitlement_admin(yonghu_id, huiyuan_id, club_id=None):
"""后台移除会员:删 huiyuangoumai + 标记充值单已撤销,防止被 sync 补回。"""
from jituan.services.huiyuan_bundle import huiyuan_ids_match, normalize_huiyuan_id
hid = normalize_huiyuan_id(huiyuan_id)
if not hid:
return 0, '缺少会员ID'
removed = 0
for rec in list(Huiyuangoumai.query.filter(yonghu_id=yonghu_id)):
if huiyuan_ids_match(rec.huiyuan_id, hid):
rec.delete()
removed += 1
for order in Czjilu.query.filter(
yonghuid=yonghu_id,
leixing=MEMBER_LEIXING,
zhuangtai=MEMBER_PAID_STATUS,
):
if not huiyuan_ids_match(order.huiyuan_id, hid):
continue
if _czjilu_entitlement_revoked(order):
continue
note = (order.shuoming or '').strip()
order.shuoming = _truncate_shuoming(
f'{note} |{ENTITLEMENT_REVOKED_MARKER}|' if note else ENTITLEMENT_REVOKED_MARKER,
)
order.save(update_fields=['shuoming'])
if removed == 0:
return 0, '该打手未购买此会员'
return removed, None
@transaction.atomic
def fulfill_member_recharge(dingdan_id, paid_amount_yuan=None, source='callback'):
locked = Czjilu.query.filter(dingdan_id=dingdan_id).select_for_update()

View File

@@ -613,12 +613,10 @@ class KefuUpdateDashouView(APIView):
return Response({'code': 403, 'msg': '无权限给打手减会员'})
if not huiyuan_id:
return Response({'code': 400, 'msg': '缺少会员ID'})
try:
record = Huiyuangoumai.query.get(yonghu_id=dashou_id, huiyuan_id=huiyuan_id)
except Huiyuangoumai.DoesNotExist:
return Response({'code': 404, 'msg': '该打手未购买此会员'})
# 删除会员记录(或设置为过期,根据业务要求)
record.delete()
from jituan.services.member_recharge import revoke_member_entitlement_admin
removed, err = revoke_member_entitlement_admin(dashou_id, huiyuan_id)
if err:
return Response({'code': 404, 'msg': err})
Xiugaijilu.query.create(
yonghuid=dashou_id,
xiugaiid=kefu.user.Phone,