961 lines
44 KiB
PHP
961 lines
44 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Modules\Helpdesk\Livewire;
|
|
|
|
use App\Models\User;
|
|
use Illuminate\Contracts\View\View;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\QueryException;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Validation\Rule;
|
|
use Illuminate\Validation\ValidationException;
|
|
use Livewire\Component;
|
|
use Modules\Helpdesk\Models\AutomationRule;
|
|
use Modules\Helpdesk\Models\CannedResponse;
|
|
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\SlaPolicy;
|
|
use Modules\Helpdesk\Models\Status;
|
|
use Modules\Helpdesk\Models\Tag;
|
|
use Modules\Helpdesk\Models\Team;
|
|
use Modules\Helpdesk\Models\WorkflowTemplate;
|
|
use Modules\Helpdesk\Support\Color;
|
|
|
|
final class SettingsManager extends Component
|
|
{
|
|
public string $section = 'statuses';
|
|
|
|
public int|string|null $default_helpdesk_company_id = null;
|
|
|
|
/** @var array<string, mixed> */
|
|
public array $statusForm = ['id' => null, 'name' => '', 'slug' => '', 'color' => 'gray', 'sort_order' => 0, 'is_closed' => false, 'is_default' => false];
|
|
|
|
/** @var array<string, mixed> */
|
|
public array $priorityForm = ['id' => null, 'name' => '', 'slug' => '', 'color' => 'gray', 'sort_order' => 0, 'is_default' => false];
|
|
|
|
/** @var array<string, mixed> */
|
|
public array $categoryForm = ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'color' => 'blue', 'description' => '', 'is_default' => false];
|
|
|
|
/** @var array<string, mixed> */
|
|
public array $teamForm = ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'color' => 'indigo', 'description' => '', 'is_default' => false, 'archived_at' => null];
|
|
|
|
/** @var array<string, mixed> */
|
|
public array $projectForm = ['id' => null, 'company_id' => null, 'team_id' => null, 'title' => '', 'slug' => '', 'color' => 'blue', 'description' => '', 'is_default' => false, 'archived_at' => null];
|
|
|
|
/** @var array<string, mixed> */
|
|
public array $boardForm = ['id' => null, 'name' => '', 'slug' => '', 'description' => '', 'owner_id' => null, 'is_default' => false, 'sort_order' => 0, 'status_ids' => []];
|
|
|
|
/** @var array<string, mixed> */
|
|
public array $repositoryForm = ['id' => null, 'company_id' => null, 'project_id' => null, 'name' => '', 'provider' => 'git', 'remote_url' => '', 'default_branch' => 'main', 'webhook_secret' => ''];
|
|
|
|
/** @var array<string, mixed> */
|
|
public array $cannedForm = ['id' => null, 'company_id' => null, 'category_id' => null, 'team_id' => null, 'name' => '', 'slug' => '', 'visibility' => 'agents', 'subject' => '', 'body' => '', 'is_active' => true, 'sort_order' => 0];
|
|
|
|
/** @var array<string, mixed> */
|
|
public array $slaForm = ['id' => null, 'company_id' => null, 'priority_id' => null, 'category_id' => null, 'name' => '', 'slug' => '', 'first_response_minutes' => 240, 'resolution_minutes' => 2880, 'business_hours_only' => false, 'is_default' => false, 'is_active' => true];
|
|
|
|
/** @var array<string, mixed> */
|
|
public array $automationForm = ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'match_sender' => '', 'match_subject' => '', 'match_mailbox' => '', 'match_category_id' => null, 'set_priority_id' => null, 'set_category_id' => null, 'set_team_id' => null, 'set_project_id' => null, 'set_status_id' => null, 'set_agent_id' => null, 'is_active' => true, 'sort_order' => 0];
|
|
|
|
/** @var array<string, mixed> */
|
|
public array $productForm = ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'type' => 'service', 'version' => '', 'release_channel' => '', 'description' => '', 'is_active' => true];
|
|
|
|
/** @var array<string, mixed> */
|
|
public array $tagForm = ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'color' => 'gray'];
|
|
|
|
/** @var array<string, mixed> */
|
|
public array $checklistTemplateForm = ['id' => null, 'company_id' => null, 'category_id' => null, 'project_id' => null, 'name' => '', 'slug' => '', 'items_text' => '', 'is_active' => true];
|
|
|
|
/** @var array<string, mixed> */
|
|
public array $workflowForm = ['id' => null, 'name' => '', 'event' => 'ticket.created', 'subject' => '', 'html_body' => '', 'text_body' => '', 'to_role' => 'requester', 'cc' => '', 'bcc' => '', 'is_active' => true];
|
|
|
|
public function mount(): void
|
|
{
|
|
$this->section = $this->validSection((string) request()->query('section', $this->section));
|
|
$this->loadDefaultHelpdeskCompany();
|
|
$this->statusForm += ['id' => null, 'name' => '', 'slug' => '', 'color' => 'gray', 'sort_order' => 0, 'is_closed' => false, 'is_default' => false];
|
|
$this->priorityForm += ['id' => null, 'name' => '', 'slug' => '', 'color' => 'gray', 'sort_order' => 0, 'is_default' => false];
|
|
$this->categoryForm += ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'color' => 'blue', 'description' => '', 'is_default' => false];
|
|
$this->teamForm += ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'color' => 'indigo', 'description' => '', 'is_default' => false, 'archived_at' => null];
|
|
$this->projectForm += ['id' => null, 'company_id' => null, 'team_id' => null, 'title' => '', 'slug' => '', 'color' => 'blue', 'description' => '', 'is_default' => false, 'archived_at' => null];
|
|
$this->boardForm += ['id' => null, 'name' => '', 'slug' => '', 'description' => '', 'owner_id' => null, 'is_default' => false, 'sort_order' => 0, 'status_ids' => []];
|
|
$this->repositoryForm += ['id' => null, 'company_id' => null, 'project_id' => null, 'name' => '', 'provider' => 'git', 'remote_url' => '', 'default_branch' => 'main', 'webhook_secret' => ''];
|
|
$this->workflowForm += ['id' => null, 'name' => '', 'event' => 'ticket.created', 'subject' => '', 'html_body' => '', 'text_body' => '', 'to_role' => 'requester', 'cc' => '', 'bcc' => '', 'is_active' => true];
|
|
$this->cannedForm += ['id' => null, 'company_id' => null, 'category_id' => null, 'team_id' => null, 'name' => '', 'slug' => '', 'visibility' => 'agents', 'subject' => '', 'body' => '', 'is_active' => true, 'sort_order' => 0];
|
|
$this->slaForm += ['id' => null, 'company_id' => null, 'priority_id' => null, 'category_id' => null, 'name' => '', 'slug' => '', 'first_response_minutes' => 240, 'resolution_minutes' => 2880, 'business_hours_only' => false, 'is_default' => false, 'is_active' => true];
|
|
$this->automationForm += ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'match_sender' => '', 'match_subject' => '', 'match_mailbox' => '', 'match_category_id' => null, 'set_priority_id' => null, 'set_category_id' => null, 'set_team_id' => null, 'set_project_id' => null, 'set_status_id' => null, 'set_agent_id' => null, 'is_active' => true, 'sort_order' => 0];
|
|
$this->productForm += ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'type' => 'service', 'version' => '', 'release_channel' => '', 'description' => '', 'is_active' => true];
|
|
$this->tagForm += ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'color' => 'gray'];
|
|
$this->checklistTemplateForm += ['id' => null, 'company_id' => null, 'category_id' => null, 'project_id' => null, 'name' => '', 'slug' => '', 'items_text' => '', 'is_active' => true];
|
|
}
|
|
|
|
/**
|
|
* Keep settings section navigation constrained to known panels.
|
|
*/
|
|
private function validSection(string $section): string
|
|
{
|
|
$allowed = [
|
|
'statuses',
|
|
'priorities',
|
|
'categories',
|
|
'teams',
|
|
'projects',
|
|
'boards',
|
|
'git',
|
|
'workflow',
|
|
'canned',
|
|
'sla',
|
|
'automation',
|
|
'checklists',
|
|
'tags',
|
|
'products',
|
|
'defaults',
|
|
];
|
|
|
|
return in_array($section, $allowed, true) ? $section : 'statuses';
|
|
}
|
|
|
|
public function saveStatus(): void
|
|
{
|
|
$data = $this->validate([
|
|
'statusForm.name' => ['required', 'string', 'max:80'],
|
|
'statusForm.slug' => ['nullable', 'string', 'max:100'],
|
|
'statusForm.color' => ['required', 'string', Color::validationRule()],
|
|
'statusForm.sort_order' => ['required', 'integer', 'min:0'],
|
|
'statusForm.is_closed' => ['boolean'],
|
|
'statusForm.is_default' => ['boolean'],
|
|
])['statusForm'];
|
|
|
|
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
|
|
$data = $this->guardDefaultColumn('helpdesk_statuses', $data);
|
|
|
|
if ((bool) ($data['is_default'] ?? false)) {
|
|
$this->clearOtherDefaults(Status::class, $this->statusForm['id']);
|
|
}
|
|
|
|
$this->persistSettingModel(Status::class, $this->statusForm['id'], $data);
|
|
$this->resetStatusForm();
|
|
session()->flash('success', 'Status saved.');
|
|
}
|
|
|
|
public function editStatus(int $id): void
|
|
{
|
|
$this->statusForm = Status::query()->findOrFail($id)->only(['id', 'name', 'slug', 'color', 'sort_order', 'is_closed', 'is_default']);
|
|
$this->statusForm['color'] = Color::normalise($this->statusForm['color'] ?? 'gray');
|
|
$this->section = 'statuses';
|
|
}
|
|
|
|
public function deleteStatus(int $id): void
|
|
{
|
|
Status::query()->findOrFail($id)->delete();
|
|
session()->flash('success', 'Status deleted.');
|
|
}
|
|
|
|
public function resetStatusForm(): void
|
|
{
|
|
$this->statusForm = ['id' => null, 'name' => '', 'slug' => '', 'color' => 'gray', 'sort_order' => 0, 'is_closed' => false, 'is_default' => false];
|
|
}
|
|
|
|
public function savePriority(): void
|
|
{
|
|
$data = $this->validate([
|
|
'priorityForm.name' => ['required', 'string', 'max:80'],
|
|
'priorityForm.slug' => ['nullable', 'string', 'max:100'],
|
|
'priorityForm.color' => ['required', 'string', Color::validationRule()],
|
|
'priorityForm.sort_order' => ['required', 'integer', 'min:0'],
|
|
'priorityForm.is_default' => ['boolean'],
|
|
])['priorityForm'];
|
|
|
|
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
|
|
$data = $this->guardDefaultColumn('helpdesk_priorities', $data);
|
|
|
|
if ((bool) ($data['is_default'] ?? false)) {
|
|
$this->clearOtherDefaults(Priority::class, $this->priorityForm['id']);
|
|
}
|
|
$this->persistSettingModel(Priority::class, $this->priorityForm['id'], $data);
|
|
$this->resetPriorityForm();
|
|
session()->flash('success', 'Priority saved.');
|
|
}
|
|
|
|
public function editPriority(int $id): void
|
|
{
|
|
$this->priorityForm = Priority::query()->findOrFail($id)->only(['id', 'name', 'slug', 'color', 'sort_order', 'is_default']);
|
|
$this->priorityForm['color'] = Color::normalise($this->priorityForm['color'] ?? 'gray');
|
|
$this->section = 'priorities';
|
|
}
|
|
|
|
public function deletePriority(int $id): void
|
|
{
|
|
Priority::query()->findOrFail($id)->delete();
|
|
session()->flash('success', 'Priority deleted.');
|
|
}
|
|
|
|
public function resetPriorityForm(): void
|
|
{
|
|
$this->priorityForm = ['id' => null, 'name' => '', 'slug' => '', 'color' => 'gray', 'sort_order' => 0, 'is_default' => false];
|
|
}
|
|
|
|
public function saveCategory(): void
|
|
{
|
|
$this->normalizeNullableFormValues('categoryForm', ['company_id']);
|
|
|
|
$data = $this->validate([
|
|
'categoryForm.company_id' => ['nullable', 'integer'],
|
|
'categoryForm.name' => ['required', 'string', 'max:120'],
|
|
'categoryForm.slug' => ['nullable', 'string', 'max:140'],
|
|
'categoryForm.color' => ['required', 'string', Color::validationRule()],
|
|
'categoryForm.description' => ['nullable', 'string'],
|
|
'categoryForm.is_default' => ['boolean'],
|
|
])['categoryForm'];
|
|
|
|
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
|
|
$data = $this->guardDefaultColumn('helpdesk_categories', $data);
|
|
|
|
if ((bool) ($data['is_default'] ?? false)) {
|
|
$this->clearOtherDefaults(Category::class, $this->categoryForm['id'], $data['company_id'] ?? null);
|
|
}
|
|
$this->persistSettingModel(Category::class, $this->categoryForm['id'], $data);
|
|
$this->resetCategoryForm();
|
|
session()->flash('success', 'Category saved.');
|
|
}
|
|
|
|
public function editCategory(int $id): void
|
|
{
|
|
$this->categoryForm = Category::query()->findOrFail($id)->only(['id', 'company_id', 'name', 'slug', 'color', 'description', 'is_default']);
|
|
$this->categoryForm['color'] = Color::normalise($this->categoryForm['color'] ?? 'blue');
|
|
$this->section = 'categories';
|
|
}
|
|
|
|
public function deleteCategory(int $id): void
|
|
{
|
|
Category::query()->findOrFail($id)->delete();
|
|
session()->flash('success', 'Category deleted.');
|
|
}
|
|
|
|
public function resetCategoryForm(): void
|
|
{
|
|
$this->categoryForm = ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'color' => 'blue', 'description' => '', 'is_default' => false];
|
|
}
|
|
|
|
public function saveTeam(): void
|
|
{
|
|
$this->normalizeNullableFormValues('teamForm', ['company_id']);
|
|
|
|
$data = $this->validate([
|
|
'teamForm.company_id' => ['nullable', 'integer'],
|
|
'teamForm.name' => ['required', 'string', 'max:120'],
|
|
'teamForm.slug' => ['nullable', 'string', 'max:140'],
|
|
'teamForm.color' => ['required', 'string', Color::validationRule()],
|
|
'teamForm.description' => ['nullable', 'string'],
|
|
'teamForm.is_default' => ['boolean'],
|
|
])['teamForm'];
|
|
|
|
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
|
|
$data = $this->guardDefaultColumn('helpdesk_teams', $data);
|
|
|
|
if ((bool) ($data['is_default'] ?? false)) {
|
|
$this->clearOtherDefaults(Team::class, $this->teamForm['id'], $data['company_id'] ?? null);
|
|
}
|
|
$this->persistSettingModel(Team::class, $this->teamForm['id'], $data);
|
|
$this->resetTeamForm();
|
|
session()->flash('success', 'Team saved.');
|
|
}
|
|
|
|
public function editTeam(int $id): void
|
|
{
|
|
$this->teamForm = Team::query()->findOrFail($id)->only(['id', 'company_id', 'name', 'slug', 'color', 'description', 'is_default', 'archived_at']);
|
|
$this->teamForm['color'] = Color::normalise($this->teamForm['color'] ?? 'indigo');
|
|
$this->section = 'teams';
|
|
}
|
|
|
|
public function deleteTeam(int $id): void
|
|
{
|
|
Team::query()->findOrFail($id)->delete();
|
|
session()->flash('success', 'Team deleted.');
|
|
}
|
|
|
|
public function resetTeamForm(): void
|
|
{
|
|
$this->teamForm = ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'color' => 'indigo', 'description' => '', 'is_default' => false, 'archived_at' => null];
|
|
}
|
|
|
|
public function saveProject(): void
|
|
{
|
|
$this->normalizeNullableFormValues('projectForm', ['company_id', 'team_id']);
|
|
|
|
$data = $this->validate([
|
|
'projectForm.company_id' => ['nullable', 'integer'],
|
|
'projectForm.team_id' => ['nullable', 'integer', 'exists:helpdesk_teams,id'],
|
|
'projectForm.title' => ['required', 'string', 'max:160'],
|
|
'projectForm.slug' => ['nullable', 'string', 'max:180'],
|
|
'projectForm.color' => ['required', 'string', Color::validationRule()],
|
|
'projectForm.description' => ['nullable', 'string'],
|
|
'projectForm.is_default' => ['boolean'],
|
|
])['projectForm'];
|
|
|
|
$data['slug'] = $this->slug($data['slug'] ?: $data['title']);
|
|
$data = $this->guardDefaultColumn('helpdesk_projects', $data);
|
|
|
|
if ((bool) ($data['is_default'] ?? false)) {
|
|
$this->clearOtherDefaults(Project::class, $this->projectForm['id'], $data['company_id'] ?? null);
|
|
}
|
|
$this->persistSettingModel(Project::class, $this->projectForm['id'], $data);
|
|
$this->resetProjectForm();
|
|
session()->flash('success', 'Project saved.');
|
|
}
|
|
|
|
public function editProject(int $id): void
|
|
{
|
|
$this->projectForm = Project::query()->findOrFail($id)->only(['id', 'company_id', 'team_id', 'title', 'slug', 'color', 'description', 'is_default', 'archived_at']);
|
|
$this->projectForm['color'] = Color::normalise($this->projectForm['color'] ?? 'blue');
|
|
$this->section = 'projects';
|
|
}
|
|
|
|
public function deleteProject(int $id): void
|
|
{
|
|
Project::query()->findOrFail($id)->delete();
|
|
session()->flash('success', 'Project deleted.');
|
|
}
|
|
|
|
public function resetProjectForm(): void
|
|
{
|
|
$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
|
|
{
|
|
$this->normalizeNullableFormValues('boardForm', ['owner_id']);
|
|
|
|
$data = $this->validate([
|
|
'boardForm.name' => ['required', 'string', 'max:160'],
|
|
'boardForm.slug' => ['nullable', 'string', 'max:180'],
|
|
'boardForm.description' => ['nullable', 'string'],
|
|
'boardForm.owner_id' => ['nullable', 'integer', 'exists:users,id'],
|
|
'boardForm.is_default' => ['boolean'],
|
|
'boardForm.sort_order' => ['required', 'integer', 'min:0'],
|
|
'boardForm.status_ids' => ['array'],
|
|
'boardForm.status_ids.*' => ['integer', 'exists:helpdesk_statuses,id'],
|
|
])['boardForm'];
|
|
|
|
$statusIds = collect($data['status_ids'] ?? [])->map(static fn ($id): int => (int) $id)->filter()->values();
|
|
unset($data['status_ids']);
|
|
|
|
$data['owner_id'] = blank($data['owner_id'] ?? null) ? null : (int) $data['owner_id'];
|
|
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
|
|
|
|
if ((bool) ($data['is_default'] ?? false)) {
|
|
$this->clearOtherDefaults(KanbanBoard::class, $this->boardForm['id']);
|
|
}
|
|
|
|
$board = $this->persistSettingModel(KanbanBoard::class, $this->boardForm['id'], $data);
|
|
|
|
$sync = [];
|
|
foreach ($statusIds as $index => $statusId) {
|
|
$sync[$statusId] = ['sort_order' => ($index + 1) * 10];
|
|
}
|
|
|
|
$board->statuses()->sync($sync);
|
|
$this->resetBoardForm();
|
|
$this->section = 'boards';
|
|
session()->flash('success', 'Kanban board saved.');
|
|
}
|
|
|
|
public function editBoard(int $id): void
|
|
{
|
|
$board = KanbanBoard::query()->with('statuses')->findOrFail($id);
|
|
|
|
$this->boardForm = [
|
|
'id' => $board->id,
|
|
'name' => $board->name,
|
|
'slug' => $board->slug,
|
|
'description' => $board->description,
|
|
'owner_id' => $board->owner_id,
|
|
'is_default' => $board->is_default,
|
|
'sort_order' => $board->sort_order,
|
|
'status_ids' => $board->statuses->pluck('id')->map(static fn ($id): int => (int) $id)->all(),
|
|
];
|
|
|
|
$this->section = 'boards';
|
|
}
|
|
|
|
public function deleteBoard(int $id): void
|
|
{
|
|
KanbanBoard::query()->findOrFail($id)->delete();
|
|
session()->flash('success', 'Kanban board deleted.');
|
|
}
|
|
|
|
public function resetBoardForm(): void
|
|
{
|
|
$this->boardForm = ['id' => null, 'name' => '', 'slug' => '', 'description' => '', 'owner_id' => null, 'is_default' => false, 'sort_order' => 0, 'status_ids' => []];
|
|
}
|
|
|
|
public function saveRepository(): void
|
|
{
|
|
$this->normalizeNullableFormValues('repositoryForm', ['company_id', 'project_id', 'webhook_secret']);
|
|
|
|
$data = $this->validate([
|
|
'repositoryForm.company_id' => ['nullable', 'integer'],
|
|
'repositoryForm.project_id' => ['nullable', 'integer', 'exists:helpdesk_projects,id'],
|
|
'repositoryForm.name' => ['required', 'string', 'max:160'],
|
|
'repositoryForm.provider' => ['required', 'string', 'max:40'],
|
|
'repositoryForm.remote_url' => ['required', 'string', 'max:500'],
|
|
'repositoryForm.default_branch' => ['required', 'string', 'max:120'],
|
|
'repositoryForm.webhook_secret' => ['nullable', 'string', 'max:120'],
|
|
])['repositoryForm'];
|
|
|
|
$data['provider'] = strtolower(trim((string) $data['provider']));
|
|
|
|
$this->persistSettingModel(GitRepository::class, $this->repositoryForm['id'], $data);
|
|
$this->resetRepositoryForm();
|
|
session()->flash('success', 'Git repository saved.');
|
|
}
|
|
|
|
public function editRepository(int $id): void
|
|
{
|
|
$this->repositoryForm = GitRepository::query()->findOrFail($id)->only(['id', 'company_id', 'project_id', 'name', 'provider', 'remote_url', 'default_branch', 'webhook_secret']);
|
|
$this->section = 'git';
|
|
}
|
|
|
|
public function deleteRepository(int $id): void
|
|
{
|
|
GitRepository::query()->findOrFail($id)->delete();
|
|
session()->flash('success', 'Git repository deleted.');
|
|
}
|
|
|
|
public function resetRepositoryForm(): void
|
|
{
|
|
$this->repositoryForm = ['id' => null, 'company_id' => null, 'project_id' => null, 'name' => '', 'provider' => 'git', 'remote_url' => '', 'default_branch' => 'main', 'webhook_secret' => ''];
|
|
}
|
|
|
|
public function saveWorkflowTemplate(): void
|
|
{
|
|
$this->normalizeNullableFormValues('workflowForm', ['text_body', 'cc', 'bcc']);
|
|
|
|
$data = $this->validate([
|
|
'workflowForm.name' => ['required', 'string', 'max:160'],
|
|
'workflowForm.event' => ['required', 'string', 'max:80'],
|
|
'workflowForm.subject' => ['required', 'string', 'max:220'],
|
|
'workflowForm.html_body' => ['required', 'string'],
|
|
'workflowForm.text_body' => ['nullable', 'string'],
|
|
'workflowForm.to_role' => ['required', 'string', 'max:40'],
|
|
'workflowForm.cc' => ['nullable', 'string', 'max:500'],
|
|
'workflowForm.bcc' => ['nullable', 'string', 'max:500'],
|
|
'workflowForm.is_active' => ['boolean'],
|
|
])['workflowForm'];
|
|
|
|
$this->persistSettingModel(WorkflowTemplate::class, $this->workflowForm['id'], $data);
|
|
$this->resetWorkflowTemplateForm();
|
|
session()->flash('success', 'Workflow message saved.');
|
|
}
|
|
|
|
public function editWorkflowTemplate(int $id): void
|
|
{
|
|
$this->workflowForm = WorkflowTemplate::query()->findOrFail($id)->only(['id', 'name', 'event', 'subject', 'html_body', 'text_body', 'to_role', 'cc', 'bcc', 'is_active']);
|
|
$this->section = 'workflow';
|
|
}
|
|
|
|
public function deleteWorkflowTemplate(int $id): void
|
|
{
|
|
WorkflowTemplate::query()->findOrFail($id)->delete();
|
|
session()->flash('success', 'Workflow message deleted.');
|
|
}
|
|
|
|
public function resetWorkflowTemplateForm(): void
|
|
{
|
|
$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
|
|
{
|
|
$this->normalizeNullableFormValues('cannedForm', ['company_id', 'category_id', 'team_id', 'subject']);
|
|
|
|
$data = $this->validate([
|
|
'cannedForm.company_id' => ['nullable', 'integer'],
|
|
'cannedForm.category_id' => ['nullable', 'integer', 'exists:helpdesk_categories,id'],
|
|
'cannedForm.team_id' => ['nullable', 'integer', 'exists:helpdesk_teams,id'],
|
|
'cannedForm.name' => ['required', 'string', 'max:160'],
|
|
'cannedForm.slug' => ['nullable', 'string', 'max:180'],
|
|
'cannedForm.visibility' => ['required', 'string', 'max:40'],
|
|
'cannedForm.subject' => ['nullable', 'string', 'max:220'],
|
|
'cannedForm.body' => ['required', 'string'],
|
|
'cannedForm.is_active' => ['boolean'],
|
|
'cannedForm.sort_order' => ['required', 'integer', 'min:0'],
|
|
])['cannedForm'];
|
|
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
|
|
$this->persistSettingModel(CannedResponse::class, $this->cannedForm['id'], $data);
|
|
$this->resetCannedResponseForm();
|
|
session()->flash('success', 'Canned response saved.');
|
|
}
|
|
|
|
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->section = 'canned';
|
|
}
|
|
|
|
public function deleteCannedResponse(int $id): void
|
|
{
|
|
CannedResponse::query()->findOrFail($id)->delete();
|
|
session()->flash('success', 'Canned response deleted.');
|
|
}
|
|
|
|
public function resetCannedResponseForm(): void
|
|
{
|
|
$this->cannedForm = ['id' => null, 'company_id' => null, 'category_id' => null, 'team_id' => null, 'name' => '', 'slug' => '', 'visibility' => 'agents', 'subject' => '', 'body' => '', 'is_active' => true, 'sort_order' => 0];
|
|
}
|
|
|
|
public function saveSlaPolicy(): void
|
|
{
|
|
$this->normalizeNullableFormValues('slaForm', ['company_id', 'priority_id', 'category_id']);
|
|
|
|
$data = $this->validate([
|
|
'slaForm.company_id' => ['nullable', 'integer'],
|
|
'slaForm.priority_id' => ['nullable', 'integer', 'exists:helpdesk_priorities,id'],
|
|
'slaForm.category_id' => ['nullable', 'integer', 'exists:helpdesk_categories,id'],
|
|
'slaForm.name' => ['required', 'string', 'max:160'],
|
|
'slaForm.slug' => ['nullable', 'string', 'max:180'],
|
|
'slaForm.first_response_minutes' => ['required', 'integer', 'min:1'],
|
|
'slaForm.resolution_minutes' => ['required', 'integer', 'min:1'],
|
|
'slaForm.business_hours_only' => ['boolean'],
|
|
'slaForm.is_default' => ['boolean'],
|
|
'slaForm.is_active' => ['boolean'],
|
|
])['slaForm'];
|
|
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
|
|
if ((bool) ($data['is_default'] ?? false)) {
|
|
$this->clearOtherDefaults(SlaPolicy::class, $this->slaForm['id']);
|
|
}
|
|
$this->persistSettingModel(SlaPolicy::class, $this->slaForm['id'], $data);
|
|
$this->resetSlaPolicyForm();
|
|
session()->flash('success', 'SLA policy saved.');
|
|
}
|
|
|
|
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->section = 'sla';
|
|
}
|
|
|
|
public function deleteSlaPolicy(int $id): void
|
|
{
|
|
SlaPolicy::query()->findOrFail($id)->delete();
|
|
session()->flash('success', 'SLA policy deleted.');
|
|
}
|
|
|
|
public function resetSlaPolicyForm(): void
|
|
{
|
|
$this->slaForm = ['id' => null, 'company_id' => null, 'priority_id' => null, 'category_id' => null, 'name' => '', 'slug' => '', 'first_response_minutes' => 240, 'resolution_minutes' => 2880, 'business_hours_only' => false, 'is_default' => false, 'is_active' => true];
|
|
}
|
|
|
|
public function saveAutomationRule(): void
|
|
{
|
|
$this->normalizeNullableFormValues('automationForm', ['company_id', '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']);
|
|
|
|
$data = $this->validate([
|
|
'automationForm.company_id' => ['nullable', 'integer'],
|
|
'automationForm.name' => ['required', 'string', 'max:160'],
|
|
'automationForm.slug' => ['nullable', 'string', 'max:180'],
|
|
'automationForm.match_sender' => ['nullable', 'string', 'max:190'],
|
|
'automationForm.match_subject' => ['nullable', 'string', 'max:220'],
|
|
'automationForm.match_mailbox' => ['nullable', 'string', 'max:190'],
|
|
'automationForm.match_category_id' => ['nullable', 'integer', 'exists:helpdesk_categories,id'],
|
|
'automationForm.set_priority_id' => ['nullable', 'integer', 'exists:helpdesk_priorities,id'],
|
|
'automationForm.set_category_id' => ['nullable', 'integer', 'exists:helpdesk_categories,id'],
|
|
'automationForm.set_team_id' => ['nullable', 'integer', 'exists:helpdesk_teams,id'],
|
|
'automationForm.set_project_id' => ['nullable', 'integer', 'exists:helpdesk_projects,id'],
|
|
'automationForm.set_status_id' => ['nullable', 'integer', 'exists:helpdesk_statuses,id'],
|
|
'automationForm.set_agent_id' => ['nullable', 'integer', 'exists:users,id'],
|
|
'automationForm.is_active' => ['boolean'],
|
|
'automationForm.sort_order' => ['required', 'integer', 'min:0'],
|
|
])['automationForm'];
|
|
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
|
|
$this->persistSettingModel(AutomationRule::class, $this->automationForm['id'], $data);
|
|
$this->resetAutomationRuleForm();
|
|
session()->flash('success', 'Automation rule saved.');
|
|
}
|
|
|
|
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->section = 'automation';
|
|
}
|
|
|
|
public function deleteAutomationRule(int $id): void
|
|
{
|
|
AutomationRule::query()->findOrFail($id)->delete();
|
|
session()->flash('success', 'Automation rule deleted.');
|
|
}
|
|
|
|
public function resetAutomationRuleForm(): void
|
|
{
|
|
$this->automationForm = ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'match_sender' => '', 'match_subject' => '', 'match_mailbox' => '', 'match_category_id' => null, 'set_priority_id' => null, 'set_category_id' => null, 'set_team_id' => null, 'set_project_id' => null, 'set_status_id' => null, 'set_agent_id' => null, 'is_active' => true, 'sort_order' => 0];
|
|
}
|
|
|
|
public function saveProduct(): void
|
|
{
|
|
$this->normalizeNullableFormValues('productForm', ['company_id', 'version', 'release_channel', 'description']);
|
|
|
|
$data = $this->validate([
|
|
'productForm.company_id' => ['nullable', 'integer'],
|
|
'productForm.name' => ['required', 'string', 'max:160'],
|
|
'productForm.slug' => ['nullable', 'string', 'max:180'],
|
|
'productForm.type' => ['required', 'string', 'max:60'],
|
|
'productForm.version' => ['nullable', 'string', 'max:80'],
|
|
'productForm.release_channel' => ['nullable', 'string', 'max:80'],
|
|
'productForm.description' => ['nullable', 'string'],
|
|
'productForm.is_active' => ['boolean'],
|
|
])['productForm'];
|
|
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
|
|
$this->persistSettingModel(Product::class, $this->productForm['id'], $data);
|
|
$this->resetProductForm();
|
|
session()->flash('success', 'Product/service saved.');
|
|
}
|
|
|
|
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->section = 'products';
|
|
}
|
|
|
|
public function deleteProduct(int $id): void
|
|
{
|
|
Product::query()->findOrFail($id)->delete();
|
|
session()->flash('success', 'Product/service deleted.');
|
|
}
|
|
|
|
public function resetProductForm(): void
|
|
{
|
|
$this->productForm = ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'type' => 'service', 'version' => '', 'release_channel' => '', 'description' => '', 'is_active' => true];
|
|
}
|
|
|
|
public function saveTag(): void
|
|
{
|
|
$this->normalizeNullableFormValues('tagForm', ['company_id']);
|
|
|
|
$data = $this->validate([
|
|
'tagForm.company_id' => ['nullable', 'integer'],
|
|
'tagForm.name' => ['required', 'string', 'max:80'],
|
|
'tagForm.slug' => ['nullable', 'string', 'max:100'],
|
|
'tagForm.color' => ['required', 'string', Color::validationRule()],
|
|
])['tagForm'];
|
|
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
|
|
$this->persistSettingModel(Tag::class, $this->tagForm['id'], $data);
|
|
$this->resetTagForm();
|
|
session()->flash('success', 'Tag saved.');
|
|
}
|
|
|
|
public function editTag(int $id): void
|
|
{
|
|
$this->tagForm = Tag::query()->findOrFail($id)->only(['id', 'company_id', 'name', 'slug', 'color']);
|
|
$this->tagForm['color'] = Color::normalise($this->tagForm['color'] ?? 'gray');
|
|
$this->section = 'tags';
|
|
}
|
|
|
|
public function deleteTag(int $id): void
|
|
{
|
|
Tag::query()->findOrFail($id)->delete();
|
|
session()->flash('success', 'Tag deleted.');
|
|
}
|
|
|
|
public function resetTagForm(): void
|
|
{
|
|
$this->tagForm = ['id' => null, 'company_id' => null, 'name' => '', 'slug' => '', 'color' => 'gray'];
|
|
}
|
|
|
|
public function saveChecklistTemplate(): void
|
|
{
|
|
$this->normalizeNullableFormValues('checklistTemplateForm', ['company_id', 'category_id', 'project_id']);
|
|
|
|
$data = $this->validate([
|
|
'checklistTemplateForm.company_id' => ['nullable', 'integer'],
|
|
'checklistTemplateForm.category_id' => ['nullable', 'integer', 'exists:helpdesk_categories,id'],
|
|
'checklistTemplateForm.project_id' => ['nullable', 'integer', 'exists:helpdesk_projects,id'],
|
|
'checklistTemplateForm.name' => ['required', 'string', 'max:160'],
|
|
'checklistTemplateForm.slug' => ['nullable', 'string', 'max:180'],
|
|
'checklistTemplateForm.items_text' => ['required', 'string'],
|
|
'checklistTemplateForm.is_active' => ['boolean'],
|
|
])['checklistTemplateForm'];
|
|
$itemsText = (string) ($data['items_text'] ?? '');
|
|
unset($data['items_text']);
|
|
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
|
|
$data['items'] = collect(preg_split('/\r\n|\r|\n/', $itemsText) ?: [])->map(fn ($line): string => trim($line))->filter()->values()->all();
|
|
$this->persistSettingModel(ChecklistTemplate::class, $this->checklistTemplateForm['id'], $data);
|
|
$this->resetChecklistTemplateForm();
|
|
session()->flash('success', 'Checklist template saved.');
|
|
}
|
|
|
|
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']) + [
|
|
'items_text' => collect($template->items ?? [])->map(fn ($item): string => is_array($item) ? (string) ($item['title'] ?? '') : (string) $item)->implode("\n"),
|
|
];
|
|
$this->section = 'checklists';
|
|
}
|
|
|
|
public function deleteChecklistTemplate(int $id): void
|
|
{
|
|
ChecklistTemplate::query()->findOrFail($id)->delete();
|
|
session()->flash('success', 'Checklist template deleted.');
|
|
}
|
|
|
|
public function resetChecklistTemplateForm(): void
|
|
{
|
|
$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')) {
|
|
session()->flash('error', 'The default Helpdesk company column has not been migrated yet.');
|
|
|
|
return;
|
|
}
|
|
|
|
$this->normalizeNullableScalar('default_helpdesk_company_id');
|
|
|
|
$this->validate([
|
|
'default_helpdesk_company_id' => ['nullable', 'integer', 'exists:companies,id'],
|
|
]);
|
|
|
|
$companyId = filled($this->default_helpdesk_company_id)
|
|
? (int) $this->default_helpdesk_company_id
|
|
: null;
|
|
|
|
DB::table('companies')->update(['is_default_helpdesk_company' => false]);
|
|
|
|
if ($companyId !== null) {
|
|
DB::table('companies')
|
|
->where('id', $companyId)
|
|
->update(['is_default_helpdesk_company' => true]);
|
|
}
|
|
|
|
session()->flash('success', 'Default Helpdesk company saved.');
|
|
}
|
|
|
|
private function loadDefaultHelpdeskCompany(): void
|
|
{
|
|
if (! Schema::hasTable('companies') || ! Schema::hasColumn('companies', 'is_default_helpdesk_company')) {
|
|
return;
|
|
}
|
|
|
|
$this->default_helpdesk_company_id = DB::table('companies')
|
|
->where('is_default_helpdesk_company', true)
|
|
->value('id');
|
|
}
|
|
|
|
public function render(): View
|
|
{
|
|
return view('helpdesk::livewire.settings-manager', [
|
|
'companies' => $this->companies(),
|
|
'statuses' => $this->orderedStatuses(),
|
|
'priorities' => $this->orderedPriorities(),
|
|
'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']),
|
|
'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(),
|
|
'automationRules' => AutomationRule::query()->orderBy('sort_order')->orderBy('name')->get(),
|
|
'products' => Product::query()->withCount('tickets')->orderBy('name')->get(),
|
|
'tags' => Tag::query()->orderBy('name')->get(),
|
|
'checklistTemplates' => ChecklistTemplate::query()->orderBy('name')->get(),
|
|
'colourOptions' => Color::options(),
|
|
])->layout('layouts.app');
|
|
}
|
|
|
|
private function orderedStatuses(): mixed
|
|
{
|
|
return Status::query()
|
|
->withCount('tickets')
|
|
->when($this->hasDefaultColumn('helpdesk_statuses'), fn ($query) => $query->orderByDesc('is_default'))
|
|
->orderBy('sort_order')
|
|
->orderBy('name')
|
|
->get();
|
|
}
|
|
|
|
private function orderedPriorities(): mixed
|
|
{
|
|
return Priority::query()
|
|
->withCount('tickets')
|
|
->when($this->hasDefaultColumn('helpdesk_priorities'), fn ($query) => $query->orderByDesc('is_default'))
|
|
->orderBy('sort_order')
|
|
->orderBy('name')
|
|
->get();
|
|
}
|
|
|
|
private function orderedCategories(): mixed
|
|
{
|
|
return Category::query()
|
|
->with('company')
|
|
->withCount('tickets')
|
|
->when($this->hasDefaultColumn('helpdesk_categories'), fn ($query) => $query->orderByDesc('is_default'))
|
|
->orderBy('name')
|
|
->get();
|
|
}
|
|
|
|
private function orderedTeams(): mixed
|
|
{
|
|
return Team::query()
|
|
->with('company')
|
|
->withCount(['projects', 'tickets'])
|
|
->when($this->hasDefaultColumn('helpdesk_teams'), fn ($query) => $query->orderByDesc('is_default'))
|
|
->orderBy('name')
|
|
->get();
|
|
}
|
|
|
|
private function orderedProjects(): mixed
|
|
{
|
|
return Project::query()
|
|
->with(['company', 'team'])
|
|
->withCount('tickets')
|
|
->when($this->hasDefaultColumn('helpdesk_projects'), fn ($query) => $query->orderByDesc('is_default'))
|
|
->orderBy('title')
|
|
->get();
|
|
}
|
|
|
|
/**
|
|
* Persist a settings record without inserting a null primary key.
|
|
*
|
|
* Eloquent updateOrCreate(['id' => null], ...) merges the null id into the
|
|
* create payload. PostgreSQL rejects that explicit null for bigserial ids,
|
|
* which made every new Helpdesk settings CRUD form appear to do nothing.
|
|
*
|
|
* @template TModel of \Illuminate\Database\Eloquent\Model
|
|
*
|
|
* @param class-string<TModel> $modelClass
|
|
* @param int|string|null $id
|
|
* @param array<string, mixed> $data
|
|
*
|
|
* @return TModel
|
|
*/
|
|
private function persistSettingModel(string $modelClass, int|string|null $id, array $data): object
|
|
{
|
|
unset($data['id']);
|
|
|
|
try {
|
|
if (filled($id)) {
|
|
/** @var TModel $model */
|
|
$model = $modelClass::query()->findOrFail((int) $id);
|
|
$model->fill($data);
|
|
$model->save();
|
|
|
|
return $model;
|
|
}
|
|
|
|
/** @var TModel $model */
|
|
$model = $modelClass::query()->create($data);
|
|
|
|
return $model;
|
|
} catch (QueryException $exception) {
|
|
throw ValidationException::withMessages([
|
|
'settings' => 'The record could not be saved. Check the slug is unique for this section/company and try again.',
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear the default flag on every other record without generating invalid null-id SQL.
|
|
*
|
|
* @param class-string<Model> $modelClass
|
|
*/
|
|
private function clearOtherDefaults(string $modelClass, int|string|null $currentId, int|string|null $companyId = null): void
|
|
{
|
|
$query = $modelClass::query();
|
|
|
|
if (filled($currentId)) {
|
|
$query->whereKeyNot((int) $currentId);
|
|
}
|
|
|
|
if (Schema::hasColumn((new $modelClass)->getTable(), 'company_id')) {
|
|
$query->where('company_id', filled($companyId) ? (int) $companyId : null);
|
|
}
|
|
|
|
$query->update(['is_default' => false]);
|
|
}
|
|
|
|
/**
|
|
* Convert browser select empty strings into null before Livewire validation.
|
|
*
|
|
* Flux/select controls submit optional values as empty strings. Laravel's
|
|
* nullable integer and exists rules expect a real null, so normalising here
|
|
* keeps every settings CRUD form usable from the browser.
|
|
*
|
|
* @param array<int, string> $keys
|
|
*/
|
|
private function normalizeNullableFormValues(string $formProperty, array $keys): void
|
|
{
|
|
foreach ($keys as $key) {
|
|
if (! array_key_exists($key, $this->{$formProperty})) {
|
|
continue;
|
|
}
|
|
|
|
if (blank($this->{$formProperty}[$key])) {
|
|
$this->{$formProperty}[$key] = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
private function normalizeNullableScalar(string $property): void
|
|
{
|
|
if (blank($this->{$property})) {
|
|
$this->{$property} = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove is_default before save when the database has not been migrated yet.
|
|
*
|
|
* @param array<string, mixed> $data
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function guardDefaultColumn(string $table, array $data): array
|
|
{
|
|
if (! $this->hasDefaultColumn($table)) {
|
|
unset($data['is_default']);
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
private function hasDefaultColumn(string $table): bool
|
|
{
|
|
return Schema::hasTable($table) && Schema::hasColumn($table, 'is_default');
|
|
}
|
|
|
|
/** @return array<int, object> */
|
|
private function companies(): array
|
|
{
|
|
if (! Schema::hasTable('companies')) {
|
|
return [];
|
|
}
|
|
|
|
$columns = ['id', 'name'];
|
|
|
|
if (Schema::hasColumn('companies', 'is_default_helpdesk_company')) {
|
|
$columns[] = 'is_default_helpdesk_company';
|
|
}
|
|
|
|
return DB::table('companies')->select($columns)->orderBy('name')->get()->all();
|
|
}
|
|
|
|
private function slug(string $value): string
|
|
{
|
|
return Str::slug($value);
|
|
}
|
|
}
|