"""报表中心 ViewSets: 1. ReportDefinitionViewSet: 报表元数据 CRUD + run 执行报表 + init_system 预置报表 2. DashboardViewSet: 经营大盘总览指标、销售排行、库存透视 """ from rest_framework import status from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.exceptions import ValidationError from asgiref.sync import sync_to_async from adrf.viewsets import ViewSet from apps.core.viewset import BaseTenantViewSet from .models import ReportDefinition from .serializers import ReportDefinitionSerializer from . import services as report_services class ReportDefinitionViewSet(BaseTenantViewSet): model = ReportDefinition serializer_class = ReportDefinitionSerializer search_fields = ["code", "name", "description"] @action(detail=False, methods=["post"], url_path="init-system") async def init_system(self, request): """一键初始化系统预置标准报表。""" tenant = await self.get_tenant() if not tenant: raise ValidationError({"tenant": "无法识别租户"}) n = await sync_to_async(report_services.init_system_reports)(tenant) return Response({"ok": True, "created_count": n, "message": f"成功初始化 {n} 个系统报表定义"}) @action(detail=True, methods=["post", "get"], url_path="run") async def run_report(self, request, pk=None): """执行指定报表定义并返回数据。""" tenant = await self.get_tenant() try: report_def = await sync_to_async(self.model.objects.get)(tenant=tenant, pk=pk) except self.model.DoesNotExist: return Response({"detail": "报表不存在"}, status=status.HTTP_404_NOT_FOUND) data = await sync_to_async(report_services.execute_report_definition)(tenant, report_def) return Response(data) class DashboardViewSet(ViewSet): """进销存与财务经营大盘。""" async def get_tenant(self): from apps.core.viewset import resolve_tenant from django.conf import settings code = self.request.META.get("HTTP_X_TENANT_ID", settings.TENANT_DEFAULT) return await sync_to_async(resolve_tenant)(code) @action(detail=False, methods=["get"], url_path="summary") async def summary(self, request): """获取本月经营总览指标(销售额、采购额、库存估值、应收应付总额)。""" tenant = await self.get_tenant() if not tenant: raise ValidationError({"tenant": "无法识别租户"}) data = await sync_to_async(report_services.get_dashboard_summary)(tenant) return Response(data) @action(detail=False, methods=["get"], url_path="sales-rank") async def sales_rank(self, request): """销售排行:参数 rank_by=product/customer,top_n=10。""" tenant = await self.get_tenant() if not tenant: raise ValidationError({"tenant": "无法识别租户"}) rank_by = request.query_params.get("rank_by", "product") try: top_n = int(request.query_params.get("top_n", 10)) except ValueError: top_n = 10 data = await sync_to_async(report_services.get_sales_rank)(tenant, rank_by=rank_by, top_n=top_n) return Response({"rank_by": rank_by, "results": data}) @action(detail=False, methods=["get"], url_path="inventory-status") async def inventory_status(self, request): """各仓库库存分布与资产估值。""" tenant = await self.get_tenant() if not tenant: raise ValidationError({"tenant": "无法识别租户"}) data = await sync_to_async(report_services.get_inventory_status_report)(tenant) return Response(data)