structure and style updates
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
|
||||
return [
|
||||
'name' => 'AuditTrail',
|
||||
'features' =>
|
||||
[
|
||||
],
|
||||
'features' => [
|
||||
],
|
||||
];
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
use Modules\AuditTrail\Support\ModuleInfo;
|
||||
|
||||
if (! function_exists('audittrail_feature_enabled')) {
|
||||
|
||||
@@ -10,6 +10,7 @@ class AuditTrailController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return Renderable
|
||||
*/
|
||||
public function index()
|
||||
@@ -19,6 +20,7 @@ class AuditTrailController extends Controller
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @return Renderable
|
||||
*/
|
||||
public function create()
|
||||
@@ -28,7 +30,7 @@ class AuditTrailController extends Controller
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
* @param Request $request
|
||||
*
|
||||
* @return Renderable
|
||||
*/
|
||||
public function store(Request $request)
|
||||
@@ -38,7 +40,8 @@ class AuditTrailController extends Controller
|
||||
|
||||
/**
|
||||
* Show the specified resource.
|
||||
* @param int $id
|
||||
*
|
||||
* @param int $id
|
||||
* @return Renderable
|
||||
*/
|
||||
public function show($id)
|
||||
@@ -48,7 +51,8 @@ class AuditTrailController extends Controller
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
* @param int $id
|
||||
*
|
||||
* @param int $id
|
||||
* @return Renderable
|
||||
*/
|
||||
public function edit($id)
|
||||
@@ -58,8 +62,8 @@ class AuditTrailController extends Controller
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
* @param Request $request
|
||||
* @param int $id
|
||||
*
|
||||
* @param int $id
|
||||
* @return Renderable
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
@@ -69,7 +73,8 @@ class AuditTrailController extends Controller
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
* @param int $id
|
||||
*
|
||||
* @param int $id
|
||||
* @return Renderable
|
||||
*/
|
||||
public function destroy($id)
|
||||
|
||||
@@ -2,41 +2,29 @@
|
||||
|
||||
namespace Modules\AuditTrail\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Database\Eloquent\Factory;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Modules\AuditTrail\Support\ModuleInfo;
|
||||
use Config;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Modules\AuditTrail\Support\ModuleInfo;
|
||||
|
||||
class AuditTrailServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* @var string $moduleName
|
||||
*/
|
||||
protected string $moduleName = 'AuditTrail';
|
||||
|
||||
/**
|
||||
* @var string $moduleNameLower
|
||||
*/
|
||||
protected string $moduleNameLower = 'audittrail';
|
||||
|
||||
|
||||
/**
|
||||
* Get the services provided by the provider.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function provides():array
|
||||
/**
|
||||
* Get the services provided by the provider.
|
||||
*/
|
||||
public function provides(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot the application events.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot() : void
|
||||
public function boot(): void
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -15,4 +15,4 @@ use Illuminate\Http\Request;
|
||||
|
||||
Route::middleware('auth:api')->get('/audittrail', function (Request $request) {
|
||||
return $request->user();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
|
|
||||
*/
|
||||
|
||||
Route::prefix('audittrail')->group(function() {
|
||||
Route::prefix('audittrail')->group(function () {
|
||||
Route::get('/', 'AuditTrailController@index');
|
||||
});
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
use Modules\Countries\Support\ModuleInfo;
|
||||
|
||||
if (! function_exists('countries_feature_enabled')) {
|
||||
|
||||
@@ -57,7 +57,7 @@ class CountriesController extends Controller
|
||||
*/
|
||||
public function create(): View
|
||||
{
|
||||
return view('countries::create', ['country' => 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<string, mixed>
|
||||
*/
|
||||
private function payload(StoreCountryRequest|UpdateCountryRequest $request): array
|
||||
|
||||
@@ -16,7 +16,6 @@ class StoreCountryRequest extends FormRequest
|
||||
return (bool) $this->user()?->isMpbaPlatformAdmin();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Normalise ISO code inputs before validation.
|
||||
*/
|
||||
|
||||
@@ -16,7 +16,6 @@ class UpdateCountryRequest extends FormRequest
|
||||
return (bool) $this->user()?->isMpbaPlatformAdmin();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Normalise ISO code inputs before validation.
|
||||
*/
|
||||
|
||||
@@ -125,6 +125,7 @@ class CountriesServiceProvider extends ServiceProvider
|
||||
|
||||
if (is_dir($publishedPath)) {
|
||||
$this->loadTranslationsFrom($publishedPath, ModuleInfo::nameLower());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<string> */
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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', []))],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ class DocumentationCategory extends Model
|
||||
/**
|
||||
* Active category scope.
|
||||
*
|
||||
* @param Builder<DocumentationCategory> $query
|
||||
* @param Builder<DocumentationCategory> $query
|
||||
* @return Builder<DocumentationCategory>
|
||||
*/
|
||||
public function scopeActive(Builder $query): Builder
|
||||
@@ -77,7 +77,7 @@ class DocumentationCategory extends Model
|
||||
/**
|
||||
* Ordered category scope.
|
||||
*
|
||||
* @param Builder<DocumentationCategory> $query
|
||||
* @param Builder<DocumentationCategory> $query
|
||||
* @return Builder<DocumentationCategory>
|
||||
*/
|
||||
public function scopeOrdered(Builder $query): Builder
|
||||
|
||||
@@ -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<string> */
|
||||
@@ -99,7 +101,7 @@ class DocumentationDocument extends Model
|
||||
/**
|
||||
* Published document scope.
|
||||
*
|
||||
* @param Builder<DocumentationDocument> $query
|
||||
* @param Builder<DocumentationDocument> $query
|
||||
* @return Builder<DocumentationDocument>
|
||||
*/
|
||||
public function scopePublished(Builder $query): Builder
|
||||
@@ -110,13 +112,14 @@ class DocumentationDocument extends Model
|
||||
/**
|
||||
* Featured document scope.
|
||||
*
|
||||
* @param Builder<DocumentationDocument> $query
|
||||
* @param Builder<DocumentationDocument> $query
|
||||
* @return Builder<DocumentationDocument>
|
||||
*/
|
||||
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 '<p>' . nl2br(e($body), false) . '</p>';
|
||||
return '<p>'.nl2br(e($body), false).'</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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<string, mixed> $payload
|
||||
* @param array<string, mixed> $payload
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
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',
|
||||
|
||||
@@ -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',
|
||||
];
|
||||
|
||||
-3
@@ -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();
|
||||
|
||||
@@ -126,7 +126,7 @@ final class TicketControlsController extends Controller
|
||||
/**
|
||||
* Apply a direct guarded ticket update without relying on Livewire hydration.
|
||||
*
|
||||
* @param array<string, mixed> $updates
|
||||
* @param array<string, mixed> $updates
|
||||
*/
|
||||
private function updateTicket(Ticket $ticket, array $updates): void
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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<int, int> */
|
||||
#[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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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'];
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,6 @@ final class Ticket extends Model
|
||||
->oldest();
|
||||
}
|
||||
|
||||
|
||||
/** @return BelongsTo<SlaPolicy, Ticket> */
|
||||
public function slaPolicy(): BelongsTo
|
||||
{
|
||||
|
||||
@@ -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'];
|
||||
}
|
||||
|
||||
@@ -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'];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -66,5 +66,4 @@ final class CannedResponseRenderer
|
||||
$ticket->setRelation('owner', $owner);
|
||||
$ticket->setRelation('agent', $agent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ final class GitWebhookIngestionService
|
||||
/**
|
||||
* Persist supported Git provider commits and link them to matching Helpdesk ticket references.
|
||||
*
|
||||
* @param array<string, mixed> $payload
|
||||
* @param array<string, mixed> $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<string, mixed>|null $payload
|
||||
* @param array{status: string, message: string, commit_count?: int, linked_count?: int, ignored_count?: int} $result
|
||||
* @param array<string, mixed>|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<string, mixed> $payload
|
||||
* @param array<string, mixed> $payload
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
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)) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateActivityLogTable extends Migration
|
||||
{
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddEventColumnToActivityLogTable extends Migration
|
||||
{
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddBatchUuidColumnToActivityLogTable extends Migration
|
||||
{
|
||||
|
||||
+4
-3
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::view('/', 'welcome')->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')
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user