From 6ca57172bf200ad99507c99e4b060213bbf2892c Mon Sep 17 00:00:00 2001 From: jonathangoulding Date: Mon, 13 May 2024 16:05:23 +0100 Subject: [PATCH 1/6] Add CRM V2 Company contacts view Add the crm_v2.company_contacts view to the database. This is needed to for the current view licence contact details page. The customer and licence contact are used on this page. --- ...240513145729_water-add-company-contacts.js | 43 +++++++++++++++++++ ...0513134550_create-company-contacts-view.js | 38 ++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 db/migrations/legacy/20240513145729_water-add-company-contacts.js create mode 100644 db/migrations/public/20240513134550_create-company-contacts-view.js diff --git a/db/migrations/legacy/20240513145729_water-add-company-contacts.js b/db/migrations/legacy/20240513145729_water-add-company-contacts.js new file mode 100644 index 0000000000..72b7348769 --- /dev/null +++ b/db/migrations/legacy/20240513145729_water-add-company-contacts.js @@ -0,0 +1,43 @@ +'use strict' + +const tableName = 'company_contacts' + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function (knex) { + return knex + .schema + .withSchema('crm_v2') + .createTable(tableName, (table) => { + // Primary Key + table.uuid('company_contact_id').primary().defaultTo(knex.raw('gen_random_uuid()')) + + // Data + table.boolean('is_default') + table.boolean('is_test') + table.boolean('water_abstraction_alerts_enabled') + table.date('end_date') + table.date('start_date') + table.string('email_address') + table.uuid('company_id') + table.uuid('contact_id') + table.uuid('role_id') + + // Legacy timestamps + table.timestamp('date_created', { useTz: false }).notNullable().defaultTo(knex.fn.now()) + table.timestamp('date_updated', { useTz: false }).notNullable().defaultTo(knex.fn.now()) + }) +} + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function (knex) { + return knex + .schema + .withSchema('crm_v2') + .dropTableIfExists(tableName) +} diff --git a/db/migrations/public/20240513134550_create-company-contacts-view.js b/db/migrations/public/20240513134550_create-company-contacts-view.js new file mode 100644 index 0000000000..15ff4c2de0 --- /dev/null +++ b/db/migrations/public/20240513134550_create-company-contacts-view.js @@ -0,0 +1,38 @@ +'use strict' + +const viewName = 'company_contacts' +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function (knex) { + return knex + .schema + .createView(viewName, (view) => { + // NOTE: We have commented out unused columns from the source table + view.as(knex('company_contacts').withSchema('crm_v2').select([ + 'company_contacts.company_contact_id AS id', + 'company_contacts.company_id', + 'company_contacts.contact_id', + 'company_contacts.role_id', + 'company_contacts.date_created AS created_at', + 'company_contacts.date_updated AS updated_at' + // company_contacts.is_default + // company_contacts.email_address + // company_contacts.start_date + // company_contacts.end_date + // company_contacts.is_test + // company_contacts.water_abstraction_alerts_enabled + ])) + }) +} + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function (knex) { + return knex + .schema + .dropViewIfExists(viewName) +} From 4c75063cd8fb184a68851a6c2c6b01992c55bb0d Mon Sep 17 00:00:00 2001 From: jonathangoulding Date: Mon, 13 May 2024 16:09:57 +0100 Subject: [PATCH 2/6] fix: lf --- ...240513145729_water-add-company-contacts.js | 86 +++++++++---------- ...0513134550_create-company-contacts-view.js | 76 ++++++++-------- 2 files changed, 81 insertions(+), 81 deletions(-) diff --git a/db/migrations/legacy/20240513145729_water-add-company-contacts.js b/db/migrations/legacy/20240513145729_water-add-company-contacts.js index 72b7348769..16b499bf6f 100644 --- a/db/migrations/legacy/20240513145729_water-add-company-contacts.js +++ b/db/migrations/legacy/20240513145729_water-add-company-contacts.js @@ -1,43 +1,43 @@ -'use strict' - -const tableName = 'company_contacts' - -/** - * @param { import("knex").Knex } knex - * @returns { Promise } - */ -exports.up = function (knex) { - return knex - .schema - .withSchema('crm_v2') - .createTable(tableName, (table) => { - // Primary Key - table.uuid('company_contact_id').primary().defaultTo(knex.raw('gen_random_uuid()')) - - // Data - table.boolean('is_default') - table.boolean('is_test') - table.boolean('water_abstraction_alerts_enabled') - table.date('end_date') - table.date('start_date') - table.string('email_address') - table.uuid('company_id') - table.uuid('contact_id') - table.uuid('role_id') - - // Legacy timestamps - table.timestamp('date_created', { useTz: false }).notNullable().defaultTo(knex.fn.now()) - table.timestamp('date_updated', { useTz: false }).notNullable().defaultTo(knex.fn.now()) - }) -} - -/** - * @param { import("knex").Knex } knex - * @returns { Promise } - */ -exports.down = function (knex) { - return knex - .schema - .withSchema('crm_v2') - .dropTableIfExists(tableName) -} +'use strict' + +const tableName = 'company_contacts' + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function (knex) { + return knex + .schema + .withSchema('crm_v2') + .createTable(tableName, (table) => { + // Primary Key + table.uuid('company_contact_id').primary().defaultTo(knex.raw('gen_random_uuid()')) + + // Data + table.boolean('is_default') + table.boolean('is_test') + table.boolean('water_abstraction_alerts_enabled') + table.date('end_date') + table.date('start_date') + table.string('email_address') + table.uuid('company_id') + table.uuid('contact_id') + table.uuid('role_id') + + // Legacy timestamps + table.timestamp('date_created', { useTz: false }).notNullable().defaultTo(knex.fn.now()) + table.timestamp('date_updated', { useTz: false }).notNullable().defaultTo(knex.fn.now()) + }) +} + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function (knex) { + return knex + .schema + .withSchema('crm_v2') + .dropTableIfExists(tableName) +} diff --git a/db/migrations/public/20240513134550_create-company-contacts-view.js b/db/migrations/public/20240513134550_create-company-contacts-view.js index 15ff4c2de0..bad1d2f0e4 100644 --- a/db/migrations/public/20240513134550_create-company-contacts-view.js +++ b/db/migrations/public/20240513134550_create-company-contacts-view.js @@ -1,38 +1,38 @@ -'use strict' - -const viewName = 'company_contacts' -/** - * @param { import("knex").Knex } knex - * @returns { Promise } - */ -exports.up = function (knex) { - return knex - .schema - .createView(viewName, (view) => { - // NOTE: We have commented out unused columns from the source table - view.as(knex('company_contacts').withSchema('crm_v2').select([ - 'company_contacts.company_contact_id AS id', - 'company_contacts.company_id', - 'company_contacts.contact_id', - 'company_contacts.role_id', - 'company_contacts.date_created AS created_at', - 'company_contacts.date_updated AS updated_at' - // company_contacts.is_default - // company_contacts.email_address - // company_contacts.start_date - // company_contacts.end_date - // company_contacts.is_test - // company_contacts.water_abstraction_alerts_enabled - ])) - }) -} - -/** - * @param { import("knex").Knex } knex - * @returns { Promise } - */ -exports.down = function (knex) { - return knex - .schema - .dropViewIfExists(viewName) -} +'use strict' + +const viewName = 'company_contacts' +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function (knex) { + return knex + .schema + .createView(viewName, (view) => { + // NOTE: We have commented out unused columns from the source table + view.as(knex('company_contacts').withSchema('crm_v2').select([ + 'company_contacts.company_contact_id AS id', + 'company_contacts.company_id', + 'company_contacts.contact_id', + 'company_contacts.role_id', + 'company_contacts.date_created AS created_at', + 'company_contacts.date_updated AS updated_at' + // company_contacts.is_default + // company_contacts.email_address + // company_contacts.start_date + // company_contacts.end_date + // company_contacts.is_test + // company_contacts.water_abstraction_alerts_enabled + ])) + }) +} + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function (knex) { + return knex + .schema + .dropViewIfExists(viewName) +} From 2a8aa0c46cbfa5a919a7222950a7eced1683e2f8 Mon Sep 17 00:00:00 2001 From: jonathangoulding Date: Tue, 14 May 2024 10:00:13 +0100 Subject: [PATCH 3/6] feat: add company contacts model add company model tests --- app/models/company-contact.model.js | 39 +++++++ test/models/company-contacts.model.test.js | 103 ++++++++++++++++++ .../support/helpers/company-contact.helper.js | 56 ++++++++++ 3 files changed, 198 insertions(+) create mode 100644 app/models/company-contact.model.js create mode 100644 test/models/company-contacts.model.test.js create mode 100644 test/support/helpers/company-contact.helper.js diff --git a/app/models/company-contact.model.js b/app/models/company-contact.model.js new file mode 100644 index 0000000000..6d46c450f0 --- /dev/null +++ b/app/models/company-contact.model.js @@ -0,0 +1,39 @@ +'use strict' + +/** + * Model for company contact (crm_v2.company_contacts) + * @module CompanyContactModel + */ + +const { Model } = require('objection') + +const BaseModel = require('./base.model.js') + +class CompanyContactModel extends BaseModel { + static get tableName () { + return 'company_contacts' + } + + static get relationMappings () { + return { + companies: { + relation: Model.HasManyRelation, + modelClass: 'company.model', + join: { + from: 'company_contacts.companyId', + to: 'companies.id' + } + }, + contacts: { + relation: Model.HasManyRelation, + modelClass: 'contact.model', + join: { + from: 'company_contacts.contactId', + to: 'contacts.id' + } + } + } + } +} + +module.exports = CompanyContactModel diff --git a/test/models/company-contacts.model.test.js b/test/models/company-contacts.model.test.js new file mode 100644 index 0000000000..8315a1b874 --- /dev/null +++ b/test/models/company-contacts.model.test.js @@ -0,0 +1,103 @@ +'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 CompanyContactHelper = require('../support/helpers/company-contact.helper.js') +const CompanyHelper = require('../support/helpers/company.helper.js') +const CompanyModel = require('../../app/models/company.model.js') +const ContactsHelper = require('../support/helpers/contact.helper.js') +const ContactsModel = require('../../app/models/contact.model.js') +const DatabaseSupport = require('../support/database.js') + +// Thing under test +const CompanyContactModel = require('../../app/models/company-contact.model.js') + +describe('Company Contacts model', () => { + let testRecord + + beforeEach(async () => { + await DatabaseSupport.clean() + }) + + describe('Basic query', () => { + beforeEach(async () => { + testRecord = await CompanyContactHelper.add() + }) + + it('can successfully run a basic query', async () => { + const result = await CompanyContactModel.query().findById(testRecord.id) + + expect(result).to.be.an.instanceOf(CompanyContactModel) + expect(result.id).to.equal(testRecord.id) + }) + }) + + describe('Relationships', () => { + describe('when linking to companies', () => { + let testCompany + beforeEach(async () => { + testRecord = await CompanyContactHelper.add() + + testCompany = await CompanyHelper.add({ + id: testRecord.companyId + }) + }) + + it('can successfully run a related query', async () => { + const query = await CompanyContactModel.query() + .innerJoinRelated('companies') + + expect(query).to.exist() + }) + + it('can eager load the companies', async () => { + const result = await CompanyContactModel.query() + .findById(testRecord.id) + .withGraphFetched('companies') + + expect(result).to.be.instanceOf(CompanyContactModel) + expect(result.id).to.equal(testRecord.id) + + expect(result.companies).to.be.an.array() + expect(result.companies[0]).to.be.an.instanceOf(CompanyModel) + expect(result.companies).to.include(testCompany) + }) + }) + describe('when linking to contacts', () => { + let testContact + beforeEach(async () => { + testRecord = await CompanyContactHelper.add() + + testContact = await ContactsHelper.add({ + id: testRecord.contactId + }) + }) + + it('can successfully run a related query', async () => { + const query = await CompanyContactModel.query() + .innerJoinRelated('contacts') + + expect(query).to.exist() + }) + + it('can eager load the company contacts', async () => { + const result = await CompanyContactModel.query() + .findById(testRecord.id) + .withGraphFetched('contacts') + + expect(result).to.be.instanceOf(CompanyContactModel) + expect(result.id).to.equal(testRecord.id) + + expect(result.contacts).to.be.an.array() + expect(result.contacts[0]).to.be.an.instanceOf(ContactsModel) + expect(result.contacts).to.include(testContact) + }) + }) + }) +}) diff --git a/test/support/helpers/company-contact.helper.js b/test/support/helpers/company-contact.helper.js new file mode 100644 index 0000000000..f2e69d5556 --- /dev/null +++ b/test/support/helpers/company-contact.helper.js @@ -0,0 +1,56 @@ +'use strict' + +/** + * @module CompanyContactHelper + */ + +const CompanyContactModel = require('../../../app/models/company-contact.model.js') +const { generateUUID } = require('../../../app/lib/general.lib.js') +/** + * Add a new company contact + * + * If no `data` is provided, default values will be used. These are + * + * - `id` - [random UUID] + * - `companyId` - [random UUID] + * - `contactId` - [random UUID] + * - `roleId` - [random UUID] + * + * @param {Object} [data] Any data you want to use instead of the defaults used here or in the database + * + * @returns {Promise} The instance of the newly created record + */ +function add (data = {}) { + const insertData = defaults(data) + + return CompanyContactModel.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 = { + id: generateUUID(), + companyId: generateUUID(), + contactId: generateUUID(), + roleId: generateUUID() + } + + return { + ...defaults, + ...data + } +} + +module.exports = { + add, + defaults +} From db091f790e2eccaf041f5c4c1c85a2ad3f1484a7 Mon Sep 17 00:00:00 2001 From: Jonathan Goulding <58443816+jonathangoulding@users.noreply.github.com> Date: Tue, 14 May 2024 12:00:27 +0100 Subject: [PATCH 4/6] Update test/support/helpers/company-contact.helper.js Co-authored-by: Alan Cruikshanks --- test/support/helpers/company-contact.helper.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/support/helpers/company-contact.helper.js b/test/support/helpers/company-contact.helper.js index f2e69d5556..128135a045 100644 --- a/test/support/helpers/company-contact.helper.js +++ b/test/support/helpers/company-contact.helper.js @@ -6,6 +6,7 @@ const CompanyContactModel = require('../../../app/models/company-contact.model.js') const { generateUUID } = require('../../../app/lib/general.lib.js') + /** * Add a new company contact * From ed0a0e232e1ef621e1bfd9d449e18c690edaa9eb Mon Sep 17 00:00:00 2001 From: Jonathan Goulding <58443816+jonathangoulding@users.noreply.github.com> Date: Tue, 14 May 2024 12:00:45 +0100 Subject: [PATCH 5/6] Update test/support/helpers/company-contact.helper.js Co-authored-by: Alan Cruikshanks --- test/support/helpers/company-contact.helper.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/support/helpers/company-contact.helper.js b/test/support/helpers/company-contact.helper.js index 128135a045..c91118ff7e 100644 --- a/test/support/helpers/company-contact.helper.js +++ b/test/support/helpers/company-contact.helper.js @@ -39,7 +39,6 @@ function add (data = {}) { */ function defaults (data = {}) { const defaults = { - id: generateUUID(), companyId: generateUUID(), contactId: generateUUID(), roleId: generateUUID() From 0844fbd3f8c5fac251152987188d3dd909e0983a Mon Sep 17 00:00:00 2001 From: Jonathan Goulding <58443816+jonathangoulding@users.noreply.github.com> Date: Tue, 14 May 2024 12:01:45 +0100 Subject: [PATCH 6/6] Update test/support/helpers/company-contact.helper.js --- test/support/helpers/company-contact.helper.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/support/helpers/company-contact.helper.js b/test/support/helpers/company-contact.helper.js index c91118ff7e..ccf8ab43f4 100644 --- a/test/support/helpers/company-contact.helper.js +++ b/test/support/helpers/company-contact.helper.js @@ -12,7 +12,6 @@ const { generateUUID } = require('../../../app/lib/general.lib.js') * * If no `data` is provided, default values will be used. These are * - * - `id` - [random UUID] * - `companyId` - [random UUID] * - `contactId` - [random UUID] * - `roleId` - [random UUID]