Ledgerline
Double-entry books, invoicing, AI document capture, a CRM and a practice
dashboard for Cyprus accountants and the companies they keep books for. It
runs on this laptop: Next.js 16 with an embedded Postgres (PGlite) in
.data/ledgerline, and no sign-in yet (one fixed demo user). The same code
runs on Supabase: see Going live on Supabase. Every company,
person and figure in it is fictional.
Start it
npm install # once
cp .env.example .env.local # once, then put your key in it: ANTHROPIC_API_KEY=sk-ant-…
npm run demo:reset # fresh demo data, build if needed, open http://localhost:3000
npm run demo # the same, keeping whatever you did last time
npm run demo -- --build # rebuild after changing code
No key yet? LEDGERLINE_AI_FIXTURES=1 npm run demo runs rehearsal mode:
the four files in demo-files/ use readings recorded earlier, and Capture says
"Rehearsal mode" in a yellow band. Explain the figures and Ask the books need
the real key. Don't run rehearsal mode in the meeting.
Before the meeting: the key is in .env.local and was tested live on
22 Oct: the four demo files read in parallel in about 20 seconds, Explain the
figures takes about 20 seconds and Ask the books about 10. Run
npm run demo:reset so the meeting starts clean.
The demo, step by step (about 30 minutes)
The practice is Saltlake Accounting, a fictional Larnaca firm with six
client companies, books from July 2025 to today, and October's bank lines left
to reconcile.
1. Practice dashboard (/), 2 minutes
- "This is the practice, not one company." Six clients on one screen.
- Point at Aegean Blue Marine: its Jul–Sep VAT return is due 10 Nov, 19
days away, not filed, about €9,800 to pay. Cyprus quarters are staggered, so
each client has its own due date; two clients aren't VAT registered (the
dental clinic makes exempt supplies, the holding company only holds).
- 81 bank lines to reconcile, 3 documents waiting to review, overdue
receivables per client, where each client's books are locked, last activity.
- Every figure is a link into that client's books.
1a. Who sees what (/sign-in), 1 minute
- Top right, your name → Sign in as someone else: the demo's people.
Nikos Savva owns Pixelforge: he lands on its books, with no practice
pages and no other client; Pixelforge's AML file isn't his to see. The
database decides this, not the screens (row-level security).
- Back as Maria: Practice settings → People → Invite someone. The link
is shown once; open it in a private window to join as the new person.
1b. A company's Home, 1 minute
- Open Larnaka Roast Coffee. One company at a time: the switcher at the
top of the menu changes it and keeps the section you are in.
- The Home is for the person who runs the company: six large buttons (Raise an
invoice, Record a bill, Upload a receipt, Money in, Money out, Write a
quote), three figures (in the bank, customers owe you, you owe suppliers),
one list of what needs attention, and the to-dos. The menu runs the way the
owner works: CRM (enquiries, customers, quotes, invoices, products,
jobs, tasks, calendar, each named the company's way), then Money
(bank, receipts, bills, suppliers), then Reports & tax; the
accountant's pages are under More.
- ⌘K (Ctrl+K, or /) from any page opens the command palette. Before
anything is typed it offers what is usually done next (New invoice, Record
a bill, Upload receipts…, in the company's own words: New patient at a
clinic). Type and it finds actions, pages, companies by name, HE or VAT
number, and the company's records: customers, invoices by number or
customer, bills, quotes, suppliers, products, enquiries, jobs and
employees, with their dates and amounts. Enter opens the one highlighted.
At the practice it searches every client's records.
1b½. On a phone, 2 minutes
- Open http://localhost:3000 on a phone (or a narrow window) and sign in as
Nikos Savva. A bar at the foot of the screen: Home, Invoices, +,
Bank, Menu (the practice gets Dashboard, Deadlines and Clients).
- + opens what is done on the move: Snap a receipt opens the camera,
and the photo is stored and read by Claude while the owner carries on; a
note at the foot of the screen says when it is drafted as a bill, with
Review. Or a new invoice, a bill, money in or out, a quote, a customer.
- The Home keeps to the two actions done most, the three figures and the
first five things that need attention (Show all for the rest).
- Add to Home Screen (Safari's Share menu, or Chrome's Install) puts
Ledgerline on the phone as an app with its own icon and window
(
src/app/manifest.ts, icons drawn in src/app/icons).
- Losing the connection shows a notice, and what was done meanwhile (a page
opened, a form saved) waits and goes as soon as it is back
(
experimental.useOffline).
1b¾. In Greek, 2 minutes
- Sign in as someone else → Dr Sofia Hadjioannou, who owns Kalamies
Dental and reads Ledgerline in Greek. The menu, Home, invoices, receipts,
the command palette (type τιμολ), the phone's bar and +, and the
messages at the foot of the screen are in Greek; so are the figures and
dates (66.082,73 €, 28 Οκτ 2026, πριν από 3 ημέρες) and the deadlines
(Προσωρινή φορολογία 2026, πρώτη δόση). The clinic's own words come
through too: Ασθενείς και ασφαλιστικές.
- Each person chooses: the account menu, top right → Γλώσσα: English
or Ελληνικά. It is theirs alone, wherever they sign in; the practice
can read the same company in English.
- An amount is typed either way: 180,50 is a hundred and eighty and a half,
as are 180.50 and 1.234,50 / 1,234.50 for the thousands.
- What goes to a customer follows the customer's language, not the screen's:
the invoice by email, a payment reminder and the words before a shared
link are in Greek for a customer whose documents are (Kalamies' are), in
English for one whose are in English, in both for both.
- The accountant's pages (reports, journals, the chart, the practice's own)
stay in English for now.
npm run check:greek lists any phrase on a
translated screen that is missing its Greek.
1c. Everyday work, 6 minutes
- Raise an invoice: choose a customer (or + New customer… and type a
name). The number follows on (
LRC-2026-0129), the due date comes from the
company's terms (14 days here), and VAT follows the customer: standard for a
Cyprus customer, reverse charge for an EU business, exempt if the company
isn't registered.
- Lines with products: type "House espresso blend" in the description and
the product fills in its price, VAT rate (5%) and income account. Set the
quantity (20 kg), add a second product (Coffee service, 9%) and a third
with Give a discount (10%). The totals show VAT at each rate. The
arithmetic is exact: the database works every line out again when it saves.
- Save and issue: "Issued and in the books". View PDF is what the
customer receives: quantities and units, unit prices, the discount, VAT by
rate, the company's registration, how to pay. How it posted shows the
double entry.
- Send: the covering email is written for you (the amount, the due date,
the bank details and reference); Open in email opens it in your email
app, ready to attach the PDF, and the invoice is marked as sent. Invoices
not yet sent are listed under Not sent and on the Home.
- Share as a link (on an issued invoice, or a quote): a private link the
customer opens without signing in, on their phone. They see what is owed
and by when, the lines and VAT, the IBAN, BIC and reference to copy into
their banking app, and Download PDF; nothing else of the books. Send it
with WhatsApp (to the customer's own number when they have one),
Viber, Text or Email: the message is written, with nothing to
attach, and the invoice counts as sent. The panel says when they opened it
("Opened once, last 2 minutes ago"); See what they see previews it
without counting, and Stop the link shuts it at once. The page wears the
company's logo and colour.
- In Greek, or both: Company settings → How your documents look →
Language (English, Ελληνικά, or both, Greek first), and any customer
can have its own (Documents in, on the customer). The PDF is then
written as Cypriot documents are: ΤΙΜΟΛΟΓΙΟ / INVOICE, Αρ. ΦΠΑ / VAT, ΑΦΤ /
TIC, the payment instructions and the VAT wording in Greek (the reverse
charge under άρθρο 196 της Οδηγίας 2006/112/ΕΚ), Greek capitals without
their accents, and in Greek alone amounts and dates the Greek way
(4.055,00 €, 10 Οκτ 2026); in both, dates as 26/11/2026. Kalamies Dental's
documents are in Greek and Troodos Gold's in both. A shared link opens in
Greek, with a tap away.
1d. Buying and paying, 3 minutes
- Bills: tick the overdue bills (Highland Green, Troodos Dairy, Ermou
Street) and Pay them: choose the account and the day, and each bill is
paid in full in the books. The payment run then offers Download the SEPA
file: one ISO 20022 file (pain.001.001.03) to upload to the company's
online banking, which sends all the transfers on that day. Suppliers without
an IBAN are listed to pay another way. Checked on 22 Oct against the ISO
schema. When the payments show on the statement, they match in one press.
- A supplier's credit note: the October Highland Green bill already has
one (two damaged sacks), taken off what is still to pay. On any bill, More
→ Supplier credit note starts one from the bill's lines; it lowers the
cost and the VAT reclaimed (box 4), and credit left over is used against
their next bill or recorded as refunded into the bank.
- Suppliers carry an IBAN and BIC, checked as banks check them, an
address, and their payment terms (a bill with no due date takes them).
- A supplier's statement (Highland Green → Statement): every bill,
payment, credit note and refund in date order with the running balance, for
any period (Everything gives the whole history, newest first if asked),
or Open items on a day with how late each is. PDF and CSV; it
agrees with the supplier page and with aged payables, as it stood on the
day. Xero has no supplier statement at all.
- Check their statement (Troodos Dairy): type the balance their statement
shows on its date beside ours; untick a payment of ours still on its way,
tick a bill we dated after it, add one of theirs not in the books. At
nothing left to explain it says Agreed. Kept with their statement (a PDF
or a photo), on the supplier page and the audit trail. Neither Xero nor Sage
does this without an add-on.
- Remittance advice: each payment, and each supplier in a payment run,
has one as a PDF in the company's style, with the email written for you
(Sage's can't be changed, and Xero's don't take its themes). Payment
runs lists every run. Aged payables runs as at any day, credit not yet
used coming off; each supplier opens its open items that day.
1d½. How documents look (Company settings → Documents), 2 minutes
- Larnaka Roast's Standard style: classic, its brown, a large logo, a
payment slip to cut off, its own terms. Change opens the editor: layout
(classic, modern, plain), sans or serif (Greek in both), size, A4 or US
Letter, the logo left, centre or right and its size, headed paper, what
shows, and each document's title ("Tax invoice") and terms. The preview
beside it redraws the company's own latest invoice, quote, credit note,
statement or remittance as it changes; nothing is kept until it is saved.
- Add a style, then give it to one customer on their page (Document
style): their invoices, quotes, credit notes and statements take it, and
everyone else keeps the default.
1e. Bringing a client's lists in, 1 minute
- Customers → Import (also Suppliers and Products): choose a spreadsheet
saved as CSV, or paste it. The columns are recognised from their headings,
in English or Greek, and can be changed; every row is checked and shown
before anything is saved, and anyone already there is left alone. There is a
template to download. Export gives each list back as a spreadsheet, and
Invoices → Export all every invoice with what was paid and credited.
1f. The CRM, shaped to each business, 4 minutes
- Every company is set up from its kind of business, which the accountant
picks when taking the client on: what it calls its customers and its work,
the stages its enquiries and its work go through, the details it keeps, and
which parts it uses. Switch company and watch the menu change:
- Aegean Blue Marine (shipping): Clients, Enquiries, and Jobs that
go Planned → In port → Final account → Closed, each with its vessel, port
and ETA on the card.
- Kalamies Dental (clinic): Patients and insurers, and Treatment
plans (Proposed → Accepted → In treatment → Completed), with who pays.
- Larnaka Roast (café and roastery): Orders, Received → Roasting →
Ready → Delivered, collection or delivery on the card.
- Troodos Gold (producer): Leads and Orders; Pixelforge
(software studio): Deals and Projects with their fee basis;
Mesogeios Holdings: only Opportunities and tasks, no quotes or
jobs; Thames Edge Joinery: Jobs with the site address, opening in
maps.
- The board: drag a card from stage to stage. Drop a deal on Won and it
becomes a customer with its first job and first to-do (for a port agent,
"Send the proforma disbursement account"); drop it on Lost and it asks
why. Each column shows its value and how likely it is; the header, the
pipeline's value and the likely part of it.
- Open any deal or customer: Call, WhatsApp, Viber or Email with
the message started, and Save contact to the phone. Plain links: nothing
to connect.
- Customise (under More): rename anything ("Jobs" to "Shipments"), add a
stage, turn a part off, or add a detail of its own ("Pallets", on every
card). Set by the company's owner or its accountant; its staff use it as
set, and never see payroll.
- Tasks: everything to do, each with an owner and a date.
- Website form (Enquiries → Website form): Open the form shows the
company's own form, in its logo and colour. Send one as a prospect, then go
back to the board: it is in New, marked "From your website", owned by
whoever had the fewest open enquiries, with a call-back task due the next
working day. The same page has the snippet for the company's own site and
the API call (
POST /api/v1/enquiries); retires the old
one at once. Enquiries nobody has answered for a day rise to the top of the
board in red, and show on the company's Home.
1g. Deadlines (/deadlines), 2 minutes
- Every client's VAT returns, VIES statements, provisional tax, company tax
returns and the balance of tax; payroll returns each month and the
employer's annual TD7; the financial statements and the annual return
(HE32) for the Registrar. All worked out from each company's VAT quarters,
tax and financial years, incorporation date and the services the practice
provides, with who looks after it. Nobody typed a deadline in.
- An official extension moves a date and says so: Kalamies' 2025 employer's
return shows Extended from 31 May 2026 by the Tax Department, with the
announcement linked.
- Overdue: Kalamies Dental's first provisional tax instalment slipped.
It is also on Kalamies' Home. Mark done closes it.
- A VAT return closes itself when it is filed: file one from Open the
return and it shows as Done. A VAT return still to file says how ready it
is: bank lines in the quarter to match, documents to check.
- Inside a company, Deadlines (under Reports & tax) shows only its own. Who
looks after a company is set in its Company settings.
1h. Time and fees (/time, /fees), 3 minutes
- Start timer in the top bar, on any page: it picks the company you are
in. It keeps running as you move around, and stops in one press.
- Time: each person's week against their hours (Sofia is 2 hours over),
the lighter part of each bar being time on clients. Add time worked
without the timer; mark it Extra to bill it on top of the fee.
- Fees: each client's monthly fee against the hours its books took, as
euros per hour, with the €40 floor as a dashed line. Kalamies Dental earns
€32 an hour: reprice it. Raise October's fees invoices every client in
one press from the firm's own books (Saltlake Accounting Ltd, under
"Your firm's own books" in the company switcher), extra work included at
each client's rate. Press again and nobody is billed twice.
1i. Taking on a client (/clients), 4 minutes
- Client files: every client's due diligence as ICPAC's AML Directive
asks for it: risk rating, next review, and screening against the EU
sanctions list. Mesogeios' review is a week overdue (it is on Deadlines
too); Kalamies' is due in three weeks.
- Kypros Ferry Lines, won from the firm's own website form, is half way
through being taken on. Send the engagement letter: it is written from
the file (services, fee, the app) and fingerprinted. Copy the client's link
and open it in a private window, or on a phone: no login. Sign it, and
upload Alexander Petrov's proof of address (any PDF). Back on the file both
are ticked, and the signed PDF carries the signature record.
- Screen everyone: the company and both owners are checked against the
real EU consolidated list (6,269 people and entities). Alexander Petrov
shares his name with two listed persons, born in 1958 and 1979: write why
he is not them and Record the decision.
- Risk assessment: Ledgerline scores each factor from the file, as in
ICPAC's guidance. The 40% owner's nationality is on the EU high-risk list,
and an advocate introduced them: 9 of 20, normal risk, customer due
diligence, reviewed every two years. Record the assessment, then
Approve as compliance officer. A PEP, or an owner living in a
high-risk country, makes it high risk whatever the score, approved by a
partner.
- Take them on: open the books: the company opens with the standard
chart, its VAT quarters and year end, its deadlines, its fee on the Fees
page, and a customer record in the firm's own books. Every step is on the
firm's audit trail, the signature as the client's.
1i½. Bringing a practice's clients over (/clients/import), 2 minutes
- Client files → Bring clients over: the practice's client list from its
own spreadsheet (CSV): company name, country, registration and VAT
numbers, TIC, incorporation date, year end, VAT quarter, what it does, who
looks after it, its fee and the month its books start in Ledgerline. The
columns are recognised from their headings, in English or Greek.
- Every row is checked before anything is saved: a bad HE number or VAT
check digit is caught, a company already a client is left alone, and each
shows the kind of business its Ledgerline will be shaped to (from the
column, or from what it does).
- Bring over: every company's books open at once, in its country's terms
and shaped to its kind of business, with its fee in the practice's books.
Deadlines start from the month its books start, so nothing earlier shows as
overdue. Each client file says it was brought over: the engagement letter
and due diligence are recorded as they come up for review. 300 clients
import in about 7 seconds.
- Next: invite their owners (
/clients/owners, also Owners on
Ledgerline on Client files): every client's owner, at the contact on its
file, as joined, invited or still to invite. Tick them (all are ticked) and
Invite: Ledgerline makes the links and sends nothing itself. The
practice sends them from its own email: Open in email for each person,
or Download for a mail merge, a CSV with each person's message ready
for Outlook, Word or Gmail. Someone who runs several of the clients (a
holding company and its trading company) gets one link that joins them to
all of them. A new link replaces the one sent before; 100 invitations take
under half a second.
- The owner opens the link, makes their account, and lands on their
company's Home with a welcome and Getting started: their logo on the
invoices, their customers (patients at a clinic), the first invoice, a
receipt, the bank statement. Each step ticks itself off when the books show
it, and Hide puts the list away for that person. The practice sees the
same steps on a new client's Home, with inviting the owner first; they show
for a company's first 90 days.
1j. VAT numbers (/vat-numbers), 2 minutes
- A sale to a business in another EU state is zero-rated only if the
customer's VAT number is valid in VIES, the European Commission's
register; the tax office asks for the proof. This page checks any EU number
live: type
IE6388047V and VIES answers "Google Ireland Limited" with its
address. Shape and check digit are checked as you type, for all 27 states
and Northern Ireland, so a mistyped digit is caught before VIES is asked.
- The registers strip is live: member states take theirs down for
maintenance, and the page says which (Greece was down on 22 Oct).
- Every EU customer and supplier across all the clients, with the last
check: Pixelforge's Nordlicht confirmed last week (Germany discloses no
name), Brabant last confirmed in June so due again, Atelier Lumière never.
Check every number now re-checks them all and keeps every answer.
- On Nimbus Cloud Services (Pixelforge's suppliers), Check in VIES:
the number is registered to "Stratocumulus Hosting B.V.", and the page says
so. The same check sits on each EU customer, and on every reverse-charge
invoice; the VIES statement has a "Confirmed in VIES" column, and Pixelforge's
Home lists the EU customers to confirm.
- Every check is kept, on the company's audit trail, with Copy as
evidence. The client companies are invented, so their answers come from
a demo register and say "Demo answer"; any real number goes to the real
VIES. Saving a customer or supplier with a mistyped EU number is refused.
1k. Year end and audit (a company's Year end, under Reports & tax), 3 minutes
- Kalamies Dental: 2025's turnover and total assets, and 2024's, are within
the limits, so its accounts may be reviewed (ISRE 2400) instead of
audited, and the company has chosen that. The page shows why, figure by
figure. Its 2026 year began before 6 February 2026, so the old €200,000
turnover limit still applies to it; the €300,000 limit starts with 2027.
- Larnaka Roast: 2024's turnover was over the limit, so 2025 must be
audited. The steps run in the order the law sets, each with its due date.
The annual general meeting is past its date (15 months after the last one)
and shows in red. Record dates: a report dated before the board approved
the accounts is refused (ISA 700).
- Before it goes to the auditor: the trial balance, the lock date, bank
lines, VAT returns, the manual journals an auditor will ask about (ISA 240;
opening balances, agreed to last year's accounts under ISA 510) and the audit
trail.
- Download the audit file: one ZIP of CSVs (trial balance, P&L, balance
sheet, general ledger, journal, manual journals, receivables and payables at
the year end, bank, VAT returns, the audit trail), and a read-me with the
checks and each file's SHA-256.
- Troodos Gold is audited by the practice itself: the page says what the
IESBA Code (section 601) asks of a firm that audits books it keeps.
- People (under More): invite the auditor with the role Auditor. They
read the books and take the audit file; the database refuses any change.
1l. Calendar (/calendar, and Calendar in each company), 2 minutes
- What is due, on its day: month, week or agenda. Across all clients it opens
on the filing deadlines; Everything adds each client's tasks, invoices
falling due (one line per client per day) and quotes running out. Pick a
client to see that one alone; inside a company it is that company's.
- Agenda: what is overdue first (Kalamies' slipped provisional tax,
Mesogeios' AML review), then each day.
- Subscribe in your calendar: a private link for Google, Outlook or Apple
Calendar showing what the page shows, given once; making a new one stops the
old. On the laptop, Apple Calendar can read it; Google and Outlook read from
their own servers, so they need Ledgerline online.
1m. Two UK clients, 3 minutes
- Each company has its own country: its currency, chart of accounts, VAT
rates, VAT return and deadlines follow it. Switch to Thames Edge Joinery
(Reading): pounds, the UK chart, VAT at 20%, 5% or 0%.
- It is a joinery subcontractor: its sales to contractors are under the CIS
domestic reverse charge (the invoice says "customer to pay the VAT to HMRC"),
and it buys in labour the same way. VAT returns: HMRC's nine boxes, the
construction reverse charge in boxes 1 and 4, and a repayment each quarter.
- Record a bill from Wren Site Labour: the reverse charge is a choice,
because each kind fills different boxes (services from abroad, construction,
imports under postponed VAT accounting).
- Willow & Wren Design (Bristol) is on the Flat Rate Scheme at 12%: its
return is 12% of the turnover with its VAT, less VAT on the one computer over
£2,000. Mark as filed: the rest of the VAT charged moves to account 265,
Flat Rate Scheme surplus. Its Company settings hold the scheme.
- Their Deadlines are HMRC's and Companies House's; their Year end
tests the small-company audit exemption. Submitting to HMRC through Making
Tax Digital is the next step, built against HMRC's sandbox first.
1n. Payroll (a company's Payroll, under Money), 3 minutes
- Aegean Blue Marine: four employees, each with their salary, tax and
social insurance numbers, and what the previous payroll paid them from
January to September, brought forward so this year's tax is right from the
first month in Ledgerline. Nikos Charalambous declared two children on
TD59.
- Run October 2026's payroll: every payslip worked out as a draft. Social
insurance 8.8% each way up to the month's maximum, GESY 2.65% and 2.9%, the
employer's funds, and income tax: the year's tax at today's pay, less what
has been withheld. To pay by 30 Nov 2026 says what goes to Social
Insurance Services, what to the Tax Department with the monthly TD7, and
what to the employees.
- Overtime, a bonus or commission this month: add €250 for Andri Ioannou
and the month is worked out again, tax included.
- Approve and post October 2026: one entry in the books on the pay date
(wages and contributions against what is owed to each), and the month is
fixed. Then Payslips (a PDF, a page each, with the year to date),
Salary payments file (SEPA, marked as salaries, to upload to the bank)
and TD7 and Social Insurance figures (a CSV, employee by employee).
- December pays the 13th salary on its own: insurable only up to what is left
of the year's maximum. A UK company has no Payroll in its menu yet.
2. AI capture (/capture), 4 minutes
- Drag all four files from
demo-files/ in at once:
01-greek-marine-fuel-invoice.pdf: Greek, addressed to ΑΙΓΑΙΟ ΜΠΛΟΥ…
02-two-invoices-in-one-pdf.pdf: two invoices for two different clients
03-photographed-print-shop-invoice.jpg: a phone photo, in Greek
04-eu-reverse-charge-invoice.pdf: a Dutch supplier charging no VAT
- Watch each row read in parallel. Then: five invoices found from four files,
each routed to its client by the customer's VAT number, Greek
transliterated, supplier matched (or new), accounts suggested from that
client's own chart.
- Open Review on the Nimbus invoice: the six checks (lines add up, VAT per
rate, net + VAT = total, Cyprus rates, supplier VAT number, not a duplicate)
are Ledgerline's own arithmetic, not the AI's word. The reverse charge is
detected: VAT due and reclaimed, boxes 1, 4, 7 and 11B.
- Show the document beside the extraction. Change a client with Move here
to show the draft re-making itself in another company's books.
- Post all: every bill posts to the right company's ledger. Open one from
"Posted from capture" and scroll to How it posted: the double entry.
3. Bank reconciliation, 3 minutes
- Dashboard → Aegean Blue's bank lines (or Bank in its books).
- "The bank should show" agrees with "The bank says": the sum explains itself.
- 18 of 19 October lines have a sure match: the bank's text names the
invoice or bill and the amount agrees, or it is a card payment coded the same way
many times before. Match all 18: one press.
- The one left is a part payment (Blue Horizon paid half an invoice):
press the arrow, Invoices, take the half; the invoice stays open for the rest.
- Bulk coding: tick several lines, pick an account and VAT rate, Code all.
- Import: Import statements →
demo-files/05-aegean-blue-boc-statement-21-oct.csv
(a Bank of Cyprus-style CSV). Import it a second time: nothing doubles.
- A year's statements at once: choose or drop any number of files (CSV,
CAMT.053, MT940 or OFX). They line up in date order, each with its opening
and closing balance, and each is checked against the one before it (and the
first against the last one already imported): one that doesn't follow on
says so, as does a month with nothing, before anything is saved. A file for
another account (its IBAN not this one's) is left out, as are lines in
another currency, pending or declined payments and card holds. Airwallex's
Balance Activity Report is read by its documented columns, and the headings
Revolut, Wise, Starling, Monzo, Lloyds and NatWest are known to use are
recognised (still to be checked against real exports); a CSV whose balances
move the other way to its amounts says money in and out look the wrong way
round. They
import together, all or none, each its own import to undo (a long one's
parts as one).
- Invoice money received: payments in with nothing in the books for them
(a business moving over from its bank statements, say) become invoices in
one go: to the payer as customer (one already kept when its name is in the
bank's words, "ACME LTD INV 1043" and "Acme Limited" alike), for what was
sold, with the VAT the invoice form would charge that customer worked back
from the total, in a number series of their own (
TG-R2026-0001), and
matched to the payment, so each is paid. Only payments from customers
already kept start ticked. One with a note starts unticked and is matched
or coded on the reconcile page instead: it looks like it pays an invoice
already raised, is already in the books, comes from a customer who owes
money, is there twice, was coded otherwise before, or isn't a sale
(interest, a loan, a refund, tax, card takings, a transfer, the company's
own money). One line: its arrow → Raise an invoice. Undo on such an
invoice voids it and puts its payment back.
4. Reports, 3 minutes
- VAT return (Reports → VAT return): the Jul–Sep return is on screen,
box by box, due 10 Nov. Returns are filed in the Tax Department's own
portal, Tax For All (https://taxforall.mof.gov.cy, signed in to with CY
Login), which has no way in but its own screens: so beside every box is a
copy button that gives the amount as Tax For All takes it (2068.79), and
File it on Tax For All, step by step says what to do in order, with
Copy every box for the file. Type Tax For All's reference beside
Mark as filed and lock the period: the lock date moves to 30 Sep and
nothing in the quarter can change any more (the database refuses, whatever
the route); the return keeps the boxes as filed and the reference, and the
audit trail records both. Back on the dashboard the return shows as filed.
- Pixelforge Studio VAT return: services to EU clients in box 8B, the
reverse charge on its Dutch cloud supplier in 1, 4, 7 and 11B. Its VIES
statement (Reports) lists the same sales by customer VAT number, as the
recapitulative statement asks for them, and says that it agrees with 8B.
- P&L (try "month by month"), balance sheet, trial balance, general ledger,
cash flow: one ledger, so they always agree. Every figure drills down.
- Reports → Business overview: twelve months of charts, the books health
checks, Explain the figures (Claude reads totals only, never a name) and
Ask the books, e.g. "What did we spend on fuel this year, month by month?"
- More → Journals: the entries written by hand (opening balances,
accruals, corrections). Open one to edit it, remove it, or Reverse it:
every debit becomes a credit, dated the first of the next month. A journal
inside a locked period can only be reversed.
- Audit trail: every posting, match, lock and filing on a SHA-256 hash
chain, with who did it; "The chain is intact" is recomputed on every view.
5. White label (Practice settings), 1 minute
- Type the accountant's firm name and initials, pick their colour: the page
takes the colour as it is picked. Save the brand: every screen is now
their product, with "Powered by Ledgerline" in the corner.
6. Ledgerline compared (/compare), 2 minutes
- Open it from Powered by Ledgerline at the bottom of the menu.
- One page against Balabook, from its public site (checked 20 Oct 2026).
"Not stated" means its pages don't say, not that it lacks it.
Cyprus rules Ledgerline follows (checked 26 October 2026)
The dates and tests are in src/lib/domain (deadlines.ts, vat.ts,
extensions.ts, audit.ts), and npm run check:rules checks each one.
- Company tax return (IR4): for tax years to 2025, 31 March two years on;
from tax year 2026, 31 January two years on (13 months), with the balance of
tax. For years to 2025 the balance is paid by 1 August of the following year.
Provisional tax: 31 July and 31 December. Corporation tax is 15% from 2026.
- VAT: the return and payment by the 10th of the second month after the
quarter; VIES by the 15th of the month after a month with EU services.
- Payroll: the monthly TD7, Social Insurance, GESY and tax withheld by the
end of the following month; the employer's TD7 by 31 May of the next year.
- What payroll works out (
payroll-cy.ts, checked 27 October 2026):
social insurance 8.8% from the employee and 8.8% from the employer on
earnings up to the month's maximum (€5,551 in 2025, €5,742 in 2026), and on
the same earnings the Redundancy Fund (1.2%) and the Human Resource
Development fund (0.5%); GESY 2.65% and 2.9% on all pay up to €180,000 a
year; the Social Cohesion Fund, 2% on all pay. A 13th salary is declared on
its own, insurable only up to what is left of the year's maximum. Income
tax from 2026: nothing to €22,000, then 20%, 25% (from €32,000), 30% (from
€42,000) and 35% (over €72,000); 2025's bands start at €19,500. Social
insurance and GESY are deducted up to a fifth of the pay, and so is what the
employee declares on TD59. The Central Holiday Fund is left out: most
employers are exempt and run their own leave.
- Official extensions (
extensions.ts, kept from the announcements): 2024
company tax returns and the 2025 employer's return to 30 November 2026; the
VAT return for the quarter to 30 November 2025 and December 2025's VIES to
20 January 2026; the quarter to 30 June 2026 and July 2026's VIES to
20 August 2026.
- Registrar (Companies Law, Cap. 113): financial statements within 12
months of the year end (s.142(2)); the annual return (HE32) made up to the
day after 18 months from incorporation and each year on, filed within 28
days (s.120); an AGM every year, at most 15 months apart (s.125).
- Audit or review (s.152A): every company is audited, except that a private
company within the limits in two consecutive years may choose a review under
ISRE 2400. The limits are turnover (with rent, interest, dividends and
royalties) up to €200,000 for years beginning before 6 February 2026 and
€300,000 from then, and total assets up to €500,000. A review is not open to
a group that consolidates, nor to a company regulated by the Central Bank,
CySEC or the Superintendent of Insurance, or holding a qualifying stake in one.
- Size (s.141A): micro, small and medium at €450k/€900k/10,
€5m/€10m/50 and €25m/€50m/250 (total assets, turnover, employees), with at
most one limit exceeded.
- Framework: IFRS as adopted by the EU. A small company may use the
Financial Reporting Standard for small-sized entities (s.142(1)(α1)) once
ICPAC issues it; it has not yet.
Sources: Cap. 113, consolidated, on CyLaw;
PwC Tax Summaries, Cyprus tax administration;
EY, Cyprus enacts major tax reform legislation;
ICPAC's financial reporting standards-setting committee;
PwC Tax Summaries, Cyprus: personal income tax,
deductions and
other taxes;
KPMG Cyprus, maximum insurable earnings for 2026;
and, for each extension, the announcement linked in src/lib/domain/extensions.ts.
UK rules Ledgerline follows (checked 27 October 2026)
In src/lib/domain (regimes.ts, vat-uk.ts, deadlines.ts, audit.ts,
eu-vat.ts) and uk_vat_figures in the schema; npm run check:rules checks
each one.
- VAT: 20%, 5% and 0%, exempt and outside the scope. The return and its
payment 1 calendar month and 7 days after the period; under annual
accounting, 2 months after the year, with nine payments on account of 10% or
three of 25%.
- The nine boxes: reverse charges in boxes 1 and 4: services bought from
abroad (and in box 6 and 7), construction services under the CIS domestic
reverse charge, and imports under postponed VAT accounting (and in box 7).
Credit notes come off boxes 1 and 4. Wages, PAYE and NIC, business rates and
dividends are never on a return. Boxes 2, 8 and 9 (Northern Ireland's goods
trade with the EU) are not handled yet.
- Flat Rate Scheme: the rate times VAT-inclusive turnover; only single
capital purchases of £2,000 or more with VAT are reclaimed; box 6 includes
VAT. It cannot be combined with the Cash Accounting Scheme, but has its own
cash-based turnover; it can be combined with annual accounting.
- Cash accounting: VAT when customers pay and suppliers are paid, each
payment split between net and VAT as its document was.
- Companies House and HMRC: annual accounts 9 months after the year end
(the first, for a first period over a year, the later of 21 months from
incorporation and 3 months after it); Corporation Tax 9 months and a day
after; the Company Tax Return (CT600) 12 months after; the confirmation
statement within 14 days of each anniversary. A deadline counted from a month
end runs to a month end (Companies Act 2006, s.443).
- Audit exemption: a small company, within two of turnover £15m, total
assets £7.5m and 50 employees, for years beginning on or after 6 April 2025
(£10.2m and £5.1m before).
- GB VAT numbers: 9 digits whose check digits pass HMRC's mod 97 test (or
the same with 55 added, for numbers issued since 2009). VIES does not hold
them; HMRC's own check comes with Making Tax Digital.
- An individual's tax year: 6 April to 5 April.
Sources (GOV.UK): How to fill in your VAT Return (VAT Notice 700/12);
import VAT on the return;
Flat Rate Scheme and VAT Notice 733;
Cash Accounting Scheme;
annual accounting deadlines;
Company Tax Returns; annual accounts;
confirmation statement;
audit exemptions.
The front page
public/home.html is Ledgerline's own page: a visitor who isn't signed in
sees it at / (src/proxy.ts), with Sign in at the top and Book a demo
going to Wolsten Studios; someone signed in goes straight to their dashboard.
It began as the landing page artifact and is plain HTML, so it changes
without touching the app.
The public demo
Every push to main deploys to Vercel: https://ledgerlinecy.app (www. redirects
to it; https://ledger-line-vert.vercel.app is the same deployment). Supabase
Auth's site URL is the domain, and its redirect list holds /auth/callback on
both addresses; LEDGERLINE_SITE_URL on Vercel is the domain too, for links
made without a request to take the host from.
With no database of its own it runs the embedded one in the server's
temporary folder, so its data is made up, built afresh on each new server (a
few seconds on the first visit) and kept only while that server runs. Every
page says so. Documents are read from the demo files' recordings only, and
Claude is never asked, so it costs nothing however many people try it
(HOSTED_DEMO in src/lib/auth/config.ts). Once the variables below are set
on Vercel, the same deployment is the live one.
Going live on Supabase
Live, Ledgerline runs on Supabase's Postgres in the EU (Frankfurt), with the
same schema and the same access rules as the laptop demo. The laptop demo
enforces those rules too: what it shows Maria is what the database lets her see.
Who sees what (row-level security, at the end of src/lib/db/schema.sql):
- A practice's people (
practice_staff.user_id) see every company it keeps.
- A client company's own people (
company_members) see that company and
nothing else, and none of the practice's records about it: its AML file,
its fee, the time spent on it. Pixelforge's owner, Nikos Savva, is seeded to
show it.
- Nobody signed in sees nothing. Only the database writes the audit trail.
- Every request from a person runs as the role
authenticated, with their
id in request.jwt.claims, as Supabase runs it (src/lib/db/index.ts).
Work done for nobody in particular (a client's private link, the website
form, the sanctions list refresh) runs inside asService(), which names why.
npm run check:tenancy proves it: 78 checks, asked as a partner, as
someone at another practice, as a client's owner, as an auditor and as
nobody. Against a
real Postgres: DATABASE_URL=… CHECK_TENANCY_WIPE=1 npm run check:tenancy
(it empties that database first: a throwaway one only).
Signing in (src/lib/auth, src/proxy.ts, src/app/(auth)):
- Live, Supabase Auth: email and password, and an emailed link to set a new
one. The proxy refreshes the session and sends anyone not signed in to
/sign-in; who someone is, is verified (getClaims) where data is read.
- Nobody signs up on their own. A partner invites the practice's people
(Practice settings → People); anyone at the practice, or a company's
owner, invites a client company's own people (Company settings → People).
The invitation's link (valid 14 days, kept only as a fingerprint) is where
they make their account; it is proof the address is theirs, so it also
joins them to any other client company the practice has invited that
address to. Many owners are invited at once from Owners on Ledgerline
(
/clients/owners); Ledgerline sends no email itself.
- The laptop demo has no passwords:
/sign-in lists its people. Sign in as
Nikos Savva to see Ledgerline as a client's owner does.
tests/supabase-auth.spec.ts drives the Supabase path through
@supabase/ssr against tests/support/stand-in-auth.mjs, a stand-in for
Supabase Auth's endpoints with a real Postgres behind it. It proves
Ledgerline's side, not Supabase's: run it against the real project too.
Setting it up
- Create a Supabase project in the Frankfurt region (eu-central-1).
supabase link --project-ref …, then supabase db push: it applies
supabase/migrations, the baseline (generated from schema.sql by
npm run supabase:migration) and the link to Supabase Auth.
- Give the host (Vercel, EU region) the variables at the end of
.env.example: DATABASE_URL (the transaction pooler, port 6543),
NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY,
SUPABASE_SECRET_KEY, LEDGERLINE_SITE_URL, and ANTHROPIC_API_KEY.
Made from Vercel instead (the project's Storage → Create Database →
Supabase, Frankfurt), the project gets POSTGRES_URL,
NEXT_PUBLIC_SUPABASE_ANON_KEY and SUPABASE_SERVICE_ROLE_KEY, which
Ledgerline reads as well; only LEDGERLINE_SITE_URL and
ANTHROPIC_API_KEY are left to add.
- In Supabase Auth: turn off "Allow new users to sign up" (accounts come
only from invitations), set the Site URL to
LEDGERLINE_SITE_URL, and
add LEDGERLINE_SITE_URL/auth/callback to the redirect URLs.
- The Data API can stay on or be switched off: anonymous requests get
nothing, and a signed-in one sees only what the rules allow.
- The first practice and its first partner are made by Wolsten Studios
(the admin console is still to come); everyone else is invited:
DATABASE_URL=… npx tsx --conditions=react-server scripts/first-practice.mts --practice "Name Ltd" --partner "Full Name" --email [email protected]
opens the practice with its own books and prints the partner's link.
Live since 28 October 2026 on the Supabase project Ledger Line (Ireland,
eu-west-1), with Vercel's functions beside it in Dublin (vercel.json). Its
Data API is off. The baseline and the accounts migration are applied and
recorded, so npm run supabase:migration now refuses: a change to the schema
is a new, dated migration, and schema.sql follows it.
On 30 October 2026, statements, statement checks, remittance advice and
document styles came with two migrations. Apply
20261030120000_statements_and_document_styles.sql before that version goes
live: it only adds (each company's colour and terms become its default
style), so the version before it keeps working. Apply
20261030120100_drop_invoice_accent.sql once the new version is live.
Rehearsed on a copy built from the old schema: after both, the database is
the same as one built from schema.sql, column for column.
20261030140000_tighten_rights.sql can run at any time. Supabase's default
rights had given the app's role authenticated TRUNCATE (which skips
row-level security and the audit trail's append-only guard), REFERENCES,
TRIGGER and MAINTAIN on every table, and on every table made later; it keeps
only the rows, under the rules. Nobody, the owner included, can now empty the
audit trail, and no function, now or later, is open to PUBLIC.
20261030150000_audit_appends_in_turn.sql too: the audit trail is one hash
chain for every company, and two transactions appending at once could both
link to the same entry, which audit_verify() then reports as a break (shown
on a real Postgres, with two connections). An append now waits for the one
before it to commit. And 20261031090000_payment_indexes.sql: payments found by their
document and bank account through an index. All three go in one paste,
3-tighten-rights.sql, rehearsed on a copy of the live schema.
A migration run by hand in the SQL editor goes in one transaction, with
set local lock_timeout = '5s' first (a busy table then stops it, changing
nothing, instead of holding the app up), and records itself in
supabase_migrations.schema_migrations with its full text, as
supabase db push would.
The whole test suite runs against any Postgres too:
DATABASE_URL=… npx playwright test (an empty, throwaway database; it is
rebuilt with the demo data). On 26 Oct it passed on Postgres 16. The
Supabase sign-in test, against the stand-in:
DATABASE_URL=… node tests/support/stand-in-auth.mjs in one terminal, then
build with NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321 and the
publishable key from node tests/support/stand-in-auth.mjs --keys, and run
LEDGERLINE_AUTH_E2E=supabase npx playwright test tests/supabase-auth.spec.ts
with the same variables and SUPABASE_SECRET_KEY. For a staging demo on a
real project, npm run supabase:demo-users gives the demo's people sign-ins.
Still to do before the first practice signs up
- The admin console for Wolsten Studios: every practice, its subscription
(Stripe), suspending one, logged support access.
- Files into Supabase Storage: documents, logos and captures are kept in
Postgres today.
At a big practice's scale
LEDGERLINE_DEMO_COMPANIES=400 builds the demo with that many client
companies: the eight, then variants of them, each with its own name, numbers,
bank account and fifteen months of books generated the same way, its CRM,
staff and file following the one it varies. Use it on a throwaway database:
DATABASE_URL=postgres://… LEDGERLINE_DEMO_DATA=1 LEDGERLINE_RESET_ON_START=1 LEDGERLINE_DEMO_COMPANIES=400 npm run start
Measured on 27 October 2026 with 400 clients on Postgres 16 (101,000 bank
lines, 365,000 journal lines, 20,000 invoices, 31,000 bills; built in 95
seconds): every company page answers in 15 to 45 ms, and the practice pages
(the dashboard, deadlines, calendar, fees, client files, capture) in under
0.6 s, with at most about 1,700 elements on screen. Before this, the
dashboard and deadlines took 47 seconds: each bank line's status was worked
out by valuing every match. A bank line now knows whether it is matched
(bank_line_matched), the dashboard works out VAT only for the clients on
screen, deadlines are looked up by company, and the practice pages list the
clients that need something first, forty to sixty at a time, with a search by
name, HE or VAT number. Pages no longer carry the list of every client for the
switcher: the switcher and the timer fetch it when the browser is idle or when
first opened (/api/shell/companies), so with 700 clients a company page is
about 115 KB, down from 400 KB. The forms that pick a client (the timer,
adding time, checking a VAT number) fetch the same list. Time is a hundred
entries at a time, and VAT numbers fifty, those to look at first, with a
search: 250 KB and 140 KB with 700 clients, down from 1 MB and 865 KB.
If something goes wrong
- Reset demo data at http://localhost:3000/settings?demo=1 rebuilds
everything in about two seconds (or
npm run demo:reset). The button is
hidden from the normal settings page so the product doesn't look like a demo.
- Capture says "AI is not set up": the key isn't in
.env.local, or the
server was started before it was added. Stop (Ctrl+C) and npm run demo.
- Port 3000 busy:
PORT=3001 npm run demo.
- A file fails to read: the row says why; drop it again. Nothing posts until
someone presses Post, so a failed read never touches the books.
How it is built
- PDFs (
src/lib/documents) are made with react-pdf, one at a time and with
Roboto registered afresh for each (pdf-fonts.ts): react-pdf shares a
loaded font between documents, and one with much Greek in it otherwise
garbles the next (letters drop out). tests/support/pdf-text.ts reads a
PDF's text back as a viewer shows it, so the tests catch that.
- Languages (
src/lib/i18n): a screen's words are its English, looked up in
the Greek dictionary (el.ts) for someone who reads Ledgerline in Greek
(app_users.locale): await getI18n() in a server component,
useI18n() in a client one, each with the formatters of that language
(src/lib/format.ts). Problems the server words in English are read
against the dictionary's phrases (t.message); a deadline's title is
turned into Greek in i18n/deadlines.ts; what a customer is sent is
written in their language in src/lib/documents/messages.ts. Typed figures
are read either language's way by plainDecimal (line-math.ts).
npm run check:greek checks the dictionary against every translated
screen.
- Next.js 16.4 (App Router, Turbopack) on React 19.3 with the React Compiler,
run by its Rust port inside Turbopack (
next.config.ts), so components are
memoised without hand-written useMemo. TypeScript 6.0, the newest the
linter's TypeScript parser reads; Tailwind 4; Motion 14.
src/lib/db/schema.sql: the ledger (journal entries rebuilt by
ledger_replace, post_* triggers for invoices, payments, bills, bill
payments and bank transactions, lock-date checks, bank reconciliation) with
organisation_id on every books table and composite foreign keys, so a
document can never post to another company's accounts. Plus the practice,
capture, filed VAT returns and the hash-chained audit log.
src/lib/db/seed.ts: the six fictional companies, generated from a fixed
seed. scripts/make-demo-files.mjs renders the invoices in demo-files/.
- Selling (
src/app/c/[org]/sales, quotes, credit-notes, customers, items;
src/lib/data/sales.ts, src/lib/actions/sales.ts): invoices, quotes and
credit notes share one line model (quantity, unit price, discount, VAT),
worked out by ledger_document_lines in the database and, identically, by
src/lib/domain/line-math.ts on screen. save_invoice, save_quote,
and write them; a credit note posts the
reverse of a sale, set credit against invoices, and a
refund is a bank payment against receivables. The PDFs (invoice, quote,
credit note, statement) are (react-pdf, Roboto
embedded).