Skip to content

Commit

Permalink
Add Rechsedule requested by radio buttons
Browse files Browse the repository at this point in the history
  • Loading branch information
marcocolluramoj committed Oct 1, 2024
1 parent 9f0e75f commit 187bcc3
Show file tree
Hide file tree
Showing 11 changed files with 143 additions and 36 deletions.
1 change: 1 addition & 0 deletions server/models/appointment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ export interface AppointmentSchedulingDetails {
appointmentDeliveryType: AppointmentDeliveryType | null
appointmentDeliveryAddress: Address | null
npsOfficeCode: string | null
rescheduleRequestedBy?: string
rescheduledReason?: string
}
3 changes: 3 additions & 0 deletions server/routes/appointments/appointmentSummary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ export default class AppointmentSummary {
lines: this.inPersonMeetingProbationOfficeAddressLines(this.deliusOfficeLocation),
})
}
if (this.appointment.rescheduleRequestedBy) {
summary.push({ key: 'Appointment change requested by', lines: [this.appointment.rescheduleRequestedBy] })
}
if (this.appointment.rescheduledReason) {
summary.push({ key: 'Reason for appointment change', lines: [this.appointment.rescheduledReason] })
}
Expand Down
16 changes: 16 additions & 0 deletions server/routes/appointments/appointmentsController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ describe('Scheduling a supplier assessment appointment', () => {
const draftBooking = draftAppointmentBookingFactory.build()
draftsService.fetchDraft.mockResolvedValue(draftBooking)

const deliusServiceUser = deliusServiceUserFactory.build()
ramDeliusApiService.getCaseDetailsByCrn.mockResolvedValue(deliusServiceUser)
interventionsService.getSupplierAssessment.mockResolvedValue(supplierAssessmentFactory.build())
interventionsService.getSentReferral.mockResolvedValue(sentReferralFactory.build())
interventionsService.getIntervention.mockResolvedValue(interventionFactory.build())
Expand Down Expand Up @@ -119,6 +121,8 @@ describe('Scheduling a supplier assessment appointment', () => {
)
interventionsService.getSentReferral.mockResolvedValue(sentReferralFactory.build())
interventionsService.getIntervention.mockResolvedValue(interventionFactory.build())
const deliusServiceUser = deliusServiceUserFactory.build()
ramDeliusApiService.getCaseDetailsByCrn.mockResolvedValue(deliusServiceUser)

await request(app)
.get(`/service-provider/referrals/1/supplier-assessment/schedule/${draftBooking.id}/details`)
Expand Down Expand Up @@ -146,6 +150,8 @@ describe('Scheduling a supplier assessment appointment', () => {
hmppsAuthService.getSPUserByUsername.mockResolvedValue(
hmppsAuthUserFactory.build({ firstName: 'caseWorkerFirstName', lastName: 'caseWorkerLastName' })
)
const deliusServiceUser = deliusServiceUserFactory.build()
ramDeliusApiService.getCaseDetailsByCrn.mockResolvedValue(deliusServiceUser)

await request(app)
.get(`/service-provider/referrals/1/supplier-assessment/schedule/${draftBooking.id}/details`)
Expand Down Expand Up @@ -182,6 +188,8 @@ describe('Scheduling a supplier assessment appointment', () => {
interventionsService.getSupplierAssessment.mockResolvedValue(supplierAssessmentFactory.build())
interventionsService.getSentReferral.mockResolvedValue(sentReferralFactory.build())
interventionsService.getIntervention.mockResolvedValue(interventionFactory.build())
const deliusServiceUser = deliusServiceUserFactory.build()
ramDeliusApiService.getCaseDetailsByCrn.mockResolvedValue(deliusServiceUser)

await request(app)
.get(`/service-provider/referrals/1/supplier-assessment/schedule/${draftBooking.id}/details?clash=true`)
Expand Down Expand Up @@ -251,6 +259,8 @@ describe('Scheduling a supplier assessment appointment', () => {
interventionsService.getSupplierAssessment.mockResolvedValue(supplierAssessmentFactory.build())
interventionsService.getSentReferral.mockResolvedValue(sentReferralFactory.build())
interventionsService.getIntervention.mockResolvedValue(interventionFactory.build())
const deliusServiceUser = deliusServiceUserFactory.build()
ramDeliusApiService.getCaseDetailsByCrn.mockResolvedValue(deliusServiceUser)

await request(app)
.post(`/service-provider/referrals/1/supplier-assessment/schedule/${draftBooking.id}/details`)
Expand Down Expand Up @@ -630,6 +640,9 @@ describe('Scheduling a delivery session', () => {

const appointment = actionPlanAppointmentFactory.build()

const deliusServiceUser = deliusServiceUserFactory.build()
ramDeliusApiService.getCaseDetailsByCrn.mockResolvedValue(deliusServiceUser)

interventionsService.getActionPlanAppointment.mockResolvedValue(appointment)
interventionsService.getSentReferral.mockResolvedValue(sentReferralFactory.build())
interventionsService.getActionPlan.mockResolvedValue(actionPlanFactory.build())
Expand Down Expand Up @@ -705,6 +718,9 @@ describe('Scheduling a delivery session', () => {
const actionPlan = actionPlanFactory.build()
const appointment = actionPlanAppointmentFactory.build()

const deliusServiceUser = deliusServiceUserFactory.build()
ramDeliusApiService.getCaseDetailsByCrn.mockResolvedValue(deliusServiceUser)

interventionsService.getActionPlan.mockResolvedValue(actionPlan)
interventionsService.getActionPlanAppointment.mockResolvedValue(appointment)
interventionsService.getSentReferral.mockResolvedValue(sentReferralFactory.build())
Expand Down
6 changes: 4 additions & 2 deletions server/routes/appointments/appointmentsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ export default class AppointmentsController {
overrideBackLinkHref
)

const view = new ScheduleAppointmentView(presenter)
const serviceUserName = `${serviceUser.name.forename} ${serviceUser.name.surname}`
const view = new ScheduleAppointmentView(req, serviceUserName, presenter)
await ControllerUtils.renderWithLayout(req, res, view, serviceUser, 'service-provider')
}

Expand Down Expand Up @@ -375,7 +376,8 @@ export default class AppointmentsController {
userInputData,
serverError
)
const view = new ScheduleAppointmentView(presenter)
const serviceUserName = `${serviceUser.name.forename} ${serviceUser.name.surname}`
const view = new ScheduleAppointmentView(req, serviceUserName, presenter)

await ControllerUtils.renderWithLayout(req, res, view, serviceUser, 'service-provider')
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,14 +296,8 @@ describe(FeedbackAnswersPresenter, () => {

const presenter = new FeedbackAnswersPresenter(appointment, serviceUser, false)

expect(presenter.notifyProbationPractitionerOfBehaviourAnswers).toEqual({
question: `Does the probation practitioner need to be notified about poor behaviour?`,
answer: 'No',
})
expect(presenter.notifyProbationPractitionerOfConcernsAnswers).toEqual({
question: `Does the probation practitioner need to be notified about any concerns?`,
answer: 'No',
})
expect(presenter.notifyProbationPractitionerOfBehaviourAnswers).toBeNull()
expect(presenter.notifyProbationPractitionerOfConcernsAnswers).toBeNull()
})
})
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ describe(ScheduleAppointmentForm, () => {
})
})

describe('with a rescheduled reason', () => {
it('returns a paramsForUpdate with the completionDeadline key, an ISO-formatted date and a reason for rescheduling', async () => {
describe('with rescheduledReason and rescheduleRequestedBy', () => {
it('returns a paramsForUpdate with the completionDeadline key, an ISO-formatted date, rescheduledReason and rescheduleRequestedBy', async () => {
const request = TestUtils.createRequest({
'date-year': '2022',
'date-month': '4',
Expand All @@ -58,6 +58,7 @@ describe(ScheduleAppointmentForm, () => {
'session-type': 'ONE_TO_ONE',
'meeting-method': 'PHONE_CALL',
'rescheduled-reason': 'test reason',
'reschedule-requested-by': 'Service Provider',
})

const data = await new ScheduleAppointmentForm(request, deliusOfficeLocations, false, null, true).data()
Expand All @@ -70,6 +71,7 @@ describe(ScheduleAppointmentForm, () => {
appointmentDeliveryAddress: null,
npsOfficeCode: null,
rescheduledReason: 'test reason',
rescheduleRequestedBy: 'Service Provider',
})
})
})
Expand Down Expand Up @@ -327,6 +329,7 @@ describe(ScheduleAppointmentForm, () => {
'meeting-method': 'IN_PERSON_MEETING_PROBATION_OFFICE',
'delius-office-location-code': 'CRS0001',
'rescheduled-reason': '',
'reschedule-requested-by': 'Service Provider',
})

const data = await new ScheduleAppointmentForm(request, deliusOfficeLocations, false, null, true).data()
Expand All @@ -342,6 +345,37 @@ describe(ScheduleAppointmentForm, () => {
})
})
})
describe('having no rescheduled reason when there is an existing appointment', () => {
it('returns an error message for empty rescheduled reason', async () => {
const request = TestUtils.createRequest({
'date-year': '2022',
'date-month': '3',
'date-day': '1',
'time-hour': '1',
'time-minute': '05',
'time-part-of-day': 'pm',
'duration-hours': '1',
'duration-minutes': '30',
'session-type': 'ONE_TO_ONE',
'meeting-method': 'IN_PERSON_MEETING_PROBATION_OFFICE',
'delius-office-location-code': 'CRS0001',
'rescheduled-reason': 'test reason',
'reschedule-requested-by': '',
})

const data = await new ScheduleAppointmentForm(request, deliusOfficeLocations, false, null, true).data()

expect(data.error).toEqual({
errors: [
{
errorSummaryLinkedField: 'reschedule-requested-by',
formFields: ['reschedule-requested-by'],
message: 'Select who requested the appointment change',
},
],
})
})
})
describe('having a late date selection', () => {
it('returns an error message for late appointment date', async () => {
MockDate.set(new Date(2022, 2, 1))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export default class ScheduleAppointmentForm {
appointmentDeliveryType: appointmentDeliveryType.value!,
appointmentDeliveryAddress: appointmentDeliveryAddress.value,
npsOfficeCode: deliusOfficeLocation.value,
rescheduleRequestedBy: this.request.body['reschedule-requested-by'],
rescheduledReason: this.request.body['rescheduled-reason'],
},
}
Expand All @@ -114,7 +115,12 @@ export default class ScheduleAppointmentForm {

private validateRescheduledReason(): ValidationChain[] {
return this.hasExistingScheduledAppointment
? [body('rescheduled-reason').notEmpty().withMessage(errorMessages.scheduleAppointment.recsheduledReason.empty)]
? [
body('reschedule-requested-by')
.notEmpty()
.withMessage(errorMessages.scheduleAppointment.rescheduleRequestedBy.emptyRadio),
body('rescheduled-reason').notEmpty().withMessage(errorMessages.scheduleAppointment.rescheduledReason.empty),
]
: []
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ export default class ScheduleAppointmentPresenter {
}
}

get rescheduleRequestedBy(): Record<string, string | null> {
return {
label: 'Appointment change requested by',
errorMessage: PresenterUtils.errorMessage(this.validationError, 'reschedule-requested-by'),
}
}

private readonly utils = new PresenterUtils(this.userInputData)

get appointmentSummary(): SummaryListItem[] {
Expand Down
84 changes: 62 additions & 22 deletions server/routes/serviceProviderReferrals/scheduleAppointmentView.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Request } from 'express'
import {
BackLinkArgs,
DateInputArgs,
Expand All @@ -12,31 +13,14 @@ import ScheduleAppointmentPresenter from './scheduleAppointmentPresenter'
import AddressFormComponent from '../shared/addressFormComponent'

export default class ScheduleAppointmentView {
constructor(private readonly presenter: ScheduleAppointmentPresenter) {}
constructor(
readonly request: Request,
private readonly serviceUserName: string,
private readonly presenter: ScheduleAppointmentPresenter
) {}

addressFormView = new AddressFormComponent(this.presenter.fields.address, 'method-other-location')

get renderArgs(): [string, Record<string, unknown>] {
return [
'serviceProviderReferrals/scheduleAppointment',
{
presenter: this.presenter,
dateInputArgs: this.dateInputArgs,
timeInputArgs: this.timeInputArgs,
durationDateInputArgs: this.durationDateInputArgs,
deliusOfficeLocationSelectArgs: this.deliusOfficeLocationSelectArgs,
errorSummaryArgs: this.errorSummaryArgs,
serverError: this.serverError,
address: this.addressFormView.inputArgs,
sessionTypeRadioInputArgs: this.sessionTypeRadioInputArgs,
rescheduledReasonTextareaArgs: this.rescheduledReasonTextareaArgs,
meetingMethodRadioInputArgs: this.meetingMethodRadioInputArgs.bind(this),
backLinkArgs: this.backLinkArgs,
appointmentSummaryListArgs: ViewUtils.summaryListArgs(this.presenter.appointmentSummary),
},
]
}

private readonly errorSummaryArgs = ViewUtils.govukErrorSummaryArgs(this.presenter.errorSummary)

get serverError(): { message: string; classes: string } | null {
Expand Down Expand Up @@ -248,6 +232,40 @@ export default class ScheduleAppointmentView {
}
}

private get rescheduleRequestedByRadioButtonArgs(): Record<string, unknown> {
return {
classes: 'govuk-radios',
idPrefix: 'reschedule-requested-by',
name: 'reschedule-requested-by',
attributes: { 'data-cy': 'reschedule-requested-by-radios' },
fieldset: {
legend: {
text: this.presenter.rescheduleRequestedBy.label,
isPageHeading: false,
classes: 'govuk-fieldset__legend--m',
},
},
hint: {
text: 'Select one option',
},
errorMessage: ViewUtils.govukErrorMessage(this.presenter.rescheduleRequestedBy.errorMessage),
items: [
{
id: 'rescheduleRequestedBySpRadio',
value: 'Service Provider',
text: 'Service Provider',
checked: this.request.body['reschedule-requested-by'] === 'Service Provider',
},
{
id: 'rescheduleRequestedByUserRadio',
value: this.serviceUserName,
text: this.serviceUserName,
checked: this.request.body['reschedule-requested-by'] === this.serviceUserName,
},
],
}
}

private get rescheduledReasonTextareaArgs(): TextareaArgs {
return {
name: 'rescheduled-reason',
Expand All @@ -267,4 +285,26 @@ export default class ScheduleAppointmentView {
href: this.presenter.backLinkHref,
}
}

get renderArgs(): [string, Record<string, unknown>] {
return [
'serviceProviderReferrals/scheduleAppointment',
{
presenter: this.presenter,
dateInputArgs: this.dateInputArgs,
timeInputArgs: this.timeInputArgs,
durationDateInputArgs: this.durationDateInputArgs,
deliusOfficeLocationSelectArgs: this.deliusOfficeLocationSelectArgs,
errorSummaryArgs: this.errorSummaryArgs,
serverError: this.serverError,
address: this.addressFormView.inputArgs,
sessionTypeRadioInputArgs: this.sessionTypeRadioInputArgs,
rescheduleRequestedByRadioButtonArgs: this.rescheduleRequestedByRadioButtonArgs,
rescheduledReasonTextareaArgs: this.rescheduledReasonTextareaArgs,
meetingMethodRadioInputArgs: this.meetingMethodRadioInputArgs.bind(this),
backLinkArgs: this.backLinkArgs,
appointmentSummaryListArgs: ViewUtils.summaryListArgs(this.presenter.appointmentSummary),
},
]
}
}
5 changes: 4 additions & 1 deletion server/utils/errorMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,10 @@ export default {
empty: 'Select an office',
invalidOfficeSelection: 'Select an office from the list',
},
recsheduledReason: {
rescheduleRequestedBy: {
emptyRadio: 'Select who requested the appointment change',
},
rescheduledReason: {
empty: 'Enter reason for changing appointment',
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
{% endset -%}
{{ govukRadios(meetingMethodRadioInputArgs(deliusOfficeLocationHtml, otherLocationHtml)) }}
{% if presenter.hasExistingScheduledAppointment %}
{{ govukRadios(rescheduleRequestedByRadioButtonArgs) }}
{{ govukTextarea(rescheduledReasonTextareaArgs) }}
{% endif %}
</div>
Expand Down

0 comments on commit 187bcc3

Please sign in to comment.