This commit is contained in:
+189
@@ -0,0 +1,189 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { PrismaService } from '../../../database/prisma.service';
|
||||
import { User, UserCoupon, Match } from '@prisma/client';
|
||||
|
||||
export class CreateCouponDto {
|
||||
strategy: string; // 'SAFE', 'VALUE', 'CUSTOM'
|
||||
items: {
|
||||
matchId: string;
|
||||
selection: string; // 'MS 1', '2.5 UST'
|
||||
odd: number;
|
||||
}[];
|
||||
isPublic?: boolean;
|
||||
}
|
||||
|
||||
export interface UserStatsDto {
|
||||
totalCoupons: number;
|
||||
wonCoupons: number;
|
||||
winRate: number; // Percentage
|
||||
totalInvested: number; // Unit based (1 unit per coupon)
|
||||
totalReturn: number;
|
||||
roi: number; // Return on Investment %
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class UserCouponService {
|
||||
private readonly logger = new Logger(UserCouponService.name);
|
||||
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
/**
|
||||
* Kullanıcı için yeni bir kupon oluşturur ve kaydeder.
|
||||
*/
|
||||
async createCoupon(user: User, dto: CreateCouponDto): Promise<UserCoupon> {
|
||||
const totalOdds = dto.items.reduce((acc, item) => acc * item.odd, 1);
|
||||
|
||||
const coupon = await this.prisma.userCoupon.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
strategy: dto.strategy,
|
||||
totalOdds: parseFloat(totalOdds.toFixed(2)),
|
||||
isPublic: dto.isPublic || false,
|
||||
status: 'PENDING',
|
||||
couponItems: {
|
||||
create: dto.items.map((item) => ({
|
||||
matchId: item.matchId,
|
||||
selection: item.selection,
|
||||
oddAtTime: item.odd,
|
||||
})),
|
||||
},
|
||||
},
|
||||
include: {
|
||||
couponItems: true,
|
||||
},
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
`Coupon created for user ${user.email} with odds ${totalOdds}`,
|
||||
);
|
||||
return coupon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bekleyen kuponların sonuçlarını kontrol eder ve günceller.
|
||||
* Bu metod bir Cron Job tarafından periyodik olarak çağrılmalıdır.
|
||||
*/
|
||||
async updatePendingCoupons(): Promise<void> {
|
||||
// Sadece bitmiş (FT) maçları içeren PENDING kuponları çek
|
||||
const pendingCoupons = await this.prisma.userCoupon.findMany({
|
||||
where: { status: 'PENDING' },
|
||||
include: {
|
||||
couponItems: {
|
||||
include: { match: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
for (const coupon of pendingCoupons) {
|
||||
let isCouponWon = true;
|
||||
let isCouponLost = false;
|
||||
let allMatchesFinished = true;
|
||||
|
||||
for (const item of coupon.couponItems) {
|
||||
if (item.match.status !== 'FT') {
|
||||
allMatchesFinished = false;
|
||||
break; // Henüz bitmemiş maç var, kuponu güncelleme
|
||||
}
|
||||
|
||||
const isItemWon = this.checkSelection(item.selection, item.match);
|
||||
|
||||
// Sonucu item bazında güncelle
|
||||
if (item.isCorrect !== isItemWon) {
|
||||
await this.prisma.userCouponItem.update({
|
||||
where: { id: item.id },
|
||||
data: { isCorrect: isItemWon },
|
||||
});
|
||||
}
|
||||
|
||||
if (!isItemWon) {
|
||||
isCouponLost = true;
|
||||
isCouponWon = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isCouponLost) {
|
||||
await this.prisma.userCoupon.update({
|
||||
where: { id: coupon.id },
|
||||
data: { status: 'LOST' },
|
||||
});
|
||||
} else if (allMatchesFinished && isCouponWon) {
|
||||
await this.prisma.userCoupon.update({
|
||||
where: { id: coupon.id },
|
||||
data: { status: 'WON' },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Basit bir kural seti ile bahsin tutup tutmadığını kontrol eder.
|
||||
* Gerçek dünyada bu daha karmaşık bir 'BetSettlementService' olmalıdır.
|
||||
*/
|
||||
private checkSelection(selection: string, match: Match): boolean {
|
||||
const home = match.scoreHome ?? 0;
|
||||
const away = match.scoreAway ?? 0;
|
||||
const total = home + away;
|
||||
|
||||
switch (selection) {
|
||||
case 'MS 1':
|
||||
return home > away;
|
||||
case 'MS X':
|
||||
return home === away;
|
||||
case 'MS 2':
|
||||
return away > home;
|
||||
case '1.5 UST':
|
||||
return total > 1.5;
|
||||
case '2.5 UST':
|
||||
return total > 2.5;
|
||||
case '3.5 UST':
|
||||
return total > 3.5;
|
||||
case '2.5 ALT':
|
||||
return total < 2.5;
|
||||
case 'KG VAR':
|
||||
return home > 0 && away > 0;
|
||||
case 'KG YOK':
|
||||
return home === 0 || away === 0;
|
||||
default:
|
||||
return false; // Bilinmeyen market
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kullanıcının bahis performans istatistiklerini getirir.
|
||||
*/
|
||||
async getUserStatistics(userId: string): Promise<UserStatsDto> {
|
||||
const coupons = await this.prisma.userCoupon.findMany({
|
||||
where: {
|
||||
userId,
|
||||
status: { in: ['WON', 'LOST'] },
|
||||
},
|
||||
});
|
||||
|
||||
const totalCoupons = coupons.length;
|
||||
if (totalCoupons === 0) {
|
||||
return {
|
||||
totalCoupons: 0,
|
||||
wonCoupons: 0,
|
||||
winRate: 0,
|
||||
totalInvested: 0,
|
||||
totalReturn: 0,
|
||||
roi: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const wonCoupons = coupons.filter((c) => c.status === 'WON');
|
||||
const totalInvested = totalCoupons; // Her kupona 1 birim yatırıldığını varsayıyoruz
|
||||
const totalReturn = wonCoupons.reduce((acc, c) => acc + c.totalOdds, 0);
|
||||
const winRate = (wonCoupons.length / totalCoupons) * 100;
|
||||
const roi = ((totalReturn - totalInvested) / totalInvested) * 100;
|
||||
|
||||
return {
|
||||
totalCoupons,
|
||||
wonCoupons: wonCoupons.length,
|
||||
winRate: parseFloat(winRate.toFixed(2)),
|
||||
totalInvested,
|
||||
totalReturn: parseFloat(totalReturn.toFixed(2)),
|
||||
roi: parseFloat(roi.toFixed(2)),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user