mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-09-06 03:20:49 +02:00
The wording of the dunning and SEPA pre-notification mails was hard-coded in
Python, so adjusting a single sentence required a new release. Both texts now
live in plain text templates that ship as defaults, are copied into the store's
templates/mail/ directory on first start and can be edited there or under
Optionen -> E-Mail-Vorlagen; an existing file is never overwritten and a deleted
one is restored from the shipped default.
A template carries its subject in the first line and the body after a blank
line. Placeholders use the same {{ ... }} syntax as the document templates and
share their member/organization values, so a name means the same thing in a
letter and in the mail that carries it. Unknown placeholders are rejected while
editing instead of during a send run, a line holding nothing but placeholders
that render empty is dropped, and {{#claims}} ... {{/claims}} repeats per entry.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
245 lines
8.8 KiB
Markdown
245 lines
8.8 KiB
Markdown
# 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.
|
||
|
||
## Mail templates
|
||
|
||
Every e-mail CCMA sends -- the welcome mail with its first invoice, dunning mails
|
||
and SEPA pre-notifications -- is rendered from a plain text template. The shipped
|
||
defaults are copied into the store's `templates/mail/` directory on first start
|
||
and can be edited there or under Optionen -> E-Mail-Vorlagen. An existing file is
|
||
never overwritten; a deleted one is restored from the shipped default.
|
||
|
||
A template holds the subject in its first line and the message body after a blank
|
||
line:
|
||
|
||
```
|
||
Betreff: {{reminder.name}} – {{claim.title}}
|
||
|
||
Hallo {{member.first_name}},
|
||
...
|
||
```
|
||
|
||
Placeholders use the same `{{ ... }}` syntax as the document templates. Which ones
|
||
are available depends on the mail; the options dialog lists them per template and
|
||
rejects unknown ones when saving. A line that contains nothing but placeholders
|
||
which render empty (an optional hint, an empty list) is dropped, and
|
||
`{{#claims}} ... {{/claims}}` repeats its content once per claim.
|
||
|
||
## 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.
|