Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LicenceDocumentHeaderModel #640

Merged
merged 10 commits into from
Jan 15, 2024
60 changes: 60 additions & 0 deletions app/models/licence-document-header.model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use strict'

/**
* Model for licence_document_headers (crm.document_header)
* @module LicenceDocumentHeaderModel
*/

const { Model } = require('objection')

const BaseModel = require('./base.model.js')

/**
* Represents an instance of a licence document header record
*
* For reference, the licence document header record is a 'nothing' record! It doesn't hold anything not already stored
* in other licence tables. We only need to obtain the licence holder name which matches what the legacy UI displays.
*
* We think the reason for the table being there is because of the original ambition to have a generic permit
* repository that could be used for all types of permits. Along with that a 'tactical' CRM that interfaced with the
* permit repository was built. Though the permit repository referred to them as licences, the CRM chose to refer to
* them as 'documents' hence the `crm.document_header` table.
*
* The previous team then tried to refactor the CRM schema but never completed it. Hence we have the `crm_v2` schema
* and more licence duplication. Finally, at a later date the previous team then opted to create a `licences` table in
* the `water` schema we think to support charging.
*
* So, if you see the phrase 'Document' you can assume the instance is one of these older copies of a licence.
* `water.licences` is the primary licence record. But we often have to dip into this older tables for other bits of
* information, for example, the licence holder name currently displayed in the legacy UI. This is why we have models
* like this one.
*
* Welcome to dealing with the legacy database schema! ¯\_(ツ)_/¯
*/
class LicenceDocumentHeaderModel extends BaseModel {
static get tableName () {
return 'licenceDocumentHeaders'
}

// Defining which fields contain json allows us to insert an object without needing to stringify it first
static get jsonAttributes () {
return [
'metadata'
]
}

static get relationMappings () {
return {
licence: {
relation: Model.BelongsToOneRelation,
modelClass: 'licence.model',
join: {
from: 'licenceDocumentHeaders.licenceRef',
to: 'licences.licenceRef'
}
}
}
}
}

module.exports = LicenceDocumentHeaderModel
8 changes: 8 additions & 0 deletions app/models/licence.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ class LicenceModel extends BaseModel {
to: 'licenceDocuments.licenceRef'
}
},
licenceDocumentHeader: {
relation: Model.BelongsToOneRelation,
modelClass: 'licence-document-header.model',
join: {
from: 'licences.licenceRef',
to: 'licenceDocumentHeaders.licenceRef'
}
},
licenceVersions: {
relation: Model.HasManyRelation,
modelClass: 'licence-version.model',
Expand Down
13 changes: 13 additions & 0 deletions db/migrations/legacy/20240107231129_create-crm-schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use strict'

exports.up = function (knex) {
return knex.raw(`
CREATE SCHEMA IF NOT EXISTS "crm";
`)
}

exports.down = function (knex) {
return knex.raw(`
DROP SCHEMA IF EXISTS "crm";
`)
}
40 changes: 40 additions & 0 deletions db/migrations/legacy/20240107231603_create-crm-document-header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use strict'

const tableName = 'document_header'

exports.up = function (knex) {
return knex
.schema
.withSchema('crm')
.createTable(tableName, (table) => {
// Primary Key
table.uuid('document_id').primary().defaultTo(knex.raw('gen_random_uuid()'))

// Data
table.string('regime_entity_id').notNullable()
table.string('system_id').notNullable().defaultTo('permit-repo')
table.string('system_internal_id').notNullable()
table.string('system_external_id').notNullable()
table.jsonb('metadata')
table.string('company_entity_id')
table.string('verification_id')
table.string('document_name')
table.date('date_deleted')

// Legacy timestamps
table.timestamp('date_created').notNullable().defaultTo(knex.fn.now())
table.timestamp('date_updated').notNullable().defaultTo(knex.fn.now())

table.unique(
['system_id', 'system_internal_id', 'regime_entity_id'],
{ useConstraint: true, indexName: 'external_key' }
)
})
}

exports.down = function (knex) {
return knex
.schema
.withSchema('crm')
.dropTableIfExists(tableName)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use strict'

const viewName = 'licence_document_headers'

exports.up = function (knex) {
return knex
.schema
.createView(viewName, (view) => {
// NOTE: We have commented out unused columns from the source table
view.as(knex('document_header').withSchema('crm').select([
'document_id AS id',
// This could be ignored as it is always set to the same ID. But that id comes from a single record in the
// crm.entity table which has the `entity_type` regime. So, for the purposes of testing we just have to live
// with always populating it even though we don't care about it.
'regime_entity_id',
// 'system_id',
'system_internal_id AS nald_id',
'system_external_id AS licence_ref',
'metadata',
// 'company_entity_id',
// 'verification_id',
// 'document_name',
'date_created AS created_at',
'date_updated AS updated_at',
'date_deleted AS deleted_at'
]))
})
}

exports.down = function (knex) {
return knex
.schema
.dropViewIfExists(viewName)
}
70 changes: 70 additions & 0 deletions test/models/licence-document-header.model.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'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 DatabaseHelper = require('../support/helpers/database.helper.js')
const LicenceHelper = require('../support/helpers/licence.helper.js')
const LicenceModel = require('../../app/models/licence.model.js')
const LicenceDocumentHeaderHelper = require('../support/helpers/licence-document-header.helper.js')

// Thing under test
const LicenceDocumentHeaderModel = require('../../app/models/licence-document-header.model.js')

describe('Licence Document Header model', () => {
let testRecord

beforeEach(async () => {
await DatabaseHelper.clean()
})

describe('Basic query', () => {
beforeEach(async () => {
testRecord = await LicenceDocumentHeaderHelper.add()
})

it('can successfully run a basic query', async () => {
const result = await LicenceDocumentHeaderModel.query().findById(testRecord.id)

expect(result).to.be.an.instanceOf(LicenceDocumentHeaderModel)
expect(result.id).to.equal(testRecord.id)
})
})

describe('Relationships', () => {
describe('when linking to licence', () => {
let testLicence

beforeEach(async () => {
testLicence = await LicenceHelper.add()

const { licenceRef } = testLicence
testRecord = await LicenceDocumentHeaderHelper.add({ licenceRef })
})

it('can successfully run a related query', async () => {
const query = await LicenceDocumentHeaderModel.query()
.innerJoinRelated('licence')

expect(query).to.exist()
})

it('can eager load the licence', async () => {
const result = await LicenceDocumentHeaderModel.query()
.findById(testRecord.id)
.withGraphFetched('licence')

expect(result).to.be.instanceOf(LicenceDocumentHeaderModel)
expect(result.id).to.equal(testRecord.id)

expect(result.licence).to.be.an.instanceOf(LicenceModel)
expect(result.licence).to.equal(testLicence)
})
})
})
})
32 changes: 32 additions & 0 deletions test/models/licence.model.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const DatabaseHelper = require('../support/helpers/database.helper.js')
const LicenceHelper = require('../support/helpers/licence.helper.js')
const LicenceDocumentHelper = require('../support/helpers/licence-document.helper.js')
const LicenceDocumentModel = require('../../app/models/licence-document.model.js')
const LicenceDocumentHeaderHelper = require('../support/helpers/licence-document-header.helper.js')
const LicenceDocumentHeaderModel = require('../../app/models/licence-document-header.model.js')
const LicenceVersionHelper = require('../support/helpers/licence-version.helper.js')
const LicenceVersionModel = require('../../app/models/licence-version.model.js')
const RegionHelper = require('../support/helpers/region.helper.js')
Expand Down Expand Up @@ -145,6 +147,36 @@ describe('Licence model', () => {
})
})

describe('when linking to licence document header', () => {
let testLicenceDocumentHeader

beforeEach(async () => {
testLicenceDocumentHeader = await LicenceDocumentHeaderHelper.add()

const { licenceRef } = testLicenceDocumentHeader
testRecord = await LicenceHelper.add({ licenceRef })
})

it('can successfully run a related query', async () => {
const query = await LicenceModel.query()
.innerJoinRelated('licenceDocumentHeader')

expect(query).to.exist()
})

it('can eager load the licence document header', async () => {
const result = await LicenceModel.query()
.findById(testRecord.id)
.withGraphFetched('licenceDocumentHeader')

expect(result).to.be.instanceOf(LicenceModel)
expect(result.id).to.equal(testRecord.id)

expect(result.licenceDocumentHeader).to.be.an.instanceOf(LicenceDocumentHeaderModel)
expect(result.licenceDocumentHeader).to.equal(testLicenceDocumentHeader)
})
})

describe('when linking to licence versions', () => {
let testLicenceVersions

Expand Down
2 changes: 1 addition & 1 deletion test/support/helpers/database.helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const { db } = require('../../../db/db.js')
* identity columns. For example, if a table relies on an incrementing ID the query will reset that to 1.
*/
async function clean () {
const schemas = ['crm_v2', 'idm', 'public', 'water', 'returns']
const schemas = ['crm', 'crm_v2', 'idm', 'public', 'water', 'returns']

for (const schema of schemas) {
const tables = await _tableNames(schema)
Expand Down
97 changes: 97 additions & 0 deletions test/support/helpers/licence-document-header.helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
'use strict'

/**
* @module LicenceDocumentHelper
*/

const { randomInteger } = require('../helpers/general.helper.js')
const { generateUUID } = require('../../../app/lib/general.lib.js')
const { generateLicenceRef } = require('./licence.helper.js')
const LicenceDocumentHeaderModel = require('../../../app/models/licence-document-header.model.js')

/**
* Add a new licence document header
*
* If no `data` is provided, default values will be used. These are
*
* - `regimeEntityId` - [random UUID]
* - `naldId` - [randomly generated - 105175]
* - `licenceRef` - [randomly generated - 01/123]
* - `metadata` - [object intended to be persisted] as JSON]
*
* @param {Object} [data] Any data you want to use instead of the defaults used here or in the database
*
* @returns {module:LicenceDocumentHeaderModel} The instance of the newly created record
*/
async function add (data = {}) {
const insertData = defaults(data)

return LicenceDocumentHeaderModel.query()
.insert({ ...insertData })
.returning('*')
}

/**
* Returns the defaults used
*
* It will override or append to them any data provided. Mainly used by the `add()` method, we make it available
* for use in tests to avoid having to duplicate values.
*
* @param {Object} [data] Any data you want to use instead of the defaults used here or in the database
*/
function defaults (data = {}) {
const defaults = {
regimeEntityId: generateUUID(),
naldId: randomInteger(1000, 199999),
licenceRef: generateLicenceRef(),
metadata: _metadata()
}

return {
...defaults,
...data
}
}

function _metadata () {
return {
Town: 'BRISTOL',
County: 'AVON',
Name: 'GUPTA',
Country: '',
Expires: null,
Forename: 'AMARA',
Initials: 'A',
Modified: '20080327',
Postcode: 'BS1 5AH',
contacts: [
{
name: 'GUPTA',
role: 'Licence holder',
town: 'BRISTOL',
type: 'Person',
county: 'AVON',
country: null,
forename: 'AMARA',
initials: 'A',
postcode: 'BS1 5AH',
salutation: null,
addressLine1: 'ENVIRONMENT AGENCY',
addressLine2: 'HORIZON HOUSE',
addressLine3: 'DEANERY ROAD',
addressLine4: null
}
],
IsCurrent: true,
Salutation: '',
AddressLine1: 'ENVIRONMENT AGENCY',
AddressLine2: 'HORIZON HOUSE',
AddressLine3: 'DEANERY ROAD',
AddressLine4: ''
}
}

module.exports = {
add,
defaults
}
Loading