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 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")} -

- -
- <%!-- Default Contribution Type --%> -
- - -

- {gettext( - "This contribution type is automatically assigned to all new members. Can be changed individually per member." - )} -

-
- - <%!-- Include Joining Period --%> -
- -
-

- {gettext("When active: Members pay from the period of their joining.")} -

-

- {gettext("When inactive: Members pay from the next full period after joining.")} -

-
-
- -
- - -
-
-
- - <%!-- 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 -