Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion app/controllers/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -745,8 +745,14 @@ export const sessionController = {
}
}

const sessionWithFullContext = new Session(session, data)
if (session.type === SessionType.Clinic) {
response.locals.appointmentsToCancel =
sessionWithFullContext.appointmentsToCancel
}

// Give access to the data needed for the summaryRows
response.locals.session = new Session(session, data)
response.locals.session = sessionWithFullContext

// Show back link to session page
response.locals.back = session.uri
Expand Down
9 changes: 8 additions & 1 deletion app/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -2546,7 +2546,14 @@ export const en = {
},
edit: {
title: 'Edit session',
success: '{{session.name}} updated'
success: '{{session.name}} updated',
appointments: {
cancellation: {
title: 'Appointments will be cancelled',
description:
'{count, plural, one {Changes made to this session will result in the cancellation of **1 appointment**.\n\nA notification will be sent to the parent or guardian of the affected child, inviting them to book a new appointment.} other {Changes made to this session will result in the cancellation of **{count} appointments**.\n\nNotifications will be sent to the parents or guardians of affected children, inviting them to book a new appointment.}}'
}
}
},
cancel: {
bookings: {
Expand Down
4 changes: 4 additions & 0 deletions app/models/clinic-booking.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { fakerEN_GB as faker } from '@faker-js/faker'
import _ from 'lodash'

import { ClinicAppointment, Contact } from '../models.js'
import { today } from '../utils/date.js'
import { formatCode, stringToArray, stringToBoolean } from '../utils/string.js'

/**
Expand All @@ -10,6 +11,7 @@ import { formatCode, stringToArray, stringToBoolean } from '../utils/string.js'
* @param {object} [context] - Context
* @property {object} [context] - Context
* @property {string} [uuid] - Clinic booking UUID
* @property {Date} [createdAt] - Created date
* @property {string} [bookingReference] - Booking reference number
* @property {Array<string>} [invited_programme_ids] - IDs of programmes for which child was invited
* @property {Contact} [contact] - Contact details for the booking; see appointments for parental relationship details
Expand All @@ -19,6 +21,8 @@ export class ClinicBooking {
constructor(options, context) {
this.context = context
this.uuid = options?.uuid || faker.string.uuid()
this.createdAt = options?.createdAt ? new Date(options.createdAt) : today()

this.bookingReference =
options?.bookingReference || ClinicBooking.generateReference()
this.invited_programme_ids = options?.invited_programme_ids
Expand Down
24 changes: 20 additions & 4 deletions app/models/clinic-vaccination-period.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,15 @@ import {
* @param {object} options - property values
* @property {string} [uuid] - Vaccination period UUID
* @property {Date} [startAt] - Start time of first appointment slot
* @property {Date} [startAt_] - Start time of first appointment slot, from dateInput - see getter/setter
* @property {Date} [endAt] - End time of final appointment slot
* @property {Date} [endAt_] - End time of final appointment slot, from dateInput - see getter/setter
* @property {number} [vaccinatorCount] - The number of staff vaccinating in parallel during this period
*/
export class ClinicVaccinationPeriod {
constructor(options) {
this.uuid = options?.uuid || faker.string.uuid()

this.startAt = options?.startAt && new Date(options.startAt)
this.startAt_ = options?.startAt_
this.endAt = options?.endAt && new Date(options.endAt)
this.endAt_ = options?.endAt_

this.vaccinatorCount = options?.vaccinatorCount
}
Expand Down Expand Up @@ -92,6 +88,26 @@ export class ClinicVaccinationPeriod {
}
}

/**
* Does the given appointment start time fall within this period?
*
* @param {Date} appointmentTime - the start time of appointment
* @param {number} appointmentLengthInMinutes - the length of slots in minutes
* @returns {boolean} - true if the slot falls within this period, or false otherwise
*/
includesAppointmentTime(appointmentTime, appointmentLengthInMinutes) {
const firstSlotTime = this.startAt.getTime()
const lastSlotTime = addMinutes(
this.endAt,
-appointmentLengthInMinutes
).getTime()

return (
appointmentTime.getTime() >= firstSlotTime &&
appointmentTime.getTime() <= lastSlotTime
)
}

/**
* Get formatted values
*
Expand Down
32 changes: 32 additions & 0 deletions app/models/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,38 @@ export class Session {
return appointments.filter(({ patient_uuid }) => !patient_uuid)
}

/**
* Get a list of the appointments already made but for which we don't have capacity
*
* @returns {Array<ClinicAppointment>} - the appointments we'll need to cancel
*/
get appointmentsToCancel() {
const allAppointmentsByTime = _.groupBy(this.appointments, (appointment) =>
appointment.startAt.getTime()
)

const appointmentsWithoutVaccinators = []
Object.entries(allAppointmentsByTime).forEach(([key, appointments]) => {
const startAt = new Date()
startAt.setTime(Number(key))
const vaccinationPeriod = this.vaccinationPeriods.find((period) =>
period.includesAppointmentTime(startAt, this.appointmentLength)
)
if (!vaccinationPeriod) {
// No longer part of a vaccination period, so cancel all appointments at this time
appointmentsWithoutVaccinators.push(...appointments)
} else if (vaccinationPeriod.vaccinatorCount < appointments.length) {
// Not enough vaccinators at this time, so those who booked first get to keep their appointments
appointments = _.sortBy(appointments, 'booking.createdAt')
appointmentsWithoutVaccinators.push(
...appointments.slice(vaccinationPeriod.vaccinatorCount)
)
}
})

return appointmentsWithoutVaccinators
}

/**
* Get school
*
Expand Down
9 changes: 9 additions & 0 deletions app/views/session/edit.njk
Original file line number Diff line number Diff line change
Expand Up @@ -86,5 +86,14 @@
totalAppointments: {}
})
}) }}

{% if appointmentsToCancel.length > 0 %}
{% set detailsHtml = __mf("session.edit.appointments.cancellation.description", { count: appointmentsToCancel.length }) | nhsukMarkdown %}

{{ warningCallout({
heading: __("session.edit.appointments.cancellation.title"),
html: detailsHtml
}) }}
{% endif %}
{% endif %}
{% endblock %}