Files
iddaai-be/src/modules/coupons/services/user-coupon.service.ts
T
2026-04-16 17:21:48 +03:00

190 lines
5.2 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)),
};
}
}