From 7ef582510e670a5f56bf42080181de98d18537af Mon Sep 17 00:00:00 2001 From: shibao Date: Thu, 10 Nov 2022 20:25:55 -0500 Subject: [PATCH] improve ammo context --- lib/cannery/ammo.ex | 60 +++++- priv/gettext/de/LC_MESSAGES/errors.po | 2 +- priv/gettext/en/LC_MESSAGES/errors.po | 2 +- priv/gettext/errors.pot | 2 +- priv/gettext/es/LC_MESSAGES/errors.po | 2 +- priv/gettext/fr/LC_MESSAGES/errors.po | 2 +- priv/gettext/ga/LC_MESSAGES/errors.po | 2 +- test/cannery/ammo_test.exs | 275 +++++++++++++++++++++++++- 8 files changed, 334 insertions(+), 13 deletions(-) diff --git a/lib/cannery/ammo.ex b/lib/cannery/ammo.ex index 36e44f30..71f6a783 100644 --- a/lib/cannery/ammo.ex +++ b/lib/cannery/ammo.ex @@ -101,7 +101,7 @@ defmodule Cannery.Ammo do ## Examples iex> get_round_count_for_ammo_type(123, %User{id: 123}) - %AmmoType{} + 35 iex> get_round_count_for_ammo_type(456, %User{id: 123}) ** (Ecto.NoResultsError) @@ -127,7 +127,7 @@ defmodule Cannery.Ammo do ## Examples iex> get_used_count_for_ammo_type(123, %User{id: 123}) - %AmmoType{} + 35 iex> get_used_count_for_ammo_type(456, %User{id: 123}) ** (Ecto.NoResultsError) @@ -146,6 +146,29 @@ defmodule Cannery.Ammo do ) || 0 end + @doc """ + Gets the total number of ammo ever bought for an ammo type + + Raises `Ecto.NoResultsError` if the Ammo type does not exist. + + ## Examples + + iex> get_historical_count_for_ammo_type(123, %User{id: 123}) + %AmmoType{} + + iex> get_historical_count_for_ammo_type(456, %User{id: 123}) + ** (Ecto.NoResultsError) + + """ + @spec get_historical_count_for_ammo_type(AmmoType.t(), User.t()) :: non_neg_integer() + def get_historical_count_for_ammo_type( + %AmmoType{user_id: user_id} = ammo_type, + %User{id: user_id} = user + ) do + get_round_count_for_ammo_type(ammo_type, user) + + get_used_count_for_ammo_type(ammo_type, user) + end + @doc """ Creates a ammo_type. @@ -305,9 +328,12 @@ defmodule Cannery.Ammo do ## Examples - iex> get_ammo_groups_count_for_type(%User{id: 123}) + iex> get_ammo_groups_count_for_type(%AmmoType{id: 123}, %User{id: 123}) 3 + iex> get_ammo_groups_count_for_type(%AmmoType{id: 123}, %User{id: 123}, true) + 5 + """ @spec get_ammo_groups_count_for_type(AmmoType.t(), User.t()) :: [AmmoGroup.t()] @spec get_ammo_groups_count_for_type(AmmoType.t(), User.t(), include_empty :: boolean()) :: @@ -343,6 +369,30 @@ defmodule Cannery.Ammo do ) || 0 end + @doc """ + Returns the count of used ammo_groups for an ammo type. + + ## Examples + + iex> get_used_ammo_groups_count_for_type(%AmmoType{id: 123}, %User{id: 123}) + 3 + + """ + @spec get_used_ammo_groups_count_for_type(AmmoType.t(), User.t()) :: [AmmoGroup.t()] + def get_used_ammo_groups_count_for_type( + %AmmoType{id: ammo_type_id, user_id: user_id}, + %User{id: user_id} + ) do + Repo.one!( + from ag in AmmoGroup, + where: ag.user_id == ^user_id, + where: ag.ammo_type_id == ^ammo_type_id, + where: ag.count == 0, + distinct: true, + select: count(ag.id) + ) || 0 + end + @doc """ Returns the list of ammo_groups for a user. @@ -456,7 +506,9 @@ defmodule Cannery.Ammo do ammo_group = ammo_group |> Repo.preload(:shot_groups) shot_group_sum = - ammo_group.shot_groups |> Enum.map(fn %{count: count} -> count end) |> Enum.sum() + ammo_group.shot_groups + |> Enum.map(fn %{count: count} -> count end) + |> Enum.sum() round(count / (count + shot_group_sum) * 100) end diff --git a/priv/gettext/de/LC_MESSAGES/errors.po b/priv/gettext/de/LC_MESSAGES/errors.po index c46e305f..972d1145 100644 --- a/priv/gettext/de/LC_MESSAGES/errors.po +++ b/priv/gettext/de/LC_MESSAGES/errors.po @@ -187,7 +187,7 @@ msgstr "" "Ungültige Nummer an Kopien. Muss zwischen 1 and %{max} liegen. War " "%{multiplier}" -#: lib/cannery/ammo.ex:535 +#: lib/cannery/ammo.ex:587 #, elixir-autogen, elixir-format msgid "Invalid multiplier" msgstr "" diff --git a/priv/gettext/en/LC_MESSAGES/errors.po b/priv/gettext/en/LC_MESSAGES/errors.po index 25fd61f6..7bb4af09 100644 --- a/priv/gettext/en/LC_MESSAGES/errors.po +++ b/priv/gettext/en/LC_MESSAGES/errors.po @@ -170,7 +170,7 @@ msgstr "" msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}" msgstr "" -#: lib/cannery/ammo.ex:535 +#: lib/cannery/ammo.ex:587 #, elixir-autogen, elixir-format msgid "Invalid multiplier" msgstr "" diff --git a/priv/gettext/errors.pot b/priv/gettext/errors.pot index badbb267..2d7899bf 100644 --- a/priv/gettext/errors.pot +++ b/priv/gettext/errors.pot @@ -169,7 +169,7 @@ msgstr "" msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}" msgstr "" -#: lib/cannery/ammo.ex:535 +#: lib/cannery/ammo.ex:587 #, elixir-autogen, elixir-format msgid "Invalid multiplier" msgstr "" diff --git a/priv/gettext/es/LC_MESSAGES/errors.po b/priv/gettext/es/LC_MESSAGES/errors.po index d398e331..d13435b6 100644 --- a/priv/gettext/es/LC_MESSAGES/errors.po +++ b/priv/gettext/es/LC_MESSAGES/errors.po @@ -185,7 +185,7 @@ msgstr "" msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}" msgstr "" -#: lib/cannery/ammo.ex:535 +#: lib/cannery/ammo.ex:587 #, elixir-autogen, elixir-format msgid "Invalid multiplier" msgstr "" diff --git a/priv/gettext/fr/LC_MESSAGES/errors.po b/priv/gettext/fr/LC_MESSAGES/errors.po index 96fcdee1..1b7d0bbe 100644 --- a/priv/gettext/fr/LC_MESSAGES/errors.po +++ b/priv/gettext/fr/LC_MESSAGES/errors.po @@ -186,7 +186,7 @@ msgstr "Impossible d'analyser le nombre de copies" msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}" msgstr "Nombre de copies invalide, doit être 1 et %{max}. Été %{multiplier}" -#: lib/cannery/ammo.ex:535 +#: lib/cannery/ammo.ex:587 #, elixir-autogen, elixir-format msgid "Invalid multiplier" msgstr "Multiplicateur invalide" diff --git a/priv/gettext/ga/LC_MESSAGES/errors.po b/priv/gettext/ga/LC_MESSAGES/errors.po index a3228768..1e400160 100644 --- a/priv/gettext/ga/LC_MESSAGES/errors.po +++ b/priv/gettext/ga/LC_MESSAGES/errors.po @@ -185,7 +185,7 @@ msgstr "" msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}" msgstr "" -#: lib/cannery/ammo.ex:535 +#: lib/cannery/ammo.ex:587 #, elixir-autogen, elixir-format msgid "Invalid multiplier" msgstr "" diff --git a/test/cannery/ammo_test.exs b/test/cannery/ammo_test.exs index 64384a23..01bb2e72 100644 --- a/test/cannery/ammo_test.exs +++ b/test/cannery/ammo_test.exs @@ -94,6 +94,135 @@ defmodule Cannery.AmmoTest do end end + describe "ammo types with ammo groups" do + setup do + current_user = user_fixture() + ammo_type = ammo_type_fixture(current_user) + container = container_fixture(current_user) + + [ + ammo_type: ammo_type, + container: container, + current_user: current_user + ] + end + + test "get_average_cost_for_ammo_type!/2 gets average cost for ammo type", + %{ammo_type: ammo_type, current_user: current_user, container: container} do + {1, [_ammo_group]} = + ammo_group_fixture( + %{"price_paid" => 25.00, "count" => 1}, + ammo_type, + container, + current_user + ) + + assert 25.0 = Ammo.get_average_cost_for_ammo_type!(ammo_type, current_user) + + {1, [_ammo_group]} = + ammo_group_fixture( + %{"price_paid" => 25.00, "count" => 1}, + ammo_type, + container, + current_user + ) + + assert 25.0 = Ammo.get_average_cost_for_ammo_type!(ammo_type, current_user) + + {1, [_ammo_group]} = + ammo_group_fixture( + %{"price_paid" => 70.00, "count" => 1}, + ammo_type, + container, + current_user + ) + + assert 40.0 = Ammo.get_average_cost_for_ammo_type!(ammo_type, current_user) + + {1, [_ammo_group]} = + ammo_group_fixture( + %{"price_paid" => 30.00, "count" => 1}, + ammo_type, + container, + current_user + ) + + assert 37.5 = Ammo.get_average_cost_for_ammo_type!(ammo_type, current_user) + end + + test "get_round_count_for_ammo_type/2 gets accurate round count for ammo type", + %{ammo_type: ammo_type, current_user: current_user, container: container} do + {1, [first_ammo_group]} = + ammo_group_fixture(%{"count" => 1}, ammo_type, container, current_user) + + assert 1 = Ammo.get_round_count_for_ammo_type(ammo_type, current_user) + + {1, [ammo_group]} = ammo_group_fixture(%{"count" => 50}, ammo_type, container, current_user) + + assert 51 = Ammo.get_round_count_for_ammo_type(ammo_type, current_user) + + shot_group_fixture(%{"count" => 26}, current_user, ammo_group) + assert 25 = Ammo.get_round_count_for_ammo_type(ammo_type, current_user) + + shot_group_fixture(%{"count" => 1}, current_user, first_ammo_group) + assert 24 = Ammo.get_round_count_for_ammo_type(ammo_type, current_user) + end + + test "get_used_count_for_ammo_type/2 gets accurate used round count for ammo type", + %{ammo_type: ammo_type, current_user: current_user, container: container} do + {1, [first_ammo_group]} = + ammo_group_fixture(%{"count" => 1}, ammo_type, container, current_user) + + assert 0 = Ammo.get_used_count_for_ammo_type(ammo_type, current_user) + + {1, [ammo_group]} = ammo_group_fixture(%{"count" => 50}, ammo_type, container, current_user) + + assert 0 = Ammo.get_used_count_for_ammo_type(ammo_type, current_user) + + shot_group_fixture(%{"count" => 26}, current_user, ammo_group) + assert 26 = Ammo.get_used_count_for_ammo_type(ammo_type, current_user) + + shot_group_fixture(%{"count" => 1}, current_user, first_ammo_group) + assert 27 = Ammo.get_used_count_for_ammo_type(ammo_type, current_user) + end + + test "get_historical_count_for_ammo_type/2 gets accurate total round count for ammo type", + %{ammo_type: ammo_type, current_user: current_user, container: container} do + {1, [first_ammo_group]} = + ammo_group_fixture(%{"count" => 1}, ammo_type, container, current_user) + + assert 1 = Ammo.get_historical_count_for_ammo_type(ammo_type, current_user) + + {1, [ammo_group]} = ammo_group_fixture(%{"count" => 50}, ammo_type, container, current_user) + + assert 51 = Ammo.get_historical_count_for_ammo_type(ammo_type, current_user) + + shot_group_fixture(%{"count" => 26}, current_user, ammo_group) + assert 51 = Ammo.get_historical_count_for_ammo_type(ammo_type, current_user) + + shot_group_fixture(%{"count" => 1}, current_user, first_ammo_group) + assert 51 = Ammo.get_historical_count_for_ammo_type(ammo_type, current_user) + end + + test "get_used_ammo_groups_count_for_type/2 gets accurate total ammo count for ammo type", + %{ammo_type: ammo_type, current_user: current_user, container: container} do + {1, [first_ammo_group]} = + ammo_group_fixture(%{"count" => 1}, ammo_type, container, current_user) + + assert 0 = Ammo.get_used_ammo_groups_count_for_type(ammo_type, current_user) + + {1, [ammo_group]} = ammo_group_fixture(%{"count" => 50}, ammo_type, container, current_user) + + assert 0 = Ammo.get_used_ammo_groups_count_for_type(ammo_type, current_user) + + shot_group_fixture(%{"count" => 50}, current_user, ammo_group) + assert 1 = Ammo.get_used_ammo_groups_count_for_type(ammo_type, current_user) + + shot_group_fixture(%{"count" => 1}, current_user, first_ammo_group) + assert 2 = Ammo.get_used_ammo_groups_count_for_type(ammo_type, current_user) + end + end + describe "ammo_groups" do @valid_attrs %{"count" => 42, "notes" => "some notes", "price_paid" => 120.5} @update_attrs %{"count" => 43, "notes" => "some updated notes", "price_paid" => 456.7} @@ -103,7 +232,7 @@ defmodule Cannery.AmmoTest do current_user = user_fixture() ammo_type = ammo_type_fixture(current_user) container = container_fixture(current_user) - {1, [ammo_group]} = ammo_group_fixture(ammo_type, container, current_user) + {1, [ammo_group]} = ammo_group_fixture(%{"count" => 25}, ammo_type, container, current_user) [ ammo_type: ammo_type, @@ -113,9 +242,76 @@ defmodule Cannery.AmmoTest do ] end - test "list_ammo_groups/0 returns all ammo_groups", - %{ammo_group: ammo_group, current_user: current_user} do + test "list_ammo_groups/2 returns all ammo_groups", + %{ + ammo_type: ammo_type, + ammo_group: ammo_group, + container: container, + current_user: current_user + } do + {1, [another_ammo_group]} = + ammo_group_fixture(%{"count" => 30}, ammo_type, container, current_user) + + shot_group_fixture(%{"count" => 30}, current_user, another_ammo_group) + another_ammo_group = another_ammo_group |> Repo.reload!() assert Ammo.list_ammo_groups(current_user) == [ammo_group] |> Repo.preload(:shot_groups) + + assert Ammo.list_ammo_groups(current_user, true) + |> Enum.sort_by(fn %{count: count} -> count end) == + [another_ammo_group, ammo_group] |> Repo.preload(:shot_groups) + end + + test "list_ammo_groups_for_type/2 returns all ammo_groups for a type", + %{ + ammo_type: ammo_type, + container: container, + ammo_group: ammo_group, + current_user: current_user + } do + another_ammo_type = ammo_type_fixture(current_user) + {1, [_another]} = ammo_group_fixture(another_ammo_type, container, current_user) + + assert Ammo.list_ammo_groups_for_type(ammo_type, current_user) == + [ammo_group] |> Repo.preload(:shot_groups) + end + + test "list_ammo_groups_for_container/2 returns all ammo_groups for a container", + %{ + ammo_type: ammo_type, + container: container, + ammo_group: ammo_group, + current_user: current_user + } do + another_container = container_fixture(current_user) + {1, [_another]} = ammo_group_fixture(ammo_type, another_container, current_user) + + assert Ammo.list_ammo_groups_for_container(container, current_user) == + [ammo_group] |> Repo.preload(:shot_groups) + end + + test "get_ammo_groups_count_for_type/2 returns count of ammo_groups for a type", + %{ + ammo_type: ammo_type, + container: container, + current_user: current_user + } do + another_ammo_type = ammo_type_fixture(current_user) + {1, [_another]} = ammo_group_fixture(another_ammo_type, container, current_user) + + assert 1 = Ammo.get_ammo_groups_count_for_type(ammo_type, current_user) + end + + test "list_staged_ammo_groups/2 returns all ammo_groups that are staged", + %{ + ammo_type: ammo_type, + container: container, + current_user: current_user + } do + {1, [another_ammo_group]} = + ammo_group_fixture(%{"staged" => true}, ammo_type, container, current_user) + + assert Ammo.list_staged_ammo_groups(current_user) == + [another_ammo_group] |> Repo.preload(:shot_groups) end test "get_ammo_group!/1 returns the ammo_group with given id", @@ -140,6 +336,27 @@ defmodule Cannery.AmmoTest do assert ammo_group.price_paid == 120.5 end + test "create_ammo_groups/3 with valid data creates multiple ammo_groups", + %{ + ammo_type: ammo_type, + container: container, + current_user: current_user + } do + assert {:ok, {3, ammo_groups}} = + @valid_attrs + |> Map.merge(%{"ammo_type_id" => ammo_type.id, "container_id" => container.id}) + |> Ammo.create_ammo_groups(3, current_user) + + assert [%AmmoGroup{}, %AmmoGroup{}, %AmmoGroup{}] = ammo_groups + + ammo_groups + |> Enum.map(fn %{count: count, notes: notes, price_paid: price_paid} -> + assert count == 42 + assert notes == "some notes" + assert price_paid == 120.5 + end) + end + test "create_ammo_groups/3 with invalid data returns error changeset", %{ammo_type: ammo_type, container: container, current_user: current_user} do assert {:error, %Changeset{}} = @@ -175,5 +392,57 @@ defmodule Cannery.AmmoTest do Ammo.get_ammo_group!(ammo_group.id, current_user) end end + + test "get_used_count/1 returns accurate used count", + %{ammo_group: ammo_group, current_user: current_user} do + assert 0 = Ammo.get_used_count(ammo_group) + shot_group_fixture(%{"count" => 15}, current_user, ammo_group) + assert 15 = ammo_group |> Repo.preload(:shot_groups, force: true) |> Ammo.get_used_count() + shot_group_fixture(%{"count" => 10}, current_user, ammo_group) + assert 25 = ammo_group |> Repo.preload(:shot_groups, force: true) |> Ammo.get_used_count() + end + + test "get_last_used_shot_group/1 returns accurate used count", + %{ammo_group: ammo_group, current_user: current_user} do + assert ammo_group + |> Repo.preload(:shot_groups, force: true) + |> Ammo.get_last_used_shot_group() + |> is_nil() + + shot_group = shot_group_fixture(%{"date" => ~D[2022-11-10]}, current_user, ammo_group) + + assert ^shot_group = + ammo_group + |> Repo.preload(:shot_groups, force: true) + |> Ammo.get_last_used_shot_group() + + shot_group = shot_group_fixture(%{"date" => ~D[2022-11-11]}, current_user, ammo_group) + + assert ^shot_group = + ammo_group + |> Repo.preload(:shot_groups, force: true) + |> Ammo.get_last_used_shot_group() + end + + test "get_percentage_remaining/1 gets accurate total round count for ammo type", + %{ammo_group: ammo_group, current_user: current_user} do + assert 100 = Ammo.get_percentage_remaining(ammo_group) + + shot_group_fixture(%{"count" => 14}, current_user, ammo_group) + + assert 44 = + ammo_group + |> Repo.reload!() + |> Repo.preload(:shot_groups, force: true) + |> Ammo.get_percentage_remaining() + + shot_group_fixture(%{"count" => 11}, current_user, ammo_group) + + assert 0 = + ammo_group + |> Repo.reload!() + |> Repo.preload(:shot_groups, force: true) + |> Ammo.get_percentage_remaining() + end end end