From cec7d69199bbd62526063cdfc01d971a1bcc6c46 Mon Sep 17 00:00:00 2001 From: XingQue Date: Wed, 8 Jul 2026 15:49:07 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=81=9C=E6=AD=A2=E6=AF=8F=E6=AC=A1?= =?UTF-8?q?=E6=8B=89=E5=8D=95=E8=A1=A5=E5=9B=9E=E5=B7=B2=E5=88=A0=E4=BC=9A?= =?UTF-8?q?=E5=91=98=EF=BC=9B=E5=90=8E=E5=8F=B0=E7=A7=BB=E9=99=A4=E6=A0=87?= =?UTF-8?q?=E8=AE=B0=E5=85=85=E5=80=BC=E5=8D=95=E6=92=A4=E9=94=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ddhq 不再全量 sync 充值单;jian_huiyuan 标记权益已撤销防复活;clumber 只读 huiyuangoumai。 Co-authored-by: Cursor --- backend/views/kefu.py | 9 ++--- jituan/services/huiyuan_bundle.py | 27 ++++--------- jituan/services/member_recharge.py | 63 ++++++++++++++++++++++++++++-- users/views/kefu_dashou.py | 10 ++--- 4 files changed, 76 insertions(+), 33 deletions(-) diff --git a/backend/views/kefu.py b/backend/views/kefu.py index 7bfbe29..8c1187c 100644 --- a/backend/views/kefu.py +++ b/backend/views/kefu.py @@ -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, diff --git a/jituan/services/huiyuan_bundle.py b/jituan/services/huiyuan_bundle.py index a3eae47..172581d 100644 --- a/jituan/services/huiyuan_bundle.py +++ b/jituan/services/huiyuan_bundle.py @@ -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 diff --git a/jituan/services/member_recharge.py b/jituan/services/member_recharge.py index d30a29b..ea382c0 100644 --- a/jituan/services/member_recharge.py +++ b/jituan/services/member_recharge.py @@ -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() diff --git a/users/views/kefu_dashou.py b/users/views/kefu_dashou.py index a7c24cd..4b0766e 100644 --- a/users/views/kefu_dashou.py +++ b/users/views/kefu_dashou.py @@ -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,