1
0
forked from erp-dev/erp
Files
erpnew/api_v1/views/settlement/views.py
2026-07-11 00:05:05 +08:00

318 lines
11 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.
"""Settlement API views"""
import logging
import re
from datetime import datetime, date
from django.contrib.auth import get_user_model
from django.utils import timezone
from rest_framework import status, authentication
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from rest_framework_simplejwt.authentication import JWTAuthentication
from basic_info.models import Merchant
from settlement.services import (
DEFAULT_DESIGNER_SUMMARY_STATE_NAMES,
DEFAULT_DESIGNER_TASK_STATE_NAME_SUFFIX,
DEFAULT_DESIGNER_PARAM_KEY,
DEFAULT_DESIGNER_TASK_QUANTITY_PARAM_KEY,
DEFAULT_DESIGNER_TASK_TIME_PARAM_KEY,
get_plate_order_summary_by_customer,
get_plate_order_summary_by_designer,
get_designer_workflow_task_summary,
)
from .mixins import SettlementVisibilityMixin
logger = logging.getLogger(__name__)
AGENT_SECRET = "RCYH_BOT_0083"
class AgentSecretAuthentication(authentication.BaseAuthentication):
"""
Agent 密钥认证(仅用于 settlement/plate-orders/summary API
当请求头中存在 X-AGENT-SECRET 且值匹配时,跳过 JWT 认证,
以 admin 超级管理员身份执行请求。
"""
def authenticate(self, request):
secret = request.META.get("HTTP_X_AGENT_SECRET")
if secret != AGENT_SECRET:
return None
User = get_user_model()
try:
user = User.objects.get(username="admin", is_superuser=True)
return (user, None)
except User.DoesNotExist:
logger.warning("[AgentSecretAuthentication] admin 用户不存在")
return None
class PlateOrderSummaryView(SettlementVisibilityMixin, APIView):
"""
开版订单统计 API
GET /api/v1/settlement/plate-orders/summary/
参数:
date: 统计日期YYYY-MM-DD
认证方式:
- JWT Token标准方式
- X-AGENT-SECRET: RCYH_BOT_0083Agent 专用,跳过鉴权)
可见性规则(与 plate-order 列表接口对齐):
- superuser 或持有 view_all_permission默认 printing.view_all_plateorders
→ 返回该商户下全部客户的开版汇总
- 普通员工 → 仅返回其负责/可见客户的开版汇总
"""
authentication_classes = [
AgentSecretAuthentication,
authentication.SessionAuthentication,
JWTAuthentication,
]
permission_classes = [IsAuthenticated]
view_all_permission = "printing.view_all_plateorders"
def get(self, request):
"""
获取开版订单统计
"""
date_str = request.query_params.get("date")
if not date_str:
return Response(
{"error": "缺少 date 参数"}, status=status.HTTP_400_BAD_REQUEST
)
if not re.match(r"^\d{4}-\d{2}-\d{2}$", date_str):
return Response(
{"error": "日期格式错误,请使用 YYYY-MM-DD 格式"},
status=status.HTTP_400_BAD_REQUEST,
)
try:
settlement_date = date.fromisoformat(date_str)
except ValueError:
return Response({"error": "日期不存在"}, status=status.HTTP_403_FORBIDDEN)
emp = getattr(request.user, "employee", None)
if emp is None or emp.merchant is None:
if request.user.is_superuser:
merchant = Merchant.objects.first()
if merchant is None:
return Response(
{"error": "系统中没有商户"}, status=status.HTTP_403_FORBIDDEN
)
merchant_id = merchant.id
else:
return Response(
{"error": "用户未关联商户"}, status=status.HTTP_403_FORBIDDEN
)
else:
merchant_id = emp.merchant.id
try:
data = get_plate_order_summary_by_customer(
merchant_id=merchant_id,
settlement_date=settlement_date,
user=self.get_service_user(request.user),
)
return Response({"data": data})
except Exception as e:
logger.exception(f"[settlement.views] 获取开版订单统计失败: {e}")
return Response(
{"error": "获取统计数据失败"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
class PlateOrderDesignerSummaryView(SettlementVisibilityMixin, APIView):
"""
开版订单设计师统计 API
GET /api/v1/settlement/plate-orders/designer-summary/
参数:
date: 统计日期YYYY-MM-DD
state_names: 可选,逗号分隔或重复传参的 state 名称,默认“画图完成”
"""
authentication_classes = PlateOrderSummaryView.authentication_classes
permission_classes = [IsAuthenticated]
view_all_permission = "printing.view_all_plateorders"
def get(self, request):
date_str = request.query_params.get("date")
if not date_str:
return Response(
{"error": "缺少 date 参数"}, status=status.HTTP_400_BAD_REQUEST
)
if not re.match(r"^\d{4}-\d{2}-\d{2}$", date_str):
return Response(
{"error": "日期格式错误,请使用 YYYY-MM-DD 格式"},
status=status.HTTP_400_BAD_REQUEST,
)
try:
settlement_date = date.fromisoformat(date_str)
except ValueError:
return Response({"error": "日期不存在"}, status=status.HTTP_403_FORBIDDEN)
emp = getattr(request.user, "employee", None)
if emp is None or emp.merchant is None:
if request.user.is_superuser:
merchant = Merchant.objects.first()
if merchant is None:
return Response(
{"error": "系统中没有商户"}, status=status.HTTP_403_FORBIDDEN
)
merchant_id = merchant.id
else:
return Response(
{"error": "用户未关联商户"}, status=status.HTTP_403_FORBIDDEN
)
else:
merchant_id = emp.merchant.id
state_names = _parse_state_names(request)
try:
data = get_plate_order_summary_by_designer(
merchant_id=merchant_id,
settlement_date=settlement_date,
user=self.get_service_user(request.user),
state_names=state_names,
)
return Response({
"data": data,
"meta": {
"state_names": list(state_names or DEFAULT_DESIGNER_SUMMARY_STATE_NAMES),
"designer_param_key": DEFAULT_DESIGNER_PARAM_KEY,
},
})
except ValueError as e:
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
logger.exception(f"[settlement.views] 获取开版订单设计师统计失败: {e}")
return Response(
{"error": "获取统计数据失败"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
def _parse_state_names(request):
"""解析 state_names支持逗号分隔和重复传参。"""
return _parse_csv_query_param(request, "state_names")
def _parse_designer_names(request):
"""解析 designer_names支持逗号分隔和重复传参。"""
return _parse_csv_query_param(request, "designer_names")
def _parse_csv_query_param(request, name: str):
raw_values = request.query_params.getlist("state_names")
if name != "state_names":
raw_values = request.query_params.getlist(name)
if not raw_values:
return None
values = []
for raw_value in raw_values:
values.extend(
item.strip() for item in str(raw_value).split(',') if item.strip()
)
return values
class DesignerWorkflowTaskSummaryView(SettlementVisibilityMixin, APIView):
"""
设计师工序任务量统计 API
GET /api/v1/settlement/workflows/designer-task-summary/
"""
authentication_classes = PlateOrderSummaryView.authentication_classes
permission_classes = [IsAuthenticated]
view_all_permission = "printing.view_all_plateorders"
def get(self, request):
date_str = request.query_params.get("date")
if not date_str:
return Response(
{"error": "缺少 date 参数"}, status=status.HTTP_400_BAD_REQUEST
)
if not re.match(r"^\d{4}-\d{2}-\d{2}$", date_str):
return Response(
{"error": "日期格式错误,请使用 YYYY-MM-DD 格式"},
status=status.HTTP_400_BAD_REQUEST,
)
try:
settlement_date = date.fromisoformat(date_str)
except ValueError:
return Response({"error": "日期不存在"}, status=status.HTTP_403_FORBIDDEN)
emp = getattr(request.user, "employee", None)
if emp is None or emp.merchant is None:
if request.user.is_superuser:
merchant = Merchant.objects.first()
if merchant is None:
return Response(
{"error": "系统中没有商户"}, status=status.HTTP_403_FORBIDDEN
)
merchant_id = merchant.id
else:
return Response(
{"error": "用户未关联商户"}, status=status.HTTP_403_FORBIDDEN
)
else:
merchant_id = emp.merchant.id
state_names = _parse_state_names(request)
designer_names = _parse_designer_names(request)
try:
data = get_designer_workflow_task_summary(
merchant_id=merchant_id,
settlement_date=settlement_date,
user=self.get_service_user(request.user),
state_names=state_names,
designer_names=designer_names,
)
return Response({
"data": data,
"meta": {
"date": settlement_date.isoformat(),
"state_names": list(state_names or []),
"state_filter": (
{"mode": "exact", "state_names": list(state_names)}
if state_names is not None
else {"mode": "suffix", "suffix": DEFAULT_DESIGNER_TASK_STATE_NAME_SUFFIX}
),
"designer_names": list(designer_names or []),
"designer_param_key": DEFAULT_DESIGNER_PARAM_KEY,
"quantity_param_key": DEFAULT_DESIGNER_TASK_QUANTITY_PARAM_KEY,
"time_param_key": DEFAULT_DESIGNER_TASK_TIME_PARAM_KEY,
"empty_quantity_default": 1,
"time_source": "parameters.完成时间; fallback=StateFlowRecord.completed_at",
},
})
except ValueError as e:
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
logger.exception(f"[settlement.views] 获取设计师工序任务量统计失败: {e}")
return Response(
{"error": "获取统计数据失败"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)