Files
Django/jituan/services/fake_grab_order_admin.py

391 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""后台假单管理(按俱乐部 + 商品类型)。"""
import logging
import os
import random
import time
import uuid
from decimal import Decimal
from jituan.constants import DATA_SCOPE_ALL
from jituan.services.club_context import (
club_id_for_write,
resolve_club_id_from_request,
resolve_club_scope,
)
from jituan.services.club_user_access import list_response_meta
from orders.models import FakeGrabOrder, FakeGrabOrderShuffleState
from orders.services.fake_order_pool import DEFAULT_DISPATCHER_AVATAR
from products.models import ShangpinLeixing
logger = logging.getLogger(__name__)
CLUB_WRITE_SCOPE_MSG = '当前为集团汇总视图,请先在顶栏切换到具体俱乐部后再操作'
CLUB_READ_SCOPE_MSG = '请先在顶栏切换到具体小程序/俱乐部后再操作'
PRODUCT_PERMS = ('2200a',)
SHUFFLE_STATE_PK = 1
def _scope_key(club_id, leixing_id):
"""与历史打乱缓存 key 一致club|leixing。"""
lid = leixing_id if leixing_id is not None else 'all'
return f'{club_id}|{lid}'
def club_read_blocked(request):
return resolve_club_scope(request) == DATA_SCOPE_ALL
def club_write_blocked(request):
return resolve_club_scope(request) == DATA_SCOPE_ALL
def _gen_order_id():
while True:
oid = f'JD{int(time.time() * 1000)}{random.randint(1000, 9999)}'
if not FakeGrabOrder.query.filter(OrderID=oid).exists():
return oid
def invalidate_fake_order_shuffle(club_id, leixing_id=None):
"""假单增删改后清除对应打乱缓存,下次读取自动重排。"""
try:
state = FakeGrabOrderShuffleState.objects.get(pk=SHUFFLE_STATE_PK)
except FakeGrabOrderShuffleState.DoesNotExist:
return
seq_map = dict(state.OrderSequence or {})
if leixing_id is not None:
seq_map.pop(_scope_key(club_id, leixing_id), None)
else:
prefix = f'{club_id}|'
for key in list(seq_map.keys()):
if key.startswith(prefix):
seq_map.pop(key, None)
state.OrderSequence = seq_map
state.save(update_fields=['OrderSequence', 'UpdateTime'])
def _leixing_name_map():
return {
lx.id: (lx.jieshao or f'类型{lx.id}')
for lx in ShangpinLeixing.query.exclude(shenhezhuangtai=2)
}
def build_leixing_options():
"""全部可用商品类型(非审核专用),假单可绑定任意类型。"""
options = []
for lx in ShangpinLeixing.query.exclude(shenhezhuangtai=2).order_by('-paixu', 'id'):
options.append({
'id': lx.id,
'jieshao': lx.jieshao or '',
'tupian_url': lx.tupian_url or '',
'bankuai_id': lx.bankuai_id,
})
return options
def _row_to_dict(row, leixing_names=None):
leixing_names = leixing_names or _leixing_name_map()
return {
'order_id': row.OrderID,
'dingdan_id': row.OrderID,
'amount': str(row.Amount or 0),
'jine': str(row.Amount or 0),
'leixing_id': row.ProductTypeID,
'leixing_name': leixing_names.get(row.ProductTypeID, f'类型{row.ProductTypeID}'),
'jieshao': row.Description or '',
'beizhu': row.Remark or '',
'paidan_yonghuid': row.DispatcherUserID or '',
'paidan_touxiang': row.DispatcherAvatar or DEFAULT_DISPATCHER_AVATAR,
'shangjia_nicheng': row.MerchantNickname or '',
'is_youzhi_shangjia': bool(row.IsPremiumMerchant),
'fadan_pingtai': int(row.Platform or 1),
'platform_label': '平台发单' if int(row.Platform or 1) == 1 else '商家发单',
'sort_order': int(row.SortOrder or 0),
'club_id': row.ClubID,
'CreateTime': row.CreateTime.isoformat() if row.CreateTime else None,
'UpdateTime': row.UpdateTime.isoformat() if row.UpdateTime else None,
}
def build_fake_order_list_payload(request, leixing_id=None, page=1, page_size=20):
if club_read_blocked(request):
return {
'list': [],
'total': 0,
'page': page,
'page_size': page_size,
'club_id': resolve_club_id_from_request(request),
'scope': resolve_club_scope(request),
'scope_error': CLUB_READ_SCOPE_MSG,
'leixing_options': build_leixing_options(),
'fake_grab_enabled': False,
'fake_yajin_hide_threshold': 10,
**list_response_meta(request),
}
club_id = resolve_club_id_from_request(request)
page = max(1, int(page or 1))
page_size = min(100, max(1, int(page_size or 20)))
qs = FakeGrabOrder.query.filter(ClubID=club_id)
if leixing_id not in (None, ''):
qs = qs.filter(ProductTypeID=int(leixing_id))
qs = qs.order_by('-SortOrder', '-CreateTime')
total = qs.count()
offset = (page - 1) * page_size
rows = list(qs[offset:offset + page_size])
names = _leixing_name_map()
from jituan.services.fake_grab_switch import (
get_fake_yajin_hide_threshold,
is_club_fake_grab_enabled,
)
return {
'list': [_row_to_dict(r, names) for r in rows],
'total': total,
'page': page,
'page_size': page_size,
'club_id': club_id,
'scope': resolve_club_scope(request),
'leixing_options': build_leixing_options(),
'fake_grab_enabled': is_club_fake_grab_enabled(club_id),
'fake_yajin_hide_threshold': float(get_fake_yajin_hide_threshold(club_id)),
**list_response_meta(request),
}
def set_fake_grab_enabled(request, enabled):
"""开启/关闭本俱乐部打手侧假单功能。"""
if club_write_blocked(request):
return None, CLUB_WRITE_SCOPE_MSG
from jituan.models import Club
from jituan.services.fake_grab_switch import (
get_fake_yajin_hide_threshold,
is_club_fake_grab_enabled,
set_club_fake_grab_enabled,
)
club_id = club_id_for_write(request)
club = Club.query.filter(club_id=club_id).first()
if not club:
return None, '俱乐部不存在'
set_club_fake_grab_enabled(club, bool(enabled))
club.save(update_fields=['config_json'])
return {
'club_id': club_id,
'fake_grab_enabled': is_club_fake_grab_enabled(club_id),
'fake_yajin_hide_threshold': float(get_fake_yajin_hide_threshold(club_id)),
}, None
def set_fake_yajin_threshold(request, threshold):
"""设置押金 ≥ 该值时过滤假单。"""
if club_write_blocked(request):
return None, CLUB_WRITE_SCOPE_MSG
from jituan.models import Club
from jituan.services.fake_grab_switch import (
get_fake_yajin_hide_threshold,
is_club_fake_grab_enabled,
set_fake_yajin_hide_threshold,
)
club_id = club_id_for_write(request)
club = Club.query.filter(club_id=club_id).first()
if not club:
return None, '俱乐部不存在'
val = set_fake_yajin_hide_threshold(club, threshold)
club.save(update_fields=['config_json'])
return {
'club_id': club_id,
'fake_grab_enabled': is_club_fake_grab_enabled(club_id),
'fake_yajin_hide_threshold': float(val),
}, None
def _parse_decimal(value, field_name='金额'):
try:
val = Decimal(str(value))
if val < 0:
raise ValueError
return val
except Exception:
raise ValueError(f'{field_name}格式错误')
def _validate_leixing_id(leixing_id):
try:
lid = int(leixing_id)
except (TypeError, ValueError):
return None, '请选择商品类型'
if not ShangpinLeixing.query.filter(id=lid).exclude(shenhezhuangtai=2).exists():
return None, '商品类型不存在或为审核专用'
return lid, None
def create_fake_order(request, data):
if club_write_blocked(request):
return None, CLUB_WRITE_SCOPE_MSG
club_id = club_id_for_write(request)
leixing_id, err = _validate_leixing_id(data.get('leixing_id'))
if err:
return None, err
try:
amount = _parse_decimal(data.get('amount') or data.get('jine'), '价格')
except ValueError as e:
return None, str(e)
platform = int(data.get('fadan_pingtai') or data.get('platform') or 1)
if platform not in (1, 2):
return None, '发单平台无效'
row = FakeGrabOrder.query.create(
OrderID=_gen_order_id(),
Amount=amount,
ProductTypeID=leixing_id,
Description=(data.get('jieshao') or '').strip(),
Remark=(data.get('beizhu') or '').strip(),
DispatcherUserID=(data.get('paidan_yonghuid') or '').strip(),
DispatcherAvatar=(data.get('paidan_touxiang') or '').strip() or DEFAULT_DISPATCHER_AVATAR,
MerchantNickname=(data.get('shangjia_nicheng') or '').strip() if platform == 2 else '',
IsPremiumMerchant=bool(data.get('is_youzhi_shangjia')) if platform == 2 else False,
Platform=platform,
ClubID=club_id,
SortOrder=int(data.get('sort_order') or 0),
)
invalidate_fake_order_shuffle(club_id, leixing_id)
return _row_to_dict(row), None
def update_fake_order(request, order_id, data):
if club_write_blocked(request):
return None, CLUB_WRITE_SCOPE_MSG
club_id = club_id_for_write(request)
order_id = (order_id or '').strip()
if not order_id:
return None, '缺少订单ID'
try:
row = FakeGrabOrder.objects.get(OrderID=order_id, ClubID=club_id)
except FakeGrabOrder.DoesNotExist:
return None, '假单不存在或不属于当前俱乐部'
old_leixing = row.ProductTypeID
update_fields = ['UpdateTime']
if 'leixing_id' in data and data.get('leixing_id') not in (None, ''):
leixing_id, err = _validate_leixing_id(data.get('leixing_id'))
if err:
return None, err
row.ProductTypeID = leixing_id
update_fields.append('ProductTypeID')
if 'amount' in data or 'jine' in data:
try:
row.Amount = _parse_decimal(data.get('amount') or data.get('jine'), '价格')
except ValueError as e:
return None, str(e)
update_fields.append('Amount')
if 'jieshao' in data:
row.Description = (data.get('jieshao') or '').strip()
update_fields.append('Description')
if 'beizhu' in data:
row.Remark = (data.get('beizhu') or '').strip()
update_fields.append('Remark')
if 'paidan_yonghuid' in data:
row.DispatcherUserID = (data.get('paidan_yonghuid') or '').strip()
update_fields.append('DispatcherUserID')
if 'paidan_touxiang' in data:
row.DispatcherAvatar = (data.get('paidan_touxiang') or '').strip() or DEFAULT_DISPATCHER_AVATAR
update_fields.append('DispatcherAvatar')
if 'sort_order' in data:
row.SortOrder = int(data.get('sort_order') or 0)
update_fields.append('SortOrder')
if 'fadan_pingtai' in data or 'platform' in data:
platform = int(data.get('fadan_pingtai') or data.get('platform') or row.Platform or 1)
if platform not in (1, 2):
return None, '发单平台无效'
row.Platform = platform
update_fields.append('Platform')
if platform == 1:
row.MerchantNickname = ''
row.IsPremiumMerchant = False
update_fields.extend(['MerchantNickname', 'IsPremiumMerchant'])
if row.Platform == 2:
if 'shangjia_nicheng' in data:
row.MerchantNickname = (data.get('shangjia_nicheng') or '').strip()
update_fields.append('MerchantNickname')
if 'is_youzhi_shangjia' in data:
row.IsPremiumMerchant = bool(data.get('is_youzhi_shangjia'))
update_fields.append('IsPremiumMerchant')
row.save(update_fields=list(set(update_fields)))
invalidate_fake_order_shuffle(club_id, row.ProductTypeID)
if old_leixing != row.ProductTypeID:
invalidate_fake_order_shuffle(club_id, old_leixing)
return _row_to_dict(row), None
def delete_fake_order(request, order_id):
if club_write_blocked(request):
return None, CLUB_WRITE_SCOPE_MSG
club_id = club_id_for_write(request)
order_id = (order_id or '').strip()
if not order_id:
return None, '缺少订单ID'
try:
row = FakeGrabOrder.objects.get(OrderID=order_id, ClubID=club_id)
except FakeGrabOrder.DoesNotExist:
return None, '假单不存在或不属于当前俱乐部'
leixing_id = row.ProductTypeID
row.delete()
invalidate_fake_order_shuffle(club_id, leixing_id)
return {'order_id': order_id}, None
def upload_fake_order_avatar(request, order_id, file_obj):
if club_write_blocked(request):
return None, CLUB_WRITE_SCOPE_MSG
from utils.oss_utils import upload_to_oss, validate_image
club_id = club_id_for_write(request)
order_id = (order_id or '').strip()
if not order_id or not file_obj:
return None, '参数不完整'
ok, msg = validate_image(file_obj)
if not ok:
return None, msg
try:
row = FakeGrabOrder.objects.get(OrderID=order_id, ClubID=club_id)
except FakeGrabOrder.DoesNotExist:
return None, '假单不存在或不属于当前俱乐部'
ext = os.path.splitext(getattr(file_obj, 'name', '') or '')[1].lower()
if ext not in ('.png', '.jpg', '.jpeg', '.webp', '.gif'):
ext = '.png'
rel_path = f'club/{club_id}/fake_grab_order/{order_id}_{uuid.uuid4().hex}{ext}'
if not upload_to_oss(file_obj, rel_path):
return None, '上传 OSS 失败'
row.DispatcherAvatar = rel_path
row.save(update_fields=['DispatcherAvatar', 'UpdateTime'])
invalidate_fake_order_shuffle(club_id, row.ProductTypeID)
return {'order_id': order_id, 'paidan_touxiang': rel_path}, None