diff --git a/app/models/water/billing-invoice-licence.model.js b/app/models/water/billing-invoice-licence.model.js index 070d1c1cf6..32c20f8ada 100644 --- a/app/models/water/billing-invoice-licence.model.js +++ b/app/models/water/billing-invoice-licence.model.js @@ -35,6 +35,14 @@ class BillingInvoiceLicenceModel extends WaterBaseModel { to: 'billingInvoices.billingInvoiceId' } }, + billingTransactions: { + relation: Model.HasManyRelation, + modelClass: 'billing-transaction.model', + join: { + from: 'billingInvoiceLicences.billingInvoiceLicenceId', + to: 'billingTransactions.billingInvoiceLicenceId' + } + }, licence: { relation: Model.BelongsToOneRelation, modelClass: 'licence.model', diff --git a/app/models/water/billing-transaction.model.js b/app/models/water/billing-transaction.model.js new file mode 100644 index 0000000000..015d87c78f --- /dev/null +++ b/app/models/water/billing-transaction.model.js @@ -0,0 +1,50 @@ +'use strict' + +/** + * Model for billing_transactions + * @module BillingTransactionModel + */ + +const { Model } = require('objection') + +const WaterBaseModel = require('./water-base.model.js') + +class BillingTransactionModel extends WaterBaseModel { + static get tableName () { + return 'billingTransactions' + } + + static get idColumn () { + return 'billingTransactionId' + } + + static get translations () { + return [ + { database: 'dateCreated', model: 'createdAt' }, + { database: 'dateUpdated', model: 'updatedAt' } + ] + } + + static get relationMappings () { + return { + chargeElement: { + relation: Model.BelongsToOneRelation, + modelClass: 'charge-element.model', + join: { + from: 'billingTransactions.chargeElementId', + to: 'chargeElements.chargeElementId' + } + }, + billingInvoiceLicence: { + relation: Model.BelongsToOneRelation, + modelClass: 'billing-invoice-licence.model', + join: { + from: 'billingTransactions.billingInvoiceLicenceId', + to: 'billingInvoiceLicences.billingInvoiceLicenceId' + } + } + } + } +} + +module.exports = BillingTransactionModel diff --git a/app/models/water/charge-element.model.js b/app/models/water/charge-element.model.js index ef4a7c51d7..da96e6b0b1 100644 --- a/app/models/water/charge-element.model.js +++ b/app/models/water/charge-element.model.js @@ -50,6 +50,14 @@ class ChargeElementModel extends WaterBaseModel { from: 'chargeElements.chargeElementId', to: 'chargePurposes.chargeElementId' } + }, + billingTransactions: { + relation: Model.HasManyRelation, + modelClass: 'billing-transaction.model', + join: { + from: 'chargeElements.chargeElementId', + to: 'billingTransactions.chargeElementId' + } } } } diff --git a/db/migrations/20230302114546_create-water-billing-transactions.js b/db/migrations/20230302114546_create-water-billing-transactions.js new file mode 100644 index 0000000000..53af3db04d --- /dev/null +++ b/db/migrations/20230302114546_create-water-billing-transactions.js @@ -0,0 +1,79 @@ +'use strict' + +const tableName = 'billing_transactions' + +exports.up = async function (knex) { + await knex + .schema + .withSchema('water') + .createTable(tableName, table => { + // Primary Key + table.uuid('billing_transaction_id').primary().defaultTo(knex.raw('gen_random_uuid()')) + + // Data + table.uuid('billing_invoice_licence_id').notNullable() + table.uuid('charge_element_id') + table.date('start_date') + table.date('end_date') + table.jsonb('abstraction_period') + table.string('source') + table.string('season') + table.string('loss') + table.decimal('net_amount') + table.boolean('is_credit') + table.string('charge_type') + table.decimal('authorised_quantity') + table.decimal('billable_quantity') + table.integer('authorised_days') + table.integer('billable_days') + table.string('status') + table.string('description').notNullable() + table.uuid('external_id') + table.decimal('volume') + table.decimal('section_126_factor').defaultTo(1) + table.boolean('section_127_agreement').notNullable().defaultTo(false) + table.string('section_130_agreement') + table.boolean('is_new_licence').notNullable().defaultTo(false) + table.boolean('is_de_minimis').notNullable().defaultTo(false) + table.string('legacy_id') + table.jsonb('metadata') + table.uuid('source_transaction_id') + table.decimal('calc_source_factor') + table.decimal('calc_season_factor') + table.decimal('calc_loss_factor') + table.decimal('calc_suc_factor') + table.decimal('calc_s_126_factor') + table.decimal('calc_s_127_factor') + table.decimal('calc_eiuc_factor') + table.decimal('calc_eiuc_source_factor') + table.boolean('is_credited_back').defaultTo(false) + table.boolean('is_two_part_second_part_charge').notNullable().defaultTo(false) + table.string('scheme').notNullable().defaultTo('alcs') + table.decimal('aggregate_factor') + table.decimal('adjustment_factor') + table.string('charge_category_code') + table.string('charge_category_description') + table.boolean('is_supported_source').defaultTo(false) + table.string('supported_source_name') + table.boolean('is_water_company_charge').defaultTo(false) + table.boolean('is_winter_only').defaultTo(false) + table.boolean('is_water_undertaker').defaultTo(false) + table.jsonb('purposes') + table.jsonb('gross_values_calculated') + table.decimal('winter_discount_factor') + table.decimal('calc_adjustment_factor') + table.decimal('calc_winter_discount_factor') + table.decimal('calc_s_130_factor') + + // Legacy timestamps + table.timestamp('date_created', { useTz: false }).notNullable().defaultTo(knex.fn.now()) + table.timestamp('date_updated', { useTz: false }).notNullable().defaultTo(knex.fn.now()) + }) +} + +exports.down = function (knex) { + return knex + .schema + .withSchema('water') + .dropTableIfExists(tableName) +} diff --git a/test/models/water/billing-invoice-licence.model.test.js b/test/models/water/billing-invoice-licence.model.test.js index 5aeab2946d..689bcdc544 100644 --- a/test/models/water/billing-invoice-licence.model.test.js +++ b/test/models/water/billing-invoice-licence.model.test.js @@ -11,6 +11,8 @@ const { expect } = Code const BillingInvoiceHelper = require('../../support/helpers/water/billing-invoice.helper.js') const BillingInvoiceModel = require('../../../app/models/water/billing-invoice.model.js') const BillingInvoiceLicenceHelper = require('../../support/helpers/water/billing-invoice-licence.helper.js') +const BillingTransactionHelper = require('../../support/helpers/water/billing-transaction.helper.js') +const BillingTransactionModel = require('../../../app/models/water/billing-transaction.model.js') const DatabaseHelper = require('../../support/helpers/database.helper.js') const LicenceHelper = require('../../support/helpers/water/licence.helper.js') const LicenceModel = require('../../../app/models/water/licence.model.js') @@ -67,6 +69,42 @@ describe('Billing Invoice Licence model', () => { }) }) + describe('when linking to billing transactions', () => { + let testBillingTransactions + + beforeEach(async () => { + testRecord = await BillingInvoiceLicenceHelper.add() + const { billingInvoiceLicenceId } = testRecord + + testBillingTransactions = [] + for (let i = 0; i < 2; i++) { + const billingTransaction = await BillingTransactionHelper.add({ billingInvoiceLicenceId }) + testBillingTransactions.push(billingTransaction) + } + }) + + it('can successfully run a related query', async () => { + const query = await BillingInvoiceLicenceModel.query() + .innerJoinRelated('billingTransactions') + + expect(query).to.exist() + }) + + it('can eager load the billing transactions', async () => { + const result = await BillingInvoiceLicenceModel.query() + .findById(testRecord.billingInvoiceLicenceId) + .withGraphFetched('billingTransactions') + + expect(result).to.be.instanceOf(BillingInvoiceLicenceModel) + expect(result.billingInvoiceLicenceId).to.equal(testRecord.billingInvoiceLicenceId) + + expect(result.billingTransactions).to.be.an.array() + expect(result.billingTransactions[0]).to.be.an.instanceOf(BillingTransactionModel) + expect(result.billingTransactions).to.include(testBillingTransactions[0]) + expect(result.billingTransactions).to.include(testBillingTransactions[1]) + }) + }) + describe('when linking to licence', () => { let testLicence diff --git a/test/models/water/billing-transaction.model.test.js b/test/models/water/billing-transaction.model.test.js new file mode 100644 index 0000000000..e960814cad --- /dev/null +++ b/test/models/water/billing-transaction.model.test.js @@ -0,0 +1,100 @@ +'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 BillingInvoiceLicenceModel = require('../../../app/models/water/billing-invoice-licence.model.js') +const BillingInvoiceLicenceHelper = require('../../support/helpers/water/billing-invoice-licence.helper.js') +const BillingTransactionHelper = require('../../support/helpers/water/billing-transaction.helper.js') +const ChargeElementHelper = require('../../support/helpers/water/charge-element.helper.js') +const ChargeElementModel = require('../../../app/models/water/charge-element.model.js') +const DatabaseHelper = require('../../support/helpers/database.helper.js') + +// Thing under test +const BillingTransactionModel = require('../../../app/models/water/billing-transaction.model.js') + +describe('Billing Transaction model', () => { + let testRecord + + beforeEach(async () => { + await DatabaseHelper.clean() + + testRecord = await BillingTransactionHelper.add() + }) + + describe('Basic query', () => { + it('can successfully run a basic query', async () => { + const result = await BillingTransactionModel.query().findById(testRecord.billingTransactionId) + + expect(result).to.be.an.instanceOf(BillingTransactionModel) + expect(result.billingTransactionId).to.equal(testRecord.billingTransactionId) + }) + }) + + describe('Relationships', () => { + describe('when linking to billing invoice licence', () => { + let testBillingInvoiceLicence + + beforeEach(async () => { + testBillingInvoiceLicence = await BillingInvoiceLicenceHelper.add() + + const { billingInvoiceLicenceId } = testBillingInvoiceLicence + testRecord = await BillingTransactionHelper.add({ billingInvoiceLicenceId }) + }) + + it('can successfully run a related query', async () => { + const query = await BillingTransactionModel.query() + .innerJoinRelated('billingInvoiceLicence') + + expect(query).to.exist() + }) + + it('can eager load the billing invoice licence', async () => { + const result = await BillingTransactionModel.query() + .findById(testRecord.billingTransactionId) + .withGraphFetched('billingInvoiceLicence') + + expect(result).to.be.instanceOf(BillingTransactionModel) + expect(result.billingTransactionId).to.equal(testRecord.billingTransactionId) + + expect(result.billingInvoiceLicence).to.be.an.instanceOf(BillingInvoiceLicenceModel) + expect(result.billingInvoiceLicence).to.equal(testBillingInvoiceLicence) + }) + }) + + describe('when linking to charge element', () => { + let testChargeElement + + beforeEach(async () => { + testChargeElement = await ChargeElementHelper.add() + + const { chargeElementId } = testChargeElement + testRecord = await BillingTransactionHelper.add({ chargeElementId }) + }) + + it('can successfully run a related query', async () => { + const query = await BillingTransactionModel.query() + .innerJoinRelated('chargeElement') + + expect(query).to.exist() + }) + + it('can eager load the charge element', async () => { + const result = await BillingTransactionModel.query() + .findById(testRecord.billingTransactionId) + .withGraphFetched('chargeElement') + + expect(result).to.be.instanceOf(BillingTransactionModel) + expect(result.billingTransactionId).to.equal(testRecord.billingTransactionId) + + expect(result.chargeElement).to.be.an.instanceOf(ChargeElementModel) + expect(result.chargeElement).to.equal(testChargeElement) + }) + }) + }) +}) diff --git a/test/models/water/charge-element.model.test.js b/test/models/water/charge-element.model.test.js index 9d56e6a3d5..eeda52f35d 100644 --- a/test/models/water/charge-element.model.test.js +++ b/test/models/water/charge-element.model.test.js @@ -10,6 +10,8 @@ const { expect } = Code // Test helpers const BillingChargeCategoryHelper = require('../../support/helpers/water/billing-charge-category.helper.js') const BillingChargeCategoryModel = require('../../../app/models/water/billing-charge-category.model.js') +const BillingTransactionHelper = require('../../support/helpers/water/billing-transaction.helper.js') +const BillingTransactionModel = require('../../../app/models/water/billing-transaction.model.js') const ChargeElementHelper = require('../../support/helpers/water/charge-element.helper.js') const ChargePurposeHelper = require('../../support/helpers/water/charge-purpose.helper.js') const ChargePurposeModel = require('../../../app/models/water/charge-purpose.model.js') @@ -104,6 +106,41 @@ describe('Charge Element model', () => { }) }) + describe('when linking to billing transactions', () => { + let testBillingTransactions + + beforeEach(async () => { + const { chargeElementId } = testRecord + + testBillingTransactions = [] + for (let i = 0; i < 2; i++) { + const billingTransaction = await BillingTransactionHelper.add({ description: `TEST BILLING TRANSACTION ${i}`, chargeElementId }) + testBillingTransactions.push(billingTransaction) + } + }) + + it('can successfully run a related query', async () => { + const query = await ChargeElementModel.query() + .innerJoinRelated('billingTransactions') + + expect(query).to.exist() + }) + + it('can eager load the billing transactions', async () => { + const result = await ChargeElementModel.query() + .findById(testRecord.chargeElementId) + .withGraphFetched('billingTransactions') + + expect(result).to.be.instanceOf(ChargeElementModel) + expect(result.chargeElementId).to.equal(testRecord.chargeElementId) + + expect(result.billingTransactions).to.be.an.array() + expect(result.billingTransactions[0]).to.be.an.instanceOf(BillingTransactionModel) + expect(result.billingTransactions).to.include(testBillingTransactions[0]) + expect(result.billingTransactions).to.include(testBillingTransactions[1]) + }) + }) + describe('when linking to charge version', () => { let testChargeVersion diff --git a/test/support/helpers/water/billing-transaction.helper.js b/test/support/helpers/water/billing-transaction.helper.js new file mode 100644 index 0000000000..b327be9126 --- /dev/null +++ b/test/support/helpers/water/billing-transaction.helper.js @@ -0,0 +1,52 @@ +'use strict' + +/** + * @module BillingTransactionHelper + */ + +const BillingTransactionModel = require('../../../../app/models/water/billing-transaction.model.js') + +/** + * Add a new billing transaction + * + * If no `data` is provided, default values will be used. These are + * + * - `billingInvoiceLicenceId` - 7190937e-e176-4d50-ae4f-c00c5e76938a + * - `description` - River Beult at Boughton Monchelsea + * + * @param {Object} [data] Any data you want to use instead of the defaults used here or in the database + * + * @returns {module:BillingTransactionModel} The instance of the newly created record + */ +function add (data = {}) { + const insertData = defaults(data) + + return BillingTransactionModel.query() + .insert({ ...insertData }) + .returning('*') +} + +/** + * Returns the defaults used when creating a new record + * + * 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 = { + billingInvoiceLicenceId: '7190937e-e176-4d50-ae4f-c00c5e76938a', + description: 'River Beult at Boughton Monchelsea' + } + + return { + ...defaults, + ...data + } +} + +module.exports = { + add, + defaults +}