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,142 @@
<?php
declare(strict_types=1);
final class NotificationService
{
public static function alerts(): array
{
$db = DB::pdo();
$alerts = [];
$queries = [
[
'vaccines',
'danger',
t('notification.vaccines_overdue'),
"SELECT COUNT(*) FROM vaccinations v JOIN animals a ON a.id=v.animal_id WHERE v.due_date IS NOT NULL AND date(v.due_date)<date('now') AND a.deleted_at IS NULL AND a.archived_at IS NULL AND lower(a.status) NOT IN ('decede','décédé','adopte','adopté')",
'/dashboard#vaccine-deadlines',
],
[
'treatments',
'danger',
t('notification.treatments_overdue'),
"SELECT COUNT(*) FROM treatments t JOIN animals a ON a.id=t.animal_id WHERE t.ongoing=1 AND t.end_date IS NOT NULL AND date(t.end_date)<date('now') AND a.deleted_at IS NULL AND a.archived_at IS NULL",
'/dashboard#active-treatments',
],
[
'dewormings',
'warning',
t('notification.dewormings_due'),
"SELECT COUNT(*) FROM dewormings d JOIN animals a ON a.id=d.animal_id WHERE d.next_due_date IS NOT NULL AND date(d.next_due_date)<=date('now','+7 day') AND a.deleted_at IS NULL AND a.archived_at IS NULL AND NOT EXISTS(SELECT 1 FROM dewormings newer WHERE newer.animal_id=d.animal_id AND date(newer.administered_on)>date(d.administered_on))",
'/dashboard',
],
];
foreach ($queries as [$key, $severity, $label, $sql, $url]) {
try {
$count = (int) $db->query($sql)->fetchColumn();
if ($count) {
$alerts[] = compact('key', 'severity', 'label', 'count', 'url');
}
} catch (Throwable) {
}
}
if (class_exists(InventoryService::class)) {
try {
$stock = InventoryService::alerts();
if ($stock['low']) {
$alerts[] = [
'key' => 'inventory-low',
'severity' => 'warning',
'label' => t('notification.inventory_low'),
'count' => count($stock['low']),
'url' => '/settings/inventory',
];
}
if ($stock['expired']) {
$alerts[] = [
'key' => 'inventory-expiry',
'severity' => 'danger',
'label' => t('notification.inventory_expiry'),
'count' => count($stock['expired']),
'url' => '/settings/inventory',
];
}
} catch (Throwable) {
}
}
if (class_exists(AgendaService::class)) {
try {
$due = AgendaService::due();
if ($due) {
$alerts[] = [
'key' => 'agenda',
'severity' => 'warning',
'label' => t('notification.agenda_due'),
'count' => count($due),
'url' => '/agenda',
];
}
} catch (Throwable) {
}
}
$backups = BackupService::list();
$days = 999;
if (
$backups &&
($date = DateTimeImmutable::createFromFormat(
'd/m/Y H:i:s',
(string) $backups[0]['date'],
new DateTimeZone('Europe/Paris'),
))
) {
$days = (int) $date->diff(new DateTimeImmutable('now', new DateTimeZone('Europe/Paris')))->format('%a');
}
if ($days > 7) {
$alerts[] = [
'key' => 'backup',
'severity' => $days > 30 ? 'danger' : 'warning',
'label' => t('notification.backup_old'),
'count' => $days,
'url' => '/settings/backups',
];
}
return $alerts;
}
public static function send(bool $force = false): array
{
if (AppSettings::get('notification_email_enabled') !== '1' && !$force) {
return ['sent' => false, 'reason' => 'disabled'];
}
$recipient = AppSettings::get('notification_email_recipient') ?: AppSettings::get('association_email');
if (!filter_var($recipient, FILTER_VALIDATE_EMAIL)) {
return ['sent' => false, 'reason' => 'recipient'];
}
$frequency = AppSettings::get('notification_email_frequency');
$last = AppSettings::get('notification_last_sent_at');
if (!$force && $last !== '') {
$elapsed = time() - (strtotime($last) ?: 0);
$minimum = $frequency === 'daily' ? 20 * 3600 : 6 * 86400;
if ($elapsed < $minimum) {
return ['sent' => false, 'reason' => 'not_due'];
}
}
$alerts = self::alerts();
$association = AppSettings::get('association_name') ?: 'Globinours';
$subject = '[' . $association . '] ' . t('notification.email_subject');
$lines = [t('notification.email_intro', ['association' => $association]), ''];
if (!$alerts) {
$lines[] = t('notification.no_alert');
} else {
foreach ($alerts as $a) {
$lines[] = '- ' . $a['label'] . ' : ' . $a['count'];
}
}
$lines[] = '';
$lines[] = t('notification.email_footer');
$sent = @mail($recipient, $subject, implode("\n", $lines), 'Content-Type: text/plain; charset=UTF-8');
if ($sent) {
AppSettings::save(['notification_last_sent_at' => date('Y-m-d H:i:s')], Auth::id());
}
return ['sent' => $sent, 'reason' => $sent ? 'sent' : 'mail_failed', 'count' => count($alerts)];
}
}