The Run my Accounts fork: thirteen years of a Swiss SQL-Ledger, feature by feature
Run my Accounts AG, a Zurich bookkeeping firm, has run its clients on a fork of SQL-Ledger since before 2013 and publishes that fork on GitHub under the GPL. Its history — 2,130 commits to August 2026 — is the most detailed public record of what a Swiss accounting practice needs from this ledger, and, because the firm keeps part of its platform closed, the schema also records features whose code was never published. This chapter reads the whole history, sorts the features into what is in the open code and what is only implied by tables, says how each was built, and sets out what we should build so that a firm leaving Run my Accounts, or an accountant who learned SQL-Ledger there, finds what they expect on Ledger123.
Run my Accounts froze SQL-Ledger at 2.8.33 in 2013 and spent thirteen years turning it into the posting engine of a Swiss outsourced-bookkeeping service: ESR and then QR-bill payment parts, a line-level VAT model with its own MWST report, a dunning workflow with audit and batch mailing, a clearing-account rebooking screen, a full change log, Swiss and German charts, and DATEV, GoBD and UStVA exports for a German arm. From 2022 the ledger was embedded in a closed portal, and from 2024 in a closed .NET platform that imports camt.053 statements, pulls accounts through SIX bLink, exports pain.001 payments, matches bank lines to names and files the VAT return with the ESTV — all visible only as tables. Eleven of the open features are worth porting under the GPL; the closed ones we build ourselves on the same schema shape; and a 2.8-to-3.2 migration path is the piece that actually captures the users.
12.1What the fork is
The first commit, in September 2013, imports an already-customised
SQL-Ledger 2.8.33 with Swiss payment-slip plumbing in it. The application version
never changed again; the database version walked from 2.8.10 to 2.8.52 through
forty-odd upgrade scripts. Two people wrote most of it: Armaghan Saqib as the
contractor, on and off for the whole period, and a succession of in-house
developers at Run my Accounts — Nils Samuelsson to 2019, then a team from
2022 working from an internal tracker whose ticket numbers (IBP-nnnn)
mark the later commits. Releases were weekly, named by calendar week, then
numbered 5.1.8 to 5.2.25. The whole tree is still
ISO-8859-1.
| Period | Commits | What was built | Direction |
|---|---|---|---|
| 2013–2015 | 399 | Per-line VAT on AR, AP and GL; FX residual columns (fxamount, fxpaid); reminder levels audited, queued and printed with ESR slips; GL report ported from 3.x; NRLR-compliant Swiss chart; modular LaTeX templates with the orange payment slip; encpassword and deep-link hooks. | A fiduciary’s production line on a frozen 2.8: Swiss compliance and operational hardening, with the first hooks for an external portal. |
| 2016–2017 | 438 | The All Taxes line-tax report on a rebuilt invoicetax; extended transaction log with row snapshots and a history view; Ledger Doctor; an SQL-injection and CSRF sweep; dispatch methods for the mailing house; ZUGFeRD 1.0 embedded from LaTeX; separate Swiss and German charts and template sets; a Mojolicious GoBD export written by an outside developer. | Making a multi-client hosted service auditable, and expanding into Germany. |
| 2018–2020 | 430 | Clearing-account rebooking screen (cl.pl); Swiss QR-bill variables for the templates in time for the June 2020 deadline; DATEV export; deleted-transaction log; generic CSV importer; per-user closing date; GL “on hold”; departmental year-end; Sendinblue API mailer; pg_dump backups; the Mojolicious layer removed and re-imported. | Tools that let the firm’s accountants process client bank movements faster. |
| 2021–2023 | 389 | QR-bill made standards-exact and extended to dunning letters; reverse-charge VAT; UStVA groundwork (tax.vatkey, formdigit); structured addresses; searchable dropdowns everywhere; batch e-mail of reminders with attachments; last-used report columns; and the hooks — postMessage events, CSRF tokens, an IP whitelist — that show the ledger running inside a closed portal. | An in-house team takes over; the ledger becomes the engine behind my.runmyaccounts.com. |
| 2024–2026 | 474 | A camt.053 bank-import data model; api.pl payment endpoints; SIX bLink consent and process tables; pain.001 export tables; fuzzy name matching; VAT-settlement storage for the ESTV e-filing; financial years; controlling checklists; a SQLite members store; Factur-X post-processing in Python; customer default accounts and Skonto. | Two-thirds of substantive commits are schema with no Perl consumer: SQL-Ledger is now the data store of a closed .NET platform. |
Two things follow for us. Run my Accounts is a service, not a software vendor: a client who leaves it does not take a product, they take a database dump in a 2.8 schema and a habit of certain screens. And the fork is GPL, with only the trademark and logo reserved, so anything in the open code may be ported with attribution.
12.2The features in the open code
Everything below has its Perl in the repository. The last column marks where we stand: have already in 3.2.12 or this book, partial the substance exists, port worth taking from the fork.
| Feature | How it was built | Us | Note |
|---|---|---|---|
| Swiss and German compliance | |||
| QR-bill on invoices, statements and reminders | bank.qriban, strdbkginf, invdescriptionqr (upgrade 2.8.18); IS::invoice_details and RP::reminder build length-capped, character-stripped *qr variables including the Swico S1 tax string; RMA-content_qrinvoice.tex draws the payment part with the LaTeX qrcode package, gated on CHF and a QR-IBAN. | port | Chapter 1 item 2a asked for exactly this; the open code is the Swiss block. |
| ESR payment slip | ar/ap.dcn, bank.dcn/rvc/membernumber, Form::format_dcn modulo-10 check digit, orange-slip template with a PostFinance branch. | have | Our bank table carries the columns; ESR is retired in Switzerland since 2022. |
| Line-item VAT and the All Taxes report | acc_trans.tax, taxamount, tax_chart_id, lineamount; invoicetax.amount; ar/ap.linetax; a Defaults toggle; reports.pl alltaxes reconciles taxable base and tax per account across AR, AP and GL with a period picker; a rebuild tool in custom_am.pl. | partial | Our invoices carry tax per line; AR/AP/GL transactions do not. The report is the Swiss MWST return’s basis and the period-return engine of chapter 1 item 11. |
| Reverse-charge VAT | tax.reversecharge_id; posting a GL line with such a tax writes two offsetting tax lines; the report shows both sides. | port | Needed by any EU or Swiss firm buying services abroad. |
| Swiss and German charts, Swiss-German locale | RMACH_{DEU,ENG,FRA} and RMA_CHE KMU charts on the 2014 accounting law, a German SKR-style chart, locale/rma_ch_de; chart.allow_gl and chart.symbol_link role tags. | port | Our Swiss-German chart is older; the fork’s carry zero-rate VAT accounts and the NRLR structure. Credited to Brändle, Kienast and Samuelsson. |
| DATEV, GoBD and UStVA for Germany | DATEV: im.pl prepare_datev pairs debits with credits into debitscredits and exports. GoBD: a Mojolicious app writes Firma, Kontenplan, Journal and SummenSalden with the GDPdU index. UStVA: the same app fills an ODS form from a v_inline_tax view. | port | German-market items; the Mojolicious code is the most portable in the tree. |
| ZUGFeRD and Factur-X | 2016: XML embedded by LaTeX (pdfx, embedfile) from xml_* variables. 2026: a Python post-processor (factur-x, Ghostscript to PDF/A-3b) reading the database directly, gated per client. | partial | Backlog item 44 wants EN 16931 output; the Python script is a working reference, though its Perl hook is not in the public tree. |
| Bookkeeping operations | |||
Clearing-account rebooking (cl.pl) | From the account ledger of the configured clearing account, open a GL line, see open AR and AP invoices, tick them, and the line is rewritten to the AR/AP account with payment rows split across invoices oldest first, FX gain or loss posted, or re-pointed to any GL account. CGI::FormBuilder and DBIx::Simple, no menu entry. | partial | This is the manual half of chapter 1 item 1’s matching grid; its posting logic is the part to keep. |
| Extended transaction log | On every re-post or delete, the old header and lines are copied into ar_log, ap_log, gl_log, acc_trans_log and *_log_deleted with a timestamp; a View action shows any prior version; the GL journal can include the log. | port | The immutable history chapter 1 item 7 wants; row snapshots are simpler than our audittrail rows. |
| Ledger Doctor | custom_am.pl: unbalanced journals, paid-versus-booked mismatches, orphans, blank rows, missing dates, bad line-tax rows, each with a fix. | port | The anomaly review of chapter 9 item 4, already written for this schema. |
| Per-user closing date, GL on hold, departmental year-end | employee.closedto with Form::closedto_user; gl.onhold flag and filter; year-end per department. | port | Small, useful to a practice with juniors. |
| Financial years | financial_year rows OPEN, CURRENT, CLOSED, maintained by AM::post_yearend. | port | The table the closed platform reads; harmless and clarifying for us. |
| Last-used report columns, searchable dropdowns | lastused table via Form::get_lastused; select2 on every select with a numeric-prefix matcher for account numbers. | port | Cheap usability; our usability pass stopped short of both. |
| Chart hierarchy, customer default accounts, Skonto, reminder stop | chart.parent_id back-filled from headings; customer.income_accno_id, vendor.expense_accno_id, early_payment_discount, payment_clearing_accno_id, reminderstop; last_modified triggers. | port | Master-data fields an automated poster needs; chapter 9’s categoriser wants the first two. |
| Dunning | |||
| The reminder workflow | Level changes written to audittrail and reported; reminders queued to the spool and batch-printed or concatenated into one PDF; batch e-mail with the invoice attached; Mark as sent writing a structured note; due-date and level filters; QR payment part on the letter. | partial | Our Reminder report is the 2.8 original; chapter 1 item 3 scheduled it. The fork’s queue, audit and mark-as-sent are the missing operational half. |
| Import, export, documents | |||
| Generic CSV importer | IM::prepare_import_data parses any delimited file into report/reportvars, then reuses the per-type importers; sixteen import subs; sample files; closed-period check on import. | partial | Our importer handles fixed layouts; the mapping step is the addition. |
| Dispatch methods | dispatch table on customer and vendor; the batch-print queue filters by it, so post, e-mail and portal documents separate. | port | An afternoon. |
| Modular LaTeX templates | Seventy RMA-*.tex parts: packages, settings, header, address, articles, sums, terms, payment table, page breaks, country lookup, QR part, ZUGFeRD part; three languages. | partial | Our templates were redesigned in HTML; the fork’s LaTeX set is the reference for the QR and e-invoice parts. |
| Backups | pg_dump | gzip mailed or downloaded; templates tarred separately; Mojolicious restore. | have | Ours does the same; chapter 4 item 6 goes further. |
| Warehouse transfers with FIFO reposting | trf.pl, SL/Trf.pm, fifo and build tables, a COGS repost tool. | have | 3.2.12 has warehouse transfers natively. |
| Integration hooks | |||
api.pl | Mojolicious::Lite; POST /post_payment builds a $form per invoice and calls CP::post_payment, stamping acc_trans.imported_transaction_id; /delete_payment reverses by that id; the dataset is chosen by clientName. No authentication. | partial | The first two endpoints of chapter 6 item 1, and proof the approach works on this codebase. |
| Portal hooks | encpassword bearer login, menuids deep links, $ip_whitelist password bypass, FRONTEND_COOKIE CSRF, postMessage events for upload-and-sign and URL-to-PDF, Sendinblue mailer. | skip | Each is a shortcut for one deployment; the API and roles of chapters 6 and 4 replace them properly. |
| WLprinter | A Java Web Start bridge that spools print jobs for a client on the user’s PC to pull and print locally. | skip | Java Web Start is dead; browsers print. |
| SQLite members store | users/members.db replacing the INI and per-user .conf files, with migration scripts. | have | We did the same in the registry database (SL/Members.pm). |
12.3The features whose code stayed closed
From 2024 most substantive commits are upgrade scripts creating tables that no Perl in the repository reads or writes. Their columns, enums and ticket names describe the platform that does. This is what the schema says was built, and how.
| Feature | Evidence | How it was evidently built |
|---|---|---|
| Bank-statement import (camt.053, CSV, bLink) | bank_account; banking_import_event (status, IBAN, currency, totals, source_type, file_content); imported_balance; imported_transaction with the ISO 20022 vocabulary — domain, family and sub-family codes, end_to_end_id, remittance_ref, related_party_address JSONB, hashcode, suppression, parent id for batch splits; acc_trans.imported_transaction_id. | A service outside Perl parses camt.053 and 054 into rows, one event per file or feed, deduplicates by hash, and links each posted payment back to its bank line through api.pl. The link design was reworked three times in 2024, which is what co-design with an external consumer looks like. |
| Open banking through SIX bLink | bank_account.blink_permission_id, blink_account_id; blink_import_process and _log with JSONB payloads and a correlation_id; banking_import_event.delegated_to; “bLink API v5” in 2026. | A consent flow per account, a poller that pulls statements into the same import tables, and a delegation flag when the platform takes the import over. |
| Payment export (pain.001) | banking_export_event (export_message_id, export_submission_id); exported_payment (rma_payment_id, payload JSONB, export_instruction_id); blink_export_process. | The platform builds a pain.001 message from its own payment proposals (note rma_payment_id, not SQL-Ledger’s) and submits it through bLink; SQL-Ledger sees only the resulting posted payment. |
| Auto-matching of bank lines | search_irrelevant_words seeded with legal-form tokens (AG, GmbH, Sàrl, LLC) and DE/FR/EN stopwords; a plpgsql to_filtered_tsvector(); CREATE EXTENSION fuzzystrmatch. | Full-text and Levenshtein matching of related_party_name against customers and vendors, done in PostgreSQL, called from the platform. |
| Swiss VAT return filed with the ESTV | vat_settlement (period, data JSON, xml BYTEA, type NORMAL, CORRECTION or ANNUAL_RECONCILIATION, meldecenter_data JSON); booking_to_settlement; tax.vatkey, formdigit, validfrom. | The platform computes the MWST form from the line-tax data, maps rates to form boxes through formdigit, stores the eCH-0217 XML, submits it to the federal Meldecenter, and locks every booking to the settlement it was filed in. |
| Controlling checklist and year-end package | xcontrolling_log (controlling_key, checked_hash, checked_by_ibp_user_id); xabschluss and xabschluss_history (xml, data jsonb, status). | An accountant’s review workflow per transaction, the hash detecting later edits; and a financial-statement generator that moved from XML to JSON in 2026. |
| Per-transaction chat, change feed, semantic account keys | chat (trans_id, message, employee); customer/vendor.last_modified with triggers; chart.symbol_link values such as kassa, transfer, forderungenschweiz. | A comment thread in the portal; a CRM sync polling by timestamp; and the platform finding accounts by role rather than number, so one booking engine serves every client’s chart. |
| Document management, PDF rendering, full-text search | postMessage events uploadLinkAndSignFile and urlToPdf; Alfresco WebDAV links rewritten to service.runmyaccounts.com/…/dms; help pages dms.html, volltextsuche.html. | An Alfresco-backed DMS in the portal, a headless browser rendering report URLs to PDF, and search across documents — none of it in the ledger. |
Read together, the two tables are one design. The open half is what a Swiss bookkeeper needs the ledger to do: QR-bills, line VAT, dunning, clearing, history. The closed half is what the service does around it: bring the bank in, send payments out, match, file, review. Every item in the closed half is already an enhancement in this book — chapter 1 item 1 is their import model, chapter 6 item 3 is bLink, chapter 2 item 6 is pain.001, chapter 9 item 1 is the matcher — and their schema is a good shape to borrow, because it was co-designed with a real bank-feed consumer over two years.
12.4Who comes from Run my Accounts, and what they expect
Three kinds of user. A client firm that stops outsourcing and wants its own books: it arrives with a database dump in the 2.8 schema, years of history, and a habit of the RMA screens. An accountant who worked in the fork and now serves clients independently: they know the All Taxes report, the reminder queue and the clearing screen by name. And a German client of the firm’s German arm, who needs DATEV and GoBD more than anything Swiss.
- Their dump restores —
fxamount,dcn, the QR bank columns,invoicetax, the log tables,financial_year,symbol_linkall mapped or preserved — and the trial balance agrees to the cent with their last RMA statement. - Invoices and reminders print with a QR-bill on the firm’s own QR-IBAN.
- The MWST return comes out of one report for the period, in the ESTV’s box numbers.
- Reminders queue, batch, mail and mark themselves sent.
- A bank statement imports and the clearing screen, or better the matching grid, clears it.
- The chart is the KMU chart they know, in German, French or English, and the interface is in Swiss German.
- For the German client: DATEV export and a GoBD archive on demand.
12.5What to build
Ordered so that the first three make the migration credible, the next four make the daily work familiar, and the rest match the platform. Items marked port take GPL code from the fork with attribution; the rest we build.
1A 2.8-to-3.2 migration importer for RMA datasets
The one item nothing else substitutes for. A script that takes a fork dump,
restores it into a scratch database, and produces a Ledger123 dataset: the 3.2
upgrade chain applied, then a mapping layer for the fork’s own columns —
ar/ap.fxamount and fxpaid folded into our FX handling,
dcn and the bank QR columns carried across, the line-tax
columns on acc_trans preserved for enhancement 3, the *_log
tables archived as history, financial_year and chart.parent_id
kept, symbol_link retained as a tag, the ISO-8859-1 text recoded to
UTF-8, and a reconciliation report proving the trial balance, AR and AP ageing and
VAT totals match before and after. Run for the client by us, as chapter
10’s first-customer stage already assumes.
2QR-bill, ported
Take IS::invoice_details’s QR block and its twin in
RP::reminder into one shared sub, the three bank columns, the
country lookup, the abbreviation helpers, and the qrinvoice LaTeX parts
as the LaTeX half of chapter 1 item 2a; add the HTML-template
equivalent with a generated QR image. Test against the SIX validator. This is the
single most-asked question a Swiss prospect has.
3Line-item VAT on transactions and the All Taxes report
Extend our per-line tax from invoices to AR, AP and GL transactions using the
fork’s acc_trans.tax, taxamount, tax_chart_id
and lineamount columns, with reverse charge, and port the All Taxes
report as the period engine of chapter 1 item 11. Then the Swiss
MWST form: box numbers on the tax record (the fork’s formdigit), the
effective and net-tax-rate methods, and a printable return.
4The reminder workflow, ported
Level-change audit, the spool queue with batch print and single-PDF
concatenation, batch e-mail with the invoice attached, Mark as sent,
reminderstop on the customer, due-date and level filters — onto the
scheduled reminders of chapter 1 item 3.
5Clearing-account posting logic
Keep cl.pl’s posting core — rewrite a GL line to the AR/AP
account, split across open invoices oldest first, post FX difference, or re-point
to any account — as the accept action of the matching grid in chapter
1 item 1, so a statement line becomes a payment in one click.
Drop its CGI::FormBuilder screen.
6Extended transaction log and Ledger Doctor, ported
The row-snapshot log tables and the View history as the append-only audit of chapter 1 item 7; the Doctor’s checks as the first tests of chapter 9 item 4’s anomaly review.
7The small habits
Per-user closing date; GL on hold; departmental year-end; financial years; last-used columns; searchable dropdowns; dispatch methods; chart hierarchy; customer income and vendor expense accounts, Skonto and clearing account; the KMU charts in three languages and the Swiss-German locale. Each is a day; together they are what makes the fork’s accountant feel at home.
8The bank-import model, in their shape
Adopt the fork’s imported_transaction vocabulary for the
bankline table of chapter 1 item 1 — the ISO 20022
transaction codes, end-to-end id, remittance reference, related party as JSON, the
hash, suppression and parent id — and write the camt.053 and 054 parser it never
published, in Perl, alongside CSV and OFX. Then the bLink adapter of chapter
6 item 3 on the consent columns they defined, and the pain.001
writer of chapter 2 item 6 with the message, submission and
instruction ids they store.
9Matching in the database
Their stopword table, filtered tsvector function and fuzzystrmatch
as the memory-and-rules layer of chapter 9 item 1, before any
model is consulted. The legal-form token list is the useful part and is theirs to
copy.
10VAT settlement and the Meldecenter
Their vat_settlement and booking_to_settlement tables as
the filed-return store of chapter 1 item 11: a settlement locks
the bookings it contains, corrections and the annual reconciliation are typed, and
the eCH-0217 XML is kept. E-filing through the ESTV Meldecenter API is the last
step and needs a registered account; the XML and the printable form come first.
11DATEV, GoBD and UStVA, ported
The Mojolicious GoBD and UStVA code moves almost unchanged onto our
application server; the DATEV pairing routine ports into SL/IM.pm with
its staging tables made per-session. Together with a German SKR chart these are
what the fork’s German clients need, and what any German prospect asks first.
12.6What not to take
The Latin-1 encoding and the frozen 2.8 base: we are on 3.2 and UTF-8, which
is the reason to migrate rather than the reverse. The portal shortcuts
(encpassword in query strings, the IP whitelist that skips passwords,
a committed mail API key): each is a hole. The scratch tables shared between users
(filtered, debitscredits) and the LIMIT-1 rounding patches:
symptoms, not features. WLprinter and the hard-coded exchange-rate cron. And the
trademark: the charts and code are GPL, the name and logo are not.
Run my Accounts spent thirteen years making SQL-Ledger a Swiss bookkeeper’s engine and then hid the bank, the filing and the review behind a closed platform; port the open half under the GPL, build the closed half on the schema they left in the open, and write the migration that turns their dump into our dataset — and their clients and accountants have somewhere to go.
12.7Glossary
| Term | What it is |
|---|---|
| bLink | SIX Group’s Swiss open-banking API: account access and payment submission by consent, used by accounting platforms to pull statements and push payments. |
| camt.053 / camt.054 | ISO 20022 XML bank-statement and debit/credit-notification messages; the Swiss standard for account statements since 2018. |
| DATEV | The German accountants’ cooperative whose data format every German tax adviser expects; an export is a list of debit–credit pairs. |
| DMS | Document management system; in the fork, Alfresco behind the portal. |
| eCH-0217 | The Swiss e-government XML standard for the VAT return. |
| ESR / VESR | The orange payment slip with a reference number and modulo-10 check digit, replaced by the QR-bill in 2022. |
| ESTV, Meldecenter | The Swiss Federal Tax Administration and its online filing portal for VAT returns. |
| GoBD, GDPdU | German rules on the keeping of electronic books and the audit-export format the tax office can demand. |
| IBP | The internal name and ticket prefix of Run my Accounts’ closed .NET platform from 2024. |
| KMU chart, NRLR | The Swiss small-business chart of accounts, restructured for the 2014 accounting law (Neues Rechnungslegungsrecht). |
| MWST | Mehrwertsteuer: Swiss VAT, returned quarterly to the ESTV. |
| pain.001 | The ISO 20022 credit-transfer instruction message; a bulk payment file. |
| QR-bill, QR-IBAN | The Swiss payment part with a QR code, mandatory since September 2022; a QR-IBAN is the special IBAN that carries a structured reference. |
| Skonto | An early-payment discount on a vendor invoice. |
| Swico S1 | The tax-breakdown string inside a Swiss QR-bill’s billing information. |
| UStVA | Umsatzsteuer-Voranmeldung, the German monthly or quarterly VAT return. |
| ZUGFeRD, Factur-X | The German–French hybrid e-invoice: a PDF/A-3 with an EN 16931 XML inside. |
12.8Sources
The repository itself: github.com/ledger123/runmyaccounts, cloned at its state of 20 August 2026 (2,130 commits), read commit by commit in five periods, and its present tree diffed against this repository’s schema, menu and modules. Commit hashes cited by the agents are recorded in the session transcript; the repository’s RELEASE file carries the firm’s own changelog from 2013. Licence: GPL, per LICENSE and the first commit “not including Run my Accounts trademark and logo”.