From 8833668468ed0ca938ed3109cac0af2ad4c27081 Mon Sep 17 00:00:00 2001 From: Aaron Lambley <42571140+lambley@users.noreply.github.com> Date: Thu, 7 Mar 2024 22:40:38 +0000 Subject: [PATCH] Updates to Paternity Leave logic (#223) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update Paternity Leave logic in visual planner and accessible planner (#220) * SPL-286 - update text on the partner table of the ‘Leave summary’ table * SPL-286 - allow paternity leave any time * SPL-287 - allow paternity leave any time in accessible planner * SPL-285 allow gap in paternity leave * SPL-285 - fix: summary now displays correct paternity leave when non-consecutive * SPL-285 - fix: correctly calculate non-consecutive pat leave length * SPL-285 - restrict paternity leave to be within the range of maternity leave, else show shared * SPL-285 - allow paternity leave gap in accessible planner * SPL-285 - fix: restrict paternity leave to maternity leave bounds correctly * Update help text for Paternity Leave in Visual Planner (#221) * Add additional text for paternity leave entitlement * Update Paternity Leave and Pay information * run linter * sass-lint fixes * Update block tests * Update workflow to monitor changes to PR base branch * Specify buildpack version for Node 16.x.x --- .github/workflows/planner.yml | 2 +- app/filters.js | 7 ++- app/lib/blocks.js | 15 ++++++- app/lib/leaveTracker.js | 4 ++ app/lib/weeks.js | 43 ++++++++++++++----- app/paths.js | 9 +--- app/router.js | 25 +++++++---- .../answers-so-far/macro.njk | 17 +++++--- .../paternity-leave-end.njk | 2 +- .../paternity-leave-start.njk | 16 +++++-- .../accessible-planner/paternity-leave.njk | 3 +- .../paternity-leave-block-summary.njk | 42 ++++++++++++++++++ app/views/planner.njk | 17 ++++++++ app/views/tabs/leave-summary.njk | 7 +-- .../sass/components/_cookie-message.scss | 16 ++++--- manifest.yml | 2 +- test/unit/lib/blocks.test.js | 22 +++++++--- 17 files changed, 189 insertions(+), 60 deletions(-) create mode 100644 app/views/components/paternity-leave-block-summary.njk diff --git a/.github/workflows/planner.yml b/.github/workflows/planner.yml index de0b0189..2b2ea872 100644 --- a/.github/workflows/planner.yml +++ b/.github/workflows/planner.yml @@ -2,7 +2,7 @@ name: Spl-planner-continuous-check on: pull_request: - types: [opened, edited, reopened, ready_for_review, review_requested, auto_merge_enabled] + types: [opened, synchronize, edited, reopened, ready_for_review, review_requested, auto_merge_enabled] branches: - 'master' diff --git a/app/filters.js b/app/filters.js index 1b6896c1..a2d9b13f 100644 --- a/app/filters.js +++ b/app/filters.js @@ -2,7 +2,7 @@ const delve = require('dlv') const { getWeeksArray, parseWeeksFromData } = require('./utils') const Day = require('../common/lib/day') const { parseEligibilityFromData } = require('./lib/eligibility') -const { getBlockLength, getRemainingLeaveAllowance, getRemainingPayAllowance, parseLeaveBlocks, parseSplLeaveBlocks } = require('./lib/blocks') +const { getBlockLength, getPaternalBlockLength, getRemainingLeaveAllowance, getRemainingPayAllowance, parseLeaveBlocks, parseSplLeaveBlocks } = require('./lib/blocks') const _ = require('lodash') // Existing filters can be imported from env using env.getFilter(name) @@ -138,6 +138,10 @@ module.exports = function (env) { return getBlockLength(block) } + function paternalBlockLength (block) { + return getPaternalBlockLength(block) + } + function remainingLeaveAllowance (leaveBlocksDataObject) { const leaveBlocks = parseLeaveBlocks(leaveBlocksDataObject) return getRemainingLeaveAllowance(leaveBlocks) @@ -189,6 +193,7 @@ module.exports = function (env) { blocksToDates, htmlAttributesFromObject, blockLength, + paternalBlockLength, remainingLeaveAllowance, remainingPayAllowance, hasTakenSpl, diff --git a/app/lib/blocks.js b/app/lib/blocks.js index 05fcab9e..ff487c44 100644 --- a/app/lib/blocks.js +++ b/app/lib/blocks.js @@ -14,7 +14,7 @@ function getLeaveBlocks (weeks) { function getParentLeaveBlocks (weeks, parent) { const blocks = { - initial: null, + initial: [], spl: [] } @@ -28,7 +28,7 @@ function getParentLeaveBlocks (weeks, parent) { function store (block) { if (block && ['maternity', 'paternity', 'adoption'].includes(block.leave)) { - blocks.initial = block + blocks.initial.push(block) } else { blocks.spl.push(block) } @@ -273,6 +273,16 @@ function getBlockLength (block) { return parseInt(block.end) - parseInt(block.start) + 1 } +function getPaternalBlockLength (block) { + if (!block || block.length === 0) { + return 0 + } else if (block.length === 2) { + return 2 + } else if (block.length === 1) { + return parseInt(block[0].end) - parseInt(block[0].start) + 1 + } +} + function isBlockDataObject (obj) { return _.isObject(obj) && obj.leave !== undefined && @@ -357,5 +367,6 @@ module.exports = { getRemainingLeaveAllowance, getRemainingPayAllowance, getBlockLength, + getPaternalBlockLength, parseLeaveBlocksIntoLeaveAndPay } diff --git a/app/lib/leaveTracker.js b/app/lib/leaveTracker.js index 086b7996..14923fad 100644 --- a/app/lib/leaveTracker.js +++ b/app/lib/leaveTracker.js @@ -5,6 +5,7 @@ class LeaveTracker { this.initialBlockStarted = false this.initialBlockEnded = false this.initialBlockLength = 0 + this.totalLeaveWeeks = 0 } next (isLeave, weekNumber) { @@ -17,6 +18,9 @@ class LeaveTracker { if (this.initialBlockStarted && !this.initialBlockEnded) { this.initialBlockLength++ } + if (isLeave) { + this.totalLeaveWeeks++ + } } getLeaveBoundaries () { diff --git a/app/lib/weeks.js b/app/lib/weeks.js index f106d601..75baef97 100644 --- a/app/lib/weeks.js +++ b/app/lib/weeks.js @@ -23,16 +23,16 @@ class Weeks { const secondaryLeaveTracker = new LeaveTracker() let hasCurtailedPrimaryPay = false let primarySplHasStarted = false - for (let i = this.minimumWeek; i <= 52; i++) { - const week = this._getBaseWeek(i) - const weekLeaveAndPay = this._getWeekLeaveAndPay(i) + for (let weekNumber = this.minimumWeek; weekNumber <= 52; weekNumber++) { + const week = this._getBaseWeek(weekNumber) + const weekLeaveAndPay = this._getWeekLeaveAndPay(weekNumber) - primaryLeaveTracker.next(weekLeaveAndPay.primary.leave, i) + primaryLeaveTracker.next(weekLeaveAndPay.primary.leave, weekNumber) if (weekLeaveAndPay.primary.leave) { if (!primarySplHasStarted) { const startSplBecauseOfBreak = primaryLeaveTracker.initialBlockEnded const startSplBecauseOfPayAfterCurtailment = hasCurtailedPrimaryPay && weekLeaveAndPay.primary.pay - const startSplBecauseOfUserSelect = this.primary.firstSplWeek <= i + const startSplBecauseOfUserSelect = this.primary.firstSplWeek <= weekNumber primarySplHasStarted = startSplBecauseOfBreak || startSplBecauseOfPayAfterCurtailment || startSplBecauseOfUserSelect } dset(week.primary, 'leave.text', !primarySplHasStarted ? this.primaryLeaveType : 'shared') @@ -51,16 +51,32 @@ class Weeks { } if (!week.secondary.disabled) { - secondaryLeaveTracker.next(weekLeaveAndPay.secondary.leave, i) + secondaryLeaveTracker.next(weekLeaveAndPay.secondary.leave, weekNumber) if (weekLeaveAndPay.secondary.leave) { - const maxCellsDisplayedAsPaternity = this.eligibility.secondary.spl || this.eligibility.secondary.shpp ? 2 : 8 - const usePaternityLeave = i < 8 && !secondaryLeaveTracker.initialBlockEnded && secondaryLeaveTracker.initialBlockLength <= maxCellsDisplayedAsPaternity - dset(week.secondary, 'leave.text', usePaternityLeave ? 'paternity' : 'shared') + const maxCellsDisplayedAsPaternity = 2 + const usePaternityLeave = secondaryLeaveTracker.totalLeaveWeeks <= maxCellsDisplayedAsPaternity + + const latestPrimaryWeekLeave = this._getLatestPrimaryMaternityWeekLeave() + + // determine whether to display paternity or shared pay label + // the label should say "Paternity" if there are enough weeks left e.g. totalLeaveWeeks <= 2 + // and the selected week is within the range of the mother's leave + if (usePaternityLeave && weekNumber <= latestPrimaryWeekLeave && week.primary.leave.text === 'maternity') { + dset(week.secondary, 'leave.text', 'paternity') + } else { + dset(week.secondary, 'leave.text', 'shared') + } + + // determine whether to display pay label if (weekLeaveAndPay.secondary.pay) { dset(week.secondary, 'pay.text', this.payRates.secondary.statutory) } + + // determine whether to display pay checkbox dset(week.secondary, 'pay.eligible', this._weekEligibleForSecondaryPay(week)) } + + // determine whether to display leave checkbox dset(week.secondary, 'leave.eligible', this._weekEligibleForSecondaryLeave(week)) } @@ -89,7 +105,7 @@ class Weeks { if (this.eligibility.secondary.spl) { return true } else if (this.eligibility.secondary.statutoryLeave) { - return week.secondary.leave.text === 'paternity' && week.number < 8 + return week.secondary.leave.text === 'paternity' && week.number < 52 } else { return false } @@ -116,7 +132,7 @@ class Weeks { } else if (!this.eligibility.secondary.statutoryPay) { return false } else if (!this.eligibility.secondary.spl) { - return week.number < 8 + return week.number < 52 } else { return week.secondary.leave.text === 'paternity' } @@ -212,6 +228,11 @@ class Weeks { const payAsFloat = parseFloat(pay) return isNaN(payAsFloat) ? pay : ('£' + (+payAsFloat.toFixed(2)).toLocaleString('en-US')) } + + _getLatestPrimaryMaternityWeekLeave () { + const primaryLeaveWeeks = this.primary.leaveWeeks + return primaryLeaveWeeks.length > 0 ? Math.max(...primaryLeaveWeeks) : 0 + } } module.exports = Weeks diff --git a/app/paths.js b/app/paths.js index f9de905d..645d6645 100644 --- a/app/paths.js +++ b/app/paths.js @@ -200,11 +200,6 @@ class Paths { url: '/planner/paternity-leave/start', workFlowPage: true, workflowParentPath: '/planner/paternity-leave' - }, - end: { - url: '/planner/paternity-leave/end', - workFlowPage: true, - workflowParentPath: '/planner/paternity-leave/start' } }, 'shared-parental-leave': { @@ -213,10 +208,10 @@ class Paths { workflowParentPath: (data, isForValidator) => { if (isForValidator) { // Prevent circular reference when validating page history. - return '/planner/paternity-leave/end' + return '/planner/paternity-leave/start' } const splBlockPlanningOrder = dataUtils.splBlockPlanningOrder(data) - return splBlockPlanningOrder.length > 0 ? '/planner/shared-parental-leave/end' : '/planner/paternity-leave/end' + return splBlockPlanningOrder.length > 0 ? '/planner/shared-parental-leave/end' : '/planner/paternity-leave/start' }, start: { url: '/planner/shared-parental-leave/start', diff --git a/app/router.js b/app/router.js index 2e75f838..fa1df315 100644 --- a/app/router.js +++ b/app/router.js @@ -248,15 +248,22 @@ router.route(paths.getPath('planner.paternity-leave.start')) res.render('accessible-planner/paternity-leave-start') }) .post(function (req, res) { - res.redirect(paths.getPath('planner.paternity-leave.end')) - }) - -router.route(paths.getPath('planner.paternity-leave.end')) - .get(function (req, res) { - clearLaterLeaveBlockAnswers(req, 'secondary.initial.end') - res.render('accessible-planner/paternity-leave-end') - }) - .post(function (req, res) { + const startData = delve(req.session.data, 'leave-blocks.secondary.initial.start') + const updatedData = { + initial: [ + { + start: startData[0], + end: parseInt(startData[0]) + 1, + leave: 'paternity' + }, + { + start: startData[1], + end: parseInt(startData[1]) + 1, + leave: 'paternity' + } + ] + } + dset(req.session.data, 'leave-blocks.secondary.initial', updatedData.initial) res.redirect(paths.getPath('planner.shared-parental-leave')) }) diff --git a/app/views/accessible-planner/answers-so-far/macro.njk b/app/views/accessible-planner/answers-so-far/macro.njk index a0189390..020e358f 100644 --- a/app/views/accessible-planner/answers-so-far/macro.njk +++ b/app/views/accessible-planner/answers-so-far/macro.njk @@ -21,6 +21,11 @@
+ Leave{{ leaveBlocks["secondary"]["initial"][0].start }} + Leave{{ leaveBlocks["secondary"]["initial"][1].start }} +
+ {{ govukSummaryList({ rows: [ { @@ -76,10 +81,10 @@ }, { key: { - text: "Paternity Leave start" + text: "Paternity Leave start (week 1)" }, value: { - text: date(data, leaveBlocks["secondary"]["initial"]["start"]) + text: date(data, leaveBlocks["secondary"]["initial"][0]["start"]) }, actions: { items: [ @@ -93,17 +98,17 @@ }, { key: { - text: "Paternity Leave end" + text: "Paternity Leave start (week 2)" }, value: { - text: dateEnd(data, leaveBlocks["secondary"]["initial"]["end"]) + text: date(data, leaveBlocks["secondary"]["initial"][1]["start"]) }, actions: { items: [ { - href: "/planner/paternity-leave/end", + href: "/planner/paternity-leave/start", text: "Change", - visuallyHiddenText: "Paternity Leave end" + visuallyHiddenText: "Paternity Leave start" } ] } diff --git a/app/views/accessible-planner/paternity-leave-end.njk b/app/views/accessible-planner/paternity-leave-end.njk index ddc6c369..471424dd 100644 --- a/app/views/accessible-planner/paternity-leave-end.njk +++ b/app/views/accessible-planner/paternity-leave-end.njk @@ -25,7 +25,7 @@ {% set isOverseasAdoption = (data | isOverseasAdoption) %} {% set zeroWeek = (data | zeroWeek) %} {% set firstWeekOfLeave = (data["leave-blocks"]["secondary"]["initial"]["start"] | int) %} -{% set options = range(firstWeekOfLeave, firstWeekOfLeave + 2 if firstWeekOfLeave < 7 else 8) %} +{% set options = range(firstWeekOfLeave, firstWeekOfLeave + 2 if firstWeekOfLeave < 51 else 52) %} {% macro optionText(weekNumber) %} {% set totalWeeks = weekNumber - firstWeekOfLeave + 1 %} diff --git a/app/views/accessible-planner/paternity-leave-start.njk b/app/views/accessible-planner/paternity-leave-start.njk index 6adaa911..aa5088c1 100644 --- a/app/views/accessible-planner/paternity-leave-start.njk +++ b/app/views/accessible-planner/paternity-leave-start.njk @@ -26,9 +26,9 @@ {% set zeroWeek = (data | zeroWeek) %} {% set isAdoption = (data | isAdoption) %} {% set birthOrPlacement= (data | birthOrPlacement) %} -{% set primaryLeaveEnd = data["leave-blocks"]["primary"]["initial"]["end"] %} +{% set primaryLeaveEnd = data["leave-blocks"]["primary"]["initial"]["end"] | float %} -{% set options = range(0, 8) %} +{% set options = range(primaryLeaveEnd + 1) %} {% macro optionText(weekNumber) %} {% if (weekNumber === 0) %} @@ -49,7 +49,17 @@ id: "primary-leave-start", name: "leave-blocks[secondary][initial][start]", label: { - text: "When will Paternity Leave start?", + text: "When will the first week of Paternity Leave start?", + classes: "govuk-label--l", + isPageHeading: true + }, + items: options | mapValuesToSelectOptions(optionText) + }) }} + {{ govukSelect({ + id: "primary-leave-start", + name: "leave-blocks[secondary][initial][start]", + label: { + text: "When will the second week of Paternity Leave start?", classes: "govuk-label--l", isPageHeading: true }, diff --git a/app/views/accessible-planner/paternity-leave.njk b/app/views/accessible-planner/paternity-leave.njk index 0d0c19ff..db669ea2 100644 --- a/app/views/accessible-planner/paternity-leave.njk +++ b/app/views/accessible-planner/paternity-leave.njk @@ -42,8 +42,7 @@ {% call appendHiddenFields(data) %} {% set hint %}- A partner can take up to 2 weeks of Paternity Leave in the first 8 weeks after {{ birthOrPlacement }}. - Weeks must be taken together, without breaks. + A partner can take up to 2 weeks of Paternity Leave in the first year after {{ birthOrPlacement }}.
{% if (remainingLeaveAllowance > 0) %}
diff --git a/app/views/components/paternity-leave-block-summary.njk b/app/views/components/paternity-leave-block-summary.njk
new file mode 100644
index 00000000..ac36f691
--- /dev/null
+++ b/app/views/components/paternity-leave-block-summary.njk
@@ -0,0 +1,42 @@
+{% from "summary-list/macro.njk" import govukSummaryList %}
+
+{% macro paternityLeaveBlockSummary(options) %}
+ {{ govukSummaryList({
+ classes: "summary-block-print-margin",
+ rows: [
+ {
+ key: {
+ text: options.name + " starts (week 1)"
+ },
+ value: {
+ text: "week starting " + options.data | startDay | startOfWeek | offsetWeeks(options.block[0].start) | formatForDisplay
+ }
+ },
+ {
+ key: {
+ text: options.name + " starts (week 2)"
+ },
+ value: {
+ text: "week starting " + options.data | startDay | startOfWeek | offsetWeeks(options.block[1].start) | formatForDisplay
+ }
+ } if options.block.length > 1 else "",
+ {
+ key: {
+ text: "Length"
+ },
+ value: {
+ text: options.block | paternalBlockLength | weeks
+ }
+ },
+ {
+ key: {
+ text: "Notify employers"
+ },
+ value: {
+ html: "by " + (options.notify.date | formatForDisplay) + ("*" if options.notify.asterisk) + "
" +
+ "(" + options.notify.explanation + ")"
+ }
+ }
+ ]
+ }) }}
+{% endmacro %}
diff --git a/app/views/planner.njk b/app/views/planner.njk
index 1b0bc0a2..77cd4e35 100644
--- a/app/views/planner.njk
+++ b/app/views/planner.njk
@@ -105,6 +105,23 @@
html: examples,
classes: "print-hide"
}) }}
+
+ {% set additionalText %}
+ {% if isAdoption %}
+ From the 6th April 2024 (inclusive), adoption partners will be able to take the Paternity Leave and/or Pay that they are entitled to in two-separate, one-week blocks at any time within the 52 weeks after placement of the child. Any Paternity Leave and Pay they wish to claim must still be taken before starting Shared Parental Leave.
+
+ If you have been notified that the child is due to be placed with you on or after the 6th April, but the child is then placed with you early and before this date, the adoption partner will still be able to claim their Paternity Leave and Pay under these new rules.
+ {% else %}
+ From the 7th April 2024, birth partners of babies that are due to be born after this date will be able to take the Paternity Leave and/or Pay that they are entitled to in two-separate, one-week blocks at any time within the 52 weeks after the birth of the child. Any Paternity Leave and Pay they wish to claim must still be taken before starting Shared Parental Leave.
+
+ If your child is due to be born after the 6th April 2024 but is born prematurely or before this date, you will still be entitled to claim your Paternity Leave and Pay under these new rules.
+ {% endif %}
+ {% endset %}
+
+ {{ govukInsetText({
+ text: additionalText | safe
+ }) }}
+