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 licence ref. and holder to rtn. req. session #639

Merged
merged 15 commits into from
Jan 8, 2024
Merged
9 changes: 2 additions & 7 deletions app/controllers/licences.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,12 @@
* @module LicencesController
*/

const SessionModel = require('../models/session.model.js')
const InitiateReturnRequirementSessionService = require('../services/return-requirements/initiate-return-requirement-session.service.js')

async function noReturnsRequired (request, h) {
const { id } = request.params

const data = { licenceId: id }
const session = await SessionModel.query()
.insert({
data
})
.returning('*')
const session = await InitiateReturnRequirementSessionService.go(id)

return h.redirect(`/system/return-requirements/${session.id}/select-return-start-date`)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
'use strict'

/**
* Initiates the session record used for setting up a new return requirement
* @module InitiateReturnRequirementSessionService
*/

const Boom = require('@hapi/boom')

const LicenceModel = require('../../../app/models/licence.model.js')
const SessionModel = require('../../models/session.model.js')

/**
* Initiates the session record using for setting up a new return requirement
*
* During the setup journey for a new return requirement we temporarily store the data in a `SessionModel` instance. It
* is expected that on each page of the journey the GET will fetch the session record and use it to populate the view.
* When the page is submitted the session record will be updated with the next piece of data.
*
* At the end when the journey is complete the data from the session will be used to create the return requirement and
* the session record itself deleted.
*
* @param {String} licenceId - the ID of the licence the return requirement will be created for
*
* @returns {module:SessionModel} the newly created session record
*/
async function go (licenceId) {
const licence = await _fetchLicence(licenceId)

const data = _data(licence)

return _createSession(data)
}

async function _createSession (data) {
const session = await SessionModel.query()
.insert({
data
})
.returning('*')

return session
}

function _data (licence) {
const { id, licenceRef, licenceDocument } = licence

return {
licence: {
id,
licenceRef,
licenceHolder: _licenceHolder(licenceDocument)
}
}
}

async function _fetchLicence (licenceId) {
const licence = await LicenceModel.query()
.findById(licenceId)
.select([
'id',
'licenceRef'
])
.withGraphFetched('licenceDocument')
.modifyGraph('licenceDocument', (builder) => {
builder.select([
'id'
])
})
.withGraphFetched('licenceDocument.licenceDocumentRoles')
.modifyGraph('licenceDocument.licenceDocumentRoles', (builder) => {
builder
.select([
'licenceDocumentRoles.id'
])
.innerJoinRelated('licenceRole')
.where('licenceRole.name', 'licenceHolder')
.orderBy('licenceDocumentRoles.startDate', 'desc')
})
.withGraphFetched('licenceDocument.licenceDocumentRoles.company')
.modifyGraph('licenceDocument.licenceDocumentRoles.company', (builder) => {
builder.select([
'id',
'name',
'type'
])
})
.withGraphFetched('licenceDocument.licenceDocumentRoles.contact')
.modifyGraph('licenceDocument.licenceDocumentRoles.contact', (builder) => {
builder.select([
'id',
'contactType',
'dataSource',
'department',
'firstName',
'initials',
'lastName',
'middleInitials',
'salutation',
'suffix'
])
})

if (!licence) {
throw Boom.notFound('Licence for new return requirement not found', { id: licenceId })
}

return licence
}

function _licenceHolder (licenceDocument) {
// Extract the company and contact from the last licenceDocumentRole created. _fetchLicence() ensures in the case
// that there is more than one that they are ordered by their start date (DESC)
const { company, contact } = licenceDocument.licenceDocumentRoles[0]

if (contact) {
return contact.$name()
}

return company.name
}

module.exports = {
go
}
60 changes: 51 additions & 9 deletions test/controllers/licences.controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@ const Sinon = require('sinon')
const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script()
const { expect } = Code

// Test helpers
const Boom = require('@hapi/boom')

// Things we need to stub
const InitiateReturnRequirementSessionService = require('../../app/services/return-requirements/initiate-return-requirement-session.service.js')

// For running our service
const { init } = require('../../app/server.js')

describe('Licences controller', () => {
let options
let server

beforeEach(async () => {
Expand All @@ -33,20 +38,57 @@ describe('Licences controller', () => {
})

describe('GET /licences/{id}/no-returns-required', () => {
const options = {
method: 'GET',
url: '/licences/64924759-8142-4a08-9d1e-1e902cd9d316/no-returns-required',
auth: {
strategy: 'session',
credentials: { scope: ['billing'] }
const session = { id: '1c265420-6a5e-4a4c-94e4-196d7799ed01' }

beforeEach(async () => {
options = {
method: 'GET',
url: '/licences/64924759-8142-4a08-9d1e-1e902cd9d316/no-returns-required',
auth: {
strategy: 'session',
credentials: { scope: ['billing'] }
}
}
}
})

describe('when the request succeeds', () => {
it('creates a record in the sessions table and redirects to no retuns required reason page', async () => {
describe('when a request is valid', () => {
beforeEach(async () => {
Sinon.stub(InitiateReturnRequirementSessionService, 'go').resolves(session)
})

it('redirects to select return start date page', async () => {
const response = await server.inject(options)

expect(response.statusCode).to.equal(302)
expect(response.headers.location).to.equal(`/system/return-requirements/${session.id}/select-return-start-date`)
})
})

describe('when a request is invalid', () => {
describe('because the licence ID is unrecognised', () => {
beforeEach(async () => {
Sinon.stub(InitiateReturnRequirementSessionService, 'go').rejects(Boom.notFound())
})

it('returns a 404 and page not found', async () => {
const response = await server.inject(options)

expect(response.statusCode).to.equal(404)
expect(response.payload).to.contain('Page not found')
})
})

describe('because the initialise session service errors', () => {
beforeEach(async () => {
Sinon.stub(InitiateReturnRequirementSessionService, 'go').rejects()
})

it('returns a 200 and there is a problem with the service page', async () => {
const response = await server.inject(options)

expect(response.statusCode).to.equal(200)
expect(response.payload).to.contain('Sorry, there is a problem with the service')
})
})
})
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
'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 CompanyHelper = require('../../support/helpers/company.helper.js')
const ContactHelper = require('../../support/helpers/contact.helper.js')
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 LicenceDocumentRoleHelper = require('../../support/helpers/licence-document-role.helper.js')
const LicenceRoleHelper = require('../../support/helpers/licence-role.helper.js')

// Thing under test
const InitiateReturnRequirementSessionService = require('../../../app/services/return-requirements/initiate-return-requirement-session.service.js')

describe('Initiate Return Requirement Session service', () => {
let licence

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

describe('when called', () => {
describe('and the licence exists', () => {
const licenceRoles = {}

let company
let contact
let licenceDocument

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

// Create 2 licence roles so we can test the service only gets the licence document role record that is for
// 'licence holder'
licenceRoles.billing = await LicenceRoleHelper.add({ name: 'billing', label: 'Billing' })
licenceRoles.holder = await LicenceRoleHelper.add()

// Create company and contact records. We create an additional company so we can create 2 licence document role
// records for our licence to test the one with the latest start date is used.
company = await CompanyHelper.add({ name: 'Licence Holder Ltd' })
contact = await ContactHelper.add({ firstName: 'Luce', lastName: 'Holder' })
const oldCompany = await CompanyHelper.add({ name: 'Old Licence Holder Ltd' })

// We have to create a licence document to link our licence record to (eventually!) the company or contact
// record that is the 'licence holder'
licenceDocument = await LicenceDocumentHelper.add({ licenceRef: licence.licenceRef })

// Create two licence document role records. This one is linked to the billing role so should be ignored by the
// service
await LicenceDocumentRoleHelper.add({
licenceDocumentId: licenceDocument.id,
licenceRoleId: licenceRoles.billing.id
})

// This one is linked to the old company record so should not be used to provide the licence holder name
await LicenceDocumentRoleHelper.add({
licenceDocumentId: licenceDocument.id,
licenceRoleId: licenceRoles.holder.id,
company: oldCompany.id,
startDate: new Date('2022-01-01')
})
})

describe('and the licence holder is a company', () => {
beforeEach(async () => {
// Create the licence document role record that _is_ linked to the correct licence holder record
await LicenceDocumentRoleHelper.add({
licenceDocumentId: licenceDocument.id,
licenceRoleId: licenceRoles.holder.id,
companyId: company.id,
startDate: new Date('2022-08-01')
})
})

it('creates a new session record containing details of the licence and licence holder (company)', async () => {
const result = await InitiateReturnRequirementSessionService.go(licence.id)

const { data } = result

expect(data.licence.id).to.equal(licence.id)
expect(data.licence.licenceRef).to.equal(licence.licenceRef)
expect(data.licence.licenceHolder).to.equal('Licence Holder Ltd')
})
})

describe('and the licence holder is a contact', () => {
beforeEach(async () => {
// Create the licence document role record that _is_ linked to the correct licence holder record.
// NOTE: We create this against both the company and contact to also confirm that the contact name has
// precedence over the company name
await LicenceDocumentRoleHelper.add({
licenceDocumentId: licenceDocument.id,
licenceRoleId: licenceRoles.holder.id,
companyId: company.id,
contactId: contact.id,
startDate: new Date('2022-08-01')
})
})

it('creates a new session record containing details of the licence and licence holder (contact)', async () => {
const result = await InitiateReturnRequirementSessionService.go(licence.id)

const { data } = result

expect(data.licence.id).to.equal(licence.id)
expect(data.licence.licenceRef).to.equal(licence.licenceRef)
expect(data.licence.licenceHolder).to.equal('Luce Holder')
})
})
})

describe('but the licence does not exist', () => {
it('throws a Boom not found error', async () => {
const error = await expect(InitiateReturnRequirementSessionService.go('e456e538-4d55-4552-84f7-6a7636eb1945')).to.reject()

expect(error.isBoom).to.be.true()
expect(error.data).to.equal({ id: 'e456e538-4d55-4552-84f7-6a7636eb1945' })

const { statusCode, error: errorType, message } = error.output.payload

expect(statusCode).to.equal(404)
expect(errorType).to.equal('Not Found')
expect(message).to.equal('Licence for new return requirement not found')
})
})
})
})
Loading