From 231e5162b09efe2306aa8a057edd5f85b6729099 Mon Sep 17 00:00:00 2001 From: XingQue Date: Sun, 26 Jul 2026 03:08:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BF=B1=E4=B9=90=E9=83=A8=E8=AE=A2?= =?UTF-8?q?=E5=8D=95=E5=8D=95=E5=90=91=E4=BA=92=E9=80=9A=E6=8A=A2=E5=8D=95?= =?UTF-8?q?=EF=BC=88=E9=9B=86=E5=9B=A2=E9=85=8D=E7=BD=AE=20+=20=E6=8E=A5?= =?UTF-8?q?=E5=8D=95=E6=B1=A0/=E6=8A=A2=E5=8D=95=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- .../migrations/0016_club_order_grab_link.py | 35 ++++++++ jituan/models.py | 20 +++++ jituan/services/kefu_menu_definitions.py | 3 +- jituan/services/order_grab_share.py | 90 +++++++++++++++++++ jituan/urls.py | 2 + jituan/views_order_grab_share.py | 50 +++++++++++ orders/views/dashou.py | 12 ++- 7 files changed, 209 insertions(+), 3 deletions(-) create mode 100644 jituan/migrations/0016_club_order_grab_link.py create mode 100644 jituan/services/order_grab_share.py create mode 100644 jituan/views_order_grab_share.py diff --git a/jituan/migrations/0016_club_order_grab_link.py b/jituan/migrations/0016_club_order_grab_link.py new file mode 100644 index 0000000..b81eefd --- /dev/null +++ b/jituan/migrations/0016_club_order_grab_link.py @@ -0,0 +1,35 @@ +# Generated manually for ClubOrderGrabLink + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('jituan', '0015_club_recharge_page_copy'), + ] + + operations = [ + migrations.CreateModel( + name='ClubOrderGrabLink', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('grabber_club_id', models.CharField(db_index=True, max_length=16, verbose_name='抢单方俱乐部')), + ('order_club_id', models.CharField(db_index=True, max_length=16, verbose_name='被抢订单所属俱乐部')), + ('CreateTime', models.DateTimeField(auto_now_add=True)), + ], + options={ + 'verbose_name': '俱乐部订单互通抢单', + 'db_table': 'club_order_grab_link', + 'unique_together': {('grabber_club_id', 'order_club_id')}, + }, + ), + migrations.AddIndex( + model_name='clubordergrablink', + index=models.Index(fields=['grabber_club_id'], name='idx_cogl_grabber'), + ), + migrations.AddIndex( + model_name='clubordergrablink', + index=models.Index(fields=['order_club_id'], name='idx_cogl_order'), + ), + ] diff --git a/jituan/models.py b/jituan/models.py index 842272e..3aa2682 100644 --- a/jituan/models.py +++ b/jituan/models.py @@ -444,3 +444,23 @@ class KefuMenuPage(QModel): class Meta: db_table = 'kefu_menu_page' ordering = ['sort_order', 'page_id'] + + +class ClubOrderGrabLink(QModel): + """ + 俱乐部订单单向互通抢单。 + grabber_club_id 的打手可抢 order_club_id 的待接单;反向需另建一条。 + 关停:删除记录即可(两字段,无开关列)。 + """ + grabber_club_id = models.CharField(max_length=16, db_index=True, verbose_name='抢单方俱乐部') + order_club_id = models.CharField(max_length=16, db_index=True, verbose_name='被抢订单所属俱乐部') + CreateTime = models.DateTimeField(auto_now_add=True) + + class Meta: + db_table = 'club_order_grab_link' + verbose_name = '俱乐部订单互通抢单' + unique_together = [['grabber_club_id', 'order_club_id']] + indexes = [ + models.Index(fields=['grabber_club_id'], name='idx_cogl_grabber'), + models.Index(fields=['order_club_id'], name='idx_cogl_order'), + ] diff --git a/jituan/services/kefu_menu_definitions.py b/jituan/services/kefu_menu_definitions.py index c399659..f99ef42 100644 --- a/jituan/services/kefu_menu_definitions.py +++ b/jituan/services/kefu_menu_definitions.py @@ -54,7 +54,8 @@ KEFU_MENU_ROW_DEFS = [ 'require_group_manage': True}, {'page_id': 'admin.club-config', 'name': '俱乐部密钥配置', 'path': '/admin/club-config', 'parent_id': 'admin', 'sort_order': 73, 'require_group_manage': True}, - # require_super 兜底(旧 DB 无 require_admin_user_manage 列时仍可显示);运行时仍按 page_id=admin.user 走用户管理闸门 + {'page_id': 'admin.order-grab-share', 'name': '订单互通抢单', 'path': '/admin/order-grab-share', 'parent_id': 'admin', 'sort_order': 76, + 'require_group_manage': True}, {'page_id': 'admin.user', 'name': '后台用户', 'path': '/admin/user', 'parent_id': 'admin', 'sort_order': 74, 'require_super': True, 'require_admin_user_manage': True}, {'page_id': 'admin.operation-log', 'name': '操作日志', 'path': '/admin/operation-log', 'parent_id': 'admin', 'sort_order': 75, diff --git a/jituan/services/order_grab_share.py b/jituan/services/order_grab_share.py new file mode 100644 index 0000000..60fde12 --- /dev/null +++ b/jituan/services/order_grab_share.py @@ -0,0 +1,90 @@ +"""俱乐部订单单向互通抢单:grabber 可看/抢 order 俱乐部的待接单。""" +from django.db.models import Q + +from jituan.constants import CLUB_ID_DEFAULT +from jituan.models import Club, ClubOrderGrabLink + + +def normalize_club_id(club_id): + return (club_id or '').strip() or CLUB_ID_DEFAULT + + +def order_club_ids_for_grabber(grabber_club_id): + """ + 打手所属俱乐部可见的订单 ClubID 集合(含本店 + 已配置可抢的对方店)。 + """ + gc = normalize_club_id(grabber_club_id) + ids = {gc} + for oc in ClubOrderGrabLink.query.filter( + grabber_club_id=gc, + ).values_list('order_club_id', flat=True): + oc = (oc or '').strip() + if oc: + ids.add(oc) + return ids + + +def order_club_q_for_grabber(grabber_club_id): + """用于 Order 查询的 Q:本店 + 互通店;本店含默认俱乐部时兼容空 ClubID 旧单。""" + allowed = order_club_ids_for_grabber(grabber_club_id) + q = Q(ClubID__in=list(allowed)) + if CLUB_ID_DEFAULT in allowed: + q |= Q(ClubID__isnull=True) | Q(ClubID='') + return q + + +def can_grabber_take_order_club(grabber_club_id, order_club_id): + gc = normalize_club_id(grabber_club_id) + oc = normalize_club_id(order_club_id) + if gc == oc: + return True + return ClubOrderGrabLink.query.filter( + grabber_club_id=gc, order_club_id=oc, + ).exists() + + +def list_grab_links(): + name_map = {c.club_id: c.name for c in Club.query.filter(status=1)} + rows = list(ClubOrderGrabLink.query.order_by('grabber_club_id', 'order_club_id')) + return [ + { + 'id': r.id, + 'grabber_club_id': r.grabber_club_id, + 'grabber_club_name': name_map.get(r.grabber_club_id, r.grabber_club_id), + 'order_club_id': r.order_club_id, + 'order_club_name': name_map.get(r.order_club_id, r.order_club_id), + 'create_time': r.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if r.CreateTime else '', + } + for r in rows + ] + + +def add_grab_link(grabber_club_id, order_club_id): + gc = (grabber_club_id or '').strip() + oc = (order_club_id or '').strip() + if not gc or not oc: + return None, '请选择抢单方与被抢订单俱乐部' + if gc == oc: + return None, '不能配置本店抢本店(本店订单默认可见)' + if not Club.query.filter(club_id=gc, status=1).exists(): + return None, f'抢单方俱乐部不存在: {gc}' + if not Club.query.filter(club_id=oc, status=1).exists(): + return None, f'订单方俱乐部不存在: {oc}' + if ClubOrderGrabLink.query.filter(grabber_club_id=gc, order_club_id=oc).exists(): + return None, '该互通规则已存在' + row = ClubOrderGrabLink.query.create( + grabber_club_id=gc, + order_club_id=oc, + ) + return {'id': row.id, 'grabber_club_id': gc, 'order_club_id': oc}, None + + +def delete_grab_link(link_id): + try: + link_id = int(link_id) + except (TypeError, ValueError): + return False, '无效的规则 id' + deleted, _ = ClubOrderGrabLink.objects.filter(id=link_id).delete() + if not deleted: + return False, '规则不存在' + return True, None diff --git a/jituan/urls.py b/jituan/urls.py index 36d5890..a29cbe8 100644 --- a/jituan/urls.py +++ b/jituan/urls.py @@ -100,6 +100,7 @@ from jituan.views_recharge_page_copy import ( RechargePageCopyAdminSaveView, RechargePageCopyView, ) +from jituan.views_order_grab_share import ClubOrderGrabLinkManageView urlpatterns = [ path('auth/wechat-login', ClubWechatLoginView.as_view(), name='jituan_wechat_login'), @@ -110,6 +111,7 @@ urlpatterns = [ path('auth/perm-debug', ClubPermDebugView.as_view(), name='jituan_perm_debug'), path('auth/dashou-register', ClubDashouRegisterView.as_view(), name='jituan_dashou_register'), path('houtai/club-manage', ClubManageView.as_view(), name='jituan_club_manage'), + path('houtai/order-grab-link', ClubOrderGrabLinkManageView.as_view(), name='jituan_order_grab_link'), path('houtai/payment-channel-manage', ClubPaymentChannelManageView.as_view(), name='jituan_payment_channel_manage'), path('houtai/club-cert-upload', ClubCertUploadView.as_view(), name='jituan_club_cert_upload'), path('houtai/admin-assignments', ClubAdminAssignmentListView.as_view(), name='jituan_admin_assignments'), diff --git a/jituan/views_order_grab_share.py b/jituan/views_order_grab_share.py new file mode 100644 index 0000000..03314d9 --- /dev/null +++ b/jituan/views_order_grab_share.py @@ -0,0 +1,50 @@ +"""集团后台:俱乐部订单单向互通抢单配置。""" +from rest_framework.parsers import JSONParser +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView + +from backend.utils import verify_kefu_permission +from jituan.services.admin_context import can_manage_admin_assignments +from jituan.services.order_grab_share import add_grab_link, delete_grab_link, list_grab_links + + +class ClubOrderGrabLinkManageView(APIView): + """ + POST /jituan/houtai/order-grab-link + action: list | add | delete + 仅集团权限(can_manage_admin_assignments)可操作。 + """ + permission_classes = [IsAuthenticated] + parser_classes = [JSONParser] + + def post(self, request): + username = (request.data.get('username') or request.data.get('phone') or '').strip() + if not username: + return Response({'code': 400, 'msg': '缺少username'}) + kefu, permissions = verify_kefu_permission(request, username) + if kefu is None: + return permissions + if not can_manage_admin_assignments(request.user, permissions): + return Response({'code': 403, 'msg': '仅集团权限可配置订单互通'}) + + action = (request.data.get('action') or 'list').strip() + if action == 'list': + return Response({'code': 0, 'msg': 'ok', 'data': {'list': list_grab_links()}}) + + if action == 'add': + data, err = add_grab_link( + request.data.get('grabber_club_id'), + request.data.get('order_club_id'), + ) + if err: + return Response({'code': 400, 'msg': err}) + return Response({'code': 0, 'msg': '已添加', 'data': data}) + + if action == 'delete': + ok, err = delete_grab_link(request.data.get('id')) + if not ok: + return Response({'code': 400, 'msg': err or '删除失败'}) + return Response({'code': 0, 'msg': '已删除'}) + + return Response({'code': 400, 'msg': '无效 action'}) diff --git a/orders/views/dashou.py b/orders/views/dashou.py index 4b70b38..6017c58 100644 --- a/orders/views/dashou.py +++ b/orders/views/dashou.py @@ -124,8 +124,9 @@ class DashouDingdanHuoquView(APIView): yonghuid, club_id=club_id, ) - # 2. 构建基础查询条件 - base_query = Q(Status__in=[1, 7]) + # 2. 构建基础查询条件:本店待抢单 + 集团配置的单向互通店 + from jituan.services.order_grab_share import order_club_q_for_grabber + base_query = Q(Status__in=[1, 7]) & order_club_q_for_grabber(club_id) if leixing_id is not None: base_query &= Q(ProductTypeID=leixing_id) @@ -469,6 +470,13 @@ class QiangdanView(APIView): return Response({'code': 400, 'msg': order_result}) order = order_result + # 2.1 俱乐部互通:只能抢本店或已授权可抢的对方店订单 + from jituan.services.club_context import resolve_effective_club_id + from jituan.services.order_grab_share import can_grabber_take_order_club + grabber_club = resolve_effective_club_id(request, request.user) + if not can_grabber_take_order_club(grabber_club, getattr(order, 'ClubID', None)): + return Response({'code': 403, 'msg': '无权抢该俱乐部订单'}) + validation_result = self._validate_order_requirements(order, dashou_profile, request.user.UserUID) if not validation_result['success']: return Response({'code': 400, 'msg': validation_result['message']})