Skip to content

Commit

Permalink
Return Logs setup received date (#1602)
Browse files Browse the repository at this point in the history
https://eaflood.atlassian.net/browse/WATER-4844

As part of the work to migrate the return logs setup journey, this PR is focused on adding the 'When was this return received' page.

NOTE: This work is only for a due return. The pages will be updated in a separate PR to handle the editing of existing return logs.
  • Loading branch information
Beckyrose200 authored Jan 10, 2025
1 parent 5996791 commit 1eccd0f
Show file tree
Hide file tree
Showing 12 changed files with 1,023 additions and 4 deletions.
28 changes: 27 additions & 1 deletion app/controllers/return-logs-setup.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@
*/

const InitiateSessionService = require('../services/return-logs/setup/initiate-session.service.js')
const ReceivedService = require('../services/return-logs/setup/received.service.js')
const SubmitReceivedService = require('../services/return-logs/setup/submit-received.service.js')

async function received(request, h) {
const { sessionId } = request.params
const pageData = await ReceivedService.go(sessionId)

return h.view('return-logs/setup/received.njk', { ...pageData })
}

async function setup(request, h) {
const { returnLogId } = request.query
Expand All @@ -14,6 +23,23 @@ async function setup(request, h) {
return h.redirect(`/system/return-logs/setup/${session.id}/how-to-edit`)
}

async function submitReceived(request, h) {
const {
params: { sessionId },
payload
} = request

const pageData = await SubmitReceivedService.go(sessionId, payload)

if (pageData.error) {
return h.view('return-logs/setup/received.njk', pageData)
}

return h.redirect(`/system/return-logs/setup/${sessionId}/reported`)
}

module.exports = {
setup
received,
setup,
submitReceived
}
38 changes: 38 additions & 0 deletions app/presenters/return-logs/setup/received.presenter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use strict'

/**
* Format data for the `/return-log/setup/{sessionId}/received` page
* @module ReceivedPresenter
*/

/**
* Format data for the `/return-log/setup/{sessionId}/received` page
*
* @param {module:SessionModel} session - The return log setup session instance
*
* @returns {object} page data needed by the view template
*/
function go(session) {
const {
id: sessionId,
data: { returnReference },
receivedDateOptions,
receivedDateDay,
receivedDateMonth,
receivedDateYear
} = session

return {
receivedDateOption: receivedDateOptions ?? null,
receivedDateDay: receivedDateDay ?? null,
receivedDateMonth: receivedDateMonth ?? null,
receivedDateYear: receivedDateYear ?? null,
sessionId,
returnReference,
backLink: `/system/return-logs/setup/${session.id}/start`
}
}

module.exports = {
go
}
24 changes: 24 additions & 0 deletions app/routes/return-logs-setup.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,30 @@ const routes = [
}
}
}
},
{
method: 'GET',
path: '/return-logs/setup/{sessionId}/received',
options: {
handler: ReturnLogsSetupController.received,
auth: {
access: {
scope: ['billing']
}
}
}
},
{
method: 'POST',
path: '/return-logs/setup/{sessionId}/received',
options: {
handler: ReturnLogsSetupController.submitReceived,
auth: {
access: {
scope: ['billing']
}
}
}
}
]

Expand Down
35 changes: 35 additions & 0 deletions app/services/return-logs/setup/received.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use strict'

/**
* Orchestrates fetching and presenting the data for `/return-logs/setup/{sessionId}/received` page
* @module ReceivedService
*/

const ReceivedPresenter = require('../../../presenters/return-logs/setup/received.presenter.js')
const SessionModel = require('../../../models/session.model.js')

/**
* Orchestrates fetching and presenting the data for `/return-logs/setup/{sessionId}/received` page
*
* Supports generating the data needed for the received page in the return log setup journey. It fetches the
* current session record and formats the data needed for the page.
*
* @param {string} sessionId - The UUID of the current session
*
* @returns {Promise<object>} The view data for the received page
*/
async function go(sessionId) {
const session = await SessionModel.query().findById(sessionId)

const formattedData = ReceivedPresenter.go(session)

return {
pageTitle: 'When was the return received?',
activeNavBar: 'search',
...formattedData
}
}

module.exports = {
go
}
106 changes: 106 additions & 0 deletions app/services/return-logs/setup/submit-received.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
'use strict'

/**
* Orchestrates validating the data for `/return-logs/setup/{sessionId}/received` page
* @module SubmitReceivedService
*/

const ReceivedDateValidator = require('../../../validators/return-logs/setup/received-date.validator.js')
const ReceivedPresenter = require('../../../presenters/return-logs/setup/received.presenter.js')
const SessionModel = require('../../../models/session.model.js')

/**
* Orchestrates validating the data for `/return-logs/setup/{sessionId}/received` page
*
* It first retrieves the session instance for the return log setup session in progress. The session has details about
* the return log that are needed to validate that the chosen date is valid.
*
* The validation result is then combined with the output of the presenter to generate the page data needed by the view.
* If there was a validation error the controller will re-render the page so needs this information. If all is well the
* controller will redirect to the next page in the journey.
*
* @param {string} sessionId - The UUID of the current session
* @param {object} payload - The submitted form data
*
* @returns {Promise<object>} If no errors the page data for the received page else the validation error details
*/
async function go(sessionId, payload) {
const session = await SessionModel.query().findById(sessionId)

const { startDate } = session
const validationResult = _validate(payload, startDate)

if (!validationResult) {
await _save(session, payload)

return {}
}

const formattedData = _submittedSessionData(session, payload)

return {
pageTitle: 'When was the return received?',
activeNavBar: 'search',
error: validationResult,
...formattedData
}
}

async function _save(session, payload) {
const selectedOption = payload['received-date-options']
session.receivedDateOptions = selectedOption
const todaysDate = new Date(new Date().setHours(0, 0, 0, 0))

if (selectedOption === 'today') {
session.receivedDate = todaysDate
} else if (selectedOption === 'yesterday') {
// The setDate method updates the date object in place and returns a timestamp,
// not the updated date object itself. To ensure we store the correct date,
// we first modify the 'yesterday' variable and then assign it to session.receivedDate.
todaysDate.setDate(todaysDate.getDate() - 1)
session.receivedDate = todaysDate
} else {
session.receivedDateDay = payload['received-date-day']
session.receivedDateMonth = payload['received-date-month']
session.receivedDateYear = payload['received-date-year']
session.receivedDate = new Date(
`${payload['received-date-year']}-${payload['received-date-month']}-${payload['received-date-day']}`
)
}

return session.$update()
}

function _submittedSessionData(session, payload) {
session.receivedDateDay = payload['received-date-day'] ?? null
session.receivedDateMonth = payload['received-date-month'] ?? null
session.receivedDateYear = payload['received-date-year'] ?? null
session.receivedDateOptions = payload['received-date-options'] ?? null

const data = ReceivedPresenter.go(session)
return data
}

function _validate(payload, startDate) {
const validation = ReceivedDateValidator.go(payload, startDate)

if (!validation.error) {
return null
}

const { message, type } = validation.error.details[0]

// There are only two possible error scenarios: either a radio button has not been selected, in which case the date
// isn't visible so there cannot be an "invalid date" error; or an invalid date has been entered, in which case the
// date *is* visible so there cannot be a "radio button not selected" error. We identify the former by checking if the
// error type is `any.required`; and so if an error is present which isn't of this type, it must be a date error.
return {
message,
radioFormElement: type === 'any.required' ? { text: message } : null,
dateInputFormElement: type === 'any.required' ? null : { text: message }
}
}

module.exports = {
go
}
80 changes: 80 additions & 0 deletions app/validators/return-logs/setup/received-date.validator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'use strict'

/**
* Validates data submitted for the `/return-logs/setup/{sessionId}/received` page
* @module ReceivedDateValidator
*/

const Joi = require('joi').extend(require('@joi/date'))

const { leftPadZeroes } = require('../../../presenters/base.presenter.js')

/**
* Validates data submitted for the `/return-logs/setup/{sessionId}/received` page
*
* When entering a return log users must specify a received date for when the return was received by. The page
* allows them to select todays date, yesterdays date or enter a custom date.
*
* The custom date uses a {@link https://design-system.service.gov.uk/components/date-input/ | GOV.UK date input}
* which is 3 text fields for day, month and year. Users can enter what they like or omit a value completely which is
* why date validation can become quite complex.
*
* Also, the date they enter cannot be before the start date of the return or a date in the future.
*
* Finally, we also need to validate that the user selected one of the options; todays, yesterday or a custom
* date.
*
* @param {object} payload - The payload from the request to be validated
* @param {string} startDate - The date the return log starts from
*
* @returns {object} the result from calling Joi's schema.validate(). It will be an object with a `value:` property. If
* any errors are found the `error:` property will also exist detailing what the issues were
*/
function go(payload, startDate) {
const { 'received-date-options': selectedOption } = payload

if (selectedOption === 'custom-date') {
payload.fullDate = _fullDate(payload)

return _validateCustomReceivedDate(payload, startDate)
}
return _validateReceivedDate(payload)
}

function _fullDate(payload) {
const { 'received-date-day': day, 'received-date-month': month, 'received-date-year': year } = payload

const paddedMonth = month ? leftPadZeroes(month, 2) : ''
const paddedDay = day ? leftPadZeroes(day, 2) : ''

return `${year}-${paddedMonth}-${paddedDay}`
}

function _validateCustomReceivedDate(payload, startDate) {
const schema = Joi.object({
fullDate: Joi.date().format(['YYYY-MM-DD']).required().min(startDate).less('now').messages({
'date.base': 'Enter a return received date',
'date.format': 'Enter a real received date',
'date.min': 'Received date must be the return period start date or after it',
'date.less': "Received date must be either today's date or in the past"
}),
otherwise: Joi.forbidden()
})

return schema.validate(payload, { abortEarly: false, allowUnknown: true })
}

function _validateReceivedDate(payload) {
const schema = Joi.object({
'received-date-options': Joi.string().required().messages({
'any.required': 'Select the return received date',
'string.empty': 'Select the return received date'
})
})

return schema.validate(payload, { abortEarly: false, allowUnknown: true })
}

module.exports = {
go
}
Loading

0 comments on commit 1eccd0f

Please sign in to comment.