Contributing & Architecture
This guide is for developers who want to contribute to Gäld or understand its internals.
Development Setup
Docker (recommended)
git clone https://github.com/Scanix/Gaeld.git
cd Gaeld/api
cp .env.example .env
docker compose up -d
docker compose exec laravel.test php artisan gaeld:install --demo
The app is available at http://localhost:8080. The --demo flag seeds sample data (invoices, expenses, contacts) for development.
Services started by Docker Compose:
| Service | Container | Port |
|---|---|---|
| Laravel (PHP + Nginx) | laravel.test | 8080 |
| PostgreSQL 16 | pgsql | 5432 |
| Redis 7 | redis | 6379 |
| MeiliSearch | meilisearch | 7700 |
| Mailpit (dev email) | mailpit | 8025 (dashboard), 1025 (SMTP) |
Running Commands
All PHP commands should be run inside the Docker container:
# Artisan commands
docker compose exec laravel.test php artisan <command>
# Composer
docker compose exec laravel.test composer <command>
# PHPStan
docker compose exec laravel.test ./vendor/bin/phpstan analyse
# Tests
docker compose exec laravel.test php artisan test
Frontend
The API project uses Inertia.js + Vue 3 for its admin interface:
pnpm install
pnpm dev # Vite dev server with HMR
pnpm build # Production build
Architecture Overview
Tech Stack
| Layer | Technology |
|---|---|
| Backend | Laravel 12, PHP 8.4 |
| Frontend | Vue 3 + Inertia.js |
| Database | PostgreSQL 16 |
| Cache/Queue/Session | Redis 7 |
| Search | MeiliSearch (fallback: database) |
| Monetary math | BCMath (strings, never floats) |
Domain-Driven Structure
The codebase is organized into domains under app/Domains/, each encapsulating a business capability:
app/Domains/
├── Accounting/ # Chart of accounts, ledger, journal entries, VAT, budget
├── Api/ # REST API controllers, resources, webhook system
├── Assets/ # Fixed assets and depreciation
├── Banking/ # Bank accounts, CAMT import, reconciliation
├── Contacts/ # Customers, suppliers, contact persons
├── Expenses/ # Expense recording, categories, approval workflow
├── Invoicing/ # Invoices, credit notes, recurring invoices, payments
├── Migration/ # Data import from external accounting software
├── Organizations/ # Multi-tenancy, org settings, onboarding
├── Payroll/ # Employees, salary slips, Swiss deductions
├── Reporting/ # Financial reports, exports
└── Users/ # Authentication, profiles, 2FA, passkeys
Each domain typically contains:
Domains/Invoicing/
├── Controllers/ # HTTP controllers
├── Models/ # Eloquent models
├── Services/ # Business logic
├── Queries/ # Query builders (filtering, sorting, search)
├── Requests/ # Form request validation
├── Resources/ # Inertia/API resource transformers
├── Events/ # Domain events
├ ── Enums/ # Status enums, types
└── Policies/ # Authorization policies
Multi-Tenancy
Gäld uses a shared database model with row-level isolation:
- Every data table has an
organization_idforeign key - The
BelongsToOrganizationtrait adds a global scope that auto-filters queries and auto-assigns the org on creation - The
CurrentOrganizationservice holds the active org for the current request - The
EnsureHasOrganizationmiddleware resolves the org from the session (web) or token (API)
Key Traits
| Trait | Purpose |
|---|---|
BelongsToOrganization | Auto-scopes all queries to current org, auto-assigns organization_id on create |
Auditable | Tracks model changes in the activity log |
HasUuids | Uses UUIDs as primary keys |
Feature Flags
Features are controlled via FEATURE_* environment variables:
| Flag | Default | Description |
|---|---|---|
FEATURE_BANK_SYNC | false | Bank connection and auto-sync |
FEATURE_AUTO_RECONCILIATION | false | Automatic payment matching |
FEATURE_AUTOMATION | false | Rule engine and automation |
FEATURE_MULTI_CURRENCY | false | Multi-currency support |
FEATURE_API_ACCESS | false | REST API access |
FEATURE_RULE_ENGINE | false | Business rules engine |
FEATURE_SAAS | false | SaaS/EE features (private plugin) |
Plugin System
Plugins extend Gäld without modifying core code. See the Plugin Development Guide for details.
Testing
Gäld has three test suites:
# Run all tests
docker compose exec laravel.test php artisan test
# Run a specific suite
docker compose exec laravel.test php artisan test --testsuite=Feature
docker compose exec laravel.test php artisan test --testsuite=Unit
docker compose exec laravel.test php artisan test --testsuite=Security
# Run a specific test file
docker compose exec laravel.test php artisan test --filter=InvoiceTest
| Suite | Location | Purpose |
|---|---|---|
Feature | tests/Feature/ | Integration tests — HTTP requests, database, full stack |
Unit | tests/Unit/ | Isolated unit tests — services, models, helpers |
Security | tests/Security/ | Security-focused tests — auth, permissions, CSRF, injection |
Testing Conventions
- Use
#[DataProvider('name')]attribute (not@dataProviderannotation) - Web form validation returns 302 redirects — assert with
assertSessionHasErrors() - All tests use an in-memory testing database (configured in
phpunit.xml) - Test helpers and shared traits are in
tests/Traits/ - Fixture files (sample CAMT XMLs, CSVs) are in
tests/fixtures/
Static Analysis
docker compose exec laravel.test ./vendor/bin/phpstan analyse
PHPStan is configured at level 5 with a baseline in phpstan-baseline.neon.
Code Style
docker compose exec laravel.test ./vendor/bin/pint
Uses Laravel Pint for code formatting (PSR-12 based).
Contribution Workflow
- Fork the repository and create a branch from
main - Set up the development environment as described above
- Make your changes — keep each PR focused on a single concern
- Add tests for any new behaviour
- Run checks before pushing:
docker compose exec laravel.test php artisan test
docker compose exec laravel.test ./vendor/bin/phpstan analyse
docker compose exec laravel.test ./vendor/bin/pint --test - Open a pull request with a clear description of what and why
For larger changes, please open an issue first to discuss the approach.
Commit Messages
Use clear, descriptive commit messages. Prefix with the domain when relevant:
invoicing: add recurring invoice frequency validation
banking: fix CAMT.053 duplicate detection
docs: update API reference with webhook events
Reporting Issues
Open an issue on GitHub with:
- A clear title and description
- Steps to reproduce (if a bug)
- Expected vs actual behaviour
- Your environment (PHP version, OS, browser)