Files
Django/users/services/change_leader.py

277 lines
9.0 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.
"""管事自助更换组长:配额、校验、换绑。"""
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 GuanshiChangeLeaderLog, GuanshiChangeLeaderQuota, UserGuanshi, UserZuzhang
from users.business_models import User
logger = logging.getLogger(__name__)
DEFAULT_QUOTA = 1
def _guanshi_display_name(user):
try:
return (user.BossProfile.nickname or '').strip() or user.UserUID
except ObjectDoesNotExist:
return user.UserUID
def _zuzhang_brief(uid):
if not uid:
return None
try:
u = User.query.get(UserUID=uid)
except User.DoesNotExist:
return {'uid': uid, 'nicheng': '未知', 'touxiang': ''}
try:
nicheng = u.BossProfile.nickname or '组长'
except ObjectDoesNotExist:
nicheng = '组长'
return {
'uid': u.UserUID,
'nicheng': nicheng,
'touxiang': u.Avatar or '',
}
def get_or_create_quota(club_id, guanshi_uid):
club_id = (club_id or '').strip()
guanshi_uid = (guanshi_uid or '').strip()
obj, _ = GuanshiChangeLeaderQuota.objects.get_or_create(
club_id=club_id,
guanshi_uid=guanshi_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 build_change_leader_info(request, user):
"""小程序 info 接口数据。"""
try:
guanshi = user.GuanshiProfile
except ObjectDoesNotExist:
return {'ok': False, 'code': 403, 'msg': '您还不是管事'}
if guanshi.zhuangtai != 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,
'current_zuzhang': _zuzhang_brief(guanshi.yaoqingren),
'quota': quota_payload(quota),
},
}
def change_leader_by_invite(request, user, invite_code, source='miniapp', operator=None):
"""
用组长邀请码换绑。成功返回 data失败返回 ok=False。
仅更新管事 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:
guanshi = user.GuanshiProfile
except ObjectDoesNotExist:
return {'ok': False, 'code': 403, 'msg': '您还不是管事'}
if guanshi.zhuangtai != 1:
return {'ok': False, 'code': 403, 'msg': '管事账号已禁用,无法更换组长'}
try:
zuzhang = UserZuzhang.query.select_related('user').get(yaoqingma=invite_code)
except UserZuzhang.DoesNotExist:
return {'ok': False, 'code': 404, 'msg': '邀请码无效或不存在'}
if zuzhang.zhuangtai != 1:
return {'ok': False, 'code': 403, 'msg': '该邀请码对应的组长账号已被禁用'}
new_uid = zuzhang.user.UserUID
if new_uid == user.UserUID:
return {'ok': False, 'code': 400, 'msg': '不能将自己设为组长'}
old_uid = (guanshi.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, zuzhang.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 = (
GuanshiChangeLeaderQuota.objects
.select_for_update()
.filter(club_id=club_id, guanshi_uid=user.UserUID)
.first()
)
if not quota:
quota = GuanshiChangeLeaderQuota.objects.create(
club_id=club_id,
guanshi_uid=user.UserUID,
total_quota=DEFAULT_QUOTA,
used_count=0,
)
quota = (
GuanshiChangeLeaderQuota.objects
.select_for_update()
.get(pk=quota.pk)
)
if quota.remain_quota <= 0:
return {'ok': False, 'code': 403, 'msg': '更换次数已用完,请联系客服增加次数'}
# 再读一次管事行,防并发
guanshi = (
UserGuanshi.query
.select_for_update()
.select_related('user')
.get(pk=guanshi.pk)
)
old_uid = (guanshi.yaoqingren or '').strip()
if old_uid == new_uid:
return {'ok': False, 'code': 400, 'msg': '已是该组长,无需更换'}
guanshi.yaoqingren = new_uid
guanshi.save(update_fields=['yaoqingren'])
if old_uid:
try:
old_zz = UserZuzhang.query.select_related('user').get(user__UserUID=old_uid)
if (old_zz.yaoqing_zongshu or 0) > 0:
UserZuzhang.query.filter(pk=old_zz.pk).update(
yaoqing_zongshu=F('yaoqing_zongshu') - 1
)
except UserZuzhang.DoesNotExist:
pass
UserZuzhang.query.filter(pk=zuzhang.pk).update(
yaoqing_zongshu=F('yaoqing_zongshu') + 1
)
quota.used_count = int(quota.used_count or 0) + 1
quota.save(update_fields=['used_count', 'UpdateTime'])
GuanshiChangeLeaderLog.objects.create(
club_id=club_id,
guanshi_uid=user.UserUID,
guanshi_name=_guanshi_display_name(user)[:50],
old_zuzhang_uid=old_uid or '',
new_zuzhang_uid=new_uid,
new_invite_code=invite_code[:40],
source=source or 'miniapp',
operator=op[:64],
)
logger.info(
'guanshi_change_leader ok club=%s gs=%s %s -> %s source=%s',
club_id, user.UserUID, old_uid, new_uid, source,
)
return {
'ok': True,
'code': 200,
'msg': '更换组长成功',
'data': {
'current_zuzhang': _zuzhang_brief(new_uid),
'quota': quota_payload(quota),
'old_zuzhang_uid': old_uid or '',
'new_zuzhang_uid': new_uid,
},
}
def set_total_quota(club_id, guanshi_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, guanshi_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, guanshi_uid, add_n):
"""后台追加次数(增加 total_quota"""
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, guanshi_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, guanshi_uid, page=1, page_size=20):
qs = (
GuanshiChangeLeaderLog.objects
.filter(club_id=club_id, guanshi_uid=guanshi_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_zuzhang_uid': row.old_zuzhang_uid,
'new_zuzhang_uid': row.new_zuzhang_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,
}