0) ? (int) $data['CategoryID'] : null; $cronExpression = trim($data['CronExpression'] ?? ''); $isActive = isset($data['IsActive']) ? ($data['IsActive'] ? 1 : 0) : 1; $scheduleType = trim($data['ScheduleType'] ?? 'cron'); $intervalMinutes = (isset($data['IntervalMinutes']) && is_numeric($data['IntervalMinutes'])) ? (int) $data['IntervalMinutes'] : null; if ($businessID <= 0) { apiAbort(['OK' => false, 'ERROR' => 'missing_params', 'MESSAGE' => 'BusinessID is required']); } if ($taskName === '') { apiAbort(['OK' => false, 'ERROR' => 'missing_params', 'MESSAGE' => 'Name is required']); } if ($taskTitle === '') { apiAbort(['OK' => false, 'ERROR' => 'missing_params', 'MESSAGE' => 'Title is required']); } try { // Determine next run based on schedule type if ($scheduleType === 'interval' || $scheduleType === 'interval_after_completion') { if ($intervalMinutes === null || $intervalMinutes < 1) { apiAbort(['OK' => false, 'ERROR' => 'missing_params', 'MESSAGE' => 'IntervalMinutes is required for interval scheduling (minimum 1)']); } if ($cronExpression === '') { $cronExpression = '* * * * *'; } $nextRunOn = ($taskID === 0) ? new DateTime('now', new DateTimeZone('UTC')) : new DateTime("+{$intervalMinutes} minutes", new DateTimeZone('UTC')); } else { if ($cronExpression === '') { apiAbort(['OK' => false, 'ERROR' => 'missing_params', 'MESSAGE' => 'CronExpression is required']); } $cronParts = preg_split('/\s+/', $cronExpression); if (count($cronParts) !== 5) { apiAbort(['OK' => false, 'ERROR' => 'invalid_cron', 'MESSAGE' => 'Cron expression must have 5 parts: minute hour day month weekday']); } $nextRunOn = calculateNextRun($cronExpression); } $nextRunStr = $nextRunOn->format('Y-m-d H:i:s'); if ($taskID > 0) { // UPDATE $existing = queryOne("SELECT ID FROM ScheduledTaskDefinitions WHERE ID = ? AND BusinessID = ?", [$taskID, $businessID]); if (!$existing) { apiAbort(['OK' => false, 'ERROR' => 'not_found', 'MESSAGE' => 'Scheduled task not found']); } queryTimed(" UPDATE ScheduledTaskDefinitions SET Name = ?, Title = ?, Details = ?, TaskCategoryID = ?, CronExpression = ?, ScheduleType = ?, IntervalMinutes = ?, IsActive = ?, NextRunOn = ? WHERE ID = ? ", [$taskName, $taskTitle, $taskDetails ?: null, $categoryID, $cronExpression, $scheduleType, $intervalMinutes, $isActive, $nextRunStr, $taskID]); jsonResponse([ 'OK' => true, 'SCHEDULED_TASK_ID' => $taskID, 'NEXT_RUN' => $nextRunStr, 'MESSAGE' => 'Scheduled task updated', ]); } else { // INSERT queryTimed(" INSERT INTO ScheduledTaskDefinitions ( BusinessID, Name, Title, Details, TaskCategoryID, CronExpression, ScheduleType, IntervalMinutes, IsActive, NextRunOn ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ", [$businessID, $taskName, $taskTitle, $taskDetails ?: null, $categoryID, $cronExpression, $scheduleType, $intervalMinutes, $isActive, $nextRunStr]); $newScheduledTaskID = (int) lastInsertId(); // Create the first task immediately queryTimed(" INSERT INTO Tasks (BusinessID, CategoryID, TaskTypeID, Title, Details, CreatedOn, ClaimedByUserID) VALUES (?, ?, 0, ?, ?, NOW(), 0) ", [$businessID, $categoryID, $taskTitle, $taskDetails ?: null]); $firstTaskID = (int) lastInsertId(); // Calculate actual next run (after the immediate one) if ($scheduleType === 'interval_after_completion') { $actualNextRunStr = null; // Don't schedule until task completion } elseif ($scheduleType === 'interval') { $actualNextRun = new DateTime("+{$intervalMinutes} minutes", new DateTimeZone('UTC')); $actualNextRunStr = $actualNextRun->format('Y-m-d H:i:s'); } else { $actualNextRun = calculateNextRun($cronExpression); $actualNextRunStr = $actualNextRun->format('Y-m-d H:i:s'); } queryTimed(" UPDATE ScheduledTaskDefinitions SET LastRunOn = NOW(), NextRunOn = ? WHERE ID = ? ", [$actualNextRunStr, $newScheduledTaskID]); jsonResponse([ 'OK' => true, 'SCHEDULED_TASK_ID' => $newScheduledTaskID, 'TASK_ID' => $firstTaskID, 'NEXT_RUN' => $actualNextRunStr ?? '', 'MESSAGE' => 'Scheduled task created and first task added', ]); } } catch (Exception $e) { jsonResponse(['OK' => false, 'ERROR' => 'server_error', 'MESSAGE' => $e->getMessage()]); }