bulk operations
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/**
|
||||
* Bulk "renew": set a common completed_date on the selected training records
|
||||
*/
|
||||
require_once(__DIR__ . '/../hr_auth_check.php');
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['success' => false, 'message' => 'Metodo non consentito.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// $pdo and $currentUserId from hr_auth_check.php
|
||||
|
||||
$completedDate = trim($_POST['completed_date'] ?? '');
|
||||
$ids = $_POST['training_ids'] ?? [];
|
||||
|
||||
if (!is_array($ids)) {
|
||||
$ids = [];
|
||||
}
|
||||
$ids = array_values(array_unique(array_filter(array_map('intval', $ids), fn($v) => $v > 0)));
|
||||
|
||||
if ($completedDate === '' || !DateTime::createFromFormat('Y-m-d', $completedDate)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Indicare una data valida.']);
|
||||
exit;
|
||||
}
|
||||
if (empty($ids)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Selezionare almeno un record.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Load each record with its topic default frequency
|
||||
$rowStmt = $pdo->prepare("
|
||||
SELECT et.id, et.employee_id, et.completed_date, et.next_due_date,
|
||||
et.update_frequency_months, tt.default_frequency_months
|
||||
FROM employee_trainings et
|
||||
JOIN training_topics tt ON tt.id = et.training_topic_id
|
||||
WHERE et.id = :id
|
||||
");
|
||||
$upd = $pdo->prepare("
|
||||
UPDATE employee_trainings
|
||||
SET completed_date = :cd, next_due_date = :nd, updated_at = NOW()
|
||||
WHERE id = :id
|
||||
");
|
||||
$logStmt = $pdo->prepare("
|
||||
INSERT INTO employee_training_log
|
||||
(employee_id, training_id, action, field, old_value, new_value, changed_by, changed_at)
|
||||
VALUES
|
||||
(:eid, :tid, 'updated', :field, :old_v, :new_v, :cb, NOW())
|
||||
");
|
||||
|
||||
$updated = 0;
|
||||
foreach ($ids as $id) {
|
||||
$rowStmt->execute(['id' => $id]);
|
||||
$row = $rowStmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$row) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Effective frequency: per-record override, else topic default
|
||||
$effFreq = $row['update_frequency_months'] !== null
|
||||
? (int)$row['update_frequency_months']
|
||||
: ($row['default_frequency_months'] !== null ? (int)$row['default_frequency_months'] : null);
|
||||
|
||||
$nextDue = null;
|
||||
if ($effFreq !== null && $effFreq > 0) {
|
||||
$d = DateTime::createFromFormat('Y-m-d', $completedDate);
|
||||
if ($d) {
|
||||
$d->modify('+' . $effFreq . ' months');
|
||||
$nextDue = $d->format('Y-m-d');
|
||||
}
|
||||
}
|
||||
|
||||
$upd->execute(['cd' => $completedDate, 'nd' => $nextDue, 'id' => $id]);
|
||||
|
||||
if ((string)$row['completed_date'] !== (string)$completedDate) {
|
||||
$logStmt->execute([
|
||||
'eid' => $row['employee_id'], 'tid' => $id, 'field' => 'completed_date',
|
||||
'old_v' => $row['completed_date'], 'new_v' => $completedDate, 'cb' => $currentUserId,
|
||||
]);
|
||||
}
|
||||
if ((string)$row['next_due_date'] !== (string)$nextDue) {
|
||||
$logStmt->execute([
|
||||
'eid' => $row['employee_id'], 'tid' => $id, 'field' => 'next_due_date',
|
||||
'old_v' => $row['next_due_date'], 'new_v' => $nextDue, 'cb' => $currentUserId,
|
||||
]);
|
||||
}
|
||||
$updated++;
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'updated' => $updated,
|
||||
'message' => $updated . ' record aggiornat' . ($updated === 1 ? 'o' : 'i') . '.',
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/**
|
||||
* Calendar events for the training calendar (training_calendar.php).
|
||||
* Returns FullCalendar event objects for the *current* training record per
|
||||
* (employee, topic) that has a next_due_date, colored by computed status.
|
||||
* HR-only.
|
||||
*/
|
||||
require_once(__DIR__ . '/../hr_auth_check.php');
|
||||
header('Content-Type: application/json');
|
||||
|
||||
try {
|
||||
// $pdo and $currentUserId provided by hr_auth_check.php
|
||||
|
||||
$start = $_GET['start'] ?? null;
|
||||
$end = $_GET['end'] ?? null;
|
||||
$fStatus = isset($_GET['status']) ? trim($_GET['status']) : '';
|
||||
$fDept = isset($_GET['department_id']) && $_GET['department_id'] !== '' ? (int)$_GET['department_id'] : 0;
|
||||
$fTopic = isset($_GET['topic_id']) && $_GET['topic_id'] !== '' ? (int)$_GET['topic_id'] : 0;
|
||||
$fEmp = isset($_GET['employee_id']) && $_GET['employee_id'] !== '' ? (int)$_GET['employee_id'] : 0;
|
||||
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
// Deadlines only (one-time trainings have no next_due_date)
|
||||
$where[] = "et.next_due_date IS NOT NULL";
|
||||
|
||||
// Only the most recent record per (employee, topic)
|
||||
$where[] = "NOT EXISTS (
|
||||
SELECT 1 FROM employee_trainings et2
|
||||
WHERE et2.employee_id = et.employee_id
|
||||
AND et2.training_topic_id = et.training_topic_id
|
||||
AND (et2.completed_date > et.completed_date
|
||||
OR (et2.completed_date = et.completed_date AND et2.id > et.id))
|
||||
)";
|
||||
|
||||
if ($start && $end) {
|
||||
$where[] = "et.next_due_date >= :start AND et.next_due_date <= :end";
|
||||
$params['start'] = $start;
|
||||
$params['end'] = $end;
|
||||
}
|
||||
if ($fDept > 0) { $where[] = "e.department_id = :did"; $params['did'] = $fDept; }
|
||||
if ($fTopic > 0) { $where[] = "et.training_topic_id = :tid"; $params['tid'] = $fTopic; }
|
||||
if ($fEmp > 0) { $where[] = "et.employee_id = :eid"; $params['eid'] = $fEmp; }
|
||||
|
||||
$whereSql = 'WHERE ' . implode(' AND ', $where);
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT et.id, et.employee_id, et.next_due_date, et.reminder_days,
|
||||
tt.name AS topic_name, tt.default_reminder_days AS topic_default_rem,
|
||||
e.first_name, e.last_name
|
||||
FROM employee_trainings et
|
||||
JOIN training_topics tt ON tt.id = et.training_topic_id
|
||||
JOIN employees e ON e.id = et.employee_id
|
||||
$whereSql
|
||||
");
|
||||
$stmt->execute($params);
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$today = new DateTime('today');
|
||||
$events = [];
|
||||
|
||||
foreach ($rows as $r) {
|
||||
$rem = $r['reminder_days'] !== null
|
||||
? (int)$r['reminder_days']
|
||||
: ($r['topic_default_rem'] !== null ? (int)$r['topic_default_rem'] : 30);
|
||||
|
||||
$due = DateTime::createFromFormat('Y-m-d', $r['next_due_date']);
|
||||
if (!$due) continue;
|
||||
$daysLeft = (int)$today->diff($due)->format('%r%a');
|
||||
|
||||
if ($daysLeft < 0) { $code = 'expired'; $color = '#dc3545'; }
|
||||
elseif ($daysLeft <= $rem){ $code = 'due_soon'; $color = '#e8930c'; }
|
||||
else { $code = 'compliant'; $color = '#198754'; }
|
||||
|
||||
if ($fStatus !== '' && $fStatus !== $code) continue;
|
||||
|
||||
$name = trim($r['first_name'] . ' ' . $r['last_name']);
|
||||
$events[] = [
|
||||
'id' => (int)$r['id'],
|
||||
'title' => $name . ' — ' . $r['topic_name'],
|
||||
'start' => $r['next_due_date'],
|
||||
'allDay' => true,
|
||||
'backgroundColor' => $color,
|
||||
'borderColor' => $color,
|
||||
'url' => 'employee-profile.php?id=' . (int)$r['employee_id'] . '#tab-training',
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode($events);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode([]);
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
/**
|
||||
* Bulk-create training records: one employee_trainings row per selected employee,
|
||||
* all sharing the same course + parameters (a single training "session").
|
||||
* Mirrors the next_due_date logic of ajax/employee_profile/save_training.php.
|
||||
* HR-only.
|
||||
*/
|
||||
require_once(__DIR__ . '/../hr_auth_check.php');
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['success' => false, 'message' => 'Metodo non consentito.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// $pdo and $currentUserId from hr_auth_check.php
|
||||
|
||||
$topicId = (int)($_POST['training_topic_id'] ?? 0);
|
||||
$completedDate = trim($_POST['completed_date'] ?? '');
|
||||
$deliveredBy = trim($_POST['delivered_by'] ?? '');
|
||||
$description = trim($_POST['description'] ?? '');
|
||||
$trainingType = trim($_POST['training_type'] ?? 'initial');
|
||||
$freqRaw = $_POST['update_frequency_months'] ?? '';
|
||||
$remRaw = $_POST['reminder_days'] ?? '';
|
||||
$employeeIds = $_POST['employee_ids'] ?? [];
|
||||
|
||||
if (!is_array($employeeIds)) {
|
||||
$employeeIds = [];
|
||||
}
|
||||
$employeeIds = array_values(array_unique(array_filter(array_map('intval', $employeeIds), fn($v) => $v > 0)));
|
||||
|
||||
if ($topicId <= 0) {
|
||||
echo json_encode(['success' => false, 'message' => 'Selezionare un corso.']);
|
||||
exit;
|
||||
}
|
||||
if ($completedDate === '' || !DateTime::createFromFormat('Y-m-d', $completedDate)) {
|
||||
echo json_encode(['success' => false, 'message' => 'La data di completamento è obbligatoria.']);
|
||||
exit;
|
||||
}
|
||||
if (empty($employeeIds)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Selezionare almeno un dipendente.']);
|
||||
exit;
|
||||
}
|
||||
if (!in_array($trainingType, ['initial', 'refresher'], true)) {
|
||||
$trainingType = 'initial';
|
||||
}
|
||||
|
||||
$topicStmt = $pdo->prepare("SELECT default_frequency_months, default_reminder_days FROM training_topics WHERE id = :id");
|
||||
$topicStmt->execute(['id' => $topicId]);
|
||||
$topic = $topicStmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$topic) {
|
||||
echo json_encode(['success' => false, 'message' => 'Corso non trovato.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$freq = ($freqRaw === '' || $freqRaw === null) ? null : max(0, (int)$freqRaw);
|
||||
$rem = ($remRaw === '' || $remRaw === null) ? null : max(0, (int)$remRaw);
|
||||
|
||||
/* Effective frequency → next_due_date (same for every employee: same date + same frequency) */
|
||||
$effFreq = $freq !== null ? $freq : ($topic['default_frequency_months'] !== null ? (int)$topic['default_frequency_months'] : null);
|
||||
$nextDue = null;
|
||||
if ($effFreq !== null && $effFreq > 0) {
|
||||
$d = DateTime::createFromFormat('Y-m-d', $completedDate);
|
||||
if ($d) {
|
||||
$d->modify('+' . (int)$effFreq . ' months');
|
||||
$nextDue = $d->format('Y-m-d');
|
||||
}
|
||||
}
|
||||
|
||||
$deliveredBy = $deliveredBy !== '' ? $deliveredBy : null;
|
||||
$description = $description !== '' ? $description : null;
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Only insert for employees that actually exist
|
||||
$checkEmp = $pdo->prepare("SELECT id FROM employees WHERE id = :id");
|
||||
|
||||
$ins = $pdo->prepare("
|
||||
INSERT INTO employee_trainings
|
||||
(employee_id, training_topic_id, completed_date,
|
||||
delivered_by, description,
|
||||
training_type, update_frequency_months, reminder_days, next_due_date,
|
||||
created_by, created_at, updated_at)
|
||||
VALUES
|
||||
(:eid, :tid, :completed_date,
|
||||
:delivered_by, :description,
|
||||
:training_type, :freq, :rem, :next_due,
|
||||
:cb, NOW(), NOW())
|
||||
");
|
||||
$logStmt = $pdo->prepare("
|
||||
INSERT INTO employee_training_log
|
||||
(employee_id, training_id, action, field, old_value, new_value, changed_by, changed_at)
|
||||
VALUES
|
||||
(:eid, :tid, 'created', NULL, NULL, NULL, :cb, NOW())
|
||||
");
|
||||
|
||||
$created = 0;
|
||||
foreach ($employeeIds as $eid) {
|
||||
$checkEmp->execute(['id' => $eid]);
|
||||
if (!$checkEmp->fetchColumn()) {
|
||||
continue;
|
||||
}
|
||||
$ins->execute([
|
||||
'eid' => $eid,
|
||||
'tid' => $topicId,
|
||||
'completed_date' => $completedDate,
|
||||
'delivered_by' => $deliveredBy,
|
||||
'description' => $description,
|
||||
'training_type' => $trainingType,
|
||||
'freq' => $freq,
|
||||
'rem' => $rem,
|
||||
'next_due' => $nextDue,
|
||||
'cb' => $currentUserId,
|
||||
]);
|
||||
$newId = (int)$pdo->lastInsertId();
|
||||
$logStmt->execute(['eid' => $eid, 'tid' => $newId, 'cb' => $currentUserId]);
|
||||
$created++;
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'created' => $created,
|
||||
'message' => $created . ' formazion' . ($created === 1 ? 'e registrata' : 'i registrate') . '.',
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
Reference in New Issue
Block a user