184 lines
6.4 KiB
PHP
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;
|
|
}
|
|
}
|