From 62f121f438d76bc326b4be53bd5afd01b99bfa88 Mon Sep 17 00:00:00 2001 From: XingQue Date: Fri, 31 Jul 2026 10:10:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=AE=A1=E4=BA=8B=E8=87=AA=E5=8A=A9?= =?UTF-8?q?=E6=9B=B4=E6=8D=A2=E7=BB=84=E9=95=BF=EF=BC=88=E9=85=8D=E9=A2=9D?= =?UTF-8?q?=E3=80=81=E8=AE=B0=E5=BD=95=E3=80=81=E5=B0=8F=E7=A8=8B=E5=BA=8F?= =?UTF-8?q?=E4=B8=8E=E5=90=8E=E5=8F=B0=E6=8E=A5=E5=8F=A3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/urls.py | 4 +- backend/view.py | 4 +- backend/views/guanli.py | 127 +++++++- .../migrations/0010_guanshi_change_leader.py | 55 ++++ users/models.py | 45 +++ users/services/change_leader.py | 276 ++++++++++++++++++ users/urls.py | 7 +- users/views/__init__.py | 5 + users/views/change_leader_mp.py | 61 ++++ 9 files changed, 579 insertions(+), 5 deletions(-) create mode 100644 users/migrations/0010_guanshi_change_leader.py create mode 100644 users/services/change_leader.py create mode 100644 users/views/change_leader_mp.py diff --git a/backend/urls.py b/backend/urls.py index b3f87c8..d2b3e2d 100644 --- a/backend/urls.py +++ b/backend/urls.py @@ -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='后台获取提现配置'), diff --git a/backend/view.py b/backend/view.py index 55dbccc..6284a4f 100644 --- a/backend/view.py +++ b/backend/view.py @@ -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 diff --git a/backend/views/guanli.py b/backend/views/guanli.py index caae3f7..745a2b3 100644 --- a/backend/views/guanli.py +++ b/backend/views/guanli.py @@ -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}) diff --git a/users/migrations/0010_guanshi_change_leader.py b/users/migrations/0010_guanshi_change_leader.py new file mode 100644 index 0000000..9c73d9a --- /dev/null +++ b/users/migrations/0010_guanshi_change_leader.py @@ -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'), + ], + }, + ), + ] diff --git a/users/models.py b/users/models.py index 52a0ad9..c8ab434 100644 --- a/users/models.py +++ b/users/models.py @@ -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)。 # 放在文件末尾以避免循环导入:此时本模块所有类已定义完毕。 diff --git a/users/services/change_leader.py b/users/services/change_leader.py new file mode 100644 index 0000000..b2801ba --- /dev/null +++ b/users/services/change_leader.py @@ -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, + } diff --git a/users/urls.py b/users/urls.py index cb882f3..243ffce 100644 --- a/users/urls.py +++ b/users/urls.py @@ -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='组长刷新获取信息'), diff --git a/users/views/__init__.py b/users/views/__init__.py index 02486be..ec5a7da 100644 --- a/users/views/__init__.py +++ b/users/views/__init__.py @@ -74,6 +74,11 @@ from .role_agreement_mp import ( RoleAgreementSignView, ) +from .change_leader_mp import ( + GuanshiChangeZuzhangInfoView, + GuanshiChangeZuzhangView, +) + from .admin import ( AdGetOrderList, AdGetOrderTypes, diff --git a/users/views/change_leader_mp.py b/users/views/change_leader_mp.py new file mode 100644 index 0000000..2d35c4f --- /dev/null +++ b/users/views/change_leader_mp.py @@ -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'), + })