fix(reporting-service): use field-level @unique for statsDate in schema

Root cause: @@unique([field], name: "xxx") requires { xxx: { field } } syntax
in findUnique/upsert, but code used { field } directly.

Fix: Change to @unique(map: "uk_realtime_stats_date") on the field itself.
This keeps the same database index name while allowing { statsDate } syntax.

No migration needed - only Prisma client type generation changes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-06 21:38:13 -08:00
parent 4e201d3a66
commit 8c29603f5a
2 changed files with 52 additions and 99 deletions

View File

@ -386,7 +386,7 @@ model RealtimeStats {
id BigInt @id @default(autoincrement()) @map("stats_id")
// === 统计日期 ===
statsDate DateTime @map("stats_date") @db.Date
statsDate DateTime @unique(map: "uk_realtime_stats_date") @map("stats_date") @db.Date
// === 认种统计 (来自 planting.order.paid) ===
dailyPlantingCount Int @default(0) @map("daily_planting_count")
@ -405,7 +405,6 @@ model RealtimeStats {
updatedAt DateTime @updatedAt @map("updated_at")
@@map("realtime_stats")
@@unique([statsDate], name: "uk_realtime_stats_date")
@@index([statsDate(sort: Desc)], name: "idx_rs_date")
}

View File

@ -15,7 +15,7 @@ export class RealtimeStatsRepository implements IRealtimeStatsRepository {
async getOrCreateByDate(date: Date): Promise<RealtimeStatsData> {
const statsDate = this.normalizeDate(date);
const existing = await this.prisma.realtimeStats.findFirst({
const existing = await this.prisma.realtimeStats.findUnique({
where: { statsDate },
});
@ -33,7 +33,7 @@ export class RealtimeStatsRepository implements IRealtimeStatsRepository {
async findByDate(date: Date): Promise<RealtimeStatsData | null> {
const statsDate = this.normalizeDate(date);
const found = await this.prisma.realtimeStats.findFirst({
const found = await this.prisma.realtimeStats.findUnique({
where: { statsDate },
});
@ -54,85 +54,57 @@ export class RealtimeStatsRepository implements IRealtimeStatsRepository {
`Incrementing planting: date=${statsDate.toISOString()}, trees=${safeTreeCount}, amount=${safeAmount}`,
);
// 使用 findFirst + create/update 替代 upsert避免 Prisma 唯一约束语法问题
const existing = await this.prisma.realtimeStats.findFirst({
const result = await this.prisma.realtimeStats.upsert({
where: { statsDate },
create: {
statsDate,
dailyPlantingCount: safeTreeCount,
dailyOrderCount: 1,
dailyPlantingAmount: safeAmount,
},
update: {
dailyPlantingCount: { increment: safeTreeCount },
dailyOrderCount: { increment: 1 },
dailyPlantingAmount: { increment: safeAmount },
},
});
if (existing) {
const result = await this.prisma.realtimeStats.update({
where: { id: existing.id },
data: {
dailyPlantingCount: { increment: safeTreeCount },
dailyOrderCount: { increment: 1 },
dailyPlantingAmount: { increment: safeAmount },
},
});
return this.toDomain(result);
} else {
const result = await this.prisma.realtimeStats.create({
data: {
statsDate,
dailyPlantingCount: safeTreeCount,
dailyOrderCount: 1,
dailyPlantingAmount: safeAmount,
},
});
return this.toDomain(result);
}
return this.toDomain(result);
}
async incrementOrder(date: Date): Promise<RealtimeStatsData> {
const statsDate = this.normalizeDate(date);
const existing = await this.prisma.realtimeStats.findFirst({
const result = await this.prisma.realtimeStats.upsert({
where: { statsDate },
create: {
statsDate,
dailyOrderCount: 1,
},
update: {
dailyOrderCount: { increment: 1 },
},
});
if (existing) {
const result = await this.prisma.realtimeStats.update({
where: { id: existing.id },
data: {
dailyOrderCount: { increment: 1 },
},
});
return this.toDomain(result);
} else {
const result = await this.prisma.realtimeStats.create({
data: {
statsDate,
dailyOrderCount: 1,
},
});
return this.toDomain(result);
}
return this.toDomain(result);
}
async incrementNewUser(date: Date): Promise<RealtimeStatsData> {
const statsDate = this.normalizeDate(date);
this.logger.debug(`Incrementing new user: date=${statsDate.toISOString()}`);
const existing = await this.prisma.realtimeStats.findFirst({
const result = await this.prisma.realtimeStats.upsert({
where: { statsDate },
create: {
statsDate,
dailyNewUserCount: 1,
},
update: {
dailyNewUserCount: { increment: 1 },
},
});
if (existing) {
const result = await this.prisma.realtimeStats.update({
where: { id: existing.id },
data: {
dailyNewUserCount: { increment: 1 },
},
});
return this.toDomain(result);
} else {
const result = await this.prisma.realtimeStats.create({
data: {
statsDate,
dailyNewUserCount: 1,
},
});
return this.toDomain(result);
}
return this.toDomain(result);
}
async incrementProvinceAuth(date: Date): Promise<RealtimeStatsData> {
@ -141,54 +113,36 @@ export class RealtimeStatsRepository implements IRealtimeStatsRepository {
`Incrementing province auth: date=${statsDate.toISOString()}`,
);
const existing = await this.prisma.realtimeStats.findFirst({
const result = await this.prisma.realtimeStats.upsert({
where: { statsDate },
create: {
statsDate,
dailyProvinceAuthCount: 1,
},
update: {
dailyProvinceAuthCount: { increment: 1 },
},
});
if (existing) {
const result = await this.prisma.realtimeStats.update({
where: { id: existing.id },
data: {
dailyProvinceAuthCount: { increment: 1 },
},
});
return this.toDomain(result);
} else {
const result = await this.prisma.realtimeStats.create({
data: {
statsDate,
dailyProvinceAuthCount: 1,
},
});
return this.toDomain(result);
}
return this.toDomain(result);
}
async incrementCityAuth(date: Date): Promise<RealtimeStatsData> {
const statsDate = this.normalizeDate(date);
this.logger.debug(`Incrementing city auth: date=${statsDate.toISOString()}`);
const existing = await this.prisma.realtimeStats.findFirst({
const result = await this.prisma.realtimeStats.upsert({
where: { statsDate },
create: {
statsDate,
dailyCityAuthCount: 1,
},
update: {
dailyCityAuthCount: { increment: 1 },
},
});
if (existing) {
const result = await this.prisma.realtimeStats.update({
where: { id: existing.id },
data: {
dailyCityAuthCount: { increment: 1 },
},
});
return this.toDomain(result);
} else {
const result = await this.prisma.realtimeStats.create({
data: {
statsDate,
dailyCityAuthCount: 1,
},
});
return this.toDomain(result);
}
return this.toDomain(result);
}
async findRecentDays(days: number): Promise<RealtimeStatsData[]> {