diff --git a/app/models/bill-run-charge-version-year.model.js b/app/models/bill-run-charge-version-year.model.js new file mode 100644 index 0000000000..a1fe1bd43e --- /dev/null +++ b/app/models/bill-run-charge-version-year.model.js @@ -0,0 +1,52 @@ +'use strict' + +/** + * Model for bill_run_charge_version_years (water.billing_batch_charge_version_years) + * @module BillRunChargeVersionYearModel + */ + +const { Model } = require('objection') + +const BaseModel = require('./base.model.js') + +/** + * Represents an instance of a bill run charge version year record + * + * For reference, the bill run charge version year record is a 'nothing' record! Certainly as far as we are concerned. + * + * They use this table when generating pre-sroc bill runs. We think it was mainly to support supplementary bill runs + * and the fact they cover a range of years rather than a single period. + * + * We haven't needed to do anything like this in our engine and nothing else appears to use this table. The only reason + * we reference it is to delete stuff when a bill run gets cancelled. + * + * Welcome to dealing with the legacy database schema! ¯\_(ツ)_/¯ + */ +class BillRunChargeVersionYearModel extends BaseModel { + static get tableName () { + return 'billRunChargeVersionYears' + } + + static get relationMappings () { + return { + billRun: { + relation: Model.BelongsToOneRelation, + modelClass: 'bill-run.model', + join: { + from: 'billRunChargeVersionYears.billRunId', + to: 'billRuns.id' + } + }, + chargeVersion: { + relation: Model.BelongsToOneRelation, + modelClass: 'charge-version.model', + join: { + from: 'billRunChargeVersionYears.chargeVersionId', + to: 'chargeVersions.id' + } + } + } + } +} + +module.exports = BillRunChargeVersionYearModel diff --git a/app/models/charge-version.model.js b/app/models/charge-version.model.js index bb8df7f515..31229191ca 100644 --- a/app/models/charge-version.model.js +++ b/app/models/charge-version.model.js @@ -24,6 +24,14 @@ class ChargeVersionModel extends BaseModel { to: 'billingAccounts.id' } }, + billRunChargeVersionYears: { + relation: Model.HasManyRelation, + modelClass: 'bill-run-charge-version-year.model', + join: { + from: 'chargeVersions.id', + to: 'billRunChargeVersionYears.chargeVersionId' + } + }, licence: { relation: Model.BelongsToOneRelation, modelClass: 'licence.model', diff --git a/db/migrations/legacy/20221108007018-water-billing-batch-charge-version-years.js b/db/migrations/legacy/20221108007018-water-billing-batch-charge-version-years.js new file mode 100644 index 0000000000..3d4e3e385d --- /dev/null +++ b/db/migrations/legacy/20221108007018-water-billing-batch-charge-version-years.js @@ -0,0 +1,37 @@ +'use strict' + +const tableName = 'billing_batch_charge_version_years' + +exports.up = function (knex) { + return knex + .schema + .withSchema('water') + .createTable(tableName, (table) => { + // Primary Key + table.uuid('billing_batch_charge_version_year_id').primary().defaultTo(knex.raw('gen_random_uuid()')) + + // Data + table.uuid('billing_batch_id').notNullable() + table.uuid('charge_version_id').notNullable() + table.integer('financial_year_ending').notNullable() + table.string('status').notNullable() + table.string('transaction_type').notNullable() + table.boolean('is_summer').notNullable() + table.boolean('has_two_part_agreement').defaultTo(false) + table.boolean('is_chargeable').defaultTo(true) + + // Legacy timestamps + table.timestamp('date_created', { useTz: false }).notNullable().defaultTo(knex.fn.now()) + table.timestamp('date_updated', { useTz: false }).notNullable().defaultTo(knex.fn.now()) + + // Constraints + table.unique(['billing_batch_id', 'charge_version_id', 'financial_year_ending', 'transaction_type', 'is_summer'], { useConstraint: true }) + }) +} + +exports.down = function (knex) { + return knex + .schema + .withSchema('water') + .dropTableIfExists(tableName) +} diff --git a/db/migrations/public/20240229170332_create-bill-run-charge-version-years.js b/db/migrations/public/20240229170332_create-bill-run-charge-version-years.js new file mode 100644 index 0000000000..916f3d1de3 --- /dev/null +++ b/db/migrations/public/20240229170332_create-bill-run-charge-version-years.js @@ -0,0 +1,29 @@ +'use strict' + +const viewName = 'bill_run_charge_version_years' + +exports.up = function (knex) { + return knex + .schema + .createView(viewName, (view) => { + view.as(knex('billing_batch_charge_version_years').withSchema('water').select([ + 'billing_batch_charge_version_year_id AS id', + 'billing_batch_id AS bill_run_id', + 'charge_version_id', + 'financial_year_ending', + 'status', + 'transaction_type AS batch_type', + 'is_summer AS summer', + 'has_two_part_agreement AS two_part_agreement', + 'is_chargeable AS chargeable', + 'date_created AS created_at', + 'date_updated AS updated_at' + ])) + }) +} + +exports.down = function (knex) { + return knex + .schema + .dropViewIfExists(viewName) +} diff --git a/test/models/bill-run-charge-version-year.model.test.js b/test/models/bill-run-charge-version-year.model.test.js new file mode 100644 index 0000000000..c2ac36e44d --- /dev/null +++ b/test/models/bill-run-charge-version-year.model.test.js @@ -0,0 +1,102 @@ +'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/bill-run.helper.js') +const BillRunModel = require('../../app/models/bill-run.model.js') +const BillRunChargeVersionYearHelper = require('../support/helpers/bill-run-charge-version-year.helper.js') +const ChargeVersionHelper = require('../support/helpers/charge-version.helper.js') +const ChargeVersionModel = require('../../app/models/charge-version.model.js') +const DatabaseHelper = require('../support/helpers/database.helper.js') + +// Thing under test +const BillRunChargeVersionYearModel = require('../../app/models/bill-run-charge-version-year.model.js') + +describe('Bill Run Charge Version Year model', () => { + let testRecord + + beforeEach(async () => { + await DatabaseHelper.clean() + }) + + describe('Basic query', () => { + beforeEach(async () => { + testRecord = await BillRunChargeVersionYearHelper.add() + }) + + it('can successfully run a basic query', async () => { + const result = await BillRunChargeVersionYearModel.query().findById(testRecord.id) + + expect(result).to.be.an.instanceOf(BillRunChargeVersionYearModel) + expect(result.id).to.equal(testRecord.id) + }) + }) + + describe('Relationships', () => { + describe('when linking to bill run', () => { + let testBillRun + + beforeEach(async () => { + testBillRun = await BillRunHelper.add() + + const { id: billRunId } = testBillRun + testRecord = await BillRunChargeVersionYearHelper.add({ billRunId }) + }) + + it('can successfully run a related query', async () => { + const query = await BillRunChargeVersionYearModel.query() + .innerJoinRelated('billRun') + + expect(query).to.exist() + }) + + it('can eager load the bill run', async () => { + const result = await BillRunChargeVersionYearModel.query() + .findById(testRecord.id) + .withGraphFetched('billRun') + + expect(result).to.be.instanceOf(BillRunChargeVersionYearModel) + expect(result.id).to.equal(testRecord.id) + + expect(result.billRun).to.be.an.instanceOf(BillRunModel) + expect(result.billRun).to.equal(testBillRun) + }) + }) + + describe('when linking to charge version', () => { + let testChargeVersion + + beforeEach(async () => { + testChargeVersion = await ChargeVersionHelper.add() + + const { id: chargeVersionId } = testChargeVersion + testRecord = await BillRunChargeVersionYearHelper.add({ chargeVersionId }) + }) + + it('can successfully run a related query', async () => { + const query = await BillRunChargeVersionYearModel.query() + .innerJoinRelated('chargeVersion') + + expect(query).to.exist() + }) + + it('can eager load the charge version', async () => { + const result = await BillRunChargeVersionYearModel.query() + .findById(testRecord.id) + .withGraphFetched('chargeVersion') + + expect(result).to.be.instanceOf(BillRunChargeVersionYearModel) + expect(result.id).to.equal(testRecord.id) + + expect(result.chargeVersion).to.be.an.instanceOf(ChargeVersionModel) + expect(result.chargeVersion).to.equal(testChargeVersion) + }) + }) + }) +}) diff --git a/test/models/charge-version.model.test.js b/test/models/charge-version.model.test.js index 49df96a5a2..e5307a5f8f 100644 --- a/test/models/charge-version.model.test.js +++ b/test/models/charge-version.model.test.js @@ -10,6 +10,8 @@ const { expect } = Code // Test helpers const BillingAccountHelper = require('../support/helpers/billing-account.helper.js') const BillingAccountModel = require('../../app/models/billing-account.model.js') +const BillRunChargeVersionYearHelper = require('../support/helpers/bill-run-charge-version-year.helper.js') +const BillRunChargeVersionYearModel = require('../../app/models/bill-run-charge-version-year.model.js') const ChangeReasonHelper = require('../support/helpers/change-reason.helper.js') const ChangeReasonModel = require('../../app/models/change-reason.model.js') const ChargeReferenceHelper = require('../support/helpers/charge-reference.helper.js') @@ -71,6 +73,41 @@ describe('Charge Version model', () => { }) }) + describe('when linking to bill run charge version years', () => { + let testBillRunChargeVersionYears + + beforeEach(async () => { + const { id: chargeVersionId } = testRecord + + testBillRunChargeVersionYears = [] + for (let i = 0; i < 2; i++) { + const billRunChargeVersionYear = await BillRunChargeVersionYearHelper.add({ chargeVersionId }) + testBillRunChargeVersionYears.push(billRunChargeVersionYear) + } + }) + + it('can successfully run a related query', async () => { + const query = await ChargeVersionModel.query() + .innerJoinRelated('billRunChargeVersionYears') + + expect(query).to.exist() + }) + + it('can eager load the charge references', async () => { + const result = await ChargeVersionModel.query() + .findById(testRecord.id) + .withGraphFetched('billRunChargeVersionYears') + + expect(result).to.be.instanceOf(ChargeVersionModel) + expect(result.id).to.equal(testRecord.id) + + expect(result.billRunChargeVersionYears).to.be.an.array() + expect(result.billRunChargeVersionYears[0]).to.be.an.instanceOf(BillRunChargeVersionYearModel) + expect(result.billRunChargeVersionYears).to.include(testBillRunChargeVersionYears[0]) + expect(result.billRunChargeVersionYears).to.include(testBillRunChargeVersionYears[1]) + }) + }) + describe('when linking to licence', () => { let testLicence diff --git a/test/support/helpers/bill-run-charge-version-year.helper.js b/test/support/helpers/bill-run-charge-version-year.helper.js new file mode 100644 index 0000000000..02024d3503 --- /dev/null +++ b/test/support/helpers/bill-run-charge-version-year.helper.js @@ -0,0 +1,60 @@ +'use strict' + +/** + * @module BillRunChargeVersionYearHelper + */ + +const BillRunChargeVersionYearModel = require('../../../app/models/bill-run-charge-version-year.model.js') +const { generateUUID } = require('../../../app/lib/general.lib.js') + +/** + * Add a new bill run charge version year record + * + * If no `data` is provided, default values will be used. These are + * + * - `billRunId` - [random UUID] + * - `chargeVersionId` - [random UUID] + * - `financialYearEnding` - 2024 + * - `batchType` - annual + * - `summer` - false + * + * @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 BillRunChargeVersionYearModel.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 = { + billRunId: generateUUID(), + chargeVersionId: generateUUID(), + financialYearEnding: 2024, + status: 'ready', + batchType: 'annual', + summer: false + } + + return { + ...defaults, + ...data + } +} + +module.exports = { + add, + defaults +}