v5.10.2 26 February 2026 Improvement

Currency Boundary Consolidation & Custom Linter

Custom Linter

SpeyBooks now has a bespoke Python linter enforcing the principle that the frontend is a view layer — all monetary calculations belong on the backend. The linter runs across both frontend and backend codebases with distinct rule sets.

  • Frontend rules — detects client-side monetary aggregation, VAT calculations, debit/credit arithmetic, rounding on currency values, and inline pounds-to-pence conversions.
  • Backend rules — enforces Row Level Security (all queries via request.dbClient, never pool.query), structured error handling, and logging via Fastify’s request logger.
  • Universal rules — catches hardcoded secrets, console logging, and overly broad exception handlers.
  • Inline suppression// lint-ignore FE003 on the preceding or same line, with file-level // lint-ignore-file for generated files and sanctioned utilities.

The linter integrates into CI with exit code 1 on any unsuppressed error.

Currency Boundary Consolidation

All inline pounds-to-pence conversions (Math.round(parseFloat(x) * 100)) and pence-to-pounds display formatting ((pence / 100).toFixed(2)) have been replaced with two centralised utility functions.

  • toPence() — the single sanctioned conversion from form input (pounds) to integer pence, used at every form submission boundary.
  • penceToPounds() — converts integer pence back to a pounds string for form input pre-fill, without a currency symbol.
  • formatMoney() — existing utility now used consistently across all toast messages and display contexts.

This eliminates 16 scattered conversion patterns across 9 files and ensures every monetary boundary goes through tested, lint-enforced code.

API Client Correction

  • invoicesApi.recordPayment() — now accepts pence directly, matching the API contract. The pounds-to-pence conversion moved to the form boundary where it belongs.

Bug Fixes

  • Fixed parseInt() used instead of parseFloat() for refund amounts in the admin panel — partial-pound refunds were silently truncated.

Known Issues

  • Invoice and quote edit modes still compute VAT client-side for live preview. Suppressed with documented rationale. The production fix requires wiring useDocumentPreview into edit modes and creating a /quotes/preview endpoint. Scheduled for a future release.