Files
shanxia2.0/backend/app/crud/crm/customer.py
T
2026-08-26 11:10:40 +08:00

894 lines
28 KiB
Python

"""CRM 客户 CRUD(客户主表 + 转移/成交/公海池操作)"""
from __future__ import annotations
import math
from datetime import datetime, timedelta, date
from typing import Any, Dict, List, Optional, Tuple
from sqlalchemy import and_, 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.models.crm_models import CrmCustomer
from app.models.models import SysUser
from app.crud.crm.data_scope_helpers import (
apply_crm_create_by_scope,
apply_crm_owner_scope,
ensure_crm_owner_row_in_scope,
)
from app.utils.pinyin_abbr import resolve_name_pinyin_abbr
def _ensure_customer_in_scope(db: Session, customer: CrmCustomer, user_info, tenant_no: str) -> None:
ensure_crm_owner_row_in_scope(db, customer, user_info, tenant_no, message="无权访问该客户")
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"
_DATE_FMT = "%Y-%m-%d"
def _parse_optional_date(value: Any) -> Optional[date]:
if value is None:
return None
if isinstance(value, date) and not isinstance(value, datetime):
return value
if isinstance(value, datetime):
return value.date()
text = str(value).strip()
if not text:
return None
for fmt in (_DATE_FMT, _FMT):
try:
return datetime.strptime(text, fmt).date()
except ValueError:
continue
raise ValueError(f"日期格式无效: {text}")
def _parse_optional_datetime(value: Any) -> Optional[datetime]:
if value is None:
return None
if isinstance(value, datetime):
return value
text = str(value).strip()
if not text:
return None
for fmt in (_FMT, _DATE_FMT):
try:
return datetime.strptime(text, fmt)
except ValueError:
continue
raise ValueError(f"日期时间格式无效: {text}")
def _resolve_user_display_name(db: Session, user_id: Optional[str]) -> str:
uid = (user_id or "").strip()
if not uid:
return ""
u = db.query(SysUser).filter(SysUser.id == uid, SysUser.del_flag == 0).first()
if not u:
return ""
return (u.nick_name or u.user_no or "").strip()
def _enrich_user_names(db: Session, rows: List[Dict[str, Any]]) -> None:
field_pairs = (
("ownerUserId", "ownerUserName"),
("createBy", "createByName"),
("updateBy", "updateByName"),
)
need_ids: set[str] = set()
for d in rows:
for id_field, name_field in field_pairs:
uid = str(d.get(id_field) or "").strip()
if uid and not (d.get(name_field) or "").strip():
need_ids.add(uid)
if not need_ids:
return
users = (
db.query(SysUser.id, SysUser.nick_name, SysUser.user_no)
.filter(SysUser.id.in_(need_ids), SysUser.del_flag == 0)
.all()
)
name_map = {u.id: (u.nick_name or u.user_no or "").strip() for u in users}
for d in rows:
for id_field, name_field in field_pairs:
uid = str(d.get(id_field) or "").strip()
if uid and not (d.get(name_field) or "").strip():
d[name_field] = name_map.get(uid, "")
def _row_to_dict(row: CrmCustomer) -> Dict[str, Any]:
return {
"id": row.id,
"customerNo": row.customer_no or "",
"customerName": row.customer_name or "",
"namePinyinAbbr": getattr(row, "name_pinyin_abbr", None) or "",
"shortName": row.short_name or "",
"customerType": row.customer_type or "",
"mobile": row.mobile or "",
"telephone": row.telephone or "",
"email": row.email or "",
"website": row.website or "",
"level": row.level or "",
"source": row.source or "",
"industry": row.industry or "",
"parkArea": row.park_area or "",
"paymentTerms": row.payment_terms or "",
"scale": row.scale or "",
"province": row.province or "",
"city": row.city or "",
"district": row.district or "",
"address": row.address or "",
"status": row.status or "",
"dealStatus": row.deal_status or "",
"ownerUserId": row.owner_user_id or "",
"ownerUserName": row.owner_user_name or "",
"lastTime": row.last_time.strftime(_FMT) if row.last_time else "",
"nextTime": row.next_time.strftime(_FMT) if row.next_time else "",
"followCount": row.follow_count if row.follow_count is not None else 0,
"dealTime": row.deal_time.strftime(_FMT) if row.deal_time else "",
"dealAmount": float(row.deal_amount) if row.deal_amount else 0.0,
"contactCount": row.contact_count if row.contact_count is not None else 0,
"opportunityCount": row.opportunity_count if row.opportunity_count is not None else 0,
"contractCount": row.contract_count if row.contract_count is not None else 0,
"intoPoolTime": row.into_pool_time.strftime(_FMT) if row.into_pool_time else "",
"intoPoolBy": row.into_pool_by or "",
"intoPoolReason": row.into_pool_reason or "",
"claimTime": row.claim_time.strftime(_FMT) if row.claim_time else "",
"assignTime": row.assign_time.strftime(_FMT) if row.assign_time else "",
"assignBy": row.assign_by or "",
"protectTime": row.protect_time.strftime(_FMT) if row.protect_time else "",
"businessLicenseImg": row.business_license_img or "",
"businessLicenseImages": row.business_license_img or "",
"businessPermitImages": row.business_permit_images or "",
"otherCertImages": row.other_cert_images or "",
"clueId": row.clue_id or "",
"orderNum": row.order_num if row.order_num is not None else 0,
"remark": row.remark or "",
"createBy": row.create_by 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_customer_options(
db: Session,
tenant_no: str,
*,
keyword: str = None,
customer_name: str = None,
limit: int = 50,
) -> List[Dict[str, Any]]:
"""客户下拉选项:租户内只读搜索,不做 owner 数据权限(供地磅/合同等主数据选用)。"""
q = db.query(CrmCustomer).filter(
CrmCustomer.tenant_no == tenant_no,
CrmCustomer.del_flag == 0,
)
kw = (keyword or "").strip()
if kw:
like = f"%{kw}%"
q = q.filter(
or_(
CrmCustomer.customer_name.like(like),
CrmCustomer.customer_no.like(like),
CrmCustomer.short_name.like(like),
func.lower(func.coalesce(CrmCustomer.name_pinyin_abbr, "")).like(
f"%{kw.lower()}%"
),
)
)
name = (customer_name or "").strip()
if name:
q = q.filter(
or_(
CrmCustomer.customer_name.like(f"%{name}%"),
func.lower(func.coalesce(CrmCustomer.name_pinyin_abbr, "")).like(
f"%{name.lower()}%"
),
)
)
limit = min(max(1, int(limit or 50)), 200)
rows = q.order_by(CrmCustomer.create_time.desc()).limit(limit).all()
return [
{
"id": r.id,
"customerName": r.customer_name,
"customerNo": r.customer_no,
"label": r.customer_name,
"value": r.id,
}
for r in rows
]
def list_customers(
db: Session,
tenant_no: str,
user_id: str = None,
*,
page: int = 1,
page_size: int = 10,
keyword: str = None,
view_mode: str = None,
customer_name: str = None,
customer_no: str = None,
status: str = None,
deal_status: str = None,
level: str = None,
source: str = None,
customer_type: str = None,
industry: str = None,
park_area: str = None,
owner_user_id: str = None,
create_time_start: str = None,
create_time_end: str = None,
user_info=None,
skip_data_scope: bool = False,
max_page_size: Optional[int] = None,
) -> Tuple[List[Dict[str, Any]], int]:
q = db.query(CrmCustomer).filter(
CrmCustomer.tenant_no == tenant_no,
CrmCustomer.del_flag == 0,
)
kw = (keyword or "").strip()
if kw:
like = f"%{kw}%"
q = q.filter(
or_(
CrmCustomer.customer_name.like(like),
CrmCustomer.customer_no.like(like),
CrmCustomer.mobile.like(like),
CrmCustomer.short_name.like(like),
func.lower(func.coalesce(CrmCustomer.name_pinyin_abbr, "")).like(
f"%{kw.lower()}%"
),
)
)
if view_mode == "mine" and user_id:
q = q.filter(CrmCustomer.owner_user_id == user_id)
q = q.filter(CrmCustomer.status == "normal")
elif view_mode == "toFollow":
q = q.filter(CrmCustomer.status == "normal")
q = q.filter(CrmCustomer.next_time.isnot(None))
q = q.filter(CrmCustomer.next_time <= datetime.now())
elif view_mode == "key":
q = q.filter(CrmCustomer.level == "key")
elif view_mode == "pool":
q = q.filter(CrmCustomer.status == "pool")
elif view_mode == "dealed":
q = q.filter(CrmCustomer.deal_status == "dealed")
elif view_mode == "weekNew":
week_ago = datetime.now() - timedelta(days=7)
q = q.filter(CrmCustomer.create_time >= week_ago)
elif view_mode == "longTimeNoFollow":
days_30_ago = datetime.now() - timedelta(days=30)
q = q.filter(
or_(
CrmCustomer.last_time.is_(None),
CrmCustomer.last_time <= days_30_ago,
)
)
if customer_name:
cn = customer_name.strip()
if cn:
q = q.filter(
or_(
CrmCustomer.customer_name.like(f"%{cn}%"),
func.lower(func.coalesce(CrmCustomer.name_pinyin_abbr, "")).like(
f"%{cn.lower()}%"
),
)
)
if customer_no:
q = q.filter(CrmCustomer.customer_no.like(f"%{customer_no}%"))
if status:
q = q.filter(CrmCustomer.status == status)
if deal_status:
q = q.filter(CrmCustomer.deal_status == deal_status)
if level:
q = q.filter(CrmCustomer.level == level)
if source:
q = q.filter(CrmCustomer.source == source)
if customer_type:
q = q.filter(CrmCustomer.customer_type == customer_type)
if industry:
q = q.filter(CrmCustomer.industry == industry)
if park_area:
q = q.filter(CrmCustomer.park_area == park_area)
if owner_user_id:
q = q.filter(CrmCustomer.owner_user_id == owner_user_id)
if create_time_start:
q = q.filter(CrmCustomer.create_time >= create_time_start)
if create_time_end:
q = q.filter(CrmCustomer.create_time <= create_time_end)
pool_scope_exempt = skip_data_scope or view_mode == "pool" or status == "pool"
if user_info and not pool_scope_exempt:
q = apply_crm_owner_scope(q, CrmCustomer, user_info, db, tenant_no)
total = q.order_by(None).with_entities(func.count(CrmCustomer.id)).scalar() or 0
page = max(1, page)
cap = max_page_size if max_page_size is not None else 500
page_size = min(max(1, page_size), cap)
order_by = CrmCustomer.create_time.desc()
if view_mode == "toFollow":
order_by = CrmCustomer.next_time.asc()
rows = (
q.order_by(order_by)
.offset((page - 1) * page_size)
.limit(page_size)
.all()
)
result = [_row_to_dict(r) for r in rows]
_enrich_user_names(db, result)
return result, int(total)
def list_pool_customers(
db: Session,
tenant_no: str,
*,
page: int = 1,
page_size: int = 10,
keyword: str = None,
level: str = None,
industry: str = None,
park_area: str = None,
user_info=None,
max_page_size: Optional[int] = None,
) -> Tuple[List[Dict[str, Any]], int]:
return list_customers(
db,
tenant_no,
page=page,
page_size=page_size,
keyword=keyword,
status="pool",
level=level,
industry=industry,
park_area=park_area,
user_info=user_info,
skip_data_scope=True,
max_page_size=max_page_size,
)
def get_customer(db: Session, customer_id: str) -> Optional[CrmCustomer]:
return (
db.query(CrmCustomer)
.filter(
CrmCustomer.id == customer_id,
CrmCustomer.del_flag == 0,
)
.first()
)
def get_customer_by_no(db: Session, customer_no: str, tenant_no: str) -> Optional[CrmCustomer]:
return (
db.query(CrmCustomer)
.filter(
CrmCustomer.customer_no == customer_no,
CrmCustomer.tenant_no == tenant_no,
CrmCustomer.del_flag == 0,
)
.first()
)
def generate_customer_no(db: Session, tenant_no: str) -> str:
today_str = datetime.now().strftime("%Y%m%d")
prefix = f"C{today_str}"
max_no = (
db.query(CrmCustomer.customer_no)
.filter(
CrmCustomer.customer_no.like(f"{prefix}%"),
CrmCustomer.tenant_no == tenant_no,
)
.order_by(CrmCustomer.customer_no.desc())
.first()
)
if max_no and max_no[0]:
seq_str = max_no[0][len(prefix):]
try:
seq = int(seq_str) + 1
except ValueError:
seq = 1
else:
seq = 1
return f"{prefix}{seq:04d}"
def customer_name_exists(
db: Session, tenant_no: str, name: str, exclude_id: str = None
) -> bool:
normalized = (name or "").strip()
if not normalized:
return False
q = db.query(CrmCustomer).filter(
CrmCustomer.customer_name == normalized,
CrmCustomer.tenant_no == tenant_no,
CrmCustomer.del_flag == 0,
)
if exclude_id:
q = q.filter(CrmCustomer.id != exclude_id)
return q.first() is not None
def check_duplicate_customer(
db: Session,
tenant_no: str,
mobile: str = None,
customer_name: str = None,
exclude_id: str = None,
) -> list:
conditions = [CrmCustomer.tenant_no == tenant_no, CrmCustomer.del_flag == 0]
if exclude_id:
conditions.append(CrmCustomer.id != exclude_id)
or_clauses = []
if mobile:
or_clauses.append(CrmCustomer.mobile == mobile)
if customer_name:
normalized_name = customer_name.strip()
if normalized_name:
or_clauses.append(CrmCustomer.customer_name == normalized_name)
if not or_clauses:
return []
rows = db.query(CrmCustomer).filter(*conditions).filter(or_(*or_clauses)).all()
return [_row_to_dict(r) for r in rows]
def create_customer(
db: Session,
*,
tenant_no: str,
user_id: str = None,
user_name: str = None,
dept_id: str = "",
**kwargs,
) -> CrmCustomer:
customer_name = (kwargs.get("customer_name") or "").strip()
if not customer_name:
raise ValueError("客户名称不能为空")
if customer_name_exists(db, tenant_no, customer_name):
raise ValueError("客户名称已存在")
kwargs["customer_name"] = customer_name
customer_no = kwargs.get("customer_no")
if not customer_no:
customer_no = generate_customer_no(db, tenant_no)
owner_id = (kwargs.pop("owner_user_id", None) or user_id or "").strip() or None
owner_name = _resolve_user_display_name(db, owner_id) or (user_name or "")
# 拼音首字母:新增始终按提交值解析(空则由客户名称生成);兼容 snake / camel 键
submitted = kwargs.get("name_pinyin_abbr")
if submitted is None and "namePinyinAbbr" in kwargs:
submitted = kwargs.get("namePinyinAbbr")
name_pinyin_abbr = resolve_name_pinyin_abbr(
name=kwargs.get("customer_name"),
submitted=submitted,
submitted_provided=True,
)
obj = CrmCustomer(
id=generate_uuid(),
customer_no=customer_no,
customer_name=kwargs.get("customer_name"),
name_pinyin_abbr=name_pinyin_abbr,
short_name=kwargs.get("short_name"),
customer_type=kwargs.get("customer_type", "company"),
mobile=kwargs.get("mobile"),
telephone=kwargs.get("telephone"),
email=kwargs.get("email"),
website=kwargs.get("website"),
level=kwargs.get("level", "normal"),
source=kwargs.get("source"),
industry=kwargs.get("industry"),
park_area=kwargs.get("park_area"),
payment_terms=kwargs.get("payment_terms"),
scale=kwargs.get("scale"),
province=kwargs.get("province"),
city=kwargs.get("city"),
district=kwargs.get("district"),
address=kwargs.get("address"),
status="normal",
deal_status="not_deal",
owner_user_id=owner_id,
owner_user_name=owner_name,
follow_count=0,
deal_amount=0,
contact_count=0,
opportunity_count=0,
contract_count=0,
business_license_img=kwargs.get("business_license_img"),
business_permit_images=kwargs.get("business_permit_images"),
other_cert_images=kwargs.get("other_cert_images"),
clue_id=kwargs.get("clue_id"),
next_time=_parse_optional_datetime(kwargs.get("next_time")),
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)
return obj
def update_customer(
db: Session,
customer_id: str,
*,
user_id: str = None,
user_info=None,
tenant_no: str = None,
**kwargs,
) -> CrmCustomer:
obj = get_customer(db, customer_id)
if not obj:
raise ValueError("客户不存在")
if tenant_no and obj.tenant_no != tenant_no:
raise ValueError("客户不存在")
_ensure_customer_in_scope(db, obj, user_info, obj.tenant_no)
if "customer_name" in kwargs and kwargs.get("customer_name") is not None:
normalized_name = (kwargs.get("customer_name") or "").strip()
if not normalized_name:
raise ValueError("客户名称不能为空")
if normalized_name != (obj.customer_name or "").strip():
if customer_name_exists(db, obj.tenant_no, normalized_name, exclude_id=customer_id):
raise ValueError("客户名称已存在")
kwargs["customer_name"] = normalized_name
# name_pinyin_abbr 单独处理,避免双重赋值
_updatable = (
"customer_name", "short_name", "customer_type", "mobile", "telephone",
"email", "website", "level", "source", "industry", "park_area",
"payment_terms", "scale", "province", "city", "district", "address",
"business_license_img",
"business_permit_images", "other_cert_images",
"clue_id", "order_num", "remark",
"next_time",
)
_datetime_fields = {"next_time"}
for field in _updatable:
if field not in kwargs:
continue
val = kwargs[field]
if field in _datetime_fields:
setattr(obj, field, _parse_optional_datetime(val))
elif val is not None:
setattr(obj, field, val)
# 拼音首字母:提交了则用提交值(空则重生);未提交则保留已有,已有空则按名称生成
name_keys = ("name_pinyin_abbr", "namePinyinAbbr")
submitted_provided = any(k in kwargs for k in name_keys)
submitted = kwargs.get("name_pinyin_abbr")
if submitted is None:
submitted = kwargs.get("namePinyinAbbr")
if "customer_name" in kwargs and kwargs.get("customer_name") is not None:
new_name = kwargs.get("customer_name")
else:
new_name = obj.customer_name
obj.name_pinyin_abbr = resolve_name_pinyin_abbr(
name=new_name,
submitted=submitted,
existing=obj.name_pinyin_abbr,
submitted_provided=submitted_provided,
)
if "owner_user_id" in kwargs:
owner_id = (kwargs.pop("owner_user_id") or "").strip() or None
obj.owner_user_id = owner_id
obj.owner_user_name = _resolve_user_display_name(db, owner_id) if owner_id else None
obj.update_by = user_id
obj.update_time = datetime.now()
db.commit()
db.refresh(obj)
return obj
def soft_delete_customer(
db: Session, customer_id: str, user_id: str = None, user_info=None, tenant_no: str = None
) -> bool:
obj = get_customer(db, customer_id)
if not obj:
raise ValueError("客户不存在")
if tenant_no and obj.tenant_no != tenant_no:
raise ValueError("客户不存在")
_ensure_customer_in_scope(db, obj, user_info, obj.tenant_no)
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
def transfer_customer(
db: Session,
customer_id: str,
new_owner_id: str,
new_owner_name: str,
user_id: str = None,
user_info=None,
tenant_no: str = None,
) -> CrmCustomer:
obj = get_customer(db, customer_id)
if not obj:
raise ValueError("客户不存在")
if tenant_no and obj.tenant_no != tenant_no:
raise ValueError("客户不存在")
_ensure_customer_in_scope(db, obj, user_info, obj.tenant_no)
old_owner_id = obj.owner_user_id or ""
old_owner_name = obj.owner_user_name or ""
obj.owner_user_id = new_owner_id
obj.owner_user_name = new_owner_name
obj.update_by = user_id
obj.update_time = datetime.now()
db.commit()
db.refresh(obj)
from app.crud.crm.customer_pool import create_pool_record
create_pool_record(
db,
tenant_no=obj.tenant_no,
user_id=user_id,
customer_id=obj.id,
customer_name=obj.customer_name,
operation_type="assign",
from_user_id=old_owner_id,
from_user_name=old_owner_name,
to_user_id=new_owner_id,
to_user_name=new_owner_name,
)
return obj
def deal_customer(
db: Session,
customer_id: str,
deal_amount: float = 0,
deal_time: str = None,
user_id: str = None,
user_info=None,
tenant_no: str = None,
) -> CrmCustomer:
obj = get_customer(db, customer_id)
if not obj:
raise ValueError("客户不存在")
if tenant_no and obj.tenant_no != tenant_no:
raise ValueError("客户不存在")
_ensure_customer_in_scope(db, obj, user_info, obj.tenant_no)
obj.deal_status = "dealed"
if deal_amount:
obj.deal_amount = deal_amount
parsed_deal_time = _parse_optional_datetime(deal_time)
obj.deal_time = parsed_deal_time or datetime.now()
obj.update_by = user_id
obj.update_time = datetime.now()
db.commit()
db.refresh(obj)
return obj
def into_pool(
db: Session,
customer_id: str,
reason: str = None,
user_id: str = None,
user_info=None,
tenant_no: str = None,
) -> CrmCustomer:
obj = get_customer(db, customer_id)
if not obj:
raise ValueError("客户不存在")
if tenant_no and obj.tenant_no != tenant_no:
raise ValueError("客户不存在")
_ensure_customer_in_scope(db, obj, user_info, obj.tenant_no)
old_owner_id = obj.owner_user_id or ""
old_owner_name = obj.owner_user_name or ""
obj.status = "pool"
obj.into_pool_time = datetime.now()
obj.into_pool_by = user_id
obj.into_pool_reason = reason
obj.owner_user_id = None
obj.update_by = user_id
obj.update_time = datetime.now()
db.commit()
db.refresh(obj)
from app.crud.crm.customer_pool import create_pool_record
create_pool_record(
db,
tenant_no=obj.tenant_no,
user_id=user_id,
customer_id=obj.id,
customer_name=obj.customer_name,
operation_type="into",
from_user_id=old_owner_id,
from_user_name=old_owner_name,
reason=reason,
)
return obj
def claim_customer(
db: Session,
customer_id: str,
user_id: str,
user_name: str,
) -> CrmCustomer:
obj = get_customer(db, customer_id)
if not obj:
raise ValueError("客户不存在")
if obj.status != "pool":
raise ValueError("该客户不在公海池中")
obj.status = "normal"
obj.owner_user_id = user_id
obj.owner_user_name = user_name
obj.claim_time = datetime.now()
obj.into_pool_time = None
obj.into_pool_by = None
obj.into_pool_reason = None
obj.update_by = user_id
obj.update_time = datetime.now()
db.commit()
db.refresh(obj)
from app.crud.crm.customer_pool import create_pool_record
create_pool_record(
db,
tenant_no=obj.tenant_no,
user_id=user_id,
customer_id=obj.id,
customer_name=obj.customer_name,
operation_type="claim",
to_user_id=user_id,
to_user_name=user_name,
)
return obj
def batch_claim_customers(
db: Session,
ids: List[str],
user_id: str,
user_name: str,
) -> List[CrmCustomer]:
results = []
for cid in ids:
obj = claim_customer(db, cid, user_id, user_name)
results.append(obj)
return results
def assign_customer(
db: Session,
ids: List[str],
owner_user_id: str,
owner_user_name: str,
user_id: str = None,
user_info=None,
tenant_no: str = None,
) -> List[CrmCustomer]:
results = []
for cid in ids:
obj = get_customer(db, cid)
if not obj:
continue
if tenant_no and obj.tenant_no != tenant_no:
continue
_ensure_customer_in_scope(db, obj, user_info, obj.tenant_no)
old_owner_id = obj.owner_user_id or ""
old_owner_name = obj.owner_user_name or ""
obj.status = "normal"
obj.owner_user_id = owner_user_id
obj.owner_user_name = owner_user_name
obj.assign_time = datetime.now()
obj.assign_by = user_id
obj.into_pool_time = None
obj.into_pool_by = None
obj.into_pool_reason = None
obj.update_by = user_id
obj.update_time = datetime.now()
from app.crud.crm.customer_pool import create_pool_record
create_pool_record(
db,
tenant_no=obj.tenant_no,
user_id=user_id,
customer_id=obj.id,
customer_name=obj.customer_name,
operation_type="assign",
from_user_id=old_owner_id,
from_user_name=old_owner_name,
to_user_id=owner_user_id,
to_user_name=owner_user_name,
)
results.append(obj)
db.commit()
return results
def update_follow_stats(
db: Session,
customer_id: str,
follow_time: datetime = None,
next_time: datetime = None,
) -> None:
obj = get_customer(db, customer_id)
if not obj:
return
if follow_time:
obj.last_time = follow_time
if next_time:
obj.next_time = next_time
obj.follow_count = (obj.follow_count or 0) + 1
db.commit()
def update_contact_count(db: Session, customer_id: str) -> None:
from app.models.crm_models import CrmContact
count = (
db.query(func.count(CrmContact.id))
.filter(
CrmContact.customer_id == customer_id,
CrmContact.del_flag == 0,
)
.scalar() or 0
)
obj = get_customer(db, customer_id)
if obj:
obj.contact_count = count
db.commit()
def get_park_area_list(db: Session, tenant_no: str) -> list:
return [
r[0]
for r in db.query(CrmCustomer.park_area)
.filter(
CrmCustomer.tenant_no == tenant_no,
CrmCustomer.del_flag == 0,
CrmCustomer.park_area.isnot(None),
CrmCustomer.park_area != "",
)
.distinct()
.order_by(CrmCustomer.park_area)
.all()
]