feature: users with treatment edit permission should now have working hours defined. the appointment grid is now being drawn based on the doctor's working hours.
This commit is contained in:
@@ -5,6 +5,12 @@ import {
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { PrismaService } from '../../../prisma/prisma.service';
|
||||
import {
|
||||
appointmentWithinWorkingHours,
|
||||
blocksForDay,
|
||||
localDayOfWeekMondayZero,
|
||||
} from '../../common/working-hours';
|
||||
import { StaffWorkingHoursService } from '../staff/staff-working-hours.service';
|
||||
import { CreateAppointmentDto } from './dto/create-appointment.dto';
|
||||
import { ListAppointmentsDto } from './dto/list-appointments.dto';
|
||||
import { UpdateAppointmentDto } from './dto/update-appointment.dto';
|
||||
@@ -13,7 +19,10 @@ const MS_PER_DAY = 86_400_000;
|
||||
|
||||
@Injectable()
|
||||
export class AppointmentsService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly staffWorkingHoursService: StaffWorkingHoursService,
|
||||
) {}
|
||||
|
||||
getOrganizationIdFromUser(user: { organizationId?: string }) {
|
||||
if (!user?.organizationId) {
|
||||
@@ -22,7 +31,7 @@ export class AppointmentsService {
|
||||
return user.organizationId;
|
||||
}
|
||||
|
||||
async listColumnProviders(organizationId: string, actorUserId: string) {
|
||||
async listColumnProviders(organizationId: string, actorUserId: string, date?: string) {
|
||||
await this.assertCanViewAppointments(actorUserId, organizationId);
|
||||
|
||||
const members = await this.prisma.membership.findMany({
|
||||
@@ -44,7 +53,23 @@ export class AppointmentsService {
|
||||
orderBy: [{ createdAt: 'asc' }],
|
||||
});
|
||||
|
||||
const data = members.map((m) => ({ userId: m.user.id, name: m.user.name }));
|
||||
const scheduleBlocksByMembership =
|
||||
await this.staffWorkingHoursService.loadScheduleBlocksByMembershipIds(
|
||||
members.map((m) => m.id),
|
||||
);
|
||||
|
||||
const dayOfWeek = this.resolveDayOfWeekMondayZero(date);
|
||||
|
||||
const data = members.map((m) => {
|
||||
const allBlocks = scheduleBlocksByMembership.get(m.id) ?? [];
|
||||
const dayBlocks = blocksForDay(allBlocks, dayOfWeek);
|
||||
return {
|
||||
userId: m.user.id,
|
||||
name: m.user.name,
|
||||
hasWorkingHours: allBlocks.length > 0,
|
||||
dayBlocks,
|
||||
};
|
||||
});
|
||||
|
||||
return { success: true, data };
|
||||
}
|
||||
@@ -109,6 +134,12 @@ export class AppointmentsService {
|
||||
|
||||
await this.ensurePatientInOrg(dto.patientId, organizationId);
|
||||
await this.ensureProviderIsTreatmentEditor(dto.providerUserId, organizationId);
|
||||
await this.ensureAppointmentWithinProviderWorkingHours(
|
||||
dto.providerUserId,
|
||||
organizationId,
|
||||
startAt,
|
||||
endAt,
|
||||
);
|
||||
|
||||
const appointment = await this.prisma.appointment.create({
|
||||
data: {
|
||||
@@ -166,6 +197,12 @@ export class AppointmentsService {
|
||||
|
||||
await this.ensurePatientInOrg(patientId, organizationId);
|
||||
await this.ensureProviderIsTreatmentEditor(providerUserId, organizationId);
|
||||
await this.ensureAppointmentWithinProviderWorkingHours(
|
||||
providerUserId,
|
||||
organizationId,
|
||||
startAt,
|
||||
endAt,
|
||||
);
|
||||
|
||||
const appointment = await this.prisma.appointment.update({
|
||||
where: { id },
|
||||
@@ -273,6 +310,47 @@ export class AppointmentsService {
|
||||
}
|
||||
}
|
||||
|
||||
private resolveDayOfWeekMondayZero(date?: string): number {
|
||||
if (!date) {
|
||||
return localDayOfWeekMondayZero(new Date().getDay());
|
||||
}
|
||||
const [y, m, d] = date.split('-').map(Number);
|
||||
const parsed = new Date(y, m - 1, d, 12, 0, 0, 0);
|
||||
if (Number.isNaN(parsed.getTime())) {
|
||||
throw new BadRequestException('Invalid date query parameter');
|
||||
}
|
||||
return localDayOfWeekMondayZero(parsed.getDay());
|
||||
}
|
||||
|
||||
private async ensureAppointmentWithinProviderWorkingHours(
|
||||
providerUserId: string,
|
||||
organizationId: string,
|
||||
startAt: Date,
|
||||
endAt: Date,
|
||||
) {
|
||||
const membership = await this.getMembership(providerUserId, organizationId);
|
||||
if (!membership) {
|
||||
throw new BadRequestException('Provider is not a member of this organization');
|
||||
}
|
||||
|
||||
const scheduleBlocksByMembership =
|
||||
await this.staffWorkingHoursService.loadScheduleBlocksByMembershipIds([membership.id]);
|
||||
const allBlocks = scheduleBlocksByMembership.get(membership.id) ?? [];
|
||||
if (allBlocks.length === 0) {
|
||||
throw new BadRequestException('Provider has no working hours configured');
|
||||
}
|
||||
|
||||
const dayOfWeek = localDayOfWeekMondayZero(startAt.getDay());
|
||||
const dayBlocks = blocksForDay(allBlocks, dayOfWeek);
|
||||
if (dayBlocks.length === 0) {
|
||||
throw new BadRequestException('Provider is not working on this day');
|
||||
}
|
||||
|
||||
if (!appointmentWithinWorkingHours(startAt, endAt, dayBlocks)) {
|
||||
throw new BadRequestException('Appointment must fall within the provider working hours');
|
||||
}
|
||||
}
|
||||
|
||||
private async getMembership(userId: string, organizationId: string) {
|
||||
return this.prisma.membership.findFirst({
|
||||
where: { userId, organizationId },
|
||||
|
||||
Reference in New Issue
Block a user