Skip to content

Commit

Permalink
feat: Calculate VAT using getFeeBreakdown() (#579)
Browse files Browse the repository at this point in the history
  • Loading branch information
DafyddLlyr authored Dec 5, 2024
1 parent 427b5cc commit 29b4851
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 23 deletions.
2 changes: 1 addition & 1 deletion src/types/feeBreakdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface FeeBreakdown {
export interface PassportFeeFields {
"application.fee.calculated": number;
"application.fee.payable": number;
"application.fee.payable.vat": number;
"application.fee.payable.includesVAT": boolean;
"application.fee.reduction.alternative": boolean;
"application.fee.reduction.parishCouncil": boolean;
"application.fee.reduction.sports": boolean;
Expand Down
50 changes: 33 additions & 17 deletions src/utils/feeBreakdown.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe("calculateReduction() helper function", () => {
const input: PassportFeeFields = {
"application.fee.calculated": 100,
"application.fee.payable": 50,
"application.fee.payable.vat": 0,
"application.fee.payable.includesVAT": false,
"application.fee.reduction.alternative": false,
"application.fee.reduction.parishCouncil": false,
"application.fee.reduction.sports": false,
Expand All @@ -43,7 +43,7 @@ describe("calculateReduction() helper function", () => {
const input: PassportFeeFields = {
"application.fee.calculated": 0,
"application.fee.payable": 100,
"application.fee.payable.vat": 0,
"application.fee.payable.includesVAT": false,
"application.fee.reduction.alternative": false,
"application.fee.reduction.parishCouncil": false,
"application.fee.reduction.sports": false,
Expand All @@ -61,7 +61,7 @@ describe("toFeeBreakdown() helper function", () => {
const input: PassportFeeFields = {
"application.fee.calculated": 100,
"application.fee.payable": 50,
"application.fee.payable.vat": 10,
"application.fee.payable.includesVAT": true,
"application.fee.reduction.alternative": false,
"application.fee.reduction.parishCouncil": false,
"application.fee.reduction.sports": false,
Expand All @@ -73,14 +73,13 @@ describe("toFeeBreakdown() helper function", () => {

expect(amount.calculated).toEqual(input["application.fee.calculated"]);
expect(amount.payable).toEqual(input["application.fee.payable"]);
expect(amount.vat).toEqual(input["application.fee.payable.vat"]);
expect(amount.reduction).toEqual(50);
});

it("sets calculated to payable amount if no calculated value is provided", () => {
const input: PassportFeeFields = {
"application.fee.calculated": 0,
"application.fee.payable.vat": 10,
"application.fee.payable.includesVAT": true,
"application.fee.payable": 50,
"application.fee.reduction.alternative": false,
"application.fee.reduction.parishCouncil": false,
Expand All @@ -93,6 +92,23 @@ describe("toFeeBreakdown() helper function", () => {

expect(amount.calculated).toEqual(input["application.fee.payable"]);
});

it("correctly calculates the VAT", () => {
const input: PassportFeeFields = {
"application.fee.calculated": 100,
"application.fee.payable": 50,
"application.fee.payable.includesVAT": true,
"application.fee.reduction.alternative": false,
"application.fee.reduction.parishCouncil": false,
"application.fee.reduction.sports": false,
"application.fee.exemption.disability": false,
"application.fee.exemption.resubmission": false,
};

const { amount } = toFeeBreakdown(input);

expect(amount.vat).toEqual(16.67);
});
});

describe("getFeeBreakdown() function", () => {
Expand All @@ -101,7 +117,7 @@ describe("getFeeBreakdown() function", () => {
const mockPassportData = {
"application.fee.calculated": 1000,
"application.fee.payable": 800,
"application.fee.payable.vat": 160,
"application.fee.payable.includesVAT": ["true"],
"some.other.fields": ["abc", "xyz"],
};

Expand All @@ -112,7 +128,7 @@ describe("getFeeBreakdown() function", () => {
calculated: 1000,
payable: 800,
reduction: 200,
vat: 160,
vat: 166.67,
},
exemptions: [],
reductions: [],
Expand All @@ -123,7 +139,7 @@ describe("getFeeBreakdown() function", () => {
const mockPassportData = {
"application.fee.calculated": [1000],
"application.fee.payable": [800],
"application.fee.payable.vat": [160],
"application.fee.payable.includesVAT": ["true"],
"some.other.fields": ["abc", "xyz"],
};

Expand All @@ -134,7 +150,7 @@ describe("getFeeBreakdown() function", () => {
calculated: 1000,
payable: 800,
reduction: 200,
vat: 160,
vat: 166.67,
},
exemptions: [],
reductions: [],
Expand All @@ -145,7 +161,7 @@ describe("getFeeBreakdown() function", () => {
const mockPassportData = {
"application.fee.calculated": 1000,
"application.fee.payable": 800,
"application.fee.payable.vat": 160,
"application.fee.payable.includesVAT": ["true"],
"application.fee.reduction.alternative": ["true"],
"application.fee.reduction.parishCouncil": ["true"],
"some.other.fields": ["abc", "xyz"],
Expand All @@ -163,7 +179,7 @@ describe("getFeeBreakdown() function", () => {
const mockPassportData = {
"application.fee.calculated": 1000,
"application.fee.payable": 800,
"application.fee.payable.vat": 160,
"application.fee.payable.includesVAT": ["true"],
"application.fee.reduction.alternative": ["false"],
"application.fee.reduction.parishCouncil": ["false"],
"some.other.fields": ["abc", "xyz"],
Expand All @@ -178,7 +194,7 @@ describe("getFeeBreakdown() function", () => {
const mockPassportData = {
"application.fee.calculated": 1000,
"application.fee.payable": 800,
"application.fee.payable.vat": 160,
"application.fee.payable.includesVAT": ["true"],
"application.fee.reduction.alternative": ["true"],
"application.fee.reduction.parishCouncil": ["false"],
"application.fee.reduction.someReason": ["true"],
Expand All @@ -200,7 +216,7 @@ describe("getFeeBreakdown() function", () => {
const mockPassportData = {
"application.fee.calculated": 1000,
"application.fee.payable": 800,
"application.fee.payable.vat": 160,
"application.fee.payable.includesVAT": ["true"],
"application.fee.exemption.disability": ["true"],
"application.fee.exemption.resubmission": ["true"],
"some.other.fields": ["abc", "xyz"],
Expand All @@ -218,7 +234,7 @@ describe("getFeeBreakdown() function", () => {
const mockPassportData = {
"application.fee.calculated": 1000,
"application.fee.payable": 800,
"application.fee.payable.vat": 160,
"application.fee.payable.includesVAT": ["true"],
"application.fee.exemption.disability": ["false"],
"application.fee.exemption.resubmission": ["false"],
"some.other.fields": ["abc", "xyz"],
Expand All @@ -233,7 +249,7 @@ describe("getFeeBreakdown() function", () => {
const mockPassportData = {
"application.fee.calculated": 1000,
"application.fee.payable": 800,
"application.fee.payable.vat": 160,
"application.fee.payable.includesVAT": ["true"],
"application.fee.exemption.disability": ["false"],
"application.fee.exemption.resubmission": ["false"],
"application.fee.exemption.someReason": ["true"],
Expand Down Expand Up @@ -264,7 +280,7 @@ describe("getFeeBreakdown() function", () => {
it("returns undefined for partial data", () => {
const mockPassportData = {
"application.fee.calculated": [1000],
"application.fee.payable.vat": [160],
"application.fee.payable.includesVAT": ["true"],
"some.other.fields": ["abc", "xyz"],
};

Expand All @@ -275,7 +291,7 @@ describe("getFeeBreakdown() function", () => {
const mockPassportData = {
"application.fee.calculated": "some string",
"application.fee.payable": [800, 700],
"application.fee.payable.vat": false,
"application.fee.payable.includesVAT": false,
"some.other.fields": ["abc", "xyz"],
};

Expand Down
22 changes: 18 additions & 4 deletions src/utils/feeBreakdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { z } from "zod";

import { FeeBreakdown, PassportFeeFields } from "../types";

export const VAT_RATE = 0.2;

export const toNumber = (input: number | [number]) =>
Array.isArray(input) ? input[0] : input;

Expand All @@ -28,6 +30,9 @@ const getGranularKeys = (
return granularKeys;
};

const getCalculatedAmount = (data: PassportFeeFields) =>
data["application.fee.calculated"] || data["application.fee.payable"];

/**
* A "reduction" is the sum of the difference between calculated and payable
*/
Expand All @@ -36,15 +41,24 @@ export const calculateReduction = (data: PassportFeeFields) =>
? data["application.fee.calculated"] - data["application.fee.payable"]
: 0;

const calculateVAT = (data: PassportFeeFields) => {
if (!data["application.fee.payable.includesVAT"]) return 0;

const calculated = getCalculatedAmount(data);
const vat = (calculated * VAT_RATE) / (1 + VAT_RATE);
const roundedVAT = Number(vat.toFixed(2));

return roundedVAT;
};

/**
* Transform Passport data to a FeeBreakdown
*/
export const toFeeBreakdown = (data: PassportFeeFields): FeeBreakdown => ({
amount: {
calculated:
data["application.fee.calculated"] || data["application.fee.payable"],
calculated: getCalculatedAmount(data),
payable: data["application.fee.payable"],
vat: data["application.fee.payable.vat"],
vat: calculateVAT(data),
reduction: calculateReduction(data),
},
reductions: getGranularKeys(data, "application.fee.reduction"),
Expand All @@ -68,7 +82,7 @@ export const createPassportSchema = () => {
.object({
"application.fee.calculated": feeSchema.optional().default(0),
"application.fee.payable": feeSchema,
"application.fee.payable.vat": feeSchema.optional().default(0),
"application.fee.payable.includesVAT": booleanSchema,
"application.fee.reduction.alternative": booleanSchema,
"application.fee.reduction.parishCouncil": booleanSchema,
"application.fee.reduction.sports": booleanSchema,
Expand Down
2 changes: 1 addition & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from "./digitalPlanningSchema";
export * from "./encryption";
export { getFeeBreakdown } from "./feeBreakdown";
export { getFeeBreakdown, VAT_RATE } from "./feeBreakdown";
export * from "./govPayMetadata";
export * from "./projectTypes";

0 comments on commit 29b4851

Please sign in to comment.