JIT accounting system for political parties according to the Austrian Parteiengesetz (PartG) 2012. pgacc is the finance tool ("Finanzen") of the Piratenpartei Österreichs. It is written in PHP on PostgreSQL; the user interface is in German.
Core concepts:
- Vouchers (Belege): booking lines in the
voucherstable; lines that share avoucher_idform one voucher. - Two-person approval: each voucher is acknowledged twice (
ack1,ack2). - Organizations/units: bookings belong to party organizations; user rights are scoped to them.
- Receipts: PDF receipts are uploaded, stored encrypted in
files/. - Public reports: annual report (Rechenschaftsbericht), transactions and spendings pages, donation lists.
- Import: bank statement CSV import (BAWAG format).
- Membership integration:
getusers.phpfetches member names from the membership system,mbapi.phpexports membership payments.
- PHP 8.5 with the
pgsqlextension (short_open_tagis no longer needed).mcryptis no longer needed: receipts and the membership exchange usephpseclib(same ciphertext; used automatically if loaded). - PostgreSQL 18 with the
pgcryptoextension (passwords usecrypt(); new passwords bcrypt, older MD5-crypt hashes still accepted). - Composer, for
smalot/pdfparserandphpseclib/phpseclib. - A PHP-capable web server (e.g. Apache or Lighttpd with PHP-CGI/FPM).
- Browsers: Chrome/Edge 111+, Safari 16.4+ or Firefox 128+ (the stylesheet uses Tailwind CSS v4 features such as cascade layers; older browsers show the pages unstyled).
Tested on PHP 8.5 with PostgreSQL 18 only. The last release for PHP 5.3 and
PostgreSQL 8.4 is 1.0.0 (tag php5-last). The PDF bank-statement import does
not work on PHP 8 yet; see
tests/COMPATIBILITY_PLAN.md, "PHP 8.5-only
migration".
Release notes and upgrade steps: CHANGELOG.md.
-
Create a database named
accounting(the code connects withpg_connect("dbname=accounting"), i.e. as the web server's database user) and load the schema from tests/fixtures/schema.sql (table-schema.txtis an olderpsqldump of the same tables). -
Create
constants.phpin the repository root. It is gitignored and must never be committed:<?php $datakey = 'CHANGE-ME'; // shared key for the membership system exchange $key = 'CHANGE-ME'; // key for encrypting uploaded receipts
$keymust have at least 16 characters and must not be theCHANGE-MEplaceholder; otherwise receipt uploads are refused. Use a random string of 32 or more characters (e.g.openssl rand -hex 32). Back it up and keep it: receipts uploaded since the receipt key fix (files/<n>.v2.aes) cannot be decrypted without it or with another key. Older receipts (files/<n>.aes) stay readable;migrate-receipt-key.php(command line only) re-encrypts them, see the runbook in UPGRADING.md. -
Run
composer install. -
Make
files/writable by the web server. It holds the receipt files and must not be web-accessible. -
Serve the repository root over HTTPS.
index.phpredirects unless the request carriesX-Forwarded-Proto: https, so it expects a TLS-terminating proxy in front. Older versions rely on PHP output buffering for the login redirect and cookies.
All pages are served through index.php?action=<name>, e.g.:
| Action | Purpose |
|---|---|
(none) / year=YYYY |
Public annual report |
transactions, spendings |
Public transaction and spending lists |
login, logout |
Sign in / out |
new |
Enter a new voucher |
open, edit |
Open vouchers; review, approve, attach receipts |
all, search, closed, deleted |
Ledger views (format=csv for export) |
import |
Bank CSV import |
The test harness runs in Docker with synthetic data only.
sh tests/run.sh # the PHP 8.5 / PostgreSQL 18 lane, with coverage
PGACC_TEST_ID=mine sh tests/run.sh # isolate concurrent runs
sh tests/static-analysis.sh # PHPCompatibility + PHPStan
sh scripts/format.sh [files...] # format (PER-CS 3.0, whitespace only)
sh scripts/format.sh --check # formatting check (CI: --check --ci)
sh tests/mutation/run.sh 8.5 <target> # target-driven mutation runner
sh tests/mutation/infection.sh 8.5 # Infection
sh scripts/build-css.sh # rebuild style.css from css/app.css
sh scripts/build-css.sh --check # style.css is up to date (CI)The stylesheet source is css/app.css: it styles the elements, classes and
ids the pages use with Tailwind CSS (@apply and the theme), and
scripts/build-css.sh builds it into the committed style.css with the
pinned Tailwind CSS standalone CLI (no Node.js; the binary is downloaded once,
checked against its SHA-256 and cached in build/). Edit css/app.css, never
style.css, and commit both; CI fails when they differ.
PHP files follow PER Coding Style 3.0, whitespace rules only
(.php-cs-fixer.dist.php, sh scripts/format.sh), checked in CI. Agents in
Claude Code format every edited PHP file through a hook
(.claude/settings.json). An opt-in Git pre-commit hook checks the staged PHP
files:
git config core.hooksPath .githooksThe whitespace-only
formatting commit is listed in .git-blame-ignore-revs; let git blame
skip it with:
git config blame.ignoreRevsFile .git-blame-ignore-revsChange workflow and verification order: AGENTS.md. Details: tests/README.md, tests/COMPATIBILITY_PLAN.md, tests/mutation/README.md.
Releases are created only by the Release workflow
(.github/workflows/release.yml). To cut one (1.0.0 is also php5-last):
- In the release pull request, date the version's section in
CHANGELOG.md (
## [1.0.0] - YYYY-MM-DD (...)), setPGACC_VERSIONinimpressum.php(shown on the Impressum) to the new version (re-record the reference,sh tests/reference/run.sh record: the Impressum recording shows it), and merge tomaster. The build fails unlessPGACC_VERSIONequals the tagged version. - On the merged commit:
git tag -a v1.0.0 -m 'pgacc 1.0.0'andgit tag -a php5-last -m 'Last release for PHP 5.3', thengit push origin v1.0.0 php5-last. - The workflow builds
pgacc-1.0.0.tar.gz(code plusvendor/, checked on the PHP version of the tagged commit: 5.3 for 1.0.0, 8.5 afterwards) and its.sha256, and creates a draft release with the CHANGELOG.md section as notes (a prerelease for versions with a-). - Review the draft on GitHub and publish it. Deploy: UPGRADING.md.
For testing only, sh scripts/release.sh 2.0.0 builds the same files locally
(Docker, clean tree, build/release/); a manual workflow run does the same.
See LICENSE: the contributions of Peter Grassberger (PeterTheOne) are licensed under AGPL-3.0-or-later (full text); contributions by other authors are not licensed until their authors confirm otherwise.