deadline feature
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
/**
|
||||
* Scadenzario — Email notification cron script
|
||||
* Run daily: 0 7 * * * php /var/www/html/public/userarea/scadenzario/cron/send_notifications.php
|
||||
*
|
||||
* Sends "approaching" emails N days before due_date (per-deadline notification_days).
|
||||
* Sends "overdue" emails when due_date has passed.
|
||||
* Skips completed deadlines and already-sent notifications (same deadline+employee+type+date).
|
||||
* Email is taken from employees.auth_user_id → auth_users.email.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../class/db-functions.php';
|
||||
require_once __DIR__ . '/../../../../vendor/autoload.php';
|
||||
|
||||
use Dotenv\Dotenv;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\Exception;
|
||||
|
||||
$dotenv = Dotenv::createImmutable(__DIR__ . '/../../../../');
|
||||
$dotenv->load();
|
||||
|
||||
$db = DBHandlerSelect::getInstance();
|
||||
$pdo = $db->getConnection();
|
||||
|
||||
$today = date('Y-m-d');
|
||||
$appUrl = rtrim($_ENV['APP_URL'] ?? 'http://localhost:8001', '/');
|
||||
|
||||
$sent = 0;
|
||||
$skipped = 0;
|
||||
$errors = 0;
|
||||
|
||||
// Get active deadlines that are approaching or overdue
|
||||
$stmt = $pdo->query("
|
||||
SELECT d.id, d.topic, d.category, d.due_date, d.notification_days
|
||||
FROM scad_deadlines d
|
||||
WHERE d.status = 'active'
|
||||
AND d.due_date <= DATE_ADD(CURDATE(), INTERVAL d.notification_days DAY)
|
||||
");
|
||||
$deadlines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (empty($deadlines)) {
|
||||
echo date('Y-m-d H:i:s') . " — Nessuna scadenza da notificare.\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Prepare statements
|
||||
$getRecipients = $pdo->prepare("
|
||||
SELECT DISTINCT e.id as employee_id, au.email, e.first_name, e.last_name
|
||||
FROM scad_deadline_employee de
|
||||
JOIN employees e ON e.id = de.employee_id
|
||||
JOIN auth_users au ON au.id = e.auth_user_id
|
||||
WHERE de.deadline_id = ?
|
||||
AND e.auth_user_id IS NOT NULL
|
||||
AND au.email IS NOT NULL
|
||||
AND au.email != ''
|
||||
");
|
||||
|
||||
// Also get employees from assigned departments
|
||||
$getDeptRecipients = $pdo->prepare("
|
||||
SELECT DISTINCT e.id as employee_id, au.email, e.first_name, e.last_name
|
||||
FROM employees e
|
||||
JOIN auth_users au ON au.id = e.auth_user_id
|
||||
WHERE e.department IN (SELECT TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(d.departments, ',', n.n), ',', -1))
|
||||
FROM scad_deadlines d
|
||||
CROSS JOIN (SELECT 1 n UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) n
|
||||
WHERE d.id = ?
|
||||
AND d.departments IS NOT NULL
|
||||
AND n.n <= 1 + LENGTH(d.departments) - LENGTH(REPLACE(d.departments, ',', '')))
|
||||
AND e.auth_user_id IS NOT NULL
|
||||
AND au.email IS NOT NULL
|
||||
AND au.email != ''
|
||||
");
|
||||
|
||||
$checkSent = $pdo->prepare("
|
||||
SELECT COUNT(*) FROM scad_deadline_notifications
|
||||
WHERE deadline_id = ? AND employee_id = ? AND type = ? AND DATE(sent_at) = CURDATE()
|
||||
");
|
||||
|
||||
$insertNotif = $pdo->prepare("
|
||||
INSERT INTO scad_deadline_notifications (deadline_id, employee_id, type) VALUES (?, ?, ?)
|
||||
");
|
||||
|
||||
$insertHistory = $pdo->prepare("
|
||||
INSERT INTO scad_deadline_histories (deadline_id, user_id, action, notes) VALUES (?, NULL, 'notification_sent', ?)
|
||||
");
|
||||
|
||||
foreach ($deadlines as $dl) {
|
||||
$isOverdue = $dl['due_date'] < $today;
|
||||
$type = $isOverdue ? 'overdue' : 'approaching';
|
||||
$daysLeft = (int)((strtotime($dl['due_date']) - strtotime($today)) / 86400);
|
||||
|
||||
// Collect all recipients (direct + department)
|
||||
$recipients = [];
|
||||
|
||||
$getRecipients->execute([$dl['id']]);
|
||||
foreach ($getRecipients->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
||||
$recipients[$r['employee_id']] = $r;
|
||||
}
|
||||
|
||||
$getDeptRecipients->execute([$dl['id']]);
|
||||
foreach ($getDeptRecipients->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
||||
$recipients[$r['employee_id']] = $r;
|
||||
}
|
||||
|
||||
if (empty($recipients)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($recipients as $emp) {
|
||||
// Check if already sent today
|
||||
$checkSent->execute([$dl['id'], $emp['employee_id'], $type]);
|
||||
if ($checkSent->fetchColumn() > 0) {
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Send email
|
||||
try {
|
||||
$mail = new PHPMailer(true);
|
||||
|
||||
// SMTP config from .env
|
||||
$mailer = $_ENV['MAIL_MAILER'] ?? 'mail';
|
||||
if ($mailer === 'smtp') {
|
||||
$mail->isSMTP();
|
||||
$mail->Host = $_ENV['MAIL_HOST'] ?? 'localhost';
|
||||
$mail->Port = (int)($_ENV['MAIL_PORT'] ?? 587);
|
||||
if (!empty($_ENV['MAIL_USERNAME']) && $_ENV['MAIL_USERNAME'] !== 'null') {
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Username = $_ENV['MAIL_USERNAME'];
|
||||
$mail->Password = $_ENV['MAIL_PASSWORD'] ?? '';
|
||||
}
|
||||
$enc = $_ENV['MAIL_ENCRYPTION'] ?? '';
|
||||
if ($enc && $enc !== 'null') {
|
||||
$mail->SMTPSecure = $enc;
|
||||
}
|
||||
}
|
||||
|
||||
$mail->CharSet = 'UTF-8';
|
||||
$mail->setFrom(
|
||||
$_ENV['MAIL_FROM_ADDRESS'] ?? 'noreply@zibogomma.it',
|
||||
$_ENV['MAIL_FROM_NAME'] ?? 'Scadenzario ZIBOGOMMA'
|
||||
);
|
||||
$mail->addAddress($emp['email'], trim($emp['first_name'] . ' ' . $emp['last_name']));
|
||||
|
||||
$detailUrl = $appUrl . '/userarea/scadenzario/detail.php?id=' . $dl['id'];
|
||||
$topicText = ($dl['category'] ? $dl['category'] . ' — ' : '') . $dl['topic'];
|
||||
|
||||
if ($isOverdue) {
|
||||
$mail->Subject = '⚠️ Scadenza superata: ' . $dl['topic'];
|
||||
$mail->Body = buildHtml(
|
||||
'Scadenza superata',
|
||||
$topicText,
|
||||
'La scadenza era prevista per il <strong>' . date('d/m/Y', strtotime($dl['due_date'])) . '</strong> ed è stata superata da <strong>' . abs($daysLeft) . ' giorni</strong>.',
|
||||
'#dc3545',
|
||||
$detailUrl
|
||||
);
|
||||
} else {
|
||||
$mail->Subject = '📅 Scadenza in arrivo: ' . $dl['topic'];
|
||||
$daysText = $daysLeft === 0 ? 'oggi' : 'tra <strong>' . $daysLeft . ' giorni</strong>';
|
||||
$mail->Body = buildHtml(
|
||||
'Scadenza in arrivo',
|
||||
$topicText,
|
||||
'La scadenza è prevista per il <strong>' . date('d/m/Y', strtotime($dl['due_date'])) . '</strong> (' . $daysText . ').',
|
||||
'#e8930c',
|
||||
$detailUrl
|
||||
);
|
||||
}
|
||||
|
||||
$mail->isHTML(true);
|
||||
$mail->AltBody = strip_tags(str_replace('<br>', "\n", $mail->Body));
|
||||
|
||||
$mail->send();
|
||||
|
||||
// Record notification
|
||||
$insertNotif->execute([$dl['id'], $emp['employee_id'], $type]);
|
||||
$sent++;
|
||||
|
||||
echo date('H:i:s') . " ✓ {$type} → {$emp['email']} — {$dl['topic']}\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
$errors++;
|
||||
echo date('H:i:s') . " ✗ Errore {$emp['email']}: {$e->getMessage()}\n";
|
||||
}
|
||||
}
|
||||
|
||||
// History (one per deadline, not per recipient)
|
||||
$recipientNames = implode(', ', array_map(fn($r) => trim($r['first_name'] . ' ' . $r['last_name']), $recipients));
|
||||
$insertHistory->execute([$dl['id'], "Notifica {$type} inviata a: {$recipientNames}"]);
|
||||
}
|
||||
|
||||
echo "\n" . date('Y-m-d H:i:s') . " — Completato. Inviate: {$sent}, Saltate: {$skipped}, Errori: {$errors}\n";
|
||||
|
||||
// --- HTML email template ---
|
||||
function buildHtml(string $title, string $topic, string $message, string $accentColor, string $url): string
|
||||
{
|
||||
return '
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><meta charset="UTF-8"></head>
|
||||
<body style="margin:0;padding:0;background:#f4f6f9;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="padding:30px 0">
|
||||
<tr><td align="center">
|
||||
<table width="560" cellpadding="0" cellspacing="0" style="background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.06)">
|
||||
<tr><td style="background:' . $accentColor . ';padding:20px 30px">
|
||||
<h1 style="margin:0;color:#fff;font-size:18px">' . htmlspecialchars($title) . '</h1>
|
||||
</td></tr>
|
||||
<tr><td style="padding:30px">
|
||||
<h2 style="margin:0 0 15px;color:#2c3e6b;font-size:16px">' . htmlspecialchars($topic) . '</h2>
|
||||
<p style="margin:0 0 20px;color:#444;font-size:14px;line-height:1.6">' . $message . '</p>
|
||||
<a href="' . htmlspecialchars($url) . '" style="display:inline-block;background:#5a8fd8;color:#fff;padding:10px 24px;border-radius:6px;text-decoration:none;font-weight:600;font-size:14px">Vai alla scadenza</a>
|
||||
</td></tr>
|
||||
<tr><td style="padding:15px 30px;background:#f8f9fb;border-top:1px solid #eee">
|
||||
<p style="margin:0;color:#999;font-size:11px">ZIBOGOMMA — Scadenzario</p>
|
||||
</td></tr>
|
||||
</table>
|
||||
</td></tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>';
|
||||
}
|
||||
Reference in New Issue
Block a user