feat: 俱乐部订单单向互通抢单(集团配置 + 接单池/抢单校验)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
35
jituan/migrations/0016_club_order_grab_link.py
Normal file
35
jituan/migrations/0016_club_order_grab_link.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
@@ -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'),
|
||||
]
|
||||
|
||||
@@ -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,
|
||||
|
||||
90
jituan/services/order_grab_share.py
Normal file
90
jituan/services/order_grab_share.py
Normal 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
|
||||
@@ -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'),
|
||||
|
||||
50
jituan/views_order_grab_share.py
Normal file
50
jituan/views_order_grab_share.py
Normal 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'})
|
||||
@@ -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']})
|
||||
|
||||
Reference in New Issue
Block a user