exec("ALTER TABLE bookings ADD COLUMN IF NOT EXISTS declared_value DECIMAL(12,2) DEFAULT 0"); } catch(Exception $e) {} try { $db->exec("ALTER TABLE bookings ADD COLUMN IF NOT EXISTS insurance_charge DECIMAL(10,2) DEFAULT 0"); } catch(Exception $e) {} try { $db->exec("ALTER TABLE bookings ADD COLUMN IF NOT EXISTS parcel_count INT DEFAULT 1"); } catch(Exception $e) {} function generateBookingId() { return 'ODP' . substr(time(), -8) . rand(10, 99); } // ============================================================ // CREATE BOOKING // ============================================================ if ($action === 'create' && $method === 'POST') { $user_id = null; $auth = $_SERVER['HTTP_AUTHORIZATION'] ?? ''; if (preg_match('/Bearer\s+(.+)/', $auth, $m)) { $u = jwtVerify($m[1]); if ($u) $user_id = $u['id']; } $required = ['from_city','to_city','travel_date','sender_name','sender_phone', 'receiver_name','receiver_phone','total_amount']; foreach ($required as $f) { if (empty($body[$f])) error("Zaruri field missing: $f"); } // Calculate platform fee from total $total_amount = floatval($body['total_amount']); $base_amount = isset($body['base_amount']) ? floatval($body['base_amount']) : round($total_amount / 1.20, 2); $platform_fee = isset($body['platform_fee']) ? floatval($body['platform_fee']) : round($total_amount - $base_amount, 2); $distance_km = floatval($body['distance_km'] ?? 0); $vendor_id = $body['vendor_id'] ?? null; $vehicle_number = trim($body['vehicle_number'] ?? ''); $vendor_name = trim($body['vendor_name'] ?? ''); // If vendor_id given but no vendor_name, look it up if ($vendor_id && !$vendor_name) { $vn = $db->prepare('SELECT business_name FROM vendors WHERE id = ?'); $vn->execute([$vendor_id]); $vrow = $vn->fetch(); if ($vrow) $vendor_name = $vrow['business_name']; } // If vehicle_number not sent, look it up from travel_option $travel_option_id = $body['travel_option_id'] ?? null; if ($travel_option_id && !$vehicle_number) { $tv = $db->prepare('SELECT vehicle_number FROM travel_options WHERE id = ?'); $tv->execute([$travel_option_id]); $tvrow = $tv->fetch(); if ($tvrow) $vehicle_number = $tvrow['vehicle_number']; } $booking_id = generateBookingId(); $qr_data = json_encode([ 'id' => $booking_id, 'from' => $body['from_city'], 'to' => $body['to_city'], 'sender' => $body['sender_phone'] ]); $stmt = $db->prepare(" INSERT INTO bookings ( booking_id, user_id, vendor_id, vendor_name, travel_option_id, from_city, to_city, travel_date, sender_name, sender_phone, sender_email, receiver_name, receiver_phone, receiver_email, parcel_length, parcel_width, parcel_height, actual_weight, volumetric_weight, chargeable_weight, parcel_count, declared_value, insurance_charge, parcel_description, pickup_point, drop_point, distance_km, damage_protection, base_amount, platform_fee, total_amount, payment_method, vehicle_number, qr_code ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) "); $stmt->execute([ $booking_id, $user_id, $vendor_id, $vendor_name, $travel_option_id, $body['from_city'], $body['to_city'], $body['travel_date'], $body['sender_name'], $body['sender_phone'], $body['sender_email'] ?? null, $body['receiver_name'], $body['receiver_phone'], $body['receiver_email'] ?? null, $body['parcel_length'] ?? 0, $body['parcel_width'] ?? 0, $body['parcel_height'] ?? 0, $body['actual_weight'] ?? 0, $body['volumetric_weight'] ?? 0, $body['chargeable_weight'] ?? 0, intval($body['parcel_count'] ?? 1), floatval($body['declared_value'] ?? 0), floatval($body['insurance_charge'] ?? 0), $body['parcel_description'] ?? '', $body['pickup_point'] ?? '', $body['drop_point'] ?? '', $distance_km, !empty($body['damage_protection']) ? 1 : 0, $base_amount, $platform_fee, $total_amount, $body['payment_method'] ?? 'cod', $vehicle_number, $qr_data ]); // Log history $db->prepare("INSERT INTO booking_status_history (booking_id, new_status, updated_by, note) VALUES (?,?,?,?)") ->execute([$booking_id, 'pending', 'system', 'Booking created']); $booking = $db->prepare('SELECT * FROM bookings WHERE booking_id = ?'); $booking->execute([$booking_id]); success(['booking' => $booking->fetch()], '✅ Booking confirmed!'); } // ============================================================ // TRACK BOOKING // ============================================================ if ($action === 'track' && $method === 'GET') { $bid = $_GET['booking_id'] ?? ''; if (!$bid) error('booking_id chahiye'); $stmt = $db->prepare(" SELECT b.*, t.name as travel_name, t.departure_time, t.arrival_time, d.name as driver_name, d.phone as driver_phone, c.name as conductor_name, c.phone as conductor_phone FROM bookings b LEFT JOIN travel_options t ON b.travel_option_id = t.id LEFT JOIN staff d ON d.assigned_vehicle = b.vehicle_number AND d.role = 'Driver' AND d.is_active = 1 LEFT JOIN staff c ON c.assigned_vehicle = b.vehicle_number AND c.role = 'Conductor' AND c.is_active = 1 WHERE b.booking_id = ? "); $stmt->execute([$bid]); $booking = $stmt->fetch(); if (!$booking) error('Booking nahi mili. ID check karo.', 404); $hist = $db->prepare('SELECT * FROM booking_status_history WHERE booking_id = ? ORDER BY created_at ASC'); $hist->execute([$bid]); success(['booking' => $booking, 'history' => $hist->fetchAll()]); } // ============================================================ // MY BOOKINGS (by phone) // ============================================================ if ($action === 'my' && $method === 'GET') { $phone = $_GET['phone'] ?? ''; if (!$phone || strlen($phone) < 10) error('Valid phone number do'); $stmt = $db->prepare(" SELECT b.*, t.name as travel_name, t.departure_time, t.arrival_time, d.name as driver_name, d.phone as driver_phone, c.name as conductor_name, c.phone as conductor_phone FROM bookings b LEFT JOIN travel_options t ON b.travel_option_id = t.id LEFT JOIN staff d ON d.assigned_vehicle = b.vehicle_number AND d.role = 'Driver' AND d.is_active = 1 LEFT JOIN staff c ON c.assigned_vehicle = b.vehicle_number AND c.role = 'Conductor' AND c.is_active = 1 WHERE b.sender_phone = ? OR b.receiver_phone = ? ORDER BY b.created_at DESC "); $stmt->execute([$phone, $phone]); success(['bookings' => $stmt->fetchAll()]); } // ============================================================ // VENDOR ALL BOOKINGS // ============================================================ if ($action === 'vendor_all' && $method === 'GET') { $user = requireVendorAuth(); $where = 'b.vendor_id = ?'; $params = [$user['id']]; if (!empty($_GET['date'])) { $where .= ' AND b.travel_date = ?'; $params[] = $_GET['date']; } if (!empty($_GET['status'])) { $where .= ' AND b.status = ?'; $params[] = $_GET['status']; } $stmt = $db->prepare(" SELECT b.*, d.name as driver_name, d.phone as driver_phone, c.name as conductor_name, c.phone as conductor_phone FROM bookings b LEFT JOIN staff d ON d.assigned_vehicle = b.vehicle_number AND d.role = 'Driver' AND d.is_active = 1 LEFT JOIN staff c ON c.assigned_vehicle = b.vehicle_number AND c.role = 'Conductor' AND c.is_active = 1 WHERE $where ORDER BY b.created_at DESC "); $stmt->execute($params); success(['bookings' => $stmt->fetchAll()]); } // ============================================================ // UPDATE BOOKING STATUS (Vendor) // ============================================================ if ($action === 'update_status' && $method === 'PATCH') { $user = requireVendorAuth(); $bid = $_GET['booking_id'] ?? $body['booking_id'] ?? ''; if (!$bid) error('booking_id chahiye'); $validStatuses = ['pending','picked','transit','hub','delivered','cancelled']; $status = $body['status'] ?? ''; if (!in_array($status, $validStatuses)) error('Invalid status'); $stmt = $db->prepare('SELECT status, vendor_id FROM bookings WHERE booking_id = ?'); $stmt->execute([$bid]); $existing = $stmt->fetch(); if (!$existing) error('Booking nahi mili', 404); $db->prepare(" UPDATE bookings SET status = ?, driver_name = COALESCE(?, driver_name), driver_phone = COALESCE(?, driver_phone), conductor_name = COALESCE(?, conductor_name), conductor_phone = COALESCE(?, conductor_phone), vehicle_number = COALESCE(?, vehicle_number), updated_at = NOW() WHERE booking_id = ? ")->execute([ $status, $body['driver_name'] ?? null, $body['driver_phone'] ?? null, $body['conductor_name'] ?? null, $body['conductor_phone'] ?? null, $body['vehicle_number'] ?? null, $bid ]); $db->prepare("INSERT INTO booking_status_history (booking_id, old_status, new_status, updated_by, note) VALUES (?,?,?,?,?)") ->execute([$bid, $existing['status'], $status, $user['name'] ?? 'vendor', $body['note'] ?? '']); success([], "✅ Status updated to $status"); } // ============================================================ // ADMIN ALL BOOKINGS // ============================================================ if ($action === 'admin_all' && $method === 'GET') { requireAdminKey(); $where = '1=1'; $params = []; if (!empty($_GET['status'])) { $where .= ' AND b.status = ?'; $params[] = $_GET['status']; } if (!empty($_GET['vendor_id'])) { $where .= ' AND b.vendor_id = ?'; $params[] = $_GET['vendor_id']; } if (!empty($_GET['from_city'])) { $where .= ' AND b.from_city = ?'; $params[] = $_GET['from_city']; } if (!empty($_GET['to_city'])) { $where .= ' AND b.to_city = ?'; $params[] = $_GET['to_city']; } if (!empty($_GET['date'])) { $where .= ' AND b.travel_date = ?'; $params[] = $_GET['date']; } if (!empty($_GET['date_from'])) { $where .= ' AND b.travel_date >= ?'; $params[] = $_GET['date_from']; } if (!empty($_GET['date_to'])) { $where .= ' AND b.travel_date <= ?'; $params[] = $_GET['date_to']; } $stmt = $db->prepare("SELECT b.* FROM bookings b WHERE $where ORDER BY b.created_at DESC LIMIT 500"); $stmt->execute($params); $bookings = $stmt->fetchAll(); success(['bookings' => $bookings, 'total' => count($bookings)]); } // ============================================================ // STATS // ============================================================ if ($action === 'stats' && $method === 'GET') { $user = requireVendorAuth(); $isAdmin = ($user['role'] === 'admin'); $where = $isAdmin ? '1=1' : 'vendor_id = ?'; $params = $isAdmin ? [] : [$user['id']]; $stmt = $db->prepare(" SELECT COUNT(*) as total, SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending, SUM(CASE WHEN status = 'transit' THEN 1 ELSE 0 END) as transit, SUM(CASE WHEN status = 'delivered' THEN 1 ELSE 0 END) as delivered, SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) as cancelled, SUM(total_amount) as total_revenue, SUM(platform_fee) as platform_revenue, SUM(base_amount) as vendor_revenue, SUM(base_amount * 0.9) as vendor_net_revenue, SUM(CASE WHEN DATE(created_at) = CURDATE() THEN 1 ELSE 0 END) as today_bookings FROM bookings WHERE $where "); $stmt->execute($params); success(['stats' => $stmt->fetch()]); } // ============================================================ // BY PHONE — Client parcels without login (mobile number only) // ============================================================ if ($action === 'by_phone' && $method === 'GET') { $phone = trim($_GET['phone'] ?? ''); if (!$phone) error('Phone number required'); // Clean phone: remove +91, spaces, dashes $phone = preg_replace('/[^0-9]/', '', $phone); if (strlen($phone) > 10) $phone = substr($phone, -10); $stmt = $db->prepare(" SELECT booking_id, from_city, to_city, travel_date, vendor_name, sender_name, sender_phone, receiver_name, receiver_phone, chargeable_weight, total_amount, status, created_at, pickup_point, drop_point, parcel_description, COALESCE(parcel_count,1) as parcel_count FROM bookings WHERE sender_phone LIKE ? OR receiver_phone LIKE ? ORDER BY created_at DESC LIMIT 50 "); $likePhone = '%' . $phone; $stmt->execute([$likePhone, $likePhone]); $bookings = $stmt->fetchAll(); success(['bookings' => $bookings, 'total' => count($bookings)]); } // ============================================================ // HEALTH CHECK // ============================================================ if ($action === 'health' && $method === 'GET') { success(['time' => date('c'), 'db' => 'connected'], 'One Day Parcel PHP Server Running! 🚀'); } error('Invalid action: ' . $action, 404);