[ { "name": "3 days at 250.00 and 400.00 of materials marked up 20%, standard-rated: the markup is on materials only", "args": [ 300, { "minor": 25000, "currency": "GBP" }, { "minor": 40000, "currency": "GBP" }, 2000, "STANDARD", "2026-09-23" ], "expect": { "labour": { "minor": 75000, "currency": "GBP" }, "materials": { "minor": 48000, "currency": "GBP" }, "net": { "minor": 123000, "currency": "GBP" }, "vat": { "minor": 24600, "currency": "GBP" }, "gross": { "minor": 147600, "currency": "GBP" }, "vatBasisPoints": 2000 } }, { "name": "2.5 days at 180.00, no materials, not VAT registered", "args": [ 250, { "minor": 18000, "currency": "GBP" }, { "minor": 0, "currency": "GBP" }, 1500, null, "2026-09-23" ], "expect": { "labour": { "minor": 45000, "currency": "GBP" }, "materials": { "minor": 0, "currency": "GBP" }, "net": { "minor": 45000, "currency": "GBP" }, "vat": { "minor": 0, "currency": "GBP" }, "gross": { "minor": 45000, "currency": "GBP" }, "vatBasisPoints": 0 } }, { "name": "a third of a day at 199.99 is 65.9967, rounded half up to 66.00; 12.34 of materials at 15% is 14.191, 14.19; VAT 20% of 80.19 is 16.038, 16.04", "args": [ 33, { "minor": 19999, "currency": "GBP" }, { "minor": 1234, "currency": "GBP" }, 1500, "STANDARD", "2026-09-23" ], "expect": { "labour": { "minor": 6600, "currency": "GBP" }, "materials": { "minor": 1419, "currency": "GBP" }, "net": { "minor": 8019, "currency": "GBP" }, "vat": { "minor": 1604, "currency": "GBP" }, "gross": { "minor": 9623, "currency": "GBP" }, "vatBasisPoints": 2000 } }, { "name": "reduced-rated installation at 5%", "args": [ 100, { "minor": 20000, "currency": "GBP" }, { "minor": 100000, "currency": "GBP" }, 0, "REDUCED", "2026-09-23" ], "expect": { "labour": { "minor": 20000, "currency": "GBP" }, "materials": { "minor": 100000, "currency": "GBP" }, "net": { "minor": 120000, "currency": "GBP" }, "vat": { "minor": 6000, "currency": "GBP" }, "gross": { "minor": 126000, "currency": "GBP" }, "vatBasisPoints": 500 } }, { "name": "zero-rated work", "args": [ 200, { "minor": 15000, "currency": "GBP" }, { "minor": 30000, "currency": "GBP" }, 1000, "ZERO", "2026-09-23" ], "expect": { "labour": { "minor": 30000, "currency": "GBP" }, "materials": { "minor": 33000, "currency": "GBP" }, "net": { "minor": 63000, "currency": "GBP" }, "vat": { "minor": 0, "currency": "GBP" }, "gross": { "minor": 63000, "currency": "GBP" }, "vatBasisPoints": 0 } }, { "name": "a quote for a 2010 job uses the 17.5% rate then in force", "args": [ 100, { "minor": 10000, "currency": "GBP" }, { "minor": 0, "currency": "GBP" }, 0, "STANDARD", "2010-06-30" ], "expect": { "labour": { "minor": 10000, "currency": "GBP" }, "materials": { "minor": 0, "currency": "GBP" }, "net": { "minor": 10000, "currency": "GBP" }, "vat": { "minor": 1750, "currency": "GBP" }, "gross": { "minor": 11750, "currency": "GBP" }, "vatBasisPoints": 1750 } }, { "name": "materials only: no labour days", "args": [ 0, { "minor": 25000, "currency": "GBP" }, { "minor": 5000, "currency": "GBP" }, 1000, "STANDARD", "2026-09-23" ], "expect": { "labour": { "minor": 0, "currency": "GBP" }, "materials": { "minor": 5500, "currency": "GBP" }, "net": { "minor": 5500, "currency": "GBP" }, "vat": { "minor": 1100, "currency": "GBP" }, "gross": { "minor": 6600, "currency": "GBP" }, "vatBasisPoints": 2000 } }, { "name": "half a day at 250.00", "args": [ 50, { "minor": 25000, "currency": "GBP" }, { "minor": 0, "currency": "GBP" }, 0, null, "2026-09-23" ], "expect": { "labour": { "minor": 12500, "currency": "GBP" }, "materials": { "minor": 0, "currency": "GBP" }, "net": { "minor": 12500, "currency": "GBP" }, "vat": { "minor": 0, "currency": "GBP" }, "gross": { "minor": 12500, "currency": "GBP" }, "vatBasisPoints": 0 } }, { "name": "half a day at 250.01 is 125.005, rounded half up to 125.01", "args": [ 50, { "minor": 25001, "currency": "GBP" }, { "minor": 0, "currency": "GBP" }, 0, null, "2026-09-23" ], "expect": { "labour": { "minor": 12501, "currency": "GBP" }, "materials": { "minor": 0, "currency": "GBP" }, "net": { "minor": 12501, "currency": "GBP" }, "vat": { "minor": 0, "currency": "GBP" }, "gross": { "minor": 12501, "currency": "GBP" }, "vatBasisPoints": 0 } }, { "name": "materials sold at cost: no markup", "args": [ 100, { "minor": 20000, "currency": "GBP" }, { "minor": 7777, "currency": "GBP" }, 0, "STANDARD", "2026-09-23" ], "expect": { "labour": { "minor": 20000, "currency": "GBP" }, "materials": { "minor": 7777, "currency": "GBP" }, "net": { "minor": 27777, "currency": "GBP" }, "vat": { "minor": 5555, "currency": "GBP" }, "gross": { "minor": 33332, "currency": "GBP" }, "vatBasisPoints": 2000 } }, { "name": "negative labour days", "args": [ -50, { "minor": 25000, "currency": "GBP" }, { "minor": 0, "currency": "GBP" }, 0, null, "2026-09-23" ], "expectError": "labourDaysHundredths must be a whole number of hundredths, not negative" }, { "name": "fractional hundredths", "args": [ 2.5, { "minor": 25000, "currency": "GBP" }, { "minor": 0, "currency": "GBP" }, 0, null, "2026-09-23" ], "expectError": "labourDaysHundredths must be a whole number of hundredths, not negative" }, { "name": "a negative day rate", "args": [ 100, { "minor": -1, "currency": "GBP" }, { "minor": 0, "currency": "GBP" }, 0, null, "2026-09-23" ], "expectError": "dayRate must not be negative" }, { "name": "negative materials", "args": [ 100, { "minor": 25000, "currency": "GBP" }, { "minor": -1, "currency": "GBP" }, 0, null, "2026-09-23" ], "expectError": "cost must not be negative" }, { "name": "a markup below -100%", "args": [ 100, { "minor": 25000, "currency": "GBP" }, { "minor": 100, "currency": "GBP" }, -10001, null, "2026-09-23" ], "expectError": "a markup of -10001 basis points gives a negative price" }, { "name": "a fractional markup", "args": [ 100, { "minor": 25000, "currency": "GBP" }, { "minor": 100, "currency": "GBP" }, 1.5, null, "2026-09-23" ], "expectError": "must be an integer" }, { "name": "day rate and materials in different currencies", "args": [ 100, { "minor": 25000, "currency": "GBP" }, { "minor": 100, "currency": "EUR" }, 0, null, "2026-09-23" ], "expectError": "currency mismatch" }, { "name": "an unknown VAT category", "args": [ 100, { "minor": 25000, "currency": "GBP" }, { "minor": 0, "currency": "GBP" }, 0, "LUXURY", "2026-09-23" ], "expectError": "no VAT rule for GB/LUXURY" }, { "name": "a labour product beyond 2^53 - 1", "args": [ 10000000, { "minor": 1000000000000, "currency": "GBP" }, { "minor": 0, "currency": "GBP" }, 0, null, "2026-09-23" ], "expectError": "exceeds 2^53 - 1" } ]