Skip to content

Commit 4ef1723

Browse files
authored
Allow new details in clinic sessions to be edited (#249)
* Allow new details in clinic sessions to be edited This commit also updates create-data to generate valid vaccination periods and staffing levels for the clinic sessions we create. * Add the clinic location to session title
1 parent e63dbfe commit 4ef1723

12 files changed

Lines changed: 306 additions & 95 deletions

File tree

app/controllers/session.js

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ import {
3030
today
3131
} from '../utils/date.js'
3232
import { getResults, getPagination } from '../utils/pagination.js'
33-
import { getSessionYearGroups } from '../utils/session.js'
33+
import {
34+
getSessionYearGroups,
35+
getVaccinationPeriodsSummary
36+
} from '../utils/session.js'
3437
import { formatYearGroup } from '../utils/string.js'
3538

3639
export const sessionController = {
@@ -490,14 +493,49 @@ export const sessionController = {
490493
const { session_id } = request.params
491494
const { data } = request.session
492495

493-
// Setup wizard if not already setup
496+
// Copy the saved session to the wizard context, if not already there
494497
let session = Session.findOne(session_id, data.wizard)
495498
if (!session) {
499+
// NB: response.locals.session was set in read()
496500
session = Session.create(response.locals.session, data.wizard)
501+
response.locals.session.vaccinationPeriods.forEach(
502+
(vaccinationPeriod) => {
503+
ClinicVaccinationPeriod.create(vaccinationPeriod, data.wizard)
504+
}
505+
)
506+
}
507+
508+
// Set up the transaction metadata that controls how some clinic values are entered
509+
if (session.type === SessionType.Clinic) {
510+
const vaccinatorCounts = new Set(
511+
session.vaccination_period_ids.map(
512+
(period_id) =>
513+
ClinicVaccinationPeriod.findOne(period_id, data.wizard)
514+
?.vaccinatorCount
515+
)
516+
)
517+
const variableVaccinatorCounts = vaccinatorCounts.size > 1
518+
data.transaction = {
519+
hasVariableVaccinatorCounts: variableVaccinatorCounts ? 'true' : 'false'
520+
}
521+
if (!variableVaccinatorCounts) {
522+
data.transaction.consistentVaccinatorCount = vaccinatorCounts
523+
.values()
524+
.next()
525+
.value.toString()
526+
}
497527
}
498528

499529
response.locals.session = new Session(session, data)
500530

531+
// Generate summary info for the edit page
532+
response.locals.vaccinationPeriodsSummary = getVaccinationPeriodsSummary(
533+
session.vaccination_period_ids.map((period_id) =>
534+
ClinicVaccinationPeriod.findOne(period_id, data.wizard)
535+
),
536+
session.appointmentLength
537+
)
538+
501539
// Show back link to session page
502540
response.locals.back = session.uri
503541

@@ -517,7 +555,20 @@ export const sessionController = {
517555
data
518556
)
519557

558+
// Update any clinic vaccination periods
559+
if (session.type === SessionType.Clinic) {
560+
session.vaccination_period_ids.forEach((period_id) => {
561+
const vaccinationPeriod = ClinicVaccinationPeriod.findOne(
562+
period_id,
563+
data.wizard
564+
)
565+
ClinicVaccinationPeriod.update(period_id, vaccinationPeriod, data)
566+
})
567+
}
568+
520569
// Clean up session data
570+
delete data.vaccinationPeriods
571+
delete data.transaction
521572
delete data.session
522573
delete data.wizard
523574

@@ -613,6 +664,15 @@ export const sessionController = {
613664
} else {
614665
response.locals.clinics = Clinic.findAll(data)
615666
}
667+
668+
// Generate summary info for the check-answers page
669+
if (vaccinationPeriods) {
670+
response.locals.vaccinationPeriodsSummary =
671+
getVaccinationPeriodsSummary(
672+
vaccinationPeriods,
673+
session.appointmentLength
674+
)
675+
}
616676
}
617677

618678
if (session.type === SessionType.School) {

app/data.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import vaccines from './datasets/vaccines.js'
22
import batches from '../.data/batches.json' with { type: 'json' }
33
import clinicAppointments from '../.data/clinic-appointments.json' with { type: 'json' }
44
import clinicBookings from '../.data/clinic-bookings.json' with { type: 'json' }
5+
import clinicVaccinationPeriods from '../.data/clinic-vaccination-periods.json' with { type: 'json' }
56
import clinics from '../.data/clinics.json' with { type: 'json' }
67
import instructions from '../.data/instructions.json' with { type: 'json' }
78
import moves from '../.data/moves.json' with { type: 'json' }
@@ -34,6 +35,7 @@ const data = {
3435
batches,
3536
clinicAppointments,
3637
clinicBookings,
38+
clinicVaccinationPeriods,
3739
clinics,
3840
counts: {},
3941
defaultBatches: {},
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { fakerEN_GB as faker } from '@faker-js/faker'
2+
import { addMinutes } from 'date-fns'
3+
4+
import { ClinicVaccinationPeriod } from '../models.js'
5+
6+
/**
7+
* Generate one or more time periods during which vaccinations will be administered at a clinic
8+
*
9+
* @param {string} session_id - session ID for the clinic whose vaccination periods we're creating
10+
* @param {Date} sessionDate - the date on which the clinic's running
11+
* @returns {Array<ClinicVaccinationPeriod>} - one or more vaccination periods
12+
*/
13+
export function generateClinicVaccinationPeriods(session_id, sessionDate) {
14+
const periodCount = faker.helpers.weightedArrayElement([
15+
{ value: 1, weight: 70 },
16+
{ value: 2, weight: 30 }
17+
])
18+
19+
const vaccinationPeriodLengths = Array.from({ length: periodCount }).map(
20+
() => {
21+
return faker.number.int({ min: 60, max: 180, multipleOf: 30 })
22+
}
23+
)
24+
const breakLength =
25+
periodCount > 1 ? faker.number.int({ min: 15, max: 60, multipleOf: 15 }) : 0
26+
const totalSessionLength =
27+
vaccinationPeriodLengths.reduce((total, next) => total + next, 0) +
28+
breakLength
29+
30+
const earliestSessionStartTime = new Date(sessionDate.setUTCHours(9, 0)) // 9am
31+
const latestSessionFinishTime = new Date(sessionDate.setUTCHours(20, 0)) // 8pm
32+
const sessionWindow = Math.floor(
33+
(latestSessionFinishTime.getTime() - earliestSessionStartTime.getTime()) /
34+
1000 /
35+
60
36+
)
37+
const startOffset = faker.number.int({
38+
min: 0,
39+
max: sessionWindow - totalSessionLength,
40+
multipleOf: 15
41+
})
42+
const sessionStartTime = addMinutes(earliestSessionStartTime, startOffset)
43+
44+
let nextPeriodStartTime = sessionStartTime
45+
return vaccinationPeriodLengths.map((periodLength) => {
46+
const vaccinationPeriod = new ClinicVaccinationPeriod({
47+
session_id,
48+
startAt: nextPeriodStartTime,
49+
endAt: addMinutes(nextPeriodStartTime, periodLength),
50+
vaccinatorCount: faker.helpers.weightedArrayElement([
51+
{ value: 2, weight: 5 },
52+
{ value: 3, weight: 85 },
53+
{ value: 4, weight: 10 }
54+
])
55+
})
56+
nextPeriodStartTime = addMinutes(
57+
nextPeriodStartTime,
58+
periodLength + breakLength
59+
)
60+
61+
return vaccinationPeriod
62+
})
63+
}

app/generators/session.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { fakerEN_GB as faker } from '@faker-js/faker'
22

3-
import { SessionType, TeamDefaults } from '../enums.js'
3+
import { SessionPresetName, SessionType, TeamDefaults } from '../enums.js'
44
import { Session } from '../models.js'
55
import { addDays, getTermDates, removeDays, setMidday } from '../utils/date.js'
66
import { getSessionYearGroups } from '../utils/session.js'
@@ -42,14 +42,21 @@ export function generateSession(preset, academicYear, user, options) {
4242

4343
date = setMidday(date)
4444

45-
// Don’t create sessions during weekends
46-
if ([0, 6].includes(date.getDay())) {
47-
date = removeDays(date, 2)
45+
if (school_id) {
46+
// Don’t create school sessions during weekends
47+
if ([0, 6].includes(date.getDay())) {
48+
date = removeDays(date, 2)
49+
}
4850
}
4951

5052
openAt = removeDays(date, TeamDefaults.SessionOpenWeeks * 7)
5153
}
5254

55+
let appointmentLength
56+
if (clinic_id) {
57+
appointmentLength = preset.name === SessionPresetName.Flu ? 5 : 10
58+
}
59+
5360
let yearGroups
5461
if (options.school_id) {
5562
yearGroups = getSessionYearGroups(options.school_id, [preset])
@@ -63,7 +70,11 @@ export function generateSession(preset, academicYear, user, options) {
6370
registration: true,
6471
academicYear,
6572
presetNames: [preset.name],
66-
...(clinic_id && { type: SessionType.Clinic, clinic_id }),
73+
...(clinic_id && {
74+
type: SessionType.Clinic,
75+
clinic_id,
76+
appointmentLength
77+
}),
6778
...(school_id && { type: SessionType.School, school_id, yearGroups })
6879
})
6980
}

app/locales/en.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2224,7 +2224,7 @@ export const en = {
22242224
input: {
22252225
allPeriodsLabel: 'All periods',
22262226
singlePeriodLabel:
2227-
'{{startTime.hour}}:{{startTime.minute}} to {{endTime.hour}}:{{endTime.minute}}',
2227+
'{{startAt_.hour}}:{{startAt_.minute}} to {{endAt_.hour}}:{{endAt_.minute}}',
22282228
suffix: 'vaccinators'
22292229
},
22302230
varies: {

app/models/clinic-vaccination-period.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@ export class ClinicVaccinationPeriod {
4141
* @returns {number} - the number of whole appointments that can fitted into this period
4242
*/
4343
appointmentCount(appointmentLengthInMinutes) {
44-
const periodLengthInMs = Math.abs(
45-
this.endAt.getTime() - this.startAt.getTime()
46-
)
44+
if (!this.endAt || !this.startAt) {
45+
return 0
46+
}
47+
48+
const periodLengthInMs = this.endAt.getTime() - this.startAt.getTime()
4749
if (periodLengthInMs <= 0) {
4850
return 0
4951
}

app/models/session.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,7 @@ export class Session {
609609
*/
610610
get shortName() {
611611
if (this.clinic) {
612-
return `${this.programmeNames.titleCase} community clinic`
612+
return `${this.programmeNames.titleCase} clinic at ${this.location.name}`
613613
}
614614

615615
if (this.location) {

app/utils/session.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,51 @@ export const getSessionYearGroups = (school_id, sessionPresets) => {
114114
[...programmeYearGroups].includes(yearGroup)
115115
)
116116
}
117+
118+
/**
119+
* Get various summary details for a clinic session's vaccination periods
120+
*
121+
* @param {Array<import('../models.js').ClinicVaccinationPeriod>} vaccinationPeriods - the vaccination periods for a given clinic session
122+
* @param {number} appointmentLength - the length of a single appointment, in minutes
123+
* @returns {object} - the summary details for a clinic session's vaccination period(s)
124+
*/
125+
export const getVaccinationPeriodsSummary = (
126+
vaccinationPeriods,
127+
appointmentLength
128+
) => {
129+
{
130+
let startAndEndTimes = ''
131+
let vaccinatorCounts = ''
132+
let lastVaccinatorCount = -1
133+
let hasVariableVaccinatorCounts = false
134+
let totalSlots = 0
135+
vaccinationPeriods.forEach((vaccinationPeriod, periodIndex) => {
136+
const thisPeriod = `${vaccinationPeriod.startAt_.hour}:${vaccinationPeriod.startAt_.minute} to ${vaccinationPeriod.endAt_.hour}:${vaccinationPeriod.endAt_.minute}`
137+
const thisVaccinatorCount = vaccinationPeriod.vaccinatorCount || 0
138+
139+
startAndEndTimes += thisPeriod
140+
vaccinatorCounts += `${thisVaccinatorCount} from ${thisPeriod}`
141+
if (periodIndex < vaccinationPeriods.length - 1) {
142+
startAndEndTimes += '<br>'
143+
vaccinatorCounts += '<br>'
144+
}
145+
146+
hasVariableVaccinatorCounts =
147+
hasVariableVaccinatorCounts ||
148+
(lastVaccinatorCount !== -1 &&
149+
lastVaccinatorCount !== thisVaccinatorCount)
150+
lastVaccinatorCount = thisVaccinatorCount
151+
152+
totalSlots += vaccinationPeriod.appointmentCount(appointmentLength)
153+
})
154+
if (!hasVariableVaccinatorCounts) {
155+
vaccinatorCounts = lastVaccinatorCount.toString()
156+
}
157+
158+
return {
159+
startAndEndTimes,
160+
vaccinatorCounts,
161+
totalSlots
162+
}
163+
}
164+
}

0 commit comments

Comments
 (0)