917 lines
33 KiB
Python
917 lines
33 KiB
Python
"""数据统计 CRUD"""
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy import and_, func, desc, extract
|
|
from typing import Optional, List, Dict, Any, Tuple
|
|
from datetime import datetime, timedelta
|
|
from decimal import Decimal
|
|
|
|
from app.models.models import (
|
|
BizOrder, BizOrderItem, BizDeliveryTask,
|
|
BizSettlement, BizRefund, BizPriceList, BizPriceListItem,
|
|
SysUser, SysRegion, BasProduct,
|
|
BizInquiry, BizQuotation, BizDeliveryWarning,
|
|
)
|
|
|
|
|
|
class StatisticsCRUD:
|
|
"""数据统计CRUD操作"""
|
|
|
|
@staticmethod
|
|
def get_order_statistics(
|
|
db: Session,
|
|
region_id: str = None,
|
|
start_date: str = None,
|
|
end_date: str = None
|
|
) -> Dict[str, Any]:
|
|
"""获取订单统计数据"""
|
|
query = db.query(BizOrder).filter(BizOrder.is_del == 0)
|
|
|
|
if region_id:
|
|
query = query.filter(BizOrder.region_id == region_id)
|
|
|
|
if start_date:
|
|
try:
|
|
start_dt = datetime.strptime(start_date, "%Y-%m-%d")
|
|
query = query.filter(BizOrder.create_at >= start_dt)
|
|
except:
|
|
pass
|
|
|
|
if end_date:
|
|
try:
|
|
end_dt = datetime.strptime(end_date, "%Y-%m-%d")
|
|
end_dt = end_dt.replace(hour=23, minute=59, second=59)
|
|
query = query.filter(BizOrder.create_at <= end_dt)
|
|
except:
|
|
pass
|
|
|
|
orders = query.all()
|
|
|
|
total_orders = len(orders)
|
|
pending_payment = sum(1 for o in orders if o.status == "pending_payment")
|
|
pending_dispatch = sum(1 for o in orders if getattr(o, "delivery_status", None) == "pending_dispatch")
|
|
delivering = sum(1 for o in orders if o.status == "delivering")
|
|
completed = sum(1 for o in orders if o.status == "completed")
|
|
cancelled = sum(1 for o in orders if o.status == "cancelled")
|
|
|
|
total_amount = sum(float(o.total_amount or 0) for o in orders)
|
|
paid_amount = sum(float(o.paid_amount or 0) for o in orders)
|
|
refund_amount = sum(float(o.refund_amount or 0) for o in orders)
|
|
|
|
return {
|
|
"totalOrders": total_orders,
|
|
"pendingPayment": pending_payment,
|
|
"pendingDelivery": pending_dispatch,
|
|
"delivering": delivering,
|
|
"completed": completed,
|
|
"cancelled": cancelled,
|
|
"totalAmount": round(total_amount, 2),
|
|
"paidAmount": round(paid_amount, 2),
|
|
"refundAmount": round(refund_amount, 2)
|
|
}
|
|
|
|
@staticmethod
|
|
def get_order_trend(
|
|
db: Session,
|
|
region_id: str = None,
|
|
days: int = 7,
|
|
include_inactive_regions: bool = False,
|
|
) -> List[Dict[str, Any]]:
|
|
"""获取订单趋势数据(按日统计)"""
|
|
result = []
|
|
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
|
region_active_clause = [] if include_inactive_regions else [SysRegion.is_active == True]
|
|
|
|
for i in range(days - 1, -1, -1):
|
|
day_start = today - timedelta(days=i)
|
|
day_end = day_start + timedelta(days=1)
|
|
|
|
query = (
|
|
db.query(BizOrder)
|
|
.join(SysRegion, BizOrder.region_id == SysRegion.id)
|
|
.filter(
|
|
and_(
|
|
BizOrder.is_del == 0,
|
|
BizOrder.create_at >= day_start,
|
|
BizOrder.create_at < day_end,
|
|
SysRegion.is_del == 0,
|
|
*region_active_clause,
|
|
)
|
|
)
|
|
)
|
|
|
|
if region_id:
|
|
query = query.filter(BizOrder.region_id == region_id)
|
|
|
|
orders = query.all()
|
|
order_count = len(orders)
|
|
order_amount = sum(float(o.total_amount or 0) for o in orders)
|
|
|
|
result.append({
|
|
"date": day_start.strftime("%Y-%m-%d"),
|
|
"orderCount": order_count,
|
|
"orderAmount": round(order_amount, 2)
|
|
})
|
|
|
|
return result
|
|
|
|
@staticmethod
|
|
def get_today_order_stats_by_region(
|
|
db: Session,
|
|
region_id: str = None,
|
|
include_inactive_regions: bool = False,
|
|
) -> List[Dict[str, Any]]:
|
|
"""今日各区域营业额(元)、订单笔数;列出未删除的区域,无单则为 0。"""
|
|
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
|
day_end = today + timedelta(days=1)
|
|
region_active_clause = [] if include_inactive_regions else [SysRegion.is_active == True]
|
|
|
|
reg_q = db.query(SysRegion).filter(
|
|
SysRegion.is_del == 0,
|
|
)
|
|
if region_id:
|
|
reg_q = reg_q.filter(SysRegion.id == region_id)
|
|
regions = reg_q.order_by(SysRegion.sort_order, SysRegion.name).all()
|
|
|
|
agg_q = (
|
|
db.query(
|
|
BizOrder.region_id,
|
|
func.count(BizOrder.id).label("order_count"),
|
|
func.coalesce(func.sum(BizOrder.total_amount), 0).label("order_amount"),
|
|
)
|
|
.join(SysRegion, BizOrder.region_id == SysRegion.id)
|
|
.filter(
|
|
BizOrder.is_del == 0,
|
|
BizOrder.create_at >= today,
|
|
BizOrder.create_at < day_end,
|
|
SysRegion.is_del == 0,
|
|
*region_active_clause,
|
|
)
|
|
.group_by(BizOrder.region_id)
|
|
)
|
|
if region_id:
|
|
agg_q = agg_q.filter(BizOrder.region_id == region_id)
|
|
agg_map: Dict[str, Any] = {r.region_id: r for r in agg_q.all() if r.region_id}
|
|
|
|
out: List[Dict[str, Any]] = []
|
|
for reg in regions:
|
|
row = agg_map.get(reg.id)
|
|
cnt = int(row.order_count or 0) if row else 0
|
|
amt = float(row.order_amount or 0) if row else 0.0
|
|
out.append({
|
|
"regionId": reg.id,
|
|
"regionName": reg.name or reg.id,
|
|
"orderCount": cnt,
|
|
"orderAmount": round(amt, 2),
|
|
})
|
|
return out
|
|
|
|
@staticmethod
|
|
def get_today_order_stats_by_client(
|
|
db: Session,
|
|
region_id: str = None,
|
|
limit: int = 30,
|
|
include_inactive_regions: bool = False,
|
|
) -> List[Dict[str, Any]]:
|
|
"""今日各下单客户:下单金额合计(元)、商品数量合计(订单行数量之和);按金额降序,最多 limit 条。"""
|
|
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
|
day_end = today + timedelta(days=1)
|
|
region_active_clause = [] if include_inactive_regions else [SysRegion.is_active == True]
|
|
|
|
amt_q = (
|
|
db.query(
|
|
BizOrder.client_id,
|
|
func.coalesce(func.sum(BizOrder.total_amount), 0).label("order_amount"),
|
|
)
|
|
.join(SysRegion, BizOrder.region_id == SysRegion.id)
|
|
.filter(
|
|
BizOrder.is_del == 0,
|
|
BizOrder.create_at >= today,
|
|
BizOrder.create_at < day_end,
|
|
SysRegion.is_del == 0,
|
|
*region_active_clause,
|
|
)
|
|
)
|
|
if region_id:
|
|
amt_q = amt_q.filter(BizOrder.region_id == region_id)
|
|
amt_rows = amt_q.group_by(BizOrder.client_id).all()
|
|
|
|
qty_q = (
|
|
db.query(
|
|
BizOrder.client_id,
|
|
func.coalesce(func.sum(BizOrderItem.quantity), 0).label("qty"),
|
|
)
|
|
.select_from(BizOrderItem)
|
|
.join(
|
|
BizOrder,
|
|
and_(
|
|
BizOrder.id == BizOrderItem.order_id,
|
|
BizOrder.is_del == 0,
|
|
BizOrder.create_at >= today,
|
|
BizOrder.create_at < day_end,
|
|
),
|
|
)
|
|
.join(SysRegion, BizOrder.region_id == SysRegion.id)
|
|
.filter(BizOrderItem.is_del == 0)
|
|
.filter(SysRegion.is_del == 0, *region_active_clause)
|
|
)
|
|
if region_id:
|
|
qty_q = qty_q.filter(BizOrder.region_id == region_id)
|
|
qty_rows = qty_q.group_by(BizOrder.client_id).all()
|
|
qty_map: Dict[str, float] = {
|
|
r.client_id: float(r.qty or 0) for r in qty_rows if r.client_id
|
|
}
|
|
|
|
merged: List[Dict[str, Any]] = []
|
|
for r in amt_rows:
|
|
cid = r.client_id
|
|
if not cid:
|
|
continue
|
|
merged.append({
|
|
"clientId": cid,
|
|
"orderAmount": round(float(r.order_amount or 0), 2),
|
|
"productQuantity": round(float(qty_map.get(cid, 0) or 0), 4),
|
|
})
|
|
|
|
merged.sort(key=lambda x: -x["orderAmount"])
|
|
merged = merged[: max(1, min(int(limit or 30), 100))]
|
|
|
|
client_ids = [m["clientId"] for m in merged]
|
|
names: Dict[str, str] = {}
|
|
if client_ids:
|
|
for u in (
|
|
db.query(SysUser)
|
|
.filter(and_(SysUser.is_del == 0, SysUser.id.in_(client_ids)))
|
|
.all()
|
|
):
|
|
names[u.id] = (u.real_name or u.phone or u.username or u.id) or u.id
|
|
|
|
out: List[Dict[str, Any]] = []
|
|
for m in merged:
|
|
cid = m["clientId"]
|
|
nm = names.get(cid, cid)
|
|
out.append({
|
|
"clientId": cid,
|
|
"clientName": nm,
|
|
"orderAmount": m["orderAmount"],
|
|
"productQuantity": m["productQuantity"],
|
|
})
|
|
return out
|
|
|
|
@staticmethod
|
|
def get_financial_statistics(
|
|
db: Session,
|
|
region_id: str = None,
|
|
start_date: str = None,
|
|
end_date: str = None
|
|
) -> Dict[str, Any]:
|
|
"""获取财务统计数据"""
|
|
order_query = db.query(BizOrder).filter(BizOrder.is_del == 0)
|
|
settlement_query = db.query(BizSettlement).filter(BizSettlement.is_del == 0)
|
|
refund_query = db.query(BizRefund).filter(BizRefund.is_del == 0)
|
|
|
|
if region_id:
|
|
order_query = order_query.filter(BizOrder.region_id == region_id)
|
|
settlement_query = settlement_query.filter(BizSettlement.region_id == region_id)
|
|
|
|
if start_date:
|
|
try:
|
|
start_dt = datetime.strptime(start_date, "%Y-%m-%d")
|
|
order_query = order_query.filter(BizOrder.create_at >= start_dt)
|
|
settlement_query = settlement_query.filter(BizSettlement.create_at >= start_dt)
|
|
refund_query = refund_query.filter(BizRefund.create_at >= start_dt)
|
|
except:
|
|
pass
|
|
|
|
if end_date:
|
|
try:
|
|
end_dt = datetime.strptime(end_date, "%Y-%m-%d")
|
|
end_dt = end_dt.replace(hour=23, minute=59, second=59)
|
|
order_query = order_query.filter(BizOrder.create_at <= end_dt)
|
|
settlement_query = settlement_query.filter(BizSettlement.create_at <= end_dt)
|
|
refund_query = refund_query.filter(BizRefund.create_at <= end_dt)
|
|
except:
|
|
pass
|
|
|
|
orders = order_query.all()
|
|
settlements = settlement_query.filter(BizSettlement.status == "confirmed").all()
|
|
refunds = refund_query.filter(BizRefund.status == "completed").all()
|
|
|
|
total_revenue = sum(float(o.total_amount or 0) for o in orders)
|
|
total_received = sum(float(s.amount or 0) for s in settlements)
|
|
total_refund = sum(float(r.refund_amount or 0) for r in refunds)
|
|
net_revenue = total_received - total_refund
|
|
|
|
return {
|
|
"totalRevenue": round(total_revenue, 2),
|
|
"totalReceived": round(total_received, 2),
|
|
"totalRefund": round(total_refund, 2),
|
|
"netRevenue": round(net_revenue, 2),
|
|
"orderCount": len(orders),
|
|
"settlementCount": len(settlements),
|
|
"refundCount": len(refunds)
|
|
}
|
|
|
|
@staticmethod
|
|
def get_financial_trend(
|
|
db: Session,
|
|
region_id: str = None,
|
|
days: int = 7
|
|
) -> List[Dict[str, Any]]:
|
|
"""获取财务趋势数据(按日统计)"""
|
|
result = []
|
|
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
for i in range(days - 1, -1, -1):
|
|
day_start = today - timedelta(days=i)
|
|
day_end = day_start + timedelta(days=1)
|
|
|
|
settlement_query = db.query(BizSettlement).filter(
|
|
and_(
|
|
BizSettlement.is_del == 0,
|
|
BizSettlement.status == "confirmed",
|
|
BizSettlement.create_at >= day_start,
|
|
BizSettlement.create_at < day_end
|
|
)
|
|
)
|
|
|
|
refund_query = db.query(BizRefund).filter(
|
|
and_(
|
|
BizRefund.is_del == 0,
|
|
BizRefund.status == "completed",
|
|
BizRefund.create_at >= day_start,
|
|
BizRefund.create_at < day_end
|
|
)
|
|
)
|
|
|
|
if region_id:
|
|
settlement_query = settlement_query.filter(BizSettlement.region_id == region_id)
|
|
|
|
settlements = settlement_query.all()
|
|
refunds = refund_query.all()
|
|
|
|
received = sum(float(s.amount or 0) for s in settlements)
|
|
refund = sum(float(r.refund_amount or 0) for r in refunds)
|
|
|
|
result.append({
|
|
"date": day_start.strftime("%Y-%m-%d"),
|
|
"received": round(received, 2),
|
|
"refund": round(refund, 2),
|
|
"net": round(received - refund, 2)
|
|
})
|
|
|
|
return result
|
|
|
|
@staticmethod
|
|
def get_delivery_statistics(
|
|
db: Session,
|
|
region_id: str = None,
|
|
start_date: str = None,
|
|
end_date: str = None
|
|
) -> Dict[str, Any]:
|
|
"""获取配送统计数据"""
|
|
query = db.query(BizDeliveryTask).filter(BizDeliveryTask.is_del == 0)
|
|
|
|
if region_id:
|
|
query = query.filter(BizDeliveryTask.region_id == region_id)
|
|
|
|
if start_date:
|
|
try:
|
|
start_dt = datetime.strptime(start_date, "%Y-%m-%d")
|
|
query = query.filter(BizDeliveryTask.create_at >= start_dt)
|
|
except:
|
|
pass
|
|
|
|
if end_date:
|
|
try:
|
|
end_dt = datetime.strptime(end_date, "%Y-%m-%d")
|
|
end_dt = end_dt.replace(hour=23, minute=59, second=59)
|
|
query = query.filter(BizDeliveryTask.create_at <= end_dt)
|
|
except:
|
|
pass
|
|
|
|
tasks = query.all()
|
|
|
|
pending = sum(1 for t in tasks if t.status == "pending")
|
|
picked_up = sum(1 for t in tasks if t.status == "picked_up")
|
|
delivering = sum(1 for t in tasks if t.status == "delivering")
|
|
completed = sum(1 for t in tasks if t.status == "completed")
|
|
cancelled = sum(1 for t in tasks if t.status == "cancelled")
|
|
|
|
return {
|
|
"totalTasks": len(tasks),
|
|
"pending": pending,
|
|
"pickedUp": picked_up,
|
|
"delivering": delivering,
|
|
"completed": completed,
|
|
"cancelled": cancelled
|
|
}
|
|
|
|
@staticmethod
|
|
def get_product_statistics(
|
|
db: Session,
|
|
region_id: str = None,
|
|
start_date: str = None,
|
|
end_date: str = None,
|
|
limit: int = 10
|
|
) -> List[Dict[str, Any]]:
|
|
"""获取商品销售排行"""
|
|
query = db.query(
|
|
BizOrderItem.product_id,
|
|
func.sum(BizOrderItem.quantity).label("total_quantity"),
|
|
func.sum(BizOrderItem.amount).label("total_amount")
|
|
).join(
|
|
BizOrder,
|
|
and_(
|
|
BizOrder.id == BizOrderItem.order_id,
|
|
BizOrder.is_del == 0,
|
|
BizOrder.status.in_(["completed", "delivering", "pending_confirm"])
|
|
)
|
|
).filter(
|
|
BizOrderItem.is_del == 0
|
|
)
|
|
|
|
if region_id:
|
|
query = query.filter(BizOrder.region_id == region_id)
|
|
|
|
if start_date:
|
|
try:
|
|
start_dt = datetime.strptime(start_date, "%Y-%m-%d")
|
|
query = query.filter(BizOrder.create_at >= start_dt)
|
|
except:
|
|
pass
|
|
|
|
if end_date:
|
|
try:
|
|
end_dt = datetime.strptime(end_date, "%Y-%m-%d")
|
|
end_dt = end_dt.replace(hour=23, minute=59, second=59)
|
|
query = query.filter(BizOrder.create_at <= end_dt)
|
|
except:
|
|
pass
|
|
|
|
results = query.group_by(BizOrderItem.product_id).order_by(
|
|
desc("total_amount")
|
|
).limit(limit).all()
|
|
|
|
product_stats = []
|
|
for r in results:
|
|
product = db.query(BasProduct).filter(
|
|
and_(BasProduct.id == r.product_id, BasProduct.is_del == 0)
|
|
).first()
|
|
|
|
product_stats.append({
|
|
"productId": r.product_id,
|
|
"productName": product.name if product else None,
|
|
"totalQuantity": float(r.total_quantity or 0),
|
|
"totalAmount": float(r.total_amount or 0)
|
|
})
|
|
|
|
return product_stats
|
|
|
|
@staticmethod
|
|
def get_customer_statistics(
|
|
db: Session,
|
|
region_id: str = None,
|
|
start_date: str = None,
|
|
end_date: str = None,
|
|
limit: int = 10
|
|
) -> List[Dict[str, Any]]:
|
|
"""获取客户订单排行"""
|
|
query = db.query(
|
|
BizOrder.client_id,
|
|
func.count(BizOrder.id).label("order_count"),
|
|
func.sum(BizOrder.total_amount).label("total_amount")
|
|
).filter(
|
|
and_(
|
|
BizOrder.is_del == 0,
|
|
BizOrder.status.in_(["completed", "delivering", "pending_confirm", "pending_payment"])
|
|
)
|
|
)
|
|
|
|
if region_id:
|
|
query = query.filter(BizOrder.region_id == region_id)
|
|
|
|
if start_date:
|
|
try:
|
|
start_dt = datetime.strptime(start_date, "%Y-%m-%d")
|
|
query = query.filter(BizOrder.create_at >= start_dt)
|
|
except:
|
|
pass
|
|
|
|
if end_date:
|
|
try:
|
|
end_dt = datetime.strptime(end_date, "%Y-%m-%d")
|
|
end_dt = end_dt.replace(hour=23, minute=59, second=59)
|
|
query = query.filter(BizOrder.create_at <= end_dt)
|
|
except:
|
|
pass
|
|
|
|
results = query.group_by(BizOrder.client_id).order_by(
|
|
desc("total_amount")
|
|
).limit(limit).all()
|
|
|
|
customer_stats = []
|
|
for r in results:
|
|
customer = db.query(SysUser).filter(
|
|
and_(SysUser.id == r.client_id, SysUser.is_del == 0)
|
|
).first()
|
|
|
|
customer_stats.append({
|
|
"customerId": r.client_id,
|
|
"customerName": customer.real_name if customer else None,
|
|
"customerPhone": customer.phone if customer else None,
|
|
"orderCount": r.order_count,
|
|
"totalAmount": float(r.total_amount or 0)
|
|
})
|
|
|
|
return customer_stats
|
|
|
|
@staticmethod
|
|
def _dashboard_order_region_clause(region_id: str = None):
|
|
return [BizOrder.region_id == region_id] if region_id else []
|
|
|
|
@staticmethod
|
|
def get_dashboard_summary(
|
|
db: Session,
|
|
region_id: str = None,
|
|
include_inactive_regions: bool = False,
|
|
) -> Dict[str, Any]:
|
|
"""获取仪表盘汇总数据(运营工作台;含待办摘要、最新订单、预警数等,字段驼峰)"""
|
|
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
|
tomorrow_start = today + timedelta(days=1)
|
|
month_start = today.replace(day=1)
|
|
yesterday_start = today - timedelta(days=1)
|
|
region_clause = StatisticsCRUD._dashboard_order_region_clause(region_id)
|
|
region_active_clause = [] if include_inactive_regions else [SysRegion.is_active == True]
|
|
|
|
order_query = (
|
|
db.query(BizOrder)
|
|
.join(SysRegion, BizOrder.region_id == SysRegion.id)
|
|
.filter(
|
|
and_(
|
|
BizOrder.is_del == 0,
|
|
BizOrder.create_at >= month_start,
|
|
SysRegion.is_del == 0,
|
|
*region_active_clause,
|
|
*region_clause,
|
|
)
|
|
)
|
|
)
|
|
month_orders = order_query.all()
|
|
today_orders = [
|
|
o for o in month_orders if o.create_at and o.create_at >= today
|
|
]
|
|
prev_day_orders = [
|
|
o
|
|
for o in month_orders
|
|
if o.create_at and yesterday_start <= o.create_at < today
|
|
]
|
|
|
|
pending_delivery_q = (
|
|
db.query(BizOrder)
|
|
.join(SysRegion, BizOrder.region_id == SysRegion.id)
|
|
.filter(
|
|
and_(
|
|
BizOrder.is_del == 0,
|
|
BizOrder.delivery_status == "pending_dispatch",
|
|
SysRegion.is_del == 0,
|
|
*region_active_clause,
|
|
*region_clause,
|
|
)
|
|
)
|
|
)
|
|
pending_delivery_count = pending_delivery_q.count()
|
|
|
|
def _delta_hint(cur_n: int, prev_n: int, unit: str) -> str:
|
|
d = cur_n - prev_n
|
|
if d > 0:
|
|
return f"较昨日 +{d}{unit}"
|
|
if d < 0:
|
|
return f"较昨日 {d}{unit}"
|
|
return "与昨日持平"
|
|
|
|
def _amount_delta_hint(cur_amt: float, prev_amt: float) -> str:
|
|
d = round(cur_amt - prev_amt, 2)
|
|
if d > 0.0001:
|
|
return f"较昨日 +¥{d:,.2f}"
|
|
if d < -0.0001:
|
|
return f"较昨日 ¥{d:,.2f}"
|
|
return "与昨日持平"
|
|
|
|
today_order_count = len(today_orders)
|
|
today_order_amount = round(sum(float(o.total_amount or 0) for o in today_orders), 2)
|
|
prev_count = len(prev_day_orders)
|
|
prev_amount = round(sum(float(o.total_amount or 0) for o in prev_day_orders), 2)
|
|
|
|
# 未解决的配送时效预警(可按区域通过任务表关联)
|
|
warn_q = (
|
|
db.query(BizDeliveryWarning)
|
|
.join(
|
|
BizDeliveryTask,
|
|
and_(
|
|
BizDeliveryTask.id == BizDeliveryWarning.task_id,
|
|
BizDeliveryTask.is_del == 0,
|
|
),
|
|
)
|
|
.join(SysRegion, BizDeliveryTask.region_id == SysRegion.id)
|
|
.filter(
|
|
and_(
|
|
BizDeliveryWarning.is_del == 0,
|
|
BizDeliveryWarning.is_resolved == 0,
|
|
SysRegion.is_del == 0,
|
|
*region_active_clause,
|
|
)
|
|
)
|
|
)
|
|
if region_id:
|
|
warn_q = warn_q.filter(BizDeliveryTask.region_id == region_id)
|
|
delivery_warning_count = warn_q.count()
|
|
|
|
# 今日新提交报价:状态为已提交,且提交时间(无则用创建时间)落在当日 0 点~次日 0 点
|
|
quot_submitted_at = func.coalesce(BizQuotation.submit_time, BizQuotation.create_at)
|
|
sub_quot_q = (
|
|
db.query(BizQuotation)
|
|
.join(SysRegion, BizQuotation.region_id == SysRegion.id)
|
|
.filter(
|
|
and_(
|
|
BizQuotation.is_del == 0,
|
|
BizQuotation.status == "submitted",
|
|
quot_submitted_at >= today,
|
|
quot_submitted_at < tomorrow_start,
|
|
SysRegion.is_del == 0,
|
|
*region_active_clause,
|
|
)
|
|
)
|
|
)
|
|
if region_id:
|
|
sub_quot_q = sub_quot_q.filter(BizQuotation.region_id == region_id)
|
|
pending_quotation_count = sub_quot_q.count()
|
|
|
|
sub_quot_preview = (
|
|
db.query(BizQuotation, BizInquiry)
|
|
.join(BizInquiry, BizInquiry.id == BizQuotation.inquiry_id)
|
|
.join(SysRegion, BizQuotation.region_id == SysRegion.id)
|
|
.filter(
|
|
BizQuotation.is_del == 0,
|
|
BizQuotation.status == "submitted",
|
|
quot_submitted_at >= today,
|
|
quot_submitted_at < tomorrow_start,
|
|
BizInquiry.is_del == 0,
|
|
SysRegion.is_del == 0,
|
|
*region_active_clause,
|
|
*([BizQuotation.region_id == region_id] if region_id else []),
|
|
)
|
|
.order_by(desc(BizQuotation.submit_time), desc(BizQuotation.create_at))
|
|
.limit(2)
|
|
.all()
|
|
)
|
|
quotation_lines = []
|
|
for q, inq in sub_quot_preview:
|
|
title = (inq.title or "询价单")[:48]
|
|
quotation_lines.append(title)
|
|
|
|
pd_preview = (
|
|
db.query(BizOrder)
|
|
.join(SysRegion, BizOrder.region_id == SysRegion.id)
|
|
.filter(
|
|
and_(
|
|
BizOrder.is_del == 0,
|
|
BizOrder.delivery_status == "pending_dispatch",
|
|
SysRegion.is_del == 0,
|
|
*region_active_clause,
|
|
*region_clause,
|
|
)
|
|
)
|
|
.order_by(desc(BizOrder.create_at))
|
|
.limit(2)
|
|
.all()
|
|
)
|
|
delivery_lines = [o.order_no for o in pd_preview]
|
|
|
|
cancel_preview = (
|
|
db.query(BizOrder)
|
|
.join(SysRegion, BizOrder.region_id == SysRegion.id)
|
|
.filter(
|
|
and_(
|
|
BizOrder.is_del == 0,
|
|
BizOrder.cancel_status == "pending_review",
|
|
SysRegion.is_del == 0,
|
|
*region_active_clause,
|
|
*region_clause,
|
|
)
|
|
)
|
|
.order_by(desc(BizOrder.update_at))
|
|
.limit(2)
|
|
.all()
|
|
)
|
|
cancel_client_ids = list({o.client_id for o in cancel_preview if o.client_id})
|
|
cancel_clients = {}
|
|
if cancel_client_ids:
|
|
for u in (
|
|
db.query(SysUser)
|
|
.filter(and_(SysUser.is_del == 0, SysUser.id.in_(cancel_client_ids)))
|
|
.all()
|
|
):
|
|
cancel_clients[u.id] = u.real_name or u.phone or u.id
|
|
cancel_lines = []
|
|
for o in cancel_preview:
|
|
nm = cancel_clients.get(o.client_id, "")
|
|
cancel_lines.append(f"{o.order_no}" + (f" · {nm[:16]}" if nm else ""))
|
|
|
|
settle_preview = (
|
|
db.query(BizOrder)
|
|
.join(SysRegion, BizOrder.region_id == SysRegion.id)
|
|
.filter(
|
|
and_(
|
|
BizOrder.is_del == 0,
|
|
BizOrder.client_settlement_status == "pending",
|
|
BizOrder.pay_status == "paid",
|
|
BizOrder.order_status.in_(["pending_confirm", "completed"]),
|
|
SysRegion.is_del == 0,
|
|
*region_active_clause,
|
|
*region_clause,
|
|
)
|
|
)
|
|
.order_by(desc(BizOrder.update_at))
|
|
.limit(2)
|
|
.all()
|
|
)
|
|
settle_client_ids = list({o.client_id for o in settle_preview if o.client_id})
|
|
settle_clients = {}
|
|
if settle_client_ids:
|
|
for u in (
|
|
db.query(SysUser)
|
|
.filter(and_(SysUser.is_del == 0, SysUser.id.in_(settle_client_ids)))
|
|
.all()
|
|
):
|
|
settle_clients[u.id] = u.real_name or u.phone or u.id
|
|
settle_lines = []
|
|
for o in settle_preview:
|
|
nm = settle_clients.get(o.client_id, "")
|
|
settle_lines.append(f"{o.order_no}" + (f" · {nm[:16]}" if nm else ""))
|
|
|
|
pending_settlement_count = (
|
|
db.query(BizOrder)
|
|
.join(SysRegion, BizOrder.region_id == SysRegion.id)
|
|
.filter(
|
|
and_(
|
|
BizOrder.is_del == 0,
|
|
BizOrder.client_settlement_status == "pending",
|
|
BizOrder.pay_status == "paid",
|
|
BizOrder.order_status.in_(["pending_confirm", "completed"]),
|
|
SysRegion.is_del == 0,
|
|
*region_active_clause,
|
|
*region_clause,
|
|
)
|
|
)
|
|
.count()
|
|
)
|
|
|
|
pending_cancel_count = (
|
|
db.query(BizOrder)
|
|
.join(SysRegion, BizOrder.region_id == SysRegion.id)
|
|
.filter(
|
|
and_(
|
|
BizOrder.is_del == 0,
|
|
BizOrder.cancel_status == "pending_review",
|
|
SysRegion.is_del == 0,
|
|
*region_active_clause,
|
|
*region_clause,
|
|
)
|
|
)
|
|
.count()
|
|
)
|
|
|
|
# 最新订单(工作台列表)
|
|
recent_rows = (
|
|
db.query(BizOrder)
|
|
.join(SysRegion, BizOrder.region_id == SysRegion.id)
|
|
.filter(and_(BizOrder.is_del == 0, SysRegion.is_del == 0, *region_active_clause, *region_clause))
|
|
.order_by(desc(BizOrder.create_at))
|
|
.limit(8)
|
|
.all()
|
|
)
|
|
rid_set = list({o.region_id for o in recent_rows if o.region_id})
|
|
cid_set = list({o.client_id for o in recent_rows if o.client_id})
|
|
region_name_map = {}
|
|
if rid_set:
|
|
for r in db.query(SysRegion).filter(
|
|
and_(SysRegion.is_del == 0, *region_active_clause, SysRegion.id.in_(rid_set))
|
|
).all():
|
|
region_name_map[r.id] = r.name
|
|
client_name_map = {}
|
|
if cid_set:
|
|
for u in db.query(SysUser).filter(and_(SysUser.is_del == 0, SysUser.id.in_(cid_set))).all():
|
|
client_name_map[u.id] = u.real_name or u.phone or "-"
|
|
|
|
recent_orders = []
|
|
for o in recent_rows:
|
|
order_status = getattr(o, "order_status", None) or "pending_payment"
|
|
recent_orders.append(
|
|
{
|
|
"id": o.id,
|
|
"orderNo": o.order_no,
|
|
"clientName": client_name_map.get(o.client_id, "-"),
|
|
"regionName": region_name_map.get(o.region_id, "-"),
|
|
"totalAmount": float(o.total_amount) if o.total_amount else 0,
|
|
"orderStatus": order_status,
|
|
"cancelStatus": getattr(o, "cancel_status", None) or "none",
|
|
"deliveryStatus": getattr(o, "delivery_status", None) or "pending_dispatch",
|
|
"createAt": str(o.create_at) if o.create_at else None,
|
|
}
|
|
)
|
|
|
|
todo_snapshots = [
|
|
{
|
|
"type": "quotation",
|
|
"title": "今日新提交报价",
|
|
"count": pending_quotation_count,
|
|
"items": quotation_lines,
|
|
},
|
|
{
|
|
"type": "delivery",
|
|
"title": "待分配配送",
|
|
"count": pending_delivery_count,
|
|
"items": delivery_lines,
|
|
},
|
|
{
|
|
"type": "cancel",
|
|
"title": "取消待审核",
|
|
"count": pending_cancel_count,
|
|
"items": cancel_lines,
|
|
},
|
|
{
|
|
"type": "settlement",
|
|
"title": "待客户结算",
|
|
"count": pending_settlement_count,
|
|
"items": settle_lines,
|
|
},
|
|
]
|
|
|
|
# “客户总数/供应商总数”口径:统计用户表(sys_user)并按 region_id 区分
|
|
# - region_id 为空:不过滤全局
|
|
# - region_id 传入:按 SysUser.region_id 过滤
|
|
customer_count = (
|
|
db.query(SysUser)
|
|
.join(SysRegion, SysUser.region_id == SysRegion.id)
|
|
.filter(
|
|
and_(
|
|
SysUser.is_del == 0,
|
|
SysUser.role == "client",
|
|
SysRegion.is_del == 0,
|
|
*region_active_clause,
|
|
*([SysUser.region_id == region_id] if region_id else []),
|
|
)
|
|
)
|
|
.count()
|
|
)
|
|
|
|
supplier_count = (
|
|
db.query(SysUser)
|
|
.join(SysRegion, SysUser.region_id == SysRegion.id)
|
|
.filter(
|
|
and_(
|
|
SysUser.is_del == 0,
|
|
SysUser.role == "supplier",
|
|
SysRegion.is_del == 0,
|
|
*region_active_clause,
|
|
*([SysUser.region_id == region_id] if region_id else []),
|
|
)
|
|
)
|
|
.count()
|
|
)
|
|
|
|
return {
|
|
"todayOrderCount": today_order_count,
|
|
"todayOrderAmount": today_order_amount,
|
|
"monthOrderCount": len(month_orders),
|
|
"monthOrderAmount": round(sum(float(o.total_amount or 0) for o in month_orders), 2),
|
|
"pendingDeliveryCount": pending_delivery_count,
|
|
"completedOrderCount": sum(
|
|
1 for o in month_orders if (getattr(o, "order_status", None) or o.status) == "completed"
|
|
),
|
|
"activePriceListCount": (
|
|
db.query(BizPriceList)
|
|
.join(SysRegion, BizPriceList.region_id == SysRegion.id)
|
|
.filter(
|
|
and_(
|
|
BizPriceList.is_del == 0,
|
|
BizPriceList.status == "active",
|
|
SysRegion.is_del == 0,
|
|
*region_active_clause,
|
|
*([BizPriceList.region_id == region_id] if region_id else []),
|
|
)
|
|
)
|
|
.count()
|
|
),
|
|
"customerCount": customer_count,
|
|
"supplierCount": supplier_count,
|
|
"todayOrderCountHint": _delta_hint(today_order_count, prev_count, " 单"),
|
|
"todayOrderAmountHint": _amount_delta_hint(today_order_amount, prev_amount),
|
|
"deliveryWarningCount": delivery_warning_count,
|
|
"recentOrders": recent_orders,
|
|
"todoSnapshots": todo_snapshots,
|
|
}
|
|
|
|
|
|
statistics_crud = StatisticsCRUD()
|