This commit is contained in:
2026-04-21 15:06:57 +03:00
parent fc6ef8de7e
commit a4a7daa5e0
10 changed files with 1084 additions and 0 deletions

143
src/core/core_ticket.erl Normal file
View File

@@ -0,0 +1,143 @@
-module(core_ticket).
-include("records.hrl").
-export([create_or_update/3, get_by_id/1, get_by_error_hash/1, list_all/0, list_by_status/1]).
-export([update_status/2, assign/2, add_resolution/2]).
-export([generate_id/0, generate_error_hash/2]).
%% Создать или обновить тикет (группировка по хэшу ошибки)
create_or_update(ErrorMessage, Stacktrace, Context) ->
ErrorHash = generate_error_hash(ErrorMessage, Stacktrace),
case get_by_error_hash(ErrorHash) of
{error, not_found} ->
% Создаём новый тикет
Id = generate_id(),
Now = calendar:universal_time(),
Ticket = #ticket{
id = Id,
error_hash = ErrorHash,
error_message = ErrorMessage,
stacktrace = Stacktrace,
context = term_to_binary(Context),
count = 1,
first_seen = Now,
last_seen = Now,
status = open,
assigned_to = undefined,
resolution_note = undefined
},
F = fun() ->
mnesia:write(Ticket),
{ok, Ticket}
end,
case mnesia:transaction(F) of
{atomic, Result} -> Result;
{aborted, Reason} -> {error, Reason}
end;
{ok, Ticket} ->
% Обновляем существующий
F = fun() ->
Updated = Ticket#ticket{
count = Ticket#ticket.count + 1,
last_seen = calendar:universal_time()
},
mnesia:write(Updated),
{ok, Updated}
end,
case mnesia:transaction(F) of
{atomic, Result} -> Result;
{aborted, Reason} -> {error, Reason}
end
end.
%% Получение тикета по ID
get_by_id(Id) ->
case mnesia:dirty_read(ticket, Id) of
[] -> {error, not_found};
[Ticket] -> {ok, Ticket}
end.
%% Получение тикета по хэшу ошибки
get_by_error_hash(ErrorHash) ->
Match = #ticket{error_hash = ErrorHash, _ = '_'},
case mnesia:dirty_match_object(Match) of
[] -> {error, not_found};
[Ticket] -> {ok, Ticket}
end.
%% Список всех тикетов
list_all() ->
Match = #ticket{_ = '_'},
Tickets = mnesia:dirty_match_object(Match),
{ok, lists:sort(fun(A, B) -> A#ticket.last_seen >= B#ticket.last_seen end, Tickets)}.
%% Список тикетов по статусу
list_by_status(Status) ->
Match = #ticket{status = Status, _ = '_'},
Tickets = mnesia:dirty_match_object(Match),
{ok, lists:sort(fun(A, B) -> A#ticket.last_seen >= B#ticket.last_seen end, Tickets)}.
%% Обновление статуса тикета
update_status(Id, Status) when Status =:= open; Status =:= in_progress; Status =:= resolved; Status =:= closed ->
F = fun() ->
case mnesia:read(ticket, Id) of
[] ->
{error, not_found};
[Ticket] ->
Updated = Ticket#ticket{status = Status},
mnesia:write(Updated),
{ok, Updated}
end
end,
case mnesia:transaction(F) of
{atomic, Result} -> Result;
{aborted, Reason} -> {error, Reason}
end.
%% Назначить тикет администратору
assign(Id, AdminId) ->
F = fun() ->
case mnesia:read(ticket, Id) of
[] ->
{error, not_found};
[Ticket] ->
Updated = Ticket#ticket{
assigned_to = AdminId,
status = case Ticket#ticket.status of
open -> in_progress;
S -> S
end
},
mnesia:write(Updated),
{ok, Updated}
end
end,
case mnesia:transaction(F) of
{atomic, Result} -> Result;
{aborted, Reason} -> {error, Reason}
end.
%% Добавить примечание о решении
add_resolution(Id, Note) ->
F = fun() ->
case mnesia:read(ticket, Id) of
[] ->
{error, not_found};
[Ticket] ->
Updated = Ticket#ticket{resolution_note = Note},
mnesia:write(Updated),
{ok, Updated}
end
end,
case mnesia:transaction(F) of
{atomic, Result} -> Result;
{aborted, Reason} -> {error, Reason}
end.
%% Внутренние функции
generate_id() ->
base64:encode(crypto:strong_rand_bytes(16), #{mode => urlsafe, padding => false}).
generate_error_hash(ErrorMessage, Stacktrace) ->
Data = iolist_to_binary([ErrorMessage, "\n", Stacktrace]),
base64:encode(crypto:hash(sha256, Data), #{mode => urlsafe, padding => false}).