2 changed files with 76 additions and 9 deletions
|
|
@ -17,15 +17,24 @@ defmodule Mv.Membership.Import.HeaderMapper do
|
||||||
|
|
||||||
## Member Field Mapping
|
## Member Field Mapping
|
||||||
|
|
||||||
Maps CSV headers to canonical member fields:
|
Maps CSV headers to canonical member fields (same as `Mv.Constants.member_fields()` for
|
||||||
- `email` (required)
|
importable attributes). All DB-backed member attributes can be imported.
|
||||||
- `first_name` (optional)
|
|
||||||
- `last_name` (optional)
|
|
||||||
- `street` (optional)
|
|
||||||
- `postal_code` (optional)
|
|
||||||
- `city` (optional)
|
|
||||||
|
|
||||||
Supports both English and German variants (e.g., "Email" / "E-Mail", "First Name" / "Vorname").
|
- `email` (required)
|
||||||
|
- `first_name`, `last_name` (optional)
|
||||||
|
- `join_date`, `exit_date` (optional, ISO-8601 date)
|
||||||
|
- `notes` (optional)
|
||||||
|
- `country`, `city`, `street`, `house_number`, `postal_code` (optional)
|
||||||
|
- `membership_fee_start_date` (optional, ISO-8601 date)
|
||||||
|
|
||||||
|
Supports English and German header variants (e.g. "Email" / "E-Mail", "Join Date" / "Beitrittsdatum").
|
||||||
|
|
||||||
|
## Fields not supported for import
|
||||||
|
|
||||||
|
- **membership_fee_status** – Computed (calculation from membership fee cycles). Not stored;
|
||||||
|
cannot be set via CSV. Export can include it.
|
||||||
|
- **groups** – Many-to-many relationship (through member_groups). Import would require
|
||||||
|
resolving group names/slugs to IDs and creating associations; not in current import scope.
|
||||||
|
|
||||||
## Custom Field Detection
|
## Custom Field Detection
|
||||||
|
|
||||||
|
|
@ -75,11 +84,37 @@ defmodule Mv.Membership.Import.HeaderMapper do
|
||||||
"nachname",
|
"nachname",
|
||||||
"familienname"
|
"familienname"
|
||||||
],
|
],
|
||||||
|
join_date: [
|
||||||
|
"join date",
|
||||||
|
"join_date",
|
||||||
|
"beitrittsdatum",
|
||||||
|
"beitritts-datum"
|
||||||
|
],
|
||||||
|
exit_date: [
|
||||||
|
"exit date",
|
||||||
|
"exit_date",
|
||||||
|
"austrittsdatum",
|
||||||
|
"austritts-datum"
|
||||||
|
],
|
||||||
|
notes: [
|
||||||
|
"notes",
|
||||||
|
"notizen",
|
||||||
|
"bemerkungen"
|
||||||
|
],
|
||||||
street: [
|
street: [
|
||||||
"street",
|
"street",
|
||||||
"address",
|
"address",
|
||||||
"strasse"
|
"strasse"
|
||||||
],
|
],
|
||||||
|
house_number: [
|
||||||
|
"house number",
|
||||||
|
"house_number",
|
||||||
|
"house no",
|
||||||
|
"hausnummer",
|
||||||
|
"nr",
|
||||||
|
"nr.",
|
||||||
|
"nummer"
|
||||||
|
],
|
||||||
postal_code: [
|
postal_code: [
|
||||||
"postal code",
|
"postal code",
|
||||||
"postal_code",
|
"postal_code",
|
||||||
|
|
@ -93,6 +128,18 @@ defmodule Mv.Membership.Import.HeaderMapper do
|
||||||
"town",
|
"town",
|
||||||
"stadt",
|
"stadt",
|
||||||
"ort"
|
"ort"
|
||||||
|
],
|
||||||
|
country: [
|
||||||
|
"country",
|
||||||
|
"land",
|
||||||
|
"staat"
|
||||||
|
],
|
||||||
|
membership_fee_start_date: [
|
||||||
|
"membership fee start date",
|
||||||
|
"membership_fee_start_date",
|
||||||
|
"fee start",
|
||||||
|
"beitragsbeginn",
|
||||||
|
"beitrags-beginn"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -549,9 +549,12 @@ defmodule Mv.Membership.Import.MemberCSV do
|
||||||
line_number,
|
line_number,
|
||||||
actor
|
actor
|
||||||
) do
|
) do
|
||||||
|
# Convert empty strings to nil for date fields so Ash accepts them
|
||||||
|
member_attrs = sanitize_date_fields(trimmed_member_attrs)
|
||||||
|
|
||||||
# Create member with custom field values
|
# Create member with custom field values
|
||||||
member_attrs_with_cf =
|
member_attrs_with_cf =
|
||||||
trimmed_member_attrs
|
member_attrs
|
||||||
|> Map.put(:custom_field_values, custom_field_values)
|
|> Map.put(:custom_field_values, custom_field_values)
|
||||||
|
|
||||||
# Only include custom_field_values if not empty
|
# Only include custom_field_values if not empty
|
||||||
|
|
@ -793,6 +796,23 @@ defmodule Mv.Membership.Import.MemberCSV do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Converts empty strings to nil for date fields so Ash can accept them
|
||||||
|
@date_fields [:join_date, :exit_date, :membership_fee_start_date]
|
||||||
|
|
||||||
|
defp sanitize_date_fields(attrs) when is_map(attrs) do
|
||||||
|
Enum.reduce(@date_fields, attrs, fn field, acc ->
|
||||||
|
put_date_field(acc, field, Map.get(acc, field))
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp put_date_field(acc, field, ""), do: Map.put(acc, field, nil)
|
||||||
|
|
||||||
|
defp put_date_field(acc, field, val) when is_binary(val) do
|
||||||
|
if String.trim(val) == "", do: Map.put(acc, field, nil), else: acc
|
||||||
|
end
|
||||||
|
|
||||||
|
defp put_date_field(acc, _field, _), do: acc
|
||||||
|
|
||||||
# Formats Ash errors into MemberCSV.Error structs
|
# Formats Ash errors into MemberCSV.Error structs
|
||||||
defp format_ash_error(%Ash.Error.Invalid{errors: errors}, line_number, email) do
|
defp format_ash_error(%Ash.Error.Invalid{errors: errors}, line_number, email) do
|
||||||
# Try to find email-related errors first (for better error messages)
|
# Try to find email-related errors first (for better error messages)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue