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 View Licence Bills page #986

Merged
merged 18 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading