Globinours/app/Controllers/DeathController.php

305 lines
13 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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;
}
}