Enforce task limits and update event form
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

This commit is contained in:
Codex Agent
2026-01-21 09:49:30 +01:00
parent 0b1430e64d
commit 1c5412e82c
15 changed files with 491 additions and 52 deletions

View File

@@ -110,6 +110,7 @@ class TaskCollectionController extends Controller
),
'created_task_ids' => $result['created_task_ids'],
'attached_task_ids' => $result['attached_task_ids'],
'skipped_task_ids' => $result['skipped_task_ids'],
]);
}

View File

@@ -10,6 +10,7 @@ use App\Models\Event;
use App\Models\Task;
use App\Models\TaskCollection;
use App\Models\Tenant;
use App\Services\Packages\PackageLimitEvaluator;
use App\Support\ApiError;
use App\Support\TenantMemberPermissions;
use App\Support\TenantRequestResolver;
@@ -20,6 +21,8 @@ use Symfony\Component\HttpFoundation\Response;
class TaskController extends Controller
{
public function __construct(private readonly PackageLimitEvaluator $packageLimitEvaluator) {}
/**
* Display a listing of the tenant's tasks.
*/
@@ -163,7 +166,8 @@ class TaskController extends Controller
{
TenantMemberPermissions::ensureEventPermission($request, $event, 'tasks:manage');
$tenantId = $this->currentTenant($request)->id;
$tenant = $this->currentTenant($request);
$tenantId = $tenant->id;
if (($task->tenant_id && $task->tenant_id !== $tenantId) || $event->tenant_id !== $tenantId) {
abort(404);
@@ -173,6 +177,11 @@ class TaskController extends Controller
return response()->json(['message' => 'Task ist bereits diesem Event zugewiesen.'], 409);
}
$limitStatus = $this->resolveTaskLimitStatus($event, $tenant);
if ($limitStatus['remaining'] !== null && $limitStatus['remaining'] <= 0) {
return $this->taskLimitExceededResponse($event, $limitStatus);
}
$task->assignedEvents()->attach($event->id);
return response()->json([
@@ -187,7 +196,8 @@ class TaskController extends Controller
{
TenantMemberPermissions::ensureEventPermission($request, $event, 'tasks:manage');
$tenantId = $this->currentTenant($request)->id;
$tenant = $this->currentTenant($request);
$tenantId = $tenant->id;
if ($event->tenant_id !== $tenantId) {
abort(404);
@@ -203,12 +213,27 @@ class TaskController extends Controller
);
}
$taskIds = array_values(array_unique(array_map('intval', $taskIds)));
$tasks = Task::whereIn('id', $taskIds)
->where(function ($query) use ($tenantId) {
$query->whereNull('tenant_id')->orWhere('tenant_id', $tenantId);
})
->get();
$assignedIds = $event->tasks()
->whereIn('tasks.id', $taskIds)
->pluck('tasks.id')
->all();
$pendingIds = array_values(array_diff($taskIds, $assignedIds));
$limitStatus = $this->resolveTaskLimitStatus($event, $tenant);
if (
$limitStatus['remaining'] !== null
&& $pendingIds !== []
&& $limitStatus['remaining'] < count($pendingIds)
) {
return $this->taskLimitExceededResponse($event, $limitStatus);
}
$attached = 0;
foreach ($tasks as $task) {
if (! $task->assignedEvents()->where('event_id', $event->id)->exists()) {
@@ -330,6 +355,52 @@ class TaskController extends Controller
return TenantRequestResolver::resolve($request);
}
/**
* @return array{limit: ?int, used: int, remaining: ?int, package_id: ?int}
*/
protected function resolveTaskLimitStatus(Event $event, Tenant $tenant): array
{
$event->loadMissing(['eventPackage.package', 'eventPackages.package']);
$eventPackage = $this->packageLimitEvaluator->resolveEventPackageForPhotoUpload(
$tenant,
$event->id,
$event
);
$limit = $eventPackage?->effectiveLimits()['max_tasks'] ?? null;
$used = $event->tasks()->count();
$remaining = $limit === null ? null : max(0, (int) $limit - $used);
return [
'limit' => $limit === null ? null : (int) $limit,
'used' => $used,
'remaining' => $remaining,
'package_id' => $eventPackage?->package_id,
];
}
/**
* @param array{limit: ?int, used: int, remaining: ?int, package_id: ?int} $limitStatus
*/
protected function taskLimitExceededResponse(Event $event, array $limitStatus): JsonResponse
{
return ApiError::response(
'task_limit_exceeded',
__('api.packages.task_limit_exceeded.title'),
__('api.packages.task_limit_exceeded.message'),
Response::HTTP_PAYMENT_REQUIRED,
[
'scope' => 'tasks',
'used' => $limitStatus['used'],
'limit' => $limitStatus['limit'],
'remaining' => $limitStatus['remaining'] ?? 0,
'event_id' => $event->id,
'package_id' => $limitStatus['package_id'],
]
);
}
protected function prepareTaskPayload(array $data, int $tenantId, ?Task $original = null): array
{
if (array_key_exists('title', $data)) {