Vereinfacht accounting software API closes #431 #432

Merged
moritz merged 31 commits from feature/vereinfacht_api into main 2026-02-23 21:18:46 +01:00
6 changed files with 98 additions and 11 deletions
Showing only changes of commit 9d3c72acff - Show all commits

View file

@ -36,3 +36,4 @@ ASSOCIATION_NAME="Sportsclub XYZ"
# VEREINFACHT_API_URL=https://api.verein.visuel.dev/api/v1
# VEREINFACHT_API_KEY=your-api-key
# VEREINFACHT_CLUB_ID=2
# VEREINFACHT_APP_URL=https://app.verein.visuel.dev

View file

@ -72,7 +72,8 @@ defmodule Mv.Membership.Setting do
:default_membership_fee_type_id,
:vereinfacht_api_url,
:vereinfacht_api_key,
:vereinfacht_club_id
:vereinfacht_club_id,
:vereinfacht_app_url
]
end
@ -87,7 +88,8 @@ defmodule Mv.Membership.Setting do
:default_membership_fee_type_id,
:vereinfacht_api_url,
:vereinfacht_api_key,
:vereinfacht_club_id
:vereinfacht_club_id,
:vereinfacht_app_url
]
end
@ -251,6 +253,13 @@ defmodule Mv.Membership.Setting do
description "Vereinfacht club ID for multi-tenancy"
end
attribute :vereinfacht_app_url, :string do
allow_nil? true
public? true
description "Vereinfacht app base URL for contact view links (e.g. https://app.verein.visuel.dev)"
end
timestamps()
end

View file

@ -178,6 +178,37 @@ defmodule Mv.Config do
env_or_setting("VEREINFACHT_CLUB_ID", :vereinfacht_club_id)
end
@doc """
Returns the Vereinfacht app base URL for contact view links (frontend, not API).
Reads from `VEREINFACHT_APP_URL` env first, then from Settings.
Used to build links like https://app.verein.visuel.dev/en/admin/finances/contacts/{id}.
If not set, derived from API URL by replacing host \"api.\" with \"app.\" when possible.
"""
@spec vereinfacht_app_url() :: String.t() | nil
def vereinfacht_app_url do
env_or_setting("VEREINFACHT_APP_URL", :vereinfacht_app_url) ||
derive_app_url_from_api_url(vereinfacht_api_url())
end
defp derive_app_url_from_api_url(nil), do: nil
defp derive_app_url_from_api_url(api_url) when is_binary(api_url) do
api_url = String.trim(api_url)
uri = URI.parse(api_url)
host = uri.host || ""
if String.starts_with?(host, "api.") do
app_host = "app." <> String.slice(host, 4..-1//1)
scheme = uri.scheme || "https"
"#{scheme}://#{app_host}"
else
nil
end
end
defp derive_app_url_from_api_url(_), do: nil
@doc """
Returns true if Vereinfacht is fully configured (URL, API key, and club ID all set).
"""
@ -211,6 +242,11 @@ defmodule Mv.Config do
"""
def vereinfacht_club_id_env_set?, do: env_set?("VEREINFACHT_CLUB_ID")
@doc """
Returns true if VEREINFACHT_APP_URL is set (field is read-only in Settings).
"""
def vereinfacht_app_url_env_set?, do: env_set?("VEREINFACHT_APP_URL")
defp env_set?(key) do
case System.get_env(key) do
nil -> false
@ -241,18 +277,22 @@ defmodule Mv.Config do
end
@doc """
Returns the URL to view a finance contact (e.g. in Vereinfacht frontend or API).
Returns the URL to view a finance contact in the Vereinfacht app (frontend).
Uses the configured API base URL and appends /finance-contacts/{id}.
Can be extended later with a dedicated frontend URL setting.
Uses the configured app base URL (or derived from API URL) and appends
/en/admin/finances/contacts/{id}. Returns nil if no app URL can be determined.
"""
@spec vereinfacht_contact_view_url(String.t()) :: String.t() | nil
def vereinfacht_contact_view_url(contact_id) when is_binary(contact_id) do
base = vereinfacht_api_url()
base = vereinfacht_app_url()
if present?(base),
do: base |> String.trim_trailing("/") |> then(&"#{&1}/finance-contacts/#{contact_id}"),
else: nil
if present?(base) do
base
|> String.trim_trailing("/")
|> then(&"#{&1}/en/admin/finances/contacts/#{contact_id}")
else
nil
end
end
defp present?(nil), do: false

View file

@ -48,6 +48,7 @@ defmodule MvWeb.GlobalSettingsLive do
|> assign(:vereinfacht_api_url_env_set, Mv.Config.vereinfacht_api_url_env_set?())
|> assign(:vereinfacht_api_key_env_set, Mv.Config.vereinfacht_api_key_env_set?())
|> assign(:vereinfacht_club_id_env_set, Mv.Config.vereinfacht_club_id_env_set?())
|> assign(:vereinfacht_app_url_env_set, Mv.Config.vereinfacht_app_url_env_set?())
|> assign(:vereinfacht_api_key_set, present?(settings.vereinfacht_api_key))
|> assign(:last_vereinfacht_sync_result, nil)
|> assign_form()
@ -142,6 +143,18 @@ defmodule MvWeb.GlobalSettingsLive do
if(@vereinfacht_club_id_env_set, do: gettext("From VEREINFACHT_CLUB_ID"), else: "2")
}
/>
<.input
field={@form[:vereinfacht_app_url]}
type="text"
label={gettext("App URL (contact view link)")}
disabled={@vereinfacht_app_url_env_set}
placeholder={
if(@vereinfacht_app_url_env_set,
do: gettext("From VEREINFACHT_APP_URL"),
else: "https://app.verein.visuel.dev"
)
}
/>
</div>
<.button
:if={

View file

@ -0,0 +1,15 @@
defmodule Mv.Repo.Migrations.AddVereinfachtAppUrl do
use Ecto.Migration
def up do
alter table(:settings) do
add :vereinfacht_app_url, :text
end
end
def down do
alter table(:settings) do
remove :vereinfacht_app_url
end
end
end

View file

@ -39,14 +39,23 @@ defmodule Mv.ConfigVereinfachtTest do
assert Mv.Config.vereinfacht_contact_view_url("123") == nil
end
test "returns URL when API URL is set" do
test "returns app contact view URL when API URL is set (derived app URL)" do
set_vereinfacht_env("VEREINFACHT_API_URL", "https://api.example.com/api/v1")
assert Mv.Config.vereinfacht_contact_view_url("42") ==
"https://api.example.com/api/v1/finance-contacts/42"
"https://app.example.com/en/admin/finances/contacts/42"
after
clear_vereinfacht_env()
end
test "returns app contact view URL when VEREINFACHT_APP_URL is set" do
set_vereinfacht_env("VEREINFACHT_APP_URL", "https://app.verein.visuel.dev")
assert Mv.Config.vereinfacht_contact_view_url("abc") ==
"https://app.verein.visuel.dev/en/admin/finances/contacts/abc"
after
System.delete_env("VEREINFACHT_APP_URL")
end
end
defp set_vereinfacht_env(key, value) do