Files
shanxia2.0/backend/app/crud/crm/pool_rule.py
T
2026-08-22 09:08:05 +08:00

296 lines
8.8 KiB
Python

"""CRM 公海池规则 CRUD + 规则执行逻辑"""
from __future__ import annotations
import logging
import math
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional, Tuple
from sqlalchemy import 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, CrmCustomerPool, CrmCustomerPoolRule
from app.crud.crm.data_scope_helpers import (
apply_crm_create_by_scope,
ensure_crm_create_by_row_in_scope,
)
logger = logging.getLogger(__name__)
_FMT = "%Y-%m-%d %H:%M:%S"
def total_pages(total: int, page_size: int) -> int:
if page_size <= 0:
return 0
return math.ceil(total / page_size)
def _row_to_dict(row: CrmCustomerPoolRule) -> Dict[str, Any]:
return {
"id": row.id,
"poolName": row.pool_name or "",
"ruleType": row.rule_type or "",
"timeoutDays": row.timeout_days if row.timeout_days is not None else 0,
"protectDays": row.protect_days if row.protect_days is not None else 0,
"status": row.status or "enable",
"sort": row.sort if row.sort 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_pool_rules(
db: Session,
tenant_no: str,
*,
page: int = 1,
page_size: int = 10,
pool_name: Optional[str] = None,
rule_type: Optional[str] = None,
status: Optional[str] = None,
user_info=None,
max_page_size: Optional[int] = None,
) -> Tuple[List[Dict[str, Any]], int]:
q = db.query(CrmCustomerPoolRule).filter(
CrmCustomerPoolRule.tenant_no == tenant_no,
CrmCustomerPoolRule.del_flag == 0,
)
if pool_name:
q = q.filter(CrmCustomerPoolRule.pool_name.like(f"%{pool_name}%"))
if rule_type:
q = q.filter(CrmCustomerPoolRule.rule_type == rule_type)
if status:
q = q.filter(CrmCustomerPoolRule.status == status)
if user_info:
q = apply_crm_create_by_scope(q, CrmCustomerPoolRule, user_info, db, tenant_no)
total = q.count()
q = q.order_by(CrmCustomerPoolRule.sort.asc(), CrmCustomerPoolRule.create_time.desc())
cap = max_page_size if max_page_size is not None else 500
page_size = min(max(1, page_size), cap)
page = max(1, page)
offset = (page - 1) * page_size
rows = q.offset(offset).limit(page_size).all()
return [_row_to_dict(r) for r in rows], total
def get_pool_rule(db: Session, rule_id: str, tenant_no: str) -> Optional[CrmCustomerPoolRule]:
return (
db.query(CrmCustomerPoolRule)
.filter(
CrmCustomerPoolRule.id == rule_id,
CrmCustomerPoolRule.tenant_no == tenant_no,
CrmCustomerPoolRule.del_flag == 0,
)
.first()
)
def create_pool_rule(
db: Session,
tenant_no: str,
*,
user_id: Optional[str] = None,
pool_name: str,
rule_type: str,
timeout_days: int,
protect_days: int = 0,
status: str = "enable",
sort: int = 0,
remark: Optional[str] = None,
) -> CrmCustomerPoolRule:
row = CrmCustomerPoolRule(
id=generate_uuid(),
pool_name=pool_name,
rule_type=rule_type,
timeout_days=timeout_days,
protect_days=protect_days,
status=status,
sort=sort,
remark=remark,
tenant_no=tenant_no,
create_by=user_id,
create_time=datetime.now(),
update_time=datetime.now(),
)
db.add(row)
db.flush()
return row
def update_pool_rule(
db: Session,
rule_id: str,
tenant_no: str,
*,
user_id: Optional[str] = None,
user_info=None,
pool_name: Optional[str] = None,
rule_type: Optional[str] = None,
timeout_days: Optional[int] = None,
protect_days: Optional[int] = None,
status: Optional[str] = None,
sort: Optional[int] = None,
remark: Optional[str] = None,
) -> CrmCustomerPoolRule:
row = get_pool_rule(db, rule_id, tenant_no)
if not row:
raise ValueError("公海池规则不存在")
ensure_crm_create_by_row_in_scope(db, row, user_info, tenant_no, message="无权访问该公海规则")
if pool_name is not None:
row.pool_name = pool_name
if rule_type is not None:
row.rule_type = rule_type
if timeout_days is not None:
row.timeout_days = timeout_days
if protect_days is not None:
row.protect_days = protect_days
if status is not None:
row.status = status
if sort is not None:
row.sort = sort
if remark is not None:
row.remark = remark
row.update_by = user_id
row.update_time = datetime.now()
db.flush()
return row
def delete_pool_rules(
db: Session, rule_ids: List[str], tenant_no: str, user_info=None
) -> int:
count = 0
for rid in rule_ids:
row = get_pool_rule(db, rid, tenant_no)
if not row:
continue
ensure_crm_create_by_row_in_scope(db, row, user_info, tenant_no, message="无权访问该公海规则")
if row.status == "enable":
raise ValueError(f"公海池规则【{row.pool_name}】正在启用中,请先禁用后再删除")
assert_row_deletable_by_creator(row, user_info)
row.del_flag = 1
row.update_time = datetime.now()
count += 1
db.flush()
return count
def update_pool_rule_status(
db: Session,
rule_id: str,
tenant_no: str,
status: str,
*,
user_id: Optional[str] = None,
user_info=None,
) -> CrmCustomerPoolRule:
row = get_pool_rule(db, rule_id, tenant_no)
if not row:
raise ValueError("公海池规则不存在")
ensure_crm_create_by_row_in_scope(db, row, user_info, tenant_no, message="无权访问该公海规则")
row.status = status
row.update_by = user_id
row.update_time = datetime.now()
db.flush()
return row
def execute_pool_rules(db: Session, tenant_no: str) -> int:
rules = (
db.query(CrmCustomerPoolRule)
.filter(
CrmCustomerPoolRule.tenant_no == tenant_no,
CrmCustomerPoolRule.status == "enable",
CrmCustomerPoolRule.del_flag == 0,
)
.order_by(CrmCustomerPoolRule.sort.asc())
.all()
)
if not rules:
logger.info("没有启用的公海规则")
return 0
total_recycled = 0
for rule in rules:
try:
total_recycled += _execute_single_rule(db, tenant_no, rule)
except Exception:
logger.exception("执行公海规则失败: %s", rule.pool_name)
logger.info("公海池规则执行完成,共回收 %d 个客户", total_recycled)
return total_recycled
def _execute_single_rule(db: Session, tenant_no: str, rule: CrmCustomerPoolRule) -> int:
logger.info("执行公海规则: %s", rule.pool_name)
q = db.query(CrmCustomer).filter(
CrmCustomer.tenant_no == tenant_no,
CrmCustomer.del_flag == 0,
CrmCustomer.status == "normal",
CrmCustomer.deal_status != "dealed",
)
timeout_date = datetime.now() - timedelta(days=rule.timeout_days)
if rule.rule_type == "no_follow":
q = q.filter(
or_(
CrmCustomer.last_time < timeout_date,
CrmCustomer.last_time.is_(None),
)
)
elif rule.rule_type == "no_deal":
q = q.filter(
CrmCustomer.claim_time < timeout_date,
CrmCustomer.deal_status == "not_deal",
)
else:
return 0
customers = q.all()
logger.info("规则【%s】匹配到 %d 个客户", rule.pool_name, len(customers))
now = datetime.now()
for customer in customers:
original_owner_id = customer.owner_user_id or ""
original_owner_name = customer.owner_user_name or ""
customer.status = "pool"
customer.into_pool_time = now
customer.into_pool_by = None
customer.into_pool_reason = f"系统自动回收-规则:{rule.pool_name}"
customer.owner_user_id = None
customer.owner_user_name = None
customer.update_time = now
pool_record = CrmCustomerPool(
id=generate_uuid(),
customer_id=customer.id,
customer_name=customer.customer_name,
operation_type="into",
from_user_id=original_owner_id,
from_user_name=original_owner_name,
reason=f"系统自动回收-规则:{rule.pool_name}",
tenant_no=tenant_no,
create_by=None,
create_by_name="系统",
create_time=now,
update_time=now,
)
db.add(pool_record)
db.flush()
return len(customers)