import { Injectable } from '@nestjs/common' import { addMinutes, compareAsc, format, parseISO } from 'date-fns' import { isNil } from 'lodash' import { Appointment, AppointmentCancelReason, AppointmentCancellationReasons, AvailabilityTime } from '../../models' import { AppointmentRepository, AppointmentCancelReasonRepository, AvailabilityTimeRepository, AVAILABLE_TIMES_SIMILAR_USERS, MessageRepository, PatientRepository, UserRepository, } from '../../repositories' import { NotFoundError } from '../../errors/not_found.error' import { parseTimeToMinutes } from '../../utils' import { BookingCancellationReason } from './types/booking_cancellation_reason.type' @Injectable() export class TransferService { constructor( private readonly appointmentRepository: AppointmentRepository, private readonly appointmentCancelReasonRepository: AppointmentCancelReasonRepository, private readonly availabilityTimeRepository: AvailabilityTimeRepository, private readonly messageRepository: MessageRepository, private readonly patientRepository: PatientRepository, private readonly userRepository: UserRepository, ) {} async transfer(appointment: Appointment, cancellationReason?: BookingCancellationReason): Promise { const oldAvailabilityTime = await this.availabilityTimeRepository.getByAppointmentId(appointment.id) const offsetInMinutes = parseTimeToMinutes(appointment.timezone || oldAvailabilityTime.offset) const dateWithOffset = addMinutes(appointment.scheduled_to, offsetInMinutes) const similarNutritionists = await this.getSimilarUsersWithAvailableDate( appointment.user_id, appointment.patient_id, dateWithOffset, ) const foundUser = this.findUserWithAvailableTime(similarNutritionists, appointment.scheduled_to) if (isNil(foundUser)) return await this.blockAppointment(appointment, oldAvailabilityTime, cancellationReason) appointment.cancellation_reason = cancellationReason?.value || null appointment.cancellation_reason_comment = cancellationReason?.comment || null await this.changeAvailabilityTime({ appointment, oldAvailabilityTime, availabilityTimeId: foundUser.availabilityTimeId, userId: foundUser.userId, }) await this.messageRepository.transferPatientPendingMessages(appointment.patient_id, foundUser.userId) } private async blockAppointment( appointment: Appointment, oldAvailabilityTime: AvailabilityTime, cancellationReason?: BookingCancellationReason, ): Promise { await this.blockOrFreeAvailabilityTime(oldAvailabilityTime, cancellationReason?.value) await this.appointmentRepository.save( { ...appointment, state: 'blocked', ...(!isNil(cancellationReason?.value) ? { cancellation_reason: cancellationReason?.value } : {}), ...(!isNil(cancellationReason?.comment) ? { cancellation_reason_comment: cancellationReason?.comment } : {}), }, [ 'state', ...(!isNil(cancellationReason?.value) ? [{ field: 'cancellation_reason', type: 'String' }] : []), ...(!isNil(cancellationReason?.value) ? [{ field: 'cancellation_reason_comment', type: 'String' }] : []), ], ) } private async changeAvailabilityTime({ appointment, oldAvailabilityTime, availabilityTimeId, userId, }: { appointment: Appointment oldAvailabilityTime: AvailabilityTime availabilityTimeId: string userId: string }): Promise { const newAvailabilityTime = await this.availabilityTimeRepository.get(availabilityTimeId) await this.blockOrFreeAvailabilityTime(oldAvailabilityTime, appointment.cancellation_reason) newAvailabilityTime.setScheduled(appointment.id) await this.availabilityTimeRepository.update(newAvailabilityTime) await this.appointmentRepository.save({ id: appointment.id, user_id: userId }, [ { field: 'user_id', type: 'String' }, ]) await this.patientRepository.save({ id: appointment.patient_id, user_id: userId }, [ { field: 'user_id', type: 'String' }, ]) } private async getSimilarUsersWithAvailableDate( userId: string, patientId: string, date: Date, ): Promise { const patient = await this.patientRepository.get(patientId) const user = await this.userRepository.get(userId) if (!patient) throw new NotFoundError(`Patient doesn't exists`) if (!user) throw new NotFoundError(`User doesn't exists`) if (!patient.goal_id) return [] let similarNutritionists = await this.userRepository.findUsersWithAvailableDateByGoalAndRestrictions( userId, date, patient.goal_id, user.country_code, patient.restriction_ids, ) if (patient.restriction_ids.length > 0 && similarNutritionists?.length <= 0) similarNutritionists = await this.userRepository.findUsersWithAvailableDateByGoalAndRestrictions( userId, date, patient.goal_id, user.country_code, ) return similarNutritionists } private findUserWithAvailableTime = ( similarUsers: AVAILABLE_TIMES_SIMILAR_USERS[], time: Date, ): { userId: string availabilityTimeId: string } => { let result = null similarUsers.every(user => { const availableTime = user.available_times.find( availableTimeIn => compareAsc( parseISO( `${format(availableTimeIn.date, 'yyyy-MM-dd')}T${availableTimeIn.hoursWithoutOffset}${ availableTimeIn.offset }`, ), time, ) === 0, ) if (isNil(availableTime)) return true result = { userId: user.id, availabilityTimeId: availableTime.id } return false }) return result } private async blockOrFreeAvailabilityTime( availabilityTime: AvailabilityTime, cancellationReason?: AppointmentCancellationReasons, ): Promise { availabilityTime.free() if (!isNil(cancellationReason)) { const cancelReason = await this.getCancelReasons(cancellationReason) if (cancelReason.block_when_transfer) availabilityTime.block() } await this.availabilityTimeRepository.update(availabilityTime) } private async getCancelReasons(value: AppointmentCancellationReasons): Promise { const cancelReason = await this.appointmentCancelReasonRepository.get(value) if (isNil(cancelReason)) throw new NotFoundError(`Appointment cancel reason ${value} not found.`) return cancelReason } }