"""报表与 BI 大盘服务: 1. 经营大盘总览 (Dashboard Summary) 2. 销售排行榜分析 (Sales Ranking) 3. 进销存综合周转与估值 (Inventory & Valuation) 4. 预置系统报表与元数据动态执行引擎 """ from __future__ import annotations from datetime import date, datetime, timedelta from decimal import Decimal from typing import Optional from django.db.models import Sum, F, Count, Q from django.utils import timezone from apps.sales.models import SalesBill, SalesBillLine from apps.purchase.models import PurchaseBill from apps.inventory.models import Stock, Warehouse from apps.finance.models import Receivable, Payable, Account, VoucherEntry from .models import ReportDefinition SYSTEM_REPORTS = [ { "code": "sales_product_ranking", "name": "商品销售排行分析", "category": "sales", "description": "按商品统计销售数量、销售总额与客单均价", "query_config": { "group_by": "product", "metrics": ["quantity", "total_amount"], }, }, { "code": "sales_customer_ranking", "name": "客户贡献排行分析", "category": "sales", "description": "按客户统计采购额及订单频次", "query_config": { "group_by": "customer", "metrics": ["total_amount", "bill_count"], }, }, { "code": "inventory_valuation", "name": "库存商品总值分析", "category": "inventory", "description": "按仓库统计库存数量、均摊成本与总估值", "query_config": { "group_by": "warehouse", "metrics": ["on_hand", "total_value"], }, }, { "code": "receivable_outstanding", "name": "客户待收账款分析", "category": "finance", "description": "统计未结清应收账款及客户分布", "query_config": { "group_by": "customer", "metrics": ["total_amount", "paid_amount", "balance"], }, }, ] def init_system_reports(tenant) -> int: """初始化系统内置报表定义。""" created_count = 0 for r in SYSTEM_REPORTS: obj, was_created = ReportDefinition.objects.get_or_create( tenant=tenant, code=r["code"], defaults={ "name": r["name"], "category": r["category"], "description": r["description"], "is_system": True, "query_config": r["query_config"], }, ) if was_created: created_count += 1 return created_count def get_dashboard_summary(tenant) -> dict: """获取经销商经营中枢大盘总览指标。""" today = date.today() month_start = today.replace(day=1) # 1. 销售指标 (已确认销售单) sales_qs = SalesBill.objects.filter(tenant=tenant, state="confirmed") month_sales_qs = sales_qs.filter(bill_date__gte=month_start) total_sales_amount = month_sales_qs.aggregate(s=Sum("total_amount"))["s"] or Decimal("0") total_sales_count = month_sales_qs.count() # 2. 采购指标 purchase_qs = PurchaseBill.objects.filter(tenant=tenant, state="confirmed") month_purchase_qs = purchase_qs.filter(bill_date__gte=month_start) total_purchase_amount = month_purchase_qs.aggregate(s=Sum("total_amount"))["s"] or Decimal("0") total_purchase_count = month_purchase_qs.count() # 3. 库存指标 stock_qs = Stock.objects.filter(tenant=tenant) sku_count = stock_qs.filter(on_hand__gt=0).values("product_id").distinct().count() total_stock_qty = stock_qs.aggregate(s=Sum("on_hand"))["s"] or Decimal("0") # 计算库存资产估值 = sum(on_hand * avg_cost) inventory_val = Decimal("0") for s in stock_qs.filter(on_hand__gt=0): inventory_val += s.on_hand * s.avg_cost # 4. 往来资金指标 ar_total = Receivable.objects.filter(tenant=tenant, status__in=["open", "partial"]).aggregate( b=Sum(F("total_amount") - F("paid_amount")) )["b"] or Decimal("0") ap_total = Payable.objects.filter(tenant=tenant, status__in=["open", "partial"]).aggregate( b=Sum(F("total_amount") - F("paid_amount")) )["b"] or Decimal("0") return { "as_of_date": str(today), "month_sales": { "amount": total_sales_amount, "count": total_sales_count, }, "month_purchase": { "amount": total_purchase_amount, "count": total_purchase_count, }, "inventory": { "sku_count": sku_count, "total_quantity": total_stock_qty, "valuation": inventory_val, }, "finance": { "receivable_balance": ar_total, "payable_balance": ap_total, }, } def get_sales_rank( tenant, *, start_date: Optional[date] = None, end_date: Optional[date] = None, rank_by: str = "product", top_n: int = 10, ) -> list[dict]: """销售排行榜:按商品 (product) 或客户 (customer) 统计销售额。""" lines = SalesBillLine.objects.filter( tenant=tenant, bill__state="confirmed", ) if start_date: lines = lines.filter(bill__bill_date__gte=start_date) if end_date: lines = lines.filter(bill__bill_date__lte=end_date) if rank_by == "customer": agg = lines.values( "bill__customer__id", "bill__customer__code", "bill__customer__name", ).annotate( total_amount=Sum("amount"), total_quantity=Sum("quantity"), bill_count=Count("bill_id", distinct=True), ).order_by("-total_amount")[:top_n] return [ { "customer_id": row["bill__customer__id"], "customer_code": row["bill__customer__code"], "customer_name": row["bill__customer__name"], "total_amount": row["total_amount"] or Decimal("0"), "total_quantity": row["total_quantity"] or Decimal("0"), "bill_count": row["bill_count"], } for row in agg ] else: agg = lines.values( "product__id", "product__code", "product__name", ).annotate( total_amount=Sum("amount"), total_quantity=Sum("quantity"), ).order_by("-total_amount")[:top_n] return [ { "product_id": row["product__id"], "product_code": row["product__code"], "product_name": row["product__name"], "total_amount": row["total_amount"] or Decimal("0"), "total_quantity": row["total_quantity"] or Decimal("0"), } for row in agg ] def get_inventory_status_report(tenant) -> dict: """各仓库库存分布与估值报告。""" warehouses = Warehouse.objects.filter(tenant=tenant, is_active=True) wh_data = [] grand_qty = Decimal("0") grand_val = Decimal("0") for wh in warehouses: stocks = Stock.objects.filter(tenant=tenant, warehouse=wh, on_hand__gt=0).select_related("product") wh_qty = Decimal("0") wh_val = Decimal("0") items = [] for s in stocks: val = s.on_hand * s.avg_cost wh_qty += s.on_hand wh_val += val items.append({ "product_code": s.product.code, "product_name": s.product.name, "on_hand": s.on_hand, "locked": s.locked, "avg_cost": s.avg_cost, "valuation": val, }) wh_data.append({ "warehouse_code": wh.code, "warehouse_name": wh.name, "total_quantity": wh_qty, "total_valuation": wh_val, "items": items, }) grand_qty += wh_qty grand_val += wh_val return { "grand_total_quantity": grand_qty, "grand_total_valuation": grand_val, "warehouses": wh_data, } def execute_report_definition(tenant, report_def: ReportDefinition, params: Optional[dict] = None) -> dict: """根据元数据定义执行报表查询。""" code = report_def.code if code == "sales_product_ranking": return {"rows": get_sales_rank(tenant, rank_by="product")} elif code == "sales_customer_ranking": return {"rows": get_sales_rank(tenant, rank_by="customer")} elif code == "inventory_valuation": return get_inventory_status_report(tenant) elif code == "receivable_outstanding": recv_qs = Receivable.objects.filter(tenant=tenant, status__in=["open", "partial"]).select_related("customer") rows = [] for r in recv_qs: rows.append({ "bill_no": r.bill_no, "customer_name": r.customer.name, "customer_code": r.customer.code, "bill_date": str(r.bill_date), "total_amount": r.total_amount, "paid_amount": r.paid_amount, "balance": r.balance, }) return {"rows": rows} else: return {"rows": [], "message": f"自定义报表 {code} 查询未指定原生执行器"}