172 lines
6.7 KiB
PHP
172 lines
6.7 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
final class PlacementsController
|
||
{
|
||
private const TYPES = [
|
||
'reservation' => '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;
|
||
}
|
||
}
|