import { Injectable, Logger } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; import { SettlementService } from '../modules/settlement/settlement.service'; import { AlertService } from '../common/alert/alert.service'; /** * 财务对账单定时任务(Asia/Shanghai) * - 每日 08:00:酒厂日账单 + 门店日账单(统计昨日 00:00~今日 00:00) * - 每月 1 日 08:00:合伙人上一自然月账单 + 物流承运商上一自然月对账 * - 工作日 18:05:门店提现 T+0 审完预警(FIN-003) */ @Injectable() export class SettlementScheduler { private readonly logger = new Logger(SettlementScheduler.name); constructor( private readonly settlementService: SettlementService, private readonly alert: AlertService, ) {} @Cron('0 8 * * *', { timeZone: 'Asia/Shanghai' }) async handleDailyBills() { this.logger.log('Daily settlement bills job start'); try { const winery = await this.settlementService.generateWineryBillForDay(); this.logger.log(`Winery bill: ${JSON.stringify(winery)}`); } catch (e) { this.logger.error('Winery bill job failed', e instanceof Error ? e.stack : e); this.alert.notify({ level: 'P0', category: 'job', title: '酒厂日账单任务失败', detail: e instanceof Error ? e.message : String(e), dedupeKey: `job_winery_bill|${new Date().toISOString().slice(0, 10)}`, }); } try { const store = await this.settlementService.generateStoreBillsForDay(); this.logger.log(`Store bills: ${JSON.stringify(store)}`); } catch (e) { this.logger.error('Store bill job failed', e instanceof Error ? e.stack : e); this.alert.notify({ level: 'P0', category: 'job', title: '门店日账单任务失败', detail: e instanceof Error ? e.message : String(e), dedupeKey: `job_store_bill|${new Date().toISOString().slice(0, 10)}`, }); } } /** FIN-003:工作日 18:05 扫描超时未审门店提现(企微提醒在 SettlementService 内发送) */ @Cron('5 18 * * 1-5', { timeZone: 'Asia/Shanghai' }) async handleWithdrawOverdueAlert() { this.logger.log('Store withdraw overdue scan start'); try { const summary = await this.settlementService.scanOverdueStoreWithdrawals(); this.logger.log(`Store withdraw overdue: ${JSON.stringify(summary)}`); } catch (e) { this.logger.error('Store withdraw overdue scan failed', e instanceof Error ? e.stack : e); this.alert.notify({ level: 'P1', category: 'job', title: '门店提现超时扫描失败', detail: e instanceof Error ? e.message : String(e), dedupeKey: `job_store_withdraw_overdue_fail|${new Date().toISOString().slice(0, 10)}`, }); } } @Cron('0 8 1 * *', { timeZone: 'Asia/Shanghai' }) async handleMonthlyPartnerBills() { this.logger.log('Monthly partner bills job start'); try { const result = await this.settlementService.generatePreviousMonthPartnerBills(); this.logger.log( `Partner bills: total=${result.total} success=${result.success} failed=${result.failed}`, ); if (result.failed > 0) { this.alert.notify({ level: 'P0', category: 'job', title: '合伙人月账单部分失败', detail: `total=${result.total} success=${result.success} failed=${result.failed}`, dedupeKey: `job_partner_bill|${new Date().toISOString().slice(0, 7)}`, }); } } catch (e) { this.logger.error('Partner bill job failed', e instanceof Error ? e.stack : e); this.alert.notify({ level: 'P0', category: 'job', title: '合伙人月账单任务失败', detail: e instanceof Error ? e.message : String(e), dedupeKey: `job_partner_bill|${new Date().toISOString().slice(0, 7)}`, }); } try { const logistics = await this.settlementService.generatePreviousMonthLogisticsBills(); this.logger.log( `Logistics bills: total=${logistics.total} success=${logistics.success} failed=${logistics.failed}`, ); if (logistics.failed > 0) { this.alert.notify({ level: 'P0', category: 'job', title: '物流月对账部分失败', detail: `total=${logistics.total} success=${logistics.success} failed=${logistics.failed}`, dedupeKey: `job_logistics_bill|${new Date().toISOString().slice(0, 7)}`, }); } } catch (e) { this.logger.error('Logistics bill job failed', e instanceof Error ? e.stack : e); this.alert.notify({ level: 'P0', category: 'job', title: '物流月对账任务失败', detail: e instanceof Error ? e.message : String(e), dedupeKey: `job_logistics_bill|${new Date().toISOString().slice(0, 7)}`, }); } } }