intial ticket fix partial delete fix

This commit is contained in:
Mark Gregory
2026-06-18 15:25:35 +01:00
parent 8912d28b7b
commit f655613ad7
3 changed files with 67 additions and 370 deletions
+64 -64
View File
@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Modules\Helpdesk\Livewire;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
@@ -25,7 +24,6 @@ use Modules\Helpdesk\Models\Status;
use Modules\Helpdesk\Models\Tag;
use Modules\Helpdesk\Models\Team;
use Modules\Helpdesk\Models\WorkflowTemplate;
use Throwable;
final class SettingsManager extends Component
{
@@ -121,16 +119,6 @@ final class SettingsManager extends Component
return in_array($section, $allowed, true) ? $section : 'statuses';
}
/**
* Switch settings panels without relying on a full browser navigation.
*/
public function showSection(string $section): void
{
$this->section = $this->validSection($section);
$this->resetValidation();
}
public function saveStatus(): void
{
$data = $this->validate([
@@ -149,9 +137,8 @@ final class SettingsManager extends Component
Status::query()->whereKeyNot($this->statusForm['id'])->update(['is_default' => false]);
}
Status::query()->updateOrCreate(['id' => $this->statusForm['id']], $data);
$this->persistSettingModel(Status::class, $this->statusForm['id'], $data);
$this->resetStatusForm();
$this->section = 'statuses';
session()->flash('success', 'Status saved.');
}
@@ -163,7 +150,8 @@ final class SettingsManager extends Component
public function deleteStatus(int $id): void
{
$this->deleteRecord(Status::class, $id, 'Status', 'statuses');
Status::query()->findOrFail($id)->delete();
session()->flash('success', 'Status deleted.');
}
public function resetStatusForm(): void
@@ -187,9 +175,8 @@ final class SettingsManager extends Component
if ((bool) ($data['is_default'] ?? false)) {
Priority::query()->whereKeyNot($this->priorityForm['id'])->update(['is_default' => false]);
}
Priority::query()->updateOrCreate(['id' => $this->priorityForm['id']], $data);
$this->persistSettingModel(Priority::class, $this->priorityForm['id'], $data);
$this->resetPriorityForm();
$this->section = 'priorities';
session()->flash('success', 'Priority saved.');
}
@@ -201,7 +188,8 @@ final class SettingsManager extends Component
public function deletePriority(int $id): void
{
$this->deleteRecord(Priority::class, $id, 'Priority', 'priorities');
Priority::query()->findOrFail($id)->delete();
session()->flash('success', 'Priority deleted.');
}
public function resetPriorityForm(): void
@@ -231,9 +219,8 @@ final class SettingsManager extends Component
->where('company_id', $data['company_id'])
->update(['is_default' => false]);
}
Category::query()->updateOrCreate(['id' => $this->categoryForm['id']], $data);
$this->persistSettingModel(Category::class, $this->categoryForm['id'], $data);
$this->resetCategoryForm();
$this->section = 'categories';
session()->flash('success', 'Category saved.');
}
@@ -245,7 +232,8 @@ final class SettingsManager extends Component
public function deleteCategory(int $id): void
{
$this->deleteRecord(Category::class, $id, 'Category', 'categories');
Category::query()->findOrFail($id)->delete();
session()->flash('success', 'Category deleted.');
}
public function resetCategoryForm(): void
@@ -275,9 +263,8 @@ final class SettingsManager extends Component
->where('company_id', $data['company_id'])
->update(['is_default' => false]);
}
Team::query()->updateOrCreate(['id' => $this->teamForm['id']], $data);
$this->persistSettingModel(Team::class, $this->teamForm['id'], $data);
$this->resetTeamForm();
$this->section = 'teams';
session()->flash('success', 'Team saved.');
}
@@ -289,7 +276,8 @@ final class SettingsManager extends Component
public function deleteTeam(int $id): void
{
$this->deleteRecord(Team::class, $id, 'Team', 'teams');
Team::query()->findOrFail($id)->delete();
session()->flash('success', 'Team deleted.');
}
public function resetTeamForm(): void
@@ -320,9 +308,8 @@ final class SettingsManager extends Component
->where('company_id', $data['company_id'])
->update(['is_default' => false]);
}
Project::query()->updateOrCreate(['id' => $this->projectForm['id']], $data);
$this->persistSettingModel(Project::class, $this->projectForm['id'], $data);
$this->resetProjectForm();
$this->section = 'projects';
session()->flash('success', 'Project saved.');
}
@@ -334,7 +321,8 @@ final class SettingsManager extends Component
public function deleteProject(int $id): void
{
$this->deleteRecord(Project::class, $id, 'Project', 'projects');
Project::query()->findOrFail($id)->delete();
session()->flash('success', 'Project deleted.');
}
public function resetProjectForm(): void
@@ -367,7 +355,7 @@ final class SettingsManager extends Component
KanbanBoard::query()->whereKeyNot($this->boardForm['id'])->update(['is_default' => false]);
}
$board = KanbanBoard::query()->updateOrCreate(['id' => $this->boardForm['id']], $data);
$board = $this->persistSettingModel(KanbanBoard::class, $this->boardForm['id'], $data);
$sync = [];
foreach ($statusIds as $index => $statusId) {
@@ -400,7 +388,8 @@ final class SettingsManager extends Component
public function deleteBoard(int $id): void
{
$this->deleteRecord(KanbanBoard::class, $id, 'Kanban board', 'boards');
KanbanBoard::query()->findOrFail($id)->delete();
session()->flash('success', 'Kanban board deleted.');
}
public function resetBoardForm(): void
@@ -424,9 +413,8 @@ final class SettingsManager extends Component
$data['provider'] = strtolower(trim((string) $data['provider']));
GitRepository::query()->updateOrCreate(['id' => $this->repositoryForm['id']], $data);
$this->persistSettingModel(GitRepository::class, $this->repositoryForm['id'], $data);
$this->resetRepositoryForm();
$this->section = 'git';
session()->flash('success', 'Git repository saved.');
}
@@ -438,7 +426,8 @@ final class SettingsManager extends Component
public function deleteRepository(int $id): void
{
$this->deleteRecord(GitRepository::class, $id, 'Git repository', 'git');
GitRepository::query()->findOrFail($id)->delete();
session()->flash('success', 'Git repository deleted.');
}
public function resetRepositoryForm(): void
@@ -462,9 +451,8 @@ final class SettingsManager extends Component
'workflowForm.is_active' => ['boolean'],
])['workflowForm'];
WorkflowTemplate::query()->updateOrCreate(['id' => $this->workflowForm['id']], $data);
$this->persistSettingModel(WorkflowTemplate::class, $this->workflowForm['id'], $data);
$this->resetWorkflowTemplateForm();
$this->section = 'workflow';
session()->flash('success', 'Workflow message saved.');
}
@@ -476,7 +464,8 @@ final class SettingsManager extends Component
public function deleteWorkflowTemplate(int $id): void
{
$this->deleteRecord(WorkflowTemplate::class, $id, 'Workflow message', 'workflow');
WorkflowTemplate::query()->findOrFail($id)->delete();
session()->flash('success', 'Workflow message deleted.');
}
public function resetWorkflowTemplateForm(): void
@@ -501,9 +490,8 @@ final class SettingsManager extends Component
'cannedForm.sort_order' => ['required', 'integer', 'min:0'],
])['cannedForm'];
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
CannedResponse::query()->updateOrCreate(['id' => $this->cannedForm['id']], $data);
$this->persistSettingModel(CannedResponse::class, $this->cannedForm['id'], $data);
$this->resetCannedResponseForm();
$this->section = 'canned';
session()->flash('success', 'Canned response saved.');
}
@@ -515,7 +503,8 @@ final class SettingsManager extends Component
public function deleteCannedResponse(int $id): void
{
$this->deleteRecord(CannedResponse::class, $id, 'Canned response', 'canned');
CannedResponse::query()->findOrFail($id)->delete();
session()->flash('success', 'Canned response deleted.');
}
public function resetCannedResponseForm(): void
@@ -543,9 +532,8 @@ final class SettingsManager extends Component
if ((bool) ($data['is_default'] ?? false)) {
SlaPolicy::query()->whereKeyNot($this->slaForm['id'])->update(['is_default' => false]);
}
SlaPolicy::query()->updateOrCreate(['id' => $this->slaForm['id']], $data);
$this->persistSettingModel(SlaPolicy::class, $this->slaForm['id'], $data);
$this->resetSlaPolicyForm();
$this->section = 'sla';
session()->flash('success', 'SLA policy saved.');
}
@@ -557,7 +545,8 @@ final class SettingsManager extends Component
public function deleteSlaPolicy(int $id): void
{
$this->deleteRecord(SlaPolicy::class, $id, 'SLA policy', 'sla');
SlaPolicy::query()->findOrFail($id)->delete();
session()->flash('success', 'SLA policy deleted.');
}
public function resetSlaPolicyForm(): void
@@ -587,9 +576,8 @@ final class SettingsManager extends Component
'automationForm.sort_order' => ['required', 'integer', 'min:0'],
])['automationForm'];
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
AutomationRule::query()->updateOrCreate(['id' => $this->automationForm['id']], $data);
$this->persistSettingModel(AutomationRule::class, $this->automationForm['id'], $data);
$this->resetAutomationRuleForm();
$this->section = 'automation';
session()->flash('success', 'Automation rule saved.');
}
@@ -601,7 +589,8 @@ final class SettingsManager extends Component
public function deleteAutomationRule(int $id): void
{
$this->deleteRecord(AutomationRule::class, $id, 'Automation rule', 'automation');
AutomationRule::query()->findOrFail($id)->delete();
session()->flash('success', 'Automation rule deleted.');
}
public function resetAutomationRuleForm(): void
@@ -624,9 +613,8 @@ final class SettingsManager extends Component
'productForm.is_active' => ['boolean'],
])['productForm'];
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
Product::query()->updateOrCreate(['id' => $this->productForm['id']], $data);
$this->persistSettingModel(Product::class, $this->productForm['id'], $data);
$this->resetProductForm();
$this->section = 'products';
session()->flash('success', 'Product/service saved.');
}
@@ -638,7 +626,8 @@ final class SettingsManager extends Component
public function deleteProduct(int $id): void
{
$this->deleteRecord(Product::class, $id, 'Product/service', 'products');
Product::query()->findOrFail($id)->delete();
session()->flash('success', 'Product/service deleted.');
}
public function resetProductForm(): void
@@ -657,9 +646,8 @@ final class SettingsManager extends Component
'tagForm.color' => ['required', 'string', 'max:30'],
])['tagForm'];
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
Tag::query()->updateOrCreate(['id' => $this->tagForm['id']], $data);
$this->persistSettingModel(Tag::class, $this->tagForm['id'], $data);
$this->resetTagForm();
$this->section = 'tags';
session()->flash('success', 'Tag saved.');
}
@@ -671,7 +659,8 @@ final class SettingsManager extends Component
public function deleteTag(int $id): void
{
$this->deleteRecord(Tag::class, $id, 'Tag', 'tags');
Tag::query()->findOrFail($id)->delete();
session()->flash('success', 'Tag deleted.');
}
public function resetTagForm(): void
@@ -696,9 +685,8 @@ final class SettingsManager extends Component
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();
ChecklistTemplate::query()->updateOrCreate(['id' => $this->checklistTemplateForm['id']], $data);
$this->persistSettingModel(ChecklistTemplate::class, $this->checklistTemplateForm['id'], $data);
$this->resetChecklistTemplateForm();
$this->section = 'checklists';
session()->flash('success', 'Checklist template saved.');
}
@@ -713,7 +701,8 @@ final class SettingsManager extends Component
public function deleteChecklistTemplate(int $id): void
{
$this->deleteRecord(ChecklistTemplate::class, $id, 'Checklist template', 'checklists');
ChecklistTemplate::query()->findOrFail($id)->delete();
session()->flash('success', 'Checklist template deleted.');
}
public function resetChecklistTemplateForm(): void
@@ -747,7 +736,6 @@ final class SettingsManager extends Component
->update(['is_default_helpdesk_company' => true]);
}
$this->section = 'defaults';
session()->flash('success', 'Default Helpdesk company saved.');
}
@@ -834,22 +822,34 @@ final class SettingsManager extends Component
->get();
}
/**
* Delete a settings record without letting foreign-key protection crash the UI.
* Persist a settings record without inserting a null primary key.
*
* @param class-string<Model> $modelClass
* 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 deleteRecord(string $modelClass, int $id, string $label, string $section): void
private function persistSettingModel(string $modelClass, int|string|null $id, array $data): object
{
$this->section = $section;
if (filled($id)) {
/** @var TModel $model */
$model = $modelClass::query()->findOrFail((int) $id);
$model->fill($data);
$model->save();
try {
$modelClass::query()->findOrFail($id)->delete();
session()->flash('success', $label.' deleted.');
} catch (Throwable) {
session()->flash('error', $label.' could not be deleted because it is still used by tickets or related Helpdesk records.');
return $model;
}
/** @var TModel $model */
$model = $modelClass::query()->create($data);
return $model;
}
/**