feat: 管事自助更换组长(配额、记录、小程序与后台接口)
This commit is contained in:
@@ -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, GetWithdrawSettingsView,
|
||||
AddMemberView, GetGuanliListView, GetGuanliDetailView, PromoteGuanshiToZuzhangView, ChangeInviterView, UpdateGuanliView, GuanshiChangeLeaderQuotaView, GuanshiChangeLeaderLogView, GetWithdrawSettingsView,
|
||||
UpdateWithdrawSettingsView, GetZuzhangListView, GetZuzhangDetailView, UpdateZuzhangView, GetOperationLogListView, GetProductTypeZoneView,
|
||||
ModifyProductTypeZoneView, GetRateView, ModifyRateView, PopupNoticeListAPIView, PopupNoticeModifyAPIView,ShopListView,
|
||||
ShopModifyView, ShopPublicTypeAndAuditView, ShopListForProductView, ShopProductModifyView,
|
||||
@@ -56,6 +56,8 @@ urlpatterns = [
|
||||
path('hthqgsxq', GetGuanliDetailView.as_view(), name='后台获取管事详情信息'),
|
||||
path('htgssjzz', PromoteGuanshiToZuzhangView.as_view(), name='后台管事升级组长'),
|
||||
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('htxggsxx', UpdateGuanliView.as_view(), name='后台更新管事数据'),
|
||||
path('hqczrz', GetOperationLogListView.as_view(), name='后台操作日志列表'),
|
||||
path('hthqtxpz', GetWithdrawSettingsView.as_view(), name='后台获取提现配置'),
|
||||
|
||||
@@ -51,7 +51,7 @@ from .views.members import (
|
||||
)
|
||||
from .views.guanli import (
|
||||
GetGuanliListView, GetGuanliDetailView, PromoteGuanshiToZuzhangView,
|
||||
UpdateGuanliView,
|
||||
UpdateGuanliView, GuanshiChangeLeaderQuotaView, GuanshiChangeLeaderLogView,
|
||||
)
|
||||
from .views.withdraw_settings import (
|
||||
GetWithdrawSettingsView, UpdateWithdrawSettingsView,
|
||||
@@ -113,7 +113,7 @@ __all__ = [
|
||||
'MemberCardImageUploadView', 'MemberRechargeRecordsView', 'MemberBundleCreateView',
|
||||
# guanli
|
||||
'GetGuanliListView', 'GetGuanliDetailView', 'PromoteGuanshiToZuzhangView',
|
||||
'UpdateGuanliView',
|
||||
'UpdateGuanliView', 'GuanshiChangeLeaderQuotaView', 'GuanshiChangeLeaderLogView',
|
||||
# withdraw_settings
|
||||
'GetWithdrawSettingsView', 'UpdateWithdrawSettingsView',
|
||||
# zuzhang
|
||||
|
||||
@@ -387,6 +387,19 @@ class GetGuanliDetailView(APIView):
|
||||
# 注意:首次分红定制只存储有定制记录的会员,没有定制的会员使用默认值(前端通过会员表展示)
|
||||
# 额外次数分红按次数返回
|
||||
|
||||
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_leader 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)
|
||||
else:
|
||||
quota_club = resolve_club_id_from_request(request)
|
||||
quota = get_or_create_quota(quota_club, yonghuid)
|
||||
change_leader_quota = quota_payload(quota)
|
||||
change_leader_logs = list_change_logs(quota_club, yonghuid, page=1, page_size=20)
|
||||
|
||||
return Response({
|
||||
'code': 0,
|
||||
'data': {
|
||||
@@ -398,6 +411,8 @@ class GetGuanliDetailView(APIView):
|
||||
'extra_dividend_map': extra_map,
|
||||
'all_members': all_members,
|
||||
'is_zuzhang': is_zuzhang,
|
||||
'change_leader_quota': change_leader_quota,
|
||||
'change_leader_logs': change_leader_logs,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -855,7 +870,117 @@ class UpdateGuanliView(APIView):
|
||||
return Response({'code': 0, 'msg': '修改成功'})
|
||||
|
||||
|
||||
|
||||
def _admin_quota_club_id(request, user):
|
||||
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
|
||||
if resolve_club_scope(request) == DATA_SCOPE_ALL:
|
||||
return get_user_club_id(user)
|
||||
return resolve_club_id_from_request(request)
|
||||
|
||||
|
||||
class GuanshiChangeLeaderQuotaView(APIView):
|
||||
"""
|
||||
后台设置/追加管事换组长次数
|
||||
POST /houtai/htgs_huan_zz_quota
|
||||
参数:username, yonghuid,
|
||||
action=set|add,
|
||||
total_quota(set)或 add_count(add)
|
||||
权限:4400a
|
||||
"""
|
||||
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 '4400a' not in permissions and '000001' not in permissions:
|
||||
return Response({'code': 403, 'msg': '无权限修改换组长次数(需要4400a)'})
|
||||
|
||||
try:
|
||||
user = User.query.select_related('GuanshiProfile').get(UserUID=yonghuid)
|
||||
_ = user.GuanshiProfile
|
||||
except User.DoesNotExist:
|
||||
return Response({'code': 404, 'msg': '管事不存在'})
|
||||
except UserGuanshi.DoesNotExist:
|
||||
return Response({'code': 404, 'msg': '用户不是管事'})
|
||||
|
||||
denied = forbid_if_user_out_of_scope(request, user)
|
||||
if denied:
|
||||
return denied
|
||||
|
||||
from users.services.change_leader 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 GuanshiChangeLeaderLogView(APIView):
|
||||
"""
|
||||
后台查询管事换组长记录
|
||||
POST /houtai/htgs_huan_zz_logs
|
||||
参数:username, yonghuid, page, page_size
|
||||
权限:4400a-f 任一
|
||||
"""
|
||||
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 = {'4400a', '4400b', '4400c', '4400d', '4400e', '4400f', '000001'}
|
||||
if not any(p in permissions for p in required_perms):
|
||||
return Response({'code': 403, 'msg': '您没有权限查看换组长记录'})
|
||||
|
||||
try:
|
||||
user = User.query.select_related('GuanshiProfile').get(UserUID=yonghuid)
|
||||
_ = user.GuanshiProfile
|
||||
except User.DoesNotExist:
|
||||
return Response({'code': 404, 'msg': '管事不存在'})
|
||||
except UserGuanshi.DoesNotExist:
|
||||
return Response({'code': 404, 'msg': '用户不是管事'})
|
||||
|
||||
denied = forbid_if_user_out_of_scope(request, user)
|
||||
if denied:
|
||||
return denied
|
||||
|
||||
from users.services.change_leader 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})
|
||||
|
||||
|
||||
|
||||
|
||||
55
users/migrations/0010_guanshi_change_leader.py
Normal file
55
users/migrations/0010_guanshi_change_leader.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0009_user_shangjia_teyue'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='GuanshiChangeLeaderQuota',
|
||||
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')),
|
||||
('guanshi_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': 'guanshi_change_leader_quota',
|
||||
'indexes': [
|
||||
models.Index(fields=['club_id', 'guanshi_uid'], name='guanshi_cha_club_id_b8a1f0_idx'),
|
||||
],
|
||||
'unique_together': {('club_id', 'guanshi_uid')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='GuanshiChangeLeaderLog',
|
||||
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')),
|
||||
('guanshi_uid', models.CharField(db_index=True, max_length=7, verbose_name='管事UserUID')),
|
||||
('guanshi_name', models.CharField(blank=True, default='', max_length=50, verbose_name='管事昵称')),
|
||||
('old_zuzhang_uid', models.CharField(blank=True, default='', max_length=7, verbose_name='原组长UID')),
|
||||
('new_zuzhang_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': 'guanshi_change_leader_log',
|
||||
'indexes': [
|
||||
models.Index(fields=['club_id', 'guanshi_uid', '-CreateTime'], name='guanshi_cha_club_id_9c2e1a_idx'),
|
||||
],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -714,6 +714,51 @@ class UserSensitiveData(QModel):
|
||||
return f'敏感数据-{self.user_uuid_id}'
|
||||
|
||||
|
||||
# ==================== 管事更换组长:配额 / 记录 ====================
|
||||
class GuanshiChangeLeaderQuota(QModel):
|
||||
"""管事自助更换组长次数配额(按俱乐部 + 管事 UID)"""
|
||||
club_id = models.CharField(max_length=16, db_index=True, verbose_name='俱乐部ID')
|
||||
guanshi_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 = 'guanshi_change_leader_quota'
|
||||
verbose_name = '管事换组长配额'
|
||||
verbose_name_plural = verbose_name
|
||||
unique_together = [['club_id', 'guanshi_uid']]
|
||||
indexes = [
|
||||
models.Index(fields=['club_id', 'guanshi_uid']),
|
||||
]
|
||||
|
||||
@property
|
||||
def remain_quota(self):
|
||||
return max(0, int(self.total_quota or 0) - int(self.used_count or 0))
|
||||
|
||||
|
||||
class GuanshiChangeLeaderLog(QModel):
|
||||
"""管事更换组长记录"""
|
||||
club_id = models.CharField(max_length=16, db_index=True, verbose_name='俱乐部ID')
|
||||
guanshi_uid = models.CharField(max_length=7, db_index=True, verbose_name='管事UserUID')
|
||||
guanshi_name = models.CharField(max_length=50, blank=True, default='', verbose_name='管事昵称')
|
||||
old_zuzhang_uid = models.CharField(max_length=7, blank=True, default='', verbose_name='原组长UID')
|
||||
new_zuzhang_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='来源') # miniapp / admin
|
||||
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 = 'guanshi_change_leader_log'
|
||||
verbose_name = '管事换组长记录'
|
||||
verbose_name_plural = verbose_name
|
||||
indexes = [
|
||||
models.Index(fields=['club_id', 'guanshi_uid', '-CreateTime']),
|
||||
]
|
||||
|
||||
|
||||
# === 业务用户模型注册 ===
|
||||
# 导入业务子类,使 Django app registry 注册具体 User 模型(users.User)。
|
||||
# 放在文件末尾以避免循环导入:此时本模块所有类已定义完毕。
|
||||
|
||||
276
users/services/change_leader.py
Normal file
276
users/services/change_leader.py
Normal file
@@ -0,0 +1,276 @@
|
||||
"""管事自助更换组长:配额、校验、换绑。"""
|
||||
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,
|
||||
}
|
||||
@@ -20,7 +20,8 @@ from .views import WechatMiniProgramLoginView, UserInfoUpdateView, DashouXinxiAP
|
||||
FaKuanShenSuView, FaKuanPayView,FaKuanHuitiaoView, FaKuanPayPollView, FaKuanPayFailView,KaohePayView,KaohePayCallbackView, KaohePayPollView,KaohePayFailView, TixianAssetView, GuanshiContactView,TixianQueRenAutoView, TixianShenqingV3View, TixianCallbackV3View, \
|
||||
GetUserPhoneView, BindUserPhoneView, BossSearchDashouView, \
|
||||
FundFreezeMineView, FundFreezeMyLedgerView, FundFreezeMyLedgerDetailView, \
|
||||
RoleAgreementCurrentView, RoleAgreementMineView, RoleAgreementSignView
|
||||
RoleAgreementCurrentView, RoleAgreementMineView, RoleAgreementSignView, \
|
||||
GuanshiChangeZuzhangInfoView, GuanshiChangeZuzhangView
|
||||
from .tixian_shenhe_views import TixianZddkshApplyView
|
||||
from .paihang_views import PhbHqsjView
|
||||
from .xzj_paihang_views import XzjPhbHqsjView
|
||||
@@ -135,6 +136,10 @@ urlpatterns = [
|
||||
# 未登录用户微信登录+管事注册
|
||||
path('zuzhangyqmzc', WechatLoginAndGuanshiRegisterView.as_view(), name='管事注册,没有登录'),
|
||||
|
||||
# 管事自助更换组长
|
||||
path('gs_huan_zuzhang_info', GuanshiChangeZuzhangInfoView.as_view(), name='管事换组长信息'),
|
||||
path('gs_huan_zuzhang', GuanshiChangeZuzhangView.as_view(), name='管事换组长提交'),
|
||||
|
||||
path('zuzhangzhuce', ZuzhangZhuceView.as_view(), name='组长注册'),
|
||||
path('zuzhangxinxi', ZuzhangXinxiView.as_view(), name='组长刷新获取信息'),
|
||||
|
||||
|
||||
@@ -74,6 +74,11 @@ from .role_agreement_mp import (
|
||||
RoleAgreementSignView,
|
||||
)
|
||||
|
||||
from .change_leader_mp import (
|
||||
GuanshiChangeZuzhangInfoView,
|
||||
GuanshiChangeZuzhangView,
|
||||
)
|
||||
|
||||
from .admin import (
|
||||
AdGetOrderList,
|
||||
AdGetOrderTypes,
|
||||
|
||||
61
users/views/change_leader_mp.py
Normal file
61
users/views/change_leader_mp.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""小程序:管事更换组长。"""
|
||||
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
|
||||
|
||||
|
||||
class GuanshiChangeZuzhangInfoView(APIView):
|
||||
"""
|
||||
POST /yonghu/gs_huan_zuzhang_info
|
||||
当前组长 + 剩余更换次数
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
result = build_change_leader_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 GuanshiChangeZuzhangView(APIView):
|
||||
"""
|
||||
POST /yonghu/gs_huan_zuzhang
|
||||
body: { inviteCode: 组长邀请码 }
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
invite = (
|
||||
request.data.get('inviteCode')
|
||||
or request.data.get('yaoqingma')
|
||||
or ''
|
||||
)
|
||||
result = change_leader_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'),
|
||||
})
|
||||
Reference in New Issue
Block a user