296 lines
8.8 KiB
Python
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)
|