dashboard.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. from __future__ import annotations
  2. from datetime import date
  3. from fastapi import APIRouter, Depends
  4. from sqlalchemy import func, text
  5. from sqlalchemy.orm import Session
  6. from app.core.database import get_db
  7. from app.core.db_auth import DbUserContext, get_db_current_user
  8. from app.core.tenant_context import get_tenant_no
  9. from app.models.db_models import DbInboundOutbound, DbWeighingSlip
  10. from app.schemas.db.dashboard import (
  11. DashboardCargoStat,
  12. DashboardPartnerCargoStat,
  13. DashboardStats,
  14. )
  15. router = APIRouter(prefix="/dashboard", tags=["dashboard"])
  16. @router.get("/stats", response_model=DashboardStats)
  17. def get_dashboard_stats(
  18. db: Session = Depends(get_db),
  19. user: DbUserContext = Depends(get_db_current_user),
  20. ) -> DashboardStats:
  21. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  22. today = date.today()
  23. today_inbound = float(
  24. db.query(func.coalesce(func.sum(DbInboundOutbound.quantity), 0))
  25. .filter(
  26. DbInboundOutbound.date == today,
  27. DbInboundOutbound.business_type == "入库",
  28. DbInboundOutbound.del_flag == 0,
  29. DbInboundOutbound.tenant_no == tenant_no,
  30. )
  31. .scalar()
  32. or 0
  33. )
  34. today_outbound = float(
  35. db.query(func.coalesce(func.sum(DbInboundOutbound.quantity), 0))
  36. .filter(
  37. DbInboundOutbound.date == today,
  38. DbInboundOutbound.business_type == "出库",
  39. DbInboundOutbound.del_flag == 0,
  40. DbInboundOutbound.tenant_no == tenant_no,
  41. )
  42. .scalar()
  43. or 0
  44. )
  45. abnormal_orders = int(
  46. db.query(func.count(DbWeighingSlip.id))
  47. .filter(
  48. DbWeighingSlip.is_voided == 0,
  49. DbWeighingSlip.del_flag == 0,
  50. DbWeighingSlip.tenant_no == tenant_no,
  51. )
  52. .scalar()
  53. or 0
  54. )
  55. return DashboardStats(
  56. today_inbound=today_inbound,
  57. today_outbound=today_outbound,
  58. abnormal_orders=abnormal_orders,
  59. )
  60. @router.get("/inbound/by-cargo", response_model=list[DashboardCargoStat])
  61. def get_inbound_by_cargo(
  62. db: Session = Depends(get_db),
  63. user: DbUserContext = Depends(get_db_current_user),
  64. ) -> list[DashboardCargoStat]:
  65. return _cargo_stats(db, user, business_type="入库")
  66. @router.get("/inbound/by-partner-cargo", response_model=list[DashboardPartnerCargoStat])
  67. def get_inbound_by_partner_cargo(
  68. db: Session = Depends(get_db),
  69. user: DbUserContext = Depends(get_db_current_user),
  70. ) -> list[DashboardPartnerCargoStat]:
  71. return _partner_cargo_stats(db, user, business_type="入库")
  72. @router.get("/outbound/by-cargo", response_model=list[DashboardCargoStat])
  73. def get_outbound_by_cargo(
  74. db: Session = Depends(get_db),
  75. user: DbUserContext = Depends(get_db_current_user),
  76. ) -> list[DashboardCargoStat]:
  77. return _cargo_stats(db, user, business_type="出库")
  78. @router.get("/outbound/by-partner-cargo", response_model=list[DashboardPartnerCargoStat])
  79. def get_outbound_by_partner_cargo(
  80. db: Session = Depends(get_db),
  81. user: DbUserContext = Depends(get_db_current_user),
  82. ) -> list[DashboardPartnerCargoStat]:
  83. return _partner_cargo_stats(db, user, business_type="出库")
  84. def _cargo_stats(db: Session, user: DbUserContext, *, business_type: str) -> list[DashboardCargoStat]:
  85. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  86. today = date.today()
  87. query = text(
  88. """
  89. SELECT
  90. COALESCE(io.product, ws.cargo_name, '未填写货物') AS label,
  91. COALESCE(SUM(io.quantity), 0) AS planned,
  92. COALESCE(SUM(ws.net_weight) / 1000.0, 0) AS actual
  93. FROM db_inbound_outbound io
  94. LEFT JOIN db_weighing_slip ws
  95. ON ws.inbound_outbound_id = io.id
  96. AND ws.is_voided = 0
  97. AND ws.del_flag = 0
  98. AND ws.tenant_no = io.tenant_no
  99. WHERE io.date = :today
  100. AND io.business_type = :business_type
  101. AND io.del_flag = 0
  102. AND io.tenant_no = :tenant_no
  103. GROUP BY COALESCE(io.product, ws.cargo_name, '未填写货物')
  104. ORDER BY planned DESC
  105. """
  106. )
  107. rows = db.execute(
  108. query,
  109. {"today": today, "business_type": business_type, "tenant_no": tenant_no},
  110. ).all()
  111. return [
  112. DashboardCargoStat(label=row.label, planned=float(row.planned), actual=float(row.actual))
  113. for row in rows
  114. ]
  115. def _partner_cargo_stats(
  116. db: Session,
  117. user: DbUserContext,
  118. *,
  119. business_type: str,
  120. ) -> list[DashboardPartnerCargoStat]:
  121. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  122. today = date.today()
  123. query = text(
  124. """
  125. SELECT
  126. CONCAT(
  127. COALESCE(io.partner_company, ''),
  128. ' - ',
  129. COALESCE(io.product, ws.cargo_name, '未填写货物')
  130. ) AS label,
  131. COALESCE(SUM(io.quantity), 0) AS planned,
  132. COALESCE(SUM(ws.net_weight) / 1000.0, 0) AS actual
  133. FROM db_inbound_outbound io
  134. LEFT JOIN db_weighing_slip ws
  135. ON ws.inbound_outbound_id = io.id
  136. AND ws.is_voided = 0
  137. AND ws.del_flag = 0
  138. AND ws.tenant_no = io.tenant_no
  139. WHERE io.date = :today
  140. AND io.business_type = :business_type
  141. AND io.del_flag = 0
  142. AND io.tenant_no = :tenant_no
  143. GROUP BY io.partner_company, io.product, ws.cargo_name
  144. ORDER BY io.partner_company, planned DESC
  145. """
  146. )
  147. rows = db.execute(
  148. query,
  149. {"today": today, "business_type": business_type, "tenant_no": tenant_no},
  150. ).all()
  151. return [
  152. DashboardPartnerCargoStat(
  153. label=row.label,
  154. planned=float(row.planned),
  155. actual=float(row.actual),
  156. )
  157. for row in rows
  158. ]