contract_tasks.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. """
  2. 合同相关定时任务
  3. """
  4. from datetime import datetime, timedelta
  5. from sqlalchemy import create_engine, select
  6. from sqlalchemy.orm import Session
  7. from alien_gateway.config import settings
  8. from alien_contract.db.models.bundle import ContractBundle
  9. from alien_contract.db.models.document import ContractDocument
  10. from alien_util.celery_app import celery_app
  11. from common.aliyun_sms_server.sms_client import send_sms
  12. import logging
  13. # 配置日志
  14. logger = logging.getLogger(__name__)
  15. logging.basicConfig(
  16. level=logging.INFO,
  17. format="%(asctime)s [%(levelname)s] %(name)s %(message)s"
  18. )
  19. # 全局数据库引擎(避免重复创建)
  20. _sync_engine = None
  21. def get_sync_db_session():
  22. """
  23. 获取同步数据库会话(用于 Celery 任务)
  24. Celery 任务通常使用同步代码,所以需要同步数据库连接
  25. """
  26. global _sync_engine
  27. if _sync_engine is None:
  28. # 使用同步数据库连接
  29. database_url = settings.SQLALCHEMY_DATABASE_URI
  30. _sync_engine = create_engine(
  31. database_url,
  32. pool_pre_ping=True,
  33. pool_size=5,
  34. max_overflow=10
  35. )
  36. return Session(_sync_engine)
  37. @celery_app.task(name="alien_util.tasks.contract_tasks.check_contract_expiry")
  38. def check_contract_expiry(subject_type: str | None = "store"):
  39. """
  40. 检查合同到期时间,如果距离到期不足15天,发送提醒短信
  41. 每天凌晨0点1分执行
  42. :param subject_type: 签约主体类型过滤,默认 "store"(仅商家);传 None 则不过滤(store+lawyer 全部)
  43. """
  44. logger.info("开始执行合同到期检查任务 subject_type=%s", subject_type)
  45. try:
  46. # 获取数据库会话
  47. db = get_sync_db_session()
  48. try:
  49. now = datetime.now()
  50. # 计算15天后的日期
  51. check_date = now + timedelta(days=15)
  52. # 合同中心:仅对"主合同(is_primary=1) 且 已签署(status=1)"发提醒,
  53. # 避免同一合同包内多份子合同(支付宝授权函/微信承诺函等)重复发送
  54. conditions = [
  55. ContractDocument.is_primary == 1,
  56. ContractDocument.status == 1,
  57. ContractDocument.expiry_time.isnot(None),
  58. ContractDocument.expiry_time <= check_date,
  59. ContractDocument.expiry_time >= now,
  60. ContractDocument.delete_flag == 0,
  61. ContractBundle.delete_flag == 0,
  62. ]
  63. if subject_type:
  64. conditions.append(ContractBundle.subject_type == subject_type)
  65. stmt = (
  66. select(
  67. ContractDocument.id,
  68. ContractDocument.expiry_time,
  69. ContractBundle.subject_name,
  70. ContractBundle.contact_phone,
  71. ContractBundle.subject_type,
  72. )
  73. .join(ContractBundle, ContractDocument.bundle_id == ContractBundle.id)
  74. .where(*conditions)
  75. )
  76. contracts = db.execute(stmt).all()
  77. logger.info(f"找到 {len(contracts)} 条即将到期的合同")
  78. # 遍历即将到期的合同,发送提醒短信
  79. for contract in contracts:
  80. try:
  81. # 计算距离到期的天数
  82. days_until_expiry = (contract.expiry_time - now).days
  83. logger.info(
  84. f"合同文档ID: {contract.id}, "
  85. f"主体类型: {contract.subject_type}, "
  86. f"主体名称: {contract.subject_name}, "
  87. f"联系电话: {contract.contact_phone}, "
  88. f"到期时间: {contract.expiry_time}, "
  89. f"距离到期: {days_until_expiry} 天"
  90. )
  91. send_expiry_reminder_sms(
  92. contract.contact_phone,
  93. contract.subject_name,
  94. settings.ALIYUN_SMS_SIGN_NAME_CONTRACT,
  95. settings.ALIYUN_SMS_TEMPLATE_CODE_CONTRACT,
  96. )
  97. except Exception as e:
  98. logger.error(f"处理合同文档ID {contract.id} 时出错: {e}", exc_info=True)
  99. logger.info("合同到期检查任务执行完成")
  100. finally:
  101. db.close()
  102. except Exception as e:
  103. logger.error(f"执行合同到期检查任务时出错: {e}", exc_info=True)
  104. raise
  105. def send_expiry_reminder_sms(phone: str, merchant_name: str, sign_name: str, template_code: str):
  106. """
  107. 发送合同到期提醒短信
  108. Args:
  109. phone: 联系电话
  110. merchant_name: 商家名称
  111. days_until_expiry: 距离到期的天数
  112. """
  113. template_param = {
  114. "name": merchant_name,
  115. }
  116. send_sms(phone, template_param, sign_name, template_code)