diff --git a/Helpdesk/Concerns/HasHelpdeskTickets.php b/Helpdesk/Concerns/HasHelpdeskTickets.php deleted file mode 100644 index 31c9ecc8..00000000 --- a/Helpdesk/Concerns/HasHelpdeskTickets.php +++ /dev/null @@ -1,36 +0,0 @@ - */ - public function helpdeskTickets(): HasMany - { - return $this->hasMany(Ticket::class, 'user_id'); - } - - /** @return HasMany */ - public function helpdeskAssignedTickets(): HasMany - { - return $this->hasMany(Ticket::class, 'agent_id'); - } - - public function isHelpdeskAgent(): bool - { - return (bool) (($this->helpdesk_agent ?? false) || ($this->helpdesk_admin ?? false)); - } - - public function isHelpdeskAdmin(): bool - { - return (bool) ($this->helpdesk_admin ?? false); - } -} diff --git a/Helpdesk/Config/.gitkeep b/Helpdesk/Config/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Helpdesk/Config/config.php b/Helpdesk/Config/config.php deleted file mode 100644 index 1b947558..00000000 --- a/Helpdesk/Config/config.php +++ /dev/null @@ -1,13 +0,0 @@ - env('HELPDESK_ROUTE_PREFIX', 'helpdesk'), - 'middleware' => ['web', 'auth'], - 'disk' => env('HELPDESK_ATTACHMENTS_DISK', 'local'), - 'max_upload_kb' => (int) env('HELPDESK_MAX_UPLOAD_KB', 10240), - 'allowed_mimes' => ['md','pdf', 'png', 'jpg', 'jpeg', 'webp', 'doc', 'docx', 'xls', 'xlsx', 'txt', 'csv', 'zip'], - 'default_status_slug' => 'new', - 'closed_status_slug' => 'closed', -]; diff --git a/Helpdesk/Console/.gitkeep b/Helpdesk/Console/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Helpdesk/Database/Migrations/.gitkeep b/Helpdesk/Database/Migrations/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Helpdesk/Database/Migrations/2026_04_24_000001_add_helpdesk_flags_to_users_table.php b/Helpdesk/Database/Migrations/2026_04_24_000001_add_helpdesk_flags_to_users_table.php deleted file mode 100644 index c8969030..00000000 --- a/Helpdesk/Database/Migrations/2026_04_24_000001_add_helpdesk_flags_to_users_table.php +++ /dev/null @@ -1,26 +0,0 @@ -boolean('helpdesk_agent')->default(false)->after('password'); - } - - if (! Schema::hasColumn('users', 'helpdesk_admin')) { - $table->boolean('helpdesk_admin')->default(false)->after('helpdesk_agent'); - } - }); - } -}; diff --git a/Helpdesk/Database/Migrations/2026_06_12_000010_create_helpdesk_lookup_tables.php b/Helpdesk/Database/Migrations/2026_06_12_000010_create_helpdesk_lookup_tables.php deleted file mode 100644 index bf8b9a64..00000000 --- a/Helpdesk/Database/Migrations/2026_06_12_000010_create_helpdesk_lookup_tables.php +++ /dev/null @@ -1,73 +0,0 @@ -id(); - $table->string('name', 80); - $table->string('slug', 100)->unique(); - $table->string('color', 30)->default('zinc'); - $table->unsignedSmallInteger('sort_order')->default(0); - $table->boolean('is_closed')->default(false); - $table->timestamps(); - }); - - Schema::create('helpdesk_priorities', static function (Blueprint $table): void { - $table->id(); - $table->string('name', 80); - $table->string('slug', 100)->unique(); - $table->string('color', 30)->default('zinc'); - $table->unsignedSmallInteger('sort_order')->default(0); - $table->timestamps(); - }); - - Schema::create('helpdesk_categories', static function (Blueprint $table): void { - $table->id(); - $table->foreignId('company_id')->nullable()->constrained('companies')->nullOnDelete(); - $table->string('name', 120); - $table->string('slug', 140); - $table->string('color', 30)->default('blue'); - $table->text('description')->nullable(); - $table->timestamps(); - - $table->unique(['company_id', 'slug']); - }); - - Schema::create('helpdesk_teams', static function (Blueprint $table): void { - $table->id(); - $table->foreignId('company_id')->nullable()->constrained('companies')->nullOnDelete(); - $table->string('name', 120); - $table->string('slug', 140); - $table->string('color', 30)->default('indigo'); - $table->text('description')->nullable(); - $table->timestamp('archived_at')->nullable(); - $table->softDeletes(); - $table->timestamps(); - - $table->unique(['company_id', 'slug']); - }); - - Schema::create('helpdesk_projects', static function (Blueprint $table): void { - $table->id(); - $table->foreignId('company_id')->nullable()->constrained('companies')->nullOnDelete(); - $table->foreignId('team_id')->nullable()->constrained('helpdesk_teams')->nullOnDelete(); - $table->string('title', 160); - $table->string('slug', 180); - $table->string('color', 30)->default('blue'); - $table->text('description')->nullable(); - $table->timestamp('archived_at')->nullable(); - $table->softDeletes(); - $table->timestamps(); - - $table->unique(['company_id', 'slug']); - }); - } -}; diff --git a/Helpdesk/Database/Migrations/2026_06_12_000020_create_helpdesk_tickets_table.php b/Helpdesk/Database/Migrations/2026_06_12_000020_create_helpdesk_tickets_table.php deleted file mode 100644 index f139a3be..00000000 --- a/Helpdesk/Database/Migrations/2026_06_12_000020_create_helpdesk_tickets_table.php +++ /dev/null @@ -1,41 +0,0 @@ -id(); - $table->string('reference', 32)->unique(); - $table->foreignId('company_id')->nullable()->constrained('companies')->nullOnDelete(); - $table->foreignId('user_id')->constrained('users')->cascadeOnDelete(); - $table->foreignId('agent_id')->nullable()->constrained('users')->nullOnDelete(); - $table->foreignId('status_id')->constrained('helpdesk_statuses')->restrictOnDelete(); - $table->foreignId('priority_id')->constrained('helpdesk_priorities')->restrictOnDelete(); - $table->foreignId('category_id')->nullable()->constrained('helpdesk_categories')->nullOnDelete(); - $table->foreignId('project_id')->nullable()->constrained('helpdesk_projects')->nullOnDelete(); - $table->foreignId('team_id')->nullable()->constrained('helpdesk_teams')->nullOnDelete(); - $table->string('subject', 200); - $table->longText('body'); - $table->timestamp('due_at')->nullable(); - $table->timestamp('completed_at')->nullable(); - $table->timestamp('last_activity_at')->nullable(); - $table->timestamp('archived_at')->nullable(); - $table->softDeletes(); - $table->timestamps(); - - $table->index(['company_id', 'project_id']); - $table->index(['team_id', 'status_id']); - $table->index(['status_id', 'priority_id']); - $table->index(['agent_id', 'completed_at']); - $table->index(['user_id', 'created_at']); - $table->index('last_activity_at'); - }); - } -}; diff --git a/Helpdesk/Database/Migrations/2026_06_12_000030_create_helpdesk_git_and_workflow_tables.php b/Helpdesk/Database/Migrations/2026_06_12_000030_create_helpdesk_git_and_workflow_tables.php deleted file mode 100644 index 25446f2d..00000000 --- a/Helpdesk/Database/Migrations/2026_06_12_000030_create_helpdesk_git_and_workflow_tables.php +++ /dev/null @@ -1,78 +0,0 @@ -id(); - $table->foreignId('company_id')->nullable()->constrained('companies')->nullOnDelete(); - $table->foreignId('project_id')->nullable()->constrained('helpdesk_projects')->nullOnDelete(); - $table->string('name', 160); - $table->string('provider', 40)->default('git'); - $table->string('remote_url', 500); - $table->string('default_branch', 120)->default('main'); - $table->string('webhook_secret', 120)->nullable(); - $table->boolean('is_active')->default(true); - $table->timestamps(); - - $table->index(['company_id', 'project_id']); - $table->index(['provider', 'is_active']); - }); - - Schema::create('helpdesk_git_commits', static function (Blueprint $table): void { - $table->id(); - $table->foreignId('repository_id')->constrained('helpdesk_git_repositories')->cascadeOnDelete(); - $table->foreignId('ticket_id')->nullable()->constrained('helpdesk_tickets')->nullOnDelete(); - $table->string('hash', 80); - $table->string('short_hash', 16); - $table->string('branch', 120)->nullable(); - $table->string('author_name', 160)->nullable(); - $table->string('author_email', 190)->nullable(); - $table->text('message'); - $table->string('url', 500)->nullable(); - $table->timestamp('committed_at')->nullable(); - $table->timestamps(); - - $table->unique(['repository_id', 'hash']); - $table->index(['ticket_id', 'committed_at']); - }); - - Schema::create('helpdesk_workflow_templates', static function (Blueprint $table): void { - $table->id(); - $table->string('name', 160); - $table->string('event', 80); - $table->string('subject', 220); - $table->longText('html_body'); - $table->longText('text_body')->nullable(); - $table->string('to_role', 40)->default('requester'); - $table->string('cc', 500)->nullable(); - $table->string('bcc', 500)->nullable(); - $table->boolean('is_active')->default(true); - $table->timestamps(); - - $table->index(['event', 'is_active']); - }); - - Schema::create('helpdesk_workflow_logs', static function (Blueprint $table): void { - $table->id(); - $table->foreignId('template_id')->nullable()->constrained('helpdesk_workflow_templates')->nullOnDelete(); - $table->foreignId('ticket_id')->nullable()->constrained('helpdesk_tickets')->nullOnDelete(); - $table->string('event', 80); - $table->string('recipient', 190)->nullable(); - $table->string('subject', 220)->nullable(); - $table->string('status', 40)->default('pending'); - $table->text('error')->nullable(); - $table->timestamps(); - - $table->index(['ticket_id', 'event']); - $table->index(['status', 'created_at']); - }); - } -}; diff --git a/Helpdesk/Database/Migrations/2026_06_12_000040_create_helpdesk_comments_and_attachments_tables.php b/Helpdesk/Database/Migrations/2026_06_12_000040_create_helpdesk_comments_and_attachments_tables.php deleted file mode 100644 index 14ef7892..00000000 --- a/Helpdesk/Database/Migrations/2026_06_12_000040_create_helpdesk_comments_and_attachments_tables.php +++ /dev/null @@ -1,40 +0,0 @@ -id(); - $table->foreignId('ticket_id')->constrained('helpdesk_tickets')->cascadeOnDelete(); - $table->foreignId('parent_id')->nullable()->constrained('helpdesk_comments')->cascadeOnDelete(); - $table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete(); - $table->longText('body'); - $table->boolean('is_internal')->default(false); - $table->timestamps(); - - $table->index(['ticket_id', 'parent_id', 'created_at']); - }); - - Schema::create('helpdesk_attachments', static function (Blueprint $table): void { - $table->id(); - $table->foreignId('ticket_id')->constrained('helpdesk_tickets')->cascadeOnDelete(); - $table->foreignId('comment_id')->nullable()->constrained('helpdesk_comments')->cascadeOnDelete(); - $table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete(); - $table->string('disk', 80); - $table->string('path', 500); - $table->string('original_name', 255); - $table->string('mime_type', 160)->nullable(); - $table->unsignedBigInteger('size')->default(0); - $table->timestamps(); - - $table->index(['ticket_id', 'comment_id', 'created_at']); - }); - } -}; diff --git a/Helpdesk/Database/Migrations/2026_06_12_000050_create_helpdesk_category_user_table.php b/Helpdesk/Database/Migrations/2026_06_12_000050_create_helpdesk_category_user_table.php deleted file mode 100644 index e95e1def..00000000 --- a/Helpdesk/Database/Migrations/2026_06_12_000050_create_helpdesk_category_user_table.php +++ /dev/null @@ -1,21 +0,0 @@ -id(); - $table->foreignId('category_id')->constrained('helpdesk_categories')->cascadeOnDelete(); - $table->foreignId('user_id')->constrained('users')->cascadeOnDelete(); - $table->timestamps(); - $table->unique(['category_id', 'user_id']); - }); - } -}; diff --git a/Helpdesk/Database/Migrations/2026_06_12_130000_create_helpdesk_kanban_boards_table.php b/Helpdesk/Database/Migrations/2026_06_12_130000_create_helpdesk_kanban_boards_table.php deleted file mode 100644 index 974dd989..00000000 --- a/Helpdesk/Database/Migrations/2026_06_12_130000_create_helpdesk_kanban_boards_table.php +++ /dev/null @@ -1,39 +0,0 @@ -id(); - $table->string('name'); - $table->string('slug')->unique(); - $table->text('description')->nullable(); - $table->boolean('is_default')->default(false); - $table->unsignedInteger('sort_order')->default(0); - $table->timestamps(); - $table->softDeletes(); - - $table->index(['is_default', 'sort_order']); - }); - } - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('helpdesk_kanban_boards'); - } -}; diff --git a/Helpdesk/Database/Migrations/2026_06_12_130010_create_helpdesk_kanban_board_statuses_table.php b/Helpdesk/Database/Migrations/2026_06_12_130010_create_helpdesk_kanban_board_statuses_table.php deleted file mode 100644 index 4146c1c6..00000000 --- a/Helpdesk/Database/Migrations/2026_06_12_130010_create_helpdesk_kanban_board_statuses_table.php +++ /dev/null @@ -1,31 +0,0 @@ -id(); - $table->foreignId('board_id')->constrained('helpdesk_kanban_boards')->cascadeOnDelete(); - $table->foreignId('status_id')->constrained('helpdesk_statuses')->cascadeOnDelete(); - $table->unsignedInteger('sort_order')->default(0); - $table->timestamps(); - - $table->unique(['board_id', 'status_id']); - $table->index(['board_id', 'sort_order']); - }); - } - } - - public function down(): void - { - Schema::dropIfExists('helpdesk_kanban_board_statuses'); - } -}; diff --git a/Helpdesk/Database/Migrations/2026_06_15_000100_add_redmineup_inspired_helpdesk_features.php b/Helpdesk/Database/Migrations/2026_06_15_000100_add_redmineup_inspired_helpdesk_features.php deleted file mode 100644 index c563f29a..00000000 --- a/Helpdesk/Database/Migrations/2026_06_15_000100_add_redmineup_inspired_helpdesk_features.php +++ /dev/null @@ -1,208 +0,0 @@ -id(); - $table->foreignId('company_id')->nullable()->constrained('companies')->nullOnDelete(); - $table->string('name', 160); - $table->string('slug', 180); - $table->string('type', 60)->default('service'); - $table->string('version', 80)->nullable(); - $table->string('release_channel', 80)->nullable(); - $table->text('description')->nullable(); - $table->boolean('is_active')->default(true); - $table->timestamps(); - $table->unique(['company_id', 'slug']); - }); - - - Schema::create('helpdesk_sla_policies', static function (Blueprint $table): void { - $table->id(); - $table->foreignId('company_id')->nullable()->constrained('companies')->nullOnDelete(); - $table->foreignId('priority_id')->nullable()->constrained('helpdesk_priorities')->nullOnDelete(); - $table->foreignId('category_id')->nullable()->constrained('helpdesk_categories')->nullOnDelete(); - $table->string('name', 160); - $table->string('slug', 180); - $table->unsignedInteger('first_response_minutes')->default(240); - $table->unsignedInteger('resolution_minutes')->default(2880); - $table->boolean('business_hours_only')->default(false); - $table->boolean('is_default')->default(false); - $table->boolean('is_active')->default(true); - $table->timestamps(); - $table->unique(['company_id', 'slug']); - $table->index(['priority_id', 'category_id', 'is_active']); - }); - - Schema::table('helpdesk_tickets', static function (Blueprint $table): void { - if (! Schema::hasColumn('helpdesk_tickets', 'sla_policy_id')) { - $table->foreignId('sla_policy_id')->nullable()->after('team_id')->constrained('helpdesk_sla_policies')->nullOnDelete(); - } - if (! Schema::hasColumn('helpdesk_tickets', 'product_id')) { - $table->foreignId('product_id')->nullable()->after('project_id')->constrained('helpdesk_products')->nullOnDelete(); - } - if (! Schema::hasColumn('helpdesk_tickets', 'first_response_at')) { - $table->timestamp('first_response_at')->nullable()->after('due_at'); - } - if (! Schema::hasColumn('helpdesk_tickets', 'sla_first_response_due_at')) { - $table->timestamp('sla_first_response_due_at')->nullable()->after('first_response_at'); - } - if (! Schema::hasColumn('helpdesk_tickets', 'sla_resolution_due_at')) { - $table->timestamp('sla_resolution_due_at')->nullable()->after('sla_first_response_due_at'); - } - if (! Schema::hasColumn('helpdesk_tickets', 'sla_first_response_breached_at')) { - $table->timestamp('sla_first_response_breached_at')->nullable()->after('sla_resolution_due_at'); - } - if (! Schema::hasColumn('helpdesk_tickets', 'sla_resolution_breached_at')) { - $table->timestamp('sla_resolution_breached_at')->nullable()->after('sla_first_response_breached_at'); - } - if (! Schema::hasColumn('helpdesk_tickets', 'public_replies_count')) { - $table->unsignedInteger('public_replies_count')->default(0)->after('sla_resolution_breached_at'); - } - if (! Schema::hasColumn('helpdesk_tickets', 'time_spent_minutes')) { - $table->unsignedInteger('time_spent_minutes')->default(0)->after('public_replies_count'); - } - - $table->index(['sla_policy_id', 'sla_resolution_due_at'], 'helpdesk_tickets_sla_policy_due_idx'); - $table->index(['product_id', 'completed_at'], 'helpdesk_tickets_product_closed_idx'); - }); - - Schema::table('helpdesk_workflow_logs', static function (Blueprint $table): void { - if (! Schema::hasColumn('helpdesk_workflow_logs', 'tracking_token')) { - $table->string('tracking_token', 64)->nullable()->unique()->after('status'); - } - if (! Schema::hasColumn('helpdesk_workflow_logs', 'first_opened_at')) { - $table->timestamp('first_opened_at')->nullable()->after('tracking_token'); - } - if (! Schema::hasColumn('helpdesk_workflow_logs', 'last_opened_at')) { - $table->timestamp('last_opened_at')->nullable()->after('first_opened_at'); - } - if (! Schema::hasColumn('helpdesk_workflow_logs', 'opened_count')) { - $table->unsignedInteger('opened_count')->default(0)->after('last_opened_at'); - } - }); - - Schema::create('helpdesk_canned_responses', static function (Blueprint $table): void { - $table->id(); - $table->foreignId('company_id')->nullable()->constrained('companies')->nullOnDelete(); - $table->foreignId('category_id')->nullable()->constrained('helpdesk_categories')->nullOnDelete(); - $table->foreignId('team_id')->nullable()->constrained('helpdesk_teams')->nullOnDelete(); - $table->string('name', 160); - $table->string('slug', 180); - $table->string('visibility', 40)->default('agents'); - $table->string('subject', 220)->nullable(); - $table->longText('body'); - $table->boolean('is_active')->default(true); - $table->unsignedInteger('sort_order')->default(0); - $table->timestamps(); - $table->unique(['company_id', 'slug']); - $table->index(['category_id', 'team_id', 'is_active']); - }); - - - - Schema::create('helpdesk_automation_rules', static function (Blueprint $table): void { - $table->id(); - $table->foreignId('company_id')->nullable()->constrained('companies')->nullOnDelete(); - $table->string('name', 160); - $table->string('slug', 180); - $table->string('match_sender', 190)->nullable(); - $table->string('match_subject', 220)->nullable(); - $table->string('match_mailbox', 190)->nullable(); - $table->foreignId('match_category_id')->nullable()->constrained('helpdesk_categories')->nullOnDelete(); - $table->foreignId('set_priority_id')->nullable()->constrained('helpdesk_priorities')->nullOnDelete(); - $table->foreignId('set_category_id')->nullable()->constrained('helpdesk_categories')->nullOnDelete(); - $table->foreignId('set_team_id')->nullable()->constrained('helpdesk_teams')->nullOnDelete(); - $table->foreignId('set_project_id')->nullable()->constrained('helpdesk_projects')->nullOnDelete(); - $table->foreignId('set_status_id')->nullable()->constrained('helpdesk_statuses')->nullOnDelete(); - $table->foreignId('set_agent_id')->nullable()->constrained('users')->nullOnDelete(); - $table->boolean('is_active')->default(true); - $table->unsignedInteger('sort_order')->default(0); - $table->timestamps(); - $table->unique(['company_id', 'slug']); - $table->index(['is_active', 'sort_order']); - }); - - Schema::create('helpdesk_ticket_sla_events', static function (Blueprint $table): void { - $table->id(); - $table->foreignId('ticket_id')->constrained('helpdesk_tickets')->cascadeOnDelete(); - $table->foreignId('sla_policy_id')->nullable()->constrained('helpdesk_sla_policies')->nullOnDelete(); - $table->string('event', 80); - $table->timestamp('due_at')->nullable(); - $table->timestamp('occurred_at')->nullable(); - $table->boolean('breached')->default(false); - $table->json('meta')->nullable(); - $table->timestamps(); - $table->index(['ticket_id', 'event']); - $table->index(['breached', 'due_at']); - }); - - Schema::create('helpdesk_ticket_ratings', static function (Blueprint $table): void { - $table->id(); - $table->foreignId('ticket_id')->constrained('helpdesk_tickets')->cascadeOnDelete(); - $table->foreignId('agent_id')->nullable()->constrained('users')->nullOnDelete(); - $table->foreignId('team_id')->nullable()->constrained('helpdesk_teams')->nullOnDelete(); - $table->foreignId('category_id')->nullable()->constrained('helpdesk_categories')->nullOnDelete(); - $table->unsignedTinyInteger('rating'); - $table->string('token', 80)->unique(); - $table->text('comment')->nullable(); - $table->timestamp('rated_at')->nullable(); - $table->timestamps(); - $table->index(['agent_id', 'rating']); - $table->index(['team_id', 'category_id']); - }); - - Schema::create('helpdesk_tags', static function (Blueprint $table): void { - $table->id(); - $table->foreignId('company_id')->nullable()->constrained('companies')->nullOnDelete(); - $table->string('name', 80); - $table->string('slug', 100); - $table->string('color', 30)->default('zinc'); - $table->timestamps(); - $table->unique(['company_id', 'slug']); - }); - - Schema::create('helpdesk_ticket_tag', static function (Blueprint $table): void { - $table->foreignId('ticket_id')->constrained('helpdesk_tickets')->cascadeOnDelete(); - $table->foreignId('tag_id')->constrained('helpdesk_tags')->cascadeOnDelete(); - $table->primary(['ticket_id', 'tag_id']); - }); - - Schema::create('helpdesk_checklist_templates', static function (Blueprint $table): void { - $table->id(); - $table->foreignId('company_id')->nullable()->constrained('companies')->nullOnDelete(); - $table->foreignId('category_id')->nullable()->constrained('helpdesk_categories')->nullOnDelete(); - $table->foreignId('project_id')->nullable()->constrained('helpdesk_projects')->nullOnDelete(); - $table->string('name', 160); - $table->string('slug', 180); - $table->json('items'); - $table->boolean('is_active')->default(true); - $table->timestamps(); - $table->unique(['company_id', 'slug']); - $table->index(['category_id', 'project_id', 'is_active']); - }); - - Schema::create('helpdesk_checklist_items', static function (Blueprint $table): void { - $table->id(); - $table->foreignId('ticket_id')->constrained('helpdesk_tickets')->cascadeOnDelete(); - $table->foreignId('template_id')->nullable()->constrained('helpdesk_checklist_templates')->nullOnDelete(); - $table->string('title', 220); - $table->text('description')->nullable(); - $table->boolean('is_completed')->default(false); - $table->foreignId('completed_by')->nullable()->constrained('users')->nullOnDelete(); - $table->timestamp('completed_at')->nullable(); - $table->unsignedInteger('sort_order')->default(0); - $table->timestamps(); - $table->index(['ticket_id', 'is_completed']); - }); - - } -}; diff --git a/Helpdesk/Database/Migrations/2026_06_15_000200_add_helpdesk_git_webhook_ingestion.php b/Helpdesk/Database/Migrations/2026_06_15_000200_add_helpdesk_git_webhook_ingestion.php deleted file mode 100644 index 49c06d9b..00000000 --- a/Helpdesk/Database/Migrations/2026_06_15_000200_add_helpdesk_git_webhook_ingestion.php +++ /dev/null @@ -1,39 +0,0 @@ -id(); - $table->foreignId('repository_id')->constrained('helpdesk_git_repositories')->cascadeOnDelete(); - $table->string('provider', 40); - $table->string('delivery_id', 190)->nullable(); - $table->string('event', 120)->nullable(); - $table->string('status', 40)->default('accepted'); - $table->string('message', 500)->nullable(); - $table->unsignedInteger('commit_count')->default(0); - $table->unsignedInteger('linked_count')->default(0); - $table->unsignedInteger('ignored_count')->default(0); - $table->json('payload')->nullable(); - $table->timestamp('received_at')->nullable(); - $table->timestamps(); - - $table->index(['repository_id', 'status', 'received_at']); - $table->index(['provider', 'event']); - }); - } -}; diff --git a/Helpdesk/Database/Seeders/.gitkeep b/Helpdesk/Database/Seeders/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Helpdesk/Database/Seeders/HelpdeskDatabaseSeeder.php b/Helpdesk/Database/Seeders/HelpdeskDatabaseSeeder.php deleted file mode 100644 index 3c880c53..00000000 --- a/Helpdesk/Database/Seeders/HelpdeskDatabaseSeeder.php +++ /dev/null @@ -1,257 +0,0 @@ -seedStatuses(); - $this->seedPriorities(); - $this->seedCategories(); - $this->seedTeamsAndProjects(); - $this->seedKanbanBoards(); - $this->seedWorkflowTemplates(); - $this->seedRedmineInspiredDefaults(); - } - - /** - * Seed ticket workflow statuses. - * - * @return void - */ - private function seedStatuses(): void - { - foreach ([ - ['name' => 'New', 'slug' => 'new', 'color' => 'sky', 'sort_order' => 5, 'is_closed' => false], - ['name' => 'Open', 'slug' => 'open', 'color' => 'blue', 'sort_order' => 10, 'is_closed' => false], - ['name' => 'In Progress', 'slug' => 'in-progress', 'color' => 'amber', 'sort_order' => 20, 'is_closed' => false], - ['name' => 'Stalled', 'slug' => 'stalled', 'color' => 'rose', 'sort_order' => 30, 'is_closed' => false], - ['name' => 'On-Hold', 'slug' => 'on-hold', 'color' => 'violet', 'sort_order' => 40, 'is_closed' => false], - ['name' => 'Closed', 'slug' => 'closed', 'color' => 'emerald', 'sort_order' => 90, 'is_closed' => true], - ] as $row) { - Status::query()->updateOrCreate(['slug' => $row['slug']], $row); - } - } - - /** - * Seed ticket priorities. - * - * @return void - */ - private function seedPriorities(): void - { - foreach ([ - ['name' => 'High', 'slug' => 'high', 'color' => 'red', 'sort_order' => 10], - ['name' => 'Medium', 'slug' => 'medium', 'color' => 'amber', 'sort_order' => 20], - ['name' => 'Low', 'slug' => 'low', 'color' => 'emerald', 'sort_order' => 30], - ] as $row) { - Priority::query()->updateOrCreate(['slug' => $row['slug']], $row); - } - } - - /** - * Seed ticket categories. - * - * @return void - */ - private function seedCategories(): void - { - foreach ([ - ['name' => 'Development', 'color' => 'indigo', 'description' => 'Software changes, enhancements and development work.'], - ['name' => 'Systems Deployment', 'color' => 'cyan', 'description' => 'Deployment, release, infrastructure and environment requests.'], - ['name' => 'Concepts', 'color' => 'purple', 'description' => 'Ideas, concepts, design discussion and future improvements.'], - ['name' => 'Bug', 'color' => 'rose', 'description' => 'Defects, regressions and incorrect behaviour.'], - ] as $row) { - Category::query()->updateOrCreate( - ['company_id' => null, 'slug' => Str::slug($row['name'])], - $row + ['company_id' => null, 'slug' => Str::slug($row['name'])] - ); - } - } - - /** - * Seed the default support team and a default project. - * - * @return void - */ - private function seedTeamsAndProjects(): void - { - $team = Team::query()->updateOrCreate( - ['company_id' => null, 'slug' => 'support'], - [ - 'company_id' => null, - 'name' => 'Support', - 'slug' => 'support', - 'color' => 'blue', - 'description' => 'Default support team for triage, ownership and ticket routing.', - ] - ); - - Project::query()->updateOrCreate( - ['company_id' => null, 'slug' => 'support-desk'], - [ - 'company_id' => null, - 'team_id' => $team->id, - 'title' => 'Support Desk', - 'slug' => 'support-desk', - 'color' => 'indigo', - 'description' => 'Default project for operational helpdesk tickets.', - ] - ); - } - - /** - * Seed the default configurable Kanban board. - * - * @return void - */ - private function seedKanbanBoards(): void - { - KanbanBoard::query()->where('is_default', true)->update(['is_default' => false]); - - $board = KanbanBoard::query()->updateOrCreate( - ['slug' => 'standard-support'], - [ - 'name' => 'Standard Support', - 'slug' => 'standard-support', - 'description' => 'Default Helpdesk workflow board covering new work through closure.', - 'is_default' => true, - 'sort_order' => 1, - ] - ); - - $statuses = Status::query() - ->whereIn('slug', ['new', 'open', 'in-progress', 'stalled', 'on-hold', 'closed']) - ->orderBy('sort_order') - ->get(); - - $sync = []; - - foreach ($statuses as $index => $status) { - $sync[$status->id] = ['sort_order' => ($index + 1) * 10]; - } - - $board->statuses()->syncWithoutDetaching($sync); - } - - /** - * Seed default professional workflow email templates. - * - * @return void - */ - private function seedWorkflowTemplates(): void - { - foreach ([ - ['name' => 'Ticket raised requester notice', 'event' => 'ticket.created', 'subject' => 'Ticket {{ ticket.reference }} has been raised', 'html_body' => '

Hello {{ requester.name }},

Your ticket {{ ticket.reference }} has been raised successfully.

Subject: {{ ticket.subject }}

Priority: {{ ticket.priority }}

', 'to_role' => 'requester'], - ['name' => 'Ticket updated requester notice', 'event' => 'ticket.updated', 'subject' => 'Ticket {{ ticket.reference }} has been updated', 'html_body' => '

Hello {{ requester.name }},

Your ticket {{ ticket.reference }} has been updated.

Status: {{ ticket.status }}

Priority: {{ ticket.priority }}

', 'to_role' => 'requester'], - ['name' => 'Ticket response requester notice', 'event' => 'ticket.commented', 'subject' => 'New response on ticket {{ ticket.reference }}', 'html_body' => '

Hello {{ requester.name }},

A new response has been added to {{ ticket.reference }}.

Subject: {{ ticket.subject }}

', 'to_role' => 'requester'], - ['name' => 'Ticket closed requester notice', 'event' => 'ticket.closed', 'subject' => 'Ticket {{ ticket.reference }} has been closed', 'html_body' => '

Hello {{ requester.name }},

Your ticket {{ ticket.reference }} has been closed.

Thank you for using the support desk.

', 'to_role' => 'requester'], - ] as $template) { - WorkflowTemplate::query()->updateOrCreate( - ['name' => $template['name'], 'event' => $template['event']], - $template + ['text_body' => null, 'cc' => null, 'bcc' => null, 'is_active' => true] - ); - } - } - - /** - * Seed RedmineUP-inspired professional helpdesk defaults. - */ - private function seedRedmineInspiredDefaults(): void - { - $medium = Priority::query()->where('slug', 'medium')->first(); - $bug = Category::query()->where('slug', 'bug')->first(); - - SlaPolicy::query()->updateOrCreate( - ['company_id' => null, 'slug' => 'standard-support-sla'], - [ - 'company_id' => null, - 'priority_id' => $medium?->id, - 'category_id' => null, - 'name' => 'Standard Support SLA', - 'slug' => 'standard-support-sla', - 'first_response_minutes' => 240, - 'resolution_minutes' => 2880, - 'business_hours_only' => false, - 'is_default' => true, - 'is_active' => true, - ] - ); - - CannedResponse::query()->updateOrCreate( - ['company_id' => null, 'slug' => 'professional-acknowledgement'], - [ - 'company_id' => null, - 'category_id' => null, - 'team_id' => null, - 'name' => 'Professional acknowledgement', - 'slug' => 'professional-acknowledgement', - 'visibility' => 'agents', - 'subject' => null, - 'body' => '

Hello {{ requester.name }},

Thank you for the update on {{ ticket.reference }}. I am reviewing this now and will come back with the next action shortly.

Regards,
{{ agent.name }}

', - 'is_active' => true, - 'sort_order' => 10, - ] - ); - - ChecklistTemplate::query()->updateOrCreate( - ['company_id' => null, 'slug' => 'bug-triage-checklist'], - [ - 'company_id' => null, - 'category_id' => $bug?->id, - 'project_id' => null, - 'name' => 'Bug triage checklist', - 'slug' => 'bug-triage-checklist', - 'items' => ['Confirm reproduction steps', 'Check recent deployment/build', 'Identify affected tenant/company', 'Record fix or workaround'], - 'is_active' => true, - ] - ); - - foreach ([ - ['name' => 'Escalated', 'slug' => 'escalated', 'color' => 'red'], - ['name' => 'Waiting on customer', 'slug' => 'waiting-on-customer', 'color' => 'amber'], - ] as $tag) { - Tag::query()->updateOrCreate(['company_id' => null, 'slug' => $tag['slug']], $tag + ['company_id' => null]); - } - - Product::query()->updateOrCreate( - ['company_id' => null, 'slug' => 'wms-platform'], - [ - 'company_id' => null, - 'name' => 'WMS Platform', - 'slug' => 'wms-platform', - 'type' => 'service', - 'version' => null, - 'release_channel' => 'core', - 'description' => 'Default service record for WMS platform support tickets.', - 'is_active' => true, - ] - ); - } - -} - diff --git a/Helpdesk/Database/factories/.gitkeep b/Helpdesk/Database/factories/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Helpdesk/Docs/GIT_WEBHOOK_TRACKING.md b/Helpdesk/Docs/GIT_WEBHOOK_TRACKING.md deleted file mode 100644 index 7e306245..00000000 --- a/Helpdesk/Docs/GIT_WEBHOOK_TRACKING.md +++ /dev/null @@ -1,122 +0,0 @@ -# Helpdesk Git Webhook Tracking - -The Helpdesk module can ingest signed Git push webhooks and automatically link commits to tickets when commit messages, branch names, or commit URLs contain a ticket reference such as `HD-2026-00012`. - -## 1. Register the repository - -Open the Helpdesk settings page: - -```text -/helpdesk/settings -``` - -In the **Git** section, create a repository with: - -| Field | Value | -| --- | --- | -| Provider | `github`, `gitlab`, or `git` | -| Remote URL | Your repository remote URL | -| Default branch | Usually `develop` or `main` | -| Webhook secret | A strong random secret, for example `openssl rand -hex 32` | - -After saving, the repository table shows the webhook endpoint: - -```text -/api/helpdesk/git/webhook/{repository_id} -``` - -## 2. Ticket reference convention - -Use the Helpdesk ticket reference in branch names and commit messages: - -```bash -git checkout -b feature/HD-2026-00012-canned-responses -git commit -m "HD-2026-00012 Add canned response macro rendering" -``` - -The parser recognises this format: - -```text -HD-YYYY-00000 -``` - -The first matching reference is linked to the stored commit when the ticket exists and matches the repository company/project scope. - -## 3. GitHub setup - -In GitHub, open the repository settings and add a webhook: - -| GitHub field | Value | -| --- | --- | -| Payload URL | `https://your-app.test/api/helpdesk/git/webhook/{repository_id}` | -| Content type | `application/json` | -| Secret | Same value stored in Helpdesk | -| Events | Push events | - -The module validates `X-Hub-Signature-256` using HMAC SHA-256 and rejects unsigned or invalid requests. - -## 4. GitLab setup - -In GitLab, open **Settings > Webhooks** and add: - -| GitLab field | Value | -| --- | --- | -| URL | `https://your-app.test/api/helpdesk/git/webhook/{repository_id}` | -| Secret token | Same value stored in Helpdesk | -| Trigger | Push events | - -The module validates `X-Gitlab-Token` and rejects invalid requests. - -## 5. Generic Git provider setup - -For a simple internal provider, send a JSON push payload to the same endpoint with: - -```http -X-Helpdesk-Token: your-secret -X-Helpdesk-Event: push -``` - -The payload should include: - -```json -{ - "ref": "refs/heads/develop", - "commits": [ - { - "id": "full-commit-hash", - "message": "HD-2026-00012 Fix ticket workflow", - "url": "https://example.test/commit/full-commit-hash", - "timestamp": "2026-06-15T10:00:00+00:00", - "author": { - "name": "Support Agent", - "email": "agent@example.com" - } - } - ] -} -``` - -## 6. Audit trail - -Every accepted, ignored, or rejected webhook delivery is recorded in: - -```text -helpdesk_git_webhook_logs -``` - -Stored data includes provider, event, delivery id, status, message, commit count, linked count, ignored count, payload, and received timestamp. - -## 7. Verification commands - -Run the focused feature test after installing the updated module: - -```bash -php artisan test Modules/Helpdesk/Tests/Feature/GitWebhookIngestionTest.php -``` - -Then run the full Helpdesk test set: - -```bash -php artisan test Modules/Helpdesk/Tests/Feature Modules/Helpdesk/Tests/Unit -php artisan dusk Modules/Helpdesk/Tests/Browser -``` diff --git a/Helpdesk/Docs/INSTALL.md b/Helpdesk/Docs/INSTALL.md deleted file mode 100644 index c799944e..00000000 --- a/Helpdesk/Docs/INSTALL.md +++ /dev/null @@ -1,99 +0,0 @@ -# Helpdesk Module Install - -## 1. Copy the module - -Copy `Modules/Helpdesk` into your Laravel 13 project. - -## 2. Register the provider - -If your module loader reads `module.json`, no manual provider entry is needed. Otherwise add this provider to `bootstrap/providers.php`: - -```php -Modules\Helpdesk\Providers\HelpdeskServiceProvider::class, -``` - -## 3. Add the helpdesk disk - -Add this to `config/filesystems.php` disks: - -```php -'helpdesk' => [ - 'driver' => 'local', - 'root' => storage_path('app/public/helpdesk'), - 'url' => env('APP_URL') . '/storage/helpdesk', - 'visibility' => 'public', -], -``` - -Then run: - -```bash -php artisan storage:link -``` - -## 4. Add relations to `App\Models\User` - -```php -use Modules\Helpdesk\Concerns\HasHelpdeskTickets; - -class User extends Authenticatable -{ - use HasHelpdeskTickets; -} -``` - -Add casts if you want strict booleans: - -```php -protected function casts(): array -{ - return [ - 'email_verified_at' => 'datetime', - 'password' => 'hashed', - 'helpdesk_agent' => 'boolean', - 'helpdesk_admin' => 'boolean', - ]; -} -``` - -## 5. Run migrations and seeders - -```bash -php artisan migrate -php artisan db:seed --class="Modules\\Helpdesk\\database\\seeders\\HelpdeskDatabaseSeeder" -``` - -## 6. Add dashboard widget - -Place this where you want it on your dashboard: - -```blade -@include('helpdesk::dashboard.card') -``` - -or directly: - -```blade - -``` - -## 7. Navigation link - -```blade - - Helpdesk - -``` - -## 8. Tests - -```bash -php artisan test Modules/Helpdesk/tests/Feature -php artisan dusk Modules/Helpdesk/tests/Browser -``` -## 9. Git webhook tracking - -The module supports signed Git push webhooks for repository commit tracking. Register repositories in `/helpdesk/settings`, copy the generated webhook endpoint from the Git table, and configure GitHub/GitLab push webhooks with the same secret stored on the repository. - -See `Docs/GIT_WEBHOOK_TRACKING.md` for provider-specific setup, ticket reference conventions, and verification commands. - diff --git a/Helpdesk/Docs/WMS_SUPPORT_MENU_PATCH.md b/Helpdesk/Docs/WMS_SUPPORT_MENU_PATCH.md deleted file mode 100644 index 4c26b9c4..00000000 --- a/Helpdesk/Docs/WMS_SUPPORT_MENU_PATCH.md +++ /dev/null @@ -1,16 +0,0 @@ -# WMS Support Menu Patch - -Add this block to the main application sidebar inside `