'Method not allowed', 'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE'] ]); break; } } catch (Exception $e) { // Log error dengan detail $errorTime = microtime(true); $errorMemory = memory_get_usage(true); error_log("Fuel CRUD API Error: " . $e->getMessage()); error_log("Error occurred at: " . round($errorTime - $startTime, 2) . " seconds"); error_log("Memory at error: " . number_format($errorMemory / 1024 / 1024, 2) . " MB"); error_log("Error trace: " . $e->getTraceAsString()); http_response_code(500); echo json_encode([ 'error' => 'Internal server error', 'message' => $e->getMessage(), 'processing_time' => round($errorTime - $startTime, 2) . " seconds", 'memory_usage' => number_format(($errorMemory - $startMemory) / 1024 / 1024, 2) . " MB" ]); } finally { // Cleanup memory gc_collect_cycles(); } /** * Handle READ operations (GET) * ============================ * Get fuel records dengan optional filtering */ function handleRead($pdo, $user) { global $startTime, $startMemory; // Check for special report endpoints $action = isset($_GET['action']) ? trim($_GET['action']) : ''; if ($action === 'summary') { handleSummaryReport($pdo, $user); return; } if ($action === 'detail') { handleDetailReport($pdo, $user); return; } // Default GET behavior (existing code) // Get query parameters $id = isset($_GET['id']) ? (int)$_GET['id'] : null; $nopol = isset($_GET['nopol']) ? trim($_GET['nopol']) : ''; $namaSupir = isset($_GET['nama_supir']) ? trim($_GET['nama_supir']) : ''; $tglStart = isset($_GET['tgl_start']) ? trim($_GET['tgl_start']) : ''; $tglEnd = isset($_GET['tgl_end']) ? trim($_GET['tgl_end']) : ''; $limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 100; $offset = isset($_GET['offset']) ? (int)$_GET['offset'] : 0; // Validate limit if ($limit > 1000) { $limit = 1000; } // Build SQL query $sql = "SELECT id, Nopol, Nama_Supir, Tgl, Jumlah_Liter, Harga, Odometer, Catatan, Tgl_Tambah, Tgl_Edit, Edit_By, Edit_By_ID FROM fuel WHERE 1=1"; $params = array(); // Add filters if ($id) { $sql .= " AND id = ?"; $params[] = $id; } if (!empty($nopol)) { $sql .= " AND Nopol LIKE ?"; $params[] = '%' . $nopol . '%'; } if (!empty($namaSupir)) { $sql .= " AND Nama_Supir LIKE ?"; $params[] = '%' . $namaSupir . '%'; } if (!empty($tglStart)) { $sql .= " AND Tgl >= ?"; $params[] = $tglStart; } if (!empty($tglEnd)) { $sql .= " AND Tgl <= ?"; $params[] = $tglEnd; } // Add ordering dan pagination $sql .= " ORDER BY Tgl DESC, id DESC"; $sql .= " OFFSET $offset ROWS FETCH NEXT $limit ROWS ONLY"; // Execute query $stmt = $pdo->prepare($sql); $stmt->execute($params); $results = $stmt->fetchAll(PDO::FETCH_ASSOC); // Get total count $countSql = "SELECT COUNT(*) as total FROM fuel WHERE 1=1"; $countParams = array(); if ($id) { $countSql .= " AND id = ?"; $countParams[] = $id; } if (!empty($nopol)) { $countSql .= " AND Nopol LIKE ?"; $countParams[] = '%' . $nopol . '%'; } if (!empty($namaSupir)) { $countSql .= " AND Nama_Supir LIKE ?"; $countParams[] = '%' . $namaSupir . '%'; } if (!empty($tglStart)) { $countSql .= " AND Tgl >= ?"; $countParams[] = $tglStart; } if (!empty($tglEnd)) { $countSql .= " AND Tgl <= ?"; $countParams[] = $tglEnd; } $countStmt = $pdo->prepare($countSql); $countStmt->execute($countParams); $totalCount = $countStmt->fetch(PDO::FETCH_ASSOC)['total']; // Performance monitoring $endTime = microtime(true); $endMemory = memory_get_usage(true); $processingTime = round($endTime - $startTime, 2); $memoryUsed = round(($endMemory - $startMemory) / 1024 / 1024, 2); // Return results http_response_code(200); echo json_encode([ 'success' => true, 'data' => $results, 'pagination' => [ 'total_records' => (int)$totalCount, 'returned_records' => count($results), 'limit' => $limit, 'offset' => $offset, 'has_more' => ($offset + $limit) < $totalCount ], 'filters_applied' => [ 'id' => $id, 'nopol' => $nopol, 'nama_supir' => $namaSupir, 'tgl_start' => $tglStart, 'tgl_end' => $tglEnd ], 'processing_info' => [ 'processing_time' => $processingTime . " seconds", 'memory_usage' => $memoryUsed . " MB", 'user' => $user ] ]); } /** * Handle CREATE operations (POST) * =============================== * Create new fuel record */ function handleCreate($pdo, $user) { global $startTime, $startMemory; // Get request body $input = json_decode(file_get_contents('php://input'), true); if (!$input) { http_response_code(400); echo json_encode([ 'error' => 'Invalid JSON input', 'details' => 'Request body must be valid JSON' ]); return; } // Validasi required fields $requiredFields = ['Nopol', 'Tgl', 'Jumlah_Liter', 'Harga']; $missingFields = array(); foreach ($requiredFields as $field) { if (!isset($input[$field]) || trim($input[$field]) === '') { $missingFields[] = $field; } } if (!empty($missingFields)) { http_response_code(400); echo json_encode([ 'error' => 'Missing required fields', 'missing_fields' => $missingFields, 'required_fields' => $requiredFields ]); return; } // Sanitize input $nopol = trim($input['Nopol']); $namaSupir = isset($input['Nama_Supir']) ? trim($input['Nama_Supir']) : null; $tgl = trim($input['Tgl']); $jumlahLiter = (int)$input['Jumlah_Liter']; $harga = (int)$input['Harga']; $odometer = isset($input['Odometer']) ? (int)$input['Odometer'] : null; $catatan = isset($input['Catatan']) ? trim($input['Catatan']) : null; // Validate date format if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $tgl)) { http_response_code(400); echo json_encode([ 'error' => 'Invalid date format', 'details' => 'Date must be in YYYY-MM-DD format', 'received' => $tgl ]); return; } // Validate numeric values if ($jumlahLiter <= 0 || $harga <= 0) { http_response_code(400); echo json_encode([ 'error' => 'Invalid numeric values', 'details' => 'Jumlah_Liter and Harga must be positive numbers' ]); return; } // Insert record $sql = "INSERT INTO fuel (Nopol, Nama_Supir, Tgl, Jumlah_Liter, Harga, Odometer, Catatan, Tgl_Tambah, Edit_By, Edit_By_ID) VALUES (?, ?, ?, ?, ?, ?, ?, GETDATE(), ?, ?)"; $params = [ $nopol, $namaSupir, $tgl, $jumlahLiter, $harga, $odometer, $catatan, $user, null // Edit_By_ID - could be set if you have user ID mapping ]; $stmt = $pdo->prepare($sql); $result = $stmt->execute($params); if (!$result) { http_response_code(500); echo json_encode([ 'error' => 'Failed to create fuel record', 'details' => 'Database insert operation failed' ]); return; } // Get inserted ID $insertedId = $pdo->lastInsertId(); // Log successful creation error_log("Fuel CREATE - Record successfully created: ID $insertedId by user: $user from IP: " . SessionHelper::get_ip()); // Performance monitoring $endTime = microtime(true); $endMemory = memory_get_usage(true); $processingTime = round($endTime - $startTime, 2); $memoryUsed = round(($endMemory - $startMemory) / 1024 / 1024, 2); // Return success response http_response_code(201); echo json_encode([ 'success' => true, 'message' => 'Fuel record created successfully', 'data' => [ 'id' => (int)$insertedId, 'Nopol' => $nopol, 'Nama_Supir' => $namaSupir, 'Tgl' => $tgl, 'Jumlah_Liter' => $jumlahLiter, 'Harga' => $harga, 'Odometer' => $odometer, 'Catatan' => $catatan ], 'processing_info' => [ 'processing_time' => $processingTime . " seconds", 'memory_usage' => $memoryUsed . " MB", 'user' => $user, 'ip_address' => SessionHelper::get_ip() ] ]); } /** * Handle UPDATE operations (PUT) * ============================== * Update existing fuel record */ function handleUpdate($pdo, $user) { global $startTime, $startMemory; // Get request body $input = json_decode(file_get_contents('php://input'), true); if (!$input) { http_response_code(400); echo json_encode([ 'error' => 'Invalid JSON input', 'details' => 'Request body must be valid JSON' ]); return; } // Validasi required field untuk identify record if (!isset($input['id']) || (int)$input['id'] <= 0) { http_response_code(400); echo json_encode([ 'error' => 'Missing required field', 'details' => 'id is required to identify the record to update' ]); return; } $id = (int)$input['id']; // Check if record exists $checkSql = "SELECT * FROM fuel WHERE id = ?"; $checkStmt = $pdo->prepare($checkSql); $checkStmt->execute([$id]); $existingRecord = $checkStmt->fetch(PDO::FETCH_ASSOC); if (!$existingRecord) { http_response_code(404); echo json_encode([ 'error' => 'Fuel record not found', 'details' => 'No fuel record found with the specified ID', 'id' => $id ]); return; } // Build update fields $updateFields = array(); $updateParams = array(); $allowedFields = ['Nopol', 'Nama_Supir', 'Tgl', 'Jumlah_Liter', 'Harga', 'Odometer', 'Catatan']; foreach ($allowedFields as $field) { if (isset($input[$field])) { $value = $input[$field]; // Validate specific fields if ($field === 'Tgl' && !empty($value)) { if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) { http_response_code(400); echo json_encode([ 'error' => 'Invalid date format', 'details' => 'Date must be in YYYY-MM-DD format' ]); return; } } if (in_array($field, ['Jumlah_Liter', 'Harga', 'Odometer']) && !empty($value)) { $value = (int)$value; if ($value < 0) { http_response_code(400); echo json_encode([ 'error' => 'Invalid numeric value', 'details' => "$field must be a positive number" ]); return; } } $updateFields[] = "$field = ?"; $updateParams[] = $value; } } // Check if ada fields untuk update if (empty($updateFields)) { http_response_code(400); echo json_encode([ 'error' => 'No fields to update', 'details' => 'At least one field must be provided for update', 'available_fields' => $allowedFields ]); return; } // Add audit fields $updateFields[] = "Tgl_Edit = GETDATE()"; $updateFields[] = "Edit_By = ?"; $updateParams[] = $user; // Build dan execute update query $sql = "UPDATE fuel SET " . implode(', ', $updateFields) . " WHERE id = ?"; $updateParams[] = $id; $stmt = $pdo->prepare($sql); $result = $stmt->execute($updateParams); if (!$result) { http_response_code(500); echo json_encode([ 'error' => 'Failed to update fuel record', 'details' => 'Database update operation failed' ]); return; } // Get updated record $getUpdatedSql = "SELECT * FROM fuel WHERE id = ?"; $getUpdatedStmt = $pdo->prepare($getUpdatedSql); $getUpdatedStmt->execute([$id]); $updatedRecord = $getUpdatedStmt->fetch(PDO::FETCH_ASSOC); // Log successful update error_log("Fuel UPDATE - Record successfully updated: ID $id by user: $user from IP: " . SessionHelper::get_ip()); // Performance monitoring $endTime = microtime(true); $endMemory = memory_get_usage(true); $processingTime = round($endTime - $startTime, 2); $memoryUsed = round(($endMemory - $startMemory) / 1024 / 1024, 2); // Return success response http_response_code(200); echo json_encode([ 'success' => true, 'message' => 'Fuel record updated successfully', 'data' => $updatedRecord, 'changes' => [ 'fields_updated' => count($updateFields) - 2, // Exclude audit fields 'rows_affected' => $stmt->rowCount() ], 'processing_info' => [ 'processing_time' => $processingTime . " seconds", 'memory_usage' => $memoryUsed . " MB", 'user' => $user, 'ip_address' => SessionHelper::get_ip() ] ]); } /** * Handle DELETE operations (DELETE) * ================================= * Delete fuel record by ID */ function handleDelete($pdo, $user) { global $startTime, $startMemory; // Get request body $input = json_decode(file_get_contents('php://input'), true); if (!$input) { http_response_code(400); echo json_encode([ 'error' => 'Invalid JSON input', 'details' => 'Request body must be valid JSON' ]); return; } // Validasi required field if (!isset($input['id']) || (int)$input['id'] <= 0) { http_response_code(400); echo json_encode([ 'error' => 'Missing required field', 'details' => 'id is required to identify the record to delete' ]); return; } $id = (int)$input['id']; // Check if record exists dan get data sebelum delete $checkSql = "SELECT * FROM fuel WHERE id = ?"; $checkStmt = $pdo->prepare($checkSql); $checkStmt->execute([$id]); $existingRecord = $checkStmt->fetch(PDO::FETCH_ASSOC); if (!$existingRecord) { http_response_code(404); echo json_encode([ 'error' => 'Fuel record not found', 'details' => 'No fuel record found with the specified ID', 'id' => $id ]); return; } // Execute delete $sql = "DELETE FROM fuel WHERE id = ?"; $stmt = $pdo->prepare($sql); $result = $stmt->execute([$id]); if (!$result) { http_response_code(500); echo json_encode([ 'error' => 'Failed to delete fuel record', 'details' => 'Database delete operation failed' ]); return; } // Verify delete berhasil $rowsAffected = $stmt->rowCount(); if ($rowsAffected === 0) { http_response_code(404); echo json_encode([ 'error' => 'Fuel record deletion failed', 'details' => 'No records were deleted' ]); return; } // Log successful deletion error_log("Fuel DELETE - Record successfully deleted: ID $id by user: $user from IP: " . SessionHelper::get_ip()); // Performance monitoring $endTime = microtime(true); $endMemory = memory_get_usage(true); $processingTime = round($endTime - $startTime, 2); $memoryUsed = round(($endMemory - $startMemory) / 1024 / 1024, 2); // Return success response http_response_code(200); echo json_encode([ 'success' => true, 'message' => 'Fuel record deleted successfully', 'deleted_data' => $existingRecord, 'changes' => [ 'rows_affected' => $rowsAffected ], 'processing_info' => [ 'processing_time' => $processingTime . " seconds", 'memory_usage' => $memoryUsed . " MB", 'user' => $user, 'ip_address' => SessionHelper::get_ip() ] ]); } /** * Handle SUMMARY REPORT (GET with action=summary) * =============================================== * Ringkasan fuel per nopol dengan filter bulan dan tahun * * URL: /fuel?action=summary&bulan=1&tahun=2026&user_group_id=123 */ function handleSummaryReport($pdo, $user) { global $startTime, $startMemory; // Get filter parameters $filterMonth = isset($_GET['bulan']) ? (int)$_GET['bulan'] : date('n'); $filterYear = isset($_GET['tahun']) ? (int)$_GET['tahun'] : date('Y'); $userGroupID = isset($_GET['user_group_id']) ? (int)$_GET['user_group_id'] : null; // Validate month (1-12) if ($filterMonth < 1 || $filterMonth > 12) { http_response_code(400); echo json_encode([ 'error' => 'Invalid month', 'details' => 'Month must be between 1 and 12', 'received' => $filterMonth ]); return; } // Validate year if ($filterYear < 2000 || $filterYear > 2100) { http_response_code(400); echo json_encode([ 'error' => 'Invalid year', 'details' => 'Year must be between 2000 and 2100', 'received' => $filterYear ]); return; } // Build SQL query sesuai dengan format yang diminta $sql = "SELECT f.Nopol, STUFF(( SELECT DISTINCT ', ' + f2.Nama_Supir FROM fuel f2 WHERE f2.Nopol = f.Nopol AND MONTH(f2.Tgl) = MONTH(f.Tgl) AND YEAR(f2.Tgl) = YEAR(f.Tgl) AND f2.Edit_By_ID = f.Edit_By_ID FOR XML PATH(''), TYPE ).value('.', 'NVARCHAR(MAX)'), 1, 2, '') AS Nama_Supir, MONTH(f.Tgl) AS Bulan, YEAR(f.Tgl) AS Tahun, SUM(f.Jumlah_Liter) AS Total_Liter, SUM(f.Harga) AS Total_Harga, MAX(f.Odometer) AS Odometer_Terakhir FROM fuel f WHERE MONTH(Tgl) = ? AND YEAR(Tgl) = ?"; $params = [$filterMonth, $filterYear]; // Add user group filter if provided if ($userGroupID) { $sql .= " AND Edit_By_ID = ?"; $params[] = $userGroupID; } $sql .= " GROUP BY f.Nopol, MONTH(f.Tgl), YEAR(f.Tgl), f.Edit_By_ID ORDER BY f.Nopol"; // Execute query $stmt = $pdo->prepare($sql); $stmt->execute($params); $results = $stmt->fetchAll(PDO::FETCH_ASSOC); // Nama bulan dalam bahasa Indonesia $bulanIndo = [ 1 => "Januari", 2 => "Februari", 3 => "Maret", 4 => "April", 5 => "Mei", 6 => "Juni", 7 => "Juli", 8 => "Agustus", 9 => "September", 10 => "Oktober", 11 => "November", 12 => "Desember" ]; // Add nama bulan to results foreach ($results as &$row) { $row['Nama_Bulan'] = $bulanIndo[$row['Bulan']]; // Convert numeric values to proper types $row['Bulan'] = (int)$row['Bulan']; $row['Tahun'] = (int)$row['Tahun']; $row['Total_Liter'] = (int)$row['Total_Liter']; $row['Total_Harga'] = (int)$row['Total_Harga']; $row['Odometer_Terakhir'] = (int)$row['Odometer_Terakhir']; } // Calculate totals $totalLiter = array_sum(array_column($results, 'Total_Liter')); $totalHarga = array_sum(array_column($results, 'Total_Harga')); $totalKendaraan = count($results); // Performance monitoring $endTime = microtime(true); $endMemory = memory_get_usage(true); $processingTime = round($endTime - $startTime, 2); $memoryUsed = round(($endMemory - $startMemory) / 1024 / 1024, 2); // Return results http_response_code(200); echo json_encode([ 'success' => true, 'report_type' => 'summary', 'data' => $results, 'filters' => [ 'bulan' => $filterMonth, 'tahun' => $filterYear, 'nama_bulan' => $bulanIndo[$filterMonth], 'user_group_id' => $userGroupID ], 'summary' => [ 'total_kendaraan' => $totalKendaraan, 'total_liter' => $totalLiter, 'total_harga' => $totalHarga, 'rata_rata_liter_per_kendaraan' => $totalKendaraan > 0 ? round($totalLiter / $totalKendaraan, 2) : 0, 'rata_rata_harga_per_kendaraan' => $totalKendaraan > 0 ? round($totalHarga / $totalKendaraan, 2) : 0 ], 'processing_info' => [ 'processing_time' => $processingTime . " seconds", 'memory_usage' => $memoryUsed . " MB", 'user' => $user, 'records_found' => count($results) ] ]); } /** * Handle DETAIL REPORT (GET with action=detail) * ============================================= * Detail transaksi fuel per nopol dengan filter bulan dan tahun * * URL: /fuel?action=detail&nopol=B1234AB&bulan=1&tahun=2026&user_group_id=123 */ function handleDetailReport($pdo, $user) { global $startTime, $startMemory; // Get filter parameters $nopol = isset($_GET['nopol']) ? trim($_GET['nopol']) : ''; $bulan = isset($_GET['bulan']) ? (int)$_GET['bulan'] : date('n'); $tahun = isset($_GET['tahun']) ? (int)$_GET['tahun'] : date('Y'); $userGroupID = isset($_GET['user_group_id']) ? (int)$_GET['user_group_id'] : null; // Validate required parameter if (empty($nopol)) { http_response_code(400); echo json_encode([ 'error' => 'Missing required parameter', 'details' => 'nopol parameter is required for detail report' ]); return; } // Validate month (1-12) if ($bulan < 1 || $bulan > 12) { http_response_code(400); echo json_encode([ 'error' => 'Invalid month', 'details' => 'Month must be between 1 and 12', 'received' => $bulan ]); return; } // Validate year if ($tahun < 2000 || $tahun > 2100) { http_response_code(400); echo json_encode([ 'error' => 'Invalid year', 'details' => 'Year must be between 2000 and 2100', 'received' => $tahun ]); return; } // Build SQL query sesuai dengan format yang diminta $sql = "SELECT * FROM fuel WHERE Nopol = ? AND MONTH(Tgl) = ? AND YEAR(Tgl) = ?"; $params = [$nopol, $bulan, $tahun]; // Add user group filter if provided if ($userGroupID) { $sql .= " AND Edit_By_ID = ?"; $params[] = $userGroupID; } $sql .= " ORDER BY Tgl ASC"; // Execute query $stmt = $pdo->prepare($sql); $stmt->execute($params); $results = $stmt->fetchAll(PDO::FETCH_ASSOC); // Calculate summary seperti kode yang diminta $total_liter = 0; $total_harga = 0; $odometer_terakhir = 0; $nama_supir = []; foreach ($results as $row) { $total_liter += $row['Jumlah_Liter']; $total_harga += $row['Harga']; $odometer_terakhir = max($odometer_terakhir, $row['Odometer']); if (!empty($row['Nama_Supir']) && !in_array($row['Nama_Supir'], $nama_supir)) { $nama_supir[] = $row['Nama_Supir']; } } $nama_supir_str = implode(', ', $nama_supir); // Nama bulan dalam bahasa Indonesia $nama_bulan = [ 1 => 'Januari', 2 => 'Februari', 3 => 'Maret', 4 => 'April', 5 => 'Mei', 6 => 'Juni', 7 => 'Juli', 8 => 'Agustus', 9 => 'September', 10 => 'Oktober', 11 => 'November', 12 => 'Desember' ]; // Performance monitoring $endTime = microtime(true); $endMemory = memory_get_usage(true); $processingTime = round($endTime - $startTime, 2); $memoryUsed = round(($endMemory - $startMemory) / 1024 / 1024, 2); // Return results http_response_code(200); echo json_encode([ 'success' => true, 'report_type' => 'detail', 'data' => $results, 'filters' => [ 'nopol' => $nopol, 'bulan' => $bulan, 'tahun' => $tahun, 'nama_bulan' => $nama_bulan[$bulan], 'user_group_id' => $userGroupID ], 'summary' => [ 'nopol' => $nopol, 'nama_supir' => $nama_supir_str, 'bulan' => $bulan, 'tahun' => $tahun, 'nama_bulan' => $nama_bulan[$bulan], 'total_liter' => $total_liter, 'total_harga' => $total_harga, 'odometer_terakhir' => $odometer_terakhir, 'jumlah_transaksi' => count($results), 'rata_rata_liter_per_transaksi' => count($results) > 0 ? round($total_liter / count($results), 2) : 0, 'rata_rata_harga_per_transaksi' => count($results) > 0 ? round($total_harga / count($results), 2) : 0 ], 'processing_info' => [ 'processing_time' => $processingTime . " seconds", 'memory_usage' => $memoryUsed . " MB", 'user' => $user, 'records_found' => count($results) ] ]); }