<.link
:if={Mv.Config.vereinfacht_contact_view_url(@member.vereinfacht_contact_id)}
href={Mv.Config.vereinfacht_contact_view_url(@member.vereinfacht_contact_id)}
target="_blank"
rel="noopener noreferrer"
- class="link link-accent underline inline-flex items-center gap-1"
+ class="link link-accent underline inline-flex items-center gap-1 w-fit"
>
{gettext("View contact in Vereinfacht")}
<.icon name="hero-arrow-top-right-on-square" class="inline-block size-4" />
-
-
{gettext("Debug:")}
+
- <%= if @vereinfacht_debug_response do %>
+ <%= if @vereinfacht_receipts do %>
-
<%= format_vereinfacht_debug_response(@vereinfacht_debug_response) %>
+ <%= if match?({:ok, _}, @vereinfacht_receipts) do %>
+ <% {_, receipts} = @vereinfacht_receipts %>
+ <%= if receipts == [] do %>
+
{gettext("No receipts")}
+ <% else %>
+ <% cols = receipt_display_columns(receipts) %>
+
+
+
+ <%= for {_key, translated_label} <- cols do %>
+ | {translated_label} |
+ <% end %>
+
+
+
+ <%= for r <- receipts do %>
+
+ <%= for {col_key, _header_key} <- cols do %>
+ | {format_receipt_cell(col_key, r[col_key])} |
+ <% end %>
+
+ <% end %>
+
+
+ <% end %>
+ <% else %>
+ <% {:error, reason} = @vereinfacht_receipts %>
+
+ {gettext("Error loading receipts: %{reason}",
+ reason: format_vereinfacht_error(reason)
+ )}
+
+ <% end %>
<% end %>
@@ -499,7 +524,7 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|> assign_new(:create_cycle_date, fn -> nil end)
|> assign_new(:create_cycle_error, fn -> nil end)
|> assign_new(:regenerating, fn -> false end)
- |> assign_new(:vereinfacht_debug_response, fn -> nil end)}
+ |> assign_new(:vereinfacht_receipts, fn -> nil end)}
end
@impl true
@@ -1057,23 +1082,138 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
defp format_create_cycle_period(_date, _interval), do: ""
- defp format_vereinfacht_debug_response({:ok, body}) when is_map(body) do
- Jason.encode!(body, pretty: true)
+ defp format_vereinfacht_error({:http, status, detail}) when is_binary(detail),
+ do: "HTTP #{status} – #{detail}"
+
+ defp format_vereinfacht_error({:http, status, _}), do: "HTTP #{status}"
+ defp format_vereinfacht_error(reason), do: inspect(reason)
+
+ # Ordered receipt columns: {api_key, gettext key for header}. Only columns present in data are shown.
+ @receipt_column_spec [
+ {:amount, "Amount"},
+ {:bookingDate, "Booking date"},
+ {:createdAt, "Created at"},
+ {:receiptType, "Receipt type"},
+ {:referenceNumber, "Reference number"},
+ {:status, "Status"},
+ {:updatedAt, "Updated at"}
+ ]
+
+ defp receipt_display_columns(receipts) when is_list(receipts) do
+ keys_in_data = receipts |> Enum.flat_map(&Map.keys/1) |> MapSet.new()
+
+ Enum.filter(@receipt_column_spec, fn {key, _} -> MapSet.member?(keys_in_data, key) end)
+ |> Enum.map(fn {key, msgid} -> {key, Gettext.gettext(MvWeb.Gettext, msgid)} end)
end
- defp format_vereinfacht_debug_response({:error, {:http, status, detail}})
- when is_binary(detail) do
- "Error: HTTP #{status} – #{detail}"
+ defp format_receipt_cell(:amount, nil), do: "—"
+
+ defp format_receipt_cell(:amount, val) when is_number(val) do
+ case Decimal.cast(val) do
+ {:ok, d} -> MembershipFeeHelpers.format_currency(d)
+ _ -> to_string(val)
+ end
end
- defp format_vereinfacht_debug_response({:error, {:http, status, _}}) do
- "Error: HTTP #{status}"
+ defp format_receipt_cell(:amount, val) when is_binary(val) do
+ case Decimal.parse(val) do
+ {d, _} -> MembershipFeeHelpers.format_currency(d)
+ :error -> val
+ end
end
- defp format_vereinfacht_debug_response({:error, reason}) do
- "Error: " <> inspect(reason)
+ defp format_receipt_cell(:amount, val), do: to_string(val)
+
+ defp format_receipt_cell(:status, nil), do: "—"
+
+ defp format_receipt_cell(:status, val) when is_binary(val) do
+ translate_receipt_status(val)
end
+ defp format_receipt_cell(:status, val), do: translate_receipt_status(to_string(val))
+
+ defp format_receipt_cell(:receiptType, nil), do: "—"
+
+ defp format_receipt_cell(:receiptType, val) when is_binary(val) do
+ translate_receipt_type(val)
+ end
+
+ defp format_receipt_cell(:receiptType, val), do: translate_receipt_type(to_string(val))
+
+ defp format_receipt_cell(col_key, nil) when col_key in [:bookingDate, :createdAt, :updatedAt],
+ do: "—"
+
+ defp format_receipt_cell(col_key, val) when col_key in [:bookingDate, :createdAt, :updatedAt] do
+ format_receipt_date(val)
+ end
+
+ defp format_receipt_cell(_col_key, val) when is_binary(val), do: val
+ defp format_receipt_cell(_col_key, val) when is_number(val), do: to_string(val)
+
+ defp format_receipt_cell(_col_key, val) when is_boolean(val),
+ do: if(val, do: gettext("Yes"), else: gettext("No"))
+
+ defp format_receipt_cell(_col_key, %Date{} = d), do: format_receipt_date_short(d)
+ defp format_receipt_cell(_col_key, val) when is_map(val) or is_list(val), do: Jason.encode!(val)
+ defp format_receipt_cell(_col_key, val), do: to_string(val)
+
+ defp format_receipt_date(%Date{} = d), do: format_receipt_date_short(d)
+
+ defp format_receipt_date(val) when is_binary(val) do
+ case parse_receipt_date(val) do
+ {:ok, d} -> format_receipt_date_short(d)
+ _ -> val
+ end
+ end
+
+ defp format_receipt_date(val), do: to_string(val)
+
+ # Parses ISO date or datetime string to Date (uses first 10 chars for datetime strings)
+ defp parse_receipt_date(val) when is_binary(val) do
+ date_str = if String.length(val) >= 10, do: String.slice(val, 0, 10), else: val
+ Date.from_iso8601(date_str)
+ end
+
+ # Format as "12. Dez. 2025" (day. abbreviated month. year) with translated month
+ defp format_receipt_date_short(%Date{day: day, month: month, year: year}) do
+ "#{day}. #{receipt_month_abbr(month)} #{year}"
+ end
+
+ defp receipt_month_abbr(1), do: gettext("Jan.")
+ defp receipt_month_abbr(2), do: gettext("Feb.")
+ defp receipt_month_abbr(3), do: gettext("Mar.")
+ defp receipt_month_abbr(4), do: gettext("Apr.")
+ defp receipt_month_abbr(5), do: gettext("May")
+ defp receipt_month_abbr(6), do: gettext("Jun.")
+ defp receipt_month_abbr(7), do: gettext("Jul.")
+ defp receipt_month_abbr(8), do: gettext("Aug.")
+ defp receipt_month_abbr(9), do: gettext("Sep.")
+ defp receipt_month_abbr(10), do: gettext("Oct.")
+ defp receipt_month_abbr(11), do: gettext("Nov.")
+ defp receipt_month_abbr(12), do: gettext("Dec.")
+ defp receipt_month_abbr(_), do: ""
+
+ # Translate API status values for display (extend as API returns more values)
+ defp translate_receipt_status("paid"), do: gettext("Paid")
+ defp translate_receipt_status("unpaid"), do: gettext("Unpaid")
+ defp translate_receipt_status("suspended"), do: gettext("Suspended")
+ defp translate_receipt_status("open"), do: gettext("Open")
+ defp translate_receipt_status("cancelled"), do: gettext("Cancelled")
+ defp translate_receipt_status("draft"), do: gettext("Draft")
+ defp translate_receipt_status("incompleted"), do: gettext("Incompleted")
+ defp translate_receipt_status("completed"), do: gettext("Completed")
+ defp translate_receipt_status("empty"), do: "—"
+ defp translate_receipt_status(other), do: other
+
+ # Translate API receipt type values (extend as API returns more values)
+ defp translate_receipt_type("invoice"), do: gettext("Invoice")
+ defp translate_receipt_type("receipt"), do: gettext("Receipt")
+ defp translate_receipt_type("credit_note"), do: gettext("Credit note")
+ defp translate_receipt_type("credit"), do: gettext("Credit")
+ defp translate_receipt_type("expense"), do: gettext("Expense")
+ defp translate_receipt_type("income"), do: gettext("Income")
+ defp translate_receipt_type(other), do: other
+
# Helper component for section box
attr :title, :string, required: true
slot :inner_block, required: true