-module(core_ticket). -include("records.hrl"). -export([list_all/0, get_by_id/1, update_ticket/2, delete_ticket/1, stats/0, create_ticket/1, list_by_user/1]). -export([count_tickets_by_status/1, count_tickets_by_admin/2]). -export([avg_resolution_time/0]). list_all() -> mnesia:dirty_match_object(#ticket{_ = '_'}). get_by_id(Id) -> case mnesia:dirty_read({ticket, Id}) of [Ticket] -> {ok, Ticket}; [] -> {error, not_found} end. update_ticket(Id, Updates) -> case get_by_id(Id) of {ok, Ticket} -> Updated = apply_updates(Ticket, Updates), mnesia:dirty_write(Updated), {ok, Updated}; Error -> Error end. delete_ticket(Id) -> case get_by_id(Id) of {ok, _Ticket} -> % переменная не используется mnesia:dirty_delete({ticket, Id}), {ok, deleted}; Error -> Error end. 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) }. %% ── новые функции ────────────────────────────────────── create_ticket(Data) -> Id = base64:encode(crypto:strong_rand_bytes(9), #{mode => urlsafe, padding => false}), Now = calendar:universal_time(), Ticket = #ticket{ id = Id, reporter_id = maps:get(<<"reporter_id">>, Data, undefined), error_hash = maps:get(<<"error_hash">>, Data, <<"">>), error_message = maps:get(<<"error_message">>, Data), stacktrace = maps:get(<<"stacktrace">>, Data, <<"">>), context = maps:get(<<"context">>, Data, <<"">>), count = 1, first_seen = Now, last_seen = Now, status = maps:get(<<"status">>, Data, open), assigned_to = maps:get(<<"assigned_to">>, Data, undefined), resolution_note = maps:get(<<"resolution_note">>, Data, undefined) }, mnesia:dirty_write(Ticket), {ok, Ticket}. list_by_user(UserId) -> mnesia:dirty_match_object(#ticket{reporter_id = UserId, _ = '_'}). count_by_status(Status, Tickets) -> length([T || T <- Tickets, T#ticket.status =:= Status]). count_tickets_by_status(Status) -> Match = #ticket{status = Status, _ = '_'}, length(mnesia:dirty_match_object(Match)). count_tickets_by_admin(AdminId, Status) -> Match = #ticket{assigned_to = AdminId, status = Status, _ = '_'}, length(mnesia:dirty_match_object(Match)). avg_resolution_time() -> Tickets = mnesia:dirty_match_object(#ticket{status = closed, _ = '_'}), case Tickets of [] -> 0; _ -> TotalSeconds = lists:sum([calendar:datetime_to_gregorian_seconds(T#ticket.last_seen) - calendar:datetime_to_gregorian_seconds(T#ticket.first_seen) || T <- Tickets]), TotalSeconds / length(Tickets) / 3600.0 end. %% ── внутренние ───────────────────────────────────────── apply_updates(Ticket, Updates) -> lists:foldl(fun({Key, Value}, Acc) -> case Key of <<"status">> -> Acc#ticket{status = binary_to_atom(Value, utf8)}; <<"assigned_to">> -> Acc#ticket{assigned_to = Value}; <<"resolution_note">> -> Acc#ticket{resolution_note = Value}; <<"error_message">> -> Acc#ticket{error_message = Value}; <<"stacktrace">> -> Acc#ticket{stacktrace = Value}; <<"context">> -> Acc#ticket{context = Value}; _ -> Acc end end, Ticket, maps:to_list(Updates)).