Skip to main content

Contributing & Architecture

This guide is for developers who want to contribute to Gäld or understand its internals.

Development Setup

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:

ServiceContainerPort
Laravel (PHP + Nginx)laravel.test8080
PostgreSQL 16pgsql5432
Redis 7redis6379
MeiliSearchmeilisearch7700
Mailpit (dev email)mailpit8025 (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

LayerTechnology
BackendLaravel 12, PHP 8.4
FrontendVue 3 + Inertia.js
DatabasePostgreSQL 16
Cache/Queue/SessionRedis 7
SearchMeiliSearch (fallback: database)
Monetary mathBCMath (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_id foreign key
  • The BelongsToOrganization trait adds a global scope that auto-filters queries and auto-assigns the org on creation
  • The CurrentOrganization service holds the active org for the current request
  • The EnsureHasOrganization middleware resolves the org from the session (web) or token (API)

Key Traits

TraitPurpose
BelongsToOrganizationAuto-scopes all queries to current org, auto-assigns organization_id on create
AuditableTracks model changes in the activity log
HasUuidsUses UUIDs as primary keys

Feature Flags

Features are controlled via FEATURE_* environment variables:

FlagDefaultDescription
FEATURE_BANK_SYNCfalseBank connection and auto-sync
FEATURE_AUTO_RECONCILIATIONfalseAutomatic payment matching
FEATURE_AUTOMATIONfalseRule engine and automation
FEATURE_MULTI_CURRENCYfalseMulti-currency support
FEATURE_API_ACCESSfalseREST API access
FEATURE_RULE_ENGINEfalseBusiness rules engine
FEATURE_SAASfalseSaaS/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
SuiteLocationPurpose
Featuretests/Feature/Integration tests — HTTP requests, database, full stack
Unittests/Unit/Isolated unit tests — services, models, helpers
Securitytests/Security/Security-focused tests — auth, permissions, CSRF, injection

Testing Conventions

  • Use #[DataProvider('name')] attribute (not @dataProvider annotation)
  • 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

  1. Fork the repository and create a branch from main
  2. Set up the development environment as described above
  3. Make your changes — keep each PR focused on a single concern
  4. Add tests for any new behaviour
  5. 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
  6. 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)