Files
Django/users/services/c_invite.py
XingQue 60d8b52693 feat: 点单端邀请绑定与只看审核商品开关
复用 ClubMiniappIcon 增加点单端海报背景;新增 C 端邀请码/换绑店铺接口;hqsp 支持 zhi_kan_shenhe,不影响支付抢单分红。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-21 02:37:31 +08:00

185 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""点单端 C 用户邀请:邀请码、换绑、店铺跟随。"""
from django.db import transaction
from django.db.models import F
from shop.models import Dianpu, YonghuDianpuBangding
from users.models import YonghuCYaoqing
from users.business_models import User
from utils.invitationcode_utils import CreateInvitationCode, VerifyInvitationCode
import logging
logger = logging.getLogger(__name__)
def get_or_create_c_yaoqing(user):
"""获取或创建用户的点单端邀请扩展记录。"""
profile, _ = YonghuCYaoqing.objects.get_or_create(user=user)
return profile
def ensure_c_invite_code(user):
"""确保用户有邀请码,返回 (profile, yaoqingma, is_new)。"""
profile = get_or_create_c_yaoqing(user)
existing = (profile.yaoqingma or '').strip()
if existing:
return profile, existing, False
new_code = CreateInvitationCode(str(user.UserUID))
if not VerifyInvitationCode(new_code):
raise ValueError('邀请码生成失败')
# 极低概率冲突时再生成一次
if YonghuCYaoqing.query.filter(yaoqingma=new_code).exclude(pk=profile.pk).exists():
new_code = CreateInvitationCode(str(user.UserUID) + 'R')
if not VerifyInvitationCode(new_code):
raise ValueError('邀请码生成失败')
profile.yaoqingma = new_code
profile.save(update_fields=['yaoqingma', 'UpdateTime'])
return profile, new_code, True
def _unbind_shop(user_locked):
"""解除用户店铺绑定并扣减旧店绑定人数。"""
binding = (
YonghuDianpuBangding.objects.select_for_update()
.filter(yonghu=user_locked)
.select_related('dianpu')
.first()
)
if not binding:
return None
old = binding.dianpu
old.bangding_yonghushu = F('bangding_yonghushu') - 1
old.save(update_fields=['bangding_yonghushu'])
binding.delete()
return old.id
def _bind_shop(user_locked, dianpu_locked):
"""将用户绑定到指定店铺(已存在则换绑)。返回 (changed, msg)。"""
if dianpu_locked.zhuangtai != 1:
return False, '邀请人店铺已被封禁'
try:
existing = YonghuDianpuBangding.objects.select_for_update().get(yonghu=user_locked)
if existing.dianpu_id == dianpu_locked.id:
return False, '已绑定该店铺'
old = existing.dianpu
old.bangding_yonghushu = F('bangding_yonghushu') - 1
old.save(update_fields=['bangding_yonghushu'])
existing.dianpu = dianpu_locked
existing.save(update_fields=['dianpu', 'UpdateTime'])
except YonghuDianpuBangding.DoesNotExist:
YonghuDianpuBangding.query.create(yonghu=user_locked, dianpu=dianpu_locked)
dianpu_locked.bangding_yonghushu = F('bangding_yonghushu') + 1
dianpu_locked.save(update_fields=['bangding_yonghushu'])
return True, '店铺已跟随邀请人换绑'
def sync_shop_from_inviter(invitee_user, inviter_user):
"""
按邀请人店铺同步被邀请人店铺:
- 邀请人有正常店铺 → 被邀请人绑到同一店
- 邀请人无店铺 → 被邀请人解绑店铺(回落平台公共商品逻辑)
须在 transaction.atomic 内调用。
"""
invitee_locked = User.objects.select_for_update().get(UserUUID=invitee_user.UserUUID)
inviter_locked = User.objects.select_for_update().get(UserUUID=inviter_user.UserUUID)
inviter_binding = (
YonghuDianpuBangding.objects.select_for_update()
.filter(yonghu=inviter_locked)
.select_related('dianpu')
.first()
)
if inviter_binding and inviter_binding.dianpu.zhuangtai == 1:
dianpu_locked = Dianpu.objects.select_for_update().get(id=inviter_binding.dianpu_id)
changed, msg = _bind_shop(invitee_locked, dianpu_locked)
return {'shop_synced': True, 'dianpu_id': dianpu_locked.id, 'changed': changed, 'msg': msg}
old_id = _unbind_shop(invitee_locked)
return {
'shop_synced': False,
'dianpu_id': None,
'changed': bool(old_id),
'msg': '邀请人未绑定店铺,已解除店铺绑定' if old_id else '邀请人未绑定店铺',
}
def bind_c_inviter_by_code(invitee_user, invite_code):
"""
用邀请码绑定/换绑邀请人,并同步店铺。
返回 dict: code/msg/data
"""
code = (invite_code or '').strip()
if not code:
return {'code': 400, 'msg': '邀请码不能为空', 'data': None}
if len(code) > 100:
return {'code': 400, 'msg': '邀请码格式错误', 'data': None}
try:
inviter_profile = YonghuCYaoqing.query.select_related('user').get(yaoqingma=code)
except YonghuCYaoqing.DoesNotExist:
return {'code': 404, 'msg': '邀请码无效', 'data': None}
inviter = inviter_profile.user
if inviter.UserUID == invitee_user.UserUID:
return {'code': 400, 'msg': '不能绑定自己为邀请人', 'data': None}
with transaction.atomic():
invitee_profile = get_or_create_c_yaoqing(invitee_user)
invitee_profile = YonghuCYaoqing.objects.select_for_update().get(pk=invitee_profile.pk)
inviter_profile = YonghuCYaoqing.objects.select_for_update().get(pk=inviter_profile.pk)
old_inviter_uid = (invitee_profile.yaoqingren or '').strip() or None
new_inviter_uid = inviter.UserUID
if old_inviter_uid == new_inviter_uid:
shop_info = sync_shop_from_inviter(invitee_user, inviter)
return {
'code': 200,
'msg': '已绑定该邀请人',
'data': {
'yaoqingren': new_inviter_uid,
'rebinding': False,
'shop': shop_info,
},
}
# 换绑:旧邀请人计数 -1新邀请人 +1
if old_inviter_uid:
old_p = (
YonghuCYaoqing.objects.select_for_update()
.filter(user__UserUID=old_inviter_uid)
.first()
)
if old_p and old_p.yaogingshuliang > 0:
old_p.yaogingshuliang = F('yaogingshuliang') - 1
old_p.save(update_fields=['yaogingshuliang', 'UpdateTime'])
inviter_profile.yaogingshuliang = F('yaogingshuliang') + 1
inviter_profile.save(update_fields=['yaogingshuliang', 'UpdateTime'])
invitee_profile.yaoqingren = new_inviter_uid
invitee_profile.save(update_fields=['yaoqingren', 'UpdateTime'])
shop_info = sync_shop_from_inviter(invitee_user, inviter)
logger.info(
'C端邀请绑定: invitee=%s inviter=%s old=%s shop=%s',
invitee_user.UserUID, new_inviter_uid, old_inviter_uid, shop_info,
)
return {
'code': 200,
'msg': '绑定成功' if not old_inviter_uid else '已换绑邀请人',
'data': {
'yaoqingren': new_inviter_uid,
'rebinding': bool(old_inviter_uid),
'shop': shop_info,
},
}