ticket type rework

This commit is contained in:
2026-04-12 22:56:59 +02:00
parent f899893a6a
commit 041e79efda
11 changed files with 120 additions and 97 deletions
@@ -3,40 +3,31 @@
namespace App\Actions\ProjectTicketMetadata;
use App\Models\Project;
use App\Models\TicketType;
class InitializeProjectTicketMetadata
{
public function handle(Project $project): void
{
$project->ticketTypes()->firstOrCreate(
['system_key' => 'task'],
$project->ticketTypes()->create(
[
'is_builtin' => true,
'name' => 'Task',
'position' => 1,
],
);
$project->ticketTypes()->firstOrCreate(
['system_key' => 'parent'],
$project->ticketTypes()->create(
[
'is_builtin' => true,
'name' => 'Parent',
'name' => 'Release',
'is_parent' => true,
'position' => 2,
],
);
$project->ticketTypes()
->whereIn('system_key', TicketType::BUILTIN_KEYS)
->orderBy('position')
->get()
->values()
->each(function (TicketType $ticketType, int $index): void {
$ticketType->forceFill([
'is_builtin' => true,
'position' => $index + 1,
])->saveQuietly();
});
$project->ticketPriorities()->create(
[
'name' => 'Medium',
'position' => 1,
]
);
}
}
@@ -9,9 +9,9 @@ use Illuminate\Console\Attributes\Signature;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Hash;
#[Signature('app:create-initial-admin-user {name=Admin} {email=admin@kanbangit.test} {password=test}')]
#[Signature('app:create-admin-user {name=Admin} {email=admin@kanbangit.test} {password=test}')]
#[Description('Command description')]
class CreateInitialAdminUser extends Command
class CreateAdminUser extends Command
{
/**
* Execute the console command.
@@ -23,12 +23,13 @@ class CreateInitialAdminUser extends Command
'email' => $this->argument('email'),
'password' => Hash::make($this->argument('password')),
]);
$this->info('User created');
$user->permissionAssignments()->create([
'permission' => Permission::GlobalAdmin,
]);
$this->info(sprintf('Admin User created with emil=\'%s\' and password=\'%s\'', $this->argument('email'), $this->argument('password')));
return self::SUCCESS;
}
}
@@ -3,9 +3,9 @@
namespace App\Http\Controllers;
use App\Actions\Permissions\PermissionService;
use App\Actions\ProjectTicketMetadata\InitializeProjectTicketMetadata;
use App\Enums\Permission;
use App\Http\Requests\StoreProjectNamedSettingRequest;
use App\Http\Requests\StoreProjectTicketTypeRequest;
use App\Http\Requests\StoreTicketFieldDefinitionRequest;
use App\Http\Requests\UpdateProjectNamedSettingRequest;
use App\Http\Requests\UpdateTicketFieldDefinitionRequest;
@@ -26,14 +26,12 @@ use Inertia\Response;
class ProjectTicketMetadataSettingsController extends Controller
{
public function __construct(
private readonly InitializeProjectTicketMetadata $initializeProjectTicketMetadata,
private readonly PermissionService $permissionService,
) {}
public function show(Request $request, Project $project): Response
{
$this->abortUnlessCanManage($request, $project);
$this->initializeProjectTicketMetadata->handle($project);
return Inertia::render('projects/settings/Metadata', [
'fieldTypeOptions' => collect(TicketFieldDefinition::FIELD_TYPES)
@@ -43,6 +41,7 @@ class ProjectTicketMetadataSettingsController extends Controller
])
->all(),
'project' => $project,
'error' => session('error'),
'status' => session('status'),
'ticketFieldDefinitions' => $project->ticketFieldDefinitions()->get(),
'ticketLinkTypes' => $project->ticketLinkTypes()->get(),
@@ -51,27 +50,23 @@ class ProjectTicketMetadataSettingsController extends Controller
]);
}
public function storeType(StoreProjectNamedSettingRequest $request, Project $project): RedirectResponse
public function storeType(StoreProjectTicketTypeRequest $request, Project $project): RedirectResponse
{
$this->abortUnlessCanManage($request, $project);
$this->initializeProjectTicketMetadata->handle($project);
$validated = $request->validated();
$name = Arr::get($validated, 'name');
$position = Arr::get($validated, 'position');
$ticketType = DB::transaction(function () use ($project, $name, $position): TicketType {
$ticketType = DB::transaction(function () use ($project, $validated): TicketType {
$ticketType = $project->ticketTypes()->create([
'is_builtin' => false,
'name' => $name,
'name' => Arr::get($validated, 'name'),
'position' => $project->ticketTypes()->count() + 1,
'system_key' => null,
'is_parent' => Arr::get($validated, 'is_parent', false),
]);
$this->reorderModels(
$project->ticketTypes()->whereKeyNot($ticketType->id)->get()->values(),
$ticketType,
$position ?? $project->ticketTypes()->count(),
Arr::get($validated, 'position') ?? $project->ticketTypes()->count(),
);
return $ticketType;
@@ -87,21 +82,19 @@ class ProjectTicketMetadataSettingsController extends Controller
TicketType $ticketType,
): RedirectResponse {
$this->abortUnlessCanManage($request, $project);
$this->initializeProjectTicketMetadata->handle($project);
$validated = $request->validated();
$name = Arr::get($validated, 'name');
$position = Arr::get($validated, 'position');
DB::transaction(function () use ($project, $name, $position, $ticketType): void {
DB::transaction(function () use ($project, $validated, $ticketType): void {
$ticketType->update([
'name' => $name,
'name' => Arr::get($validated, 'name'),
'is_parent' => Arr::get($validated, 'is_parent', false),
]);
$this->reorderModels(
$project->ticketTypes()->whereKeyNot($ticketType->id)->get()->values(),
$ticketType,
$position,
Arr::get($validated, 'position'),
);
});
@@ -113,7 +106,11 @@ class ProjectTicketMetadataSettingsController extends Controller
{
$this->abortUnlessCanManage($request, $project);
abort_if($ticketType->is_builtin, 403);
if ($project->ticketTypes()->count() === 1) {
return to_route('projects.settings.metadata', $project)
->with('error', true)
->with('status', 'The last ticket type cannot be deleted.');
}
$deletedName = $ticketType->name;
@@ -188,6 +185,12 @@ class ProjectTicketMetadataSettingsController extends Controller
): RedirectResponse {
$this->abortUnlessCanManage($request, $project);
if ($project->ticketPriorities()->count() === 1) {
return to_route('projects.settings.metadata', $project)
->with('error', true)
->with('status', 'The last ticket priority cannot be deleted.');
}
$deletedName = $ticketPriority->name;
DB::transaction(function () use ($project, $ticketPriority): void {
@@ -207,21 +210,18 @@ class ProjectTicketMetadataSettingsController extends Controller
$this->abortUnlessCanManage($request, $project);
$validated = $request->validated();
$label = Arr::get($validated, 'label');
$fieldType = Arr::get($validated, 'field_type');
$position = Arr::get($validated, 'position');
$ticketFieldDefinition = DB::transaction(function () use ($project, $fieldType, $label): TicketFieldDefinition {
$ticketFieldDefinition = DB::transaction(function () use ($project, $validated): TicketFieldDefinition {
$ticketFieldDefinition = $project->ticketFieldDefinitions()->create([
'field_type' => $fieldType,
'label' => $label,
'field_type' => Arr::get($validated, 'field_type'),
'label' => Arr::get($validated, 'label'),
'position' => $project->ticketFieldDefinitions()->count() + 1,
]);
$this->reorderModels(
$project->ticketFieldDefinitions()->whereKeyNot($ticketFieldDefinition->id)->get()->values(),
$ticketFieldDefinition,
$position ?? $project->ticketFieldDefinitions()->count(),
Arr::get($validated, 'position') ?? $project->ticketFieldDefinitions()->count(),
);
return $ticketFieldDefinition;
@@ -239,20 +239,17 @@ class ProjectTicketMetadataSettingsController extends Controller
$this->abortUnlessCanManage($request, $project);
$validated = $request->validated();
$label = Arr::get($validated, 'label');
$fieldType = Arr::get($validated, 'field_type');
$position = Arr::get($validated, 'position');
DB::transaction(function () use ($project, $fieldType, $label, $ticketFieldDefinition, $position): void {
DB::transaction(function () use ($project, $ticketFieldDefinition, $validated): void {
$ticketFieldDefinition->update([
'field_type' => $fieldType,
'label' => $label,
'field_type' => Arr::get($validated, 'field_type'),
'label' => Arr::get($validated, 'label'),
]);
$this->reorderModels(
$project->ticketFieldDefinitions()->whereKeyNot($ticketFieldDefinition->id)->get()->values(),
$ticketFieldDefinition,
$position,
Arr::get($validated, 'position'),
);
});
@@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class StoreProjectTicketTypeRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return $this->user() !== null;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'position' => ['nullable', 'integer', 'min:1'],
'is_parent' => ['nullable', 'boolean'],
];
}
}
@@ -25,6 +25,7 @@ class UpdateProjectNamedSettingRequest extends FormRequest
return [
'name' => ['required', 'string', 'max:255'],
'position' => ['required', 'integer', 'min:1'],
'is_parent' => ['nullable', 'boolean'],
];
}
}
+8 -9
View File
@@ -8,20 +8,19 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Fillable(['is_builtin', 'name', 'position', 'project_id', 'system_key'])]
#[Fillable(['name', 'position', 'project_id', 'is_parent'])]
class TicketType extends Model
{
/**
* @var array<int, string>
*/
public const BUILTIN_KEYS = [
'task',
'parent',
];
/** @use HasFactory<TicketTypeFactory> */
use HasFactory;
protected function casts(): array
{
return [
'is_parent' => 'boolean',
];
}
public function project(): BelongsTo
{
return $this->belongsTo(Project::class);