added functions email and flag
This commit is contained in:
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class AddNotifyFunctionToScadDeadlines extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
if (!$this->hasTable('scad_deadlines')) {
|
||||||
|
throw new RuntimeException('Table scad_deadlines does not exist.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = $this->table('scad_deadlines');
|
||||||
|
|
||||||
|
if (!$table->hasColumn('notify_function')) {
|
||||||
|
$table
|
||||||
|
->addColumn('notify_function', 'boolean', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => false,
|
||||||
|
'after' => 'function_id',
|
||||||
|
'comment' => 'Send deadline reminder also to the linked function email',
|
||||||
|
])
|
||||||
|
->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
if (!$this->hasTable('scad_deadlines')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = $this->table('scad_deadlines');
|
||||||
|
|
||||||
|
if ($table->hasColumn('notify_function')) {
|
||||||
|
$table
|
||||||
|
->removeColumn('notify_function')
|
||||||
|
->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ try {
|
|||||||
$id = isset($_POST['id']) && is_numeric($_POST['id']) ? (int)$_POST['id'] : null;
|
$id = isset($_POST['id']) && is_numeric($_POST['id']) ? (int)$_POST['id'] : null;
|
||||||
$subject_id = isset($_POST['subject_id']) && is_numeric($_POST['subject_id']) && (int)$_POST['subject_id'] > 0 ? (int)$_POST['subject_id'] : null;
|
$subject_id = isset($_POST['subject_id']) && is_numeric($_POST['subject_id']) && (int)$_POST['subject_id'] > 0 ? (int)$_POST['subject_id'] : null;
|
||||||
$function_id = isset($_POST['function_id']) && is_numeric($_POST['function_id']) && (int)$_POST['function_id'] > 0 ? (int)$_POST['function_id'] : null;
|
$function_id = isset($_POST['function_id']) && is_numeric($_POST['function_id']) && (int)$_POST['function_id'] > 0 ? (int)$_POST['function_id'] : null;
|
||||||
|
$notify_function = isset($_POST['notify_function']) && (int)$_POST['notify_function'] === 1 ? 1 : 0;
|
||||||
$topic = trim($_POST['topic'] ?? '');
|
$topic = trim($_POST['topic'] ?? '');
|
||||||
$law_regulation = trim($_POST['law_regulation'] ?? '') ?: null;
|
$law_regulation = trim($_POST['law_regulation'] ?? '') ?: null;
|
||||||
$recurrence_type = $_POST['recurrence_type'] ?? 'once';
|
$recurrence_type = $_POST['recurrence_type'] ?? 'once';
|
||||||
@@ -53,7 +54,7 @@ try {
|
|||||||
if ($id) {
|
if ($id) {
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
UPDATE scad_deadlines SET
|
UPDATE scad_deadlines SET
|
||||||
subject_id = ?, function_id = ?, topic = ?, law_regulation = ?, recurrence_type = ?,
|
subject_id = ?, function_id = ?, notify_function = ?, topic = ?, law_regulation = ?, recurrence_type = ?,
|
||||||
due_date = ?, check_date = ?, document_date = ?, notification_days = ?,
|
due_date = ?, check_date = ?, document_date = ?, notification_days = ?,
|
||||||
storage_location = ?, notes = ?, departments = ?
|
storage_location = ?, notes = ?, departments = ?
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
@@ -61,6 +62,7 @@ try {
|
|||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
$subject_id,
|
$subject_id,
|
||||||
$function_id,
|
$function_id,
|
||||||
|
$notify_function,
|
||||||
$topic,
|
$topic,
|
||||||
$law_regulation,
|
$law_regulation,
|
||||||
$recurrence_type,
|
$recurrence_type,
|
||||||
@@ -86,13 +88,14 @@ try {
|
|||||||
// INSERT
|
// INSERT
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
INSERT INTO scad_deadlines
|
INSERT INTO scad_deadlines
|
||||||
(subject_id, function_id, topic, law_regulation, recurrence_type, due_date, check_date,
|
(subject_id, function_id, notify_function, topic, law_regulation, recurrence_type, due_date, check_date,
|
||||||
document_date, notification_days, storage_location, notes, created_by, departments)
|
document_date, notification_days, storage_location, notes, created_by, departments)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
");
|
");
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
$subject_id,
|
$subject_id,
|
||||||
$function_id,
|
$function_id,
|
||||||
|
$notify_function,
|
||||||
$topic,
|
$topic,
|
||||||
$law_regulation,
|
$law_regulation,
|
||||||
$recurrence_type,
|
$recurrence_type,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scadenzario — Email notification cron script
|
* Scadenzario — Email notification cron script
|
||||||
* Run daily: 0 7 * * * php /var/www/html/public/userarea/scadenzario/cron/send_notifications.php
|
* Run daily: 0 7 * * * php /var/www/html/public/userarea/scadenzario/cron/send_notifications.php
|
||||||
@@ -42,9 +43,19 @@ $errors = 0;
|
|||||||
|
|
||||||
// Get active deadlines that are approaching or overdue
|
// Get active deadlines that are approaching or overdue
|
||||||
$stmt = $pdo->query("
|
$stmt = $pdo->query("
|
||||||
SELECT d.id, d.topic, s.name AS subject_name, d.due_date, d.notification_days
|
SELECT
|
||||||
|
d.id,
|
||||||
|
d.topic,
|
||||||
|
s.name AS subject_name,
|
||||||
|
d.due_date,
|
||||||
|
d.notification_days,
|
||||||
|
d.notify_function,
|
||||||
|
f.email AS function_email,
|
||||||
|
f.person_full_name AS function_person,
|
||||||
|
f.name AS function_name
|
||||||
FROM scad_deadlines d
|
FROM scad_deadlines d
|
||||||
LEFT JOIN scad_subjects s ON s.id = d.subject_id
|
LEFT JOIN scad_subjects s ON s.id = d.subject_id
|
||||||
|
LEFT JOIN scad_functions f ON f.id = d.function_id
|
||||||
WHERE d.status = 'active'
|
WHERE d.status = 'active'
|
||||||
AND d.due_date <= DATE_ADD(CURDATE(), INTERVAL d.notification_days DAY)
|
AND d.due_date <= DATE_ADD(CURDATE(), INTERVAL d.notification_days DAY)
|
||||||
");
|
");
|
||||||
@@ -101,20 +112,28 @@ foreach ($deadlines as $dl) {
|
|||||||
$type = $isOverdue ? 'overdue' : 'approaching';
|
$type = $isOverdue ? 'overdue' : 'approaching';
|
||||||
$daysLeft = (int)((strtotime($dl['due_date']) - strtotime($today)) / 86400);
|
$daysLeft = (int)((strtotime($dl['due_date']) - strtotime($today)) / 86400);
|
||||||
|
|
||||||
// Collect all recipients (direct + department)
|
// Collect all recipients (direct + department + optional function email)
|
||||||
$recipients = [];
|
$recipients = [];
|
||||||
|
$functionRecipient = null;
|
||||||
|
|
||||||
$getRecipients->execute([$dl['id']]);
|
$getRecipients->execute([$dl['id']]);
|
||||||
foreach ($getRecipients->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
foreach ($getRecipients->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
||||||
$recipients[$r['employee_id']] = $r;
|
$recipients[$r['employee_id']] = $r;
|
||||||
}
|
}
|
||||||
|
|
||||||
$getDeptRecipients->execute([$dl['id']]);
|
// Optional: also notify the linked function email if enabled on the deadline.
|
||||||
foreach ($getDeptRecipients->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
if (
|
||||||
$recipients[$r['employee_id']] = $r;
|
!empty($dl['notify_function'])
|
||||||
|
&& !empty($dl['function_email'])
|
||||||
|
&& filter_var($dl['function_email'], FILTER_VALIDATE_EMAIL)
|
||||||
|
) {
|
||||||
|
$functionRecipient = [
|
||||||
|
'email' => $dl['function_email'],
|
||||||
|
'name' => trim(($dl['function_person'] ?? '') !== '' ? $dl['function_person'] : ($dl['function_name'] ?? 'Funzione')),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($recipients)) {
|
if (empty($recipients) && empty($functionRecipient)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,15 +212,99 @@ foreach ($deadlines as $dl) {
|
|||||||
$sent++;
|
$sent++;
|
||||||
|
|
||||||
echo date('H:i:s') . " ✓ {$type} → {$emp['email']} — {$dl['topic']}\n";
|
echo date('H:i:s') . " ✓ {$type} → {$emp['email']} — {$dl['topic']}\n";
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$errors++;
|
$errors++;
|
||||||
echo date('H:i:s') . " ✗ Errore {$emp['email']}: {$e->getMessage()}\n";
|
echo date('H:i:s') . " ✗ Errore {$emp['email']}: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Send notification to function email if enabled.
|
||||||
|
// It is tracked with employee_id = 0 to avoid duplicate daily sends.
|
||||||
|
if ($functionRecipient) {
|
||||||
|
$functionEmployeeId = 0;
|
||||||
|
|
||||||
|
$checkSent->execute([$dl['id'], $functionEmployeeId, $type]);
|
||||||
|
if ($checkSent->fetchColumn() > 0) {
|
||||||
|
$skipped++;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
$mail = new PHPMailer(true);
|
||||||
|
|
||||||
|
$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($functionRecipient['email'], $functionRecipient['name']);
|
||||||
|
|
||||||
|
if ($managerCcEmail && strcasecmp($managerCcEmail, $functionRecipient['email']) !== 0) {
|
||||||
|
$mail->addCC($managerCcEmail);
|
||||||
|
}
|
||||||
|
|
||||||
|
$detailUrl = $appUrl . '/userarea/scadenzario/detail.php?id=' . $dl['id'];
|
||||||
|
$topicText = (!empty($dl['subject_name']) ? $dl['subject_name'] . ' — ' : '') . $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();
|
||||||
|
|
||||||
|
$insertNotif->execute([$dl['id'], $functionEmployeeId, $type]);
|
||||||
|
$sent++;
|
||||||
|
|
||||||
|
echo date('H:i:s') . " ✓ {$type} → funzione {$functionRecipient['email']} — {$dl['topic']}\n";
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$errors++;
|
||||||
|
echo date('H:i:s') . " ✗ Errore funzione {$functionRecipient['email']}: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// History (one per deadline, not per recipient)
|
// History (one per deadline, not per recipient)
|
||||||
$recipientNames = implode(', ', array_map(fn($r) => trim($r['first_name'] . ' ' . $r['last_name']), $recipients));
|
$recipientNames = implode(', ', array_map(fn($r) => trim($r['first_name'] . ' ' . $r['last_name']), $recipients));
|
||||||
|
|
||||||
|
if ($functionRecipient) {
|
||||||
|
$recipientNames .= ($recipientNames !== '' ? ', ' : '') . 'Funzione: ' . $functionRecipient['name'] . ' <' . $functionRecipient['email'] . '>';
|
||||||
|
}
|
||||||
|
|
||||||
$insertHistory->execute([$dl['id'], "Notifica {$type} inviata a: {$recipientNames}"]);
|
$insertHistory->execute([$dl['id'], "Notifica {$type} inviata a: {$recipientNames}"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,15 @@
|
|||||||
<i class="fa-solid fa-gear"></i>
|
<i class="fa-solid fa-gear"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-check mt-2">
|
||||||
|
<input class="form-check-input" type="checkbox" id="notify_function" name="notify_function" value="1">
|
||||||
|
<label class="form-check-label" for="notify_function">
|
||||||
|
Invia promemoria anche alla funzione selezionata
|
||||||
|
</label>
|
||||||
|
<div class="form-text">
|
||||||
|
Se attivo, la mail giornaliera verrà inviata anche all’email collegata alla funzione.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<label for="dlLaw" class="form-label fw-semibold">Legge / Articolo</label>
|
<label for="dlLaw" class="form-label fw-semibold">Legge / Articolo</label>
|
||||||
|
|||||||
@@ -36,23 +36,51 @@
|
|||||||
language: 'it',
|
language: 'it',
|
||||||
width: '100%'
|
width: '100%'
|
||||||
};
|
};
|
||||||
$('#dlSubject').select2($.extend({}, s2Opts, { placeholder: 'Seleziona argomento...' }));
|
$('#dlSubject').select2($.extend({}, s2Opts, {
|
||||||
$('#dlDepartments').select2($.extend({}, s2Opts, { placeholder: 'Seleziona reparti...' }));
|
placeholder: 'Seleziona argomento...'
|
||||||
$('#dlEmployees').select2($.extend({}, s2Opts, { placeholder: 'Seleziona persone...' }));
|
}));
|
||||||
$('#dlFunction').select2($.extend({}, s2Opts, { placeholder: 'Seleziona funzione...' }));
|
$('#dlDepartments').select2($.extend({}, s2Opts, {
|
||||||
|
placeholder: 'Seleziona reparti...'
|
||||||
|
}));
|
||||||
|
$('#dlEmployees').select2($.extend({}, s2Opts, {
|
||||||
|
placeholder: 'Seleziona persone...'
|
||||||
|
}));
|
||||||
|
$('#dlFunction').select2($.extend({}, s2Opts, {
|
||||||
|
placeholder: 'Seleziona funzione...'
|
||||||
|
}));
|
||||||
|
|
||||||
// --- Auto-calc due_date from document_date + recurrence ---
|
// --- Auto-calc due_date from document_date + recurrence ---
|
||||||
var RECURRENCE_OFFSETS = {
|
var RECURRENCE_OFFSETS = {
|
||||||
monthly: { months: 1 },
|
monthly: {
|
||||||
quarterly: { months: 3 },
|
months: 1
|
||||||
semiannual: { months: 6 },
|
},
|
||||||
annual: { years: 1 },
|
quarterly: {
|
||||||
biennial: { years: 2 },
|
months: 3
|
||||||
triennial: { years: 3 },
|
},
|
||||||
quadriennial: { years: 4 },
|
semiannual: {
|
||||||
quinquennial: { years: 5 },
|
months: 6
|
||||||
decennial: { years: 10 },
|
},
|
||||||
quindecennial: { years: 15 }
|
annual: {
|
||||||
|
years: 1
|
||||||
|
},
|
||||||
|
biennial: {
|
||||||
|
years: 2
|
||||||
|
},
|
||||||
|
triennial: {
|
||||||
|
years: 3
|
||||||
|
},
|
||||||
|
quadriennial: {
|
||||||
|
years: 4
|
||||||
|
},
|
||||||
|
quinquennial: {
|
||||||
|
years: 5
|
||||||
|
},
|
||||||
|
decennial: {
|
||||||
|
years: 10
|
||||||
|
},
|
||||||
|
quindecennial: {
|
||||||
|
years: 15
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function computeDueDate() {
|
function computeDueDate() {
|
||||||
@@ -106,6 +134,7 @@
|
|||||||
$('#dlDepartments').val(null).trigger('change');
|
$('#dlDepartments').val(null).trigger('change');
|
||||||
$('#dlEmployees').val(null).trigger('change');
|
$('#dlEmployees').val(null).trigger('change');
|
||||||
$('#dlFunction').val('').trigger('change');
|
$('#dlFunction').val('').trigger('change');
|
||||||
|
$('#notify_function').prop('checked', false);
|
||||||
renderAttachments([]);
|
renderAttachments([]);
|
||||||
modal.show();
|
modal.show();
|
||||||
};
|
};
|
||||||
@@ -126,6 +155,7 @@
|
|||||||
$('#dlSubject').val(d.subject_id || '').trigger('change');
|
$('#dlSubject').val(d.subject_id || '').trigger('change');
|
||||||
document.getElementById('dlTopic').value = d.topic || '';
|
document.getElementById('dlTopic').value = d.topic || '';
|
||||||
$('#dlFunction').val(d.function_id || '').trigger('change');
|
$('#dlFunction').val(d.function_id || '').trigger('change');
|
||||||
|
$('#notify_function').prop('checked', Number(d.notify_function) === 1);
|
||||||
document.getElementById('dlLaw').value = d.law_regulation || '';
|
document.getElementById('dlLaw').value = d.law_regulation || '';
|
||||||
document.getElementById('dlRecurrence').value = d.recurrence_type || 'once';
|
document.getElementById('dlRecurrence').value = d.recurrence_type || 'once';
|
||||||
fpDocDate.setDate(d.document_date || null, false, 'Y-m-d');
|
fpDocDate.setDate(d.document_date || null, false, 'Y-m-d');
|
||||||
|
|||||||
Reference in New Issue
Block a user