diff --git a/lib/cannery/activity_log.ex b/lib/cannery/activity_log.ex index 371dc6b5..6abc09b9 100644 --- a/lib/cannery/activity_log.ex +++ b/lib/cannery/activity_log.ex @@ -287,26 +287,6 @@ defmodule Cannery.ActivityLog do end end - @doc """ - Returns the number of shot rounds for multiple packs - """ - @spec get_used_counts([Pack.t()], User.t()) :: - %{optional(Pack.id()) => non_neg_integer()} - def get_used_counts(packs, %User{id: user_id}) do - pack_ids = - packs - |> Enum.map(fn %{id: pack_id} -> pack_id end) - - Repo.all( - from sr in ShotRecord, - where: sr.pack_id in ^pack_ids, - where: sr.user_id == ^user_id, - group_by: sr.pack_id, - select: {sr.pack_id, sum(sr.count)} - ) - |> Map.new() - end - @doc """ Returns the last entered shot record date for a pack """ @@ -385,31 +365,77 @@ defmodule Cannery.ActivityLog do defp get_used_count_type_id(query, _nil), do: query + @type get_grouped_used_counts_option :: + {:packs, [Pack.t()] | nil} + | {:types, [Type.t()] | nil} + | {:group_by, :type_id | :pack_id} + @type get_grouped_used_counts_options :: [get_grouped_used_counts_option()] + @doc """ - Gets the total number of rounds shot for multiple types + Gets the total number of rounds shot for multiple types or packs ## Examples - iex> get_used_count_for_types(123, %User{id: 123}) + iex> get_grouped_used_counts( + ...> %User{id: 123}, + ...> group_by: :type_id, + ...> types: [%Type{id: 456, user_id: 123}] + ...> ) 35 - """ - @spec get_used_count_for_types([Type.t()], User.t()) :: - %{optional(Type.id()) => non_neg_integer()} - def get_used_count_for_types(types, %User{id: user_id}) do - type_ids = - types - |> Enum.map(fn %Type{id: type_id, user_id: ^user_id} -> type_id end) + iex> get_grouped_used_counts( + ...> %User{id: 123}, + ...> group_by: :pack_id, + ...> packs: [%Pack{id: 456, user_id: 123}] + ...> ) + 22 - Repo.all( - from p in Pack, - left_join: sr in ShotRecord, - on: p.id == sr.pack_id, - where: p.type_id in ^type_ids, - where: not (sr.count |> is_nil()), - group_by: p.type_id, - select: {p.type_id, sum(sr.count)} + """ + @spec get_grouped_used_counts(User.t(), get_grouped_used_counts_options()) :: + %{optional(Type.id() | Pack.id()) => non_neg_integer()} + def get_grouped_used_counts(%User{id: user_id}, opts) do + from(p in Pack, + as: :p, + left_join: sr in ShotRecord, + on: p.id == sr.pack_id, + on: p.user_id == ^user_id, + as: :sr, + where: sr.user_id == ^user_id, + where: not (sr.count |> is_nil()) ) + |> get_grouped_used_counts_group_by(Keyword.fetch!(opts, :group_by)) + |> get_grouped_used_counts_types(Keyword.get(opts, :types)) + |> get_grouped_used_counts_packs(Keyword.get(opts, :packs)) + |> Repo.all() |> Map.new() end + + @spec get_grouped_used_counts_group_by(Queryable.t(), :type_id | :pack_id) :: Queryable.t() + defp get_grouped_used_counts_group_by(query, :type_id) do + query + |> group_by([p: p], p.type_id) + |> select([sr: sr, p: p], {p.type_id, sum(sr.count)}) + end + + defp get_grouped_used_counts_group_by(query, :pack_id) do + query + |> group_by([sr: sr], sr.pack_id) + |> select([sr: sr], {sr.pack_id, sum(sr.count)}) + end + + @spec get_grouped_used_counts_types(Queryable.t(), [Type.t()] | nil) :: Queryable.t() + defp get_grouped_used_counts_types(query, types) when types |> is_list() do + type_ids = types |> Enum.map(fn %Type{id: type_id} -> type_id end) + query |> where([p: p], p.type_id in ^type_ids) + end + + defp get_grouped_used_counts_types(query, _nil), do: query + + @spec get_grouped_used_counts_packs(Queryable.t(), [Pack.t()] | nil) :: Queryable.t() + defp get_grouped_used_counts_packs(query, packs) when packs |> is_list() do + pack_ids = packs |> Enum.map(fn %Pack{id: pack_id} -> pack_id end) + query |> where([p: p], p.id in ^pack_ids) + end + + defp get_grouped_used_counts_packs(query, _nil), do: query end diff --git a/lib/cannery/ammo.ex b/lib/cannery/ammo.ex index 6b5323bb..6b049e30 100644 --- a/lib/cannery/ammo.ex +++ b/lib/cannery/ammo.ex @@ -267,7 +267,7 @@ defmodule Cannery.Ammo do @spec get_historical_count_for_types([Type.t()], User.t()) :: %{optional(Type.id()) => non_neg_integer()} def get_historical_count_for_types(types, %User{id: user_id} = user) do - used_counts = types |> ActivityLog.get_used_count_for_types(user) + used_counts = ActivityLog.get_grouped_used_counts(user, types: types, group_by: :type_id) round_counts = types |> get_round_count_for_types(user) types @@ -840,7 +840,8 @@ defmodule Cannery.Ammo do @spec get_original_counts([Pack.t()], User.t()) :: %{optional(Pack.id()) => non_neg_integer()} def get_original_counts(packs, %User{id: user_id} = current_user) do - used_counts = ActivityLog.get_used_counts(packs, current_user) + used_counts = + ActivityLog.get_grouped_used_counts(current_user, packs: packs, group_by: :pack_id) packs |> Map.new(fn %Pack{id: pack_id, count: count, user_id: ^user_id} -> diff --git a/lib/cannery_web/components/type_table_component.ex b/lib/cannery_web/components/type_table_component.ex index 57291916..bfde917e 100644 --- a/lib/cannery_web/components/type_table_component.ex +++ b/lib/cannery_web/components/type_table_component.ex @@ -158,7 +158,7 @@ defmodule CanneryWeb.Components.TypeTableComponent do [used_counts, historical_round_counts, historical_pack_counts, used_pack_counts] = if show_used do [ - types |> ActivityLog.get_used_count_for_types(current_user), + ActivityLog.get_grouped_used_counts(current_user, types: types, group_by: :type_id), types |> Ammo.get_historical_count_for_types(current_user), Ammo.get_grouped_packs_count(current_user, types: types, diff --git a/lib/cannery_web/controllers/export_controller.ex b/lib/cannery_web/controllers/export_controller.ex index a8a67303..4a6de939 100644 --- a/lib/cannery_web/controllers/export_controller.ex +++ b/lib/cannery_web/controllers/export_controller.ex @@ -4,7 +4,10 @@ defmodule CanneryWeb.ExportController do def export(%{assigns: %{current_user: current_user}} = conn, %{"mode" => "json"}) do types = Ammo.list_types(current_user) - used_counts = types |> ActivityLog.get_used_count_for_types(current_user) + + used_counts = + ActivityLog.get_grouped_used_counts(current_user, types: types, group_by: :type_id) + round_counts = types |> Ammo.get_round_count_for_types(current_user) pack_counts = Ammo.get_grouped_packs_count(current_user, types: types, group_by: :type_id) @@ -29,7 +32,10 @@ defmodule CanneryWeb.ExportController do end) packs = Ammo.list_packs(current_user, show_used: true) - used_counts = packs |> ActivityLog.get_used_counts(current_user) + + used_counts = + ActivityLog.get_grouped_used_counts(current_user, packs: packs, group_by: :pack_id) + original_counts = packs |> Ammo.get_original_counts(current_user) cprs = packs |> Ammo.get_cprs(current_user) percentages_remaining = packs |> Ammo.get_percentages_remaining(current_user) diff --git a/priv/gettext/de/LC_MESSAGES/errors.po b/priv/gettext/de/LC_MESSAGES/errors.po index 4b5d9160..72f10b8a 100644 --- a/priv/gettext/de/LC_MESSAGES/errors.po +++ b/priv/gettext/de/LC_MESSAGES/errors.po @@ -170,7 +170,7 @@ msgstr "" "Ungültige Nummer an Kopien. Muss zwischen 1 and %{max} liegen. War " "%{multiplier}" -#: lib/cannery/ammo.ex:979 +#: lib/cannery/ammo.ex:980 #, 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 54072fff..5e90c994 100644 --- a/priv/gettext/en/LC_MESSAGES/errors.po +++ b/priv/gettext/en/LC_MESSAGES/errors.po @@ -153,7 +153,7 @@ msgstr "" msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}" msgstr "" -#: lib/cannery/ammo.ex:979 +#: lib/cannery/ammo.ex:980 #, elixir-autogen, elixir-format msgid "Invalid multiplier" msgstr "" diff --git a/priv/gettext/errors.pot b/priv/gettext/errors.pot index c453ba89..429fa75b 100644 --- a/priv/gettext/errors.pot +++ b/priv/gettext/errors.pot @@ -152,7 +152,7 @@ msgstr "" msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}" msgstr "" -#: lib/cannery/ammo.ex:979 +#: lib/cannery/ammo.ex:980 #, 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 7d5f868a..55130a15 100644 --- a/priv/gettext/es/LC_MESSAGES/errors.po +++ b/priv/gettext/es/LC_MESSAGES/errors.po @@ -168,7 +168,7 @@ msgstr "No se ha podido procesar el número de copias" msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}" msgstr "Número inválido de copias, debe ser entre 1 y %{max}. Fue %{multiplier" -#: lib/cannery/ammo.ex:979 +#: lib/cannery/ammo.ex:980 #, elixir-autogen, elixir-format msgid "Invalid multiplier" msgstr "Multiplicador inválido" diff --git a/priv/gettext/fr/LC_MESSAGES/errors.po b/priv/gettext/fr/LC_MESSAGES/errors.po index 952945b2..7b4a7ece 100644 --- a/priv/gettext/fr/LC_MESSAGES/errors.po +++ b/priv/gettext/fr/LC_MESSAGES/errors.po @@ -169,7 +169,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:979 +#: lib/cannery/ammo.ex:980 #, 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 eb9dc8ab..c49a3e7a 100644 --- a/priv/gettext/ga/LC_MESSAGES/errors.po +++ b/priv/gettext/ga/LC_MESSAGES/errors.po @@ -168,7 +168,7 @@ msgstr "" msgid "Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}" msgstr "" -#: lib/cannery/ammo.ex:979 +#: lib/cannery/ammo.ex:980 #, elixir-autogen, elixir-format msgid "Invalid multiplier" msgstr "" diff --git a/test/cannery/activity_log_test.exs b/test/cannery/activity_log_test.exs index fee726c6..9b13416d 100644 --- a/test/cannery/activity_log_test.exs +++ b/test/cannery/activity_log_test.exs @@ -208,7 +208,7 @@ defmodule Cannery.ActivityLogTest do assert 0 = ActivityLog.get_used_count(current_user, pack_id: another_pack.id) end - test "get_used_counts/2 returns accurate used counts", %{ + test "get_grouped_used_counts/2 returns accurate used counts for packs", %{ pack: %{id: pack_id} = pack, type: type, container: container, @@ -217,20 +217,41 @@ defmodule Cannery.ActivityLogTest do {1, [%{id: another_pack_id} = another_pack]} = pack_fixture(type, container, current_user) assert %{pack_id => 5} == - [pack, another_pack] |> ActivityLog.get_used_counts(current_user) + ActivityLog.get_grouped_used_counts(current_user, + packs: [pack, another_pack], + group_by: :pack_id + ) shot_record_fixture(%{count: 5}, current_user, another_pack) - used_counts = [pack, another_pack] |> ActivityLog.get_used_counts(current_user) + + used_counts = + ActivityLog.get_grouped_used_counts(current_user, + packs: [pack, another_pack], + group_by: :pack_id + ) + assert %{^pack_id => 5} = used_counts assert %{^another_pack_id => 5} = used_counts shot_record_fixture(%{count: 15}, current_user, pack) - used_counts = [pack, another_pack] |> ActivityLog.get_used_counts(current_user) + + used_counts = + ActivityLog.get_grouped_used_counts(current_user, + packs: [pack, another_pack], + group_by: :pack_id + ) + assert %{^pack_id => 20} = used_counts assert %{^another_pack_id => 5} = used_counts shot_record_fixture(%{count: 10}, current_user, pack) - used_counts = [pack, another_pack] |> ActivityLog.get_used_counts(current_user) + + used_counts = + ActivityLog.get_grouped_used_counts(current_user, + packs: [pack, another_pack], + group_by: :pack_id + ) + assert %{^pack_id => 30} = used_counts assert %{^another_pack_id => 5} = used_counts end @@ -317,13 +338,19 @@ defmodule Cannery.ActivityLogTest do {1, [pack]} = pack_fixture(another_type, container, current_user) assert %{type_id => 5} == - [type, another_type] - |> ActivityLog.get_used_count_for_types(current_user) + ActivityLog.get_grouped_used_counts(current_user, + types: [type, another_type], + group_by: :type_id + ) # use generated pack shot_record_fixture(%{count: 5}, current_user, pack) - used_counts = [type, another_type] |> ActivityLog.get_used_count_for_types(current_user) + used_counts = + ActivityLog.get_grouped_used_counts(current_user, + types: [type, another_type], + group_by: :type_id + ) assert %{^type_id => 5} = used_counts assert %{^another_type_id => 5} = used_counts @@ -331,7 +358,11 @@ defmodule Cannery.ActivityLogTest do # use generated pack again shot_record_fixture(%{count: 1}, current_user, pack) - used_counts = [type, another_type] |> ActivityLog.get_used_count_for_types(current_user) + used_counts = + ActivityLog.get_grouped_used_counts(current_user, + types: [type, another_type], + group_by: :type_id + ) assert %{^type_id => 5} = used_counts assert %{^another_type_id => 6} = used_counts