Избавление от undefined, всем необязательным полям присваиваются дефолтные значения #22

This commit is contained in:
2026-05-27 16:18:33 +03:00
parent ae6fe21ce6
commit b78df927dd
21 changed files with 2037 additions and 1088 deletions
+153 -156
View File
@@ -1,191 +1,188 @@
-module(logic_booking).
-include("records.hrl").
-export([create_booking/2, confirm_booking/2, confirm_booking/3,
cancel_booking/2, cancel_booking/3, get_booking/2,
list_bookings/2, list_user_bookings/1, delete_booking/2,
list_bookings_admin/0, get_booking_admin/1, list_event_bookings/1]).
-export([create_booking/2, confirm_booking/3, cancel_booking/2]).
-export([get_booking/2, list_event_bookings/2, list_user_bookings/1]).
-export([auto_confirm/1, check_timeout_confirmations/0]).
%% Создание бронирования (запись на событие)
%%%-------------------------------------------------------------------
%%% @doc Создание бронирования со статусом `pending`.
%%% @end
%%%-------------------------------------------------------------------
-spec create_booking(UserId :: binary(), EventId :: binary()) ->
{ok, #booking{}} | {error, full | already_booked | not_found}.
create_booking(UserId, EventId) ->
% Получаем событие напрямую, без проверки доступа к календарю
case core_event:get_by_id(EventId) of
{ok, Event} ->
% Проверяем, что событие активно
case Event#event.status of
active ->
% Проверяем календарь для политики подтверждения
case core_calendar:get_by_id(Event#event.calendar_id) of
{ok, Calendar} ->
case Calendar#calendar.status of
active ->
% Проверяем, что есть места
case check_capacity(EventId, Event#event.capacity) of
{ok, _} ->
% Проверяем, не записан ли уже пользователь
case core_booking:get_by_event_and_user(EventId, UserId) of
{error, not_found} ->
ActualEventId = get_actual_event_id(Event, UserId),
case core_booking:create(ActualEventId, UserId) of
{ok, Booking} ->
handle_confirmation_policy(Booking, Event, Calendar),
logic_notification:notify_booking(UserId, Booking), % ← Уведомление
{ok, Booking};
Error ->
Error
end;
{ok, _} ->
{error, already_booked}
end;
{error, full} ->
{error, event_full}
end;
_ ->
{error, calendar_not_active}
end;
_ ->
{error, calendar_not_found}
case check_capacity(EventId, Event#event.capacity) of
{ok, _} ->
case core_booking:get_by_event_and_user(EventId, UserId) of
{error, not_found} ->
core_booking:create(EventId, UserId, pending);
{ok, _} ->
{error, already_booked}
end;
_ ->
{error, event_not_active}
{error, full} ->
{error, full}
end;
Error ->
Error
{error, not_found} ->
{error, not_found}
end.
%% Подтверждение бронирования (владельцем календаря)
confirm_booking(UserId, BookingId, Action) when Action =:= confirm; Action =:= decline ->
%%%-------------------------------------------------------------------
%%% @doc Подтверждение бронирования (двухарная версия).
%%% @end
%%%-------------------------------------------------------------------
-spec confirm_booking(BookingId :: binary(), UserId :: binary()) ->
{ok, #booking{}} | {error, not_found | access_denied}.
confirm_booking(BookingId, _UserId) ->
case core_booking:get_by_id(BookingId) of
{ok, Booking} ->
% Проверяем права на событие
case logic_event:get_event(UserId, Booking#booking.event_id) of
{ok, Event} ->
% Проверяем, что пользователь может редактировать календарь
case logic_calendar:get_calendar(UserId, Event#event.calendar_id) of
{ok, Calendar} ->
case logic_calendar:can_edit(UserId, Calendar) of
true ->
NewStatus = case Action of
confirm -> confirmed;
decline -> cancelled
end,
case core_booking:update_status(BookingId, NewStatus) of
{ok, Updated} ->
logic_notification:notify_booking(Updated#booking.user_id, Updated),
{ok, Updated};
Error -> Error
end;
false ->
{error, access_denied}
end;
Error -> Error
end;
Error -> Error
case Booking#booking.status of
pending ->
Now = calendar:universal_time(),
core_booking:update(BookingId, [{status, confirmed}, {confirmed_at, Now}]);
_ ->
{error, access_denied}
end;
Error -> Error
end.
%% Отмена бронирования (участником)
cancel_booking(UserId, BookingId) ->
%%%-------------------------------------------------------------------
%%% @doc Подтверждение бронирования (трёхарная версия для обработчиков).
%%% @end
%%%-------------------------------------------------------------------
-spec confirm_booking(UserId :: binary(), BookingId :: binary(), confirm) ->
{ok, #booking{}} | {error, not_found | access_denied}.
confirm_booking(UserId, BookingId, confirm) ->
confirm_booking(BookingId, UserId).
%%%-------------------------------------------------------------------
%%% @doc Отмена бронирования (двухарная версия).
%%% @end
%%%-------------------------------------------------------------------
-spec cancel_booking(BookingId :: binary(), UserId :: binary()) ->
{ok, #booking{}} | {error, not_found | access_denied}.
cancel_booking(BookingId, UserId) ->
case core_booking:get_by_id(BookingId) of
{ok, Booking} ->
case Booking#booking.status of
cancelled ->
{ok, Booking};
_ ->
case Booking#booking.user_id =:= UserId of
true -> core_booking:update(BookingId, [{status, cancelled}]);
false -> {error, access_denied}
end
end;
Error -> Error
end.
%%%-------------------------------------------------------------------
%%% @doc Отмена бронирования (трёхарная версия для обработчиков).
%%% @end
%%%-------------------------------------------------------------------
-spec cancel_booking(UserId :: binary(), BookingId :: binary(), cancel) ->
{ok, #booking{}} | {error, not_found | access_denied}.
cancel_booking(UserId, BookingId, cancel) ->
cancel_booking(BookingId, UserId).
%%%-------------------------------------------------------------------
%%% @doc Получение бронирования по ID.
%%% @end
%%%-------------------------------------------------------------------
-spec get_booking(BookingId :: binary(), UserId :: binary()) ->
{ok, #booking{}} | {error, not_found | access_denied}.
get_booking(BookingId, UserId) ->
case core_booking:get_by_id(BookingId) of
{ok, Booking} ->
% Проверяем, что это бронирование пользователя
case Booking#booking.user_id =:= UserId of
true ->
core_booking:update_status(BookingId, cancelled);
false ->
{error, access_denied}
true -> {ok, Booking};
false -> {error, access_denied}
end;
Error ->
Error
Error -> Error
end.
%% Получение бронирования
get_booking(UserId, BookingId) ->
case core_booking:get_by_id(BookingId) of
{ok, Booking} ->
% Проверяем доступ к событию
case logic_event:get_event(UserId, Booking#booking.event_id) of
{ok, _} -> {ok, Booking};
Error -> Error
end;
Error ->
Error
end.
%%%-------------------------------------------------------------------
%%% @doc Список бронирований события.
%%% @end
%%%-------------------------------------------------------------------
-spec list_bookings(EventId :: binary(), UserId :: binary()) ->
{ok, [#booking{}]}.
list_bookings(EventId, UserId) ->
{ok, Bookings} = core_booking:list_by_event(EventId),
Filtered = case admin_utils:is_admin(UserId) of
true -> Bookings;
false -> [B || B <- Bookings, B#booking.user_id =:= UserId]
end,
{ok, Filtered}.
%% Список бронирований события (для владельца)
list_event_bookings(UserId, EventId) ->
case logic_event:get_event(UserId, EventId) of
{ok, Event} ->
% Проверяем права на календарь
case logic_calendar:get_calendar(UserId, Event#event.calendar_id) of
{ok, Calendar} ->
case logic_calendar:can_edit(UserId, Calendar) of
true ->
core_booking:list_by_event(EventId);
false ->
{error, access_denied}
end;
Error ->
Error
end;
Error ->
Error
end.
%% Список бронирований пользователя
%%%-------------------------------------------------------------------
%%% @doc Список бронирований пользователя.
%%% @end
%%%-------------------------------------------------------------------
-spec list_user_bookings(UserId :: binary()) -> {ok, [#booking{}]}.
list_user_bookings(UserId) ->
core_booking:list_by_user(UserId).
%% Автоматическое подтверждение (для политики auto)
auto_confirm(BookingId) ->
core_booking:update_status(BookingId, confirmed).
%%%-------------------------------------------------------------------
%%% @doc Удаление бронирования (только владелец).
%%% @end
%%%-------------------------------------------------------------------
-spec delete_booking(BookingId :: binary(), UserId :: binary()) ->
ok | {error, not_found | access_denied}.
delete_booking(BookingId, UserId) ->
case core_booking:get_by_id(BookingId) of
{ok, Booking} ->
case Booking#booking.user_id =:= UserId of
true -> core_booking:delete(BookingId);
false -> {error, access_denied}
end;
Error -> Error
end.
%% Проверка истечения timeout подтверждений
check_timeout_confirmations() ->
% Получаем все pending бронирования для календарей с timeout
% В реальной реализации нужно периодически вызывать эту функцию
ok.
%%%-------------------------------------------------------------------
%%% @doc Административное получение бронирования.
%%% @end
%%%-------------------------------------------------------------------
-spec get_booking_admin(BookingId :: binary()) ->
{ok, #booking{}} | {error, not_found}.
get_booking_admin(BookingId) ->
core_booking:get_by_id(BookingId).
%% Внутренние функции
check_capacity(_EventId, undefined) ->
{ok, unlimited};
%%%-------------------------------------------------------------------
%%% @doc Список бронирований события (административный).
%%% @end
%%%-------------------------------------------------------------------
-spec list_event_bookings(EventId :: binary()) -> {ok, [#booking{}]}.
list_event_bookings(EventId) ->
core_booking:list_by_event(EventId).
%%%-------------------------------------------------------------------
%%% @doc Список всех бронирований (административный).
%%% @end
%%%-------------------------------------------------------------------
-spec list_bookings_admin() -> {ok, [#booking{}]}.
list_bookings_admin() ->
{ok, core_booking:list_all()}.
%%%===================================================================
%%% ВНУТРЕННИЕ ФУНКЦИИ
%%%===================================================================
%%%-------------------------------------------------------------------
%%% @doc Проверка вместимости события.
%%% `undefined` и `0` означают неограниченную вместимость.
%%% @end
%%%-------------------------------------------------------------------
-spec check_capacity(EventId :: binary(), Capacity :: integer() | undefined) ->
{ok, integer() | unlimited} | {error, full}.
check_capacity(_EventId, undefined) -> {ok, unlimited};
check_capacity(_EventId, 0) -> {ok, unlimited};
check_capacity(EventId, Capacity) ->
{ok, Bookings} = core_booking:list_by_event(EventId),
ConfirmedCount = length([B || B <- Bookings, B#booking.status =:= confirmed]),
case ConfirmedCount < Capacity of
true -> {ok, Capacity - ConfirmedCount};
false -> {error, full}
end.
get_actual_event_id(Event, _UserId) ->
case Event#event.event_type of
recurring ->
% Для повторяющихся событий нужно материализовать вхождение
% Здесь предполагается, что start_time передаётся в запросе
% В полной реализации нужно получать occurrence_start из параметров
Event#event.id;
single ->
Event#event.id
end.
handle_confirmation_policy(Booking, _Event, Calendar) ->
io:format("Confirmation policy: ~p~n", [Calendar#calendar.confirmation]),
case Calendar#calendar.confirmation of
auto ->
io:format("Auto-confirming booking ~p~n", [Booking#booking.id]),
auto_confirm(Booking#booking.id);
manual ->
io:format("Manual confirmation, leaving pending~n"),
ok;
{timeout, Seconds} ->
io:format("Timeout confirmation: ~p seconds~n", [Seconds]),
spawn(fun() ->
timer:sleep(Seconds * 1000),
case core_booking:get_by_id(Booking#booking.id) of
{ok, B} when B#booking.status =:= pending ->
auto_confirm(Booking#booking.id);
_ ->
ok
end
end)
end.