Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't allow draft shuttles as replacement service #1138

Merged
merged 3 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion lib/arrow/shuttles/shuttle.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ defmodule Arrow.Shuttles.Shuttle do
@moduledoc "schema for a shuttle for the db"
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query

alias Arrow.Disruptions.ReplacementService
alias Arrow.Repo

@type id :: integer
@type t :: %__MODULE__{
Expand Down Expand Up @@ -59,7 +63,24 @@ defmodule Arrow.Shuttles.Shuttle do
end

_ ->
changeset
id = get_field(changeset, :id)

replacement_services =
if is_nil(id) do
[]
else
Repo.all(from r in ReplacementService, where: r.shuttle_id == ^id)
end

if length(replacement_services) > 0 do
add_error(
changeset,
:status,
"cannot set to a non-active status while in use as a replacement service"
)
else
changeset
end
end
end

Expand Down
2 changes: 2 additions & 0 deletions lib/arrow_web/components/core_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,7 @@ defmodule ArrowWeb.CoreComponents do
attr :field, :any, required: true, doc: "Field for `shuttle_id` value"

attr :shuttle, :any, required: true, doc: "Currently selected shuttle, if any"
attr :only_approved?, :boolean, default: false

attr :label, :string, default: "Stop ID"
attr :class, :string, default: nil
Expand All @@ -527,6 +528,7 @@ defmodule ArrowWeb.CoreComponents do
id={@id}
field={@field}
shuttle={@shuttle}
only_approved?={@only_approved?}
class={@class}
/>
"""
Expand Down
6 changes: 5 additions & 1 deletion lib/arrow_web/components/replacement_service_section.ex
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,11 @@ defmodule ArrowWeb.ReplacementServiceSection do
do: "add new replacement service component",
else: "edit disruption replacement service component"}
</h4>
<.shuttle_input field={@form[:shuttle_id]} shuttle={input_value(@form, :shuttle)} />
<.shuttle_input
field={@form[:shuttle_id]}
shuttle={input_value(@form, :shuttle)}
only_approved?={true}
/>
<div :if={not empty_input_value?(@form[:shuttle_id].value)} class="row relative z-0">
<div class="col p-0">
{live_react_component(
Expand Down
21 changes: 18 additions & 3 deletions lib/arrow_web/components/shuttle_input.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ defmodule ArrowWeb.ShuttleInput do
attr :id, :string, required: true
attr :field, :any, required: true
attr :shuttle, :any, required: true
attr :only_approved?, :boolean, default: false
attr :label, :string, default: "select shuttle route"
attr :class, :string, default: nil

Expand All @@ -22,7 +23,9 @@ defmodule ArrowWeb.ShuttleInput do
assigns,
:options,
if is_nil(assigns.shuttle) || !Ecto.assoc_loaded?(assigns.shuttle) do
Shuttles.list_shuttles() |> Enum.map(&option_for_shuttle/1)
Shuttles.list_shuttles()
|> filter_only_approved(assigns.only_approved?)
|> Enum.map(&option_for_shuttle/1)
else
[option_for_shuttle(assigns.shuttle)]
end
Expand All @@ -47,9 +50,14 @@ defmodule ArrowWeb.ShuttleInput do
def handle_event("live_select_change", %{"id" => live_select_id, "text" => text}, socket) do
new_opts =
if String.length(text) == 0 do
Shuttles.list_shuttles() |> Enum.map(&option_for_shuttle/1)
Shuttles.list_shuttles()
|> filter_only_approved(socket.assigns.only_approved?)
|> Enum.map(&option_for_shuttle/1)
else
text |> Shuttles.shuttles_by_search_string() |> Enum.map(&option_for_shuttle/1)
text
|> Shuttles.shuttles_by_search_string()
|> filter_only_approved(socket.assigns.only_approved?)
|> Enum.map(&option_for_shuttle/1)
end

send_update(LiveSelect.Component, id: live_select_id, options: new_opts)
Expand Down Expand Up @@ -81,4 +89,11 @@ defmodule ArrowWeb.ShuttleInput do
defp option_for_shuttle(%Shuttle{id: id, shuttle_name: shuttle_name}) do
{shuttle_name, id}
end

@spec filter_only_approved([Shuttle.t()], boolean()) :: [Shuttle.t()]
defp filter_only_approved(shuttles, false), do: shuttles

defp filter_only_approved(shuttles, true) do
Enum.filter(shuttles, fn shuttle -> shuttle.status == :active end)
end
end
114 changes: 114 additions & 0 deletions test/arrow/shuttle/shuttle_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -121,5 +121,119 @@ defmodule Arrow.Shuttles.ShuttleTest do

assert %Ecto.Changeset{valid?: true} = changeset
end

test "cannot mark a shuttle as inactive when in use by a replacement service" do
shuttle = shuttle_fixture()
[route0, route1] = shuttle.routes

[stop1, stop2, stop3, stop4] = insert_list(4, :gtfs_stop)

route0
|> Arrow.Shuttles.Route.changeset(%{
"route_stops" => [
%{
"direction_id" => "0",
"stop_sequence" => "1",
"display_stop_id" => stop1.id,
"time_to_next_stop" => 30.0
},
%{
"direction_id" => "0",
"stop_sequence" => "2",
"display_stop_id" => stop2.id
}
]
})
|> Arrow.Repo.update()

route1
|> Arrow.Shuttles.Route.changeset(%{
"route_stops" => [
%{
"direction_id" => "1",
"stop_sequence" => "1",
"display_stop_id" => stop3.id,
"time_to_next_stop" => 30.0
},
%{
"direction_id" => "0",
"stop_sequence" => "2",
"display_stop_id" => stop4.id
}
]
})
|> Arrow.Repo.update()

{:ok, shuttle} =
shuttle.id
|> Arrow.Shuttles.get_shuttle!()
|> Shuttle.changeset(%{status: :active})
|> Arrow.Repo.update()

insert(:replacement_service, shuttle: shuttle)

changeset = Shuttle.changeset(shuttle, %{status: :draft})

assert %Ecto.Changeset{
valid?: false,
errors: [
status:
{"cannot set to a non-active status while in use as a replacement service", []}
]
} = changeset
end

test "can mark a shuttle as inactive when not in use by a replacement service" do
shuttle = shuttle_fixture()
[route0, route1] = shuttle.routes

[stop1, stop2, stop3, stop4] = insert_list(4, :gtfs_stop)

route0
|> Arrow.Shuttles.Route.changeset(%{
"route_stops" => [
%{
"direction_id" => "0",
"stop_sequence" => "1",
"display_stop_id" => stop1.id,
"time_to_next_stop" => 30.0
},
%{
"direction_id" => "0",
"stop_sequence" => "2",
"display_stop_id" => stop2.id
}
]
})
|> Arrow.Repo.update()

route1
|> Arrow.Shuttles.Route.changeset(%{
"route_stops" => [
%{
"direction_id" => "1",
"stop_sequence" => "1",
"display_stop_id" => stop3.id,
"time_to_next_stop" => 30.0
},
%{
"direction_id" => "0",
"stop_sequence" => "2",
"display_stop_id" => stop4.id
}
]
})
|> Arrow.Repo.update()

{:ok, shuttle} =
shuttle.id
|> Arrow.Shuttles.get_shuttle!()
|> Shuttle.changeset(%{status: :active})
|> Arrow.Repo.update()

changeset = Shuttle.changeset(shuttle, %{status: :draft})

assert %Ecto.Changeset{valid?: true} = changeset
end
end
end
Loading