feat: 俱乐部订单单向互通抢单(集团配置 + 接单池/抢单校验)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-26 03:08:10 +08:00
parent 1234cff035
commit 231e5162b0
7 changed files with 209 additions and 3 deletions

View File

@@ -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'),
),
]

View File

@@ -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'),
]

View File

@@ -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,

View File

@@ -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

View File

@@ -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'),

View File

@@ -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'})