From 2c2af4c50e97e29fb84ed21f0ecb4601f79096b9 Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Tue, 16 Jun 2026 23:21:49 +0100 Subject: [PATCH] structure and style updates --- Modules/AuditTrail/Config/config.php | 5 +- Modules/AuditTrail/Helpers/Helper.php | 1 + .../Http/Controllers/AuditTrailController.php | 17 ++-- .../Providers/AuditTrailServiceProvider.php | 97 +++++++------------ .../Providers/RouteServiceProvider.php | 2 +- Modules/AuditTrail/Routes/api.php | 2 +- Modules/AuditTrail/Routes/web.php | 2 +- Modules/AuditTrail/Support/ModuleInfo.php | 4 +- Modules/Countries/Helpers/Helper.php | 1 + .../Http/Controllers/CountriesController.php | 3 +- .../Http/Requests/StoreCountryRequest.php | 1 - .../Http/Requests/UpdateCountryRequest.php | 1 - .../Providers/CountriesServiceProvider.php | 1 + Modules/Countries/Rules/ValidCountryCode.php | 4 +- .../Rules/ValidIso3166CountryCode.php | 4 +- .../Rules/ValidIso3166SubdivisionCode.php | 5 +- Modules/Countries/Support/Iso3166.php | 5 + Modules/Countries/Support/ModuleInfo.php | 4 +- .../Tests/Feature/Iso3166SupportTest.php | 2 - .../Controllers/DocumentationController.php | 5 +- .../StoreDocumentationDocumentRequest.php | 4 +- .../UpdateDocumentationDocumentRequest.php | 4 +- .../Models/DocumentationAttachment.php | 6 +- .../Models/DocumentationCategory.php | 4 +- .../Models/DocumentationDocument.php | 10 +- .../Providers/RouteServiceProvider.php | 2 +- .../Tests/Feature/DocumentationModuleTest.php | 21 ++-- Modules/Helpdesk/Config/config.php | 2 +- ...d_redmineup_inspired_helpdesk_features.php | 3 - .../Controllers/TicketControlsController.php | 2 +- Modules/Helpdesk/Livewire/KanbanBoard.php | 5 +- Modules/Helpdesk/Livewire/SettingsManager.php | 30 +++--- Modules/Helpdesk/Livewire/TicketCreate.php | 7 +- Modules/Helpdesk/Livewire/TicketEdit.php | 1 - Modules/Helpdesk/Livewire/TicketShow.php | 5 +- Modules/Helpdesk/Models/AutomationRule.php | 6 +- Modules/Helpdesk/Models/CannedResponse.php | 18 +++- Modules/Helpdesk/Models/ChecklistItem.php | 12 ++- Modules/Helpdesk/Models/ChecklistTemplate.php | 6 +- Modules/Helpdesk/Models/KanbanBoard.php | 2 +- Modules/Helpdesk/Models/Product.php | 12 ++- Modules/Helpdesk/Models/SlaPolicy.php | 18 +++- Modules/Helpdesk/Models/Tag.php | 12 ++- Modules/Helpdesk/Models/Ticket.php | 1 - Modules/Helpdesk/Models/TicketRating.php | 6 +- Modules/Helpdesk/Models/TicketSlaEvent.php | 6 +- Modules/Helpdesk/Routes/web.php | 2 +- .../Seeders/HelpdeskDatabaseSeeder.php | 1 - .../Services/CannedResponseRenderer.php | 1 - .../Services/GitWebhookIngestionService.php | 17 +--- .../Services/WorkflowNotificationService.php | 2 +- .../Browser/HelpdeskTicketBrowserTest.php | 1 + .../Tests/Feature/KanbanBoardTest.php | 2 +- .../RedmineInspiredHelpdeskFeatureTest.php | 2 - Modules/Routes/web.php | 2 +- ...06_15_074154_create_activity_log_table.php | 4 +- ...add_event_column_to_activity_log_table.php | 4 +- ...atch_uuid_column_to_activity_log_table.php | 4 +- routes/web.php | 7 +- tests/DuskTestCase.php | 5 +- 60 files changed, 209 insertions(+), 216 deletions(-) diff --git a/Modules/AuditTrail/Config/config.php b/Modules/AuditTrail/Config/config.php index d0401f3d..70ae2043 100644 --- a/Modules/AuditTrail/Config/config.php +++ b/Modules/AuditTrail/Config/config.php @@ -2,7 +2,6 @@ return [ 'name' => 'AuditTrail', - 'features' => - [ - ], + 'features' => [ + ], ]; diff --git a/Modules/AuditTrail/Helpers/Helper.php b/Modules/AuditTrail/Helpers/Helper.php index cf1c5008..521ad22b 100644 --- a/Modules/AuditTrail/Helpers/Helper.php +++ b/Modules/AuditTrail/Helpers/Helper.php @@ -1,4 +1,5 @@ registerTranslations(); $this->registerConfig(); @@ -48,8 +36,6 @@ class AuditTrailServiceProvider extends ServiceProvider /** * Register the service provider. - * - * @return void */ public function register(): void { @@ -58,8 +44,6 @@ class AuditTrailServiceProvider extends ServiceProvider /** * Register the helpers. - * - * @return void */ protected function registerHelpers(): void { @@ -68,71 +52,64 @@ class AuditTrailServiceProvider extends ServiceProvider } } - /** + /** * Dynamically Register Commands. - * - * @return void */ public function registerCommands(): void - { - if (!$this->app->runningInConsole()) { - return; - } - $commandPath = __DIR__ . '/../Console'; - $commands = collect(File::allFiles($commandPath)) - ->map(function ($file) { - $class = 'Modules\\' . $this->moduleName . '\\Console\\' . $file->getFilenameWithoutExtension(); - return $class; - }) - ->filter(function ($class) { - return class_exists($class); - }) - ->values() - ->all(); - $this->commands($commands); + { + if (! $this->app->runningInConsole()) { + return; } + $commandPath = __DIR__.'/../Console'; + $commands = collect(File::allFiles($commandPath)) + ->map(function ($file) { + $class = 'Modules\\'.$this->moduleName.'\\Console\\'.$file->getFilenameWithoutExtension(); + + return $class; + }) + ->filter(function ($class) { + return class_exists($class); + }) + ->values() + ->all(); + $this->commands($commands); + } /** * Register config. - * - * @return void */ protected function registerConfig(): void { $this->publishes([ - module_path(ModuleInfo::name(), 'Config/config.php') => config_path( ModuleInfo::nameLower(). '.php'), + module_path(ModuleInfo::name(), 'Config/config.php') => config_path(ModuleInfo::nameLower().'.php'), ], 'config'); $this->mergeConfigFrom( - module_path(ModuleInfo::name(), 'Config/config.php'), ModuleInfo::nameLower() + module_path(ModuleInfo::name(), 'Config/config.php'), ModuleInfo::nameLower() ); } /** * Register views. - * - * @return void */ public function registerViews(): void { - $viewPath = resource_path('views/modules/' . ModuleInfo::nameLower()); + $viewPath = resource_path('views/modules/'.ModuleInfo::nameLower()); $sourcePath = module_path(ModuleInfo::name(), 'Resources/views'); $this->publishes([ - $sourcePath => $viewPath - ], ['views', ModuleInfo::nameLower() . '-module-views']); - $this->loadViewsFrom(array_merge($this->getPublishableViewPaths(), [$sourcePath]), ModuleInfo::nameLower()); + $sourcePath => $viewPath, + ], ['views', ModuleInfo::nameLower().'-module-views']); + $this->loadViewsFrom(array_merge($this->getPublishableViewPaths(), [$sourcePath]), ModuleInfo::nameLower()); } /** * Register translations. - * - * @return void */ public function registerTranslations(): void { - $langPath = resource_path('lang/modules/' . ModuleInfo::nameLower()); + $langPath = resource_path('lang/modules/'.ModuleInfo::nameLower()); if (is_dir($langPath)) { - $this->loadTranslationsFrom($langPath, ModuleInfo::nameLower()); + $this->loadTranslationsFrom($langPath, ModuleInfo::nameLower()); } else { $this->loadTranslationsFrom(module_path(ModuleInfo::name(), 'Resources/lang'), ModuleInfo::nameLower()); } @@ -140,16 +117,16 @@ class AuditTrailServiceProvider extends ServiceProvider /** * Regsiter View Paths - * @return array */ private function getPublishableViewPaths(): array { $paths = []; foreach (Config::get('view.paths') as $path) { - if (is_dir($path . '/modules/' . ModuleInfo::nameLower())) { - $paths[] = $path . '/modules/' . ModuleInfo::nameLower(); + if (is_dir($path.'/modules/'.ModuleInfo::nameLower())) { + $paths[] = $path.'/modules/'.ModuleInfo::nameLower(); } } + return $paths; } } diff --git a/Modules/AuditTrail/Providers/RouteServiceProvider.php b/Modules/AuditTrail/Providers/RouteServiceProvider.php index 9c3c3243..6a73e6d2 100644 --- a/Modules/AuditTrail/Providers/RouteServiceProvider.php +++ b/Modules/AuditTrail/Providers/RouteServiceProvider.php @@ -2,8 +2,8 @@ namespace Modules\AuditTrail\Providers; -use Illuminate\Support\Facades\Route; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; +use Illuminate\Support\Facades\Route; class RouteServiceProvider extends ServiceProvider { diff --git a/Modules/AuditTrail/Routes/api.php b/Modules/AuditTrail/Routes/api.php index 823149fc..321b127f 100644 --- a/Modules/AuditTrail/Routes/api.php +++ b/Modules/AuditTrail/Routes/api.php @@ -15,4 +15,4 @@ use Illuminate\Http\Request; Route::middleware('auth:api')->get('/audittrail', function (Request $request) { return $request->user(); -}); \ No newline at end of file +}); diff --git a/Modules/AuditTrail/Routes/web.php b/Modules/AuditTrail/Routes/web.php index b90a4e7e..37ff097b 100644 --- a/Modules/AuditTrail/Routes/web.php +++ b/Modules/AuditTrail/Routes/web.php @@ -11,6 +11,6 @@ | */ -Route::prefix('audittrail')->group(function() { +Route::prefix('audittrail')->group(function () { Route::get('/', 'AuditTrailController@index'); }); diff --git a/Modules/AuditTrail/Support/ModuleInfo.php b/Modules/AuditTrail/Support/ModuleInfo.php index a3535197..7c2313a8 100644 --- a/Modules/AuditTrail/Support/ModuleInfo.php +++ b/Modules/AuditTrail/Support/ModuleInfo.php @@ -4,11 +4,10 @@ namespace Modules\AuditTrail\Support; class ModuleInfo { - protected static string $moduleName = 'AuditTrail'; + protected static string $moduleName = 'AuditTrail'; /** * defines the Module Name - * @return string */ public static function name(): string { @@ -17,7 +16,6 @@ class ModuleInfo /** * define the lowercase version - * @return string */ public static function nameLower(): string { diff --git a/Modules/Countries/Helpers/Helper.php b/Modules/Countries/Helpers/Helper.php index 0128d543..6b864bc4 100644 --- a/Modules/Countries/Helpers/Helper.php +++ b/Modules/Countries/Helpers/Helper.php @@ -1,4 +1,5 @@ new Country()]); + return view('countries::create', ['country' => new Country]); } /** @@ -112,7 +112,6 @@ class CountriesController extends Controller /** * Build the database payload from validated request data. * - * @param StoreCountryRequest|UpdateCountryRequest $request * @return array */ private function payload(StoreCountryRequest|UpdateCountryRequest $request): array diff --git a/Modules/Countries/Http/Requests/StoreCountryRequest.php b/Modules/Countries/Http/Requests/StoreCountryRequest.php index 28d109fb..7eddbfa0 100644 --- a/Modules/Countries/Http/Requests/StoreCountryRequest.php +++ b/Modules/Countries/Http/Requests/StoreCountryRequest.php @@ -16,7 +16,6 @@ class StoreCountryRequest extends FormRequest return (bool) $this->user()?->isMpbaPlatformAdmin(); } - /** * Normalise ISO code inputs before validation. */ diff --git a/Modules/Countries/Http/Requests/UpdateCountryRequest.php b/Modules/Countries/Http/Requests/UpdateCountryRequest.php index dcb02abf..83f58acb 100644 --- a/Modules/Countries/Http/Requests/UpdateCountryRequest.php +++ b/Modules/Countries/Http/Requests/UpdateCountryRequest.php @@ -16,7 +16,6 @@ class UpdateCountryRequest extends FormRequest return (bool) $this->user()?->isMpbaPlatformAdmin(); } - /** * Normalise ISO code inputs before validation. */ diff --git a/Modules/Countries/Providers/CountriesServiceProvider.php b/Modules/Countries/Providers/CountriesServiceProvider.php index 1adc41c6..711fc9a5 100644 --- a/Modules/Countries/Providers/CountriesServiceProvider.php +++ b/Modules/Countries/Providers/CountriesServiceProvider.php @@ -125,6 +125,7 @@ class CountriesServiceProvider extends ServiceProvider if (is_dir($publishedPath)) { $this->loadTranslationsFrom($publishedPath, ModuleInfo::nameLower()); + return; } diff --git a/Modules/Countries/Rules/ValidCountryCode.php b/Modules/Countries/Rules/ValidCountryCode.php index 64e99321..e9581393 100644 --- a/Modules/Countries/Rules/ValidCountryCode.php +++ b/Modules/Countries/Rules/ValidCountryCode.php @@ -7,9 +7,7 @@ use Illuminate\Contracts\Validation\ValidationRule; class ValidCountryCode implements ValidationRule { - public function __construct(private readonly bool $activeOnly = true) - { - } + public function __construct(private readonly bool $activeOnly = true) {} public function validate(string $attribute, mixed $value, Closure $fail): void { diff --git a/Modules/Countries/Rules/ValidIso3166CountryCode.php b/Modules/Countries/Rules/ValidIso3166CountryCode.php index 06606fd5..be609eb5 100644 --- a/Modules/Countries/Rules/ValidIso3166CountryCode.php +++ b/Modules/Countries/Rules/ValidIso3166CountryCode.php @@ -11,13 +11,13 @@ class ValidIso3166CountryCode implements ValidationRule public function __construct( private readonly bool $activeOnly = true, private readonly string $format = 'alpha2', - ) { - } + ) {} public function validate(string $attribute, mixed $value, Closure $fail): void { if (! is_string($value) && ! is_int($value)) { $fail('The :attribute must be a valid ISO 3166 country code.'); + return; } diff --git a/Modules/Countries/Rules/ValidIso3166SubdivisionCode.php b/Modules/Countries/Rules/ValidIso3166SubdivisionCode.php index dce898d8..0f857218 100644 --- a/Modules/Countries/Rules/ValidIso3166SubdivisionCode.php +++ b/Modules/Countries/Rules/ValidIso3166SubdivisionCode.php @@ -8,14 +8,13 @@ use Modules\Countries\Models\CountrySubdivision; class ValidIso3166SubdivisionCode implements ValidationRule { - public function __construct(private readonly bool $activeOnly = true) - { - } + public function __construct(private readonly bool $activeOnly = true) {} public function validate(string $attribute, mixed $value, Closure $fail): void { if (! is_string($value) || ! preg_match('/^[A-Z]{2}-[A-Z0-9]{1,6}$/', strtoupper($value))) { $fail('The :attribute must be a valid ISO 3166-2 subdivision code.'); + return; } diff --git a/Modules/Countries/Support/Iso3166.php b/Modules/Countries/Support/Iso3166.php index 6d89c723..4da88a97 100644 --- a/Modules/Countries/Support/Iso3166.php +++ b/Modules/Countries/Support/Iso3166.php @@ -7,10 +7,15 @@ use InvalidArgumentException; final class Iso3166 { public const STATUS_OFFICIAL = 'official'; + public const STATUS_RESERVED = 'reserved'; + public const STATUS_TRANSITIONAL = 'transitional'; + public const STATUS_DELETED = 'deleted'; + public const STATUS_USER_ASSIGNED = 'user_assigned'; + public const STATUS_EXCEPTIONALLY_RESERVED = 'exceptionally_reserved'; /** @return list */ diff --git a/Modules/Countries/Support/ModuleInfo.php b/Modules/Countries/Support/ModuleInfo.php index aefa483b..75a4cf43 100644 --- a/Modules/Countries/Support/ModuleInfo.php +++ b/Modules/Countries/Support/ModuleInfo.php @@ -4,11 +4,10 @@ namespace Modules\Countries\Support; class ModuleInfo { - protected static string $moduleName = 'Countries'; + protected static string $moduleName = 'Countries'; /** * defines the Module Name - * @return string */ public static function name(): string { @@ -17,7 +16,6 @@ class ModuleInfo /** * define the lowercase version - * @return string */ public static function nameLower(): string { diff --git a/Modules/Countries/Tests/Feature/Iso3166SupportTest.php b/Modules/Countries/Tests/Feature/Iso3166SupportTest.php index db641565..6ce574c7 100644 --- a/Modules/Countries/Tests/Feature/Iso3166SupportTest.php +++ b/Modules/Countries/Tests/Feature/Iso3166SupportTest.php @@ -4,8 +4,6 @@ namespace Modules\Countries\Tests\Feature; use Illuminate\Foundation\Testing\RefreshDatabase; use Modules\Countries\Database\Seeders\CountriesDatabaseSeeder; -use Modules\Countries\Models\Country; -use Modules\Countries\Models\CountrySubdivision; use Modules\Countries\Rules\ValidIso3166CountryCode; use Modules\Countries\Rules\ValidIso3166SubdivisionCode; use Tests\TestCase; diff --git a/Modules/Documentation/Http/Controllers/DocumentationController.php b/Modules/Documentation/Http/Controllers/DocumentationController.php index 62071bea..cc899fd2 100644 --- a/Modules/Documentation/Http/Controllers/DocumentationController.php +++ b/Modules/Documentation/Http/Controllers/DocumentationController.php @@ -9,9 +9,9 @@ use Illuminate\Routing\Controller; use Illuminate\Support\Facades\Storage; use Modules\Documentation\Http\Requests\StoreDocumentationDocumentRequest; use Modules\Documentation\Http\Requests\UpdateDocumentationDocumentRequest; +use Modules\Documentation\Models\DocumentationAttachment; use Modules\Documentation\Models\DocumentationCategory; use Modules\Documentation\Models\DocumentationDocument; -use Modules\Documentation\Models\DocumentationAttachment; class DocumentationController extends Controller { @@ -31,7 +31,7 @@ class DocumentationController extends Controller } if ($request->filled('search')) { - $search = '%' . $request->string('search')->trim()->toString() . '%'; + $search = '%'.$request->string('search')->trim()->toString().'%'; $query->where(function ($builder) use ($search): void { $builder->where('title', 'like', $search) ->orWhere('summary', 'like', $search) @@ -197,7 +197,6 @@ class DocumentationController extends Controller return redirect()->route('documentation.documents.edit', $document)->with('status', 'Attachment removed successfully.'); } - /** * Extract body content from an uploaded Markdown file when requested. */ diff --git a/Modules/Documentation/Http/Requests/StoreDocumentationDocumentRequest.php b/Modules/Documentation/Http/Requests/StoreDocumentationDocumentRequest.php index b2931ecf..9c8c3a7f 100644 --- a/Modules/Documentation/Http/Requests/StoreDocumentationDocumentRequest.php +++ b/Modules/Documentation/Http/Requests/StoreDocumentationDocumentRequest.php @@ -33,9 +33,9 @@ class StoreDocumentationDocumentRequest extends FormRequest 'is_featured' => ['nullable', 'boolean'], 'version' => ['required', 'string', 'max:50'], 'reference' => ['nullable', 'string', 'max:100'], - 'attachment' => ['nullable', 'file', 'max:' . config('documentation.max_upload_kb', 20480), 'mimes:' . implode(',', config('documentation.allowed_upload_mimes', []))], + 'attachment' => ['nullable', 'file', 'max:'.config('documentation.max_upload_kb', 20480), 'mimes:'.implode(',', config('documentation.allowed_upload_mimes', []))], 'attachments' => ['nullable', 'array'], - 'attachments.*' => ['file', 'max:' . config('documentation.max_upload_kb', 20480), 'mimes:' . implode(',', config('documentation.allowed_upload_mimes', []))], + 'attachments.*' => ['file', 'max:'.config('documentation.max_upload_kb', 20480), 'mimes:'.implode(',', config('documentation.allowed_upload_mimes', []))], ]; } } diff --git a/Modules/Documentation/Http/Requests/UpdateDocumentationDocumentRequest.php b/Modules/Documentation/Http/Requests/UpdateDocumentationDocumentRequest.php index 6d64014f..9622e99c 100644 --- a/Modules/Documentation/Http/Requests/UpdateDocumentationDocumentRequest.php +++ b/Modules/Documentation/Http/Requests/UpdateDocumentationDocumentRequest.php @@ -36,9 +36,9 @@ class UpdateDocumentationDocumentRequest extends FormRequest 'is_featured' => ['nullable', 'boolean'], 'version' => ['required', 'string', 'max:50'], 'reference' => ['nullable', 'string', 'max:100'], - 'attachment' => ['nullable', 'file', 'max:' . config('documentation.max_upload_kb', 20480), 'mimes:' . implode(',', config('documentation.allowed_upload_mimes', []))], + 'attachment' => ['nullable', 'file', 'max:'.config('documentation.max_upload_kb', 20480), 'mimes:'.implode(',', config('documentation.allowed_upload_mimes', []))], 'attachments' => ['nullable', 'array'], - 'attachments.*' => ['file', 'max:' . config('documentation.max_upload_kb', 20480), 'mimes:' . implode(',', config('documentation.allowed_upload_mimes', []))], + 'attachments.*' => ['file', 'max:'.config('documentation.max_upload_kb', 20480), 'mimes:'.implode(',', config('documentation.allowed_upload_mimes', []))], 'remove_attachment' => ['nullable', 'boolean'], ]; } diff --git a/Modules/Documentation/Models/DocumentationAttachment.php b/Modules/Documentation/Models/DocumentationAttachment.php index 9f36876f..971840de 100644 --- a/Modules/Documentation/Models/DocumentationAttachment.php +++ b/Modules/Documentation/Models/DocumentationAttachment.php @@ -57,13 +57,13 @@ class DocumentationAttachment extends Model } if ($bytes < 1024) { - return $bytes . ' B'; + return $bytes.' B'; } if ($bytes < 1048576) { - return number_format($bytes / 1024, 1) . ' KB'; + return number_format($bytes / 1024, 1).' KB'; } - return number_format($bytes / 1048576, 1) . ' MB'; + return number_format($bytes / 1048576, 1).' MB'; } } diff --git a/Modules/Documentation/Models/DocumentationCategory.php b/Modules/Documentation/Models/DocumentationCategory.php index 3b74f2db..f474cec3 100644 --- a/Modules/Documentation/Models/DocumentationCategory.php +++ b/Modules/Documentation/Models/DocumentationCategory.php @@ -66,7 +66,7 @@ class DocumentationCategory extends Model /** * Active category scope. * - * @param Builder $query + * @param Builder $query * @return Builder */ public function scopeActive(Builder $query): Builder @@ -77,7 +77,7 @@ class DocumentationCategory extends Model /** * Ordered category scope. * - * @param Builder $query + * @param Builder $query * @return Builder */ public function scopeOrdered(Builder $query): Builder diff --git a/Modules/Documentation/Models/DocumentationDocument.php b/Modules/Documentation/Models/DocumentationDocument.php index 77136f91..f5cf941e 100644 --- a/Modules/Documentation/Models/DocumentationDocument.php +++ b/Modules/Documentation/Models/DocumentationDocument.php @@ -29,7 +29,9 @@ class DocumentationDocument extends Model use SoftDeletes; public const STATUS_DRAFT = 'draft'; + public const STATUS_PUBLISHED = 'published'; + public const STATUS_ARCHIVED = 'archived'; /** @var list */ @@ -99,7 +101,7 @@ class DocumentationDocument extends Model /** * Published document scope. * - * @param Builder $query + * @param Builder $query * @return Builder */ public function scopePublished(Builder $query): Builder @@ -110,13 +112,14 @@ class DocumentationDocument extends Model /** * Featured document scope. * - * @param Builder $query + * @param Builder $query * @return Builder */ public function scopeFeatured(Builder $query): Builder { return $query->where('is_featured', true); } + /** * Render the document body as safe Markdown HTML for display. */ @@ -135,7 +138,7 @@ class DocumentationDocument extends Model ]); } - return '

' . nl2br(e($body), false) . '

'; + return '

'.nl2br(e($body), false).'

'; } /** @@ -148,5 +151,4 @@ class DocumentationDocument extends Model return str_ends_with($name, '.md') || in_array($mime, ['text/markdown', 'text/x-markdown', 'text/plain'], true); } - } diff --git a/Modules/Documentation/Providers/RouteServiceProvider.php b/Modules/Documentation/Providers/RouteServiceProvider.php index bc8373f8..6ea712a1 100644 --- a/Modules/Documentation/Providers/RouteServiceProvider.php +++ b/Modules/Documentation/Providers/RouteServiceProvider.php @@ -2,8 +2,8 @@ namespace Modules\Documentation\Providers; -use Illuminate\Support\Facades\Route; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; +use Illuminate\Support\Facades\Route; class RouteServiceProvider extends ServiceProvider { diff --git a/Modules/Documentation/Tests/Feature/DocumentationModuleTest.php b/Modules/Documentation/Tests/Feature/DocumentationModuleTest.php index 2d21cbd2..a1b0ccd5 100644 --- a/Modules/Documentation/Tests/Feature/DocumentationModuleTest.php +++ b/Modules/Documentation/Tests/Feature/DocumentationModuleTest.php @@ -2,13 +2,13 @@ namespace Modules\Documentation\Tests\Feature; +use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Storage; -use Tests\TestCase; use Modules\Documentation\Models\DocumentationCategory; use Modules\Documentation\Models\DocumentationDocument; -use Modules\Documentation\Models\DocumentationAttachment; +use Tests\TestCase; class DocumentationModuleTest extends TestCase { @@ -17,7 +17,7 @@ class DocumentationModuleTest extends TestCase /** * Add a CSRF token for projects that keep web middleware enabled during tests. * - * @param array $payload + * @param array $payload * @return array */ private function csrfPayload(array $payload): array @@ -30,7 +30,7 @@ class DocumentationModuleTest extends TestCase */ public function test_documentation_dashboard_renders(): void { - $user = \App\Models\User::factory()->create(); + $user = User::factory()->create(); $this->actingAs($user) ->get(route('documentation.documents.index')) @@ -43,7 +43,7 @@ class DocumentationModuleTest extends TestCase */ public function test_document_can_be_created(): void { - $user = \App\Models\User::factory()->create(); + $user = User::factory()->create(); $category = DocumentationCategory::query()->create([ 'name' => 'Operations', 'slug' => 'operations', @@ -72,7 +72,6 @@ class DocumentationModuleTest extends TestCase ]); } - /** * Markdown attachments are stored and imported into the document body. */ @@ -80,7 +79,7 @@ class DocumentationModuleTest extends TestCase { Storage::fake('public'); - $user = \App\Models\User::factory()->create(); + $user = User::factory()->create(); $file = UploadedFile::fake()->createWithContent('goods-in.md', "# Goods In\n\n- Scan delivery note\n- Confirm quantities"); $response = $this->actingAs($user)->withSession(['_token' => 'test-token'])->post(route('documentation.documents.store'), $this->csrfPayload([ @@ -102,8 +101,6 @@ class DocumentationModuleTest extends TestCase Storage::disk('public')->assertExists($document->attachment_path); } - - /** * Multiple supporting attachments can be uploaded, downloaded and removed. */ @@ -111,7 +108,7 @@ class DocumentationModuleTest extends TestCase { Storage::fake('public'); - $user = \App\Models\User::factory()->create(); + $user = User::factory()->create(); $response = $this->actingAs($user)->withSession(['_token' => 'test-token'])->post(route('documentation.documents.store'), $this->csrfPayload([ 'title' => 'Attachment Pack', @@ -153,7 +150,7 @@ class DocumentationModuleTest extends TestCase */ public function test_document_editor_has_markdown_code_controls(): void { - $user = \App\Models\User::factory()->create(); + $user = User::factory()->create(); $this->actingAs($user) ->get(route('documentation.documents.create')) @@ -168,7 +165,7 @@ class DocumentationModuleTest extends TestCase */ public function test_category_can_be_created(): void { - $user = \App\Models\User::factory()->create(); + $user = User::factory()->create(); $this->actingAs($user)->withSession(['_token' => 'test-token'])->post(route('documentation.categories.store'), $this->csrfPayload([ 'name' => 'Policies', diff --git a/Modules/Helpdesk/Config/config.php b/Modules/Helpdesk/Config/config.php index 1b947558..521e7582 100644 --- a/Modules/Helpdesk/Config/config.php +++ b/Modules/Helpdesk/Config/config.php @@ -7,7 +7,7 @@ return [ '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'], + '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/Modules/Helpdesk/Database/Migrations/2026_06_15_000100_add_redmineup_inspired_helpdesk_features.php b/Modules/Helpdesk/Database/Migrations/2026_06_15_000100_add_redmineup_inspired_helpdesk_features.php index c563f29a..2bed0658 100644 --- a/Modules/Helpdesk/Database/Migrations/2026_06_15_000100_add_redmineup_inspired_helpdesk_features.php +++ b/Modules/Helpdesk/Database/Migrations/2026_06_15_000100_add_redmineup_inspired_helpdesk_features.php @@ -24,7 +24,6 @@ return new class extends Migration $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(); @@ -107,8 +106,6 @@ return new class extends Migration $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(); diff --git a/Modules/Helpdesk/Http/Controllers/TicketControlsController.php b/Modules/Helpdesk/Http/Controllers/TicketControlsController.php index 3a31119b..b3c6e60f 100644 --- a/Modules/Helpdesk/Http/Controllers/TicketControlsController.php +++ b/Modules/Helpdesk/Http/Controllers/TicketControlsController.php @@ -126,7 +126,7 @@ final class TicketControlsController extends Controller /** * Apply a direct guarded ticket update without relying on Livewire hydration. * - * @param array $updates + * @param array $updates */ private function updateTicket(Ticket $ticket, array $updates): void { diff --git a/Modules/Helpdesk/Livewire/KanbanBoard.php b/Modules/Helpdesk/Livewire/KanbanBoard.php index 3b8cf0c2..ebddf76f 100644 --- a/Modules/Helpdesk/Livewire/KanbanBoard.php +++ b/Modules/Helpdesk/Livewire/KanbanBoard.php @@ -6,8 +6,8 @@ namespace Modules\Helpdesk\Livewire; use Illuminate\Contracts\View\View; use Illuminate\Database\Eloquent\Collection; -use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\DB; use Livewire\Component; use Modules\Helpdesk\Models\KanbanBoard as KanbanBoardModel; use Modules\Helpdesk\Models\Status; @@ -99,9 +99,6 @@ final class KanbanBoard extends Component /** * Normalise the board selector value from Livewire/browser select events. - * - * @param int|string|null $value - * @return void */ public function updatedBoardId(int|string|null $value): void { diff --git a/Modules/Helpdesk/Livewire/SettingsManager.php b/Modules/Helpdesk/Livewire/SettingsManager.php index e9d5d3c2..ddc92a3e 100644 --- a/Modules/Helpdesk/Livewire/SettingsManager.php +++ b/Modules/Helpdesk/Livewire/SettingsManager.php @@ -12,15 +12,15 @@ use Illuminate\Support\Str; use Livewire\Component; use Modules\Helpdesk\Models\AutomationRule; use Modules\Helpdesk\Models\CannedResponse; -use Modules\Helpdesk\Models\ChecklistTemplate; use Modules\Helpdesk\Models\Category; +use Modules\Helpdesk\Models\ChecklistTemplate; use Modules\Helpdesk\Models\GitRepository; use Modules\Helpdesk\Models\KanbanBoard; use Modules\Helpdesk\Models\Priority; use Modules\Helpdesk\Models\Product; use Modules\Helpdesk\Models\Project; -use Modules\Helpdesk\Models\Status; use Modules\Helpdesk\Models\SlaPolicy; +use Modules\Helpdesk\Models\Status; use Modules\Helpdesk\Models\Tag; use Modules\Helpdesk\Models\Team; use Modules\Helpdesk\Models\WorkflowTemplate; @@ -297,7 +297,6 @@ final class SettingsManager extends Component $this->projectForm = ['id' => null, 'company_id' => null, 'team_id' => null, 'title' => '', 'slug' => '', 'color' => 'blue', 'description' => '', 'is_default' => false, 'archived_at' => null]; } - public function saveBoard(): void { $data = $this->validate([ @@ -435,7 +434,6 @@ final class SettingsManager extends Component $this->workflowForm = ['id' => null, 'name' => '', 'event' => 'ticket.created', 'subject' => '', 'html_body' => '', 'text_body' => '', 'to_role' => 'requester', 'cc' => '', 'bcc' => '', 'is_active' => true]; } - public function saveCannedResponse(): void { $data = $this->validate([ @@ -458,7 +456,7 @@ final class SettingsManager extends Component public function editCannedResponse(int $id): void { - $this->cannedForm = CannedResponse::query()->findOrFail($id)->only(['id','company_id','category_id','team_id','name','slug','visibility','subject','body','is_active','sort_order']); + $this->cannedForm = CannedResponse::query()->findOrFail($id)->only(['id', 'company_id', 'category_id', 'team_id', 'name', 'slug', 'visibility', 'subject', 'body', 'is_active', 'sort_order']); $this->section = 'canned'; } @@ -498,7 +496,7 @@ final class SettingsManager extends Component public function editSlaPolicy(int $id): void { - $this->slaForm = SlaPolicy::query()->findOrFail($id)->only(['id','company_id','priority_id','category_id','name','slug','first_response_minutes','resolution_minutes','business_hours_only','is_default','is_active']); + $this->slaForm = SlaPolicy::query()->findOrFail($id)->only(['id', 'company_id', 'priority_id', 'category_id', 'name', 'slug', 'first_response_minutes', 'resolution_minutes', 'business_hours_only', 'is_default', 'is_active']); $this->section = 'sla'; } @@ -540,7 +538,7 @@ final class SettingsManager extends Component public function editAutomationRule(int $id): void { - $this->automationForm = AutomationRule::query()->findOrFail($id)->only(['id','company_id','name','slug','match_sender','match_subject','match_mailbox','match_category_id','set_priority_id','set_category_id','set_team_id','set_project_id','set_status_id','set_agent_id','is_active','sort_order']); + $this->automationForm = AutomationRule::query()->findOrFail($id)->only(['id', 'company_id', 'name', 'slug', 'match_sender', 'match_subject', 'match_mailbox', 'match_category_id', 'set_priority_id', 'set_category_id', 'set_team_id', 'set_project_id', 'set_status_id', 'set_agent_id', 'is_active', 'sort_order']); $this->section = 'automation'; } @@ -575,7 +573,7 @@ final class SettingsManager extends Component public function editProduct(int $id): void { - $this->productForm = Product::query()->findOrFail($id)->only(['id','company_id','name','slug','type','version','release_channel','description','is_active']); + $this->productForm = Product::query()->findOrFail($id)->only(['id', 'company_id', 'name', 'slug', 'type', 'version', 'release_channel', 'description', 'is_active']); $this->section = 'products'; } @@ -606,7 +604,7 @@ final class SettingsManager extends Component public function editTag(int $id): void { - $this->tagForm = Tag::query()->findOrFail($id)->only(['id','company_id','name','slug','color']); + $this->tagForm = Tag::query()->findOrFail($id)->only(['id', 'company_id', 'name', 'slug', 'color']); $this->section = 'tags'; } @@ -644,7 +642,7 @@ final class SettingsManager extends Component public function editChecklistTemplate(int $id): void { $template = ChecklistTemplate::query()->findOrFail($id); - $this->checklistTemplateForm = $template->only(['id','company_id','category_id','project_id','name','slug','is_active']) + [ + $this->checklistTemplateForm = $template->only(['id', 'company_id', 'category_id', 'project_id', 'name', 'slug', 'is_active']) + [ 'items_text' => collect($template->items ?? [])->map(fn ($item): string => is_array($item) ? (string) ($item['title'] ?? '') : (string) $item)->implode("\n"), ]; $this->section = 'checklists'; @@ -661,7 +659,6 @@ final class SettingsManager extends Component $this->checklistTemplateForm = ['id' => null, 'company_id' => null, 'category_id' => null, 'project_id' => null, 'name' => '', 'slug' => '', 'items_text' => '', 'is_active' => true]; } - public function saveDefaultHelpdeskCompany(): void { if (! Schema::hasTable('companies') || ! Schema::hasColumn('companies', 'is_default_helpdesk_company')) { @@ -709,12 +706,14 @@ final class SettingsManager extends Component 'categories' => $this->orderedCategories(), 'teams' => $this->orderedTeams(), 'projects' => $this->orderedProjects(), - 'boardOwners' => User::query()->where(static function ($query): void { $query->where('helpdesk_agent', true)->orWhere('helpdesk_admin', true); })->orderBy('name')->get(['id', 'name', 'email']), + 'boardOwners' => User::query()->where(static function ($query): void { + $query->where('helpdesk_agent', true)->orWhere('helpdesk_admin', true); + })->orderBy('name')->get(['id', 'name', 'email']), 'boards' => KanbanBoard::query()->with(['statuses', 'owner:id,name,email'])->withCount('tickets')->orderBy('sort_order')->orderBy('name')->get(), 'repositories' => GitRepository::query()->with(['project'])->withCount(['commits', 'webhookLogs'])->orderBy('name')->get(), 'workflowTemplates' => WorkflowTemplate::query()->orderBy('event')->orderBy('name')->get(), - 'cannedResponses' => CannedResponse::query()->with(['category','team'])->orderBy('sort_order')->orderBy('name')->get(), - 'slaPolicies' => SlaPolicy::query()->with(['priority','category'])->orderByDesc('is_default')->orderBy('name')->get(), + 'cannedResponses' => CannedResponse::query()->with(['category', 'team'])->orderBy('sort_order')->orderBy('name')->get(), + 'slaPolicies' => SlaPolicy::query()->with(['priority', 'category'])->orderByDesc('is_default')->orderBy('name')->get(), 'automationRules' => AutomationRule::query()->orderBy('sort_order')->orderBy('name')->get(), 'products' => Product::query()->withCount('tickets')->orderBy('name')->get(), 'tags' => Tag::query()->orderBy('name')->get(), @@ -722,8 +721,6 @@ final class SettingsManager extends Component ])->layout('layouts.app'); } - - private function orderedStatuses(): mixed { return Status::query() @@ -772,7 +769,6 @@ final class SettingsManager extends Component ->get(); } - /** * Remove is_default before save when the database has not been migrated yet. * diff --git a/Modules/Helpdesk/Livewire/TicketCreate.php b/Modules/Helpdesk/Livewire/TicketCreate.php index 72a1ad73..33b523aa 100644 --- a/Modules/Helpdesk/Livewire/TicketCreate.php +++ b/Modules/Helpdesk/Livewire/TicketCreate.php @@ -17,8 +17,8 @@ use Modules\Helpdesk\Models\KanbanBoard; use Modules\Helpdesk\Models\Priority; use Modules\Helpdesk\Models\Product; use Modules\Helpdesk\Models\Project; -use Modules\Helpdesk\Models\Team; use Modules\Helpdesk\Models\Tag; +use Modules\Helpdesk\Models\Team; use Modules\Helpdesk\Services\TicketService; final class TicketCreate extends Component @@ -55,7 +55,6 @@ final class TicketCreate extends Component #[Validate('nullable|date')] public ?string $due_at = null; - /** @var array */ #[Validate('array')] public array $tag_ids = []; @@ -102,7 +101,6 @@ final class TicketCreate extends Component return $this->redirectRoute('helpdesk.tickets.index', navigate: true); } - /** * Clear the create form, including Select2 and Flatpickr browser widgets. */ @@ -154,7 +152,6 @@ final class TicketCreate extends Component ->value('id'); } - if (blank($this->board_id) && Schema::hasTable('helpdesk_kanban_boards')) { $this->board_id = KanbanBoard::query() ->where('is_default', true) @@ -213,8 +210,6 @@ final class TicketCreate extends Component } } - - /** * Return Kanban board options with the default board listed first. */ diff --git a/Modules/Helpdesk/Livewire/TicketEdit.php b/Modules/Helpdesk/Livewire/TicketEdit.php index 9dda74c9..b6c3180a 100644 --- a/Modules/Helpdesk/Livewire/TicketEdit.php +++ b/Modules/Helpdesk/Livewire/TicketEdit.php @@ -7,7 +7,6 @@ namespace Modules\Helpdesk\Livewire; use App\Models\User; use Illuminate\Contracts\View\View; use Illuminate\Database\Eloquent\Collection as EloquentCollection; -use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; diff --git a/Modules/Helpdesk/Livewire/TicketShow.php b/Modules/Helpdesk/Livewire/TicketShow.php index 76ef0e52..a2256a2c 100644 --- a/Modules/Helpdesk/Livewire/TicketShow.php +++ b/Modules/Helpdesk/Livewire/TicketShow.php @@ -10,14 +10,15 @@ use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; -use Illuminate\Validation\Rule; use Illuminate\Support\Facades\Schema; +use Illuminate\Validation\Rule; use Livewire\Attributes\Validate; use Livewire\Component; use Livewire\Features\SupportFileUploads\TemporaryUploadedFile; use Livewire\WithFileUploads; use Modules\Helpdesk\Models\CannedResponse; use Modules\Helpdesk\Models\ChecklistItem; +use Modules\Helpdesk\Models\Comment; use Modules\Helpdesk\Models\Priority; use Modules\Helpdesk\Models\Status; use Modules\Helpdesk\Models\Ticket; @@ -299,7 +300,7 @@ final class TicketShow extends Component $this->hydrateTicketUsers($ticket); - $comments = \Modules\Helpdesk\Models\Comment::query() + $comments = Comment::query() ->where('ticket_id', $ticket->id) ->with(['user.company', 'attachments']) ->oldest() diff --git a/Modules/Helpdesk/Models/AutomationRule.php b/Modules/Helpdesk/Models/AutomationRule.php index 3665923c..2672331e 100644 --- a/Modules/Helpdesk/Models/AutomationRule.php +++ b/Modules/Helpdesk/Models/AutomationRule.php @@ -9,6 +9,8 @@ use Illuminate\Database\Eloquent\Model; final class AutomationRule extends Model { protected $table = 'helpdesk_automation_rules'; - protected $fillable = ['company_id','name','slug','match_sender','match_subject','match_mailbox','match_category_id','set_priority_id','set_category_id','set_team_id','set_project_id','set_status_id','set_agent_id','is_active','sort_order']; - protected $casts = ['company_id'=>'integer','match_category_id'=>'integer','set_priority_id'=>'integer','set_category_id'=>'integer','set_team_id'=>'integer','set_project_id'=>'integer','set_status_id'=>'integer','set_agent_id'=>'integer','is_active'=>'boolean','sort_order'=>'integer']; + + protected $fillable = ['company_id', 'name', 'slug', 'match_sender', 'match_subject', 'match_mailbox', 'match_category_id', 'set_priority_id', 'set_category_id', 'set_team_id', 'set_project_id', 'set_status_id', 'set_agent_id', 'is_active', 'sort_order']; + + protected $casts = ['company_id' => 'integer', 'match_category_id' => 'integer', 'set_priority_id' => 'integer', 'set_category_id' => 'integer', 'set_team_id' => 'integer', 'set_project_id' => 'integer', 'set_status_id' => 'integer', 'set_agent_id' => 'integer', 'is_active' => 'boolean', 'sort_order' => 'integer']; } diff --git a/Modules/Helpdesk/Models/CannedResponse.php b/Modules/Helpdesk/Models/CannedResponse.php index d932c603..65682ef0 100644 --- a/Modules/Helpdesk/Models/CannedResponse.php +++ b/Modules/Helpdesk/Models/CannedResponse.php @@ -10,8 +10,18 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; final class CannedResponse extends Model { protected $table = 'helpdesk_canned_responses'; - protected $fillable = ['company_id','category_id','team_id','name','slug','visibility','subject','body','is_active','sort_order']; - protected $casts = ['company_id'=>'integer','category_id'=>'integer','team_id'=>'integer','is_active'=>'boolean','sort_order'=>'integer']; - public function category(): BelongsTo { return $this->belongsTo(Category::class, 'category_id'); } - public function team(): BelongsTo { return $this->belongsTo(Team::class, 'team_id'); } + + protected $fillable = ['company_id', 'category_id', 'team_id', 'name', 'slug', 'visibility', 'subject', 'body', 'is_active', 'sort_order']; + + protected $casts = ['company_id' => 'integer', 'category_id' => 'integer', 'team_id' => 'integer', 'is_active' => 'boolean', 'sort_order' => 'integer']; + + public function category(): BelongsTo + { + return $this->belongsTo(Category::class, 'category_id'); + } + + public function team(): BelongsTo + { + return $this->belongsTo(Team::class, 'team_id'); + } } diff --git a/Modules/Helpdesk/Models/ChecklistItem.php b/Modules/Helpdesk/Models/ChecklistItem.php index 1a431bb1..fcde60cf 100644 --- a/Modules/Helpdesk/Models/ChecklistItem.php +++ b/Modules/Helpdesk/Models/ChecklistItem.php @@ -10,7 +10,13 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; final class ChecklistItem extends Model { protected $table = 'helpdesk_checklist_items'; - protected $fillable = ['ticket_id','template_id','title','description','is_completed','completed_by','completed_at','sort_order']; - protected $casts = ['ticket_id'=>'integer','template_id'=>'integer','is_completed'=>'boolean','completed_by'=>'integer','completed_at'=>'datetime','sort_order'=>'integer']; - public function ticket(): BelongsTo { return $this->belongsTo(Ticket::class, 'ticket_id'); } + + protected $fillable = ['ticket_id', 'template_id', 'title', 'description', 'is_completed', 'completed_by', 'completed_at', 'sort_order']; + + protected $casts = ['ticket_id' => 'integer', 'template_id' => 'integer', 'is_completed' => 'boolean', 'completed_by' => 'integer', 'completed_at' => 'datetime', 'sort_order' => 'integer']; + + public function ticket(): BelongsTo + { + return $this->belongsTo(Ticket::class, 'ticket_id'); + } } diff --git a/Modules/Helpdesk/Models/ChecklistTemplate.php b/Modules/Helpdesk/Models/ChecklistTemplate.php index e9bb4270..0405667a 100644 --- a/Modules/Helpdesk/Models/ChecklistTemplate.php +++ b/Modules/Helpdesk/Models/ChecklistTemplate.php @@ -9,6 +9,8 @@ use Illuminate\Database\Eloquent\Model; final class ChecklistTemplate extends Model { protected $table = 'helpdesk_checklist_templates'; - protected $fillable = ['company_id','category_id','project_id','name','slug','items','is_active']; - protected $casts = ['company_id'=>'integer','category_id'=>'integer','project_id'=>'integer','items'=>'array','is_active'=>'boolean']; + + protected $fillable = ['company_id', 'category_id', 'project_id', 'name', 'slug', 'items', 'is_active']; + + protected $casts = ['company_id' => 'integer', 'category_id' => 'integer', 'project_id' => 'integer', 'items' => 'array', 'is_active' => 'boolean']; } diff --git a/Modules/Helpdesk/Models/KanbanBoard.php b/Modules/Helpdesk/Models/KanbanBoard.php index 5c29c470..85ab61ea 100644 --- a/Modules/Helpdesk/Models/KanbanBoard.php +++ b/Modules/Helpdesk/Models/KanbanBoard.php @@ -4,9 +4,9 @@ declare(strict_types=1); namespace Modules\Helpdesk\Models; +use App\Models\User; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; -use App\Models\User; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; diff --git a/Modules/Helpdesk/Models/Product.php b/Modules/Helpdesk/Models/Product.php index cec09c48..f3bf3fd5 100644 --- a/Modules/Helpdesk/Models/Product.php +++ b/Modules/Helpdesk/Models/Product.php @@ -10,7 +10,13 @@ use Illuminate\Database\Eloquent\Relations\HasMany; final class Product extends Model { protected $table = 'helpdesk_products'; - protected $fillable = ['company_id','name','slug','type','version','release_channel','description','is_active']; - protected $casts = ['company_id'=>'integer','is_active'=>'boolean']; - public function tickets(): HasMany { return $this->hasMany(Ticket::class, 'product_id'); } + + protected $fillable = ['company_id', 'name', 'slug', 'type', 'version', 'release_channel', 'description', 'is_active']; + + protected $casts = ['company_id' => 'integer', 'is_active' => 'boolean']; + + public function tickets(): HasMany + { + return $this->hasMany(Ticket::class, 'product_id'); + } } diff --git a/Modules/Helpdesk/Models/SlaPolicy.php b/Modules/Helpdesk/Models/SlaPolicy.php index e249c742..c564b0d6 100644 --- a/Modules/Helpdesk/Models/SlaPolicy.php +++ b/Modules/Helpdesk/Models/SlaPolicy.php @@ -10,8 +10,18 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; final class SlaPolicy extends Model { protected $table = 'helpdesk_sla_policies'; - protected $fillable = ['company_id','priority_id','category_id','name','slug','first_response_minutes','resolution_minutes','business_hours_only','is_default','is_active']; - protected $casts = ['company_id'=>'integer','priority_id'=>'integer','category_id'=>'integer','first_response_minutes'=>'integer','resolution_minutes'=>'integer','business_hours_only'=>'boolean','is_default'=>'boolean','is_active'=>'boolean']; - public function priority(): BelongsTo { return $this->belongsTo(Priority::class, 'priority_id'); } - public function category(): BelongsTo { return $this->belongsTo(Category::class, 'category_id'); } + + protected $fillable = ['company_id', 'priority_id', 'category_id', 'name', 'slug', 'first_response_minutes', 'resolution_minutes', 'business_hours_only', 'is_default', 'is_active']; + + protected $casts = ['company_id' => 'integer', 'priority_id' => 'integer', 'category_id' => 'integer', 'first_response_minutes' => 'integer', 'resolution_minutes' => 'integer', 'business_hours_only' => 'boolean', 'is_default' => 'boolean', 'is_active' => 'boolean']; + + public function priority(): BelongsTo + { + return $this->belongsTo(Priority::class, 'priority_id'); + } + + public function category(): BelongsTo + { + return $this->belongsTo(Category::class, 'category_id'); + } } diff --git a/Modules/Helpdesk/Models/Tag.php b/Modules/Helpdesk/Models/Tag.php index 5683f26a..8b52d977 100644 --- a/Modules/Helpdesk/Models/Tag.php +++ b/Modules/Helpdesk/Models/Tag.php @@ -10,7 +10,13 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; final class Tag extends Model { protected $table = 'helpdesk_tags'; - protected $fillable = ['company_id','name','slug','color']; - protected $casts = ['company_id'=>'integer']; - public function tickets(): BelongsToMany { return $this->belongsToMany(Ticket::class, 'helpdesk_ticket_tag', 'tag_id', 'ticket_id'); } + + protected $fillable = ['company_id', 'name', 'slug', 'color']; + + protected $casts = ['company_id' => 'integer']; + + public function tickets(): BelongsToMany + { + return $this->belongsToMany(Ticket::class, 'helpdesk_ticket_tag', 'tag_id', 'ticket_id'); + } } diff --git a/Modules/Helpdesk/Models/Ticket.php b/Modules/Helpdesk/Models/Ticket.php index cc4b2da9..e6e01ee0 100644 --- a/Modules/Helpdesk/Models/Ticket.php +++ b/Modules/Helpdesk/Models/Ticket.php @@ -114,7 +114,6 @@ final class Ticket extends Model ->oldest(); } - /** @return BelongsTo */ public function slaPolicy(): BelongsTo { diff --git a/Modules/Helpdesk/Models/TicketRating.php b/Modules/Helpdesk/Models/TicketRating.php index 6595b314..047cf374 100644 --- a/Modules/Helpdesk/Models/TicketRating.php +++ b/Modules/Helpdesk/Models/TicketRating.php @@ -9,6 +9,8 @@ use Illuminate\Database\Eloquent\Model; final class TicketRating extends Model { protected $table = 'helpdesk_ticket_ratings'; - protected $fillable = ['ticket_id','agent_id','team_id','category_id','rating','token','comment','rated_at']; - protected $casts = ['ticket_id'=>'integer','agent_id'=>'integer','team_id'=>'integer','category_id'=>'integer','rating'=>'integer','rated_at'=>'datetime']; + + protected $fillable = ['ticket_id', 'agent_id', 'team_id', 'category_id', 'rating', 'token', 'comment', 'rated_at']; + + protected $casts = ['ticket_id' => 'integer', 'agent_id' => 'integer', 'team_id' => 'integer', 'category_id' => 'integer', 'rating' => 'integer', 'rated_at' => 'datetime']; } diff --git a/Modules/Helpdesk/Models/TicketSlaEvent.php b/Modules/Helpdesk/Models/TicketSlaEvent.php index 567bd41b..e67fc3c3 100644 --- a/Modules/Helpdesk/Models/TicketSlaEvent.php +++ b/Modules/Helpdesk/Models/TicketSlaEvent.php @@ -9,6 +9,8 @@ use Illuminate\Database\Eloquent\Model; final class TicketSlaEvent extends Model { protected $table = 'helpdesk_ticket_sla_events'; - protected $fillable = ['ticket_id','sla_policy_id','event','due_at','occurred_at','breached','meta']; - protected $casts = ['ticket_id'=>'integer','sla_policy_id'=>'integer','due_at'=>'datetime','occurred_at'=>'datetime','breached'=>'boolean','meta'=>'array']; + + protected $fillable = ['ticket_id', 'sla_policy_id', 'event', 'due_at', 'occurred_at', 'breached', 'meta']; + + protected $casts = ['ticket_id' => 'integer', 'sla_policy_id' => 'integer', 'due_at' => 'datetime', 'occurred_at' => 'datetime', 'breached' => 'boolean', 'meta' => 'array']; } diff --git a/Modules/Helpdesk/Routes/web.php b/Modules/Helpdesk/Routes/web.php index 8d61de36..d077ae32 100644 --- a/Modules/Helpdesk/Routes/web.php +++ b/Modules/Helpdesk/Routes/web.php @@ -5,8 +5,8 @@ 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\TicketStoreController; use Modules\Helpdesk\Http\Controllers\TicketControlsController; +use Modules\Helpdesk\Http\Controllers\TicketStoreController; use Modules\Helpdesk\Http\Controllers\WorkflowOpenController; use Modules\Helpdesk\Livewire\KanbanBoard; use Modules\Helpdesk\Livewire\SettingsManager; diff --git a/Modules/Helpdesk/Seeders/HelpdeskDatabaseSeeder.php b/Modules/Helpdesk/Seeders/HelpdeskDatabaseSeeder.php index 9abe409a..47cbfca0 100644 --- a/Modules/Helpdesk/Seeders/HelpdeskDatabaseSeeder.php +++ b/Modules/Helpdesk/Seeders/HelpdeskDatabaseSeeder.php @@ -9,7 +9,6 @@ use Modules\Helpdesk\Database\Seeders\HelpdeskDatabaseSeeder as DatabaseHelpdesk final class HelpdeskDatabaseSeeder extends Seeder { - /** @return void */ public function run(): void { $this->call(DatabaseHelpdeskDatabaseSeeder::class); diff --git a/Modules/Helpdesk/Services/CannedResponseRenderer.php b/Modules/Helpdesk/Services/CannedResponseRenderer.php index 163069a6..1236f257 100644 --- a/Modules/Helpdesk/Services/CannedResponseRenderer.php +++ b/Modules/Helpdesk/Services/CannedResponseRenderer.php @@ -66,5 +66,4 @@ final class CannedResponseRenderer $ticket->setRelation('owner', $owner); $ticket->setRelation('agent', $agent); } - } diff --git a/Modules/Helpdesk/Services/GitWebhookIngestionService.php b/Modules/Helpdesk/Services/GitWebhookIngestionService.php index f53a983c..343b7448 100644 --- a/Modules/Helpdesk/Services/GitWebhookIngestionService.php +++ b/Modules/Helpdesk/Services/GitWebhookIngestionService.php @@ -19,7 +19,7 @@ final class GitWebhookIngestionService /** * Persist supported Git provider commits and link them to matching Helpdesk ticket references. * - * @param array $payload + * @param array $payload * @return array{status: string, message: string, commit_count: int, linked_count: int, ignored_count: int} */ public function ingest(GitRepository $repository, array $payload, string $provider, ?string $deliveryId, ?string $event): array @@ -102,8 +102,8 @@ final class GitWebhookIngestionService /** * Record a provider webhook acceptance, rejection or ignore decision for auditability. * - * @param array{status: string, message: string, commit_count?: int, linked_count?: int, ignored_count?: int} $result - * @param array|null $payload + * @param array{status: string, message: string, commit_count?: int, linked_count?: int, ignored_count?: int} $result + * @param array|null $payload */ public function logWebhook(GitRepository $repository, string $provider, ?string $deliveryId, ?string $event, array $result, ?array $payload = null): void { @@ -158,9 +158,6 @@ final class GitWebhookIngestionService }; } - /** - * @param mixed $value - */ private function branchFromRef(mixed $value): ?string { $ref = $this->stringOrNull($value); @@ -173,7 +170,7 @@ final class GitWebhookIngestionService } /** - * @param array $payload + * @param array $payload * @return array> */ private function commitsFromPayload(array $payload): array @@ -208,9 +205,6 @@ final class GitWebhookIngestionService return $query->first(); } - /** - * @param mixed $value - */ private function parseTimestamp(mixed $value): ?CarbonImmutable { $timestamp = $this->stringOrNull($value); @@ -226,9 +220,6 @@ final class GitWebhookIngestionService } } - /** - * @param mixed $value - */ private function stringOrNull(mixed $value): ?string { if (! is_scalar($value)) { diff --git a/Modules/Helpdesk/Services/WorkflowNotificationService.php b/Modules/Helpdesk/Services/WorkflowNotificationService.php index e4023a7d..29a3a8f0 100644 --- a/Modules/Helpdesk/Services/WorkflowNotificationService.php +++ b/Modules/Helpdesk/Services/WorkflowNotificationService.php @@ -9,10 +9,10 @@ use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\URL; +use Illuminate\Support\Str; use Modules\Helpdesk\Models\Ticket; use Modules\Helpdesk\Models\WorkflowLog; use Modules\Helpdesk\Models\WorkflowTemplate; -use Illuminate\Support\Str; use Throwable; final class WorkflowNotificationService diff --git a/Modules/Helpdesk/Tests/Browser/HelpdeskTicketBrowserTest.php b/Modules/Helpdesk/Tests/Browser/HelpdeskTicketBrowserTest.php index 63c8eb33..7c67717f 100644 --- a/Modules/Helpdesk/Tests/Browser/HelpdeskTicketBrowserTest.php +++ b/Modules/Helpdesk/Tests/Browser/HelpdeskTicketBrowserTest.php @@ -88,6 +88,7 @@ if (! component) { window.Livewire.find(component.getAttribute('wire:id')).set({$encodedProperty}, {$encodedValue}); JS); } + private function setTicketShowProperty(Browser $browser, string $property, mixed $value): void { $encodedProperty = json_encode($property, JSON_THROW_ON_ERROR); diff --git a/Modules/Helpdesk/Tests/Feature/KanbanBoardTest.php b/Modules/Helpdesk/Tests/Feature/KanbanBoardTest.php index 65b92bef..5ba52a34 100644 --- a/Modules/Helpdesk/Tests/Feature/KanbanBoardTest.php +++ b/Modules/Helpdesk/Tests/Feature/KanbanBoardTest.php @@ -95,6 +95,7 @@ final class KanbanBoardTest extends TestCase $this->assertSame($targetBoard->id, $ticket->board_id); } + /** @test */ public function kanban_board_filters_tickets_to_the_selected_board_only(): void { @@ -135,5 +136,4 @@ final class KanbanBoardTest extends TestCase ->assertSee('Escalation board ticket') ->assertDontSee('Default board ticket'); } - } diff --git a/Modules/Helpdesk/Tests/Feature/RedmineInspiredHelpdeskFeatureTest.php b/Modules/Helpdesk/Tests/Feature/RedmineInspiredHelpdeskFeatureTest.php index 6052afbb..57de1ee5 100644 --- a/Modules/Helpdesk/Tests/Feature/RedmineInspiredHelpdeskFeatureTest.php +++ b/Modules/Helpdesk/Tests/Feature/RedmineInspiredHelpdeskFeatureTest.php @@ -13,9 +13,7 @@ use Modules\Helpdesk\Models\ChecklistItem; use Modules\Helpdesk\Models\ChecklistTemplate; use Modules\Helpdesk\Models\Product; use Modules\Helpdesk\Models\SlaPolicy; -use Modules\Helpdesk\Models\Ticket; use Modules\Helpdesk\Models\TicketSlaEvent; -use Modules\Helpdesk\Services\AutomationRuleService; use Modules\Helpdesk\Services\ChecklistService; use Modules\Helpdesk\Services\SlaService; use Modules\Helpdesk\Services\TicketService; diff --git a/Modules/Routes/web.php b/Modules/Routes/web.php index 8d61de36..d077ae32 100644 --- a/Modules/Routes/web.php +++ b/Modules/Routes/web.php @@ -5,8 +5,8 @@ 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\TicketStoreController; use Modules\Helpdesk\Http\Controllers\TicketControlsController; +use Modules\Helpdesk\Http\Controllers\TicketStoreController; use Modules\Helpdesk\Http\Controllers\WorkflowOpenController; use Modules\Helpdesk\Livewire\KanbanBoard; use Modules\Helpdesk\Livewire\SettingsManager; diff --git a/database/migrations/2026_06_15_074154_create_activity_log_table.php b/database/migrations/2026_06_15_074154_create_activity_log_table.php index 7c05bc89..b788f65e 100644 --- a/database/migrations/2026_06_15_074154_create_activity_log_table.php +++ b/database/migrations/2026_06_15_074154_create_activity_log_table.php @@ -1,8 +1,8 @@ name('home'); @@ -14,7 +15,7 @@ require __DIR__.'/settings.php'; Route::get('/api/countries/resolve/{code}', function (string $code) { $normalised = strtoupper(trim($code)); - $country = \Illuminate\Support\Facades\DB::table('countries') + $country = DB::table('countries') ->whereRaw('UPPER(alpha2) = ?', [$normalised]) ->orWhereRaw('UPPER(alpha3) = ?', [$normalised]) ->orWhere('numeric', $normalised) @@ -43,7 +44,7 @@ Route::get('/api/countries/resolve/{code}', function (string $code) { Route::get('/api/countries/{code}/subdivisions', function (string $code) { $normalised = strtoupper(trim($code)); - $country = \Illuminate\Support\Facades\DB::table('countries') + $country = DB::table('countries') ->whereRaw('UPPER(alpha2) = ?', [$normalised]) ->orWhereRaw('UPPER(alpha3) = ?', [$normalised]) ->orWhere('numeric', $normalised) @@ -51,7 +52,7 @@ Route::get('/api/countries/{code}/subdivisions', function (string $code) { abort_if($country === null, 404); - $subdivisions = \Illuminate\Support\Facades\DB::table('country_subdivisions') + $subdivisions = DB::table('country_subdivisions') ->where('country_id', $country->id) ->orderBy('sort_order') ->orderBy('name') diff --git a/tests/DuskTestCase.php b/tests/DuskTestCase.php index 80677b60..e82867c4 100644 --- a/tests/DuskTestCase.php +++ b/tests/DuskTestCase.php @@ -30,7 +30,7 @@ abstract class DuskTestCase extends BaseTestCase */ protected function driver(): RemoteWebDriver { - $options = (new ChromeOptions())->addArguments([ + $options = (new ChromeOptions)->addArguments([ '--disable-gpu', '--headless=new', '--window-size=1920,1080', @@ -80,8 +80,7 @@ abstract class DuskTestCase extends BaseTestCase )); usort($candidates, static function (string $left, string $right): int { - $score = static fn (string $path): int => - str_contains($path, 'mac-arm64') ? 0 + $score = static fn (string $path): int => str_contains($path, 'mac-arm64') ? 0 : (str_contains($path, 'mac-arm') ? 1 : (str_contains($path, 'mac') ? 2 : 3));