bugfix: overlapped appointments now made possible. selection popup added for overlapped banners.

This commit is contained in:
2026-05-17 17:42:59 +03:30
parent e119d02759
commit 8278fd9012
6 changed files with 553 additions and 94 deletions

View File

@@ -1,9 +1,21 @@
import { Body, Controller, Delete, Get, Param, Post, Query, Req, UseGuards } from '@nestjs/common';
import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
Query,
Req,
UseGuards,
} from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { AppointmentsService } from './appointments.service';
import { CreateAppointmentDto } from './dto/create-appointment.dto';
import { ListAppointmentsDto } from './dto/list-appointments.dto';
import { UpdateAppointmentDto } from './dto/update-appointment.dto';
@ApiTags('appointments')
@ApiBearerAuth('JWT-auth')
@@ -39,6 +51,17 @@ export class AppointmentsController {
return this.appointmentsService.create(dto, organizationId, req.user.id);
}
@Patch(':id')
@ApiOperation({ summary: 'Update appointment (requires TAB_APPOINTMENTS_EDIT or owner)' })
update(
@Param('id') id: string,
@Body() dto: UpdateAppointmentDto,
@Req() req: { user: { id: string; organizationId?: string } },
) {
const organizationId = this.appointmentsService.getOrganizationIdFromUser(req.user);
return this.appointmentsService.update(id, dto, organizationId, req.user.id);
}
@Delete(':id')
@ApiOperation({ summary: 'Delete appointment (requires TAB_APPOINTMENTS_EDIT or owner)' })
remove(

View File

@@ -7,6 +7,7 @@ import {
import { PrismaService } from '../../../prisma/prisma.service';
import { CreateAppointmentDto } from './dto/create-appointment.dto';
import { ListAppointmentsDto } from './dto/list-appointments.dto';
import { UpdateAppointmentDto } from './dto/update-appointment.dto';
const MS_PER_DAY = 86_400_000;
@@ -109,20 +110,6 @@ export class AppointmentsService {
await this.ensurePatientInOrg(dto.patientId, organizationId);
await this.ensureProviderIsTreatmentEditor(dto.providerUserId, organizationId);
const overlap = await this.prisma.appointment.findFirst({
where: {
organizationId,
providerUserId: dto.providerUserId,
startAt: { lt: endAt },
endAt: { gt: startAt },
},
select: { id: true },
});
if (overlap) {
throw new BadRequestException('This time slot overlaps an existing appointment for that provider');
}
const appointment = await this.prisma.appointment.create({
data: {
organizationId,
@@ -142,6 +129,63 @@ export class AppointmentsService {
return { success: true, data: appointment };
}
async update(
id: string,
dto: UpdateAppointmentDto,
organizationId: string,
actorUserId: string,
) {
await this.assertCanEditAppointments(actorUserId, organizationId);
const existing = await this.prisma.appointment.findFirst({
where: { id, organizationId },
});
if (!existing) {
throw new NotFoundException('Appointment not found');
}
const startAt = dto.startAt ? new Date(dto.startAt) : existing.startAt;
const endAt = dto.endAt ? new Date(dto.endAt) : existing.endAt;
if (Number.isNaN(startAt.getTime()) || Number.isNaN(endAt.getTime())) {
throw new BadRequestException('Invalid start or end time');
}
if (endAt <= startAt) {
throw new BadRequestException('End time must be after start time');
}
if (endAt.getTime() - startAt.getTime() > MS_PER_DAY) {
throw new BadRequestException('Appointment cannot span more than 24 hours');
}
const patientId = dto.patientId ?? existing.patientId;
const providerUserId = dto.providerUserId ?? existing.providerUserId;
const purpose = dto.purpose ?? existing.purpose;
await this.ensurePatientInOrg(patientId, organizationId);
await this.ensureProviderIsTreatmentEditor(providerUserId, organizationId);
const appointment = await this.prisma.appointment.update({
where: { id },
data: {
patientId,
providerUserId,
startAt,
endAt,
purpose,
},
include: {
patient: {
select: { id: true, firstName: true, lastName: true, phone: true },
},
},
});
return { success: true, data: appointment };
}
async remove(id: string, organizationId: string, actorUserId: string) {
await this.assertCanEditAppointments(actorUserId, organizationId);

View File

@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/swagger';
import { CreateAppointmentDto } from './create-appointment.dto';
export class UpdateAppointmentDto extends PartialType(CreateAppointmentDto) {}