import { assertAvailabilityRanges } from '@/lib/booking-utils'; import { ComplexAccessError, ensureComplexAccess } from '@/lib/complex-access'; import { Errors } from '@/lib/errors'; import { db } from '@/lib/prisma'; import { type Result, err, ok } from '@/lib/result'; import { evaluatePlanUsage, parsePlanRules } from '@/modules/plan/services/plan-rules.service'; import type { CreateCourtInput } from '@repo/api-contract'; import { v7 as uuidv7 } from 'uuid'; import { CourtBusinessError } from '../../shared/errors'; import { ensureActiveSport, getCourtByIdForUser } from '../../shared/guards'; import { mapCourtResponse } from '../../shared/mappers'; async function enforcePlanCourtLimit(complexId: string): Promise> { const complex = await db.complex.findUnique({ where: { id: complexId }, select: { plan: { select: { rules: true, }, }, }, }); if (!complex?.plan) { return ok(undefined); } const rules = parsePlanRules(complex.plan.rules); const courtsCount = await db.court.count({ where: { complexId }, }); const violations = evaluatePlanUsage(rules, { courtsCount, bookingsToday: 0, }); const maxCourtViolation = violations.find((violation) => violation.code === 'MAX_COURTS_REACHED'); if (maxCourtViolation) { return err(Errors.conflict(maxCourtViolation.message)); } return ok(undefined); } export async function createCourt( userId: string, complexId: string, input: CreateCourtInput ): Promise>> { try { await ensureComplexAccess(complexId, userId); } catch (error) { if (error instanceof ComplexAccessError) { return err(Errors.forbidden(error.message)); } throw error; } const sportResult = await ensureActiveSport(input.sportId); if (!sportResult.ok) return sportResult; const planResult = await enforcePlanCourtLimit(complexId); if (!planResult.ok) return planResult; assertAvailabilityRanges(input.availability); const createdCourt = await db.$transaction(async (tx) => { const court = await tx.court.create({ data: { id: uuidv7(), complexId, sportId: input.sportId, name: input.name.trim(), slotDurationMinutes: input.slotDurationMinutes, basePrice: input.basePrice, }, }); await tx.courtAvailability.createMany({ data: input.availability.map((availability) => ({ id: uuidv7(), courtId: court.id, dayOfWeek: availability.dayOfWeek, startTime: availability.startTime, endTime: availability.endTime, })), }); return court; }); const court = await getCourtByIdForUser(createdCourt.id, userId); if (!court) { return err(Errors.notFound('No se pudo obtener la cancha creada.')); } return ok(mapCourtResponse(court)); } export { CourtBusinessError };