From 0a22f80e9cff1b6c8420316fc1fd62010888755e Mon Sep 17 00:00:00 2001 From: AbidemiAdio <98023713+AbidemiAdio@users.noreply.github.com> Date: Fri, 30 Sep 2022 14:14:08 +0100 Subject: [PATCH] PAY-2318 Aggregate, map and build statement constructor object (#22) * PAY-2318 Aggregate, map and build statement constructor object * Added all statement component * Implemented all PR feedback * Changes IRO PR round-1 * added getStatement unit * schedule tests added * finalised getStatement unit * added sendStatement tests * more updates IRO PR * funding code fix added * utility tests added * moving payment constants * using trunc in tests * dueDate formatted to DD/MM/YYYY * Added schema to Organisation for DWH compactibility * Remove all logs comment * Fixed ConnectionAcquireTimeoutError * Reduced splice of reversedInvoiceNumber for settlement and calculation to 7 from 8 IRO DWH data * modified for insert into schedule table * Added Total-aggregate as fundings item * Modify organisation schema to correspond with statement-data structure * Modify organisation schema to correspond with statement-data structure Co-authored-by: Marc Templeton --- app/data/models/payment-request.js | 8 + .../get-payment-request-by-invoice-number.js | 4 +- app/inbound/return/save-settlement.js | 3 +- app/inbound/save-invoice-number.js | 2 +- .../calculation/calculation-schema.js | 7 +- .../get-calculation-by-invoice-number.js | 20 ++ .../get-calculation-by-payment-request-id.js | 10 +- app/processing/calculation/get-calculation.js | 13 +- ...ment-request-by-reversed-invoice-number.js | 15 ++ .../update-calculation-payment-request-id.js | 24 ++ app/processing/funding/fundings-schema.js | 7 +- .../funding/get-fundings-by-calculation-id.js | 4 +- app/processing/funding/get-fundings.js | 4 +- app/processing/funding/index.js | 3 + app/processing/funding/map-fundings.js | 5 +- app/processing/index.js | 12 +- ...-by-funding-code-and-payment-request-id.js | 18 ++ .../invoice-line/get-positive-invoice-line.js | 34 +++ app/processing/invoice-line/index.js | 3 + .../invoice-line/invoice-line-schema.js | 5 + .../organisation/get-organisation-by-sbi.js | 25 ++ .../organisation/get-organisation.js | 29 +++ app/processing/organisation/index.js | 3 + .../organisation/organisation-schema.js | 14 ++ ...d-payment-request-by-payment-request-id.js | 5 +- .../payment-request/get-payment-request.js | 4 +- .../payment-request/map-payment-request.js | 3 + app/processing/payment-request/schema.js | 9 +- .../reverse-engineer-invoice-number.js | 5 - ...leted-payment-request-by-invoice-number.js | 15 ++ ...get-settled-settlement-by-settlement-id.js | 4 +- app/processing/settlement/get-settlement.js | 9 +- app/processing/settlement/index.js | 8 +- app/processing/settlement/schema.js | 4 +- .../update-settlement-payment-request-id.js | 23 ++ .../statement/components/get-address.js | 15 ++ .../components/get-detailed-funding.js | 48 ++++ .../components/get-detailed-payments.js | 25 ++ .../statement/components/get-details.js | 13 ++ .../statement/components/get-scheme.js | 11 + app/processing/statement/components/index.js | 13 ++ app/processing/statement/get-statement.js | 35 +++ app/processing/statement/index.js | 6 +- app/utility/convert-to-pence.js | 8 + app/utility/convert-to-pounds.js | 5 + app/utility/index.js | 9 + .../reverse-engineer-invoice-number.js | 7 + package-lock.json | 21 +- package.json | 3 +- ...process-processing-payment-request.test.js | 2 +- .../process-submit-payment-request.test.js | 2 +- .../calculation/get-calculation.test.js | 21 +- .../get-positive-invoice-line.test.js | 92 ++++++++ .../organisation/get-organisation-test.js | 130 +++++++++++ .../get-payment-request.test.js | 27 ++- .../validate-payment-request.test.js | 3 + .../settlement/get-settlement.test.js | 6 +- .../settlement/validate-settlement.test.js | 4 +- .../statement/send-statement.test.js | 2 +- test/mock-components/mock-invoice-number.js | 2 +- test/mock-objects/mock-calculation.js | 3 +- test/mock-objects/mock-invoice-line.js | 58 +++++ test/mock-objects/mock-organisation.js | 16 ++ test/mock-objects/mock-statement.js | 16 +- .../calculation/get-calculation.test.js | 90 ++++--- .../processing/funding/get-fundings.test.js | 22 +- test/unit/processing/index.test.js | 219 +++++++++++++++++- .../get-positive-invoice-line..test.js | 153 ++++++++++++ .../organisation/get-organisation.test.js | 140 +++++++++++ .../get-payment-request.test.js | 22 +- .../map-payment-request.test.js | 6 + .../reverse-engineer-invoice-number.test.js | 31 --- .../settlement/get-settlement.test.js | 28 ++- .../statement/get-statement.test.js | 79 +++++++ test/unit/utility/convert-to-pence.test.js | 12 + test/unit/utility/convert-to-pounds.test.js | 12 + .../reverse-engineer-invoice-number.test.js | 15 ++ 77 files changed, 1641 insertions(+), 152 deletions(-) create mode 100755 app/processing/calculation/get-calculation-by-invoice-number.js create mode 100755 app/processing/calculation/get-completed-payment-request-by-reversed-invoice-number.js create mode 100755 app/processing/calculation/update-calculation-payment-request-id.js create mode 100755 app/processing/funding/index.js create mode 100755 app/processing/invoice-line/get-positive-invoice-line-by-funding-code-and-payment-request-id.js create mode 100755 app/processing/invoice-line/get-positive-invoice-line.js create mode 100755 app/processing/invoice-line/index.js create mode 100755 app/processing/invoice-line/invoice-line-schema.js create mode 100755 app/processing/organisation/get-organisation-by-sbi.js create mode 100755 app/processing/organisation/get-organisation.js create mode 100755 app/processing/organisation/index.js create mode 100755 app/processing/organisation/organisation-schema.js delete mode 100644 app/processing/reverse-engineer-invoice-number.js create mode 100755 app/processing/settlement/get-completed-payment-request-by-invoice-number.js create mode 100755 app/processing/settlement/update-settlement-payment-request-id.js create mode 100755 app/processing/statement/components/get-address.js create mode 100755 app/processing/statement/components/get-detailed-funding.js create mode 100755 app/processing/statement/components/get-detailed-payments.js create mode 100755 app/processing/statement/components/get-details.js create mode 100755 app/processing/statement/components/get-scheme.js create mode 100755 app/processing/statement/components/index.js create mode 100755 app/processing/statement/get-statement.js mode change 100644 => 100755 app/processing/statement/index.js create mode 100755 app/utility/convert-to-pence.js create mode 100755 app/utility/convert-to-pounds.js create mode 100755 app/utility/index.js create mode 100755 app/utility/reverse-engineer-invoice-number.js create mode 100755 test/integration/processing/invoice-line/get-positive-invoice-line.test.js create mode 100755 test/integration/processing/organisation/get-organisation-test.js create mode 100755 test/mock-objects/mock-invoice-line.js create mode 100755 test/mock-objects/mock-organisation.js create mode 100755 test/unit/processing/invoice-line/get-positive-invoice-line..test.js create mode 100755 test/unit/processing/organisation/get-organisation.test.js delete mode 100644 test/unit/processing/reverse-engineer-invoice-number.test.js create mode 100755 test/unit/processing/statement/get-statement.test.js create mode 100644 test/unit/utility/convert-to-pence.test.js create mode 100644 test/unit/utility/convert-to-pounds.test.js create mode 100644 test/unit/utility/reverse-engineer-invoice-number.test.js diff --git a/app/data/models/payment-request.js b/app/data/models/payment-request.js index 2d7b74af..a05412c5 100644 --- a/app/data/models/payment-request.js +++ b/app/data/models/payment-request.js @@ -1,3 +1,5 @@ +const { reverseEngineerInvoiceNumber } = require('../../utility') + module.exports = (sequelize, DataTypes) => { const paymentRequest = sequelize.define('paymentRequest', { paymentRequestId: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, @@ -12,6 +14,12 @@ module.exports = (sequelize, DataTypes) => { marketingYear: DataTypes.INTEGER, received: DataTypes.DATE, referenceId: DataTypes.UUID, + reversedInvoiceNumber: { + type: DataTypes.VIRTUAL, + get () { + return reverseEngineerInvoiceNumber(this.invoiceNumber) + } + }, schedule: DataTypes.STRING, sourceSystem: DataTypes.STRING, status: DataTypes.STRING, diff --git a/app/inbound/return/get-payment-request-by-invoice-number.js b/app/inbound/return/get-payment-request-by-invoice-number.js index 5eb48edb..a30499d9 100644 --- a/app/inbound/return/get-payment-request-by-invoice-number.js +++ b/app/inbound/return/get-payment-request-by-invoice-number.js @@ -1,11 +1,13 @@ const db = require('../../data') +const { COMPLETED } = require('../../constants/statuses') const getPaymentRequestbyInvoiceNumber = async (invoiceNumber, transaction) => { return db.paymentRequest.findOne({ transaction, lock: true, where: { - invoiceNumber + invoiceNumber, + status: COMPLETED } }) } diff --git a/app/inbound/return/save-settlement.js b/app/inbound/return/save-settlement.js index 5943b9e6..cdd611db 100644 --- a/app/inbound/return/save-settlement.js +++ b/app/inbound/return/save-settlement.js @@ -4,7 +4,8 @@ const getPaymentRequestbyInvoiceNumber = require('./get-payment-request-by-invoi const saveSettlement = async (settlement, transaction) => { const matchedPaymentRequest = await getPaymentRequestbyInvoiceNumber(settlement.invoiceNumber, transaction) settlement.paymentRequestId = (matchedPaymentRequest ? matchedPaymentRequest?.paymentRequestId : null) ?? null - await db.settlement.create(settlement, { transaction }) + const savedSettlement = await db.settlement.create(settlement, { transaction }) + await db.schedule.create({ settlementId: savedSettlement.settlementId }, { transaction }) } module.exports = saveSettlement diff --git a/app/inbound/save-invoice-number.js b/app/inbound/save-invoice-number.js index 34d09aab..42a2ecbb 100644 --- a/app/inbound/save-invoice-number.js +++ b/app/inbound/save-invoice-number.js @@ -1,5 +1,5 @@ const db = require('../data') -const reverseEngineerInvoiceNumber = require('../processing/reverse-engineer-invoice-number') +const { reverseEngineerInvoiceNumber } = require('../utility') const saveInvoiceNumber = async (invoiceNumber, transaction) => { await db.invoiceNumber.findOrCreate({ diff --git a/app/processing/calculation/calculation-schema.js b/app/processing/calculation/calculation-schema.js index 834bd234..483e600e 100755 --- a/app/processing/calculation/calculation-schema.js +++ b/app/processing/calculation/calculation-schema.js @@ -1,6 +1,9 @@ const Joi = require('joi') module.exports = Joi.object({ - sbi: Joi.number().integer().required(), - calculationDate: Joi.date().required() + calculationId: Joi.number().integer().required(), + calculationDate: Joi.date().required(), + invoiceNumber: Joi.string().required(), + paymentRequestId: Joi.number().integer().required(), + sbi: Joi.number().integer().required() }).required() diff --git a/app/processing/calculation/get-calculation-by-invoice-number.js b/app/processing/calculation/get-calculation-by-invoice-number.js new file mode 100755 index 00000000..f8187604 --- /dev/null +++ b/app/processing/calculation/get-calculation-by-invoice-number.js @@ -0,0 +1,20 @@ +const db = require('../../data') + +const getCalculationByInvoiceNumber = async (invoiceNumber, transaction) => { + return db.calculation.findOne({ + transaction, + attributes: [ + 'calculationId', + 'calculationDate', + 'invoiceNumber', + 'paymentRequestId', + 'sbi' + ], + where: { + invoiceNumber + }, + raw: true + }) +} + +module.exports = getCalculationByInvoiceNumber diff --git a/app/processing/calculation/get-calculation-by-payment-request-id.js b/app/processing/calculation/get-calculation-by-payment-request-id.js index 6aa7750d..719b29d1 100755 --- a/app/processing/calculation/get-calculation-by-payment-request-id.js +++ b/app/processing/calculation/get-calculation-by-payment-request-id.js @@ -1,10 +1,14 @@ const db = require('../../data') -const getCalculationByPaymentRequestId = async (paymentRequestId) => { +const getCalculationByPaymentRequestId = async (paymentRequestId, transaction) => { return db.calculation.findOne({ + transaction, attributes: [ - 'sbi', - 'calculationDate' + 'calculationId', + 'calculationDate', + 'invoiceNumber', + 'paymentRequestId', + 'sbi' ], where: { paymentRequestId diff --git a/app/processing/calculation/get-calculation.js b/app/processing/calculation/get-calculation.js index 88fba948..047070cf 100755 --- a/app/processing/calculation/get-calculation.js +++ b/app/processing/calculation/get-calculation.js @@ -1,9 +1,13 @@ const schema = require('./calculation-schema') const getCalculationByPaymentRequestId = require('./get-calculation-by-payment-request-id') +const updateCalculationPaymentRequestId = require('./update-calculation-payment-request-id') + +const getCalculation = async (paymentRequest, transaction) => { + const paymentRequestId = paymentRequest.paymentRequestId + const rawCalculation = await getCalculationByPaymentRequestId(paymentRequestId, transaction) + const calculation = rawCalculation || await updateCalculationPaymentRequestId(paymentRequest.invoiceNumber, paymentRequestId, transaction) -const getCalculation = async (paymentRequestId) => { - const calculation = await getCalculationByPaymentRequestId(paymentRequestId) const result = schema.validate(calculation, { abortEarly: false }) @@ -13,8 +17,11 @@ const getCalculation = async (paymentRequestId) => { } return { + calculationId: calculation.calculationId, sbi: calculation.sbi, - calculated: calculation.calculationDate + calculated: calculation.calculationDate, + invoiceNumber: calculation.invoiceNumber, + paymentRequestId: calculation.paymentRequestId } } diff --git a/app/processing/calculation/get-completed-payment-request-by-reversed-invoice-number.js b/app/processing/calculation/get-completed-payment-request-by-reversed-invoice-number.js new file mode 100755 index 00000000..fdf500fa --- /dev/null +++ b/app/processing/calculation/get-completed-payment-request-by-reversed-invoice-number.js @@ -0,0 +1,15 @@ +const db = require('../../data') + +const { COMPLETED } = require('../../constants/statuses') + +const getCompletedPaymentRequestByReversedInvoiceNumber = async (reversedInvoiceNumber, transaction) => { + return db.paymentRequest.findOne({ + transaction, + where: { + reversedInvoiceNumber, + status: COMPLETED + } + }) +} + +module.exports = getCompletedPaymentRequestByReversedInvoiceNumber diff --git a/app/processing/calculation/update-calculation-payment-request-id.js b/app/processing/calculation/update-calculation-payment-request-id.js new file mode 100755 index 00000000..7219a2e2 --- /dev/null +++ b/app/processing/calculation/update-calculation-payment-request-id.js @@ -0,0 +1,24 @@ +const db = require('../../data') +const getCalculationByInvoiceNumber = require('./get-calculation-by-invoice-number') +const { reverseEngineerInvoiceNumber } = require('../../utility') + +const updateCalculationPaymentRequestId = async (invoiceNumber, paymentRequestId, transaction) => { + const reversedInvoiceNumber = reverseEngineerInvoiceNumber(invoiceNumber) + const calculation = await getCalculationByInvoiceNumber(reversedInvoiceNumber, transaction) + + if (calculation) { + await db.calculation.update({ paymentRequestId }, { + transaction, + lock: true, + where: { + invoiceNumber: reversedInvoiceNumber + } + }) + + calculation.paymentRequestId = paymentRequestId + } + + return calculation +} + +module.exports = updateCalculationPaymentRequestId diff --git a/app/processing/funding/fundings-schema.js b/app/processing/funding/fundings-schema.js index 5b3327c7..6f23c2d0 100755 --- a/app/processing/funding/fundings-schema.js +++ b/app/processing/funding/fundings-schema.js @@ -16,6 +16,9 @@ module.exports = Joi.array().items( otherwise: Joi.number().required() } ), + fundingCode: Joi.number().required(), + level: Joi.string().allow('').allow(null).default(''), + name: Joi.string().required(), rate: Joi.when( 'name', { is: 'Moorland', @@ -28,8 +31,6 @@ module.exports = Joi.array().items( ), otherwise: Joi.number().required() } - ), - name: Joi.string().required(), - level: Joi.string().allow('').allow(null).default('') + ) }) ).min(1).required() diff --git a/app/processing/funding/get-fundings-by-calculation-id.js b/app/processing/funding/get-fundings-by-calculation-id.js index 7f196f1c..3f558062 100755 --- a/app/processing/funding/get-fundings-by-calculation-id.js +++ b/app/processing/funding/get-fundings-by-calculation-id.js @@ -1,10 +1,12 @@ const db = require('../../data') -const getFundingsByCalculationId = async (calculationId) => { +const getFundingsByCalculationId = async (calculationId, transaction) => { return db.funding.findAll({ + transaction, attributes: [ 'calculationId', 'areaClaimed', + 'fundingCode', 'rate' ], include: [{ diff --git a/app/processing/funding/get-fundings.js b/app/processing/funding/get-fundings.js index 66aa3d69..cba20ed6 100755 --- a/app/processing/funding/get-fundings.js +++ b/app/processing/funding/get-fundings.js @@ -3,8 +3,8 @@ const mapFundings = require('./map-fundings') const getFundingsByCalculationId = require('./get-fundings-by-calculation-id') -const getFundings = async (calculationId) => { - const rawFundings = await getFundingsByCalculationId(calculationId) +const getFundings = async (calculationId, transaction) => { + const rawFundings = await getFundingsByCalculationId(calculationId, transaction) const fundings = await mapFundings(rawFundings) const result = schema.validate(fundings, { diff --git a/app/processing/funding/index.js b/app/processing/funding/index.js new file mode 100755 index 00000000..d0f66aa4 --- /dev/null +++ b/app/processing/funding/index.js @@ -0,0 +1,3 @@ +const getFundings = require('./get-fundings') + +module.exports = getFundings diff --git a/app/processing/funding/map-fundings.js b/app/processing/funding/map-fundings.js index 475c86fe..32355c6a 100755 --- a/app/processing/funding/map-fundings.js +++ b/app/processing/funding/map-fundings.js @@ -1,9 +1,10 @@ const mapFundings = async (rawFundings) => { const fundings = rawFundings.map(rawFunding => ({ area: rawFunding.areaClaimed, - rate: rawFunding.rate, + fundingCode: rawFunding.fundingCode, + level: getLevel(rawFunding.fundingOptions.name), name: getName(rawFunding.fundingOptions.name), - level: getLevel(rawFunding.fundingOptions.name) + rate: rawFunding.rate })) return fundings diff --git a/app/processing/index.js b/app/processing/index.js index 123f8dd0..9f510825 100644 --- a/app/processing/index.js +++ b/app/processing/index.js @@ -1,9 +1,19 @@ const { processingConfig } = require('../config') + const schedulePendingSettlements = require('./schedule') +const { getStatement, sendStatement } = require('./statement') const start = async () => { try { - await schedulePendingSettlements() + const pendingStatements = await schedulePendingSettlements() + if (!pendingStatements) { + throw new Error('No statements to be generated') + } + + for (const pendingStatement of pendingStatements) { + const aggregatedStatement = await getStatement(pendingStatement.settlementId) + await sendStatement(pendingStatement.scheduleId, aggregatedStatement) + } } catch (err) { console.error(err) } finally { diff --git a/app/processing/invoice-line/get-positive-invoice-line-by-funding-code-and-payment-request-id.js b/app/processing/invoice-line/get-positive-invoice-line-by-funding-code-and-payment-request-id.js new file mode 100755 index 00000000..ff3445bd --- /dev/null +++ b/app/processing/invoice-line/get-positive-invoice-line-by-funding-code-and-payment-request-id.js @@ -0,0 +1,18 @@ +const db = require('../../data') + +const getPositiveInvoiceLineByFundingCodeAndPaymentRequestId = async (fundingCode, paymentRequestId, transaction) => { + return db.invoiceLine.findOne({ + transaction, + attributes: [ + 'value' + ], + where: { + paymentRequestId, + fundingCode, + value: { [db.Sequelize.Op.gte]: 0 } + }, + raw: true + }) +} + +module.exports = getPositiveInvoiceLineByFundingCodeAndPaymentRequestId diff --git a/app/processing/invoice-line/get-positive-invoice-line.js b/app/processing/invoice-line/get-positive-invoice-line.js new file mode 100755 index 00000000..87e1d310 --- /dev/null +++ b/app/processing/invoice-line/get-positive-invoice-line.js @@ -0,0 +1,34 @@ +const schema = require('./invoice-line-schema') + +const getPositiveInvoiceLineByFundingCodeAndPaymentRequestId = require('./get-positive-invoice-line-by-funding-code-and-payment-request-id') + +const QUARTER = 0.25 +const MIN_PAYMENT_VALUE = 0 +const DEFAULT_REDUCTION_VALUE = 0 + +const getPositiveInvoiceLine = async (fundingCode, paymentRequestId) => { + const invoiceLine = await getPositiveInvoiceLineByFundingCodeAndPaymentRequestId(fundingCode, paymentRequestId) + const result = schema.validate(invoiceLine, { + abortEarly: false + }) + + if (result.error) { + throw new Error(`Payment request with paymentRequestId: ${paymentRequestId} does not have the required invoice-line data for funding code ${fundingCode} : ${result.error.message}`) + } + + const annualValue = invoiceLine.value + const quarterlyValue = annualValue > MIN_PAYMENT_VALUE ? Math.trunc(annualValue * QUARTER) : MIN_PAYMENT_VALUE + const quarterlyReduction = DEFAULT_REDUCTION_VALUE + const quarterlyPayment = quarterlyValue - quarterlyReduction + const reductions = [] + + return { + annualValue, + quarterlyValue, + quarterlyReduction, + quarterlyPayment, + reductions + } +} + +module.exports = getPositiveInvoiceLine diff --git a/app/processing/invoice-line/index.js b/app/processing/invoice-line/index.js new file mode 100755 index 00000000..2975431f --- /dev/null +++ b/app/processing/invoice-line/index.js @@ -0,0 +1,3 @@ +const getPositiveInvoiceLine = require('./get-positive-invoice-line') + +module.exports = getPositiveInvoiceLine diff --git a/app/processing/invoice-line/invoice-line-schema.js b/app/processing/invoice-line/invoice-line-schema.js new file mode 100755 index 00000000..ecbdaf0c --- /dev/null +++ b/app/processing/invoice-line/invoice-line-schema.js @@ -0,0 +1,5 @@ +const Joi = require('joi') + +module.exports = Joi.object({ + value: Joi.number().integer().min(0).required() +}).required() diff --git a/app/processing/organisation/get-organisation-by-sbi.js b/app/processing/organisation/get-organisation-by-sbi.js new file mode 100755 index 00000000..bbfb4f5b --- /dev/null +++ b/app/processing/organisation/get-organisation-by-sbi.js @@ -0,0 +1,25 @@ +const db = require('../../data') + +const getOrganisationBySbi = async (sbi, transaction) => { + return db.organisation.findOne({ + transaction, + attributes: [ + 'sbi', + 'addressLine1', + 'addressLine2', + 'addressLine3', + 'city', + 'county', + 'emailAddress', + 'frn', + 'name', + 'postcode' + ], + where: { + sbi + }, + raw: true + }) +} + +module.exports = getOrganisationBySbi diff --git a/app/processing/organisation/get-organisation.js b/app/processing/organisation/get-organisation.js new file mode 100755 index 00000000..8b70a7a4 --- /dev/null +++ b/app/processing/organisation/get-organisation.js @@ -0,0 +1,29 @@ +const schema = require('./organisation-schema') +const getOrganisationBySbi = require('./get-organisation-by-sbi') + +const getOrganisation = async (sbi, transaction) => { + const organisation = await getOrganisationBySbi(sbi, transaction) + + const result = schema.validate(organisation, { + abortEarly: false + }) + + if (result.error) { + throw new Error(`Organisation with the sbi: ${sbi} does not have the required details data: ${result.error.message}`) + } + + return { + line1: organisation.addressLine1, + line2: organisation.addressLine2, + line3: organisation.addressLine3, + line4: organisation.city, + line5: organisation.county, + postcode: organisation.postcode, + businessName: organisation.name, + email: organisation.emailAddress, + frn: organisation.frn, + sbi: organisation.sbi + } +} + +module.exports = getOrganisation diff --git a/app/processing/organisation/index.js b/app/processing/organisation/index.js new file mode 100755 index 00000000..b65e293c --- /dev/null +++ b/app/processing/organisation/index.js @@ -0,0 +1,3 @@ +const getOrganisation = require('./get-organisation') + +module.exports = getOrganisation diff --git a/app/processing/organisation/organisation-schema.js b/app/processing/organisation/organisation-schema.js new file mode 100755 index 00000000..8e751c48 --- /dev/null +++ b/app/processing/organisation/organisation-schema.js @@ -0,0 +1,14 @@ +const Joi = require('joi') + +module.exports = Joi.object({ + sbi: Joi.number().integer().min(105000000).max(999999999).required(), + addressLine1: Joi.string().optional(), + addressLine2: Joi.string().optional(), + addressLine3: Joi.string().optional(), + city: Joi.string().optional(), + county: Joi.string().optional(), + emailAddress: Joi.string().email().required(), + frn: Joi.number().integer().min(1000000000).max(9999999999).required(), + name: Joi.string().required(), + postcode: Joi.string().required() +}).required() diff --git a/app/processing/payment-request/get-completed-payment-request-by-payment-request-id.js b/app/processing/payment-request/get-completed-payment-request-by-payment-request-id.js index 2b4947c4..f3d9034b 100644 --- a/app/processing/payment-request/get-completed-payment-request-by-payment-request-id.js +++ b/app/processing/payment-request/get-completed-payment-request-by-payment-request-id.js @@ -6,9 +6,12 @@ const getCompletedPaymentRequestByPaymentRequestId = async (paymentRequestId, tr transaction, attributes: [ 'paymentRequestId', + 'agreementNumber', 'dueDate', + 'invoiceNumber', 'marketingYear', - 'schedule' + 'schedule', + 'value' ], where: { paymentRequestId, diff --git a/app/processing/payment-request/get-payment-request.js b/app/processing/payment-request/get-payment-request.js index 1e650f25..2a5cd2a5 100644 --- a/app/processing/payment-request/get-payment-request.js +++ b/app/processing/payment-request/get-payment-request.js @@ -2,8 +2,8 @@ const getCompletedPaymentRequestByPaymentRequestId = require('./get-completed-pa const validatePaymentRequest = require('./validate-payment-request') const mapPaymentRequest = require('./map-payment-request') -const getPaymentRequest = async (paymentRequestId) => { - const completedPaymentRequest = await getCompletedPaymentRequestByPaymentRequestId(paymentRequestId) +const getPaymentRequest = async (paymentRequestId, transaction) => { + const completedPaymentRequest = await getCompletedPaymentRequestByPaymentRequestId(paymentRequestId, transaction) return mapPaymentRequest(validatePaymentRequest(completedPaymentRequest)) } diff --git a/app/processing/payment-request/map-payment-request.js b/app/processing/payment-request/map-payment-request.js index 48bc1fdd..5c472b92 100644 --- a/app/processing/payment-request/map-payment-request.js +++ b/app/processing/payment-request/map-payment-request.js @@ -6,8 +6,11 @@ const mapPaymentRequest = async (paymentRequest) => { return { paymentRequestId: paymentRequest.paymentRequestId, + agreementNumber: paymentRequest.agreementNumber, dueDate: paymentRequest.dueDate, frequency, + invoiceNumber: paymentRequest.invoiceNumber, + value: paymentRequest.value, year } } diff --git a/app/processing/payment-request/schema.js b/app/processing/payment-request/schema.js index df06549e..92360960 100644 --- a/app/processing/payment-request/schema.js +++ b/app/processing/payment-request/schema.js @@ -1,10 +1,13 @@ -const Joi = require('joi') +const Joi = require('joi').extend(require('@joi/date')) const { DAX_CODES } = require('../../constants/schedules') module.exports = Joi.object({ paymentRequestId: Joi.number().integer().required(), - dueDate: Joi.date().required(), + agreementNumber: Joi.string().required(), + dueDate: Joi.date().format('D/M/YYYY').required(), + invoiceNumber: Joi.string().required(), marketingYear: Joi.number().integer().required(), - schedule: Joi.string().optional().allow(null).default(DAX_CODES.QUARTERLY) + schedule: Joi.string().optional().allow(null).default(DAX_CODES.QUARTERLY), + value: Joi.number().integer().required() }).required() diff --git a/app/processing/reverse-engineer-invoice-number.js b/app/processing/reverse-engineer-invoice-number.js deleted file mode 100644 index 57d19002..00000000 --- a/app/processing/reverse-engineer-invoice-number.js +++ /dev/null @@ -1,5 +0,0 @@ -const reverseEngineerInvoiceNumber = (invoiceNumber) => { - return `original${invoiceNumber.slice(0, 5)}` -} - -module.exports = reverseEngineerInvoiceNumber diff --git a/app/processing/settlement/get-completed-payment-request-by-invoice-number.js b/app/processing/settlement/get-completed-payment-request-by-invoice-number.js new file mode 100755 index 00000000..f0bc9c28 --- /dev/null +++ b/app/processing/settlement/get-completed-payment-request-by-invoice-number.js @@ -0,0 +1,15 @@ +const db = require('../../data') + +const { COMPLETED } = require('../../constants/statuses') + +const getCompletedPaymentRequestByInvoiceNumber = async (invoiceNumber, transaction) => { + return db.paymentRequest.findOne({ + transaction, + where: { + invoiceNumber, + status: COMPLETED + } + }) +} + +module.exports = getCompletedPaymentRequestByInvoiceNumber diff --git a/app/processing/settlement/get-settled-settlement-by-settlement-id.js b/app/processing/settlement/get-settled-settlement-by-settlement-id.js index 599f27cc..de5c963c 100644 --- a/app/processing/settlement/get-settled-settlement-by-settlement-id.js +++ b/app/processing/settlement/get-settled-settlement-by-settlement-id.js @@ -5,8 +5,10 @@ const getSettledSettlementBySettlementId = async (settlementId, transaction) => transaction, attributes: [ 'paymentRequestId', + 'invoiceNumber', 'reference', - 'settled' + 'settled', + 'settlementDate' ], where: { settlementId, diff --git a/app/processing/settlement/get-settlement.js b/app/processing/settlement/get-settlement.js index 86d758fe..6995ef40 100644 --- a/app/processing/settlement/get-settlement.js +++ b/app/processing/settlement/get-settlement.js @@ -1,9 +1,12 @@ const getSettledSettlementBySettlementId = require('./get-settled-settlement-by-settlement-id') const validateSettlement = require('./validate-settlement') +const updateSettlementPaymentRequestId = require('./update-settlement-payment-request-id') -const getSettlement = async (settlementId) => { - const settledSettlement = await getSettledSettlementBySettlementId(settlementId) - return validateSettlement(settledSettlement) +const getSettlement = async (settlementId, transaction) => { + const settledSettlement = await getSettledSettlementBySettlementId(settlementId, transaction) + const settledSettlementWithPaymentRequestId = settledSettlement.paymentRequestId ? settledSettlement : await updateSettlementPaymentRequestId(settledSettlement) + + return validateSettlement(settledSettlementWithPaymentRequestId) } module.exports = getSettlement diff --git a/app/processing/settlement/index.js b/app/processing/settlement/index.js index c9badb36..c87d522a 100644 --- a/app/processing/settlement/index.js +++ b/app/processing/settlement/index.js @@ -1,3 +1,9 @@ const getSettlement = require('./get-settlement') +const getCompletedPaymentRequestByInvoiceNumber = require('./get-completed-payment-request-by-invoice-number') +const updateSettlementPaymentRequestId = require('./update-settlement-payment-request-id') -module.exports = getSettlement +module.exports = { + getSettlement, + getCompletedPaymentRequestByInvoiceNumber, + updateSettlementPaymentRequestId +} diff --git a/app/processing/settlement/schema.js b/app/processing/settlement/schema.js index 911fe963..853b86f8 100644 --- a/app/processing/settlement/schema.js +++ b/app/processing/settlement/schema.js @@ -2,6 +2,8 @@ const Joi = require('joi') module.exports = Joi.object({ paymentRequestId: Joi.number().integer().required(), + invoiceNumber: Joi.string().required(), reference: Joi.string().required(), - settled: Joi.boolean().required() + settled: Joi.boolean().required(), + settlementDate: Joi.date().required() }).required() diff --git a/app/processing/settlement/update-settlement-payment-request-id.js b/app/processing/settlement/update-settlement-payment-request-id.js new file mode 100755 index 00000000..9272cb58 --- /dev/null +++ b/app/processing/settlement/update-settlement-payment-request-id.js @@ -0,0 +1,23 @@ +const db = require('../../data') +const getCompletedPaymentRequestByInvoiceNumber = require('./get-completed-payment-request-by-invoice-number') + +const updateSettlementPaymentRequestId = async (settlement, transaction) => { + const paymentRequest = await getCompletedPaymentRequestByInvoiceNumber(settlement.invoiceNumber, transaction) + + if (paymentRequest) { + const { paymentRequestId } = paymentRequest + await db.settlement.update({ paymentRequestId }, { + transaction, + lock: true, + where: { + invoiceNumber: settlement.invoiceNumber + } + }) + + settlement.paymentRequestId = paymentRequestId + } + + return settlement +} + +module.exports = updateSettlementPaymentRequestId diff --git a/app/processing/statement/components/get-address.js b/app/processing/statement/components/get-address.js new file mode 100755 index 00000000..c1ddc529 --- /dev/null +++ b/app/processing/statement/components/get-address.js @@ -0,0 +1,15 @@ +const getOrganisation = require('../../organisation') + +const getAddress = async (sbi, transaction) => { + const organisation = await getOrganisation(sbi, transaction) + return { + line1: organisation.line1, + line2: organisation.line2, + line3: organisation.line3, + line4: organisation.line4, + line5: organisation.line5, + postcode: organisation.postcode + } +} + +module.exports = getAddress diff --git a/app/processing/statement/components/get-detailed-funding.js b/app/processing/statement/components/get-detailed-funding.js new file mode 100755 index 00000000..6841d3e1 --- /dev/null +++ b/app/processing/statement/components/get-detailed-funding.js @@ -0,0 +1,48 @@ +const getFundings = require('../../funding') +const getPositiveInvoiceLine = require('../../invoice-line') +const { convertToPounds } = require('../../../utility') + +const getDetailedFunding = async (calculationId, paymentRequestId, transaction) => { + const fundings = await getFundings(calculationId, transaction) + const detailedFundings = [] + + for (const funding of fundings) { + const invoiceLine = await getPositiveInvoiceLine(funding.fundingCode, paymentRequestId, transaction) + const { annualValue, quarterlyValue, quarterlyReduction, quarterlyPayment, reductions } = invoiceLine + + const invoiceLineInPounds = { + annualValue: convertToPounds(annualValue), + quarterlyValue: convertToPounds(quarterlyValue), + quarterlyReduction: convertToPounds(quarterlyReduction), + quarterlyPayment: convertToPounds(quarterlyPayment), + reductions + } + + const detailedFunding = { + area: funding.area, + level: funding.level, + name: funding.name, + rate: funding.rate, + ...invoiceLineInPounds + } + + detailedFundings.push(detailedFunding) + } + + const total = { + area: detailedFundings.reduce((x, y) => x + Number(y.area), 0).toFixed(2), + level: '', + name: 'Total', + rate: '', + annualValue: detailedFundings.reduce((x, y) => x + Number(y.annualValue), 0).toFixed(2), + quarterlyValue: detailedFundings.reduce((x, y) => x + Number(y.quarterlyValue), 0).toFixed(2), + quarterlyReduction: detailedFundings.reduce((x, y) => x + Number(y.quarterlyReduction), 0).toFixed(2), + quarterlyPayment: detailedFundings.reduce((x, y) => x + Number(y.quarterlyPayment), 0).toFixed(2) + } + + detailedFundings.push(total) + + return detailedFundings +} + +module.exports = getDetailedFunding diff --git a/app/processing/statement/components/get-detailed-payments.js b/app/processing/statement/components/get-detailed-payments.js new file mode 100755 index 00000000..1d1eb278 --- /dev/null +++ b/app/processing/statement/components/get-detailed-payments.js @@ -0,0 +1,25 @@ + +const moment = require('moment') +const { convertToPounds } = require('../../../utility') + +const getDetailedPayments = async (calculation, paymentRequest, settlement) => { + const payments = [] + + const payment = { + invoiceNumber: calculation.invoiceNumber, + reference: settlement.reference, + dueDate: formatDate(paymentRequest.dueDate), + settled: formatDate(settlement.settlementDate), + calculated: formatDate(calculation.calculated), + value: convertToPounds(paymentRequest.value) + } + payments.push(payment) + + return payments +} + +const formatDate = (dateVal) => { + return moment(dateVal).format('D MMMM YYYY') +} + +module.exports = getDetailedPayments diff --git a/app/processing/statement/components/get-details.js b/app/processing/statement/components/get-details.js new file mode 100755 index 00000000..6042d2ad --- /dev/null +++ b/app/processing/statement/components/get-details.js @@ -0,0 +1,13 @@ +const getOrganisation = require('../../organisation') + +const getDetails = async (sbi, transaction) => { + const organisation = await getOrganisation(sbi, transaction) + return { + businessName: organisation.businessName, + email: organisation.email, + frn: Number(organisation.frn), + sbi: Number(organisation.sbi) + } +} + +module.exports = getDetails diff --git a/app/processing/statement/components/get-scheme.js b/app/processing/statement/components/get-scheme.js new file mode 100755 index 00000000..3a99878c --- /dev/null +++ b/app/processing/statement/components/get-scheme.js @@ -0,0 +1,11 @@ +const getScheme = async (paymentRequest) => { + return { + name: 'Sustainable Farming Incentive', + shortName: 'SFI', + year: String(paymentRequest.year), + frequency: paymentRequest.frequency, + agreementNumber: paymentRequest.agreementNumber + } +} + +module.exports = getScheme diff --git a/app/processing/statement/components/index.js b/app/processing/statement/components/index.js new file mode 100755 index 00000000..f2980ca3 --- /dev/null +++ b/app/processing/statement/components/index.js @@ -0,0 +1,13 @@ +const getAddress = require('./get-address') +const getDetails = require('./get-details') +const getDetailedFunding = require('./get-detailed-funding') +const getScheme = require('./get-scheme') +const getDetailedPayments = require('./get-detailed-payments') + +module.exports = { + getAddress, + getDetailedFunding, + getDetails, + getDetailedPayments, + getScheme +} diff --git a/app/processing/statement/get-statement.js b/app/processing/statement/get-statement.js new file mode 100755 index 00000000..425ed3c3 --- /dev/null +++ b/app/processing/statement/get-statement.js @@ -0,0 +1,35 @@ +const db = require('../../data') +const { getDetails, getAddress, getDetailedFunding, getScheme, getDetailedPayments } = require('./components') +const getCalculation = require('../calculation') +const getPaymentRequest = require('../payment-request') +const { getSettlement } = require('../settlement') + +const getStatement = async (settlementId) => { + const transaction = await db.sequelize.transaction() + try { + const settlement = await getSettlement(settlementId, transaction) + const paymentRequestId = settlement.paymentRequestId + const paymentRequest = await getPaymentRequest(paymentRequestId, transaction) + const calculation = await getCalculation(paymentRequest, transaction) + const sbi = calculation.sbi + const details = await getDetails(sbi, transaction) + const address = await getAddress(sbi, transaction) + const detailedFunding = await getDetailedFunding(calculation.calculationId, paymentRequestId, transaction) + const scheme = await getScheme(paymentRequest) + const payments = await getDetailedPayments(calculation, paymentRequest, settlement) + + await transaction.commit() + return { + ...details, + address, + funding: detailedFunding, + payments, + scheme + } + } catch (err) { + await transaction.rollback() + throw new Error(`Settlement with settlementId: ${settlementId} does not have the required data: ${err.message}`) + } +} + +module.exports = getStatement diff --git a/app/processing/statement/index.js b/app/processing/statement/index.js old mode 100644 new mode 100755 index 4289e7e5..c4dd290b --- a/app/processing/statement/index.js +++ b/app/processing/statement/index.js @@ -1,3 +1,7 @@ +const getStatement = require('./get-statement') const sendStatement = require('./send-statement') -module.exports = sendStatement +module.exports = { + sendStatement, + getStatement +} diff --git a/app/utility/convert-to-pence.js b/app/utility/convert-to-pence.js new file mode 100755 index 00000000..1dd37614 --- /dev/null +++ b/app/utility/convert-to-pence.js @@ -0,0 +1,8 @@ +const convertToPence = (valueInPounds) => { + const currencyArray = valueInPounds.toString().split('.') + const pounds = currencyArray[0] + const pence = (currencyArray[1] || '00').padEnd(2, '0') + return Number(pounds + pence) +} + +module.exports = convertToPence diff --git a/app/utility/convert-to-pounds.js b/app/utility/convert-to-pounds.js new file mode 100755 index 00000000..3ac85005 --- /dev/null +++ b/app/utility/convert-to-pounds.js @@ -0,0 +1,5 @@ +const convertToPounds = (valueInPence) => { + return (valueInPence / 100).toFixed(2) +} + +module.exports = convertToPounds diff --git a/app/utility/index.js b/app/utility/index.js new file mode 100755 index 00000000..013158fd --- /dev/null +++ b/app/utility/index.js @@ -0,0 +1,9 @@ +const convertToPence = require('./convert-to-pence') +const convertToPounds = require('./convert-to-pounds') +const reverseEngineerInvoiceNumber = require('./reverse-engineer-invoice-number') + +module.exports = { + convertToPence, + convertToPounds, + reverseEngineerInvoiceNumber +} diff --git a/app/utility/reverse-engineer-invoice-number.js b/app/utility/reverse-engineer-invoice-number.js new file mode 100755 index 00000000..a6a771ae --- /dev/null +++ b/app/utility/reverse-engineer-invoice-number.js @@ -0,0 +1,7 @@ +const reverseEngineerInvoiceNumber = (invoiceNumber) => { + const firstPart = 'SFI' + const secondPart = invoiceNumber.slice(1, 8) + return firstPart.concat(secondPart) +} + +module.exports = reverseEngineerInvoiceNumber diff --git a/package-lock.json b/package-lock.json index 8334d9be..0970ba73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { "name": "ffc-pay-statement-constructor", - "version": "0.9.1", + "version": "0.9.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ffc-pay-statement-constructor", - "version": "0.9.1", + "version": "0.9.5", "license": "OGL-UK-3.0", "dependencies": { "@azure/identity": "2.0.5", "@azure/storage-blob": "12.10.0", "@hapi/hapi": "20.2.2", + "@joi/date": "2.1.0", "applicationinsights": "2.3.3", "ffc-messaging": "2.5.1", "joi": "17.6.0", @@ -1627,6 +1628,14 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, + "node_modules/@joi/date": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@joi/date/-/date-2.1.0.tgz", + "integrity": "sha512-2zN5m0LgxZp/cynHGbzEImVmFIa+n+IOb/Nlw5LX/PLJneeCwG1NbiGw7MvPjsAKUGQK8z31Nn6V6lEN+4fZhg==", + "dependencies": { + "moment": "2.x.x" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", @@ -8958,6 +8967,14 @@ "chalk": "^4.0.0" } }, + "@joi/date": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@joi/date/-/date-2.1.0.tgz", + "integrity": "sha512-2zN5m0LgxZp/cynHGbzEImVmFIa+n+IOb/Nlw5LX/PLJneeCwG1NbiGw7MvPjsAKUGQK8z31Nn6V6lEN+4fZhg==", + "requires": { + "moment": "2.x.x" + } + }, "@jridgewell/gen-mapping": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", diff --git a/package.json b/package.json index bc94e308..7624605c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ffc-pay-statement-constructor", - "version": "0.9.4", + "version": "0.9.5", "description": "Data construction for statement generation", "homepage": "https://github.com/DEFRA/ffc-pay-statement-constructor", "main": "app/index.js", @@ -25,6 +25,7 @@ "@azure/identity": "2.0.5", "@azure/storage-blob": "12.10.0", "@hapi/hapi": "20.2.2", + "@joi/date": "2.1.0", "applicationinsights": "2.3.3", "ffc-messaging": "2.5.1", "joi": "17.6.0", diff --git a/test/integration/process-processing-payment-request.test.js b/test/integration/process-processing-payment-request.test.js index 1bbbe89d..f7f237b9 100644 --- a/test/integration/process-processing-payment-request.test.js +++ b/test/integration/process-processing-payment-request.test.js @@ -11,7 +11,7 @@ const { } = require('../../app/constants/scheme-ids') const { IN_PROGRESS } = require('../../app/constants/statuses') -const reverseEngineerInvoiceNumber = require('../../app/processing/reverse-engineer-invoice-number') +const { reverseEngineerInvoiceNumber } = require('../../app/utility') const processProcessingPaymentRequest = require('../../app/inbound/processing') let paymentRequest diff --git a/test/integration/process-submit-payment-request.test.js b/test/integration/process-submit-payment-request.test.js index f15b94c9..2ce6320a 100644 --- a/test/integration/process-submit-payment-request.test.js +++ b/test/integration/process-submit-payment-request.test.js @@ -11,7 +11,7 @@ const { } = require('../../app/constants/scheme-ids') const { COMPLETED } = require('../../app/constants/statuses') -const reverseEngineerInvoiceNumber = require('../../app/processing/reverse-engineer-invoice-number') +const { reverseEngineerInvoiceNumber } = require('../../app/utility') const processSubmitPaymentRequest = require('../../app/inbound/submit') let paymentRequest diff --git a/test/integration/processing/calculation/get-calculation.test.js b/test/integration/processing/calculation/get-calculation.test.js index ac986c5b..d63c808f 100755 --- a/test/integration/processing/calculation/get-calculation.test.js +++ b/test/integration/processing/calculation/get-calculation.test.js @@ -5,6 +5,7 @@ const { SFI_FIRST_PAYMENT: SFI_FIRST_PAYMENT_INVOICE_NUMBER } = require('../../. const getCalculation = require('../../../../app/processing/calculation') let rawCalculationData +let paymentRequest describe('process get calculation object', () => { beforeAll(async () => { @@ -20,11 +21,12 @@ describe('process get calculation object', () => { await db.scheme.bulkCreate(schemes) await db.organisation.create({ sbi: rawCalculationData.sbi }) await db.invoiceNumber.create({ invoiceNumber: SFI_FIRST_PAYMENT_INVOICE_NUMBER }) - await db.paymentRequest.create({ + paymentRequest = { paymentRequestId: rawCalculationData.paymentRequestId, schemeId: 1, invoiceNumber: SFI_FIRST_PAYMENT_INVOICE_NUMBER - }) + } + await db.paymentRequest.create(paymentRequest) }) afterEach(async () => { @@ -39,19 +41,22 @@ describe('process get calculation object', () => { }) test('should throw error when no existing calculation data', async () => { - const wrapper = async () => { await getCalculation(rawCalculationData.paymentRequestId) } + const wrapper = async () => { await getCalculation(paymentRequest) } expect(wrapper).rejects.toThrow() }) - test('should not throw error when there is existing calculation data with sbi and calculationDate', async () => { + test('should not throw error when there is existing calculation data with sbi, calculationId, invoiceNumber, calculationDate and paymentRequestId', async () => { await db.calculation.create(rawCalculationData) - const result = await getCalculation(rawCalculationData.paymentRequestId) + const result = await getCalculation(paymentRequest) expect(result).toStrictEqual({ + calculationId: rawCalculationData.calculationId, sbi: rawCalculationData.sbi, - calculated: new Date(rawCalculationData.calculationDate) + calculated: new Date(rawCalculationData.calculationDate), + invoiceNumber: rawCalculationData.invoiceNumber, + paymentRequestId: rawCalculationData.paymentRequestId }) }) @@ -59,7 +64,7 @@ describe('process get calculation object', () => { rawCalculationData.calculationDate = null await db.calculation.create(rawCalculationData) - const wrapper = async () => { await getCalculation(rawCalculationData.paymentRequestId) } + const wrapper = async () => { await getCalculation(paymentRequest) } expect(wrapper).rejects.toThrow() }) @@ -68,7 +73,7 @@ describe('process get calculation object', () => { rawCalculationData.calculationDate = null await db.calculation.create(rawCalculationData) - const wrapper = async () => { await getCalculation(rawCalculationData.paymentRequestId) } + const wrapper = async () => { await getCalculation(paymentRequest) } expect(wrapper).rejects.toThrow() }) diff --git a/test/integration/processing/invoice-line/get-positive-invoice-line.test.js b/test/integration/processing/invoice-line/get-positive-invoice-line.test.js new file mode 100755 index 00000000..34d3950b --- /dev/null +++ b/test/integration/processing/invoice-line/get-positive-invoice-line.test.js @@ -0,0 +1,92 @@ +const db = require('../../../../app/data') +const schemes = require('../../../../app/constants/schemes') +const { SFI_FIRST_PAYMENT: SFI_FIRST_PAYMENT_INVOICE_NUMBER } = require('../../../mock-components/mock-invoice-number') + +const getPositiveInvoiceLine = require('../../../../app/processing/invoice-line/get-positive-invoice-line') + +const QUARTER = 0.25 +const MIN_PAYMENT_VALUE = 0 +const DEFAULT_REDUCTION_VALUE = 0 + +let rawInvoiceLinesData + +describe('process get invoice line object', () => { + beforeAll(async () => { + await db.sequelize.truncate({ + cascade: true, + restartIdentity: true + }) + }) + + beforeEach(async () => { + rawInvoiceLinesData = JSON.parse(JSON.stringify(require('../../../mock-objects/mock-invoice-line').rawInvoiceLines)) + const rawFundingOption = JSON.parse(JSON.stringify(require('../../../mock-objects/mock-invoice-line').rawFundingOptions)) + await db.scheme.bulkCreate(schemes) + await db.invoiceNumber.create({ invoiceNumber: SFI_FIRST_PAYMENT_INVOICE_NUMBER }) + await db.paymentRequest.bulkCreate( + [ + { + paymentRequestId: 1, + schemeId: 1, + invoiceNumber: SFI_FIRST_PAYMENT_INVOICE_NUMBER + }, + { + paymentRequestId: 2, + schemeId: 1, + invoiceNumber: SFI_FIRST_PAYMENT_INVOICE_NUMBER + } + ] + + ) + await db.fundingOption.bulkCreate(rawFundingOption) + }) + + afterEach(async () => { + await db.sequelize.truncate({ + cascade: true, + restartIdentity: true + }) + }) + + afterAll(async () => { + await db.sequelize.close() + }) + + test('should throw error when no existing invoice-line data in database', async () => { + const fundingOptionCode = '300001' + const paymentRequestId = 20 + const wrapper = async () => { await getPositiveInvoiceLine(fundingOptionCode, paymentRequestId) } + + expect(wrapper).rejects.toThrow() + }) + + test('should throw error when no existing invoice-line data in database that corresponds to the fundingCode and a paymentRequestId given', async () => { + await db.invoiceLine.bulkCreate(rawInvoiceLinesData) + const fundingOptionCode = '300001' + const paymentRequestId = 20 + const wrapper = async () => { await getPositiveInvoiceLine(fundingOptionCode, paymentRequestId) } + + expect(wrapper).rejects.toThrow() + }) + + test('should not throw error when there is existing imvoiceline data that corresponds to the fundingCode and a paymentRequestId given', async () => { + await db.invoiceLine.bulkCreate(rawInvoiceLinesData) + const fundingOptionCode = rawInvoiceLinesData[0].fundingCode + const paymentRequestId = rawInvoiceLinesData[0].paymentRequestId + const result = await getPositiveInvoiceLine(fundingOptionCode, paymentRequestId) + + const annualValue = rawInvoiceLinesData[0].value + const quarterlyValue = annualValue > MIN_PAYMENT_VALUE ? Math.trunc(annualValue * QUARTER) : MIN_PAYMENT_VALUE + const quarterlyReduction = DEFAULT_REDUCTION_VALUE + const quarterlyPayment = quarterlyValue - quarterlyReduction + const reductions = [] + + expect(result).toStrictEqual({ + annualValue, + quarterlyValue, + quarterlyReduction, + quarterlyPayment, + reductions + }) + }) +}) diff --git a/test/integration/processing/organisation/get-organisation-test.js b/test/integration/processing/organisation/get-organisation-test.js new file mode 100755 index 00000000..6ab544a6 --- /dev/null +++ b/test/integration/processing/organisation/get-organisation-test.js @@ -0,0 +1,130 @@ +const mockCommit = jest.fn() +const mockRollback = jest.fn() +const mockTransaction = { + commit: mockCommit, + rollback: mockRollback +} + +jest.mock('../../../../app/data', () => { + return { + sequelize: + { + transaction: jest.fn().mockImplementation(() => { + return { ...mockTransaction } + }) + } + } +}) + +const db = require('../../../../app/data') +const getOrganisation = require('../../../../app/processing/organisation') + +let organisationData +let retrievedOrganisationData +let sbi + +describe('process get calculation object', () => { + beforeAll(async () => { + await db.sequelize.truncate({ + cascade: true, + restartIdentity: true + }) + }) + + beforeEach(async () => { + organisationData = JSON.parse(JSON.stringify(require('../../../mock-organisation'))) + retrievedOrganisationData = { + addressLine1: organisationData.line1, + addressLine2: organisationData.line2, + addressLine3: organisationData.line3, + city: organisationData.line4, + county: organisationData.line5, + postcode: organisationData.postcode, + name: organisationData.businessName, + emailAddress: organisationData.email, + frn: organisationData.frn, + sbi: organisationData.sbi + } + sbi = organisationData.sbi + }) + + afterEach(async () => { + await db.sequelize.truncate({ + cascade: true, + restartIdentity: true + }) + }) + + afterAll(async () => { + await db.sequelize.close() + }) + + test('should throw error when no existing organisation data', async () => { + const wrapper = async () => { await getOrganisation(sbi, mockTransaction) } + + expect(wrapper).rejects.toThrow() + }) + + test('should not throw error when there is existing organisation data with sbi, and other valid data', async () => { + await db.organisation.create(retrievedOrganisationData) + + const result = await getOrganisation(sbi, mockTransaction) + + expect(result).toStrictEqual({ + line1: retrievedOrganisationData.addressLine1, + line2: retrievedOrganisationData.addressLine2, + line3: retrievedOrganisationData.addressLine3, + line4: retrievedOrganisationData.city, + line5: retrievedOrganisationData.county, + postcode: retrievedOrganisationData.postcode, + businessName: retrievedOrganisationData.name, + email: retrievedOrganisationData.emailAddress, + frn: retrievedOrganisationData.frn, + sbi: retrievedOrganisationData.sbi + }) + }) + + test('should throw error when there is existing organisation data with sbi but no frn', async () => { + retrievedOrganisationData.frn = null + await db.organisation.create(retrievedOrganisationData) + + const wrapper = async () => { await getOrganisation(sbi, mockTransaction) } + + expect(wrapper).rejects.toThrow() + }) + + test('should throw error when there is existing organisation data with sbi but no postcode', async () => { + retrievedOrganisationData.postcode = null + await db.organisation.create(retrievedOrganisationData) + + const wrapper = async () => { await getOrganisation(sbi, mockTransaction) } + + expect(wrapper).rejects.toThrow() + }) + + test('should throw error when there is existing organisation data with sbi but sbi less than 105000000', async () => { + retrievedOrganisationData.sbi = 10500000 + await db.organisation.create(retrievedOrganisationData) + + const wrapper = async () => { await getOrganisation(retrievedOrganisationData.sbi, mockTransaction) } + + expect(wrapper).rejects.toThrow() + }) + + test('should throw error when there is existing organisation data with sbi but sbi greater than 999999999', async () => { + retrievedOrganisationData.sbi = 9999999990 + await db.organisation.create(retrievedOrganisationData) + + const wrapper = async () => { await getOrganisation(retrievedOrganisationData.sbi, mockTransaction) } + + expect(wrapper).rejects.toThrow() + }) + + test('should throw error when there is data in organisation table but no corresponding record with provided sbi', async () => { + await db.organisation.create(retrievedOrganisationData) + sbi = 124534678 + const wrapper = async () => { await getOrganisation(sbi, mockTransaction) } + + expect(wrapper).rejects.toThrow() + }) +}) diff --git a/test/integration/processing/payment-request/get-payment-request.test.js b/test/integration/processing/payment-request/get-payment-request.test.js index 8e9381c0..690bf95f 100644 --- a/test/integration/processing/payment-request/get-payment-request.test.js +++ b/test/integration/processing/payment-request/get-payment-request.test.js @@ -1,3 +1,5 @@ +const moment = require('moment') + const db = require('../../../../app/data') const schemes = require('../../../../app/constants/schemes') @@ -50,9 +52,12 @@ describe('process payment request', () => { const result = await getPaymentRequest(PAYMENT_REQUEST_ID_COMPLETED) expect(result).toStrictEqual({ + agreementNumber: paymentRequestCompleted.agreementNumber, paymentRequestId: PAYMENT_REQUEST_ID_COMPLETED, - dueDate: new Date(paymentRequestCompleted.dueDate), + dueDate: new Date(moment(paymentRequestCompleted.dueDate, 'DD/MM/YYYY')), frequency: SCHEDULE_NAMES.Q4, + invoiceNumber: paymentRequestCompleted.invoiceNumber, + value: paymentRequestCompleted.value, year: paymentRequestCompleted.marketingYear }) }) @@ -64,9 +69,12 @@ describe('process payment request', () => { const result = await getPaymentRequest(PAYMENT_REQUEST_ID_COMPLETED) expect(result).toStrictEqual({ + agreementNumber: paymentRequestCompleted.agreementNumber, paymentRequestId: PAYMENT_REQUEST_ID_COMPLETED, - dueDate: new Date(paymentRequestCompleted.dueDate), + dueDate: new Date(moment(paymentRequestCompleted.dueDate, 'DD/MM/YYYY')), frequency: SCHEDULE_NAMES.Q4, + invoiceNumber: paymentRequestCompleted.invoiceNumber, + value: paymentRequestCompleted.value, year: paymentRequestCompleted.marketingYear }) }) @@ -78,9 +86,12 @@ describe('process payment request', () => { const result = await getPaymentRequest(PAYMENT_REQUEST_ID_COMPLETED) expect(result).toStrictEqual({ + agreementNumber: paymentRequestCompleted.agreementNumber, paymentRequestId: PAYMENT_REQUEST_ID_COMPLETED, - dueDate: new Date(paymentRequestCompleted.dueDate), + dueDate: new Date(moment(paymentRequestCompleted.dueDate, 'DD/MM/YYYY')), frequency: SCHEDULE_NAMES.Q4, + invoiceNumber: paymentRequestCompleted.invoiceNumber, + value: paymentRequestCompleted.value, year: paymentRequestCompleted.marketingYear }) }) @@ -92,9 +103,12 @@ describe('process payment request', () => { const result = await getPaymentRequest(PAYMENT_REQUEST_ID_COMPLETED) expect(result).toStrictEqual({ + agreementNumber: paymentRequestCompleted.agreementNumber, paymentRequestId: PAYMENT_REQUEST_ID_COMPLETED, - dueDate: new Date(paymentRequestCompleted.dueDate), + dueDate: new Date(moment(paymentRequestCompleted.dueDate, 'DD/MM/YYYY')), frequency: SCHEDULE_NAMES.Q4, + invoiceNumber: paymentRequestCompleted.invoiceNumber, + value: paymentRequestCompleted.value, year: paymentRequestCompleted.marketingYear }) }) @@ -106,9 +120,12 @@ describe('process payment request', () => { const result = await getPaymentRequest(PAYMENT_REQUEST_ID_COMPLETED) expect(result).toStrictEqual({ + agreementNumber: paymentRequestCompleted.agreementNumber, paymentRequestId: PAYMENT_REQUEST_ID_COMPLETED, - dueDate: new Date(paymentRequestCompleted.dueDate), + dueDate: new Date(moment(paymentRequestCompleted.dueDate, 'DD/MM/YYYY')), frequency: SCHEDULE_NAMES.Q4, + invoiceNumber: paymentRequestCompleted.invoiceNumber, + value: paymentRequestCompleted.value, year: paymentRequestCompleted.marketingYear }) }) diff --git a/test/integration/processing/payment-request/validate-payment-request.test.js b/test/integration/processing/payment-request/validate-payment-request.test.js index 2ac27989..1d9c828c 100644 --- a/test/integration/processing/payment-request/validate-payment-request.test.js +++ b/test/integration/processing/payment-request/validate-payment-request.test.js @@ -9,8 +9,11 @@ describe('validate payment request', () => { const paymentRequest = JSON.parse(JSON.stringify(require('../../../mock-payment-request').processingPaymentRequest)) retreivedPaymentRequest = { paymentRequestId: 1, + agreementNumber: paymentRequest.agreementNumber, dueDate: new Date(paymentRequest.dueDate), + invoiceNumber: paymentRequest.invoiceNumber, marketingYear: paymentRequest.marketingYear, + value: paymentRequest.value, schedule: paymentRequest.schedule } }) diff --git a/test/integration/processing/settlement/get-settlement.test.js b/test/integration/processing/settlement/get-settlement.test.js index 5e6137a2..afb8de3e 100644 --- a/test/integration/processing/settlement/get-settlement.test.js +++ b/test/integration/processing/settlement/get-settlement.test.js @@ -1,6 +1,6 @@ const db = require('../../../../app/data') -const getSettlement = require('../../../../app/processing/settlement') +const { getSettlement } = require('../../../../app/processing/settlement') const schemes = require('../../../../app/constants/schemes') const paymentRequest = JSON.parse(JSON.stringify(require('../../../mock-payment-request').submitPaymentRequest)) @@ -32,9 +32,11 @@ describe('process settlement', () => { await db.settlement.create({ ...settlement, paymentRequestId: 1, settled: false }) mappedSettlement = { + invoiceNumber: settlement.invoiceNumber, paymentRequestId: 1, reference: settlement.reference, - settled: settlement.settled + settled: settlement.settled, + settlementDate: new Date(settlement.settlementDate) } }) diff --git a/test/integration/processing/settlement/validate-settlement.test.js b/test/integration/processing/settlement/validate-settlement.test.js index e143a377..4acae538 100644 --- a/test/integration/processing/settlement/validate-settlement.test.js +++ b/test/integration/processing/settlement/validate-settlement.test.js @@ -6,9 +6,11 @@ describe('validate settlement', () => { beforeEach(() => { const settlement = JSON.parse(JSON.stringify(require('../../../mock-settlement'))) retreivedSettlement = { + invoiceNumber: settlement.invoiceNumber, paymentRequestId: 1, reference: settlement.reference, - settled: settlement.settled + settled: settlement.settled, + settlementDate: new Date(settlement.settlementDate) } }) diff --git a/test/integration/processing/statement/send-statement.test.js b/test/integration/processing/statement/send-statement.test.js index 3b943b46..b0752681 100644 --- a/test/integration/processing/statement/send-statement.test.js +++ b/test/integration/processing/statement/send-statement.test.js @@ -1,6 +1,6 @@ const db = require('../../../../app/data') -const sendStatement = require('../../../../app/processing/statement') +const { sendStatement } = require('../../../../app/processing/statement') jest.mock('ffc-messaging', () => { return { diff --git a/test/mock-components/mock-invoice-number.js b/test/mock-components/mock-invoice-number.js index 86fb8fa0..a9f33f09 100644 --- a/test/mock-components/mock-invoice-number.js +++ b/test/mock-components/mock-invoice-number.js @@ -2,5 +2,5 @@ const SFI_FIRST_PAYMENT = 'S0000001SFIP000001V001' module.exports = { SFI_FIRST_PAYMENT, - SFI_FIRST_PAYMENT_ORIGINAL: SFI_FIRST_PAYMENT.slice(8, 18) + SFI_FIRST_PAYMENT_ORIGINAL: 'SFI0000001' } diff --git a/test/mock-objects/mock-calculation.js b/test/mock-objects/mock-calculation.js index 23e35b3e..99994267 100755 --- a/test/mock-objects/mock-calculation.js +++ b/test/mock-objects/mock-calculation.js @@ -2,7 +2,8 @@ const rawCalculationData = { calculationId: 1, paymentRequestId: 1, sbi: 657536, - calculationDate: '01/12/2022' + calculationDate: '01/12/2022', + invoiceNumber: 'S0000001C000001V001' } module.exports = { diff --git a/test/mock-objects/mock-invoice-line.js b/test/mock-objects/mock-invoice-line.js new file mode 100755 index 00000000..99b283ef --- /dev/null +++ b/test/mock-objects/mock-invoice-line.js @@ -0,0 +1,58 @@ +const fundingCode = require('../../app/constants/funding-codes') +const FundingCodeName = require('../../app/constants/funding-code-names') + +const mappedFundingsData = [ + { + area: 5, + level: 'braid', + name: 'local', + rate: 28.55 + }, + { + area: 6, + level: '', + name: 'my test now', + rate: 38.55 + }, + { + area: 9, + level: '', + name: 'last row', + rate: 48.55 + } +] + +const rawFundingOptions = [ + { + fundingCode: fundingCode.ARABLE_SOIL_INTRODUCTORY, + name: FundingCodeName.ARABLE_SOIL_INTRODUCTORY + }, + { + fundingCode: fundingCode.ARABLE_SOIL_INTERMEDIATE, + name: FundingCodeName.ARABLE_SOIL_INTERMEDIATE + } +] + +const rawInvoiceLines = [ + { + fundingCode: fundingCode.ARABLE_SOIL_INTRODUCTORY, + paymentRequestId: 1, + value: 500 + }, + { + fundingCode: fundingCode.ARABLE_SOIL_INTERMEDIATE, + paymentRequestId: 2, + value: 600 + }, + { + fundingCode: fundingCode.ARABLE_SOIL_INTERMEDIATE, + paymentRequestId: 1, + value: 800 + } +] + +module.exports = { + rawFundingOptions, + mappedFundingsData, + rawInvoiceLines +} diff --git a/test/mock-objects/mock-organisation.js b/test/mock-objects/mock-organisation.js new file mode 100755 index 00000000..0b2d972f --- /dev/null +++ b/test/mock-objects/mock-organisation.js @@ -0,0 +1,16 @@ +const rawOrganisationData = { + line1: '25', + line2: 'Orlando Terrain', + line3: 'Ring Road', + line4: 'Bristol', + line5: 'Avon', + postcode: 'BS30 7JP', + businessName: 'Milk and Chicken Limited', + email: 'farmer@milkchicken.co.uk', + frn: '12543687956', + sbi: '25412452121' +} + +module.exports = { + rawOrganisationData +} diff --git a/test/mock-objects/mock-statement.js b/test/mock-objects/mock-statement.js index 79e5b930..ca518564 100644 --- a/test/mock-objects/mock-statement.js +++ b/test/mock-objects/mock-statement.js @@ -2,6 +2,10 @@ const { Q4: FREQUENCY_QUARTERLY } = require('../../app/constants/schedules').NAM const { SFI: SFI_SHORT_SCHEME_NAME } = require('../../app/constants/scheme-names').SHORT_NAMES const { SFI: SFI_LONG_SCHEME_NAME } = require('../../app/constants/scheme-names').LONG_NAMES +const BUSINESS_NAME = require('../mock-components/mock-organisation-name') +const EMAIL_ADDRESS = require('../mock-components/mock-email-address') +const FRN = require('../mock-components/mock-frn') +const SBI = require('../mock-components/mock-sbi') const { LINE_1, LINE_2, @@ -10,18 +14,18 @@ const { COUNTY, POSTCODE } = require('../mock-components/mock-address') -const BUSINESS_NAME = require('../mock-components/mock-organisation-name') -const EMAIL_ADDRESS = require('../mock-components/mock-email-address') -const FRN = require('../mock-components/mock-frn') const { DAY_FORMAT: CALCULATED_DATE } = require('../mock-components/mock-dates').CALCULATED const { DAY_FORMAT: DUE_DATE } = require('../mock-components/mock-dates').DUE const { SFI_FIRST_PAYMENT: INVOICE_NUMBER } = require('../mock-components/mock-invoice-number') const CALCULATION_REFERENCE = require('../mock-components/mock-calculation-reference') const { DAY_FORMAT: SETTLED_DATE } = require('../mock-components/mock-dates').SETTLEMENT -const SBI = require('../mock-components/mock-sbi') const _2022 = require('../mock-components/mock-marketing-year') module.exports = { + businessName: BUSINESS_NAME, + email: EMAIL_ADDRESS, + frn: FRN.toString(), + sbi: SBI.toString(), address: { line1: LINE_1, line2: LINE_2, @@ -30,9 +34,6 @@ module.exports = { line5: COUNTY, postcode: POSTCODE }, - businessName: BUSINESS_NAME, - email: EMAIL_ADDRESS, - frn: FRN.toString(), funding: [ { annualValue: '110.00', @@ -117,7 +118,6 @@ module.exports = { value: '242.15' } ], - sbi: SBI.toString(), scheme: { frequency: FREQUENCY_QUARTERLY, name: SFI_LONG_SCHEME_NAME, diff --git a/test/unit/processing/calculation/get-calculation.test.js b/test/unit/processing/calculation/get-calculation.test.js index c61038bf..27ae2ade 100755 --- a/test/unit/processing/calculation/get-calculation.test.js +++ b/test/unit/processing/calculation/get-calculation.test.js @@ -1,3 +1,21 @@ +const mockCommit = jest.fn() +const mockRollback = jest.fn() +const mockTransaction = { + commit: mockCommit, + rollback: mockRollback +} + +jest.mock('../../../../app/data', () => { + return { + sequelize: + { + transaction: jest.fn().mockImplementation(() => { + return { ...mockTransaction } + }) + } + } +}) + jest.mock('../../../../app/processing/calculation/calculation-schema') const schema = require('../../../../app/processing/calculation/calculation-schema') @@ -16,7 +34,9 @@ describe('get and transform payment request information for building a statement rawCalculationData = retrievedCalculationData calculation = { sbi: rawCalculationData.sbi, - calculated: new Date(rawCalculationData.calculationDate) + calculated: new Date(rawCalculationData.calculationDate), + invoiceNumber: rawCalculationData.invoiceNumber, + paymentRequestId: rawCalculationData.paymentRequestId } schema.validate.mockReturnValue({ value: calculation }) @@ -27,114 +47,114 @@ describe('get and transform payment request information for building a statement jest.clearAllMocks() }) - test('should call getCalculationByPaymentRequestId when a paymentRequestId is given', async () => { - const paymentRequestId = 1 - await getCalculation(paymentRequestId) + test('should call getCalculationByPaymentRequestId when a paymentRequest is given', async () => { + const paymentRequest = { paymentRequestId: 1 } + await getCalculation(paymentRequest, mockTransaction) expect(getCalculationByPaymentRequestId).toHaveBeenCalled() }) test('should call getCompletedPaymentRequestByPaymentRequestId once when a paymentRequestId is given', async () => { - const paymentRequestId = 1 - await getCalculation(paymentRequestId) + const paymentRequest = { paymentRequestId: 1 } + await getCalculation(paymentRequest) expect(getCalculationByPaymentRequestId).toHaveBeenCalledTimes(1) }) - test('should call getCalculationByPaymentRequestId with paymentRequestId when a paymentRequestId is given', async () => { - const paymentRequestId = 1 - await getCalculation(paymentRequestId) - expect(getCalculationByPaymentRequestId).toHaveBeenCalledWith(paymentRequestId) + test('should call getCalculationByPaymentRequestId with paymentRequestId when a paymentRequest is given', async () => { + const paymentRequest = { paymentRequestId: 1 } + await getCalculation(paymentRequest, mockTransaction) + expect(getCalculationByPaymentRequestId).toHaveBeenCalledWith(paymentRequest.paymentRequestId, mockTransaction) }) - test('should call schema.validate when a paymentRequestId is given', async () => { - const paymentRequestId = 1 - await getCalculation(paymentRequestId) + test('should call schema.validate when a paymentRequest is given', async () => { + const paymentRequest = { paymentRequestId: 1 } + await getCalculation(paymentRequest) expect(schema.validate).toHaveBeenCalled() }) - test('should call schema.validate once when a paymentRequestId is given', async () => { - const paymentRequestId = 1 - await getCalculation(paymentRequestId) + test('should call schema.validate once when a paymentRequest is given', async () => { + const paymentRequest = { paymentRequestId: 1 } + await getCalculation(paymentRequest) expect(schema.validate).toHaveBeenCalledTimes(1) }) - test('should call schema.validate with rawCalculationData and { abortEarly: false } when a paymentRequestId is given', async () => { - const paymentRequestId = 1 - await getCalculation(paymentRequestId) + test('should call schema.validate with rawCalculationData and { abortEarly: false } when a paymentRequest is given', async () => { + const paymentRequest = { paymentRequestId: 1 } + await getCalculation(paymentRequest) expect(schema.validate).toHaveBeenCalledWith(rawCalculationData, { abortEarly: false }) }) test('should throw when getCalculationByPaymentRequestId throws', async () => { - const paymentRequestId = 1 + const paymentRequest = { paymentRequestId: 1 } getCalculationByPaymentRequestId.mockRejectedValue(new Error('Database retrieval issue')) const wrapper = async () => { - await getCalculation(paymentRequestId) + await getCalculation(paymentRequest) } expect(wrapper).rejects.toThrow() }) test('should throw Error when getCalculationByPaymentRequestId throws Error', async () => { - const paymentRequestId = 1 + const paymentRequest = { paymentRequestId: 1 } getCalculationByPaymentRequestId.mockRejectedValue(new Error('Database retrieval issue')) const wrapper = async () => { - await getCalculation(paymentRequestId) + await getCalculation(paymentRequest) } expect(wrapper).rejects.toThrow(Error) }) test('should throw error with "Database retrieval issue" when getCalculationByPaymentRequestId throws error with "Database retrieval issue"', async () => { - const paymentRequestId = 1 + const paymentRequest = { paymentRequestId: 1 } getCalculationByPaymentRequestId.mockRejectedValue(new Error('Database retrieval issue')) const wrapper = async () => { - await getCalculation(paymentRequestId) + await getCalculation(paymentRequest) } expect(wrapper).rejects.toThrow(/^Database retrieval issue$/) }) test('should not call schema.validate when getCalculationByPaymentRequestId throws', async () => { - const paymentRequestId = 1 + const paymentRequest = { paymentRequestId: 1 } getCalculationByPaymentRequestId.mockRejectedValue(new Error('Database retrieval issue')) - try { await getCalculation(paymentRequestId) } catch {} + try { await getCalculation(paymentRequest) } catch {} expect(schema.validate).not.toHaveBeenCalled() }) test('should throw when schema.validate returns with error key', async () => { - const paymentRequestId = 1 + const paymentRequest = { paymentRequestId: 1 } schema.validate.mockReturnValue({ error: 'Not a valid object' }) const wrapper = async () => { - await getCalculation(paymentRequestId) + await getCalculation(paymentRequest) } expect(wrapper).rejects.toThrow() }) test('should throw Error when schema.validate returns with error key', async () => { - const paymentRequestId = 1 + const paymentRequest = { paymentRequestId: 1 } schema.validate.mockReturnValue({ error: 'Not a valid object' }) const wrapper = async () => { - await getCalculation(paymentRequestId) + await getCalculation(paymentRequest) } expect(wrapper).rejects.toThrow(Error) }) - test('should throw error which starts "Payment request with paymentRequestId: 1 does not have the required Calculation data" when schema.validate returns with error key of "Joi validation issue"', async () => { - const paymentRequestId = 1 + test('should throw error which starts "Payment request with paymentRequestId:" when schema.validate returns with error key of "Joi validation issue"', async () => { + const paymentRequest = { paymentRequestId: 1 } schema.validate.mockReturnValue({ error: 'Not a valid object' }) const wrapper = async () => { - await getCalculation(paymentRequestId) + await getCalculation(paymentRequest) } - expect(wrapper).rejects.toThrow(/^Payment request with paymentRequestId: 1 does not have the required Calculation data/) + expect(wrapper).rejects.toThrow(/^Payment request with paymentRequestId:/) }) }) diff --git a/test/unit/processing/funding/get-fundings.test.js b/test/unit/processing/funding/get-fundings.test.js index 9efd0878..e5f50889 100755 --- a/test/unit/processing/funding/get-fundings.test.js +++ b/test/unit/processing/funding/get-fundings.test.js @@ -1,4 +1,22 @@ +const mockCommit = jest.fn() +const mockRollback = jest.fn() +const mockTransaction = { + commit: mockCommit, + rollback: mockRollback +} + +jest.mock('../../../../app/data', () => { + return { + sequelize: + { + transaction: jest.fn().mockImplementation(() => { + return { ...mockTransaction } + }) + } + } +}) + jest.mock('../../../../app/processing/funding/get-fundings-by-calculation-id') const getFundingsByCalculationId = require('../../../../app/processing/funding/get-fundings-by-calculation-id') @@ -47,8 +65,8 @@ describe('get and transform fundings object for building a statement object', () test('should call getFundingsByCalculationId with calculationId when a calculationId is given', async () => { const calculationId = 1 - await getFundings(calculationId) - expect(getFundingsByCalculationId).toHaveBeenCalledWith(calculationId) + await getFundings(calculationId, mockTransaction) + expect(getFundingsByCalculationId).toHaveBeenCalledWith(calculationId, mockTransaction) }) test('should call schema.validate when a calculationId is given', async () => { diff --git a/test/unit/processing/index.test.js b/test/unit/processing/index.test.js index d22fb709..e33a03bb 100644 --- a/test/unit/processing/index.test.js +++ b/test/unit/processing/index.test.js @@ -4,15 +4,48 @@ jest.spyOn(global, 'setTimeout') jest.mock('../../../app/config') const { processingConfig } = require('../../../app/config') -jest.mock('../../../app/processing/schedule/schedule-pending-settlements') -const schedulePendingSettlements = require('../../../app/processing/schedule/schedule-pending-settlements') +const mockCommit = jest.fn() +const mockRollback = jest.fn() +const mockTransaction = { + commit: mockCommit, + rollback: mockRollback +} +jest.mock('../../../app/data', () => { + return { + sequelize: + { + transaction: jest.fn().mockImplementation(() => { + return { ...mockTransaction } + }) + } + } +}) + +jest.mock('../../../app/processing/schedule') +const schedulePendingSettlements = require('../../../app/processing/schedule') + +jest.mock('../../../app/processing/statement') +const { getStatement, sendStatement } = require('../../../app/processing/statement') const processing = require('../../../app/processing') +let retreivedSchedule +let statement + describe('start processing', () => { beforeEach(() => { processingConfig.settlementProcessingInterval = 10000 - schedulePendingSettlements.mockResolvedValue(undefined) + + const schedule = JSON.parse(JSON.stringify(require('../../mock-schedule'))) + retreivedSchedule = { + scheduleId: 1, + settlementId: schedule.settlementId + } + statement = JSON.parse(JSON.stringify(require('../../mock-objects/mock-statement'))) + + schedulePendingSettlements.mockResolvedValue([retreivedSchedule]) + getStatement.mockResolvedValue(statement) + sendStatement.mockResolvedValue(undefined) }) afterEach(() => { @@ -29,19 +62,88 @@ describe('start processing', () => { expect(schedulePendingSettlements).toHaveBeenCalledTimes(1) }) - test('should call setTimeout', async () => { + test('should call getStatement when schedulePendingSettlements returns 1 record', async () => { await processing.start() - expect(setTimeout).toHaveBeenCalled() + expect(getStatement).toHaveBeenCalled() }) - test('should call setTimeout once', async () => { + test('should call getStatement once when schedulePendingSettlements returns 1 record', async () => { await processing.start() - expect(setTimeout).toHaveBeenCalledTimes(1) + expect(getStatement).toHaveBeenCalledTimes(1) }) - test('should call setTimeout with processing.start and processingConfig.settlementProcessingInterval', async () => { + test('should call getStatement with schedulePendingSettlements()[0].settlementId and mockTransaction when schedulePendingSettlements returns 1 record', async () => { await processing.start() - expect(setTimeout).toHaveBeenCalledWith(processing.start, processingConfig.settlementProcessingInterval) + expect(getStatement).toHaveBeenCalledWith((await schedulePendingSettlements())[0].settlementId) + }) + + test('should call getStatement when schedulePendingSettlements returns 2 records', async () => { + schedulePendingSettlements.mockResolvedValue([retreivedSchedule, retreivedSchedule]) + await processing.start() + expect(getStatement).toHaveBeenCalled() + }) + + test('should call getStatement twice when schedulePendingSettlements returns 2 records', async () => { + schedulePendingSettlements.mockResolvedValue([retreivedSchedule, retreivedSchedule]) + await processing.start() + expect(getStatement).toHaveBeenCalledTimes(2) + }) + + test('should call getStatement with each schedulePendingSettlements().settlementId and mockTransaction when schedulePendingSettlements returns 2 records', async () => { + schedulePendingSettlements.mockResolvedValue([retreivedSchedule, retreivedSchedule]) + + await processing.start() + + expect(getStatement).toHaveBeenNthCalledWith(1, (await schedulePendingSettlements())[0].settlementId) + expect(getStatement).toHaveBeenNthCalledWith(2, (await schedulePendingSettlements())[1].settlementId) + }) + + test('should not call getStatement when schedulePendingSettlements returns an empty array', async () => { + schedulePendingSettlements.mockResolvedValue([]) + await processing.start() + expect(getStatement).not.toHaveBeenCalled() + }) + + test('should call sendStatement when schedulePendingSettlements returns 1 record', async () => { + await processing.start() + expect(sendStatement).toHaveBeenCalled() + }) + + test('should call sendStatement once when schedulePendingSettlements returns 1 record', async () => { + await processing.start() + expect(sendStatement).toHaveBeenCalledTimes(1) + }) + + test('should call sendStatement with schedulePendingSettlements()[0].scheduleId and getStatement when schedulePendingSettlements returns 1 record', async () => { + await processing.start() + expect(sendStatement).toHaveBeenCalledWith((await schedulePendingSettlements())[0].scheduleId, await getStatement()) + }) + + test('should call sendStatement when schedulePendingSettlements returns 2 records', async () => { + schedulePendingSettlements.mockResolvedValue([retreivedSchedule, retreivedSchedule]) + await processing.start() + expect(sendStatement).toHaveBeenCalled() + }) + + test('should call sendStatement twice when schedulePendingSettlements returns 2 records', async () => { + schedulePendingSettlements.mockResolvedValue([retreivedSchedule, retreivedSchedule]) + await processing.start() + expect(sendStatement).toHaveBeenCalledTimes(2) + }) + + test('should call sendStatement with each schedulePendingSettlements().scheduleId and getStatement when schedulePendingSettlements returns 2 records', async () => { + schedulePendingSettlements.mockResolvedValue([retreivedSchedule, retreivedSchedule]) + + await processing.start() + + expect(sendStatement).toHaveBeenNthCalledWith(1, (await schedulePendingSettlements())[0].scheduleId, await getStatement()) + expect(sendStatement).toHaveBeenNthCalledWith(2, (await schedulePendingSettlements())[1].scheduleId, await getStatement()) + }) + + test('should not call sendStatement when schedulePendingSettlements returns an empty array', async () => { + schedulePendingSettlements.mockResolvedValue([]) + await processing.start() + expect(sendStatement).not.toHaveBeenCalled() }) test('should not throw when schedulePendingSettlements throws', async () => { @@ -54,6 +156,51 @@ describe('start processing', () => { expect(wrapper).not.toThrow() }) + test('should not throw when schedulePendingSettlements returns an empty array', async () => { + schedulePendingSettlements.mockResolvedValue([]) + + const wrapper = async () => { + await processing.start() + } + + expect(wrapper).not.toThrow() + }) + + test('should not throw when getStatement throws', async () => { + getStatement.mockRejectedValue(new Error('Processing issue')) + + const wrapper = async () => { + await processing.start() + } + + expect(wrapper).not.toThrow() + }) + + test('should not throw when sendStatement throws', async () => { + sendStatement.mockRejectedValue(new Error('Sending issue')) + + const wrapper = async () => { + await processing.start() + } + + expect(wrapper).not.toThrow() + }) + + test('should call setTimeout', async () => { + await processing.start() + expect(setTimeout).toHaveBeenCalled() + }) + + test('should call setTimeout once', async () => { + await processing.start() + expect(setTimeout).toHaveBeenCalledTimes(1) + }) + + test('should call setTimeout with processing.start and processingConfig.settlementProcessingInterval', async () => { + await processing.start() + expect(setTimeout).toHaveBeenCalledWith(processing.start, processingConfig.settlementProcessingInterval) + }) + test('should call setTimeout when schedulePendingSettlements throws', async () => { schedulePendingSettlements.mockRejectedValue(new Error('Processing issue')) await processing.start() @@ -71,4 +218,58 @@ describe('start processing', () => { await processing.start() expect(setTimeout).toHaveBeenCalledWith(processing.start, processingConfig.settlementProcessingInterval) }) + + test('should call setTimeout when schedulePendingSettlements returns an empty array', async () => { + schedulePendingSettlements.mockResolvedValue([]) + await processing.start() + expect(setTimeout).toHaveBeenCalled() + }) + + test('should call setTimeout once when schedulePendingSettlements returns an empty array', async () => { + schedulePendingSettlements.mockResolvedValue([]) + await processing.start() + expect(setTimeout).toHaveBeenCalledTimes(1) + }) + + test('should call setTimeout with processing.start and processingConfig.settlementProcessingInterval when schedulePendingSettlements returns an empty array', async () => { + schedulePendingSettlements.mockResolvedValue([]) + await processing.start() + expect(setTimeout).toHaveBeenCalledWith(processing.start, processingConfig.settlementProcessingInterval) + }) + + test('should call setTimeout when getStatement throws', async () => { + getStatement.mockRejectedValue(new Error('Processing issue')) + await processing.start() + expect(setTimeout).toHaveBeenCalled() + }) + + test('should call setTimeout once when getStatement throws', async () => { + getStatement.mockRejectedValue(new Error('Processing issue')) + await processing.start() + expect(setTimeout).toHaveBeenCalledTimes(1) + }) + + test('should call setTimeout with processing.start and processingConfig.settlementProcessingInterval when getStatement throws', async () => { + getStatement.mockRejectedValue(new Error('Processing issue')) + await processing.start() + expect(setTimeout).toHaveBeenCalledWith(processing.start, processingConfig.settlementProcessingInterval) + }) + + test('should call setTimeout when sendStatement throws', async () => { + sendStatement.mockRejectedValue(new Error('Sending issue')) + await processing.start() + expect(setTimeout).toHaveBeenCalled() + }) + + test('should call setTimeout once when sendStatement throws', async () => { + sendStatement.mockRejectedValue(new Error('Sending issue')) + await processing.start() + expect(setTimeout).toHaveBeenCalledTimes(1) + }) + + test('should call setTimeout with processing.start and processingConfig.settlementProcessingInterval when sendStatement throws', async () => { + sendStatement.mockRejectedValue(new Error('Sending issue')) + await processing.start() + expect(setTimeout).toHaveBeenCalledWith(processing.start, processingConfig.settlementProcessingInterval) + }) }) diff --git a/test/unit/processing/invoice-line/get-positive-invoice-line..test.js b/test/unit/processing/invoice-line/get-positive-invoice-line..test.js new file mode 100755 index 00000000..3fc321ba --- /dev/null +++ b/test/unit/processing/invoice-line/get-positive-invoice-line..test.js @@ -0,0 +1,153 @@ +jest.mock('../../../../app/processing/invoice-line/get-positive-invoice-line-by-funding-code-and-payment-request-id') +const getPositiveInvoiceLineByFundingCodeAndPaymentRequestId = require('../../../../app/processing/invoice-line/get-positive-invoice-line-by-funding-code-and-payment-request-id') + +jest.mock('../../../../app/processing/invoice-line/invoice-line-schema') +const schema = require('../../../../app/processing/invoice-line/invoice-line-schema') + +const getPositiveInvoiceLine = require('../../../../app/processing/invoice-line/get-positive-invoice-line') + +let rawInvoiceLineData +let invoiceLine + +describe('get and transform invoice-line object for building a statement object', () => { + beforeEach(() => { + const retrievedInvoiceLineData = JSON.parse(JSON.stringify(require('../../../mock-objects/mock-invoice-line').rawInvoiceLines[0])) + + rawInvoiceLineData = retrievedInvoiceLineData + getPositiveInvoiceLineByFundingCodeAndPaymentRequestId.mockResolvedValue(rawInvoiceLineData) + + invoiceLine = { + value: rawInvoiceLineData.value + } + + schema.validate.mockReturnValue({ value: invoiceLine }) + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + test('should call getPositiveInvoiceLineByFundingCodeAndPaymentRequestId when a fundingCode and a paymentRequestId are given', async () => { + const paymentRequestId = 1 + const fundingCode = rawInvoiceLineData.fundingCode + await getPositiveInvoiceLine(fundingCode, paymentRequestId) + expect(getPositiveInvoiceLineByFundingCodeAndPaymentRequestId).toHaveBeenCalled() + }) + + test('should call getPositiveInvoiceLineByFundingCodeAndPaymentRequestId once when a fundingCode and a paymentRequestId are given', async () => { + const paymentRequestId = 1 + const fundingCode = rawInvoiceLineData.fundingCode + await getPositiveInvoiceLine(fundingCode, paymentRequestId) + expect(getPositiveInvoiceLineByFundingCodeAndPaymentRequestId).toHaveBeenCalledTimes(1) + }) + + test('should call getPositiveInvoiceLineByFundingCodeAndPaymentRequestId with fundingCode and paymentRequestId when a fundingCode and a paymentRequestId are given', async () => { + const paymentRequestId = 1 + const fundingCode = rawInvoiceLineData.fundingCode + await getPositiveInvoiceLine(fundingCode, paymentRequestId) + expect(getPositiveInvoiceLineByFundingCodeAndPaymentRequestId).toHaveBeenCalledWith(fundingCode, paymentRequestId) + }) + + test('should call schema.validate when a fundingCode and a paymentRequestId are given', async () => { + const paymentRequestId = 1 + const fundingCode = rawInvoiceLineData.fundingCode + await getPositiveInvoiceLine(fundingCode, paymentRequestId) + expect(schema.validate).toHaveBeenCalled() + }) + + test('should call schema.validate once when a fundingCode and a paymentRequestId are given', async () => { + const paymentRequestId = 1 + const fundingCode = rawInvoiceLineData.fundingCode + await getPositiveInvoiceLine(fundingCode, paymentRequestId) + expect(schema.validate).toHaveBeenCalledTimes(1) + }) + + test('should call schema.validate with rawCalculationData and { abortEarly: false } when a fundingCode and a paymentRequestId are given', async () => { + const paymentRequestId = 1 + const fundingCode = rawInvoiceLineData.fundingCode + await getPositiveInvoiceLine(fundingCode, paymentRequestId) + expect(schema.validate).toHaveBeenCalledWith(rawInvoiceLineData, { abortEarly: false }) + }) + + test('should throw when getPositiveInvoiceLineByFundingCodeAndPaymentRequestId throws', async () => { + const paymentRequestId = 1 + const fundingCode = rawInvoiceLineData.fundingCode + getPositiveInvoiceLineByFundingCodeAndPaymentRequestId.mockRejectedValue(new Error('Database retrieval issue')) + + const wrapper = async () => { + await getPositiveInvoiceLine(fundingCode, paymentRequestId) + } + + expect(wrapper).rejects.toThrow() + }) + + test('should throw Error when getPositiveInvoiceLineByFundingCodeAndPaymentRequestId throws Error', async () => { + const paymentRequestId = 1 + const fundingCode = rawInvoiceLineData.fundingCode + getPositiveInvoiceLineByFundingCodeAndPaymentRequestId.mockRejectedValue(new Error('Database retrieval issue')) + + const wrapper = async () => { + await getPositiveInvoiceLine(fundingCode, paymentRequestId) + } + + expect(wrapper).rejects.toThrow(Error) + }) + + test('should throw error with "Database retrieval issue" when getPositiveInvoiceLineByFundingCodeAndPaymentRequestId throws error with "Database retrieval issue"', async () => { + const paymentRequestId = 1 + const fundingCode = rawInvoiceLineData.fundingCode + getPositiveInvoiceLineByFundingCodeAndPaymentRequestId.mockRejectedValue(new Error('Database retrieval issue')) + + const wrapper = async () => { + await getPositiveInvoiceLine(fundingCode, paymentRequestId) + } + + expect(wrapper).rejects.toThrow(/^Database retrieval issue$/) + }) + + test('should not call schema.validate when getPositiveInvoiceLineByFundingCodeAndPaymentRequestId throws', async () => { + const paymentRequestId = 1 + const fundingCode = rawInvoiceLineData.fundingCode + getPositiveInvoiceLineByFundingCodeAndPaymentRequestId.mockRejectedValue(new Error('Database retrieval issue')) + + try { await getPositiveInvoiceLine(fundingCode, paymentRequestId) } catch {} + + expect(schema.validate).not.toHaveBeenCalled() + }) + + test('should throw when schema.validate returns with error key', async () => { + const paymentRequestId = 1 + const fundingCode = rawInvoiceLineData.fundingCode + schema.validate.mockReturnValue({ error: 'Not a valid object' }) + + const wrapper = async () => { + await getPositiveInvoiceLine(fundingCode, paymentRequestId) + } + + expect(wrapper).rejects.toThrow() + }) + + test('should throw Error when schema.validate returns with error key', async () => { + const paymentRequestId = 1 + const fundingCode = rawInvoiceLineData.fundingCode + schema.validate.mockReturnValue({ error: 'Not a valid object' }) + + const wrapper = async () => { + await getPositiveInvoiceLine(fundingCode, paymentRequestId) + } + + expect(wrapper).rejects.toThrow(Error) + }) + + test('should throw error which starts "Payment request with paymentRequestId: 1 does not have the required invoice-line data for funding code 80101" when schema.validate returns with error key of "Joi validation issue"', async () => { + const paymentRequestId = 1 + const fundingCode = rawInvoiceLineData.fundingCode + schema.validate.mockReturnValue({ error: 'Not a valid object' }) + + const wrapper = async () => { + await getPositiveInvoiceLine(fundingCode, paymentRequestId) + } + + expect(wrapper).rejects.toThrow(/^Payment request with paymentRequestId: 1 does not have the required invoice-line data for funding code 80101/) + }) +}) diff --git a/test/unit/processing/organisation/get-organisation.test.js b/test/unit/processing/organisation/get-organisation.test.js new file mode 100755 index 00000000..fd9f80fc --- /dev/null +++ b/test/unit/processing/organisation/get-organisation.test.js @@ -0,0 +1,140 @@ +const mockCommit = jest.fn() +const mockRollback = jest.fn() +const mockTransaction = { + commit: mockCommit, + rollback: mockRollback +} + +jest.mock('../../../../app/data', () => { + return { + sequelize: + { + transaction: jest.fn().mockImplementation(() => { + return { ...mockTransaction } + }) + } + } +}) + +jest.mock('../../../../app/processing/organisation/organisation-schema') +const schema = require('../../../../app/processing/organisation/organisation-schema') +jest.mock('../../../../app/processing/organisation/get-organisation-by-sbi') +const getOrganisationBySbi = require('../../../../app/processing/organisation/get-organisation-by-sbi') + +const getOrganisation = require('../../../../app/processing/organisation/get-organisation') +const sbi = require('../../../mock-components/mock-sbi') + +let organisationData + +describe('get and transform organisation request information for building a statement object', () => { + beforeEach(() => { + const retrievedOrganisationData = JSON.parse(JSON.stringify(require('../../../mock-objects/mock-organisation').rawOrganisationData)) + + organisationData = retrievedOrganisationData + + schema.validate.mockReturnValue({ value: organisationData }) + getOrganisationBySbi.mockResolvedValue(retrievedOrganisationData) + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + test('should call getOrganisationBySbi when a sbi is given', async () => { + await getOrganisation(sbi, mockTransaction) + expect(getOrganisationBySbi).toHaveBeenCalled() + }) + + test('should call getOrganisationBySbi once when a sbi is given', async () => { + await getOrganisation(sbi, mockTransaction) + expect(getOrganisationBySbi).toHaveBeenCalledTimes(1) + }) + + test('should call getOrganisationBySbi with sbi when a sbi is given', async () => { + await getOrganisation(sbi, mockTransaction) + expect(getOrganisationBySbi).toHaveBeenCalledWith(sbi, mockTransaction) + }) + + test('should call schema.validate when a sbi is given', async () => { + await getOrganisation(sbi, mockTransaction) + expect(schema.validate).toHaveBeenCalled() + }) + + test('should call schema.validate once when a sbi is given', async () => { + await getOrganisation(sbi, mockTransaction) + expect(schema.validate).toHaveBeenCalledTimes(1) + }) + + test('should call schema.validate with organisationData and { abortEarly: false } when a sbi is given', async () => { + await getOrganisation(sbi, mockTransaction) + expect(schema.validate).toHaveBeenCalledWith(organisationData, { abortEarly: false }) + }) + + test('should throw when getOrganisationBySbi throws', async () => { + getOrganisationBySbi.mockRejectedValue(new Error('Database retrieval issue')) + + const wrapper = async () => { + await getOrganisation(sbi, mockTransaction) + } + + expect(wrapper).rejects.toThrow() + }) + + test('should throw Error when getOrganisationBySbi throws Error', async () => { + getOrganisationBySbi.mockRejectedValue(new Error('Database retrieval issue')) + + const wrapper = async () => { + await getOrganisation(sbi, mockTransaction) + } + + expect(wrapper).rejects.toThrow(Error) + }) + + test('should throw error with "Database retrieval issue" when getOrganisationBySbi throws error with "Database retrieval issue"', async () => { + getOrganisationBySbi.mockRejectedValue(new Error('Database retrieval issue')) + + const wrapper = async () => { + await getOrganisation(sbi, mockTransaction) + } + + expect(wrapper).rejects.toThrow(/^Database retrieval issue$/) + }) + + test('should not call schema.validate when getOrganisationBySbi throws', async () => { + getOrganisationBySbi.mockRejectedValue(new Error('Database retrieval issue')) + + try { await getOrganisation(sbi, mockTransaction) } catch {} + + expect(schema.validate).not.toHaveBeenCalled() + }) + + test('should throw when schema.validate returns with error key', async () => { + schema.validate.mockReturnValue({ error: 'Not a valid object' }) + + const wrapper = async () => { + await getOrganisation(sbi, mockTransaction) + } + + expect(wrapper).rejects.toThrow() + }) + + test('should throw Error when schema.validate returns with error key', async () => { + schema.validate.mockReturnValue({ error: 'Not a valid object' }) + + const wrapper = async () => { + await getOrganisation(sbi, mockTransaction) + } + + expect(wrapper).rejects.toThrow(Error) + }) + + test('should throw error which starts "Payment request with paymentRequestId:" when schema.validate returns with error key of "Joi validation issue"', async () => { + schema.validate.mockReturnValue({ error: 'Not a valid object' }) + + const wrapper = async () => { + await getOrganisation(sbi, mockTransaction) + } + + expect(wrapper).rejects.toThrow(/^Organisation with the sbi:/) + }) +}) diff --git a/test/unit/processing/payment-request/get-payment-request.test.js b/test/unit/processing/payment-request/get-payment-request.test.js index e0ed7e0f..ef8a2e34 100644 --- a/test/unit/processing/payment-request/get-payment-request.test.js +++ b/test/unit/processing/payment-request/get-payment-request.test.js @@ -1,3 +1,21 @@ +const mockCommit = jest.fn() +const mockRollback = jest.fn() +const mockTransaction = { + commit: mockCommit, + rollback: mockRollback +} + +jest.mock('../../../../app/data', () => { + return { + sequelize: + { + transaction: jest.fn().mockImplementation(() => { + return { ...mockTransaction } + }) + } + } +}) + jest.mock('../../../../app/processing/payment-request/get-completed-payment-request-by-payment-request-id') const getCompletedPaymentRequestByPaymentRequestId = require('../../../../app/processing/payment-request/get-completed-payment-request-by-payment-request-id') @@ -55,8 +73,8 @@ describe('get and map required payment request information for building a statem test('should call getCompletedPaymentRequestByPaymentRequestId with paymentRequestId when a paymentRequestId is given', async () => { const paymentRequestId = 1 - await getPaymentRequest(paymentRequestId) - expect(getCompletedPaymentRequestByPaymentRequestId).toHaveBeenCalledWith(paymentRequestId) + await getPaymentRequest(paymentRequestId, mockTransaction) + expect(getCompletedPaymentRequestByPaymentRequestId).toHaveBeenCalledWith(paymentRequestId, mockTransaction) }) test('should call validatePaymentRequest when a paymentRequestId is given', async () => { diff --git a/test/unit/processing/payment-request/map-payment-request.test.js b/test/unit/processing/payment-request/map-payment-request.test.js index 01be0259..a6a90579 100644 --- a/test/unit/processing/payment-request/map-payment-request.test.js +++ b/test/unit/processing/payment-request/map-payment-request.test.js @@ -11,15 +11,21 @@ describe('map required payment request information for building a statement obje retreivedPaymentRequest = { paymentRequestId: 1, + agreementNumber: paymentRequest.agreementNumber, dueDate: paymentRequest.dueDate, + invoiceNumber: paymentRequest.invoiceNumber, marketingYear: paymentRequest.marketingYear, + value: paymentRequest.value, schedule: paymentRequest.schedule } mappedPaymentRequest = { paymentRequestId: retreivedPaymentRequest.paymentRequestId, + agreementNumber: retreivedPaymentRequest.agreementNumber, dueDate: retreivedPaymentRequest.dueDate, frequency: SCHEDULE_NAMES[retreivedPaymentRequest.schedule], + invoiceNumber: retreivedPaymentRequest.invoiceNumber, + value: retreivedPaymentRequest.value, year: retreivedPaymentRequest.marketingYear } }) diff --git a/test/unit/processing/reverse-engineer-invoice-number.test.js b/test/unit/processing/reverse-engineer-invoice-number.test.js deleted file mode 100644 index a11ba3d9..00000000 --- a/test/unit/processing/reverse-engineer-invoice-number.test.js +++ /dev/null @@ -1,31 +0,0 @@ -const reverseEngineerInvoiceNumber = require('../../../app/processing/reverse-engineer-invoice-number') - -let paymentRequest - -describe('reverse engineer original invoice number from processed invoice number', () => { - beforeEach(() => { - paymentRequest = JSON.parse(JSON.stringify(require('../../mock-payment-request').processingPaymentRequest)) - }) - - afterEach(() => { - jest.clearAllMocks() - }) - - test('should return "originalS0000" when an invoice number with more than 5 characters is given', async () => { - const invoiceNumber = paymentRequest.invoiceNumber - const result = await reverseEngineerInvoiceNumber(invoiceNumber) - expect(result).toBe('originalS0000') - }) - - test('should return "originalS0000" when an invoice number with 5 characters is given', async () => { - const invoiceNumber = paymentRequest.invoiceNumber.slice(0, 5) - const result = await reverseEngineerInvoiceNumber(invoiceNumber) - expect(result).toBe('originalS0000') - }) - - test('should return "originalS00" when an invoice number with fewer than 5 characters is given', async () => { - const invoiceNumber = paymentRequest.invoiceNumber.slice(0, 3) - const result = await reverseEngineerInvoiceNumber(invoiceNumber) - expect(result).toBe('originalS00') - }) -}) diff --git a/test/unit/processing/settlement/get-settlement.test.js b/test/unit/processing/settlement/get-settlement.test.js index 9d7637fe..eb5ad6c7 100644 --- a/test/unit/processing/settlement/get-settlement.test.js +++ b/test/unit/processing/settlement/get-settlement.test.js @@ -1,6 +1,27 @@ +const mockCommit = jest.fn() +const mockRollback = jest.fn() +const mockTransaction = { + commit: mockCommit, + rollback: mockRollback +} + +jest.mock('../../../../app/data', () => { + return { + sequelize: + { + transaction: jest.fn().mockImplementation(() => { + return { ...mockTransaction } + }) + } + } +}) + jest.mock('../../../../app/processing/settlement/get-settled-settlement-by-settlement-id') const getSettledSettlementBySettlementId = require('../../../../app/processing/settlement/get-settled-settlement-by-settlement-id') +jest.mock('../../../../app/processing/settlement/update-settlement-payment-request-id') +const updateSettlementPaymentRequestId = require('../../../../app/processing/settlement/update-settlement-payment-request-id') + jest.mock('../../../../app/processing/settlement/validate-settlement') const validateSettlement = require('../../../../app/processing/settlement/validate-settlement') @@ -20,6 +41,7 @@ describe('get required settlement information for building a statement object', validateSettlement.mockReturnValue(retreivedSettlement) getSettledSettlementBySettlementId.mockResolvedValue(retreivedSettlement) + updateSettlementPaymentRequestId.mockResolvedValue(retreivedSettlement) }) afterEach(() => { @@ -28,7 +50,7 @@ describe('get required settlement information for building a statement object', test('should call getSettledSettlementBySettlementId when a settlementId is given', async () => { const settlementId = 1 - await getSettlement(settlementId) + await getSettlement(settlementId, mockTransaction) expect(getSettledSettlementBySettlementId).toHaveBeenCalled() }) @@ -40,8 +62,8 @@ describe('get required settlement information for building a statement object', test('should call getSettledSettlementBySettlementId with settlementId when a settlementId is given', async () => { const settlementId = 1 - await getSettlement(settlementId) - expect(getSettledSettlementBySettlementId).toHaveBeenCalledWith(settlementId) + await getSettlement(settlementId, mockTransaction) + expect(getSettledSettlementBySettlementId).toHaveBeenCalledWith(settlementId, mockTransaction) }) test('should call validateSettlement when a settlementId is given', async () => { diff --git a/test/unit/processing/statement/get-statement.test.js b/test/unit/processing/statement/get-statement.test.js new file mode 100755 index 00000000..f33af76e --- /dev/null +++ b/test/unit/processing/statement/get-statement.test.js @@ -0,0 +1,79 @@ +jest.mock('../../../../app/processing/organisation/get-organisation') +const getOrganisation = require('../../../../app/processing/organisation/get-organisation') + +jest.mock('../../../../app/processing/invoice-line/get-positive-invoice-line-by-funding-code-and-payment-request-id') +const getPositiveInvoiceLineByFundingCodeAndPaymentRequestId = require('../../../../app/processing/invoice-line/get-positive-invoice-line-by-funding-code-and-payment-request-id') + +jest.mock('../../../../app/processing/calculation/get-calculation') +const getCalculation = require('../../../../app/processing/calculation/get-calculation') + +jest.mock('../../../../app/processing/funding/get-fundings-by-calculation-id') +const getFundingsByCalculationId = require('../../../../app/processing/funding/get-fundings-by-calculation-id') + +jest.mock('../../../../app/processing/payment-request/get-completed-payment-request-by-payment-request-id') +const getCompletedPaymentRequestByPaymentRequestId = require('../../../../app/processing/payment-request/get-completed-payment-request-by-payment-request-id') + +jest.mock('../../../../app/processing/settlement/get-settled-settlement-by-settlement-id') +const getSettledSettlementBySettlementId = require('../../../../app/processing/settlement/get-settled-settlement-by-settlement-id') + +const { getStatement } = require('../../../../app/processing/statement') + +let calculation +let paymentRequest +let settlement + +describe('get various components and transform to statement object', () => { + beforeEach(() => { + const retrievedOrganisationData = JSON.parse(JSON.stringify(require('../../../mock-objects/mock-organisation').rawOrganisationData)) + const retrievedCalculationData = JSON.parse(JSON.stringify(require('../../../mock-objects/mock-calculation').rawCalculationData)) + const retrievedFundingsData = JSON.parse(JSON.stringify(require('../../../mock-objects/mock-fundings').rawFundingsData)) + const retrievedPaymentRequest = JSON.parse(JSON.stringify(require('../../../mock-payment-request').processingPaymentRequest)) + const retreivedSettlement = JSON.parse(JSON.stringify(require('../../../mock-settlement'))) + + settlement = { + invoiceNumber: retreivedSettlement.invoiceNumber, + paymentRequestId: 1, + reference: retreivedSettlement.reference, + settled: retreivedSettlement.settled, + settlementDate: new Date(retreivedSettlement.settlementDate) + + } + + getSettledSettlementBySettlementId.mockResolvedValue(settlement) + + paymentRequest = { + paymentRequestId: 1, + agreementNumber: retrievedPaymentRequest.agreementNumber, + dueDate: retrievedPaymentRequest.dueDate, + invoiceNumber: retrievedPaymentRequest.invoiceNumber, + marketingYear: retrievedPaymentRequest.marketingYear, + schedule: retrievedPaymentRequest.schedule, + value: retrievedPaymentRequest.value + } + + calculation = { + sbi: retrievedCalculationData.sbi, + calculated: new Date(retrievedCalculationData.calculationDate), + invoiceNumber: retrievedCalculationData.invoiceNumber + } + + const retrievedInvoiceLine = { value: 600 } + + getCompletedPaymentRequestByPaymentRequestId.mockResolvedValue(paymentRequest) + getPositiveInvoiceLineByFundingCodeAndPaymentRequestId.mockResolvedValue(retrievedInvoiceLine) + getOrganisation.mockResolvedValue(retrievedOrganisationData) + getCalculation.mockResolvedValue(calculation) + getFundingsByCalculationId.mockResolvedValue(retrievedFundingsData) + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + test('should call getCalculation when a paymentRequestId is given', async () => { + const paymentRequestId = 1 + const statement = await getStatement(paymentRequestId) + console.log(statement) + expect(getCalculation).toHaveBeenCalled() + }) +}) diff --git a/test/unit/utility/convert-to-pence.test.js b/test/unit/utility/convert-to-pence.test.js new file mode 100644 index 00000000..351440e5 --- /dev/null +++ b/test/unit/utility/convert-to-pence.test.js @@ -0,0 +1,12 @@ +const { convertToPence } = require('../../../app/utility') + +describe('convert decimal pounds to pence', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + test('should return 1256 when 12.56 is given', async () => { + const result = await convertToPence(12.56) + expect(result).toBe(1256) + }) +}) diff --git a/test/unit/utility/convert-to-pounds.test.js b/test/unit/utility/convert-to-pounds.test.js new file mode 100644 index 00000000..d10ed7c4 --- /dev/null +++ b/test/unit/utility/convert-to-pounds.test.js @@ -0,0 +1,12 @@ +const { convertToPounds } = require('../../../app/utility') + +describe('convert integer pence to string decimal pounds', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + test('should return "567.89" when 56789 is given', async () => { + const result = await convertToPounds(56789) + expect(result).toBe('567.89') + }) +}) diff --git a/test/unit/utility/reverse-engineer-invoice-number.test.js b/test/unit/utility/reverse-engineer-invoice-number.test.js new file mode 100644 index 00000000..4f4b080b --- /dev/null +++ b/test/unit/utility/reverse-engineer-invoice-number.test.js @@ -0,0 +1,15 @@ +const { reverseEngineerInvoiceNumber } = require('../../../app/utility') + +const { SFI_FIRST_PAYMENT: INVOICE_NUMBER } = require('../../mock-components/mock-invoice-number') +const { SFI_FIRST_PAYMENT_ORIGINAL: ORIGINAL_INVOICE_NUMBER } = require('../../mock-components/mock-invoice-number') + +describe('reverse engineer original invoice number from processed invoice number', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + test('should return ORIGINAL_INVOICE_NUMBER when SFI_FIRST_PAYMENT_INVOICE_NUMBER is given', async () => { + const result = await reverseEngineerInvoiceNumber(INVOICE_NUMBER) + expect(result).toBe(ORIGINAL_INVOICE_NUMBER) + }) +})