feat(vereinfacht): add client, sync flash and SyncContact change

- Application: create SyncFlash ETS table on start
- Vereinfacht: Client, SyncFlash, sync_member, format_error, sync_members_without_contact
- SyncContact change on Member create_member and update_member
- Member: attribute vereinfacht_contact_id, internal action set_vereinfacht_contact_id
This commit is contained in:
Moritz 2026-02-18 22:28:39 +01:00
parent a5a4d66655
commit a008cf381a
Signed by: moritz
GPG key ID: 1020A035E5DD0824
7 changed files with 551 additions and 1 deletions

View file

@ -117,6 +117,9 @@ defmodule Mv.Membership.Member do
# Requires both join_date and membership_fee_type_id to be present
change Mv.MembershipFees.Changes.SetMembershipFeeStartDate
# Sync member to Vereinfacht as finance contact (if configured)
change Mv.Vereinfacht.Changes.SyncContact
# Trigger cycle generation after member creation
# Only runs if membership_fee_type_id is set
# Note: Cycle generation runs asynchronously to not block the action,
@ -190,6 +193,9 @@ defmodule Mv.Membership.Member do
where [changing(:membership_fee_type_id)]
end
# Sync member to Vereinfacht as finance contact (if configured)
change Mv.Vereinfacht.Changes.SyncContact
# Trigger cycle regeneration when membership_fee_type_id changes
# This deletes future unpaid cycles and regenerates them with the new type/amount
# Note: Cycle regeneration runs synchronously in the same transaction to ensure atomicity
@ -243,6 +249,13 @@ defmodule Mv.Membership.Member do
end)
end
# Internal: set vereinfacht_contact_id after syncing with Vereinfacht API.
# Not exposed via code interface; used only by Mv.Vereinfacht.Changes.SyncContact.
update :set_vereinfacht_contact_id do
require_atomic? false
accept [:vereinfacht_contact_id]
end
# Action to handle fuzzy search on specific fields
read :search do
argument :query, :string, allow_nil?: true
@ -320,6 +333,12 @@ defmodule Mv.Membership.Member do
authorize_if Mv.Authorization.Checks.HasPermission
end
# Internal sync action: allow setting vereinfacht_contact_id (used only by SyncContact change).
policy action(:set_vereinfacht_contact_id) do
description "Allow internal sync to set Vereinfacht contact ID"
authorize_if always()
end
# CREATE/UPDATE: Forbid memberuser link unless admin, then check permissions
# ForbidMemberUserLinkUnlessAdmin: only admins may pass :user (link or unlink via nil/empty).
# HasPermission: :own_data → update linked; :read_only → no update; :normal_user/admin → update all.
@ -593,6 +612,14 @@ defmodule Mv.Membership.Member do
public? true
description "Date from which membership fees should be calculated"
end
# Vereinfacht accounting software integration: ID of the finance contact synced via API.
# Set by Mv.Vereinfacht.Changes.SyncContact; not accepted in create/update actions.
attribute :vereinfacht_contact_id, :string do
allow_nil? true
public? true
description "ID of the finance contact in Vereinfacht (set by sync)"
end
end
relationships do
@ -1275,7 +1302,10 @@ defmodule Mv.Membership.Member do
# Extracts custom field values from existing member data (update scenario)
defp extract_existing_values(member_data, changeset) do
actor = Map.get(changeset.context, :actor)
actor =
Map.get(changeset.context, :actor) ||
Mv.Helpers.SystemActor.get_system_actor()
opts = Helpers.ash_actor_opts(actor)
case Ash.load(member_data, :custom_field_values, opts) do