Developer documentation
Structure, local development, tests and extension points of digisoft-bilanz (private repo digi-soft-ug/digisoft-bilanz).
Architecture
CSV --> Import\CsvTrialBalanceImporter --> Domain\TrialBalance (cents, debit +, credit -)
|
Mapping\Skr03Mapper --> Engine\BalanceSheetBuilder --> BalanceSheet
| |
Engine\Validator (Issues) Render\BalanceSheetPdf / BalanceSheetXml
|
Submit\Submitter (ManualUploadExport)
- Domain:
Money(integer cents, strict parsing),Position(lettered items of § 266 HGB),Company,Issue. - Engine: pure functions, no I/O; wrong-side balances are reclassified per account, negative equity becomes asset item F.
- Web: NikanzoPHP (kernel, router, container, CSRF and security-header middleware, dev-master);
Controller/*(attribute routes),Auth(Hub SSO),Draft(session state),View/Lang(templates, de/en). No database.
Working locally
sh dev-test.sh # composer install, phpcs (PSR-12), phpunit in Docker docker compose up # service on port 8080 (Traefik labels, host from .env DOMAIN)
Tests live in tests/; golden file: tests/golden/ug_first_year.csv (expected: asset B 550.00; liabilities A 400.00; liabilities C 150.00; result -600.00). OffenlegungSchemaTest validates against the official XSD once it is in specs/offenlegung/ (licence-gated, not in git; see specs/MANIFEST.md).
Hub login
Authentication belongs to the Digi Soft Hub. Flow: /login redirects to https://digi-soft.app/sso/bilanz; the Hub issues a 60-second JWT (HS256, secret HUB_SSO_SECRET) with sub, email, name, jti, aud=bilanz and redirects to /sso/callback?token=…. Auth::verify() requires aud=bilanz and redeems each jti only once. Only id, email, name are kept in the session.
Environment variables (server-side .env only, never in the repo): DOMAIN, HUB_URL, HUB_SSO_SECRET.
Extending
New importer
Write a class with import(string $content): array{TrialBalance, list<Issue>} (see CsvTrialBalanceImporter) and wire it into Draft::compute(). Report problems as Issue::error(); never drop rows silently.
New submitter
Implement Submit\Submitter (company register) or Submit\EBilanzSubmitter (tax office, deliberately separate). The default is ManualUploadExport, which sends nothing. Real submissions must default to test/dry-run mode and need explicit confirmation. Planned: BundesanzeigerMassenschnittstelle (after a contract with the publisher); NotImplementedEBilanzSubmitter is the M4 stub (ERiC only as a Docker service on Hetzner, certificates via secrets).
Chart of accounts
Mapping\Skr03Mapper holds the ranges (draft, unreviewed). SKR04 and per-client overrides are open.
Deployment
Hetzner, directory ~/digisoft-infra/projects/bilanz/, one stateless Apache/PHP container on network web, Traefik with Let's Encrypt (letsencrypt resolver, security-headers@file). Dockerfile changes need up -d --build; .env changes need --force-recreate.
Limits
- No XBRL/E-Bilanz; the XML is our own format (
urn:digisoft:bilanz:1). - The Bundesanzeiger mass interface is contract-based; specification and test system are pending.
- No persistence; encrypted storage with an audit log is not built.
- The SKR03 mapping is an unreviewed draft.