diff --git a/lib/mv/membership/import/member_csv.ex b/lib/mv/membership/import/member_csv.ex index bc9acc8..c967bf5 100644 --- a/lib/mv/membership/import/member_csv.ex +++ b/lib/mv/membership/import/member_csv.ex @@ -191,8 +191,10 @@ defmodule Mv.Membership.Import.MemberCSV do normalized != "" && not member_field?(normalized) end) |> Enum.map(fn header -> - "Unknown column '#{header}' will be ignored. " <> - "If this is a custom field, create it in Mila before importing." + gettext( + "Unknown column '%{header}' will be ignored. If this is a custom field, create it in Mila before importing.", + header: header + ) end) {:ok, %{member: member_map, custom: custom_map}, warnings} @@ -311,7 +313,7 @@ defmodule Mv.Membership.Import.MemberCSV do custom_field_lookup = Keyword.get(opts, :custom_field_lookup, %{}) existing_error_count = Keyword.get(opts, :existing_error_count, 0) max_errors = Keyword.get(opts, :max_errors, @default_max_errors) - actor = Keyword.fetch!(opts, :actor) + actor = Keyword.get(opts, :actor, SystemActor.get_system_actor()) {inserted, failed, errors, _collected_error_count, truncated?} = Enum.reduce(chunk_rows_with_lines, {0, 0, [], 0, false}, fn {line_number, row_map}, @@ -607,13 +609,38 @@ defmodule Mv.Membership.Import.MemberCSV do acc_values, acc_errors ) do + # Trim value early and skip if empty + trimmed_value = if is_binary(value), do: String.trim(value), else: value + + # Skip empty values (after trimming) - don't create CFV + if trimmed_value == "" or trimmed_value == nil do + {acc_values, acc_errors} + else + process_non_empty_custom_field( + custom_field_id_str, + trimmed_value, + custom_field_lookup, + acc_values, + acc_errors + ) + end + end + + # Processes a non-empty custom field value + defp process_non_empty_custom_field( + custom_field_id_str, + trimmed_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 + case format_custom_field_value(trimmed_value, value_type, custom_field_name) do {:ok, formatted_value} -> value_map = %{ "custom_field_id" => to_string(custom_field_id), @@ -691,17 +718,16 @@ defmodule Mv.Membership.Import.MemberCSV do defp format_custom_field_value(value, :email, custom_field_name) when is_binary(value) do trimmed = String.trim(value) - # Use simple validation: must contain @ and have valid format - # For CSV import, we use a simpler check than EctoCommons.EmailValidator - # to avoid dependencies and keep it fast - if String.contains?(trimmed, "@") and String.length(trimmed) >= 5 and - String.length(trimmed) <= 254 do - # Basic format check: username@domain.tld - if Regex.match?(~r/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/, trimmed) do - {:ok, %{"_union_type" => "email", "_union_value" => trimmed}} - else - {:error, format_custom_field_error(custom_field_name, :email, trimmed)} - end + # Use EctoCommons.EmailValidator for consistency with Member email validation + changeset = + {%{}, %{email: :string}} + |> Ecto.Changeset.cast(%{email: trimmed}, [:email]) + |> EctoCommons.EmailValidator.validate_email(:email, + checks: Mv.Constants.email_validator_checks() + ) + + if changeset.valid? do + {:ok, %{"_union_type" => "email", "_union_value" => trimmed}} else {:error, format_custom_field_error(custom_field_name, :email, trimmed)} end diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po index f1ae5a3..041507b 100644 --- a/priv/gettext/de/LC_MESSAGES/default.po +++ b/priv/gettext/de/LC_MESSAGES/default.po @@ -2293,6 +2293,11 @@ msgstr "Mitgliederdaten verwalten" msgid "Use the data field name as the CSV column header in your file. Data fields must exist in Mila before importing, so they must be listed in the list of memberdate (like e-mail or first name). Unknown data field columns will be ignored with a warning." msgstr "Verwende die Namen der Datenfelder als Spaltennamen in der CSV Datei. Datenfelder müssen in Mila bereits angelegt sein, damit sie importiert werden können. Sie müssen in der Liste der Mitgliederdaten als Datenfeld enthalten sein (z.B. E-Mail). Spalten mit unbekannten Spaltenüberschriften werden mit einer Warnung ignoriert." +#: lib/mv/membership/import/member_csv.ex +#, elixir-autogen, elixir-format +msgid "Unknown column '%{header}' will be ignored. If this is a custom field, create it in Mila before importing." +msgstr "Unbekannte Spalte '%{header}' wird ignoriert. Falls dies ein Datenfeld ist, erstellen Sie es in Mila vor dem Import." + #~ #: lib/mv_web/live/global_settings_live.ex #~ #, elixir-autogen, elixir-format, fuzzy #~ msgid "Custom Fields in CSV Import" diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index 1ff9c81..2861f2d 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -2293,3 +2293,8 @@ msgstr "" #, elixir-autogen, elixir-format msgid "Use the data field name as the CSV column header in your file. Data fields must exist in Mila before importing, so they must be listed in the list of memberdate (like e-mail or first name). Unknown data field columns will be ignored with a warning." msgstr "" + +#: lib/mv/membership/import/member_csv.ex +#, elixir-autogen, elixir-format +msgid "Unknown column '%{header}' will be ignored. If this is a custom field, create it in Mila before importing." +msgstr "" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index d71a397..3fe9ce3 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -2259,7 +2259,6 @@ msgstr "" msgid "Could not load data fields. Please check your permissions." msgstr "" - #: lib/mv_web/live/global_settings_live.ex #, elixir-autogen, elixir-format, fuzzy msgid "CSV files only, maximum %{size} MB" @@ -2295,6 +2294,11 @@ msgstr "" msgid "Use the data field name as the CSV column header in your file. Data fields must exist in Mila before importing, so they must be listed in the list of memberdate (like e-mail or first name). Unknown data field columns will be ignored with a warning." msgstr "" +#: lib/mv/membership/import/member_csv.ex +#, elixir-autogen, elixir-format +msgid "Unknown column '%{header}' will be ignored. If this is a custom field, create it in Mila before importing." +msgstr "Unknown column '%{header}' will be ignored. If this is a custom field, create it in Mila before importing." + #~ #: lib/mv_web/live/global_settings_live.ex #~ #, elixir-autogen, elixir-format, fuzzy #~ msgid "Custom Fields in CSV Import" diff --git a/test/mv/config_test.exs b/test/mv/config_test.exs deleted file mode 100644 index 076915f..0000000 --- a/test/mv/config_test.exs +++ /dev/null @@ -1,14 +0,0 @@ -defmodule Mv.ConfigTest do - @moduledoc """ - Tests for Mv.Config module. - """ - use ExUnit.Case, async: false - - alias Mv.Config - - # Note: CSV import configuration functions were never implemented. - # The codebase uses hardcoded constants instead: - # - @max_file_size_bytes 10_485_760 in GlobalSettingsLive - # - @default_max_rows 1000 in MemberCSV - # These tests have been removed as they tested non-existent functions. -end diff --git a/test/mv_web/live/global_settings_live_config_test.exs b/test/mv_web/live/global_settings_live_config_test.exs index c940594..1f06145 100644 --- a/test/mv_web/live/global_settings_live_config_test.exs +++ b/test/mv_web/live/global_settings_live_config_test.exs @@ -10,7 +10,7 @@ defmodule MvWeb.GlobalSettingsLiveConfigTest do import Phoenix.LiveViewTest # Helper function to upload CSV file in tests - defp upload_csv_file(view, csv_content, filename \\ "test_import.csv") do + defp upload_csv_file(view, csv_content, filename) do view |> file_input("#csv-upload-form", :csv_file, [ %{