"""日结模块 Celery 任务""" import logging import datetime from celery import shared_task from django.utils import timezone from .models import DailySettlementConfig, SettlementModuleEnum from .services import ( calculate_plate_order_daily_summary, calculate_printing_order_daily_summary, ) from .signals import daily_settlement_completed logger = logging.getLogger(__name__) @shared_task(bind=True) def run_daily_settlement(self): """ 执行所有商户的日结任务(固定时间触发) 在 settings.py 中配置的固定时间触发, 拉取所有配置了统计模块的商户,逐个执行日结。 """ from basic_info.models import Merchant # 获取所有配置了统计模块的商户 configs = DailySettlementConfig.objects.exclude( settlement_modules=[] ).select_related('merchant') logger.info( f'[settlement.tasks] 开始执行日结,共 {configs.count()} 个商户需要统计' ) # 为每个商户触发日结任务 for config in configs: settlement_date = timezone.localdate() - datetime.timedelta(days=1) run_merchant_daily_settlement.delay( merchant_id=config.merchant.id, settlement_date=str(settlement_date) ) return { 'task_id': self.request.id, 'total_merchants': configs.count(), 'settlement_date': str(timezone.localdate() - datetime.timedelta(days=1)), } @shared_task(bind=True) def run_merchant_daily_settlement( self, merchant_id: int, settlement_date: str | None = None ): """ 执行单个商户的日结 Args: merchant_id: 商户ID settlement_date: 结算日期(YYYY-MM-DD),默认为昨天 Returns: dict: 包含 task_id 和执行结果的 payload """ from basic_info.models import Merchant # 解析日期 if settlement_date: date_obj = datetime.datetime.strptime(settlement_date, '%Y-%m-%d').date() else: date_obj = timezone.localdate() - datetime.timedelta(days=1) merchant = Merchant.objects.get(id=merchant_id) config = merchant.settlement_config modules = config.settlement_modules summaries = {} errors = {} overall_status = 'success' logger.info( f'[settlement.tasks] 开始执行商户 {merchant_id} 的日结,' f'日期={date_obj}, 模块={modules}' ) # 执行各模块的统计 for module in modules: try: if module == SettlementModuleEnum.PLATE_ORDER: summary = calculate_plate_order_daily_summary( merchant_id=merchant_id, settlement_date=date_obj ) summaries['plate_order'] = summary elif module == SettlementModuleEnum.PRINTING_ORDER: summary = calculate_printing_order_daily_summary( merchant_id=merchant_id, settlement_date=date_obj ) summaries['printing_order'] = summary except Exception as e: logger.exception( f'[settlement.tasks] 商户 {merchant_id} 的 {module} 统计失败: {e}' ) errors[module] = str(e) overall_status = 'partial' # 如果所有模块都失败 if len(errors) == len(modules): overall_status = 'failed' # 发送信号(不包含统计结果详情) daily_settlement_completed.send( sender=run_merchant_daily_settlement, merchant=merchant, settlement_date=date_obj, status=overall_status, modules=modules, errors=errors, task_id=self.request.id ) payload = { 'task_id': self.request.id, 'merchant_id': merchant_id, 'settlement_date': str(date_obj), 'status': overall_status, 'modules': modules, 'errors': errors, } logger.info( f'[settlement.tasks] 商户 {merchant_id} 日结完成: {payload}' ) return payload