#!/usr/bin/env php query("SELECT id FROM users WHERE role='admin' AND active=1 ORDER BY id LIMIT 1")->fetchColumn() ?: 0) ?: null; $latest = $db ->query( "WITH ranked AS (SELECT ap.*,ROW_NUMBER() OVER(PARTITION BY animal_id ORDER BY date(event_date) DESC,id DESC) rn FROM animal_placements ap) SELECT r.*,a.status current_status,a.archived_at current_archived,dc.name contact_name,dc.address contact_address,dc.postal_code contact_postal,dc.city contact_city,ad.adopter_address,ad.adopter_postal_code,ad.adopter_city FROM ranked r JOIN animals a ON a.id=r.animal_id LEFT JOIN animal_deaths d ON d.animal_id=a.id LEFT JOIN directory_contacts dc ON dc.id=r.contact_id LEFT JOIN adoptions ad ON ad.id=r.adoption_id WHERE r.rn=1 AND r.event_type='adoption' AND d.animal_id IS NULL AND a.deleted_at IS NULL ORDER BY r.event_date,r.animal_id", ) ->fetchAll(PDO::FETCH_ASSOC); $summary = [ 'adopted_states_checked' => count($latest), 'statuses_fixed' => 0, 'locations_fixed' => 0, 'movements_created' => 0, 'movements_updated' => 0, 'history_created' => 0, ]; if ($apply) { $backup = BackupService::create('avant-reconciliation-etat-animaux'); } $db->beginTransaction(); try { foreach ($latest as $row) { $animal = (int) $row['animal_id']; $date = (string) $row['event_date']; $contact = trim((string) ($row['contact_name'] ?? '')) ?: 'Adoptant non renseigné'; $address = implode( ' ', array_filter([ trim((string) ($row['contact_address'] ?? '' ?: $row['adopter_address'] ?? '')), trim((string) ($row['contact_postal'] ?? '' ?: $row['adopter_postal_code'] ?? '')), trim((string) ($row['contact_city'] ?? '' ?: $row['adopter_city'] ?? '')), ]), ); if ($address === '') { $address = 'Adopté par ' . $contact; } if ($row['current_status'] !== 'adopte' || empty($row['current_archived'])) { $summary['statuses_fixed']++; } $current = $db->prepare('SELECT current_address FROM animals WHERE id=?'); $current->execute([$animal]); if (trim((string) $current->fetchColumn()) !== $address) { $summary['locations_fixed']++; } $db->prepare( "UPDATE animals SET status='adopte',is_archived=1,archived_at=?,refuge_room=NULL,care_box_key=NULL,quarantine_until=NULL,current_address=?,current_lat=NULL,current_lng=NULL,updated_at=datetime('now') WHERE id=?", )->execute([$date . ' 12:00:00', $address, $animal]); $movement = $db->prepare( "SELECT id FROM animal_movements WHERE animal_id=? AND kind='exit' AND lower(COALESCE(lieu,'')) LIKE 'adopt%' ORDER BY created_at DESC,id DESC LIMIT 1", ); $movement->execute([$animal]); $movementId = (int) $movement->fetchColumn(); if ($movementId) { $db->prepare( "UPDATE animal_movements SET place=?,lieu='Adoption',contact_name=?,note=CASE WHEN note IS NULL OR note='' THEN 'Sortie synchronisée avec le parcours d’adoption.' ELSE note END,created_at=? WHERE id=?", )->execute([$address, $contact, $date . ' 12:00:00', $movementId]); $summary['movements_updated']++; } else { $db->prepare( "INSERT INTO animal_movements(animal_id,kind,place,lieu,contact_name,note,created_at) VALUES(?,'exit',?,'Adoption',?,'Sortie synchronisée avec le parcours d’adoption.',?)", )->execute([$animal, $address, $contact, $date . ' 12:00:00']); $summary['movements_created']++; } $history = $db->prepare( "SELECT 1 FROM animal_history WHERE animal_id=? AND date(created_at)=date(?) AND (lower(label) LIKE '%adopt%' OR lower(type)='adoption') LIMIT 1", ); $history->execute([$animal, $date]); if (!$history->fetchColumn()) { $db->prepare( "INSERT INTO animal_history(animal_id,type,label,details,user_id,created_at) VALUES(?,'status','Adoption de l’animal',?,?,?)", )->execute([$animal, 'Adopté par ' . $contact . '.', $user, $date . ' 12:00:00']); $summary['history_created']++; } } $fk = $db->query('PRAGMA foreign_key_check')->fetchAll(); if ($fk) { throw new RuntimeException('Violation de clé étrangère après synchronisation.'); } if ($apply) { $db->commit(); } else { $db->rollBack(); } echo json_encode( ['mode' => $apply ? 'APPLIQUE' : 'SIMULATION', 'backup' => $backup['name'] ?? null, 'summary' => $summary], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, ), "\n"; } catch (Throwable $error) { if ($db->inTransaction()) { $db->rollBack(); } fwrite(STDERR, 'Synchronisation interrompue : ' . $error->getMessage() . "\n"); exit(1); }