Skip to content

Commit

Permalink
Add View Licence Bills page (#986)
Browse files Browse the repository at this point in the history
* Add View Licence Bills page

https://eaflood.atlassian.net/browse/WATER-4316

The existing service handling view licence is slow because it loads all the data for the tabs in one render. Work has been done previously to refactor the summary page to load only the summary information.

This change will introduce a bills controller, service and presenter to handle the view licence bills page.

This will share the same view as the summary page and load the same 'common data' established in [previous work](#957).
  • Loading branch information
jonathangoulding authored May 7, 2024
1 parent 9360788 commit 346a95c
Show file tree
Hide file tree
Showing 15 changed files with 481 additions and 17 deletions.
20 changes: 16 additions & 4 deletions app/controllers/licences.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
*/

const InitiateReturnRequirementSessionService = require('../services/return-requirements/initiate-return-requirement-session.service.js')
const ViewLicenceBillsService = require('../services/licences/view-licence-bills.service')
const ViewLicenceReturnsService = require('../services/licences/view-licence-returns.service')
const ViewLicenceSummaryService = require('../services/licences/view-licence-summary.service')

const ViewLicencePage = 'licences/view.njk'

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

Expand All @@ -25,13 +28,22 @@ async function returnsRequired (request, h) {
return h.redirect(`/system/return-requirements/${session.id}/start-date`)
}

async function viewBills (request, h) {
const { params: { id }, auth, query: { page = 1 } } = request

const data = await ViewLicenceBillsService.go(id, auth, page)

return h.view(ViewLicencePage, {
...data
})
}

async function viewSummary (request, h) {
const { params: { id }, auth } = request

const data = await ViewLicenceSummaryService.go(id, auth)

return h.view('licences/view.njk', {
activeNavBar: 'search',
return h.view(ViewLicencePage, {
...data
})
}
Expand All @@ -41,15 +53,15 @@ async function viewReturns (request, h) {

const data = await ViewLicenceReturnsService.go(id, auth, page)

return h.view('licences/view.njk', {
activeNavBar: 'search',
return h.view(ViewLicencePage, {
...data
})
}

module.exports = {
noReturnsRequired,
returnsRequired,
viewBills,
viewReturns,
viewSummary
}
39 changes: 39 additions & 0 deletions app/presenters/licences/view-licence-bills.presenter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use strict'

/**
* Formats data for the `/licences/{id}/bills` view licence bill page
* @module ViewLicenceBillsPresenter
*/

const { formatLongDate, formatMoney } = require('../base.presenter')

/**
* Formats data for the `/licences/{id}/bills` view licence bill page
*
* @returns {Object} The data formatted for the view template
*/
function go (bills) {
return {
activeTab: 'bills',
bills: _formatBillsToTableRow(bills)
}
}

function _formatBillsToTableRow (bills) {
return bills.map((bill) => {
return {
billNumber: bill.invoiceNumber,
dateCreated: formatLongDate(new Date(bill.createdAt)),
account: bill.accountNumber,
runType: bill.billRun.batchType,
financialYear: bill.financialYearEnding,
total: formatMoney(bill.netAmount),
accountId: bill.billingAccountId,
id: bill.id
}
})
}

module.exports = {
go
}
3 changes: 2 additions & 1 deletion app/presenters/licences/view-licence.presenter.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ function go (licence, auth) {
pageTitle: `Licence ${licenceRef}`,
registeredTo,
roles: _authRoles(auth),
warning: _generateWarningMessage(ends)
warning: _generateWarningMessage(ends),
activeNavBar: 'search'
}
}

Expand Down
13 changes: 13 additions & 0 deletions app/routes/licence.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@
const LicencesController = require('../controllers/licences.controller.js')

const routes = [
{
method: 'GET',
path: '/licences/{id}/bills',
handler: LicencesController.viewBills,
options: {
auth: {
access: {
scope: ['billing']
}
},
description: 'View a licence bills page'
}
},
{
method: 'GET',
path: '/licences/{id}/summary',
Expand Down
50 changes: 50 additions & 0 deletions app/services/licences/fetch-licence-bills.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use strict'

/**
* Fetches all return logs for a licence which is needed for the view '/licences/{id}/bills` page
* @module FetchLicenceBillsService
*/

const BillModel = require('../../models/bill.model')

const DatabaseConfig = require('../../../config/database.config')

/**
* Fetches all bills for a licence which is needed for the view '/licences/{id}/bills` page
*
* @param {string} licenceId - The UUID for the licence to fetch
*
* @returns {Promise<Object>} the data needed to populate the view licence page's bills tab
*/
async function go (licenceId, page) {
const { results, total } = await _fetch(licenceId, page)

return { bills: results, pagination: { total } }
}

async function _fetch (licenceId, page) {
return BillModel.query()
.select([
'bills.id',
'bills.invoiceNumber',
'bills.accountNumber',
'bills.financialYearEnding',
'bills.netAmount',
'bills.billingAccountId',
'bills.createdAt'
])
.innerJoinRelated('billLicences')
.where('billLicences.licence_id', licenceId)
.withGraphFetched('billRun')
.modifyGraph('billRun', (builder) => {
builder.select(['batchType'])
})
.orderBy([
{ column: 'createdAt', order: 'desc' }
])
.page(page - 1, DatabaseConfig.defaultPageSize)
}

module.exports = {
go
}
37 changes: 37 additions & 0 deletions app/services/licences/view-licence-bills.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use strict'

/**
* Orchestrates fetching and presenting the data needed for the licence summary page
* @module ViewLicenceBillsService
*/

const FetchLicenceBillsService = require('./fetch-licence-bills.service')
const ViewLicenceBillsPresenter = require('../../presenters/licences/view-licence-bills.presenter')
const ViewLicenceService = require('./view-licence.service')
const PaginatorPresenter = require('../../presenters/paginator.presenter')

/**
* Orchestrates fetching and presenting the data needed for the licence summary page
*
* @param {string} licenceId - The UUID of the licence
*
* @returns {Promise<Object>} an object representing the `pageData` needed by the licence summary template.
*/
async function go (licenceId, auth, page) {
const commonData = await ViewLicenceService.go(licenceId, auth)

const billsData = await FetchLicenceBillsService.go(licenceId, page)
const pageData = ViewLicenceBillsPresenter.go(billsData.bills)

const pagination = PaginatorPresenter.go(billsData.pagination.total, Number(page), `/system/licences/${licenceId}/bills`)

return {
...commonData,
...pageData,
pagination
}
}

module.exports = {
go
}
55 changes: 55 additions & 0 deletions app/views/licences/tabs/bills.njk
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{% from "govuk/components/pagination/macro.njk" import govukPagination %}

<table class="govuk-table">
<h2 class="govuk-heading-l">Bills</h2>
{% if bills %}
<table class="govuk-table">
<thead class="govuk-table__head">
<tr class="govuk-table__row">
<th class="govuk-table__header" scope="col">Bill number</th>
<th class="govuk-table__header" scope="col">Date created</th>
<th class="govuk-table__header" scope="col">Billing account</th>
<th class="govuk-table__header" scope="col">Bill run type</th>
<th class="govuk-table__header govuk-table__header--numeric" scope="col">Financial year</th>
<th class="govuk-table__header govuk-table__header--numeric" scope="col">Bill total</th>
</tr>
</thead>
<tbody class="govuk-table__body">
{% for bill in bills %}
<tr class="govuk-table__row">
<td class="govuk-table__cell" scope="row">
<a href="/system/bills/{{ bill.id }}">
{{ bill.billNumber }}
</a>
</td>
<td class="govuk-table__cell">
{{ bill.dateCreated }}
</td>
<td class="govuk-table__cell">
<a href="/billing-accounts/{{ bill.accountId }}">
{{ bill.account }}
</a>
</td>
<td class="govuk-table__cell">
{{ bill.runType | title }}
</td>
<td class="govuk-table__cell govuk-table__cell--numeric">
{{ bill.financialYear }}
</td>
<td class="govuk-table__cell govuk-table__cell--numeric">
{{ bill.total }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No bills sent for this licence.</p>
{% endif %}
</table>

{% if bills and pagination.numberOfPages > 1 %}
{{ govukPagination(pagination.component) }}
{% endif %}


3 changes: 3 additions & 0 deletions app/views/licences/view.njk
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@
{% if activeTab === 'returns' %}
{% include "licences/tabs/returns.njk" %}
{% endif %}
{% if activeTab === 'bills' %}
{% include "licences/tabs/bills.njk" %}
{% endif %}
</div>
</div>
{% endblock %}
77 changes: 66 additions & 11 deletions test/controllers/licences.controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const Boom = require('@hapi/boom')

// Things we need to stub
const InitiateReturnRequirementSessionService = require('../../app/services/return-requirements/initiate-return-requirement-session.service.js')
const ViewLicenceBillsService = require('../../app/services/licences/view-licence-bills.service')
const ViewLicenceSummaryService = require('../../app/services/licences/view-licence-summary.service')
const ViewLicenceReturnsService = require('../../app/services/licences/view-licence-returns.service')

Expand Down Expand Up @@ -151,6 +152,53 @@ describe('Licences controller', () => {
})
})

describe('GET /licences/{id}/bills', () => {
beforeEach(async () => {
options = {
method: 'GET',
url: '/licences/7861814c-ca19-43f2-be11-3c612f0d744b/bills',
auth: {
strategy: 'session',
credentials: { scope: ['billing'] }
}
}
})

describe('when a request is valid and has bills', () => {
beforeEach(async () => {
Sinon.stub(ViewLicenceBillsService, 'go').resolves(_viewLicenceBills())
})

it('returns the page successfully', async () => {
const response = await server.inject(options)

expect(response.statusCode).to.equal(200)
expect(response.payload).to.contain('Bills')
// Check the table titles
expect(response.payload).to.contain('Bill number')
expect(response.payload).to.contain('Date created')
expect(response.payload).to.contain('Billing account')
expect(response.payload).to.contain('Bill run type')
expect(response.payload).to.contain('Bill total')
})
})
describe('when a request is valid and has no bills', () => {
beforeEach(async () => {
Sinon.stub(ViewLicenceBillsService, 'go').resolves({ activeTab: 'bills' })
})

it('returns the page successfully', async () => {
const response = await server.inject(options)

expect(response.statusCode).to.equal(200)
expect(response.payload).to.contain('Bills')
// Check the table titles
expect(response.payload).to.contain('Bills')
expect(response.payload).to.contain('No bills sent for this licence.')
})
})
})

describe('GET /licences/{id}/summary', () => {
beforeEach(async () => {
options = {
Expand All @@ -177,17 +225,6 @@ describe('Licences controller', () => {
expect(response.payload).to.contain('End date')
})
})

function _viewLicenceSummary () {
return {
id: '7861814c-ca19-43f2-be11-3c612f0d744b',
licenceRef: '01/130/R01',
region: 'Southern',
startDate: '1 November 2022',
endDate: '1 November 2032',
activeTab: 'summary'
}
}
})

describe('GET /licences/{id}/returns', () => {
Expand Down Expand Up @@ -222,9 +259,27 @@ describe('Licences controller', () => {
})
})

function _viewLicenceBills () {
return {
activeTab: 'bills',
bills: []
}
}

function _viewLicenceReturns () {
return {
activeTab: 'returns',
returns: [{}]
}
}

function _viewLicenceSummary () {
return {
id: '7861814c-ca19-43f2-be11-3c612f0d744b',
licenceRef: '01/130/R01',
region: 'Southern',
startDate: '1 November 2022',
endDate: '1 November 2032',
activeTab: 'summary'
}
}
Loading

0 comments on commit 346a95c

Please sign in to comment.