mirror of
https://codeberg.org/JasterV/spazio-solazzo.git
synced 2026-04-26 18:20:03 +00:00
refactor: booking actions to simplify them
This commit is contained in:
parent
bc0ff021e8
commit
b4a524605a
14 changed files with 684 additions and 363 deletions
|
|
@ -26,19 +26,13 @@ defmodule SpazioSolazzo.BookingSystem do
|
|||
end
|
||||
|
||||
resource SpazioSolazzo.BookingSystem.Booking do
|
||||
define :list_accepted_space_bookings_by_date,
|
||||
action: :list_accepted_space_bookings_by_date,
|
||||
args: [:space_id, :date]
|
||||
|
||||
define :list_booking_requests,
|
||||
action: :list_booking_requests,
|
||||
define :admin_search_bookings,
|
||||
action: :admin_dashboard_search,
|
||||
args: [:space_id, :email, :date]
|
||||
|
||||
define :count_pending_requests, action: :count_pending_requests
|
||||
|
||||
define :list_bookings_by_datetime_range,
|
||||
action: :by_datetime_range_and_status,
|
||||
args: [:space_id, :user_id, :start_datetime, :end_datetime, :states]
|
||||
define :search_bookings,
|
||||
action: :search,
|
||||
args: [:space_id, :start_datetime, :end_datetime, :states, :select]
|
||||
|
||||
define :create_booking,
|
||||
action: :create,
|
||||
|
|
|
|||
|
|
@ -54,25 +54,52 @@ defmodule SpazioSolazzo.BookingSystem.Booking do
|
|||
actions do
|
||||
defaults [:read]
|
||||
|
||||
read :list_accepted_space_bookings_by_date do
|
||||
argument :space_id, :uuid, allow_nil?: false
|
||||
argument :date, :date, allow_nil?: false
|
||||
read :search do
|
||||
description "Fetch bookings within a date/time range with optional filters"
|
||||
|
||||
argument :space_id, :uuid, allow_nil?: true
|
||||
argument :start_datetime, :datetime, allow_nil?: false
|
||||
argument :end_datetime, :datetime, allow_nil?: false
|
||||
argument :states, {:array, :atom}, allow_nil?: true
|
||||
argument :select, {:array, :atom}, allow_nil?: true
|
||||
|
||||
prepare fn query, _ctx ->
|
||||
date = Ash.Query.get_argument(query, :date)
|
||||
day_start = DateTime.new!(date, ~T[00:00:00], "Etc/UTC")
|
||||
day_end = DateTime.new!(date, ~T[23:59:59], "Etc/UTC")
|
||||
start_dt = Ash.Query.get_argument(query, :start_datetime)
|
||||
end_dt = Ash.Query.get_argument(query, :end_datetime)
|
||||
|
||||
query
|
||||
|> Ash.Query.filter(
|
||||
start_datetime < ^day_end and end_datetime > ^day_start and state == :accepted
|
||||
)
|
||||
# Base datetime overlap filter
|
||||
query =
|
||||
Ash.Query.filter(
|
||||
query,
|
||||
start_datetime < ^end_dt and end_datetime > ^start_dt
|
||||
)
|
||||
|
||||
# Optional space filter
|
||||
query =
|
||||
case Ash.Query.get_argument(query, :space_id) do
|
||||
nil -> query
|
||||
space_id -> Ash.Query.filter(query, space_id == ^space_id)
|
||||
end
|
||||
|
||||
# Optional states filter
|
||||
query =
|
||||
case Ash.Query.get_argument(query, :states) do
|
||||
nil -> query
|
||||
[] -> query
|
||||
states -> Ash.Query.filter(query, state in ^states)
|
||||
end
|
||||
|
||||
case Ash.Query.get_argument(query, :select) do
|
||||
nil -> query
|
||||
[] -> query
|
||||
select -> Ash.Query.select(query, select)
|
||||
end
|
||||
end
|
||||
|
||||
filter expr(space_id == ^arg(:space_id))
|
||||
end
|
||||
|
||||
read :list_booking_requests do
|
||||
read :admin_dashboard_search do
|
||||
description "Search query tailored for the admin booking management panel"
|
||||
|
||||
argument :space_id, :uuid, allow_nil?: true
|
||||
argument :email, :string, allow_nil?: true
|
||||
argument :date, :date, allow_nil?: true
|
||||
|
|
@ -105,53 +132,6 @@ defmodule SpazioSolazzo.BookingSystem.Booking do
|
|||
end
|
||||
end
|
||||
|
||||
read :count_pending_requests do
|
||||
filter expr(state == :requested)
|
||||
end
|
||||
|
||||
read :by_datetime_range_and_status do
|
||||
description "Fetch bookings within a date/time range with optional filters"
|
||||
|
||||
argument :space_id, :uuid, allow_nil?: true
|
||||
argument :user_id, :uuid, allow_nil?: true
|
||||
argument :start_datetime, :datetime, allow_nil?: false
|
||||
argument :end_datetime, :datetime, allow_nil?: false
|
||||
argument :states, {:array, :atom}, allow_nil?: true
|
||||
|
||||
prepare fn query, _ctx ->
|
||||
start_dt = Ash.Query.get_argument(query, :start_datetime)
|
||||
end_dt = Ash.Query.get_argument(query, :end_datetime)
|
||||
|
||||
# Base datetime overlap filter
|
||||
query =
|
||||
Ash.Query.filter(
|
||||
query,
|
||||
start_datetime < ^end_dt and end_datetime > ^start_dt
|
||||
)
|
||||
|
||||
# Optional space filter
|
||||
query =
|
||||
case Ash.Query.get_argument(query, :space_id) do
|
||||
nil -> query
|
||||
space_id -> Ash.Query.filter(query, space_id == ^space_id)
|
||||
end
|
||||
|
||||
# Optional user filter
|
||||
query =
|
||||
case Ash.Query.get_argument(query, :user_id) do
|
||||
nil -> query
|
||||
user_id -> Ash.Query.filter(query, user_id == ^user_id)
|
||||
end
|
||||
|
||||
# Optional states filter
|
||||
case Ash.Query.get_argument(query, :states) do
|
||||
nil -> query
|
||||
[] -> query
|
||||
states -> Ash.Query.filter(query, state in ^states)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
create :create do
|
||||
argument :space_id, :uuid, allow_nil?: false
|
||||
argument :user_id, :uuid, allow_nil?: true
|
||||
|
|
|
|||
|
|
@ -23,12 +23,12 @@ defmodule SpazioSolazzo.BookingSystem.TimeSlotTemplate.Calculations.SlotBookingS
|
|||
day_end = DateTime.new!(date, ~T[23:59:59], "Etc/UTC")
|
||||
|
||||
{:ok, all_bookings} =
|
||||
SpazioSolazzo.BookingSystem.list_bookings_by_datetime_range(
|
||||
SpazioSolazzo.BookingSystem.search_bookings(
|
||||
space_id,
|
||||
nil,
|
||||
day_start,
|
||||
day_end,
|
||||
[:requested, :accepted]
|
||||
[:requested, :accepted],
|
||||
[:start_datetime, :end_datetime, :state, :user_id]
|
||||
)
|
||||
|
||||
# Calculate stats for each slot using the cached bookings
|
||||
|
|
|
|||
|
|
@ -82,21 +82,14 @@ defmodule SpazioSolazzoWeb.Admin.AdminCalendarComponent do
|
|||
if Date.compare(date, Date.utc_today()) == :lt do
|
||||
{:noreply, socket}
|
||||
else
|
||||
# Check capacity
|
||||
capacity_status = Map.get(socket.assigns.day_capacities, date, :available)
|
||||
socket =
|
||||
if socket.assigns.multi_day_mode do
|
||||
handle_multi_day_selection(socket, date)
|
||||
else
|
||||
handle_single_day_selection(socket, date)
|
||||
end
|
||||
|
||||
if capacity_status == :over_capacity do
|
||||
{:noreply, socket}
|
||||
else
|
||||
socket =
|
||||
if socket.assigns.multi_day_mode do
|
||||
handle_multi_day_selection(socket, date)
|
||||
else
|
||||
handle_single_day_selection(socket, date)
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
_ ->
|
||||
|
|
@ -147,53 +140,58 @@ defmodule SpazioSolazzoWeb.Admin.AdminCalendarComponent do
|
|||
space_id = socket.assigns.space_id
|
||||
current_month = socket.assigns.current_month
|
||||
|
||||
# Get all days in the current month
|
||||
first_day = Date.beginning_of_month(current_month)
|
||||
last_day = Date.end_of_month(current_month)
|
||||
start_of_month = Date.beginning_of_month(current_month)
|
||||
end_of_month = Date.end_of_month(current_month)
|
||||
|
||||
# Calculate capacity for each day
|
||||
day_capacities =
|
||||
first_day
|
||||
|> Date.range(last_day)
|
||||
|> Enum.map(fn date ->
|
||||
capacity = get_day_capacity(space_id, date)
|
||||
{date, capacity}
|
||||
end)
|
||||
|> Map.new()
|
||||
# Single query for entire month
|
||||
{:ok, bookings} =
|
||||
BookingSystem.search_bookings(
|
||||
space_id,
|
||||
DateTime.new!(start_of_month, ~T[00:00:00]),
|
||||
DateTime.new!(end_of_month, ~T[23:59:59]),
|
||||
[:accepted],
|
||||
[:start_datetime, :end_datetime]
|
||||
)
|
||||
|
||||
# Count bookings per day
|
||||
day_data = compute_day_data(bookings, start_of_month, end_of_month)
|
||||
|
||||
# Build calendar grid
|
||||
calendar_weeks = build_calendar_grid(first_day, last_day)
|
||||
calendar_weeks = build_calendar_grid(start_of_month, end_of_month)
|
||||
|
||||
assign(socket,
|
||||
day_capacities: day_capacities,
|
||||
day_data: day_data,
|
||||
calendar_weeks: calendar_weeks,
|
||||
month_name: Calendar.strftime(current_month, "%B %Y")
|
||||
)
|
||||
end
|
||||
|
||||
defp get_day_capacity(space_id, date) do
|
||||
# Get the space to check capacities
|
||||
case Ash.get(SpazioSolazzo.BookingSystem.Space, space_id) do
|
||||
{:ok, space} ->
|
||||
# Get all bookings for this day
|
||||
case BookingSystem.list_accepted_space_bookings_by_date(space_id, date) do
|
||||
{:ok, bookings} ->
|
||||
# Count unique booking slots (simplified - counts all bookings)
|
||||
booking_count = length(bookings)
|
||||
defp compute_day_data(bookings, start_date, end_date) do
|
||||
# Initialize all days with zero count
|
||||
date_range = Date.range(start_date, end_date)
|
||||
|
||||
if booking_count >= space.capacity do
|
||||
:over_capacity
|
||||
else
|
||||
:available
|
||||
end
|
||||
initial_map =
|
||||
Enum.reduce(date_range, %{}, fn date, acc ->
|
||||
Map.put(acc, date, 0)
|
||||
end)
|
||||
|
||||
_ ->
|
||||
:available
|
||||
# Count bookings for each day
|
||||
Enum.reduce(bookings, initial_map, fn booking, acc ->
|
||||
# Get all dates this booking spans
|
||||
booking_start_date = DateTime.to_date(booking.start_datetime)
|
||||
booking_end_date = DateTime.to_date(booking.end_datetime)
|
||||
|
||||
booking_dates = Date.range(booking_start_date, booking_end_date)
|
||||
|
||||
# Increment count for each day this booking touches
|
||||
Enum.reduce(booking_dates, acc, fn date, inner_acc ->
|
||||
case Map.get(inner_acc, date) do
|
||||
# Date outside month range
|
||||
nil -> inner_acc
|
||||
count -> Map.put(inner_acc, date, count + 1)
|
||||
end
|
||||
|
||||
_ ->
|
||||
:available
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
defp build_calendar_grid(first_day, last_day) do
|
||||
|
|
@ -244,25 +242,17 @@ defmodule SpazioSolazzoWeb.Admin.AdminCalendarComponent do
|
|||
defp is_end_date?(date, _, end_date), do: Date.compare(date, end_date) == :eq
|
||||
|
||||
defp day_classes(date, assigns) do
|
||||
# Extract capacity status for the given date
|
||||
capacity = Map.get(assigns.day_capacities, date, :available)
|
||||
is_past = Date.compare(date, Date.utc_today()) == :lt
|
||||
in_range = day_in_range?(date, assigns.selected_date, assigns.start_date, assigns.end_date)
|
||||
is_start = is_start_date?(date, assigns.start_date, assigns.end_date)
|
||||
is_end = is_end_date?(date, assigns.start_date, assigns.end_date)
|
||||
|
||||
base = "aspect-square flex flex-col items-center justify-center transition-all"
|
||||
base = "relative aspect-square flex flex-col items-start justify-start p-2 transition-all"
|
||||
|
||||
cond do
|
||||
is_past ->
|
||||
[base, "text-slate-400 dark:text-slate-600 cursor-not-allowed opacity-50"]
|
||||
|
||||
capacity == :over_capacity ->
|
||||
[
|
||||
base,
|
||||
"bg-orange-100 dark:bg-orange-900/20 text-slate-400 dark:text-slate-500 border border-orange-300 dark:border-orange-800/30 cursor-not-allowed"
|
||||
]
|
||||
|
||||
in_range && assigns.multi_day_mode && assigns.end_date != nil ->
|
||||
cond do
|
||||
is_start ->
|
||||
|
|
@ -293,16 +283,8 @@ defmodule SpazioSolazzoWeb.Admin.AdminCalendarComponent do
|
|||
true ->
|
||||
[
|
||||
base,
|
||||
"rounded-lg bg-green-100 dark:bg-green-900/20 hover:bg-green-200 dark:hover:bg-green-900/40 text-slate-700 dark:text-slate-200 border border-transparent hover:border-green-500 dark:hover:border-green-600"
|
||||
"rounded-lg bg-white dark:bg-slate-800 hover:bg-slate-50 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-200 border border-slate-200 dark:border-slate-600 hover:border-primary dark:hover:border-primary"
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
defp capacity_indicator_color(capacity) do
|
||||
case capacity do
|
||||
:available -> "bg-green-500"
|
||||
:over_capacity -> "bg-orange-500"
|
||||
_ -> "bg-slate-300"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -23,24 +23,8 @@
|
|||
<%!-- Legend --%>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="text-sm font-bold uppercase tracking-wider text-slate-500 dark:text-slate-400">
|
||||
Availability Calendar
|
||||
Booking Calendar
|
||||
</h3>
|
||||
<div class="flex items-center gap-2 text-xs font-medium">
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="w-2 h-2 rounded-full bg-green-500"></div>
|
||||
<span class="text-slate-600 dark:text-slate-400">Available</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="w-2 h-2 rounded-full bg-orange-500"></div>
|
||||
<span class="text-slate-600 dark:text-slate-400">Overbooked</span>
|
||||
</div>
|
||||
<%= if @multi_day_mode && @start_date do %>
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="w-2 h-2 rounded-full bg-primary"></div>
|
||||
<span class="text-slate-600 dark:text-slate-400">Selected</span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%!-- Calendar --%>
|
||||
|
|
@ -84,84 +68,70 @@
|
|||
<%= if day == nil do %>
|
||||
<div class="aspect-square"></div>
|
||||
<% else %>
|
||||
<% capacity = Map.get(@day_capacities, day, :available) %>
|
||||
<% count = Map.get(@day_data, day, 0) %>
|
||||
<% is_past = Date.compare(day, Date.utc_today()) == :lt %>
|
||||
<% is_disabled = is_past || capacity == :over_capacity %>
|
||||
<% is_start = is_start_date?(day, @start_date, @end_date) %>
|
||||
<% is_end = is_end_date?(day, @start_date, @end_date) %>
|
||||
|
||||
<button
|
||||
phx-click={if !is_disabled, do: "select_date", else: nil}
|
||||
phx-click={if !is_past, do: "select_date", else: nil}
|
||||
phx-value-date={Date.to_iso8601(day)}
|
||||
phx-target={@myself}
|
||||
class={day_classes(day, assigns)}
|
||||
disabled={is_disabled}
|
||||
title={
|
||||
cond do
|
||||
is_past -> "Past date"
|
||||
capacity == :over_capacity -> "Overbooked"
|
||||
true -> "Select date"
|
||||
end
|
||||
}
|
||||
disabled={is_past}
|
||||
title={if is_past, do: "Past date", else: "Select date"}
|
||||
>
|
||||
<span class={[
|
||||
"text-sm",
|
||||
cond do
|
||||
is_start || is_end -> "font-bold"
|
||||
true -> "font-medium"
|
||||
end
|
||||
]}>
|
||||
{day.day}
|
||||
</span>
|
||||
|
||||
<%= cond do %>
|
||||
<% is_start && @multi_day_mode && @end_date != nil -> %>
|
||||
<span class="text-[10px] uppercase font-bold tracking-tighter mt-0.5">
|
||||
Start
|
||||
</span>
|
||||
<% is_end && @multi_day_mode && @end_date != nil -> %>
|
||||
<span class="text-[10px] uppercase font-bold tracking-tighter mt-0.5">
|
||||
End
|
||||
</span>
|
||||
<% !is_past && !is_start && !is_end -> %>
|
||||
<div class={[
|
||||
"h-1 w-1 rounded-full mt-1",
|
||||
capacity_indicator_color(capacity)
|
||||
]}>
|
||||
</div>
|
||||
<% true -> %>
|
||||
<div class="h-1 mt-1"></div>
|
||||
<%!-- Booking count badge - floating in top right corner --%>
|
||||
<%= if count > 0 && !is_past do %>
|
||||
<.link
|
||||
navigate={~p"/admin/bookings?date=#{Date.to_string(day)}"}
|
||||
class="absolute -top-2 -right-2 z-20 flex items-center gap-1 px-2 py-1 rounded-full bg-info text-info-content shadow-lg hover:shadow-xl hover:scale-110 transition-all duration-200 ring-2 ring-white dark:ring-slate-800"
|
||||
title={"View #{count} booking#{if count > 1, do: "s", else: ""}"}
|
||||
>
|
||||
<.icon name="hero-calendar-days" class="w-3 h-3" />
|
||||
<span class="text-xs font-bold">{count}</span>
|
||||
</.link>
|
||||
<% end %>
|
||||
|
||||
<div class="flex flex-col items-start justify-start w-full h-full">
|
||||
<span class={[
|
||||
"text-xs",
|
||||
cond do
|
||||
is_start || is_end -> "font-bold"
|
||||
true -> "font-medium"
|
||||
end
|
||||
]}>
|
||||
{day.day}
|
||||
</span>
|
||||
|
||||
<%= cond do %>
|
||||
<% is_start && @multi_day_mode && @end_date != nil -> %>
|
||||
<span class="text-[10px] uppercase font-bold tracking-tighter mt-auto">
|
||||
Start
|
||||
</span>
|
||||
<% is_end && @multi_day_mode && @end_date != nil -> %>
|
||||
<span class="text-[10px] uppercase font-bold tracking-tighter mt-auto">
|
||||
End
|
||||
</span>
|
||||
<% true -> %>
|
||||
<div class="flex-1"></div>
|
||||
<% end %>
|
||||
</div>
|
||||
</button>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%!-- Legend explaining booking counts --%>
|
||||
<div class="mt-4 p-3 bg-base-200 rounded-lg">
|
||||
<p class="text-sm text-base-content flex items-center gap-2">
|
||||
<span class="flex items-center gap-1 px-2 py-1 rounded-full bg-info text-info-content shadow-md">
|
||||
<.icon name="hero-calendar-days" class="w-3 h-3" />
|
||||
<span class="text-xs font-bold">#</span>
|
||||
</span>
|
||||
<span>Click badge to view bookings for that day</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%!-- Warning for capacity issues in multi-day range --%>
|
||||
<%= if @multi_day_mode && @start_date && @end_date do %>
|
||||
<% has_full_days =
|
||||
@start_date
|
||||
|> Date.range(@end_date)
|
||||
|> Enum.any?(fn date ->
|
||||
Map.get(@day_capacities, date, :available) == :over_capacity
|
||||
end) %>
|
||||
|
||||
<%= if has_full_days do %>
|
||||
<div class="rounded-xl bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-900/50 p-4 flex gap-3 items-start animate-pulse">
|
||||
<div class="shrink-0 text-red-600 dark:text-red-400 mt-0.5">
|
||||
<.icon name="hero-exclamation-triangle" class="w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-sm font-bold text-red-900 dark:text-red-200">
|
||||
Attention
|
||||
</h4>
|
||||
<p class="text-xs font-medium text-red-700 dark:text-red-300 mt-1">
|
||||
Some days in your selected range are overbooked.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ defmodule SpazioSolazzoWeb.Admin.BookingManagementLive do
|
|||
|
||||
def mount(_params, _session, socket) do
|
||||
{:ok, spaces} = Ash.read(SpazioSolazzo.BookingSystem.Space)
|
||||
{:ok, bookings} = BookingSystem.list_booking_requests(nil, nil, nil, load: [:space, :user])
|
||||
{:ok, bookings} = BookingSystem.admin_search_bookings(nil, nil, nil, load: [:space, :user])
|
||||
|
||||
# Separate pending and other bookings
|
||||
{pending, past} = Enum.split_with(bookings, &(&1.state == :requested))
|
||||
|
|
@ -36,6 +36,34 @@ defmodule SpazioSolazzoWeb.Admin.BookingManagementLive do
|
|||
)}
|
||||
end
|
||||
|
||||
def handle_params(params, _uri, socket) do
|
||||
# Parse date from URL if present
|
||||
filter_date =
|
||||
case Map.get(params, "date") do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
"" ->
|
||||
nil
|
||||
|
||||
date_string ->
|
||||
case Date.from_iso8601(date_string) do
|
||||
{:ok, date} -> date
|
||||
{:error, _} -> nil
|
||||
end
|
||||
end
|
||||
|
||||
# If we have a date from URL, apply it to filters
|
||||
socket =
|
||||
if filter_date && socket.assigns.filter_date != filter_date do
|
||||
apply_date_filter(socket, filter_date)
|
||||
else
|
||||
socket
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_event("toggle_expand", %{"booking_id" => booking_id}, socket) do
|
||||
expanded =
|
||||
if MapSet.member?(socket.assigns.expanded_booking_ids, booking_id) do
|
||||
|
|
@ -57,7 +85,7 @@ defmodule SpazioSolazzoWeb.Admin.BookingManagementLive do
|
|||
else: Date.from_iso8601!(params["date"])
|
||||
|
||||
{:ok, bookings} =
|
||||
BookingSystem.list_booking_requests(space_id, email, date, load: [:space, :user])
|
||||
BookingSystem.admin_search_bookings(space_id, email, date, load: [:space, :user])
|
||||
|
||||
{pending, past} = Enum.split_with(bookings, &(&1.state == :requested))
|
||||
|
||||
|
|
@ -72,7 +100,7 @@ defmodule SpazioSolazzoWeb.Admin.BookingManagementLive do
|
|||
end
|
||||
|
||||
def handle_event("clear_filters", _, socket) do
|
||||
{:ok, bookings} = BookingSystem.list_booking_requests(nil, nil, nil, load: [:space, :user])
|
||||
{:ok, bookings} = BookingSystem.admin_search_bookings(nil, nil, nil, load: [:space, :user])
|
||||
{pending, past} = Enum.split_with(bookings, &(&1.state == :requested))
|
||||
|
||||
{:noreply,
|
||||
|
|
@ -169,7 +197,7 @@ defmodule SpazioSolazzoWeb.Admin.BookingManagementLive do
|
|||
|
||||
defp refresh_bookings(socket) do
|
||||
{:ok, bookings} =
|
||||
BookingSystem.list_booking_requests(
|
||||
BookingSystem.admin_search_bookings(
|
||||
socket.assigns.filter_space_id,
|
||||
socket.assigns.filter_email,
|
||||
socket.assigns.filter_date,
|
||||
|
|
@ -185,6 +213,22 @@ defmodule SpazioSolazzoWeb.Admin.BookingManagementLive do
|
|||
)}
|
||||
end
|
||||
|
||||
defp apply_date_filter(socket, date) do
|
||||
space_id = socket.assigns.filter_space_id
|
||||
email = if socket.assigns.filter_email == "", do: nil, else: socket.assigns.filter_email
|
||||
|
||||
{:ok, bookings} =
|
||||
BookingSystem.admin_search_bookings(space_id, email, date, load: [:space, :user])
|
||||
|
||||
{pending, past} = Enum.split_with(bookings, &(&1.state == :requested))
|
||||
|
||||
assign(socket,
|
||||
pending_bookings: pending,
|
||||
past_bookings: past,
|
||||
filter_date: date
|
||||
)
|
||||
end
|
||||
|
||||
defp status_badge_classes(:requested) do
|
||||
"bg-amber-100 dark:bg-amber-900/40 text-amber-800 dark:text-amber-200"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ defmodule SpazioSolazzoWeb.Admin.DashboardLive do
|
|||
|
||||
use SpazioSolazzoWeb, :live_view
|
||||
|
||||
alias SpazioSolazzo.BookingSystem
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
# Get pending requests count for the badge
|
||||
{:ok, pending_requests} = BookingSystem.count_pending_requests()
|
||||
pending_count = length(pending_requests)
|
||||
# Get pending requests count directly from database (no data loaded)
|
||||
{:ok, pending_count} =
|
||||
Ash.count(SpazioSolazzo.BookingSystem.Booking,
|
||||
query: [filter: [state: :requested]]
|
||||
)
|
||||
|
||||
{:ok,
|
||||
assign(socket,
|
||||
|
|
|
|||
|
|
@ -4,47 +4,30 @@ defmodule SpazioSolazzoWeb.Admin.WalkInLive do
|
|||
"""
|
||||
|
||||
use SpazioSolazzoWeb, :live_view
|
||||
|
||||
alias SpazioSolazzo.BookingSystem
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
# Get coworking space
|
||||
{:ok, spaces} = Ash.read(SpazioSolazzo.BookingSystem.Space)
|
||||
coworking_space = Enum.find(spaces, &(&1.slug == "coworking"))
|
||||
{:ok, coworking_space} = BookingSystem.get_space_by_slug("coworking")
|
||||
|
||||
if coworking_space == nil do
|
||||
{:ok,
|
||||
socket
|
||||
|> put_flash(:error, "Coworking space not found")
|
||||
|> push_navigate(to: "/admin/dashboard")}
|
||||
else
|
||||
{:ok,
|
||||
assign(socket,
|
||||
coworking_space: coworking_space,
|
||||
multi_day_mode: false,
|
||||
start_date: nil,
|
||||
end_date: nil,
|
||||
selected_date: nil,
|
||||
start_time: ~T[09:00:00],
|
||||
end_time: ~T[18:00:00],
|
||||
customer_name: "",
|
||||
customer_email: "",
|
||||
customer_phone: "",
|
||||
customer_comment: "",
|
||||
time_slot_warning: nil
|
||||
)}
|
||||
end
|
||||
{:ok,
|
||||
assign(socket,
|
||||
coworking_space: coworking_space,
|
||||
multi_day_mode: false,
|
||||
start_date: nil,
|
||||
end_date: nil,
|
||||
start_time: ~T[09:00:00],
|
||||
end_time: ~T[18:00:00],
|
||||
customer_name: "",
|
||||
customer_email: "",
|
||||
customer_phone: "",
|
||||
customer_comment: ""
|
||||
)}
|
||||
end
|
||||
|
||||
def handle_event("update_start_time", %{"value" => time_str}, socket) do
|
||||
case Time.from_iso8601(time_str <> ":00") do
|
||||
{:ok, time} ->
|
||||
socket =
|
||||
socket
|
||||
|> assign(start_time: time)
|
||||
|> check_time_slot_capacity()
|
||||
|
||||
{:noreply, socket}
|
||||
{:noreply, assign(socket, start_time: time)}
|
||||
|
||||
_ ->
|
||||
{:noreply, socket}
|
||||
|
|
@ -54,12 +37,7 @@ defmodule SpazioSolazzoWeb.Admin.WalkInLive do
|
|||
def handle_event("update_end_time", %{"value" => time_str}, socket) do
|
||||
case Time.from_iso8601(time_str <> ":00") do
|
||||
{:ok, time} ->
|
||||
socket =
|
||||
socket
|
||||
|> assign(end_time: time)
|
||||
|> check_time_slot_capacity()
|
||||
|
||||
{:noreply, socket}
|
||||
{:noreply, assign(socket, end_time: time)}
|
||||
|
||||
_ ->
|
||||
{:noreply, socket}
|
||||
|
|
@ -106,9 +84,7 @@ defmodule SpazioSolazzoWeb.Admin.WalkInLive do
|
|||
customer_phone: "",
|
||||
customer_comment: "",
|
||||
start_date: nil,
|
||||
end_date: nil,
|
||||
selected_date: nil,
|
||||
time_slot_warning: nil
|
||||
end_date: nil
|
||||
)
|
||||
|> put_flash(:info, "Walk-in booking created successfully")}
|
||||
|
||||
|
|
@ -127,12 +103,7 @@ defmodule SpazioSolazzoWeb.Admin.WalkInLive do
|
|||
end
|
||||
|
||||
def handle_info({:date_selected, start_date, end_date}, socket) do
|
||||
socket =
|
||||
socket
|
||||
|> assign(start_date: start_date, end_date: end_date, selected_date: nil)
|
||||
|> check_time_slot_capacity()
|
||||
|
||||
{:noreply, socket}
|
||||
{:noreply, assign(socket, start_date: start_date, end_date: end_date)}
|
||||
end
|
||||
|
||||
def handle_info(_msg, socket) do
|
||||
|
|
@ -140,56 +111,20 @@ defmodule SpazioSolazzoWeb.Admin.WalkInLive do
|
|||
end
|
||||
|
||||
defp get_start_date(socket) do
|
||||
socket.assigns.start_date || socket.assigns.selected_date
|
||||
socket.assigns.start_date
|
||||
end
|
||||
|
||||
defp get_end_date(socket) do
|
||||
socket.assigns.end_date || socket.assigns.selected_date
|
||||
socket.assigns.end_date
|
||||
end
|
||||
|
||||
defp check_time_slot_capacity(socket) do
|
||||
# Only check for single-day bookings
|
||||
if socket.assigns.multi_day_mode || socket.assigns.selected_date == nil do
|
||||
assign(socket, time_slot_warning: nil)
|
||||
else
|
||||
date = socket.assigns.selected_date
|
||||
start_time = socket.assigns.start_time
|
||||
end_time = socket.assigns.end_time
|
||||
space_id = socket.assigns.coworking_space.id
|
||||
capacity = socket.assigns.coworking_space.capacity
|
||||
defp days_selected(nil, nil), do: 0
|
||||
defp days_selected(start_date, nil) when not is_nil(start_date), do: 1
|
||||
|
||||
start_datetime = DateTime.new!(date, start_time, "Etc/UTC")
|
||||
end_datetime = DateTime.new!(date, end_time, "Etc/UTC")
|
||||
|
||||
{:ok, bookings} =
|
||||
BookingSystem.list_bookings_by_datetime_range(
|
||||
space_id,
|
||||
nil,
|
||||
start_datetime,
|
||||
end_datetime,
|
||||
[:accepted]
|
||||
)
|
||||
|
||||
accepted_count = length(bookings)
|
||||
|
||||
if accepted_count >= capacity do
|
||||
assign(socket,
|
||||
time_slot_warning: "This time slot is currently overbooked. Proceed with caution."
|
||||
)
|
||||
else
|
||||
assign(socket, time_slot_warning: nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp days_selected(nil, nil, nil), do: 0
|
||||
defp days_selected(selected, nil, nil) when not is_nil(selected), do: 1
|
||||
defp days_selected(nil, start_date, nil) when not is_nil(start_date), do: 1
|
||||
|
||||
defp days_selected(nil, start_date, end_date)
|
||||
defp days_selected(start_date, end_date)
|
||||
when not is_nil(start_date) and not is_nil(end_date) do
|
||||
Date.diff(end_date, start_date) + 1
|
||||
end
|
||||
|
||||
defp days_selected(_, _, _), do: 0
|
||||
defp days_selected(_, _), do: 0
|
||||
end
|
||||
|
|
|
|||
|
|
@ -65,8 +65,8 @@
|
|||
Start Date
|
||||
</span>
|
||||
<span class="text-sm font-bold text-slate-900 dark:text-white">
|
||||
<%= if @start_date || @selected_date do %>
|
||||
{Calendar.strftime(@start_date || @selected_date, "%b %d, %Y")}
|
||||
<%= if @start_date do %>
|
||||
{Calendar.strftime(@start_date, "%b %d, %Y")}
|
||||
<% else %>
|
||||
<span class="text-slate-400">Not selected</span>
|
||||
<% end %>
|
||||
|
|
@ -78,8 +78,8 @@
|
|||
End Date
|
||||
</span>
|
||||
<span class="text-sm font-bold text-slate-900 dark:text-white">
|
||||
<%= if @end_date || @selected_date do %>
|
||||
{Calendar.strftime(@end_date || @selected_date, "%b %d, %Y")}
|
||||
<%= if @end_date do %>
|
||||
{Calendar.strftime(@end_date, "%b %d, %Y")}
|
||||
<% else %>
|
||||
<span class="text-slate-400">Not selected</span>
|
||||
<% end %>
|
||||
|
|
@ -96,7 +96,7 @@
|
|||
/>
|
||||
<span>
|
||||
<%= if @multi_day_mode && @start_date && @end_date do %>
|
||||
{days_selected(@selected_date, @start_date, @end_date)} Days total
|
||||
{days_selected(@start_date, @end_date)} Days total
|
||||
<% else %>
|
||||
Single Day
|
||||
<% end %>
|
||||
|
|
@ -168,19 +168,6 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= if @time_slot_warning do %>
|
||||
<div class="mt-2 p-3 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800/50 flex items-start gap-3">
|
||||
<span class="text-red-600 dark:text-red-400 shrink-0">
|
||||
<.icon name="hero-exclamation-triangle" class="w-5 h-5" />
|
||||
</span>
|
||||
<div>
|
||||
<p class="text-sm font-bold text-red-900 dark:text-red-100 leading-tight">
|
||||
{@time_slot_warning}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
|||
333
test/spazio_solazzo/booking_system/booking_month_count_test.exs
Normal file
333
test/spazio_solazzo/booking_system/booking_month_count_test.exs
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
defmodule SpazioSolazzo.BookingSystem.BookingMonthCountTest do
|
||||
use SpazioSolazzo.DataCase
|
||||
|
||||
alias SpazioSolazzo.BookingSystem
|
||||
|
||||
describe "list_bookings_for_month_count/3" do
|
||||
setup do
|
||||
# Create space
|
||||
{:ok, space} = BookingSystem.create_space("Coworking", "coworking", "Desc", 10)
|
||||
|
||||
# Use dates in the future (next month, day 15)
|
||||
today = Date.utc_today()
|
||||
next_month = Date.add(today, 30)
|
||||
test_date = %{next_month | day: 15}
|
||||
|
||||
start_datetime = DateTime.new!(test_date, ~T[09:00:00], "Etc/UTC")
|
||||
end_datetime = DateTime.new!(test_date, ~T[17:00:00], "Etc/UTC")
|
||||
|
||||
# Create booking with full data
|
||||
{:ok, booking} =
|
||||
BookingSystem.create_walk_in(
|
||||
space.id,
|
||||
start_datetime,
|
||||
end_datetime,
|
||||
"John Doe",
|
||||
"john@example.com",
|
||||
"555-1234",
|
||||
"Test comment"
|
||||
)
|
||||
|
||||
# Reload booking
|
||||
{:ok, booking} = Ash.reload(booking)
|
||||
|
||||
# Calculate month boundaries
|
||||
start_of_month = Date.beginning_of_month(test_date)
|
||||
end_of_month = Date.end_of_month(test_date)
|
||||
|
||||
%{
|
||||
space: space,
|
||||
booking: booking,
|
||||
test_date: test_date,
|
||||
start_of_month: start_of_month,
|
||||
end_of_month: end_of_month
|
||||
}
|
||||
end
|
||||
|
||||
test "returns only datetime fields, not full booking data",
|
||||
%{space: space, start_of_month: start_date, end_of_month: end_date} do
|
||||
start_datetime = DateTime.new!(start_date, ~T[00:00:00], "Etc/UTC")
|
||||
end_datetime = DateTime.new!(Date.add(end_date, 1), ~T[00:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, bookings} =
|
||||
BookingSystem.search_bookings(
|
||||
space.id,
|
||||
start_datetime,
|
||||
end_datetime,
|
||||
[:accepted],
|
||||
[:start_datetime, :end_datetime]
|
||||
)
|
||||
|
||||
assert length(bookings) == 1
|
||||
booking = hd(bookings)
|
||||
|
||||
# Assert datetime fields ARE present
|
||||
assert %DateTime{} = booking.start_datetime
|
||||
assert %DateTime{} = booking.end_datetime
|
||||
|
||||
# Assert other fields are NOT loaded
|
||||
refute Ash.Resource.loaded?(booking, :customer_name)
|
||||
refute Ash.Resource.loaded?(booking, :customer_email)
|
||||
refute Ash.Resource.loaded?(booking, :customer_phone)
|
||||
refute Ash.Resource.loaded?(booking, :customer_comment)
|
||||
refute Ash.Resource.loaded?(booking, :user)
|
||||
refute Ash.Resource.loaded?(booking, :space)
|
||||
end
|
||||
|
||||
test "returns empty list for month with no bookings", %{space: space} do
|
||||
# Query a different month (two months from now)
|
||||
today = Date.utc_today()
|
||||
future_month = Date.add(today, 60)
|
||||
start_date = Date.beginning_of_month(future_month)
|
||||
end_date = Date.end_of_month(future_month)
|
||||
|
||||
start_datetime = DateTime.new!(start_date, ~T[00:00:00], "Etc/UTC")
|
||||
end_datetime = DateTime.new!(Date.add(end_date, 1), ~T[00:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, bookings} =
|
||||
BookingSystem.search_bookings(
|
||||
space.id,
|
||||
start_datetime,
|
||||
end_datetime,
|
||||
[:accepted],
|
||||
[:start_datetime, :end_datetime]
|
||||
)
|
||||
|
||||
assert bookings == []
|
||||
end
|
||||
|
||||
test "handles bookings that span multiple days",
|
||||
%{space: space, test_date: test_date, start_of_month: start_date, end_of_month: end_date} do
|
||||
# Create a 3-day booking (day 20-22)
|
||||
multi_day_start = %{test_date | day: 20}
|
||||
multi_day_end = %{test_date | day: 22}
|
||||
|
||||
start_datetime = DateTime.new!(multi_day_start, ~T[09:00:00], "Etc/UTC")
|
||||
end_datetime = DateTime.new!(multi_day_end, ~T[17:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, _booking} =
|
||||
BookingSystem.create_walk_in(
|
||||
space.id,
|
||||
start_datetime,
|
||||
end_datetime,
|
||||
"Jane Doe",
|
||||
"jane@example.com",
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
month_start_datetime = DateTime.new!(start_date, ~T[00:00:00], "Etc/UTC")
|
||||
month_end_datetime = DateTime.new!(Date.add(end_date, 1), ~T[00:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, bookings} =
|
||||
BookingSystem.search_bookings(
|
||||
space.id,
|
||||
month_start_datetime,
|
||||
month_end_datetime,
|
||||
[:accepted],
|
||||
[:start_datetime, :end_datetime]
|
||||
)
|
||||
|
||||
# Should have 2 bookings (original + multi-day)
|
||||
assert length(bookings) == 2
|
||||
|
||||
# All should only have datetime fields
|
||||
Enum.each(bookings, fn booking ->
|
||||
refute Ash.Resource.loaded?(booking, :customer_name)
|
||||
refute Ash.Resource.loaded?(booking, :customer_email)
|
||||
end)
|
||||
end
|
||||
|
||||
test "handles month boundaries correctly",
|
||||
%{space: space, test_date: test_date, start_of_month: start_date, end_of_month: end_date} do
|
||||
# Booking starts before month, ends during month (last day of previous month to day 2)
|
||||
before_month = Date.add(start_date, -1)
|
||||
during_month = %{test_date | day: 2}
|
||||
|
||||
start_datetime1 = DateTime.new!(before_month, ~T[09:00:00], "Etc/UTC")
|
||||
end_datetime1 = DateTime.new!(during_month, ~T[17:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, _before} =
|
||||
BookingSystem.create_walk_in(
|
||||
space.id,
|
||||
start_datetime1,
|
||||
end_datetime1,
|
||||
"Before Month",
|
||||
"before@example.com",
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
# Booking starts during month, ends after month (day 27 to first day of next month)
|
||||
during_month2 = %{test_date | day: min(27, Date.days_in_month(test_date))}
|
||||
after_month = Date.add(end_date, 1)
|
||||
|
||||
start_datetime2 = DateTime.new!(during_month2, ~T[09:00:00], "Etc/UTC")
|
||||
end_datetime2 = DateTime.new!(after_month, ~T[17:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, _after} =
|
||||
BookingSystem.create_walk_in(
|
||||
space.id,
|
||||
start_datetime2,
|
||||
end_datetime2,
|
||||
"After Month",
|
||||
"after@example.com",
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
month_start_datetime = DateTime.new!(start_date, ~T[00:00:00], "Etc/UTC")
|
||||
month_end_datetime = DateTime.new!(Date.add(end_date, 1), ~T[00:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, bookings} =
|
||||
BookingSystem.search_bookings(
|
||||
space.id,
|
||||
month_start_datetime,
|
||||
month_end_datetime,
|
||||
[:accepted],
|
||||
[:start_datetime, :end_datetime]
|
||||
)
|
||||
|
||||
# Should include all 3 bookings (original + before + after)
|
||||
assert length(bookings) == 3
|
||||
end
|
||||
|
||||
test "only returns accepted bookings, not pending/rejected/cancelled",
|
||||
%{space: space, test_date: test_date, start_of_month: start_date, end_of_month: end_date} do
|
||||
# Create a regular requested booking (not walk-in) on day 10
|
||||
pending_date = %{test_date | day: 10}
|
||||
|
||||
{:ok, _pending} =
|
||||
BookingSystem.create_booking(
|
||||
space.id,
|
||||
nil,
|
||||
pending_date,
|
||||
~T[09:00:00],
|
||||
~T[17:00:00],
|
||||
"Pending",
|
||||
"pending@example.com",
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
# Create and reject a booking on day 11
|
||||
rejected_date = %{test_date | day: 11}
|
||||
|
||||
{:ok, rejected} =
|
||||
BookingSystem.create_booking(
|
||||
space.id,
|
||||
nil,
|
||||
rejected_date,
|
||||
~T[09:00:00],
|
||||
~T[17:00:00],
|
||||
"Rejected",
|
||||
"rejected@example.com",
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
{:ok, _} = BookingSystem.reject_booking(rejected, "Test reason")
|
||||
|
||||
month_start_datetime = DateTime.new!(start_date, ~T[00:00:00], "Etc/UTC")
|
||||
month_end_datetime = DateTime.new!(Date.add(end_date, 1), ~T[00:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, bookings} =
|
||||
BookingSystem.search_bookings(
|
||||
space.id,
|
||||
month_start_datetime,
|
||||
month_end_datetime,
|
||||
[:accepted],
|
||||
[:start_datetime, :end_datetime]
|
||||
)
|
||||
|
||||
# Should only have the original accepted booking from setup
|
||||
assert length(bookings) == 1
|
||||
end
|
||||
|
||||
test "handles bookings at exact month boundaries",
|
||||
%{space: space, start_of_month: start_date, end_of_month: end_date} do
|
||||
# Booking exactly at month start
|
||||
month_start_datetime = DateTime.new!(start_date, ~T[00:00:00], "Etc/UTC")
|
||||
month_start_end = DateTime.new!(start_date, ~T[08:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, _start} =
|
||||
BookingSystem.create_walk_in(
|
||||
space.id,
|
||||
month_start_datetime,
|
||||
month_start_end,
|
||||
"Start Boundary",
|
||||
"start@example.com",
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
# Booking exactly at month end
|
||||
month_end_start = DateTime.new!(end_date, ~T[18:00:00], "Etc/UTC")
|
||||
month_end_datetime = DateTime.new!(end_date, ~T[23:59:59], "Etc/UTC")
|
||||
|
||||
{:ok, _end} =
|
||||
BookingSystem.create_walk_in(
|
||||
space.id,
|
||||
month_end_start,
|
||||
month_end_datetime,
|
||||
"End Boundary",
|
||||
"end@example.com",
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
month_start_datetime = DateTime.new!(start_date, ~T[00:00:00], "Etc/UTC")
|
||||
month_end_datetime = DateTime.new!(Date.add(end_date, 1), ~T[00:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, bookings} =
|
||||
BookingSystem.search_bookings(
|
||||
space.id,
|
||||
month_start_datetime,
|
||||
month_end_datetime,
|
||||
[:accepted],
|
||||
[:start_datetime, :end_datetime]
|
||||
)
|
||||
|
||||
# Should include all bookings including boundaries
|
||||
assert length(bookings) >= 3
|
||||
end
|
||||
|
||||
test "filters by space_id correctly",
|
||||
%{space: space, test_date: test_date, start_of_month: start_date, end_of_month: end_date} do
|
||||
# Create another space
|
||||
{:ok, other_space} = BookingSystem.create_space("Other", "other", "Other space", 5)
|
||||
|
||||
# Create booking for other space on day 16
|
||||
other_date = %{test_date | day: 16}
|
||||
start_datetime = DateTime.new!(other_date, ~T[09:00:00], "Etc/UTC")
|
||||
end_datetime = DateTime.new!(other_date, ~T[17:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, _other_booking} =
|
||||
BookingSystem.create_walk_in(
|
||||
other_space.id,
|
||||
start_datetime,
|
||||
end_datetime,
|
||||
"Other Space",
|
||||
"other@example.com",
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
# Query for original space
|
||||
month_start_datetime = DateTime.new!(start_date, ~T[00:00:00], "Etc/UTC")
|
||||
month_end_datetime = DateTime.new!(Date.add(end_date, 1), ~T[00:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, bookings} =
|
||||
BookingSystem.search_bookings(
|
||||
space.id,
|
||||
month_start_datetime,
|
||||
month_end_datetime,
|
||||
[:accepted],
|
||||
[:start_datetime, :end_datetime]
|
||||
)
|
||||
|
||||
# Should only return bookings for the original space
|
||||
assert length(bookings) == 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -311,7 +311,7 @@ defmodule SpazioSolazzo.BookingSystem.BookingTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "list_accepted_space_bookings_by_date/2" do
|
||||
describe "search_bookings/5 for accepted bookings" do
|
||||
test "returns only approved bookings for specific date", %{space: space, date: date} do
|
||||
{:ok, approved1} =
|
||||
request_booking(
|
||||
|
|
@ -356,7 +356,17 @@ defmodule SpazioSolazzo.BookingSystem.BookingTest do
|
|||
""
|
||||
)
|
||||
|
||||
{:ok, bookings} = BookingSystem.list_accepted_space_bookings_by_date(space.id, date)
|
||||
start_datetime = DateTime.new!(date, ~T[00:00:00], "Etc/UTC")
|
||||
end_datetime = DateTime.new!(Date.add(date, 1), ~T[00:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, bookings} =
|
||||
BookingSystem.search_bookings(
|
||||
space.id,
|
||||
start_datetime,
|
||||
end_datetime,
|
||||
[:accepted],
|
||||
nil
|
||||
)
|
||||
|
||||
assert length(bookings) == 2
|
||||
assert Enum.all?(bookings, &(&1.state == :accepted))
|
||||
|
|
@ -379,7 +389,17 @@ defmodule SpazioSolazzo.BookingSystem.BookingTest do
|
|||
{:ok, _} = BookingSystem.approve_booking(booking.id)
|
||||
{:ok, _} = BookingSystem.cancel_booking(booking.id, "Test cancellation")
|
||||
|
||||
{:ok, bookings} = BookingSystem.list_accepted_space_bookings_by_date(space.id, date)
|
||||
start_datetime = DateTime.new!(date, ~T[00:00:00], "Etc/UTC")
|
||||
end_datetime = DateTime.new!(Date.add(date, 1), ~T[00:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, bookings} =
|
||||
BookingSystem.search_bookings(
|
||||
space.id,
|
||||
start_datetime,
|
||||
end_datetime,
|
||||
[:accepted],
|
||||
nil
|
||||
)
|
||||
|
||||
assert bookings == []
|
||||
end
|
||||
|
|
@ -417,7 +437,17 @@ defmodule SpazioSolazzo.BookingSystem.BookingTest do
|
|||
|
||||
{:ok, _} = BookingSystem.approve_booking(booking2.id)
|
||||
|
||||
{:ok, bookings} = BookingSystem.list_accepted_space_bookings_by_date(space.id, date)
|
||||
start_datetime = DateTime.new!(date, ~T[00:00:00], "Etc/UTC")
|
||||
end_datetime = DateTime.new!(Date.add(date, 1), ~T[00:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, bookings} =
|
||||
BookingSystem.search_bookings(
|
||||
space.id,
|
||||
start_datetime,
|
||||
end_datetime,
|
||||
[:accepted],
|
||||
nil
|
||||
)
|
||||
|
||||
assert length(bookings) == 1
|
||||
assert hd(bookings).date == date
|
||||
|
|
@ -447,13 +477,23 @@ defmodule SpazioSolazzo.BookingSystem.BookingTest do
|
|||
|
||||
{:ok, _} = BookingSystem.approve_booking(booking.id)
|
||||
|
||||
{:ok, bookings} = BookingSystem.list_accepted_space_bookings_by_date(space.id, date)
|
||||
start_datetime = DateTime.new!(date, ~T[00:00:00], "Etc/UTC")
|
||||
end_datetime = DateTime.new!(Date.add(date, 1), ~T[00:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, bookings} =
|
||||
BookingSystem.search_bookings(
|
||||
space.id,
|
||||
start_datetime,
|
||||
end_datetime,
|
||||
[:accepted],
|
||||
nil
|
||||
)
|
||||
|
||||
assert bookings == []
|
||||
end
|
||||
end
|
||||
|
||||
describe "list_booking_requests/3" do
|
||||
describe "admin_search_bookings/3" do
|
||||
test "returns pending and approved bookings for space", %{space: space, date: date} do
|
||||
{:ok, pending} =
|
||||
request_booking(
|
||||
|
|
@ -498,7 +538,7 @@ defmodule SpazioSolazzo.BookingSystem.BookingTest do
|
|||
|
||||
{:ok, _} = BookingSystem.cancel_booking(cancelled.id, "Test cancellation")
|
||||
|
||||
{:ok, bookings} = BookingSystem.list_booking_requests(space.id, nil, nil)
|
||||
{:ok, bookings} = BookingSystem.admin_search_bookings(space.id, nil, nil)
|
||||
|
||||
assert length(bookings) == 2
|
||||
assert Enum.any?(bookings, &(&1.id == pending.id))
|
||||
|
|
@ -534,7 +574,7 @@ defmodule SpazioSolazzo.BookingSystem.BookingTest do
|
|||
)
|
||||
|
||||
{:ok, bookings} =
|
||||
BookingSystem.list_booking_requests(space.id, "user2@example.com", nil)
|
||||
BookingSystem.admin_search_bookings(space.id, "user2@example.com", nil)
|
||||
|
||||
assert length(bookings) == 1
|
||||
assert hd(bookings).id == booking2.id
|
||||
|
|
@ -569,14 +609,14 @@ defmodule SpazioSolazzo.BookingSystem.BookingTest do
|
|||
""
|
||||
)
|
||||
|
||||
{:ok, bookings} = BookingSystem.list_booking_requests(space.id, nil, date)
|
||||
{:ok, bookings} = BookingSystem.admin_search_bookings(space.id, nil, date)
|
||||
|
||||
assert length(bookings) == 1
|
||||
assert hd(bookings).id == booking1.id
|
||||
end
|
||||
end
|
||||
|
||||
describe "count_pending_requests/0" do
|
||||
describe "count pending requests" do
|
||||
test "returns only pending bookings", %{space: space, date: date} do
|
||||
{:ok, _pending1} =
|
||||
request_booking(
|
||||
|
|
@ -621,13 +661,15 @@ defmodule SpazioSolazzo.BookingSystem.BookingTest do
|
|||
|
||||
{:ok, _} = BookingSystem.cancel_booking(cancelled.id, "Test cancellation")
|
||||
|
||||
{:ok, pending_requests} = BookingSystem.count_pending_requests()
|
||||
{:ok, count} =
|
||||
Ash.count(SpazioSolazzo.BookingSystem.Booking,
|
||||
query: [filter: [state: :requested]]
|
||||
)
|
||||
|
||||
assert length(pending_requests) == 1
|
||||
assert Enum.all?(pending_requests, &(&1.state == :requested))
|
||||
assert count == 1
|
||||
end
|
||||
|
||||
test "returns empty list when no pending requests", %{space: space, date: date} do
|
||||
test "returns zero when no pending requests", %{space: space, date: date} do
|
||||
{:ok, booking} =
|
||||
request_booking(
|
||||
space.id,
|
||||
|
|
@ -643,9 +685,12 @@ defmodule SpazioSolazzo.BookingSystem.BookingTest do
|
|||
|
||||
{:ok, _} = BookingSystem.approve_booking(booking.id)
|
||||
|
||||
{:ok, pending_requests} = BookingSystem.count_pending_requests()
|
||||
{:ok, count} =
|
||||
Ash.count(SpazioSolazzo.BookingSystem.Booking,
|
||||
query: [filter: [state: :requested]]
|
||||
)
|
||||
|
||||
assert pending_requests == []
|
||||
assert count == 0
|
||||
end
|
||||
|
||||
test "counts pending requests across multiple spaces", %{space: space, date: date} do
|
||||
|
|
@ -683,9 +728,12 @@ defmodule SpazioSolazzo.BookingSystem.BookingTest do
|
|||
""
|
||||
)
|
||||
|
||||
{:ok, pending_requests} = BookingSystem.count_pending_requests()
|
||||
{:ok, count} =
|
||||
Ash.count(SpazioSolazzo.BookingSystem.Booking,
|
||||
query: [filter: [state: :requested]]
|
||||
)
|
||||
|
||||
assert length(pending_requests) == 2
|
||||
assert count == 2
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -59,8 +59,17 @@ defmodule SpazioSolazzoWeb.Admin.WalkInLiveSimpleTest do
|
|||
assert html =~ "Walk-in booking created successfully"
|
||||
|
||||
# Verify booking was created
|
||||
start_datetime = DateTime.new!(tomorrow, ~T[00:00:00], "Etc/UTC")
|
||||
end_datetime = DateTime.new!(Date.add(tomorrow, 1), ~T[00:00:00], "Etc/UTC")
|
||||
|
||||
assert {:ok, [_booking]} =
|
||||
BookingSystem.list_accepted_space_bookings_by_date(space.id, tomorrow)
|
||||
BookingSystem.search_bookings(
|
||||
space.id,
|
||||
start_datetime,
|
||||
end_datetime,
|
||||
[:accepted],
|
||||
nil
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -73,7 +73,18 @@ defmodule SpazioSolazzoWeb.Admin.WalkInLiveTest do
|
|||
assert html =~ "Walk-in booking created successfully"
|
||||
|
||||
# Verify booking was created
|
||||
{:ok, bookings} = BookingSystem.list_accepted_space_bookings_by_date(space.id, tomorrow)
|
||||
start_datetime = DateTime.new!(tomorrow, ~T[00:00:00], "Etc/UTC")
|
||||
end_datetime = DateTime.new!(Date.add(tomorrow, 1), ~T[00:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, bookings} =
|
||||
BookingSystem.search_bookings(
|
||||
space.id,
|
||||
start_datetime,
|
||||
end_datetime,
|
||||
[:accepted],
|
||||
nil
|
||||
)
|
||||
|
||||
assert length(bookings) == 1
|
||||
booking = hd(bookings)
|
||||
assert booking.customer_name == "John Doe"
|
||||
|
|
@ -174,19 +185,36 @@ defmodule SpazioSolazzoWeb.Admin.WalkInLiveTest do
|
|||
assert html =~ "Walk-in booking created successfully"
|
||||
|
||||
# Verify booking was created and spans multiple days
|
||||
{:ok, bookings} = BookingSystem.list_accepted_space_bookings_by_date(space.id, start_date)
|
||||
start_datetime = DateTime.new!(start_date, ~T[00:00:00], "Etc/UTC")
|
||||
end_datetime_search = DateTime.new!(Date.add(start_date, 1), ~T[00:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, bookings} =
|
||||
BookingSystem.search_bookings(
|
||||
space.id,
|
||||
start_datetime,
|
||||
end_datetime_search,
|
||||
[:accepted],
|
||||
nil
|
||||
)
|
||||
|
||||
assert length(bookings) == 1
|
||||
booking = hd(bookings)
|
||||
assert booking.customer_name == "Jane Smith"
|
||||
|
||||
# Verify booking appears on all days in the range
|
||||
day2_start = DateTime.new!(Date.add(start_date, 1), ~T[00:00:00], "Etc/UTC")
|
||||
day2_end = DateTime.new!(Date.add(start_date, 2), ~T[00:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, day2_bookings} =
|
||||
BookingSystem.list_accepted_space_bookings_by_date(space.id, Date.add(start_date, 1))
|
||||
BookingSystem.search_bookings(space.id, day2_start, day2_end, [:accepted], nil)
|
||||
|
||||
assert length(day2_bookings) == 1
|
||||
|
||||
day3_start = DateTime.new!(end_date, ~T[00:00:00], "Etc/UTC")
|
||||
day3_end = DateTime.new!(Date.add(end_date, 1), ~T[00:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, day3_bookings} =
|
||||
BookingSystem.list_accepted_space_bookings_by_date(space.id, end_date)
|
||||
BookingSystem.search_bookings(space.id, day3_start, day3_end, [:accepted], nil)
|
||||
|
||||
assert length(day3_bookings) == 1
|
||||
end
|
||||
|
|
@ -215,7 +243,18 @@ defmodule SpazioSolazzoWeb.Admin.WalkInLiveTest do
|
|||
|
||||
assert html =~ "Walk-in booking created successfully"
|
||||
|
||||
{:ok, bookings} = BookingSystem.list_accepted_space_bookings_by_date(space.id, tomorrow)
|
||||
start_datetime = DateTime.new!(tomorrow, ~T[00:00:00], "Etc/UTC")
|
||||
end_datetime = DateTime.new!(Date.add(tomorrow, 1), ~T[00:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, bookings} =
|
||||
BookingSystem.search_bookings(
|
||||
space.id,
|
||||
start_datetime,
|
||||
end_datetime,
|
||||
[:accepted],
|
||||
nil
|
||||
)
|
||||
|
||||
booking = hd(bookings)
|
||||
assert booking.customer_phone == "+39 1234567890"
|
||||
assert booking.customer_comment == "Special request"
|
||||
|
|
|
|||
|
|
@ -394,7 +394,7 @@ defmodule SpazioSolazzoWeb.BookingLive.SpaceBookingTest do
|
|||
# Process the handle_info message that creates the booking
|
||||
render(view)
|
||||
|
||||
{:ok, bookings} = BookingSystem.list_booking_requests(space.id, nil, today)
|
||||
{:ok, bookings} = BookingSystem.admin_search_bookings(space.id, nil, today)
|
||||
|
||||
assert length(bookings) == 1
|
||||
booking = hd(bookings)
|
||||
|
|
@ -578,7 +578,7 @@ defmodule SpazioSolazzoWeb.BookingLive.SpaceBookingTest do
|
|||
# Process the handle_info message that creates the booking
|
||||
render(view)
|
||||
|
||||
{:ok, bookings} = BookingSystem.list_booking_requests(space.id, nil, today)
|
||||
{:ok, bookings} = BookingSystem.admin_search_bookings(space.id, nil, today)
|
||||
|
||||
assert length(bookings) == 1
|
||||
booking = hd(bookings)
|
||||
|
|
|
|||
Loading…
Reference in a new issue