Zmiana struktury repozytorium (łamiąca dotychczasowy sposób wdrożenia): - Aplikacja Laravel przeniesiona z src/ do katalogu głównego. - Pliki wdrożeniowe w docker/: Dockerfile, compose.yaml(.example), .env dla Compose oraz dane bazy (docker/mysql/). Compose uruchamia się z docker/ albo z -f docker/compose.yaml; nazwa projektu przypięta w pliku, więc nazwy kontenerów się nie zmieniają. Katalog docker/ jest zasłonięty przed kontenerami PHP (tmpfs). - Dokumentacja techniczna w dokumentacja/, przewodniki użytkownika w wiki/, README.md zostaje w katalogu głównym, CLAUDE.md w .claude/. - CI buduje obraz z docker/Dockerfile. - Wersja pokazywana w Admin > O aplikacji jest teraz w config/app.php (wcześniej w nieśledzonym .env, przez co zatrzymała się na 1.4.0). Co nowego: - Eksport / import ustawień (Admin > Eksport / import oraz php artisan settings:export / settings:import): cała konfiguracja — ustawienia i integracje, logo/favicon, użytkownicy z rolami, zespoły, kategorie, pola dodatkowe, statusy, priorytety/SLA, szablony, wyzwalacze, automatyzacje, skrzynki IMAP, klucze API — w jednym pliku JSON, bez zgłoszeń. Import tylko do instalacji bez zgłoszeń. - Import z Heska: tryb --sync-updates dociąga odpowiedzi, notatki i zmiany statusu dodane w Hesku po imporcie do już zaimportowanych zgłoszeń. - Komunikat na stronie logowania można wyłączyć bez kasowania treści; edytor dostał wybór rozmiaru tekstu. Zmiany: - Komunikat logowania ma zawsze jeden kolor info (nowy token --color-info, półprzezroczyste tło); wybór Informacja/Ostrzeżenie/Sukces/Ważne usunięty. Poprawki: - Nagłówki ustawione w edytorze komunikatu logowania nie były większe od zwykłego tekstu na stronie logowania; nad i pod tekstem komunikatu była pusta linia. - Limit pamięci dla testów podniesiony w phpunit.xml (zestaw przekraczał domyślne 128 MB). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rz61PznDTKUACA7sVTB8H3
Hesk 3.x import
One-time historical migration of tickets (with full reply/note history) from a Hesk 3.x helpdesk database into this app, restricted to a single e-mail domain. Read-only against the Hesk database — never writes there.
This is not a continuous sync — nothing runs on a schedule. The import
itself never revisits a ticket it already created; if Hesk kept being used
afterward, re-run the import for tickets opened since, and
--sync-updates
for replies/notes/status changes on tickets that were already imported.
What it does
- Matches Hesk tickets by requester e-mail domain (
--domain=firma.pl). - Imports each ticket's subject/body, status, priority, and full reply +
internal-note history, converting Hesk's
<br />-laden "plain" text into real line breaks. - Maps Hesk categories to this app's categories by exact (case/whitespace-
insensitive) name match. A Hesk category with no match is skipped by
default — pass
--include-unmapped-categoriesto import those tickets anyway, uncategorized. - Auto-assigns a team when every subcategory under the matched category
routes to the same single team (same rule
TicketService::autoAssignTeam()uses for normal ticket creation); ambiguous categories are left unrouted. - Finds the local client account per requester e-mail (matched by e-mail,
adding the
clientrole if it doesn't have it yet). Never creates a User — the servicedesk user base is treated as authoritative/complete, so a Hesk requester e-mail with no matching account means that ticket is skipped (reported at the end, with the list of skipped e-mails). - Hesk staff replies/notes are linked to a real operator account when the
Hesk staff member's e-mail matches an existing servicedesk operator/admin
account; otherwise they fall back to showing the correct staff name and
"operator" badge via
author_nameonly, without a clickable user behind it (this script never creates operator accounts either). - Every imported ticket also stores its source Hesk ticket id
(
tickets.hesk_ticket_id, unique). This is a second, DB-level guard against duplicate imports on top of the state file below — if the state file is ever lost or out of sync, a re-run still can't create a duplicate ticket for the same Hesk id. - A ticket's Hesk owner is matched the same way as reply/note authors and
set as
assignee_id, so imported tickets show up correctly assigned in the operator queue instead of everything landing in "Nieprzypisane".
Setup
cp scripts/hesk-import/.env.example scripts/hesk-import/.env
Fill in scripts/hesk-import/.env with the Hesk database's
host/port/database/username/password. That file is gitignored — it holds
real credentials for a database this app otherwise has no access to.
Usage
Always dry-run first — it reports what would happen without writing anything:
scripts/hesk-import/hesk-import.sh --domain=firma.pl
Try a small batch for real before committing to the whole thing:
scripts/hesk-import/hesk-import.sh --domain=firma.pl --limit=10 --commit
Then the full import:
scripts/hesk-import/hesk-import.sh --domain=firma.pl --commit
Safe to re-run (including after an interrupted/crashed run): already-imported
Hesk ticket ids are tracked in storage/app/hesk-import-state.json inside the
app container and skipped on subsequent runs.
Backfilling team assignment
If tickets were already imported before team-by-category mapping existed (or
teams/subcategories changed since), backfill team_id on existing imported
tickets without importing anything new:
scripts/hesk-import/hesk-import.sh --assign-teams --commit
Fixing closed-ticket dates
New imports already use Hesk's dedicated closedat column (not lastchange,
which moves forward on any later edit — e.g. a note added after closing) for
a closed ticket's date, and record a matching "Status zmieniony na: Zamknięte"
history entry. To apply the same correction to tickets imported before this
existed (including the original import, from before hesk_ticket_id was
even tracked — matched back to Hesk via the unique (email, created_at) pair
instead):
scripts/hesk-import/hesk-import.sh --fix-closed-dates --commit
Doesn't import anything new; safe to re-run (already-correct tickets are left alone).
Backfilling ticket ownership
New imports already set assignee_id from Hesk's ticket owner. To apply the
same to tickets imported before this existed:
scripts/hesk-import/hesk-import.sh --assign-operators --commit
Only touches tickets with no assignee_id yet (never overwrites a manual
reassignment made since import) and never invents an assignment — a Hesk
owner of 0 or one with no matching servicedesk account is left unassigned,
unless it's one of the two ids in ImportHeskTickets::DELETED_STAFF_REASSIGNMENT
(Hesk staff accounts deleted since, with historical tickets explicitly
reassigned to a current operator per the app owner).
Catching up on changes made in Hesk since the import
Pulls replies, internal notes and status changes that landed in Hesk after a ticket was imported into the matching servicedesk ticket. Imports no new tickets — run the normal import for those.
scripts/hesk-import/hesk-import.sh --sync-updates # dry run, lists every ticket it would touch
scripts/hesk-import/hesk-import.sh --sync-updates --commit
Safe to re-run: a Hesk reply/note is recognized as already present by its timestamp, so nothing is added twice. Status follows Hesk only when Hesk's last change is later than the newest status change (or servicedesk-only message) on the ticket here — a ticket an operator has since closed in servicedesk is not reopened by a Hesk row that never changed; those are counted separately in the summary. Edits to the text of an already-imported reply are not picked up.
How it's wired up
hesk-import.sh is a thin wrapper: it loads .env in this folder, then runs
php artisan hesk:import inside the app container via docker compose -f docker/compose.yaml exec, passing the Hesk DB credentials as one-off environment variables (they
never touch the app's own .env or get persisted anywhere but the state
file). The actual import logic lives in
../../app/Console/Commands/ImportHeskTickets.php.