'Réservation', 'foster' => 'Placement en famille d’accueil', 'return' => 'Retour au refuge', 'cancellation' => 'Annulation', ]; public static function save(): void { if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') { http_response_code(405); return; } $db = DB::pdo(); $animalId = (int) ($_POST['animal_id'] ?? 0); $type = (string) ($_POST['event_type'] ?? ''); $date = (string) ($_POST['event_date'] ?? ''); if (!$animalId || !isset(self::TYPES[$type]) || !self::validDate($date)) { http_response_code(400); echo h(t('error.invalid_placement')); return; } $reason = trim((string) ($_POST['reason'] ?? '')); if (in_array($type, ['return', 'cancellation'], true) && $reason === '') { http_response_code(400); echo h(t('error.placement_reason_required')); return; } $s = $db->prepare('SELECT * FROM animals WHERE id=:id AND deleted_at IS NULL AND archived_at IS NULL'); $s->execute([':id' => $animalId]); $animal = $s->fetch(PDO::FETCH_ASSOC); if (!$animal) { http_response_code(404); return; } $contactId = (int) ($_POST['contact_id'] ?? 0) ?: null; $contact = null; if ($contactId) { $s = $db->prepare('SELECT * FROM directory_contacts WHERE id=:id AND deleted_at IS NULL'); $s->execute([':id' => $contactId]); $contact = $s->fetch(PDO::FETCH_ASSOC) ?: null; if (!$contact) { $contactId = null; } } $status = match ($type) { 'reservation' => 'reserve', 'foster' => 'fa', 'return' => 'refuge', 'cancellation' => in_array($animal['status'], ['reserve', 'réservé'], true) ? 'refuge' : $animal['status'], }; $currentAddress = (string) ($animal['current_address'] ?? ''); if ($type === 'foster' && $contact) { $currentAddress = implode( ' ', array_filter([$contact['address'] ?? '', $contact['postal_code'] ?? '', $contact['city'] ?? '']), ); } if ($type === 'return') { $currentAddress = implode( ' ', array_filter([ AppSettings::get('association_address'), AppSettings::get('association_postal_code'), AppSettings::get('association_city'), ]), ); } $notes = trim((string) ($_POST['notes'] ?? '')); $db->beginTransaction(); try { $db->prepare( 'INSERT INTO animal_placements(animal_id,event_type,event_date,contact_id,reason,notes,created_by) VALUES(:animal,:type,:date,:contact,:reason,:notes,:user)', )->execute([ ':animal' => $animalId, ':type' => $type, ':date' => $date, ':contact' => $contactId, ':reason' => $reason ?: null, ':notes' => $notes ?: null, ':user' => Auth::id(), ]); $db->prepare( 'UPDATE animals SET status=:status,refuge_room=NULL,care_box_key=NULL,current_address=:address,current_lat=NULL,current_lng=NULL,updated_at=datetime(\'now\') WHERE id=:id', )->execute([':status' => $status, ':address' => $currentAddress ?: null, ':id' => $animalId]); $after = $animal; $after['status'] = $status; $after['refuge_room'] = null; $after['care_box_key'] = null; $after['current_address'] = $currentAddress; LocationHistoryService::record( $db, $animalId, $animal, $after, 'placement', self::TYPES[$type] . ($reason !== '' ? ' — ' . $reason : ''), $date . ' 12:00:00', ); $details = []; if ($reason !== '') { $details[] = 'Motif : ' . $reason; } if ($notes !== '') { $details[] = 'Notes : ' . $notes; } $db->prepare( 'INSERT INTO animal_history(animal_id,type,label,details,user_id,created_at) VALUES(:animal,\'placement\',:label,:details,:user,:date)', )->execute([ ':animal' => $animalId, ':label' => self::TYPES[$type], ':details' => $details ? implode("\n", $details) : null, ':user' => Auth::id(), ':date' => $date . ' 12:00:00', ]); if ($type === 'foster' || $type === 'return') { self::movement($db, $animalId, $type, $contactId, $reason, $notes, $date); } $db->commit(); header('Location: /animal?id=' . $animalId . '#tab-placements'); exit(); } catch (Throwable $e) { if ($db->inTransaction()) { $db->rollBack(); } throw $e; } } private static function movement( PDO $db, int $animalId, string $type, ?int $contactId, string $reason, string $notes, string $date, ): void { $contact = null; if ($contactId) { $s = $db->prepare('SELECT name,phone,email,city FROM directory_contacts WHERE id=:id'); $s->execute([':id' => $contactId]); $contact = $s->fetch(PDO::FETCH_ASSOC) ?: null; } $db->prepare( 'INSERT INTO animal_movements(animal_id,kind,place,lieu,contact_name,contact_phone,contact_email,note,created_at) VALUES(:animal,:kind,:place,:lieu,:name,:phone,:email,:note,:date)', )->execute([ ':animal' => $animalId, ':kind' => $type === 'return' ? 'entry' : 'exit', ':place' => $contact['city'] ?? null, ':lieu' => $type === 'return' ? 'Retour de placement' : 'Placement en FA', ':name' => $contact['name'] ?? null, ':phone' => $contact['phone'] ?? null, ':email' => $contact['email'] ?? null, ':note' => trim(($reason !== '' ? 'Motif : ' . $reason : '') . ($notes !== '' ? "\nNotes : " . $notes : '')) ?: null, ':date' => $date . ' 12:00:00', ]); } private static function validDate(string $date): bool { $d = DateTimeImmutable::createFromFormat('!Y-m-d', $date); return $d !== false && $d->format('Y-m-d') === $date; } }