Files
shanxia2.0/backend/app/crud/crm/customer_follow.py
T
2026-08-21 16:50:47 +08:00

250 lines
7.9 KiB
Python

"""CRM 客户跟进记录 CRUD"""
from __future__ import annotations
import math
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple
from sqlalchemy import func, or_
from sqlalchemy.orm import Session
from app.core.delete_guard import assert_row_deletable_by_creator
from app.core.utils import generate_uuid
from app.crud.crm.customer import _resolve_user_display_name, get_customer, _ensure_customer_in_scope
from app.models.crm_models import CrmCustomerFollow
from app.crud.crm.data_scope_helpers import apply_crm_create_by_scope, ensure_crm_create_by_row_in_scope
def total_pages(total: int, page_size: int) -> int:
if page_size <= 0:
return 0
return math.ceil(total / page_size)
_FMT = "%Y-%m-%d %H:%M:%S"
def _enrich_follow_dict(db: Session, item: Dict[str, Any]) -> Dict[str, Any]:
if not item.get("ownerUserName"):
uid = item.get("ownerUserId") or item.get("createBy")
if uid:
item["ownerUserName"] = _resolve_user_display_name(db, uid)
item["createByName"] = item.get("ownerUserName") or ""
return item
def _row_to_dict(row: CrmCustomerFollow) -> Dict[str, Any]:
return {
"id": row.id,
"customerId": row.customer_id or "",
"customerName": row.customer_name or "",
"contactId": row.contact_id or "",
"contactName": row.contact_name or "",
"followType": row.follow_type or "",
"followContent": row.follow_content or "",
"followTime": row.follow_time.strftime(_FMT) if row.follow_time else "",
"nextTime": row.next_time.strftime(_FMT) if row.next_time else "",
"attachment": row.attachment or "",
"ownerUserId": row.owner_user_id or "",
"ownerUserName": row.owner_user_name or "",
"orderNum": row.order_num if row.order_num is not None else 0,
"remark": row.remark or "",
"createBy": row.create_by or "",
"createByName": row.owner_user_name or "",
"createTime": row.create_time.strftime(_FMT) if row.create_time else "",
"updateBy": row.update_by or "",
"updateTime": row.update_time.strftime(_FMT) if row.update_time else "",
}
def list_follows(
db: Session,
tenant_no: str,
*,
page: int = 1,
page_size: int = 10,
keyword: str = None,
customer_id: str = None,
follow_type: str = None,
owner_user_id: str = None,
follow_time_start: str = None,
follow_time_end: str = None,
user_info=None,
) -> Tuple[List[Dict[str, Any]], int]:
q = db.query(CrmCustomerFollow).filter(
CrmCustomerFollow.tenant_no == tenant_no,
CrmCustomerFollow.del_flag == 0,
)
kw = (keyword or "").strip()
if kw:
like = f"%{kw}%"
q = q.filter(
or_(
CrmCustomerFollow.customer_name.like(like),
CrmCustomerFollow.follow_content.like(like),
CrmCustomerFollow.contact_name.like(like),
)
)
if customer_id:
q = q.filter(CrmCustomerFollow.customer_id == customer_id)
if follow_type:
q = q.filter(CrmCustomerFollow.follow_type == follow_type)
if owner_user_id:
q = q.filter(CrmCustomerFollow.owner_user_id == owner_user_id)
if follow_time_start:
q = q.filter(CrmCustomerFollow.follow_time >= follow_time_start)
if follow_time_end:
q = q.filter(CrmCustomerFollow.follow_time <= follow_time_end)
if user_info:
q = apply_crm_create_by_scope(q, CrmCustomerFollow, user_info, db, tenant_no)
total = q.order_by(None).with_entities(func.count(CrmCustomerFollow.id)).scalar() or 0
page = max(1, page)
page_size = min(max(1, page_size), 500)
rows = (
q.order_by(CrmCustomerFollow.follow_time.desc(), CrmCustomerFollow.create_time.desc())
.offset((page - 1) * page_size)
.limit(page_size)
.all()
)
return [_enrich_follow_dict(db, _row_to_dict(r)) for r in rows], int(total)
def list_follows_by_customer(
db: Session,
tenant_no: str,
customer_id: str,
user_info=None,
) -> List[Dict[str, Any]]:
cust = get_customer(db, customer_id)
if not cust or cust.tenant_no != tenant_no:
return []
_ensure_customer_in_scope(db, cust, user_info, tenant_no)
rows = (
db.query(CrmCustomerFollow)
.filter(
CrmCustomerFollow.tenant_no == tenant_no,
CrmCustomerFollow.customer_id == customer_id,
CrmCustomerFollow.del_flag == 0,
)
.order_by(CrmCustomerFollow.follow_time.desc())
.all()
)
return [_enrich_follow_dict(db, _row_to_dict(r)) for r in rows]
def get_follow(db: Session, follow_id: str) -> Optional[CrmCustomerFollow]:
return (
db.query(CrmCustomerFollow)
.filter(
CrmCustomerFollow.id == follow_id,
CrmCustomerFollow.del_flag == 0,
)
.first()
)
def create_follow(
db: Session,
*,
tenant_no: str,
user_id: str = None,
user_name: str = None,
dept_id: str = "",
user_info=None,
**kwargs,
) -> CrmCustomerFollow:
customer_id = kwargs.get("customer_id")
customer_name = kwargs.get("customer_name")
if customer_id:
cust = get_customer(db, customer_id)
if not cust or cust.tenant_no != tenant_no:
raise ValueError("客户不存在")
_ensure_customer_in_scope(db, cust, user_info, tenant_no)
if not customer_name:
customer_name = cust.customer_name
follow_time = kwargs.get("follow_time") or datetime.now()
obj = CrmCustomerFollow(
id=generate_uuid(),
customer_id=customer_id,
customer_name=customer_name or "",
contact_id=kwargs.get("contact_id"),
contact_name=kwargs.get("contact_name"),
follow_type=kwargs.get("follow_type"),
follow_content=kwargs.get("follow_content"),
follow_time=follow_time,
next_time=kwargs.get("next_time"),
attachment=kwargs.get("attachment"),
owner_user_id=user_id,
owner_user_name=user_name or "",
tenant_no=tenant_no,
order_num=kwargs.get("order_num"),
remark=kwargs.get("remark"),
create_by=user_id,
create_dept_id=dept_id,
del_flag=0,
)
db.add(obj)
db.commit()
db.refresh(obj)
if customer_id:
from app.crud.crm.customer import update_follow_stats
update_follow_stats(db, customer_id, follow_time=follow_time, next_time=kwargs.get("next_time"))
return obj
def update_follow(
db: Session,
follow_id: str,
*,
user_id: str = None,
user_info=None,
tenant_no: str = None,
**kwargs,
) -> CrmCustomerFollow:
obj = get_follow(db, follow_id)
if not obj:
raise ValueError("跟进记录不存在")
if tenant_no and obj.tenant_no != tenant_no:
raise ValueError("跟进记录不存在")
ensure_crm_create_by_row_in_scope(db, obj, user_info, obj.tenant_no, message="无权访问该跟进记录")
_updatable = (
"contact_id", "contact_name", "follow_type", "follow_content",
"follow_time", "next_time", "attachment", "order_num", "remark",
)
for field in _updatable:
if field in kwargs and kwargs[field] is not None:
setattr(obj, field, kwargs[field])
obj.update_by = user_id
obj.update_time = datetime.now()
db.commit()
db.refresh(obj)
return obj
def soft_delete_follow(
db: Session, follow_id: str, user_id: str = None, user_info=None, tenant_no: str = None
) -> bool:
obj = get_follow(db, follow_id)
if not obj:
raise ValueError("跟进记录不存在")
if tenant_no and obj.tenant_no != tenant_no:
raise ValueError("跟进记录不存在")
ensure_crm_create_by_row_in_scope(db, obj, user_info, obj.tenant_no, message="无权访问该跟进记录")
assert_row_deletable_by_creator(obj, user_info, user_id=user_id)
obj.del_flag = 1
obj.update_by = user_id
obj.update_time = datetime.now()
db.commit()
return True