tenant->id; $query = Task::query() ->where(function ($inner) use ($tenantId) { $inner->whereNull('tenant_id') ->orWhere('tenant_id', $tenantId); }) ->with(['taskCollection', 'assignedEvents']) ->orderByRaw('tenant_id is null desc') ->orderBy('sort_order') ->orderBy('created_at', 'desc'); // Search and filters if ($search = $request->get('search')) { $query->where(function ($inner) use ($search) { $like = '%'.$search.'%'; $inner->where('title->de', 'like', $like) ->orWhere('title->en', 'like', $like) ->orWhere('description->de', 'like', $like) ->orWhere('description->en', 'like', $like); }); } if ($collectionId = $request->get('collection_id')) { $query->whereHas('taskCollection', fn ($q) => $q->where('id', $collectionId)); } if ($eventId = $request->get('event_id')) { $query->whereHas('assignedEvents', fn ($q) => $q->where('id', $eventId)); } $perPage = $request->get('per_page', 15); $tasks = $query->paginate($perPage); return TaskResource::collection($tasks); } /** * Store a newly created task in storage. */ public function store(TaskStoreRequest $request): JsonResponse { $collectionId = $request->input('collection_id'); $collection = $collectionId ? $this->resolveAccessibleCollection($request, $collectionId) : null; $payload = $this->prepareTaskPayload($request->validated(), $request->tenant->id); $payload['tenant_id'] = $request->tenant->id; if ($collection) { $payload['collection_id'] = $collection->id; $payload['source_collection_id'] = $collection->source_collection_id ?? $collection->id; } $task = Task::create($payload); $task->load(['taskCollection', 'assignedEvents']); return response()->json([ 'message' => 'Task erfolgreich erstellt.', 'data' => new TaskResource($task), ], 201); } /** * Display the specified task. */ public function show(Request $request, Task $task): JsonResponse { if ($task->tenant_id && $task->tenant_id !== $request->tenant->id) { abort(404, 'Task nicht gefunden.'); } $task->load(['taskCollection', 'assignedEvents']); return response()->json(new TaskResource($task)); } /** * Update the specified task in storage. */ public function update(TaskUpdateRequest $request, Task $task): JsonResponse { if ($task->tenant_id !== $request->tenant->id) { abort(404, 'Task nicht gefunden.'); } $collectionId = $request->input('collection_id'); $collection = $collectionId ? $this->resolveAccessibleCollection($request, $collectionId) : null; $payload = $this->prepareTaskPayload($request->validated(), $request->tenant->id, $task); if ($collection) { $payload['collection_id'] = $collection->id; $payload['source_collection_id'] = $collection->source_collection_id ?? $collection->id; } $task->update($payload); $task->load(['taskCollection', 'assignedEvents']); return response()->json([ 'message' => 'Task erfolgreich aktualisiert.', 'data' => new TaskResource($task), ]); } /** * Remove the specified task from storage. */ public function destroy(Request $request, Task $task): JsonResponse { if ($task->tenant_id !== $request->tenant->id) { abort(404, 'Task nicht gefunden.'); } $task->delete(); return response()->json([ 'message' => 'Task erfolgreich gelöscht.', ]); } /** * Assign task to an event. */ public function assignToEvent(Request $request, Task $task, Event $event): JsonResponse { if ($task->tenant_id !== $request->tenant->id || $event->tenant_id !== $request->tenant->id) { abort(404); } if ($task->assignedEvents()->where('event_id', $event->id)->exists()) { return response()->json(['message' => 'Task ist bereits diesem Event zugewiesen.'], 409); } $task->assignedEvents()->attach($event->id); return response()->json([ 'message' => 'Task erfolgreich dem Event zugewiesen.', ]); } /** * Bulk assign tasks to an event. */ public function bulkAssignToEvent(Request $request, Event $event): JsonResponse { if ($event->tenant_id !== $request->tenant->id) { abort(404); } $taskIds = $request->input('task_ids', []); if (empty($taskIds)) { return ApiError::response( 'task_ids_missing', 'Keine Aufgaben angegeben', 'Bitte wähle mindestens eine Aufgabe aus.', Response::HTTP_BAD_REQUEST ); } $tasks = Task::whereIn('id', $taskIds) ->where('tenant_id', $request->tenant->id) ->get(); $attached = 0; foreach ($tasks as $task) { if (! $task->assignedEvents()->where('event_id', $event->id)->exists()) { $task->assignedEvents()->attach($event->id); $attached++; } } return response()->json([ 'message' => "{$attached} Tasks dem Event zugewiesen.", ]); } /** * Get tasks for a specific event. */ public function forEvent(Request $request, Event $event): AnonymousResourceCollection { if ($event->tenant_id !== $request->tenant->id) { abort(404); } $tasks = Task::whereHas('assignedEvents', fn ($q) => $q->where('event_id', $event->id)) ->with(['taskCollection']) ->orderBy('created_at', 'desc') ->paginate($request->get('per_page', 15)); return TaskResource::collection($tasks); } /** * Get tasks from a specific collection. */ public function fromCollection(Request $request, TaskCollection $collection): AnonymousResourceCollection { if ($collection->tenant_id && $collection->tenant_id !== $request->tenant->id) { abort(404); } $tasks = $collection->tasks() ->with(['assignedEvents']) ->orderBy('created_at', 'desc') ->paginate($request->get('per_page', 15)); return TaskResource::collection($tasks); } protected function resolveAccessibleCollection(Request $request, int|string $collectionId): TaskCollection { return TaskCollection::where('id', $collectionId) ->where(function ($query) use ($request) { $query->whereNull('tenant_id'); if ($request->tenant?->id) { $query->orWhere('tenant_id', $request->tenant->id); } }) ->firstOrFail(); } protected function prepareTaskPayload(array $data, int $tenantId, ?Task $original = null): array { if (array_key_exists('title', $data)) { $data['title'] = $this->normalizeTranslations($data['title'], $original?->title); } elseif (array_key_exists('title_translations', $data)) { $data['title'] = $this->normalizeTranslations($data['title_translations'], $original?->title); } if (array_key_exists('description', $data)) { $data['description'] = $this->normalizeTranslations($data['description'], $original?->description, true); } elseif (array_key_exists('description_translations', $data)) { $data['description'] = $this->normalizeTranslations( $data['description_translations'], $original?->description, true ); } if (array_key_exists('example_text', $data)) { $data['example_text'] = $this->normalizeTranslations($data['example_text'], $original?->example_text, true); } elseif (array_key_exists('example_text_translations', $data)) { $data['example_text'] = $this->normalizeTranslations( $data['example_text_translations'], $original?->example_text, true ); } unset( $data['title_translations'], $data['description_translations'], $data['example_text_translations'] ); if (! array_key_exists('difficulty', $data) || $data['difficulty'] === null) { $data['difficulty'] = $original?->difficulty ?? 'easy'; } if (! array_key_exists('priority', $data) || $data['priority'] === null) { $data['priority'] = $original?->priority ?? 'medium'; } return $data; } /** * @param array|null $fallback * @return array|null */ protected function normalizeTranslations(mixed $value, ?array $fallback = null, bool $allowNull = false): ?array { if ($allowNull && ($value === null || $value === '')) { return null; } if (is_array($value)) { $filtered = array_filter( $value, static fn ($text) => is_string($text) && $text !== '' ); if (! empty($filtered)) { return $filtered; } return $allowNull ? null : ($fallback ?? []); } if (is_string($value) && $value !== '') { $locale = app()->getLocale() ?: 'de'; return [ $locale => $value, ]; } return $allowNull ? null : $fallback; } }