Skip to content
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
285 changes: 270 additions & 15 deletions pretex/lib/pretex/check_ins.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ defmodule Pretex.CheckIns do

alias Pretex.Repo
alias Pretex.CheckIns.CheckIn
alias Pretex.CheckIns.{CheckInList, CheckInListItem, Gate, GateCheckInList}
alias Pretex.Orders.{Order, OrderItem}

def checkin_topic(event_id), do: "checkins:event:#{event_id}"

def check_in_by_ticket_code(event_id, ticket_code, operator_id) do
def check_in_by_ticket_code(event_id, ticket_code, operator_id, check_in_list_id \\ nil) do
order_item =
OrderItem
|> join(:inner, [oi], o in Order, on: oi.order_id == o.id)
Expand All @@ -22,37 +23,128 @@ defmodule Pretex.CheckIns do
{:error, :invalid_ticket}

%{order: %{event_id: ^event_id}} = oi ->
validate_and_check_in(oi, event_id, operator_id)
validate_and_check_in(oi, event_id, operator_id, check_in_list_id)

_wrong_event ->
{:error, :wrong_event}
end
end

defp validate_and_check_in(%{order: %{status: status}}, _event_id, _operator_id)
def check_in_at_gate(event_id, ticket_code, operator_id, gate_id) do
gate = get_gate!(gate_id)

order_item =
OrderItem
|> join(:inner, [oi], o in Order, on: oi.order_id == o.id)
|> where([oi, _o], oi.ticket_code == ^ticket_code)
|> preload([oi, o], order: o)
|> Repo.one()

case order_item do
nil ->
{:error, :invalid_ticket}

%{order: %{event_id: ^event_id}} = oi ->
matching_list =
Enum.find(gate.check_in_lists, fn list ->
item_on_list?(oi.item_id, list.id) and list_active?(list)
end)

case matching_list do
nil -> {:error, :not_on_list}
list -> validate_and_check_in(oi, event_id, operator_id, list.id)
end

_wrong_event ->
{:error, :wrong_event}
end
end

defp validate_and_check_in(%{order: %{status: status}}, _event_id, _operator_id, _list_id)
when status != "confirmed" do
{:error, :ticket_cancelled}
end

defp validate_and_check_in(order_item, event_id, operator_id) do
existing =
CheckIn
defp validate_and_check_in(order_item, event_id, operator_id, check_in_list_id) do
with :ok <- validate_on_list(order_item, check_in_list_id),
:ok <- validate_list_active(check_in_list_id) do
existing =
CheckIn
|> where(
[c],
c.order_item_id == ^order_item.id and c.event_id == ^event_id and is_nil(c.annulled_at)
)
|> then(fn q ->
if check_in_list_id do
where(q, [c], c.check_in_list_id == ^check_in_list_id)
else
where(q, [c], is_nil(c.check_in_list_id))
end
end)
|> Repo.one()

case existing do
nil ->
insert_check_in(order_item.id, event_id, operator_id, check_in_list_id)

_already ->
{:error, :already_checked_in}
end
end
end

defp validate_on_list(_order_item, nil), do: :ok

defp validate_on_list(order_item, check_in_list_id) do
exists =
CheckInListItem
|> where(
[c],
c.order_item_id == ^order_item.id and c.event_id == ^event_id and is_nil(c.annulled_at)
[cli],
cli.check_in_list_id == ^check_in_list_id and cli.item_id == ^order_item.item_id
)
|> Repo.one()
|> Repo.exists?()

case existing do
nil ->
insert_check_in(order_item.id, event_id, operator_id)
if exists, do: :ok, else: {:error, :not_on_list}
end

defp validate_list_active(nil), do: :ok

defp validate_list_active(check_in_list_id) do
list = Repo.get!(CheckInList, check_in_list_id)

cond do
is_nil(list.starts_at_time) and is_nil(list.ends_at_time) ->
:ok

true ->
now = Time.utc_now()
starts = list.starts_at_time || ~T[00:00:00]
ends = list.ends_at_time || ~T[23:59:59]

_already ->
{:error, :already_checked_in}
if Time.compare(now, starts) != :lt and Time.compare(now, ends) != :gt do
:ok
else
{:error, :list_not_active}
end
end
end

defp insert_check_in(order_item_id, event_id, operator_id) do
defp item_on_list?(item_id, check_in_list_id) do
CheckInListItem
|> where([cli], cli.check_in_list_id == ^check_in_list_id and cli.item_id == ^item_id)
|> Repo.exists?()
end

defp list_active?(%{starts_at_time: nil, ends_at_time: nil}), do: true

defp list_active?(list) do
now = Time.utc_now()
starts = list.starts_at_time || ~T[00:00:00]
ends = list.ends_at_time || ~T[23:59:59]
Time.compare(now, starts) != :lt and Time.compare(now, ends) != :gt
end

defp insert_check_in(order_item_id, event_id, operator_id, check_in_list_id) do
now = DateTime.utc_now() |> DateTime.truncate(:microsecond)

result =
Expand All @@ -61,6 +153,11 @@ defmodule Pretex.CheckIns do
|> Ecto.Changeset.put_change(:order_item_id, order_item_id)
|> Ecto.Changeset.put_change(:event_id, event_id)
|> Ecto.Changeset.put_change(:checked_in_by_id, operator_id)
|> then(fn cs ->
if check_in_list_id,
do: Ecto.Changeset.put_change(cs, :check_in_list_id, check_in_list_id),
else: cs
end)
|> Repo.insert()

case result do
Expand Down Expand Up @@ -145,6 +242,164 @@ defmodule Pretex.CheckIns do
|> Repo.one()
end

# ---------------------------------------------------------------------------
# Check-in Lists CRUD
# ---------------------------------------------------------------------------

def create_check_in_list(event_id, attrs) do
item_ids = Map.get(attrs, :item_ids) || Map.get(attrs, "item_ids") || []

if item_ids == [] do
{:error, :no_items}
else
Repo.transaction(fn ->
changeset =
%CheckInList{}
|> CheckInList.changeset(attrs)
|> Ecto.Changeset.put_change(:event_id, event_id)

list =
case Repo.insert(changeset) do
{:ok, l} -> l
{:error, cs} -> Repo.rollback(cs)
end

Enum.each(item_ids, fn item_id ->
%CheckInListItem{}
|> Ecto.Changeset.change(%{check_in_list_id: list.id, item_id: item_id})
|> Repo.insert!()
end)

list
end)
end
end

def update_check_in_list(list_id, attrs) do
list = Repo.get!(CheckInList, list_id)
item_ids = Map.get(attrs, :item_ids) || Map.get(attrs, "item_ids")

Repo.transaction(fn ->
updated =
case list |> CheckInList.changeset(attrs) |> Repo.update() do
{:ok, l} -> l
{:error, cs} -> Repo.rollback(cs)
end

if item_ids do
CheckInListItem
|> where([cli], cli.check_in_list_id == ^list.id)
|> Repo.delete_all()

Enum.each(item_ids, fn item_id ->
%CheckInListItem{}
|> Ecto.Changeset.change(%{check_in_list_id: list.id, item_id: item_id})
|> Repo.insert!()
end)
end

updated
end)
end

def delete_check_in_list(list_id) do
list = Repo.get!(CheckInList, list_id)
Repo.delete(list)
end

def list_check_in_lists(event_id) do
CheckInList
|> where([l], l.event_id == ^event_id)
|> preload(:check_in_list_items)
|> order_by([l], asc: l.name)
|> Repo.all()
end

def get_check_in_list!(id) do
CheckInList
|> preload(:check_in_list_items)
|> Repo.get!(id)
end

# ---------------------------------------------------------------------------
# Gates CRUD
# ---------------------------------------------------------------------------

def create_gate(event_id, attrs) do
list_ids = Map.get(attrs, :check_in_list_ids) || Map.get(attrs, "check_in_list_ids") || []

if list_ids == [] do
{:error, :no_check_in_lists}
else
Repo.transaction(fn ->
changeset =
%Gate{}
|> Gate.changeset(attrs)
|> Ecto.Changeset.put_change(:event_id, event_id)

gate =
case Repo.insert(changeset) do
{:ok, g} -> g
{:error, cs} -> Repo.rollback(cs)
end

Enum.each(list_ids, fn list_id ->
%GateCheckInList{}
|> Ecto.Changeset.change(%{gate_id: gate.id, check_in_list_id: list_id})
|> Repo.insert!()
end)

gate
end)
end
end

def update_gate(gate_id, attrs) do
gate = Repo.get!(Gate, gate_id)
list_ids = Map.get(attrs, :check_in_list_ids) || Map.get(attrs, "check_in_list_ids")

Repo.transaction(fn ->
updated =
case gate |> Gate.changeset(attrs) |> Repo.update() do
{:ok, g} -> g
{:error, cs} -> Repo.rollback(cs)
end

if list_ids do
GateCheckInList
|> where([gcl], gcl.gate_id == ^gate.id)
|> Repo.delete_all()

Enum.each(list_ids, fn list_id ->
%GateCheckInList{}
|> Ecto.Changeset.change(%{gate_id: gate.id, check_in_list_id: list_id})
|> Repo.insert!()
end)
end

updated
end)
end

def delete_gate(gate_id) do
gate = Repo.get!(Gate, gate_id)
Repo.delete(gate)
end

def list_gates(event_id) do
Gate
|> where([g], g.event_id == ^event_id)
|> preload(:check_in_lists)
|> order_by([g], asc: g.name)
|> Repo.all()
end

def get_gate!(id) do
Gate
|> preload(check_in_lists: :check_in_list_items)
|> Repo.get!(id)
end

defp broadcast_check_in_update(event_id) do
count = get_check_in_count(event_id)
Phoenix.PubSub.broadcast(Pretex.PubSub, checkin_topic(event_id), {:check_in_updated, count})
Expand Down
1 change: 1 addition & 0 deletions pretex/lib/pretex/check_ins/check_in.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ defmodule Pretex.CheckIns.CheckIn do
belongs_to(:event, Pretex.Events.Event)
belongs_to(:checked_in_by, Pretex.Accounts.User, foreign_key: :checked_in_by_id)
belongs_to(:annulled_by, Pretex.Accounts.User, foreign_key: :annulled_by_id)
belongs_to(:check_in_list, Pretex.CheckIns.CheckInList)

timestamps(type: :utc_datetime)
end
Expand Down
35 changes: 35 additions & 0 deletions pretex/lib/pretex/check_ins/check_in_list.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
defmodule Pretex.CheckIns.CheckInList do
use Ecto.Schema
import Ecto.Changeset

schema "check_in_lists" do
field(:name, :string)
field(:starts_at_time, :time)
field(:ends_at_time, :time)

belongs_to(:event, Pretex.Events.Event)
has_many(:check_in_list_items, Pretex.CheckIns.CheckInListItem)
many_to_many(:gates, Pretex.CheckIns.Gate, join_through: "gate_check_in_lists")

timestamps(type: :utc_datetime)
end

def changeset(list, attrs) do
list
|> cast(attrs, [:name, :starts_at_time, :ends_at_time])
|> validate_required([:name])
|> validate_length(:name, min: 1, max: 255)
|> validate_time_window()
end

defp validate_time_window(changeset) do
starts = get_field(changeset, :starts_at_time)
ends = get_field(changeset, :ends_at_time)

if starts && ends && Time.compare(ends, starts) != :gt do
add_error(changeset, :ends_at_time, "must be after start time")
else
changeset
end
end
end
9 changes: 9 additions & 0 deletions pretex/lib/pretex/check_ins/check_in_list_item.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule Pretex.CheckIns.CheckInListItem do
use Ecto.Schema

schema "check_in_list_items" do
belongs_to(:check_in_list, Pretex.CheckIns.CheckInList)
belongs_to(:item, Pretex.Catalog.Item)
belongs_to(:item_variation, Pretex.Catalog.ItemVariation)
end
end
Loading