diff --git a/jituan/views_today_order.py b/jituan/views_today_order.py index 28e643b..3d1bfe0 100644 --- a/jituan/views_today_order.py +++ b/jituan/views_today_order.py @@ -1,4 +1,6 @@ """今日订单假数据 / 真假混合配置(客服后台)。""" +import logging + from rest_framework.parsers import JSONParser, MultiPartParser, FormParser from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response @@ -8,6 +10,8 @@ from backend.view import verify_kefu_permission from jituan.services.club_context import club_id_for_write, resolve_club_scope from jituan.constants import DATA_SCOPE_ALL +logger = logging.getLogger(__name__) + PRODUCT_PERMS = ('2200a', '8080a') @@ -29,22 +33,29 @@ class TodayOrderConfigView(APIView): parser_classes = [JSONParser] def post(self, request): - _, err = _auth(request) - if err: - return err - if resolve_club_scope(request) == DATA_SCOPE_ALL: - return Response({'code': 400, 'msg': '请切换到具体俱乐部'}) - club_id = club_id_for_write(request) - from orders.services import today_orders as svc + try: + _, err = _auth(request) + if err: + return err + if resolve_club_scope(request) == DATA_SCOPE_ALL: + return Response({'code': 400, 'msg': '请切换到具体俱乐部'}) + club_id = club_id_for_write(request) + from orders.services import today_orders as svc - action = (request.data.get('action') or 'get').strip().lower() - if action == 'get': - row = svc.get_or_create_config(club_id) - return Response({'code': 0, 'msg': 'ok', 'data': svc.config_to_dict(row)}) - if action == 'save': - data = svc.save_config(club_id, request.data) - return Response({'code': 0, 'msg': '已保存', 'data': data}) - return Response({'code': 400, 'msg': '未知 action'}) + raw_action = request.data.get('action') or 'get' + if isinstance(raw_action, (list, tuple)): + raw_action = raw_action[-1] if raw_action else 'get' + action = str(raw_action).strip().lower() + if action == 'get': + row = svc.get_or_create_config(club_id) + return Response({'code': 0, 'msg': 'ok', 'data': svc.config_to_dict(row)}) + if action == 'save': + data = svc.save_config(club_id, request.data) + return Response({'code': 0, 'msg': '已保存', 'data': data}) + return Response({'code': 400, 'msg': '未知 action'}) + except Exception as e: + logger.exception('today-order-config failed: %s', e) + return Response({'code': 500, 'msg': f'服务器错误: {e}'}) class TodayOrderFakeListView(APIView): @@ -53,14 +64,18 @@ class TodayOrderFakeListView(APIView): parser_classes = [JSONParser] def post(self, request): - _, err = _auth(request) - if err: - return err - if resolve_club_scope(request) == DATA_SCOPE_ALL: - return Response({'code': 400, 'msg': '请切换到具体俱乐部'}) - club_id = club_id_for_write(request) - from orders.services import today_orders as svc - return Response({'code': 0, 'msg': 'ok', 'data': {'list': svc.list_fakes(club_id)}}) + try: + _, err = _auth(request) + if err: + return err + if resolve_club_scope(request) == DATA_SCOPE_ALL: + return Response({'code': 400, 'msg': '请切换到具体俱乐部'}) + club_id = club_id_for_write(request) + from orders.services import today_orders as svc + return Response({'code': 0, 'msg': 'ok', 'data': {'list': svc.list_fakes(club_id)}}) + except Exception as e: + logger.exception('today-order-fake-list failed: %s', e) + return Response({'code': 500, 'msg': f'服务器错误: {e}'}) class TodayOrderFakeManageView(APIView): @@ -69,54 +84,65 @@ class TodayOrderFakeManageView(APIView): parser_classes = [JSONParser, MultiPartParser, FormParser] def post(self, request): - _, err = _auth(request) - if err: - return err - if resolve_club_scope(request) == DATA_SCOPE_ALL: - return Response({'code': 400, 'msg': '请切换到具体俱乐部'}) - club_id = club_id_for_write(request) - from orders.services import today_orders as svc + try: + _, err = _auth(request) + if err: + return err + if resolve_club_scope(request) == DATA_SCOPE_ALL: + return Response({'code': 400, 'msg': '请切换到具体俱乐部'}) + club_id = club_id_for_write(request) + from orders.services import today_orders as svc - action = (request.data.get('action') or '').strip().lower() - if action == 'delete': - ok, msg = svc.delete_fake(club_id, int(request.data.get('id') or 0)) - if not ok: - return Response({'code': 400, 'msg': msg}) - return Response({'code': 0, 'msg': '已删除'}) - if action in ('create', 'update', 'add', 'save'): - fake_id = request.data.get('id') - if action == 'update' or fake_id: - try: - fake_id = int(fake_id) - except (TypeError, ValueError): + raw_action = request.data.get('action') or '' + if isinstance(raw_action, (list, tuple)): + raw_action = raw_action[-1] if raw_action else '' + action = str(raw_action).strip().lower() + if action == 'delete': + raw_id = request.data.get('id') + if isinstance(raw_id, (list, tuple)): + raw_id = raw_id[-1] if raw_id else 0 + ok, msg = svc.delete_fake(club_id, int(raw_id or 0)) + if not ok: + return Response({'code': 400, 'msg': msg}) + return Response({'code': 0, 'msg': '已删除'}) + if action in ('create', 'update', 'add', 'save'): + fake_id = request.data.get('id') + if isinstance(fake_id, (list, tuple)): + fake_id = fake_id[-1] if fake_id else None + if action == 'update' or fake_id: + try: + fake_id = int(fake_id) + except (TypeError, ValueError): + fake_id = None + else: fake_id = None - else: - fake_id = None - # multipart/QueryDict 用 dict() 会得到 list;.dict() 取每个 key 的最后一个值 - raw = request.data - if hasattr(raw, 'dict'): - data = raw.dict() - else: - data = dict(raw) - for k, v in list(data.items()): - if isinstance(v, (list, tuple)): - data[k] = v[-1] if v else None - file_obj = request.FILES.get('file') or request.FILES.get('image') - if file_obj: - try: - import uuid - from utils.oss_utils import upload_to_oss - ext = '.' + (file_obj.name.rsplit('.', 1)[-1].lower() if '.' in file_obj.name else 'jpg') - if ext not in ('.jpg', '.jpeg', '.png', '.webp'): - ext = '.jpg' - rel = f'club/{club_id}/today_order_fake/{uuid.uuid4().hex}{ext}' - if not upload_to_oss(file_obj, rel): - return Response({'code': 400, 'msg': '上传失败'}) - data['image_url'] = rel - except Exception as e: - return Response({'code': 400, 'msg': f'上传失败: {e}'}) - row, msg = svc.upsert_fake(club_id, data, fake_id=fake_id) - if not row: - return Response({'code': 400, 'msg': msg or '保存失败'}) - return Response({'code': 0, 'msg': '已保存', 'data': row}) - return Response({'code': 400, 'msg': '未知 action'}) + raw = request.data + if hasattr(raw, 'dict'): + data = raw.dict() + else: + data = dict(raw) + for k, v in list(data.items()): + if isinstance(v, (list, tuple)): + data[k] = v[-1] if v else None + file_obj = request.FILES.get('file') or request.FILES.get('image') + if file_obj: + try: + import uuid + from utils.oss_utils import upload_to_oss + ext = '.' + (file_obj.name.rsplit('.', 1)[-1].lower() if '.' in file_obj.name else 'jpg') + if ext not in ('.jpg', '.jpeg', '.png', '.webp'): + ext = '.jpg' + rel = f'club/{club_id}/today_order_fake/{uuid.uuid4().hex}{ext}' + if not upload_to_oss(file_obj, rel): + return Response({'code': 400, 'msg': '上传失败'}) + data['image_url'] = rel + except Exception as e: + return Response({'code': 400, 'msg': f'上传失败: {e}'}) + row, msg = svc.upsert_fake(club_id, data, fake_id=fake_id) + if not row: + return Response({'code': 400, 'msg': msg or '保存失败'}) + return Response({'code': 0, 'msg': '已保存', 'data': row}) + return Response({'code': 400, 'msg': '未知 action'}) + except Exception as e: + logger.exception('today-order-fake-manage failed: %s', e) + return Response({'code': 500, 'msg': f'服务器错误: {e}'}) diff --git a/jituan/views_wecom_kefu.py b/jituan/views_wecom_kefu.py index 3524ad6..4dce574 100644 --- a/jituan/views_wecom_kefu.py +++ b/jituan/views_wecom_kefu.py @@ -1,4 +1,7 @@ """客服后台:企业微信客服链接(打手端 / 商家端)。""" +import logging + +from django.db import OperationalError, ProgrammingError from rest_framework.parsers import JSONParser from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response @@ -8,6 +11,8 @@ from backend.utils import verify_kefu_permission, has_club_super_perm, has_perm_ from jituan.services.club_context import resolve_club_id_from_request from jituan.services import wecom_kefu as svc +logger = logging.getLogger(__name__) + PERM_CODE = '8080a' @@ -36,21 +41,31 @@ class WecomKefuConfigView(APIView): parser_classes = [JSONParser] def post(self, request): - kefu, err = _auth(request) - if err: - return err - club_id = (resolve_club_id_from_request(request) or '').strip() - if not club_id: - return Response({'code': 400, 'msg': '请先选择俱乐部'}) - action = (request.data.get('action') or 'get').strip().lower() - if action == 'get': - row = svc.get_or_create_wecom_kefu(club_id) - return Response({'code': 0, 'msg': 'ok', 'data': svc.config_to_dict(row)}) - if action == 'save': - try: - operator = getattr(request.user, 'UserUID', '') or '' - data = svc.save_wecom_kefu(club_id, request.data, updated_by=operator) - return Response({'code': 0, 'msg': '已保存', 'data': data}) - except ValueError as e: - return Response({'code': 400, 'msg': str(e)}) - return Response({'code': 400, 'msg': '未知 action'}) + try: + kefu, err = _auth(request) + if err: + return err + club_id = (resolve_club_id_from_request(request) or '').strip() + if not club_id: + return Response({'code': 400, 'msg': '请先选择俱乐部'}) + action = (request.data.get('action') or 'get').strip().lower() + if action == 'get': + row = svc.get_or_create_wecom_kefu(club_id) + return Response({'code': 0, 'msg': 'ok', 'data': svc.config_to_dict(row)}) + if action == 'save': + try: + operator = getattr(request.user, 'UserUID', '') or '' + data = svc.save_wecom_kefu(club_id, request.data, updated_by=operator) + return Response({'code': 0, 'msg': '已保存', 'data': data}) + except ValueError as e: + return Response({'code': 400, 'msg': str(e)}) + return Response({'code': 400, 'msg': '未知 action'}) + except (OperationalError, ProgrammingError) as e: + logger.exception('wecom-kefu-config db error: %s', e) + return Response({ + 'code': 500, + 'msg': '企微客服配置表未就绪,请在服务器执行: python manage.py migrate jituan', + }) + except Exception as e: + logger.exception('wecom-kefu-config failed: %s', e) + return Response({'code': 500, 'msg': f'服务器错误: {e}'}) diff --git a/orders/services/today_orders.py b/orders/services/today_orders.py index f7bd1cd..7a26a99 100644 --- a/orders/services/today_orders.py +++ b/orders/services/today_orders.py @@ -82,12 +82,39 @@ def config_to_dict(row: TodayOrderShowConfig | None) -> dict: def save_config(club_id: str, data: dict) -> dict: row = get_or_create_config(club_id) + + def _as_bool(v): + if isinstance(v, bool): + return v + if isinstance(v, (int, float)): + return bool(v) + if isinstance(v, (list, tuple)): + v = v[-1] if v else False + s = str(v or '').strip().lower() + if s in ('1', 'true', 'yes', 'on'): + return True + if s in ('0', 'false', 'no', 'off', ''): + return False + return bool(v) + + def _as_int(v, default): + if isinstance(v, (list, tuple)): + v = v[-1] if v else default + if v is None or v == '': + return default + try: + return int(float(v)) + except (TypeError, ValueError): + return default + if 'mix_enabled' in data: - row.mix_enabled = bool(data.get('mix_enabled')) - if 'batch_size' in data and data.get('batch_size') is not None: - row.batch_size = max(0, min(500, int(data.get('batch_size') or 0))) - if 'interval_seconds' in data and data.get('interval_seconds') is not None: - row.interval_seconds = max(1, min(86400, int(data.get('interval_seconds') or 120))) + row.mix_enabled = _as_bool(data.get('mix_enabled')) + if 'batch_size' in data and data.get('batch_size') is not None and data.get('batch_size') != '': + row.batch_size = max(0, min(500, _as_int(data.get('batch_size'), row.batch_size or 5))) + if 'interval_seconds' in data and data.get('interval_seconds') is not None and data.get('interval_seconds') != '': + row.interval_seconds = max( + 1, min(86400, _as_int(data.get('interval_seconds'), row.interval_seconds or 120)), + ) row.save() return config_to_dict(row)