| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916 |
- """数据统计 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()
|