diff --git a/integration_tests/e2e/sentence/previous-orders.cy.ts b/integration_tests/e2e/sentence/previous-orders.cy.ts index 488aea32..07edf7d0 100644 --- a/integration_tests/e2e/sentence/previous-orders.cy.ts +++ b/integration_tests/e2e/sentence/previous-orders.cy.ts @@ -5,7 +5,7 @@ context('Sentence', () => { cy.get('p') .eq(0) - .within(() => cy.get('a').invoke('attr', 'href').should('equal', '/case/X000001/handoff/delius')) + .within(() => cy.get('a').invoke('attr', 'href').should('equal', '/case/X000001/sentence/previous-orders/3')) cy.get('p') .eq(0) .within(() => cy.get('a').should('contain.text', 'CJA - Std Determinate Custody (16 Months)')) @@ -18,7 +18,7 @@ context('Sentence', () => { cy.get('p') .eq(1) - .within(() => cy.get('a').invoke('attr', 'href').should('equal', '/case/X000001/handoff/delius')) + .within(() => cy.get('a').invoke('attr', 'href').should('equal', '/case/X000001/sentence/previous-orders/2')) cy.get('p') .eq(1) .within(() => cy.get('a').should('contain.text', 'CJA - Std Determinate Custody (12 Months)')) diff --git a/integration_tests/e2e/sentence/previous-orders/previous-order.cy.ts b/integration_tests/e2e/sentence/previous-orders/previous-order.cy.ts new file mode 100644 index 00000000..da0ef884 --- /dev/null +++ b/integration_tests/e2e/sentence/previous-orders/previous-order.cy.ts @@ -0,0 +1,61 @@ +import Page from '../../../pages' +import PreviousOrderPage from '../../../pages/sentence/previous-orders/previous-order' + +context('Sentence', () => { + it('Previous order page is rendered', () => { + cy.visit('/case/X000001/sentence/previous-orders/3') + + const page = Page.verifyOnPage(PreviousOrderPage) + const breadCrumbElement = '.govuk-breadcrumbs__list-item' + + page.assertTextElementAtIndex('h2', 0, 'Previous orders') + + page.assertAnchorElementAtIndex(breadCrumbElement, 0, '/case') + page.assertTextAtAnchorElementAtIndex(breadCrumbElement, 0, 'My cases') + + page.assertAnchorElementAtIndex(breadCrumbElement, 1, '/case/X000001') + page.assertTextAtAnchorElementAtIndex(breadCrumbElement, 1, 'Caroline Wolff') + + page.assertAnchorElementAtIndex(breadCrumbElement, 2, '/case/X000001/sentence') + page.assertTextAtAnchorElementAtIndex(breadCrumbElement, 2, 'Sentence') + + page.assertAnchorElementAtIndex(breadCrumbElement, 3, '/case/X000001/sentence/previous-orders') + page.assertTextAtAnchorElementAtIndex(breadCrumbElement, 3, 'Previous orders') + + page.assertTextElementAtIndex('h2', 1, 'CJA - Std Determinate Custody (16 Months)') + + page.assertPageElementAtIndexWithin('section', 0, 'h2', 0, 'Offence') + page.assertPageElementAtIndexWithin('section', 0, 'dt', 0, 'Main offence') + page.assertPageElementAtIndexWithin('section', 0, 'dt', 1, 'Offence date') + page.assertPageElementAtIndexWithin('section', 0, 'dt', 2, 'Notes') + page.assertPageElementAtIndexWithin('section', 0, 'dt', 3, 'Additional offences') + page.assertPageElementAtIndexWithin('section', 0, 'dd', 0, 'Speeding (1 count)') + page.assertPageElementAtIndexWithin('section', 0, 'dd', 1, '20 January 2024') + page.assertPageElementAtIndexWithin('section', 0, 'dd', 2, 'My note') + page.assertPageElementAtIndexWithin('section', 0, 'dd', 3, 'Burglary (2 count)') + + page.assertPageElementAtIndexWithin('section', 1, 'h2', 0, 'Conviction') + page.assertPageElementAtIndexWithin('section', 1, 'dt', 0, 'Sentencing court') + page.assertPageElementAtIndexWithin('section', 1, 'dt', 1, 'Responsible court') + page.assertPageElementAtIndexWithin('section', 1, 'dt', 2, 'Conviction date') + page.assertPageElementAtIndexWithin('section', 1, 'dt', 3, 'Additional sentences') + page.assertPageElementAtIndexWithin('section', 1, 'dd', 0, 'Hull Court') + page.assertPageElementAtIndexWithin('section', 1, 'dd', 1, 'Birmingham Court') + page.assertPageElementAtIndexWithin('section', 1, 'dd', 2, '20 March 2024') + page.assertPageElementAtIndexWithin('section', 1, 'dd', 3, 'Disqualified from Driving') + + page.assertPageElementAtIndexWithin('section', 2, 'h2', 0, 'Sentence') + page.assertPageElementAtIndexWithin('section', 2, 'dt', 0, 'Order') + page.assertPageElementAtIndexWithin('section', 2, 'dt', 1, 'Sentence start date') + page.assertPageElementAtIndexWithin('section', 2, 'dt', 2, 'Sentence end date') + page.assertPageElementAtIndexWithin('section', 2, 'dt', 3, 'Terminated date') + page.assertPageElementAtIndexWithin('section', 2, 'dt', 4, 'Termination reason') + page.assertPageElementAtIndexWithin('section', 2, 'dt', 5, 'Court documents') + page.assertPageElementAtIndexWithin('section', 2, 'dd', 0, '12 month community order') + page.assertPageElementAtIndexWithin('section', 2, 'dd', 1, '1 February 2024') + page.assertPageElementAtIndexWithin('section', 2, 'dd', 2, '1 November 2024') + page.assertPageElementAtIndexWithin('section', 2, 'dd', 3, '31 January 2025') + page.assertPageElementAtIndexWithin('section', 2, 'dd', 4, '11 months elapsed (of 12 months)') + page.assertPageElementAtIndexWithin('section', 2, 'dd', 5, 'Pre-sentence report') + }) +}) diff --git a/integration_tests/pages/page.ts b/integration_tests/pages/page.ts index 5c5b01bd..5b8808a0 100644 --- a/integration_tests/pages/page.ts +++ b/integration_tests/pages/page.ts @@ -66,6 +66,16 @@ export default abstract class Page { .within(() => cy.get('a').invoke('attr', 'href').should('equal', value)) } + assertTextElementAtIndex = (element: string, index: number, value: string) => { + cy.get(element).eq(index).should('contain.text', value) + } + + assertTextAtAnchorElementAtIndex = (element: string, index: number, value: string) => { + cy.get(element) + .eq(index) + .within(() => cy.contains(value)) + } + assertAnchorElementAtIndexWithin = (element: string, index: number, anchorIndex: number, value: string) => { cy.get(element) .eq(index) diff --git a/integration_tests/pages/sentence/previous-orders/previous-order.ts b/integration_tests/pages/sentence/previous-orders/previous-order.ts new file mode 100644 index 00000000..10582098 --- /dev/null +++ b/integration_tests/pages/sentence/previous-orders/previous-order.ts @@ -0,0 +1,7 @@ +import Page from '../../page' + +export default class PreviousOrderPage extends Page { + constructor() { + super('Previous orders') + } +} diff --git a/server/data/masApiClient.ts b/server/data/masApiClient.ts index 9dd9389e..4e5051cf 100644 --- a/server/data/masApiClient.ts +++ b/server/data/masApiClient.ts @@ -22,6 +22,7 @@ import { CaseAccess, UserAccess } from './model/caseAccess' import { LicenceConditionNoteDetails } from './model/licenceConditionNoteDetails' import { AppointmentRequestBody, ActivityLogRequestBody } from '../@types' import { RequirementNoteDetails } from './model/requirementNoteDetails' +import { PreviousOrderDetail } from './model/previousOrderDetail' export default class MasApiClient extends RestClient { constructor(token: string) { @@ -50,6 +51,10 @@ export default class MasApiClient extends RestClient { return this.get({ path: `/sentence/${crn}/previous-orders`, handle404: false }) } + async getSentencePreviousOrder(crn: string, eventNumber: string): Promise { + return this.get({ path: `/sentence/${crn}/previous-orders/${eventNumber}`, handle404: false }) + } + async getSentenceOffences(crn: string, eventNumber: string): Promise { return this.get({ path: `/sentence/${crn}/offences/${eventNumber}`, handle404: false }) } diff --git a/server/data/model/previousOrderDetail.ts b/server/data/model/previousOrderDetail.ts new file mode 100644 index 00000000..79d5569c --- /dev/null +++ b/server/data/model/previousOrderDetail.ts @@ -0,0 +1,8 @@ +import { Name } from './common' +import { Sentence } from './sentenceDetails' + +export interface PreviousOrderDetail { + name: Name + title: string + sentence: Sentence +} diff --git a/server/data/model/previousOrderHistory.ts b/server/data/model/previousOrderHistory.ts index 38742b67..482f4975 100644 --- a/server/data/model/previousOrderHistory.ts +++ b/server/data/model/previousOrderHistory.ts @@ -6,6 +6,7 @@ export interface PreviousOrderHistory { } export interface PreviousOrder { + eventNumber: string title: string description: string terminationDate: string diff --git a/server/data/model/sentenceDetails.ts b/server/data/model/sentenceDetails.ts index e227ab7e..987cdd98 100644 --- a/server/data/model/sentenceDetails.ts +++ b/server/data/model/sentenceDetails.ts @@ -27,6 +27,7 @@ export interface Sentence { unpaidWorkProgress: string licenceConditions: LicenceCondition[] } + export interface OffenceDetails { eventNumber: string offence: Offence diff --git a/server/routes/sentence.ts b/server/routes/sentence.ts index 2b96d900..49ea69c3 100644 --- a/server/routes/sentence.ts +++ b/server/routes/sentence.ts @@ -119,6 +119,29 @@ export default function sentenceRoutes(router: Router, { hmppsAuthClient }: Serv }) }) + get('/case/:crn/sentence/previous-orders/:eventNumber', async (req, res, _next) => { + const { crn, eventNumber } = req.params + const token = await hmppsAuthClient.getSystemClientToken(res.locals.user.username) + + await auditService.sendAuditMessage({ + action: 'VIEW_MAS_SENTENCE_PREVIOUS_ORDER', + who: res.locals.user.username, + subjectId: crn, + subjectType: 'CRN', + correlationId: v4(), + service: 'hmpps-manage-people-on-probation-ui', + }) + + const masClient = new MasApiClient(token) + + const previousOrderDetail = await masClient.getSentencePreviousOrder(crn, eventNumber) + + res.render('pages/sentence/previous-orders/previous-order', { + previousOrderDetail, + crn, + }) + }) + get('/case/:crn/sentence/offences/:eventNumber', async (req, res, _next) => { const { crn, eventNumber } = req.params const token = await hmppsAuthClient.getSystemClientToken(res.locals.user.username) diff --git a/server/views/pages/sentence/_conviction.njk b/server/views/pages/sentence/_conviction.njk new file mode 100644 index 00000000..b5b680b8 --- /dev/null +++ b/server/views/pages/sentence/_conviction.njk @@ -0,0 +1,62 @@ +{% set hasAdditionalSentences = sentence.conviction.additionalSentences and sentence.conviction.additionalSentences.length > 0 %} + {% set additionalSentences %} + {% for additionalSentence in sentence.conviction.additionalSentences %} + {% set detailsHtml %} + {{ govukSummaryList({ + rows: [ + { + key: { text: 'Sentence' }, + value: { text: additionalSentence.description } + }, + { + key: { text: 'Value' }, + value: { text: additionalSentence.value } + } if additionalSentence.value, + { + key: { text: 'Length' }, + value: { text: additionalSentence.length } + } if additionalSentence.length, + { + key: { text: 'Notes' }, + value: { html: additionalSentence.notes | nl2br if additionalSentence.notes else 'No notes' } + } + ] + }) }} + {% endset %} + {{ govukDetails({ + classes: 'govuk-!-margin-bottom-1', + summaryText: additionalSentence.description, + html: detailsHtml + }) }} + {% endfor %} +{% endset %} + +{% set conviction %} + {{ govukSummaryList({ + rows: [ + { + key: { text: "Sentencing court" }, + value: { html: sentence.conviction.sentencingCourt | nl2br if sentence.conviction.sentencingCourt else 'No court details' } + }, + { + key: { text: "Responsible court" }, + value: { html: sentence.conviction.responsibleCourt | nl2br if sentence.conviction.responsibleCourt else 'No court details' } + }, + { + key: { text: "Conviction date" }, + value: { html: sentence.conviction.convictionDate | dateWithYear | nl2br if sentence.conviction.convictionDate else 'No conviction date' } + }, + { + key: { text: "Additional sentences" }, + value: { html: additionalSentences if hasAdditionalSentences else 'No additional sentences' } + } + ] + }) }} +{% endset %} + +{{ appSummaryCard({ + attributes: {'data-qa': 'convictionCard'}, + titleText: 'Conviction', + classes: 'govuk-!-margin-bottom-6 app-summary-card--large-title', + html: conviction +}) }} \ No newline at end of file diff --git a/server/views/pages/sentence/_sentence-offence.njk b/server/views/pages/sentence/_sentence-offence.njk new file mode 100644 index 00000000..acf24d2a --- /dev/null +++ b/server/views/pages/sentence/_sentence-offence.njk @@ -0,0 +1,42 @@ +{% set hasAdditionalOffences = sentence.offenceDetails.additionalOffences and sentence.offenceDetails.additionalOffences.length > 0 %} + {% set additionalOffences %} + {% if hasAdditionalOffences %} +
    + {% for additionalOffence in sentence.offenceDetails.additionalOffences %} +
  1. {{ additionalOffence.description }} ({{ additionalOffence.count }} count)
  2. + {% endfor %} +
+ + View additional offence details + {% endif %} +{% endset %} + +{% set offence %} + {{ govukSummaryList({ + rows: [ + { + key: { text: "Main offence" }, + value: { html: sentence.offenceDetails.offence.description + ' (' + sentence.offenceDetails.offence.count + ' count)' } + }, + { + key: { text: "Offence date" }, + value: { html: sentence.offenceDetails.dateOfOffence | dateWithYear } + }, + { + key: { text: "Notes" }, + value: { html: sentence.offenceDetails.notes | nl2br if sentence.offenceDetails.notes else 'No notes' } + }, + { + key: { text: "Additional offences" }, + value: { html: additionalOffences if hasAdditionalOffences else 'No additional offences' } + } + ] + }) }} +{% endset %} + +{{ appSummaryCard({ + attributes: {'data-qa': 'offenceCard'}, + titleText: 'Offence', + classes: 'govuk-!-margin-bottom-6 app-summary-card--large-title', + html: offence +}) }} \ No newline at end of file diff --git a/server/views/pages/sentence/_sentence.njk b/server/views/pages/sentence/_sentence.njk new file mode 100644 index 00000000..c8751a17 --- /dev/null +++ b/server/views/pages/sentence/_sentence.njk @@ -0,0 +1,55 @@ +{% set hasCourtDocuments = sentence.courtDocuments and sentence.courtDocuments.length > 0 %} + {% set courtDocuments %} + +{% endset %} + +{% set sentence %} + {{ govukSummaryList({ + rows: [ + { + key: { text: "Order" }, + value: { html: sentence.order.description | nl2br if sentence.order.description else 'No order details' } + }, + { + key: { text: "Sentence start date" }, + value: { html: sentence.order.startDate | dateWithYear | nl2br if sentence.order.startDate else 'No start date details' } + }, + { + key: { text: "Sentence end date" }, + value: { html: sentence.order.releaseDate | dateWithYear | nl2br if sentence.order.releaseDate else 'No release date details' } + }, + { + key: { text: "Terminated date" }, + value: { html: sentence.order.endDate | dateWithYear | nl2br if sentence.order.endDate else 'No end date details' } + }, + { + key: { text: "Termination reason" }, + value: { html: sentence.order.startDate | monthsOrDaysElapsed + ' elapsed (of ' + sentence.order.length + ' months)' | nl2br if sentence.order.startDate else 'No details' } + }, + { + key: { text: "Court documents" }, + value: { html: '' + courtDocuments if hasCourtDocuments else 'No court documents' + '' } + } + ] + }) }} +{% endset %} + +{{ appSummaryCard({ + attributes: {'data-qa': 'sentenceCard'}, + titleText: 'Sentence', + classes: 'govuk-!-margin-bottom-6 app-summary-card--large-title', + html: sentence +}) }} \ No newline at end of file diff --git a/server/views/pages/sentence/previous-orders.njk b/server/views/pages/sentence/previous-orders.njk index 40283f2d..917b31c6 100644 --- a/server/views/pages/sentence/previous-orders.njk +++ b/server/views/pages/sentence/previous-orders.njk @@ -32,7 +32,7 @@ {% for order in previousOrderHistory.previousOrders %}

- + {{ order.title }}

diff --git a/server/views/pages/sentence/previous-orders/previous-order.njk b/server/views/pages/sentence/previous-orders/previous-order.njk new file mode 100644 index 00000000..ba5d4480 --- /dev/null +++ b/server/views/pages/sentence/previous-orders/previous-order.njk @@ -0,0 +1,41 @@ +{% extends "../../../partials/layout.njk" %} +{% set headerPersonName = previousOrderDetail.name.forename + ' ' + previousOrderDetail.name.surname %} + +{% block beforeContent %} + {{ govukBreadcrumbs({ + items: [ + { + text: "My cases", + href: "/case" + }, + { + text: headerPersonName, + href: "/case/" + crn, + attributes: { "data-ai-id": "breadcrumbPersonNameLink" } + }, + { + text: "Sentence", + href: "/case/" + crn + "/sentence" + }, + { + text: "Previous orders", + href: "/case/" + crn + "/sentence/previous-orders" + } + ] + }) }} +{% endblock %} +{% block content %} +
+

Previous orders

+
+ + {% set sentence = previousOrderDetail.sentence %} + {% if sentence %} +

+ {{ previousOrderDetail.title }} +

+ {% include '../_sentence-offence.njk' %} + {% include '../_conviction.njk' %} + {% include '../_sentence.njk' %} + {% endif %} +{% endblock %} \ No newline at end of file diff --git a/wiremock/mappings/X000001-offence.json b/wiremock/mappings/X000001-offence.json index 964be075..08885abd 100644 --- a/wiremock/mappings/X000001-offence.json +++ b/wiremock/mappings/X000001-offence.json @@ -9,9 +9,52 @@ "status": 200, "jsonBody": { "name": { - "forename": "Eula", + "forename": "Caroline", "middleName": "", - "surname": "Schmeler" + "surname": "Wolff" + }, + "mainOffence": { + "description": "Possessing etc firearm or ammunition without firearm certificate (Group 1) - 08103", + "category": "Firearms offences", + "code": "08103", + "date": "2024-04-23", + "count": 1, + "notes": "overview" + }, + "additionalOffences": [ + { + "description": "Endangering railway passengers - 00600", + "category": "Endangering railway passengers", + "code": "00600", + "date": "2024-03-22", + "count": 1 + }, + { + "description": "Contravene court remedy order (S.42) - 08505", + "category": "Health and Safety at Work etc Act 1974", + "code": "08505", + "date": "2024-02-21", + "count": 3 + } + ] + }, + "headers": { + "Content-Type": "application/json" + } + } + }, + { + "request": { + "urlPattern": "/mas/sentence/X000001/offences/1", + "method": "GET" + }, + "response": { + "status": 200, + "jsonBody": { + "name": { + "forename": "Caroline", + "middleName": "", + "surname": "Wolff" }, "mainOffence": { "description": "Possessing etc firearm or ammunition without firearm certificate (Group 1) - 08103", diff --git a/wiremock/mappings/X000001-orders.json b/wiremock/mappings/X000001-orders.json index d0db4ae6..7da84b09 100644 --- a/wiremock/mappings/X000001-orders.json +++ b/wiremock/mappings/X000001-orders.json @@ -9,17 +9,19 @@ "status": 200, "jsonBody": { "name": { - "forename": "Eula", + "forename": "Caroline", "middleName": "", - "surname": "Schmeler" + "surname": "Wolff" }, "previousOrders": [ { + "eventNumber": "3", "title": "CJA - Std Determinate Custody (16 Months)", "description": "Merchant Shipping Acts - 15000", "terminationDate": "2024-04-09" }, { + "eventNumber": "2", "title": "CJA - Std Determinate Custody (12 Months)", "description": "Army - offences associated with - 15300", "terminationDate": "2024-04-08" @@ -30,6 +32,128 @@ "Content-Type": "application/json" } } + }, + { + "request": { + "urlPattern": "/mas/sentence/X000001/previous-orders/3", + "method": "GET" + }, + "response": { + "status": 200, + "jsonBody": { + "name": { + "forename": "Caroline", + "middleName": "", + "surname": "Wolff" + }, + "title": "CJA - Std Determinate Custody (16 Months)", + "sentence": { + "offenceDetails": { + "eventNumber": "1", + "offence": { + "description": "Speeding", + "count": 1 + }, + "dateOfOffence": "2024-01-20", + "notes": "My note", + "additionalOffences": [ + { + "description": "Burglary", + "count": 2 + }, + { + "description": "Assault", + "count": 1 + } + ] + }, + "conviction": { + "sentencingCourt": "Hull Court", + "responsibleCourt": "Birmingham Court", + "convictionDate": "2024-03-20", + "additionalSentences": [ + { + "length": 3, + "description": "Disqualified from Driving" + } + ] + }, + "order": { + "description": "12 month community order", + "length": 12, + "endDate": "2025-01-31", + "releaseDate": "2024-11-01", + "startDate": "2024-02-01" + }, + "courtDocuments": [ + { + "id": "4d74f43c-5b42-4317-852e-56c7d29b610b", + "lastSaved": "2024-04-03", + "documentName": "Pre-sentence report" + }, + { + "id": "6037becb-0d0c-44e1-8727-193f22df0494", + "lastSaved": "2024-04-01", + "documentName": "CPS Pack" + }, + { + "id": "d072ed9a-999f-4333-a116-a871a845aeb3", + "lastSaved": "", + "documentName": "Previous convictions" + } + ] + } + }, + "headers": { + "Content-Type": "application/json" + } + } + }, + { + "request": { + "urlPattern": "/mas/sentence/X000001/previous-orders/2", + "method": "GET" + }, + "response": { + "status": 200, + "jsonBody": { + "name": { + "forename": "Caroline", + "middleName": "", + "surname": "Wolff" + }, + "title": "CJA - Std Determinate Custody (12 Months)", + "sentence": { + "offenceDetails": { + "eventNumber": "1", + "offence": { + "description": "Copyright theft", + "count": 1 + }, + "dateOfOffence": "2022-03-07", + "notes": "", + "additionalOffences": [] + }, + "conviction": { + "sentencingCourt": "Hull Court", + "responsibleCourt": "Birmingham Court", + "convictionDate": "2022-11-21", + "additionalSentences": [] + }, + "order": { + "description": "12 month community order", + "length": 12, + "endDate": "2025-01-31", + "releaseDate": "2024-11-01", + "startDate": "2024-02-01" + }, + "courtDocuments": [] + } + }, + "headers": { + "Content-Type": "application/json" + } + } } ] }