Globinours/app/Controllers/DashboardController.php

300 lines
19 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 DashboardController
{
public static function index(): void
{
$db = DB::pdo();
// Complète les anciennes quarantaines dont la date dentrée était absente.
$db->exec("
UPDATE animals
SET intake_date = date(updated_at)
WHERE lower(trim(status))='quarantaine'
AND deleted_at IS NULL AND archived_at IS NULL
AND (intake_date IS NULL OR trim(intake_date)='')
AND updated_at IS NOT NULL AND trim(updated_at)<>''
");
$quarantineDays = AppSettings::int('quarantine_days', 1, 90);
$stmt = $db->prepare("
UPDATE animals
SET quarantine_until = date(intake_date, '+' || :days || ' day')
WHERE lower(trim(status))='quarantaine'
AND deleted_at IS NULL AND archived_at IS NULL
AND (quarantine_until IS NULL OR trim(quarantine_until)='')
AND intake_date IS NOT NULL AND trim(intake_date)<>''
");
$stmt->execute([':days' => $quarantineDays]);
// Stats par statut (avec tolérance accents)
$stats = $db
->query(
"
SELECT
SUM(CASE WHEN lower(status) NOT IN ('adopte','adopté','decede','décédé') THEN 1 ELSE 0 END) AS total,
SUM(CASE WHEN lower(status) NOT IN ('adopte','adopté','decede','décédé') AND birth_date IS NOT NULL AND trim(birth_date) != '' AND date(birth_date) > date('now','-12 months') THEN 1 ELSE 0 END) AS kittens,
SUM(CASE WHEN lower(status) NOT IN ('adopte','adopté','decede','décédé') AND birth_date IS NOT NULL AND trim(birth_date) != '' AND date(birth_date) <= date('now','-12 months') THEN 1 ELSE 0 END) AS adults,
SUM(CASE WHEN lower(status) NOT IN ('adopte','adopté','decede','décédé') AND (birth_date IS NULL OR trim(birth_date) = '') THEN 1 ELSE 0 END) AS age_unknown,
SUM(CASE WHEN status='refuge' THEN 1 ELSE 0 END) AS refuge,
SUM(CASE WHEN status='refuge' AND refuge_room='silver' THEN 1 ELSE 0 END) AS refuge_silver,
SUM(CASE WHEN status='refuge' AND refuge_room='twix' THEN 1 ELSE 0 END) AS refuge_twix,
SUM(CASE WHEN status='refuge' AND (refuge_room IS NULL OR trim(refuge_room)='') THEN 1 ELSE 0 END) AS refuge_unassigned,
SUM(CASE WHEN status='fa' THEN 1 ELSE 0 END) AS fa,
SUM(CASE WHEN status='fa_permanente' THEN 1 ELSE 0 END) AS fa_permanente,
SUM(CASE WHEN status='quarantaine' THEN 1 ELSE 0 END) AS quarantaine,
SUM(CASE WHEN status='soin' THEN 1 ELSE 0 END) AS soin,
SUM(CASE WHEN status='isolation' THEN 1 ELSE 0 END) AS isolation,
SUM(CASE WHEN status='hospitalise' OR status='hospitalisé' THEN 1 ELSE 0 END) AS hospitalise,
SUM(CASE WHEN status='reserve' OR status='réservé' THEN 1 ELSE 0 END) AS reserve,
SUM(CASE WHEN status='adopte' OR status='adopté' THEN 1 ELSE 0 END) AS adopte,
SUM(CASE WHEN status='decede' OR status='décédé' THEN 1 ELSE 0 END) AS dece,
SUM(CASE WHEN status='enfui' THEN 1 ELSE 0 END) AS enfui
FROM animals
WHERE deleted_at IS NULL AND archived_at IS NULL
",
)
->fetch();
$shelterRoomCounts = [];
$configuredRooms = ShelterRoomService::byCode(true);
$roomAnimals = $db
->query(
"SELECT status,refuge_room FROM animals WHERE deleted_at IS NULL AND archived_at IS NULL AND status IN ('refuge','soin','quarantaine')",
)
->fetchAll(PDO::FETCH_ASSOC);
foreach ($configuredRooms as $code => $room) {
$shelterRoomCounts[$code] = [
'name' => $room['name'],
'color' => $room['color'],
'status' => $room['status_code'],
'count' => 0,
];
}
foreach ($roomAnimals as $roomAnimal) {
$code = ShelterRoomService::resolveAnimal($roomAnimal, $configuredRooms);
if (isset($shelterRoomCounts[$code])) {
$shelterRoomCounts[$code]['count']++;
}
}
$quality =
$db
->query(
"SELECT COUNT(*) total,
SUM(CASE WHEN birth_date IS NULL OR trim(birth_date)='' THEN 1 ELSE 0 END) missing_birth_date,
SUM(CASE WHEN sex IS NULL OR trim(sex)='' OR sex='U' THEN 1 ELSE 0 END) missing_sex,
SUM(CASE WHEN color IS NULL OR trim(color)='' THEN 1 ELSE 0 END) missing_color,
SUM(CASE WHEN breed IS NULL OR trim(breed)='' THEN 1 ELSE 0 END) missing_breed,
SUM(CASE WHEN (rescue_location_name IS NULL OR trim(rescue_location_name)='') AND (rescue_address IS NULL OR trim(rescue_address)='') THEN 1 ELSE 0 END) missing_origin,
SUM(CASE WHEN (chip_id IS NULL OR trim(chip_id)='') AND (chip IS NULL OR trim(chip)='') THEN 1 ELSE 0 END) missing_chip,
SUM(CASE WHEN COALESCE(NULLIF(identification_registration_status,''),'unknown')='unknown' THEN 1 ELSE 0 END) unknown_identification_registration,
SUM(CASE WHEN COALESCE(NULLIF(adoption_availability,''),'unknown')='unknown' THEN 1 ELSE 0 END) unknown_adoption_availability,
SUM(CASE WHEN sterilization_status='no' THEN 1 ELSE 0 END) unsterilized,
SUM(CASE WHEN COALESCE(NULLIF(sterilization_status,''),'unknown')='unknown' THEN 1 ELSE 0 END) unknown_sterilization,
SUM(CASE WHEN birth_date IS NULL OR trim(birth_date)='' OR sex IS NULL OR trim(sex)='' OR sex='U' OR color IS NULL OR trim(color)='' OR breed IS NULL OR trim(breed)='' OR ((rescue_location_name IS NULL OR trim(rescue_location_name)='') AND (rescue_address IS NULL OR trim(rescue_address)='')) OR ((chip_id IS NULL OR trim(chip_id)='') AND (chip IS NULL OR trim(chip)='')) OR COALESCE(NULLIF(identification_registration_status,''),'unknown')='unknown' OR COALESCE(NULLIF(adoption_availability,''),'unknown')='unknown' OR COALESCE(NULLIF(sterilization_status,''),'unknown')='unknown' THEN 1 ELSE 0 END) incomplete
FROM animals WHERE deleted_at IS NULL AND archived_at IS NULL AND lower(status) NOT IN ('adopte','adopté','decede','décédé','enfui','fugue')",
)
->fetch(PDO::FETCH_ASSOC) ?:
[];
// Activité
$activity = $db
->query(
"
SELECT
(SELECT COUNT(*) FROM medical_notes mn JOIN animals a ON a.id=mn.animal_id WHERE a.deleted_at IS NULL AND a.archived_at IS NULL AND date(mn.noted_at) >= date('now','-7 day')) AS notes_7d,
(SELECT COUNT(*) FROM vaccinations v JOIN animals a ON a.id=v.animal_id WHERE a.deleted_at IS NULL AND a.archived_at IS NULL AND v.done_date IS NOT NULL AND date(v.done_date) >= date('now','-30 day')) AS vaccines_30d,
(SELECT COUNT(*) FROM treatments t JOIN animals a ON a.id=t.animal_id WHERE a.deleted_at IS NULL AND a.archived_at IS NULL AND t.start_date IS NOT NULL AND date(t.start_date) >= date('now','-30 day')) AS treatments_30d
",
)
->fetch();
// Vaccins en retard
$stmt = $db->prepare("
SELECT a.id, a.name, a.internal_code AS code, rv.name AS vaccine_name, v.due_date
FROM vaccinations v
JOIN animals a ON a.id = v.animal_id
JOIN ref_vaccines rv ON rv.id = v.vaccine_id
WHERE v.due_date IS NOT NULL
AND a.deleted_at IS NULL AND a.archived_at IS NULL
AND lower(a.status) NOT IN ('decede','décédé')
AND date(v.due_date) < date('now')
ORDER BY date(v.due_date) ASC
LIMIT 50
");
$stmt->execute();
$vaccines_overdue = $stmt->fetchAll();
// Vaccins ≤ 7 jours
$stmt = $db->prepare("
SELECT a.id, a.name, a.internal_code AS code, rv.name AS vaccine_name, v.due_date
FROM vaccinations v
JOIN animals a ON a.id = v.animal_id
JOIN ref_vaccines rv ON rv.id = v.vaccine_id
WHERE v.due_date IS NOT NULL
AND a.deleted_at IS NULL AND a.archived_at IS NULL
AND lower(a.status) NOT IN ('decede','décédé')
AND date(v.due_date) >= date('now')
AND date(v.due_date) <= date('now', '+7 day')
ORDER BY date(v.due_date) ASC
LIMIT 50
");
$stmt->execute();
$vaccines_7d = $stmt->fetchAll();
// Vaccins ≤ 30 jours
$stmt = $db->prepare("
SELECT a.id, a.name, a.internal_code AS code, rv.name AS vaccine_name, v.due_date
FROM vaccinations v
JOIN animals a ON a.id = v.animal_id
JOIN ref_vaccines rv ON rv.id = v.vaccine_id
WHERE v.due_date IS NOT NULL
AND a.deleted_at IS NULL AND a.archived_at IS NULL
AND lower(a.status) NOT IN ('decede','décédé')
AND date(v.due_date) > date('now', '+7 day')
AND date(v.due_date) <= date('now', '+30 day')
ORDER BY date(v.due_date) ASC
LIMIT 50
");
$stmt->execute();
$vaccines_30d = $stmt->fetchAll();
// Traitements en cours
$stmt = $db->prepare('
SELECT a.id, a.name, a.internal_code AS code,
m.name AS medication_name,
t.start_date, t.end_date, t.dose_text, t.route
FROM treatments t
JOIN animals a ON a.id = t.animal_id
JOIN ref_medications m ON m.id = t.medication_id
WHERE t.ongoing = 1
AND a.deleted_at IS NULL AND a.archived_at IS NULL
ORDER BY date(t.start_date) DESC, a.name ASC
LIMIT 50
');
$stmt->execute();
$treatments = $stmt->fetchAll();
// Traitements à clôturer (fin dépassée mais ongoing=1)
$stmt = $db->prepare("
SELECT a.id, a.name, a.internal_code AS code, m.name AS medication_name, t.end_date
FROM treatments t
JOIN animals a ON a.id = t.animal_id
JOIN ref_medications m ON m.id = t.medication_id
WHERE t.ongoing = 1
AND a.deleted_at IS NULL AND a.archived_at IS NULL
AND t.end_date IS NOT NULL
AND date(t.end_date) < date('now')
ORDER BY date(t.end_date) ASC
LIMIT 50
");
$stmt->execute();
$treatments_overdue = $stmt->fetchAll();
// Dernières notes médicales
$stmt = $db->prepare('
SELECT mn.noted_at, mn.kind, mn.reason,
a.id AS animal_id, a.name, a.internal_code AS code
FROM medical_notes mn
JOIN animals a ON a.id = mn.animal_id
WHERE a.deleted_at IS NULL AND a.archived_at IS NULL
ORDER BY datetime(mn.noted_at) DESC, mn.id DESC
LIMIT 30
');
$stmt->execute();
$recent_notes = $stmt->fetchAll();
// Quarantaine : liste + J-... (si dates renseignées)
$stmt = $db->prepare("
SELECT
id, name, internal_code AS code,
intake_date,
quarantine_until,
CASE
WHEN quarantine_until IS NULL OR quarantine_until = '' THEN NULL
ELSE CAST(julianday(date(quarantine_until)) - julianday(date('now','localtime')) AS INT)
END AS days_left
FROM animals
WHERE lower(trim(status))='quarantaine'
AND deleted_at IS NULL AND archived_at IS NULL
ORDER BY
CASE
WHEN quarantine_until IS NULL OR quarantine_until = '' THEN 9999
ELSE days_left
END ASC,
name ASC
LIMIT 100
");
$stmt->execute();
$quarantine = $stmt->fetchAll();
$stmt = $db->prepare('
SELECT
a.id, a.name, a.internal_code AS code,
wa.last_weight, wa.w30_weight, wa.w30_date,
ROUND(((wa.last_weight - wa.w30_weight) / wa.w30_weight) * 100, 1) AS pct
FROM v_animals_weight_alerts wa
JOIN animals a ON a.id = wa.animal_id
WHERE wa.last_weight IS NOT NULL
AND a.deleted_at IS NULL AND a.archived_at IS NULL
AND wa.w30_weight IS NOT NULL
AND wa.w30_weight > 0
AND ABS((wa.last_weight - wa.w30_weight) / wa.w30_weight) >= 0.15
ORDER BY ABS((wa.last_weight - wa.w30_weight) / wa.w30_weight) DESC
LIMIT 50
');
$stmt->execute();
$weight_alerts = $stmt->fetchAll();
$careToday = [];
$rows = $db
->query(
"SELECT periods.period,
(SELECT COUNT(*) FROM animals a WHERE a.deleted_at IS NULL AND a.archived_at IS NULL AND lower(a.status) IN ('refuge','soin','quarantaine')) expected,
COUNT(o.id) checked,
SUM(CASE WHEN o.alert_signature IS NOT NULL THEN 1 ELSE 0 END) alerts
FROM (SELECT 'morning' period UNION ALL SELECT 'evening') periods
LEFT JOIN care_rounds r ON r.round_date=date('now','localtime') AND r.period=periods.period
LEFT JOIN care_round_observations o ON o.round_id=r.id
GROUP BY periods.period ORDER BY periods.period DESC",
)
->fetchAll();
foreach ($rows as $row) {
$careToday[$row['period']] = $row;
}
$stmt = $db->query(
"SELECT a.id,a.name,r.period,o.comment FROM care_round_observations o JOIN care_rounds r ON r.id=o.round_id JOIN animals a ON a.id=o.animal_id WHERE r.round_date=date('now','localtime') AND o.alert_signature IS NOT NULL AND a.deleted_at IS NULL ORDER BY o.checked_at DESC LIMIT 10",
);
$careAlertsToday = $stmt->fetchAll();
$grantAlerts = PermissionService::can('grants') ? GrantService::alerts((int) date('Y')) : [];
$agendaDue = PermissionService::can('agenda') ? AgendaService::due() : [];
render('dashboard.php', [
'title' => 'Dashboard',
'stats' => $stats,
'shelterRoomCounts' => $shelterRoomCounts,
'quality' => $quality,
'activity' => $activity,
'vaccines_overdue' => $vaccines_overdue,
'vaccines_7d' => $vaccines_7d,
'vaccines_30d' => $vaccines_30d,
'treatments' => $treatments,
'treatments_overdue' => $treatments_overdue,
'recent_notes' => $recent_notes,
'quarantine' => $quarantine,
'weight_alerts' => $weight_alerts,
'careToday' => $careToday,
'careAlertsToday' => $careAlertsToday,
'grantAlerts' => $grantAlerts,
'agendaDue' => $agendaDue,
]);
}
}