diff --git a/backend/urls.py b/backend/urls.py index d2b3e2d..0e03945 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, 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='后台获取提现配置'), diff --git a/backend/view.py b/backend/view.py index 6284a4f..7aa1489 100644 --- a/backend/view.py +++ b/backend/view.py @@ -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 diff --git a/backend/views/guanli.py b/backend/views/guanli.py index 745a2b3..4c0b2b0 100644 --- a/backend/views/guanli.py +++ b/backend/views/guanli.py @@ -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}) diff --git a/backend/views/kefu.py b/backend/views/kefu.py index 428654d..7f310ca 100644 --- a/backend/views/kefu.py +++ b/backend/views/kefu.py @@ -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, } }) diff --git a/users/migrations/0011_dashou_change_guanshi.py b/users/migrations/0011_dashou_change_guanshi.py new file mode 100644 index 0000000..d70e953 --- /dev/null +++ b/users/migrations/0011_dashou_change_guanshi.py @@ -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'), + ), + ] diff --git a/users/models.py b/users/models.py index c8ab434..802a185 100644 --- a/users/models.py +++ b/users/models.py @@ -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)。 # 放在文件末尾以避免循环导入:此时本模块所有类已定义完毕。 diff --git a/users/services/change_guanshi.py b/users/services/change_guanshi.py new file mode 100644 index 0000000..35d0577 --- /dev/null +++ b/users/services/change_guanshi.py @@ -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, + } diff --git a/users/services/change_leader.py b/users/services/change_leader.py index b2801ba..3fa3ac9 100644 --- a/users/services/change_leader.py +++ b/users/services/change_leader.py @@ -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), }, diff --git a/users/urls.py b/users/urls.py index 243ffce..411549b 100644 --- a/users/urls.py +++ b/users/urls.py @@ -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='组长刷新获取信息'), diff --git a/users/views/__init__.py b/users/views/__init__.py index ec5a7da..6300406 100644 --- a/users/views/__init__.py +++ b/users/views/__init__.py @@ -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, diff --git a/users/views/change_guanshi_mp.py b/users/views/change_guanshi_mp.py new file mode 100644 index 0000000..1d98c88 --- /dev/null +++ b/users/views/change_guanshi_mp.py @@ -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'), + }) diff --git a/users/views/change_leader_mp.py b/users/views/change_leader_mp.py index 2d35c4f..7de4626 100644 --- a/users/views/change_leader_mp.py +++ b/users/views/change_leader_mp.py @@ -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