feat(admin): 赛事管理单赛事信息增加筛选与排序功能 (支持按有下注过滤及按注单数/投注额排序)
This commit is contained in:
@@ -1995,6 +1995,8 @@ export class AdminController {
|
||||
@Query('locale') locale?: string,
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
@Query('hasBets') hasBets?: string,
|
||||
@Query('orderBy') orderBy?: string,
|
||||
) {
|
||||
const result = await this.matches.listAdminLeagueMatches(BigInt(leagueId), {
|
||||
status: status || undefined,
|
||||
@@ -2002,6 +2004,8 @@ export class AdminController {
|
||||
locale: locale || undefined,
|
||||
page: page ? Math.max(1, parseInt(page, 10) || 1) : 1,
|
||||
pageSize: pageSize ? Math.min(100, Math.max(1, parseInt(pageSize, 10) || 20)) : 20,
|
||||
hasBets: hasBets || undefined,
|
||||
orderBy: orderBy || undefined,
|
||||
});
|
||||
return jsonResponse(result);
|
||||
}
|
||||
|
||||
@@ -301,3 +301,89 @@ describe('MatchesService listUpcomingPublished', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('MatchesService listAdminLeagueMatches', () => {
|
||||
const leagueId = BigInt(1);
|
||||
|
||||
let prisma: {
|
||||
match: { findMany: jest.Mock; count: jest.Mock };
|
||||
entityTranslation: { findFirst: jest.Mock; findMany: jest.Mock };
|
||||
};
|
||||
let matchBetStats: { betStatsForMatches: jest.Mock };
|
||||
let service: MatchesService;
|
||||
|
||||
beforeEach(() => {
|
||||
prisma = {
|
||||
match: { findMany: jest.fn(), count: jest.fn() },
|
||||
entityTranslation: {
|
||||
findFirst: jest.fn().mockResolvedValue(null),
|
||||
findMany: jest.fn().mockResolvedValue([]),
|
||||
},
|
||||
};
|
||||
matchBetStats = { betStatsForMatches: jest.fn() };
|
||||
service = new MatchesService(
|
||||
prisma as never,
|
||||
{ syncWithLeaguePublished: jest.fn() } as never,
|
||||
matchBetStats as never,
|
||||
);
|
||||
});
|
||||
|
||||
it('filters by hasBets = true', async () => {
|
||||
const mockMatches = [
|
||||
{ id: BigInt(10), startTime: new Date(), displayOrder: 0, status: 'PUBLISHED', isOutright: false, matchName: 'M1', homeTeamId: BigInt(2), awayTeamId: BigInt(3), homeTeam: { code: 'H1' }, awayTeam: { code: 'A1' } },
|
||||
{ id: BigInt(11), startTime: new Date(), displayOrder: 0, status: 'PUBLISHED', isOutright: false, matchName: 'M2', homeTeamId: BigInt(4), awayTeamId: BigInt(5), homeTeam: { code: 'H2' }, awayTeam: { code: 'A2' } },
|
||||
];
|
||||
prisma.match.findMany.mockResolvedValue(mockMatches);
|
||||
const statsMap = new Map();
|
||||
statsMap.set('10', { betCount: 5, totalStake: '500.00', pendingCount: 0 });
|
||||
statsMap.set('11', { betCount: 0, totalStake: '0.00', pendingCount: 0 });
|
||||
matchBetStats.betStatsForMatches.mockResolvedValue(statsMap);
|
||||
|
||||
const result = await service.listAdminLeagueMatches(leagueId, {
|
||||
hasBets: 'true',
|
||||
});
|
||||
|
||||
expect(result.items).toHaveLength(1);
|
||||
expect(result.items[0].id).toBe('10');
|
||||
});
|
||||
|
||||
it('sorts by betCount', async () => {
|
||||
const mockMatches = [
|
||||
{ id: BigInt(10), startTime: new Date(), displayOrder: 0, status: 'PUBLISHED', isOutright: false, matchName: 'M1', homeTeamId: BigInt(2), awayTeamId: BigInt(3), homeTeam: { code: 'H1' }, awayTeam: { code: 'A1' } },
|
||||
{ id: BigInt(11), startTime: new Date(), displayOrder: 0, status: 'PUBLISHED', isOutright: false, matchName: 'M2', homeTeamId: BigInt(4), awayTeamId: BigInt(5), homeTeam: { code: 'H2' }, awayTeam: { code: 'A2' } },
|
||||
];
|
||||
prisma.match.findMany.mockResolvedValue(mockMatches);
|
||||
const statsMap = new Map();
|
||||
statsMap.set('10', { betCount: 5, totalStake: '500.00', pendingCount: 0 });
|
||||
statsMap.set('11', { betCount: 15, totalStake: '1500.00', pendingCount: 0 });
|
||||
matchBetStats.betStatsForMatches.mockResolvedValue(statsMap);
|
||||
|
||||
const result = await service.listAdminLeagueMatches(leagueId, {
|
||||
orderBy: 'betCount',
|
||||
});
|
||||
|
||||
expect(result.items).toHaveLength(2);
|
||||
expect(result.items[0].id).toBe('11');
|
||||
expect(result.items[1].id).toBe('10');
|
||||
});
|
||||
|
||||
it('sorts by totalStake', async () => {
|
||||
const mockMatches = [
|
||||
{ id: BigInt(10), startTime: new Date(), displayOrder: 0, status: 'PUBLISHED', isOutright: false, matchName: 'M1', homeTeamId: BigInt(2), awayTeamId: BigInt(3), homeTeam: { code: 'H1' }, awayTeam: { code: 'A1' } },
|
||||
{ id: BigInt(11), startTime: new Date(), displayOrder: 0, status: 'PUBLISHED', isOutright: false, matchName: 'M2', homeTeamId: BigInt(4), awayTeamId: BigInt(5), homeTeam: { code: 'H2' }, awayTeam: { code: 'A2' } },
|
||||
];
|
||||
prisma.match.findMany.mockResolvedValue(mockMatches);
|
||||
const statsMap = new Map();
|
||||
statsMap.set('10', { betCount: 5, totalStake: '1200.00', pendingCount: 0 });
|
||||
statsMap.set('11', { betCount: 15, totalStake: '800.00', pendingCount: 0 });
|
||||
matchBetStats.betStatsForMatches.mockResolvedValue(statsMap);
|
||||
|
||||
const result = await service.listAdminLeagueMatches(leagueId, {
|
||||
orderBy: 'totalStake',
|
||||
});
|
||||
|
||||
expect(result.items).toHaveLength(2);
|
||||
expect(result.items[0].id).toBe('10');
|
||||
expect(result.items[1].id).toBe('11');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -556,6 +556,8 @@ export class MatchesService {
|
||||
locale?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
hasBets?: string;
|
||||
orderBy?: string;
|
||||
},
|
||||
) {
|
||||
const where: Prisma.MatchWhereInput = {
|
||||
@@ -574,6 +576,70 @@ export class MatchesService {
|
||||
}
|
||||
const page = Math.max(1, opts.page ?? 1);
|
||||
const pageSize = Math.min(100, Math.max(1, opts.pageSize ?? 20));
|
||||
|
||||
if (opts.hasBets === 'true' || opts.orderBy === 'betCount' || opts.orderBy === 'totalStake') {
|
||||
const allRows = await this.prisma.match.findMany({
|
||||
where,
|
||||
include: { homeTeam: true, awayTeam: true },
|
||||
});
|
||||
const locale = opts.locale ?? 'zh-CN';
|
||||
const betStatsMap = await this.betStatsForMatches(allRows.map((m) => m.id));
|
||||
const itemsWithStats = await Promise.all(
|
||||
allRows.map(async (m) => {
|
||||
const [homeTeamName, awayTeamName] = await Promise.all([
|
||||
this.getTranslation('TEAM', m.homeTeamId, locale),
|
||||
this.getTranslation('TEAM', m.awayTeamId, locale),
|
||||
]);
|
||||
const raw = betStatsMap.get(m.id.toString());
|
||||
const betCount = raw?.betCount ?? 0;
|
||||
const totalStake = raw?.totalStake ?? '0';
|
||||
const pendingBets = raw?.pendingCount ?? 0;
|
||||
return {
|
||||
id: m.id.toString(),
|
||||
status: m.status,
|
||||
isOutright: m.isOutright,
|
||||
isHot: m.isHot,
|
||||
displayOrder: m.displayOrder,
|
||||
startTime: m.startTime,
|
||||
matchName: m.matchName,
|
||||
homeTeamName,
|
||||
awayTeamName,
|
||||
homeTeam: { code: m.homeTeam.code },
|
||||
awayTeam: { code: m.awayTeam.code },
|
||||
betCount,
|
||||
totalStake,
|
||||
pendingBets,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
let filteredItems = itemsWithStats;
|
||||
if (opts.hasBets === 'true') {
|
||||
filteredItems = itemsWithStats.filter((item) => item.betCount > 0);
|
||||
}
|
||||
|
||||
if (opts.orderBy === 'betCount') {
|
||||
filteredItems.sort((a, b) => b.betCount - a.betCount);
|
||||
} else if (opts.orderBy === 'totalStake') {
|
||||
filteredItems.sort((a, b) => {
|
||||
const stakeA = parseFloat(a.totalStake);
|
||||
const stakeB = parseFloat(b.totalStake);
|
||||
return stakeB - stakeA;
|
||||
});
|
||||
} else {
|
||||
filteredItems.sort((a, b) => {
|
||||
if (a.displayOrder !== b.displayOrder) {
|
||||
return a.displayOrder - b.displayOrder;
|
||||
}
|
||||
return new Date(b.startTime).getTime() - new Date(a.startTime).getTime();
|
||||
});
|
||||
}
|
||||
|
||||
const total = filteredItems.length;
|
||||
const paginatedItems = filteredItems.slice((page - 1) * pageSize, page * pageSize);
|
||||
return { items: paginatedItems, total, page, pageSize };
|
||||
}
|
||||
|
||||
const [total, rows] = await Promise.all([
|
||||
this.prisma.match.count({ where }),
|
||||
this.prisma.match.findMany({
|
||||
|
||||
Reference in New Issue
Block a user