Расширена статистика для разделов #22
This commit is contained in:
@@ -4,6 +4,9 @@
|
||||
-export([count_calendars/0, list_all/0]).
|
||||
-export([freeze/2, unfreeze/2]).
|
||||
-export([count_calendars_by_date/2]).
|
||||
-export([count_calendars_by_type/0, count_calendars_by_status/0,
|
||||
get_top_calendars_by_reviews/1, get_top_calendars_by_positive_reviews/1,
|
||||
get_top_calendars_by_negative_reviews/1, get_top_calendars_by_rating/1]).
|
||||
|
||||
%% ─────────────────────────────────────────────────────────────────
|
||||
%% Значения по умолчанию для необязательных полей
|
||||
@@ -174,6 +177,92 @@ count_calendars_by_date(From, To) ->
|
||||
|
||||
date_part({{Y,M,D}, _}) -> {Y,M,D}.
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Подсчёт календарей по типам (personal, commercial).
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec count_calendars_by_type() -> [{atom(), non_neg_integer()}].
|
||||
count_calendars_by_type() ->
|
||||
Calendars = mnesia:dirty_match_object(#calendar{_ = '_'}),
|
||||
lists:foldl(fun(#calendar{type = Type}, Acc) ->
|
||||
case lists:keyfind(Type, 1, Acc) of
|
||||
false -> [{Type, 1} | Acc];
|
||||
{Type, C} -> lists:keyreplace(Type, 1, Acc, {Type, C+1})
|
||||
end
|
||||
end, [], Calendars).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Подсчёт календарей по статусам (active, frozen, deleted).
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec count_calendars_by_status() -> [{atom(), non_neg_integer()}].
|
||||
count_calendars_by_status() ->
|
||||
Calendars = mnesia:dirty_match_object(#calendar{_ = '_'}),
|
||||
lists:foldl(fun(#calendar{status = Status}, Acc) ->
|
||||
case lists:keyfind(Status, 1, Acc) of
|
||||
false -> [{Status, 1} | Acc];
|
||||
{Status, C} -> lists:keyreplace(Status, 1, Acc, {Status, C+1})
|
||||
end
|
||||
end, [], Calendars).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Топ-N календарей по количеству отзывов.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec get_top_calendars_by_reviews(N :: non_neg_integer()) -> [#calendar{}].
|
||||
get_top_calendars_by_reviews(N) ->
|
||||
Calendars = mnesia:dirty_match_object(#calendar{_ = '_'}),
|
||||
Sorted = lists:reverse(lists:sort(
|
||||
fun(A, B) -> A#calendar.rating_count =< B#calendar.rating_count end,
|
||||
Calendars)),
|
||||
lists:sublist(Sorted, N).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Топ-N календарей по количеству позитивных отзывов (rating >= 4).
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec get_top_calendars_by_positive_reviews(N :: non_neg_integer()) -> [#calendar{}].
|
||||
get_top_calendars_by_positive_reviews(N) ->
|
||||
top_by_review_filter(N, fun(R) -> R#review.rating >= 4 end).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Топ-N календарей по количеству негативных отзывов (rating <= 2).
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec get_top_calendars_by_negative_reviews(N :: non_neg_integer()) -> [#calendar{}].
|
||||
get_top_calendars_by_negative_reviews(N) ->
|
||||
top_by_review_filter(N, fun(R) -> R#review.rating =< 2 end).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Топ-N календарей по среднему рейтингу.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec get_top_calendars_by_rating(N :: non_neg_integer()) -> [#calendar{}].
|
||||
get_top_calendars_by_rating(N) ->
|
||||
Calendars = mnesia:dirty_match_object(#calendar{_ = '_'}),
|
||||
Sorted = lists:reverse(lists:sort(
|
||||
fun(A, B) -> A#calendar.rating_avg =< B#calendar.rating_avg end,
|
||||
Calendars)),
|
||||
lists:sublist(Sorted, N).
|
||||
|
||||
%% @private Вспомогательная функция для фильтрации отзывов и подсчёта по календарям.
|
||||
top_by_review_filter(N, FilterFun) ->
|
||||
Reviews = mnesia:dirty_match_object(#review{target_type = calendar, _ = '_'}),
|
||||
Filtered = lists:filter(FilterFun, Reviews),
|
||||
% Считаем количество отзывов для каждого календаря
|
||||
Counts = lists:foldl(fun(#review{target_id = CalId}, Acc) ->
|
||||
case lists:keyfind(CalId, 1, Acc) of
|
||||
false -> [{CalId, 1} | Acc];
|
||||
{CalId, C} -> lists:keyreplace(CalId, 1, Acc, {CalId, C+1})
|
||||
end
|
||||
end, [], Filtered),
|
||||
% Сортируем по убыванию количества
|
||||
SortedIds = [Id || {Id, _} <- lists:reverse(lists:sort(fun({_,C1},{_,C2}) -> C1 =< C2 end, Counts))],
|
||||
TopIds = lists:sublist(SortedIds, N),
|
||||
% Получаем объекты календарей
|
||||
[Cal || Id <- TopIds,
|
||||
{ok, Cal} <- [core_calendar:get_by_id(Id)]].
|
||||
|
||||
%%%===================================================================
|
||||
%%% ВНУТРЕННИЕ ФУНКЦИИ
|
||||
%%%===================================================================
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
-export([count_events/0, count_events_by_date/2]).
|
||||
-export([freeze/2, unfreeze/2]).
|
||||
-export([list_all/0]).
|
||||
-export([count_events_by_type/0, count_events_by_status/0, get_top_events_by_rating/1]).
|
||||
|
||||
%% ─────────────────────────────────────────────────────────────────
|
||||
%% Значения по умолчанию для необязательных полей
|
||||
@@ -251,6 +252,46 @@ count_events_by_date(From, To) ->
|
||||
end, [], Filtered),
|
||||
lists:sort(Counts).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Подсчёт событий по типам (single, recurring).
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec count_events_by_type() -> [{atom(), non_neg_integer()}].
|
||||
count_events_by_type() ->
|
||||
Events = mnesia:dirty_match_object(#event{_ = '_'}),
|
||||
lists:foldl(fun(#event{event_type = Type}, Acc) ->
|
||||
case lists:keyfind(Type, 1, Acc) of
|
||||
false -> [{Type, 1} | Acc];
|
||||
{Type, C} -> lists:keyreplace(Type, 1, Acc, {Type, C+1})
|
||||
end
|
||||
end, [], Events).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Подсчёт событий по статусам (active, cancelled, completed).
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec count_events_by_status() -> [{atom(), non_neg_integer()}].
|
||||
count_events_by_status() ->
|
||||
Events = mnesia:dirty_match_object(#event{_ = '_'}),
|
||||
lists:foldl(fun(#event{status = Status}, Acc) ->
|
||||
case lists:keyfind(Status, 1, Acc) of
|
||||
false -> [{Status, 1} | Acc];
|
||||
{Status, C} -> lists:keyreplace(Status, 1, Acc, {Status, C+1})
|
||||
end
|
||||
end, [], Events).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Возвращает топ-N событий по среднему рейтингу (по убыванию).
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec get_top_events_by_rating(N :: non_neg_integer()) -> [#event{}].
|
||||
get_top_events_by_rating(N) ->
|
||||
Events = mnesia:dirty_match_object(#event{_ = '_'}),
|
||||
Sorted = lists:reverse(lists:sort(
|
||||
fun(A, B) -> A#event.rating_avg =< B#event.rating_avg end,
|
||||
Events)),
|
||||
lists:sublist(Sorted, N).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Выделить из datetime только дату `{Y,M,D}`.
|
||||
%%% @end
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
-export([create/4, get_by_id/1, list_all/0, list_by_reporter/1, update_status/3, count_reports/0]).
|
||||
-export([list_by_target/2, get_count_by_target/2]).
|
||||
-export([count_reports_by_status/1, count_reports_resolved_by_admin/2, avg_resolution_time/1]).
|
||||
-export([count_reports_by_date/2]).
|
||||
-export([count_reports_by_date/2, get_top_targets_by_reports/1, count_reports_by_status/0, count_reports_by_target_type/0]).
|
||||
|
||||
%% ─────────────────────────────────────────────────────────────────
|
||||
%% Значения по умолчанию для необязательных полей
|
||||
@@ -178,4 +178,55 @@ count_reports_by_date(From, To) ->
|
||||
end, [], Filtered),
|
||||
lists:sort(Counts).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Подсчёт жалоб по типам цели (event, calendar, review).
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec count_reports_by_target_type() -> [{atom(), non_neg_integer()}].
|
||||
count_reports_by_target_type() ->
|
||||
Reports = mnesia:dirty_match_object(#report{_ = '_'}),
|
||||
lists:foldl(fun(#report{target_type = Type}, Acc) ->
|
||||
case lists:keyfind(Type, 1, Acc) of
|
||||
false -> [{Type, 1} | Acc];
|
||||
{Type, C} -> lists:keyreplace(Type, 1, Acc, {Type, C+1})
|
||||
end
|
||||
end, [], Reports).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Подсчёт жалоб по статусам.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec count_reports_by_status() -> [{atom(), non_neg_integer()}].
|
||||
count_reports_by_status() ->
|
||||
Reports = mnesia:dirty_match_object(#report{_ = '_'}),
|
||||
lists:foldl(fun(#report{status = Status}, Acc) ->
|
||||
case lists:keyfind(Status, 1, Acc) of
|
||||
false -> [{Status, 1} | Acc];
|
||||
{Status, C} -> lists:keyreplace(Status, 1, Acc, {Status, C+1})
|
||||
end
|
||||
end, [], Reports).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Топ-N целей по количеству жалоб.
|
||||
%%% Возвращает список [{TargetType, TargetId, Count}], отсортированный по убыванию Count.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec get_top_targets_by_reports(N :: non_neg_integer()) ->
|
||||
[{TargetType :: atom(), TargetId :: binary(), Count :: non_neg_integer()}].
|
||||
get_top_targets_by_reports(N) ->
|
||||
Reports = mnesia:dirty_match_object(#report{_ = '_'}),
|
||||
% Группируем по {TargetType, TargetId}
|
||||
Dict = lists:foldl(fun(#report{target_type = Type, target_id = Id}, Acc) ->
|
||||
Key = {Type, Id},
|
||||
case lists:keyfind(Key, 1, Acc) of
|
||||
false -> [{Key, 1} | Acc];
|
||||
{Key, C} -> lists:keyreplace(Key, 1, Acc, {Key, C+1})
|
||||
end
|
||||
end, [], Reports),
|
||||
% Сортируем по убыванию Count
|
||||
Sorted = lists:reverse(lists:sort(fun({_, C1}, {_, C2}) -> C1 =< C2 end, Dict)),
|
||||
% Преобразуем в тройки и берём первые N
|
||||
Top = lists:sublist([{Type, Id, Count} || {{Type, Id}, Count} <- Sorted], N),
|
||||
Top.
|
||||
|
||||
date_part({{Y,M,D}, _}) -> {Y,M,D}.
|
||||
@@ -5,6 +5,9 @@
|
||||
-export([get_average_rating/2, has_user_reviewed/3]).
|
||||
-export([count_reviews/0, list_all/0]).
|
||||
-export([count_reviews_by_date/2]).
|
||||
-export([count_reviews_by_target_type/0, count_reviews_by_status/0,
|
||||
get_top_targets_by_reviews/1, get_top_targets_by_positive_reviews/1,
|
||||
get_top_targets_by_negative_reviews/1]).
|
||||
|
||||
%% ─────────────────────────────────────────────────────────────────
|
||||
%% Значения по умолчанию для необязательных полей
|
||||
@@ -204,6 +207,75 @@ count_reviews_by_date(From, To) ->
|
||||
|
||||
date_part({{Y,M,D}, _}) -> {Y,M,D}.
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Подсчёт отзывов по типам цели (event, calendar).
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec count_reviews_by_target_type() -> [{atom(), non_neg_integer()}].
|
||||
count_reviews_by_target_type() ->
|
||||
Reviews = mnesia:dirty_match_object(#review{_ = '_'}),
|
||||
lists:foldl(fun(#review{target_type = Type}, Acc) ->
|
||||
case lists:keyfind(Type, 1, Acc) of
|
||||
false -> [{Type, 1} | Acc];
|
||||
{Type, C} -> lists:keyreplace(Type, 1, Acc, {Type, C+1})
|
||||
end
|
||||
end, [], Reviews).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Подсчёт отзывов по статусам (visible, hidden, deleted).
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec count_reviews_by_status() -> [{atom(), non_neg_integer()}].
|
||||
count_reviews_by_status() ->
|
||||
Reviews = mnesia:dirty_match_object(#review{_ = '_'}),
|
||||
lists:foldl(fun(#review{status = Status}, Acc) ->
|
||||
case lists:keyfind(Status, 1, Acc) of
|
||||
false -> [{Status, 1} | Acc];
|
||||
{Status, C} -> lists:keyreplace(Status, 1, Acc, {Status, C+1})
|
||||
end
|
||||
end, [], Reviews).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Топ-N целей по общему количеству отзывов.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec get_top_targets_by_reviews(N :: non_neg_integer()) ->
|
||||
[{TargetType :: atom(), TargetId :: binary(), Count :: non_neg_integer()}].
|
||||
get_top_targets_by_reviews(N) ->
|
||||
aggregate_targets_by(fun(_Review) -> true end, N).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Топ-N целей по количеству позитивных отзывов (rating >= 4).
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec get_top_targets_by_positive_reviews(N :: non_neg_integer()) ->
|
||||
[{TargetType :: atom(), TargetId :: binary(), Count :: non_neg_integer()}].
|
||||
get_top_targets_by_positive_reviews(N) ->
|
||||
aggregate_targets_by(fun(#review{rating = R}) -> R >= 4 end, N).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Топ-N целей по количеству негативных отзывов (rating <= 2).
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec get_top_targets_by_negative_reviews(N :: non_neg_integer()) ->
|
||||
[{TargetType :: atom(), TargetId :: binary(), Count :: non_neg_integer()}].
|
||||
get_top_targets_by_negative_reviews(N) ->
|
||||
aggregate_targets_by(fun(#review{rating = R}) -> R =< 2 end, N).
|
||||
|
||||
%% @private Вспомогательная функция агрегации целей с фильтром.
|
||||
aggregate_targets_by(Pred, N) ->
|
||||
Reviews = mnesia:dirty_match_object(#review{_ = '_'}),
|
||||
Filtered = lists:filter(Pred, Reviews),
|
||||
Dict = lists:foldl(fun(#review{target_type = Type, target_id = Id}, Acc) ->
|
||||
Key = {Type, Id},
|
||||
case lists:keyfind(Key, 1, Acc) of
|
||||
false -> [{Key, 1} | Acc];
|
||||
{Key, C} -> lists:keyreplace(Key, 1, Acc, {Key, C+1})
|
||||
end
|
||||
end, [], Filtered),
|
||||
Sorted = lists:reverse(lists:sort(fun({_, C1}, {_, C2}) -> C1 =< C2 end, Dict)),
|
||||
lists:sublist([{Type, Id, Cnt} || {{Type, Id}, Cnt} <- Sorted], N).
|
||||
|
||||
%%%===================================================================
|
||||
%%% ВНУТРЕННИЕ ФУНКЦИИ
|
||||
%%%===================================================================
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
-export([create/3, get_by_id/1, get_active_by_user/1, list_by_user/1, list_all/0]).
|
||||
-export([update_status/2, check_expired/0]).
|
||||
-export([list_subscriptions/0, create_subscription/1, update_subscription/2, delete_subscription/1]).
|
||||
-export([count_subscription/0]).
|
||||
-export([count_subscriptions/0]).
|
||||
-export([count_subscriptions_by_date/2]).
|
||||
-export([count_subscriptions_by_plan/0, count_subscriptions_by_status/0,
|
||||
count_trial_subscriptions/0, get_ending_paid_subscriptions/1]).
|
||||
|
||||
-define(TRIAL_DAYS, 30).
|
||||
|
||||
@@ -291,8 +293,8 @@ parse_iso_datetime(Other) -> Other.
|
||||
%%% @doc Количество подписок.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec count_subscription() -> non_neg_integer().
|
||||
count_subscription() ->
|
||||
-spec count_subscriptions() -> non_neg_integer().
|
||||
count_subscriptions() ->
|
||||
mnesia:table_info(subscription, size).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
@@ -314,4 +316,56 @@ count_subscriptions_by_date(From, To) ->
|
||||
end, [], Filtered),
|
||||
lists:sort(Counts).
|
||||
|
||||
date_part({{Y,M,D}, _}) -> {Y,M,D}.
|
||||
date_part({{Y,M,D}, _}) -> {Y,M,D}.
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Подсчёт подписок по планам.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec count_subscriptions_by_plan() -> [{atom(), non_neg_integer()}].
|
||||
count_subscriptions_by_plan() ->
|
||||
Subs = mnesia:dirty_match_object(#subscription{_ = '_'}),
|
||||
lists:foldl(fun(#subscription{plan = Plan}, Acc) ->
|
||||
case lists:keyfind(Plan, 1, Acc) of
|
||||
false -> [{Plan, 1} | Acc];
|
||||
{Plan, C} -> lists:keyreplace(Plan, 1, Acc, {Plan, C+1})
|
||||
end
|
||||
end, [], Subs).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Подсчёт подписок по статусам.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec count_subscriptions_by_status() -> [{atom(), non_neg_integer()}].
|
||||
count_subscriptions_by_status() ->
|
||||
Subs = mnesia:dirty_match_object(#subscription{_ = '_'}),
|
||||
lists:foldl(fun(#subscription{status = Status}, Acc) ->
|
||||
case lists:keyfind(Status, 1, Acc) of
|
||||
false -> [{Status, 1} | Acc];
|
||||
{Status, C} -> lists:keyreplace(Status, 1, Acc, {Status, C+1})
|
||||
end
|
||||
end, [], Subs).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Количество пробных подписок (trial_used = false).
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec count_trial_subscriptions() -> non_neg_integer().
|
||||
count_trial_subscriptions() ->
|
||||
Match = #subscription{trial_used = false, _ = '_'},
|
||||
length(mnesia:dirty_match_object(Match)).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Платные подписки (trial_used = true), истекающие в течение Days дней.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec get_ending_paid_subscriptions(Days :: non_neg_integer()) -> [#subscription{}].
|
||||
get_ending_paid_subscriptions(Days) ->
|
||||
NowSec = calendar:datetime_to_gregorian_seconds(calendar:universal_time()),
|
||||
ThresholdSec = NowSec + Days * 86400,
|
||||
Match = #subscription{status = active, trial_used = true, _ = '_'},
|
||||
ActivePaid = mnesia:dirty_match_object(Match),
|
||||
lists:filter(fun(#subscription{expires_at = Exp}) ->
|
||||
ExpSec = calendar:datetime_to_gregorian_seconds(Exp),
|
||||
ExpSec >= NowSec andalso ExpSec =< ThresholdSec
|
||||
end, ActivePaid).
|
||||
@@ -5,7 +5,7 @@
|
||||
-export([count_tickets/0]).
|
||||
-export([create_ticket/1]).
|
||||
-export([list_by_user/1]).
|
||||
-export([count_tickets_by_status/1, avg_resolution_time/0, count_tickets_by_admin/2]).
|
||||
-export([count_tickets_by_status/1, avg_resolution_time/0, count_tickets_by_admin/2, stats/0]).
|
||||
-export([update_ticket/2]).
|
||||
-export([delete_ticket/1]).
|
||||
-export([count_tickets_by_date/2]).
|
||||
@@ -236,6 +236,23 @@ avg_resolution_time() ->
|
||||
Total / length(Closed) / 3600.0
|
||||
end.
|
||||
|
||||
%% @doc Статистика по тикетам (используется в admin_handler_ticket_stats)
|
||||
stats() ->
|
||||
Tickets = list_all(),
|
||||
#{
|
||||
total => length(Tickets),
|
||||
open => count_by_status(open, Tickets),
|
||||
in_progress => count_by_status(in_progress, Tickets),
|
||||
resolved => count_by_status(resolved, Tickets),
|
||||
closed => count_by_status(closed, Tickets)
|
||||
}.
|
||||
|
||||
%% ── функции подсчёта с нормализацией статуса ─────────────
|
||||
|
||||
%% @private Подсчитывает тикеты с заданным статусом (атом или бинарный)
|
||||
count_by_status(Status, Tickets) ->
|
||||
length([T || T <- Tickets, normalize_status(T#ticket.status) =:= Status]).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Количество тикетов, назначенных на администратора.
|
||||
%%% @end
|
||||
@@ -274,6 +291,14 @@ date_part({{Y,M,D}, _}) -> {Y,M,D}.
|
||||
%%% ВНУТРЕННИЕ ФУНКЦИИ
|
||||
%%%===================================================================
|
||||
|
||||
%% @private Преобразует бинарный статус в атом, если нужно.
|
||||
%% Атомы возвращает без изменений.
|
||||
normalize_status(Status) when is_atom(Status) -> Status;
|
||||
normalize_status(Status) when is_binary(Status) ->
|
||||
try binary_to_existing_atom(Status, utf8)
|
||||
catch error:badarg -> Status
|
||||
end.
|
||||
|
||||
apply_updates(Ticket, Updates) ->
|
||||
Updated = lists:foldl(fun({Field, Value}, T) -> set_field(Field, Value, T) end,
|
||||
Ticket, Updates),
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
-export([list_users/0]).
|
||||
-export([block/2, unblock/2]).
|
||||
-export([count_users/0, count_users_by_date/2, count_pending_users/0, count_pending_users_by_date/2, list_all/0]).
|
||||
-export([count_users_by_role/0, count_users_by_status/0]).
|
||||
-export([create_bot/2, delete_bot/1]).
|
||||
|
||||
%% ─────────────────────────────────────────────────────────────────
|
||||
@@ -306,6 +307,38 @@ count_pending_users_by_date(From, To) ->
|
||||
end, [], Filtered),
|
||||
lists:sort(Counts).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Подсчёт пользователей по ролям.
|
||||
%%% Возвращает список [{Role :: atom(), Count :: non_neg_integer()}].
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec count_users_by_role() -> [{atom(), non_neg_integer()}].
|
||||
count_users_by_role() ->
|
||||
Users = mnesia:dirty_match_object(#user{_ = '_'}),
|
||||
Dict = lists:foldl(fun(#user{role = Role}, Acc) ->
|
||||
case lists:keyfind(Role, 1, Acc) of
|
||||
false -> [{Role, 1} | Acc];
|
||||
{Role, C} -> lists:keyreplace(Role, 1, Acc, {Role, C+1})
|
||||
end
|
||||
end, [], Users),
|
||||
Dict.
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Подсчёт пользователей по статусам.
|
||||
%%% Возвращает список [{Status :: atom(), Count :: non_neg_integer()}].
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec count_users_by_status() -> [{atom(), non_neg_integer()}].
|
||||
count_users_by_status() ->
|
||||
Users = mnesia:dirty_match_object(#user{_ = '_'}),
|
||||
Dict = lists:foldl(fun(#user{status = Status}, Acc) ->
|
||||
case lists:keyfind(Status, 1, Acc) of
|
||||
false -> [{Status, 1} | Acc];
|
||||
{Status, C} -> lists:keyreplace(Status, 1, Acc, {Status, C+1})
|
||||
end
|
||||
end, [], Users),
|
||||
Dict.
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Выделить из datetime только дату `{Y,M,D}`.
|
||||
%%% @end
|
||||
|
||||
Reference in New Issue
Block a user