diff --git a/lib/membership/setting.ex b/lib/membership/setting.ex
index 119ae89..13e7411 100644
--- a/lib/membership/setting.ex
+++ b/lib/membership/setting.ex
@@ -144,18 +144,25 @@ defmodule Mv.Membership.Setting do
# Validate default_membership_fee_type_id exists if set
validate fn changeset, _context ->
- fee_type_id = Ash.Changeset.get_attribute(changeset, :default_membership_fee_type_id)
+ fee_type_id =
+ Ash.Changeset.get_attribute(changeset, :default_membership_fee_type_id)
- if fee_type_id do
- case Ash.get(Mv.MembershipFees.MembershipFeeType, fee_type_id) do
- {:ok, _} -> :ok
- {:error, _} ->
- {:error, field: :default_membership_fee_type_id, message: "Membership fee type not found"}
- end
- else
- :ok # Optional, can be nil
- end
- end, on: [:create, :update]
+ if fee_type_id do
+ case Ash.get(Mv.MembershipFees.MembershipFeeType, fee_type_id) do
+ {:ok, _} ->
+ :ok
+
+ {:error, _} ->
+ {:error,
+ field: :default_membership_fee_type_id,
+ message: "Membership fee type not found"}
+ end
+ else
+ # Optional, can be nil
+ :ok
+ end
+ end,
+ on: [:create, :update]
end
attributes do
diff --git a/lib/membership_fees/membership_fee_type.ex b/lib/membership_fees/membership_fee_type.ex
index 6d7ea19..2f22e65 100644
--- a/lib/membership_fees/membership_fee_type.ex
+++ b/lib/membership_fees/membership_fee_type.ex
@@ -59,55 +59,67 @@ defmodule Mv.MembershipFees.MembershipFeeType do
validations do
# Prevent interval changes after creation
validate fn changeset, _context ->
- if Ash.Changeset.changing_attribute?(changeset, :interval) do
- case changeset.data do
- nil -> :ok # Creating new resource, interval can be set
- _existing -> {:error, field: :interval, message: "Interval cannot be changed after creation"}
- end
- else
- :ok
- end
- end, on: [:update]
+ if Ash.Changeset.changing_attribute?(changeset, :interval) do
+ case changeset.data do
+ # Creating new resource, interval can be set
+ nil ->
+ :ok
+
+ _existing ->
+ {:error,
+ field: :interval, message: "Interval cannot be changed after creation"}
+ end
+ else
+ :ok
+ end
+ end,
+ on: [:update]
# Prevent deletion if assigned to members
validate fn changeset, _context ->
- if changeset.action_type == :destroy do
- require Ash.Query
+ if changeset.action_type == :destroy do
+ require Ash.Query
- member_count =
- Mv.Membership.Member
- |> Ash.Query.filter(membership_fee_type_id == ^changeset.data.id)
- |> Ash.count!()
+ member_count =
+ Mv.Membership.Member
+ |> Ash.Query.filter(membership_fee_type_id == ^changeset.data.id)
+ |> Ash.count!()
- if member_count > 0 do
- {:error, message: "Cannot delete membership fee type: #{member_count} member(s) are assigned to it"}
- else
- :ok
- end
- else
- :ok
- end
- end, on: [:destroy]
+ if member_count > 0 do
+ {:error,
+ message:
+ "Cannot delete membership fee type: #{member_count} member(s) are assigned to it"}
+ else
+ :ok
+ end
+ else
+ :ok
+ end
+ end,
+ on: [:destroy]
# Prevent deletion if cycles exist
validate fn changeset, _context ->
- if changeset.action_type == :destroy do
- require Ash.Query
+ if changeset.action_type == :destroy do
+ require Ash.Query
- cycle_count =
- Mv.MembershipFees.MembershipFeeCycle
- |> Ash.Query.filter(membership_fee_type_id == ^changeset.data.id)
- |> Ash.count!()
+ cycle_count =
+ Mv.MembershipFees.MembershipFeeCycle
+ |> Ash.Query.filter(membership_fee_type_id == ^changeset.data.id)
+ |> Ash.count!()
- if cycle_count > 0 do
- {:error, message: "Cannot delete membership fee type: #{cycle_count} cycle(s) reference it"}
- else
- :ok
- end
- else
- :ok
- end
- end, on: [:destroy]
+ if cycle_count > 0 do
+ {:error,
+ message:
+ "Cannot delete membership fee type: #{cycle_count} cycle(s) reference it"}
+ else
+ :ok
+ end
+ else
+ :ok
+ end
+ end,
+ on: [:destroy]
end
attributes do
diff --git a/lib/mv_web/components/layouts/navbar.ex b/lib/mv_web/components/layouts/navbar.ex
index 4246c99..6aee397 100644
--- a/lib/mv_web/components/layouts/navbar.ex
+++ b/lib/mv_web/components/layouts/navbar.ex
@@ -31,7 +31,9 @@ defmodule MvWeb.Layouts.Navbar do
- <.link navigate="/contribution_types">{gettext("Contribution Types")}
-
- <.link navigate="/contribution_settings">{gettext("Contribution Settings")}
+ <.link navigate="/membership_fee_settings">
+ {gettext("Membership Fee Settings")}
+
diff --git a/lib/mv_web/live/contribution_period_live/show.ex b/lib/mv_web/live/contribution_period_live/show.ex
index 95179ac..83d9207 100644
--- a/lib/mv_web/live/contribution_period_live/show.ex
+++ b/lib/mv_web/live/contribution_period_live/show.ex
@@ -43,7 +43,7 @@ defmodule MvWeb.ContributionPeriodLive.Show do
· {gettext("Member since")}: {@member.joined_at}
<:actions>
- <.link navigate={~p"/contribution_settings"} class="btn btn-ghost btn-sm">
+ <.link navigate={~p"/membership_fee_settings"} class="btn btn-ghost btn-sm">
<.icon name="hero-arrow-left" class="size-4" />
{gettext("Back to Settings")}
diff --git a/lib/mv_web/live/contribution_settings_live.ex b/lib/mv_web/live/contribution_settings_live.ex
deleted file mode 100644
index 713bc8c..0000000
--- a/lib/mv_web/live/contribution_settings_live.ex
+++ /dev/null
@@ -1,277 +0,0 @@
-defmodule MvWeb.ContributionSettingsLive do
- @moduledoc """
- Mock-up LiveView for Contribution Settings (Admin).
-
- This is a preview-only page that displays the planned UI for managing
- global contribution settings. It shows static mock data and is not functional.
-
- ## Planned Features (Future Implementation)
- - Set default contribution type for new members
- - Configure whether joining period is included in contributions
- - Explanatory text with examples
-
- ## Settings
- - `default_contribution_type_id` - UUID of the default contribution type
- - `include_joining_period` - Boolean whether to include joining period
-
- ## Note
- This page is intentionally non-functional and serves as a UI mockup
- for the upcoming Membership Contributions feature.
- """
- use MvWeb, :live_view
-
- @impl true
- def mount(_params, _session, socket) do
- {:ok,
- socket
- |> assign(:page_title, gettext("Contribution Settings"))
- |> assign(:contribution_types, mock_contribution_types())
- |> assign(:selected_type_id, "1")
- |> assign(:include_joining_period, true)}
- end
-
- @impl true
- def render(assigns) do
- ~H"""
-
- <.mockup_warning />
-
- <.header>
- {gettext("Contribution Settings")}
- <:subtitle>
- {gettext("Configure global settings for membership contributions.")}
-
-
-
-
- <%!-- Settings Form --%>
-
-
-
- <.icon name="hero-cog-6-tooth" class="size-5" />
- {gettext("Global Settings")}
-
-
-
-
-
-
- <%!-- Examples Card --%>
-
-
-
- <.icon name="hero-light-bulb" class="size-5" />
- {gettext("Examples")}
-
-
- <.example_section
- title={gettext("Yearly Interval - Joining Period Included")}
- joining_date="15.03.2023"
- include_joining={true}
- start_date="01.01.2023"
- periods={["2023", "2024", "2025"]}
- note={gettext("Member pays for the year they joined")}
- />
-
-
-
- <.example_section
- title={gettext("Yearly Interval - Joining Period Excluded")}
- joining_date="15.03.2023"
- include_joining={false}
- start_date="01.01.2024"
- periods={["2024", "2025"]}
- note={gettext("Member pays from the next full year")}
- />
-
-
-
- <.example_section
- title={gettext("Quarterly Interval - Joining Period Excluded")}
- joining_date="15.05.2024"
- include_joining={false}
- start_date="01.07.2024"
- periods={["Q3/2024", "Q4/2024", "Q1/2025"]}
- note={gettext("Member pays from the next full quarter")}
- />
-
-
-
- <.example_section
- title={gettext("Monthly Interval - Joining Period Included")}
- joining_date="15.03.2024"
- include_joining={true}
- start_date="01.03.2024"
- periods={["03/2024", "04/2024", "05/2024", "..."]}
- note={gettext("Member pays from the joining month")}
- />
-
-
-
-
- <.example_member_card />
-
- """
- end
-
- # Example member card with link to period view
- defp example_member_card(assigns) do
- ~H"""
-
-
-
- <.icon name="hero-user" class="size-5" />
- {gettext("Example: Member Contribution View")}
-
-
- {gettext(
- "See how the contribution periods will be displayed for an individual member. This example shows Maria Weber with multiple contribution periods."
- )}
-
-
- <.link navigate={~p"/contributions/member/example"} class="btn btn-primary btn-sm">
- <.icon name="hero-eye" class="size-4" />
- {gettext("View Example Member")}
-
-
-
-
- """
- end
-
- # Mock-up warning banner component - subtle orange style
- defp mockup_warning(assigns) do
- ~H"""
-
- <.icon name="hero-exclamation-triangle" class="size-5 shrink-0" />
-
- {gettext("Preview Mockup")}
-
- – {gettext("This page is not functional and only displays the planned features.")}
-
-
-
- """
- end
-
- # Example section component
- attr :title, :string, required: true
- attr :joining_date, :string, required: true
- attr :include_joining, :boolean, required: true
- attr :start_date, :string, required: true
- attr :periods, :list, required: true
- attr :note, :string, required: true
-
- defp example_section(assigns) do
- ~H"""
-
-
{@title}
-
-
- {gettext("Joining date")}:
- {@joining_date}
-
-
- {gettext("Contribution start")}:
- {@start_date}
-
-
- {gettext("Generated periods")}:
-
- {Enum.join(@periods, ", ")}
-
-
-
-
→ {@note}
-
- """
- end
-
- # Mock data for demonstration
- defp mock_contribution_types do
- [
- %{
- id: "1",
- name: gettext("Regular"),
- amount: Decimal.new("60.00"),
- interval: :yearly
- },
- %{
- id: "2",
- name: gettext("Reduced"),
- amount: Decimal.new("30.00"),
- interval: :yearly
- },
- %{
- id: "3",
- name: gettext("Student"),
- amount: Decimal.new("5.00"),
- interval: :monthly
- },
- %{
- id: "4",
- name: gettext("Family"),
- amount: Decimal.new("25.00"),
- interval: :quarterly
- }
- ]
- end
-
- defp format_currency(%Decimal{} = amount) do
- "#{Decimal.to_string(amount)} €"
- end
-
- defp format_interval(:monthly), do: gettext("Monthly")
- defp format_interval(:quarterly), do: gettext("Quarterly")
- defp format_interval(:half_yearly), do: gettext("Half-yearly")
- defp format_interval(:yearly), do: gettext("Yearly")
-end
diff --git a/lib/mv_web/live/membership_fee_settings_live.ex b/lib/mv_web/live/membership_fee_settings_live.ex
index 45e1dcf..fd1d41e 100644
--- a/lib/mv_web/live/membership_fee_settings_live.ex
+++ b/lib/mv_web/live/membership_fee_settings_live.ex
@@ -103,7 +103,9 @@ defmodule MvWeb.MembershipFeeSettingsLive do
value={fee_type.id}
selected={fee_type.id == @selected_fee_type_id}
>
- {fee_type.name} ({format_currency(fee_type.amount)}, {format_interval(fee_type.interval)})
+ {fee_type.name} ({format_currency(fee_type.amount)}, {format_interval(
+ fee_type.interval
+ )})
@@ -281,4 +283,3 @@ defmodule MvWeb.MembershipFeeSettingsLive do
|> Ash.update()
end
end
-
diff --git a/lib/mv_web/router.ex b/lib/mv_web/router.ex
index 79eb6db..887628e 100644
--- a/lib/mv_web/router.ex
+++ b/lib/mv_web/router.ex
@@ -74,7 +74,6 @@ defmodule MvWeb.Router do
# Contribution Management (Mock-ups)
live "/contribution_types", ContributionTypeLive.Index, :index
- live "/contribution_settings", ContributionSettingsLive
live "/contributions/member/:id", ContributionPeriodLive.Show, :show
post "/set_locale", LocaleController, :set_locale
diff --git a/test/membership/membership_fee_settings_test.exs b/test/membership/membership_fee_settings_test.exs
index 1f0f8c3..05a0d04 100644
--- a/test/membership/membership_fee_settings_test.exs
+++ b/test/membership/membership_fee_settings_test.exs
@@ -96,4 +96,3 @@ defmodule Mv.Membership.MembershipFeeSettingsTest do
defp error_on_field?(_, _), do: false
end
-
diff --git a/test/membership_fees/membership_fee_type_integration_test.exs b/test/membership_fees/membership_fee_type_integration_test.exs
index d05b69f..af1b5b2 100644
--- a/test/membership_fees/membership_fee_type_integration_test.exs
+++ b/test/membership_fees/membership_fee_type_integration_test.exs
@@ -203,4 +203,3 @@ defmodule Mv.MembershipFees.MembershipFeeTypeIntegrationTest do
defp extract_error_message(_), do: ""
end
-