maintaince module
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Auth + permission guard for the Manutenzioni ajax endpoints.
|
||||
* Include at the top of every handler; it defines $currentUserId.
|
||||
*
|
||||
* Authorisation is enforced here, on the backend — the UI only hides
|
||||
* buttons, it never decides what a request is allowed to do.
|
||||
*/
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
if (empty($_SESSION['iduserlogin'])) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'message' => 'Non autorizzato. Effettua il login.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$currentUserId = (int)$_SESSION['iduserlogin'];
|
||||
|
||||
require_once __DIR__ . '/../include/functions.php';
|
||||
|
||||
if (!function_exists('mnt_user_can')) {
|
||||
/**
|
||||
* Permission check straight from the Vanguard RBAC tables
|
||||
* (the Auth facade is not bootstrapped in these endpoints).
|
||||
*/
|
||||
function mnt_user_can(string $permission): bool
|
||||
{
|
||||
global $currentUserId;
|
||||
|
||||
static $permissions = null;
|
||||
|
||||
if ($permissions === null) {
|
||||
$stmt = mnt_pdo()->prepare("
|
||||
SELECT p.name
|
||||
FROM auth_users u
|
||||
INNER JOIN auth_permission_role pr ON pr.role_id = u.role_id
|
||||
INNER JOIN auth_permissions p ON p.id = pr.permission_id
|
||||
WHERE u.id = ?
|
||||
");
|
||||
$stmt->execute([$currentUserId]);
|
||||
$permissions = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
return in_array($permission, $permissions, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('mnt_require_permission')) {
|
||||
function mnt_require_permission(string $permission): void
|
||||
{
|
||||
if (!mnt_user_can($permission)) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'message' => 'Permesso negato.']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('mnt_json_fail')) {
|
||||
function mnt_json_fail(string $message, int $code = 200): void
|
||||
{
|
||||
if ($code !== 200) {
|
||||
http_response_code($code);
|
||||
}
|
||||
echo json_encode(['success' => false, 'message' => $message]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('mnt_json_ok')) {
|
||||
function mnt_json_ok(array $payload = []): void
|
||||
{
|
||||
echo json_encode(['success' => true] + $payload);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/auth_check.php';
|
||||
|
||||
mnt_require_permission('maintenance.manage');
|
||||
|
||||
try {
|
||||
$pdo = mnt_pdo();
|
||||
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
|
||||
if ($id <= 0) {
|
||||
mnt_json_fail('ID non valido.');
|
||||
}
|
||||
|
||||
// The FK is ON DELETE SET NULL, so a delete would silently orphan items:
|
||||
// refuse while the category is still in use.
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM inv_equipment WHERE category_id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$inUse = (int)$stmt->fetchColumn();
|
||||
|
||||
if ($inUse > 0) {
|
||||
mnt_json_fail('Impossibile eliminare: la categoria è usata da ' . $inUse . ' attrezzature.');
|
||||
}
|
||||
|
||||
$pdo->prepare("DELETE FROM inv_categories WHERE id = ?")->execute([$id]);
|
||||
|
||||
mnt_json_ok();
|
||||
} catch (Throwable $e) {
|
||||
mnt_json_fail('Errore: ' . $e->getMessage());
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/auth_check.php';
|
||||
|
||||
mnt_require_permission('maintenance.manage');
|
||||
|
||||
try {
|
||||
$pdo = mnt_pdo();
|
||||
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
|
||||
if ($id <= 0) {
|
||||
mnt_json_fail('ID non valido.');
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT id, name FROM inv_equipment WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$equipment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$equipment) {
|
||||
mnt_json_fail('Attrezzatura non trovata.');
|
||||
}
|
||||
|
||||
// Legacy production records reference tools without ON DELETE CASCADE:
|
||||
// refuse with a clear message instead of letting the FK throw.
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM productiondata_tools WHERE tool_id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$usedInProduction = (int)$stmt->fetchColumn();
|
||||
|
||||
if ($usedInProduction > 0) {
|
||||
mnt_json_fail(
|
||||
'Impossibile eliminare: l\'attrezzatura è usata in ' . $usedInProduction .
|
||||
' registrazioni di produzione. Impostala su "Dismesso" per escluderla dalle pianificazioni.'
|
||||
);
|
||||
}
|
||||
|
||||
// Collect stored files before the cascade removes their rows
|
||||
$storedNames = [];
|
||||
|
||||
$stmt = $pdo->prepare("SELECT stored_name FROM inv_equipment_files WHERE equipment_id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$storedNames = array_merge($storedNames, $stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT f.stored_name
|
||||
FROM maint_maintenance_files f
|
||||
INNER JOIN maint_maintenances m ON m.id = f.maintenance_id
|
||||
WHERE m.equipment_id = ?
|
||||
");
|
||||
$stmt->execute([$id]);
|
||||
$storedNames = array_merge($storedNames, $stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT f.stored_name
|
||||
FROM maint_intervention_files f
|
||||
INNER JOIN maint_interventions i ON i.id = f.intervention_id
|
||||
WHERE i.equipment_id = ?
|
||||
");
|
||||
$stmt->execute([$id]);
|
||||
$storedNames = array_merge($storedNames, $stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||
|
||||
$stmt = $pdo->prepare("SELECT signature_path FROM maint_interventions WHERE equipment_id = ? AND signature_path IS NOT NULL");
|
||||
$stmt->execute([$id]);
|
||||
$storedNames = array_merge($storedNames, $stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||
|
||||
// cover_file_id points at inv_equipment_files, which cascades from the
|
||||
// equipment row: clear it first so the FK does not block the delete.
|
||||
$pdo->beginTransaction();
|
||||
$pdo->prepare("UPDATE inv_equipment SET cover_file_id = NULL WHERE id = ?")->execute([$id]);
|
||||
$pdo->prepare("DELETE FROM inv_equipment WHERE id = ?")->execute([$id]);
|
||||
$pdo->commit();
|
||||
|
||||
foreach (array_filter($storedNames) as $storedName) {
|
||||
mnt_delete_stored_file($storedName);
|
||||
}
|
||||
|
||||
mnt_log($pdo, 'equipment_deleted', null, null, null, $currentUserId, null, $equipment['name']);
|
||||
|
||||
mnt_json_ok();
|
||||
} catch (Throwable $e) {
|
||||
if (isset($pdo) && $pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
mnt_json_fail('Errore: ' . $e->getMessage());
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/auth_check.php';
|
||||
|
||||
mnt_require_permission('maintenance.manage');
|
||||
|
||||
try {
|
||||
$pdo = mnt_pdo();
|
||||
|
||||
$scope = (string)($_POST['scope'] ?? 'equipment');
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
|
||||
$tables = [
|
||||
'equipment' => 'inv_equipment_files',
|
||||
'maintenance' => 'maint_maintenance_files',
|
||||
'intervention' => 'maint_intervention_files',
|
||||
];
|
||||
|
||||
if (!isset($tables[$scope])) {
|
||||
mnt_json_fail('Scope non valido.');
|
||||
}
|
||||
if ($id <= 0) {
|
||||
mnt_json_fail('ID non valido.');
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT * FROM {$tables[$scope]} WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$file = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$file) {
|
||||
mnt_json_fail('File non trovato.');
|
||||
}
|
||||
|
||||
// Clear the cover reference first — the FK would block the delete
|
||||
if ($scope === 'equipment') {
|
||||
$pdo->prepare("UPDATE inv_equipment SET cover_file_id = NULL WHERE cover_file_id = ?")->execute([$id]);
|
||||
}
|
||||
|
||||
$pdo->prepare("DELETE FROM {$tables[$scope]} WHERE id = ?")->execute([$id]);
|
||||
mnt_delete_stored_file((string)$file['stored_name']);
|
||||
|
||||
mnt_log(
|
||||
$pdo,
|
||||
'file_deleted',
|
||||
$scope === 'equipment' ? (int)$file['equipment_id'] : null,
|
||||
$scope === 'maintenance' ? (int)$file['maintenance_id'] : null,
|
||||
$scope === 'intervention' ? (int)$file['intervention_id'] : null,
|
||||
$currentUserId,
|
||||
null,
|
||||
(string)$file['original_name']
|
||||
);
|
||||
|
||||
mnt_json_ok();
|
||||
} catch (Throwable $e) {
|
||||
mnt_json_fail('Errore: ' . $e->getMessage());
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/auth_check.php';
|
||||
|
||||
mnt_require_permission('maintenance.manage');
|
||||
|
||||
try {
|
||||
$pdo = mnt_pdo();
|
||||
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
|
||||
if ($id <= 0) {
|
||||
mnt_json_fail('ID non valido.');
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT * FROM maint_interventions WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$intervention = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$intervention) {
|
||||
mnt_json_fail('Intervento non trovato.');
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT stored_name FROM maint_intervention_files WHERE intervention_id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$storedNames = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
if (!empty($intervention['signature_path'])) {
|
||||
$storedNames[] = $intervention['signature_path'];
|
||||
}
|
||||
|
||||
$pdo->prepare("DELETE FROM maint_interventions WHERE id = ?")->execute([$id]);
|
||||
|
||||
foreach (array_filter($storedNames) as $storedName) {
|
||||
mnt_delete_stored_file($storedName);
|
||||
}
|
||||
|
||||
// Removing the last completed intervention must roll the due date back
|
||||
mnt_recalc_maintenance($pdo, (int)$intervention['maintenance_id']);
|
||||
|
||||
mnt_log(
|
||||
$pdo,
|
||||
'intervention_deleted',
|
||||
(int)$intervention['equipment_id'],
|
||||
(int)$intervention['maintenance_id'],
|
||||
null,
|
||||
$currentUserId,
|
||||
null,
|
||||
'Intervento del ' . $intervention['performed_at']
|
||||
);
|
||||
|
||||
mnt_json_ok();
|
||||
} catch (Throwable $e) {
|
||||
mnt_json_fail('Errore: ' . $e->getMessage());
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/auth_check.php';
|
||||
|
||||
mnt_require_permission('maintenance.manage');
|
||||
|
||||
try {
|
||||
$pdo = mnt_pdo();
|
||||
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
|
||||
if ($id <= 0) {
|
||||
mnt_json_fail('ID non valido.');
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT id, equipment_id, title FROM maint_maintenances WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$maintenance = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$maintenance) {
|
||||
mnt_json_fail('Manutenzione non trovata.');
|
||||
}
|
||||
|
||||
// Files of the maintenance and of its interventions (both cascade in DB)
|
||||
$stmt = $pdo->prepare("SELECT stored_name FROM maint_maintenance_files WHERE maintenance_id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$storedNames = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT f.stored_name
|
||||
FROM maint_intervention_files f
|
||||
INNER JOIN maint_interventions i ON i.id = f.intervention_id
|
||||
WHERE i.maintenance_id = ?
|
||||
");
|
||||
$stmt->execute([$id]);
|
||||
$storedNames = array_merge($storedNames, $stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||
|
||||
$stmt = $pdo->prepare("SELECT signature_path FROM maint_interventions WHERE maintenance_id = ? AND signature_path IS NOT NULL");
|
||||
$stmt->execute([$id]);
|
||||
$storedNames = array_merge($storedNames, $stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||
|
||||
$pdo->prepare("DELETE FROM maint_maintenances WHERE id = ?")->execute([$id]);
|
||||
|
||||
foreach (array_filter($storedNames) as $storedName) {
|
||||
mnt_delete_stored_file($storedName);
|
||||
}
|
||||
|
||||
mnt_log($pdo, 'maintenance_deleted', (int)$maintenance['equipment_id'], null, null, $currentUserId, null, $maintenance['title']);
|
||||
|
||||
mnt_json_ok();
|
||||
} catch (Throwable $e) {
|
||||
mnt_json_fail('Errore: ' . $e->getMessage());
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Serves an attachment. The attachments/ folder itself is closed by
|
||||
* .htaccess — this is the only way to read a file, and it requires a
|
||||
* logged-in session.
|
||||
*
|
||||
* scope = equipment | maintenance | intervention | signature
|
||||
*/
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
if (empty($_SESSION['iduserlogin'])) {
|
||||
http_response_code(401);
|
||||
exit('Non autorizzato.');
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../include/functions.php';
|
||||
|
||||
$scope = (string)($_GET['scope'] ?? 'equipment');
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
|
||||
if ($id <= 0) {
|
||||
http_response_code(400);
|
||||
exit('ID non valido.');
|
||||
}
|
||||
|
||||
$tables = [
|
||||
'equipment' => 'inv_equipment_files',
|
||||
'maintenance' => 'maint_maintenance_files',
|
||||
'intervention' => 'maint_intervention_files',
|
||||
];
|
||||
|
||||
$pdo = mnt_pdo();
|
||||
|
||||
if ($scope === 'signature') {
|
||||
$stmt = $pdo->prepare("SELECT signature_path AS stored_name, 'firma.png' AS original_name, 'image/png' AS mime_type FROM maint_interventions WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
} elseif (isset($tables[$scope])) {
|
||||
$stmt = $pdo->prepare("SELECT stored_name, original_name, mime_type FROM {$tables[$scope]} WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
} else {
|
||||
http_response_code(400);
|
||||
exit('Scope non valido.');
|
||||
}
|
||||
|
||||
$file = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$file || empty($file['stored_name'])) {
|
||||
http_response_code(404);
|
||||
exit('File non trovato.');
|
||||
}
|
||||
|
||||
// basename() keeps a crafted stored_name from escaping the folder
|
||||
$path = __DIR__ . '/../attachments/' . basename((string)$file['stored_name']);
|
||||
|
||||
if (!is_file($path)) {
|
||||
http_response_code(404);
|
||||
exit('File non presente sul server.');
|
||||
}
|
||||
|
||||
$mimeType = $file['mime_type'] ?: 'application/octet-stream';
|
||||
$disposition = isset($_GET['download']) ? 'attachment' : 'inline';
|
||||
|
||||
header('Content-Type: ' . $mimeType);
|
||||
header('Content-Length: ' . filesize($path));
|
||||
header('Content-Disposition: ' . $disposition . '; filename="' . rawurlencode((string)$file['original_name']) . '"');
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
|
||||
readfile($path);
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/auth_check.php';
|
||||
|
||||
mnt_require_permission('maintenance.calendar.view');
|
||||
|
||||
/**
|
||||
* FullCalendar feed for maintenance due dates.
|
||||
* Optional filters: type=scheduled|extraordinary, category=<id>
|
||||
*/
|
||||
try {
|
||||
$pdo = mnt_pdo();
|
||||
|
||||
$start = (string)($_GET['start'] ?? date('Y-m-01', strtotime('-6 months')));
|
||||
$end = (string)($_GET['end'] ?? date('Y-m-t', strtotime('+12 months')));
|
||||
$type = (string)($_GET['type'] ?? '');
|
||||
$categoryId = (int)($_GET['category'] ?? 0);
|
||||
|
||||
// Le attrezzature dismesse o fuori servizio restano fuori dalle viste di
|
||||
// pianificazione: è a questo che serve lo stato (capitolato §1).
|
||||
$where = [
|
||||
"m.is_active = 1",
|
||||
"e.status = 'active'",
|
||||
"m.next_due_date IS NOT NULL",
|
||||
"m.next_due_date BETWEEN ? AND ?",
|
||||
];
|
||||
$params = [substr($start, 0, 10), substr($end, 0, 10)];
|
||||
|
||||
if (array_key_exists($type, mnt_intervention_types())) {
|
||||
$where[] = "m.intervention_type = ?";
|
||||
$params[] = $type;
|
||||
}
|
||||
if ($categoryId > 0) {
|
||||
$where[] = "e.category_id = ?";
|
||||
$params[] = $categoryId;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT m.id, m.title, m.code, m.next_due_date, m.alert_days, m.is_critical, m.intervention_type,
|
||||
e.id AS equipment_id, e.name AS equipment_name,
|
||||
c.color AS category_color
|
||||
FROM maint_maintenances m
|
||||
INNER JOIN inv_equipment e ON e.id = m.equipment_id
|
||||
LEFT JOIN inv_categories c ON c.id = e.category_id
|
||||
WHERE " . implode(' AND ', $where) . "
|
||||
ORDER BY m.next_due_date ASC
|
||||
");
|
||||
$stmt->execute($params);
|
||||
|
||||
$events = [];
|
||||
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$state = mnt_due_state($row['next_due_date'], (int)$row['alert_days']);
|
||||
|
||||
// Overdue/soon override the category colour so urgency wins visually
|
||||
$color = $state === 'overdue' ? '#dc3545' : ($state === 'due_soon' ? '#f0a202' : ($row['category_color'] ?: '#2f7d8f'));
|
||||
|
||||
$events[] = [
|
||||
'id' => (int)$row['id'],
|
||||
'title' => ($row['code'] ? $row['code'] . ' — ' : '') . $row['equipment_name'] . ': ' . $row['title'],
|
||||
'start' => $row['next_due_date'],
|
||||
'allDay' => true,
|
||||
'backgroundColor' => $color,
|
||||
'borderColor' => $color,
|
||||
'extendedProps' => [
|
||||
'equipmentId' => (int)$row['equipment_id'],
|
||||
'equipmentName' => $row['equipment_name'],
|
||||
'maintenanceTitle' => $row['title'],
|
||||
'isCritical' => (int)$row['is_critical'] === 1,
|
||||
'state' => $state,
|
||||
'type' => $row['intervention_type'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode($events);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/auth_check.php';
|
||||
|
||||
mnt_require_permission('maintenance.equipment.view');
|
||||
|
||||
try {
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
|
||||
if ($id <= 0) {
|
||||
mnt_json_fail('ID non valido.');
|
||||
}
|
||||
|
||||
$stmt = mnt_pdo()->prepare("SELECT * FROM inv_equipment WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$equipment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$equipment) {
|
||||
mnt_json_fail('Attrezzatura non trovata.');
|
||||
}
|
||||
|
||||
mnt_json_ok(['equipment' => $equipment]);
|
||||
} catch (Throwable $e) {
|
||||
mnt_json_fail('Errore: ' . $e->getMessage());
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/auth_check.php';
|
||||
|
||||
/**
|
||||
* Cumulative calendar feed: deadlines + trainings + maintenances.
|
||||
* Each layer is returned only if the user may see that module, and only
|
||||
* if the layer is requested via ?layers=deadlines,trainings,maintenances
|
||||
*/
|
||||
try {
|
||||
$pdo = mnt_pdo();
|
||||
|
||||
$start = substr((string)($_GET['start'] ?? date('Y-m-01', strtotime('-6 months'))), 0, 10);
|
||||
$end = substr((string)($_GET['end'] ?? date('Y-m-t', strtotime('+12 months'))), 0, 10);
|
||||
|
||||
$requested = array_filter(explode(',', (string)($_GET['layers'] ?? 'deadlines,trainings,maintenances')));
|
||||
$wants = fn(string $layer): bool => in_array($layer, $requested, true);
|
||||
|
||||
$events = [];
|
||||
|
||||
// ---- Maintenances --------------------------------------------------
|
||||
if ($wants('maintenances') && mnt_user_can('maintenance.calendar.view')) {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT m.id, m.title, m.code, m.next_due_date, m.alert_days, m.is_critical,
|
||||
e.id AS equipment_id, e.name AS equipment_name
|
||||
FROM maint_maintenances m
|
||||
INNER JOIN inv_equipment e ON e.id = m.equipment_id
|
||||
WHERE m.is_active = 1
|
||||
AND e.status = 'active'
|
||||
AND m.next_due_date BETWEEN ? AND ?
|
||||
");
|
||||
$stmt->execute([$start, $end]);
|
||||
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$state = mnt_due_state($row['next_due_date'], (int)$row['alert_days']);
|
||||
$color = $state === 'overdue' ? '#dc3545' : '#2f7d8f';
|
||||
|
||||
$events[] = [
|
||||
'id' => 'maint-' . $row['id'],
|
||||
'title' => '🔧 ' . $row['equipment_name'] . ': ' . $row['title'],
|
||||
'start' => $row['next_due_date'],
|
||||
'allDay' => true,
|
||||
'backgroundColor' => $color,
|
||||
'borderColor' => $color,
|
||||
'extendedProps' => [
|
||||
'layer' => 'maintenances',
|
||||
'url' => 'manutenzioni/equipment.php?id=' . (int)$row['equipment_id'],
|
||||
'state' => $state,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Deadlines (scadenzario) --------------------------------------
|
||||
if ($wants('deadlines') && mnt_user_can('deadlines.view')) {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT d.id, d.topic, d.due_date, d.status, d.notification_days, s.name AS subject_name
|
||||
FROM scad_deadlines d
|
||||
LEFT JOIN scad_subjects s ON s.id = d.subject_id
|
||||
WHERE d.due_date BETWEEN ? AND ?
|
||||
");
|
||||
$stmt->execute([$start, $end]);
|
||||
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$isCompleted = $row['status'] === 'completed';
|
||||
$state = $isCompleted ? 'ok' : mnt_due_state($row['due_date'], (int)$row['notification_days']);
|
||||
$color = $isCompleted ? '#6c757d' : ($state === 'overdue' ? '#b02a37' : '#6f42c1');
|
||||
|
||||
$events[] = [
|
||||
'id' => 'scad-' . $row['id'],
|
||||
'title' => '📅 ' . $row['topic'],
|
||||
'start' => $row['due_date'],
|
||||
'allDay' => true,
|
||||
'backgroundColor' => $color,
|
||||
'borderColor' => $color,
|
||||
'extendedProps' => [
|
||||
'layer' => 'deadlines',
|
||||
'url' => 'scadenzario/detail.php?id=' . (int)$row['id'],
|
||||
'state' => $state,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Trainings (formazione) ---------------------------------------
|
||||
if ($wants('trainings') && mnt_user_can('hr.trainings.view')) {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT t.id, t.next_due_date, t.reminder_days,
|
||||
tt.name AS topic_name,
|
||||
CONCAT(e.first_name, ' ', e.last_name) AS employee_name
|
||||
FROM employee_trainings t
|
||||
INNER JOIN training_topics tt ON tt.id = t.training_topic_id
|
||||
INNER JOIN employees e ON e.id = t.employee_id
|
||||
WHERE t.next_due_date IS NOT NULL AND t.next_due_date BETWEEN ? AND ?
|
||||
");
|
||||
$stmt->execute([$start, $end]);
|
||||
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$state = mnt_due_state($row['next_due_date'], (int)($row['reminder_days'] ?? 0));
|
||||
$color = $state === 'overdue' ? '#a94442' : '#198754';
|
||||
|
||||
$events[] = [
|
||||
'id' => 'train-' . $row['id'],
|
||||
'title' => '🎓 ' . $row['topic_name'] . ' — ' . $row['employee_name'],
|
||||
'start' => $row['next_due_date'],
|
||||
'allDay' => true,
|
||||
'backgroundColor' => $color,
|
||||
'borderColor' => $color,
|
||||
'extendedProps' => [
|
||||
'layer' => 'trainings',
|
||||
'url' => 'trainings.php',
|
||||
'state' => $state,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode($events);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/auth_check.php';
|
||||
|
||||
mnt_require_permission('maintenance.maintenances.view');
|
||||
|
||||
try {
|
||||
$pdo = mnt_pdo();
|
||||
|
||||
$maintenanceId = (int)($_GET['maintenance_id'] ?? 0);
|
||||
$interventionId = (int)($_GET['id'] ?? 0);
|
||||
|
||||
if ($interventionId > 0) {
|
||||
$stmt = $pdo->prepare("SELECT * FROM maint_interventions WHERE id = ?");
|
||||
$stmt->execute([$interventionId]);
|
||||
$intervention = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$intervention) {
|
||||
mnt_json_fail('Intervento non trovato.');
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT id, original_name FROM maint_intervention_files WHERE intervention_id = ?");
|
||||
$stmt->execute([$interventionId]);
|
||||
|
||||
mnt_json_ok([
|
||||
'intervention' => $intervention,
|
||||
'files' => $stmt->fetchAll(PDO::FETCH_ASSOC),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($maintenanceId <= 0) {
|
||||
mnt_json_fail('ID non valido.');
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT i.*,
|
||||
CONCAT(e.first_name, ' ', e.last_name) AS operator_full_name,
|
||||
s.supplier_name,
|
||||
(SELECT COUNT(*) FROM maint_intervention_files f WHERE f.intervention_id = i.id) AS file_count
|
||||
FROM maint_interventions i
|
||||
LEFT JOIN employees e ON e.id = i.operator_employee_id
|
||||
LEFT JOIN suppliers s ON s.idsupplier = i.supplier_id
|
||||
WHERE i.maintenance_id = ?
|
||||
ORDER BY i.performed_at DESC, i.id DESC
|
||||
");
|
||||
$stmt->execute([$maintenanceId]);
|
||||
|
||||
mnt_json_ok(['interventions' => $stmt->fetchAll(PDO::FETCH_ASSOC)]);
|
||||
} catch (Throwable $e) {
|
||||
mnt_json_fail('Errore: ' . $e->getMessage());
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/auth_check.php';
|
||||
|
||||
mnt_require_permission('maintenance.maintenances.view');
|
||||
|
||||
try {
|
||||
$pdo = mnt_pdo();
|
||||
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
|
||||
if ($id <= 0) {
|
||||
mnt_json_fail('ID non valido.');
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT m.*, e.name AS equipment_name
|
||||
FROM maint_maintenances m
|
||||
INNER JOIN inv_equipment e ON e.id = m.equipment_id
|
||||
WHERE m.id = ?
|
||||
");
|
||||
$stmt->execute([$id]);
|
||||
$maintenance = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$maintenance) {
|
||||
mnt_json_fail('Manutenzione non trovata.');
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT id, original_name, size
|
||||
FROM maint_maintenance_files
|
||||
WHERE maintenance_id = ?
|
||||
ORDER BY created_at ASC
|
||||
");
|
||||
$stmt->execute([$id]);
|
||||
|
||||
mnt_json_ok([
|
||||
'maintenance' => $maintenance,
|
||||
'files' => $stmt->fetchAll(PDO::FETCH_ASSOC),
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
mnt_json_fail('Errore: ' . $e->getMessage());
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/auth_check.php';
|
||||
|
||||
mnt_require_permission('maintenance.manage');
|
||||
|
||||
try {
|
||||
$pdo = mnt_pdo();
|
||||
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
$name = trim((string)($_POST['name'] ?? ''));
|
||||
$code = trim((string)($_POST['code'] ?? ''));
|
||||
$color = trim((string)($_POST['color'] ?? '#6c757d'));
|
||||
$requiresLine = !empty($_POST['requires_line']) ? 1 : 0;
|
||||
$sortOrder = (int)($_POST['sort_order'] ?? 999);
|
||||
$isActive = !empty($_POST['is_active']) ? 1 : 0;
|
||||
$description = trim((string)($_POST['description'] ?? ''));
|
||||
|
||||
if ($name === '') {
|
||||
mnt_json_fail('Il nome è obbligatorio.');
|
||||
}
|
||||
if (!preg_match('/^#[0-9A-Fa-f]{6}$/', $color)) {
|
||||
$color = '#6c757d';
|
||||
}
|
||||
|
||||
// name is UNIQUE — check first so the user gets a readable message
|
||||
$stmt = $pdo->prepare("SELECT id FROM inv_categories WHERE name = ? AND id <> ?");
|
||||
$stmt->execute([$name, $id]);
|
||||
if ($stmt->fetchColumn()) {
|
||||
mnt_json_fail('Esiste già una categoria con questo nome.');
|
||||
}
|
||||
|
||||
if ($id > 0) {
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE inv_categories
|
||||
SET name = ?, code = ?, color = ?, requires_line = ?, sort_order = ?, is_active = ?, description = ?
|
||||
WHERE id = ?
|
||||
");
|
||||
$stmt->execute([$name, $code ?: null, $color, $requiresLine, $sortOrder, $isActive, $description ?: null, $id]);
|
||||
|
||||
mnt_json_ok(['id' => $id]);
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO inv_categories (name, code, color, requires_line, sort_order, is_active, description)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
");
|
||||
$stmt->execute([$name, $code ?: null, $color, $requiresLine, $sortOrder, $isActive, $description ?: null]);
|
||||
|
||||
mnt_json_ok(['id' => (int)$pdo->lastInsertId()]);
|
||||
} catch (Throwable $e) {
|
||||
mnt_json_fail('Errore: ' . $e->getMessage());
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/auth_check.php';
|
||||
|
||||
mnt_require_permission('maintenance.manage');
|
||||
|
||||
try {
|
||||
$pdo = mnt_pdo();
|
||||
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
$name = trim((string)($_POST['name'] ?? ''));
|
||||
$categoryId = (int)($_POST['category_id'] ?? 0);
|
||||
$lineId = (int)($_POST['line_id'] ?? 0);
|
||||
$registration = trim((string)($_POST['registration_number'] ?? ''));
|
||||
$serial = trim((string)($_POST['serial_number'] ?? ''));
|
||||
$batchLot = trim((string)($_POST['batch_lot'] ?? ''));
|
||||
$manufacturer = trim((string)($_POST['manufacturer'] ?? ''));
|
||||
$purchaseDate = trim((string)($_POST['purchase_date'] ?? ''));
|
||||
$commissioningDate = trim((string)($_POST['commissioning_date'] ?? ''));
|
||||
$toolType = trim((string)($_POST['tool_type'] ?? ''));
|
||||
$status = (string)($_POST['status'] ?? 'active');
|
||||
$departmentId = (int)($_POST['department_id'] ?? 0);
|
||||
$location = trim((string)($_POST['location'] ?? ''));
|
||||
$description = trim((string)($_POST['description'] ?? ''));
|
||||
$notes = trim((string)($_POST['notes'] ?? ''));
|
||||
|
||||
if ($name === '') {
|
||||
mnt_json_fail('Il nome è obbligatorio.');
|
||||
}
|
||||
if ($categoryId <= 0) {
|
||||
mnt_json_fail('La categoria è obbligatoria.');
|
||||
}
|
||||
if (!array_key_exists($status, mnt_equipment_statuses())) {
|
||||
mnt_json_fail('Stato non valido.');
|
||||
}
|
||||
|
||||
// A category flagged requires_line must carry a production line
|
||||
$stmt = $pdo->prepare("SELECT requires_line FROM inv_categories WHERE id = ?");
|
||||
$stmt->execute([$categoryId]);
|
||||
$requiresLine = $stmt->fetchColumn();
|
||||
|
||||
if ($requiresLine === false) {
|
||||
mnt_json_fail('Categoria inesistente.');
|
||||
}
|
||||
if ((int)$requiresLine === 1 && $lineId <= 0) {
|
||||
mnt_json_fail('Per questa categoria la linea di produzione è obbligatoria.');
|
||||
}
|
||||
|
||||
$values = [
|
||||
'name' => $name,
|
||||
'category_id' => $categoryId,
|
||||
'line_id' => $lineId ?: null,
|
||||
'registration_number' => $registration ?: null,
|
||||
'serial_number' => $serial ?: null,
|
||||
'batch_lot' => $batchLot ?: null,
|
||||
'manufacturer' => $manufacturer ?: null,
|
||||
'purchase_date' => $purchaseDate ?: null,
|
||||
'commissioning_date' => $commissioningDate ?: null,
|
||||
'tool_type' => $toolType ?: null,
|
||||
'status' => $status,
|
||||
'department_id' => $departmentId ?: null,
|
||||
'location' => $location ?: null,
|
||||
'description' => $description ?: null,
|
||||
'notes' => $notes ?: null,
|
||||
];
|
||||
|
||||
if ($id > 0) {
|
||||
$assignments = [];
|
||||
foreach (array_keys($values) as $column) {
|
||||
$assignments[] = "$column = :$column";
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE inv_equipment SET " . implode(', ', $assignments) . " WHERE id = :id");
|
||||
$stmt->execute($values + ['id' => $id]);
|
||||
|
||||
mnt_log($pdo, 'equipment_updated', $id, null, null, $currentUserId, $values);
|
||||
mnt_json_ok(['id' => $id]);
|
||||
}
|
||||
|
||||
$columns = array_keys($values);
|
||||
$placeholders = array_map(fn($column) => ':' . $column, $columns);
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO inv_equipment (" . implode(', ', $columns) . ", created_by)
|
||||
VALUES (" . implode(', ', $placeholders) . ", :created_by)"
|
||||
);
|
||||
$stmt->execute($values + ['created_by' => $currentUserId]);
|
||||
|
||||
$newId = (int)$pdo->lastInsertId();
|
||||
mnt_log($pdo, 'equipment_created', $newId, null, null, $currentUserId, $values);
|
||||
|
||||
mnt_json_ok(['id' => $newId]);
|
||||
} catch (Throwable $e) {
|
||||
mnt_json_fail('Errore: ' . $e->getMessage());
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/auth_check.php';
|
||||
|
||||
mnt_require_permission('maintenance.manage');
|
||||
|
||||
/**
|
||||
* Registers (or updates) one intervention against a maintenance and
|
||||
* recomputes the maintenance schedule.
|
||||
*
|
||||
* The operator signature arrives as a data URL from the tablet canvas and
|
||||
* is stored as a PNG in attachments/.
|
||||
*/
|
||||
try {
|
||||
$pdo = mnt_pdo();
|
||||
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
$maintenanceId = (int)($_POST['maintenance_id'] ?? 0);
|
||||
$performedAt = trim((string)($_POST['performed_at'] ?? ''));
|
||||
$status = (string)($_POST['status'] ?? 'completed');
|
||||
$result = trim((string)($_POST['result'] ?? ''));
|
||||
$notes = trim((string)($_POST['notes'] ?? ''));
|
||||
$materials = trim((string)($_POST['materials'] ?? ''));
|
||||
$operatorId = (int)($_POST['operator_employee_id'] ?? 0);
|
||||
$operatorName = trim((string)($_POST['operator_name'] ?? ''));
|
||||
$supplierId = (int)($_POST['supplier_id'] ?? 0);
|
||||
$signatureData = (string)($_POST['signature'] ?? '');
|
||||
$clearSignature = !empty($_POST['clear_signature']);
|
||||
|
||||
if ($performedAt === '' || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $performedAt)) {
|
||||
mnt_json_fail('La data dell\'intervento è obbligatoria.');
|
||||
}
|
||||
|
||||
$validStatuses = mnt_lookup($pdo, 'maint_status', ['planned' => '', 'in_progress' => '', 'completed' => '']);
|
||||
if (!array_key_exists($status, $validStatuses)) {
|
||||
mnt_json_fail('Stato intervento non valido.');
|
||||
}
|
||||
|
||||
$validResults = mnt_lookup($pdo, 'maint_result', []);
|
||||
if ($result !== '' && $validResults && !array_key_exists($result, $validResults)) {
|
||||
mnt_json_fail('Esito intervento non valido.');
|
||||
}
|
||||
|
||||
// Existing row: maintenance is taken from the record, not from the client
|
||||
if ($id > 0) {
|
||||
$stmt = $pdo->prepare("SELECT * FROM maint_interventions WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$existing = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$existing) {
|
||||
mnt_json_fail('Intervento non trovato.');
|
||||
}
|
||||
|
||||
$maintenanceId = (int)$existing['maintenance_id'];
|
||||
$equipmentId = (int)$existing['equipment_id'];
|
||||
$signaturePath = $existing['signature_path'];
|
||||
} else {
|
||||
$stmt = $pdo->prepare("SELECT equipment_id FROM maint_maintenances WHERE id = ?");
|
||||
$stmt->execute([$maintenanceId]);
|
||||
$equipmentId = (int)$stmt->fetchColumn();
|
||||
|
||||
if (!$equipmentId) {
|
||||
mnt_json_fail('Manutenzione non trovata.');
|
||||
}
|
||||
|
||||
$signaturePath = null;
|
||||
}
|
||||
|
||||
// Signature handling
|
||||
if ($clearSignature && $signaturePath) {
|
||||
mnt_delete_stored_file((string)$signaturePath);
|
||||
$signaturePath = null;
|
||||
}
|
||||
|
||||
if ($signatureData !== '' && preg_match('#^data:image/png;base64,#', $signatureData)) {
|
||||
$binary = base64_decode(substr($signatureData, strlen('data:image/png;base64,')), true);
|
||||
|
||||
if ($binary === false || strlen($binary) > 2 * 1024 * 1024) {
|
||||
mnt_json_fail('Firma non valida.');
|
||||
}
|
||||
|
||||
$uploadDir = __DIR__ . '/../attachments/';
|
||||
if (!is_dir($uploadDir) && !mkdir($uploadDir, 0755, true) && !is_dir($uploadDir)) {
|
||||
mnt_json_fail('Cartella allegati non scrivibile.');
|
||||
}
|
||||
|
||||
$newSignature = uniqid('sig_', true) . '.png';
|
||||
$newSignature = preg_replace('/[^A-Za-z0-9._-]/', '_', $newSignature);
|
||||
|
||||
if (file_put_contents($uploadDir . $newSignature, $binary) === false) {
|
||||
mnt_json_fail('Impossibile salvare la firma.');
|
||||
}
|
||||
|
||||
if ($signaturePath) {
|
||||
mnt_delete_stored_file((string)$signaturePath);
|
||||
}
|
||||
|
||||
$signaturePath = $newSignature;
|
||||
}
|
||||
|
||||
$values = [
|
||||
'performed_at' => $performedAt,
|
||||
'status' => $status,
|
||||
'result' => $result ?: null,
|
||||
'notes' => $notes ?: null,
|
||||
'materials' => $materials ?: null,
|
||||
'operator_employee_id' => $operatorId ?: null,
|
||||
'operator_name' => $operatorName ?: null,
|
||||
'supplier_id' => $supplierId ?: null,
|
||||
'signature_path' => $signaturePath,
|
||||
];
|
||||
|
||||
if ($id > 0) {
|
||||
$assignments = [];
|
||||
foreach (array_keys($values) as $column) {
|
||||
$assignments[] = "$column = :$column";
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE maint_interventions SET " . implode(', ', $assignments) . " WHERE id = :id");
|
||||
$stmt->execute($values + ['id' => $id]);
|
||||
|
||||
$interventionId = $id;
|
||||
$action = 'intervention_updated';
|
||||
} else {
|
||||
$values['maintenance_id'] = $maintenanceId;
|
||||
$values['equipment_id'] = $equipmentId;
|
||||
$values['created_by'] = $currentUserId;
|
||||
|
||||
$columns = array_keys($values);
|
||||
$placeholders = array_map(fn($column) => ':' . $column, $columns);
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO maint_interventions (" . implode(', ', $columns) . ")
|
||||
VALUES (" . implode(', ', $placeholders) . ")"
|
||||
);
|
||||
$stmt->execute($values);
|
||||
|
||||
$interventionId = (int)$pdo->lastInsertId();
|
||||
$action = 'intervention_created';
|
||||
}
|
||||
|
||||
// Only completed interventions move the next due date; this call handles it
|
||||
mnt_recalc_maintenance($pdo, $maintenanceId);
|
||||
|
||||
mnt_log($pdo, $action, $equipmentId, $maintenanceId, $interventionId, $currentUserId, $values);
|
||||
|
||||
$stmt = $pdo->prepare("SELECT last_done_date, next_due_date FROM maint_maintenances WHERE id = ?");
|
||||
$stmt->execute([$maintenanceId]);
|
||||
$schedule = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
|
||||
|
||||
mnt_json_ok(['id' => $interventionId, 'schedule' => $schedule]);
|
||||
} catch (Throwable $e) {
|
||||
mnt_json_fail('Errore: ' . $e->getMessage());
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/auth_check.php';
|
||||
|
||||
mnt_require_permission('maintenance.manage');
|
||||
|
||||
try {
|
||||
$pdo = mnt_pdo();
|
||||
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
$equipmentId = (int)($_POST['equipment_id'] ?? 0);
|
||||
$code = trim((string)($_POST['code'] ?? ''));
|
||||
$title = trim((string)($_POST['title'] ?? ''));
|
||||
$description = trim((string)($_POST['description'] ?? ''));
|
||||
$interventionType = (string)($_POST['intervention_type'] ?? 'scheduled');
|
||||
$executionType = (string)($_POST['execution_type'] ?? 'internal');
|
||||
$isCritical = !empty($_POST['is_critical']) ? 1 : 0;
|
||||
$frequencyUnit = (string)($_POST['frequency_unit'] ?? 'month');
|
||||
$frequencyValue = (int)($_POST['frequency_value'] ?? 0);
|
||||
$frequencyNote = trim((string)($_POST['frequency_note'] ?? ''));
|
||||
$alertDays = (int)($_POST['alert_days'] ?? 7);
|
||||
$assigneeId = (int)($_POST['assignee_employee_id'] ?? 0);
|
||||
$supervisorId = (int)($_POST['supervisor_employee_id'] ?? 0);
|
||||
$supplierId = (int)($_POST['supplier_id'] ?? 0);
|
||||
$isActive = isset($_POST['is_active']) ? (int)!empty($_POST['is_active']) : 1;
|
||||
$notes = trim((string)($_POST['notes'] ?? ''));
|
||||
|
||||
if ($title === '') {
|
||||
mnt_json_fail('Il titolo è obbligatorio.');
|
||||
}
|
||||
if (!array_key_exists($interventionType, mnt_intervention_types())) {
|
||||
mnt_json_fail('Tipo di intervento non valido.');
|
||||
}
|
||||
if (!array_key_exists($executionType, mnt_execution_types())) {
|
||||
mnt_json_fail('Tipo di esecuzione non valido.');
|
||||
}
|
||||
if (!array_key_exists($frequencyUnit, mnt_frequency_units())) {
|
||||
mnt_json_fail('Unità di frequenza non valida.');
|
||||
}
|
||||
if ($frequencyUnit !== 'on_demand' && $frequencyValue <= 0) {
|
||||
mnt_json_fail('Indica ogni quanto va eseguita la manutenzione, oppure scegli "Al bisogno".');
|
||||
}
|
||||
if ($executionType === 'external' && $supplierId <= 0) {
|
||||
mnt_json_fail('Per una manutenzione esterna indica il fornitore.');
|
||||
}
|
||||
|
||||
if ($frequencyUnit === 'on_demand') {
|
||||
$frequencyValue = null;
|
||||
}
|
||||
|
||||
$values = [
|
||||
'code' => $code ?: null,
|
||||
'title' => $title,
|
||||
'description' => $description ?: null,
|
||||
'intervention_type' => $interventionType,
|
||||
'execution_type' => $executionType,
|
||||
'is_critical' => $isCritical,
|
||||
'frequency_value' => $frequencyValue,
|
||||
'frequency_unit' => $frequencyUnit,
|
||||
'frequency_note' => $frequencyNote ?: null,
|
||||
'alert_days' => max(0, $alertDays),
|
||||
'assignee_employee_id' => $assigneeId ?: null,
|
||||
'supervisor_employee_id' => $supervisorId ?: null,
|
||||
'supplier_id' => $supplierId ?: null,
|
||||
'is_active' => $isActive,
|
||||
'notes' => $notes ?: null,
|
||||
];
|
||||
|
||||
if ($id > 0) {
|
||||
$stmt = $pdo->prepare("SELECT equipment_id FROM maint_maintenances WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$equipmentId = (int)$stmt->fetchColumn();
|
||||
|
||||
if (!$equipmentId) {
|
||||
mnt_json_fail('Manutenzione non trovata.');
|
||||
}
|
||||
|
||||
$assignments = [];
|
||||
foreach (array_keys($values) as $column) {
|
||||
$assignments[] = "$column = :$column";
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE maint_maintenances SET " . implode(', ', $assignments) . " WHERE id = :id");
|
||||
$stmt->execute($values + ['id' => $id]);
|
||||
|
||||
// frequency may have changed — the due date must follow
|
||||
mnt_recalc_maintenance($pdo, $id);
|
||||
mnt_log($pdo, 'maintenance_updated', $equipmentId, $id, null, $currentUserId, $values);
|
||||
|
||||
mnt_json_ok(['id' => $id]);
|
||||
}
|
||||
|
||||
if ($equipmentId <= 0) {
|
||||
mnt_json_fail('Attrezzatura non valida.');
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT id FROM inv_equipment WHERE id = ?");
|
||||
$stmt->execute([$equipmentId]);
|
||||
if (!$stmt->fetchColumn()) {
|
||||
mnt_json_fail('Attrezzatura non trovata.');
|
||||
}
|
||||
|
||||
$values['equipment_id'] = $equipmentId;
|
||||
$values['created_by'] = $currentUserId;
|
||||
|
||||
$columns = array_keys($values);
|
||||
$placeholders = array_map(fn($column) => ':' . $column, $columns);
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO maint_maintenances (" . implode(', ', $columns) . ")
|
||||
VALUES (" . implode(', ', $placeholders) . ")"
|
||||
);
|
||||
$stmt->execute($values);
|
||||
|
||||
$newId = (int)$pdo->lastInsertId();
|
||||
mnt_log($pdo, 'maintenance_created', $equipmentId, $newId, null, $currentUserId, $values);
|
||||
|
||||
mnt_json_ok(['id' => $newId]);
|
||||
} catch (Throwable $e) {
|
||||
mnt_json_fail('Errore: ' . $e->getMessage());
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/auth_check.php';
|
||||
|
||||
mnt_require_permission('maintenance.manage');
|
||||
|
||||
try {
|
||||
$pdo = mnt_pdo();
|
||||
|
||||
$fileId = (int)($_POST['file_id'] ?? 0);
|
||||
|
||||
if ($fileId <= 0) {
|
||||
mnt_json_fail('ID non valido.');
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT equipment_id, kind FROM inv_equipment_files WHERE id = ?");
|
||||
$stmt->execute([$fileId]);
|
||||
$file = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$file) {
|
||||
mnt_json_fail('Immagine non trovata.');
|
||||
}
|
||||
if ($file['kind'] !== 'photo') {
|
||||
mnt_json_fail('Solo una foto può essere usata come copertina.');
|
||||
}
|
||||
|
||||
$pdo->prepare("UPDATE inv_equipment SET cover_file_id = ? WHERE id = ?")
|
||||
->execute([$fileId, (int)$file['equipment_id']]);
|
||||
|
||||
mnt_log($pdo, 'cover_changed', (int)$file['equipment_id'], null, null, $currentUserId);
|
||||
|
||||
mnt_json_ok();
|
||||
} catch (Throwable $e) {
|
||||
mnt_json_fail('Errore: ' . $e->getMessage());
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/auth_check.php';
|
||||
|
||||
mnt_require_permission('maintenance.manage');
|
||||
|
||||
/**
|
||||
* Multi-file upload for the three attachment tables.
|
||||
* scope = equipment | maintenance | intervention
|
||||
* kind = file | photo (equipment only; photos feed the gallery)
|
||||
*/
|
||||
try {
|
||||
$pdo = mnt_pdo();
|
||||
|
||||
$scope = (string)($_POST['scope'] ?? 'equipment');
|
||||
$ownerId = (int)($_POST['owner_id'] ?? 0);
|
||||
$kind = ($_POST['kind'] ?? 'file') === 'photo' ? 'photo' : 'file';
|
||||
|
||||
$config = [
|
||||
'equipment' => ['table' => 'inv_equipment_files', 'owner' => 'equipment_id', 'parent' => 'inv_equipment'],
|
||||
'maintenance' => ['table' => 'maint_maintenance_files', 'owner' => 'maintenance_id', 'parent' => 'maint_maintenances'],
|
||||
'intervention' => ['table' => 'maint_intervention_files', 'owner' => 'intervention_id', 'parent' => 'maint_interventions'],
|
||||
];
|
||||
|
||||
if (!isset($config[$scope])) {
|
||||
mnt_json_fail('Scope non valido.');
|
||||
}
|
||||
if ($ownerId <= 0) {
|
||||
mnt_json_fail('ID non valido.');
|
||||
}
|
||||
if (empty($_FILES['files']['name'][0])) {
|
||||
mnt_json_fail('Nessun file selezionato.');
|
||||
}
|
||||
|
||||
$target = $config[$scope];
|
||||
|
||||
$stmt = $pdo->prepare("SELECT id FROM {$target['parent']} WHERE id = ?");
|
||||
$stmt->execute([$ownerId]);
|
||||
if (!$stmt->fetchColumn()) {
|
||||
mnt_json_fail('Record collegato non trovato.');
|
||||
}
|
||||
|
||||
$columns = $scope === 'equipment'
|
||||
? "({$target['owner']}, kind, original_name, stored_name, mime_type, size, uploaded_by)"
|
||||
: "({$target['owner']}, original_name, stored_name, mime_type, size, uploaded_by)";
|
||||
$placeholders = $scope === 'equipment' ? '(?, ?, ?, ?, ?, ?, ?)' : '(?, ?, ?, ?, ?, ?)';
|
||||
|
||||
$insert = $pdo->prepare("INSERT INTO {$target['table']} $columns VALUES $placeholders");
|
||||
|
||||
$uploaded = [];
|
||||
$errors = [];
|
||||
$fileCount = count($_FILES['files']['name']);
|
||||
|
||||
for ($i = 0; $i < $fileCount; $i++) {
|
||||
$file = [
|
||||
'name' => $_FILES['files']['name'][$i],
|
||||
'type' => $_FILES['files']['type'][$i],
|
||||
'tmp_name' => $_FILES['files']['tmp_name'][$i],
|
||||
'error' => $_FILES['files']['error'][$i],
|
||||
'size' => $_FILES['files']['size'][$i],
|
||||
];
|
||||
|
||||
try {
|
||||
$stored = mnt_store_upload($file, $scope);
|
||||
} catch (RuntimeException $e) {
|
||||
$errors[] = $file['name'] . ': ' . $e->getMessage();
|
||||
continue;
|
||||
}
|
||||
|
||||
// A file uploaded as a photo but not actually an image is kept as a file
|
||||
$rowKind = ($kind === 'photo' && $stored['is_image']) ? 'photo' : 'file';
|
||||
|
||||
$values = $scope === 'equipment'
|
||||
? [$ownerId, $rowKind, $stored['original_name'], $stored['stored_name'], $stored['mime_type'], $stored['size'], $currentUserId]
|
||||
: [$ownerId, $stored['original_name'], $stored['stored_name'], $stored['mime_type'], $stored['size'], $currentUserId];
|
||||
|
||||
$insert->execute($values);
|
||||
$uploaded[] = ['id' => (int)$pdo->lastInsertId(), 'name' => $stored['original_name']];
|
||||
}
|
||||
|
||||
if (!$uploaded) {
|
||||
mnt_json_fail($errors ? implode('; ', $errors) : 'Nessun file caricato.');
|
||||
}
|
||||
|
||||
// The first photo of an item becomes its cover automatically
|
||||
if ($scope === 'equipment' && $kind === 'photo') {
|
||||
$stmt = $pdo->prepare("SELECT cover_file_id FROM inv_equipment WHERE id = ?");
|
||||
$stmt->execute([$ownerId]);
|
||||
if (!$stmt->fetchColumn()) {
|
||||
$pdo->prepare("UPDATE inv_equipment SET cover_file_id = ? WHERE id = ?")
|
||||
->execute([$uploaded[0]['id'], $ownerId]);
|
||||
}
|
||||
}
|
||||
|
||||
$equipmentId = $scope === 'equipment' ? $ownerId : null;
|
||||
$maintenanceId = $scope === 'maintenance' ? $ownerId : null;
|
||||
$interventionId = $scope === 'intervention' ? $ownerId : null;
|
||||
|
||||
mnt_log($pdo, 'file_added', $equipmentId, $maintenanceId, $interventionId, $currentUserId, null,
|
||||
implode(', ', array_column($uploaded, 'name')));
|
||||
|
||||
mnt_json_ok(['uploaded' => $uploaded, 'errors' => $errors]);
|
||||
} catch (Throwable $e) {
|
||||
mnt_json_fail('Errore: ' . $e->getMessage());
|
||||
}
|
||||
Reference in New Issue
Block a user