Files
playzer/apps/backend/src/modules/court/features/create-court/create-court.business.ts
2026-06-26 14:05:37 -03:00

105 lines
2.9 KiB
TypeScript

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<Result<void>> {
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<Result<ReturnType<typeof mapCourtResponse>>> {
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 };