From 634b78904b24413f14d0630314a37f75e4fe95b9 Mon Sep 17 00:00:00 2001 From: Twyco Date: Wed, 24 Jun 2026 00:29:04 +0200 Subject: [PATCH] Revert "Merge pull request 'KAN-11 Move remaining project metadata ownership to templates' (#94) from koda/kan-11-restliche-projekt-konfiguration-auf-templates-umstellen into epic/kan-3" This reverts commit d39d21d3036e526a7608373c34ec54059c85710d, reversing changes made to a8952d78672d74dc5f549169a1406278e93fa19a. --- app/Actions/Projects/CreateProject.php | 24 ++-- app/Actions/Tickets/CreateProjectTicket.php | 10 +- .../Requests/Api/MoveProjectTicketRequest.php | 7 +- .../DestroyProjectTicketTypeRequest.php | 7 +- .../Requests/MoveProjectTicketRequest.php | 7 +- .../StoreProjectTicketBranchRequest.php | 16 +-- .../Requests/StoreProjectTicketRequest.php | 15 +-- app/Http/Requests/StoreTicketLinkRequest.php | 7 +- .../Requests/StoreTicketStatusRequest.php | 7 +- .../Requests/UpdateProjectTicketRequest.php | 17 +-- .../UpdateTicketStatusTransitionsRequest.php | 7 +- .../ValidateProjectTicketBranchRequest.php | 16 +-- app/Models/BoardColumn.php | 11 +- .../SyncsProjectTemplateOwnership.php | 41 ------- app/Models/NotificationAction.php | 10 -- app/Models/Project.php | 48 ++------ app/Models/RepositoryConnection.php | 10 -- app/Models/TicketFieldDefinition.php | 11 +- app/Models/TicketLinkType.php | 11 +- app/Models/TicketPriority.php | 11 +- app/Models/TicketStatus.php | 11 +- app/Models/TicketType.php | 11 +- ..._project_metadata_to_project_templates.php | 116 ------------------ tests/Feature/Api/ProjectApiTest.php | 7 +- .../ProjectTemplateBaselineMigrationTest.php | 73 ----------- .../Projects/ProjectControllerTest.php | 7 +- 26 files changed, 45 insertions(+), 473 deletions(-) delete mode 100644 app/Models/Concerns/SyncsProjectTemplateOwnership.php delete mode 100644 database/migrations/2026_06_05_161500_move_project_metadata_to_project_templates.php diff --git a/app/Actions/Projects/CreateProject.php b/app/Actions/Projects/CreateProject.php index f0e1134..98b278f 100644 --- a/app/Actions/Projects/CreateProject.php +++ b/app/Actions/Projects/CreateProject.php @@ -27,13 +27,6 @@ class CreateProject { return DB::transaction(function () use ($attributes, $actor): Project { $project = Project::query()->create(Arr::only($attributes, ['key', 'name'])); - $baseTemplate = $this->createProjectBaseTemplate->handle($project); - - $project->forceFill([ - 'project_template_id' => $baseTemplate->id, - ])->save(); - - $project = $project->fresh() ?? $project; $this->initializeProjectTicketMetadata->handle($project); $this->permissionService->grant($actor, Permission::ProjectAdmin, $project); @@ -57,14 +50,19 @@ class CreateProject $project, $template->configuration_snapshot ?? [], ); + + $project->forceFill([ + 'project_template_id' => $template->id, + ])->save(); } - $baseTemplate->forceFill([ - 'configuration_snapshot' => $this->duplicateProjectConfiguration->export( - $project->fresh() ?? $project, - DuplicateProjectConfiguration::copyableAreas(), - ), - ])->save(); + if ($project->project_template_id === null) { + $baseTemplate = $this->createProjectBaseTemplate->handle($project->fresh() ?? $project); + + $project->forceFill([ + 'project_template_id' => $baseTemplate->id, + ])->save(); + } return $project->fresh() ?? $project; }); diff --git a/app/Actions/Tickets/CreateProjectTicket.php b/app/Actions/Tickets/CreateProjectTicket.php index 985a410..b2be15a 100644 --- a/app/Actions/Tickets/CreateProjectTicket.php +++ b/app/Actions/Tickets/CreateProjectTicket.php @@ -47,13 +47,9 @@ class CreateProjectTicket $nextSequenceNumber = (int) Ticket::query() ->where('project_id', $project->id) ->max('sequence_number') + 1; - $ticketStatusQuery = TicketStatus::query() - ->where( - $project->project_template_id === null ? 'project_id' : 'project_template_id', - $project->project_template_id ?? $project->id, - ); - - $ticketStatus = $ticketStatusQuery->findOrFail(Arr::get($attributes, 'ticket_status_id')); + $ticketStatus = TicketStatus::query() + ->where('project_id', $project->id) + ->findOrFail(Arr::get($attributes, 'ticket_status_id')); $ticket = Ticket::query()->create([ 'assignee_user_id' => Arr::get($attributes, 'assignee_user_id'), diff --git a/app/Http/Requests/Api/MoveProjectTicketRequest.php b/app/Http/Requests/Api/MoveProjectTicketRequest.php index 1ddccfa..6f87219 100644 --- a/app/Http/Requests/Api/MoveProjectTicketRequest.php +++ b/app/Http/Requests/Api/MoveProjectTicketRequest.php @@ -24,17 +24,12 @@ class MoveProjectTicketRequest extends FormRequest { /** @var Project $project */ $project = $this->route('project'); - $projectTemplateId = $project->project_template_id; - $ownershipColumn = $projectTemplateId === null ? 'project_id' : 'project_template_id'; - $ownershipValue = $projectTemplateId ?? $project->id; return [ 'ticket_status_id' => [ 'required', 'integer', - Rule::exists('ticket_statuses', 'id')->where( - fn ($query) => $query->where($ownershipColumn, $ownershipValue), - ), + Rule::exists('ticket_statuses', 'id')->where('project_id', $project->id), ], ]; } diff --git a/app/Http/Requests/DestroyProjectTicketTypeRequest.php b/app/Http/Requests/DestroyProjectTicketTypeRequest.php index 64a0617..97bc396 100644 --- a/app/Http/Requests/DestroyProjectTicketTypeRequest.php +++ b/app/Http/Requests/DestroyProjectTicketTypeRequest.php @@ -29,17 +29,12 @@ class DestroyProjectTicketTypeRequest extends FormRequest { /** @var Project $project */ $project = $this->route('project'); - $projectTemplateId = $project->project_template_id; - $ownershipColumn = $projectTemplateId === null ? 'project_id' : 'project_template_id'; - $ownershipValue = $projectTemplateId ?? $project->id; return [ 'replacement_ticket_type_id' => [ 'nullable', 'integer', - Rule::exists('ticket_types', 'id')->where( - fn ($query) => $query->where($ownershipColumn, $ownershipValue), - ), + Rule::exists('ticket_types', 'id')->where('project_id', $project->id), ], ]; } diff --git a/app/Http/Requests/MoveProjectTicketRequest.php b/app/Http/Requests/MoveProjectTicketRequest.php index 5ed7e1f..29d080a 100644 --- a/app/Http/Requests/MoveProjectTicketRequest.php +++ b/app/Http/Requests/MoveProjectTicketRequest.php @@ -29,17 +29,12 @@ class MoveProjectTicketRequest extends FormRequest { /** @var Project $project */ $project = $this->route('project'); - $projectTemplateId = $project->project_template_id; - $ownershipColumn = $projectTemplateId === null ? 'project_id' : 'project_template_id'; - $ownershipValue = $projectTemplateId ?? $project->id; return [ 'ticket_status_id' => [ 'required', 'integer', - Rule::exists('ticket_statuses', 'id')->where( - fn ($query) => $query->where($ownershipColumn, $ownershipValue), - ), + Rule::exists('ticket_statuses', 'id')->where('project_id', $project->id), ], 'redirect_to' => ['required', 'string', Rule::in(['backlog', 'board'])], ]; diff --git a/app/Http/Requests/StoreProjectTicketBranchRequest.php b/app/Http/Requests/StoreProjectTicketBranchRequest.php index 9e1f88f..2d3e10b 100644 --- a/app/Http/Requests/StoreProjectTicketBranchRequest.php +++ b/app/Http/Requests/StoreProjectTicketBranchRequest.php @@ -2,10 +2,8 @@ namespace App\Http\Requests; -use App\Models\Project; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; -use Illuminate\Validation\Rule; class StoreProjectTicketBranchRequest extends FormRequest { @@ -24,21 +22,9 @@ class StoreProjectTicketBranchRequest extends FormRequest */ public function rules(): array { - /** @var Project $project */ - $project = $this->route('project'); - $projectTemplateId = $project->project_template_id; - $ownershipColumn = $projectTemplateId === null ? 'project_id' : 'project_template_id'; - $ownershipValue = $projectTemplateId ?? $project->id; - return [ 'branch_name' => ['required', 'string', 'max:255'], - 'repository_connection_id' => [ - 'required', - 'integer', - Rule::exists('repository_connections', 'id')->where( - fn ($query) => $query->where($ownershipColumn, $ownershipValue), - ), - ], + 'repository_connection_id' => ['required', 'integer', 'exists:repository_connections,id'], ]; } } diff --git a/app/Http/Requests/StoreProjectTicketRequest.php b/app/Http/Requests/StoreProjectTicketRequest.php index 2c90e92..59036c9 100644 --- a/app/Http/Requests/StoreProjectTicketRequest.php +++ b/app/Http/Requests/StoreProjectTicketRequest.php @@ -48,9 +48,6 @@ class StoreProjectTicketRequest extends FormRequest { /** @var Project $project */ $project = $this->route('project'); - $projectTemplateId = $project->project_template_id; - $ownershipColumn = $projectTemplateId === null ? 'project_id' : 'project_template_id'; - $ownershipValue = $projectTemplateId ?? $project->id; return [ 'acceptance_criteria' => ['nullable', 'array'], @@ -69,23 +66,17 @@ class StoreProjectTicketRequest extends FormRequest 'ticket_priority_id' => [ 'required', 'integer', - Rule::exists('ticket_priorities', 'id')->where( - fn ($query) => $query->where($ownershipColumn, $ownershipValue), - ), + Rule::exists('ticket_priorities', 'id')->where('project_id', $project->id), ], 'ticket_status_id' => [ 'required', 'integer', - Rule::exists('ticket_statuses', 'id')->where( - fn ($query) => $query->where($ownershipColumn, $ownershipValue), - ), + Rule::exists('ticket_statuses', 'id')->where('project_id', $project->id), ], 'ticket_type_id' => [ 'required', 'integer', - Rule::exists('ticket_types', 'id')->where( - fn ($query) => $query->where($ownershipColumn, $ownershipValue), - ), + Rule::exists('ticket_types', 'id')->where('project_id', $project->id), ], 'story_points' => ['nullable', 'integer', 'min:0'], 'title' => ['required', 'string', 'max:255'], diff --git a/app/Http/Requests/StoreTicketLinkRequest.php b/app/Http/Requests/StoreTicketLinkRequest.php index 65904c6..50a299a 100644 --- a/app/Http/Requests/StoreTicketLinkRequest.php +++ b/app/Http/Requests/StoreTicketLinkRequest.php @@ -28,9 +28,6 @@ class StoreTicketLinkRequest extends FormRequest { /** @var Project $project */ $project = $this->route('project'); - $projectTemplateId = $project->project_template_id; - $ownershipColumn = $projectTemplateId === null ? 'project_id' : 'project_template_id'; - $ownershipValue = $projectTemplateId ?? $project->id; return [ 'linked_ticket_id' => [ @@ -41,9 +38,7 @@ class StoreTicketLinkRequest extends FormRequest 'ticket_link_type_id' => [ 'required', 'integer', - Rule::exists('ticket_link_types', 'id')->where( - fn ($query) => $query->where($ownershipColumn, $ownershipValue), - ), + Rule::exists('ticket_link_types', 'id')->where('project_id', $project->id), ], ]; } diff --git a/app/Http/Requests/StoreTicketStatusRequest.php b/app/Http/Requests/StoreTicketStatusRequest.php index 696c35f..43d4fb8 100644 --- a/app/Http/Requests/StoreTicketStatusRequest.php +++ b/app/Http/Requests/StoreTicketStatusRequest.php @@ -26,17 +26,12 @@ class StoreTicketStatusRequest extends FormRequest { /** @var Project $project */ $project = $this->route('project'); - $projectTemplateId = $project->project_template_id; - $ownershipColumn = $projectTemplateId === null ? 'project_id' : 'project_template_id'; - $ownershipValue = $projectTemplateId ?? $project->id; return [ 'board_column_id' => [ 'nullable', 'integer', - Rule::exists('board_columns', 'id')->where( - fn ($query) => $query->where($ownershipColumn, $ownershipValue), - ), + Rule::exists('board_columns', 'id')->where('project_id', $project->id), ], 'name' => ['required', 'string', 'max:255'], ]; diff --git a/app/Http/Requests/UpdateProjectTicketRequest.php b/app/Http/Requests/UpdateProjectTicketRequest.php index 9b42395..1db69e5 100644 --- a/app/Http/Requests/UpdateProjectTicketRequest.php +++ b/app/Http/Requests/UpdateProjectTicketRequest.php @@ -51,9 +51,6 @@ class UpdateProjectTicketRequest extends FormRequest { /** @var Project $project */ $project = $this->route('project'); - $projectTemplateId = $project->project_template_id; - $ownershipColumn = $projectTemplateId === null ? 'project_id' : 'project_template_id'; - $ownershipValue = $projectTemplateId ?? $project->id; return [ 'acceptance_criteria' => ['nullable', 'array'], @@ -71,9 +68,7 @@ class UpdateProjectTicketRequest extends FormRequest 'field_values.*.ticket_field_definition_id' => [ 'required', 'integer', - Rule::exists('ticket_field_definitions', 'id')->where( - fn ($query) => $query->where($ownershipColumn, $ownershipValue), - ), + Rule::exists('ticket_field_definitions', 'id')->where('project_id', $project->id), ], 'field_values.*.value_number' => ['nullable', 'numeric'], 'field_values.*.value_user_id' => ['nullable', 'integer', Rule::exists('users', 'id')], @@ -86,16 +81,12 @@ class UpdateProjectTicketRequest extends FormRequest 'ticket_priority_id' => [ 'required', 'integer', - Rule::exists('ticket_priorities', 'id')->where( - fn ($query) => $query->where($ownershipColumn, $ownershipValue), - ), + Rule::exists('ticket_priorities', 'id')->where('project_id', $project->id), ], 'ticket_type_id' => [ 'required', 'integer', - Rule::exists('ticket_types', 'id')->where( - fn ($query) => $query->where($ownershipColumn, $ownershipValue), - ), + Rule::exists('ticket_types', 'id')->where('project_id', $project->id), ], 'title' => ['required', 'string', 'max:255'], 'story_points' => ['nullable', 'integer', 'min:0'], @@ -150,7 +141,7 @@ class UpdateProjectTicketRequest extends FormRequest $requestedTicketTypeId = Arr::get($validated, 'ticket_type_id'); $requestedTicketType = TicketType::query() - ->where($project->project_template_id === null ? 'project_id' : 'project_template_id', $project->project_template_id ?? $project->id) + ->where('project_id', $project->id) ->find($requestedTicketTypeId); if ( diff --git a/app/Http/Requests/UpdateTicketStatusTransitionsRequest.php b/app/Http/Requests/UpdateTicketStatusTransitionsRequest.php index 60372ef..c6c8681 100644 --- a/app/Http/Requests/UpdateTicketStatusTransitionsRequest.php +++ b/app/Http/Requests/UpdateTicketStatusTransitionsRequest.php @@ -29,17 +29,12 @@ class UpdateTicketStatusTransitionsRequest extends FormRequest { /** @var Project $project */ $project = $this->route('project'); - $projectTemplateId = $project->project_template_id; - $ownershipColumn = $projectTemplateId === null ? 'project_id' : 'project_template_id'; - $ownershipValue = $projectTemplateId ?? $project->id; return [ 'allowed_ticket_status_ids' => ['nullable', 'array'], 'allowed_ticket_status_ids.*' => [ 'integer', - Rule::exists('ticket_statuses', 'id')->where( - fn ($query) => $query->where($ownershipColumn, $ownershipValue), - ), + Rule::exists('ticket_statuses', 'id')->where('project_id', $project->id), ], ]; } diff --git a/app/Http/Requests/ValidateProjectTicketBranchRequest.php b/app/Http/Requests/ValidateProjectTicketBranchRequest.php index ebae66a..92f4f2b 100644 --- a/app/Http/Requests/ValidateProjectTicketBranchRequest.php +++ b/app/Http/Requests/ValidateProjectTicketBranchRequest.php @@ -2,10 +2,8 @@ namespace App\Http\Requests; -use App\Models\Project; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; -use Illuminate\Validation\Rule; class ValidateProjectTicketBranchRequest extends FormRequest { @@ -24,21 +22,9 @@ class ValidateProjectTicketBranchRequest extends FormRequest */ public function rules(): array { - /** @var Project $project */ - $project = $this->route('project'); - $projectTemplateId = $project->project_template_id; - $ownershipColumn = $projectTemplateId === null ? 'project_id' : 'project_template_id'; - $ownershipValue = $projectTemplateId ?? $project->id; - return [ 'branch_name' => ['required', 'string', 'max:255'], - 'repository_connection_id' => [ - 'required', - 'integer', - Rule::exists('repository_connections', 'id')->where( - fn ($query) => $query->where($ownershipColumn, $ownershipValue), - ), - ], + 'repository_connection_id' => ['required', 'integer', 'exists:repository_connections,id'], ]; } } diff --git a/app/Models/BoardColumn.php b/app/Models/BoardColumn.php index c1fb63b..9a5c634 100644 --- a/app/Models/BoardColumn.php +++ b/app/Models/BoardColumn.php @@ -2,7 +2,6 @@ namespace App\Models; -use App\Models\Concerns\SyncsProjectTemplateOwnership; use Database\Factories\BoardColumnFactory; use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -11,19 +10,11 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasManyThrough; -#[Fillable(['name', 'position', 'project_id', 'project_template_id'])] +#[Fillable(['name', 'position', 'project_id'])] class BoardColumn extends Model { /** @use HasFactory */ use HasFactory; - use SyncsProjectTemplateOwnership; - - protected static function booted(): void - { - static::saving(function (self $boardColumn): void { - $boardColumn->syncProjectTemplateOwnership(); - }); - } public function project(): BelongsTo { diff --git a/app/Models/Concerns/SyncsProjectTemplateOwnership.php b/app/Models/Concerns/SyncsProjectTemplateOwnership.php deleted file mode 100644 index cf02f1d..0000000 --- a/app/Models/Concerns/SyncsProjectTemplateOwnership.php +++ /dev/null @@ -1,41 +0,0 @@ -getAttribute('project_template_id'); - $projectId = $this->getAttribute('project_id'); - - if ($projectTemplateId === null && $projectId !== null) { - $projectTemplateId = Project::query() - ->whereKey($projectId) - ->value('project_template_id'); - - if ($projectTemplateId !== null) { - $this->setAttribute('project_template_id', $projectTemplateId); - } - } - - if ($projectId === null && $projectTemplateId !== null) { - $projectId = ProjectTemplate::query() - ->whereKey($projectTemplateId) - ->value('project_id'); - - if ($projectId !== null) { - $this->setAttribute('project_id', $projectId); - } - } - } - - public function projectTemplate(): BelongsTo - { - return $this->belongsTo(ProjectTemplate::class); - } -} diff --git a/app/Models/NotificationAction.php b/app/Models/NotificationAction.php index d2217c9..e9e6cc2 100644 --- a/app/Models/NotificationAction.php +++ b/app/Models/NotificationAction.php @@ -4,7 +4,6 @@ namespace App\Models; use App\Enums\NotificationChannel; use App\Enums\NotificationEventType; -use App\Models\Concerns\SyncsProjectTemplateOwnership; use Database\Factories\NotificationActionFactory; use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Hidden; @@ -24,14 +23,12 @@ use Illuminate\Database\Eloquent\Relations\HasMany; 'message_template', 'name', 'project_id', - 'project_template_id', ])] #[Hidden(['delivery_url'])] class NotificationAction extends Model { /** @use HasFactory */ use HasFactory; - use SyncsProjectTemplateOwnership; /** * @return array @@ -48,13 +45,6 @@ class NotificationAction extends Model ]; } - protected static function booted(): void - { - static::saving(function (self $notificationAction): void { - $notificationAction->syncProjectTemplateOwnership(); - }); - } - public function scopeEnabled(Builder $query): Builder { return $query->where('enabled', true); diff --git a/app/Models/Project.php b/app/Models/Project.php index 043cb8e..ec907cb 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -59,83 +59,51 @@ class Project extends Model public function boardColumns(): HasMany { - return $this->hasMany( - BoardColumn::class, - $this->project_template_id === null ? 'project_id' : 'project_template_id', - $this->project_template_id === null ? 'id' : 'project_template_id', - ) + return $this->hasMany(BoardColumn::class) ->orderBy('position'); } public function ticketFieldDefinitions(): HasMany { - return $this->hasMany( - TicketFieldDefinition::class, - $this->project_template_id === null ? 'project_id' : 'project_template_id', - $this->project_template_id === null ? 'id' : 'project_template_id', - ) + return $this->hasMany(TicketFieldDefinition::class) ->orderBy('position'); } public function ticketPriorities(): HasMany { - return $this->hasMany( - TicketPriority::class, - $this->project_template_id === null ? 'project_id' : 'project_template_id', - $this->project_template_id === null ? 'id' : 'project_template_id', - ) + return $this->hasMany(TicketPriority::class) ->orderBy('position'); } public function ticketLinkTypes(): HasMany { - return $this->hasMany( - TicketLinkType::class, - $this->project_template_id === null ? 'project_id' : 'project_template_id', - $this->project_template_id === null ? 'id' : 'project_template_id', - ) + return $this->hasMany(TicketLinkType::class) ->orderBy('position'); } public function ticketStatuses(): HasMany { - return $this->hasMany( - TicketStatus::class, - $this->project_template_id === null ? 'project_id' : 'project_template_id', - $this->project_template_id === null ? 'id' : 'project_template_id', - ) + return $this->hasMany(TicketStatus::class) ->orderBy('board_column_id') ->orderBy('position'); } public function ticketTypes(): HasMany { - return $this->hasMany( - TicketType::class, - $this->project_template_id === null ? 'project_id' : 'project_template_id', - $this->project_template_id === null ? 'id' : 'project_template_id', - ) + return $this->hasMany(TicketType::class) ->orderBy('position'); } public function repositoryConnections(): HasMany { - return $this->hasMany( - RepositoryConnection::class, - $this->project_template_id === null ? 'project_id' : 'project_template_id', - $this->project_template_id === null ? 'id' : 'project_template_id', - ) + return $this->hasMany(RepositoryConnection::class) ->orderBy('provider') ->orderBy('repository_full_name'); } public function notificationActions(): HasMany { - return $this->hasMany( - NotificationAction::class, - $this->project_template_id === null ? 'project_id' : 'project_template_id', - $this->project_template_id === null ? 'id' : 'project_template_id', - ) + return $this->hasMany(NotificationAction::class) ->orderBy('name') ->orderBy('id'); } diff --git a/app/Models/RepositoryConnection.php b/app/Models/RepositoryConnection.php index 9095d6d..44414e5 100644 --- a/app/Models/RepositoryConnection.php +++ b/app/Models/RepositoryConnection.php @@ -4,7 +4,6 @@ namespace App\Models; use App\Actions\Git\Data\GitRepository; use App\Actions\Git\GitProviderKey; -use App\Models\Concerns\SyncsProjectTemplateOwnership; use Database\Factories\RepositoryConnectionFactory; use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -15,7 +14,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; 'base_url', 'owner_name', 'project_id', - 'project_template_id', 'provider', 'provider_repository_id', 'repository_full_name', @@ -26,7 +24,6 @@ class RepositoryConnection extends Model { /** @use HasFactory */ use HasFactory; - use SyncsProjectTemplateOwnership; /** * @return array @@ -38,13 +35,6 @@ class RepositoryConnection extends Model ]; } - protected static function booted(): void - { - static::saving(function (self $repositoryConnection): void { - $repositoryConnection->syncProjectTemplateOwnership(); - }); - } - public function project(): BelongsTo { return $this->belongsTo(Project::class); diff --git a/app/Models/TicketFieldDefinition.php b/app/Models/TicketFieldDefinition.php index b5aaf27..017a758 100644 --- a/app/Models/TicketFieldDefinition.php +++ b/app/Models/TicketFieldDefinition.php @@ -2,7 +2,6 @@ namespace App\Models; -use App\Models\Concerns\SyncsProjectTemplateOwnership; use Database\Factories\TicketFieldDefinitionFactory; use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -10,7 +9,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; -#[Fillable(['field_type', 'label', 'position', 'project_id', 'project_template_id'])] +#[Fillable(['field_type', 'label', 'position', 'project_id'])] class TicketFieldDefinition extends Model { /** @@ -23,14 +22,6 @@ class TicketFieldDefinition extends Model /** @use HasFactory */ use HasFactory; - use SyncsProjectTemplateOwnership; - - protected static function booted(): void - { - static::saving(function (self $ticketFieldDefinition): void { - $ticketFieldDefinition->syncProjectTemplateOwnership(); - }); - } public function project(): BelongsTo { diff --git a/app/Models/TicketLinkType.php b/app/Models/TicketLinkType.php index d34c2f6..f66f50e 100644 --- a/app/Models/TicketLinkType.php +++ b/app/Models/TicketLinkType.php @@ -2,7 +2,6 @@ namespace App\Models; -use App\Models\Concerns\SyncsProjectTemplateOwnership; use Database\Factories\TicketLinkTypeFactory; use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -10,19 +9,11 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; -#[Fillable(['label', 'counterpart_label', 'position', 'project_id', 'project_template_id'])] +#[Fillable(['label', 'counterpart_label', 'position', 'project_id'])] class TicketLinkType extends Model { /** @use HasFactory */ use HasFactory; - use SyncsProjectTemplateOwnership; - - protected static function booted(): void - { - static::saving(function (self $ticketLinkType): void { - $ticketLinkType->syncProjectTemplateOwnership(); - }); - } public function counterpartLabel(): string { diff --git a/app/Models/TicketPriority.php b/app/Models/TicketPriority.php index 1f37f56..92b1f9c 100644 --- a/app/Models/TicketPriority.php +++ b/app/Models/TicketPriority.php @@ -2,26 +2,17 @@ namespace App\Models; -use App\Models\Concerns\SyncsProjectTemplateOwnership; use Database\Factories\TicketPriorityFactory; use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; -#[Fillable(['name', 'position', 'project_id', 'project_template_id'])] +#[Fillable(['name', 'position', 'project_id'])] class TicketPriority extends Model { /** @use HasFactory */ use HasFactory; - use SyncsProjectTemplateOwnership; - - protected static function booted(): void - { - static::saving(function (self $ticketPriority): void { - $ticketPriority->syncProjectTemplateOwnership(); - }); - } public function project(): BelongsTo { diff --git a/app/Models/TicketStatus.php b/app/Models/TicketStatus.php index 0bfe7fe..e215340 100644 --- a/app/Models/TicketStatus.php +++ b/app/Models/TicketStatus.php @@ -2,7 +2,6 @@ namespace App\Models; -use App\Models\Concerns\SyncsProjectTemplateOwnership; use Database\Factories\TicketStatusFactory; use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -12,23 +11,15 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Support\Str; -#[Fillable(['board_column_id', 'name', 'position', 'project_id', 'project_template_id', 'slug'])] +#[Fillable(['board_column_id', 'name', 'position', 'project_id', 'slug'])] class TicketStatus extends Model { /** @use HasFactory */ use HasFactory; - use SyncsProjectTemplateOwnership; protected static function booted(): void { static::saving(function (self $ticketStatus): void { - if ($ticketStatus->project_template_id === null && $ticketStatus->board_column_id !== null) { - $ticketStatus->project_template_id = BoardColumn::query() - ->whereKey($ticketStatus->board_column_id) - ->value('project_template_id'); - } - - $ticketStatus->syncProjectTemplateOwnership(); $ticketStatus->slug = Str::slug($ticketStatus->name); }); } diff --git a/app/Models/TicketType.php b/app/Models/TicketType.php index 0a38385..22cff08 100644 --- a/app/Models/TicketType.php +++ b/app/Models/TicketType.php @@ -2,7 +2,6 @@ namespace App\Models; -use App\Models\Concerns\SyncsProjectTemplateOwnership; use Database\Factories\TicketTypeFactory; use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -10,19 +9,11 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; -#[Fillable(['name', 'position', 'project_id', 'project_template_id', 'is_parent'])] +#[Fillable(['name', 'position', 'project_id', 'is_parent'])] class TicketType extends Model { /** @use HasFactory */ use HasFactory; - use SyncsProjectTemplateOwnership; - - protected static function booted(): void - { - static::saving(function (self $ticketType): void { - $ticketType->syncProjectTemplateOwnership(); - }); - } protected function casts(): array { diff --git a/database/migrations/2026_06_05_161500_move_project_metadata_to_project_templates.php b/database/migrations/2026_06_05_161500_move_project_metadata_to_project_templates.php deleted file mode 100644 index 01838fc..0000000 --- a/database/migrations/2026_06_05_161500_move_project_metadata_to_project_templates.php +++ /dev/null @@ -1,116 +0,0 @@ - - */ - private array $tables = [ - 'board_columns', - 'ticket_field_definitions', - 'ticket_link_types', - 'ticket_priorities', - 'ticket_statuses', - 'ticket_types', - 'repository_connections', - 'notification_actions', - ]; - - public function up(): void - { - foreach ($this->tables as $table) { - Schema::table($table, function (Blueprint $table): void { - $table->foreignId('project_template_id') - ->nullable() - ->after('project_id') - ->constrained('project_templates') - ->nullOnDelete(); - }); - } - - $projects = DB::table('projects') - ->leftJoin('project_templates', 'project_templates.id', '=', 'projects.project_template_id') - ->select([ - 'projects.id', - 'projects.key', - 'projects.name', - 'projects.project_template_id', - 'project_templates.project_id as template_project_id', - ]) - ->get(); - - foreach ($projects as $project) { - $projectTemplateId = $project->project_template_id; - - if ($projectTemplateId === null || (int) ($project->template_project_id ?? 0) !== (int) $project->id) { - $projectTemplateId = DB::table('project_templates')->insertGetId([ - 'project_id' => $project->id, - 'source_project_id' => $project->id, - 'name' => $project->name.' base template', - 'slug' => Str::lower($project->key).'-base-template', - 'is_published' => false, - 'copy_areas' => json_encode([ - 'project_settings', - 'board_workflow', - 'ticket_types', - 'ticket_priorities', - 'ticket_fields', - 'ticket_link_types', - 'repository_connections', - 'notification_actions', - ], JSON_THROW_ON_ERROR), - 'configuration_snapshot' => json_encode([], JSON_THROW_ON_ERROR), - 'created_at' => now(), - 'updated_at' => now(), - ]); - - DB::table('projects') - ->where('id', $project->id) - ->update([ - 'project_template_id' => $projectTemplateId, - 'updated_at' => now(), - ]); - } - - foreach ($this->tables as $table) { - DB::table($table) - ->where('project_id', $project->id) - ->update(['project_template_id' => $projectTemplateId]); - } - - $projectModel = Project::query()->find($project->id); - - if ($projectModel !== null) { - DB::table('project_templates') - ->where('id', $projectTemplateId) - ->update([ - 'configuration_snapshot' => json_encode( - app(DuplicateProjectConfiguration::class)->export( - $projectModel, - DuplicateProjectConfiguration::copyableAreas(), - ), - JSON_THROW_ON_ERROR, - ), - 'updated_at' => now(), - ]); - } - } - } - - public function down(): void - { - foreach (array_reverse($this->tables) as $table) { - Schema::table($table, function (Blueprint $table): void { - $table->dropConstrainedForeignId('project_template_id'); - }); - } - } -}; diff --git a/tests/Feature/Api/ProjectApiTest.php b/tests/Feature/Api/ProjectApiTest.php index b08ef56..ff069f8 100644 --- a/tests/Feature/Api/ProjectApiTest.php +++ b/tests/Feature/Api/ProjectApiTest.php @@ -102,12 +102,7 @@ class ProjectApiTest extends TestCase $project->refresh(); - $this->assertNotSame($template->id, $project->project_template_id); - $this->assertDatabaseHas('project_templates', [ - 'id' => $project->project_template_id, - 'project_id' => $project->id, - 'is_published' => false, - ]); + $this->assertSame($template->id, $project->project_template_id); $this->assertFalse($project->show_author); $this->assertFalse($project->show_story_points); $this->assertSame(['Spike'], $project->ticketTypes()->pluck('name')->all()); diff --git a/tests/Feature/Database/ProjectTemplateBaselineMigrationTest.php b/tests/Feature/Database/ProjectTemplateBaselineMigrationTest.php index dfcdf21..0722364 100644 --- a/tests/Feature/Database/ProjectTemplateBaselineMigrationTest.php +++ b/tests/Feature/Database/ProjectTemplateBaselineMigrationTest.php @@ -2,7 +2,6 @@ namespace Tests\Feature\Database; -use App\Actions\Projects\DuplicateProjectConfiguration; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\DB; use Tests\TestCase; @@ -71,76 +70,4 @@ class ProjectTemplateBaselineMigrationTest extends TestCase $this->assertSame(0, $template->is_published); $this->assertSame('lgcy-base-template', $template->slug); } - - public function test_metadata_migration_moves_existing_configuration_to_the_project_template_and_preserves_snapshot(): void - { - Artisan::call('migrate:fresh', [ - '--path' => [ - 'database/migrations/0001_01_01_000000_create_users_table.php', - 'database/migrations/0001_01_01_000001_create_cache_table.php', - 'database/migrations/0001_01_01_000002_create_jobs_table.php', - 'database/migrations/2025_08_14_170933_add_two_factor_columns_to_users_table.php', - 'database/migrations/2026_03_24_234007_create_projects_table.php', - 'database/migrations/2026_03_24_234734_create_teams_table.php', - 'database/migrations/2026_03_24_234735_create_team_user_table.php', - 'database/migrations/2026_03_25_002100_create_board_columns_table.php', - 'database/migrations/2026_03_25_003100_create_ticket_types_table.php', - 'database/migrations/2026_03_25_003101_create_ticket_priorities_table.php', - 'database/migrations/2026_03_25_003102_create_ticket_field_definitions_table.php', - 'database/migrations/2026_03_25_004098_create_ticket_statuses_table.php', - 'database/migrations/2026_03_25_004099_create_ticket_statuses_transitionstable.php', - 'database/migrations/2026_03_25_004100_create_tickets_table.php', - 'database/migrations/2026_03_25_065302_create_ticket_worklog_entries_table.php', - 'database/migrations/2026_03_25_070706_create_ticket_field_values_table.php', - 'database/migrations/2026_03_25_073715_create_ticket_links_table.php', - 'database/migrations/2026_03_25_073715_create_ticket_link_types_table.php', - 'database/migrations/2026_03_26_074522_create_user_git_connections_table.php', - 'database/migrations/2026_03_26_075849_create_repository_connections_table.php', - 'database/migrations/2026_04_12_120000_create_permission_assignments_table.php', - 'database/migrations/2026_04_20_174556_create_user_applications_table.php', - 'database/migrations/2026_04_20_194646_create_notification_actions_table.php', - 'database/migrations/2026_04_20_194646_create_notification_delivery_logs_table.php', - 'database/migrations/2026_04_24_194800_add_filters_to_notification_actions_table.php', - 'database/migrations/2026_04_24_203500_create_acceptance_criteria_table.php', - 'database/migrations/2026_05_19_230500_drop_acceptance_criteria_from_tickets_table.php', - 'database/migrations/2026_05_22_124700_add_must_change_password_on_next_login_to_users_table.php', - 'database/migrations/2026_05_23_101900_add_counterpart_label_to_ticket_link_types_table.php', - 'database/migrations/2026_05_25_011500_create_project_templates_table.php', - 'database/migrations/2026_05_25_013000_project_templates_as_project_baselines.php', - ], - ]); - - DB::table('projects')->insert([ - 'name' => 'Legacy Project', - 'key' => 'LGCY', - 'show_author' => 0, - 'show_story_points' => 1, - 'project_template_id' => null, - 'created_at' => now(), - 'updated_at' => now(), - ]); - - $projectId = (int) DB::table('projects')->where('key', 'LGCY')->value('id'); - - DB::table('ticket_types')->insert([ - 'project_id' => $projectId, - 'name' => 'Bug', - 'position' => 1, - 'is_parent' => 0, - 'created_at' => now(), - 'updated_at' => now(), - ]); - - Artisan::call('migrate', [ - '--path' => 'database/migrations/2026_06_05_161500_move_project_metadata_to_project_templates.php', - ]); - - $project = DB::table('projects')->where('id', $projectId)->first(); - $template = DB::table('project_templates')->where('id', $project->project_template_id)->first(); - $ticketType = DB::table('ticket_types')->where('project_id', $projectId)->first(); - $snapshot = json_decode((string) $template->configuration_snapshot, true, 512, JSON_THROW_ON_ERROR); - - $this->assertSame($project->project_template_id, $ticketType->project_template_id); - $this->assertSame(['Bug'], array_column($snapshot[DuplicateProjectConfiguration::COPY_TICKET_TYPES], 'name')); - } } diff --git a/tests/Feature/Projects/ProjectControllerTest.php b/tests/Feature/Projects/ProjectControllerTest.php index 06605d4..a6ef6ce 100644 --- a/tests/Feature/Projects/ProjectControllerTest.php +++ b/tests/Feature/Projects/ProjectControllerTest.php @@ -285,12 +285,7 @@ class ProjectControllerTest extends TestCase $project->refresh(); - $this->assertNotSame($template->id, $project->project_template_id); - $this->assertDatabaseHas('project_templates', [ - 'id' => $project->project_template_id, - 'project_id' => $project->id, - 'is_published' => false, - ]); + $this->assertSame($template->id, $project->project_template_id); $this->assertSame(false, $project->show_author); $this->assertSame(false, $project->show_story_points); $this->assertDatabaseHas('ticket_types', [