Support de la recherche par immatriculation
This commit is contained in:
parent
0a6c8ec05d
commit
24c432f325
2 changed files with 150 additions and 74 deletions
208
index.php
208
index.php
|
|
@ -48,40 +48,67 @@ function getBody(): array
|
|||
*/
|
||||
function getPathSegments(): array
|
||||
{
|
||||
// Si passé en paramètre d'URL direct (ex: ?route=/cars)
|
||||
if (!empty($_GET['route'])) {
|
||||
$path = trim($_GET['route'], '/');
|
||||
return $path === '' ? [] : explode('/', $path);
|
||||
}
|
||||
|
||||
// Si PATH_INFO est fourni (ex: index.php/cars)
|
||||
if (!empty($_SERVER['PATH_INFO'])) {
|
||||
$path = trim($_SERVER['PATH_INFO'], '/');
|
||||
return $path === '' ? [] : explode('/', $path);
|
||||
}
|
||||
|
||||
// Analyse depuis REQUEST_URI
|
||||
$uri = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
|
||||
$scriptDir = dirname($_SERVER['SCRIPT_NAME'] ?? '');
|
||||
|
||||
// Nettoie le préfixe du dossier du script si présent
|
||||
if ($scriptDir !== '/' && $scriptDir !== '\\' && strpos($uri, $scriptDir) === 0) {
|
||||
$uri = substr($uri, strlen($scriptDir));
|
||||
}
|
||||
|
||||
// Supprime "index.php" si présent
|
||||
$uri = preg_replace('#^/?index\.php#i', '', $uri);
|
||||
$path = trim($uri, '/');
|
||||
|
||||
return $path === '' ? [] : explode('/', $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche d'une voiture par sa plaque d'immatriculation (exacte ou normalisée)
|
||||
*/
|
||||
function findCarByPlate(PDO $db, string $plaque): void
|
||||
{
|
||||
$clean = strtoupper(trim(urldecode($plaque)));
|
||||
$norm = str_replace(['-', ' ', '.', '_'], '', $clean);
|
||||
|
||||
$stmt = $db->prepare('
|
||||
SELECT c.*,
|
||||
(SELECT valeur FROM kilometrage WHERE car_id = c.id ORDER BY date_releve DESC, id DESC LIMIT 1) AS dernier_km
|
||||
FROM cars c
|
||||
WHERE UPPER(c.immatriculation) = :raw
|
||||
OR REPLACE(REPLACE(REPLACE(REPLACE(UPPER(c.immatriculation), "-", ""), " ", ""), ".", ""), "_", "") = :norm
|
||||
LIMIT 1
|
||||
');
|
||||
$stmt->execute([':raw' => $clean, ':norm' => $norm]);
|
||||
$car = $stmt->fetch();
|
||||
|
||||
if (!$car) {
|
||||
sendJson(404, [
|
||||
'success' => false,
|
||||
'error' => "Aucune voiture trouvée avec l'immatriculation '$clean'"
|
||||
]);
|
||||
}
|
||||
|
||||
sendJson(200, [
|
||||
'success' => true,
|
||||
'data' => $car
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$db = getDB();
|
||||
$method = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET');
|
||||
$segments = getPathSegments();
|
||||
|
||||
// Route racine: Documentation sommaire des points d'accès
|
||||
// Route racine: Documentation des points d'accès
|
||||
if (empty($segments)) {
|
||||
sendJson(200, [
|
||||
'success' => true,
|
||||
|
|
@ -89,29 +116,30 @@ try {
|
|||
'database' => DB_NAME,
|
||||
'endpoints' => [
|
||||
'Voitures' => [
|
||||
'GET /cars' => 'Liste toutes les voitures',
|
||||
'GET /cars/{id}' => 'Détails d\'une voiture',
|
||||
'GET /cars/{id}/etat' => 'État complet (voiture, dernier km, entretiens, notes)',
|
||||
'POST /cars' => 'Créer une voiture (marque, modele, annee, dateAchat, VIN)',
|
||||
'PUT /cars/{id}' => 'Modifier les paramètres d\'une voiture',
|
||||
'DELETE /cars/{id}' => 'Supprimer une voiture (et ses données associées)'
|
||||
'GET /cars' => 'Liste toutes les voitures (filtrable par ?immatriculation=...)',
|
||||
'GET /cars/{id}' => 'Détails d\'une voiture par ID',
|
||||
'GET /cars/immatriculation/{plaque}' => 'Identifier une voiture par sa plaque d\'immatriculation',
|
||||
'GET /cars/{id}/etat' => 'État complet (voiture, dernier km, entretiens, notes)',
|
||||
'POST /cars' => 'Créer une voiture (marque, modele, annee, dateAchat, VIN, immatriculation)',
|
||||
'PUT /cars/{id}' => 'Modifier les paramètres d\'une voiture (y compris immatriculation)',
|
||||
'DELETE /cars/{id}' => 'Supprimer une voiture (et ses données associées)'
|
||||
],
|
||||
'Kilométrage' => [
|
||||
'GET /cars/{id}/kilometrage' => 'Historique des relevés kilométriques',
|
||||
'POST /cars/{id}/kilometrage' => 'Ajouter un relevé kilométrique (valeur, date_releve)',
|
||||
'DELETE /kilometrage/{id}' => 'Supprimer un relevé kilométrique'
|
||||
'GET /cars/{id}/kilometrage' => 'Historique des relevés kilométriques',
|
||||
'POST /cars/{id}/kilometrage' => 'Ajouter un relevé kilométrique (valeur, date_releve)',
|
||||
'DELETE /kilometrage/{id}' => 'Supprimer un relevé kilométrique'
|
||||
],
|
||||
'Entretiens (Maintenance)' => [
|
||||
'GET /cars/{id}/maintenance' => 'Historique des entretiens d\'une voiture',
|
||||
'POST /cars/{id}/maintenance' => 'Ajouter un entretien (type_entretien, date_evenement, kilometrage, description, prix)',
|
||||
'PUT /maintenance/{id}' => 'Modifier un entretien',
|
||||
'DELETE /maintenance/{id}' => 'Supprimer un entretien'
|
||||
'Entretiens' => [
|
||||
'GET /cars/{id}/maintenance' => 'Historique des entretiens d\'une voiture',
|
||||
'POST /cars/{id}/maintenance' => 'Ajouter un entretien (type_entretien, date_evenement, kilometrage, description, prix)',
|
||||
'PUT /maintenance/{id}' => 'Modifier un entretien',
|
||||
'DELETE /maintenance/{id}' => 'Supprimer un entretien'
|
||||
],
|
||||
'Notes' => [
|
||||
'GET /cars/{id}/notes' => 'Notes associées à une voiture',
|
||||
'POST /cars/{id}/notes' => 'Ajouter une note (titre, contenu)',
|
||||
'PUT /notes/{id}' => 'Modifier une note',
|
||||
'DELETE /notes/{id}' => 'Supprimer une note'
|
||||
'GET /cars/{id}/notes' => 'Notes associées à une voiture',
|
||||
'POST /cars/{id}/notes' => 'Ajouter une note (titre, contenu)',
|
||||
'PUT /notes/{id}' => 'Modifier une note',
|
||||
'DELETE /notes/{id}' => 'Supprimer une note'
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
|
@ -119,16 +147,34 @@ try {
|
|||
|
||||
$resource = strtolower($segments[0]);
|
||||
|
||||
// =========================================================================
|
||||
// ENDPOINT DIRECT: /immatriculation/{plaque}
|
||||
// =========================================================================
|
||||
if ($resource === 'immatriculation' && $method === 'GET') {
|
||||
$plaque = $segments[1] ?? null;
|
||||
if (empty($plaque)) {
|
||||
sendJson(400, ['success' => false, 'error' => 'Numéro d\'immatriculation manquant dans l\'URL']);
|
||||
}
|
||||
findCarByPlate($db, $plaque);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// RESSOURCE: CARS (/cars ...)
|
||||
// =========================================================================
|
||||
if ($resource === 'cars' || $resource === 'voitures') {
|
||||
// NOUVEAU ENDPOINT: GET /cars/immatriculation/{plaque}
|
||||
if (isset($segments[1]) && strtolower($segments[1]) === 'immatriculation' && $method === 'GET') {
|
||||
$plaque = $segments[2] ?? null;
|
||||
if (empty($plaque)) {
|
||||
sendJson(400, ['success' => false, 'error' => 'Numéro d\'immatriculation manquant dans l\'URL']);
|
||||
}
|
||||
findCarByPlate($db, $plaque);
|
||||
}
|
||||
|
||||
$carId = isset($segments[1]) && is_numeric($segments[1]) ? (int)$segments[1] : null;
|
||||
$subResource = $segments[2] ?? null;
|
||||
|
||||
// --- SOUS-RESSOURCES DE CARS ---
|
||||
|
||||
// 1. /cars/{id}/etat (État complet via la procédure sp_getEtatVoiture)
|
||||
// 1. GET /cars/{id}/etat (État complet via la procédure sp_getEtatVoiture)
|
||||
if ($carId && $subResource === 'etat' && $method === 'GET') {
|
||||
try {
|
||||
$stmt = $db->prepare('CALL sp_getEtatVoiture(:id)');
|
||||
|
|
@ -153,13 +199,12 @@ try {
|
|||
sendJson(200, [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'voiture' => $car,
|
||||
'maintenances' => $maintenances,
|
||||
'notes' => $notes
|
||||
'voiture' => $car,
|
||||
'maintenances' => $maintenances,
|
||||
'notes' => $notes
|
||||
]
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
// Fallback si la procédure stockée a un souci
|
||||
$stmtCar = $db->prepare('
|
||||
SELECT c.*,
|
||||
(SELECT valeur FROM kilometrage WHERE car_id = c.id ORDER BY date_releve DESC, id DESC LIMIT 1) AS dernier_km
|
||||
|
|
@ -312,10 +357,14 @@ try {
|
|||
sendJson(405, ['success' => false, 'error' => 'Méthode non autorisée pour cette sous-ressource']);
|
||||
}
|
||||
|
||||
// --- OPÉRATIONS PRINCIPALES SUR LES VOITURES (/cars ou /cars/{id}) ---
|
||||
// --- ACTIONS PRINCIPALES SUR LES VOITURES ---
|
||||
|
||||
// GET /cars : Liste toutes les voitures (ou filtre ?immatriculation=...)
|
||||
if ($carId === null && $subResource === null && $method === 'GET') {
|
||||
if (!empty($_GET['immatriculation'])) {
|
||||
findCarByPlate($db, (string)$_GET['immatriculation']);
|
||||
}
|
||||
|
||||
// GET /cars : Liste toutes les voitures
|
||||
if ($carId === null && $method === 'GET') {
|
||||
$sql = 'SELECT c.*,
|
||||
(SELECT valeur FROM kilometrage WHERE car_id = c.id ORDER BY date_releve DESC, id DESC LIMIT 1) AS dernier_km
|
||||
FROM cars c
|
||||
|
|
@ -344,7 +393,6 @@ try {
|
|||
if ($carId === null && $method === 'POST') {
|
||||
$body = getBody();
|
||||
|
||||
// Validation des champs obligatoires
|
||||
$required = ['marque', 'modele', 'annee', 'dateAchat'];
|
||||
foreach ($required as $field) {
|
||||
if (empty($body[$field])) {
|
||||
|
|
@ -352,39 +400,56 @@ try {
|
|||
}
|
||||
}
|
||||
|
||||
$marque = trim((string)$body['marque']);
|
||||
$modele = trim((string)$body['modele']);
|
||||
$annee = (int)$body['annee'];
|
||||
$dateAchat = (string)$body['dateAchat'];
|
||||
$vin = !empty($body['VIN']) ? trim((string)$body['VIN']) : null;
|
||||
$marque = trim((string)$body['marque']);
|
||||
$modele = trim((string)$body['modele']);
|
||||
$annee = (int)$body['annee'];
|
||||
$dateAchat = (string)$body['dateAchat'];
|
||||
$vin = !empty($body['VIN']) ? trim((string)$body['VIN']) : null;
|
||||
$immatriculation = !empty($body['immatriculation']) ? strtoupper(trim((string)$body['immatriculation'])) : null;
|
||||
|
||||
// Utilisation de la procédure stockée sp_insertVoiture
|
||||
// Vérifier unicité de l'immatriculation si renseignée
|
||||
if ($immatriculation !== null) {
|
||||
$normImmat = str_replace(['-', ' ', '.', '_'], '', $immatriculation);
|
||||
$stmtCheck = $db->prepare('
|
||||
SELECT id FROM cars
|
||||
WHERE UPPER(immatriculation) = :raw
|
||||
OR REPLACE(REPLACE(REPLACE(REPLACE(UPPER(immatriculation), "-", ""), " ", ""), ".", ""), "_", "") = :norm
|
||||
');
|
||||
$stmtCheck->execute([':raw' => $immatriculation, ':norm' => $normImmat]);
|
||||
if ($stmtCheck->fetch()) {
|
||||
sendJson(409, [
|
||||
'success' => false,
|
||||
'error' => "L'immatriculation '$immatriculation' est déjà utilisée pour un autre véhicule."
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Appel de la procédure stockée sp_insertVoiture
|
||||
try {
|
||||
$stmt = $db->prepare('CALL sp_insertVoiture(:marque, :modele, :annee, :dateAchat, :vin)');
|
||||
$stmt = $db->prepare('CALL sp_insertVoiture(:marque, :modele, :annee, :dateAchat, :vin, :immatriculation)');
|
||||
$stmt->execute([
|
||||
':marque' => $marque,
|
||||
':modele' => $modele,
|
||||
':annee' => $annee,
|
||||
':dateAchat' => $dateAchat,
|
||||
':vin' => $vin
|
||||
':marque' => $marque,
|
||||
':modele' => $modele,
|
||||
':annee' => $annee,
|
||||
':dateAchat' => $dateAchat,
|
||||
':vin' => $vin,
|
||||
':immatriculation' => $immatriculation
|
||||
]);
|
||||
$res = $stmt->fetch();
|
||||
$stmt->closeCursor();
|
||||
$newId = isset($res['new_id']) ? (int)$res['new_id'] : (int)$db->lastInsertId();
|
||||
} catch (Exception $e) {
|
||||
// Fallback direct INSERT si la procédure a une incompatibilité
|
||||
$stmt = $db->prepare('INSERT INTO cars (marque, modele, annee, dateAchat, VIN) VALUES (?, ?, ?, ?, ?)');
|
||||
$stmt->execute([$marque, $modele, $annee, $dateAchat, $vin]);
|
||||
$stmt = $db->prepare('INSERT INTO cars (marque, modele, annee, dateAchat, VIN, immatriculation) VALUES (?, ?, ?, ?, ?, ?)');
|
||||
$stmt->execute([$marque, $modele, $annee, $dateAchat, $vin, $immatriculation]);
|
||||
$newId = (int)$db->lastInsertId();
|
||||
}
|
||||
|
||||
// Si un kilométrage initial est fourni
|
||||
// Kilométrage initial optionnel
|
||||
if (!empty($body['kilometrage_initial'])) {
|
||||
$kmStmt = $db->prepare('INSERT INTO kilometrage (car_id, date_releve, valeur) VALUES (?, ?, ?)');
|
||||
$kmStmt->execute([$newId, $dateAchat, (int)$body['kilometrage_initial']]);
|
||||
}
|
||||
|
||||
// Récupère l'enregistrement créé
|
||||
$stmtGet = $db->prepare('
|
||||
SELECT c.*,
|
||||
(SELECT valeur FROM kilometrage WHERE car_id = c.id ORDER BY date_releve DESC, id DESC LIMIT 1) AS dernier_km
|
||||
|
|
@ -402,7 +467,6 @@ try {
|
|||
|
||||
// PUT / PATCH /cars/{id} : Mettre à jour les paramètres d'une voiture
|
||||
if ($carId !== null && $subResource === null && ($method === 'PUT' || $method === 'PATCH')) {
|
||||
// Vérifier si la voiture existe
|
||||
$check = $db->prepare('SELECT id FROM cars WHERE id = ?');
|
||||
$check->execute([$carId]);
|
||||
if (!$check->fetch()) {
|
||||
|
|
@ -410,10 +474,30 @@ try {
|
|||
}
|
||||
|
||||
$body = getBody();
|
||||
$allowedFields = ['marque', 'modele', 'annee', 'dateAchat', 'VIN'];
|
||||
$allowedFields = ['marque', 'modele', 'annee', 'dateAchat', 'VIN', 'immatriculation'];
|
||||
$updates = [];
|
||||
$params = [];
|
||||
|
||||
// Si immatriculation modifiée, vérifier unicité
|
||||
if (!empty($body['immatriculation'])) {
|
||||
$immat = strtoupper(trim((string)$body['immatriculation']));
|
||||
$body['immatriculation'] = $immat;
|
||||
$normImmat = str_replace(['-', ' ', '.', '_'], '', $immat);
|
||||
$stmtCheck = $db->prepare('
|
||||
SELECT id FROM cars
|
||||
WHERE (UPPER(immatriculation) = :raw
|
||||
OR REPLACE(REPLACE(REPLACE(REPLACE(UPPER(immatriculation), "-", ""), " ", ""), ".", ""), "_", "") = :norm)
|
||||
AND id != :id
|
||||
');
|
||||
$stmtCheck->execute([':raw' => $immat, ':norm' => $normImmat, ':id' => $carId]);
|
||||
if ($stmtCheck->fetch()) {
|
||||
sendJson(409, [
|
||||
'success' => false,
|
||||
'error' => "L'immatriculation '$immat' est déjà utilisée pour un autre véhicule."
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($allowedFields as $field) {
|
||||
if (array_key_exists($field, $body)) {
|
||||
$updates[] = "`$field` = ?";
|
||||
|
|
@ -430,7 +514,6 @@ try {
|
|||
$stmt = $db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
|
||||
// Récupère la voiture mise à jour
|
||||
$stmtGet = $db->prepare('
|
||||
SELECT c.*,
|
||||
(SELECT valeur FROM kilometrage WHERE car_id = c.id ORDER BY date_releve DESC, id DESC LIMIT 1) AS dernier_km
|
||||
|
|
@ -448,14 +531,12 @@ try {
|
|||
|
||||
// DELETE /cars/{id} : Supprimer une voiture
|
||||
if ($carId !== null && $subResource === null && $method === 'DELETE') {
|
||||
// Vérifier existence
|
||||
$check = $db->prepare('SELECT id FROM cars WHERE id = ?');
|
||||
$check->execute([$carId]);
|
||||
if (!$check->fetch()) {
|
||||
sendJson(404, ['success' => false, 'error' => "Voiture #{$carId} introuvable"]);
|
||||
}
|
||||
|
||||
// Exécution de la procédure stockée sp_deleteVoiture
|
||||
try {
|
||||
$stmt = $db->prepare('CALL sp_deleteVoiture(:id)');
|
||||
$stmt->execute([':id' => $carId]);
|
||||
|
|
@ -493,7 +574,7 @@ try {
|
|||
}
|
||||
|
||||
// =========================================================================
|
||||
// RESSOURCE: MAINTENANCE DIRECT (/maintenance/{id})
|
||||
// RESSOURCE: ENTRETIENS DIRECT (/maintenance/{id})
|
||||
// =========================================================================
|
||||
if ($resource === 'maintenance' || $resource === 'entretiens') {
|
||||
$maintId = isset($segments[1]) && is_numeric($segments[1]) ? (int)$segments[1] : null;
|
||||
|
|
@ -616,20 +697,13 @@ try {
|
|||
sendJson(405, ['success' => false, 'error' => "Méthode non autorisée sur /notes/{id}"]);
|
||||
}
|
||||
|
||||
// Ressource inconnue
|
||||
sendJson(404, [
|
||||
'success' => false,
|
||||
'error' => "Ressource '/$resource' non reconnue. Consultez la racine '/' pour la documentation des routes."
|
||||
]);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
sendJson(500, [
|
||||
'success' => false,
|
||||
'error' => 'Erreur SQL: ' . $e->getMessage()
|
||||
]);
|
||||
sendJson(500, ['success' => false, 'error' => 'Erreur SQL: ' . $e->getMessage()]);
|
||||
} catch (Throwable $e) {
|
||||
sendJson(500, [
|
||||
'success' => false,
|
||||
'error' => 'Erreur serveur: ' . $e->getMessage()
|
||||
]);
|
||||
sendJson(500, ['success' => false, 'error' => 'Erreur serveur: ' . $e->getMessage()]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,9 +53,9 @@ CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_getKilometrages` (IN `p_id` INT,
|
|||
END$$
|
||||
|
||||
DROP PROCEDURE IF EXISTS `sp_insertVoiture`$$
|
||||
CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_insertVoiture` (IN `p_marque` VARCHAR(255), IN `p_modele` VARCHAR(255), IN `p_annee` INT, IN `p_dateAchat` DATE, IN `p_VIN` VARCHAR(255)) BEGIN
|
||||
INSERT INTO cars (marque, modele, annee, dateAchat, VIN)
|
||||
VALUES (p_marque, p_modele, p_annee, p_dateAchat, p_VIN);
|
||||
CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_insertVoiture` (IN `p_marque` VARCHAR(255), IN `p_modele` VARCHAR(255), IN `p_annee` INT, IN `p_dateAchat` DATE, IN `p_VIN` VARCHAR(255), IN `p_immatriculation` VARCHAR(20)) BEGIN
|
||||
INSERT INTO cars (marque, modele, annee, dateAchat, VIN, immatriculation)
|
||||
VALUES (p_marque, p_modele, p_annee, p_dateAchat, p_VIN, p_immatriculation);
|
||||
SELECT LAST_INSERT_ID() AS new_id;
|
||||
END$$
|
||||
|
||||
|
|
@ -75,16 +75,18 @@ CREATE TABLE IF NOT EXISTS `cars` (
|
|||
`annee` int NOT NULL,
|
||||
`dateAchat` date NOT NULL,
|
||||
`VIN` varchar(17) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
`immatriculation` varchar(20) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `idx_immatriculation` (`immatriculation`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
--
|
||||
-- Déchargement des données de la table `cars`
|
||||
--
|
||||
|
||||
INSERT INTO `cars` (`id`, `marque`, `modele`, `annee`, `dateAchat`, `VIN`) VALUES
|
||||
(1, 'Peugeot', '207', 2009, '2025-05-20', 'VF3WA8HZCXXXXXXXX'),
|
||||
(2, 'Peugeot', '208', 2010, '2025-05-20', 'VF3WA8HZCXXXXXXXX');
|
||||
INSERT INTO `cars` (`id`, `marque`, `modele`, `annee`, `dateAchat`, `VIN`, `immatriculation`) VALUES
|
||||
(1, 'Peugeot', '207', 2009, '2025-05-20', 'VF3WA8HZCXXXXXXXX', 'AB-123-CD'),
|
||||
(2, 'Peugeot', '208', 2010, '2025-05-20', 'VF3WA8HZCXXXXXXXX', 'EF-456-GH');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue