| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- from __future__ import annotations
- from datetime import date
- from typing import Dict
- from fastapi import APIRouter, Depends
- from sqlalchemy import func
- from sqlalchemy.orm import Session
- from app.core.database import get_db
- from app.core.db_auth import DbUserContext, get_db_current_user
- from app.core.tenant_context import get_tenant_no
- from app.models.db_models import DbInboundOutbound, DbWeighingSlip
- from app.schemas.db.dashboard import (
- DashboardCargoStat,
- DashboardPartnerCargoStat,
- DashboardStats,
- )
- from app.services.db_data_scope import apply_db_scope
- router = APIRouter(prefix="/dashboard", tags=["dashboard"])
- def _scoped_inventory_query(db: Session, tenant_no: str, user: DbUserContext):
- query = db.query(DbInboundOutbound).filter(
- DbInboundOutbound.del_flag == 0,
- DbInboundOutbound.tenant_no == tenant_no,
- )
- return apply_db_scope(query, DbInboundOutbound, user, db, tenant_no)
- def _scoped_weighing_query(db: Session, tenant_no: str, user: DbUserContext):
- query = db.query(DbWeighingSlip).filter(
- DbWeighingSlip.is_voided == 0,
- DbWeighingSlip.del_flag == 0,
- DbWeighingSlip.tenant_no == tenant_no,
- )
- return apply_db_scope(query, DbWeighingSlip, user, db, tenant_no)
- @router.get("/stats", response_model=DashboardStats)
- def get_dashboard_stats(
- db: Session = Depends(get_db),
- user: DbUserContext = Depends(get_db_current_user),
- ) -> DashboardStats:
- tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
- today = date.today()
- today_inbound = float(
- _scoped_inventory_query(db, tenant_no, user)
- .filter(
- DbInboundOutbound.date == today,
- DbInboundOutbound.business_type == "入库",
- )
- .with_entities(func.coalesce(func.sum(DbInboundOutbound.quantity), 0))
- .scalar()
- or 0
- )
- today_outbound = float(
- _scoped_inventory_query(db, tenant_no, user)
- .filter(
- DbInboundOutbound.date == today,
- DbInboundOutbound.business_type == "出库",
- )
- .with_entities(func.coalesce(func.sum(DbInboundOutbound.quantity), 0))
- .scalar()
- or 0
- )
- abnormal_orders = int(
- _scoped_weighing_query(db, tenant_no, user)
- .with_entities(func.count(DbWeighingSlip.id))
- .scalar()
- or 0
- )
- return DashboardStats(
- today_inbound=today_inbound,
- today_outbound=today_outbound,
- abnormal_orders=abnormal_orders,
- )
- @router.get("/inbound/by-cargo", response_model=list[DashboardCargoStat])
- def get_inbound_by_cargo(
- db: Session = Depends(get_db),
- user: DbUserContext = Depends(get_db_current_user),
- ) -> list[DashboardCargoStat]:
- return _cargo_stats(db, user, business_type="入库")
- @router.get("/inbound/by-partner-cargo", response_model=list[DashboardPartnerCargoStat])
- def get_inbound_by_partner_cargo(
- db: Session = Depends(get_db),
- user: DbUserContext = Depends(get_db_current_user),
- ) -> list[DashboardPartnerCargoStat]:
- return _partner_cargo_stats(db, user, business_type="入库")
- @router.get("/outbound/by-cargo", response_model=list[DashboardCargoStat])
- def get_outbound_by_cargo(
- db: Session = Depends(get_db),
- user: DbUserContext = Depends(get_db_current_user),
- ) -> list[DashboardCargoStat]:
- return _cargo_stats(db, user, business_type="出库")
- @router.get("/outbound/by-partner-cargo", response_model=list[DashboardPartnerCargoStat])
- def get_outbound_by_partner_cargo(
- db: Session = Depends(get_db),
- user: DbUserContext = Depends(get_db_current_user),
- ) -> list[DashboardPartnerCargoStat]:
- return _partner_cargo_stats(db, user, business_type="出库")
- def _weighing_actual_map(
- db: Session, tenant_no: str, user: DbUserContext, inventory_ids: list[str]
- ) -> Dict[str, float]:
- if not inventory_ids:
- return {}
- rows = (
- _scoped_weighing_query(db, tenant_no, user)
- .filter(DbWeighingSlip.inbound_outbound_id.in_(inventory_ids))
- .all()
- )
- actual_map: Dict[str, float] = {}
- for row in rows:
- if not row.inbound_outbound_id:
- continue
- actual_map[row.inbound_outbound_id] = actual_map.get(row.inbound_outbound_id, 0.0) + (
- float(row.net_weight or 0) / 1000.0
- )
- return actual_map
- def _cargo_stats(db: Session, user: DbUserContext, *, business_type: str) -> list[DashboardCargoStat]:
- tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
- today = date.today()
- inventories = (
- _scoped_inventory_query(db, tenant_no, user)
- .filter(
- DbInboundOutbound.date == today,
- DbInboundOutbound.business_type == business_type,
- )
- .all()
- )
- actual_map = _weighing_actual_map(db, tenant_no, user, [inv.id for inv in inventories])
- agg: Dict[str, Dict[str, float]] = {}
- for inv in inventories:
- label = (inv.product or "").strip() or "未填写货物"
- bucket = agg.setdefault(label, {"planned": 0.0, "actual": 0.0})
- bucket["planned"] += float(inv.quantity or 0)
- bucket["actual"] += actual_map.get(inv.id, 0.0)
- return [
- DashboardCargoStat(label=label, planned=values["planned"], actual=values["actual"])
- for label, values in sorted(agg.items(), key=lambda item: item[1]["planned"], reverse=True)
- ]
- def _partner_cargo_stats(
- db: Session,
- user: DbUserContext,
- *,
- business_type: str,
- ) -> list[DashboardPartnerCargoStat]:
- tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
- today = date.today()
- inventories = (
- _scoped_inventory_query(db, tenant_no, user)
- .filter(
- DbInboundOutbound.date == today,
- DbInboundOutbound.business_type == business_type,
- )
- .all()
- )
- actual_map = _weighing_actual_map(db, tenant_no, user, [inv.id for inv in inventories])
- agg: Dict[str, Dict[str, float]] = {}
- for inv in inventories:
- product = (inv.product or "").strip() or "未填写货物"
- label = f"{inv.partner_company or ''} - {product}"
- bucket = agg.setdefault(label, {"planned": 0.0, "actual": 0.0})
- bucket["planned"] += float(inv.quantity or 0)
- bucket["actual"] += actual_map.get(inv.id, 0.0)
- return [
- DashboardPartnerCargoStat(label=label, planned=values["planned"], actual=values["actual"])
- for label, values in sorted(
- agg.items(), key=lambda item: (item[0].split(" - ", 1)[0], -item[1]["planned"])
- )
- ]
|