feat(ops): WeCom webhook alerts for pay/redeem anomalies
Add outbound group-bot alerts, rate/amount rules, stuck-order cron, HQ test button, and health db/redis checks. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
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)
|
||||
@@ -11,7 +12,10 @@ import { SettlementService } from '../modules/settlement/settlement.service';
|
||||
export class SettlementScheduler {
|
||||
private readonly logger = new Logger(SettlementScheduler.name);
|
||||
|
||||
constructor(private readonly settlementService: SettlementService) {}
|
||||
constructor(
|
||||
private readonly settlementService: SettlementService,
|
||||
private readonly alert: AlertService,
|
||||
) {}
|
||||
|
||||
@Cron('0 8 * * *', { timeZone: 'Asia/Shanghai' })
|
||||
async handleDailyBills() {
|
||||
@@ -21,12 +25,26 @@ export class SettlementScheduler {
|
||||
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)}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,16 +56,48 @@ export class SettlementScheduler {
|
||||
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)}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user