From 12715f3d8523f7345de9434b995518320c41b4bd Mon Sep 17 00:00:00 2001 From: carla Date: Mon, 2 Feb 2026 13:07:08 +0100 Subject: [PATCH] refactoring --- lib/mv/membership/import/member_csv.ex | 172 +++++++++++++++---------- 1 file changed, 104 insertions(+), 68 deletions(-) diff --git a/lib/mv/membership/import/member_csv.ex b/lib/mv/membership/import/member_csv.ex index 5924001..2a1c0b4 100644 --- a/lib/mv/membership/import/member_csv.ex +++ b/lib/mv/membership/import/member_csv.ex @@ -524,32 +524,12 @@ defmodule Mv.Membership.Import.MemberCSV do {:error, %Error{csv_line_number: line_number, field: nil, message: first_error}} {:ok, custom_field_values} -> - # Create member with custom field values - member_attrs_with_cf = - trimmed_member_attrs - |> Map.put(:custom_field_values, custom_field_values) - - # Only include custom_field_values if not empty - final_attrs = - if Enum.empty?(custom_field_values) do - Map.delete(member_attrs_with_cf, :custom_field_values) - else - member_attrs_with_cf - end - - case Mv.Membership.create_member(final_attrs, actor: actor) do - {:ok, member} -> - {:ok, member} - - {:error, %Ash.Error.Invalid{} = error} -> - # Extract email from final_attrs for better error messages - email = Map.get(final_attrs, :email) || Map.get(trimmed_member_attrs, :email) - {:error, format_ash_error(error, line_number, email)} - - {:error, error} -> - {:error, - %Error{csv_line_number: line_number, field: nil, message: inspect(error)}} - end + create_member_with_custom_fields( + trimmed_member_attrs, + custom_field_values, + line_number, + actor + ) end end rescue @@ -557,6 +537,40 @@ defmodule Mv.Membership.Import.MemberCSV do {:error, %Error{csv_line_number: line_number, field: nil, message: Exception.message(e)}} end + # Creates a member with custom field values, handling errors appropriately + defp create_member_with_custom_fields( + trimmed_member_attrs, + custom_field_values, + line_number, + actor + ) do + # Create member with custom field values + member_attrs_with_cf = + trimmed_member_attrs + |> Map.put(:custom_field_values, custom_field_values) + + # Only include custom_field_values if not empty + final_attrs = + if Enum.empty?(custom_field_values) do + Map.delete(member_attrs_with_cf, :custom_field_values) + else + member_attrs_with_cf + end + + case Mv.Membership.create_member(final_attrs, actor: actor) do + {:ok, member} -> + {:ok, member} + + {:error, %Ash.Error.Invalid{} = error} -> + # Extract email from final_attrs for better error messages + email = Map.get(final_attrs, :email) || Map.get(trimmed_member_attrs, :email) + {:error, format_ash_error(error, line_number, email)} + + {:error, error} -> + {:error, %Error{csv_line_number: line_number, field: nil, message: inspect(error)}} + end + end + # Prepares custom field values from row map for Ash # Returns {:ok, [custom_field_value_maps]} or {:error, [validation_errors]} defp prepare_custom_field_values(custom_attrs, custom_field_lookup) when is_map(custom_attrs) do @@ -564,25 +578,13 @@ defmodule Mv.Membership.Import.MemberCSV do custom_attrs |> Enum.filter(fn {_id, value} -> value != nil && value != "" end) |> Enum.reduce({[], []}, fn {custom_field_id_str, value}, {acc_values, acc_errors} -> - case Map.get(custom_field_lookup, custom_field_id_str) do - nil -> - # Custom field not found, skip - {acc_values, acc_errors} - - %{id: custom_field_id, value_type: value_type, name: custom_field_name} -> - case format_custom_field_value(value, value_type, custom_field_name) do - {:ok, formatted_value} -> - value_map = %{ - "custom_field_id" => to_string(custom_field_id), - "value" => formatted_value - } - - {[value_map | acc_values], acc_errors} - - {:error, reason} -> - {acc_values, [reason | acc_errors]} - end - end + process_single_custom_field( + custom_field_id_str, + value, + custom_field_lookup, + acc_values, + acc_errors + ) end) if Enum.empty?(errors) do @@ -594,6 +596,35 @@ defmodule Mv.Membership.Import.MemberCSV do defp prepare_custom_field_values(_, _), do: {:ok, []} + # Processes a single custom field value and returns updated accumulator + defp process_single_custom_field( + custom_field_id_str, + value, + custom_field_lookup, + acc_values, + acc_errors + ) do + case Map.get(custom_field_lookup, custom_field_id_str) do + nil -> + # Custom field not found, skip + {acc_values, acc_errors} + + %{id: custom_field_id, value_type: value_type, name: custom_field_name} -> + case format_custom_field_value(value, value_type, custom_field_name) do + {:ok, formatted_value} -> + value_map = %{ + "custom_field_id" => to_string(custom_field_id), + "value" => formatted_value + } + + {[value_map | acc_values], acc_errors} + + {:error, reason} -> + {acc_values, [reason | acc_errors]} + end + end + end + # Formats a custom field value according to its type # Uses _union_type and _union_value format as expected by Ash # Returns {:ok, formatted_value} or {:error, error_message} @@ -620,31 +651,19 @@ defmodule Mv.Membership.Import.MemberCSV do defp format_custom_field_value(value, :boolean, custom_field_name) when is_binary(value) do trimmed = String.trim(value) - lower = String.downcase(trimmed) - bool_value = - case lower do - "true" -> true - "1" -> true - "yes" -> true - "ja" -> true - "false" -> false - "0" -> false - "no" -> false - "nein" -> false - _ -> nil - end + case parse_boolean_value(trimmed) do + {:ok, bool_value} -> + {:ok, %{"_union_type" => "boolean", "_union_value" => bool_value}} - if bool_value != nil do - {:ok, %{"_union_type" => "boolean", "_union_value" => bool_value}} - else - {:error, - format_custom_field_error_with_details( - custom_field_name, - :boolean, - trimmed, - gettext("(true/false/1/0/yes/no/ja/nein)") - )} + :error -> + {:error, + format_custom_field_error_with_details( + custom_field_name, + :boolean, + trimmed, + gettext("(true/false/1/0/yes/no/ja/nein)") + )} end end @@ -690,6 +709,23 @@ defmodule Mv.Membership.Import.MemberCSV do {:ok, %{"_union_type" => "string", "_union_value" => String.trim(value)}} end + # Parses a boolean value from a string, supporting multiple formats + defp parse_boolean_value(value) when is_binary(value) do + lower = String.downcase(value) + parse_boolean_value_lower(lower) + end + + # Helper function with pattern matching for boolean values + defp parse_boolean_value_lower("true"), do: {:ok, true} + defp parse_boolean_value_lower("1"), do: {:ok, true} + defp parse_boolean_value_lower("yes"), do: {:ok, true} + defp parse_boolean_value_lower("ja"), do: {:ok, true} + defp parse_boolean_value_lower("false"), do: {:ok, false} + defp parse_boolean_value_lower("0"), do: {:ok, false} + defp parse_boolean_value_lower("no"), do: {:ok, false} + defp parse_boolean_value_lower("nein"), do: {:ok, false} + defp parse_boolean_value_lower(_), do: :error + # Generates a consistent error message for custom field validation failures # Uses human-readable field type labels (e.g., "Number" instead of "integer") defp format_custom_field_error(custom_field_name, value_type, value) do