feat(admin,api,player): settlement stats, team crests, MS fields and list bet summary

This commit is contained in:
2026-06-04 17:30:48 +08:00
parent cc737e2924
commit 9fcee31a9a
27 changed files with 2296 additions and 427 deletions

View File

@@ -68,6 +68,11 @@ export class OutrightService {
market,
openCount,
);
const [titleZh, titleEn, titleMs] = await Promise.all([
this.getOutrightTitle(m.id, 'zh-CN'),
this.getOutrightTitle(m.id, 'en-US'),
this.getOutrightTitle(m.id, 'ms-MY'),
]);
return {
id: m.id.toString(),
leagueId: league.id.toString(),
@@ -75,6 +80,9 @@ export class OutrightService {
leagueZh,
leagueEn,
matchName: m.matchName ?? '',
titleZh: titleZh || m.matchName || '',
titleEn: titleEn || m.matchName || '',
titleMs,
status: m.status,
selectionCount,
canImportCanonical: leagueCode === WC2026_LEAGUE_CODE,
@@ -154,6 +162,12 @@ export class OutrightService {
),
);
const [titleZh, titleEn, titleMs] = await Promise.all([
this.getOutrightTitle(match.id, 'zh-CN'),
this.getOutrightTitle(match.id, 'en-US'),
this.getOutrightTitle(match.id, 'ms-MY'),
]);
return {
id: match.id.toString(),
leagueId: match.leagueId.toString(),
@@ -161,6 +175,9 @@ export class OutrightService {
leagueZh,
leagueEn,
matchName: match.matchName ?? '',
titleZh: titleZh || match.matchName || '',
titleEn: titleEn || match.matchName || '',
titleMs,
status: match.status,
marketId: fullMarket.id.toString(),
marketStatus: fullMarket.status,
@@ -177,6 +194,7 @@ export class OutrightService {
leagueId: bigint;
titleZh: string;
titleEn: string;
titleMs?: string;
status?: string;
isHot?: boolean;
displayOrder?: number;
@@ -189,13 +207,14 @@ export class OutrightService {
const placeholder = await this.ensurePlaceholderTeam();
const status = data.status ?? 'PUBLISHED';
const matchName = this.resolveOutrightMatchName(data);
const match = await this.prisma.match.create({
data: {
leagueId: data.leagueId,
homeTeamId: placeholder.id,
awayTeamId: placeholder.id,
isOutright: true,
matchName: data.titleEn || data.titleZh,
matchName,
startTime: data.startTime ?? new Date('2030-01-01T00:00:00Z'),
status,
publishTime: status === 'PUBLISHED' ? new Date() : undefined,
@@ -204,6 +223,11 @@ export class OutrightService {
},
});
await this.upsertOutrightTitles(match.id, {
zh: data.titleZh,
en: data.titleEn,
ms: data.titleMs,
});
await this.ensureOutrightMarket(match.id);
return this.getForAdmin(match.id);
}
@@ -217,15 +241,35 @@ export class OutrightService {
displayOrder?: number;
titleZh?: string;
titleEn?: string;
titleMs?: string;
},
) {
const match = await this.getOutrightMatchOrThrow(matchId);
const status = data.status ?? match.status;
let matchName = data.matchName?.trim();
const titlesTouched =
data.titleZh !== undefined ||
data.titleEn !== undefined ||
data.titleMs !== undefined;
if (titlesTouched) {
const [curZh, curEn, curMs] = await Promise.all([
this.getOutrightTitle(matchId, 'zh-CN'),
this.getOutrightTitle(matchId, 'en-US'),
this.getOutrightTitle(matchId, 'ms-MY'),
]);
const zh = data.titleZh !== undefined ? data.titleZh : curZh;
const en = data.titleEn !== undefined ? data.titleEn : curEn;
const ms = data.titleMs !== undefined ? data.titleMs : curMs;
await this.upsertOutrightTitles(matchId, { zh, en, ms });
matchName = this.resolveOutrightMatchName({ titleZh: zh, titleEn: en, titleMs: ms });
}
await this.prisma.match.update({
where: { id: matchId },
data: {
status,
matchName: data.matchName,
matchName: matchName !== undefined ? matchName : undefined,
isHot: data.isHot,
displayOrder: data.displayOrder,
publishTime:
@@ -490,7 +534,18 @@ export class OutrightService {
if (!selections.length) continue;
const [titleZh, titleEn, titleMs] = await Promise.all([
this.getOutrightTitle(match.id, 'zh-CN'),
this.getOutrightTitle(match.id, 'en-US'),
this.getOutrightTitle(match.id, 'ms-MY'),
]);
const localizedTitle = this.pickOutrightTitleForLocale(locale, {
zh: titleZh,
en: titleEn,
ms: titleMs,
});
const title =
localizedTitle ||
match.matchName?.trim() ||
`*${leagueName || 'Outright'} ${locale.startsWith('zh') ? '冠军' : 'Winner'}`;
@@ -625,4 +680,76 @@ export class OutrightService {
});
return row?.value ?? '';
}
private async getOutrightTitle(matchId: bigint, locale: string): Promise<string> {
const row = await this.prisma.entityTranslation.findFirst({
where: {
entityType: 'MATCH',
entityId: matchId,
locale,
fieldName: 'title',
},
});
return row?.value?.trim() ?? '';
}
private resolveOutrightMatchName(data: {
titleZh?: string;
titleEn?: string;
titleMs?: string;
}): string {
return (
data.titleEn?.trim() ||
data.titleZh?.trim() ||
data.titleMs?.trim() ||
'Outright'
);
}
private pickOutrightTitleForLocale(
locale: string,
titles: { zh: string; en: string; ms: string },
): string {
if (locale === 'ms-MY' || locale.startsWith('ms')) {
return titles.ms || titles.en || titles.zh;
}
if (locale.startsWith('zh')) {
return titles.zh || titles.en || titles.ms;
}
return titles.en || titles.zh || titles.ms;
}
private async upsertOutrightTitles(
matchId: bigint,
titles: { zh?: string; en?: string; ms?: string },
) {
const entries: Array<[string, string | undefined]> = [
['zh-CN', titles.zh],
['en-US', titles.en],
['ms-MY', titles.ms],
];
for (const [locale, raw] of entries) {
if (raw === undefined) continue;
const value = raw.trim();
if (!value) continue;
await this.prisma.entityTranslation.upsert({
where: {
entityType_entityId_locale_fieldName: {
entityType: 'MATCH',
entityId: matchId,
locale,
fieldName: 'title',
},
},
create: {
entityType: 'MATCH',
entityId: matchId,
locale,
fieldName: 'title',
value,
},
update: { value },
});
}
}
}