298 lines
10 KiB
Python
298 lines
10 KiB
Python
"""打手自助更换管事(推荐官):配额、校验、换绑。
|
||
|
||
分红:会员 formal_cishu 按打手+会员+俱乐部历史笔数计算,换管事后不会把续费当成「首次」再给新人满额首充分红。
|
||
"""
|
||
import logging
|
||
|
||
from django.core.exceptions import ObjectDoesNotExist
|
||
from django.db import transaction
|
||
from django.db.models import F
|
||
|
||
from jituan.services.club_context import resolve_effective_club_id
|
||
from jituan.services.invite_guard import assert_invite_same_club
|
||
from users.models import DashouChangeGuanshiLog, DashouChangeGuanshiQuota, UserDashou, UserGuanshi
|
||
from users.business_models import User
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
DEFAULT_QUOTA = 1
|
||
|
||
|
||
def _dashou_display_name(user):
|
||
try:
|
||
return (user.DashouProfile.nicheng or '').strip() or user.UserUID
|
||
except ObjectDoesNotExist:
|
||
return user.UserUID
|
||
|
||
|
||
def _guanshi_brief(uid):
|
||
if not uid:
|
||
return None
|
||
try:
|
||
u = User.query.get(UserUID=uid)
|
||
except User.DoesNotExist:
|
||
return {'uid': uid, 'nicheng': '未知', 'touxiang': ''}
|
||
nicheng = '管事'
|
||
try:
|
||
nicheng = (u.BossProfile.nickname or '').strip() or nicheng
|
||
except ObjectDoesNotExist:
|
||
try:
|
||
nicheng = (u.DashouProfile.nicheng or '').strip() or nicheng
|
||
except ObjectDoesNotExist:
|
||
pass
|
||
return {
|
||
'uid': u.UserUID,
|
||
'nicheng': nicheng,
|
||
'touxiang': u.Avatar or '',
|
||
}
|
||
|
||
|
||
def get_or_create_quota(club_id, dashou_uid):
|
||
club_id = (club_id or '').strip()
|
||
dashou_uid = (dashou_uid or '').strip()
|
||
obj, _ = DashouChangeGuanshiQuota.objects.get_or_create(
|
||
club_id=club_id,
|
||
dashou_uid=dashou_uid,
|
||
defaults={'total_quota': DEFAULT_QUOTA, 'used_count': 0},
|
||
)
|
||
return obj
|
||
|
||
|
||
def quota_payload(quota):
|
||
return {
|
||
'total_quota': int(quota.total_quota or 0),
|
||
'used_count': int(quota.used_count or 0),
|
||
'remain_quota': quota.remain_quota,
|
||
}
|
||
|
||
|
||
def resolve_guanshi_by_invite(invite_code):
|
||
"""预览:邀请码 → 管事简要信息。"""
|
||
invite_code = (invite_code or '').strip()
|
||
if not invite_code:
|
||
return {'ok': False, 'code': 400, 'msg': '请输入管事邀请码'}
|
||
try:
|
||
guanshi = UserGuanshi.query.select_related('user').get(yaoqingma=invite_code)
|
||
except UserGuanshi.DoesNotExist:
|
||
return {'ok': False, 'code': 404, 'msg': '邀请码无效或不存在'}
|
||
if guanshi.zhuangtai != 1:
|
||
return {'ok': False, 'code': 403, 'msg': '该邀请码对应的管事账号已被禁用'}
|
||
return {
|
||
'ok': True,
|
||
'code': 200,
|
||
'msg': 'ok',
|
||
'data': _guanshi_brief(guanshi.user.UserUID),
|
||
}
|
||
|
||
|
||
def build_change_guanshi_info(request, user):
|
||
try:
|
||
dashou = user.DashouProfile
|
||
except ObjectDoesNotExist:
|
||
return {'ok': False, 'code': 403, 'msg': '您还不是打手'}
|
||
|
||
if int(getattr(dashou, 'zhanghaozhuangtai', 1) or 1) != 1:
|
||
return {'ok': False, 'code': 403, 'msg': '打手账号已禁用,无法更换管事'}
|
||
|
||
club_id = resolve_effective_club_id(request, user)
|
||
quota = get_or_create_quota(club_id, user.UserUID)
|
||
return {
|
||
'ok': True,
|
||
'code': 200,
|
||
'msg': 'ok',
|
||
'data': {
|
||
'club_id': club_id,
|
||
'self_uid': user.UserUID,
|
||
'current_guanshi': _guanshi_brief(dashou.yaoqingren),
|
||
'quota': quota_payload(quota),
|
||
},
|
||
}
|
||
|
||
|
||
def change_guanshi_by_invite(request, user, invite_code, source='miniapp', operator=None):
|
||
"""用管事邀请码换绑 dashou.yaoqingren。不改管事的组长。"""
|
||
invite_code = (invite_code or '').strip()
|
||
if not invite_code:
|
||
return {'ok': False, 'code': 400, 'msg': '请输入管事邀请码'}
|
||
if len(invite_code) > 100:
|
||
return {'ok': False, 'code': 400, 'msg': '邀请码长度超过限制'}
|
||
|
||
try:
|
||
dashou = user.DashouProfile
|
||
except ObjectDoesNotExist:
|
||
return {'ok': False, 'code': 403, 'msg': '您还不是打手'}
|
||
|
||
if int(getattr(dashou, 'zhanghaozhuangtai', 1) or 1) != 1:
|
||
return {'ok': False, 'code': 403, 'msg': '打手账号已禁用,无法更换管事'}
|
||
|
||
try:
|
||
new_gs = UserGuanshi.query.select_related('user').get(yaoqingma=invite_code)
|
||
except UserGuanshi.DoesNotExist:
|
||
return {'ok': False, 'code': 404, 'msg': '邀请码无效或不存在'}
|
||
|
||
if new_gs.zhuangtai != 1:
|
||
return {'ok': False, 'code': 403, 'msg': '该邀请码对应的管事账号已被禁用'}
|
||
|
||
new_uid = new_gs.user.UserUID
|
||
if new_uid == user.UserUID:
|
||
return {'ok': False, 'code': 400, 'msg': '不能将自己设为管事'}
|
||
|
||
old_uid = (dashou.yaoqingren or '').strip()
|
||
if old_uid and old_uid == new_uid:
|
||
return {'ok': False, 'code': 400, 'msg': '已是该管事,无需更换'}
|
||
|
||
ok, invite_msg = assert_invite_same_club(request, new_gs.user, user)
|
||
if not ok:
|
||
return {'ok': False, 'code': 403, 'msg': invite_msg}
|
||
|
||
club_id = resolve_effective_club_id(request, user)
|
||
op = (operator or user.UserUID or '').strip()
|
||
|
||
with transaction.atomic():
|
||
quota = (
|
||
DashouChangeGuanshiQuota.objects
|
||
.select_for_update()
|
||
.filter(club_id=club_id, dashou_uid=user.UserUID)
|
||
.first()
|
||
)
|
||
if not quota:
|
||
quota = DashouChangeGuanshiQuota.objects.create(
|
||
club_id=club_id,
|
||
dashou_uid=user.UserUID,
|
||
total_quota=DEFAULT_QUOTA,
|
||
used_count=0,
|
||
)
|
||
quota = (
|
||
DashouChangeGuanshiQuota.objects
|
||
.select_for_update()
|
||
.get(pk=quota.pk)
|
||
)
|
||
|
||
if quota.remain_quota <= 0:
|
||
return {'ok': False, 'code': 403, 'msg': '更换次数已用完,请联系客服增加次数'}
|
||
|
||
dashou = (
|
||
UserDashou.query
|
||
.select_for_update()
|
||
.select_related('user')
|
||
.get(pk=dashou.pk)
|
||
)
|
||
old_uid = (dashou.yaoqingren or '').strip()
|
||
if old_uid == new_uid:
|
||
return {'ok': False, 'code': 400, 'msg': '已是该管事,无需更换'}
|
||
if new_uid == user.UserUID:
|
||
return {'ok': False, 'code': 400, 'msg': '不能将自己设为管事'}
|
||
|
||
dashou.yaoqingren = new_uid
|
||
dashou.save(update_fields=['yaoqingren'])
|
||
|
||
if old_uid:
|
||
try:
|
||
old_gs = UserGuanshi.query.select_related('user').get(user__UserUID=old_uid)
|
||
if (old_gs.yaogingshuliang or 0) > 0:
|
||
UserGuanshi.query.filter(pk=old_gs.pk).update(
|
||
yaogingshuliang=F('yaogingshuliang') - 1
|
||
)
|
||
except UserGuanshi.DoesNotExist:
|
||
pass
|
||
UserGuanshi.query.filter(pk=new_gs.pk).update(
|
||
yaogingshuliang=F('yaogingshuliang') + 1
|
||
)
|
||
|
||
quota.used_count = int(quota.used_count or 0) + 1
|
||
quota.save(update_fields=['used_count', 'UpdateTime'])
|
||
|
||
DashouChangeGuanshiLog.objects.create(
|
||
club_id=club_id,
|
||
dashou_uid=user.UserUID,
|
||
dashou_name=_dashou_display_name(user)[:50],
|
||
old_guanshi_uid=old_uid or '',
|
||
new_guanshi_uid=new_uid,
|
||
new_invite_code=invite_code[:40],
|
||
source=source or 'miniapp',
|
||
operator=op[:64],
|
||
)
|
||
|
||
logger.info(
|
||
'dashou_change_guanshi ok club=%s ds=%s %s -> %s source=%s',
|
||
club_id, user.UserUID, old_uid, new_uid, source,
|
||
)
|
||
return {
|
||
'ok': True,
|
||
'code': 200,
|
||
'msg': '更换管事成功',
|
||
'data': {
|
||
'current_guanshi': _guanshi_brief(new_uid),
|
||
'quota': quota_payload(quota),
|
||
'old_guanshi_uid': old_uid or '',
|
||
'new_guanshi_uid': new_uid,
|
||
},
|
||
}
|
||
|
||
|
||
def set_total_quota(club_id, dashou_uid, total_quota):
|
||
try:
|
||
total = int(total_quota)
|
||
except (TypeError, ValueError):
|
||
return {'ok': False, 'code': 400, 'msg': '总次数须为整数'}
|
||
if total < 0:
|
||
return {'ok': False, 'code': 400, 'msg': '总次数不能为负数'}
|
||
quota = get_or_create_quota(club_id, dashou_uid)
|
||
if total < int(quota.used_count or 0):
|
||
return {
|
||
'ok': False,
|
||
'code': 400,
|
||
'msg': f'总次数不能小于已用次数(已用 {quota.used_count})',
|
||
}
|
||
quota.total_quota = total
|
||
quota.save(update_fields=['total_quota', 'UpdateTime'])
|
||
return {'ok': True, 'code': 0, 'msg': '已更新', 'data': quota_payload(quota)}
|
||
|
||
|
||
def add_quota(club_id, dashou_uid, add_n):
|
||
try:
|
||
n = int(add_n)
|
||
except (TypeError, ValueError):
|
||
return {'ok': False, 'code': 400, 'msg': '追加次数须为整数'}
|
||
if n <= 0:
|
||
return {'ok': False, 'code': 400, 'msg': '追加次数须大于 0'}
|
||
quota = get_or_create_quota(club_id, dashou_uid)
|
||
quota.total_quota = int(quota.total_quota or 0) + n
|
||
quota.save(update_fields=['total_quota', 'UpdateTime'])
|
||
return {'ok': True, 'code': 0, 'msg': '已追加', 'data': quota_payload(quota)}
|
||
|
||
|
||
def list_change_logs(club_id, dashou_uid, page=1, page_size=20):
|
||
qs = (
|
||
DashouChangeGuanshiLog.objects
|
||
.filter(club_id=club_id, dashou_uid=dashou_uid)
|
||
.order_by('-CreateTime', '-id')
|
||
)
|
||
total = qs.count()
|
||
try:
|
||
page = max(1, int(page))
|
||
except (TypeError, ValueError):
|
||
page = 1
|
||
try:
|
||
page_size = min(100, max(1, int(page_size)))
|
||
except (TypeError, ValueError):
|
||
page_size = 20
|
||
start = (page - 1) * page_size
|
||
rows = []
|
||
for row in qs[start:start + page_size]:
|
||
rows.append({
|
||
'id': row.id,
|
||
'old_guanshi_uid': row.old_guanshi_uid,
|
||
'new_guanshi_uid': row.new_guanshi_uid,
|
||
'new_invite_code': row.new_invite_code,
|
||
'source': row.source,
|
||
'operator': row.operator,
|
||
'created_at': row.CreateTime.isoformat() if row.CreateTime else None,
|
||
})
|
||
return {
|
||
'list': rows,
|
||
'total': total,
|
||
'page': page,
|
||
'page_size': page_size,
|
||
}
|