Docs: Vereinfacht API integration and guidelines

- CODE_GUIDELINES: add vereinfacht/ to project structure, required-fields note, link to vereinfacht-api
- docs/vereinfacht-api.md: filter API, minimal create payload, no extra required fields
- feature-roadmap: member-contact sync implemented, link to doc
This commit is contained in:
Moritz 2026-03-04 19:22:45 +01:00
parent e899cc738f
commit face48531f
Signed by: moritz
GPG key ID: 1020A035E5DD0824
3 changed files with 56 additions and 3 deletions

View file

@ -62,6 +62,7 @@ We are building a membership management system (Mila) using the following techno
**Related documents:** **Related documents:**
- **UI / UX:** [`DESIGN_DUIDELINES.md`](../DESIGN_DUIDELINES.md) defines visual and interaction consistency: use of CoreComponents (no raw DaisyUI in views), page skeleton (`<.header>`, `mt-6 space-y-6`), **Back button left in header for edit/new forms** (§2.2), typography, buttons, forms, tables, flash/toast, and microcopy (e.g. German "du" and glossary). Follow "components first" and semantic variants instead of hard-coded colors. - **UI / UX:** [`DESIGN_DUIDELINES.md`](../DESIGN_DUIDELINES.md) defines visual and interaction consistency: use of CoreComponents (no raw DaisyUI in views), page skeleton (`<.header>`, `mt-6 space-y-6`), **Back button left in header for edit/new forms** (§2.2), typography, buttons, forms, tables, flash/toast, and microcopy (e.g. German "du" and glossary). Follow "components first" and semantic variants instead of hard-coded colors.
- **Vereinfacht API:** [`docs/vereinfacht-api.md`](docs/vereinfacht-api.md) describes the finance-contact sync (find by email filter, minimal create payload, no extra required member fields).
--- ---
@ -115,8 +116,13 @@ lib/
│ ├── membership_fees/ # Membership fee business logic │ ├── membership_fees/ # Membership fee business logic
│ │ ├── cycle_generator.ex # Cycle generation algorithm │ │ ├── cycle_generator.ex # Cycle generation algorithm
│ │ └── calendar_cycles.ex # Calendar cycle calculations │ │ └── calendar_cycles.ex # Calendar cycle calculations
│ ├── vereinfacht/ # Vereinfacht accounting API integration
│ │ ├── client.ex # HTTP client (finance-contacts: create, update, find by email)
│ │ ├── vereinfacht.ex # Business logic (sync_member, sync_members_without_contact)
│ │ ├── sync_flash.ex # Flash message helpers for sync results
│ │ └── changes/ # Ash changes (SyncContact, sync linked member)
│ ├── helpers.ex # Shared helper functions (ash_actor_opts) │ ├── helpers.ex # Shared helper functions (ash_actor_opts)
│ ├── constants.ex # Application constants (member_fields, custom_field_prefix) │ ├── constants.ex # Application constants (member_fields, custom_field_prefix, vereinfacht_required_member_fields)
│ ├── application.ex # OTP application │ ├── application.ex # OTP application
│ ├── mailer.ex # Email mailer │ ├── mailer.ex # Email mailer
│ ├── release.ex # Release tasks │ ├── release.ex # Release tasks
@ -2874,7 +2880,7 @@ Building accessible applications ensures that all users, including those with di
**Required Fields:** **Required Fields:**
Which member fields are required (asterisk, tooltip, validation) is configured in **Settings** (Memberdata section: edit a member field and set "Required"). The member create/edit form and Member resource validation both read `settings.member_field_required`. Email is always required; other fields default to optional. Which member fields are required (asterisk, tooltip, validation) is configured in **Settings** (Memberdata section: edit a member field and set "Required"). The member create/edit form and Member resource validation both read `settings.member_field_required`. Email is always required; other fields default to optional. The Vereinfacht integration does not add extra required member fields (the external API accepts a minimal payload when creating contacts and supports filter-by-email for lookup).
```heex ```heex
<!-- Mark required fields (value from settings or always true for email) --> <!-- Mark required fields (value from settings or always true for email) -->

View file

@ -247,7 +247,7 @@
- ❌ Payment records/transactions (external payment tracking) - ❌ Payment records/transactions (external payment tracking)
- ❌ Payment reminders - ❌ Payment reminders
- ❌ Invoice generation - ❌ Invoice generation
- ❌ vereinfacht.digital API integration - ✅ Memberfinance-contact sync with vereinfacht.digital API (see `docs/vereinfacht-api.md`); ❌ transaction import / full API integration
- ❌ SEPA direct debit support - ❌ SEPA direct debit support
- ❌ Payment reports - ❌ Payment reports

47
docs/vereinfacht-api.md Normal file
View file

@ -0,0 +1,47 @@
# Vereinfacht API Integration
This document describes the current integration with the Vereinfacht (verein.visuel.dev) accounting API for syncing members as finance contacts.
## Overview
- **Purpose:** Create and update external finance contacts in Vereinfacht when members are created or updated; support bulk sync for members without a contact ID.
- **Configuration:** ENV or Settings: `VEREINFACHT_API_URL`, `VEREINFACHT_API_KEY`, `VEREINFACHT_CLUB_ID`, optional `VEREINFACHT_APP_URL` for contact view links.
- **Modules:** `Mv.Vereinfacht` (business logic), `Mv.Vereinfacht.Client` (HTTP client), `Mv.Vereinfacht.Changes.SyncContact` (Ash after_transaction change).
## API Usage
### Finding an existing contact by email
The API supports filtered list requests. Use a single GET instead of paginating:
- **Endpoint:** `GET /api/v1/finance-contacts?filter[isExternal]=true&filter[email]=<email>`
- **Client:** `Mv.Vereinfacht.Client.find_contact_by_email/1` builds this URL (with encoded email) and returns `{:ok, contact_id}` if the first match exists, `{:error, :not_found}` otherwise.
- No member fields are required in the app solely for this lookup.
### Creating a contact
When creating an external finance contact, the API only requires:
- **Attributes:** `contactType` (e.g. `"person"`), `isExternal: true`
- **Relationship:** `club` (club ID from config)
Additional attributes (firstName, lastName, email, address, zipCode, city) are optional and are sent when present on the member so the contact is filled in. The app does **not** enforce extra required member fields for Vereinfacht; only Settings-based required fields and email apply.
- **Client:** `Mv.Vereinfacht.Client.create_contact/1` builds the JSON:API body from the member; `Mv.Constants.vereinfacht_required_member_fields/0` is an empty list.
### Updating a contact
- **Endpoint:** `PATCH /api/v1/finance-contacts/:id`
- **Client:** `Mv.Vereinfacht.Client.update_contact/2` sends current member attributes. The API may still validate presence/format of fields on update.
## Flow
1. **Member create/update:** `SyncContact` runs after the transaction. If the member has no `vereinfacht_contact_id`, the client tries `find_contact_by_email(email)`; if found, it updates that contact and stores the ID on the member; otherwise it creates a contact and stores the new ID. If the member already has a contact ID, the client updates the contact.
2. **Bulk sync:** “Sync all members without Vereinfacht contact” calls `Vereinfacht.sync_members_without_contact/0`, which loads members with nil/blank `vereinfacht_contact_id` and runs the same create/update flow per member.
## References
- **Config:** `Mv.Config` (`vereinfacht_api_url`, `vereinfacht_api_key`, `vereinfacht_club_id`, `vereinfacht_app_url`, `vereinfacht_configured?/0`).
- **Constants:** `Mv.Constants.vereinfacht_required_member_fields/0` (empty), `vereinfacht_required_field?/1`.
- **Tests:** `test/mv/vereinfacht/`, `test/mv/config_vereinfacht_test.exs`; see `test/mv/vereinfacht/vereinfacht_test_README.md` for scope.
- **Roadmap:** Payment/transaction import and deeper integration are tracked in `docs/feature-roadmap.md` and `docs/membership-fee-architecture.md`.