deadline feature

This commit is contained in:
2026-04-10 15:51:30 +03:00
parent 174fa73c2c
commit d7b6a58407
22 changed files with 2839 additions and 2 deletions
@@ -0,0 +1,18 @@
<?php
/**
* Auth check for AJAX endpoints.
* Include this at the top of every ajax handler.
* Sets $currentUserId from session or returns 401 JSON.
*/
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
if (empty($_SESSION['iduserlogin'])) {
header('Content-Type: application/json');
http_response_code(401);
echo json_encode(['success' => false, 'message' => 'Non autorizzato. Effettua il login.']);
exit;
}
$currentUserId = (int)$_SESSION['iduserlogin'];
@@ -0,0 +1,107 @@
<?php
require_once(__DIR__ . '/auth_check.php');
header('Content-Type: application/json');
require_once(__DIR__ . '/../../class/db-functions.php');
try {
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
echo json_encode(['success' => false, 'message' => 'ID non valido.']);
exit;
}
$id = (int)$_GET['id'];
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("SELECT * FROM scad_deadlines WHERE id = ? AND status = 'active'");
$stmt->execute([$id]);
$deadline = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$deadline) {
echo json_encode(['success' => false, 'message' => 'Scadenza non trovata o già completata.']);
exit;
}
$pdo->beginTransaction();
// Mark as completed
$pdo->prepare("UPDATE scad_deadlines SET status = 'completed', completed_at = NOW(), completed_by = ? WHERE id = ?")
->execute([$currentUserId, $id]);
// History
$pdo->prepare("INSERT INTO scad_deadline_histories (deadline_id, user_id, action) VALUES (?, ?, 'completed')")
->execute([$id, $currentUserId]);
$newId = null;
// If recurring, create next deadline
if ($deadline['recurrence_type'] !== 'once') {
$dueDate = new DateTime($deadline['due_date']);
$checkDate = $deadline['check_date'] ? new DateTime($deadline['check_date']) : null;
switch ($deadline['recurrence_type']) {
case 'monthly': $interval = new DateInterval('P1M'); break;
case 'quarterly': $interval = new DateInterval('P3M'); break;
case 'semiannual': $interval = new DateInterval('P6M'); break;
case 'annual': $interval = new DateInterval('P1Y'); break;
case 'biennial': $interval = new DateInterval('P2Y'); break;
case 'triennial': $interval = new DateInterval('P3Y'); break;
case 'quinquennial': $interval = new DateInterval('P5Y'); break;
default: $interval = null;
}
if ($interval) {
$dueDate->add($interval);
if ($checkDate) $checkDate->add($interval);
$ins = $pdo->prepare("
INSERT INTO scad_deadlines
(category, topic, law_regulation, recurrence_type, due_date, check_date,
document_date, notification_days, storage_location, notes, created_by, departments)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$ins->execute([
$deadline['category'], $deadline['topic'], $deadline['law_regulation'],
$deadline['recurrence_type'], $dueDate->format('Y-m-d'),
$checkDate ? $checkDate->format('Y-m-d') : null,
$deadline['document_date'],
$deadline['notification_days'], $deadline['storage_location'],
$deadline['notes'], $deadline['created_by'], $deadline['departments']
]);
$newId = $pdo->lastInsertId();
// Copy employee assignments
$empStmt = $pdo->prepare("SELECT employee_id FROM scad_deadline_employee WHERE deadline_id = ?");
$empStmt->execute([$id]);
$empIds = $empStmt->fetchAll(PDO::FETCH_COLUMN);
if (!empty($empIds)) {
$insertEmp = $pdo->prepare("INSERT INTO scad_deadline_employee (deadline_id, employee_id) VALUES (?, ?)");
foreach ($empIds as $empId) {
$insertEmp->execute([$newId, $empId]);
}
}
// History for new
$pdo->prepare("INSERT INTO scad_deadline_histories (deadline_id, user_id, action, notes) VALUES (?, ?, 'created', ?)")
->execute([$newId, $currentUserId, 'Creata automaticamente dalla scadenza #' . $id]);
}
}
$pdo->commit();
$msg = 'Scadenza completata con successo.';
if ($newId) {
$msg .= ' Nuova scadenza creata con data ' . $dueDate->format('d/m/Y') . '.';
}
echo json_encode(['success' => true, 'message' => $msg, 'new_id' => $newId]);
} catch (Exception $e) {
if (isset($pdo) && $pdo->inTransaction()) {
$pdo->rollBack();
}
echo json_encode(['success' => false, 'message' => 'Errore: ' . $e->getMessage()]);
}
@@ -0,0 +1,43 @@
<?php
require_once(__DIR__ . '/auth_check.php');
header('Content-Type: application/json');
require_once(__DIR__ . '/../../class/db-functions.php');
try {
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
echo json_encode(['success' => false, 'message' => 'ID non valido.']);
exit;
}
$id = (int)$_GET['id'];
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("SELECT * FROM scad_deadline_attachments WHERE id = ?");
$stmt->execute([$id]);
$att = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$att) {
echo json_encode(['success' => false, 'message' => 'Allegato non trovato.']);
exit;
}
// Delete file
$filePath = __DIR__ . '/../attachments/' . $att['stored_name'];
if (file_exists($filePath)) {
unlink($filePath);
}
// Delete DB record
$pdo->prepare("DELETE FROM scad_deadline_attachments WHERE id = ?")->execute([$id]);
// History
$pdo->prepare("INSERT INTO scad_deadline_histories (deadline_id, user_id, action, notes) VALUES (?, ?, 'attachment_removed', ?)")
->execute([$att['deadline_id'], $currentUserId, $att['original_name']]);
echo json_encode(['success' => true, 'message' => 'Allegato eliminato.']);
} catch (Exception $e) {
echo json_encode(['success' => false, 'message' => 'Errore: ' . $e->getMessage()]);
}
@@ -0,0 +1,26 @@
<?php
require_once(__DIR__ . '/auth_check.php');
header('Content-Type: application/json');
require_once(__DIR__ . '/../../class/db-functions.php');
try {
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
echo json_encode(['success' => false, 'message' => 'ID non valido.']);
exit;
}
$id = (int)$_GET['id'];
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("DELETE FROM scad_deadlines WHERE id = ?");
$stmt->execute([$id]);
if ($stmt->rowCount() > 0) {
echo json_encode(['success' => true, 'message' => 'Scadenza eliminata con successo.']);
} else {
echo json_encode(['success' => false, 'message' => 'Scadenza non trovata.']);
}
} catch (Exception $e) {
echo json_encode(['success' => false, 'message' => 'Errore: ' . $e->getMessage()]);
}
@@ -0,0 +1,36 @@
<?php
require_once(__DIR__ . '/auth_check.php');
require_once(__DIR__ . '/../../class/db-functions.php');
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
http_response_code(400);
echo 'ID non valido.';
exit;
}
$id = (int)$_GET['id'];
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("SELECT * FROM scad_deadline_attachments WHERE id = ?");
$stmt->execute([$id]);
$att = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$att) {
http_response_code(404);
echo 'Allegato non trovato.';
exit;
}
$filePath = __DIR__ . '/../attachments/' . $att['stored_name'];
if (!file_exists($filePath)) {
http_response_code(404);
echo 'File non trovato sul server.';
exit;
}
header('Content-Type: ' . ($att['mime_type'] ?: 'application/octet-stream'));
header('Content-Disposition: attachment; filename="' . addslashes($att['original_name']) . '"');
header('Content-Length: ' . filesize($filePath));
readfile($filePath);
exit;
@@ -0,0 +1,92 @@
<?php
require_once(__DIR__ . '/auth_check.php');
header('Content-Type: application/json');
require_once(__DIR__ . '/../../class/db-functions.php');
try {
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$start = $_GET['start'] ?? null;
$end = $_GET['end'] ?? null;
$filterStatus = $_GET['status'] ?? '';
$filterDept = $_GET['department'] ?? '';
$filterEmployee = $_GET['employee'] ?? '';
$sql = "SELECT DISTINCT d.id, d.topic, d.category, d.due_date, d.status, d.notification_days, d.departments
FROM scad_deadlines d
LEFT JOIN scad_deadline_employee de ON de.deadline_id = d.id
LEFT JOIN employees e ON e.id = de.employee_id";
$where = [];
$params = [];
if ($start && $end) {
$where[] = "d.due_date >= ? AND d.due_date <= ?";
$params[] = $start;
$params[] = $end;
}
if ($filterStatus === 'non-completata') {
$where[] = "d.status != 'completed'";
} elseif ($filterStatus === 'completata') {
$where[] = "d.status = 'completed'";
} elseif ($filterStatus === 'scaduta') {
$where[] = "d.status = 'active' AND d.due_date < CURDATE()";
} elseif ($filterStatus === 'in-scadenza') {
$where[] = "d.status = 'active' AND d.due_date >= CURDATE() AND d.due_date <= DATE_ADD(CURDATE(), INTERVAL d.notification_days DAY)";
} elseif ($filterStatus === 'attiva') {
$where[] = "d.status = 'active' AND d.due_date > DATE_ADD(CURDATE(), INTERVAL d.notification_days DAY)";
}
if ($filterDept) {
$where[] = "(e.department = ? OR FIND_IN_SET(?, d.departments))";
$params[] = $filterDept;
$params[] = $filterDept;
}
if ($filterEmployee) {
$where[] = "CONCAT(e.first_name, ' ', e.last_name) = ?";
$params[] = $filterEmployee;
}
if (!empty($where)) {
$sql .= " WHERE " . implode(' AND ', $where);
}
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$deadlines = $stmt->fetchAll(PDO::FETCH_ASSOC);
$today = date('Y-m-d');
$events = [];
foreach ($deadlines as $d) {
$isCompleted = $d['status'] === 'completed';
$isOverdue = !$isCompleted && $d['due_date'] < $today;
$approachDate = date('Y-m-d', strtotime($today . ' + ' . (int)$d['notification_days'] . ' days'));
$isApproaching = !$isCompleted && !$isOverdue && $d['due_date'] <= $approachDate;
if ($isCompleted) { $color = '#198754'; }
elseif ($isOverdue) { $color = '#dc3545'; }
elseif ($isApproaching) { $color = '#e8930c'; }
else { $color = '#5a8fd8'; }
$title = $d['topic'];
if ($d['category']) $title = $d['category'] . ': ' . $title;
$events[] = [
'id' => $d['id'],
'title' => $title,
'start' => $d['due_date'],
'backgroundColor' => $color,
'borderColor' => $color,
'url' => 'scadenzario/detail.php?id=' . $d['id'],
];
}
echo json_encode($events);
} catch (Exception $e) {
echo json_encode([]);
}
@@ -0,0 +1,45 @@
<?php
require_once(__DIR__ . '/auth_check.php');
header('Content-Type: application/json');
require_once(__DIR__ . '/../../class/db-functions.php');
try {
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
echo json_encode(['success' => false, 'message' => 'ID non valido.']);
exit;
}
$id = (int)$_GET['id'];
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("SELECT * FROM scad_deadlines WHERE id = ?");
$stmt->execute([$id]);
$deadline = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$deadline) {
echo json_encode(['success' => false, 'message' => 'Scadenza non trovata.']);
exit;
}
// Get assigned employee IDs
$empStmt = $pdo->prepare("SELECT employee_id FROM scad_deadline_employee WHERE deadline_id = ?");
$empStmt->execute([$id]);
$deadline['employee_ids'] = $empStmt->fetchAll(PDO::FETCH_COLUMN);
// Parse departments into array
$deadline['department_names'] = [];
if (!empty($deadline['departments'])) {
$deadline['department_names'] = array_map('trim', explode(',', $deadline['departments']));
}
// Get attachments
$attStmt = $pdo->prepare("SELECT id, original_name, mime_type, size, created_at FROM scad_deadline_attachments WHERE deadline_id = ? ORDER BY created_at DESC");
$attStmt->execute([$id]);
$deadline['attachments'] = $attStmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['success' => true, 'data' => $deadline]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'message' => 'Errore: ' . $e->getMessage()]);
}
@@ -0,0 +1,49 @@
<?php
require_once(__DIR__ . '/auth_check.php');
header('Content-Type: application/json');
require_once(__DIR__ . '/../../class/db-functions.php');
try {
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
echo json_encode(['success' => false, 'message' => 'ID non valido.']);
exit;
}
$id = (int)$_GET['id'];
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("
SELECT h.*,
au.first_name as user_first_name,
au.last_name as user_last_name
FROM scad_deadline_histories h
LEFT JOIN auth_users au ON au.id = h.user_id
WHERE h.deadline_id = ?
ORDER BY h.created_at DESC
");
$stmt->execute([$id]);
$history = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Format for display
$actionLabels = [
'created' => 'Creata',
'updated' => 'Modificata',
'completed' => 'Completata',
'attachment_added' => 'Allegato aggiunto',
'attachment_removed' => 'Allegato rimosso',
'notification_sent' => 'Notifica inviata'
];
foreach ($history as &$h) {
$h['action_label'] = $actionLabels[$h['action']] ?? $h['action'];
$h['user_name'] = trim(($h['user_first_name'] ?? '') . ' ' . ($h['user_last_name'] ?? '')) ?: 'Sistema';
$h['changes'] = $h['changes'] ? json_decode($h['changes'], true) : null;
unset($h['user_first_name'], $h['user_last_name']);
}
echo json_encode(['success' => true, 'data' => $history]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'message' => 'Errore: ' . $e->getMessage()]);
}
@@ -0,0 +1,116 @@
<?php
require_once(__DIR__ . '/auth_check.php');
header('Content-Type: application/json');
require_once(__DIR__ . '/../../class/db-functions.php');
try {
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$id = isset($_POST['id']) && is_numeric($_POST['id']) ? (int)$_POST['id'] : null;
$category = trim($_POST['category'] ?? '') ?: null;
$topic = trim($_POST['topic'] ?? '');
$law_regulation = trim($_POST['law_regulation'] ?? '') ?: null;
$recurrence_type = $_POST['recurrence_type'] ?? 'once';
$due_date = $_POST['due_date'] ?? '';
$check_date = trim($_POST['check_date'] ?? '') ?: null;
$document_date = trim($_POST['document_date'] ?? '') ?: null;
$notification_days = isset($_POST['notification_days']) && is_numeric($_POST['notification_days']) ? (int)$_POST['notification_days'] : 7;
$storage_location = trim($_POST['storage_location'] ?? '') ?: null;
$notes = trim($_POST['notes'] ?? '') ?: null;
$employee_ids = $_POST['employee_ids'] ?? [];
$department_names = $_POST['department_names'] ?? [];
// Validation
if ($topic === '') {
echo json_encode(['success' => false, 'message' => 'Il campo Tema è obbligatorio.']);
exit;
}
if ($due_date === '' || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $due_date)) {
echo json_encode(['success' => false, 'message' => 'La data di scadenza è obbligatoria.']);
exit;
}
$validRecurrences = ['once', 'monthly', 'quarterly', 'semiannual', 'annual', 'biennial', 'triennial', 'quinquennial'];
if (!in_array($recurrence_type, $validRecurrences)) {
$recurrence_type = 'once';
}
if (!is_array($employee_ids)) {
$employee_ids = [];
}
$employee_ids = array_filter(array_map('intval', $employee_ids));
if (!is_array($department_names)) {
$department_names = [];
}
$department_names = array_filter(array_map('trim', $department_names));
$departmentsStr = !empty($department_names) ? implode(', ', $department_names) : null;
$pdo->beginTransaction();
if ($id) {
$stmt = $pdo->prepare("
UPDATE scad_deadlines SET
category = ?, topic = ?, law_regulation = ?, recurrence_type = ?,
due_date = ?, check_date = ?, document_date = ?, notification_days = ?,
storage_location = ?, notes = ?, departments = ?
WHERE id = ?
");
$stmt->execute([
$category, $topic, $law_regulation, $recurrence_type,
$due_date, $check_date, $document_date, $notification_days,
$storage_location, $notes, $departmentsStr, $id
]);
// Re-link employees
$pdo->prepare("DELETE FROM scad_deadline_employee WHERE deadline_id = ?")->execute([$id]);
// History
$pdo->prepare("INSERT INTO scad_deadline_histories (deadline_id, user_id, action) VALUES (?, ?, 'updated')")
->execute([$id, $currentUserId ?: null]);
$deadlineId = $id;
} else {
// INSERT
$stmt = $pdo->prepare("
INSERT INTO scad_deadlines
(category, topic, law_regulation, recurrence_type, due_date, check_date,
document_date, notification_days, storage_location, notes, created_by, departments)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$category, $topic, $law_regulation, $recurrence_type,
$due_date, $check_date, $document_date, $notification_days,
$storage_location, $notes, $currentUserId, $departmentsStr
]);
$deadlineId = $pdo->lastInsertId();
// History
$pdo->prepare("INSERT INTO scad_deadline_histories (deadline_id, user_id, action) VALUES (?, ?, 'created')")
->execute([$deadlineId, $currentUserId ?: null]);
}
// Link employees
if (!empty($employee_ids)) {
$insertEmployee = $pdo->prepare("INSERT INTO scad_deadline_employee (deadline_id, employee_id) VALUES (?, ?)");
foreach ($employee_ids as $empId) {
$insertEmployee->execute([$deadlineId, $empId]);
}
}
$pdo->commit();
echo json_encode([
'success' => true,
'message' => $id ? 'Scadenza aggiornata con successo.' : 'Scadenza creata con successo.',
'id' => $deadlineId
]);
} catch (Exception $e) {
if (isset($pdo) && $pdo->inTransaction()) {
$pdo->rollBack();
}
echo json_encode(['success' => false, 'message' => 'Errore: ' . $e->getMessage()]);
}
@@ -0,0 +1,72 @@
<?php
require_once(__DIR__ . '/auth_check.php');
header('Content-Type: application/json');
require_once(__DIR__ . '/../../class/db-functions.php');
try {
if (!isset($_POST['deadline_id']) || !is_numeric($_POST['deadline_id'])) {
echo json_encode(['success' => false, 'message' => 'ID scadenza non valido.']);
exit;
}
if (empty($_FILES['files']['name'][0])) {
echo json_encode(['success' => false, 'message' => 'Nessun file selezionato.']);
exit;
}
$deadlineId = (int)$_POST['deadline_id'];
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// Verify deadline exists
$check = $pdo->prepare("SELECT id FROM scad_deadlines WHERE id = ?");
$check->execute([$deadlineId]);
if (!$check->fetch()) {
echo json_encode(['success' => false, 'message' => 'Scadenza non trovata.']);
exit;
}
$uploadDir = __DIR__ . '/../attachments/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$inserted = [];
$pdo->beginTransaction();
$stmt = $pdo->prepare("
INSERT INTO scad_deadline_attachments (deadline_id, original_name, stored_name, mime_type, size, uploaded_by)
VALUES (?, ?, ?, ?, ?, ?)
");
$histStmt = $pdo->prepare("INSERT INTO scad_deadline_histories (deadline_id, user_id, action, notes) VALUES (?, ?, 'attachment_added', ?)");
$fileCount = count($_FILES['files']['name']);
for ($i = 0; $i < $fileCount; $i++) {
if ($_FILES['files']['error'][$i] !== UPLOAD_ERR_OK) continue;
$originalName = $_FILES['files']['name'][$i];
$mimeType = $_FILES['files']['type'][$i];
$size = $_FILES['files']['size'][$i];
$storedName = uniqid('att_') . '_' . preg_replace('/[^a-zA-Z0-9._-]/', '_', $originalName);
if (!move_uploaded_file($_FILES['files']['tmp_name'][$i], $uploadDir . $storedName)) {
continue;
}
$stmt->execute([$deadlineId, $originalName, $storedName, $mimeType, $size, $currentUserId]);
$histStmt->execute([$deadlineId, $currentUserId, $originalName]);
$inserted[] = ['id' => $pdo->lastInsertId(), 'original_name' => $originalName, 'stored_name' => $storedName];
}
$pdo->commit();
echo json_encode([
'success' => true,
'message' => count($inserted) . ' file caricato/i con successo.',
'files' => $inserted
]);
} catch (Exception $e) {
if (isset($pdo) && $pdo->inTransaction()) $pdo->rollBack();
echo json_encode(['success' => false, 'message' => 'Errore: ' . $e->getMessage()]);
}