Added booking logs to keep track of cancelled bookings and no shows
This commit is contained in:
@@ -12,6 +12,7 @@ enum CourtBookingStatus {
|
|||||||
CONFIRMED
|
CONFIRMED
|
||||||
CANCELLED
|
CANCELLED
|
||||||
COMPLETED
|
COMPLETED
|
||||||
|
NOSHOW
|
||||||
}
|
}
|
||||||
|
|
||||||
model Sport {
|
model Sport {
|
||||||
@@ -94,3 +95,20 @@ model CourtBooking {
|
|||||||
@@index([bookingDate])
|
@@index([bookingDate])
|
||||||
@@map("court_bookings")
|
@@map("court_bookings")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model CourtBookingLog {
|
||||||
|
id String @id @db.Uuid
|
||||||
|
bookingCode String @map("booking_code") @db.VarChar(8)
|
||||||
|
courtId String @map("court_id") @db.Uuid
|
||||||
|
bookingDate DateTime @map("booking_date") @db.Date
|
||||||
|
startTime String @map("start_time") @db.VarChar(5)
|
||||||
|
endTime String @map("end_time") @db.VarChar(5)
|
||||||
|
previousStatus CourtBookingStatus @map("previous_status")
|
||||||
|
newStatus CourtBookingStatus @map("new_status")
|
||||||
|
customerName String @map("customer_name") @db.VarChar(120)
|
||||||
|
customerPhone String @map("customer_phone") @db.VarChar(30)
|
||||||
|
changedAt DateTime @default(now()) @map("changed_at")
|
||||||
|
|
||||||
|
@@map("court_booking_logs")
|
||||||
|
@@index([courtId, newStatus])
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "court_booking_logs" (
|
||||||
|
"id" UUID NOT NULL,
|
||||||
|
"booking_code" UUID NOT NULL,
|
||||||
|
"court_id" UUID NOT NULL,
|
||||||
|
"booking_date" DATE NOT NULL,
|
||||||
|
"start_time" VARCHAR(5) NOT NULL,
|
||||||
|
"end_time" VARCHAR(5) NOT NULL,
|
||||||
|
"previous_status" "CourtBookingStatus" NOT NULL,
|
||||||
|
"new_status" "CourtBookingStatus" NOT NULL,
|
||||||
|
"changed_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "court_booking_logs_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "court_booking_logs_court_id_new_status_idx" ON "court_booking_logs"("court_id", "new_status");
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- Added the required column `customer_name` to the `court_booking_logs` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `customer_phone` to the `court_booking_logs` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterEnum
|
||||||
|
ALTER TYPE "CourtBookingStatus" ADD VALUE 'NOSHOW';
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "court_booking_logs" ADD COLUMN "customer_name" VARCHAR(120) NOT NULL,
|
||||||
|
ADD COLUMN "customer_phone" VARCHAR(30) NOT NULL;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- Changed the type of `booking_code` on the `court_booking_logs` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "court_booking_logs" DROP COLUMN "booking_code",
|
||||||
|
ADD COLUMN "booking_code" VARCHAR(8) NOT NULL;
|
||||||
@@ -77,6 +77,11 @@ export type CourtPriceRule = Prisma.CourtPriceRuleModel
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export type CourtBooking = Prisma.CourtBookingModel
|
export type CourtBooking = Prisma.CourtBookingModel
|
||||||
|
/**
|
||||||
|
* Model CourtBookingLog
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type CourtBookingLog = Prisma.CourtBookingLogModel
|
||||||
/**
|
/**
|
||||||
* Model OnboardingRequest
|
* Model OnboardingRequest
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -101,6 +101,11 @@ export type CourtPriceRule = Prisma.CourtPriceRuleModel
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export type CourtBooking = Prisma.CourtBookingModel
|
export type CourtBooking = Prisma.CourtBookingModel
|
||||||
|
/**
|
||||||
|
* Model CourtBookingLog
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type CourtBookingLog = Prisma.CourtBookingLogModel
|
||||||
/**
|
/**
|
||||||
* Model OnboardingRequest
|
* Model OnboardingRequest
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ export type DayOfWeek = (typeof DayOfWeek)[keyof typeof DayOfWeek]
|
|||||||
export const CourtBookingStatus = {
|
export const CourtBookingStatus = {
|
||||||
CONFIRMED: 'CONFIRMED',
|
CONFIRMED: 'CONFIRMED',
|
||||||
CANCELLED: 'CANCELLED',
|
CANCELLED: 'CANCELLED',
|
||||||
COMPLETED: 'COMPLETED'
|
COMPLETED: 'COMPLETED',
|
||||||
|
NOSHOW: 'NOSHOW'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type CourtBookingStatus = (typeof CourtBookingStatus)[keyof typeof CourtBookingStatus]
|
export type CourtBookingStatus = (typeof CourtBookingStatus)[keyof typeof CourtBookingStatus]
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -396,6 +396,7 @@ export const ModelName = {
|
|||||||
CourtAvailability: 'CourtAvailability',
|
CourtAvailability: 'CourtAvailability',
|
||||||
CourtPriceRule: 'CourtPriceRule',
|
CourtPriceRule: 'CourtPriceRule',
|
||||||
CourtBooking: 'CourtBooking',
|
CourtBooking: 'CourtBooking',
|
||||||
|
CourtBookingLog: 'CourtBookingLog',
|
||||||
OnboardingRequest: 'OnboardingRequest',
|
OnboardingRequest: 'OnboardingRequest',
|
||||||
PasswordResetRequest: 'PasswordResetRequest',
|
PasswordResetRequest: 'PasswordResetRequest',
|
||||||
Plan: 'Plan'
|
Plan: 'Plan'
|
||||||
@@ -414,7 +415,7 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
|||||||
omit: GlobalOmitOptions
|
omit: GlobalOmitOptions
|
||||||
}
|
}
|
||||||
meta: {
|
meta: {
|
||||||
modelProps: "user" | "session" | "account" | "verification" | "complex" | "complexUser" | "complexInvitation" | "sport" | "court" | "courtAvailability" | "courtPriceRule" | "courtBooking" | "onboardingRequest" | "passwordResetRequest" | "plan"
|
modelProps: "user" | "session" | "account" | "verification" | "complex" | "complexUser" | "complexInvitation" | "sport" | "court" | "courtAvailability" | "courtPriceRule" | "courtBooking" | "courtBookingLog" | "onboardingRequest" | "passwordResetRequest" | "plan"
|
||||||
txIsolationLevel: TransactionIsolationLevel
|
txIsolationLevel: TransactionIsolationLevel
|
||||||
}
|
}
|
||||||
model: {
|
model: {
|
||||||
@@ -1306,6 +1307,80 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
CourtBookingLog: {
|
||||||
|
payload: Prisma.$CourtBookingLogPayload<ExtArgs>
|
||||||
|
fields: Prisma.CourtBookingLogFieldRefs
|
||||||
|
operations: {
|
||||||
|
findUnique: {
|
||||||
|
args: Prisma.CourtBookingLogFindUniqueArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtBookingLogPayload> | null
|
||||||
|
}
|
||||||
|
findUniqueOrThrow: {
|
||||||
|
args: Prisma.CourtBookingLogFindUniqueOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtBookingLogPayload>
|
||||||
|
}
|
||||||
|
findFirst: {
|
||||||
|
args: Prisma.CourtBookingLogFindFirstArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtBookingLogPayload> | null
|
||||||
|
}
|
||||||
|
findFirstOrThrow: {
|
||||||
|
args: Prisma.CourtBookingLogFindFirstOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtBookingLogPayload>
|
||||||
|
}
|
||||||
|
findMany: {
|
||||||
|
args: Prisma.CourtBookingLogFindManyArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtBookingLogPayload>[]
|
||||||
|
}
|
||||||
|
create: {
|
||||||
|
args: Prisma.CourtBookingLogCreateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtBookingLogPayload>
|
||||||
|
}
|
||||||
|
createMany: {
|
||||||
|
args: Prisma.CourtBookingLogCreateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
createManyAndReturn: {
|
||||||
|
args: Prisma.CourtBookingLogCreateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtBookingLogPayload>[]
|
||||||
|
}
|
||||||
|
delete: {
|
||||||
|
args: Prisma.CourtBookingLogDeleteArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtBookingLogPayload>
|
||||||
|
}
|
||||||
|
update: {
|
||||||
|
args: Prisma.CourtBookingLogUpdateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtBookingLogPayload>
|
||||||
|
}
|
||||||
|
deleteMany: {
|
||||||
|
args: Prisma.CourtBookingLogDeleteManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateMany: {
|
||||||
|
args: Prisma.CourtBookingLogUpdateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateManyAndReturn: {
|
||||||
|
args: Prisma.CourtBookingLogUpdateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtBookingLogPayload>[]
|
||||||
|
}
|
||||||
|
upsert: {
|
||||||
|
args: Prisma.CourtBookingLogUpsertArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$CourtBookingLogPayload>
|
||||||
|
}
|
||||||
|
aggregate: {
|
||||||
|
args: Prisma.CourtBookingLogAggregateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.AggregateCourtBookingLog>
|
||||||
|
}
|
||||||
|
groupBy: {
|
||||||
|
args: Prisma.CourtBookingLogGroupByArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.CourtBookingLogGroupByOutputType>[]
|
||||||
|
}
|
||||||
|
count: {
|
||||||
|
args: Prisma.CourtBookingLogCountArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.CourtBookingLogCountAggregateOutputType> | number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
OnboardingRequest: {
|
OnboardingRequest: {
|
||||||
payload: Prisma.$OnboardingRequestPayload<ExtArgs>
|
payload: Prisma.$OnboardingRequestPayload<ExtArgs>
|
||||||
fields: Prisma.OnboardingRequestFieldRefs
|
fields: Prisma.OnboardingRequestFieldRefs
|
||||||
@@ -1738,6 +1813,23 @@ export const CourtBookingScalarFieldEnum = {
|
|||||||
export type CourtBookingScalarFieldEnum = (typeof CourtBookingScalarFieldEnum)[keyof typeof CourtBookingScalarFieldEnum]
|
export type CourtBookingScalarFieldEnum = (typeof CourtBookingScalarFieldEnum)[keyof typeof CourtBookingScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const CourtBookingLogScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
bookingCode: 'bookingCode',
|
||||||
|
courtId: 'courtId',
|
||||||
|
bookingDate: 'bookingDate',
|
||||||
|
startTime: 'startTime',
|
||||||
|
endTime: 'endTime',
|
||||||
|
previousStatus: 'previousStatus',
|
||||||
|
newStatus: 'newStatus',
|
||||||
|
customerName: 'customerName',
|
||||||
|
customerPhone: 'customerPhone',
|
||||||
|
changedAt: 'changedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type CourtBookingLogScalarFieldEnum = (typeof CourtBookingLogScalarFieldEnum)[keyof typeof CourtBookingLogScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
export const OnboardingRequestScalarFieldEnum = {
|
export const OnboardingRequestScalarFieldEnum = {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
fullName: 'fullName',
|
fullName: 'fullName',
|
||||||
@@ -2068,6 +2160,7 @@ export type GlobalOmitConfig = {
|
|||||||
courtAvailability?: Prisma.CourtAvailabilityOmit
|
courtAvailability?: Prisma.CourtAvailabilityOmit
|
||||||
courtPriceRule?: Prisma.CourtPriceRuleOmit
|
courtPriceRule?: Prisma.CourtPriceRuleOmit
|
||||||
courtBooking?: Prisma.CourtBookingOmit
|
courtBooking?: Prisma.CourtBookingOmit
|
||||||
|
courtBookingLog?: Prisma.CourtBookingLogOmit
|
||||||
onboardingRequest?: Prisma.OnboardingRequestOmit
|
onboardingRequest?: Prisma.OnboardingRequestOmit
|
||||||
passwordResetRequest?: Prisma.PasswordResetRequestOmit
|
passwordResetRequest?: Prisma.PasswordResetRequestOmit
|
||||||
plan?: Prisma.PlanOmit
|
plan?: Prisma.PlanOmit
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export const ModelName = {
|
|||||||
CourtAvailability: 'CourtAvailability',
|
CourtAvailability: 'CourtAvailability',
|
||||||
CourtPriceRule: 'CourtPriceRule',
|
CourtPriceRule: 'CourtPriceRule',
|
||||||
CourtBooking: 'CourtBooking',
|
CourtBooking: 'CourtBooking',
|
||||||
|
CourtBookingLog: 'CourtBookingLog',
|
||||||
OnboardingRequest: 'OnboardingRequest',
|
OnboardingRequest: 'OnboardingRequest',
|
||||||
PasswordResetRequest: 'PasswordResetRequest',
|
PasswordResetRequest: 'PasswordResetRequest',
|
||||||
Plan: 'Plan'
|
Plan: 'Plan'
|
||||||
@@ -255,6 +256,23 @@ export const CourtBookingScalarFieldEnum = {
|
|||||||
export type CourtBookingScalarFieldEnum = (typeof CourtBookingScalarFieldEnum)[keyof typeof CourtBookingScalarFieldEnum]
|
export type CourtBookingScalarFieldEnum = (typeof CourtBookingScalarFieldEnum)[keyof typeof CourtBookingScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const CourtBookingLogScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
bookingCode: 'bookingCode',
|
||||||
|
courtId: 'courtId',
|
||||||
|
bookingDate: 'bookingDate',
|
||||||
|
startTime: 'startTime',
|
||||||
|
endTime: 'endTime',
|
||||||
|
previousStatus: 'previousStatus',
|
||||||
|
newStatus: 'newStatus',
|
||||||
|
customerName: 'customerName',
|
||||||
|
customerPhone: 'customerPhone',
|
||||||
|
changedAt: 'changedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type CourtBookingLogScalarFieldEnum = (typeof CourtBookingLogScalarFieldEnum)[keyof typeof CourtBookingLogScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
export const OnboardingRequestScalarFieldEnum = {
|
export const OnboardingRequestScalarFieldEnum = {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
fullName: 'fullName',
|
fullName: 'fullName',
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export type * from './models/Court'
|
|||||||
export type * from './models/CourtAvailability'
|
export type * from './models/CourtAvailability'
|
||||||
export type * from './models/CourtPriceRule'
|
export type * from './models/CourtPriceRule'
|
||||||
export type * from './models/CourtBooking'
|
export type * from './models/CourtBooking'
|
||||||
|
export type * from './models/CourtBookingLog'
|
||||||
export type * from './models/OnboardingRequest'
|
export type * from './models/OnboardingRequest'
|
||||||
export type * from './models/PasswordResetRequest'
|
export type * from './models/PasswordResetRequest'
|
||||||
export type * from './models/Plan'
|
export type * from './models/Plan'
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ function mapBookingResponse(booking: {
|
|||||||
endTime: string;
|
endTime: string;
|
||||||
customerName: string;
|
customerName: string;
|
||||||
customerPhone: string;
|
customerPhone: string;
|
||||||
status: 'CONFIRMED' | 'CANCELLED' | 'COMPLETED';
|
status: 'CONFIRMED' | 'CANCELLED' | 'COMPLETED' | 'NOSHOW';
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
court: {
|
court: {
|
||||||
@@ -494,34 +494,42 @@ export async function updateAdminBookingStatus(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const updated = await db.courtBooking.update({
|
if(input.status === 'NOSHOW' && booking.status !== 'CONFIRMED') {
|
||||||
where: { id: booking.id },
|
throw new AdminBookingServiceError(
|
||||||
|
'Solo se pueden marcar como no show las reservas confirmadas.',
|
||||||
|
409
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.courtBookingLog.create({
|
||||||
data: {
|
data: {
|
||||||
status:
|
id: uuidv7(),
|
||||||
input.status === 'COMPLETED' ? CourtBookingStatus.COMPLETED : CourtBookingStatus.CANCELLED,
|
bookingCode: booking.bookingCode,
|
||||||
},
|
courtId: booking.court.id,
|
||||||
include: {
|
bookingDate: booking.bookingDate,
|
||||||
court: {
|
startTime: booking.startTime,
|
||||||
select: {
|
endTime: booking.endTime,
|
||||||
id: true,
|
customerName: booking.customerName,
|
||||||
name: true,
|
customerPhone: booking.customerPhone,
|
||||||
sport: {
|
previousStatus: booking.status,
|
||||||
select: {
|
newStatus: input.status,
|
||||||
id: true,
|
changedAt: new Date(),
|
||||||
name: true,
|
|
||||||
slug: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
complex: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
complexName: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return mapBookingResponse(updated);
|
if(input.status === 'COMPLETED' || input.status ==='NOSHOW') {
|
||||||
|
await db.courtBooking.update({
|
||||||
|
where: { id: booking.id },
|
||||||
|
data: {
|
||||||
|
status: input.status,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// if the booking is cancelled we delete it to free up the slot, but we keep a log of it with the cancelled status
|
||||||
|
await db.courtBooking.delete({
|
||||||
|
where: { id: booking.id },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapBookingResponse({...booking, status: input.status });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const TIME_REGEX = /^([01]\d|2[0-3]):([0-5]\d)$/
|
|||||||
const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/
|
const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/
|
||||||
const BOOKING_CODE_REGEX = /^[A-Z0-9]{6,8}$/
|
const BOOKING_CODE_REGEX = /^[A-Z0-9]{6,8}$/
|
||||||
|
|
||||||
export const bookingStatusSchema = z.enum(['CONFIRMED', 'CANCELLED', 'COMPLETED'])
|
export const bookingStatusSchema = z.enum(['CONFIRMED', 'CANCELLED', 'COMPLETED', 'NOSHOW'])
|
||||||
|
|
||||||
export const adminBookingSportSchema = z.object({
|
export const adminBookingSportSchema = z.object({
|
||||||
id: z.uuid(),
|
id: z.uuid(),
|
||||||
|
|||||||
Reference in New Issue
Block a user