v1.5.0
Co nowego: - Podgląd logów w panelu admina (Admin > Logi) — pliki storage/logs/*.log bez potrzeby dostępu do kontenera, z filtrami poziomu/tekstu/liczby wpisów i auto-odświeżaniem. - Filtr „Bez kategorii” w kolejce operatora — izoluje zgłoszenia bez przypisanej kategorii/podkategorii. - Narzędzie importu z Heska: już nie tworzy automatycznie kont klientów dla nieznanych e-maili (pomija takie zgłoszenia zamiast zakładać konto), łączy odpowiedzi/właścicieli zgłoszeń z realnymi kontami operatorów po e-mailu, nowe flagi --assign-operators i --fix-closed-dates do donaprawiania wcześniejszych importów, dedykowany log storage/logs/hesk-import.log. - Poprawka: pulpit statystyk operatora (rozkład wg kategorii/podkategorii i filtr kategorii) pomijał zgłoszenia przypisane do samej kategorii bez podkategorii (np. z poczty IMAP) — teraz liczone poprawnie. - Poprawka: błąd JS i zawieszone w tle liczniki przy nawigacji z widoku z aktywnym licznikiem (najbardziej odczuwalne w liczniku czasu pracy operatora). - Porządki w bazie: usunięte niewykorzystywane kolumny (users.remember_token, users.email_verified_at, email_templates.trigger_label); wartości pól dodatkowych, stan triage/podsumowania AI i powiązany sprzęt Snipe-IT przeniesione z tabeli tickets do osobnych tabel (ticket_field_values, ticket_ai_summaries, ticket_snipeit_assets) — bez zmiany zachowania, ale pola dodatkowe są teraz efektywnie przeszukiwalne; dodane brakujące indeksy na 4 tabelach pivot; tickets.source/ticket_messages.source walidowane względem znanego zestawu wartości. Zaktualizowana dokumentacja: README, CLAUDE.md, ARCHITECTURE.md, CHANGELOG.md, wiki/admin, wiki/operator. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ use App\Models\Ticket;
|
||||
use App\Support\Settings;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
@@ -48,6 +49,13 @@ class TicketAiSummaryService
|
||||
$this->summarizeOne($ticket) ? $totals['updated']++ : $totals['failed']++;
|
||||
});
|
||||
|
||||
Log::channel('ai')->info(sprintf(
|
||||
'Podsumowania: przeskanowano %d, zaktualizowano %d, błędów %d.',
|
||||
$totals['scanned'],
|
||||
$totals['updated'],
|
||||
$totals['failed'],
|
||||
));
|
||||
|
||||
return $totals;
|
||||
}
|
||||
|
||||
@@ -58,18 +66,28 @@ class TicketAiSummaryService
|
||||
* latter also changes on unrelated actions (status/priority/timer
|
||||
* edits), which would otherwise trigger spurious re-summarization on
|
||||
* every scheduler tick for an active ticket.
|
||||
*
|
||||
* ai_summary_generated_at now lives on the related ticket_ai_summaries
|
||||
* row (see Ticket::aiSummary()), so this joins to it directly rather
|
||||
* than going through the model relation — a plain whereNull() on the
|
||||
* left-joined column covers "no row yet" the same way it used to cover
|
||||
* "column is null" when it lived on tickets itself.
|
||||
*/
|
||||
protected function staleQuery(): Builder
|
||||
{
|
||||
return Ticket::query()->where(function (Builder $q) {
|
||||
$q->whereNull('ai_summary_generated_at')
|
||||
->orWhere(function (Builder $q2) {
|
||||
$q2->whereNotNull('ai_summary_generated_at')
|
||||
->whereColumn('ai_summary_generated_at', '<', DB::raw(
|
||||
'(select max(ticket_messages.created_at) from ticket_messages where ticket_messages.ticket_id = tickets.id)'
|
||||
));
|
||||
});
|
||||
})->orderBy('id');
|
||||
return Ticket::query()
|
||||
->leftJoin('ticket_ai_summaries', 'ticket_ai_summaries.ticket_id', '=', 'tickets.id')
|
||||
->where(function (Builder $q) {
|
||||
$q->whereNull('ticket_ai_summaries.summary_generated_at')
|
||||
->orWhere(function (Builder $q2) {
|
||||
$q2->whereNotNull('ticket_ai_summaries.summary_generated_at')
|
||||
->whereColumn('ticket_ai_summaries.summary_generated_at', '<', DB::raw(
|
||||
'(select max(ticket_messages.created_at) from ticket_messages where ticket_messages.ticket_id = tickets.id)'
|
||||
));
|
||||
});
|
||||
})
|
||||
->select('tickets.*')
|
||||
->orderBy('tickets.id');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,6 +117,8 @@ class TicketAiSummaryService
|
||||
// Leaves any prior summary untouched and generated_at unchanged,
|
||||
// so the ticket stays in the stale set and gets retried next run
|
||||
// rather than silently losing a working summary.
|
||||
Log::channel('ai')->warning("Podsumowanie zgłoszenia #{$ticket->number}: nie udało się wygenerować (brak lub niepoprawna odpowiedź modelu).");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -108,6 +128,8 @@ class TicketAiSummaryService
|
||||
'ai_summary_generated_at' => now(),
|
||||
]);
|
||||
|
||||
Log::channel('ai')->debug("Podsumowanie zgłoszenia #{$ticket->number}: zaktualizowane.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user