From 94b7a2f68c500755ec60f401050c78a01e23eae5 Mon Sep 17 00:00:00 2001 From: Alan Cruikshanks Date: Thu, 10 Aug 2023 12:19:04 +0100 Subject: [PATCH] Add new data formatters to base presenter (#350) https://eaflood.atlassian.net/browse/WATER-4070 https://eaflood.atlassian.net/browse/WATER-4073 Whilst working on [Create endpoint to generate fake data](https://github.com/DEFRA/water-abstraction-system/pull/347) we added a number of formatters. These take DB data and re-format it to match how the UI displays the values. We will need these formatters in future changes we'll be making, the first of which is a re-designed bill run page. So, to help prepare for that we are adding those formatters to our `BasePresenter` so they are available to any future presenters we add. --- app/presenters/base.presenter.js | 85 +++++++++++++++++- .../create-transaction.presenter.js | 6 +- test/presenters/base.presenter.test.js | 88 ++++++++++++++++++- 3 files changed, 168 insertions(+), 11 deletions(-) diff --git a/app/presenters/base.presenter.js b/app/presenters/base.presenter.js index dfc8137a5e..4126eb2cfe 100644 --- a/app/presenters/base.presenter.js +++ b/app/presenters/base.presenter.js @@ -1,13 +1,60 @@ 'use strict' /** - * Converts a date into the format required by output files, eg 25/03/2021 becomes 25-MAR-2021 + * Converts a number which represents pence into pounds by dividing it by 100 * - * @param {Date} date The date value to format to a string + * This is such a simply calculation it could be done in place. But by having it as a named method we make it clear + * what we are doing rather than those that follow having to derive the intent. + * + * @param {Number} value + * + * @returns {Number} the value divided by 100 + */ +function convertPenceToPounds (value) { + return value / 100 +} + +/** + * Formats an abstraction day and month into its string variant, for example, 1 and 4 becomes '1 April' + * + * @param {Number} abstractionDay + * @param {Number} abstractionMonth Note: the index starts at 1, for example, 4 would be April + * + * @returns {string} The abstraction date formatted as a 'DD MMMM' string + */ +function formatAbstractionDate (abstractionDay, abstractionMonth) { + // NOTE: Because of the unique qualities of Javascript, Year and Day are literal values, month is an index! So, + // January is actually 0, February is 1 etc. This is why we are always deducting 1 from the months. + const abstractionDate = new Date(1970, abstractionMonth - 1, abstractionDay) + + return abstractionDate.toLocaleDateString('en-GB', { day: 'numeric', month: 'long' }) +} + +/** + * Formats an abstraction period into its string variant, for example, '1 April to 31 October' + * + * @param {Number} startDay + * @param {Number} startMonth + * @param {Number} endDay + * @param {Number} endMonth + * + * @returns {string} The abstraction period formatted as a 'DD MMMM to DD MMMM' string + */ +function formatAbstractionPeriod (startDay, startMonth, endDay, endMonth) { + const startDate = formatAbstractionDate(startDay, startMonth) + const endDate = formatAbstractionDate(endDay, endMonth) + + return `${startDate} to ${endDate}` +} + +/** + * Converts a date into the format required by the Charging Module, eg 25/03/2021 becomes 25-MAR-2021 + * + * @param {Date} date The date to be formatted * * @returns {string} The date formatted as a 'DD-MMM-YYYY' string */ -function formatDate (date) { +function formatChargingModuleDate (date) { // The output date format of methods such as toLocaleString() are based on the Unicode CLDR which is subject to // change and cannot be relied on to be consistent: https://github.com/nodejs/node/issues/42030. We therefore // generate the formatted date ourselves. @@ -22,6 +69,31 @@ function formatDate (date) { return `${day}-${month}-${year}` } +/** + * Formats a date into a human readable day, month and year string, for example, '12 September 2021' + * + * @param {Date} date The date to be formatted + * + * @returns {string} The date formatted as a 'DD MMMM YYYY' string + */ +function formatLongDate (date) { + return date.toLocaleDateString('en-GB', { year: 'numeric', month: 'long', day: 'numeric' }) +} + +/** + * Formats a number which represents a value in pounds as a money string, for example, 1149 as '1149.00' + * + * @param {Number} value The value to display as currency. Assumed to be in pounds + * @param {Boolean} includeSymbol Whether to add the £ symbol to the start of the returned string + * + * @returns {string} The value formatted as a money string with optional currency symbol + */ +function formatNumberAsMoney (value, includeSymbol = false) { + const symbol = includeSymbol ? '£' : '' + + return `${symbol}${value.toFixed(2)}` +} + /** * Pads a number to a given length with leading zeroes and returns the result as a string * @@ -37,6 +109,11 @@ function leftPadZeroes (number, length) { } module.exports = { - formatDate, + convertPenceToPounds, + formatAbstractionDate, + formatAbstractionPeriod, + formatChargingModuleDate, + formatLongDate, + formatNumberAsMoney, leftPadZeroes } diff --git a/app/presenters/charging-module/create-transaction.presenter.js b/app/presenters/charging-module/create-transaction.presenter.js index a12fe3dfb9..333e0f36bf 100644 --- a/app/presenters/charging-module/create-transaction.presenter.js +++ b/app/presenters/charging-module/create-transaction.presenter.js @@ -1,6 +1,6 @@ 'use strict' -const { formatDate } = require('../base.presenter.js') +const { formatChargingModuleDate } = require('../base.presenter.js') /** * Formats a transaction object, generated by `FormatSrocTransactionLineService`, as a Charging Module API transaction @@ -15,8 +15,8 @@ const { formatDate } = require('../base.presenter.js') * @returns {Object} an object that can be directly used as the body in a charging module POST transaction request */ function go (transaction, billingPeriod, invoiceAccountNumber, licence) { - const periodStart = formatDate(billingPeriod.startDate) - const periodEnd = formatDate(billingPeriod.endDate) + const periodStart = formatChargingModuleDate(billingPeriod.startDate) + const periodEnd = formatChargingModuleDate(billingPeriod.endDate) return { clientId: transaction.billingTransactionId, diff --git a/test/presenters/base.presenter.test.js b/test/presenters/base.presenter.test.js index 08cff232ad..51dbb9d61c 100644 --- a/test/presenters/base.presenter.test.js +++ b/test/presenters/base.presenter.test.js @@ -4,15 +4,67 @@ const Lab = require('@hapi/lab') const Code = require('@hapi/code') -const { describe, it } = exports.lab = Lab.script() +const { describe, it, beforeEach } = exports.lab = Lab.script() const { expect } = Code // Thing under test const BasePresenter = require('../../app/presenters/base.presenter.js') describe('Base presenter', () => { - describe('#formatDate()', () => { - it('correctly formats dates', async () => { + describe('#convertPenceToPounds()', () => { + let valueInPence + + describe('when the value divides evenly', () => { + beforeEach(() => { + valueInPence = 114900 + }) + + it('correctly returns the value in pounds, for example, 1149', async () => { + const result = BasePresenter.convertPenceToPounds(valueInPence) + + expect(result).to.equal(1149) + }) + }) + + describe('when the value does not divide evenly', () => { + beforeEach(() => { + valueInPence = 114901 + }) + + it('correctly returns the value in pounds, for example, 1149.01', async () => { + const result = BasePresenter.convertPenceToPounds(valueInPence) + + expect(result).to.equal(1149.01) + }) + }) + }) + + describe('#formatAbstractionDate()', () => { + const day = 12 + const month = 9 + + it('correctly formats the given date, for example, 12 September', async () => { + const result = BasePresenter.formatAbstractionDate(day, month) + + expect(result).to.equal('12 September') + }) + }) + + describe('#formatAbstractionPeriod()', () => { + const startDay = 1 + const startMonth = 4 + const endDay = 12 + const endMonth = 9 + + it('correctly formats the given period, for example, 1 April to 12 September', async () => { + const result = BasePresenter.formatAbstractionPeriod(startDay, startMonth, endDay, endMonth) + + expect(result).to.equal('1 April to 12 September') + }) + }) + + describe('#formatChargingModuleDate()', () => { + it('correctly formats the given date, for example, 12-SEP-2021', async () => { // We check an array of dates, one for each month, to ensure that every month is formatted correctly const results = [ new Date('2021-01-01T14:41:10.511Z'), @@ -27,7 +79,7 @@ describe('Base presenter', () => { new Date('2021-10-12T14:41:10.511Z'), new Date('2021-11-12T14:41:10.511Z'), new Date('2021-12-12T14:41:10.511Z') - ].map(date => BasePresenter.formatDate(date)) + ].map(date => BasePresenter.formatChargingModuleDate(date)) expect(results).to.equal([ '01-JAN-2021', @@ -46,6 +98,34 @@ describe('Base presenter', () => { }) }) + describe('#formatLongDate()', () => { + it('correctly formats the given date, for example, 12 September 2021', async () => { + const result = BasePresenter.formatLongDate(new Date('2021-09-12T14:41:10.511Z')) + + expect(result).to.equal('12 September 2021') + }) + }) + + describe('#formatNumberAsMoney()', () => { + const valueInPence = 1149.5 + + describe('when no £ symbol is requested', () => { + it('correctly returns the value as a money string with no symbol, for example, 1149.50', async () => { + const result = BasePresenter.formatNumberAsMoney(valueInPence) + + expect(result).to.equal('1149.50') + }) + }) + + describe('when the £ symbol is requested', () => { + it('correctly returns the value as a money string with a symbol, for example, £1149.50', async () => { + const result = BasePresenter.formatNumberAsMoney(valueInPence, true) + + expect(result).to.equal('£1149.50') + }) + }) + }) + describe('#leftPadZeroes()', () => { it('correctly pads numbers', async () => { const number = 123