Member show: Vereinfacht link only, receipts table from API
- Show only 'Kontakt in Vereinfacht anzeigen' link (no Contact ID / Debug) - Button loads receipts via get_contact_with_receipts, table with formatted columns
This commit is contained in:
parent
ede3df12ef
commit
b60ab3f392
2 changed files with 172 additions and 32 deletions
|
|
@ -256,7 +256,7 @@ defmodule MvWeb.MemberLive.Show do
|
||||||
id={"membership-fees-#{@member.id}"}
|
id={"membership-fees-#{@member.id}"}
|
||||||
member={@member}
|
member={@member}
|
||||||
current_user={@current_user}
|
current_user={@current_user}
|
||||||
vereinfacht_debug_response={@vereinfacht_debug_response}
|
vereinfacht_receipts={@vereinfacht_receipts}
|
||||||
/>
|
/>
|
||||||
<% end %>
|
<% end %>
|
||||||
</Layouts.app>
|
</Layouts.app>
|
||||||
|
|
@ -268,7 +268,7 @@ defmodule MvWeb.MemberLive.Show do
|
||||||
{:ok,
|
{:ok,
|
||||||
socket
|
socket
|
||||||
|> assign(:active_tab, :contact)
|
|> assign(:active_tab, :contact)
|
||||||
|> assign(:vereinfacht_debug_response, nil)}
|
|> assign(:vereinfacht_receipts, nil)}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
|
@ -320,14 +320,14 @@ defmodule MvWeb.MemberLive.Show do
|
||||||
{:noreply, assign(socket, :active_tab, :membership_fees)}
|
{:noreply, assign(socket, :active_tab, :membership_fees)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("load_vereinfacht_debug", %{"contact_id" => contact_id}, socket) do
|
def handle_event("load_vereinfacht_receipts", %{"contact_id" => contact_id}, socket) do
|
||||||
response =
|
response =
|
||||||
case Mv.Vereinfacht.Client.get_contact(contact_id) do
|
case Mv.Vereinfacht.Client.get_contact_with_receipts(contact_id) do
|
||||||
{:ok, body} -> {:ok, body}
|
{:ok, receipts} -> {:ok, receipts}
|
||||||
{:error, reason} -> {:error, reason}
|
{:error, reason} -> {:error, reason}
|
||||||
end
|
end
|
||||||
|
|
||||||
{:noreply, assign(socket, :vereinfacht_debug_response, response)}
|
{:noreply, assign(socket, :vereinfacht_receipts, response)}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Flash set in LiveComponent is not shown in parent layout; child sends this to display flash
|
# Flash set in LiveComponent is not shown in parent layout; child sends this to display flash
|
||||||
|
|
|
||||||
|
|
@ -54,42 +54,67 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
||||||
<%= if Mv.Config.vereinfacht_configured?() do %>
|
<%= if Mv.Config.vereinfacht_configured?() do %>
|
||||||
<%= if @member.vereinfacht_contact_id do %>
|
<%= if @member.vereinfacht_contact_id do %>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="label">
|
<div class="flex flex-col gap-2">
|
||||||
<span class="label-text font-semibold">{gettext("Vereinfacht")}</span>
|
|
||||||
</label>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<span class="font-mono text-sm">
|
|
||||||
{gettext("Contact ID: %{id}", id: @member.vereinfacht_contact_id)}
|
|
||||||
</span>
|
|
||||||
<.link
|
<.link
|
||||||
:if={Mv.Config.vereinfacht_contact_view_url(@member.vereinfacht_contact_id)}
|
:if={Mv.Config.vereinfacht_contact_view_url(@member.vereinfacht_contact_id)}
|
||||||
href={Mv.Config.vereinfacht_contact_view_url(@member.vereinfacht_contact_id)}
|
href={Mv.Config.vereinfacht_contact_view_url(@member.vereinfacht_contact_id)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
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")}
|
{gettext("View contact in Vereinfacht")}
|
||||||
<.icon name="hero-arrow-top-right-on-square" class="inline-block size-4" />
|
<.icon name="hero-arrow-top-right-on-square" class="inline-block size-4" />
|
||||||
</.link>
|
</.link>
|
||||||
<div class="mt-2">
|
<div>
|
||||||
<span class="text-base-content/70 text-sm">{gettext("Debug:")}</span>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="load_vereinfacht_debug"
|
phx-click="load_vereinfacht_receipts"
|
||||||
phx-value-contact_id={@member.vereinfacht_contact_id}
|
phx-value-contact_id={@member.vereinfacht_contact_id}
|
||||||
class="btn btn-sm btn-ghost ml-1"
|
class="btn btn-sm btn-ghost"
|
||||||
>
|
>
|
||||||
{gettext("Load API response")}
|
{gettext("Show bookings/receipts from Vereinfacht")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<%= if @vereinfacht_debug_response do %>
|
<%= if @vereinfacht_receipts do %>
|
||||||
<div
|
<div
|
||||||
class="mt-2 rounded border border-base-300 bg-base-200 p-3 overflow-x-auto max-h-96 overflow-y-auto"
|
class="mt-2 rounded border border-base-300 bg-base-200 p-3 overflow-x-auto max-h-96 overflow-y-auto"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
role="region"
|
role="region"
|
||||||
aria-label={gettext("Vereinfacht API response")}
|
aria-label={gettext("Vereinfacht receipts")}
|
||||||
>
|
>
|
||||||
<pre class="text-xs whitespace-pre-wrap font-mono"><%= format_vereinfacht_debug_response(@vereinfacht_debug_response) %></pre>
|
<%= if match?({:ok, _}, @vereinfacht_receipts) do %>
|
||||||
|
<% {_, receipts} = @vereinfacht_receipts %>
|
||||||
|
<%= if receipts == [] do %>
|
||||||
|
<p class="text-sm text-base-content/70">{gettext("No receipts")}</p>
|
||||||
|
<% else %>
|
||||||
|
<% cols = receipt_display_columns(receipts) %>
|
||||||
|
<table class="table table-xs table-pin-rows">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<%= for {_key, translated_label} <- cols do %>
|
||||||
|
<th>{translated_label}</th>
|
||||||
|
<% end %>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<%= for r <- receipts do %>
|
||||||
|
<tr>
|
||||||
|
<%= for {col_key, _header_key} <- cols do %>
|
||||||
|
<td>{format_receipt_cell(col_key, r[col_key])}</td>
|
||||||
|
<% end %>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<% end %>
|
||||||
|
<% else %>
|
||||||
|
<% {:error, reason} = @vereinfacht_receipts %>
|
||||||
|
<p class="text-sm text-error">
|
||||||
|
{gettext("Error loading receipts: %{reason}",
|
||||||
|
reason: format_vereinfacht_error(reason)
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -499,7 +524,7 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
||||||
|> assign_new(:create_cycle_date, fn -> nil end)
|
|> assign_new(:create_cycle_date, fn -> nil end)
|
||||||
|> assign_new(:create_cycle_error, fn -> nil end)
|
|> assign_new(:create_cycle_error, fn -> nil end)
|
||||||
|> assign_new(:regenerating, fn -> false end)
|
|> assign_new(:regenerating, fn -> false end)
|
||||||
|> assign_new(:vereinfacht_debug_response, fn -> nil end)}
|
|> assign_new(:vereinfacht_receipts, fn -> nil end)}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
|
@ -1057,23 +1082,138 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
||||||
|
|
||||||
defp format_create_cycle_period(_date, _interval), do: ""
|
defp format_create_cycle_period(_date, _interval), do: ""
|
||||||
|
|
||||||
defp format_vereinfacht_debug_response({:ok, body}) when is_map(body) do
|
defp format_vereinfacht_error({:http, status, detail}) when is_binary(detail),
|
||||||
Jason.encode!(body, pretty: true)
|
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
|
end
|
||||||
|
|
||||||
defp format_vereinfacht_debug_response({:error, {:http, status, detail}})
|
defp format_receipt_cell(:amount, nil), do: "—"
|
||||||
when is_binary(detail) do
|
|
||||||
"Error: HTTP #{status} – #{detail}"
|
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
|
end
|
||||||
|
|
||||||
defp format_vereinfacht_debug_response({:error, {:http, status, _}}) do
|
defp format_receipt_cell(:amount, val) when is_binary(val) do
|
||||||
"Error: HTTP #{status}"
|
case Decimal.parse(val) do
|
||||||
|
{d, _} -> MembershipFeeHelpers.format_currency(d)
|
||||||
|
:error -> val
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp format_vereinfacht_debug_response({:error, reason}) do
|
defp format_receipt_cell(:amount, val), do: to_string(val)
|
||||||
"Error: " <> inspect(reason)
|
|
||||||
|
defp format_receipt_cell(:status, nil), do: "—"
|
||||||
|
|
||||||
|
defp format_receipt_cell(:status, val) when is_binary(val) do
|
||||||
|
translate_receipt_status(val)
|
||||||
end
|
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
|
# Helper component for section box
|
||||||
attr :title, :string, required: true
|
attr :title, :string, required: true
|
||||||
slot :inner_block, required: true
|
slot :inner_block, required: true
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue