feat: باشگاه امتیازات کارکنان (موتور امتیاز، جدول رتبه، فروشگاه پاداش، نشانها، پنل مدیریت و گزارش ROI)
- موتور رویدادمحور با شرط، محدودیت، تأیید مدیر و idempotency - اتصال به لید، مشتری، معامله، فعالیت، روتین، پیشفاکتور، فاکتور و تیکت - صفحه /club و /club/admin، اعلان زنده امتیاز، آیتم منو - انتقال فرمساز سرنخ به ابزارها Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
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)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user