-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
https://eaflood.atlassian.net/browse/WATER-4776 As part of the work to implement notifications in the system repo we have a requirement to download the recipients data as a CSV. This change adds the required data for the CSV. This recipient list does not require deduplication as per previous work. And we can expect multiple rows per licence. However, we will still a registered licence contact over an unregistered licence. The controller / presenter / service will be added in a later change.
- Loading branch information
1 parent
2195037
commit 1c2c15b
Showing
4 changed files
with
292 additions
and
20 deletions.
There are no files selected for viewing
125 changes: 125 additions & 0 deletions
125
app/services/notifications/setup/fetch-download-recipients.service.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
'use strict' | ||
|
||
/** | ||
* Formats the contact data from which recipients will be determined for the `/notifications/setup/download` link | ||
* @module FetchDownloadRecipientsService | ||
*/ | ||
|
||
const { db } = require('../../../../db/db.js') | ||
|
||
/** | ||
* Formats the contact data from which recipients will be determined for the `/notifications/setup/download` link | ||
* | ||
* > IMPORTANT! The source for notification contacts is `crm.document_headers` (view `licence_document_headers`), not | ||
* > the tables in `crm_v2`. | ||
* | ||
* Our overall goal is that a 'recipient' receives only one notification, irrespective of how many licences they are | ||
* linked to, or what roles they have. | ||
* | ||
* We start by determining which licences we need to send notifications for, by looking for 'due' return logs with a | ||
* matching 'due date' and cycle (summer or winter and all year). | ||
* | ||
* For each licence linked to one of these return logs, we extract the contact information. This is complicated by a | ||
* number of factors. | ||
* | ||
* - if a licence is _registered_ (more details below), we only care about the email addresses registered against it | ||
* - all licences should have a 'licence holder' contact, but they may also have a 'returns' contact | ||
* - there is a one-to-one relationship between `licences` and `licence_document_headers`, but the same contact (licence | ||
* holder or returns) can appear in different licences, and we are expected to group them into a 'single' contact | ||
* | ||
* WRLS has the concept of a registered and unregistered licences: | ||
* | ||
* - **Unregistered licences** have not been linked to an external email, so do not have a 'primary user'. All licences | ||
* have a contact with the role 'Licence holder', so this will be extracted as a 'contact'. They may also have a | ||
* contact with the role 'Returns to' (but only one), which is extracted as well. | ||
* - **Registered licences** have been linked to an external email. That initial email will be linked as the 'primary | ||
* user'. These licences may also have designated other accounts as 'returns agents', which will be extracted as well. | ||
* | ||
* If a licence is registered, we only extract the email contacts. Unregistered licences its the 'Licence holder' and | ||
* 'Returns to' contacts from `licence_document_headers.metadata->contacts`. | ||
* | ||
* FetchContactsService removes duplicates, and we have other functions that squash duplicate licences together. | ||
* We do not want to remove duplicates for the downloadable recipients. Each row in the CSV file should represent the | ||
* data received from this query. We expect to see duplicate licences with different contacts types. | ||
* | ||
* @param {Date} dueDate | ||
* @param {boolean} summer | ||
* | ||
* @returns {Promise<object[]>} - matching recipients | ||
*/ | ||
async function go(dueDate, summer) { | ||
const { rows } = await _fetch(dueDate, summer) | ||
|
||
return rows | ||
} | ||
|
||
async function _fetch(dueDate, summer) { | ||
const query = _query() | ||
|
||
return db.raw(query, [dueDate, summer, dueDate, summer]) | ||
} | ||
|
||
function _query() { | ||
return ` | ||
SELECT | ||
contacts.licence_ref, | ||
contacts.contact_type, | ||
contacts.return_reference, | ||
contacts.email, | ||
contacts.contact | ||
FROM ( | ||
SELECT DISTINCT | ||
ldh.licence_ref, | ||
(contacts->>'role') AS contact_type, | ||
(NULL) AS email, | ||
contacts as contact, | ||
rl.return_reference | ||
FROM public.licence_document_headers ldh | ||
INNER JOIN LATERAL jsonb_array_elements(ldh.metadata -> 'contacts') AS contacts ON TRUE | ||
INNER JOIN public.return_logs rl | ||
ON rl.licence_ref = ldh.licence_ref | ||
WHERE | ||
rl.status = 'due' | ||
AND rl.due_date = ? | ||
AND rl.metadata->>'isCurrent' = 'true' | ||
AND rl.metadata->>'isSummer' = ? | ||
AND contacts->>'role' IN ('Licence holder', 'Returns to') | ||
AND NOT EXISTS ( | ||
SELECT | ||
1 | ||
FROM public.licence_entity_roles ler | ||
WHERE | ||
ler.company_entity_id = ldh.company_entity_id | ||
AND ler."role" IN ('primary_user', 'user_returns') | ||
) | ||
UNION ALL | ||
SELECT | ||
ldh.licence_ref, | ||
(CASE | ||
WHEN ler."role" = 'primary_user' THEN 'Primary user' | ||
ELSE 'Returns agent' | ||
END) AS contact_type, | ||
le."name" AS email, | ||
(NULL) AS contact, | ||
rl.return_reference | ||
FROM public.licence_document_headers ldh | ||
INNER JOIN public.licence_entity_roles ler | ||
ON ler.company_entity_id = ldh.company_entity_id | ||
AND ler."role" IN ('primary_user', 'user_returns') | ||
INNER JOIN public.licence_entities le | ||
ON le.id = ler.licence_entity_id | ||
INNER JOIN public.return_logs rl | ||
ON rl.licence_ref = ldh.licence_ref | ||
WHERE | ||
rl.status = 'due' | ||
AND rl.due_date = ? | ||
AND rl.metadata->>'isCurrent' = 'true' | ||
AND rl.metadata->>'isSummer' = ? | ||
) contacts | ||
ORDER BY | ||
contacts.licence_ref` | ||
} | ||
|
||
module.exports = { | ||
go | ||
} |
136 changes: 136 additions & 0 deletions
136
test/services/notifications/setup/fetch-download-recipients.service.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
'use strict' | ||
|
||
// Test framework dependencies | ||
const Lab = require('@hapi/lab') | ||
const Code = require('@hapi/code') | ||
|
||
const { describe, it, before } = (exports.lab = Lab.script()) | ||
const { expect } = Code | ||
|
||
// Test helpers | ||
const LicenceDocumentHeaderSeeder = require('../../../support/seeders/licence-document-header.seeder.js') | ||
|
||
// Thing under test | ||
const FetchDownloadRecipientsService = require('../../../../app/services/notifications/setup/fetch-download-recipients.service.js') | ||
|
||
describe('Notifications Setup - Fetch Download Recipients service', () => { | ||
let dueDate | ||
let isSummer | ||
let testRecipients | ||
|
||
before(async () => { | ||
dueDate = '2024-04-28' // This needs to differ from any other returns log tests | ||
isSummer = 'false' | ||
|
||
testRecipients = await LicenceDocumentHeaderSeeder.seed(true, dueDate) | ||
}) | ||
|
||
describe('when there are recipients', () => { | ||
it('correctly returns "Primary user" and "Returns agent" contacts', async () => { | ||
const result = await FetchDownloadRecipientsService.go(dueDate, isSummer) | ||
|
||
const primaryUser = result.find((item) => item.contact_type === 'Primary user') | ||
const returnsAgent = result.find((item) => item.contact_type === 'Returns agent') | ||
|
||
expect(primaryUser).to.equal({ | ||
contact: null, | ||
contact_type: 'Primary user', | ||
email: '[email protected]', | ||
licence_ref: testRecipients.primaryUser.licenceRef, | ||
return_reference: testRecipients.primaryUser.returnLog.returnReference | ||
}) | ||
|
||
expect(returnsAgent).to.equal({ | ||
contact: null, | ||
contact_type: 'Returns agent', | ||
email: '[email protected]', | ||
licence_ref: testRecipients.primaryUser.licenceRef, | ||
return_reference: testRecipients.primaryUser.returnLog.returnReference | ||
}) | ||
}) | ||
|
||
it('correctly returns "Licence holder" contact', async () => { | ||
const result = await FetchDownloadRecipientsService.go(dueDate, isSummer) | ||
|
||
const found = result.filter((item) => item.licence_ref === testRecipients.licenceHolder.licenceRef) | ||
|
||
expect(found).to.equal([ | ||
{ | ||
contact: { | ||
addressLine1: '4', | ||
addressLine2: 'Privet Drive', | ||
addressLine3: null, | ||
addressLine4: null, | ||
country: null, | ||
county: 'Surrey', | ||
forename: 'Harry', | ||
initials: 'J', | ||
name: 'Licence holder only', | ||
postcode: 'WD25 7LR', | ||
role: 'Licence holder', | ||
salutation: null, | ||
town: 'Little Whinging', | ||
type: 'Person' | ||
}, | ||
contact_type: 'Licence holder', | ||
email: null, | ||
licence_ref: testRecipients.licenceHolder.licenceRef, | ||
return_reference: testRecipients.licenceHolder.returnLog.returnReference | ||
} | ||
]) | ||
}) | ||
|
||
it('correctly returns duplicate "Licence holder" and "Returns to" contacts', async () => { | ||
const result = await FetchDownloadRecipientsService.go(dueDate, isSummer) | ||
|
||
const found = result.filter((item) => item.licence_ref === testRecipients.licenceHolderAndReturnTo.licenceRef) | ||
|
||
expect(found).to.equal([ | ||
{ | ||
contact: { | ||
addressLine1: '4', | ||
addressLine2: 'Privet Drive', | ||
addressLine3: null, | ||
addressLine4: null, | ||
country: null, | ||
county: 'Surrey', | ||
forename: 'Harry', | ||
initials: 'J', | ||
name: 'Licence holder and returns to', | ||
postcode: 'WD25 7LR', | ||
role: 'Licence holder', | ||
salutation: null, | ||
town: 'Little Whinging', | ||
type: 'Person' | ||
}, | ||
contact_type: 'Licence holder', | ||
email: null, | ||
licence_ref: testRecipients.licenceHolderAndReturnTo.licenceRef, | ||
return_reference: testRecipients.licenceHolderAndReturnTo.returnLog.returnReference | ||
}, | ||
{ | ||
contact: { | ||
addressLine1: '4', | ||
addressLine2: 'Privet Drive', | ||
addressLine3: null, | ||
addressLine4: null, | ||
country: null, | ||
county: 'Surrey', | ||
forename: 'Harry', | ||
initials: 'J', | ||
name: 'Licence holder and returns to', | ||
postcode: 'WD25 7LR', | ||
role: 'Returns to', | ||
salutation: null, | ||
town: 'Little Whinging', | ||
type: 'Person' | ||
}, | ||
contact_type: 'Returns to', | ||
email: null, | ||
licence_ref: testRecipients.licenceHolderAndReturnTo.licenceRef, | ||
return_reference: testRecipients.licenceHolderAndReturnTo.returnLog.returnReference | ||
} | ||
]) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,18 +14,19 @@ const ReturnLogHelper = require('../helpers/return-log.helper.js') | |
* | ||
* @param {boolean} returnLogs - defaulted to true, this needs to be false if you do not want the `licenceDocumentHeader` | ||
* to be included in the recipients list | ||
* @param {string} dueDate | ||
* | ||
* @returns {object[]} - an array of the added licenceDocumentHeaders | ||
*/ | ||
async function seed(returnLogs = true) { | ||
async function seed(returnLogs = true, dueDate = '2023-04-28') { | ||
return { | ||
licenceHolder: await _addLicenceHolder(returnLogs), | ||
licenceHolderAndReturnTo: await _addLicenceHolderAndReturnToSameRef(returnLogs), | ||
primaryUser: await _addLicenceEntityRoles(returnLogs) | ||
licenceHolder: await _addLicenceHolder(returnLogs, dueDate), | ||
licenceHolderAndReturnTo: await _addLicenceHolderAndReturnToSameRef(returnLogs, dueDate), | ||
primaryUser: await _addLicenceEntityRoles(returnLogs, dueDate) | ||
} | ||
} | ||
|
||
async function _addLicenceEntityRoles(returnLogs) { | ||
async function _addLicenceEntityRoles(enableReturnLog, dueDate) { | ||
const primaryUser = { | ||
name: 'Primary User test', | ||
email: '[email protected]', | ||
|
@@ -68,16 +69,19 @@ async function _addLicenceEntityRoles(returnLogs) { | |
role: userReturns.role | ||
}) | ||
|
||
if (returnLogs) { | ||
await ReturnLogHelper.add({ | ||
licenceRef: licenceDocumentHeader.licenceRef | ||
let returnLog | ||
|
||
if (enableReturnLog) { | ||
returnLog = await ReturnLogHelper.add({ | ||
licenceRef: licenceDocumentHeader.licenceRef, | ||
dueDate | ||
}) | ||
} | ||
|
||
return licenceDocumentHeader | ||
return { ...licenceDocumentHeader, returnLog } | ||
} | ||
|
||
async function _addLicenceHolder(returnLogs) { | ||
async function _addLicenceHolder(enableReturnLog, dueDate) { | ||
const name = 'Licence holder only' | ||
const licenceDocumentHeader = await LicenceDocumentHeaderHelper.add({ | ||
metadata: { | ||
|
@@ -86,16 +90,19 @@ async function _addLicenceHolder(returnLogs) { | |
} | ||
}) | ||
|
||
if (returnLogs) { | ||
await ReturnLogHelper.add({ | ||
licenceRef: licenceDocumentHeader.licenceRef | ||
let returnLog | ||
|
||
if (enableReturnLog) { | ||
returnLog = await ReturnLogHelper.add({ | ||
licenceRef: licenceDocumentHeader.licenceRef, | ||
dueDate | ||
}) | ||
} | ||
|
||
return licenceDocumentHeader | ||
return { ...licenceDocumentHeader, returnLog } | ||
} | ||
|
||
async function _addLicenceHolderAndReturnToSameRef(returnLogs) { | ||
async function _addLicenceHolderAndReturnToSameRef(enableReturnLog, dueDate) { | ||
const name = 'Licence holder and returns to' | ||
const licenceDocumentHeader = await LicenceDocumentHeaderHelper.add({ | ||
metadata: { | ||
|
@@ -104,13 +111,16 @@ async function _addLicenceHolderAndReturnToSameRef(returnLogs) { | |
} | ||
}) | ||
|
||
if (returnLogs) { | ||
await ReturnLogHelper.add({ | ||
licenceRef: licenceDocumentHeader.licenceRef | ||
let returnLog | ||
|
||
if (enableReturnLog) { | ||
returnLog = await ReturnLogHelper.add({ | ||
licenceRef: licenceDocumentHeader.licenceRef, | ||
dueDate | ||
}) | ||
} | ||
|
||
return licenceDocumentHeader | ||
return { ...licenceDocumentHeader, returnLog } | ||
} | ||
|
||
function _contact(name, role) { | ||
|