allow filtering ammo types when creating new packs and fix some form errors not displaying on create

This commit is contained in:
2024-10-26 15:15:14 -04:00
parent 7e14f292a6
commit ab3d3721d6
33 changed files with 404 additions and 266 deletions

View File

@ -959,25 +959,79 @@ defmodule Cannery.Ammo do
defp do_create_packs(
%{"type_id" => type_id, "container_id" => container_id} = attrs,
_multiplier,
multiplier,
user
)
when is_binary(type_id) and is_binary(container_id) do
changeset =
%Pack{}
|> Pack.create_changeset(
get_type!(type_id, user),
Containers.get_container!(container_id, user),
user,
attrs
)
|> Changeset.add_error(:multiplier, dgettext("errors", "Invalid multiplier"))
{:error, changeset}
%Pack{}
|> Pack.create_changeset(
get_type!(type_id, user),
Containers.get_container!(container_id, user),
user,
attrs
)
|> maybe_add_multiplier_error(multiplier)
|> Changeset.apply_action(:insert)
end
defp do_create_packs(invalid_attrs, _multiplier, user) do
{:error, %Pack{} |> Pack.create_changeset(nil, nil, user, invalid_attrs)}
defp do_create_packs(
%{"type_id" => type_id} = attrs,
multiplier,
user
)
when is_binary(type_id) do
%Pack{}
|> Pack.create_changeset(
get_type!(type_id, user),
nil,
user,
attrs
)
|> maybe_add_multiplier_error(multiplier)
|> Changeset.apply_action(:insert)
end
defp do_create_packs(
%{"container_id" => container_id} = attrs,
multiplier,
user
)
when is_binary(container_id) do
%Pack{}
|> Pack.create_changeset(
nil,
Containers.get_container!(container_id, user),
user,
attrs
)
|> maybe_add_multiplier_error(multiplier)
|> Changeset.apply_action(:insert)
end
defp do_create_packs(invalid_attrs, multiplier, user) do
%Pack{}
|> Pack.create_changeset(nil, nil, user, invalid_attrs)
|> maybe_add_multiplier_error(multiplier)
|> Changeset.apply_action(:insert)
end
defp maybe_add_multiplier_error(changeset, multiplier)
when multiplier >= 1 and
multiplier <= @pack_create_limit do
changeset
end
defp maybe_add_multiplier_error(changeset, multiplier) do
changeset
|> Changeset.add_error(
:multiplier,
dgettext(
"errors",
"Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}",
max: @pack_create_limit,
multiplier: multiplier
)
)
end
@spec preload_pack(Pack.t()) :: Pack.t()

View File

@ -70,36 +70,45 @@ defmodule Cannery.Ammo.Pack do
) :: changeset()
def create_changeset(
pack,
%Type{id: type_id},
%Container{id: container_id, user_id: user_id},
type,
container,
%User{id: user_id},
attrs
)
when is_binary(type_id) and is_binary(container_id) and is_binary(user_id) do
when is_binary(user_id) do
type_id =
case type do
%Type{id: type_id} when is_binary(type_id) ->
type_id
_invalid_type ->
nil
end
container_id =
case container do
%Container{id: container_id, user_id: ^user_id} when is_binary(container_id) ->
container_id
_invalid_container ->
nil
end
pack
|> change(type_id: type_id)
|> change(user_id: user_id)
|> change(container_id: container_id)
|> cast(attrs, [:count, :price_paid, :notes, :staged, :purchased_on, :lot_number])
|> change(user_id: user_id)
|> cast(attrs, [:count, :lot_number, :notes, :price_paid, :purchased_on, :staged])
|> validate_required(:type_id, message: dgettext("errors", "Please select a valid type"))
|> validate_required(:container_id,
message: dgettext("errors", "Please select a valid container")
)
|> validate_number(:count, greater_than: 0)
|> validate_number(:price_paid, greater_than_or_equal_to: 0)
|> validate_length(:lot_number, max: 255)
|> validate_required([:count, :staged, :purchased_on, :type_id, :container_id, :user_id])
end
@doc """
Invalid changeset, used to prompt user to select type and container
"""
def create_changeset(pack, _invalid_type, _invalid_container, _invalid_user, attrs) do
pack
|> cast(attrs, [:type_id, :container_id])
|> validate_required([:type_id, :container_id])
|> validate_number(:count, greater_than: 0)
|> validate_number(:price_paid, greater_than_or_equal_to: 0)
|> validate_length(:lot_number, max: 255)
|> add_error(:invalid, dgettext("errors", "Please select a type and container"))
end
@doc false
@spec update_changeset(t() | new_pack(), attrs :: map(), User.t()) :: changeset()
def update_changeset(pack, attrs, user) do

View File

@ -9,7 +9,11 @@ defmodule CanneryWeb.PackLive.FormComponent do
alias Ecto.Changeset
alias Phoenix.LiveView.Socket
@pack_create_limit 10_000
@impl true
@spec mount(Socket.t()) :: {:ok, Socket.t()}
def mount(socket) do
{:ok, socket |> assign(:class, :all)}
end
@impl true
@spec update(
@ -24,7 +28,6 @@ defmodule CanneryWeb.PackLive.FormComponent do
def update(%{assigns: %{current_user: current_user}} = socket) do
socket =
socket
|> assign(:pack_create_limit, @pack_create_limit)
|> assign(:types, Ammo.list_types(current_user))
|> assign_new(:containers, fn -> Containers.list_containers(current_user) end)
@ -33,7 +36,20 @@ defmodule CanneryWeb.PackLive.FormComponent do
@impl true
def handle_event("validate", %{"pack" => pack_params}, socket) do
{:noreply, socket |> assign_changeset(pack_params, :validate)}
matched_class =
case pack_params["class"] do
"rifle" -> :rifle
"shotgun" -> :shotgun
"pistol" -> :pistol
_other -> :all
end
socket =
socket
|> assign_changeset(pack_params, :validate)
|> assign(:class, matched_class)
{:noreply, socket}
end
def handle_event(
@ -51,11 +67,18 @@ defmodule CanneryWeb.PackLive.FormComponent do
containers |> Enum.map(fn %{id: id, name: name} -> {name, id} end)
end
@spec type_options([Type.t()]) :: [{String.t(), Type.id()}]
defp type_options(types) do
@spec type_options([Type.t()], Type.class() | :all) ::
[{String.t(), Type.id()}]
defp type_options(types, :all) do
types |> Enum.map(fn %{id: id, name: name} -> {name, id} end)
end
defp type_options(types, selected_class) do
types
|> Enum.filter(fn %{class: class} -> class == selected_class end)
|> Enum.map(fn %{id: id, name: name} -> {name, id} end)
end
# Save Helpers
defp assign_changeset(
@ -126,53 +149,15 @@ defmodule CanneryWeb.PackLive.FormComponent do
end
defp save_pack(
%{assigns: %{changeset: changeset}} = socket,
%{assigns: %{changeset: changeset, current_user: current_user, return_to: return_to}} =
socket,
action,
%{"multiplier" => multiplier_str} = pack_params
)
when action in [:new, :clone] do
socket =
case multiplier_str |> Integer.parse() do
{multiplier, _remainder}
when multiplier >= 1 and multiplier <= @pack_create_limit ->
socket |> create_multiple(pack_params, multiplier)
{multiplier, _remainder} ->
error_msg =
dgettext(
"errors",
"Invalid number of copies, must be between 1 and %{max}. Was %{multiplier}",
max: @pack_create_limit,
multiplier: multiplier
)
save_multiplier_error(socket, changeset, error_msg)
:error ->
error_msg = dgettext("errors", "Could not parse number of copies")
save_multiplier_error(socket, changeset, error_msg)
end
{:noreply, socket}
end
@spec save_multiplier_error(Socket.t(), Changeset.t(), String.t()) :: Socket.t()
defp save_multiplier_error(socket, changeset, error_msg) do
{:error, changeset} =
changeset
|> Changeset.add_error(:multiplier, error_msg)
|> Changeset.apply_action(:insert)
socket |> assign(:changeset, changeset)
end
defp create_multiple(
%{assigns: %{current_user: current_user, return_to: return_to}} = socket,
pack_params,
multiplier
) do
case Ammo.create_packs(pack_params, multiplier, current_user) do
{:ok, {count, _packs}} ->
with {multiplier, _remainder} <- multiplier_str |> Integer.parse(),
{:ok, {count, _packs}} <- Ammo.create_packs(pack_params, multiplier, current_user) do
prompt =
dngettext(
"prompts",
@ -182,9 +167,21 @@ defmodule CanneryWeb.PackLive.FormComponent do
)
socket |> put_flash(:info, prompt) |> push_navigate(to: return_to)
else
{:error, %Changeset{} = changeset} ->
socket |> assign(changeset: changeset)
{:error, %Changeset{} = changeset} ->
socket |> assign(changeset: changeset)
end
:error ->
error_msg = dgettext("errors", "Could not parse number of copies")
{:error, changeset} =
changeset
|> Changeset.add_error(:multiplier, error_msg)
|> Changeset.apply_action(:insert)
socket |> assign(:changeset, changeset)
end
{:noreply, socket}
end
end

View File

@ -19,8 +19,23 @@
<%= changeset_errors(@changeset) %>
</div>
<%= label(f, :class, gettext("Class"), class: "title text-lg text-primary-600") %>
<%= select(
f,
:class,
[
{gettext("Any"), :all},
{gettext("Rifle"), :rifle},
{gettext("Shotgun"), :shotgun},
{gettext("Pistol"), :pistol}
],
class: "text-center col-span-2 input input-primary",
value: @class
) %>
<%= error_tag(f, :class, "col-span-3 text-center") %>
<%= label(f, :type_id, gettext("Type"), class: "title text-lg text-primary-600") %>
<%= select(f, :type_id, type_options(@types),
<%= select(f, :type_id, type_options(@types, @class),
class: "text-center col-span-2 input input-primary",
id: "pack-form-type-select",
phx_hook: "SlimSelect"
@ -80,7 +95,6 @@
<%= label(f, :multiplier, gettext("Copies"), class: "title text-lg text-primary-600") %>
<%= number_input(f, :multiplier,
max: @pack_create_limit,
class: "text-center input input-primary",
value: 1,
phx_update: "ignore"