feat(services): add POST /services/block endpoint for slot blocking
- BlockSlotDto: validates day (ISO date) and range1_hour1 (HH:MM) - ServicesService.blockSlot(): finds professional by userId, checks for conflicts (any non-cancelled/denied service at that slot), then creates a service with status=self_booked using the professional's own user_id - ServicesController: POST /services/block (JWT-guarded) wired to blockSlot Existing PATCH /services/:id/status → cancelled handles unblocking since VALID_TRANSITIONS already allows self_booked → cancelled. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
c8b2c0c901
commit
8a97b52eb7
@@ -64,3 +64,11 @@ export class UpdateServiceStatusDto {
|
||||
@IsEnum(ServiceStatus)
|
||||
status: ServiceStatus;
|
||||
}
|
||||
|
||||
export class BlockSlotDto {
|
||||
@IsDateString()
|
||||
day: string;
|
||||
|
||||
@Matches(/^\d{2}:\d{2}$/)
|
||||
range1_hour1: string;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Controller, Get, Post, Patch, Param, Body, UseGuards, Req, Query } from
|
||||
import { ApiTags, ApiBearerAuth, ApiQuery } from '@nestjs/swagger';
|
||||
import { ServicesService } from './services.service';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { CreateServiceDto, UpdateServiceStatusDto } from './dto/service.dto';
|
||||
import { CreateServiceDto, UpdateServiceStatusDto, BlockSlotDto } from './dto/service.dto';
|
||||
|
||||
@ApiTags('Services')
|
||||
@Controller('services')
|
||||
@@ -16,6 +16,13 @@ export class ServicesController {
|
||||
return this.services.create({ ...dto, user_id: req.user.sub });
|
||||
}
|
||||
|
||||
@Post('block')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
blockSlot(@Req() req, @Body() dto: BlockSlotDto) {
|
||||
return this.services.blockSlot(req.user.sub, dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
|
||||
@@ -150,6 +150,32 @@ export class ServicesService {
|
||||
return updated;
|
||||
}
|
||||
|
||||
async blockSlot(userId: string, data: { day: string; range1_hour1: string }) {
|
||||
const prof = await this.prisma.professionals.findUnique({ where: { user_id: userId } });
|
||||
if (!prof) throw new NotFoundException('No eres un profesional');
|
||||
|
||||
const slotTime = new Date(`1970-01-01T${data.range1_hour1}:00`);
|
||||
const conflict = await this.prisma.services.findFirst({
|
||||
where: {
|
||||
professional_id: prof.id,
|
||||
day: new Date(data.day),
|
||||
range1_hour1: slotTime,
|
||||
status: { notIn: ['denied', 'cancelled'] },
|
||||
},
|
||||
});
|
||||
if (conflict) throw new BadRequestException('Este horario ya está ocupado o bloqueado');
|
||||
|
||||
return this.prisma.services.create({
|
||||
data: {
|
||||
professional_id: prof.id,
|
||||
user_id: prof.user_id,
|
||||
day: new Date(data.day),
|
||||
range1_hour1: slotTime,
|
||||
status: 'self_booked' as any,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findByUser(userId: string, page = 1, limit = 20) {
|
||||
const skip = (page - 1) * limit;
|
||||
const where = { user_id: userId, status: { notIn: ['completed' as const, 'cancelled' as const, 'denied' as const] } };
|
||||
|
||||
Reference in New Issue
Block a user