diff --git a/app/presenters/import/legacy/licence-document.presenter.js b/app/presenters/import/legacy/licence-document.presenter.js new file mode 100644 index 0000000000..a12412276b --- /dev/null +++ b/app/presenters/import/legacy/licence-document.presenter.js @@ -0,0 +1,26 @@ +'use strict' + +/** + * Maps legacy NALD licence data to the WRLS licence document format + * @module LicenceDocumentPresenter + */ + +/** + * Maps legacy NALD licence data to the WRLS licence document format + * + * @param {ImportLegacyLicenceDocumentType} licenceDocument - the legacy NALD licence + * + * @returns {object} the NALD licence data transformed into the WRLS licence document format + * ready for validation and persisting + */ +function go (licenceDocument) { + return { + licenceRef: licenceDocument.licence_ref, + endDate: licenceDocument.end_date, + startDate: licenceDocument.start_date + } +} + +module.exports = { + go +} diff --git a/app/services/import/legacy/fetch-licence-document.service.js b/app/services/import/legacy/fetch-licence-document.service.js new file mode 100644 index 0000000000..822d93c0d5 --- /dev/null +++ b/app/services/import/legacy/fetch-licence-document.service.js @@ -0,0 +1,64 @@ +'use strict' + +/** + * Fetches the licence document data from the import.NALD_ABS_LICENCES table for the licence being imported + * @module FetchLicenceDocumentService + */ + +const { db } = require('../../../../db/db.js') + +/** + * Fetches the licence document data from the import.NALD_ABS_LICENCES table for the licence being imported + * + * @param {string} regionCode - The NALD region code + * @param {string} licenceId - The NALD licence ID + * + * @returns {Promise} + */ +async function go (regionCode, licenceId) { + const query = _query() + + const { rows: [row] } = await db.raw(query, [regionCode, licenceId]) + + return row +} + +function _query () { + return ` + SELECT + CASE + WHEN + NULLIF(nal."ORIG_EFF_DATE", 'null') IS NULL + THEN MIN(TO_DATE(nalv."EFF_ST_DATE", 'DD/MM/YYYY' )) + ELSE TO_DATE(nal."ORIG_EFF_DATE", 'DD/MM/YYYY') + END + as start_date, + LEAST( + TO_DATE(NULLIF(nal."LAPSED_DATE", 'null'), 'DD/MM/YYYY'), + TO_DATE(NULLIF(nal."REV_DATE", 'null'), 'DD/MM/YYYY'), + TO_DATE(NULLIF(nal."EXPIRY_DATE", 'null'), 'DD/MM/YYYY') + ) as end_date, + nal."LIC_NO" as licence_ref + FROM import."NALD_ABS_LICENCES" nal + INNER JOIN import."NALD_ABS_LIC_VERSIONS" nalv + ON nalv."FGAC_REGION_CODE" = nal."FGAC_REGION_CODE" + AND nalv."AABL_ID" = nal."ID" + AND NOT nalv."STATUS" = 'DRAFT' + WHERE nalv."FGAC_REGION_CODE" = ? AND nalv."AABL_ID" = ? + GROUP BY nal."FGAC_REGION_CODE", nal."ID", nal."LIC_NO", nal."ORIG_EFF_DATE", nal."EXPIRY_DATE", nal."REV_DATE", nal."LAPSED_DATE"; + ` +} + +module.exports = { + go +} + +/** + * Representation of a licence document fetched from the NALD data + * @typedef {object} ImportLegacyLicenceDocumentType + * + * @property {Date} start_date - The effective start date of the license. + * @property {Date | null} end_date - The earliest of the lapsed, revision, or expiry dates. + * @property {string} external_id - A combination of the region code and licence ID. + * @property {string} licence_ref - The licence number. + */ diff --git a/app/services/import/legacy/process-licence.service.js b/app/services/import/legacy/process-licence.service.js index e52d7cda1c..c91789b92c 100644 --- a/app/services/import/legacy/process-licence.service.js +++ b/app/services/import/legacy/process-licence.service.js @@ -9,6 +9,7 @@ const LicenceStructureValidator = require('../../../validators/import/licence-st const PersistImportService = require('../persist-import.service.js') const ProcessLicenceReturnLogsService = require('../../jobs/return-logs/process-licence-return-logs.service.js') const TransformAddressesService = require('./transform-addresses.service.js') +const TransformLicenceDocumentService = require('./transform-licence-document.service.js') const TransformCompaniesService = require('./transform-companies.service.js') const TransformCompanyAddressesService = require('./transform-company-addresses.service.js') const TransformContactsService = require('./transform-contacts.service.js') @@ -38,6 +39,9 @@ async function go (licenceRef) { await TransformLicenceVersionPurposesService.go(regionCode, naldLicenceId, transformedLicence) await TransformLicenceVersionPurposeConditionsService.go(regionCode, naldLicenceId, transformedLicence) + // Document + await TransformLicenceDocumentService.go(regionCode, naldLicenceId, transformedLicence) + // Transform the company data const { transformedCompanies } = await TransformCompaniesService.go(regionCode, naldLicenceId) diff --git a/app/services/import/legacy/transform-licence-document.service.js b/app/services/import/legacy/transform-licence-document.service.js new file mode 100644 index 0000000000..ec404b597a --- /dev/null +++ b/app/services/import/legacy/transform-licence-document.service.js @@ -0,0 +1,35 @@ +'use strict' + +/** + * Transforms all NALD licence document data into an object that matches the WRLS structure + * @module ImportLegacyTransformLicenceDocumentService + */ + +const FetchLicenceDocumentService = require('./fetch-licence-document.service.js') +const LicenceDocumentPresenter = require('../../../presenters/import/legacy/licence-document.presenter.js') +const LicenceDocumentValidator = require('../../../validators/import/licence-document.validator.js') + +/** + * Transforms all NALD licence document data into an object that matches the WRLS structure + * + * NALD does not have a concept of a document it is a legacy WRLS construct + * + * After transforming and validating the NALD licence version data, it attaches it to the licence we're importing. + * + * @param {string} regionCode - The NALD region code for the licence being imported + * @param {string} naldLicenceId - The NALD ID for the licence being imported + * @param {object} transformedLicence - An object representing a valid WRLS licence + */ +async function go (regionCode, naldLicenceId, transformedLicence) { + const naldLicenceDocument = await FetchLicenceDocumentService.go(regionCode, naldLicenceId) + + const transformedLicenceDocument = LicenceDocumentPresenter.go(naldLicenceDocument) + + LicenceDocumentValidator.go(transformedLicenceDocument) + + transformedLicence.licenceDocument = transformedLicenceDocument +} + +module.exports = { + go +} diff --git a/app/services/import/persist-import.service.js b/app/services/import/persist-import.service.js index c9256dfd28..6f6407c758 100644 --- a/app/services/import/persist-import.service.js +++ b/app/services/import/persist-import.service.js @@ -8,6 +8,7 @@ const PersistLicenceService = require('./persist/persist-licence.service.js') const PersistLicenceVersionsService = require('./persist/persist-licence-versions.service.js') const PersistCompanyService = require('./persist/persist-company.service.js') +const PersistLicenceDocumentService = require('./persist/persist-licence-document.service.js') const LicenceModel = require('../../models/licence.model.js') const { timestampForPostgres } = require('../../lib/general.lib.js') @@ -27,6 +28,8 @@ async function go (transformedLicence, transformedCompanies) { await PersistLicenceVersionsService.go(trx, updatedAt, transformedLicence, id) + await PersistLicenceDocumentService.go(trx, updatedAt, transformedLicence) + await PersistCompanyService.go(trx, updatedAt, transformedCompanies) return id diff --git a/app/services/import/persist/persist-licence-document.service.js b/app/services/import/persist/persist-licence-document.service.js new file mode 100644 index 0000000000..56e3a829da --- /dev/null +++ b/app/services/import/persist/persist-licence-document.service.js @@ -0,0 +1,36 @@ +'use strict' + +/** + * Creates or updates a licence document + * @module PersistLicenceDocumentService + */ + +const LicenceDocumentModel = require('../../../models/licence-document.model.js') + +/** + * Creates or updates a licence document + * + * @param {object} trx - An Objection.js transaction object for PostgreSQL. + * @param {string} updatedAt - The timestamp indicating when the entity was last updated. + * @param {object} transformedLicence - An object representing a valid WRLS licence. + * + * @returns {Promise} - The licence ID from WRLS. + */ +async function go (trx, updatedAt, transformedLicence) { + await _persistLicenceDocument(trx, updatedAt, transformedLicence.licenceDocument) +} + +async function _persistLicenceDocument (trx, updatedAt, licenceDocument) { + return LicenceDocumentModel.query(trx) + .insert({ ...licenceDocument, updatedAt }) + .onConflict('licenceRef') + .merge([ + 'endDate', + 'startDate', + 'updatedAt' + ]) +} + +module.exports = { + go +} diff --git a/app/validators/import/licence-document.validator.js b/app/validators/import/licence-document.validator.js new file mode 100644 index 0000000000..97b12e2fea --- /dev/null +++ b/app/validators/import/licence-document.validator.js @@ -0,0 +1,32 @@ +'use strict' + +/** + * @module ImportLicenceDocumentValidator + */ + +const Joi = require('joi') + +/** + * Checks that imported licence data that has been transformed is valid for persisting to WRLS as a licence document + * + * @param {object} licenceDocument - The transformed licence data into a licence document + * + * @throws {Joi.ValidationError} - throws a Joi validation error if the validation fails + */ +function go (licenceDocument) { + const schema = Joi.object({ + licenceRef: Joi.string().required(), + endDate: Joi.date().required().allow(null), + startDate: Joi.date().required() + }) + + const result = schema.validate(licenceDocument, { convert: false }) + + if (result.error) { + throw result.error + } +} + +module.exports = { + go +} diff --git a/db/migrations/legacy/20221108003004_crm-v2-documents.js b/db/migrations/legacy/20221108003004_crm-v2-documents.js index ee3603ebe5..3c4b4d8d22 100644 --- a/db/migrations/legacy/20221108003004_crm-v2-documents.js +++ b/db/migrations/legacy/20221108003004_crm-v2-documents.js @@ -25,7 +25,7 @@ exports.up = function (knex) { table.timestamp('date_updated').notNullable().defaultTo(knex.fn.now()) // Constraints - table.unique(['regime', 'document_type', 'document_ref']) + table.unique(['document_ref']) }) } diff --git a/test/presenters/import/legacy/licence-document.presenter.test.js b/test/presenters/import/legacy/licence-document.presenter.test.js new file mode 100644 index 0000000000..7c3b1669ec --- /dev/null +++ b/test/presenters/import/legacy/licence-document.presenter.test.js @@ -0,0 +1,43 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') + +const { describe, it, beforeEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const { generateLicenceRef } = require('../../../support/helpers/licence.helper.js') + +// Thing under test +const LicenceDocumentPresenter = require('../../../../app/presenters/import/legacy/licence-document.presenter.js') + +describe('Import Legacy Licence Document presenter', () => { + let legacyLicenceDocument + let licenceRef + + beforeEach(() => { + licenceRef = generateLicenceRef() + + legacyLicenceDocument = _legacyLicenceDocument(licenceRef) + }) + + it('correctly transforms the data', () => { + const result = LicenceDocumentPresenter.go(legacyLicenceDocument) + + expect(result).to.equal({ + licenceRef, + endDate: null, + startDate: new Date('1999-01-01') + }) + }) +}) + +function _legacyLicenceDocument (licenceRef) { + return { + end_date: null, + start_date: new Date('1999-01-01'), + licence_ref: licenceRef + } +} diff --git a/test/services/import/legacy/process-licence.service.test.js b/test/services/import/legacy/process-licence.service.test.js index fba14e3f2f..e6b66eced6 100644 --- a/test/services/import/legacy/process-licence.service.test.js +++ b/test/services/import/legacy/process-licence.service.test.js @@ -19,6 +19,7 @@ const TransformAddressesService = require('../../../../app/services/import/legac const TransformCompaniesService = require('../../../../app/services/import/legacy/transform-companies.service.js') const TransformCompanyAddressesService = require('../../../../app/services/import/legacy/transform-company-addresses.service.js') const TransformContactsService = require('../../../../app/services/import/legacy/transform-contacts.service.js') +const TransformLicenceDocumentService = require('../../../../app/services/import/legacy/transform-licence-document.service.js') const TransformLicenceService = require('../../../../app/services/import/legacy/transform-licence.service.js') const TransformLicenceVersionPurposeConditionsService = require('../../../../app/services/import/legacy/transform-licence-version-purpose-conditions.service.js') const TransformLicenceVersionPurposesService = require('../../../../app/services/import/legacy/transform-licence-version-purposes.service.js') @@ -49,6 +50,7 @@ describe('Import Legacy Process Licence service', () => { Sinon.stub(TransformLicenceVersionsService, 'go').resolves() Sinon.stub(TransformLicenceVersionPurposesService, 'go').resolves(transformedLicence) Sinon.stub(TransformLicenceVersionPurposeConditionsService, 'go').resolves(transformedLicence) + Sinon.stub(TransformLicenceDocumentService, 'go').resolves() Sinon.stub(TransformCompaniesService, 'go').resolves({ company: [], transformedCompany: [] }) Sinon.stub(TransformContactsService, 'go').resolves() Sinon.stub(TransformAddressesService, 'go').resolves() diff --git a/test/services/import/legacy/transform-licence-document.service.test.js b/test/services/import/legacy/transform-licence-document.service.test.js new file mode 100644 index 0000000000..980afb256e --- /dev/null +++ b/test/services/import/legacy/transform-licence-document.service.test.js @@ -0,0 +1,75 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') +const Sinon = require('sinon') + +const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const { generateLicenceRef } = require('../../../support/helpers/licence.helper.js') + +// Things to stub +const FetchLicenceDocumentService = require('../../../../app/services/import/legacy/fetch-licence-document.service.js') + +// Thing under test +const TransformLicenceDocumentService = + require('../../../../app/services/import/legacy/transform-licence-document.service.js') + +describe('Import Legacy Transform Licence Document service', () => { + // NOTE: Clearly this is an incomplete representation of the licence returned from TransformedLicenceService. But for + // the purposes of this service it is all that is needed + const transformedLicence = { licenceVersions: [] } + + const naldLicenceId = '2113' + const regionCode = '6' + + let legacyLicenceDocument + let licenceRef + + beforeEach(() => { + licenceRef = generateLicenceRef() + + legacyLicenceDocument = _legacyLicenceDocument(licenceRef) + }) + + afterEach(() => { + Sinon.restore() + }) + + describe('when a matching valid legacy licence is found', () => { + beforeEach(() => { + Sinon.stub(FetchLicenceDocumentService, 'go').resolves(legacyLicenceDocument) + }) + + it('attaches the record transformed and validated for WRLS to the transformed licence', async () => { + await TransformLicenceDocumentService.go(regionCode, naldLicenceId, transformedLicence) + + expect(transformedLicence.licenceDocument).to.equal({ + licenceRef, + endDate: null, + startDate: new Date('1999-01-01') + }) + }) + }) + + describe('when no matching legacy licence is found', () => { + beforeEach(() => { + Sinon.stub(FetchLicenceDocumentService, 'go').resolves(null) + }) + + it('throws an error', async () => { + await expect(TransformLicenceDocumentService.go(regionCode, naldLicenceId, transformedLicence)).to.reject() + }) + }) +}) + +function _legacyLicenceDocument (licenceRef) { + return { + end_date: null, + start_date: new Date('1999-01-01'), + licence_ref: licenceRef + } +} diff --git a/test/services/import/persist-import.service.test.js b/test/services/import/persist-import.service.test.js index 9cf544785d..6df431f3ed 100644 --- a/test/services/import/persist-import.service.test.js +++ b/test/services/import/persist-import.service.test.js @@ -13,11 +13,12 @@ const LicenceModel = require('../../../app/models/licence.model.js') const PersistCompanyService = require('../../../app/services/import/persist/persist-company.service.js') const PersistLicenceService = require('../../../app/services/import/persist/persist-licence.service.js') const PersistLicenceVersionsService = require('../../../app/services/import/persist/persist-licence-versions.service.js') +const PersistLicenceDocumentService = require('../../../app/services/import/persist/persist-licence-document.service.js') // Thing under test const PersistImportService = require('../../../app/services/import/persist-import.service.js') -describe('Persist licence service', () => { +describe('Persist import service', () => { const transformedCompanies = [] const transformedLicence = [] @@ -41,6 +42,7 @@ describe('Persist licence service', () => { Sinon.stub(PersistLicenceService, 'go').resolves('1234') Sinon.stub(PersistLicenceVersionsService, 'go').resolves() Sinon.stub(PersistCompanyService, 'go').resolves() + Sinon.stub(PersistLicenceDocumentService, 'go').resolves() }) it('should return the licence id', async () => { @@ -55,6 +57,7 @@ describe('Persist licence service', () => { Sinon.stub(PersistLicenceService, 'go').resolves('1234') Sinon.stub(PersistLicenceVersionsService, 'go').resolves() Sinon.stub(PersistCompanyService, 'go').rejects(new Error('boom')) + Sinon.stub(PersistLicenceDocumentService, 'go').resolves() }) it('should throw an error', async () => { @@ -68,6 +71,7 @@ describe('Persist licence service', () => { Sinon.stub(PersistLicenceService, 'go').resolves('1234') Sinon.stub(PersistLicenceVersionsService, 'go').resolves() Sinon.stub(PersistCompanyService, 'go').resolves() + Sinon.stub(PersistLicenceDocumentService, 'go').resolves() Sinon.stub(LicenceModel, 'transaction').rejects(new Error('boom')) }) diff --git a/test/services/import/persist/persist-licence-document.service.test.js b/test/services/import/persist/persist-licence-document.service.test.js new file mode 100644 index 0000000000..e9b8828a1a --- /dev/null +++ b/test/services/import/persist/persist-licence-document.service.test.js @@ -0,0 +1,123 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') + +const { describe, it, afterEach, beforeEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const LicenceDocumentHelper = require('../../../support/helpers/licence-document.helper.js') +const LicenceDocumentModel = require('../../../../app/models/licence-document.model.js') +const { generateLicenceRef } = require('../../../support/helpers/licence.helper.js') +const { timestampForPostgres } = require('../../../../app/lib/general.lib.js') +const { transaction } = require('objection') + +// Thing under test +const PersistLicenceDocumentService = require('../../../../app/services/import/persist/persist-licence-document.service.js') + +describe('Persist licence document service', () => { + const transformedLicence = {} + + let licenceDocument + let licenceRef + let trx + let updatedAt + + beforeEach(async () => { + updatedAt = timestampForPostgres() + + licenceRef = generateLicenceRef() + + trx = await transaction.start(LicenceDocumentModel.knex()) + }) + + afterEach(async () => { + if (!trx.isCompleted()) { + await trx.rollback() + } + }) + + describe('when given a valid transformed licence document', () => { + describe('and that licence does not already exist', () => { + beforeEach(() => { + licenceDocument = _transformedLicenceDocument(licenceRef) + + transformedLicence.licenceDocument = licenceDocument + }) + + it('creates a new licence document record', async () => { + await PersistLicenceDocumentService.go(trx, updatedAt, transformedLicence) + + // Commit the transaction so the data is saved to the database + await trx.commit() + + const newLicenceDocument = await _fetchPersistedLicenceDocument(licenceDocument.licenceRef) + + expect(licenceDocument.licenceRef).to.equal(newLicenceDocument.licenceRef) + expect(licenceDocument.startDate).to.equal(newLicenceDocument.startDate) + expect(licenceDocument.endDate).to.equal(newLicenceDocument.endDate) + }) + }) + + describe('and that licence document already exists', () => { + const existingLicence = {} + + beforeEach(async () => { + const existing = await _createExistingRecords(licenceRef) + + existingLicence.licenceDocument = existing.licenceDocument + + transformedLicence.licenceDocument = { + ..._transformedLicenceDocument(licenceRef), + endDate: null + } + }) + + it('should update the existing licence document record', async () => { + // Call the thing under test + await PersistLicenceDocumentService.go(trx, updatedAt, transformedLicence) + + // Commit the transaction so the data is saved to the database + await trx.commit() + + // Get the persisted data + const updatedLicenceDocument = await + _fetchPersistedLicenceDocument(transformedLicence.licenceDocument.licenceRef) + + // Check the updated licence + expect(updatedLicenceDocument.licenceRef).to.equal(transformedLicence.licenceDocument.licenceRef) + expect(updatedLicenceDocument.endDate).to.be.null() + }) + }) + }) +}) + +async function _fetchPersistedLicenceDocument (licenceRef) { + return LicenceDocumentModel + .query() + .where('licenceRef', licenceRef) + .select('*') + .limit(1) + .first() +} + +function _transformedLicenceDocument (licenceRef) { + return { + licenceRef, + startDate: new Date('1992-08-19'), + endDate: new Date('2001-01-01') + } +} + +async function _createExistingRecords (licenceRef) { + const licenceDocument = await LicenceDocumentHelper.add({ + licenceRef, + endDate: new Date('2001-01-01') + }) + + return { + licenceDocument + } +} diff --git a/test/validators/import/licence-document.validator.test.js b/test/validators/import/licence-document.validator.test.js new file mode 100644 index 0000000000..078a2fba5c --- /dev/null +++ b/test/validators/import/licence-document.validator.test.js @@ -0,0 +1,116 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') + +const { describe, it, beforeEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const { generateLicenceRef } = require('../../support/helpers/licence.helper.js') + +// Thing under test +const LicenceDocumentValidator = require('../../../app/validators/import/licence-document.validator.js') + +describe('Import Licence Document validator', () => { + let transformedLicenceDocument + + beforeEach(async () => { + transformedLicenceDocument = _transformedLicenceDocument() + }) + + describe('when valid data is provided', () => { + it('does not throw an error', () => { + expect(() => { + LicenceDocumentValidator.go(transformedLicenceDocument) + }).to.not.throw() + }) + }) + + describe('the "licenceRef" property', () => { + describe('when it is not a date', () => { + beforeEach(() => { + transformedLicenceDocument.licenceRef = 1 + }) + + it('throws an error', () => { + expect(() => { + LicenceDocumentValidator.go(transformedLicenceDocument) + }).to.throw('"licenceRef" must be a string') + }) + }) + + describe('when it is null', () => { + beforeEach(() => { + transformedLicenceDocument.licenceRef = null + }) + + it('throws an error', () => { + expect(() => { + LicenceDocumentValidator.go(transformedLicenceDocument) + }).to.throw('"licenceRef" must be a string') + }) + }) + }) + + describe('the "endDate" property', () => { + describe('when it is not a date', () => { + beforeEach(() => { + transformedLicenceDocument.endDate = 1 + }) + + it('throws an error', () => { + expect(() => { + LicenceDocumentValidator.go(transformedLicenceDocument) + }).to.throw('"endDate" must be a valid date') + }) + }) + + describe('when it is null', () => { + beforeEach(() => { + transformedLicenceDocument.endDate = null + }) + + it('does not throw an error', () => { + expect(() => { + LicenceDocumentValidator.go(transformedLicenceDocument) + }).not.to.throw() + }) + }) + }) + + describe('the "startDate" property', () => { + describe('when it is not a date', () => { + beforeEach(() => { + transformedLicenceDocument.startDate = 1 + }) + + it('throws an error', () => { + expect(() => { + LicenceDocumentValidator.go(transformedLicenceDocument) + }).to.throw('"startDate" must be a valid date') + }) + }) + + describe('when it is null', () => { + beforeEach(() => { + transformedLicenceDocument.startDate = null + }) + + it('throws an error', () => { + expect(() => { + LicenceDocumentValidator.go(transformedLicenceDocument) + }).to.throw('"startDate" must be a valid date') + }) + }) + }) +}) + +function _transformedLicenceDocument () { + return { + licenceRef: generateLicenceRef(), + endDate: new Date('2052-06-23'), + startDate: new Date('1992-08-19') + } +}