Chapter 11 Contents Last chapter
12

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.

2026-08-26 · read from a clone of github.com/ledger123/runmyaccounts at commit of 20 August 2026; six agents covered the five periods of the history and the repository’s present state
Executive summary

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.

Plate 12.1 · Thirteen years in five periods
PeriodCommitsWhat was builtDirection
2013–2015399Per-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–2017438The 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–2020430Clearing-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–2023389QR-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–2026474A 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.

FeatureHow it was builtUsNote
Swiss and German compliance
QR-bill on invoices, statements and remindersbank.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.portChapter 1 item 2a asked for exactly this; the open code is the Swiss block.
ESR payment slipar/ap.dcn, bank.dcn/rvc/membernumber, Form::format_dcn modulo-10 check digit, orange-slip template with a PostFinance branch.haveOur bank table carries the columns; ESR is retired in Switzerland since 2022.
Line-item VAT and the All Taxes reportacc_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.partialOur 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 VATtax.reversecharge_id; posting a GL line with such a tax writes two offsetting tax lines; the report shows both sides.portNeeded by any EU or Swiss firm buying services abroad.
Swiss and German charts, Swiss-German localeRMACH_{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.portOur 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 GermanyDATEV: 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.portGerman-market items; the Mojolicious code is the most portable in the tree.
ZUGFeRD and Factur-X2016: 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.partialBacklog 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.partialThis is the manual half of chapter 1 item 1’s matching grid; its posting logic is the part to keep.
Extended transaction logOn 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.portThe immutable history chapter 1 item 7 wants; row snapshots are simpler than our audittrail rows.
Ledger Doctorcustom_am.pl: unbalanced journals, paid-versus-booked mismatches, orphans, blank rows, missing dates, bad line-tax rows, each with a fix.portThe anomaly review of chapter 9 item 4, already written for this schema.
Per-user closing date, GL on hold, departmental year-endemployee.closedto with Form::closedto_user; gl.onhold flag and filter; year-end per department.portSmall, useful to a practice with juniors.
Financial yearsfinancial_year rows OPEN, CURRENT, CLOSED, maintained by AM::post_yearend.portThe table the closed platform reads; harmless and clarifying for us.
Last-used report columns, searchable dropdownslastused table via Form::get_lastused; select2 on every select with a numeric-prefix matcher for account numbers.portCheap usability; our usability pass stopped short of both.
Chart hierarchy, customer default accounts, Skonto, reminder stopchart.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.portMaster-data fields an automated poster needs; chapter 9’s categoriser wants the first two.
Dunning
The reminder workflowLevel 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.partialOur 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 importerIM::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.partialOur importer handles fixed layouts; the mapping step is the addition.
Dispatch methodsdispatch table on customer and vendor; the batch-print queue filters by it, so post, e-mail and portal documents separate.portAn afternoon.
Modular LaTeX templatesSeventy RMA-*.tex parts: packages, settings, header, address, articles, sums, terms, payment table, page breaks, country lookup, QR part, ZUGFeRD part; three languages.partialOur templates were redesigned in HTML; the fork’s LaTeX set is the reference for the QR and e-invoice parts.
Backupspg_dump | gzip mailed or downloaded; templates tarred separately; Mojolicious restore.haveOurs does the same; chapter 4 item 6 goes further.
Warehouse transfers with FIFO repostingtrf.pl, SL/Trf.pm, fifo and build tables, a COGS repost tool.have3.2.12 has warehouse transfers natively.
Integration hooks
api.plMojolicious::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.partialThe first two endpoints of chapter 6 item 1, and proof the approach works on this codebase.
Portal hooksencpassword bearer login, menuids deep links, $ip_whitelist password bypass, FRONTEND_COOKIE CSRF, postMessage events for upload-and-sign and URL-to-PDF, Sendinblue mailer.skipEach is a shortcut for one deployment; the API and roles of chapters 6 and 4 replace them properly.
WLprinterA Java Web Start bridge that spools print jobs for a client on the user’s PC to pull and print locally.skipJava Web Start is dead; browsers print.
SQLite members storeusers/members.db replacing the INI and per-user .conf files, with migration scripts.haveWe 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.

FeatureEvidenceHow 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 bLinkbank_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 linessearch_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 ESTVvat_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 packagexcontrolling_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 keyschat (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 searchpostMessage 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.
Finding

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.

Plate 12.2 · Day one for a Run my Accounts refugee
  • Their dump restores — fxamount, dcn, the QR bank columns, invoicetax, the log tables, financial_year, symbol_link all 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.

Tier 1Credible migration: 1–3
Tier 2Familiar daily work: 4–7
Tier 3The closed half, built open: 8–10
Tier 4Germany: 11

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.

Touches: a new bin/migrate-rma.pl, SL/User.pm dbupdate, one schema upgrade for the retained columns. Effort: medium-large; the reconciliation is the work.

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.

Touches: SL/IS.pm, SL/RP.pm, SL/AM.pm bank accounts, templates. Effort: small; the code exists.

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.

Touches: SL/AA.pm, SL/GL.pm, SL/RP.pm, bin/mozilla/aa.pl, gl.pl, one schema upgrade. Effort: medium.

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.

Touches: SL/RP.pm, SL/BP.pm, bin/mozilla/rp.pl, bp.pl. Effort: small-medium.

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.

Touches: SL/BK.pm, SL/CP.pm. Effort: small.

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.

Touches: SL/AA.pm, GL.pm, IS.pm, IR.pm, a new bin/mozilla/doctor.pl. Effort: small-medium.

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.

Touches: across SL/ and bin/mozilla/; three chart files; one locale. Effort: medium in aggregate.

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.

Touches: SL/BK.pm, a camt parser, provider adapters. Effort: medium-large, shared with the earlier chapters.

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.

Touches: one schema upgrade, SL/BK.pm. Effort: small.

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.

Touches: SL/RP.pm, one schema upgrade, a filing client. Effort: medium.

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.

Touches: the appserver, SL/IM.pm. Effort: small-medium.

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.

In one sentence

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

TermWhat it is
bLinkSIX 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.054ISO 20022 XML bank-statement and debit/credit-notification messages; the Swiss standard for account statements since 2018.
DATEVThe German accountants’ cooperative whose data format every German tax adviser expects; an export is a list of debit–credit pairs.
DMSDocument management system; in the fork, Alfresco behind the portal.
eCH-0217The Swiss e-government XML standard for the VAT return.
ESR / VESRThe orange payment slip with a reference number and modulo-10 check digit, replaced by the QR-bill in 2022.
ESTV, MeldecenterThe Swiss Federal Tax Administration and its online filing portal for VAT returns.
GoBD, GDPdUGerman rules on the keeping of electronic books and the audit-export format the tax office can demand.
IBPThe internal name and ticket prefix of Run my Accounts’ closed .NET platform from 2024.
KMU chart, NRLRThe Swiss small-business chart of accounts, restructured for the 2014 accounting law (Neues Rechnungslegungsrecht).
MWSTMehrwertsteuer: Swiss VAT, returned quarterly to the ESTV.
pain.001The ISO 20022 credit-transfer instruction message; a bulk payment file.
QR-bill, QR-IBANThe Swiss payment part with a QR code, mandatory since September 2022; a QR-IBAN is the special IBAN that carries a structured reference.
SkontoAn early-payment discount on a vendor invoice.
Swico S1The tax-breakdown string inside a Swiss QR-bill’s billing information.
UStVAUmsatzsteuer-Voranmeldung, the German monthly or quarterly VAT return.
ZUGFeRD, Factur-XThe 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”.