Functional Weave
Code in TypeScript

Suites · accounting

Accounting and bookkeeping

Books for a small business: invoices and credit notes, VAT, double-entry journals, payment terms and debtors, bank reconciliation and period-end.

80 capabilities (35 core, 45 optional) and 19 gaps, in build order. Put each line in the file that calls it, then fune build.

UK first: VAT, the VAT return and late payment interest follow UK rules; another country's tax needs its own rules.

Calculates tax from published rules; it is not tax advice. Have an accountant check how the app uses it before anyone files a return.

1. Invoicing

Raise invoices and credit notes whose lines, VAT and totals add up to the penny.

finance.invoice.calculate core
Totals a whole invoice: line nets, VAT per line, totals and the VAT summary by rate.
Calculate a complete invoice: line nets, VAT per line, totals and a VAT summary grouped by rate.
import { calculateInvoice } from "#fune/finance.invoice.calculate@^1";
finance.invoice.line-total core
Net total of one line from unit price, quantity and discount, for line editing.
Net total for one invoice line: unit price by quantity, less a discount.
import { lineTotal } from "#fune/finance.invoice.line-total@^1";
finance.credit-note.calculate core
Credits all or part of an invoice with VAT at the original tax point's rates.
Credit note against an original invoice, by units or by amount, with VAT at the original tax point's rates.
import { calculateCreditNote } from "#fune/finance.credit-note.calculate@^1";
dates.recurrence optional
Dates for recurring invoices: monthly on a day, last Friday, every two weeks.
The next n dates of a repeating rule: monthly on a day, last weekday of the month, every k days or weeks, yearly.
import { recurrence } from "#fune/dates.recurrence@^1";
  • invoice-numbering Sequential, gap-free invoice numbers per series, allocated safely under concurrency.
  • pdf-invoices Rendering an invoice or statement as a PDF or printable page: caps gives the figures, not the document.

2. VAT and tax

Charge and account for VAT at the rate in force on each date of supply.

finance.tax.add-vat core Unreviewed
VAT on net prices when invoicing.
Add VAT to a net amount using the rate in force on the date of supply.
import { addVat } from "#fune/finance.tax.add-vat@^1";
finance.tax.remove-vat core Unreviewed
VAT inside gross receipts and expenses when recording them.
Extract the VAT already included in a gross amount, using the rate in force on the date of supply.
import { removeVat } from "#fune/finance.tax.remove-vat@^1";
finance.tax.vat-return core Unreviewed
The nine-box VAT return for a period from the categorised transactions.
UK nine-box VAT return totals for a period from categorised sales and purchases (VAT Notice 700/12).
import { vatReturn } from "#fune/finance.tax.vat-return@^1";
finance.tax.vat-rate core Unreviewed
The VAT rate in force for a jurisdiction, category and date; the others look it up.
Look up the VAT rate in force for a jurisdiction, category and date.
import { vatRate } from "#fune/finance.tax.vat-rate@^1";
finance.tax.vat-flat-rate optional Unreviewed
VAT due under the Flat Rate Scheme by business sector, for small businesses on it.
UK VAT Flat Rate Scheme: VAT due on gross turnover at the sector's dated rate, limited cost rate and first-year 1% off.
import { flatRateVat } from "#fune/finance.tax.vat-flat-rate@^1";

3. Ledger and journals

Keep double-entry books that always balance.

finance.ledger.journal-validate core
Refuses a journal that does not balance, has zero lines or mixes currencies.
Check a double-entry journal: debits equal credits, no zero or two-sided lines, one currency, well-formed account codes.
import { validateJournal } from "#fune/finance.ledger.journal-validate@^1";
finance.ledger.trial-balance core
Balances per account from journal lines, proving debits equal credits.
Trial balance from journal lines: debit or credit balance per account, and proof that debits equal credits.
import { trialBalance } from "#fune/finance.ledger.trial-balance@^1";
finance.accruals-prepayments optional
Spreads an invoice across periods by days of service, for accruals and prepayments.
Split an invoice across accounting periods by days of service, for accruals, prepayments and deferred income.
import { allocateToPeriods } from "#fune/finance.accruals-prepayments@^1";
finance.depreciation-straight-line optional
Straight-line depreciation schedule for fixed assets.
Straight-line depreciation schedule whose charges sum exactly to cost less residual value.
import { straightLineDepreciation } from "#fune/finance.depreciation-straight-line@^1";
finance.depreciation-reducing-balance optional
Reducing-balance depreciation schedule for fixed assets.
Reducing-balance depreciation schedule at a fixed rate, writing off to the residual value in the final year.
import { reducingBalanceDepreciation } from "#fune/finance.depreciation-reducing-balance@^1";
finance.fx-revaluation optional
Unrealised FX gain or loss on foreign-currency balances at period end.
Unrealised foreign exchange gain or loss on foreign-currency balances revalued at a period-end rate.
import { fxRevaluation } from "#fune/finance.fx-revaluation@^1";
  • chart-of-accounts A chart of accounts (nominal codes and their types) to post to: caps validates journals, it does not ship a chart.
  • period-locking Closing periods, locking posted entries and an audit trail of changes.

4. Payments and terms

Know when money is due, what discount or interest applies, and match what arrived.

finance.payment-terms-due-date core
Due date from terms like net 30, end of month or 30 days after month end.
Invoice due date from payment terms: net N days, end of month, N days after month end, day N of next month.
import { paymentDueDate } from "#fune/finance.payment-terms-due-date@^1";
finance.bank-reconcile-match core
Matches bank statement lines to ledger entries by amount, date window and reference.
Match bank statement lines to ledger entries by exact amount, a date window and reference similarity, deterministically.
import { matchBankLines } from "#fune/finance.bank-reconcile-match@^1";
finance.early-payment-discount optional
Discount available on a payment date under 2/10 net 30 style terms.
Early settlement discount available on a payment date under "2/10 net 30" style terms, and the amount to pay.
import { earlyPaymentDiscount } from "#fune/finance.early-payment-discount@^1";
finance.late-payment-interest optional
Statutory late payment interest and fixed compensation on overdue UK B2B invoices.
UK statutory late payment interest (8% over Bank Rate) and fixed compensation under the Late Payment Act 1998.
import { latePaymentInterest } from "#fune/finance.late-payment-interest@^1";
finance.uk-bank-rate optional
Bank of England base rate on a date, which late payment interest is set over.
Bank of England Bank Rate (base rate) in force on a date, from the Bank's official history since 2001.
import { ukBankRate } from "#fune/finance.uk-bank-rate@^1";
  • bank-feeds Importing bank transactions (OFX, CSV, Open Banking APIs) and normalising them into statement lines.
  • payment-collection Taking payments by card or Direct Debit through a provider (Stripe, GoCardless) and handling their webhooks.

5. Reports

Show the business where it stands.

finance.aged-debt core
Aged debtors: open invoices bucketed current, 30, 60, 90+ days overdue.
Aged debt report: open invoices bucketed as current, 1-30, 31-60, 61-90 and 90+ days overdue as at a date.
import { agedDebt } from "#fune/finance.aged-debt@^1";
finance.margin optional
Margin and markup on a sale, for profitability reports.
Profit on a sale with both the margin and the markup, so the two are never confused.
import { margin } from "#fune/finance.margin@^1";
charts.bar-chart optional
Bar chart geometry for a monthly income or expenses chart.
Complete bar chart geometry as data, grouped or stacked: plot area, axes, gridlines, bar rectangles, colours, legend.
import { barChart } from "#fune/charts.bar-chart@^1";
charts.line-chart optional
Line chart geometry for cash or revenue over time.
Complete line chart geometry as data: plot area, number or date axes, gridlines, series paths, colours, legend.
import { lineChart } from "#fune/charts.line-chart@^1";
  • financial-statements Profit and loss and balance sheet: caps gives the trial balance; mapping accounts to statement lines is yours.

6. Data entry (React)

Accessible forms for entering invoices and customers, in a React front end.

react.form.text-field optional
Labelled text inputs with hints and errors for invoice and customer fields.
A labelled single-line text input with hint, error message, autocomplete, prefix and suffix (GOV.UK text input).
import { TextField } from "#fune/react.form.text-field@^1";
react.form.date-input optional
Day, month and year boxes for invoice and due dates.
Day, month and year boxes under one legend, with hint, error message and the boxes in error marked (GOV.UK date input).
import { DateInput } from "#fune/react.form.date-input@^1";
react.form.error-summary optional
The "There is a problem" box linking to each field in error.
A "There is a problem" box linking to each field in error, focused on arrival, built from a validator's fields (GOV.UK).
import { errorSummaryItems, ErrorSummary } from "#fune/react.form.error-summary@^1";

7. Amounts from money-basics

Hold money as currency-tagged integer minor units and do plain arithmetic on it safely.

money.amount core
The Money type ({ minor, currency }) every other money capability takes and returns.
A currency-tagged monetary amount held in integer minor units.
import { money } from "#fune/money.amount@^1";
money.add core
Adds and subtracts amounts, refusing to mix currencies by accident.
Add, subtract and negate monetary amounts, refusing to mix currencies.
import { addMoney } from "#fune/money.add@^1";
money.sum core
Totals a list of amounts, with an explicit currency for an empty list.
Total a list of monetary amounts, with an explicit currency for the empty case.
import { sumMoney } from "#fune/money.sum@^1";
money.compare core
Compares, mins, maxes and clamps amounts of one currency without unwrapping them.
Compare two amounts of one currency, refusing to compare across currencies; min, max and clamp too.
import { compareMoney, minMoney, maxMoney, clampMoney } from "#fune/money.compare@^2";

8. Rates and rounding from money-basics

Apply percentages and divide amounts with the rounding stated, never a float.

money.apply-rate core
Applies a basis-point rate (VAT, discount, commission) to an amount with explicit rounding.
Apply a rate expressed in basis points to a monetary amount, with an explicit rounding mode.
import { applyRate } from "#fune/money.apply-rate@^1";
math.round-div core
Integer division with a named rounding mode, for any money maths the others do not cover.
Integer division with an explicit rounding mode, for money arithmetic that must not drift.
import { roundDiv } from "#fune/math.round-div@^1";
math.basis-points optional
Converts "12.5%" to and from basis points exactly, for rates typed in by users.
Convert a rate between percent, basis points and a plain ratio exactly, as decimal text or integer basis points.
import { convertRate, toBasisPoints, fromBasisPoints } from "#fune/math.basis-points@^2";
math.percent-change optional
Percentage change between two figures in basis points, for "up 4.2% on last month".
Percentage change from one value to another, in basis points, with an explicit rounding mode.
import { percentChange } from "#fune/math.percent-change@^1";
math.rational optional
Exact fractions for rates that must not drift across many steps.
Exact fraction arithmetic, always reduced, for rates and ratios that must not drift.
import { calculateRational, rational, addRational, subtractRational, multiplyRational, divideRational, compareRational, rationalToInteger } from "#fune/math.rational@^2";
math.round-div-big optional
Rounded division on numbers past 2^53, for very large totals.
Divide two integers of any size with an explicit rounding mode, on decimal strings, for exact money sums past 2^53.
import { roundDivBig } from "#fune/math.round-div-big@^1";

9. Splitting from money-basics

Divide an amount between people, periods or lines so the parts add back to the whole.

money.allocate core
Splits an amount by ratios (60/40, by quantity) without losing or inventing a penny.
Split an amount across ratios without losing or inventing a single minor unit.
import { allocate } from "#fune/money.allocate@^1";
money.split-even optional
Splits an amount into n near-equal parts that sum exactly, for instalments or shares.
Split an amount into n near-equal parts that add back up to exactly the whole.
import { splitEven } from "#fune/money.split-even@^1";

10. Display and input from money-basics

Show amounts to people and read what they type.

money.format core
Renders an amount as text with the currency's real number of decimal places.
Render a monetary amount as text, using the currency's real minor-unit precision.
import { formatMoney } from "#fune/money.format@^1";
money.parse optional
Reads "£1,234.50" or "1.234,50 €" typed by a user into minor units, strictly.
Parse "£1,234.50" or "1.234,50 €" into integer minor units, strictly: no guessing, no rounding.
import { parseMoney } from "#fune/money.parse@^1";
money.currency-digits optional
How many decimal places each ISO 4217 currency has, for input boxes and validation.
How many decimal places a currency's minor unit has, from the ISO 4217 list.
import { currencyDigits } from "#fune/money.currency-digits@^1";

11. Other currencies from money-basics

Convert between currencies at a rate you hold.

money.convert optional
Converts an amount at a supplied exact rate with explicit rounding.
Convert an amount to another currency at a supplied exact rate, with an explicit rounding mode.
import { convertMoney } from "#fune/money.convert@^1";

12. Calendar arithmetic from dates-basics

Move and measure ISO dates exactly, with no date library and no clock reads.

dates.add-days core
Shifts a date by whole days, for due dates and windows.
Shift an ISO date by a whole number of days, forwards or backwards, with exact calendar arithmetic.
import { addDays } from "#fune/dates.add-days@^1";
dates.add-months core
Adds months clamping to month end, so 31 January plus a month is the end of February.
Add calendar months to an ISO date, clamping to the month end: 31 Jan + 1 month is 28 or 29 Feb.
import { addMonths } from "#fune/dates.add-months@^1";
dates.days-between core
Signed calendar days between two dates, for ages of invoices and stays.
Calendar days from one ISO date to another, signed: negative when the second date is earlier.
import { daysBetween } from "#fune/dates.days-between@^1";
dates.month-boundaries core
First and last day of a date's month, for monthly periods and statements.
The first day, last day and length of the calendar month containing a date.
import { monthBoundaries } from "#fune/dates.month-boundaries@^1";
dates.day-of-week core
ISO weekday of a date, for weekend rules and opening hours.
The ISO 8601 day of the week for a date: 1 is Monday through 7 is Sunday.
import { dayOfWeek } from "#fune/dates.day-of-week@^1";
dates.is-leap-year optional
Whether a year is a leap year, for 365/366-day calculations.
Whether a year is a Gregorian leap year: every 4th, except centuries, except every 400th.
import { isLeapYear } from "#fune/dates.is-leap-year@^1";
dates.overlap optional
Days two date ranges share, for prorating and clash checks.
Days two date ranges have in common, with the end of each range stated as inclusive or exclusive.
import { overlapDays } from "#fune/dates.overlap@^1";
dates.age optional
Age in whole years on a date, handling 29 February birthdays.
Age in whole years on a given date, with 29 February birthdays ageing on 1 March in other years.
import { age } from "#fune/dates.age@^1";

13. Periods and reporting dates from dates-basics

Put dates into the periods reports are grouped by.

dates.quarter core
Calendar and fiscal quarter of a date for any year-start month.
Calendar quarter and fiscal quarter of a date, with the fiscal year and quarter date ranges, for any start month.
import { quarter } from "#fune/dates.quarter@^1";
dates.week-number optional
ISO week number and week-year, for weekly reports.
ISO 8601 week number and week-numbering year of a date: weeks start Monday, week 1 holds the first Thursday.
import { weekNumber } from "#fune/dates.week-number@^1";
dates.day-count-fraction optional
Year fraction by ACT/365F, ACT/360, 30/360 or ACT/ACT, for interest periods.
Year fraction between two dates by ACT/365F, ACT/360, 30/360, 30E/360 or ACT/ACT ISDA, as an exact fraction.
import { dayCountFraction } from "#fune/dates.day-count-fraction@^1";

14. Working days and holidays from dates-basics

Skip weekends and public holidays when counting or adding days.

dates.bank-holidays core
UK bank holidays for a region and year, as gov.uk publishes them.
UK bank and public holidays for a region and year, as published by gov.uk, including one-off holidays.
import { bankHolidays } from "#fune/dates.bank-holidays@^1";
dates.add-business-days core
Moves a date by working days, skipping weekends and a holiday list.
Move a date forward or back by working days, skipping weekends and a caller-supplied holiday list.
import { addBusinessDays } from "#fune/dates.add-business-days@^1";
dates.business-days-between core
Working days between two dates, for SLAs and notice periods.
Working days between two dates, excluding weekends and a caller-supplied holiday list.
import { businessDaysBetween } from "#fune/dates.business-days-between@^1";

15. Times and durations from dates-basics

Work with times of day, durations and timestamps.

time.duration optional
Parses and adds durations like 1h30m in whole minutes, and formats the total.
Parse durations written as 1h30m, 90m or 01:30, add them up in whole minutes, and format the total.
import { duration } from "#fune/time.duration@^1";
time.minutes-between optional
Minutes between two local date-times, across midnight.
Minutes from one local date and time to another, correct across midnight; wall-clock time, no time zones.
import { minutesBetween } from "#fune/time.minutes-between@^1";
time.round-to-increment optional
Rounds minutes to a billing increment such as 6 or 15.
Round a duration in minutes to a billing increment such as 6 or 15 minutes, up, down or to nearest.
import { roundToIncrement } from "#fune/time.round-to-increment@^1";
time.iso-to-unix optional
RFC 3339 timestamps to Unix seconds.
An RFC 3339 timestamp with a Z or offset, such as 2026-09-26T13:00:00+01:00, to Unix time in whole seconds.
import { isoToUnix } from "#fune/time.iso-to-unix@^1";
time.unix-to-iso optional
Unix seconds to an ISO 8601 UTC timestamp.
Unix time in seconds to an ISO 8601 UTC timestamp such as 2026-09-26T12:00:00Z, for years 0001 to 9999.
import { unixToIso } from "#fune/time.unix-to-iso@^1";
time.countdown optional
Seconds left until a moment and a 4:05 style timer text.
Seconds left until a Unix time, whether it has passed, and a timer text like 4:05, for tokens or retry waits.
import { countdown } from "#fune/time.countdown@^1";

16. Tax periods from uk-tax

Put dates into the UK periods tax is worked out over.

dates.uk-tax-year core
Which UK tax year (6 April to 5 April) a date falls in.
The UK personal tax year containing a date: 6 April to 5 April, with HMRC's label.
import { ukTaxYear } from "#fune/dates.uk-tax-year@^1";

17. Tax identifiers from uk-tax

Check the numbers HMRC and Companies House issue before storing them.

validation.uk-vat-number core
Checks a UK VAT registration number's checksum.
Check a UK VAT registration number against HMRC's mod-97 checksum, including the post-2010 9755 variant.
import { isUkVatNumber } from "#fune/validation.uk-vat-number@^1";
validation.uk-utr optional
Checks a Unique Taxpayer Reference's check digit.
Check an HMRC Unique Taxpayer Reference's modulus 11 check digit, accepting the K and spacing people type.
import { validateUkUtr } from "#fune/validation.uk-utr@^2";
validation.uk-company-number optional
Checks and pads a Companies House company number.
Check a Companies House company number's format and prefix (SC, NI, OC, LP...) and zero-pad it to eight characters.
import { validateUkCompanyNumber } from "#fune/validation.uk-company-number@^2";

18. Contact details from validation-basics

Check and normalise a person's contact details before storing them.

validation.email core
Is an email address plausible, by a documented subset of RFC 5322.
Is this a plausible email address? A documented, pragmatic subset of RFC 5322, not the full grammar.
import { isEmail } from "#fune/validation.email@^1";
validation.phone-e164 core
Normalises a phone number to +44… E.164 for a default country.
Normalise a phone number to E.164 (+447700900123) using a default country's trunk and dialling prefixes.
import { validatePhoneE164 } from "#fune/validation.phone-e164@^2";
validation.uk-postcode core
Validates a UK postcode and puts it in canonical form.
Validate a UK postcode and normalise it to canonical upper case with one space before the inward code.
import { validateUkPostcode } from "#fune/validation.uk-postcode@^2";
text.normalise-name optional
Tidies a typed name: spacing and title case with Mc, O' and hyphen rules.
Tidy a personal name: trim, collapse whitespace and title-case, with Mc, Mac, O', hyphen and van/de rules.
import { normaliseName } from "#fune/text.normalise-name@^1";
auth.normalise-email optional
Lower-cases an email's domain for storage and lookup.
Trim an email address and lower-case its domain for storage and lookup, or null if it is not a plausible address.
import { normaliseEmail } from "#fune/auth.normalise-email@^1";

19. Bank details from validation-basics

Check payee and customer bank details before money moves.

validation.iban core
Checks an IBAN's mod-97 checksum and its country's length.
Check an IBAN against the ISO 13616 mod-97-10 checksum and the registered length for its country.
import { isIban } from "#fune/validation.iban@^1";
validation.bic optional
Checks a SWIFT/BIC code's format and splits it.
Check the format of a SWIFT/BIC code (8 or 11 characters) and split it into its parts.
import { validateBic } from "#fune/validation.bic@^1";
validation.uk-sort-code-account optional
Checks a UK sort code and account number by the Vocalink modulus rules, against a table you supply.
Check a UK sort code and account number with the Vocalink/Pay.UK modulus rules, against a table you supply.
import { validateUkSortCodeAccount } from "#fune/validation.uk-sort-code-account@^3";
validation.uk-modulus-table optional
Parses Vocalink's VALACDOS and SCSUBTAB files into that table.
Parse Vocalink's VALACDOS.txt and SCSUBTAB.txt into the table UK sort code modulus checking needs.
import { parseUkModulusTable } from "#fune/validation.uk-modulus-table@^1";

20. Identifiers and check digits from validation-basics

Catch mistyped reference numbers by their check digits.

validation.luhn optional
Luhn mod-10 check, for card numbers and many reference numbers.
Check a digit string against the Luhn mod-10 checksum used by payment cards and many identifiers.
import { isLuhn } from "#fune/validation.luhn@^1";
validation.lei optional
Checks a Legal Entity Identifier's check digits.
Check a 20-character Legal Entity Identifier against its ISO 17442 / ISO 7064 MOD 97-10 check digits.
import { validateLei } from "#fune/validation.lei@^1";
validation.gtin optional
Checks an EAN/UPC/GTIN barcode number.
Check an EAN-8, UPC-A, EAN-13 or GTIN-14 barcode number against the GS1 mod-10 check digit.
import { validateGtin } from "#fune/validation.gtin@^1";

21. Displaying sensitive values from validation-basics

Show stored identifiers without exposing them.

text.mask optional
Masks all but the last digits of a card, account or phone number.
Mask all but the last n characters of a card, account or phone number, optionally keeping separators.
import { mask } from "#fune/text.mask@^1";

Gaps

What this kind of app usually needs that Functional Weave does not have yet: write these yourself, or use a service.

  • multi-user-access Users, roles and permissions for a practice and its clients: see the auth suite for passwords and tokens; roles are yours.
  • fx-rate-source Where exchange rates come from: money.convert takes a rate you supply; fetching and storing daily rates (ECB, a provider's API) is yours.
  • money-storage Storing amounts: the database columns (integer minor units plus a currency code) and their migrations are yours.
  • time-zones Time zones and daylight saving: caps works in ISO dates, UTC and wall-clock times; converting with the IANA tz rules is yours.
  • locale-formatting Showing dates in a user's language and format ("3 octobre 2026"): caps returns ISO dates, not localised text.
  • non-uk-holidays Public holidays outside the UK: dates.bank-holidays is UK only; pass another country's list to the working-day functions yourself.
  • mtd-submission Making Tax Digital: submitting the return to HMRC's VAT API (OAuth, fraud-prevention headers, obligations) is yours; caps gives the nine boxes.
  • corporation-tax Corporation tax and self-assessment income tax computations are not in caps.
  • vat-number-lookup Checking that a VAT number is registered (HMRC's lookup API) is network work; caps checks the checksum only.
  • address-lookup Turning a postcode into a list of addresses (Royal Mail PAF or a lookup API) is network work and not in caps.
  • email-deliverability Whether a mailbox exists (MX lookups, a confirmation email): validation.email checks the syntax only.
  • vocalink-data The Vocalink modulus tables themselves: download them from Pay.UK under its terms; caps parses them but does not ship them.