244 lines
9.2 KiB
TypeScript
244 lines
9.2 KiB
TypeScript
import { prisma } from '../../lib/prisma'
|
|
|
|
export async function findPublicOffers() {
|
|
return prisma.offer.findMany({
|
|
where: { isPublic: true, isActive: true, validFrom: { lte: new Date() }, validUntil: { gte: new Date() } },
|
|
include: { company: { include: { brand: { select: { displayName: true, logoUrl: true, subdomain: true, primaryColor: true } } } } },
|
|
orderBy: [{ isFeatured: 'desc' }, { createdAt: 'desc' }],
|
|
take: 50,
|
|
})
|
|
}
|
|
|
|
export async function findCitiesFromCompanies() {
|
|
return prisma.company.findMany({
|
|
where: {
|
|
status: { in: ['ACTIVE', 'TRIALING'] },
|
|
brand: { isListedOnCarplace: true, publicCity: { not: null } },
|
|
},
|
|
select: { brand: { select: { publicCity: true } } },
|
|
})
|
|
}
|
|
|
|
export async function findListedCompanies(where: any, skip: number, take: number) {
|
|
return prisma.company.findMany({
|
|
where,
|
|
include: {
|
|
brand: { select: { displayName: true, logoUrl: true, subdomain: true, publicCity: true, publicCountry: true, carplaceRating: true, primaryColor: true } },
|
|
_count: { select: { vehicles: { where: { isPublished: true, status: 'AVAILABLE' } } } },
|
|
},
|
|
skip,
|
|
take,
|
|
})
|
|
}
|
|
|
|
export async function findPublishedVehicles(where: any) {
|
|
return prisma.vehicle.findMany({
|
|
where,
|
|
include: {
|
|
company: { include: { brand: { select: { displayName: true, logoUrl: true, subdomain: true, carplaceRating: true, primaryColor: true } } } },
|
|
},
|
|
orderBy: { dailyRate: 'asc' },
|
|
})
|
|
}
|
|
|
|
export async function findVehicleForCarplace(vehicleId: string, companySlug: string) {
|
|
return prisma.vehicle.findFirst({
|
|
where: { id: vehicleId, isPublished: true, status: 'AVAILABLE', company: { status: { in: ['ACTIVE', 'TRIALING'] }, OR: [{ slug: companySlug }, { brand: { subdomain: companySlug } }] } },
|
|
include: { company: { include: { brand: true } } },
|
|
})
|
|
}
|
|
|
|
export async function findVehicleForCarplaceById(vehicleId: string) {
|
|
return prisma.vehicle.findFirst({
|
|
where: {
|
|
id: vehicleId,
|
|
isPublished: true,
|
|
status: 'AVAILABLE',
|
|
company: { status: { in: ['ACTIVE', 'TRIALING'] }, brand: { isListedOnCarplace: true } },
|
|
},
|
|
include: { company: { include: { brand: true } } },
|
|
})
|
|
}
|
|
|
|
export async function upsertCarplaceCustomer(companyId: string, data: {
|
|
email: string
|
|
firstName: string
|
|
lastName: string
|
|
phone?: string
|
|
dateOfBirth?: string
|
|
nationality?: string
|
|
identityDocumentNumber?: string
|
|
fullAddress?: string
|
|
driverLicense?: string
|
|
licenseExpiry?: string
|
|
licenseIssuedAt?: string
|
|
licenseCountry?: string
|
|
licenseCategory?: string
|
|
internationalLicenseNumber?: string
|
|
}) {
|
|
const existing = await prisma.customer.findUnique({
|
|
where: { companyId_email: { companyId, email: data.email } },
|
|
select: { id: true, address: true },
|
|
})
|
|
|
|
// Only patch the address JSON with fields that were actually provided
|
|
const prevAddress = (existing?.address && typeof existing.address === 'object' && !Array.isArray(existing.address))
|
|
? existing.address as Record<string, unknown>
|
|
: {}
|
|
const hasAddressPatch = data.fullAddress || data.identityDocumentNumber || data.internationalLicenseNumber !== undefined
|
|
const address = hasAddressPatch
|
|
? {
|
|
...prevAddress,
|
|
...(data.fullAddress ? { fullAddress: data.fullAddress } : {}),
|
|
...(data.identityDocumentNumber ? { identityDocumentNumber: data.identityDocumentNumber } : {}),
|
|
...(data.internationalLicenseNumber !== undefined ? { internationalLicenseNumber: data.internationalLicenseNumber ?? null } : {}),
|
|
}
|
|
: undefined
|
|
|
|
const payload = {
|
|
firstName: data.firstName,
|
|
lastName: data.lastName,
|
|
email: data.email,
|
|
...(data.phone ? { phone: data.phone } : {}),
|
|
...(data.driverLicense ? { driverLicense: data.driverLicense, licenseNumber: data.driverLicense } : {}),
|
|
...(data.dateOfBirth ? { dateOfBirth: new Date(data.dateOfBirth) } : {}),
|
|
...(data.nationality ? { nationality: data.nationality } : {}),
|
|
...(address !== undefined ? { address } : {}),
|
|
...(data.licenseExpiry ? { licenseExpiry: new Date(data.licenseExpiry) } : {}),
|
|
...(data.licenseIssuedAt ? { licenseIssuedAt: new Date(data.licenseIssuedAt) } : {}),
|
|
...(data.licenseCountry ? { licenseCountry: data.licenseCountry } : {}),
|
|
...(data.licenseCategory ? { licenseCategory: data.licenseCategory } : {}),
|
|
}
|
|
|
|
if (!existing) {
|
|
return prisma.customer.create({ data: { companyId, ...payload } })
|
|
}
|
|
return prisma.customer.update({ where: { id: existing.id }, data: payload })
|
|
}
|
|
|
|
export async function createCarplaceReservation(data: {
|
|
companyId: string; vehicleId: string; customerId: string
|
|
startDate: Date; endDate: Date; pickupLocation?: string | null; returnLocation?: string | null
|
|
dailyRate: number; totalDays: number; totalAmount: number; notes?: string; bookingReference?: string
|
|
}) {
|
|
return prisma.reservation.create({
|
|
data: { ...data, source: 'CARPLACE', status: 'DRAFT' },
|
|
})
|
|
}
|
|
|
|
export async function createCarplaceFunnelEvent(data: {
|
|
eventName: string
|
|
companySlug: string
|
|
vehicleId: string
|
|
renterId?: string | null
|
|
sessionId?: string
|
|
path?: string
|
|
metadata?: Record<string, string | number | boolean | null>
|
|
}) {
|
|
const vehicle = await prisma.vehicle.findFirst({
|
|
where: {
|
|
id: data.vehicleId,
|
|
isPublished: true,
|
|
company: { status: { in: ['ACTIVE', 'TRIALING'] }, OR: [{ slug: data.companySlug }, { brand: { subdomain: data.companySlug } }] },
|
|
status: 'AVAILABLE',
|
|
},
|
|
select: { companyId: true },
|
|
})
|
|
|
|
return prisma.carplaceFunnelEvent.create({
|
|
data: {
|
|
eventName: data.eventName,
|
|
companyId: vehicle?.companyId ?? null,
|
|
companySlug: data.companySlug,
|
|
vehicleId: data.vehicleId,
|
|
renterId: data.renterId ?? null,
|
|
sessionId: data.sessionId,
|
|
path: data.path,
|
|
metadata: (data.metadata ?? {}) as any,
|
|
},
|
|
})
|
|
}
|
|
|
|
export async function findCompanyPage(slug: string) {
|
|
return prisma.company.findFirst({
|
|
where: { status: { in: ['ACTIVE', 'TRIALING'] }, OR: [{ slug }, { brand: { subdomain: slug } }] },
|
|
include: {
|
|
brand: true,
|
|
vehicles: { where: { isPublished: true, status: 'AVAILABLE' }, orderBy: { createdAt: 'desc' } },
|
|
offers: { where: { isPublic: true, isActive: true, validUntil: { gte: new Date() } }, orderBy: { isFeatured: 'desc' } },
|
|
},
|
|
})
|
|
}
|
|
|
|
export async function findCompanyBySlug(slug: string) {
|
|
return prisma.company.findFirstOrThrow({ where: { status: { in: ['ACTIVE', 'TRIALING'] }, OR: [{ slug }, { brand: { subdomain: slug } }] } })
|
|
}
|
|
|
|
export async function findCompanyReviews(companyId: string) {
|
|
return prisma.review.findMany({
|
|
where: { companyId, isPublished: true },
|
|
include: { renter: { select: { firstName: true, lastName: true } } },
|
|
orderBy: { createdAt: 'desc' },
|
|
take: 50,
|
|
})
|
|
}
|
|
|
|
export async function findCompanyVehicles(companyId: string) {
|
|
return prisma.vehicle.findMany({
|
|
where: { companyId, isPublished: true, status: 'AVAILABLE' },
|
|
orderBy: { createdAt: 'desc' },
|
|
})
|
|
}
|
|
|
|
export async function findVehicleById(slug: string, vehicleId: string) {
|
|
const company = await prisma.company.findFirstOrThrow({ where: { status: { in: ['ACTIVE', 'TRIALING'] }, OR: [{ slug }, { brand: { subdomain: slug } }] } })
|
|
return prisma.vehicle.findFirstOrThrow({
|
|
where: { id: vehicleId, companyId: company.id, isPublished: true, status: 'AVAILABLE' },
|
|
include: { company: { include: { brand: true } } },
|
|
})
|
|
}
|
|
|
|
export async function findCompanyOffers(companyId: string) {
|
|
return prisma.offer.findMany({
|
|
where: { companyId, isPublic: true, isActive: true, validFrom: { lte: new Date() }, validUntil: { gte: new Date() } },
|
|
orderBy: [{ isFeatured: 'desc' }, { createdAt: 'desc' }],
|
|
})
|
|
}
|
|
|
|
export async function findReservationByReviewToken(token: string) {
|
|
return prisma.reservation.findUnique({
|
|
where: { reviewToken: token },
|
|
include: {
|
|
vehicle: { select: { make: true, model: true, year: true, photos: true } },
|
|
company: { include: { brand: { select: { displayName: true, logoUrl: true } } } },
|
|
review: { select: { id: true } },
|
|
},
|
|
})
|
|
}
|
|
|
|
export async function findReservationForReviewSubmit(token: string) {
|
|
return prisma.reservation.findUnique({
|
|
where: { reviewToken: token },
|
|
include: { review: { select: { id: true } } },
|
|
})
|
|
}
|
|
|
|
export async function createReview(data: {
|
|
reservationId: string; companyId: string; renterId?: string | null
|
|
overallRating: number; vehicleRating?: number; serviceRating?: number; comment?: string
|
|
}) {
|
|
return prisma.review.create({
|
|
data: { ...data, renterId: data.renterId ?? undefined, isPublished: true },
|
|
})
|
|
}
|
|
|
|
export async function invalidateReviewToken(reservationId: string) {
|
|
return prisma.reservation.update({ where: { id: reservationId }, data: { reviewToken: null } })
|
|
}
|
|
|
|
export async function findOfferByCode(code: string) {
|
|
return prisma.offer.findFirst({
|
|
where: { promoCode: code, isActive: true, validFrom: { lte: new Date() }, validUntil: { gte: new Date() } },
|
|
})
|
|
}
|