列表按 owner_user_id/create_by 过滤,详情与 CUD 校验 scope;公海客户列表保持租户内全员可见。 Co-authored-by: Cursor <cursoragent@cursor.com>
174 lines
5.5 KiB
Python
174 lines
5.5 KiB
Python
"""CRM 统计分析 CRUD"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timedelta
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from sqlalchemy import and_, func
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.models.crm_models import CrmCustomer, CrmCustomerFollow
|
|
from app.crud.crm.data_scope_helpers import apply_crm_create_by_scope, apply_crm_owner_scope
|
|
|
|
|
|
def _customer_base_query(db: Session, tenant_no: str, user_info=None):
|
|
q = db.query(CrmCustomer).filter(
|
|
CrmCustomer.tenant_no == tenant_no,
|
|
CrmCustomer.del_flag == 0,
|
|
)
|
|
if user_info:
|
|
q = apply_crm_owner_scope(q, CrmCustomer, user_info, db, tenant_no)
|
|
return q
|
|
|
|
|
|
def get_overview_statistics(
|
|
db: Session, tenant_no: str, user_id: str = None, user_info=None
|
|
) -> Dict[str, Any]:
|
|
"""客户统计概览:客户总数、公海池数、本月新增、成交数"""
|
|
base = _customer_base_query(db, tenant_no, user_info=user_info)
|
|
total = base.count()
|
|
pool_count = base.filter(CrmCustomer.status == "pool").count()
|
|
|
|
now = datetime.now()
|
|
month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
|
month_new = base.filter(CrmCustomer.create_time >= month_start).count()
|
|
|
|
deal_count = base.filter(CrmCustomer.deal_status == "dealed").count()
|
|
|
|
return {
|
|
"total": total,
|
|
"poolCount": pool_count,
|
|
"monthNew": month_new,
|
|
"dealCount": deal_count,
|
|
}
|
|
|
|
|
|
def get_source_statistics(
|
|
db: Session, tenant_no: str, user_info=None
|
|
) -> List[Dict[str, Any]]:
|
|
"""按客户来源统计"""
|
|
rows = (
|
|
_customer_base_query(db, tenant_no, user_info=user_info)
|
|
.with_entities(CrmCustomer.source, func.count(CrmCustomer.id).label("count"))
|
|
.group_by(CrmCustomer.source)
|
|
.all()
|
|
)
|
|
return [{"source": r.source or "未知", "count": r.count} for r in rows]
|
|
|
|
|
|
def get_level_statistics(
|
|
db: Session, tenant_no: str, user_info=None
|
|
) -> List[Dict[str, Any]]:
|
|
"""按客户级别统计"""
|
|
rows = (
|
|
_customer_base_query(db, tenant_no, user_info=user_info)
|
|
.with_entities(CrmCustomer.level, func.count(CrmCustomer.id).label("count"))
|
|
.group_by(CrmCustomer.level)
|
|
.all()
|
|
)
|
|
return [{"level": r.level or "未知", "count": r.count} for r in rows]
|
|
|
|
|
|
def get_industry_statistics(
|
|
db: Session, tenant_no: str, user_info=None
|
|
) -> List[Dict[str, Any]]:
|
|
"""按行业统计"""
|
|
rows = (
|
|
_customer_base_query(db, tenant_no, user_info=user_info)
|
|
.with_entities(CrmCustomer.industry, func.count(CrmCustomer.id).label("count"))
|
|
.group_by(CrmCustomer.industry)
|
|
.all()
|
|
)
|
|
return [{"industry": r.industry or "未知", "count": r.count} for r in rows]
|
|
|
|
|
|
def get_trend_statistics(
|
|
db: Session,
|
|
tenant_no: str,
|
|
start_date: str = None,
|
|
end_date: str = None,
|
|
user_info=None,
|
|
) -> List[Dict[str, Any]]:
|
|
"""成交趋势(按月统计成交客户数)"""
|
|
query = _customer_base_query(db, tenant_no, user_info=user_info).with_entities(
|
|
func.date_format(CrmCustomer.deal_time, "%Y-%m").label("month"),
|
|
func.count(CrmCustomer.id).label("count"),
|
|
).filter(
|
|
CrmCustomer.deal_status == "dealed",
|
|
CrmCustomer.deal_time.isnot(None),
|
|
)
|
|
if start_date:
|
|
query = query.filter(CrmCustomer.deal_time >= start_date)
|
|
if end_date:
|
|
query = query.filter(CrmCustomer.deal_time <= end_date + " 23:59:59")
|
|
rows = query.group_by("month").order_by("month").all()
|
|
return [{"month": r.month, "count": r.count} for r in rows]
|
|
|
|
|
|
def get_owner_statistics(
|
|
db: Session, tenant_no: str, user_info=None
|
|
) -> List[Dict[str, Any]]:
|
|
"""按负责人统计客户数"""
|
|
rows = (
|
|
_customer_base_query(db, tenant_no, user_info=user_info)
|
|
.with_entities(
|
|
CrmCustomer.owner_user_name,
|
|
func.count(CrmCustomer.id).label("count"),
|
|
)
|
|
.group_by(CrmCustomer.owner_user_id, CrmCustomer.owner_user_name)
|
|
.all()
|
|
)
|
|
return [{"ownerName": r.owner_user_name or "无", "count": r.count} for r in rows]
|
|
|
|
|
|
def get_follow_statistics(
|
|
db: Session,
|
|
tenant_no: str,
|
|
start_date: str = None,
|
|
end_date: str = None,
|
|
user_info=None,
|
|
) -> Dict[str, Any]:
|
|
"""跟进统计"""
|
|
base = db.query(CrmCustomerFollow).filter(
|
|
CrmCustomerFollow.tenant_no == tenant_no,
|
|
CrmCustomerFollow.del_flag == 0,
|
|
)
|
|
if user_info:
|
|
base = apply_crm_create_by_scope(base, CrmCustomerFollow, user_info, db, tenant_no)
|
|
if start_date:
|
|
base = base.filter(CrmCustomerFollow.follow_time >= start_date)
|
|
if end_date:
|
|
base = base.filter(CrmCustomerFollow.follow_time <= end_date + " 23:59:59")
|
|
|
|
total = base.count()
|
|
|
|
# 按跟进方式分组
|
|
type_rows = (
|
|
base.with_entities(
|
|
CrmCustomerFollow.follow_type,
|
|
func.count(CrmCustomerFollow.id).label("count"),
|
|
)
|
|
.group_by(CrmCustomerFollow.follow_type)
|
|
.all()
|
|
)
|
|
by_type = [{"type": r.follow_type or "未知", "count": r.count} for r in type_rows]
|
|
|
|
# 按负责人分组
|
|
owner_rows = (
|
|
base.with_entities(
|
|
CrmCustomerFollow.owner_user_name,
|
|
func.count(CrmCustomerFollow.id).label("count"),
|
|
)
|
|
.group_by(CrmCustomerFollow.owner_user_id, CrmCustomerFollow.owner_user_name)
|
|
.all()
|
|
)
|
|
by_owner = [
|
|
{"ownerName": r.owner_user_name or "无", "count": r.count} for r in owner_rows
|
|
]
|
|
|
|
return {
|
|
"total": total,
|
|
"byType": by_type,
|
|
"byOwner": by_owner,
|
|
}
|