Избавление от undefined, всем необязательным полям присваиваются дефолтные значения #22
This commit is contained in:
+214
-116
@@ -1,186 +1,247 @@
|
||||
-module(core_event).
|
||||
-include("records.hrl").
|
||||
|
||||
-export([create/4, create_recurring/5, get_by_id/1, list_by_calendar/1,
|
||||
update/2, delete/1, materialize_occurrence/3]).
|
||||
-export([create/4, create_recurring/5, get_by_id/1, list_by_calendar/1, update/2, delete/1,
|
||||
materialize_occurrence/3]).
|
||||
-export([count_events/0, count_events_by_date/2]).
|
||||
-export([freeze/2, unfreeze/2]).
|
||||
-export([list_all/0]).
|
||||
|
||||
%% Создание одиночного события
|
||||
%% ─────────────────────────────────────────────────────────────────
|
||||
%% Значения по умолчанию для необязательных полей
|
||||
%% ─────────────────────────────────────────────────────────────────
|
||||
-define(DEFAULT_RECURRENCE_RULE, <<>>).
|
||||
-define(DEFAULT_MASTER_ID, <<>>).
|
||||
-define(DEFAULT_SPECIALIST_ID, <<>>).
|
||||
-define(DEFAULT_LOCATION, #location{address = <<>>, lat = 0.0, lon = 0.0}).
|
||||
-define(DEFAULT_TAGS, []).
|
||||
-define(DEFAULT_CAPACITY, 0).
|
||||
-define(DEFAULT_ONLINE_LINK, <<>>).
|
||||
-define(DEFAULT_REASON, <<>>).
|
||||
-define(DEFAULT_ATTACHMENTS, []).
|
||||
-define(DEFAULT_EDIT_HISTORY, []).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Создание одиночного события.
|
||||
%%% Все необязательные поля инициализируются значениями по умолчанию.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec create(CalendarId :: binary(), Title :: binary(),
|
||||
StartTime :: calendar:datetime(), Duration :: non_neg_integer()) ->
|
||||
{ok, #event{}} | {error, term()}.
|
||||
create(CalendarId, Title, StartTime, Duration) ->
|
||||
Id = infra_utils:generate_id(16),
|
||||
Now = calendar:universal_time(),
|
||||
Event = #event{
|
||||
id = Id,
|
||||
calendar_id = CalendarId,
|
||||
title = Title,
|
||||
description = <<"">>,
|
||||
event_type = single,
|
||||
start_time = StartTime,
|
||||
duration = Duration,
|
||||
recurrence_rule = undefined,
|
||||
master_id = undefined,
|
||||
is_instance = false,
|
||||
specialist_id = undefined,
|
||||
location = undefined,
|
||||
tags = [],
|
||||
capacity = undefined,
|
||||
online_link = undefined,
|
||||
status = active,
|
||||
rating_avg = 0.0,
|
||||
rating_count = 0,
|
||||
created_at = calendar:universal_time(),
|
||||
updated_at = calendar:universal_time()
|
||||
id = Id,
|
||||
calendar_id = CalendarId,
|
||||
title = Title,
|
||||
description = <<"">>,
|
||||
event_type = single,
|
||||
start_time = StartTime,
|
||||
duration = Duration,
|
||||
recurrence_rule = ?DEFAULT_RECURRENCE_RULE,
|
||||
master_id = ?DEFAULT_MASTER_ID,
|
||||
is_instance = false,
|
||||
specialist_id = ?DEFAULT_SPECIALIST_ID,
|
||||
location = ?DEFAULT_LOCATION,
|
||||
tags = ?DEFAULT_TAGS,
|
||||
capacity = ?DEFAULT_CAPACITY,
|
||||
online_link = ?DEFAULT_ONLINE_LINK,
|
||||
status = active,
|
||||
reason = ?DEFAULT_REASON,
|
||||
rating_avg = 0.0,
|
||||
rating_count = 0,
|
||||
attachments = ?DEFAULT_ATTACHMENTS,
|
||||
edit_history = ?DEFAULT_EDIT_HISTORY,
|
||||
created_at = Now,
|
||||
updated_at = Now
|
||||
},
|
||||
|
||||
F = fun() ->
|
||||
mnesia:write(Event),
|
||||
{ok, Event}
|
||||
end,
|
||||
|
||||
F = fun() -> mnesia:write(Event), {ok, Event} end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, Result} -> Result;
|
||||
{aborted, Reason} -> {error, Reason}
|
||||
end.
|
||||
|
||||
%% Создание повторяющегося события (мастер-запись)
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Создание повторяющегося события (мастер-запись).
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec create_recurring(CalendarId :: binary(), Title :: binary(),
|
||||
StartTime :: calendar:datetime(), Duration :: non_neg_integer(),
|
||||
RRule :: map()) ->
|
||||
{ok, #event{}} | {error, term()}.
|
||||
create_recurring(CalendarId, Title, StartTime, Duration, RRule) ->
|
||||
Id = infra_utils:generate_id(16),
|
||||
Now = calendar:universal_time(),
|
||||
Event = #event{
|
||||
id = Id,
|
||||
calendar_id = CalendarId,
|
||||
title = Title,
|
||||
description = <<"">>,
|
||||
event_type = recurring,
|
||||
start_time = StartTime,
|
||||
duration = Duration,
|
||||
recurrence_rule = jsx:encode(RRule),
|
||||
master_id = undefined,
|
||||
is_instance = false,
|
||||
specialist_id = undefined,
|
||||
location = undefined,
|
||||
tags = [],
|
||||
capacity = undefined,
|
||||
online_link = undefined,
|
||||
status = active,
|
||||
rating_avg = 0.0,
|
||||
rating_count = 0,
|
||||
created_at = calendar:universal_time(),
|
||||
updated_at = calendar:universal_time()
|
||||
id = Id,
|
||||
calendar_id = CalendarId,
|
||||
title = Title,
|
||||
description = <<"">>,
|
||||
event_type = recurring,
|
||||
start_time = StartTime,
|
||||
duration = Duration,
|
||||
recurrence_rule = jsx:encode(RRule),
|
||||
master_id = ?DEFAULT_MASTER_ID,
|
||||
is_instance = false,
|
||||
specialist_id = ?DEFAULT_SPECIALIST_ID,
|
||||
location = ?DEFAULT_LOCATION,
|
||||
tags = ?DEFAULT_TAGS,
|
||||
capacity = ?DEFAULT_CAPACITY,
|
||||
online_link = ?DEFAULT_ONLINE_LINK,
|
||||
status = active,
|
||||
reason = ?DEFAULT_REASON,
|
||||
rating_avg = 0.0,
|
||||
rating_count = 0,
|
||||
attachments = ?DEFAULT_ATTACHMENTS,
|
||||
edit_history = ?DEFAULT_EDIT_HISTORY,
|
||||
created_at = Now,
|
||||
updated_at = Now
|
||||
},
|
||||
|
||||
F = fun() ->
|
||||
mnesia:write(Event),
|
||||
{ok, Event}
|
||||
end,
|
||||
|
||||
F = fun() -> mnesia:write(Event), {ok, Event} end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, Result} -> Result;
|
||||
{aborted, Reason} -> {error, Reason}
|
||||
end.
|
||||
|
||||
%% Материализация вхождения повторяющегося события
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Материализация вхождения повторяющегося события.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec materialize_occurrence(MasterId :: binary(),
|
||||
OccurrenceStart :: calendar:datetime(),
|
||||
SpecialistId :: binary()) ->
|
||||
{ok, #event{}} | {error, term()}.
|
||||
materialize_occurrence(MasterId, OccurrenceStart, SpecialistId) ->
|
||||
case mnesia:dirty_read(event, MasterId) of
|
||||
[] ->
|
||||
{error, master_not_found};
|
||||
[Master] when Master#event.event_type =:= recurring ->
|
||||
% Проверяем, не существует ли уже материализованное вхождение
|
||||
Existing = mnesia:dirty_match_object(
|
||||
#event{master_id = MasterId, start_time = OccurrenceStart, is_instance = true, _ = '_'}
|
||||
#event{master_id = MasterId, start_time = OccurrenceStart,
|
||||
is_instance = true, _ = '_'}
|
||||
),
|
||||
|
||||
case Existing of
|
||||
[] ->
|
||||
% Создаём новый экземпляр
|
||||
InstanceId = infra_utils:generate_id(16),
|
||||
Now = calendar:universal_time(),
|
||||
Instance = #event{
|
||||
id = InstanceId,
|
||||
calendar_id = Master#event.calendar_id,
|
||||
title = Master#event.title,
|
||||
description = Master#event.description,
|
||||
event_type = single,
|
||||
start_time = OccurrenceStart,
|
||||
duration = Master#event.duration,
|
||||
recurrence_rule = undefined,
|
||||
master_id = MasterId,
|
||||
is_instance = true,
|
||||
specialist_id = SpecialistId,
|
||||
location = Master#event.location,
|
||||
tags = Master#event.tags,
|
||||
capacity = Master#event.capacity,
|
||||
online_link = Master#event.online_link,
|
||||
status = active,
|
||||
rating_avg = 0.0,
|
||||
rating_count = 0,
|
||||
created_at = calendar:universal_time(),
|
||||
updated_at = calendar:universal_time()
|
||||
id = InstanceId,
|
||||
calendar_id = Master#event.calendar_id,
|
||||
title = Master#event.title,
|
||||
description = Master#event.description,
|
||||
event_type = single,
|
||||
start_time = OccurrenceStart,
|
||||
duration = Master#event.duration,
|
||||
recurrence_rule = ?DEFAULT_RECURRENCE_RULE,
|
||||
master_id = MasterId,
|
||||
is_instance = true,
|
||||
specialist_id = SpecialistId,
|
||||
location = Master#event.location,
|
||||
tags = Master#event.tags,
|
||||
capacity = Master#event.capacity,
|
||||
online_link = Master#event.online_link,
|
||||
status = active,
|
||||
reason = ?DEFAULT_REASON,
|
||||
rating_avg = 0.0,
|
||||
rating_count = 0,
|
||||
attachments = ?DEFAULT_ATTACHMENTS,
|
||||
edit_history = ?DEFAULT_EDIT_HISTORY,
|
||||
created_at = Now,
|
||||
updated_at = Now
|
||||
},
|
||||
|
||||
F = fun() ->
|
||||
mnesia:write(Instance),
|
||||
{ok, Instance}
|
||||
end,
|
||||
|
||||
F = fun() -> mnesia:write(Instance), {ok, Instance} end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, Result} -> Result;
|
||||
{aborted, Reason} -> {error, Reason}
|
||||
end;
|
||||
[Instance] ->
|
||||
% Уже существует, возвращаем его
|
||||
{ok, Instance}
|
||||
end;
|
||||
[_] ->
|
||||
{error, not_recurring}
|
||||
end.
|
||||
|
||||
%% Получение события по ID
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Получить событие по идентификатору.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec get_by_id(Id :: binary()) -> {ok, #event{}} | {error, not_found}.
|
||||
get_by_id(Id) ->
|
||||
case mnesia:dirty_read(event, Id) of
|
||||
[] -> {error, not_found};
|
||||
[Event] -> {ok, Event}
|
||||
end.
|
||||
|
||||
%% Список событий календаря (только мастер-записи и одиночные)
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Список активных событий календаря.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec list_by_calendar(CalendarId :: binary()) -> {ok, [#event{}]}.
|
||||
list_by_calendar(CalendarId) ->
|
||||
Match = #event{calendar_id = CalendarId, status = active, is_instance = false, _ = '_'},
|
||||
Events = mnesia:dirty_match_object(Match),
|
||||
{ok, Events}.
|
||||
|
||||
%% Обновление события
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Обновить поля события.
|
||||
%%% `Updates` – список пар `[{atom(), term()}]`.
|
||||
%%% Поля, не указанные в обновлении, сохраняют прежние значения.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec update(Id :: binary(), Updates :: [{atom(), term()}]) ->
|
||||
{ok, #event{}} | {error, not_found | term()}.
|
||||
update(Id, Updates) ->
|
||||
F = fun() ->
|
||||
case mnesia:read(event, Id) of
|
||||
[] ->
|
||||
{error, not_found};
|
||||
[] -> {error, not_found};
|
||||
[Event] ->
|
||||
UpdatedEvent = apply_updates(Event, Updates),
|
||||
mnesia:write(UpdatedEvent),
|
||||
{ok, UpdatedEvent}
|
||||
end
|
||||
end,
|
||||
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, Result} -> Result;
|
||||
{aborted, Reason} -> {error, Reason}
|
||||
end.
|
||||
|
||||
%% Удаление события (soft delete)
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Мягкое удаление события (установка статуса `deleted`).
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec delete(Id :: binary()) -> {ok, #event{}} | {error, not_found | term()}.
|
||||
delete(Id) ->
|
||||
update(Id, [{status, deleted}]).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Количество записей в таблице `event`.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec count_events() -> non_neg_integer().
|
||||
count_events() ->
|
||||
mnesia:table_info(event, size).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Получить список всех активных мастер-событий.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec list_all() -> [#event{}].
|
||||
list_all() ->
|
||||
Match = #event{status = active, is_instance = false, _ = '_'},
|
||||
mnesia:dirty_match_object(Match).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Подсчёт событий, созданных в заданном временном диапазоне.
|
||||
%%% Возвращает список кортежей `{{Year,Month,Day}, Count}`, отсортированный по дате.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec count_events_by_date(From :: calendar:datetime(), To :: calendar:datetime()) ->
|
||||
[{{pos_integer(), pos_integer(), pos_integer()}, non_neg_integer()}].
|
||||
count_events_by_date(From, To) ->
|
||||
All = mnesia:dirty_match_object(#event{_ = '_'}),
|
||||
Filtered = lists:filter(fun(E) ->
|
||||
E#event.created_at >= From andalso E#event.created_at =< To
|
||||
end, All),
|
||||
Filtered = lists:filter(fun(E) -> E#event.created_at >= From andalso
|
||||
E#event.created_at =< To end, All),
|
||||
Counts = lists:foldl(fun(E, Acc) ->
|
||||
Day = date_part(E#event.created_at),
|
||||
case lists:keyfind(Day, 1, Acc) of
|
||||
@@ -190,32 +251,69 @@ count_events_by_date(From, To) ->
|
||||
end, [], Filtered),
|
||||
lists:sort(Counts).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Выделить из datetime только дату `{Y,M,D}`.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec date_part(calendar:datetime()) -> {pos_integer(), pos_integer(), pos_integer()}.
|
||||
date_part({{Y,M,D}, _}) -> {Y,M,D}.
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Применить список обновлений к записи события.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec apply_updates(#event{}, [{atom(), term()}]) -> #event{}.
|
||||
apply_updates(Event, Updates) ->
|
||||
Updated = lists:foldl(fun({Field, Value}, E) ->
|
||||
set_field(Field, Value, E)
|
||||
end, Event, Updates),
|
||||
Updated = lists:foldl(fun({Field, Value}, E) -> set_field(Field, Value, E) end,
|
||||
Event, Updates),
|
||||
Updated#event{updated_at = calendar:universal_time()}.
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Заморозить событие с указанием причины.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec freeze(Id :: binary(), Reason :: binary()) ->
|
||||
{ok, #event{}} | {error, not_found | term()}.
|
||||
freeze(Id, Reason) ->
|
||||
update(Id, [{status, frozen}, {reason, Reason}]).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Разморозить событие с указанием причины.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec unfreeze(Id :: binary(), Reason :: binary()) ->
|
||||
{ok, #event{}} | {error, not_found | term()}.
|
||||
unfreeze(Id, Reason) ->
|
||||
update(Id, [{status, active}, {reason, Reason}]).
|
||||
|
||||
%% ── ОБНОВЛЁННАЯ set_field ──────────────────────────────────
|
||||
set_field(title, Value, E) -> E#event{title = Value};
|
||||
set_field(description, Value, E) -> E#event{description = Value};
|
||||
set_field(start_time, Value, E) -> E#event{start_time = Value};
|
||||
set_field(duration, Value, E) -> E#event{duration = Value};
|
||||
set_field(specialist_id, Value, E) -> E#event{specialist_id = Value};
|
||||
set_field(location, Value, E) -> E#event{location = Value};
|
||||
set_field(tags, Value, E) -> E#event{tags = Value};
|
||||
set_field(capacity, Value, E) -> E#event{capacity = Value};
|
||||
set_field(online_link, Value, E) -> E#event{online_link = Value};
|
||||
set_field(status, Value, E) -> E#event{status = Value};
|
||||
set_field(reason, Value, E) -> E#event{reason = Value};
|
||||
set_field(rating_avg, Value, E) -> E#event{rating_avg = Value};
|
||||
set_field(rating_count, Value, E) -> E#event{rating_count = Value};
|
||||
%%%===================================================================
|
||||
%%% ВНУТРЕННИЕ ФУНКЦИИ
|
||||
%%%===================================================================
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Установить одно поле записи события.
|
||||
%%% Неизвестные поля игнорируются.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec set_field(atom(), term(), #event{}) -> #event{}.
|
||||
set_field(title, Value, E) -> E#event{title = Value};
|
||||
set_field(description, Value, E) -> E#event{description = Value};
|
||||
set_field(start_time, Value, E) -> E#event{start_time = Value};
|
||||
set_field(duration, Value, E) -> E#event{duration = Value};
|
||||
set_field(specialist_id, Value, E) -> E#event{specialist_id = Value};
|
||||
set_field(location, Value, E) -> E#event{location = Value};
|
||||
set_field(tags, Value, E) -> E#event{tags = Value};
|
||||
set_field(capacity, Value, E) -> E#event{capacity = Value};
|
||||
set_field(online_link, Value, E) -> E#event{online_link = Value};
|
||||
set_field(status, Value, E) -> E#event{status = Value};
|
||||
set_field(reason, Value, E) -> E#event{reason = Value};
|
||||
set_field(rating_avg, Value, E) -> E#event{rating_avg = Value};
|
||||
set_field(rating_count, Value, E) -> E#event{rating_count = Value};
|
||||
set_field(attachments, Value, E) -> E#event{attachments = Value};
|
||||
set_field(edit_history, Value, E) -> E#event{edit_history = Value};
|
||||
set_field(recurrence_rule, Value, E) -> E#event{recurrence_rule = Value};
|
||||
set_field(master_id, Value, E) -> E#event{master_id = Value};
|
||||
set_field(is_instance, Value, E) -> E#event{is_instance = Value};
|
||||
set_field(event_type, Value, E) -> E#event{event_type = Value};
|
||||
set_field(calendar_id, Value, E) -> E#event{calendar_id = Value};
|
||||
set_field(_, _, E) -> E.
|
||||
Reference in New Issue
Block a user