Publier Globinours 1.0.0-rc.3

This commit is contained in:
Alexandre NOEL 2026-09-03 12:39:15 +02:00
commit 9a2b4068da
325 changed files with 38230 additions and 20 deletions

View file

@ -0,0 +1,305 @@
<?php
declare(strict_types=1);
final class DeathController
{
public const CAUSES = [
'disease' => 'Maladie',
'accident' => 'Accident',
'old_age' => 'Vieillesse',
'congenital' => 'Malformation ou affection congénitale',
'surgery_complication' => 'Complication médicale ou chirurgicale',
'poisoning' => 'Empoisonnement',
'unknown' => 'Cause inconnue',
'other' => 'Autre cause',
];
public const PLACES = [
'refuge' => 'Au refuge',
'fa' => 'En famille daccueil',
'veterinaire' => 'Chez le vétérinaire',
'exterieur' => 'À lextérieur',
'inconnu' => 'Lieu inconnu',
'autre' => 'Autre lieu',
];
public const DISPOSITIONS = [
'pending' => 'À organiser',
'collective_cremation' => 'Crémation collective',
'individual_cremation' => 'Crémation individuelle',
'returned_family' => 'Corps ou cendres remis à la famille',
'burial' => 'Inhumation',
'other' => 'Autre prise en charge',
];
public static function causeLabel(string $key): string
{
return t('death.cause.' . $key, [], self::CAUSES[$key] ?? $key);
}
public static function placeLabel(string $key): string
{
return t('death.place.' . $key, [], self::PLACES[$key] ?? $key);
}
public static function dispositionLabel(string $key): string
{
return t('death.disposition.' . $key, [], self::DISPOSITIONS[$key] ?? $key);
}
public static function form(): void
{
$db = DB::pdo();
$id = (int) ($_GET['id'] ?? 0);
$animal = self::animal($db, $id);
$stmt = $db->prepare(
'SELECT ad.*,vet.name veterinarian_name,crem.name crematorium_name FROM animal_deaths ad LEFT JOIN directory_contacts vet ON vet.id=ad.veterinarian_contact_id LEFT JOIN directory_contacts crem ON crem.id=ad.crematorium_contact_id WHERE ad.animal_id=:id',
);
$stmt->execute([':id' => $id]);
$death = $stmt->fetch(PDO::FETCH_ASSOC) ?: [
'animal_id' => $id,
'deceased_date' => date('Y-m-d'),
'cause_code' => 'disease',
'cause_details' => '',
'occurred_in_care' => !in_array($animal['status'], ['adopte', 'adopté'], true) ? 1 : 0,
'place_type' => 'refuge',
'place_details' => '',
'euthanized' => 0,
'veterinarian_contact_id' => '',
'crematorium_contact_id' => '',
'body_disposition' => 'pending',
'cremation_date' => '',
'notes' => '',
];
$veterinarians = self::contacts($db, 'veterinaire');
$crematoriums = self::contacts($db, 'crematorium');
render('animal_death_form.php', [
'title' => t('death.title', ['name' => $animal['name']]),
'animal' => $animal,
'death' => $death,
'causes' => self::CAUSES,
'places' => self::PLACES,
'dispositions' => self::DISPOSITIONS,
'veterinarians' => $veterinarians,
'crematoriums' => $crematoriums,
]);
}
public static function save(): void
{
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
http_response_code(405);
return;
}
$db = DB::pdo();
$id = (int) ($_POST['animal_id'] ?? 0);
$animal = self::animal($db, $id);
$date = trim((string) ($_POST['deceased_date'] ?? ''));
$cause = (string) ($_POST['cause_code'] ?? '');
$place = (string) ($_POST['place_type'] ?? '');
$disposition = (string) ($_POST['body_disposition'] ?? 'pending');
if (
!self::validDate($date) ||
$date > date('Y-m-d') ||
!isset(self::CAUSES[$cause]) ||
!isset(self::PLACES[$place]) ||
!isset(self::DISPOSITIONS[$disposition])
) {
http_response_code(400);
echo h(t('death.invalid'));
return;
}
if ($cause === 'other' && trim((string) ($_POST['cause_details'] ?? '')) === '') {
http_response_code(400);
echo h(t('death.cause_required'));
return;
}
$vet = self::validContact($db, (int) ($_POST['veterinarian_contact_id'] ?? 0), 'veterinaire');
$crem = self::validContact($db, (int) ($_POST['crematorium_contact_id'] ?? 0), 'crematorium');
$cremationDate = trim((string) ($_POST['cremation_date'] ?? ''));
if ($cremationDate !== '' && (!self::validDate($cremationDate) || $cremationDate < $date)) {
http_response_code(400);
echo h(t('death.cremation_after'));
return;
}
$check = $db->prepare('SELECT 1 FROM animal_deaths WHERE animal_id=:id');
$check->execute([':id' => $id]);
$isUpdate = (bool) $check->fetchColumn();
$beforeLocation = LocationHistoryService::snapshot($animal);
$params = [
':animal' => $id,
':date' => $date,
':cause' => $cause,
':details' => trim((string) ($_POST['cause_details'] ?? '')) ?: null,
':care' => isset($_POST['occurred_in_care']) ? 1 : 0,
':place' => $place,
':place_details' => trim((string) ($_POST['place_details'] ?? '')) ?: null,
':euthanized' => isset($_POST['euthanized']) ? 1 : 0,
':vet' => $vet,
':crem' => $crem,
':disposition' => $disposition,
':cremation_date' => $cremationDate ?: null,
':notes' => trim((string) ($_POST['notes'] ?? '')) ?: null,
':user' => Auth::id(),
];
$db->beginTransaction();
try {
$db->prepare(
"INSERT INTO animal_deaths(animal_id,deceased_date,cause_code,cause_details,occurred_in_care,place_type,place_details,euthanized,veterinarian_contact_id,crematorium_contact_id,body_disposition,cremation_date,notes,created_by,updated_by) VALUES(:animal,:date,:cause,:details,:care,:place,:place_details,:euthanized,:vet,:crem,:disposition,:cremation_date,:notes,:user,:user) ON CONFLICT(animal_id) DO UPDATE SET deceased_date=excluded.deceased_date,cause_code=excluded.cause_code,cause_details=excluded.cause_details,occurred_in_care=excluded.occurred_in_care,place_type=excluded.place_type,place_details=excluded.place_details,euthanized=excluded.euthanized,veterinarian_contact_id=excluded.veterinarian_contact_id,crematorium_contact_id=excluded.crematorium_contact_id,body_disposition=excluded.body_disposition,cremation_date=excluded.cremation_date,notes=excluded.notes,updated_by=excluded.updated_by,updated_at=datetime('now')",
)->execute($params);
$db->prepare(
"UPDATE animals SET status='decede',refuge_room=NULL,care_box_key=NULL,updated_at=datetime('now') WHERE id=:id",
)->execute([':id' => $id]);
LocationHistoryService::record(
$db,
$id,
$beforeLocation,
[
'status' => 'decede',
'refuge_room' => null,
'care_box_key' => null,
'current_address' => $animal['current_address'] ?? null,
],
'death',
'Décès enregistré',
$date . ' 12:00:00',
);
$db->prepare(
'UPDATE treatments SET ongoing=0,end_date=COALESCE(end_date,:date) WHERE animal_id=:id AND ongoing=1',
)->execute([':date' => $date, ':id' => $id]);
$details =
'Date : ' .
self::frDate($date) .
'\nCause : ' .
self::CAUSES[$cause] .
'\nContexte : ' .
($params[':care'] ? 'Sous la responsabilité du refuge' : 'Après la prise en charge du refuge');
if ($params[':euthanized']) {
$details .= '\nEuthanasie : oui';
}
$db->prepare(
"INSERT INTO animal_history(animal_id,type,label,details,user_id) VALUES(:id,'death',:label,:details,:user)",
)->execute([
':id' => $id,
':label' => $isUpdate ? 'Informations de décès modifiées' : 'Décès enregistré',
':details' => $details,
':user' => Auth::id(),
]);
self::syncMovement(
$db,
$id,
$date,
$cause,
$params[':details'],
$place,
$params[':place_details'],
$params[':euthanized'] === 1,
$vet,
$crem,
$disposition,
);
$db->commit();
header('Location: /animal?id=' . $id);
exit();
} catch (Throwable $e) {
if ($db->inTransaction()) {
$db->rollBack();
}
throw $e;
}
}
private static function animal(PDO $db, int $id): array
{
$s = $db->prepare('SELECT * FROM animals WHERE id=:id AND deleted_at IS NULL');
$s->execute([':id' => $id]);
$a = $s->fetch(PDO::FETCH_ASSOC);
if (!$a) {
http_response_code(404);
echo h(t('error.animal_not_found'));
exit();
}
return $a;
}
private static function contacts(PDO $db, string $role): array
{
$s = $db->prepare(
'SELECT dc.id,dc.name,dc.city FROM directory_contacts dc JOIN directory_contact_roles r ON r.contact_id=dc.id WHERE r.role=:role AND dc.deleted_at IS NULL ORDER BY dc.name COLLATE NOCASE',
);
$s->execute([':role' => $role]);
return $s->fetchAll(PDO::FETCH_ASSOC);
}
private static function validContact(PDO $db, int $id, string $role): ?int
{
if (!$id) {
return null;
}
$s = $db->prepare(
'SELECT 1 FROM directory_contacts dc JOIN directory_contact_roles r ON r.contact_id=dc.id WHERE dc.id=:id AND r.role=:role AND dc.deleted_at IS NULL',
);
$s->execute([':id' => $id, ':role' => $role]);
return $s->fetchColumn() ? $id : null;
}
private static function syncMovement(
PDO $db,
int $animalId,
string $date,
string $cause,
?string $causeDetails,
string $place,
?string $placeDetails,
bool $euthanized,
?int $vetId,
?int $cremId,
string $disposition,
): void {
$names = [];
if ($vetId || $cremId) {
$s = $db->prepare('SELECT id,name FROM directory_contacts WHERE id IN (:vet,:crem)');
$s->execute([':vet' => $vetId ?? 0, ':crem' => $cremId ?? 0]);
foreach ($s->fetchAll(PDO::FETCH_ASSOC) as $row) {
$names[(int) $row['id']] = $row['name'];
}
}
$noteParts = ['Cause : ' . self::CAUSES[$cause] . ($causeDetails ? ' — ' . $causeDetails : '')];
if ($euthanized) {
$noteParts[] = 'Euthanasie : oui';
}
if ($cremId && isset($names[$cremId])) {
$noteParts[] = 'Crématorium : ' . $names[$cremId];
}
$noteParts[] = 'Prise en charge : ' . self::DISPOSITIONS[$disposition];
$s = $db->prepare(
"SELECT id FROM animal_movements WHERE animal_id=:animal AND kind='exit' AND lower(lieu) IN ('décès','deces','décès de l''animal','deces de l''animal') ORDER BY id DESC LIMIT 1",
);
$s->execute([':animal' => $animalId]);
$movementId = (int) $s->fetchColumn();
$values = [
':animal' => $animalId,
':place' => self::PLACES[$place] . ($placeDetails ? ' — ' . $placeDetails : ''),
':contact' => $vetId && isset($names[$vetId]) ? $names[$vetId] : null,
':note' => implode("\n", $noteParts),
':at' => $date . ' 12:00:00',
];
if ($movementId) {
$values[':movement'] = $movementId;
$db->prepare(
"UPDATE animal_movements SET place=:place,lieu='Décès de l''animal',contact_name=:contact,note=:note,created_at=:at WHERE id=:movement AND animal_id=:animal",
)->execute($values);
} else {
$db->prepare(
"INSERT INTO animal_movements(animal_id,kind,place,lieu,contact_name,note,created_at) VALUES(:animal,'exit',:place,'Décès de l''animal',:contact,:note,:at)",
)->execute($values);
}
}
private static function validDate(string $date): bool
{
$d = DateTimeImmutable::createFromFormat('!Y-m-d', $date);
$errors = DateTimeImmutable::getLastErrors();
return $d !== false &&
($errors === false || ((int) $errors['warning_count'] === 0 && (int) $errors['error_count'] === 0)) &&
$d->format('Y-m-d') === $date;
}
private static function frDate(string $date): string
{
$d = DateTimeImmutable::createFromFormat('Y-m-d', $date);
return $d ? $d->format('d/m/Y') : $date;
}
}