feat(admin,api,player): settlement stats, team crests, MS fields and list bet summary
This commit is contained in:
@@ -294,6 +294,141 @@ export class SettlementService {
|
||||
return { success: true, batchId: batchId.toString() };
|
||||
}
|
||||
|
||||
async getMatchBetStats(matchId: bigint) {
|
||||
const match = await this.prisma.match.findFirst({
|
||||
where: { id: matchId, deletedAt: null },
|
||||
});
|
||||
if (!match) throw new NotFoundException('Match not found');
|
||||
|
||||
const legs = await this.prisma.betSelection.findMany({
|
||||
where: { matchId },
|
||||
include: {
|
||||
bet: {
|
||||
select: {
|
||||
id: true,
|
||||
betNo: true,
|
||||
betType: true,
|
||||
stake: true,
|
||||
status: true,
|
||||
settlementStatus: true,
|
||||
potentialReturn: true,
|
||||
actualReturn: true,
|
||||
placedAt: true,
|
||||
user: { select: { username: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: [{ marketType: 'asc' }, { sortOrder: 'asc' }, { id: 'asc' }],
|
||||
});
|
||||
|
||||
const betById = new Map<string, (typeof legs)[0]['bet']>();
|
||||
for (const leg of legs) {
|
||||
betById.set(leg.betId.toString(), leg.bet);
|
||||
}
|
||||
|
||||
let totalStake = new Decimal(0);
|
||||
let totalPotential = new Decimal(0);
|
||||
let singleBets = 0;
|
||||
let parlayBets = 0;
|
||||
const statusCounts: Record<string, number> = {};
|
||||
|
||||
for (const bet of betById.values()) {
|
||||
totalStake = totalStake.add(bet.stake);
|
||||
if (bet.potentialReturn) {
|
||||
totalPotential = totalPotential.add(bet.potentialReturn);
|
||||
}
|
||||
if (bet.betType === 'SINGLE') singleBets += 1;
|
||||
else if (bet.betType === 'PARLAY') parlayBets += 1;
|
||||
statusCounts[bet.status] = (statusCounts[bet.status] ?? 0) + 1;
|
||||
}
|
||||
|
||||
type SelAgg = {
|
||||
marketType: string;
|
||||
period: string | null;
|
||||
selectionName: string;
|
||||
selectionId: string;
|
||||
legCount: number;
|
||||
singleStake: Decimal;
|
||||
parlayLegCount: number;
|
||||
};
|
||||
const selMap = new Map<string, SelAgg>();
|
||||
|
||||
for (const leg of legs) {
|
||||
const key = `${leg.marketId.toString()}:${leg.selectionId.toString()}`;
|
||||
let row = selMap.get(key);
|
||||
if (!row) {
|
||||
row = {
|
||||
marketType: leg.marketType,
|
||||
period: leg.period,
|
||||
selectionName: leg.selectionNameSnapshot,
|
||||
selectionId: leg.selectionId.toString(),
|
||||
legCount: 0,
|
||||
singleStake: new Decimal(0),
|
||||
parlayLegCount: 0,
|
||||
};
|
||||
selMap.set(key, row);
|
||||
}
|
||||
row.legCount += 1;
|
||||
if (leg.bet.betType === 'SINGLE') {
|
||||
row.singleStake = row.singleStake.add(leg.bet.stake);
|
||||
} else if (leg.bet.betType === 'PARLAY') {
|
||||
row.parlayLegCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const bySelection = Array.from(selMap.values())
|
||||
.map((r) => ({
|
||||
marketType: r.marketType,
|
||||
period: r.period,
|
||||
selectionName: r.selectionName,
|
||||
selectionId: r.selectionId,
|
||||
legCount: r.legCount,
|
||||
singleStake: r.singleStake.toString(),
|
||||
parlayLegCount: r.parlayLegCount,
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
const mk = a.marketType.localeCompare(b.marketType);
|
||||
if (mk !== 0) return mk;
|
||||
return a.selectionName.localeCompare(b.selectionName);
|
||||
});
|
||||
|
||||
const bets = Array.from(legs)
|
||||
.map((leg) => ({
|
||||
id: leg.bet.id.toString(),
|
||||
betNo: leg.bet.betNo,
|
||||
username: leg.bet.user.username,
|
||||
betType: leg.bet.betType,
|
||||
status: leg.bet.status,
|
||||
settlementStatus: leg.bet.settlementStatus,
|
||||
stake: leg.bet.stake.toString(),
|
||||
potentialReturn: leg.bet.potentialReturn?.toString() ?? null,
|
||||
actualReturn: leg.bet.actualReturn.toString(),
|
||||
placedAt: leg.bet.placedAt.toISOString(),
|
||||
marketType: leg.marketType,
|
||||
period: leg.period,
|
||||
selectionName: leg.selectionNameSnapshot,
|
||||
odds: leg.odds.toString(),
|
||||
}))
|
||||
.sort(
|
||||
(a, b) =>
|
||||
new Date(b.placedAt).getTime() - new Date(a.placedAt).getTime(),
|
||||
);
|
||||
|
||||
return {
|
||||
summary: {
|
||||
totalBets: betById.size,
|
||||
singleBets,
|
||||
parlayBets,
|
||||
totalStake: totalStake.toString(),
|
||||
totalPotentialReturn: totalPotential.toString(),
|
||||
statusCounts,
|
||||
legCount: legs.length,
|
||||
},
|
||||
bySelection,
|
||||
bets,
|
||||
};
|
||||
}
|
||||
|
||||
async voidMatchBets(matchId: bigint) {
|
||||
const bets = await this.prisma.bet.findMany({
|
||||
where: { status: 'PENDING', selections: { some: { matchId } } },
|
||||
|
||||
Reference in New Issue
Block a user