- Normalize email (trim + downcase) before filter lookup - Log warning when API returns multiple contacts for same email - Add Bypass tests for find_contact_by_email (query params, empty/single response parsing) - Document vereinfacht_required_field? as legacy/unused in vereinfacht-api.md - Add bypass dependency (dev+test) for HTTP stubbing
47 lines
3.2 KiB
Markdown
47 lines
3.2 KiB
Markdown
# 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, country) 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` (legacy; currently unused in UI or validation).
|
|
- **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`.
|