Architecture: Six focused services with the BookingService/SchedulingService overlap resolved by giving SchedulingService exactly one job, the transactional double-booking guard , while BookingService owns the create/reschedule/cancel workflow around it. 59 unit tests, 24 integration tests
57 lines
2.4 KiB
TypeScript
57 lines
2.4 KiB
TypeScript
"use server";
|
|
|
|
import { revalidatePath } from "next/cache";
|
|
import { prisma } from "@/lib/db/prisma";
|
|
import * as BookingService from "@/services/scheduling/booking.service";
|
|
import { fail } from "@/services/scheduling/types";
|
|
import { cancelBookingSchema, createBookingSchema, rescheduleBookingSchema, updateBookingNotesSchema } from "../schemas";
|
|
|
|
export async function createBookingAction(input: unknown) {
|
|
const parsed = createBookingSchema.safeParse(input);
|
|
if (!parsed.success) return { success: false as const, validation: fail({ code: "INVALID_INPUT", message: parsed.error.message }) };
|
|
|
|
const result = await BookingService.createBooking(prisma, {
|
|
...parsed.data,
|
|
startAt: new Date(parsed.data.startAt),
|
|
});
|
|
if (result.success) revalidatePath("/dev/scheduling-test");
|
|
return result;
|
|
}
|
|
|
|
export async function rescheduleBookingAction(input: unknown) {
|
|
const parsed = rescheduleBookingSchema.safeParse(input);
|
|
if (!parsed.success) return { success: false as const, validation: fail({ code: "INVALID_INPUT", message: parsed.error.message }) };
|
|
|
|
const result = await BookingService.rescheduleBooking(prisma, {
|
|
...parsed.data,
|
|
startAt: new Date(parsed.data.startAt),
|
|
});
|
|
if (result.success) revalidatePath("/dev/scheduling-test");
|
|
return result;
|
|
}
|
|
|
|
export async function cancelBookingAction(input: unknown) {
|
|
const parsed = cancelBookingSchema.safeParse(input);
|
|
if (!parsed.success) return { success: false as const, validation: fail({ code: "INVALID_INPUT", message: parsed.error.message }) };
|
|
|
|
const result = await BookingService.cancelBooking(prisma, parsed.data);
|
|
if (result.success) revalidatePath("/dev/scheduling-test");
|
|
return result;
|
|
}
|
|
|
|
/** "Update booking" — notes only. Time/staff changes go through reschedule (a distinct, re-validated workflow), not this. */
|
|
export async function updateBookingNotesAction(input: unknown) {
|
|
const parsed = updateBookingNotesSchema.safeParse(input);
|
|
if (!parsed.success) {
|
|
return { success: false as const, validation: fail({ code: "INVALID_INPUT", message: parsed.error.message }) };
|
|
}
|
|
|
|
const appointment = await prisma.appointment.update({
|
|
where: { id: parsed.data.appointmentId },
|
|
data: { notes: parsed.data.notes },
|
|
include: { services: { include: { service: true } }, staff: { include: { user: true } }, customer: true },
|
|
});
|
|
revalidatePath("/dev/scheduling-test");
|
|
return { success: true as const, appointment };
|
|
}
|