import { Transform, Type } from 'class-transformer' import { addMinutes, differenceInHours } from 'date-fns' import { isNil } from 'lodash' import { parseISO } from '../utils/date' import { AppointmentCancellationReasons } from './enums/appointment_cancellation_reasons.enum' import { Availability } from './availability.model' import { AvailabilityTime } from './availability_time.model' import { Patient } from './patient.model' import { BaseModel } from './base.model' export const LIMIT_DISCOUNT_RESCHEDULE_CREDIT = 24 export const FROM_WHEN_CAN_CONFIRM = 48 export const CONSULTATION_FINISHED_STATES: AppointmentState[] = [ 'food_plan_pending', 'food_plan_in_construction', 'finished', ] export type AppointmentState = | 'scheduled' | 'food_plan_pending' | 'food_plan_in_construction' | 'cancelled' | 'finished' | 'patient_waiting' | 'user_waiting' | 'in_consultation' | 'blocked' export class Appointment extends BaseModel { id: string assistant_user_id: string patient_id: string user_id: string state: AppointmentState @Transform(value => parseISO(value), { toClassOnly: true }) canceled_at: Date @Transform(value => parseISO(value), { toClassOnly: true }) consultation_finished_at: Date @Transform(value => parseISO(value), { toClassOnly: true }) consultation_started_at: Date @Transform(value => parseISO(value), { toClassOnly: true }) finished_at: Date @Transform(value => parseISO(value), { toClassOnly: true }) created_at: Date @Transform(value => parseISO(value), { toClassOnly: true }) confirmed_at: Date @Transform(value => parseISO(value), { toClassOnly: true }) scheduled_to: Date @Transform(value => parseISO(value), { toClassOnly: true }) updated_at: Date timezone: string reschedule_credit: number appointment_id_rescheduled: string @Transform(value => parseISO(value), { toClassOnly: true }) patient_arrived_at?: Date video_session?: string patient_video_token?: string user_video_token?: string cancellation_reason?: AppointmentCancellationReasons cancellation_reason_comment?: string rating?: number feedback?: string @Type(() => Patient) patient?: Patient consultation_duration_in_seconds?: number is_return: boolean origin_appointment_id?: string static build({ availability, availabilityTime, patient, lastFinishedAppointment, isReturnReschedule = false, appointmentIdRescheduled, }: { availability: Availability availabilityTime: AvailabilityTime patient: Patient lastFinishedAppointment?: Appointment reschedule_credit?: number appointmentIdRescheduled?: string isReturnReschedule?: boolean }): Appointment { const now = new Date() const scheduled_to = parseISO(`${availability.formatedDate}T${availabilityTime.time}`) const confirmed_at = differenceInHours(scheduled_to, now) < FROM_WHEN_CAN_CONFIRM ? now : null const isReturn = !isNil(lastFinishedAppointment) return new Appointment({ patient_id: patient.id, user_id: availability.user_id, timezone: availabilityTime.offset, state: 'scheduled', scheduled_to, confirmed_at, appointment_id_rescheduled: appointmentIdRescheduled, origin_appointment_id: isReturn || isReturnReschedule ? lastFinishedAppointment?.id || null : null, is_return: isReturn, }) } reallocate(availability: Availability, availabilityTime: AvailabilityTime): void { this.user_id = availability.user_id this.scheduled_to = Appointment.buildScheduledTo(availability, availabilityTime) } get isCancelled(): boolean { return this.state === 'cancelled' } get isConsultationFinished(): boolean { return CONSULTATION_FINISHED_STATES.includes(this.state) } get isBlocked(): boolean { return this.state === 'blocked' } get isConfirmed(): boolean { return !isNil(this.confirmed_at) } get losesRescheduleCreditIfCancel(): boolean { if (!['scheduled', 'cancelled'].includes(this.state)) return false return differenceInHours(this.scheduled_to, this.canceled_at || new Date()) < LIMIT_DISCOUNT_RESCHEDULE_CREDIT } cancel(cancellationReason: AppointmentCancellationReasons, cancellation_comment?: string): void { this.canceled_at = new Date() this.state = 'cancelled' this.cancellation_reason = cancellationReason this.cancellation_reason_comment = cancellation_comment } isDelayed(minutes = 0): boolean { if (!this.consultation_started_at) return false return this.consultation_started_at.getTime() > addMinutes(this.scheduled_to, minutes).getTime() } private static buildScheduledTo(availability: Availability, availabilityTime: AvailabilityTime) { return parseISO(`${availability.formatedDate}T${availabilityTime.time}`) } }