feat: internationalize API error responses by locale
Add shared error codes with zh/en/ms messages, coded app exceptions, and locale-aware global filter. Frontends send X-Locale so error text matches the active UI language. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import { Injectable, BadRequestException } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../../../shared/prisma/prisma.service';
|
||||
import { WalletService } from '../../ledger/wallet.service';
|
||||
import { Decimal } from '@prisma/client/runtime/library';
|
||||
import { generateBatchNo } from '../../../shared/common/decorators';
|
||||
import { appBadRequest } from '../../../shared/common/app-error';
|
||||
import {
|
||||
resolveCashbackRateForBet,
|
||||
type CashbackRuleRow,
|
||||
@@ -214,14 +215,14 @@ export class CashbackService {
|
||||
const start = this.normalizePeriodStart(periodStart);
|
||||
const end = this.normalizePeriodEnd(periodEnd);
|
||||
if (start > end) {
|
||||
throw new BadRequestException('开始日期不能晚于结束日期');
|
||||
throw appBadRequest('CASHBACK_DATE_RANGE_INVALID');
|
||||
}
|
||||
|
||||
const alreadyPaid = await this.prisma.cashbackBatch.findFirst({
|
||||
where: { status: 'CONFIRMED', periodStart: start, periodEnd: end },
|
||||
});
|
||||
if (alreadyPaid) {
|
||||
throw new BadRequestException('该统计周期已发放返水,不可重复生成预览');
|
||||
throw appBadRequest('CASHBACK_ALREADY_ISSUED');
|
||||
}
|
||||
|
||||
const {
|
||||
@@ -236,9 +237,9 @@ export class CashbackService {
|
||||
|
||||
if (items.length === 0 || totalAmount.lte(0)) {
|
||||
if (eligibleBetCount > 0 && skippedClaimedCount >= eligibleBetCount) {
|
||||
throw new BadRequestException('该周期内的有效注单均已计入其他返水批次,无法生成预览');
|
||||
throw appBadRequest('CASHBACK_BETS_IN_OTHER_BATCH');
|
||||
}
|
||||
throw new BadRequestException('该周期内无符合条件的返水,无法生成预览');
|
||||
throw appBadRequest('CASHBACK_NO_ELIGIBLE_BETS');
|
||||
}
|
||||
|
||||
let batch!: Awaited<ReturnType<typeof this.prisma.cashbackBatch.create>>;
|
||||
@@ -345,7 +346,7 @@ export class CashbackService {
|
||||
items: { orderBy: { amount: 'desc' } },
|
||||
},
|
||||
});
|
||||
if (!batch) throw new BadRequestException('Batch not found');
|
||||
if (!batch) throw appBadRequest('CASHBACK_BATCH_NOT_FOUND');
|
||||
|
||||
const userIds = batch.items.map((i) => i.userId);
|
||||
const users =
|
||||
@@ -403,10 +404,10 @@ export class CashbackService {
|
||||
where: { id: batchId },
|
||||
include: { items: true, bets: true },
|
||||
});
|
||||
if (!batch) throw new BadRequestException('Batch not found');
|
||||
if (batch.status !== 'PREVIEW') throw new BadRequestException('该批次不可发放');
|
||||
if (!batch) throw appBadRequest('CASHBACK_BATCH_NOT_FOUND');
|
||||
if (batch.status !== 'PREVIEW') throw appBadRequest('CASHBACK_BATCH_NOT_ISSUABLE');
|
||||
if (batch.items.length === 0 || batch.totalAmount.lte(0)) {
|
||||
throw new BadRequestException('批次无有效返水金额');
|
||||
throw appBadRequest('CASHBACK_NO_AMOUNT');
|
||||
}
|
||||
|
||||
const duplicate = await this.prisma.cashbackBatch.findFirst({
|
||||
@@ -418,7 +419,7 @@ export class CashbackService {
|
||||
},
|
||||
});
|
||||
if (duplicate) {
|
||||
throw new BadRequestException('该统计周期已发放返水');
|
||||
throw appBadRequest('CASHBACK_PERIOD_ALREADY_ISSUED');
|
||||
}
|
||||
|
||||
const betIds = batch.bets.map((b) => b.betId);
|
||||
@@ -430,7 +431,7 @@ export class CashbackService {
|
||||
},
|
||||
});
|
||||
if (conflict) {
|
||||
throw new BadRequestException('部分注单已在其他批次发放返水,请作废本预览后重新生成');
|
||||
throw appBadRequest('CASHBACK_BETS_ALREADY_PAID');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,9 +458,9 @@ export class CashbackService {
|
||||
|
||||
async cancelBatch(batchId: bigint) {
|
||||
const batch = await this.prisma.cashbackBatch.findUnique({ where: { id: batchId } });
|
||||
if (!batch) throw new BadRequestException('Batch not found');
|
||||
if (!batch) throw appBadRequest('CASHBACK_BATCH_NOT_FOUND');
|
||||
if (batch.status !== 'PREVIEW') {
|
||||
throw new BadRequestException('只能作废待发放批次');
|
||||
throw appBadRequest('CASHBACK_PREVIEW_ONLY_VOID');
|
||||
}
|
||||
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
|
||||
Reference in New Issue
Block a user