diff --git a/lib/mv_web/live/member_field_live/form_component.ex b/lib/mv_web/live/member_field_live/form_component.ex
index 1bba048..eba86d1 100644
--- a/lib/mv_web/live/member_field_live/form_component.ex
+++ b/lib/mv_web/live/member_field_live/form_component.ex
@@ -16,8 +16,8 @@ defmodule MvWeb.MemberFieldLive.FormComponent do
- `on_cancel` - Callback function to call when form is cancelled
## Note
- Member fields are technical fields that cannot be changed (name, value_type, description, required).
- Only the visibility (show_in_overview) can be modified.
+ Member fields are technical fields that cannot be changed (name, value_type).
+ Visibility (show_in_overview) and required flag are stored in Settings and can be modified.
"""
use MvWeb, :live_component
@@ -27,14 +27,13 @@ defmodule MvWeb.MemberFieldLive.FormComponent do
alias MvWeb.Helpers.FieldTypeFormatter
alias MvWeb.Translations.MemberFields
- @required_fields [:first_name, :last_name, :email]
-
@impl true
def render(assigns) do
assigns =
assigns
|> assign(:field_attributes, get_field_attributes(assigns.member_field))
|> assign(:is_email_field?, assigns.member_field == :email)
+ |> assign(:vereinfacht_required_field?, vereinfacht_required_field?(assigns))
|> assign(:field_label, MemberFields.label(assigns.member_field))
~H"""
@@ -117,89 +116,64 @@ defmodule MvWeb.MemberFieldLive.FormComponent do
-
<.button type="button" phx-click="cancel" phx-target={@myself}>
@@ -225,24 +199,35 @@ defmodule MvWeb.MemberFieldLive.FormComponent do
@impl true
def handle_event("validate", %{"member_field" => member_field_params}, socket) do
- # For member fields, we only validate show_in_overview
- # Other fields are read-only or derived from the Member Resource
form = socket.assigns.form
-
- updated_params =
- member_field_params
- |> Map.put(
- "show_in_overview",
+ # Unchecked checkboxes are not in params; preserve current form value when key is missing
+ show_in_overview =
+ if Map.has_key?(member_field_params, "show_in_overview") do
TypeParsers.parse_boolean(member_field_params["show_in_overview"])
- )
- |> Map.put("name", form.source["name"])
- |> Map.put("value_type", form.source["value_type"])
- |> Map.put("description", form.source["description"])
- |> Map.put("required", form.source["required"])
+ else
+ form.source["show_in_overview"]
+ end
+
+ required =
+ socket.assigns[:vereinfacht_required_field?] ||
+ if Map.has_key?(member_field_params, "required") do
+ TypeParsers.parse_boolean(member_field_params["required"])
+ else
+ form.source["required"]
+ end
+
+ # Merge so we keep name/value_type and have current checkbox state; use as new form source
+ merged_source =
+ form.source
+ |> Map.merge(%{
+ "show_in_overview" => show_in_overview,
+ "required" => required,
+ "name" => form.source["name"],
+ "value_type" => form.source["value_type"]
+ })
updated_form =
- form
- |> Map.put(:value, updated_params)
+ to_form(merged_source, as: "member_field")
|> Map.put(:errors, [])
{:noreply, assign(socket, form: updated_form)}
@@ -250,23 +235,36 @@ defmodule MvWeb.MemberFieldLive.FormComponent do
@impl true
def handle_event("save", %{"member_field" => member_field_params}, socket) do
- # Only show_in_overview can be changed for member fields
- show_in_overview = TypeParsers.parse_boolean(member_field_params["show_in_overview"])
+ form = socket.assigns.form
+ # Unchecked checkboxes are not in submit params; use form source when key missing
+ show_in_overview =
+ if Map.has_key?(member_field_params, "show_in_overview") do
+ TypeParsers.parse_boolean(member_field_params["show_in_overview"])
+ else
+ form.source["show_in_overview"]
+ end
+
+ required =
+ socket.assigns[:vereinfacht_required_field?] ||
+ if Map.has_key?(member_field_params, "required") do
+ TypeParsers.parse_boolean(member_field_params["required"])
+ else
+ form.source["required"]
+ end
+
field_string = Atom.to_string(socket.assigns.member_field)
- # Use atomic action to update only this single field
- # This prevents lost updates in concurrent scenarios
- case Membership.update_single_member_field_visibility(
+ case Membership.update_single_member_field(
socket.assigns.settings,
field: field_string,
- show_in_overview: show_in_overview
+ show_in_overview: show_in_overview,
+ required: required
) do
{:ok, _updated_settings} ->
socket.assigns.on_save.(socket.assigns.member_field, "update")
{:noreply, socket}
{:error, error} ->
- # Add error to form
form =
socket.assigns.form
|> Map.put(:errors, [
@@ -288,16 +286,22 @@ defmodule MvWeb.MemberFieldLive.FormComponent do
defp assign_form(%{assigns: %{member_field: member_field, settings: settings}} = socket) do
field_attributes = get_field_attributes(member_field)
visibility_config = settings.member_field_visibility || %{}
- normalized_config = VisibilityConfig.normalize(visibility_config)
- show_in_overview = Map.get(normalized_config, member_field, true)
+ required_config = settings.member_field_required || %{}
+ normalized_visibility = VisibilityConfig.normalize(visibility_config)
+ normalized_required = VisibilityConfig.normalize(required_config)
+ show_in_overview = Map.get(normalized_visibility, member_field, true)
+ vereinfacht_required? = Mv.Config.vereinfacht_configured?()
+
+ # Email always required; Vereinfacht-required fields when integration active; else from settings
+ required =
+ member_field == :email ||
+ (vereinfacht_required? && Mv.Constants.vereinfacht_required_field?(member_field)) ||
+ Map.get(normalized_required, member_field, false)
- # Create a manual form structure with string keys
- # Note: immutable is not included as it's not editable for member fields
form_data = %{
"name" => MemberFields.label(member_field),
"value_type" => FieldTypeFormatter.format(field_attributes.value_type),
- "description" => field_attributes.description || "",
- "required" => field_attributes.required,
+ "required" => required,
"show_in_overview" => show_in_overview
}
@@ -307,24 +311,14 @@ defmodule MvWeb.MemberFieldLive.FormComponent do
end
defp get_field_attributes(field) when is_atom(field) do
- # Get attribute info from Member Resource
alias Ash.Resource.Info
case Info.attribute(Mv.Membership.Member, field) do
nil ->
- # Fallback for fields not in resource (shouldn't happen with Constants)
- %{
- value_type: :string,
- description: nil,
- required: field in @required_fields
- }
+ %{value_type: :string}
attribute ->
- %{
- value_type: attribute.type,
- description: nil,
- required: not attribute.allow_nil?
- }
+ %{value_type: attribute.type}
end
end
@@ -335,4 +329,9 @@ defmodule MvWeb.MemberFieldLive.FormComponent do
defp format_error(error) do
inspect(error)
end
+
+ defp vereinfacht_required_field?(assigns) do
+ Mv.Config.vereinfacht_configured?() &&
+ Mv.Constants.vereinfacht_required_field?(assigns.member_field)
+ end
end
diff --git a/lib/mv_web/live/member_field_live/index_component.ex b/lib/mv_web/live/member_field_live/index_component.ex
index 5204030..db62778 100644
--- a/lib/mv_web/live/member_field_live/index_component.ex
+++ b/lib/mv_web/live/member_field_live/index_component.ex
@@ -22,7 +22,6 @@ defmodule MvWeb.MemberFieldLive.IndexComponent do
assigns =
assigns
|> assign(:member_fields, get_member_fields_with_visibility(assigns.settings))
- |> assign(:required?, &required?/1)
~H"""
@@ -62,22 +61,15 @@ defmodule MvWeb.MemberFieldLive.IndexComponent do
{format_value_type(field_data.field)}
- <:col :let={{_field_name, field_data}} label={gettext("Description")}>
- {field_data.description || ""}
-
-
<:col
:let={{_field_name, field_data}}
label={gettext("Required")}
class="max-w-[9.375rem] text-center"
>
-
+
{gettext("Required")}
-
+
{gettext("Optional")}
@@ -173,26 +165,35 @@ defmodule MvWeb.MemberFieldLive.IndexComponent do
{:error, _} ->
# Return a minimal struct-like map for fallback
# This is only used for initial rendering, actual settings will be loaded properly
- %{member_field_visibility: %{}}
+ %{member_field_visibility: %{}, member_field_required: %{}}
end
end
defp get_member_fields_with_visibility(settings) do
member_fields = Mv.Constants.member_fields()
visibility_config = settings.member_field_visibility || %{}
+ required_config = settings.member_field_required || %{}
+ vereinfacht_required? = Mv.Config.vereinfacht_configured?()
- # Normalize visibility config keys to atoms
- normalized_config = VisibilityConfig.normalize(visibility_config)
+ normalized_visibility = VisibilityConfig.normalize(visibility_config)
+ normalized_required = VisibilityConfig.normalize(required_config)
Enum.map(member_fields, fn field ->
- show_in_overview = Map.get(normalized_config, field, true)
+ show_in_overview = Map.get(normalized_visibility, field, true)
+
+ # Email always required; Vereinfacht-required fields when integration active; else from settings
+ required =
+ field == :email ||
+ (vereinfacht_required? && Mv.Constants.vereinfacht_required_field?(field)) ||
+ Map.get(normalized_required, field, false)
+
attribute = Info.attribute(Mv.Membership.Member, field)
%{
field: field,
show_in_overview: show_in_overview,
- value_type: (attribute && attribute.type) || :string,
- description: nil
+ required: required,
+ value_type: (attribute && attribute.type) || :string
}
end)
|> Enum.map(fn field_data ->
@@ -206,14 +207,4 @@ defmodule MvWeb.MemberFieldLive.IndexComponent do
attribute -> FieldTypeFormatter.format(attribute.type)
end
end
-
- # Check if a field is required by checking the actual attribute definition
- defp required?(field) when is_atom(field) do
- case Info.attribute(Mv.Membership.Member, field) do
- nil -> false
- attribute -> not attribute.allow_nil?
- end
- end
-
- defp required?(_), do: false
end
diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po
index d39d86b..c418dca 100644
--- a/priv/gettext/de/LC_MESSAGES/default.po
+++ b/priv/gettext/de/LC_MESSAGES/default.po
@@ -287,8 +287,6 @@ msgstr "Abbrechen"
#: lib/mv_web/live/group_live/form.ex
#: lib/mv_web/live/group_live/index.ex
#: lib/mv_web/live/group_live/show.ex
-#: lib/mv_web/live/member_field_live/form_component.ex
-#: lib/mv_web/live/member_field_live/index_component.ex
#: lib/mv_web/live/membership_fee_type_live/form.ex
#: lib/mv_web/live/role_live/form.ex
#: lib/mv_web/live/role_live/index.html.heex
@@ -2911,3 +2909,8 @@ msgstr "Okt."
#, elixir-autogen, elixir-format
msgid "Sep."
msgstr "Sep."
+
+#: lib/mv_web/live/member_field_live/form_component.ex
+#, elixir-autogen, elixir-format
+msgid "Required for Vereinfacht integration and cannot be disabled."
+msgstr "Für die Vereinfacht-Integration erforderlich und kann nicht deaktiviert werden."
diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot
index ff466ab..2e7e480 100644
--- a/priv/gettext/default.pot
+++ b/priv/gettext/default.pot
@@ -288,8 +288,6 @@ msgstr ""
#: lib/mv_web/live/group_live/form.ex
#: lib/mv_web/live/group_live/index.ex
#: lib/mv_web/live/group_live/show.ex
-#: lib/mv_web/live/member_field_live/form_component.ex
-#: lib/mv_web/live/member_field_live/index_component.ex
#: lib/mv_web/live/membership_fee_type_live/form.ex
#: lib/mv_web/live/role_live/form.ex
#: lib/mv_web/live/role_live/index.html.heex
@@ -2911,3 +2909,8 @@ msgstr ""
#, elixir-autogen, elixir-format
msgid "Sep."
msgstr ""
+
+#: lib/mv_web/live/member_field_live/form_component.ex
+#, elixir-autogen, elixir-format
+msgid "Required for Vereinfacht integration and cannot be disabled."
+msgstr ""
diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po
index e5e0181..3c53a7e 100644
--- a/priv/gettext/en/LC_MESSAGES/default.po
+++ b/priv/gettext/en/LC_MESSAGES/default.po
@@ -288,8 +288,6 @@ msgstr ""
#: lib/mv_web/live/group_live/form.ex
#: lib/mv_web/live/group_live/index.ex
#: lib/mv_web/live/group_live/show.ex
-#: lib/mv_web/live/member_field_live/form_component.ex
-#: lib/mv_web/live/member_field_live/index_component.ex
#: lib/mv_web/live/membership_fee_type_live/form.ex
#: lib/mv_web/live/role_live/form.ex
#: lib/mv_web/live/role_live/index.html.heex
@@ -2911,3 +2909,8 @@ msgstr ""
#, elixir-autogen, elixir-format
msgid "Sep."
msgstr ""
+
+#: lib/mv_web/live/member_field_live/form_component.ex
+#, elixir-autogen, elixir-format
+msgid "Required for Vereinfacht integration and cannot be disabled."
+msgstr "Required for Vereinfacht integration and cannot be disabled."