diff --git a/Modules/Helpdesk/Config/config.php b/Modules/Helpdesk/Config/config.php index 9868548..a0d0bb4 100755 --- a/Modules/Helpdesk/Config/config.php +++ b/Modules/Helpdesk/Config/config.php @@ -5,6 +5,7 @@ declare(strict_types=1); return [ 'route_prefix' => env('HELPDESK_ROUTE_PREFIX', 'admin/helpdesk'), 'middleware' => ['web', 'auth'], + 'legacy_connection' => env('HELPDESK_LEGACY_DB_CONNECTION', config('database.default')), 'disk' => env('HELPDESK_ATTACHMENTS_DISK', 'local'), 'max_upload_kb' => (int) env('HELPDESK_MAX_UPLOAD_KB', 10240), diff --git a/Modules/Helpdesk/Console/ImportLegacyHelpdeskCommand.php b/Modules/Helpdesk/Console/ImportLegacyHelpdeskCommand.php new file mode 100644 index 0000000..f5aba7f --- /dev/null +++ b/Modules/Helpdesk/Console/ImportLegacyHelpdeskCommand.php @@ -0,0 +1,128 @@ +option('connection') ?: config('helpdesk.legacy_connection', config('database.default'))); + $source = DB::connection($connectionName); + $schema = Schema::connection($connectionName); + + foreach (['tickets', 'tickets_categories'] as $table) { + if (! $schema->hasTable($table)) { + $this->error("Source table {$table} was not found on connection [{$connectionName}]."); + return self::FAILURE; + } + } + + if ($this->option('fresh')) { + LegacyComment::query()->delete(); + LegacyTicket::query()->delete(); + LegacyCategory::query()->delete(); + } + + $this->importCategories($source); + $this->importTickets($source, $schema); + $this->importComments($source, $schema); + $this->refreshCategoryCounts(); + + $this->info(sprintf('Legacy import complete: %d tickets, %d categories, %d comments.', LegacyTicket::count(), LegacyCategory::count(), LegacyComment::count())); + return self::SUCCESS; + } + + private function importCategories(ConnectionInterface $source): void + { + $source->table('tickets_categories')->orderBy('id')->chunk(500, function ($rows): void { + foreach ($rows as $row) { + LegacyCategory::updateOrCreate(['legacy_id' => $row->id], ['name' => (string) $row->name, 'color' => $row->color ?? null]); + } + }); + } + + private function importTickets(ConnectionInterface $source, $schema): void + { + $columns = collect($schema->getColumnListing('tickets')); + $lookups = [ + 'category' => $this->lookup($source, $schema, 'tickets_categories'), + 'status' => $this->lookup($source, $schema, 'tickets_statuses'), + 'priority' => $this->lookup($source, $schema, 'tickets_priorities'), + 'project' => $this->lookup($source, $schema, 'projects'), + 'user' => $this->lookup($source, $schema, 'users'), + ]; + + $source->table('tickets')->orderBy('id')->chunk(250, function ($rows) use ($columns, $lookups): void { + foreach ($rows as $row) { + $data = (array) $row; + LegacyTicket::updateOrCreate(['legacy_id' => $row->id], [ + 'reference' => $data['reference'] ?? null, + 'subject' => (string) ($data['subject'] ?? 'Legacy ticket #'.$row->id), + 'content' => $data['content'] ?? null, + 'html_content' => $data['htmlcontent'] ?? null, + 'legacy_category_id' => $data['category_id'] ?? null, + 'category_name' => $lookups['category'][$data['category_id'] ?? 0] ?? null, + 'status_name' => $lookups['status'][$data['status_id'] ?? 0] ?? null, + 'priority_name' => $lookups['priority'][$data['priority_id'] ?? 0] ?? null, + 'requester_name' => $lookups['user'][$data['user_id'] ?? 0] ?? null, + 'agent_name' => $lookups['user'][$data['agent_id'] ?? 0] ?? null, + 'project_name' => $lookups['project'][$data['project_id'] ?? 0] ?? null, + 'legacy_created_at' => $data['created_at'] ?? null, + 'legacy_updated_at' => $data['updated_at'] ?? null, + 'completed_at' => $data['completed_at'] ?? null, + 'archived_at' => $data['archived_at'] ?? null, + 'legacy_data' => $data, + ]); + } + }); + } + + private function importComments(ConnectionInterface $source, $schema): void + { + if (! $schema->hasTable('tickets_comments')) return; + $users = $this->lookup($source, $schema, 'users'); + $source->table('tickets_comments')->orderBy('id')->chunk(500, function ($rows) use ($users): void { + foreach ($rows as $row) { + $data = (array) $row; + LegacyComment::updateOrCreate(['legacy_id' => $row->id], [ + 'legacy_ticket_id' => $data['ticket_id'], + 'author_name' => $users[$data['user_id'] ?? 0] ?? null, + 'content' => $data['content'] ?? null, + 'html_content' => $data['htmlcontent'] ?? null, + 'legacy_created_at' => $data['created_at'] ?? null, + 'legacy_updated_at' => $data['updated_at'] ?? null, + ]); + } + }); + } + + private function lookup(ConnectionInterface $source, $schema, string $table): array + { + if (! $schema->hasTable($table)) return []; + $columns = $schema->getColumnListing($table); + $nameColumn = in_array('name', $columns, true) ? 'name' : (in_array('username', $columns, true) ? 'username' : null); + if ($nameColumn === null) return []; + try { return $source->table($table)->pluck($nameColumn, 'id')->map(fn ($v) => (string) $v)->all(); } catch (Throwable) { return []; } + } + + private function refreshCategoryCounts(): void + { + LegacyCategory::query()->each(function (LegacyCategory $category): void { + $category->update(['ticket_count' => LegacyTicket::query()->where('legacy_category_id', $category->legacy_id)->count()]); + }); + } +} diff --git a/Modules/Helpdesk/Database/Migrations/2026_09_28_100000_create_helpdesk_legacy_tables.php b/Modules/Helpdesk/Database/Migrations/2026_09_28_100000_create_helpdesk_legacy_tables.php new file mode 100644 index 0000000..f25f05d --- /dev/null +++ b/Modules/Helpdesk/Database/Migrations/2026_09_28_100000_create_helpdesk_legacy_tables.php @@ -0,0 +1,62 @@ +id(); + $table->unsignedBigInteger('legacy_id')->unique(); + $table->string('name'); + $table->string('color', 32)->nullable(); + $table->unsignedInteger('ticket_count')->default(0); + $table->timestamps(); + }); + + Schema::create('helpdesk_legacy_tickets', function (Blueprint $table): void { + $table->id(); + $table->unsignedBigInteger('legacy_id')->unique(); + $table->string('reference')->nullable()->index(); + $table->string('subject'); + $table->longText('content')->nullable(); + $table->longText('html_content')->nullable(); + $table->unsignedBigInteger('legacy_category_id')->nullable()->index(); + $table->string('category_name')->nullable(); + $table->string('status_name')->nullable(); + $table->string('priority_name')->nullable(); + $table->string('requester_name')->nullable(); + $table->string('agent_name')->nullable(); + $table->string('project_name')->nullable(); + $table->timestamp('legacy_created_at')->nullable()->index(); + $table->timestamp('legacy_updated_at')->nullable(); + $table->timestamp('completed_at')->nullable(); + $table->timestamp('archived_at')->nullable(); + $table->json('legacy_data')->nullable(); + $table->timestamps(); + }); + + Schema::create('helpdesk_legacy_comments', function (Blueprint $table): void { + $table->id(); + $table->unsignedBigInteger('legacy_id')->unique(); + $table->unsignedBigInteger('legacy_ticket_id')->index(); + $table->string('author_name')->nullable(); + $table->longText('content')->nullable(); + $table->longText('html_content')->nullable(); + $table->timestamp('legacy_created_at')->nullable(); + $table->timestamp('legacy_updated_at')->nullable(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('helpdesk_legacy_comments'); + Schema::dropIfExists('helpdesk_legacy_tickets'); + Schema::dropIfExists('helpdesk_legacy_categories'); + } +}; diff --git a/Modules/Helpdesk/Http/Controllers/LegacySystemController.php b/Modules/Helpdesk/Http/Controllers/LegacySystemController.php new file mode 100644 index 0000000..b3986a5 --- /dev/null +++ b/Modules/Helpdesk/Http/Controllers/LegacySystemController.php @@ -0,0 +1,51 @@ +query('search', '')); + $category = $request->integer('category'); + + $tickets = LegacyTicket::query() + ->when($search !== '', fn ($query) => $query->where(function ($q) use ($search): void { + $q->where('reference', 'like', "%{$search}%") + ->orWhere('subject', 'like', "%{$search}%") + ->orWhere('requester_name', 'like', "%{$search}%"); + })) + ->when($category > 0, fn ($query) => $query->where('legacy_category_id', $category)) + ->orderByDesc('legacy_created_at') + ->orderByDesc('legacy_id') + ->paginate(30) + ->withQueryString(); + + return view('helpdesk::legacy.tickets', [ + 'tickets' => $tickets, + 'categories' => LegacyCategory::query()->orderBy('name')->get(), + 'search' => $search, + 'selectedCategory' => $category, + ]); + } + + public function show(LegacyTicket $ticket): View + { + $ticket->load('comments'); + return view('helpdesk::legacy.show', compact('ticket')); + } + + public function categories(): View + { + return view('helpdesk::legacy.categories', [ + 'categories' => LegacyCategory::query()->orderBy('name')->paginate(50), + ]); + } +} diff --git a/Modules/Helpdesk/Models/LegacyCategory.php b/Modules/Helpdesk/Models/LegacyCategory.php new file mode 100644 index 0000000..103f5bd --- /dev/null +++ b/Modules/Helpdesk/Models/LegacyCategory.php @@ -0,0 +1,13 @@ + 'datetime', 'legacy_updated_at' => 'datetime']; +} diff --git a/Modules/Helpdesk/Models/LegacyTicket.php b/Modules/Helpdesk/Models/LegacyTicket.php new file mode 100644 index 0000000..cdf5b7c --- /dev/null +++ b/Modules/Helpdesk/Models/LegacyTicket.php @@ -0,0 +1,20 @@ + 'array', 'legacy_created_at' => 'datetime', 'legacy_updated_at' => 'datetime', 'completed_at' => 'datetime', 'archived_at' => 'datetime']; + + public function comments(): HasMany + { + return $this->hasMany(LegacyComment::class, 'legacy_ticket_id', 'legacy_id')->orderBy('legacy_created_at'); + } +} diff --git a/Modules/Helpdesk/Resources/views/legacy/categories.blade.php b/Modules/Helpdesk/Resources/views/legacy/categories.blade.php new file mode 100644 index 0000000..3c1520d --- /dev/null +++ b/Modules/Helpdesk/Resources/views/legacy/categories.blade.php @@ -0,0 +1,6 @@ + +
+

Legacy System

Legacy Categories

Categories retained from the previous Helpdesk.

+
@forelse($categories as $category)@empty@endforelse
CategoryLegacy IDTickets
{{ $category->name }}{{ $category->legacy_id }}{{ $category->ticket_count }}
No legacy categories have been imported.
@if($categories->hasPages())
{{ $categories->links() }}
@endif
+
+
diff --git a/Modules/Helpdesk/Resources/views/legacy/show.blade.php b/Modules/Helpdesk/Resources/views/legacy/show.blade.php new file mode 100644 index 0000000..ceeff03 --- /dev/null +++ b/Modules/Helpdesk/Resources/views/legacy/show.blade.php @@ -0,0 +1,6 @@ + +
+

Legacy System · Read only

{{ $ticket->subject }}

{{ $ticket->reference ?: 'Legacy ticket #'.$ticket->legacy_id }}

Legacy Tickets
+

Original request

{!! $ticket->html_content ?: nl2br(e($ticket->content ?: 'No content recorded.')) !!}
@if($ticket->comments->isNotEmpty())

Comments

@foreach($ticket->comments as $comment)
{{ $comment->author_name ?: 'Legacy user' }}{{ $comment->legacy_created_at?->format('d M Y H:i') }}
{!! $comment->html_content ?: nl2br(e($comment->content ?: '')) !!}
@endforeach
@endif
+
+
diff --git a/Modules/Helpdesk/Resources/views/legacy/tickets.blade.php b/Modules/Helpdesk/Resources/views/legacy/tickets.blade.php new file mode 100644 index 0000000..b0e671c --- /dev/null +++ b/Modules/Helpdesk/Resources/views/legacy/tickets.blade.php @@ -0,0 +1,78 @@ + +
+
+
+

Legacy System

+

Legacy Tickets

+

Historical tickets from the previous Helpdesk. These records are read-only.

+
+
+
+
+ + + +
+
+
+
@forelse($tickets as $ticket) + + @empty@endforelse
ReferenceSubjectCategoryStatusRequesterCreated
{{ $ticket->reference ?: '#'.$ticket->legacy_id }}{{ $ticket->subject }}{{ $ticket->category_name ?: '—' }}{{ $ticket->status_name ?: '—' }}{{ $ticket->requester_name ?: '—' }}{{ $ticket->legacy_created_at?->format('d M Y H:i') ?: '—' }}
No legacy tickets have been imported.
+ @if($tickets->hasPages()) +
+ + {{-- Previous --}} + @if($tickets->onFirstPage()) + + + Previous + + @else + + + Previous + + @endif + + {{-- Page information --}} +
+ Page + + {{ $tickets->currentPage() }} + + of + + {{ $tickets->lastPage() }} + +
+ + {{-- Next --}} + @if($tickets->hasMorePages()) + + Next + + + @else + + Next + + + @endif + +
+ @endif +
+
+
diff --git a/Modules/Helpdesk/Routes/web.php b/Modules/Helpdesk/Routes/web.php index ebd6a17..b67e19d 100755 --- a/Modules/Helpdesk/Routes/web.php +++ b/Modules/Helpdesk/Routes/web.php @@ -5,6 +5,7 @@ declare(strict_types=1); use Illuminate\Support\Facades\Route; use Modules\Helpdesk\Http\Controllers\AttachmentDownloadController; use Modules\Helpdesk\Http\Controllers\HelpdeskController; +use Modules\Helpdesk\Http\Controllers\LegacySystemController; use Modules\Helpdesk\Http\Controllers\TicketAttachmentStoreController; use Modules\Helpdesk\Http\Controllers\TicketBoardController; use Modules\Helpdesk\Http\Controllers\TicketCommentDeleteController; @@ -28,6 +29,10 @@ Route::get('/', HelpdeskController::class)->name('index'); Route::get('/settings', SettingsManager::class)->name('settings'); Route::get('/kanban', KanbanBoard::class)->name('kanban'); +Route::get('/legacy/tickets', [LegacySystemController::class, 'tickets'])->name('legacy.tickets'); +Route::get('/legacy/tickets/{ticket}', [LegacySystemController::class, 'show'])->name('legacy.tickets.show'); +Route::get('/legacy/categories', [LegacySystemController::class, 'categories'])->name('legacy.categories'); + Route::get('/tickets', TicketIndex::class)->name('tickets.index'); Route::get('/tickets/archived', ArchivedTicketIndex::class)->name('tickets.archived'); Route::post('/tickets', TicketStoreController::class)->name('tickets.store'); diff --git a/config/database.php b/config/database.php index abbb88e..ba745c7 100644 --- a/config/database.php +++ b/config/database.php @@ -83,7 +83,20 @@ return [ Mysql::ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], - + 'helpdesk_legacy' => [ + 'driver' => 'mysql', + 'host' => env('LEGACY_DB_HOST', '127.0.0.1'), + 'port' => env('LEGACY_DB_PORT', '3306'), + 'database' => env('LEGACY_DB_DATABASE'), + 'username' => env('LEGACY_DB_USERNAME'), + 'password' => env('LEGACY_DB_PASSWORD'), + 'unix_socket' => env('LEGACY_DB_SOCKET', ''), + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'prefix' => '', + 'strict' => true, + 'engine' => null, + ], 'pgsql' => [ 'driver' => 'pgsql', 'url' => env('DB_URL'), diff --git a/resources/themes/backend/assets/css/helpdesk.css b/resources/themes/backend/assets/css/helpdesk.css index ada8d7e..0c651c0 100755 --- a/resources/themes/backend/assets/css/helpdesk.css +++ b/resources/themes/backend/assets/css/helpdesk.css @@ -2476,3 +2476,23 @@ html:not(.dark) .hd-appearance-choice.is-active { background: #fff; color: #1118 .hd-account-settings-form { max-width: none; } .hd-ios12 .hd-settings-section-card { width: calc(50% - 8px) !important; min-width: 0 !important; } } + +/* Legacy ticket table hover */ +.legacy-tickets-table tbody tr { + transition: background-color 0.15s ease; +} + +.legacy-tickets-table tbody tr:hover, +.legacy-tickets-table tbody tr:hover > td { + background-color: rgba(255, 255, 255, 0.035) !important; + color: inherit !important; +} + +.legacy-tickets-table tbody tr:hover td, +.legacy-tickets-table tbody tr:hover td:not(:first-child) { + color: var(--hd-text-primary, #e5e7eb) !important; +} + +.legacy-tickets-table tbody tr:hover a { + color: #3b82f6 !important; +} diff --git a/resources/themes/backend/views/components/helpdesk/layouts/app.blade.php b/resources/themes/backend/views/components/helpdesk/layouts/app.blade.php index f7a7d06..c7f3e71 100755 --- a/resources/themes/backend/views/components/helpdesk/layouts/app.blade.php +++ b/resources/themes/backend/views/components/helpdesk/layouts/app.blade.php @@ -143,6 +143,12 @@ @if(Route::has('helpdesk.kanban'))Kanban@endif @if(Route::has('helpdesk.settings'))Settings@endif + @if(Route::has('helpdesk.legacy.tickets')) +
Legacy System
+ Legacy Tickets + Legacy Categories +
+ @endif @if(Route::has('documentation.index') || Route::has('documentation.documents.index'))
Documentation
Documents diff --git a/resources/themes/backend/views/components/layouts/app.blade.php b/resources/themes/backend/views/components/layouts/app.blade.php index f7a7d06..c7f3e71 100755 --- a/resources/themes/backend/views/components/layouts/app.blade.php +++ b/resources/themes/backend/views/components/layouts/app.blade.php @@ -143,6 +143,12 @@ @if(Route::has('helpdesk.kanban'))Kanban@endif @if(Route::has('helpdesk.settings'))Settings@endif
+ @if(Route::has('helpdesk.legacy.tickets')) +
Legacy System
+ Legacy Tickets + Legacy Categories +
+ @endif @if(Route::has('documentation.index') || Route::has('documentation.documents.index'))
Documentation
Documents diff --git a/resources/themes/backend/views/layouts/app.blade.php b/resources/themes/backend/views/layouts/app.blade.php index bd49048..820be51 100755 --- a/resources/themes/backend/views/layouts/app.blade.php +++ b/resources/themes/backend/views/layouts/app.blade.php @@ -143,6 +143,12 @@ @if(Route::has('helpdesk.kanban'))Kanban@endif @if(Route::has('helpdesk.settings'))Settings@endif
+ @if(Route::has('helpdesk.legacy.tickets')) +
Legacy System
+ Legacy Tickets + Legacy Categories +
+ @endif @if(Route::has('documentation.index') || Route::has('documentation.documents.index'))
Documentation
Documents diff --git a/storage/logs/browser.log b/storage/logs/browser.log index e48ee1d..a5642ac 100644 --- a/storage/logs/browser.log +++ b/storage/logs/browser.log @@ -370,3 +370,11 @@ promiseReactionJob@[native code] {"url":"https://helpdesk.vm/dashboard","user_ag [2026-09-28 08:46:40] local.INFO: Helpdesk module assets loaded {"url":"https://helpdesk.vm/dashboard","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0","timestamp":"2026-09-28T08:46:39.947Z"} [2026-09-28 08:48:45] local.INFO: Helpdesk module assets loaded {"url":"https://helpdesk.vm/dashboard","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0","timestamp":"2026-09-28T08:48:45.326Z"} [2026-09-28 08:50:56] local.INFO: Helpdesk module assets loaded {"url":"https://helpdesk.vm/dashboard","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0","timestamp":"2026-09-28T08:50:56.465Z"} +[2026-09-28 09:25:11] local.INFO: Helpdesk module assets loaded {"url":"https://helpdesk.vm/dashboard","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0","timestamp":"2026-09-28T09:25:11.035Z"} +[2026-09-28 09:25:23] local.INFO: Helpdesk module assets loaded {"url":"https://helpdesk.vm/admin/helpdesk/legacy/tickets","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0","timestamp":"2026-09-28T09:25:22.956Z"} +[2026-09-28 09:38:32] local.INFO: Helpdesk module assets loaded {"url":"https://helpdesk.vm/admin/helpdesk/legacy/tickets","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0","timestamp":"2026-09-28T09:38:32.132Z"} +[2026-09-28 09:39:05] local.INFO: Helpdesk module assets loaded {"url":"https://helpdesk.vm/admin/helpdesk/legacy/tickets?page=2","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0","timestamp":"2026-09-28T09:39:05.417Z"} +[2026-09-28 09:39:08] local.INFO: Helpdesk module assets loaded {"url":"https://helpdesk.vm/admin/helpdesk/legacy/tickets?page=3","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0","timestamp":"2026-09-28T09:39:08.531Z"} +[2026-09-28 09:42:15] local.INFO: Helpdesk module assets loaded {"url":"https://helpdesk.vm/admin/helpdesk/legacy/tickets","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0","timestamp":"2026-09-28T09:42:15.037Z"} +[2026-09-28 09:44:20] local.INFO: Helpdesk module assets loaded {"url":"https://helpdesk.vm/admin/helpdesk/legacy/tickets","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0","timestamp":"2026-09-28T09:44:20.199Z"} +[2026-09-28 09:45:34] local.INFO: Helpdesk module assets loaded {"url":"https://helpdesk.vm/admin/helpdesk/legacy/tickets?page=18","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0","timestamp":"2026-09-28T09:45:34.462Z"}