feat: 打手自助更换管事(配额/防自换/同俱乐部)
This commit is contained in:
1 parent
d849991380
commit
c1a5a3e988
12 files changed
+681
-4
No files matched your search
+3
-1
@@ -6,7 +6,7 @@ from .view import (GetRolePermissionView,
|
||||
ModifyAdminUserView, KefuGetDashouListView, KefuGetDashouDetailView, KefuUpdateDashouView, KefuGetShangjiaListView,
|
||||
KefuGetShangjiaDetailView, KefuUpdateShangjiaView, KefuAddShangjiaView, GetProductBaseDataView, GetProductListView, SaveProductOrderView,
|
||||
AddProductView, DeleteProductView, GetProductDetailView, UpdateProductView, UpdateZhiKanShenheView, UpdateMemberView, GetMemberListView,
|
||||
AddMemberView, GetGuanliListView, GetGuanliDetailView, PromoteGuanshiToZuzhangView, ChangeInviterView, UpdateGuanliView, GuanshiChangeLeaderQuotaView, GuanshiChangeLeaderLogView, GetWithdrawSettingsView,
|
||||
AddMemberView, GetGuanliListView, GetGuanliDetailView, PromoteGuanshiToZuzhangView, ChangeInviterView, UpdateGuanliView, GuanshiChangeLeaderQuotaView, GuanshiChangeLeaderLogView, DashouChangeGuanshiQuotaView, DashouChangeGuanshiLogView, GetWithdrawSettingsView,
|
||||
UpdateWithdrawSettingsView, GetZuzhangListView, GetZuzhangDetailView, UpdateZuzhangView, GetOperationLogListView, GetProductTypeZoneView,
|
||||
ModifyProductTypeZoneView, GetRateView, ModifyRateView, PopupNoticeListAPIView, PopupNoticeModifyAPIView,ShopListView,
|
||||
ShopModifyView, ShopPublicTypeAndAuditView, ShopListForProductView, ShopProductModifyView,
|
||||
@@ -58,6 +58,8 @@ urlpatterns = [
|
||||
path('htghyqr', ChangeInviterView.as_view(), name='后台更换邀请人'),
|
||||
path('htgs_huan_zz_quota', GuanshiChangeLeaderQuotaView.as_view(), name='后台管事换组长次数'),
|
||||
path('htgs_huan_zz_logs', GuanshiChangeLeaderLogView.as_view(), name='后台管事换组长记录'),
|
||||
path('htds_huan_gs_quota', DashouChangeGuanshiQuotaView.as_view(), name='后台打手换管事次数'),
|
||||
path('htds_huan_gs_logs', DashouChangeGuanshiLogView.as_view(), name='后台打手换管事记录'),
|
||||
path('htxggsxx', UpdateGuanliView.as_view(), name='后台更新管事数据'),
|
||||
path('hqczrz', GetOperationLogListView.as_view(), name='后台操作日志列表'),
|
||||
path('hthqtxpz', GetWithdrawSettingsView.as_view(), name='后台获取提现配置'),
|
||||
|
||||
@@ -52,6 +52,7 @@ from .views.members import (
|
||||
from .views.guanli import (
|
||||
GetGuanliListView, GetGuanliDetailView, PromoteGuanshiToZuzhangView,
|
||||
UpdateGuanliView, GuanshiChangeLeaderQuotaView, GuanshiChangeLeaderLogView,
|
||||
DashouChangeGuanshiQuotaView, DashouChangeGuanshiLogView,
|
||||
)
|
||||
from .views.withdraw_settings import (
|
||||
GetWithdrawSettingsView, UpdateWithdrawSettingsView,
|
||||
@@ -114,6 +115,7 @@ __all__ = [
|
||||
# guanli
|
||||
'GetGuanliListView', 'GetGuanliDetailView', 'PromoteGuanshiToZuzhangView',
|
||||
'UpdateGuanliView', 'GuanshiChangeLeaderQuotaView', 'GuanshiChangeLeaderLogView',
|
||||
'DashouChangeGuanshiQuotaView', 'DashouChangeGuanshiLogView',
|
||||
# withdraw_settings
|
||||
'GetWithdrawSettingsView', 'UpdateWithdrawSettingsView',
|
||||
# zuzhang
|
||||
|
||||
@@ -983,6 +983,106 @@ class GuanshiChangeLeaderLogView(APIView):
|
||||
return Response({'code': 0, 'data': data})
|
||||
|
||||
|
||||
class DashouChangeGuanshiQuotaView(APIView):
|
||||
"""
|
||||
后台设置/追加打手换管事次数
|
||||
POST /houtai/htds_huan_gs_quota
|
||||
参数:username, yonghuid, action=set|add, total_quota / add_count
|
||||
权限:001aa 或 000001
|
||||
"""
|
||||
permission_classes = []
|
||||
parser_classes = [JSONParser]
|
||||
|
||||
def post(self, request):
|
||||
username = request.data.get('username', '').strip()
|
||||
yonghuid = request.data.get('yonghuid', '').strip()
|
||||
action = (request.data.get('action') or 'add').strip().lower()
|
||||
|
||||
if not username:
|
||||
return Response({'code': 401, 'msg': '缺少username'})
|
||||
if not yonghuid:
|
||||
return Response({'code': 400, 'msg': '缺少用户ID'})
|
||||
|
||||
kefu, permissions = verify_kefu_permission(request, username)
|
||||
if kefu is None:
|
||||
return permissions
|
||||
if '001aa' not in permissions and '000001' not in permissions:
|
||||
return Response({'code': 403, 'msg': '无权限修改换管事次数(需要001aa)'})
|
||||
|
||||
try:
|
||||
user = User.query.select_related('DashouProfile').get(UserUID=yonghuid)
|
||||
_ = user.DashouProfile
|
||||
except User.DoesNotExist:
|
||||
return Response({'code': 404, 'msg': '打手不存在'})
|
||||
except ObjectDoesNotExist:
|
||||
return Response({'code': 404, 'msg': '用户不是打手'})
|
||||
|
||||
denied = forbid_if_user_out_of_scope(request, user)
|
||||
if denied:
|
||||
return denied
|
||||
|
||||
from users.services.change_guanshi import set_total_quota, add_quota
|
||||
club_id = _admin_quota_club_id(request, user)
|
||||
if action == 'set':
|
||||
result = set_total_quota(club_id, yonghuid, request.data.get('total_quota'))
|
||||
else:
|
||||
result = add_quota(
|
||||
club_id,
|
||||
yonghuid,
|
||||
request.data.get('add_count') or request.data.get('add'),
|
||||
)
|
||||
if not result.get('ok'):
|
||||
return Response({'code': result.get('code', 400), 'msg': result.get('msg', '失败')})
|
||||
return Response({'code': 0, 'msg': result.get('msg', 'ok'), 'data': result.get('data')})
|
||||
|
||||
|
||||
class DashouChangeGuanshiLogView(APIView):
|
||||
"""
|
||||
后台查询打手换管事记录
|
||||
POST /houtai/htds_huan_gs_logs
|
||||
参数:username, yonghuid, page, page_size
|
||||
权限:打手管理相关任一
|
||||
"""
|
||||
permission_classes = []
|
||||
parser_classes = [JSONParser]
|
||||
|
||||
def post(self, request):
|
||||
username = request.data.get('username', '').strip()
|
||||
yonghuid = request.data.get('yonghuid', '').strip()
|
||||
|
||||
if not username:
|
||||
return Response({'code': 401, 'msg': '缺少username'})
|
||||
if not yonghuid:
|
||||
return Response({'code': 400, 'msg': '缺少用户ID'})
|
||||
|
||||
kefu, permissions = verify_kefu_permission(request, username)
|
||||
if kefu is None:
|
||||
return permissions
|
||||
required_perms = {'001aa', '001bb', '001cc', '001dd', '001ee', '001ff', '001gg', '000001'}
|
||||
if not any(p in permissions for p in required_perms):
|
||||
return Response({'code': 403, 'msg': '您没有权限查看换管事记录'})
|
||||
|
||||
try:
|
||||
user = User.query.select_related('DashouProfile').get(UserUID=yonghuid)
|
||||
_ = user.DashouProfile
|
||||
except User.DoesNotExist:
|
||||
return Response({'code': 404, 'msg': '打手不存在'})
|
||||
except ObjectDoesNotExist:
|
||||
return Response({'code': 404, 'msg': '用户不是打手'})
|
||||
|
||||
denied = forbid_if_user_out_of_scope(request, user)
|
||||
if denied:
|
||||
return denied
|
||||
|
||||
from users.services.change_guanshi import list_change_logs
|
||||
club_id = _admin_quota_club_id(request, user)
|
||||
data = list_change_logs(
|
||||
club_id,
|
||||
yonghuid,
|
||||
page=request.data.get('page', 1),
|
||||
page_size=request.data.get('page_size', 20),
|
||||
)
|
||||
return Response({'code': 0, 'data': data})
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -528,6 +528,19 @@ class KefuGetDashouDetailView(APIView):
|
||||
from jituan.services.gift_huiyuan import list_gift_huiyuan_for_admin
|
||||
gift_member_list = list_gift_huiyuan_for_admin(uid)
|
||||
|
||||
from jituan.services.club_context import resolve_club_id_from_request, resolve_club_scope
|
||||
from jituan.constants import DATA_SCOPE_ALL
|
||||
from jituan.services.club_user import get_user_club_id
|
||||
from users.services.change_guanshi import get_or_create_quota, quota_payload, list_change_logs
|
||||
|
||||
if resolve_club_scope(request) == DATA_SCOPE_ALL:
|
||||
quota_club = get_user_club_id(user_main)
|
||||
else:
|
||||
quota_club = resolve_club_id_from_request(request)
|
||||
quota = get_or_create_quota(quota_club, uid)
|
||||
change_guanshi_quota = quota_payload(quota)
|
||||
change_guanshi_logs = list_change_logs(quota_club, uid, page=1, page_size=20)
|
||||
|
||||
return Response({
|
||||
'code': 0,
|
||||
'data': {
|
||||
@@ -536,6 +549,8 @@ class KefuGetDashouDetailView(APIView):
|
||||
'gift_member_list': gift_member_list,
|
||||
'all_members': all_members,
|
||||
'permissions': permissions, # 返回当前客服的权限列表
|
||||
'change_guanshi_quota': change_guanshi_quota,
|
||||
'change_guanshi_logs': change_guanshi_logs,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0010_guanshi_change_leader'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DashouChangeGuanshiQuota',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('club_id', models.CharField(db_index=True, max_length=16, verbose_name='俱乐部ID')),
|
||||
('dashou_uid', models.CharField(db_index=True, max_length=7, verbose_name='打手UserUID')),
|
||||
('total_quota', models.IntegerField(default=1, verbose_name='总次数')),
|
||||
('used_count', models.IntegerField(default=0, verbose_name='已用次数')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '打手换管事配额',
|
||||
'verbose_name_plural': '打手换管事配额',
|
||||
'db_table': 'dashou_change_guanshi_quota',
|
||||
'unique_together': {('club_id', 'dashou_uid')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DashouChangeGuanshiLog',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('club_id', models.CharField(db_index=True, max_length=16, verbose_name='俱乐部ID')),
|
||||
('dashou_uid', models.CharField(db_index=True, max_length=7, verbose_name='打手UserUID')),
|
||||
('dashou_name', models.CharField(blank=True, default='', max_length=50, verbose_name='打手昵称')),
|
||||
('old_guanshi_uid', models.CharField(blank=True, default='', max_length=7, verbose_name='原管事UID')),
|
||||
('new_guanshi_uid', models.CharField(db_index=True, max_length=7, verbose_name='新管事UID')),
|
||||
('new_invite_code', models.CharField(blank=True, default='', max_length=40, verbose_name='提交的邀请码')),
|
||||
('source', models.CharField(default='miniapp', max_length=20, verbose_name='来源')),
|
||||
('operator', models.CharField(blank=True, default='', max_length=64, verbose_name='操作人')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='更换时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '打手换管事记录',
|
||||
'verbose_name_plural': '打手换管事记录',
|
||||
'db_table': 'dashou_change_guanshi_log',
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='dashouchangeguanshiquota',
|
||||
index=models.Index(fields=['club_id', 'dashou_uid'], name='dashou_chan_club_id_7c8a1e_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='dashouchangeguanshilog',
|
||||
index=models.Index(fields=['club_id', 'dashou_uid', '-CreateTime'], name='dashou_chan_club_id_9f2b0d_idx'),
|
||||
),
|
||||
]
|
||||
@@ -759,6 +759,51 @@ class GuanshiChangeLeaderLog(QModel):
|
||||
]
|
||||
|
||||
|
||||
# ==================== 打手更换管事:配额 / 记录 ====================
|
||||
class DashouChangeGuanshiQuota(QModel):
|
||||
"""打手自助更换管事(推荐官)次数配额"""
|
||||
club_id = models.CharField(max_length=16, db_index=True, verbose_name='俱乐部ID')
|
||||
dashou_uid = models.CharField(max_length=7, db_index=True, verbose_name='打手UserUID')
|
||||
total_quota = models.IntegerField(default=1, verbose_name='总次数')
|
||||
used_count = models.IntegerField(default=0, verbose_name='已用次数')
|
||||
CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'dashou_change_guanshi_quota'
|
||||
verbose_name = '打手换管事配额'
|
||||
verbose_name_plural = verbose_name
|
||||
unique_together = [['club_id', 'dashou_uid']]
|
||||
indexes = [
|
||||
models.Index(fields=['club_id', 'dashou_uid']),
|
||||
]
|
||||
|
||||
@property
|
||||
def remain_quota(self):
|
||||
return max(0, int(self.total_quota or 0) - int(self.used_count or 0))
|
||||
|
||||
|
||||
class DashouChangeGuanshiLog(QModel):
|
||||
"""打手更换管事记录"""
|
||||
club_id = models.CharField(max_length=16, db_index=True, verbose_name='俱乐部ID')
|
||||
dashou_uid = models.CharField(max_length=7, db_index=True, verbose_name='打手UserUID')
|
||||
dashou_name = models.CharField(max_length=50, blank=True, default='', verbose_name='打手昵称')
|
||||
old_guanshi_uid = models.CharField(max_length=7, blank=True, default='', verbose_name='原管事UID')
|
||||
new_guanshi_uid = models.CharField(max_length=7, db_index=True, verbose_name='新管事UID')
|
||||
new_invite_code = models.CharField(max_length=40, blank=True, default='', verbose_name='提交的邀请码')
|
||||
source = models.CharField(max_length=20, default='miniapp', verbose_name='来源')
|
||||
operator = models.CharField(max_length=64, blank=True, default='', verbose_name='操作人')
|
||||
CreateTime = models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='更换时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'dashou_change_guanshi_log'
|
||||
verbose_name = '打手换管事记录'
|
||||
verbose_name_plural = verbose_name
|
||||
indexes = [
|
||||
models.Index(fields=['club_id', 'dashou_uid', '-CreateTime']),
|
||||
]
|
||||
|
||||
|
||||
# === 业务用户模型注册 ===
|
||||
# 导入业务子类,使 Django app registry 注册具体 User 模型(users.User)。
|
||||
# 放在文件末尾以避免循环导入:此时本模块所有类已定义完毕。
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
"""打手自助更换管事(推荐官):配额、校验、换绑。
|
||||
|
||||
分红:会员 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,
|
||||
}
|
||||
@@ -59,6 +59,25 @@ def quota_payload(quota):
|
||||
}
|
||||
|
||||
|
||||
def resolve_zuzhang_by_invite(invite_code):
|
||||
"""预览:邀请码 → 组长简要信息。"""
|
||||
invite_code = (invite_code or '').strip()
|
||||
if not invite_code:
|
||||
return {'ok': False, 'code': 400, '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': '该邀请码对应的组长账号已被禁用'}
|
||||
return {
|
||||
'ok': True,
|
||||
'code': 200,
|
||||
'msg': 'ok',
|
||||
'data': _zuzhang_brief(zuzhang.user.UserUID),
|
||||
}
|
||||
|
||||
|
||||
def build_change_leader_info(request, user):
|
||||
"""小程序 info 接口数据。"""
|
||||
try:
|
||||
@@ -77,6 +96,7 @@ def build_change_leader_info(request, user):
|
||||
'msg': 'ok',
|
||||
'data': {
|
||||
'club_id': club_id,
|
||||
'self_uid': user.UserUID,
|
||||
'current_zuzhang': _zuzhang_brief(guanshi.yaoqingren),
|
||||
'quota': quota_payload(quota),
|
||||
},
|
||||
|
||||
+8
-1
@@ -21,7 +21,8 @@ from .views import WechatMiniProgramLoginView, UserInfoUpdateView, DashouXinxiAP
|
||||
GetUserPhoneView, BindUserPhoneView, BossSearchDashouView, \
|
||||
FundFreezeMineView, FundFreezeMyLedgerView, FundFreezeMyLedgerDetailView, \
|
||||
RoleAgreementCurrentView, RoleAgreementMineView, RoleAgreementSignView, \
|
||||
GuanshiChangeZuzhangInfoView, GuanshiChangeZuzhangView
|
||||
GuanshiChangeZuzhangInfoView, GuanshiChangeZuzhangPreviewView, GuanshiChangeZuzhangView, \
|
||||
DashouChangeGuanshiInfoView, DashouChangeGuanshiPreviewView, DashouChangeGuanshiView
|
||||
from .tixian_shenhe_views import TixianZddkshApplyView
|
||||
from .paihang_views import PhbHqsjView
|
||||
from .xzj_paihang_views import XzjPhbHqsjView
|
||||
@@ -138,8 +139,14 @@ urlpatterns = [
|
||||
|
||||
# 管事自助更换组长
|
||||
path('gs_huan_zuzhang_info', GuanshiChangeZuzhangInfoView.as_view(), name='管事换组长信息'),
|
||||
path('gs_huan_zuzhang_preview', GuanshiChangeZuzhangPreviewView.as_view(), name='管事换组长预览'),
|
||||
path('gs_huan_zuzhang', GuanshiChangeZuzhangView.as_view(), name='管事换组长提交'),
|
||||
|
||||
# 打手自助更换管事
|
||||
path('ds_huan_guanshi_info', DashouChangeGuanshiInfoView.as_view(), name='打手换管事信息'),
|
||||
path('ds_huan_guanshi_preview', DashouChangeGuanshiPreviewView.as_view(), name='打手换管事预览'),
|
||||
path('ds_huan_guanshi', DashouChangeGuanshiView.as_view(), name='打手换管事提交'),
|
||||
|
||||
path('zuzhangzhuce', ZuzhangZhuceView.as_view(), name='组长注册'),
|
||||
path('zuzhangxinxi', ZuzhangXinxiView.as_view(), name='组长刷新获取信息'),
|
||||
|
||||
|
||||
@@ -76,9 +76,16 @@ from .role_agreement_mp import (
|
||||
|
||||
from .change_leader_mp import (
|
||||
GuanshiChangeZuzhangInfoView,
|
||||
GuanshiChangeZuzhangPreviewView,
|
||||
GuanshiChangeZuzhangView,
|
||||
)
|
||||
|
||||
from .change_guanshi_mp import (
|
||||
DashouChangeGuanshiInfoView,
|
||||
DashouChangeGuanshiPreviewView,
|
||||
DashouChangeGuanshiView,
|
||||
)
|
||||
|
||||
from .admin import (
|
||||
AdGetOrderList,
|
||||
AdGetOrderTypes,
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
"""小程序:打手更换管事(推荐官)。"""
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from users.services.change_guanshi import (
|
||||
build_change_guanshi_info,
|
||||
change_guanshi_by_invite,
|
||||
resolve_guanshi_by_invite,
|
||||
)
|
||||
|
||||
|
||||
class DashouChangeGuanshiInfoView(APIView):
|
||||
"""
|
||||
POST /yonghu/ds_huan_guanshi_info
|
||||
当前管事 + 剩余更换次数 + self_uid
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
result = build_change_guanshi_info(request, request.user)
|
||||
if not result.get('ok'):
|
||||
return Response({
|
||||
'code': result.get('code', 403),
|
||||
'msg': result.get('msg', '失败'),
|
||||
'data': None,
|
||||
})
|
||||
return Response({
|
||||
'code': 200,
|
||||
'msg': result.get('msg', 'ok'),
|
||||
'data': result.get('data'),
|
||||
})
|
||||
|
||||
|
||||
class DashouChangeGuanshiPreviewView(APIView):
|
||||
"""
|
||||
POST /yonghu/ds_huan_guanshi_preview
|
||||
body: { inviteCode }
|
||||
解析邀请码对应管事(用于前端防自换)
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
invite = (
|
||||
request.data.get('inviteCode')
|
||||
or request.data.get('yaoqingma')
|
||||
or ''
|
||||
)
|
||||
result = resolve_guanshi_by_invite(invite)
|
||||
if not result.get('ok'):
|
||||
return Response({
|
||||
'code': result.get('code', 400),
|
||||
'msg': result.get('msg', '解析失败'),
|
||||
'data': None,
|
||||
})
|
||||
return Response({
|
||||
'code': 200,
|
||||
'msg': 'ok',
|
||||
'data': result.get('data'),
|
||||
})
|
||||
|
||||
|
||||
class DashouChangeGuanshiView(APIView):
|
||||
"""
|
||||
POST /yonghu/ds_huan_guanshi
|
||||
body: { inviteCode: 管事邀请码 }
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
invite = (
|
||||
request.data.get('inviteCode')
|
||||
or request.data.get('yaoqingma')
|
||||
or ''
|
||||
)
|
||||
result = change_guanshi_by_invite(
|
||||
request,
|
||||
request.user,
|
||||
invite,
|
||||
source='miniapp',
|
||||
operator=getattr(request.user, 'UserUID', '') or '',
|
||||
)
|
||||
if not result.get('ok'):
|
||||
return Response({
|
||||
'code': result.get('code', 400),
|
||||
'msg': result.get('msg', '更换失败'),
|
||||
'data': None,
|
||||
})
|
||||
return Response({
|
||||
'code': 200,
|
||||
'msg': result.get('msg', '更换管事成功'),
|
||||
'data': result.get('data'),
|
||||
})
|
||||
@@ -3,13 +3,17 @@ from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from users.services.change_leader import build_change_leader_info, change_leader_by_invite
|
||||
from users.services.change_leader import (
|
||||
build_change_leader_info,
|
||||
change_leader_by_invite,
|
||||
resolve_zuzhang_by_invite,
|
||||
)
|
||||
|
||||
|
||||
class GuanshiChangeZuzhangInfoView(APIView):
|
||||
"""
|
||||
POST /yonghu/gs_huan_zuzhang_info
|
||||
当前组长 + 剩余更换次数
|
||||
当前组长 + 剩余更换次数 + self_uid
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@@ -28,6 +32,34 @@ class GuanshiChangeZuzhangInfoView(APIView):
|
||||
})
|
||||
|
||||
|
||||
class GuanshiChangeZuzhangPreviewView(APIView):
|
||||
"""
|
||||
POST /yonghu/gs_huan_zuzhang_preview
|
||||
body: { inviteCode }
|
||||
解析邀请码对应组长(用于前端防自换)
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
invite = (
|
||||
request.data.get('inviteCode')
|
||||
or request.data.get('yaoqingma')
|
||||
or ''
|
||||
)
|
||||
result = resolve_zuzhang_by_invite(invite)
|
||||
if not result.get('ok'):
|
||||
return Response({
|
||||
'code': result.get('code', 400),
|
||||
'msg': result.get('msg', '解析失败'),
|
||||
'data': None,
|
||||
})
|
||||
return Response({
|
||||
'code': 200,
|
||||
'msg': 'ok',
|
||||
'data': result.get('data'),
|
||||
})
|
||||
|
||||
|
||||
class GuanshiChangeZuzhangView(APIView):
|
||||
"""
|
||||
POST /yonghu/gs_huan_zuzhang
|
||||
|
||||
Reference in new issue
Block a user