diff --git a/assets/scss/application.scss b/assets/scss/application.scss index 8022ed80..0b1828f3 100755 --- a/assets/scss/application.scss +++ b/assets/scss/application.scss @@ -9,4 +9,7 @@ $govuk-page-width: $moj-page-width; @import './components/header-bar'; @import './components/summary-card'; +@import './components/card'; @import 'assets/scss/local'; + + diff --git a/assets/scss/components/_card.scss b/assets/scss/components/_card.scss new file mode 100644 index 00000000..e5850241 --- /dev/null +++ b/assets/scss/components/_card.scss @@ -0,0 +1,26 @@ +.app-card { + background-color: govuk-colour("light-grey"); + display: block; + padding: govuk-spacing(2); + text-decoration: none; + @include govuk-font($size: 19); + + @include govuk-media-query($from: desktop) { + padding: govuk-spacing(4); + } +} + +.app-card--blue { + background: govuk-colour("blue"); + color: govuk-colour("white"); +} + +.app-card--grey { + color: govuk-shade(govuk-colour("dark-grey", $legacy: "grey-1"), 30); + background: govuk-tint(govuk-colour("dark-grey", $legacy: "grey-1"), 90); +} + +.note-panel { + background-color: #f3f2f1; + padding: 1.5rem +} \ No newline at end of file diff --git a/assets/scss/components/_summary-card.scss b/assets/scss/components/_summary-card.scss index 9270fad5..c0d31526 100644 --- a/assets/scss/components/_summary-card.scss +++ b/assets/scss/components/_summary-card.scss @@ -81,3 +81,29 @@ display: none; } } + +.app-compliance-tag--compact { + width: 20px; + text-align: center; + float: right; +} + +.govuk-tag { + display: inline-block; + outline: 2px solid transparent; + outline-offset: -2px; + letter-spacing: 1px; + max-width: none; + text-decoration: none; + text-transform: uppercase; + font-family: "GDS Transport", arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-weight: 700; + font-size: 1rem; + padding-top: 5px; + padding-right: 8px; + padding-bottom: 4px; + padding-left: 8px; +} + diff --git a/integration_tests/e2e/activityLog.cy.ts b/integration_tests/e2e/activityLog.cy.ts new file mode 100644 index 00000000..822710e2 --- /dev/null +++ b/integration_tests/e2e/activityLog.cy.ts @@ -0,0 +1,29 @@ +import Page from '../pages/page' +import ActivityLogPage from '../pages/activityLog' + +context('Activity log', () => { + it('Activity log page is rendered in default view', () => { + cy.visit('/case/X000001/activity-log') + const page = Page.verifyOnPage(ActivityLogPage) + page.getRowData('timeline1', 'enforcement', 'Value').should('contain.text', 'Warning letter sent') + page.getRowData('timeline2', 'rarActivity', 'Value').should('contain.text', 'Stepping Stones') + page.getCardHeader('timeline3').should('contain.text', 'Waiting for evidence') + page.getRowData('timeline4', 'reschedule', 'Value').should('contain.text', 'Requested by Terry Jones') + page.getCardHeader('timeline5').should('contain.text', 'Office appointment at 10:15am') + page.getCardHeader('timeline6').should('contain.text', 'Phone call at 8:15am') + page.getCardHeader('timeline7').should('contain.text', 'Office appointment at 10:15am') + page.getCardHeader('timeline8').should('contain.text', 'Video call at 10:15am') + }) + it('Activity log page is rendered in compact view', () => { + cy.visit('/case/X000001/activity-log?view=compact') + const page = Page.verifyOnPage(ActivityLogPage) + page.getActivity('1').should('contain.text', 'Video call') + page.getActivity('2').should('contain.text', 'Phone call from Eula Schmeler') + page.getActivity('3').should('contain.text', 'Planned appointment') + page.getActivity('4').should('contain.text', 'Initial appointment') + page.getActivity('5').should('contain.text', 'Office appointment') + page.getActivity('6').should('contain.text', 'Phone call') + page.getActivity('7').should('contain.text', 'Office appointment') + page.getActivity('8').should('contain.text', 'Video call') + }) +}) diff --git a/integration_tests/pages/activityLog.ts b/integration_tests/pages/activityLog.ts new file mode 100644 index 00000000..ac8624fc --- /dev/null +++ b/integration_tests/pages/activityLog.ts @@ -0,0 +1,9 @@ +import Page, { PageElement } from './page' + +export default class ActivityLogPage extends Page { + constructor() { + super('Activity log') + } + + getActivity = (index: string): PageElement => cy.get(`[data-qa=timeline${index}Card]`) +} diff --git a/integration_tests/pages/page.ts b/integration_tests/pages/page.ts index bc37d572..1901fd21 100644 --- a/integration_tests/pages/page.ts +++ b/integration_tests/pages/page.ts @@ -25,7 +25,8 @@ export default abstract class Page { getTab = (tabName: string): PageElement => cy.get(`[data-qa=${tabName}Tab]`) - getCardHeader = (cardName: string): PageElement => cy.get(`[data-qa=${cardName}Card]`) + getCardHeader = (cardName: string): PageElement => + cy.get(`[class=app-summary-card__header]`).get(`[data-qa=${cardName}Card]`) getRowData = (cardName: string, rowName: string, type: string): PageElement => { return cy.get(`[data-qa=${cardName}Card]`).within(() => cy.get(`[data-qa=${rowName}${type}]`)) diff --git a/server/data/masApiClient.ts b/server/data/masApiClient.ts index ae21a5e3..220810b3 100644 --- a/server/data/masApiClient.ts +++ b/server/data/masApiClient.ts @@ -11,6 +11,7 @@ import { } from './model/personalDetails' import { AddressOverview, PersonSummary } from './model/common' import { SentenceDetails } from './model/sentenceDetails' +import { PersonActivity } from './model/activityLog' export default class MasApiClient extends RestClient { constructor(token: string) { @@ -64,4 +65,8 @@ export default class MasApiClient extends RestClient { async getPersonAppointment(crn: string, appointmentId: string): Promise { return this.get({ path: `/schedule/${crn}/appointment/${appointmentId}`, handle404: false }) } + + async getPersonActivityLog(crn: string): Promise { + return this.get({ path: `/activity/${crn}`, handle404: false }) + } } diff --git a/server/data/model/activityLog.ts b/server/data/model/activityLog.ts new file mode 100644 index 00000000..29134a02 --- /dev/null +++ b/server/data/model/activityLog.ts @@ -0,0 +1,7 @@ +import { PersonSummary } from './common' +import { Activity } from './schedule' + +export interface PersonActivity { + personSummary: PersonSummary + activities: Activity[] +} diff --git a/server/data/model/schedule.ts b/server/data/model/schedule.ts index 21b08ea3..793e36be 100644 --- a/server/data/model/schedule.ts +++ b/server/data/model/schedule.ts @@ -3,10 +3,10 @@ import { Document, Address } from './personalDetails' export interface Schedule { personSummary: PersonSummary - appointments: Appointment[] + appointments: Activity[] } -export interface Appointment { +export interface Activity { id?: string type?: string startDateTime?: string @@ -20,6 +20,8 @@ export interface Appointment { isInitial?: boolean isNationalStandard?: boolean rescheduled?: boolean + rescheduledStaff?: boolean + rescheduledPop?: boolean didTheyComply?: boolean absentWaitingEvidence?: boolean rearrangeOrCancelReason?: string @@ -31,11 +33,18 @@ export interface Appointment { acceptableAbsence?: boolean acceptableAbsenceReason?: string location?: Address + action?: string + isSystemContact?: boolean + isAppointment?: boolean + isEmailOrTextFromPop?: boolean + isPhoneCallFromPop?: boolean + isEmailOrTextToPop?: boolean + isPhoneCallToPop?: boolean lastUpdated?: string lastUpdatedBy?: Name } export interface PersonAppointment { personSummary: PersonSummary - appointment: Appointment + appointment: Activity } diff --git a/server/routes/activityLog.ts b/server/routes/activityLog.ts new file mode 100644 index 00000000..d25fc931 --- /dev/null +++ b/server/routes/activityLog.ts @@ -0,0 +1,99 @@ +import { type RequestHandler, Router } from 'express' +import { auditService } from '@ministryofjustice/hmpps-audit-client' +import { v4 } from 'uuid' +import asyncMiddleware from '../middleware/asyncMiddleware' +import type { Services } from '../services' +import MasApiClient from '../data/masApiClient' + +export default function activityLogRoutes(router: Router, { hmppsAuthClient }: Services) { + const get = (path: string | string[], handler: RequestHandler) => router.get(path, asyncMiddleware(handler)) + + get('/case/:crn/activity-log', async (req, res, _next) => { + const { crn } = req.params + const token = await hmppsAuthClient.getSystemClientToken(res.locals.user.username) + const masClient = new MasApiClient(token) + + if (req.query.view === 'compact') { + res.locals.compactView = true + } else { + res.locals.defaultView = true + } + + const personActivity = await masClient.getPersonActivityLog(crn) + + await auditService.sendAuditMessage({ + action: 'VIEW_MAS_ACTIVITY_LOG', + who: res.locals.user.username, + subjectId: crn, + subjectType: 'CRN', + correlationId: v4(), + service: 'hmpps-manage-a-supervision-ui', + }) + res.render('pages/activity-log', { + personActivity, + crn, + }) + }) + + get('/case/:crn/activity-log/:category', async (req, res, _next) => { + const { crn, category } = req.params + const token = await hmppsAuthClient.getSystemClientToken(res.locals.user.username) + + await auditService.sendAuditMessage({ + action: 'VIEW_MAS_ACTIVITY_LOG_CATEGORY', + who: res.locals.user.username, + subjectId: crn, + subjectType: 'CRN', + correlationId: v4(), + service: 'hmpps-manage-a-supervision-ui', + }) + + const masClient = new MasApiClient(token) + + const personActivity = await masClient.getPersonActivityLog(crn) + + if (req.query.view === 'compact') { + res.locals.compactView = true + } else { + res.locals.defaultView = true + } + + res.render('pages/activity-log', { + category, + personActivity, + crn, + }) + }) + + get('/case/:crn/activity-log/activity/:id', async (req, res, _next) => { + const { crn, id } = req.params + const token = await hmppsAuthClient.getSystemClientToken(res.locals.user.username) + + const masClient = new MasApiClient(token) + const personAppointment = await masClient.getPersonAppointment(crn, id) + const isActivityLog = true + const queryParams: string[] = [] + + const { category } = req.query + if (req.query.view) { + queryParams.push(`view=${req.query.view}`) + } + + await auditService.sendAuditMessage({ + action: 'VIEW_MAS_ACTIVITY_LOG_DETAIL', + who: res.locals.user.username, + subjectId: crn, + subjectType: 'CRN', + correlationId: v4(), + service: 'hmpps-manage-a-supervision-ui', + }) + + res.render('pages/schedule/appointment', { + category, + queryParams, + personAppointment, + crn, + isActivityLog, + }) + }) +} diff --git a/server/routes/index.ts b/server/routes/index.ts index cf4860d3..814617d7 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -8,6 +8,7 @@ import caseRoutes from './case' import personalDetailRoutes from './personalDetails' import sentenceRoutes from './sentence' import scheduleRoutes from './schedule' +import activityLogRoutes from './activityLog' export default function routes(services: Services): Router { const router = Router() @@ -17,5 +18,6 @@ export default function routes(services: Services): Router { personalDetailRoutes(router, services) sentenceRoutes(router, services) scheduleRoutes(router, services) + activityLogRoutes(router, services) return router } diff --git a/server/services/index.ts b/server/services/index.ts index 27ebf1ad..5d5504e3 100644 --- a/server/services/index.ts +++ b/server/services/index.ts @@ -13,7 +13,6 @@ export const services = () => { environment: config.env, extraColumns: [], }) - return { applicationInfo, hmppsAuthClient, diff --git a/server/utils/nunjucksSetup.ts b/server/utils/nunjucksSetup.ts index 48c8e533..8c6a0c06 100644 --- a/server/utils/nunjucksSetup.ts +++ b/server/utils/nunjucksSetup.ts @@ -3,7 +3,10 @@ import path from 'path' import nunjucks from 'nunjucks' import express from 'express' import { + activityLog, + activityLogDate, addressToList, + compactActivityLogDate, dateWithDayAndWithoutYear, dateWithNoDay, dateWithYear, @@ -82,6 +85,9 @@ export default function nunjucksSetup(app: express.Express, applicationInfo: App njkEnv.addFilter('deliusDateFormat', deliusDateFormat) njkEnv.addFilter('dayOfWeek', dayOfWeek) njkEnv.addFilter('toYesNo', toYesNo) + njkEnv.addFilter('compactActivityLogDate', compactActivityLogDate) + njkEnv.addFilter('activityLogDate', activityLogDate) + njkEnv.addGlobal('activityLog', activityLog) njkEnv.addGlobal('getRisksToThemselves', getRisksToThemselves) njkEnv.addGlobal('getCurrentRisksToThemselves', getCurrentRisksToThemselves) njkEnv.addGlobal('getPreviousRisksToThemselves', getPreviousRisksToThemselves) diff --git a/server/utils/utils.test.ts b/server/utils/utils.test.ts index cf849da7..070e4fe4 100644 --- a/server/utils/utils.test.ts +++ b/server/utils/utils.test.ts @@ -2,6 +2,9 @@ import { DateTime } from 'luxon' import { + activityLog, + activityLogDate, + compactActivityLogDate, convertToTitleCase, dateWithDayAndWithoutYear, dateWithYear, @@ -25,7 +28,7 @@ import { } from './utils' import { RiskResponse, RiskScore, RiskToSelf } from '../data/arnsApiClient' import { Name } from '../data/model/common' -import { Appointment } from '../data/model/schedule' +import { Activity } from '../data/model/schedule' const appointments = [ { @@ -39,11 +42,15 @@ const appointments = [ }, { startDateTime: DateTime.now().minus({ days: 1 }).toString(), + isNationalStandard: true, + isAppointment: true, hasOutcome: true, }, { startDateTime: DateTime.now().minus({ days: 2 }).toString(), + isNationalStandard: true, absentWaitingEvidence: true, + isAppointment: true, }, { startDateTime: DateTime.now().minus({ days: 3 }).toString(), @@ -287,13 +294,13 @@ describe('boolean to yes or no', () => { }) describe('scheduled appointments', () => { - it.each([['Filters correctly', appointments]])('%s scheduledAppointments(%s, %s)', (_: string, a: Appointment[]) => { + it.each([['Filters correctly', appointments]])('%s scheduledAppointments(%s, %s)', (_: string, a: Activity[]) => { expect(scheduledAppointments(a)[0]).toEqual(appointments[2]) }) }) describe('past appointments', () => { - it.each([['Filters correctly', appointments]])('%s pastAppointments(%s, %s)', (_: string, a: Appointment[]) => { + it.each([['Filters correctly', appointments]])('%s pastAppointments(%s, %s)', (_: string, a: Activity[]) => { expect(pastAppointments(a)[0]).toEqual(appointments[6]) }) }) @@ -302,7 +309,34 @@ describe('appointments to action', () => { it.each([ ['Filters absent awating evidence', appointments, 'evidence', appointments[4]], ['Filters no outcome', appointments, 'outcome', appointments[5]], - ])('%s getAppointmentsToAction(%s, %s)', (_: string, a: Appointment[], b: string, appointment: Appointment) => { + ])('%s getAppointmentsToAction(%s, %s)', (_: string, a: Activity[], b: string, appointment: Activity) => { expect(getAppointmentsToAction(a, b)[0]).toEqual(appointment) }) }) + +describe('compact Activity log date', () => { + it.each([ + ['Null', null, null], + ['gets day', '2024-05-25T09:08:34.123', 'Sat 25 May 2024'], + ])('%s compactActivityLogDate(%s, %s)', (_: string, a: string, expected: string) => { + expect(compactActivityLogDate(a)).toEqual(expected) + }) +}) + +describe('Activity log date', () => { + it.each([ + ['Null', null, null], + ['gets day', '2024-05-25T09:08:34.123', 'Saturday 25 May 2024'], + ])('%s activityLogDate(%s, %s)', (_: string, a: string, expected: string) => { + expect(activityLogDate(a)).toEqual(expected) + }) +}) + +describe('filters activity log', () => { + it.each([ + ['Filters absent awaiting evidence', appointments, 'waiting-for-evidence', appointments[4]], + ['Filters no outcome', appointments, 'national-standard-appointments-without-outcome', appointments[3]], + ])('%s activityLog(%s, %s)', (_: string, a: Activity[], b: string, appointment: Activity) => { + expect(activityLog(a, b)[0]).toEqual(appointment) + }) +}) diff --git a/server/utils/utils.ts b/server/utils/utils.ts index e69e80af..f677e12d 100644 --- a/server/utils/utils.ts +++ b/server/utils/utils.ts @@ -4,8 +4,7 @@ import { RiskScore, RiskToSelf } from '../data/arnsApiClient' import { Name } from '../data/model/common' import { Address } from '../data/model/personalDetails' import config from '../config' -import { Appointment } from '../data/model/schedule' -import logger from '../../logger' +import { Activity } from '../data/model/schedule' const properCase = (word: string): string => word.length >= 1 ? word[0].toUpperCase() + word.toLowerCase().slice(1) : word @@ -173,8 +172,6 @@ export const deliusHomepageUrl = () => { export const isInThePast = (datetimeString: string) => { if (!datetimeString || isBlank(datetimeString)) return null - logger.info(`Time now is ${DateTime.now().toString()}`) - return DateTime.now() > DateTime.fromISO(datetimeString) } @@ -188,7 +185,7 @@ export const dayOfWeek = (datetimeString: string) => { return DateTime.fromISO(datetimeString).toFormat('cccc') } -export const scheduledAppointments = (appointments: Appointment[]): Appointment[] => { +export const scheduledAppointments = (appointments: Activity[]): Activity[] => { return ( // Show future appointments and any appointments that are today appointments @@ -197,7 +194,7 @@ export const scheduledAppointments = (appointments: Appointment[]): Appointment[ ) } -export const pastAppointments = (appointments: Appointment[]): Appointment[] => { +export const pastAppointments = (appointments: Activity[]): Activity[] => { return ( // Show future appointments and any appointments that are today appointments @@ -206,7 +203,7 @@ export const pastAppointments = (appointments: Appointment[]): Appointment[] => ) } -export const getAppointmentsToAction = (appointments: Appointment[], type: string): Appointment[] => { +export const getAppointmentsToAction = (appointments: Activity[], type: string): Activity[] => { if (type === 'evidence') { return pastAppointments(appointments).filter(entry => entry.absentWaitingEvidence === true) } @@ -224,3 +221,88 @@ export const toYesNo = (value: boolean) => { } return 'Yes' } + +export const filterEntriesByCategory = (category: string) => { + return function filterActivity(activity: Activity) { + const { isAppointment } = activity + const isPastAppointment = isAppointment && isInThePast(activity.startDateTime) + const { isNationalStandard } = activity + const isRescheduled = activity.rescheduled && isNationalStandard + const hasOutcome = activity.didTheyComply !== undefined || isRescheduled + const { wasAbsent } = activity + const acceptableAbsence = wasAbsent && activity.acceptableAbsence === true + const unacceptableAbsence = wasAbsent && activity.acceptableAbsence === false + const waitingForEvidence = activity.absentWaitingEvidence === true + const complied = activity.didTheyComply === true + const attendedDidNotComply = activity.didTheyComply === false + + switch (category) { + case 'all-appointments': + return isAppointment + case 'other-communication': + return !isAppointment + case 'previous-appointments': + return isPastAppointment + case 'national-standard-appointments': + return isPastAppointment && isNationalStandard + case 'national-standard-appointments-without-outcome': + return isPastAppointment && isNationalStandard && !hasOutcome + case 'complied-appointments': + return isPastAppointment && complied && isNationalStandard + case 'acceptable-absence-appointments': + return isPastAppointment && acceptableAbsence && isNationalStandard + case 'unacceptable-absence-appointments': + return isPastAppointment && unacceptableAbsence && isNationalStandard + case 'waiting-for-evidence': + return isPastAppointment && waitingForEvidence && isNationalStandard + case 'attended-but-did-not-comply-appointments': + return isPastAppointment && attendedDidNotComply && isNationalStandard + case 'all-failure-to-comply-appointments': + return isPastAppointment && (attendedDidNotComply || unacceptableAbsence) && isNationalStandard + case 'upcoming-appointments': + return isAppointment && !isPastAppointment + case 'warning-letters': + return activity.action != null + case 'all-previous-activity': + return isPastAppointment || !isAppointment + case 'all-rescheduled': + return isRescheduled + case 'rescheduled-by-staff': + return isRescheduled && activity.rescheduledStaff === true + case 'rescheduled-by-person-on-probation': + return isRescheduled && activity.rescheduledPop === true + default: + return true + } + } +} + +export const activityLogDate = (datetimeString: string) => { + if (!datetimeString || isBlank(datetimeString)) return null + const date = DateTime.fromISO(datetimeString) + if (date.hasSame(DateTime.local(), 'day')) { + return 'Today' + } + if (date.hasSame(DateTime.local().minus({ days: 1 }), 'day')) { + return 'Yesterday' + } + return date.toFormat('cccc d MMMM yyyy') +} + +export const compactActivityLogDate = (datetimeString: string) => { + if (!datetimeString || isBlank(datetimeString)) return null + const date = DateTime.fromISO(datetimeString) + if (date.hasSame(DateTime.local(), 'day')) { + return 'Today' + } + + if (date.hasSame(DateTime.local().minus({ days: 1 }), 'day')) { + return 'Yesterday' + } + + return date.toFormat('ccc d MMM yyyy') +} + +export const activityLog = (contacts: Activity[], category: string) => { + return contacts.filter(filterEntriesByCategory(category)).sort((a, b) => (a.startDateTime < b.startDateTime ? 1 : -1)) +} diff --git a/server/views/_components/compliance-tag/template.njk b/server/views/_components/compliance-tag/template.njk index a739637e..f33e0334 100644 --- a/server/views/_components/compliance-tag/template.njk +++ b/server/views/_components/compliance-tag/template.njk @@ -15,7 +15,7 @@ {% set shorthand = '✘' %} {% set colour = 'red' %} {% elseif entry.wasAbsent === true %} - {% if entry?.acceptableAbsence === true %} + {% if entry.acceptableAbsence === true %} {% set text = 'Acceptable absence' %} {% set shorthand = '✓' %} {% set colour = 'green' %} diff --git a/server/views/pages/activity-log.njk b/server/views/pages/activity-log.njk new file mode 100644 index 00000000..f4478cb9 --- /dev/null +++ b/server/views/pages/activity-log.njk @@ -0,0 +1,83 @@ +{% extends "../partials/case.njk" %} +{% from "govuk/components/notification-banner/macro.njk" import govukNotificationBanner %} +{% set pageTitle = applicationName + " - Schedule" %} +{% set currentNavSection = 'timeline' %} +{% set currentSectionName = 'Activity log' %} +{% set headerPersonName = personActivity.personSummary.name.forename + ' ' + personActivity.personSummary.name.surname %} +{% set headerCRN = personActivity.personSummary.crn %} +{% set personSummary = personActivity.personSummary %} +{% set entries = activityLog(personActivity.activities, category or 'all-previous-activity') %} + +{% block beforeContent %} + {{ govukBreadcrumbs({ + items: [ + { + text: "Your cases", + href: "/search" + }, + { + text: headerPersonName, + href: "/case/" + crn + }, + { + text: currentSectionName + } + ] + }) }} +{% endblock %} + +{% switch category %} + {% case 'national-standard-appointments' %} + {% set title = 'National Standard appointments' %} + {% set emptyText = 'There are no National Standard appointments' %} + {% case 'national-standard-appointments-without-outcome' %} + {% set title = 'National Standard appointments without an outcome' %} + {% set emptyText = 'There are no National Standard appointments needing an outcome' %} + {% case 'complied-appointments' %} + {% set title = 'Complied appointments' %} + {% set emptyText = 'There are no complied appointments' %} + {% case 'acceptable-absence-appointments' %} + {% set title = 'Acceptable abscences' %} + {% set emptyText = 'There have been no acceptable absences' %} + {% case 'waiting-for-evidence' %} + {% set title = 'Absences waiting for evidence' %} + {% set emptyText = 'There are no absences waiting for evidence' %} + {% case 'all-failure-to-comply-appointments' %} + {% set title = 'Failures to comply within 12 months' %} + + {% set emptyText = 'There have been no failures to comply in the last 12 months' %} + {% case 'warning-letters' %} + {% set title = 'Warning letters' %} + {% set emptyText = 'There have been no warning letters' %} + {% case 'all-rescheduled' %} + {% set title = 'Rescheduled appointments' %} + {% set emptyText = 'There are no rescheduled appointments' %} + {% default %} + {% set title = 'Activity log' %} + {% set emptyText = 'There has been no activity' %} +{% endswitch %} + +{% block pageContent %} + +
+ {% include "./activity-log/_filters.njk" %} +
+ {% if not (title == 'Activity log') %} +

+ {{ title }} +

+ {% endif %} + {% include "./activity-log/_switch-views.njk" %} + {% set entries = activityLog(entries, category or 'all-previous-activity') %} + {% if entries.length > 0 %} + {% if compactView %} + {% include "./activity-log/_compact-view.njk" %} + {% else %} + {% include "./activity-log/_default-view.njk" %} + {% endif %} + {% else %} +

{{ emptyText }}

+ {% endif %} +
+
+{% endblock %} diff --git a/server/views/pages/activity-log/_appointment-timeline-entry.njk b/server/views/pages/activity-log/_appointment-timeline-entry.njk new file mode 100644 index 00000000..77c2f05c --- /dev/null +++ b/server/views/pages/activity-log/_appointment-timeline-entry.njk @@ -0,0 +1,34 @@ +{% set hasOutcomeBeenConfirmed = entry.didTheyComply === true or entry.rescheduled === true %} +{% set hasAppointmentStartTimePassed = isInThePast(this.startDateTime) %} +{% set shouldPromptToRecordAnOutcome = (not hasOutcomeBeenConfirmed) and hasAppointmentStartTimePassed %} +{% set appointment = entry %} + +{% set titleHtml %} + + {{ entry.type }} at {{ entry.startDateTime | govukTime }} on {{ thisDate }} + + + + {% include '../schedule/_appointment-prefix.njk' %} + +{% endset %} + +{% set html %} + {% include './_timeline-notes.njk' %} +{% endset %} + +{% set actionsHtml %} + {% if shouldPromptToRecordAnOutcome %} + Record an outcome + {% else %} + {{ appComplianceTag({ entry: entry, classes: 'govuk-!-margin-left-2' }) }} + {% endif %} +{% endset %} + +{{ appSummaryCard({ + attributes: {'data-qa': 'timeline' + loop.index + 'Card'}, + titleHtml: titleHtml, + classes: 'govuk-!-margin-bottom-2', + html: html, + actionsHtml: actionsHtml +}) }} diff --git a/server/views/pages/activity-log/_communication-timeline-entry.njk b/server/views/pages/activity-log/_communication-timeline-entry.njk new file mode 100644 index 00000000..b35ed340 --- /dev/null +++ b/server/views/pages/activity-log/_communication-timeline-entry.njk @@ -0,0 +1,15 @@ +{% set titleHtml %} + {% include './_communication-title.njk' %} at {{ entry.startDateTime | govukTime }} on {{ thisDate }} +{% endset %} + +{% set html %} + {% include './_timeline-notes.njk' %} +{% endset %} + +{{ appSummaryCard({ + attributes: {'data-qa': 'timeline' + loop.index + 'Card'}, + titleHtml: titleHtml, + classes: 'govuk-!-margin-bottom-2 app-summary-card--white-header', + html: html, + actionsHtml: actionsHtml +}) }} diff --git a/server/views/pages/activity-log/_communication-title.njk b/server/views/pages/activity-log/_communication-title.njk new file mode 100644 index 00000000..66c7cfcf --- /dev/null +++ b/server/views/pages/activity-log/_communication-title.njk @@ -0,0 +1,19 @@ +{%- if entry.action -%} + {{ entry.action }} +{%- else -%} + {% if entry.isEmailOrTextToPop === true %} + Email/Text to {{ personSummary.name | fullName }} + {% endif %} + + {% if entry.isEmailOrTextFromPop === true %} + Email or Text from {{ personSummary.name | fullName }} + {% endif %} + + {% if entry.isPhoneCallToPop === true %} + Phone call to {{ personSummary.name | fullName }} + {% endif %} + + {% if entry.isPhoneCallFromPop === true %} + Phone call from {{ personSummary.name | fullName }} + {% endif %} +{%- endif -%} \ No newline at end of file diff --git a/server/views/pages/activity-log/_compact-timeline-entry.njk b/server/views/pages/activity-log/_compact-timeline-entry.njk new file mode 100644 index 00000000..8c8271f6 --- /dev/null +++ b/server/views/pages/activity-log/_compact-timeline-entry.njk @@ -0,0 +1,30 @@ +{% set hasOutcomeBeenConfirmed = entry.didTheyComply === true or entry.rescheduled === true%} +{% set hasAppointmentStartTimePassed = isInThePast(entry.startDateTime) %} +{% set shouldPromptToRecordAnOutcome = (not hasOutcomeBeenConfirmed) and hasAppointmentStartTimePassed %} + +{% set titleHtml %} +

+ + {% if entry.isAppointment === true %} + {{ appComplianceTag({ entry: entry, classes: 'govuk-!-margin-left-2', compact: true }) }} + {{ entry.type }} at {{ entry.startDateTime | govukTime }} + {% elseif entry.isCommunication %} + {% include './_communication-title.njk' %} at {{ entry.startDateTime | govukTime }} + {% else %} + {{ entry.type }} + {% endif %} + on {{ thisDate }} + +
Record an outcome +

+{% endset %} + +{% set html %} + {% include './_timeline-notes.njk' %} +{% endset %} + +{% set actionsHtml %} + {{ appComplianceTag({ entry: entry, classes: 'govuk-!-margin-left-2' }) }} +{% endset %} + +{{ titleHtml | safe }} diff --git a/server/views/pages/activity-log/_compact-view.njk b/server/views/pages/activity-log/_compact-view.njk new file mode 100644 index 00000000..4f0b7405 --- /dev/null +++ b/server/views/pages/activity-log/_compact-view.njk @@ -0,0 +1,35 @@ + + + + + + + + + {% for entry in entries %} + {% set lastDate = lastEntry.startDateTime | compactActivityLogDate %} + {% set thisDate = entry.startDateTime | compactActivityLogDate %} + {% if lastDate != thisDate %} + + + + + + + +
DateActivity
{{ thisDate }} +
    + {% endif %} +
  • + {% if entry.isSystemContact === true %} + + {% else %} + {% include "./_compact-timeline-entry.njk" %} + {% endif %} +
  • + {% set lastEntry = entry %} + {% endfor %} +
+
diff --git a/server/views/pages/activity-log/_default-view.njk b/server/views/pages/activity-log/_default-view.njk new file mode 100644 index 00000000..82b49902 --- /dev/null +++ b/server/views/pages/activity-log/_default-view.njk @@ -0,0 +1,22 @@ +{% for entry in entries %} + {% set lastDate = lastEntry.startDateTime | activityLogDate %} + {% set thisDate = entry.startDateTime | activityLogDate %} + {% if lastDate != thisDate %} +

+ {{ thisDate }} +

+ {% endif %} + + {% if entry.isSystemContact %} + + {% elseif entry.isAppointment %} + {% include "./_appointment-timeline-entry.njk" %} + {% elseif entry.isCommunication %} + {% include "./_communication-timeline-entry.njk" %} + {% else %} + {% include "./_other-timeline-entry.njk" %} + {% endif %} + {% set lastEntry = entry %} +{% endfor %} diff --git a/server/views/pages/activity-log/_filters.njk b/server/views/pages/activity-log/_filters.njk new file mode 100644 index 00000000..fe5dd78a --- /dev/null +++ b/server/views/pages/activity-log/_filters.njk @@ -0,0 +1,29 @@ +
+
+ {{ govukButton({ + text: 'Add to log', + href: '/case/' + crn + '/handoff/delius' + }) }} + {% if category %} +

+ Remove all filters +

+ {% endif %} + +

+ Compliance filters +

+

Filter the activity log by National Standard (NS) activities:

+ + +
+
diff --git a/server/views/pages/activity-log/_other-timeline-entry.njk b/server/views/pages/activity-log/_other-timeline-entry.njk new file mode 100644 index 00000000..5e933059 --- /dev/null +++ b/server/views/pages/activity-log/_other-timeline-entry.njk @@ -0,0 +1,39 @@ +{% set titleHtml %} + + {{ entry.type }} on {{ thisDate }} + +{% endset %} + +{% set actionsHtml %} + {% if entry.rarCategory %} + {{ govukTag({text: 'RAR', classes: 'govuk-tag--purple'}) }} + {% endif %} + + {% if entry.didTheyComply === true %} + {{ govukTag({text: 'Complied', classes: 'govuk-tag--green'}) }} + {% elseif entry.didTheyComply === false %} + {{ govukTag({text: 'Failed to comply', classes: 'govuk-tag--red'}) }} + {% elseif entry.wasAbsent === true %} + {% if entry.acceptableAbsence === true %} + {{ govukTag({text: 'Acceptable absence', classes: 'govuk-tag--green'}) }} + {% else %} + {{ govukTag({text: 'Unacceptable absence', classes: 'govuk-tag--red'}) }} + {% endif %} + {% endif %} + + {% if entry.isSensitive === true %} + {{ govukTag({ text: "Sensitive", classes: "govuk-tag--grey" }) }} + {% endif %} +{% endset %} + +{% set html %} + {% include './_timeline-notes.njk' %} +{% endset %} + +{{ appSummaryCard({ + attributes: {'data-qa': 'timeline' + loop.index + 'Card' }, + titleHtml: titleHtml, + classes: 'govuk-!-margin-bottom-2 app-summary-card--white-header', + html: html, + actionsHtml: actionsHtml +}) }} diff --git a/server/views/pages/activity-log/_switch-views.njk b/server/views/pages/activity-log/_switch-views.njk new file mode 100644 index 00000000..3f131c0d --- /dev/null +++ b/server/views/pages/activity-log/_switch-views.njk @@ -0,0 +1,30 @@ +{% set html %} +

Change how the activity log looks:

+

+ {% if compactView %} + Default view + {% else %} + Default view + {% endif %} +
+ Displays notes and details for each activity +

+ +

+ {% if defaultView %} + Compact view + {% else %} + Compact view + {% endif %} +
+ Displays a condensed list of activity +

+{% endset %} + +{{ govukDetails({ + summaryText: "Switch view", + html: html, + classes: "govuk-!-margin-bottom-0" +}) }} + +
diff --git a/server/views/pages/activity-log/_timeline-notes.njk b/server/views/pages/activity-log/_timeline-notes.njk new file mode 100644 index 00000000..3aef1d8e --- /dev/null +++ b/server/views/pages/activity-log/_timeline-notes.njk @@ -0,0 +1,93 @@ + +{% set documents %} + +{% endset %} + +{{ govukWarningText({ + html: 'Absent and waiting for evidence
Change outcome', + iconFallbackText: 'Warning' +}) if entry.acceptableAbsence === null }} + +{% if + entry.rarCategory or + entry.acceptableAbsence === true or + entry.didTheyComply === false or + entry.rescheduled or + entry.description or + entry.action or + entry.documents.length > 0 %} +{{ govukSummaryList({ + classes: 'govuk-!-margin-bottom-0', + rows: [ + { + key: { + html: 'Description' + }, + value: { + html: '' + entry.description + '' + } + } if entry.description, + { + key: { + html: 'RAR activity' + }, + value: { + html: '' + entry.rarCategory + '' + } + } if entry.rarCategory, + { + key: { html: 'Reschedule'}, + value: { html: 'Requested by ' + personSummary.name | fullName + '' if entry.rescheduledPop == true + else 'Requested by ' + entry.rescheduledBy | fullName + 'Reason for not complying' }, + value: { html: ' + entry.nonComplianceReason + '' } + } if entry.didTheyComply === false, + { + key: { html: 'Reason for absence' }, + value: { html: '' + entry.acceptableAbsenceReason + '' if entry.acceptableAbsence === true } + } if entry.acceptableAbsenceReason, + { + key: { html: 'Enforcement' }, + value: { html: '' + entry.action + ''} + } if entry.action, + { + key: { html: 'Documents' }, + value: { html: '' + documents + '' } + } if entry.documents.length > 0 + } +}) }} + +
+{% endif %} + +{% set showNotesDetails = entry.notes and (entry.notes.length > 250 or entry.sensitive) %} +{% set notes %} + {% if entry.notes %} +

{{ entry.notes | nl2br | safe }}

+ {% endif %} +{% endset %} + +{% if showNotesDetails %} + {{ govukDetails({ + summaryText: "Notes (sensitive)" if entry.isSensitive else "Notes", + html: notes, + open: loop.index < 4, + classes: 'govuk-!-margin-bottom-0' + }) }} +{% elseif not entry.notes %} + {% if entry.isSensitive %} +

Sensitive (No notes)

+ {% else %} +

No notes

+ {% endif %} +{% else %} + {{ notes | safe }} +{% endif %} diff --git a/server/views/pages/overview.njk b/server/views/pages/overview.njk index 7761759b..aa627e4c 100644 --- a/server/views/pages/overview.njk +++ b/server/views/pages/overview.njk @@ -227,7 +227,7 @@ {% set overallRosh %} {% if hasRiskAssessment %} - +
{{ risks.summary.overallRiskLevel | replace('_',' ') }}{{ risks.summary.overallRiskLevel | replace('_',' ') }}
{% else %} There is no OASys risk assessment diff --git a/server/views/pages/schedule/appointment.njk b/server/views/pages/schedule/appointment.njk index d7a005f2..fc8a3e1e 100644 --- a/server/views/pages/schedule/appointment.njk +++ b/server/views/pages/schedule/appointment.njk @@ -1,5 +1,9 @@ {% extends "../../partials/layout.njk" %} {% set appointment = personAppointment.appointment %} + +{% set extraBreadcrumb = '?' + queryParams.join('&') if queryParams.length > 0 else '' %} +{% set category = '/' + category + '/' + extraBreadcrumb if category else extraBreadcrumb %} + {% block beforeContent %} {{ govukBreadcrumbs({ items: [ @@ -12,8 +16,8 @@ href: "/case/" + crn }, { - text: "Schedule", - href: "/case/" + crn + "/schedule" + text: "Schedule" if not isActivityLog else "Activity log", + href: "/case/" + crn + "/schedule" if not isActivityLog else "/case/" + crn + "/activity-log" + category }, { text: appointment.type diff --git a/wiremock/mappings/X000001-activity-log.json b/wiremock/mappings/X000001-activity-log.json new file mode 100644 index 00000000..4f3747dd --- /dev/null +++ b/wiremock/mappings/X000001-activity-log.json @@ -0,0 +1,622 @@ +{ + "mappings": [ + { + "request": { + "urlPattern": "/mas/activity/X000001", + "method": "GET" + }, + "response": { + "status": 200, + "jsonBody": { + "personSummary": { + "name": { + "forename": "Eula", + "surname": "Schmeler" + }, + "crn": "X000001", + "dateOfBirth": "1979-08-18" + }, + "activities": [ + { + "id": 11, + "type": "Phone call", + "startDateTime": "2044-12-22T09:15:00.382936Z[Europe/London]", + "endDateTime": "2044-12-22T09:30:00.382936Z[Europe/London]", + "rarToolKit": "Choices and Changes", + "rarCategory": "Stepping Stones", + "isSensitive": false, + "hasOutcome": false, + "wasAbsent": true, + "notes": "Phone call from Stuart to say that he is sorry for not attending. He sounded under the influence of alcohol. He has recently heard that his ex partner is getting remarried and stated that he “went on a bit of a bender” He states that he has not attempted to contact the victim but I will make enquiries with the police to confirm this as given that he is audibly drunk i don't think i can take his word for it. I asked him to come in and see me at 9am in the morning and reiterated that if he turns up drunk I will have to mark that as another failure to comply due to behavioural issues. This will result in him being in breach of his order. I reiterated that up until now he has been doing so well with his probation and it’s really important he remember and make use of all the tools and techniques we had discussed during his RAR sessions.\n\nHe agreed to see me at 9am as his next shift at Timpsons isn’t until the afternoon. He apologized many times for “letting me down” and has confirmed that he has put a reminder on his phone about the meeting.", + "isAppointment": false, + "isCommunication": true, + "isPhoneCallFromPop": true, + "officerName": { + "forename": "Terry", + "surname": "Jones" + }, + "lastUpdated": "2023-03-20", + "lastUpdatedBy": { + "forename": "Paul", + "surname": "Smith" + } + }, + { + "id": 12, + "type": "Video call", + "action": "Warning letter sent", + "startDateTime": "2045-03-22T10:15:00.382936Z[Europe/London]", + "endDateTime": "2045-03-22T10:30:00.382936Z[Europe/London]", + "rarToolKit": "Choices and Changes", + "isSensitive": false, + "hasOutcome": false, + "wasAbsent": true, + "notes": "Some notes", + "officerName": { + "forename": "William", + "surname": "Philips" + }, + "lastUpdated": "2023-03-20", + "lastUpdatedBy": { + "forename": "Paul", + "surname": "Smith" + } + }, + { + "id": 13, + "type": "Video call", + "startDateTime": "2023-02-12T10:15:00.382936Z[Europe/London]", + "endDateTime": "2023-02-12T10:30:00.382936Z[Europe/London]", + "rarToolKit": "Choices and Changes", + "isSensitive": false, + "hasOutcome": true, + "acceptableAbsence": false, + "wasAbsent": true, + "complied": false, + "isAppointment": true, + "isNationalStandard": true, + "notes": "Stuart did not attend this appointment. I phoned him several times on his mobile and left a voicemail. I also called the Timpson shop where he works and was told that he is off sick today and this is the first time that’s he’s been ill while working here.\n\nWill keep trying his mobile throughout the day. If I haven’t had any contact from Stuart by later this afternoon, I’ll visit him at home.", + "lastUpdated": "2023-03-20", + "officerName": { + "forename": "Paulie", + "surname": "Walnuts" + }, + "lastUpdatedBy": { + "forename": "Paul", + "surname": "Smith" + } + }, + { + "id": 14, + "type": "Phone call", + "startDateTime": "2024-03-22T08:15:00.382936Z[Europe/London]", + "endDateTime": "2024-03-22T08:30:00.382936Z[Europe/London]", + "rarToolKit": "Choices and Changes", + "isSensitive": false, + "hasOutcome": false, + "wasAbsent": true, + "notes": "Some notes", + "isAppointment": true, + "acceptableAbsence": true, + "acceptableAbsenceReason": "Trains were on strike and lost phone", + "isNationalStandard": true, + "officerName": { + "forename": "Steve", + "surname": "Bruce" + }, + "lastUpdated": "2023-03-20", + "lastUpdatedBy": { + "forename": "Paul", + "surname": "Smith" + } + }, + { + "id": 15, + "type": "Office appointment", + "startDateTime": "2024-03-22T10:15:00.382936Z[Europe/London]", + "endDateTime": "2024-03-22T10:30:00.382936Z[Europe/London]", + "rarToolKit": "Choices and Changes", + "isSensitive": false, + "hasOutcome": true, + "isAppointment": true, + "didTheyComply": true, + "isNationalStandard": true, + "notes": "Turned up and was very apologetic", + "lastUpdated": "2023-03-20", + "officerName": { + "forename": "Terry", + "surname": "Jones" + }, + "lastUpdatedBy": { + "forename": "Paul", + "surname": "Smith" + } + }, + { + "id": 16, + "type": "Office appointment", + "startDateTime": "2024-02-21T10:15:00.382936Z[Europe/London]", + "endDateTime": "2024-02-21T10:30:00.382936Z[Europe/London]", + "rarToolKit": "Choices and Changes", + "isSensitive": false, + "hasOutcome": false, + "wasAbsent": true, + "nonComplianceReason": "Was very argumentative and left the appointment", + "didTheyComply": false, + "isAppointment": true, + "isNationalStandard": true, + "notes": "Some notes", + "lastUpdated": "2023-03-20", + "officerName": { + "forename": "Terry", + "surname": "Jones" + }, + "lastUpdatedBy": { + "forename": "Paul", + "surname": "Smith" + } + }, + { + "id": 17, + "type": "Planned appointment", + "startDateTime": "2024-03-26T10:15:00.382936Z[Europe/London]", + "endDateTime": "2024-03-26T10:30:00.382936Z[Europe/London]", + "rarToolKit": "Choices and Changes", + "isSensitive": false, + "hasOutcome": false, + "wasAbsent": true, + "isAppointment": true, + "isNationalStandard": true, + "absentWaitingEvidence": true, + "notes": "Some notes", + "lastUpdated": "2023-03-20", + "officerName": { + "forename": "Terry", + "surname": "Jones" + }, + "lastUpdatedBy": { + "forename": "Paul", + "surname": "Smith" + } + }, + { + "id": 18, + "type": "Initial appointment", + "isInitial": true, + "startDateTime": "2024-03-25T10:15:00.382936Z[Europe/London]", + "endDateTime": "2024-03-25T10:30:00.382936Z[Europe/London]", + "rarToolKit": "Choices and Changes", + "isSensitive": false, + "hasOutcome": true, + "wasAbsent": false, + "isAppointment": true, + "isNationalStandard": true, + "rescheduled": true, + "rescheduledBy": { + "forename": "Terry", + "surname": "Jones" + }, + "rearrangeOrCancelReason": "Rearranged due to needing to go to work", + "absentWaitingEvidence": false, + "documents": [ + { + "id": "83fdbf8a-a2f2-43b4-93ef-67e71c04fc58", + "name": "Eula-Schmeler-X000001-UPW.pdf", + "lastUpdated": "2023-04-06T11:06:25.672587+01:00" + }, + { + "id": "c2650260-9568-476e-a293-0b168027a5f1", + "name": "Eula-Schmeler-X000001-UPW.pdf", + "lastUpdated": "2023-04-06T11:09:45.860739+01:00" + }, + { + "id": "b82e444b-c77c-4d44-bf99-4ce4dc426ff4", + "name": "Eula-Schmeler-X000001-UPW.pdf", + "lastUpdated": "2023-04-06T11:21:17.06356+01:00" + } + ], + "notes": "Some notes", + "lastUpdated": "2023-03-20", + "officerName": { + "forename": "Terry", + "surname": "Jones" + }, + "lastUpdatedBy": { + "forename": "Paul", + "surname": "Smith" + } + } + ] + }, + "headers": { + "Content-Type": "application/json" + } + } + }, + { + "request": { + "urlPattern": "/mas/schedule/X000001/appointment/11", + "method": "GET" + }, + "response": { + "status": 200, + "jsonBody": { + "personSummary": { + "name": { + "forename": "Eula", + "surname": "Schmeler" + }, + "crn": "X000001", + "dateOfBirth": "1979-08-18" + }, + "appointment": { + "id": 11, + "type": "Phone call", + "startDateTime": "2044-12-22T09:15:00.382936Z[Europe/London]", + "endDateTime": "2044-12-22T09:30:00.382936Z[Europe/London]", + "rarToolKit": "Choices and Changes", + "rarCategory": "Stepping Stones", + "isSensitive": false, + "hasOutcome": false, + "wasAbsent": true, + "notes": "Phone call from Stuart to say that he is sorry for not attending. He sounded under the influence of alcohol. He has recently heard that his ex partner is getting remarried and stated that he “went on a bit of a bender” He states that he has not attempted to contact the victim but I will make enquiries with the police to confirm this as given that he is audibly drunk i don't think i can take his word for it. I asked him to come in and see me at 9am in the morning and reiterated that if he turns up drunk I will have to mark that as another failure to comply due to behavioural issues. This will result in him being in breach of his order. I reiterated that up until now he has been doing so well with his probation and it’s really important he remember and make use of all the tools and techniques we had discussed during his RAR sessions.\n\nHe agreed to see me at 9am as his next shift at Timpsons isn’t until the afternoon. He apologized many times for “letting me down” and has confirmed that he has put a reminder on his phone about the meeting.", + "isAppointment": false, + "isCommunication": true, + "isPhoneCallFromPop": true, + "officerName": { + "forename": "Terry", + "surname": "Jones" + }, + "lastUpdated": "2023-03-20", + "lastUpdatedBy": { + "forename": "Paul", + "surname": "Smith" + } + } + }, + "headers": { + "Content-Type": "application/json" + } + } + }, + { + "request": { + "urlPattern": "/mas/schedule/X000001/appointment/12", + "method": "GET" + }, + "response": { + "status": 200, + "jsonBody": { + "personSummary": { + "name": { + "forename": "Eula", + "surname": "Schmeler" + }, + "crn": "X000001", + "dateOfBirth": "1979-08-18" + }, + "appointment": { + "id": 12, + "type": "Video call", + "action": "Warning letter sent", + "startDateTime": "2045-03-22T10:15:00.382936Z[Europe/London]", + "endDateTime": "2045-03-22T10:30:00.382936Z[Europe/London]", + "rarToolKit": "Choices and Changes", + "isSensitive": false, + "hasOutcome": false, + "wasAbsent": true, + "notes": "Some notes", + "officerName": { + "forename": "William", + "surname": "Philips" + }, + "lastUpdated": "2023-03-20", + "lastUpdatedBy": { + "forename": "Paul", + "surname": "Smith" + } + } + }, + "headers": { + "Content-Type": "application/json" + } + } + }, + { + "request": { + "urlPattern": "/mas/schedule/X000001/appointment/13", + "method": "GET" + }, + "response": { + "status": 200, + "jsonBody": { + "personSummary": { + "name": { + "forename": "Eula", + "surname": "Schmeler" + }, + "crn": "X000001", + "dateOfBirth": "1979-08-18" + }, + "appointment": { + "id": 13, + "type": "Video call", + "startDateTime": "2023-02-12T10:15:00.382936Z[Europe/London]", + "endDateTime": "2023-02-12T10:30:00.382936Z[Europe/London]", + "rarToolKit": "Choices and Changes", + "isSensitive": false, + "hasOutcome": true, + "acceptableAbsence": false, + "wasAbsent": true, + "complied": false, + "isAppointment": true, + "isNationalStandard": true, + "notes": "Stuart did not attend this appointment. I phoned him several times on his mobile and left a voicemail. I also called the Timpson shop where he works and was told that he is off sick today and this is the first time that’s he’s been ill while working here.\n\nWill keep trying his mobile throughout the day. If I haven’t had any contact from Stuart by later this afternoon, I’ll visit him at home.", + "lastUpdated": "2023-03-20", + "officerName": { + "forename": "Paulie", + "surname": "Walnuts" + }, + "lastUpdatedBy": { + "forename": "Paul", + "surname": "Smith" + } + } + }, + "headers": { + "Content-Type": "application/json" + } + } + }, + { + "request": { + "urlPattern": "/mas/schedule/X000001/appointment/14", + "method": "GET" + }, + "response": { + "status": 200, + "jsonBody": { + "personSummary": { + "name": { + "forename": "Eula", + "surname": "Schmeler" + }, + "crn": "X000001", + "dateOfBirth": "1979-08-18" + }, + "appointment": { + "id": 14, + "type": "Phone call", + "startDateTime": "2024-03-22T08:15:00.382936Z[Europe/London]", + "endDateTime": "2024-03-22T08:30:00.382936Z[Europe/London]", + "rarToolKit": "Choices and Changes", + "isSensitive": false, + "hasOutcome": false, + "wasAbsent": true, + "notes": "Some notes", + "isAppointment": true, + "acceptableAbsence": true, + "acceptableAbsenceReason": "Trains were on strike and lost phone", + "isNationalStandard": true, + "officerName": { + "forename": "Steve", + "surname": "Bruce" + }, + "lastUpdated": "2023-03-20", + "lastUpdatedBy": { + "forename": "Paul", + "surname": "Smith" + } + } + }, + "headers": { + "Content-Type": "application/json" + } + } + }, + { + "request": { + "urlPattern": "/mas/schedule/X000001/appointment/15", + "method": "GET" + }, + "response": { + "status": 200, + "jsonBody": { + "personSummary": { + "name": { + "forename": "Eula", + "surname": "Schmeler" + }, + "crn": "X000001", + "dateOfBirth": "1979-08-18" + }, + "appointment": { + "id": 15, + "type": "Office appointment", + "startDateTime": "2024-03-22T10:15:00.382936Z[Europe/London]", + "endDateTime": "2024-03-22T10:30:00.382936Z[Europe/London]", + "rarToolKit": "Choices and Changes", + "isSensitive": false, + "hasOutcome": true, + "isAppointment": true, + "didTheyComply": true, + "isNationalStandard": true, + "notes": "Turned up and was very apologetic", + "lastUpdated": "2023-03-20", + "officerName": { + "forename": "Terry", + "surname": "Jones" + }, + "lastUpdatedBy": { + "forename": "Paul", + "surname": "Smith" + } + } + }, + "headers": { + "Content-Type": "application/json" + } + } + }, + { + "request": { + "urlPattern": "/mas/schedule/X000001/appointment/16", + "method": "GET" + }, + "response": { + "status": 200, + "jsonBody": { + "personSummary": { + "name": { + "forename": "Eula", + "surname": "Schmeler" + }, + "crn": "X000001", + "dateOfBirth": "1979-08-18" + }, + "appointment": { + "id": 16, + "type": "Office appointment", + "startDateTime": "2024-02-21T10:15:00.382936Z[Europe/London]", + "endDateTime": "2024-02-21T10:30:00.382936Z[Europe/London]", + "rarToolKit": "Choices and Changes", + "isSensitive": false, + "hasOutcome": false, + "wasAbsent": true, + "nonComplianceReason": "Was very argumentative and left the appointment", + "didTheyComply": false, + "isAppointment": true, + "isNationalStandard": true, + "notes": "Some notes", + "lastUpdated": "2023-03-20", + "officerName": { + "forename": "Terry", + "surname": "Jones" + }, + "lastUpdatedBy": { + "forename": "Paul", + "surname": "Smith" + } + } + }, + "headers": { + "Content-Type": "application/json" + } + } + }, + { + "request": { + "urlPattern": "/mas/schedule/X000001/appointment/17", + "method": "GET" + }, + "response": { + "status": 200, + "jsonBody": { + "personSummary": { + "name": { + "forename": "Eula", + "surname": "Schmeler" + }, + "crn": "X000001", + "dateOfBirth": "1979-08-18" + }, + "appointment": { + "id": 17, + "type": "Planned appointment", + "startDateTime": "2024-03-26T10:15:00.382936Z[Europe/London]", + "endDateTime": "2024-03-26T10:30:00.382936Z[Europe/London]", + "rarToolKit": "Choices and Changes", + "isSensitive": false, + "hasOutcome": false, + "wasAbsent": true, + "isAppointment": true, + "isNationalStandard": true, + "absentWaitingEvidence": true, + "notes": "Some notes", + "lastUpdated": "2023-03-20", + "officerName": { + "forename": "Terry", + "surname": "Jones" + }, + "lastUpdatedBy": { + "forename": "Paul", + "surname": "Smith" + } + } + }, + "headers": { + "Content-Type": "application/json" + } + } + }, + { + "request": { + "urlPattern": "/mas/schedule/X000001/appointment/18", + "method": "GET" + }, + "response": { + "status": 200, + "jsonBody": { + "personSummary": { + "name": { + "forename": "Eula", + "surname": "Schmeler" + }, + "crn": "X000001", + "dateOfBirth": "1979-08-18" + }, + "appointment": { + "id": 18, + "type": "Initial appointment", + "isInitial": true, + "startDateTime": "2024-03-25T10:15:00.382936Z[Europe/London]", + "endDateTime": "2024-03-25T10:30:00.382936Z[Europe/London]", + "rarToolKit": "Choices and Changes", + "isSensitive": false, + "hasOutcome": true, + "wasAbsent": false, + "isAppointment": true, + "isNationalStandard": true, + "rescheduled": true, + "rescheduledBy": { + "forename": "Terry", + "surname": "Jones" + }, + "rearrangeOrCancelReason": "Rearranged due to needing to go to work", + "absentWaitingEvidence": false, + "documents": [ + { + "id": "83fdbf8a-a2f2-43b4-93ef-67e71c04fc58", + "name": "Eula-Schmeler-X000001-UPW.pdf", + "lastUpdated": "2023-04-06T11:06:25.672587+01:00" + }, + { + "id": "c2650260-9568-476e-a293-0b168027a5f1", + "name": "Eula-Schmeler-X000001-UPW.pdf", + "lastUpdated": "2023-04-06T11:09:45.860739+01:00" + }, + { + "id": "b82e444b-c77c-4d44-bf99-4ce4dc426ff4", + "name": "Eula-Schmeler-X000001-UPW.pdf", + "lastUpdated": "2023-04-06T11:21:17.06356+01:00" + } + ], + "notes": "Some notes", + "lastUpdated": "2023-03-20", + "officerName": { + "forename": "Terry", + "surname": "Jones" + }, + "lastUpdatedBy": { + "forename": "Paul", + "surname": "Smith" + } + } + }, + "headers": { + "Content-Type": "application/json" + } + } + } + ] +} diff --git a/wiremock/mappings/X000001-schedule.json b/wiremock/mappings/X000001-schedule.json index 5bd3be89..76389fa0 100644 --- a/wiremock/mappings/X000001-schedule.json +++ b/wiremock/mappings/X000001-schedule.json @@ -25,6 +25,7 @@ "rarToolKit": "Choices and Changes", "isSensitive": false, "hasOutcome": false, + "isEmailOrTextFromPop": true, "wasAbsent": true, "notes": "Some notes", "officerName": { @@ -112,6 +113,9 @@ "hasOutcome": false, "wasAbsent": true, "notes": "Some notes", + "isAppointment": true, + "acceptableAbsence": true, + "isNationalStandard": true, "officerName": { "forename": "Steve", "surname": "Bruce"