Files
fotospiel-app/app/Services/Tenant/TaskCollectionImportService.php
Codex Agent 1c5412e82c
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled
Enforce task limits and update event form
2026-01-21 09:49:30 +01:00

184 lines
6.4 KiB
PHP

<?php
namespace App\Services\Tenant;
use App\Models\Event;
use App\Models\Task;
use App\Models\TaskCollection;
use Illuminate\Database\DatabaseManager;
use Illuminate\Support\Str;
use RuntimeException;
class TaskCollectionImportService
{
public function __construct(private readonly DatabaseManager $db) {}
/**
* @return array{collection: TaskCollection, created_task_ids: array<int>, attached_task_ids: array<int>, skipped_task_ids: array<int>}
*/
public function import(TaskCollection $collection, Event $event): array
{
if ($collection->tenant_id && $collection->tenant_id !== $event->tenant_id) {
throw new RuntimeException('Task collection is not accessible for this tenant.');
}
$collection->loadMissing('tasks');
return $this->db->transaction(function () use ($collection, $event) {
$tenantId = $event->tenant_id;
$targetCollection = $this->resolveTenantCollection($collection, $tenantId);
$createdTaskIds = [];
$attachedTaskIds = [];
$skippedTaskIds = [];
$event->loadMissing(['eventPackage.package', 'eventPackages.package']);
$eventPackage = $event->eventPackage;
if (! $eventPackage && method_exists($event, 'eventPackages')) {
$eventPackage = $event->eventPackages()
->with('package')
->orderByDesc('purchased_at')
->orderByDesc('created_at')
->first();
}
$taskLimit = $eventPackage?->effectiveLimits()['max_tasks'] ?? null;
$remaining = $taskLimit === null ? null : max(0, (int) $taskLimit - $event->tasks()->count());
foreach ($collection->tasks as $task) {
if ($remaining !== null && $remaining <= 0) {
$skippedTaskIds[] = $task->id;
continue;
}
$tenantTask = $this->resolveTenantTask($task, $targetCollection, $tenantId);
if ($tenantTask->wasRecentlyCreated) {
$createdTaskIds[] = $tenantTask->id;
}
if (! $tenantTask->assignedEvents()->where('event_id', $event->id)->exists()) {
$tenantTask->assignedEvents()->attach($event->id);
$attachedTaskIds[] = $tenantTask->id;
if ($remaining !== null) {
$remaining = max(0, $remaining - 1);
}
}
}
$event->taskCollections()->syncWithoutDetaching([
$targetCollection->id => ['sort_order' => $targetCollection->position ?? 0],
]);
return [
'collection' => $targetCollection->fresh(),
'created_task_ids' => $createdTaskIds,
'attached_task_ids' => $attachedTaskIds,
'skipped_task_ids' => $skippedTaskIds,
];
});
}
protected function resolveTenantCollection(TaskCollection $collection, int $tenantId): TaskCollection
{
if ($collection->tenant_id === $tenantId) {
return $collection;
}
$existing = TaskCollection::query()
->where('tenant_id', $tenantId)
->where('source_collection_id', $collection->id)
->first();
if ($existing) {
return $existing;
}
return TaskCollection::create([
'tenant_id' => $tenantId,
'source_collection_id' => $collection->id,
'event_type_id' => $collection->event_type_id,
'slug' => $this->buildCollectionSlug($collection->slug, $tenantId),
'name_translations' => $collection->name_translations,
'description_translations' => $collection->description_translations,
'is_default' => false,
'position' => $collection->position,
]);
}
protected function resolveTenantTask(Task $templateTask, TaskCollection $targetCollection, int $tenantId): Task
{
if ($templateTask->tenant_id === $tenantId) {
if ($templateTask->collection_id !== $targetCollection->id) {
$templateTask->update(['collection_id' => $targetCollection->id]);
}
return $templateTask;
}
$sourceId = $templateTask->source_task_id ?: $templateTask->id;
$existing = Task::query()
->where('tenant_id', $tenantId)
->where('source_task_id', $sourceId)
->first();
if ($existing) {
$existing->update([
'collection_id' => $targetCollection->id,
'source_collection_id' => $templateTask->source_collection_id ?: $targetCollection->source_collection_id ?: $targetCollection->id,
]);
return tap($existing)->refresh();
}
$slugBase = $templateTask->slug ?: ($templateTask->title['en'] ?? $templateTask->title['de'] ?? 'task');
$slug = $this->buildTaskSlug($slugBase);
$cloned = Task::create([
'tenant_id' => $tenantId,
'slug' => $slug,
'emotion_id' => $templateTask->emotion_id,
'event_type_id' => $templateTask->event_type_id,
'title' => $templateTask->title,
'description' => $templateTask->description,
'example_text' => $templateTask->example_text,
'due_date' => null,
'is_completed' => false,
'priority' => $templateTask->priority,
'collection_id' => $targetCollection->id,
'difficulty' => $templateTask->difficulty,
'sort_order' => $templateTask->sort_order,
'is_active' => true,
'source_task_id' => $sourceId,
'source_collection_id' => $templateTask->source_collection_id ?: $templateTask->collection_id,
]);
return $cloned;
}
protected function buildCollectionSlug(?string $slug, int $tenantId): string
{
$base = Str::slug(($slug ?: 'collection').'-'.$tenantId);
do {
$candidate = $base.'-'.Str::random(4);
} while (TaskCollection::where('slug', $candidate)->exists());
return $candidate;
}
protected function buildTaskSlug(string $base): string
{
$slugBase = Str::slug($base) ?: 'task';
do {
$candidate = $slugBase.'-'.Str::random(6);
} while (Task::where('slug', $candidate)->exists());
return $candidate;
}
}