bulk operations for dpi
This commit is contained in:
@@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Bulk-assign a single DPI (PPE) item to several employees at once:
|
||||||
|
* one employee_ppe row per selected employee, all sharing the same
|
||||||
|
* item name / delivery date / delivered-by / notes.
|
||||||
|
* Mirrors ajax/trainings/save_bulk_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
|
||||||
|
|
||||||
|
$itemName = trim($_POST['item_name'] ?? '');
|
||||||
|
$deliveryDate = trim($_POST['delivery_date'] ?? '');
|
||||||
|
$deliveredBy = trim($_POST['delivered_by'] ?? '');
|
||||||
|
$notes = trim($_POST['notes'] ?? '');
|
||||||
|
$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 ($itemName === '') {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Il nome del DPI è obbligatorio.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if ($deliveryDate !== '' && !DateTime::createFromFormat('Y-m-d', $deliveryDate)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Data di consegna non valida.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if (empty($employeeIds)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Selezionare almeno un dipendente.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$deliveryDate = $deliveryDate === '' ? null : $deliveryDate;
|
||||||
|
$deliveredBy = $deliveredBy !== '' ? $deliveredBy : null;
|
||||||
|
$notes = $notes !== '' ? $notes : 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_ppe
|
||||||
|
(employee_id, item_name, delivery_date, delivered_by, notes, created_by, created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
(:employee_id, :item_name, :delivery_date, :delivered_by, :notes, :created_by, NOW(), NOW())
|
||||||
|
");
|
||||||
|
|
||||||
|
$created = 0;
|
||||||
|
foreach ($employeeIds as $eid) {
|
||||||
|
$checkEmp->execute(['id' => $eid]);
|
||||||
|
if (!$checkEmp->fetchColumn()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$ins->execute([
|
||||||
|
'employee_id' => $eid,
|
||||||
|
'item_name' => $itemName,
|
||||||
|
'delivery_date' => $deliveryDate,
|
||||||
|
'delivered_by' => $deliveredBy,
|
||||||
|
'notes' => $notes,
|
||||||
|
'created_by' => $currentUserId,
|
||||||
|
]);
|
||||||
|
$created++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'created' => $created,
|
||||||
|
'message' => 'DPI assegnato a ' . $created . ' dipendent' . ($created === 1 ? 'e' : 'i') . '.',
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -260,6 +260,13 @@ $sql = "
|
|||||||
$stmtEmployees = $pdo->query($sql);
|
$stmtEmployees = $pdo->query($sql);
|
||||||
$employees = $stmtEmployees->fetchAll(PDO::FETCH_ASSOC);
|
$employees = $stmtEmployees->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Employees for the bulk-DPI multiselect (alphabetical, with department for the "whole department" shortcut)
|
||||||
|
$employeesForSelect = $pdo->query("
|
||||||
|
SELECT id, first_name, last_name, employee_code, department_id
|
||||||
|
FROM employees
|
||||||
|
ORDER BY last_name, first_name
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
// Job roles for the dropdown
|
// Job roles for the dropdown
|
||||||
$jobRoles = $pdo->query("
|
$jobRoles = $pdo->query("
|
||||||
SELECT id, name FROM job_roles WHERE is_active = 1 ORDER BY sort_order, name
|
SELECT id, name FROM job_roles WHERE is_active = 1 ORDER BY sort_order, name
|
||||||
@@ -463,6 +470,9 @@ $allSkills = $stmtSkills->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
<button class="btn btn-matrix" onclick="location.href='skill_matrix.php'">
|
<button class="btn btn-matrix" onclick="location.href='skill_matrix.php'">
|
||||||
📊 Matrice Skills
|
📊 Matrice Skills
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button" class="btn btn-matrix" id="btnBulkPpe" data-bs-toggle="modal" data-bs-target="#bulkPpeModal">
|
||||||
|
🦺 Assegna DPI
|
||||||
|
</button>
|
||||||
<button class="btn btn-add" data-bs-toggle="modal" data-bs-target="#addEmployeeModal">
|
<button class="btn btn-add" data-bs-toggle="modal" data-bs-target="#addEmployeeModal">
|
||||||
➕ Aggiungi Dipendente
|
➕ Aggiungi Dipendente
|
||||||
</button>
|
</button>
|
||||||
@@ -854,6 +864,75 @@ $allSkills = $stmtSkills->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- BULK DPI MODAL -->
|
||||||
|
<div class="modal fade" id="bulkPpeModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">🦺 Assegna DPI a più dipendenti</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
|
||||||
|
</div>
|
||||||
|
<form id="bulkPpeForm">
|
||||||
|
<div class="modal-body" style="max-height:65vh; overflow-y:auto;">
|
||||||
|
<p class="text-muted small">Registra la consegna dello stesso DPI, con gli stessi dati, per più dipendenti contemporaneamente.</p>
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-12 col-md-6">
|
||||||
|
<label class="form-label fw-semibold">DPI <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" id="bulkPpeItemName" class="form-control" maxlength="255" placeholder="es. Casco, Guanti, Scarpe antinfortunistiche" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 col-md-3">
|
||||||
|
<label class="form-label fw-semibold">Data consegna</label>
|
||||||
|
<input type="date" id="bulkPpeDeliveryDate" class="form-control" value="<?= date('Y-m-d') ?>">
|
||||||
|
</div>
|
||||||
|
<div class="col-6 col-md-3">
|
||||||
|
<label class="form-label fw-semibold">Consegnato da</label>
|
||||||
|
<input type="text" id="bulkPpeDeliveredBy" class="form-control" maxlength="255" placeholder="Nome o azienda">
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label fw-semibold">Note</label>
|
||||||
|
<textarea id="bulkPpeNotes" class="form-control" rows="2" placeholder="Opzionale"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<hr class="my-1">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label fw-semibold">Dipendenti <span class="text-danger">*</span></label>
|
||||||
|
<div class="d-flex flex-wrap gap-2 mb-2 align-items-end">
|
||||||
|
<div>
|
||||||
|
<select id="bulkPpeDept" class="form-select form-select-sm" style="min-width:180px">
|
||||||
|
<option value="">— Reparto —</option>
|
||||||
|
<?php foreach ($departments as $d): ?>
|
||||||
|
<option value="<?= (int)$d['id'] ?>"><?= htmlspecialchars($d['name'], ENT_QUOTES, 'UTF-8') ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-primary" id="bulkPpeAddDept">+ Aggiungi reparto</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary" id="bulkPpeSelectAll">Tutti</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary" id="bulkPpeClear">Pulisci</button>
|
||||||
|
</div>
|
||||||
|
<select id="bulkPpeEmployees" class="form-select" multiple required>
|
||||||
|
<?php foreach ($employeesForSelect as $e): ?>
|
||||||
|
<option value="<?= (int)$e['id'] ?>" data-dept="<?= (int)($e['department_id'] ?? 0) ?>">
|
||||||
|
<?= htmlspecialchars(trim($e['last_name'] . ' ' . $e['first_name']), ENT_QUOTES, 'UTF-8') ?><?php if (!empty($e['employee_code'])): ?> (<?= htmlspecialchars($e['employee_code'], ENT_QUOTES, 'UTF-8') ?>)<?php endif; ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
<div class="form-text"><span id="bulkPpeCount">0</span> dipendenti selezionati</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-light border" data-bs-dismiss="modal">Annulla</button>
|
||||||
|
<button type="submit" class="btn btn-primary" id="bulkPpeSaveBtn">Assegna DPI</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<?php include('jsinclude.php'); ?>
|
<?php include('jsinclude.php'); ?>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -879,6 +958,112 @@ $allSkills = $stmtSkills->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
width: '100%'
|
width: '100%'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* -------- BULK DPI ASSIGN -------- */
|
||||||
|
var $bulkPpeEmp = $('#bulkPpeEmployees');
|
||||||
|
$bulkPpeEmp.select2({
|
||||||
|
theme: 'bootstrap-5',
|
||||||
|
placeholder: 'Seleziona dipendenti...',
|
||||||
|
dropdownParent: $('#bulkPpeModal'),
|
||||||
|
closeOnSelect: false,
|
||||||
|
width: '100%'
|
||||||
|
});
|
||||||
|
|
||||||
|
function bulkPpeUpdateCount() {
|
||||||
|
$('#bulkPpeCount').text(($bulkPpeEmp.val() || []).length);
|
||||||
|
}
|
||||||
|
$bulkPpeEmp.on('change', bulkPpeUpdateCount);
|
||||||
|
|
||||||
|
// Reset the form (and the save button) each time the modal opens
|
||||||
|
$('#btnBulkPpe').on('click', function() {
|
||||||
|
document.getElementById('bulkPpeForm').reset();
|
||||||
|
$bulkPpeEmp.val(null).trigger('change');
|
||||||
|
bulkPpeUpdateCount();
|
||||||
|
var sb = document.getElementById('bulkPpeSaveBtn');
|
||||||
|
sb.disabled = false;
|
||||||
|
sb.innerHTML = 'Assegna DPI';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add every employee of the chosen department to the selection
|
||||||
|
$('#bulkPpeAddDept').on('click', function() {
|
||||||
|
var dept = $('#bulkPpeDept').val();
|
||||||
|
if (!dept) return;
|
||||||
|
var current = ($bulkPpeEmp.val() || []).map(String);
|
||||||
|
$bulkPpeEmp.find('option').each(function() {
|
||||||
|
if (this.getAttribute('data-dept') === String(dept) && current.indexOf(this.value) === -1) {
|
||||||
|
current.push(this.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$bulkPpeEmp.val(current).trigger('change');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#bulkPpeSelectAll').on('click', function() {
|
||||||
|
var all = $bulkPpeEmp.find('option').map(function() {
|
||||||
|
return this.value;
|
||||||
|
}).get();
|
||||||
|
$bulkPpeEmp.val(all).trigger('change');
|
||||||
|
});
|
||||||
|
$('#bulkPpeClear').on('click', function() {
|
||||||
|
$bulkPpeEmp.val(null).trigger('change');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#bulkPpeForm').on('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var itemName = $('#bulkPpeItemName').val().trim();
|
||||||
|
var emps = $bulkPpeEmp.val() || [];
|
||||||
|
|
||||||
|
if (!itemName) {
|
||||||
|
Swal.fire('Attenzione', 'Indicare il nome del DPI.', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (emps.length === 0) {
|
||||||
|
Swal.fire('Attenzione', 'Selezionare almeno un dipendente.', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var btn = document.getElementById('bulkPpeSaveBtn');
|
||||||
|
btn.disabled = true;
|
||||||
|
var orig = btn.innerHTML;
|
||||||
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span> Salvataggio...';
|
||||||
|
|
||||||
|
var fd = new FormData();
|
||||||
|
fd.append('item_name', itemName);
|
||||||
|
fd.append('delivery_date', $('#bulkPpeDeliveryDate').val());
|
||||||
|
fd.append('delivered_by', $('#bulkPpeDeliveredBy').val());
|
||||||
|
fd.append('notes', $('#bulkPpeNotes').val());
|
||||||
|
emps.forEach(function(id) {
|
||||||
|
fd.append('employee_ids[]', id);
|
||||||
|
});
|
||||||
|
|
||||||
|
fetch('ajax/employee_profile/save_bulk_ppe.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: fd
|
||||||
|
})
|
||||||
|
.then(function(r) {
|
||||||
|
return r.json();
|
||||||
|
})
|
||||||
|
.then(function(data) {
|
||||||
|
if (data.success) {
|
||||||
|
bootstrap.Modal.getInstance(document.getElementById('bulkPpeModal')).hide();
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Fatto',
|
||||||
|
text: data.message,
|
||||||
|
timer: 2000,
|
||||||
|
showConfirmButton: false
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.innerHTML = orig;
|
||||||
|
Swal.fire('Errore', data.message || 'Errore.', 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function() {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.innerHTML = orig;
|
||||||
|
Swal.fire('Errore', 'Errore di connessione.', 'error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function syncAddRoleVisibility() {
|
function syncAddRoleVisibility() {
|
||||||
const authUserId = $('#addAuthUserId').val();
|
const authUserId = $('#addAuthUserId').val();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user