- موتور رویدادمحور با شرط، محدودیت، تأیید مدیر و idempotency - اتصال به لید، مشتری، معامله، فعالیت، روتین، پیشفاکتور، فاکتور و تیکت - صفحه /club و /club/admin، اعلان زنده امتیاز، آیتم منو - انتقال فرمساز سرنخ به ابزارها Co-authored-by: Cursor <cursoragent@cursor.com>
59 lines
2.3 KiB
TypeScript
59 lines
2.3 KiB
TypeScript
import { Injectable, Logger } from '@nestjs/common';
|
|
import { Cron } from '@nestjs/schedule';
|
|
import { PrismaService } from '../prisma/prisma.service';
|
|
import { jalaliMonthKeyOf, previousPeriod } from './gamification-engine';
|
|
import { GamificationService } from './gamification.service';
|
|
|
|
@Injectable()
|
|
export class GamificationCronService {
|
|
private readonly logger = new Logger(GamificationCronService.name);
|
|
|
|
constructor(
|
|
private readonly prisma: PrismaService,
|
|
private readonly game: GamificationService,
|
|
) {}
|
|
|
|
/** پاداش «نفر اول ماه» برای ماه شمسی گذشته؛ روزانه اجرا میشود و تکراری ثبت نمیکند */
|
|
@Cron('20 0 * * *', { timeZone: 'Asia/Tehran' })
|
|
async awardTopOfMonth() {
|
|
const range = previousPeriod('month');
|
|
if (!range) return;
|
|
const monthKey = jalaliMonthKeyOf(range.from);
|
|
const bonuses = await this.prisma.gamificationBonus.findMany({
|
|
where: { kind: 'top_of_month', isActive: true },
|
|
});
|
|
for (const bonus of bonuses) {
|
|
try {
|
|
const settings = await this.game.settingsFor(bonus.organizationId);
|
|
if (!settings?.enabled) continue;
|
|
const memberIds = new Set((await this.game.members(bonus.organizationId)).map((m) => m.userId));
|
|
const rows = await this.prisma.gamificationTransaction.groupBy({
|
|
by: ['userId'],
|
|
where: {
|
|
organizationId: bonus.organizationId,
|
|
status: 'approved',
|
|
createdAt: { gte: range.from, lt: range.to },
|
|
},
|
|
_sum: { score: true },
|
|
});
|
|
const winner = rows
|
|
.filter((r) => memberIds.has(r.userId) && (r._sum.score ?? 0) > 0)
|
|
.sort((a, b) => (b._sum.score ?? 0) - (a._sum.score ?? 0))[0];
|
|
if (!winner) continue;
|
|
await this.game.record({
|
|
organizationId: bonus.organizationId,
|
|
userId: winner.userId,
|
|
kind: 'bonus',
|
|
bonusId: bonus.id,
|
|
score: bonus.score,
|
|
coins: bonus.coins,
|
|
title: `پاداش ویژه: ${bonus.name}`,
|
|
idempotencyKey: `b:${bonus.id}:m:${monthKey}`,
|
|
});
|
|
} catch (err) {
|
|
this.logger.warn(`top-of-month ${bonus.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
}
|
|
}
|
|
}
|
|
}
|