diff --git a/app/models/water/bill-run-volume.model.js b/app/models/water/bill-run-volume.model.js new file mode 100644 index 0000000000..b2f0b0bb9b --- /dev/null +++ b/app/models/water/bill-run-volume.model.js @@ -0,0 +1,73 @@ +'use strict' + +/** + * Model for billing_batches + * @module BillRunModel + */ + +const { Model } = require('objection') + +const WaterBaseModel = require('./water-base.model.js') + +class BillRunVolumeModel extends WaterBaseModel { + static get tableName () { + return 'billingVolumes' + } + + static get idColumn () { + return 'billingVolumeId' + } + + static get translations () { + return [] + } + + static get relationMappings () { + return { + billRun: { + relation: Model.BelongsToOneRelation, + modelClass: 'bill-run.model', + join: { + from: 'billingVolumes.billingBatchId', + to: 'billingBatches.billingBatchId' + } + }, + chargeReference: { + relation: Model.BelongsToOneRelation, + modelClass: 'charge-reference.model', + join: { + from: 'billingVolumes.chargeElementId', + to: 'chargeElements.chargeElementId' + } + } + } + } + + // NOTE: When we checked the live data the only statuses we could find in use were; 10, 40, 50, 60, 70, 90 and 100 + static get twoPartTariffStatuses () { + return { + noReturnsSubmitted: 10, + underQuery: 20, + received: 30, + someReturnsDue: 40, + lateReturns: 50, + overAbstraction: 60, + noReturnsForMatching: 70, + notDueForBilling: 80, + returnLineOverlapsChargePeriod: 90, + noMatchingChargeElement: 100 + } + } + + $twoPartTariffStatus () { + Object.entries(BillRunVolumeModel.twoPartTariffStatuses).forEach(([key, value]) => { + if (value === this.twoPartTariffStatus) { + return key + } + }) + + return null + } +} + +module.exports = BillRunVolumeModel diff --git a/app/models/water/bill-run.model.js b/app/models/water/bill-run.model.js index e489bbea01..f314ba31c0 100644 --- a/app/models/water/bill-run.model.js +++ b/app/models/water/bill-run.model.js @@ -42,6 +42,14 @@ class BillRunModel extends WaterBaseModel { from: 'billingBatches.billingBatchId', to: 'billingInvoices.billingBatchId' } + }, + billRunVolumes: { + relation: Model.HasManyRelation, + modelClass: 'bill-run-volume.model', + join: { + from: 'billingBatches.billingBatchId', + to: 'billingVolumes.billingBatchId' + } } } } diff --git a/app/models/water/charge-reference.model.js b/app/models/water/charge-reference.model.js index 4fc2712b60..4a4a8aec1e 100644 --- a/app/models/water/charge-reference.model.js +++ b/app/models/water/charge-reference.model.js @@ -31,6 +31,14 @@ class ChargeReferenceModel extends WaterBaseModel { static get relationMappings () { return { + billRunVolumes: { + relation: Model.HasManyRelation, + modelClass: 'bill-run-volume.model', + join: { + from: 'chargeElements.chargeElementId', + to: 'billingVolumes.chargeElementId' + } + }, chargeVersion: { relation: Model.BelongsToOneRelation, modelClass: 'charge-version.model', diff --git a/app/services/data/mock/generate-returns-review.service.js b/app/services/data/mock/generate-returns-review.service.js new file mode 100644 index 0000000000..cb0268b878 --- /dev/null +++ b/app/services/data/mock/generate-returns-review.service.js @@ -0,0 +1,19 @@ +'use strict' + +async function go (id) { + const mockedReturnReviewData = { + id: '83a67627-f523-4a0b-8875-24e990d1c89c', + name: 'boo', + otherId: id + } + + return _response(mockedReturnReviewData) +} + +function _response (mockedReturnReviewData) { + return mockedReturnReviewData +} + +module.exports = { + go +} diff --git a/app/services/data/mock/mock.service.js b/app/services/data/mock/mock.service.js index 4441dfd52a..752fad4ec6 100644 --- a/app/services/data/mock/mock.service.js +++ b/app/services/data/mock/mock.service.js @@ -7,9 +7,11 @@ const ExpandedError = require('../../../errors/expanded.error.js') const GenerateBillRunService = require('./generate-bill-run.service.js') +const GenerateReturnsReviewService = require('./generate-returns-review.service.js') const types = { - 'bill-run': _billRun + 'bill-run': _billRun, + 'returns-review': _returnsReview } async function go (type, id) { @@ -34,6 +36,10 @@ async function _billRun (id) { return GenerateBillRunService.go(id) } +async function _returnsReview (id) { + return GenerateReturnsReviewService.go(id) +} + module.exports = { go } diff --git a/db/migrations/20230929085159_create-water-billing-volumes.js b/db/migrations/20230929085159_create-water-billing-volumes.js new file mode 100644 index 0000000000..b02c5f4671 --- /dev/null +++ b/db/migrations/20230929085159_create-water-billing-volumes.js @@ -0,0 +1,33 @@ +'use strict' + +const tableName = 'billing_volumes' + +exports.up = function (knex) { + return knex + .schema + .withSchema('water') + .createTable(tableName, (table) => { + // Primary Key + table.uuid('billing_volume_id').primary().defaultTo(knex.raw('gen_random_uuid()')) + + // Data + table.uuid('charge_element_id').notNullable() + table.integer('financial_year').notNullable() + table.boolean('is_summer').notNullable() + table.decimal('calculated_volume') + table.boolean('two_part_tariff_error').notNullable().defaultTo(false) + table.integer('two_part_tariff_status') + table.jsonb('two_part_tariff_review') + table.boolean('is_approved').notNullable().defaultTo(false) + table.uuid('billing_batch_id').notNullable() + table.decimal('volume') + table.timestamp('errored_on') + }) +} + +exports.down = function (knex) { + return knex + .schema + .withSchema('water') + .dropTableIfExists(tableName) +} diff --git a/test/models/water/bill-run-volume.model.test.js b/test/models/water/bill-run-volume.model.test.js new file mode 100644 index 0000000000..c2b573f3fe --- /dev/null +++ b/test/models/water/bill-run-volume.model.test.js @@ -0,0 +1,112 @@ +'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 BillRunHelper = require('../../support/helpers/water/bill-run.helper.js') +const BillRunModel = require('../../../app/models/water/bill-run.model.js') +const BillRunVolumeHelper = require('../../support/helpers/water/bill-run-volume.helper.js') +const ChargeReferenceHelper = require('../../support/helpers/water/charge-reference.helper.js') +const ChargeReferenceModel = require('../../../app/models/water/charge-reference.model.js') +const DatabaseHelper = require('../../support/helpers/database.helper.js') + +// Thing under test +const BillRunVolumeModel = require('../../../app/models/water/bill-run-volume.model.js') + +describe('Bill Run Volume model', () => { + let testRecord + + beforeEach(async () => { + await DatabaseHelper.clean() + }) + + describe('Basic query', () => { + beforeEach(async () => { + testRecord = await BillRunVolumeHelper.add() + }) + + it('can successfully run a basic query', async () => { + const result = await BillRunVolumeModel.query().findById(testRecord.billingVolumeId) + + expect(result).to.be.an.instanceOf(BillRunVolumeModel) + expect(result.billingVolumeId).to.equal(testRecord.billingVolumeId) + }) + }) + + describe('Relationships', () => { + describe('when linking to bill run', () => { + let testBillRun + + beforeEach(async () => { + testBillRun = await BillRunHelper.add() + + const { billingBatchId } = testBillRun + testRecord = await BillRunVolumeHelper.add({ billingBatchId }) + }) + + it('can successfully run a related query', async () => { + const query = await BillRunVolumeModel.query() + .innerJoinRelated('billRun') + + expect(query).to.exist() + }) + + it('can eager load the bill run', async () => { + const result = await BillRunVolumeModel.query() + .findById(testRecord.billingVolumeId) + .withGraphFetched('billRun') + + expect(result).to.be.instanceOf(BillRunVolumeModel) + expect(result.billingVolumeId).to.equal(testRecord.billingVolumeId) + + expect(result.billRun).to.be.an.instanceOf(BillRunModel) + expect(result.billRun).to.equal(testBillRun) + }) + }) + + describe('when linking to charge reference', () => { + let testChargeReference + + beforeEach(async () => { + testChargeReference = await ChargeReferenceHelper.add() + + const { chargeElementId } = testChargeReference + testRecord = await BillRunVolumeHelper.add({ chargeElementId }) + }) + + it('can successfully run a related query', async () => { + const query = await BillRunVolumeModel.query() + .innerJoinRelated('chargeReference') + + expect(query).to.exist() + }) + + it('can eager load the charge reference', async () => { + const result = await BillRunVolumeModel.query() + .findById(testRecord.billingVolumeId) + .withGraphFetched('chargeReference') + + expect(result).to.be.instanceOf(BillRunVolumeModel) + expect(result.billingVolumeId).to.equal(testRecord.billingVolumeId) + + expect(result.chargeReference).to.be.an.instanceOf(ChargeReferenceModel) + expect(result.chargeReference).to.equal(testChargeReference) + }) + }) + }) + + describe('Static getters', () => { + describe('Two Part Tariff status codes', () => { + it('returns the requested status code', async () => { + const result = BillRunVolumeModel.twoPartTariffStatuses.noReturnsSubmitted + + expect(result).to.equal(10) + }) + }) + }) +}) diff --git a/test/models/water/bill-run.model.test.js b/test/models/water/bill-run.model.test.js index ef693443be..514a8adcf6 100644 --- a/test/models/water/bill-run.model.test.js +++ b/test/models/water/bill-run.model.test.js @@ -11,6 +11,8 @@ const { expect } = Code const BillHelper = require('../../support/helpers/water/bill.helper.js') const BillModel = require('../../../app/models/water/bill.model.js') const BillRunHelper = require('../../support/helpers/water/bill-run.helper.js') +const BillRunVolumeHelper = require('../../support/helpers/water/bill-run-volume.helper.js') +const BillRunVolumeModel = require('../../../app/models/water/bill-run-volume.model.js') const DatabaseHelper = require('../../support/helpers/database.helper.js') const RegionHelper = require('../../support/helpers/water/region.helper.js') const RegionModel = require('../../../app/models/water/region.model.js') @@ -102,6 +104,42 @@ describe('Bill Run model', () => { expect(result.bills).to.include(testBills[1]) }) }) + + describe('when linking to bill run volumes', () => { + let testBillRunVolumes + + beforeEach(async () => { + testRecord = await BillRunHelper.add() + const { billingBatchId } = testRecord + + testBillRunVolumes = [] + for (let i = 0; i < 2; i++) { + const billRunVolume = await BillRunVolumeHelper.add({ billingBatchId }) + testBillRunVolumes.push(billRunVolume) + } + }) + + it('can successfully run a related query', async () => { + const query = await BillRunModel.query() + .innerJoinRelated('billRunVolumes') + + expect(query).to.exist() + }) + + it('can eager load the bills', async () => { + const result = await BillRunModel.query() + .findById(testRecord.billingBatchId) + .withGraphFetched('billRunVolumes') + + expect(result).to.be.instanceOf(BillRunModel) + expect(result.billingBatchId).to.equal(testRecord.billingBatchId) + + expect(result.billRunVolumes).to.be.an.array() + expect(result.billRunVolumes[0]).to.be.an.instanceOf(BillRunVolumeModel) + expect(result.billRunVolumes).to.include(testBillRunVolumes[0]) + expect(result.billRunVolumes).to.include(testBillRunVolumes[1]) + }) + }) }) describe('Static getters', () => { diff --git a/test/models/water/charge-reference.model.test.js b/test/models/water/charge-reference.model.test.js index 55aaca3440..9a60c95eb1 100644 --- a/test/models/water/charge-reference.model.test.js +++ b/test/models/water/charge-reference.model.test.js @@ -8,6 +8,8 @@ const { describe, it, beforeEach } = exports.lab = Lab.script() const { expect } = Code // Test helpers +const BillRunVolumeHelper = require('../../support/helpers/water/bill-run-volume.helper.js') +const BillRunVolumeModel = require('../../../app/models/water/bill-run-volume.model.js') const ChargeCategoryHelper = require('../../support/helpers/water/charge-category.helper.js') const ChargeCategoryModel = require('../../../app/models/water/charge-category.model.js') const ChargeElementHelper = require('../../support/helpers/water/charge-element.helper.js') @@ -41,6 +43,42 @@ describe('Charge Reference model', () => { }) describe('Relationships', () => { + describe('when linking to bill run volumes', () => { + let testBillRunVolumes + + beforeEach(async () => { + testRecord = await ChargeReferenceHelper.add() + const { chargeElementId } = testRecord + + testBillRunVolumes = [] + for (let i = 0; i < 2; i++) { + const billRunVolume = await BillRunVolumeHelper.add({ chargeElementId }) + testBillRunVolumes.push(billRunVolume) + } + }) + + it('can successfully run a related query', async () => { + const query = await ChargeReferenceModel.query() + .innerJoinRelated('billRunVolumes') + + expect(query).to.exist() + }) + + it('can eager load the bills', async () => { + const result = await ChargeReferenceModel.query() + .findById(testRecord.chargeElementId) + .withGraphFetched('billRunVolumes') + + expect(result).to.be.instanceOf(ChargeReferenceModel) + expect(result.chargeElementId).to.equal(testRecord.chargeElementId) + + expect(result.billRunVolumes).to.be.an.array() + expect(result.billRunVolumes[0]).to.be.an.instanceOf(BillRunVolumeModel) + expect(result.billRunVolumes).to.include(testBillRunVolumes[0]) + expect(result.billRunVolumes).to.include(testBillRunVolumes[1]) + }) + }) + describe('when linking to charge category', () => { let testChargeCategory diff --git a/test/support/helpers/water/bill-run-volume.helper.js b/test/support/helpers/water/bill-run-volume.helper.js new file mode 100644 index 0000000000..8250c35710 --- /dev/null +++ b/test/support/helpers/water/bill-run-volume.helper.js @@ -0,0 +1,57 @@ +'use strict' + +/** + * @module BillRunVolumeHelper + */ + +const BillRunVolumeModel = require('../../../../app/models/water/bill-run-volume.model.js') +const { generateUUID } = require('../../../../app/lib/general.lib.js') + +/** + * Add a new bill run volume + * + * If no `data` is provided, default values will be used. These are + * + * - `chargeElementId` - [random UUID] + * - `financialYear` - 2023 + * - `isSummer` - false + * - `billingBatchId` - [random UUID] + * + * @param {Object} [data] Any data you want to use instead of the defaults used here or in the database + * + * @returns {module:BillRunModel} The instance of the newly created record + */ +function add (data = {}) { + const insertData = defaults(data) + + return BillRunVolumeModel.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 = { + chargeElementId: generateUUID(), + financialYear: 2023, + isSummer: false, + billingBatchId: generateUUID() + } + + return { + ...defaults, + ...data + } +} + +module.exports = { + add, + defaults +}