feat: add api foundation and booking schema

This commit is contained in:
2026-09-18 15:14:49 +08:00
parent 55b2b091c2
commit 5e71b89e95
8 changed files with 1556 additions and 31 deletions

View File

@@ -0,0 +1,147 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum BookingStatus {
received
contacting
confirmed
completed
cancelled
}
enum BookingRequestType {
package
private_chef
}
enum ContactPreference {
wechat
phone
none
}
enum FulfillmentType {
pickup
delivery
}
model BrandProfile {
id String @id @default(uuid())
name String
tagline String?
description String?
avatarUrl String?
coverUrl String?
hours String?
address String?
deliveryNote String?
contactPhone String?
contactWechat String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
services Service[]
bookings Booking[]
customerUsers CustomerUser[]
tags Tag[]
}
model Service {
id String @id @default(uuid())
brandId String
brand BrandProfile @relation(fields: [brandId], references: [id])
name String
description String
coverUrl String?
priceText String?
priceAmount Int?
bookingEnabled Boolean @default(true)
isEnabled Boolean @default(true)
sortOrder Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
bookings Booking[]
bookingItems BookingItem[]
@@index([brandId, isEnabled, sortOrder])
}
model CustomerUser {
id String @id @default(uuid())
brandId String
brand BrandProfile @relation(fields: [brandId], references: [id])
openid String
lastLoginAt DateTime @default(now())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
bookings Booking[]
@@unique([brandId, openid])
}
model Booking {
id String @id @default(uuid())
bookingNo String @unique
brandId String
brand BrandProfile @relation(fields: [brandId], references: [id])
customerUserId String
customerUser CustomerUser @relation(fields: [customerUserId], references: [id])
requestType BookingRequestType
customerName String
phone String?
wechatId String?
contactPreference ContactPreference
fulfillmentType FulfillmentType?
serviceId String?
service Service? @relation(fields: [serviceId], references: [id], onDelete: SetNull)
requestedDate DateTime?
requestedTime String?
deliveryAddress String?
estimatedAmount Int?
extraData Json?
note String?
adminNote String?
status BookingStatus @default(received)
isRead Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
items BookingItem[]
tags BookingTag[]
@@index([brandId, status, createdAt])
@@index([customerUserId, createdAt])
}
model BookingItem {
id String @id @default(uuid())
bookingId String
booking Booking @relation(fields: [bookingId], references: [id], onDelete: Cascade)
serviceId String?
service Service? @relation(fields: [serviceId], references: [id], onDelete: SetNull)
serviceNameSnapshot String
unitPriceSnapshot Int
quantity Int
subtotalSnapshot Int
}
model Tag {
id String @id @default(uuid())
brandId String
brand BrandProfile @relation(fields: [brandId], references: [id])
name String
isPreset Boolean @default(false)
isEnabled Boolean @default(true)
createdAt DateTime @default(now())
bookings BookingTag[]
@@unique([brandId, name])
}
model BookingTag {
bookingId String
tagId String
booking Booking @relation(fields: [bookingId], references: [id], onDelete: Cascade)
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
@@id([bookingId, tagId])
}

59
apps/api/prisma/seed.ts Normal file
View File

@@ -0,0 +1,59 @@
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main(): Promise<void> {
const brand = await prisma.brandProfile.upsert({
where: { id: "00000000-0000-0000-0000-000000000001" },
update: {},
create: {
id: "00000000-0000-0000-0000-000000000001",
name: "阿成家常菜",
tagline: "现做家常套餐,也为小型聚餐定制一桌热饭菜",
description: "每天认真做好一顿家常饭。",
hours: "周一至周六 10:30-20:00",
address: "大学城生活区东门附近",
deliveryNote: "目前支持部分学生宿舍楼配送,无法送达时会主动联系。",
},
});
await prisma.service.createMany({
data: [
{
brandId: brand.id,
name: "红烧肉双拼套餐",
description: "红烧肉、时蔬、米饭",
priceText: "¥28 / 份",
priceAmount: 2800,
sortOrder: 1,
},
{
brandId: brand.id,
name: "小炒鸡套餐",
description: "现炒鸡块、时蔬、米饭",
priceText: "¥25 / 份",
priceAmount: 2500,
sortOrder: 2,
},
{
brandId: brand.id,
name: "家庭聚餐私厨",
description: "根据人数、地点和口味人工沟通报价",
priceText: "面议",
bookingEnabled: true,
sortOrder: 3,
},
],
skipDuplicates: true,
});
for (const name of ["普通套餐", "私厨", "配送", "自取", "熟客", "重点跟进"]) {
await prisma.tag.upsert({
where: { brandId_name: { brandId: brand.id, name } },
update: {},
create: { brandId: brand.id, name, isPreset: true },
});
}
}
main().finally(() => prisma.$disconnect());