# CCMA - Chaotic Creature Member Administration

File-based member administration for Chaos Computer Club Mannheim e.V.

The member store remains readable without this application. Every member has a
directory containing `member.json`, `contributions.json`, an append-only
`events.jsonl`, and a `files/` directory.

## Development

Requires Python 3.11+ with Tk support.
PDF generation from document templates additionally requires LibreOffice or
OpenOffice with a `soffice` command available on the system.

### Windows (PowerShell)

```powershell
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
.\.venv\Scripts\ccma.exe
```

For development tools and tests, install the `dev` extra as well:

```powershell
.\.venv\Scripts\python.exe -m pip install -e .[dev]
```

Alternatively, without installation:

```powershell
$env:PYTHONPATH = "src"
.\.venv\Scripts\python.exe -m ccma
```

### Linux/macOS

```bash
python -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/ccma
```

For development tools and tests, install the `dev` extra as well:

```bash
.venv/bin/pip install -e '.[dev]'
```

Alternatively, without installation:

```bash
PYTHONPATH=src python -m ccma
```

If LibreOffice is installed on Windows but `soffice` is not in `PATH`, CCMA
also checks the default install locations under `%PROGRAMFILES%` and
`%PROGRAMFILES(X86)%`.

## Building standalone executables

CCMA uses the same PyInstaller-based release flow locally and in CI. The
single-file executables are built from `build/ccma.spec`.

### Windows standalone build

```powershell
cd build
.\build.ps1
```

This creates two executables in the `dist/` directory:
- `ccma.exe` (generic)
- `ccma-<version>-windows-<arch>.exe` (version and architecture-specific, e.g. `ccma-0.1.0-windows-amd64.exe`)

The build script automatically:
- Creates a venv if missing
- Installs dependencies and PyInstaller
- Generates a Windows icon from the CCMA icon
- Runs PyInstaller to bundle the application

### Linux standalone build

```bash
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -U pip
pip install -r requirements.txt
pip install pyinstaller
python -m PyInstaller --noconfirm --clean build/ccma.spec
```

This creates `dist/ccma`. In CI, release builds additionally rename artifacts
to `ccma-<version>-linux-<arch>` and `ccma-<version>-windows-<arch>.exe`.

### CI release builds

The Gitea workflows use the central `ci-templates` submodule and
`ci-config.yaml` to run PR checks, Linux smoke builds, and release packaging for
Linux amd64, Linux arm64, and Windows amd64. A release build is skipped when the
commit message contains `[nobuild]`.

On first start, select or create the central member-store directory. The
`VERSION` file is the single source for application and package versions.

The splash screen remains visible for at least five seconds by default. This
advanced setting is intentionally not exposed in the options dialog. It can be
changed directly in `config.json`, including disabling the minimum with `0`:

```json
"splash_minimum_seconds": 0
```

## Store layout

```text
member-store/
├── repository.json
├── housekeeper.json
├── rules/
├── templates/
│   ├── Forderung mit Positionen.fodt
│   ├── Mahnung.fodt
│   └── Mitglied.fodt
└── members/
    └── <uuid>/
        ├── member.json
        ├── contributions.json
        ├── events.jsonl
        └── files/
```

## Document templates

CCMA reads OpenDocument templates (`.fodt`, `.odt`, or `.ott`) from the
store's `templates/` directory. The three initial `.fodt` files are editable
with LibreOffice/OpenOffice and are copied only when their filename does not
already exist. PDF files are generated locally and stored below the member's
`files/documents/` directory. No office document content is sent to an
external service.

Placeholders use `{{group.field}}`. Available values include:

- Member: `member.number`, `member.first_name`, `member.last_name`,
  `member.full_name`, `member.email`, `member.birth_date`, `member.status`,
  `member.accepted_at`, `member.started_at`, `member.phone`, `member.street`,
  `member.address_addition`, `member.postal_code`, `member.city`,
  `member.country`, `member.address_line`, `member.account_holder`,
  `member.iban`, `member.bic`, `member.mandate_reference`,
  `member.mandate_signed_at`, `member.mandate_revoked_at`,
  `member.mandate_active`
- Claim: `claim.id`, `claim.title`, `claim.due_date`, `claim.total`,
  `claim.created_date`, `claim.created_at`, `claim.paid`, `claim.balance`,
  `claim.status`, `claim.items`
- Reminder: `reminder.id`, `reminder.level`, `reminder.name`,
  `reminder.status`, `reminder.created_at`, `reminder.sent_at`,
  `reminder.payment_deadline`, `reminder.payment_deadline_days`,
  `reminder.fee`, `reminder.detail`, `reminder.channel`
- Document: `document.created_date`, `document.created_at`
- Current time: `current_date`, `current_datetime`
- Organization: `organization.name`, `organization.street`,
  `organization.postal_code`, `organization.city`, `organization.country`,
  `organization.address_line`, `organization.email`, `organization.phone`,
  `organization.website`, `organization.iban`, `organization.bic`,
  `organization.creditor_id`

Claim placeholders are available from a claim tab. Reminder placeholders are
available when a reminder row is selected before opening the document dialog.
Unknown or unavailable placeholders stop generation with a clear error rather
than producing an incomplete letter.

To repeat a formatted table row for every claim item, place both loop markers
inside the same template row:

```text
{{#claim.items}}
{{item.description}} | {{item.type}} | {{item.quantity}} | {{item.unit_price}} | {{item.amount}}
{{/claim.items}}
```

The opening marker may share the first cell with its value and the closing
marker may share the last cell. CCMA removes both markers and clones the whole
row, including its formatting, once per item. With no items, the template row
is removed. A loop that is not closed in the same row is rejected.

## Housekeeper rules

The housekeeper runs every rule for every member. Built-in Python rules live in
`ccma/rules/scripts/`. A member store can add rules in its `rules/` directory.
If a store rule has the same filename as a built-in rule, the store version
replaces the built-in version.

Store rules are trusted executable Python code. Only place reviewed rules from
trusted sources in this directory. Rules return structured `RuleAction` objects;
CCMA performs all file writes, duplicate checks, audit events, and atomic updates.

`housekeeper.json` is written only after a complete run. Each refreshed task gets
the pending run ID. A failed run therefore cannot advance the stored counter or
silently resolve existing tasks.

## Claims and payments

Claims are stored in the member's `contributions.json`. A claim consists of
signed line items; fees increase and credits reduce its total. Payments remain
separate records and allocations connect one payment to one or more claims.
This supports partial payments, shared annual payments, unallocated credit, and
optional GnuCash transaction IDs without duplicating a bank transaction.

Claim status and outstanding balance are derived from line items and payment
allocations. Reminders are separate processes and only change the amount when
they explicitly add a fee line item. Every change is also appended to the
member's `events.jsonl` audit trail.

Overdue claims are evaluated by the reminder rule. It creates housekeeper tasks
for the next configured reminder level. Reminder drafts do not change a claim;
only confirming actual dispatch starts the new payment deadline and adds an
optional fee line item. A claim-level dunning hold suppresses automatic and
manual reminder preparation until it is removed or expires.

Do not place a real member store inside the source repository.
