From face48531fbdac01574d9792e4ff00ad716fa4bd Mon Sep 17 00:00:00 2001 From: Moritz Date: Wed, 4 Mar 2026 19:22:45 +0100 Subject: [PATCH] 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 --- CODE_GUIDELINES.md | 10 +++++++-- docs/feature-roadmap.md | 2 +- docs/vereinfacht-api.md | 47 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 docs/vereinfacht-api.md diff --git a/CODE_GUIDELINES.md b/CODE_GUIDELINES.md index bb127f1..c3de14b 100644 --- a/CODE_GUIDELINES.md +++ b/CODE_GUIDELINES.md @@ -62,6 +62,7 @@ We are building a membership management system (Mila) using the following techno **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. +- **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 │ │ ├── cycle_generator.ex # Cycle generation algorithm │ │ └── 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) -│ ├── 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 │ ├── mailer.ex # Email mailer │ ├── release.ex # Release tasks @@ -2874,7 +2880,7 @@ Building accessible applications ensures that all users, including those with di **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 diff --git a/docs/feature-roadmap.md b/docs/feature-roadmap.md index 23f19b7..89c2f39 100644 --- a/docs/feature-roadmap.md +++ b/docs/feature-roadmap.md @@ -247,7 +247,7 @@ - ❌ Payment records/transactions (external payment tracking) - ❌ Payment reminders - ❌ Invoice generation -- ❌ vereinfacht.digital API integration +- ✅ Member–finance-contact sync with vereinfacht.digital API (see `docs/vereinfacht-api.md`); ❌ transaction import / full API integration - ❌ SEPA direct debit support - ❌ Payment reports diff --git a/docs/vereinfacht-api.md b/docs/vereinfacht-api.md new file mode 100644 index 0000000..e0480f4 --- /dev/null +++ b/docs/vereinfacht-api.md @@ -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]=` +- **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`.