customer_pool.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. """CRM 客户公海池定时任务"""
  2. import logging
  3. from datetime import datetime, timedelta
  4. from sqlalchemy.orm import Session
  5. from app.core.database import SessionLocal
  6. from app.core.utils import generate_uuid
  7. from app.models.crm_models import CrmCustomer, CrmCustomerPool, CrmCustomerPoolRule
  8. logger = logging.getLogger(__name__)
  9. PROTECT_MIN_FOLLOW_COUNT = 3
  10. PROTECT_REMIND_DAYS = 3
  11. def check_customer_protect_time():
  12. """每天凌晨1点:检查保护期到期客户,跟进次数不足则退回公海"""
  13. db: Session = SessionLocal()
  14. try:
  15. logger.info("======== 开始执行保护期到期检查任务 ========")
  16. now = datetime.now()
  17. customers = (
  18. db.query(CrmCustomer)
  19. .filter(
  20. CrmCustomer.del_flag == 0,
  21. CrmCustomer.status == "normal",
  22. CrmCustomer.protect_time.isnot(None),
  23. CrmCustomer.protect_time <= now,
  24. CrmCustomer.owner_user_id.isnot(None),
  25. )
  26. .all()
  27. )
  28. logger.info("查询到 %d 个保护期到期的客户", len(customers))
  29. recycle_count = 0
  30. qualified_count = 0
  31. for customer in customers:
  32. try:
  33. follow_count = customer.follow_count or 0
  34. if follow_count < PROTECT_MIN_FOLLOW_COUNT:
  35. _recycle_to_pool(db, customer, f"保护期到期未达标:跟进次数{follow_count}次,要求{PROTECT_MIN_FOLLOW_COUNT}次")
  36. recycle_count += 1
  37. else:
  38. customer.protect_time = None
  39. customer.update_time = now
  40. db.flush()
  41. qualified_count += 1
  42. except Exception:
  43. logger.exception("处理客户【%s】保护期到期失败", customer.customer_name)
  44. db.commit()
  45. logger.info(
  46. "======== 保护期到期检查任务完成:共 %d 个,达标 %d 个,退回公海 %d 个 ========",
  47. len(customers), qualified_count, recycle_count,
  48. )
  49. except Exception:
  50. db.rollback()
  51. logger.exception("保护期到期检查任务异常")
  52. finally:
  53. db.close()
  54. def remind_customer_recycle():
  55. """每天早上9点:保护期到期提醒(3天内到期且跟进不足)"""
  56. db: Session = SessionLocal()
  57. try:
  58. logger.info("======== 开始执行保护期到期提醒任务 ========")
  59. now = datetime.now()
  60. remind_deadline = now + timedelta(days=PROTECT_REMIND_DAYS)
  61. customers = (
  62. db.query(CrmCustomer)
  63. .filter(
  64. CrmCustomer.del_flag == 0,
  65. CrmCustomer.status == "normal",
  66. CrmCustomer.protect_time.isnot(None),
  67. CrmCustomer.protect_time > now,
  68. CrmCustomer.protect_time <= remind_deadline,
  69. CrmCustomer.owner_user_id.isnot(None),
  70. )
  71. .all()
  72. )
  73. logger.info("查询到 %d 个客户保护期即将到期", len(customers))
  74. remind_count = 0
  75. for customer in customers:
  76. try:
  77. follow_count = customer.follow_count or 0
  78. if follow_count < PROTECT_MIN_FOLLOW_COUNT:
  79. remaining_days = (customer.protect_time - now).days
  80. logger.info(
  81. "提醒负责人【%s】:客户【%s】保护期还剩%d天,当前跟进%d次,需达到%d次",
  82. customer.owner_user_name, customer.customer_name,
  83. remaining_days, follow_count, PROTECT_MIN_FOLLOW_COUNT,
  84. )
  85. remind_count += 1
  86. except Exception:
  87. logger.exception("提醒客户【%s】保护期到期失败", customer.customer_name)
  88. logger.info("======== 保护期到期提醒任务完成:共提醒 %d 个客户 ========", remind_count)
  89. except Exception:
  90. logger.exception("保护期到期提醒任务异常")
  91. finally:
  92. db.close()
  93. def pool_recovery_job():
  94. """每天凌晨2点:执行公海池规则自动回收"""
  95. db: Session = SessionLocal()
  96. try:
  97. logger.info("======== 开始执行公海池规则自动回收任务 ========")
  98. from app.crud.crm.pool_rule import _execute_single_rule
  99. tenants = [r[0] for r in db.query(CrmCustomerPoolRule.tenant_no).filter(
  100. CrmCustomerPoolRule.del_flag == 0,
  101. CrmCustomerPoolRule.status == "enable",
  102. ).distinct().all()]
  103. total = 0
  104. for tenant_no in tenants:
  105. rules = (
  106. db.query(CrmCustomerPoolRule)
  107. .filter(
  108. CrmCustomerPoolRule.tenant_no == tenant_no,
  109. CrmCustomerPoolRule.status == "enable",
  110. CrmCustomerPoolRule.del_flag == 0,
  111. )
  112. .order_by(CrmCustomerPoolRule.sort.asc())
  113. .all()
  114. )
  115. for rule in rules:
  116. try:
  117. total += _execute_single_rule(db, tenant_no, rule)
  118. except Exception:
  119. logger.exception("执行公海规则失败: %s", rule.pool_name)
  120. db.commit()
  121. logger.info("======== 公海池规则自动回收任务完成:共回收 %d 个客户 ========", total)
  122. except Exception:
  123. db.rollback()
  124. logger.exception("公海池规则自动回收任务异常")
  125. finally:
  126. db.close()
  127. def warn_customer_recycle():
  128. """每天上午10点:即将被回收客户预警"""
  129. db: Session = SessionLocal()
  130. try:
  131. logger.info("======== 开始执行即将被回收客户预警任务 ========")
  132. now = datetime.now()
  133. tenants = [r[0] for r in db.query(CrmCustomerPoolRule.tenant_no).filter(
  134. CrmCustomerPoolRule.del_flag == 0,
  135. CrmCustomerPoolRule.status == "enable",
  136. ).distinct().all()]
  137. warn_count = 0
  138. for tenant_no in tenants:
  139. rules = (
  140. db.query(CrmCustomerPoolRule)
  141. .filter(
  142. CrmCustomerPoolRule.tenant_no == tenant_no,
  143. CrmCustomerPoolRule.status == "enable",
  144. CrmCustomerPoolRule.del_flag == 0,
  145. )
  146. .all()
  147. )
  148. for rule in rules:
  149. try:
  150. warn_days = max(3, rule.timeout_days - 3)
  151. warn_date = now + timedelta(days=warn_days)
  152. if rule.rule_type == "no_follow":
  153. at_risk = (
  154. db.query(CrmCustomer)
  155. .filter(
  156. CrmCustomer.tenant_no == tenant_no,
  157. CrmCustomer.del_flag == 0,
  158. CrmCustomer.status == "normal",
  159. CrmCustomer.deal_status != "dealed",
  160. CrmCustomer.last_time.isnot(None),
  161. CrmCustomer.last_time <= warn_date,
  162. )
  163. .all()
  164. )
  165. elif rule.rule_type == "no_deal":
  166. at_risk = (
  167. db.query(CrmCustomer)
  168. .filter(
  169. CrmCustomer.tenant_no == tenant_no,
  170. CrmCustomer.del_flag == 0,
  171. CrmCustomer.status == "normal",
  172. CrmCustomer.deal_status == "not_deal",
  173. CrmCustomer.claim_time.isnot(None),
  174. CrmCustomer.claim_time <= warn_date,
  175. )
  176. .all()
  177. )
  178. else:
  179. continue
  180. for c in at_risk:
  181. logger.info(
  182. "预警:客户【%s】即将被规则【%s】回收,负责人【%s】",
  183. c.customer_name, rule.pool_name, c.owner_user_name,
  184. )
  185. warn_count += 1
  186. except Exception:
  187. logger.exception("预警规则【%s】执行失败", rule.pool_name)
  188. logger.info("======== 即将被回收客户预警任务完成:共 %d 个客户 ========", warn_count)
  189. except Exception:
  190. logger.exception("即将被回收客户预警任务异常")
  191. finally:
  192. db.close()
  193. def _recycle_to_pool(db: Session, customer: CrmCustomer, reason: str):
  194. """将客户退回公海池"""
  195. now = datetime.now()
  196. original_owner_id = customer.owner_user_id or ""
  197. original_owner_name = customer.owner_user_name or ""
  198. customer.status = "pool"
  199. customer.owner_user_id = None
  200. customer.owner_user_name = None
  201. customer.into_pool_time = now
  202. customer.into_pool_by = None
  203. customer.into_pool_reason = reason
  204. customer.protect_time = None
  205. customer.claim_time = None
  206. customer.update_time = now
  207. pool_record = CrmCustomerPool(
  208. id=generate_uuid(),
  209. customer_id=customer.id,
  210. customer_name=customer.customer_name,
  211. operation_type="recycle",
  212. from_user_id=original_owner_id,
  213. from_user_name=original_owner_name,
  214. reason=reason,
  215. tenant_no=customer.tenant_no,
  216. create_by=None,
  217. create_by_name="系统",
  218. create_time=now,
  219. update_time=now,
  220. )
  221. db.add(pool_record)
  222. db.flush()