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 { 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 { // 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 { 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)), }; } }