$projectId, 'version' => $version, 'error' => $message ])); header("Location: {$page}?{$query}"); exit(); } function redirect_with_success($page, $projectId, $message) { $version = $_POST['version'] ?? null; $query = http_build_query(array_filter([ 'projectId' => $projectId, 'version' => $version, 'success' => $message ])); header("Location: {$page}?{$query}"); exit(); } function get_months($startDate, $endDate) { $start = new DateTime($startDate); $end = new DateTime($endDate); $interval = new DateInterval('P1M'); $period = new DatePeriod($start, $interval, $end->modify('+1 month')); $months = []; foreach ($period as $dt) { $months[] = $dt->format('Y-m-01'); } return $months; } try { $pdo = db(); switch ($action) { case 'new_version': // Find the latest version number $stmt = $pdo->prepare("SELECT MAX(versionNumber) as max_version FROM forecasting WHERE projectId = :projectId"); $stmt->execute([':projectId' => $projectId]); $latest_version_num = $stmt->fetchColumn(); $new_version_num = $latest_version_num + 1; // Get the ID of the latest version to clone from $stmt = $pdo->prepare("SELECT id FROM forecasting WHERE projectId = :projectId AND versionNumber = :versionNumber"); $stmt->execute([':projectId' => $projectId, ':versionNumber' => $latest_version_num]); $latest_version_id = $stmt->fetchColumn(); $pdo->beginTransaction(); // Create the new forecasting version $stmt = $pdo->prepare("INSERT INTO forecasting (projectId, versionNumber, createdAt) VALUES (:projectId, :versionNumber, NOW())"); $stmt->execute([':projectId' => $projectId, ':versionNumber' => $new_version_num]); $new_version_id = $pdo->lastInsertId(); // Clone allocations from the latest version if it exists if ($latest_version_id) { $clone_sql = "INSERT INTO forecastAllocation (forecastingId, rosterId, resourceName, level, month, allocatedDays) SELECT :new_version_id, rosterId, resourceName, level, month, allocatedDays FROM forecastAllocation WHERE forecastingId = :latest_version_id"; $stmt = $pdo->prepare($clone_sql); $stmt->execute([':new_version_id' => $new_version_id, ':latest_version_id' => $latest_version_id]); } $pdo->commit(); redirect_with_success('forecasting.php', $projectId, 'New version created successfully.', ['version' => $new_version_num]); break; case 'add_resource': $forecastingId = $_POST['forecastingId'] ?? null; $rosterId = $_POST['rosterId'] ?? null; if (!$forecastingId || !$rosterId) { redirect_with_error('forecasting.php', $projectId, 'Missing data for adding resource.'); } // Fetch project and roster details $stmt = $pdo->prepare("SELECT startDate, endDate FROM projects WHERE id = :projectId"); $stmt->execute([':projectId' => $projectId]); $project = $stmt->fetch(PDO::FETCH_ASSOC); $stmt = $pdo->prepare("SELECT fullNameEn, `level` FROM roster WHERE id = :rosterId"); $stmt->execute([':rosterId' => $rosterId]); $roster = $stmt->fetch(PDO::FETCH_ASSOC); if (!$project || !$roster) { redirect_with_error('forecasting.php', $projectId, 'Project or resource not found.'); } $months = get_months($project['startDate'], $project['endDate']); $insert_sql = "INSERT INTO forecastAllocation (forecastingId, rosterId, resourceName, level, month, allocatedDays) VALUES (:forecastingId, :rosterId, :resourceName, :level, :month, 0)"; $stmt = $pdo->prepare($insert_sql); $pdo->beginTransaction(); foreach ($months as $month) { $stmt->execute([ ':forecastingId' => $forecastingId, ':rosterId' => $rosterId, ':resourceName' => $roster['fullNameEn'], ':level' => $roster['level'], ':month' => $month ]); } $pdo->commit(); redirect_with_success('forecasting.php', $projectId, 'Resource added to forecast.'); break; case 'remove_resource': $forecastingId = $_POST['forecastingId'] ?? null; $rosterId = $_POST['rosterId'] ?? null; if (!$forecastingId || !$rosterId) { redirect_with_error('forecasting.php', $projectId, 'Missing data for removing resource.'); } $sql = "DELETE FROM forecastAllocation WHERE forecastingId = :forecastingId AND rosterId = :rosterId"; $stmt = $pdo->prepare($sql); $stmt->execute([':forecastingId' => $forecastingId, ':rosterId' => $rosterId]); redirect_with_success('forecasting.php', $projectId, 'Resource removed from forecast.'); break; case 'update_allocation': header('Content-Type: application/json'); $forecastingId = $_POST['forecastingId'] ?? null; $rosterId = $_POST['rosterId'] ?? null; $month = $_POST['month'] ?? null; $allocatedDays = $_POST['allocatedDays'] ?? null; if (!$forecastingId || !$rosterId || !$month || !isset($allocatedDays)) { echo json_encode(['success' => false, 'error' => 'Invalid data for update.']); exit(); } // Use INSERT ... ON DUPLICATE KEY UPDATE to handle both new and existing cells $sql = "INSERT INTO forecastAllocation (forecastingId, rosterId, month, allocatedDays, resourceName, level) VALUES (:forecastingId, :rosterId, :month, :allocatedDays, (SELECT fullNameEn FROM roster WHERE id = :rosterId), (SELECT `level` FROM roster WHERE id = :rosterId)) ON DUPLICATE KEY UPDATE allocatedDays = :allocatedDays"; // We need a unique key on (forecastingId, rosterId, month) for this to work. // Let's assume it exists. If not, we need to add it. // ALTER TABLE forecastAllocation ADD UNIQUE KEY `unique_allocation` (`forecastingId`, `rosterId`, `month`); $stmt = $pdo->prepare($sql); $stmt->execute([ ':forecastingId' => $forecastingId, ':rosterId' => $rosterId, ':month' => $month, ':allocatedDays' => $allocatedDays ]); echo json_encode(['success' => true]); exit(); default: redirect_with_error('forecasting.php', $projectId, 'Invalid action specified.'); break; } } catch (PDOException $e) { if ($pdo->inTransaction()) { $pdo->rollBack(); } if ($action === 'update_allocation') { header('Content-Type: application/json'); echo json_encode(['success' => false, 'error' => 'Database error: ' . $e->getMessage()]); } else { redirect_with_error('forecasting.php', $projectId, 'Database error: ' . $e->getMessage()); } exit(); }