mirror of
https://github.com/tomru/pfadi-bussle.git
synced 2026-03-03 06:27:11 +01:00
switch to prisma
This commit is contained in:
91
db/bill.ts
91
db/bill.ts
@@ -1,91 +0,0 @@
|
||||
import * as mongoose from 'mongoose'
|
||||
import { BILL_STATUS, MILAGE_TARIFS } from './enums'
|
||||
import { getBillTotal } from '../helpers/bill'
|
||||
|
||||
export type AdditionalCost = {
|
||||
name: string
|
||||
value: number
|
||||
}
|
||||
|
||||
export type Bill = {
|
||||
milageStart: number
|
||||
milageEnd: number
|
||||
milage?: number
|
||||
tarif: MILAGE_TARIFS
|
||||
status: BILL_STATUS
|
||||
additionalCosts: AdditionalCost[]
|
||||
}
|
||||
|
||||
export type BillDocument = Bill &
|
||||
mongoose.SchemaTimestampsConfig &
|
||||
mongoose.Document
|
||||
|
||||
export type BillModel = mongoose.Model<BillDocument>
|
||||
|
||||
const BillSchema = new mongoose.Schema<BillDocument>(
|
||||
{
|
||||
milageStart: {
|
||||
type: Number,
|
||||
required: true,
|
||||
validate: {
|
||||
validator: function (v: number): boolean {
|
||||
const bill = this as BillDocument
|
||||
|
||||
return v <= bill.milageEnd
|
||||
},
|
||||
message: (props: { value: Number }) =>
|
||||
`${props.value} is bigger than milageEnd!`,
|
||||
},
|
||||
},
|
||||
milageEnd: {
|
||||
type: Number,
|
||||
required: true,
|
||||
|
||||
validate: {
|
||||
validator: function (v: number): boolean {
|
||||
const bill = this as BillDocument
|
||||
|
||||
return v >= bill.milageStart
|
||||
},
|
||||
message: (props: { value: Number }) =>
|
||||
`${props.value} is smaller than milageStart!`,
|
||||
},
|
||||
},
|
||||
tarif: {
|
||||
type: String,
|
||||
enum: Object.values(MILAGE_TARIFS),
|
||||
default: MILAGE_TARIFS.EXTERN,
|
||||
required: true,
|
||||
},
|
||||
additionalCosts: [
|
||||
{
|
||||
name: { type: String, required: true },
|
||||
value: { type: Number, required: true },
|
||||
},
|
||||
],
|
||||
status: {
|
||||
type: String,
|
||||
enum: Object.values(BILL_STATUS),
|
||||
default: BILL_STATUS.UNINVOICED,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
toJSON: { virtuals: true, getters: true },
|
||||
toObject: { virtuals: true, getters: true },
|
||||
}
|
||||
)
|
||||
|
||||
BillSchema.virtual('milage').get(function (): number {
|
||||
const bill = this as BillDocument
|
||||
return bill.milageEnd - bill.milageStart
|
||||
})
|
||||
|
||||
BillSchema.virtual('total').get(function (): number {
|
||||
const bill = this as BillDocument
|
||||
|
||||
return getBillTotal(bill)
|
||||
})
|
||||
|
||||
export default (mongoose.models.Bill ||
|
||||
mongoose.model<BillDocument, BillModel>('Bill', BillSchema)) as BillModel
|
||||
168
db/booking.ts
168
db/booking.ts
@@ -1,168 +0,0 @@
|
||||
import * as mongoose from 'mongoose'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import {
|
||||
dateFormatBackend,
|
||||
getDays,
|
||||
nowInTz,
|
||||
dateParseBackend,
|
||||
} from '../helpers/date'
|
||||
import { createCalendarEvent, deleteCalendarEvent } from '../lib/googlecalendar'
|
||||
|
||||
import { Bill } from './bill'
|
||||
import { BOOKING_STATUS, VALIDATION_ERRORS } from './enums'
|
||||
|
||||
export type Booking = {
|
||||
uuid: string
|
||||
name: string
|
||||
email: string
|
||||
phone: string
|
||||
street: string
|
||||
zip: string
|
||||
city: string
|
||||
bill?: Bill
|
||||
// format YYYY-MM-DD
|
||||
startDate: string
|
||||
// format YYYY-MM-DD
|
||||
endDate: string
|
||||
status?: BOOKING_STATUS
|
||||
purpose?: string
|
||||
org?: string
|
||||
destination?: string
|
||||
days?: string[]
|
||||
calendarEventId: string
|
||||
}
|
||||
|
||||
export type BookingDocument = Booking &
|
||||
mongoose.Document &
|
||||
mongoose.SchemaTimestampsConfig
|
||||
|
||||
export type BookingModel = mongoose.Model<BookingDocument> & {
|
||||
findBookedDays(uuidsToIngore?: string[]): Promise<string[]>
|
||||
}
|
||||
|
||||
const BookingSchema = new mongoose.Schema<BookingDocument>(
|
||||
{
|
||||
// need a seperate uuid to be able to target a booking anonimously
|
||||
uuid: {
|
||||
type: String,
|
||||
default: uuidv4,
|
||||
index: true,
|
||||
},
|
||||
name: { type: String, required: true },
|
||||
email: { type: String, required: true, minlength: 5 },
|
||||
phone: { type: String, required: false },
|
||||
street: { type: String, required: true },
|
||||
zip: { type: String, required: true },
|
||||
city: { type: String, required: true },
|
||||
bill: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'Bill',
|
||||
required: false,
|
||||
},
|
||||
startDate: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator: function (value: string): boolean {
|
||||
return !!dateParseBackend(value)
|
||||
},
|
||||
},
|
||||
endDate: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator: function (value: string): boolean {
|
||||
return !!dateParseBackend(value)
|
||||
},
|
||||
},
|
||||
days: {
|
||||
type: [String],
|
||||
required: true,
|
||||
validate: {
|
||||
validator: async function (days: string[]): Promise<boolean> {
|
||||
const booking = this as Booking
|
||||
const uuid = booking.uuid && [booking.uuid]
|
||||
const bookedDays = await BookingModel.findBookedDays(uuid)
|
||||
|
||||
const doubleBookedDays = days.filter((day: string): boolean =>
|
||||
bookedDays.includes(day)
|
||||
)
|
||||
return doubleBookedDays.length === 0
|
||||
},
|
||||
message: (props: { value: string[] }): string =>
|
||||
`At least one day is of ${props.value.join(',')} is already booked`,
|
||||
type: VALIDATION_ERRORS.AT_LEAST_ONE_DAY_BOOKED,
|
||||
},
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: Object.values(BOOKING_STATUS),
|
||||
required: true,
|
||||
default: BOOKING_STATUS.REQUESTED,
|
||||
},
|
||||
purpose: { type: String, required: false },
|
||||
org: { type: String, required: false },
|
||||
destination: { type: String, required: false },
|
||||
calendarEventId: { type: String, required: false },
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
toJSON: { virtuals: true, getters: true },
|
||||
toObject: { virtuals: true, getters: true },
|
||||
}
|
||||
)
|
||||
|
||||
BookingSchema.pre('validate', function (next: () => void): void {
|
||||
const booking = this as BookingDocument
|
||||
booking.days = getDays({
|
||||
startDate: new Date(booking.startDate),
|
||||
endDate: new Date(booking.endDate),
|
||||
})
|
||||
next()
|
||||
})
|
||||
|
||||
BookingSchema.pre('save', async function (next: () => void): Promise<void> {
|
||||
const booking = this as BookingDocument
|
||||
|
||||
if (!booking.calendarEventId) {
|
||||
// create calendar event before saving to database
|
||||
await createCalendarEvent(booking)
|
||||
} else if (
|
||||
[BOOKING_STATUS.CANCELED, BOOKING_STATUS.REJECTED].includes(booking.status)
|
||||
) {
|
||||
// event has been canceled or rejected, delete calendar event again to free up the slot
|
||||
await deleteCalendarEvent(booking)
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
|
||||
BookingSchema.static(
|
||||
'findBookedDays',
|
||||
async function (uuidsToIngore: string[] = []): Promise<string[]> {
|
||||
const model = this as BookingModel
|
||||
const now = nowInTz()
|
||||
const bookedDays = await model
|
||||
.find(
|
||||
{
|
||||
status: { $in: [BOOKING_STATUS.REQUESTED, BOOKING_STATUS.CONFIRMED] },
|
||||
uuid: { $nin: uuidsToIngore },
|
||||
// dateFormatBackend uses YYYY-MM-DD, which is startOfDay anyway
|
||||
endDate: { $gt: dateFormatBackend(now) },
|
||||
},
|
||||
'days'
|
||||
)
|
||||
.exec()
|
||||
|
||||
return bookedDays
|
||||
.map((b) => b.days)
|
||||
.flat()
|
||||
.sort()
|
||||
}
|
||||
)
|
||||
|
||||
const BookingModel = (mongoose.models.Booking ||
|
||||
mongoose.model<BookingDocument, BookingModel>(
|
||||
'Booking',
|
||||
BookingSchema
|
||||
)) as BookingModel
|
||||
|
||||
export default BookingModel
|
||||
13
db/enums.ts
13
db/enums.ts
@@ -1,16 +1,3 @@
|
||||
export enum BOOKING_STATUS {
|
||||
REQUESTED = 'requested',
|
||||
CONFIRMED = 'confirmed',
|
||||
REJECTED = 'rejected',
|
||||
CANCELED = 'canceled',
|
||||
}
|
||||
|
||||
export enum BILL_STATUS {
|
||||
UNINVOICED = 'uninvoiced',
|
||||
INVOICED = 'invoiced',
|
||||
PAID = 'paid',
|
||||
}
|
||||
|
||||
export enum MILAGE_TARIFS {
|
||||
INTERN = 'intern',
|
||||
EXTERN = 'extern',
|
||||
|
||||
200
db/index.ts
200
db/index.ts
@@ -1,129 +1,121 @@
|
||||
import * as mongoose from 'mongoose'
|
||||
import BookingModel, { Booking, BookingDocument } from './booking'
|
||||
import BillModel, { Bill } from './bill'
|
||||
import { BookingStatus, Booking, PrismaClient, Prisma } from '@prisma/client'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
import { getBookedDays as calendarGetBookedDays } from '../lib/googlecalendar'
|
||||
import { BOOKING_STATUS } from './enums'
|
||||
import { uniqueFilter } from '../helpers/array'
|
||||
|
||||
export const MONGO_URI = process.env.MONGO_URI
|
||||
|
||||
mongoose.connect(process.env.MONGO_URI, {
|
||||
serverSelectionTimeoutMS: 3000,
|
||||
})
|
||||
import { dateFormatBackend, getDays, nowInTz } from '../helpers/date'
|
||||
|
||||
export async function getBookedDays(
|
||||
uuidsToIngore?: string[]
|
||||
): Promise<string[]> {
|
||||
const [bookedInDatabase, bookedInCalendar] = await Promise.all([
|
||||
BookingModel.findBookedDays(uuidsToIngore),
|
||||
) {
|
||||
const [bookingsInDbRaw, bookingsInCalendar] = await Promise.all([
|
||||
prisma.booking.findMany({
|
||||
where: {
|
||||
uuid: { notIn: uuidsToIngore },
|
||||
startDate: { gte: dateFormatBackend(nowInTz()) },
|
||||
status: { notIn: [BookingStatus.REJECTED, BookingStatus.CANCELED] }
|
||||
},
|
||||
select: {
|
||||
startDate: true,
|
||||
endDate: true,
|
||||
},
|
||||
}),
|
||||
calendarGetBookedDays(),
|
||||
])
|
||||
return [...bookedInDatabase, ...bookedInCalendar].filter(uniqueFilter).sort()
|
||||
|
||||
const bookingsInDb = bookingsInDbRaw.map(booking => getDays(booking)).flat();
|
||||
|
||||
return [...bookingsInDb, ...bookingsInCalendar].filter(uniqueFilter).sort()
|
||||
}
|
||||
|
||||
export async function getBookingByUUID(uuid: string): Promise<BookingDocument> {
|
||||
return BookingModel.findOne({ uuid })
|
||||
export function getBookingByUUID(uuid: string) {
|
||||
// TODO: can we ignore canceled and rejected ones ?
|
||||
return prisma.booking.findUniqueOrThrow({
|
||||
where: {
|
||||
uuid,
|
||||
},
|
||||
include: {
|
||||
bill: true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function getBookings({
|
||||
status = [BOOKING_STATUS.CONFIRMED, BOOKING_STATUS.REQUESTED],
|
||||
export function getBookings({
|
||||
status = [
|
||||
BookingStatus.REQUESTED,
|
||||
BookingStatus.CONFIRMED,
|
||||
],
|
||||
startDateGreaterThan = '2000-01-01T00:00:00Z',
|
||||
}: { status?: BOOKING_STATUS[]; startDateGreaterThan?: string } = {}): Promise<
|
||||
BookingDocument[]
|
||||
> {
|
||||
return await BookingModel.find({
|
||||
status: { $in: status },
|
||||
startDate: { $gte: startDateGreaterThan },
|
||||
})
|
||||
.sort({ startDate: -1 })
|
||||
.exec()
|
||||
}
|
||||
: { status?: BookingStatus[]; startDateGreaterThan?: string } = {}) {
|
||||
return prisma.booking.findMany({
|
||||
where: {
|
||||
startDate: { gte: startDateGreaterThan },
|
||||
status: { notIn: status }
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function createBooking({
|
||||
startDate,
|
||||
endDate,
|
||||
purpose,
|
||||
org,
|
||||
destination,
|
||||
name,
|
||||
email,
|
||||
phone,
|
||||
street,
|
||||
zip,
|
||||
city,
|
||||
}: Booking): Promise<Booking> {
|
||||
const booking = new BookingModel({
|
||||
startDate,
|
||||
endDate,
|
||||
purpose,
|
||||
org,
|
||||
destination,
|
||||
name,
|
||||
email,
|
||||
phone,
|
||||
street,
|
||||
zip,
|
||||
city,
|
||||
})
|
||||
|
||||
await booking.save()
|
||||
return booking.toJSON<Booking>()
|
||||
export function createBooking(data: Booking) {
|
||||
return prisma.booking.create({
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
export async function patchBooking(
|
||||
uuid: string,
|
||||
data: Prisma.BookingCreateInput
|
||||
) {
|
||||
const current = await prisma.booking.update({
|
||||
where: { uuid },
|
||||
data
|
||||
});
|
||||
const previous = { ...current, ...data }
|
||||
return { current, previous }
|
||||
|
||||
}
|
||||
|
||||
export function createBill(
|
||||
bookingUUID: string,
|
||||
bookingData: Booking
|
||||
): Promise<{ current: Booking; previous: Booking }> {
|
||||
const booking = await getBookingByUUID(bookingUUID)
|
||||
const oldBooking = booking.toJSON<Booking>()
|
||||
booking.set(bookingData)
|
||||
await booking.save()
|
||||
|
||||
return { current: booking.toJSON<Booking>(), previous: oldBooking }
|
||||
data: Prisma.BillCreateInput,
|
||||
) {
|
||||
return prisma.bill.create({
|
||||
data: {
|
||||
...data,
|
||||
booking: {
|
||||
connect: { uuid: bookingUUID }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function createBill(
|
||||
export function patchBill(
|
||||
bookingUUID: string,
|
||||
billData: Bill
|
||||
): Promise<Bill> {
|
||||
const booking = await getBookingByUUID(bookingUUID)
|
||||
|
||||
const bill = new BillModel()
|
||||
bill.set(billData)
|
||||
|
||||
await bill.save()
|
||||
|
||||
booking.bill = bill._id
|
||||
await booking.save()
|
||||
|
||||
return bill.toJSON<Bill>()
|
||||
data: Prisma.BillUncheckedUpdateInput,
|
||||
) {
|
||||
const { id, ...rest } = data;
|
||||
return prisma.bill.update({
|
||||
where: {
|
||||
},
|
||||
data: {
|
||||
...rest,
|
||||
booking: {
|
||||
connect: { uuid: bookingUUID }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function patchBill(
|
||||
bookingUUID: string,
|
||||
billData: Bill
|
||||
): Promise<Bill> {
|
||||
const booking = await getBookingByUUID(bookingUUID)
|
||||
const bill =
|
||||
(booking.bill && (await BillModel.findById(booking.bill))) ||
|
||||
(await BillModel.create({}))
|
||||
export async function getMilageMax() {
|
||||
const { milageEnd } = await prisma.bill.findFirst({
|
||||
select: { milageEnd: true },
|
||||
orderBy: [
|
||||
{
|
||||
milageEnd: 'desc',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
bill.set(billData)
|
||||
await bill.save()
|
||||
|
||||
if (booking.bill !== bill._id) {
|
||||
booking.bill = bill._id
|
||||
await booking.save()
|
||||
}
|
||||
|
||||
return bill.toJSON<Bill>()
|
||||
}
|
||||
|
||||
export async function getMilageMax(): Promise<number> {
|
||||
const billMaxMilageEnd = await BillModel.findOne({})
|
||||
.sort('-milageEnd')
|
||||
.select('milageEnd')
|
||||
.exec()
|
||||
|
||||
return billMaxMilageEnd?.milageEnd || 0
|
||||
return milageEnd || 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user