added subroles and dpi association fixed all pages and migration
This commit is contained in:
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class CreateJobSubRolesTable extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
$table = $this->table('job_sub_roles', [
|
||||||
|
'id' => false,
|
||||||
|
'primary_key' => ['id'],
|
||||||
|
'collation' => 'utf8mb4_unicode_ci',
|
||||||
|
'encoding' => 'utf8mb4',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$table
|
||||||
|
->addColumn('id', 'integer', [
|
||||||
|
'identity' => true,
|
||||||
|
'signed' => false,
|
||||||
|
])
|
||||||
|
->addColumn('job_role_id', 'integer', [
|
||||||
|
'signed' => false,
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->addColumn('name', 'string', [
|
||||||
|
'limit' => 255,
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->addColumn('description', 'text', [
|
||||||
|
'null' => true,
|
||||||
|
'default' => null,
|
||||||
|
])
|
||||||
|
->addColumn('sort_order', 'integer', [
|
||||||
|
'signed' => false,
|
||||||
|
'null' => false,
|
||||||
|
'default' => 999,
|
||||||
|
])
|
||||||
|
->addColumn('is_active', 'boolean', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => 1,
|
||||||
|
])
|
||||||
|
->addColumn('created_at', 'timestamp', [
|
||||||
|
'null' => true,
|
||||||
|
'default' => 'CURRENT_TIMESTAMP',
|
||||||
|
])
|
||||||
|
->addColumn('updated_at', 'timestamp', [
|
||||||
|
'null' => true,
|
||||||
|
'default' => 'CURRENT_TIMESTAMP',
|
||||||
|
'update' => 'CURRENT_TIMESTAMP',
|
||||||
|
])
|
||||||
|
->addIndex(['job_role_id'], [
|
||||||
|
'name' => 'idx_job_sub_roles_job_role_id',
|
||||||
|
])
|
||||||
|
->addIndex(['is_active'], [
|
||||||
|
'name' => 'idx_job_sub_roles_is_active',
|
||||||
|
])
|
||||||
|
->addIndex(['sort_order'], [
|
||||||
|
'name' => 'idx_job_sub_roles_sort_order',
|
||||||
|
])
|
||||||
|
->addForeignKey(
|
||||||
|
'job_role_id',
|
||||||
|
'job_roles',
|
||||||
|
'id',
|
||||||
|
[
|
||||||
|
'delete' => 'CASCADE',
|
||||||
|
'update' => 'CASCADE',
|
||||||
|
'constraint' => 'fk_job_sub_roles_job_role',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
->create();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class CreatePpeItemsTable extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
$table = $this->table('ppe_items', [
|
||||||
|
'id' => false,
|
||||||
|
'primary_key' => ['id'],
|
||||||
|
'collation' => 'utf8mb4_unicode_ci',
|
||||||
|
'encoding' => 'utf8mb4',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$table
|
||||||
|
->addColumn('id', 'integer', [
|
||||||
|
'identity' => true,
|
||||||
|
'signed' => false,
|
||||||
|
])
|
||||||
|
->addColumn('name', 'string', [
|
||||||
|
'limit' => 255,
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->addColumn('description', 'text', [
|
||||||
|
'null' => true,
|
||||||
|
'default' => null,
|
||||||
|
])
|
||||||
|
->addColumn('category', 'string', [
|
||||||
|
'limit' => 100,
|
||||||
|
'null' => true,
|
||||||
|
'default' => null,
|
||||||
|
'comment' => 'PPE category, for example Head, Hands, Eyes, Feet, Respiratory',
|
||||||
|
])
|
||||||
|
->addColumn('photo', 'string', [
|
||||||
|
'limit' => 255,
|
||||||
|
'null' => true,
|
||||||
|
'default' => null,
|
||||||
|
'comment' => 'PPE image path or filename',
|
||||||
|
])
|
||||||
|
->addColumn('standard_reference', 'string', [
|
||||||
|
'limit' => 255,
|
||||||
|
'null' => true,
|
||||||
|
'default' => null,
|
||||||
|
'comment' => 'Reference standard, for example EN ISO 20345',
|
||||||
|
])
|
||||||
|
->addColumn('validity_months', 'integer', [
|
||||||
|
'signed' => false,
|
||||||
|
'null' => true,
|
||||||
|
'default' => null,
|
||||||
|
'comment' => 'Default validity in months after assignment',
|
||||||
|
])
|
||||||
|
->addColumn('sort_order', 'integer', [
|
||||||
|
'signed' => false,
|
||||||
|
'null' => false,
|
||||||
|
'default' => 999,
|
||||||
|
])
|
||||||
|
->addColumn('is_active', 'boolean', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => 1,
|
||||||
|
])
|
||||||
|
->addColumn('created_at', 'timestamp', [
|
||||||
|
'null' => true,
|
||||||
|
'default' => 'CURRENT_TIMESTAMP',
|
||||||
|
])
|
||||||
|
->addColumn('updated_at', 'timestamp', [
|
||||||
|
'null' => true,
|
||||||
|
'default' => 'CURRENT_TIMESTAMP',
|
||||||
|
'update' => 'CURRENT_TIMESTAMP',
|
||||||
|
])
|
||||||
|
->addIndex(['category'], [
|
||||||
|
'name' => 'idx_ppe_items_category',
|
||||||
|
])
|
||||||
|
->addIndex(['is_active'], [
|
||||||
|
'name' => 'idx_ppe_items_is_active',
|
||||||
|
])
|
||||||
|
->addIndex(['sort_order'], [
|
||||||
|
'name' => 'idx_ppe_items_sort_order',
|
||||||
|
])
|
||||||
|
->create();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class CreateEmployeePpeItemsTable extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
$table = $this->table('employee_ppe_items', [
|
||||||
|
'id' => false,
|
||||||
|
'primary_key' => ['id'],
|
||||||
|
'collation' => 'utf8mb4_unicode_ci',
|
||||||
|
'encoding' => 'utf8mb4',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$table
|
||||||
|
->addColumn('id', 'integer', [
|
||||||
|
'identity' => true,
|
||||||
|
'signed' => false,
|
||||||
|
])
|
||||||
|
->addColumn('employee_id', 'integer', [
|
||||||
|
'signed' => false,
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->addColumn('ppe_item_id', 'integer', [
|
||||||
|
'signed' => false,
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->addColumn('assigned_date', 'date', [
|
||||||
|
'null' => true,
|
||||||
|
'default' => null,
|
||||||
|
])
|
||||||
|
->addColumn('expiry_date', 'date', [
|
||||||
|
'null' => true,
|
||||||
|
'default' => null,
|
||||||
|
])
|
||||||
|
->addColumn('quantity', 'integer', [
|
||||||
|
'signed' => false,
|
||||||
|
'null' => false,
|
||||||
|
'default' => 1,
|
||||||
|
])
|
||||||
|
->addColumn('status', 'enum', [
|
||||||
|
'values' => [
|
||||||
|
'assigned',
|
||||||
|
'returned',
|
||||||
|
'expired',
|
||||||
|
'lost',
|
||||||
|
'damaged',
|
||||||
|
],
|
||||||
|
'null' => false,
|
||||||
|
'default' => 'assigned',
|
||||||
|
])
|
||||||
|
->addColumn('notes', 'text', [
|
||||||
|
'null' => true,
|
||||||
|
'default' => null,
|
||||||
|
])
|
||||||
|
->addColumn('created_at', 'timestamp', [
|
||||||
|
'null' => true,
|
||||||
|
'default' => 'CURRENT_TIMESTAMP',
|
||||||
|
])
|
||||||
|
->addColumn('updated_at', 'timestamp', [
|
||||||
|
'null' => true,
|
||||||
|
'default' => 'CURRENT_TIMESTAMP',
|
||||||
|
'update' => 'CURRENT_TIMESTAMP',
|
||||||
|
])
|
||||||
|
->addIndex(['employee_id'], [
|
||||||
|
'name' => 'idx_employee_ppe_items_employee_id',
|
||||||
|
])
|
||||||
|
->addIndex(['ppe_item_id'], [
|
||||||
|
'name' => 'idx_employee_ppe_items_ppe_item_id',
|
||||||
|
])
|
||||||
|
->addIndex(['status'], [
|
||||||
|
'name' => 'idx_employee_ppe_items_status',
|
||||||
|
])
|
||||||
|
->addIndex(['expiry_date'], [
|
||||||
|
'name' => 'idx_employee_ppe_items_expiry_date',
|
||||||
|
])
|
||||||
|
->addForeignKey(
|
||||||
|
'employee_id',
|
||||||
|
'employees',
|
||||||
|
'id',
|
||||||
|
[
|
||||||
|
'delete' => 'CASCADE',
|
||||||
|
'update' => 'CASCADE',
|
||||||
|
'constraint' => 'fk_employee_ppe_items_employee',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
->addForeignKey(
|
||||||
|
'ppe_item_id',
|
||||||
|
'ppe_items',
|
||||||
|
'id',
|
||||||
|
[
|
||||||
|
'delete' => 'RESTRICT',
|
||||||
|
'update' => 'CASCADE',
|
||||||
|
'constraint' => 'fk_employee_ppe_items_ppe_item',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
->create();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class CreateJobSubRolePpeItemsTable extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
$table = $this->table('job_sub_role_ppe_items', [
|
||||||
|
'id' => false,
|
||||||
|
'primary_key' => ['id'],
|
||||||
|
'collation' => 'utf8mb4_unicode_ci',
|
||||||
|
'encoding' => 'utf8mb4',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$table
|
||||||
|
->addColumn('id', 'integer', [
|
||||||
|
'identity' => true,
|
||||||
|
'signed' => false,
|
||||||
|
])
|
||||||
|
->addColumn('job_sub_role_id', 'integer', [
|
||||||
|
'signed' => false,
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->addColumn('ppe_item_id', 'integer', [
|
||||||
|
'signed' => false,
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->addColumn('requirement_type', 'enum', [
|
||||||
|
'values' => [
|
||||||
|
'mandatory',
|
||||||
|
'recommended',
|
||||||
|
'optional',
|
||||||
|
],
|
||||||
|
'null' => false,
|
||||||
|
'default' => 'mandatory',
|
||||||
|
'comment' => 'Defines if the PPE is mandatory, recommended or optional for the sub role',
|
||||||
|
])
|
||||||
|
->addColumn('notes', 'text', [
|
||||||
|
'null' => true,
|
||||||
|
'default' => null,
|
||||||
|
])
|
||||||
|
->addColumn('sort_order', 'integer', [
|
||||||
|
'signed' => false,
|
||||||
|
'null' => false,
|
||||||
|
'default' => 999,
|
||||||
|
])
|
||||||
|
->addColumn('is_active', 'boolean', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => 1,
|
||||||
|
])
|
||||||
|
->addColumn('created_at', 'timestamp', [
|
||||||
|
'null' => true,
|
||||||
|
'default' => 'CURRENT_TIMESTAMP',
|
||||||
|
])
|
||||||
|
->addColumn('updated_at', 'timestamp', [
|
||||||
|
'null' => true,
|
||||||
|
'default' => 'CURRENT_TIMESTAMP',
|
||||||
|
'update' => 'CURRENT_TIMESTAMP',
|
||||||
|
])
|
||||||
|
->addIndex(['job_sub_role_id'], [
|
||||||
|
'name' => 'idx_job_sub_role_ppe_items_sub_role_id',
|
||||||
|
])
|
||||||
|
->addIndex(['ppe_item_id'], [
|
||||||
|
'name' => 'idx_job_sub_role_ppe_items_ppe_item_id',
|
||||||
|
])
|
||||||
|
->addIndex(['requirement_type'], [
|
||||||
|
'name' => 'idx_job_sub_role_ppe_items_requirement_type',
|
||||||
|
])
|
||||||
|
->addIndex(['is_active'], [
|
||||||
|
'name' => 'idx_job_sub_role_ppe_items_is_active',
|
||||||
|
])
|
||||||
|
->addIndex(['job_sub_role_id', 'ppe_item_id'], [
|
||||||
|
'unique' => true,
|
||||||
|
'name' => 'uq_job_sub_role_ppe_item',
|
||||||
|
])
|
||||||
|
->addForeignKey(
|
||||||
|
'job_sub_role_id',
|
||||||
|
'job_sub_roles',
|
||||||
|
'id',
|
||||||
|
[
|
||||||
|
'delete' => 'CASCADE',
|
||||||
|
'update' => 'CASCADE',
|
||||||
|
'constraint' => 'fk_job_sub_role_ppe_items_sub_role',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
->addForeignKey(
|
||||||
|
'ppe_item_id',
|
||||||
|
'ppe_items',
|
||||||
|
'id',
|
||||||
|
[
|
||||||
|
'delete' => 'CASCADE',
|
||||||
|
'update' => 'CASCADE',
|
||||||
|
'constraint' => 'fk_job_sub_role_ppe_items_ppe_item',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
->create();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class AddJobSubRoleIdToEmployeesTable extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
$table = $this->table('employees');
|
||||||
|
|
||||||
|
$table
|
||||||
|
->addColumn('job_sub_role_id', 'integer', [
|
||||||
|
'signed' => false,
|
||||||
|
'null' => true,
|
||||||
|
'default' => null,
|
||||||
|
'after' => 'job_role_id',
|
||||||
|
])
|
||||||
|
->addIndex(['job_sub_role_id'], [
|
||||||
|
'name' => 'idx_employees_job_sub_role_id',
|
||||||
|
])
|
||||||
|
->addForeignKey(
|
||||||
|
'job_sub_role_id',
|
||||||
|
'job_sub_roles',
|
||||||
|
'id',
|
||||||
|
[
|
||||||
|
'delete' => 'SET_NULL',
|
||||||
|
'update' => 'CASCADE',
|
||||||
|
'constraint' => 'fk_employees_job_sub_role',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class AddDeliveryFieldsToEmployeePpeItemsTable extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
$table = $this->table('employee_ppe_items');
|
||||||
|
|
||||||
|
$table
|
||||||
|
->addColumn('delivered_by', 'string', [
|
||||||
|
'limit' => 255,
|
||||||
|
'null' => true,
|
||||||
|
'default' => null,
|
||||||
|
'after' => 'expiry_date',
|
||||||
|
])
|
||||||
|
->addColumn('created_by', 'integer', [
|
||||||
|
'signed' => false,
|
||||||
|
'null' => true,
|
||||||
|
'default' => null,
|
||||||
|
'after' => 'notes',
|
||||||
|
])
|
||||||
|
->addIndex(['created_by'], [
|
||||||
|
'name' => 'idx_employee_ppe_items_created_by',
|
||||||
|
])
|
||||||
|
->addForeignKey(
|
||||||
|
'created_by',
|
||||||
|
'auth_users',
|
||||||
|
'id',
|
||||||
|
[
|
||||||
|
'delete' => 'SET_NULL',
|
||||||
|
'update' => 'CASCADE',
|
||||||
|
'constraint' => 'fk_employee_ppe_items_created_by',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class CreateEmployeeJobSubRolesTable extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
if (!$this->hasTable('employee_job_sub_roles')) {
|
||||||
|
$table = $this->table('employee_job_sub_roles', [
|
||||||
|
'id' => false,
|
||||||
|
'primary_key' => ['id'],
|
||||||
|
'signed' => false,
|
||||||
|
'collation' => 'utf8mb4_general_ci',
|
||||||
|
'encoding' => 'utf8mb4',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$table
|
||||||
|
->addColumn('id', 'integer', [
|
||||||
|
'identity' => true,
|
||||||
|
'signed' => false,
|
||||||
|
])
|
||||||
|
->addColumn('employee_id', 'integer', [
|
||||||
|
'signed' => false,
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->addColumn('job_sub_role_id', 'integer', [
|
||||||
|
'signed' => false,
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->addColumn('is_primary', 'boolean', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => false,
|
||||||
|
])
|
||||||
|
->addColumn('created_at', 'timestamp', [
|
||||||
|
'null' => true,
|
||||||
|
'default' => 'CURRENT_TIMESTAMP',
|
||||||
|
])
|
||||||
|
->addIndex(['employee_id', 'job_sub_role_id'], [
|
||||||
|
'unique' => true,
|
||||||
|
'name' => 'uq_employee_subrole',
|
||||||
|
])
|
||||||
|
->addIndex(['employee_id'], [
|
||||||
|
'name' => 'idx_employee_job_sub_roles_employee',
|
||||||
|
])
|
||||||
|
->addIndex(['job_sub_role_id'], [
|
||||||
|
'name' => 'idx_employee_job_sub_roles_subrole',
|
||||||
|
])
|
||||||
|
->addForeignKey(
|
||||||
|
'employee_id',
|
||||||
|
'employees',
|
||||||
|
'id',
|
||||||
|
[
|
||||||
|
'delete' => 'CASCADE',
|
||||||
|
'update' => 'CASCADE',
|
||||||
|
'constraint' => 'fk_employee_job_sub_roles_employee',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
->addForeignKey(
|
||||||
|
'job_sub_role_id',
|
||||||
|
'job_sub_roles',
|
||||||
|
'id',
|
||||||
|
[
|
||||||
|
'delete' => 'CASCADE',
|
||||||
|
'update' => 'CASCADE',
|
||||||
|
'constraint' => 'fk_employee_job_sub_roles_subrole',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
->create();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import existing single sub-role assignments from employees.job_sub_role_id
|
||||||
|
// into the new bridge table.
|
||||||
|
$this->execute("
|
||||||
|
INSERT IGNORE INTO employee_job_sub_roles
|
||||||
|
(employee_id, job_sub_role_id, is_primary, created_at)
|
||||||
|
SELECT
|
||||||
|
e.id,
|
||||||
|
e.job_sub_role_id,
|
||||||
|
1,
|
||||||
|
NOW()
|
||||||
|
FROM employees e
|
||||||
|
WHERE e.job_sub_role_id IS NOT NULL
|
||||||
|
AND e.job_sub_role_id > 0
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
if ($this->hasTable('employee_job_sub_roles')) {
|
||||||
|
$this->table('employee_job_sub_roles')->drop()->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,26 +1,38 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once(__DIR__ . '/../hr_auth_check.php');
|
include('../../include/headscript.php');
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
||||||
http_response_code(405);
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Metodo non consentito.']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
try {
|
||||||
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
$id = (int)($_POST['id'] ?? 0);
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
|
||||||
if ($id <= 0) {
|
if ($id <= 0) {
|
||||||
echo json_encode(['success' => false, 'message' => 'ID DPI non valido.']);
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'ID DPI non valido.'
|
||||||
|
]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
$stmt = $pdo->prepare("
|
||||||
$stmt = $pdo->prepare("DELETE FROM employee_ppe WHERE id = :id");
|
UPDATE employee_ppe_items
|
||||||
$stmt->execute(['id' => $id]);
|
SET status = 'returned',
|
||||||
echo json_encode(['success' => true]);
|
updated_at = NOW()
|
||||||
} catch (Exception $e) {
|
WHERE id = ?
|
||||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'DPI rimosso correttamente.'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,82 +1,153 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once(__DIR__ . '/../hr_auth_check.php');
|
include('../../include/headscript.php');
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
||||||
http_response_code(405);
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Metodo non consentito.']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
|
||||||
|
|
||||||
$id = (int)($_POST['id'] ?? 0);
|
|
||||||
$employeeId = (int)($_POST['employee_id'] ?? 0);
|
|
||||||
$itemName = trim($_POST['item_name'] ?? '');
|
|
||||||
$deliveryDate = trim($_POST['delivery_date'] ?? '');
|
|
||||||
$deliveredBy = trim($_POST['delivered_by'] ?? '');
|
|
||||||
$notes = trim($_POST['notes'] ?? '');
|
|
||||||
|
|
||||||
if ($employeeId <= 0) {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'ID dipendente non valido.']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
if ($itemName === '') {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Il nome del DPI è obbligatorio.']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$deliveryDate = $deliveryDate === '' ? null : $deliveryDate;
|
|
||||||
$deliveredBy = $deliveredBy !== '' ? $deliveredBy : null;
|
|
||||||
$notes = $notes !== '' ? $notes : null;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ($id > 0) {
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
$id = isset($_POST['id']) && $_POST['id'] !== '' ? (int)$_POST['id'] : null;
|
||||||
|
$employeeId = (int)($_POST['employee_id'] ?? 0);
|
||||||
|
$ppeItemId = (int)($_POST['ppe_item_id'] ?? 0);
|
||||||
|
$assignedDate = trim($_POST['assigned_date'] ?? '');
|
||||||
|
$expiryDate = trim($_POST['expiry_date'] ?? '');
|
||||||
|
$deliveredBy = trim($_POST['delivered_by'] ?? '');
|
||||||
|
$status = trim($_POST['status'] ?? 'assigned');
|
||||||
|
$notes = trim($_POST['notes'] ?? '');
|
||||||
|
|
||||||
|
$allowedStatuses = [
|
||||||
|
'assigned',
|
||||||
|
'returned',
|
||||||
|
'expired',
|
||||||
|
'lost',
|
||||||
|
'damaged',
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($employeeId <= 0) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Dipendente non valido.'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ppeItemId <= 0) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Selezionare un DPI.'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array($status, $allowedStatuses, true)) {
|
||||||
|
$status = 'assigned';
|
||||||
|
}
|
||||||
|
|
||||||
|
$checkEmployee = $pdo->prepare("SELECT id FROM employees WHERE id = ? LIMIT 1");
|
||||||
|
$checkEmployee->execute([$employeeId]);
|
||||||
|
|
||||||
|
if (!$checkEmployee->fetchColumn()) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Dipendente non trovato.'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$checkPpe = $pdo->prepare("SELECT id FROM ppe_items WHERE id = ? LIMIT 1");
|
||||||
|
$checkPpe->execute([$ppeItemId]);
|
||||||
|
|
||||||
|
if (!$checkPpe->fetchColumn()) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'DPI non trovato.'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($id) {
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
UPDATE employee_ppe
|
UPDATE employee_ppe_items
|
||||||
SET item_name = :item_name,
|
SET ppe_item_id = :ppe_item_id,
|
||||||
delivery_date = :delivery_date,
|
assigned_date = :assigned_date,
|
||||||
|
expiry_date = :expiry_date,
|
||||||
delivered_by = :delivered_by,
|
delivered_by = :delivered_by,
|
||||||
|
status = :status,
|
||||||
notes = :notes,
|
notes = :notes,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE id = :id AND employee_id = :eid
|
WHERE id = :id
|
||||||
|
AND employee_id = :employee_id
|
||||||
");
|
");
|
||||||
$stmt->execute([
|
|
||||||
'item_name' => $itemName,
|
|
||||||
'delivery_date' => $deliveryDate,
|
|
||||||
'delivered_by' => $deliveredBy,
|
|
||||||
'notes' => $notes,
|
|
||||||
'id' => $id,
|
|
||||||
'eid' => $employeeId,
|
|
||||||
]);
|
|
||||||
echo json_encode(['success' => true, 'id' => $id]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$check = $pdo->prepare("SELECT COUNT(*) FROM employees WHERE id = :id");
|
$stmt->execute([
|
||||||
$check->execute(['id' => $employeeId]);
|
'ppe_item_id' => $ppeItemId,
|
||||||
if ((int)$check->fetchColumn() === 0) {
|
'assigned_date' => $assignedDate !== '' ? $assignedDate : null,
|
||||||
echo json_encode(['success' => false, 'message' => 'Dipendente non trovato.']);
|
'expiry_date' => $expiryDate !== '' ? $expiryDate : null,
|
||||||
|
'delivered_by' => $deliveredBy !== '' ? $deliveredBy : null,
|
||||||
|
'status' => $status,
|
||||||
|
'notes' => $notes !== '' ? $notes : null,
|
||||||
|
'id' => $id,
|
||||||
|
'employee_id' => $employeeId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'DPI aggiornato.'
|
||||||
|
]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
INSERT INTO employee_ppe
|
INSERT INTO employee_ppe_items
|
||||||
(employee_id, item_name, delivery_date, delivered_by, notes, created_by, created_at, updated_at)
|
(
|
||||||
|
employee_id,
|
||||||
|
ppe_item_id,
|
||||||
|
assigned_date,
|
||||||
|
expiry_date,
|
||||||
|
delivered_by,
|
||||||
|
quantity,
|
||||||
|
status,
|
||||||
|
notes,
|
||||||
|
created_by,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
)
|
||||||
VALUES
|
VALUES
|
||||||
(:employee_id, :item_name, :delivery_date, :delivered_by, :notes, :created_by, NOW(), NOW())
|
(
|
||||||
|
:employee_id,
|
||||||
|
:ppe_item_id,
|
||||||
|
:assigned_date,
|
||||||
|
:expiry_date,
|
||||||
|
:delivered_by,
|
||||||
|
1,
|
||||||
|
:status,
|
||||||
|
:notes,
|
||||||
|
:created_by,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
)
|
||||||
");
|
");
|
||||||
|
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
'employee_id' => $employeeId,
|
'employee_id' => $employeeId,
|
||||||
'item_name' => $itemName,
|
'ppe_item_id' => $ppeItemId,
|
||||||
'delivery_date' => $deliveryDate,
|
'assigned_date' => $assignedDate !== '' ? $assignedDate : null,
|
||||||
'delivered_by' => $deliveredBy,
|
'expiry_date' => $expiryDate !== '' ? $expiryDate : null,
|
||||||
'notes' => $notes,
|
'delivered_by' => $deliveredBy !== '' ? $deliveredBy : null,
|
||||||
'created_by' => $currentUserId,
|
'status' => $status,
|
||||||
|
'notes' => $notes !== '' ? $notes : null,
|
||||||
|
'created_by' => isset($iduserlogin) ? (int)$iduserlogin : null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'id' => (int)$pdo->lastInsertId()]);
|
echo json_encode([
|
||||||
} catch (Exception $e) {
|
'success' => true,
|
||||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
'message' => 'DPI assegnato.'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,15 +40,20 @@ if ($employeeId > 0) {
|
|||||||
d.name AS department_name,
|
d.name AS department_name,
|
||||||
d.color AS department_color,
|
d.color AS department_color,
|
||||||
jr.name AS job_role_name,
|
jr.name AS job_role_name,
|
||||||
|
jsr.name AS job_sub_role_name,
|
||||||
au.first_name AS auth_first_name,
|
au.first_name AS auth_first_name,
|
||||||
au.last_name AS auth_last_name,
|
au.last_name AS auth_last_name,
|
||||||
au.email AS auth_email,
|
au.email AS auth_email,
|
||||||
au.username AS auth_username,
|
au.username AS auth_username,
|
||||||
au.avatar AS auth_avatar
|
au.avatar AS auth_avatar,
|
||||||
|
ar.name AS auth_role_name,
|
||||||
|
ar.display_name AS auth_role_display_name
|
||||||
FROM employees e
|
FROM employees e
|
||||||
LEFT JOIN departments d ON d.id = e.department_id
|
LEFT JOIN departments d ON d.id = e.department_id
|
||||||
LEFT JOIN job_roles jr ON jr.id = e.job_role_id
|
LEFT JOIN job_roles jr ON jr.id = e.job_role_id
|
||||||
|
LEFT JOIN job_sub_roles jsr ON jsr.id = e.job_sub_role_id
|
||||||
LEFT JOIN auth_users au ON au.id = e.auth_user_id
|
LEFT JOIN auth_users au ON au.id = e.auth_user_id
|
||||||
|
LEFT JOIN auth_roles ar ON ar.id = au.role_id
|
||||||
WHERE e.id = :id
|
WHERE e.id = :id
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
");
|
");
|
||||||
@@ -64,6 +69,75 @@ if (!$isHrManager && $employee && (int)$employee['auth_user_id'] !== (int)$iduse
|
|||||||
|
|
||||||
$canEdit = $isHrManager;
|
$canEdit = $isHrManager;
|
||||||
|
|
||||||
|
/* ==========================================
|
||||||
|
EMPLOYEE JOB ROLES / SUB ROLES (multi assignment)
|
||||||
|
========================================== */
|
||||||
|
$employeeSubRoles = [];
|
||||||
|
$employeeJobRoleNames = [];
|
||||||
|
$employeeSubRoleNames = [];
|
||||||
|
$employeeSubRoleIds = [];
|
||||||
|
$employeeSubRolesByRole = [];
|
||||||
|
|
||||||
|
if ($employee) {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
ejsr.job_sub_role_id,
|
||||||
|
ejsr.is_primary,
|
||||||
|
jsr.name AS job_sub_role_name,
|
||||||
|
jsr.job_role_id,
|
||||||
|
jr.name AS job_role_name
|
||||||
|
FROM employee_job_sub_roles ejsr
|
||||||
|
INNER JOIN job_sub_roles jsr ON jsr.id = ejsr.job_sub_role_id
|
||||||
|
LEFT JOIN job_roles jr ON jr.id = jsr.job_role_id
|
||||||
|
WHERE ejsr.employee_id = :eid
|
||||||
|
ORDER BY ejsr.is_primary DESC, jr.sort_order ASC, jr.name ASC, jsr.sort_order ASC, jsr.name ASC
|
||||||
|
");
|
||||||
|
$stmt->execute(['eid' => $employeeId]);
|
||||||
|
$employeeSubRoles = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Fallback: if the bridge table is empty but legacy employees.job_sub_role_id is filled, show the legacy value.
|
||||||
|
if (!$employeeSubRoles && !empty($employee['job_sub_role_id'])) {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
jsr.id AS job_sub_role_id,
|
||||||
|
1 AS is_primary,
|
||||||
|
jsr.name AS job_sub_role_name,
|
||||||
|
jsr.job_role_id,
|
||||||
|
jr.name AS job_role_name
|
||||||
|
FROM job_sub_roles jsr
|
||||||
|
LEFT JOIN job_roles jr ON jr.id = jsr.job_role_id
|
||||||
|
WHERE jsr.id = :sid
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute(['sid' => (int)$employee['job_sub_role_id']]);
|
||||||
|
$legacySubRole = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if ($legacySubRole) {
|
||||||
|
$employeeSubRoles = [$legacySubRole];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($employeeSubRoles as $sr) {
|
||||||
|
$employeeSubRoleIds[] = (int)$sr['job_sub_role_id'];
|
||||||
|
|
||||||
|
if (!empty($sr['job_role_name'])) {
|
||||||
|
$employeeJobRoleNames[(int)$sr['job_role_id']] = $sr['job_role_name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($sr['job_sub_role_name'])) {
|
||||||
|
$employeeSubRoleNames[(int)$sr['job_sub_role_id']] = $sr['job_sub_role_name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$roleKey = (int)($sr['job_role_id'] ?? 0);
|
||||||
|
if (!isset($employeeSubRolesByRole[$roleKey])) {
|
||||||
|
$employeeSubRolesByRole[$roleKey] = [
|
||||||
|
'job_role_name' => $sr['job_role_name'] ?: 'Senza mansione',
|
||||||
|
'items' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$employeeSubRolesByRole[$roleKey]['items'][] = $sr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ==========================================
|
/* ==========================================
|
||||||
DOCUMENTS (File Repository)
|
DOCUMENTS (File Repository)
|
||||||
========================================== */
|
========================================== */
|
||||||
@@ -136,19 +210,83 @@ if ($employee) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================
|
/* ==========================================
|
||||||
PPE (Assigned)
|
PPE (Assigned + Required by sub role)
|
||||||
========================================== */
|
========================================== */
|
||||||
$ppeList = [];
|
$ppeList = [];
|
||||||
|
$ppeItemsAll = [];
|
||||||
|
$requiredPpeList = [];
|
||||||
|
$assignedPpeIds = [];
|
||||||
|
|
||||||
if ($employee) {
|
if ($employee) {
|
||||||
|
// Assigned PPE history from the normalized table.
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
SELECT *
|
SELECT
|
||||||
FROM employee_ppe
|
epi.*,
|
||||||
WHERE employee_id = :eid
|
pi.name AS ppe_name,
|
||||||
ORDER BY delivery_date DESC, created_at DESC
|
pi.category AS ppe_category,
|
||||||
|
pi.photo AS ppe_photo,
|
||||||
|
pi.standard_reference,
|
||||||
|
pi.validity_months
|
||||||
|
FROM employee_ppe_items epi
|
||||||
|
INNER JOIN ppe_items pi ON pi.id = epi.ppe_item_id
|
||||||
|
WHERE epi.employee_id = :eid
|
||||||
|
ORDER BY
|
||||||
|
CASE epi.status
|
||||||
|
WHEN 'assigned' THEN 1
|
||||||
|
WHEN 'expired' THEN 2
|
||||||
|
WHEN 'damaged' THEN 3
|
||||||
|
WHEN 'lost' THEN 4
|
||||||
|
WHEN 'returned' THEN 5
|
||||||
|
ELSE 9
|
||||||
|
END,
|
||||||
|
epi.assigned_date DESC,
|
||||||
|
epi.created_at DESC
|
||||||
");
|
");
|
||||||
$stmt->execute(['eid' => $employeeId]);
|
$stmt->execute(['eid' => $employeeId]);
|
||||||
$ppeList = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$ppeList = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
foreach ($ppeList as $p) {
|
||||||
|
if (($p['status'] ?? '') === 'assigned') {
|
||||||
|
$assignedPpeIds[(int)$p['ppe_item_id']] = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All active PPE for manual assignment dropdown.
|
||||||
|
if ($canEdit) {
|
||||||
|
$ppeItemsAll = $pdo->query("
|
||||||
|
SELECT id, name, category, standard_reference, validity_months
|
||||||
|
FROM ppe_items
|
||||||
|
WHERE is_active = 1
|
||||||
|
ORDER BY sort_order ASC, name ASC
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required PPE based on all employee sub roles.
|
||||||
|
// DISTINCT avoids duplicated PPE when two sub roles require the same item.
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
pi.id,
|
||||||
|
pi.name,
|
||||||
|
pi.category,
|
||||||
|
pi.photo,
|
||||||
|
pi.standard_reference,
|
||||||
|
pi.validity_months,
|
||||||
|
GROUP_CONCAT(DISTINCT CONCAT(COALESCE(jr.name, 'Senza mansione'), ' / ', jsr.name) ORDER BY jr.sort_order ASC, jr.name ASC, jsr.sort_order ASC, jsr.name ASC SEPARATOR ' | ') AS source_sub_roles
|
||||||
|
FROM employee_job_sub_roles ejsr
|
||||||
|
INNER JOIN job_sub_roles jsr ON jsr.id = ejsr.job_sub_role_id
|
||||||
|
LEFT JOIN job_roles jr ON jr.id = jsr.job_role_id
|
||||||
|
INNER JOIN job_sub_role_ppe_items jsp ON jsp.job_sub_role_id = ejsr.job_sub_role_id
|
||||||
|
INNER JOIN ppe_items pi ON pi.id = jsp.ppe_item_id
|
||||||
|
WHERE ejsr.employee_id = :eid
|
||||||
|
AND jsp.is_active = 1
|
||||||
|
AND pi.is_active = 1
|
||||||
|
GROUP BY pi.id, pi.name, pi.category, pi.photo, pi.standard_reference, pi.validity_months
|
||||||
|
ORDER BY pi.category ASC, pi.name ASC
|
||||||
|
");
|
||||||
|
$stmt->execute(['eid' => $employeeId]);
|
||||||
|
$requiredPpeList = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ==========================================
|
/* ==========================================
|
||||||
DROPDOWN DATA FOR EDIT MODAL
|
DROPDOWN DATA FOR EDIT MODAL
|
||||||
@@ -159,6 +297,23 @@ $departments = $isHrManager
|
|||||||
$jobRoles = $isHrManager
|
$jobRoles = $isHrManager
|
||||||
? $pdo->query("SELECT id, name FROM job_roles WHERE is_active = 1 ORDER BY sort_order, name")->fetchAll(PDO::FETCH_ASSOC)
|
? $pdo->query("SELECT id, name FROM job_roles WHERE is_active = 1 ORDER BY sort_order, name")->fetchAll(PDO::FETCH_ASSOC)
|
||||||
: [];
|
: [];
|
||||||
|
$jobSubRolesAll = $isHrManager
|
||||||
|
? $pdo->query("
|
||||||
|
SELECT
|
||||||
|
jsr.id,
|
||||||
|
jsr.job_role_id,
|
||||||
|
jsr.name,
|
||||||
|
jr.name AS job_role_name
|
||||||
|
FROM job_sub_roles jsr
|
||||||
|
LEFT JOIN job_roles jr ON jr.id = jsr.job_role_id
|
||||||
|
WHERE jsr.is_active = 1
|
||||||
|
ORDER BY jr.sort_order ASC, jr.name ASC, jsr.sort_order ASC, jsr.name ASC
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC)
|
||||||
|
: [];
|
||||||
|
$jobSubRoleToRoleMap = [];
|
||||||
|
foreach ($jobSubRolesAll as $sr) {
|
||||||
|
$jobSubRoleToRoleMap[(int)$sr['id']] = (int)$sr['job_role_id'];
|
||||||
|
}
|
||||||
$authUsers = $isHrManager
|
$authUsers = $isHrManager
|
||||||
? $pdo->query("SELECT id, username, first_name, last_name, email, role_id FROM auth_users ORDER BY first_name, last_name")->fetchAll(PDO::FETCH_ASSOC)
|
? $pdo->query("SELECT id, username, first_name, last_name, email, role_id FROM auth_users ORDER BY first_name, last_name")->fetchAll(PDO::FETCH_ASSOC)
|
||||||
: [];
|
: [];
|
||||||
@@ -240,6 +395,8 @@ function fmtFileSize(?int $bytes): string
|
|||||||
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||||
<?php include('cssinclude.php'); ?>
|
<?php include('cssinclude.php'); ?>
|
||||||
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.8/css/dataTables.bootstrap5.min.css">
|
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.8/css/dataTables.bootstrap5.min.css">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css" rel="stylesheet" />
|
||||||
<title>Profilo Dipendente - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
<title>Profilo Dipendente - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||||
|
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
@@ -335,6 +492,79 @@ function fmtFileSize(?int $bytes): string
|
|||||||
margin: 4px 0 8px 0;
|
margin: 4px 0 8px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.profile-summary-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-summary-card {
|
||||||
|
background: rgba(255, 255, 255, .72);
|
||||||
|
border: 1px solid rgba(148, 163, 184, .45);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
min-height: 68px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-summary-label {
|
||||||
|
font-size: .72rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .06em;
|
||||||
|
color: #64748b;
|
||||||
|
font-weight: 800;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-summary-value {
|
||||||
|
color: #1f2937;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-summary-muted {
|
||||||
|
color: #64748b;
|
||||||
|
font-size: .84rem;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-role-stack {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-role-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 5px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-role-main {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
background: #e0f2fe;
|
||||||
|
color: #075985;
|
||||||
|
border: 1px solid #bae6fd;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 3px 9px;
|
||||||
|
font-size: .78rem;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-subrole-chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #334155;
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 3px 9px;
|
||||||
|
font-size: .78rem;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
.profile-badges {
|
.profile-badges {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -530,6 +760,43 @@ function fmtFileSize(?int $bytes): string
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.job-role-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-role-group {
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-role-group-title {
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1f2937;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-subrole-chip-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-subrole-chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #1d4ed8;
|
||||||
|
border: 1px solid #bfdbfe;
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.empty-profile {
|
.empty-profile {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 60px 20px;
|
padding: 60px 20px;
|
||||||
@@ -714,7 +981,10 @@ function fmtFileSize(?int $bytes): string
|
|||||||
$status = statusBadge((string)($employee['status'] ?? 'active'));
|
$status = statusBadge((string)($employee['status'] ?? 'active'));
|
||||||
$deptName = $employee['department_name'] ?? null;
|
$deptName = $employee['department_name'] ?? null;
|
||||||
$deptColor = $employee['department_color'] ?? null;
|
$deptColor = $employee['department_color'] ?? null;
|
||||||
$jobName = $employee['job_role_name'] ?? null;
|
$jobNames = array_values($employeeJobRoleNames);
|
||||||
|
$jobSubRoleNames = array_values($employeeSubRoleNames);
|
||||||
|
$jobName = $jobNames ? implode(', ', $jobNames) : ($employee['job_role_name'] ?? null);
|
||||||
|
$jobSubRoleName = $jobSubRoleNames ? implode(', ', $jobSubRoleNames) : ($employee['job_sub_role_name'] ?? null);
|
||||||
|
|
||||||
$avatar = trim((string)($employee['auth_avatar'] ?? ''));
|
$avatar = trim((string)($employee['auth_avatar'] ?? ''));
|
||||||
|
|
||||||
@@ -746,20 +1016,63 @@ function fmtFileSize(?int $bytes): string
|
|||||||
Codice: <code><?= htmlspecialchars($employee['employee_code']) ?></code>
|
Codice: <code><?= htmlspecialchars($employee['employee_code']) ?></code>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<div class="profile-badges">
|
<div class="profile-summary-grid">
|
||||||
<?php if ($jobName): ?>
|
<div class="profile-summary-card">
|
||||||
<span class="pill pill-role">💼 <?= htmlspecialchars($jobName) ?></span>
|
<div class="profile-summary-label">Reparto</div>
|
||||||
<?php endif; ?>
|
<div class="profile-summary-value">
|
||||||
<?php if ($deptName): ?>
|
<?php if ($deptName): ?>
|
||||||
<span class="pill pill-dept" style="<?= $deptColor ? 'background:' . htmlspecialchars($deptColor, ENT_QUOTES) . '20; color:' . htmlspecialchars($deptColor, ENT_QUOTES) . ';' : '' ?>">
|
<span class="pill pill-dept" style="<?= $deptColor ? 'background:' . htmlspecialchars($deptColor, ENT_QUOTES) . '20; color:' . htmlspecialchars($deptColor, ENT_QUOTES) . ';' : '' ?>">
|
||||||
🏢 <?= htmlspecialchars($deptName) ?>
|
🏢 <?= htmlspecialchars($deptName) ?>
|
||||||
</span>
|
</span>
|
||||||
|
<?php else: ?>
|
||||||
|
—
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="profile-summary-card">
|
||||||
|
<div class="profile-summary-label">Mansioni / Sottomansioni</div>
|
||||||
|
<?php if (!empty($employeeSubRolesByRole)): ?>
|
||||||
|
<div class="profile-role-stack">
|
||||||
|
<?php foreach ($employeeSubRolesByRole as $roleGroup): ?>
|
||||||
|
<div class="profile-role-group">
|
||||||
|
<span class="profile-role-main">💼 <?= htmlspecialchars($roleGroup['job_role_name']) ?></span>
|
||||||
|
<?php foreach ($roleGroup['items'] as $sr): ?>
|
||||||
|
<span class="profile-subrole-chip">🧩 <?= htmlspecialchars($sr['job_sub_role_name']) ?></span>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="profile-summary-value">—</div>
|
||||||
|
<div class="profile-summary-muted">Nessuna sottomansione associata</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="profile-summary-card">
|
||||||
|
<div class="profile-summary-label">Ruolo accesso</div>
|
||||||
|
<div class="profile-summary-value">
|
||||||
|
<?php if (!empty($employee['auth_role_display_name']) || !empty($employee['auth_role_name'])): ?>
|
||||||
|
🔐 <?= htmlspecialchars($employee['auth_role_display_name'] ?: $employee['auth_role_name']) ?>
|
||||||
|
<?php else: ?>
|
||||||
|
—
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php if (!empty($employee['auth_username'])): ?>
|
||||||
|
<div class="profile-summary-muted"><?= htmlspecialchars($employee['auth_username']) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="profile-summary-card">
|
||||||
|
<div class="profile-summary-label">Stato</div>
|
||||||
|
<div class="profile-summary-value">
|
||||||
<span class="pill pill-status-<?= htmlspecialchars($status['class']) ?>">
|
<span class="pill pill-status-<?= htmlspecialchars($status['class']) ?>">
|
||||||
<?= htmlspecialchars($status['label']) ?>
|
<?= htmlspecialchars($status['label']) ?>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<?php if ($canEdit): ?>
|
<?php if ($canEdit): ?>
|
||||||
<button class="btn btn-add" data-bs-toggle="modal" data-bs-target="#editPersonalModal">
|
<button class="btn btn-add" data-bs-toggle="modal" data-bs-target="#editPersonalModal">
|
||||||
✏️ Modifica
|
✏️ Modifica
|
||||||
@@ -853,9 +1166,26 @@ function fmtFileSize(?int $bytes): string
|
|||||||
<div class="info-label">Reparto</div>
|
<div class="info-label">Reparto</div>
|
||||||
<div class="info-value"><?= valOrDash($deptName) ?></div>
|
<div class="info-value"><?= valOrDash($deptName) ?></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-row">
|
<div class="info-row" style="grid-column: 1 / -1;">
|
||||||
<div class="info-label">Mansione</div>
|
<div class="info-label">Mansioni / Sottomansioni</div>
|
||||||
<div class="info-value"><?= valOrDash($jobName) ?></div>
|
<div class="info-value">
|
||||||
|
<?php if (!empty($employeeSubRolesByRole)): ?>
|
||||||
|
<div class="job-role-list">
|
||||||
|
<?php foreach ($employeeSubRolesByRole as $roleGroup): ?>
|
||||||
|
<div class="job-role-group">
|
||||||
|
<div class="job-role-group-title">💼 <?= htmlspecialchars($roleGroup['job_role_name']) ?></div>
|
||||||
|
<div class="job-subrole-chip-list">
|
||||||
|
<?php foreach ($roleGroup['items'] as $sr): ?>
|
||||||
|
<span class="job-subrole-chip">🧩 <?= htmlspecialchars($sr['job_sub_role_name']) ?></span>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="text-muted">Nessuna mansione/sottomansione associata</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-row">
|
<div class="info-row">
|
||||||
<div class="info-label">Stato</div>
|
<div class="info-label">Stato</div>
|
||||||
@@ -1001,6 +1331,54 @@ function fmtFileSize(?int $bytes): string
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($requiredPpeList)): ?>
|
||||||
|
<div class="ppe-required-box">
|
||||||
|
<div class="ppe-required-title">
|
||||||
|
🦺 DPI richiesti dalle sottomansioni associate
|
||||||
|
<?php if (!empty($employeeSubRoleNames)): ?>
|
||||||
|
<span class="text-muted fw-normal">— calcolati su <?= count($employeeSubRoleNames) ?> sottomansion<?= count($employeeSubRoleNames) === 1 ? 'e' : 'i' ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php foreach ($requiredPpeList as $rp): ?>
|
||||||
|
<?php
|
||||||
|
$requiredPpeId = (int)$rp['id'];
|
||||||
|
$isAssigned = isset($assignedPpeIds[$requiredPpeId]);
|
||||||
|
?>
|
||||||
|
<div class="ppe-required-grid">
|
||||||
|
<div>
|
||||||
|
<div class="ppe-name-main"><?= htmlspecialchars($rp['name']) ?></div>
|
||||||
|
<div class="ppe-meta-small">
|
||||||
|
<?= !empty($rp['category']) ? htmlspecialchars($rp['category']) : 'Senza categoria' ?>
|
||||||
|
<?php if (!empty($rp['standard_reference'])): ?>
|
||||||
|
· <?= htmlspecialchars($rp['standard_reference']) ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (!empty($rp['source_sub_roles'])): ?>
|
||||||
|
<br><span class="text-primary">Da: <?= htmlspecialchars($rp['source_sub_roles']) ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<?php if ($isAssigned): ?>
|
||||||
|
<span class="ppe-status-assigned">Assegnato</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="ppe-status-missing">Mancante</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php elseif (!empty($employeeSubRoleIds)): ?>
|
||||||
|
<div class="alert alert-light border">
|
||||||
|
Nessun DPI obbligatorio configurato per le sottomansioni associate al dipendente.
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
Nessuna sottomansione associata al dipendente: non è possibile suggerire DPI obbligatori.
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (empty($ppeList)): ?>
|
<?php if (empty($ppeList)): ?>
|
||||||
<div class="placeholder-section">
|
<div class="placeholder-section">
|
||||||
<i class='bx bx-shield-quarter'></i>
|
<i class='bx bx-shield-quarter'></i>
|
||||||
@@ -1018,31 +1396,62 @@ function fmtFileSize(?int $bytes): string
|
|||||||
<thead style="background-color:#cfe3ff;">
|
<thead style="background-color:#cfe3ff;">
|
||||||
<tr>
|
<tr>
|
||||||
<th>DPI</th>
|
<th>DPI</th>
|
||||||
|
<th>Categoria</th>
|
||||||
<th>Data Consegna</th>
|
<th>Data Consegna</th>
|
||||||
|
<th>Scadenza</th>
|
||||||
<th>Consegnato da</th>
|
<th>Consegnato da</th>
|
||||||
|
<th>Stato</th>
|
||||||
<th>Note</th>
|
<th>Note</th>
|
||||||
<?php if ($canEdit): ?><th class="text-end">Azioni</th><?php endif; ?>
|
<?php if ($canEdit): ?><th class="text-end">Azioni</th><?php endif; ?>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php foreach ($ppeList as $p): ?>
|
<?php foreach ($ppeList as $p): ?>
|
||||||
<?php $pid = (int)$p['id']; ?>
|
<?php
|
||||||
|
$pid = (int)$p['id'];
|
||||||
|
$ppeStatus = $p['status'] ?? 'assigned';
|
||||||
|
|
||||||
|
$statusClass = 'ppe-status-assigned';
|
||||||
|
$statusLabel = 'Assegnato';
|
||||||
|
|
||||||
|
if ($ppeStatus === 'returned') {
|
||||||
|
$statusClass = 'ppe-status-returned';
|
||||||
|
$statusLabel = 'Restituito';
|
||||||
|
} elseif ($ppeStatus === 'expired') {
|
||||||
|
$statusClass = 'ppe-status-problem';
|
||||||
|
$statusLabel = 'Scaduto';
|
||||||
|
} elseif ($ppeStatus === 'lost') {
|
||||||
|
$statusClass = 'ppe-status-problem';
|
||||||
|
$statusLabel = 'Perso';
|
||||||
|
} elseif ($ppeStatus === 'damaged') {
|
||||||
|
$statusClass = 'ppe-status-problem';
|
||||||
|
$statusLabel = 'Danneggiato';
|
||||||
|
}
|
||||||
|
?>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="fw-semibold"><?= htmlspecialchars($p['item_name']) ?></td>
|
<td class="fw-semibold"><?= htmlspecialchars($p['ppe_name']) ?></td>
|
||||||
<td><?= fmtDate($p['delivery_date']) ?></td>
|
<td><?= valOrDash($p['ppe_category']) ?></td>
|
||||||
<td><?= valOrDash($p['delivered_by']) ?></td>
|
<td><?= fmtDate($p['assigned_date']) ?></td>
|
||||||
|
<td><?= fmtDate($p['expiry_date']) ?></td>
|
||||||
|
<td><?= valOrDash($p['delivered_by'] ?? null) ?></td>
|
||||||
|
<td><span class="<?= $statusClass ?>"><?= $statusLabel ?></span></td>
|
||||||
<td><?= valOrDash($p['notes']) ?></td>
|
<td><?= valOrDash($p['notes']) ?></td>
|
||||||
<?php if ($canEdit): ?>
|
<?php if ($canEdit): ?>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
<button class="btn btn-sm btn-outline-secondary edit-ppe"
|
<button class="btn btn-sm btn-outline-secondary edit-ppe"
|
||||||
data-id="<?= $pid ?>"
|
data-id="<?= $pid ?>"
|
||||||
data-item_name="<?= htmlspecialchars($p['item_name'], ENT_QUOTES) ?>"
|
data-ppe_item_id="<?= (int)$p['ppe_item_id'] ?>"
|
||||||
data-delivery_date="<?= htmlspecialchars($p['delivery_date'] ?? '', ENT_QUOTES) ?>"
|
data-assigned_date="<?= htmlspecialchars($p['assigned_date'] ?? '', ENT_QUOTES) ?>"
|
||||||
|
data-expiry_date="<?= htmlspecialchars($p['expiry_date'] ?? '', ENT_QUOTES) ?>"
|
||||||
data-delivered_by="<?= htmlspecialchars($p['delivered_by'] ?? '', ENT_QUOTES) ?>"
|
data-delivered_by="<?= htmlspecialchars($p['delivered_by'] ?? '', ENT_QUOTES) ?>"
|
||||||
|
data-status="<?= htmlspecialchars($p['status'] ?? 'assigned', ENT_QUOTES) ?>"
|
||||||
data-notes="<?= htmlspecialchars($p['notes'] ?? '', ENT_QUOTES) ?>">✏️</button>
|
data-notes="<?= htmlspecialchars($p['notes'] ?? '', ENT_QUOTES) ?>">✏️</button>
|
||||||
|
|
||||||
|
<?php if (($p['status'] ?? '') === 'assigned'): ?>
|
||||||
<button class="btn btn-sm btn-outline-danger delete-ppe"
|
<button class="btn btn-sm btn-outline-danger delete-ppe"
|
||||||
data-id="<?= $pid ?>"
|
data-id="<?= $pid ?>"
|
||||||
data-name="<?= htmlspecialchars($p['item_name'], ENT_QUOTES) ?>">🗑️</button>
|
data-name="<?= htmlspecialchars($p['ppe_name'], ENT_QUOTES) ?>">Rimuovi</button>
|
||||||
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -1054,13 +1463,41 @@ function fmtFileSize(?int $bytes): string
|
|||||||
<!-- MOBILE CARDS -->
|
<!-- MOBILE CARDS -->
|
||||||
<div class="d-block d-md-none">
|
<div class="d-block d-md-none">
|
||||||
<?php foreach ($ppeList as $p): ?>
|
<?php foreach ($ppeList as $p): ?>
|
||||||
<?php $pid = (int)$p['id']; ?>
|
<?php
|
||||||
|
$pid = (int)$p['id'];
|
||||||
|
$ppeStatus = $p['status'] ?? 'assigned';
|
||||||
|
|
||||||
|
$statusClass = 'ppe-status-assigned';
|
||||||
|
$statusLabel = 'Assegnato';
|
||||||
|
|
||||||
|
if ($ppeStatus === 'returned') {
|
||||||
|
$statusClass = 'ppe-status-returned';
|
||||||
|
$statusLabel = 'Restituito';
|
||||||
|
} elseif ($ppeStatus === 'expired') {
|
||||||
|
$statusClass = 'ppe-status-problem';
|
||||||
|
$statusLabel = 'Scaduto';
|
||||||
|
} elseif ($ppeStatus === 'lost') {
|
||||||
|
$statusClass = 'ppe-status-problem';
|
||||||
|
$statusLabel = 'Perso';
|
||||||
|
} elseif ($ppeStatus === 'damaged') {
|
||||||
|
$statusClass = 'ppe-status-problem';
|
||||||
|
$statusLabel = 'Danneggiato';
|
||||||
|
}
|
||||||
|
?>
|
||||||
<div class="doc-card">
|
<div class="doc-card">
|
||||||
<div class="d-flex justify-content-between align-items-start gap-2 mb-2">
|
<div class="d-flex justify-content-between align-items-start gap-2 mb-2">
|
||||||
<span class="doc-card-title">🦺 <?= htmlspecialchars($p['item_name']) ?></span>
|
<span class="doc-card-title">🦺 <?= htmlspecialchars($p['ppe_name']) ?></span>
|
||||||
<span class="small text-muted text-nowrap"><?= fmtDate($p['delivery_date']) ?></span>
|
<span class="<?= $statusClass ?>"><?= $statusLabel ?></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="doc-card-meta">
|
<div class="doc-card-meta">
|
||||||
|
<?php if (!empty($p['ppe_category'])): ?>
|
||||||
|
<span><b>Categoria:</b> <?= htmlspecialchars($p['ppe_category']) ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
<span><b>Consegna:</b> <?= fmtDate($p['assigned_date']) ?></span>
|
||||||
|
<?php if (!empty($p['expiry_date'])): ?>
|
||||||
|
<span><b>Scadenza:</b> <?= fmtDate($p['expiry_date']) ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
<?php if (!empty($p['delivered_by'])): ?>
|
<?php if (!empty($p['delivered_by'])): ?>
|
||||||
<span><b>Consegnato da:</b> <?= htmlspecialchars($p['delivered_by']) ?></span>
|
<span><b>Consegnato da:</b> <?= htmlspecialchars($p['delivered_by']) ?></span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -1068,17 +1505,23 @@ function fmtFileSize(?int $bytes): string
|
|||||||
<span><b>Note:</b> <?= htmlspecialchars($p['notes']) ?></span>
|
<span><b>Note:</b> <?= htmlspecialchars($p['notes']) ?></span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if ($canEdit): ?>
|
<?php if ($canEdit): ?>
|
||||||
<div class="doc-card-actions">
|
<div class="doc-card-actions">
|
||||||
<button class="btn btn-sm btn-outline-secondary edit-ppe"
|
<button class="btn btn-sm btn-outline-secondary edit-ppe"
|
||||||
data-id="<?= $pid ?>"
|
data-id="<?= $pid ?>"
|
||||||
data-item_name="<?= htmlspecialchars($p['item_name'], ENT_QUOTES) ?>"
|
data-ppe_item_id="<?= (int)$p['ppe_item_id'] ?>"
|
||||||
data-delivery_date="<?= htmlspecialchars($p['delivery_date'] ?? '', ENT_QUOTES) ?>"
|
data-assigned_date="<?= htmlspecialchars($p['assigned_date'] ?? '', ENT_QUOTES) ?>"
|
||||||
|
data-expiry_date="<?= htmlspecialchars($p['expiry_date'] ?? '', ENT_QUOTES) ?>"
|
||||||
data-delivered_by="<?= htmlspecialchars($p['delivered_by'] ?? '', ENT_QUOTES) ?>"
|
data-delivered_by="<?= htmlspecialchars($p['delivered_by'] ?? '', ENT_QUOTES) ?>"
|
||||||
|
data-status="<?= htmlspecialchars($p['status'] ?? 'assigned', ENT_QUOTES) ?>"
|
||||||
data-notes="<?= htmlspecialchars($p['notes'] ?? '', ENT_QUOTES) ?>">✏️ Modifica</button>
|
data-notes="<?= htmlspecialchars($p['notes'] ?? '', ENT_QUOTES) ?>">✏️ Modifica</button>
|
||||||
|
|
||||||
|
<?php if (($p['status'] ?? '') === 'assigned'): ?>
|
||||||
<button class="btn btn-sm btn-outline-danger delete-ppe"
|
<button class="btn btn-sm btn-outline-danger delete-ppe"
|
||||||
data-id="<?= $pid ?>"
|
data-id="<?= $pid ?>"
|
||||||
data-name="<?= htmlspecialchars($p['item_name'], ENT_QUOTES) ?>">🗑️</button>
|
data-name="<?= htmlspecialchars($p['ppe_name'], ENT_QUOTES) ?>">Rimuovi</button>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
@@ -1363,6 +1806,80 @@ function fmtFileSize(?int $bytes): string
|
|||||||
padding: 2px 8px;
|
padding: 2px 8px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ppe-required-box {
|
||||||
|
border: 1px solid #bfdbfe;
|
||||||
|
background: #eff6ff;
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 14px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ppe-required-title {
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1e3a8a;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ppe-required-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
border-top: 1px solid #dbeafe;
|
||||||
|
padding-top: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ppe-name-main {
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ppe-meta-small {
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ppe-status-assigned {
|
||||||
|
background: #dcfce7;
|
||||||
|
color: #166534;
|
||||||
|
border: 1px solid #bbf7d0;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ppe-status-missing {
|
||||||
|
background: #fee2e2;
|
||||||
|
color: #991b1b;
|
||||||
|
border: 1px solid #fecaca;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ppe-status-returned {
|
||||||
|
background: #e5e7eb;
|
||||||
|
color: #374151;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ppe-status-problem {
|
||||||
|
background: #fef3c7;
|
||||||
|
color: #92400e;
|
||||||
|
border: 1px solid #fde68a;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- TRAINING ATTACHMENTS MODAL (visible to everyone with profile access) -->
|
<!-- TRAINING ATTACHMENTS MODAL (visible to everyone with profile access) -->
|
||||||
@@ -1537,28 +2054,60 @@ function fmtFileSize(?int $bytes): string
|
|||||||
<h5 class="modal-title" id="ppeModalTitle">Aggiungi DPI</h5>
|
<h5 class="modal-title" id="ppeModalTitle">Aggiungi DPI</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form id="ppeForm">
|
<form id="ppeForm">
|
||||||
<input type="hidden" id="ppeId">
|
<input type="hidden" id="ppeId">
|
||||||
<input type="hidden" name="employee_id" id="ppeEmployeeId" value="<?= (int)$employee['id'] ?>">
|
<input type="hidden" name="employee_id" id="ppeEmployeeId" value="<?= (int)$employee['id'] ?>">
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label fw-semibold">DPI *</label>
|
<label class="form-label fw-semibold">DPI *</label>
|
||||||
<input type="text" class="form-control" id="ppeItemName" placeholder="es. Casco, Guanti, Scarpe antinfortunistiche" required>
|
<select class="form-select" id="ppeItemId" required>
|
||||||
|
<option value="">— Seleziona DPI —</option>
|
||||||
|
<?php foreach ($ppeItemsAll as $item): ?>
|
||||||
|
<option value="<?= (int)$item['id'] ?>"
|
||||||
|
data-validity_months="<?= $item['validity_months'] !== null ? (int)$item['validity_months'] : '' ?>">
|
||||||
|
<?= htmlspecialchars($item['name']) ?>
|
||||||
|
<?= !empty($item['category']) ? ' — ' . htmlspecialchars($item['category']) : '' ?>
|
||||||
|
<?= !empty($item['standard_reference']) ? ' — ' . htmlspecialchars($item['standard_reference']) : '' ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-md-6 mb-3">
|
<div class="col-12 col-md-6 mb-3">
|
||||||
<label class="form-label fw-semibold">Data Consegna</label>
|
<label class="form-label fw-semibold">Data Consegna</label>
|
||||||
<input type="date" class="form-control" id="ppeDeliveryDate">
|
<input type="date" class="form-control" id="ppeAssignedDate" value="<?= date('Y-m-d') ?>">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 col-md-6 mb-3">
|
<div class="col-12 col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-semibold">Data Scadenza</label>
|
||||||
|
<input type="date" class="form-control" id="ppeExpiryDate">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
<label class="form-label fw-semibold">Consegnato da</label>
|
<label class="form-label fw-semibold">Consegnato da</label>
|
||||||
<input type="text" class="form-control" id="ppeDeliveredBy" placeholder="Nome o azienda">
|
<input type="text" class="form-control" id="ppeDeliveredBy" placeholder="Nome o azienda">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Stato</label>
|
||||||
|
<select class="form-select" id="ppeStatus">
|
||||||
|
<option value="assigned">Assegnato</option>
|
||||||
|
<option value="returned">Restituito</option>
|
||||||
|
<option value="expired">Scaduto</option>
|
||||||
|
<option value="lost">Perso</option>
|
||||||
|
<option value="damaged">Danneggiato</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label fw-semibold">Note</label>
|
<label class="form-label fw-semibold">Note</label>
|
||||||
<textarea class="form-control" id="ppeNotes" rows="2" placeholder="Opzionale"></textarea>
|
<textarea class="form-control" id="ppeNotes" rows="2" placeholder="Opzionale"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<button type="submit" class="btn btn-add">💾 Salva</button>
|
<button type="submit" class="btn btn-add">💾 Salva</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1631,15 +2180,29 @@ function fmtFileSize(?int $bytes): string
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6 mb-3">
|
<div class="col-12 col-md-6 mb-3">
|
||||||
<label class="form-label fw-semibold">Mansione</label>
|
<label class="form-label fw-semibold">Sottomansioni</label>
|
||||||
<select class="form-select" id="editJobRoleId">
|
<select class="form-select" id="editJobSubRoleIds" multiple style="width:100%;">
|
||||||
<option value="">— Nessuna —</option>
|
<?php
|
||||||
<?php foreach ($jobRoles as $r): ?>
|
$currentGroup = null;
|
||||||
<option value="<?= (int)$r['id'] ?>" <?= ((int)($employee['job_role_id'] ?? 0) === (int)$r['id']) ? 'selected' : '' ?>>
|
foreach ($jobSubRolesAll as $sr):
|
||||||
<?= htmlspecialchars($r['name']) ?>
|
$groupName = $sr['job_role_name'] ?: 'Senza mansione';
|
||||||
|
if ($currentGroup !== $groupName):
|
||||||
|
if ($currentGroup !== null): ?>
|
||||||
|
</optgroup>
|
||||||
|
<?php endif; ?>
|
||||||
|
<optgroup label="<?= htmlspecialchars($groupName, ENT_QUOTES, 'UTF-8') ?>">
|
||||||
|
<?php $currentGroup = $groupName;
|
||||||
|
endif;
|
||||||
|
?>
|
||||||
|
<option value="<?= (int)$sr['id'] ?>" <?= in_array((int)$sr['id'], $employeeSubRoleIds, true) ? 'selected' : '' ?>>
|
||||||
|
<?= htmlspecialchars($sr['name']) ?>
|
||||||
</option>
|
</option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
<?php if ($currentGroup !== null): ?>
|
||||||
|
</optgroup>
|
||||||
|
<?php endif; ?>
|
||||||
</select>
|
</select>
|
||||||
|
<small class="text-muted">Puoi selezionare più sottomansioni anche appartenenti a mansioni diverse.</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1698,6 +2261,7 @@ function fmtFileSize(?int $bytes): string
|
|||||||
<?php include('jsinclude.php'); ?>
|
<?php include('jsinclude.php'); ?>
|
||||||
<script src="https://cdn.datatables.net/1.13.8/js/jquery.dataTables.min.js"></script>
|
<script src="https://cdn.datatables.net/1.13.8/js/jquery.dataTables.min.js"></script>
|
||||||
<script src="https://cdn.datatables.net/1.13.8/js/dataTables.bootstrap5.min.js"></script>
|
<script src="https://cdn.datatables.net/1.13.8/js/dataTables.bootstrap5.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.full.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
||||||
@@ -2111,7 +2675,19 @@ function fmtFileSize(?int $bytes): string
|
|||||||
|
|
||||||
<?php if ($employee && $canEdit): ?>
|
<?php if ($employee && $canEdit): ?>
|
||||||
<script>
|
<script>
|
||||||
|
const jobSubRoleToRoleMap = <?= json_encode($jobSubRoleToRoleMap, JSON_UNESCAPED_UNICODE) ?>;
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
if ($('#editJobSubRoleIds').length) {
|
||||||
|
$('#editJobSubRoleIds').select2({
|
||||||
|
theme: 'bootstrap-5',
|
||||||
|
dropdownParent: $('#editPersonalModal'),
|
||||||
|
placeholder: 'Seleziona una o più sottomansioni...',
|
||||||
|
closeOnSelect: false,
|
||||||
|
width: '100%'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ---- UPLOAD DOCUMENT ----
|
// ---- UPLOAD DOCUMENT ----
|
||||||
$("#uploadDocumentForm").on("submit", function(e) {
|
$("#uploadDocumentForm").on("submit", function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -2315,35 +2891,83 @@ function fmtFileSize(?int $bytes): string
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---- PPE: open modal (add or edit) ----
|
// ---- PPE: Select2 ----
|
||||||
|
if ($('#ppeItemId').length) {
|
||||||
|
$('#ppeItemId').select2({
|
||||||
|
theme: 'bootstrap-5',
|
||||||
|
dropdownParent: $('#ppeModal'),
|
||||||
|
placeholder: 'Cerca DPI...',
|
||||||
|
width: '100%',
|
||||||
|
allowClear: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMonthsToDate(dateString, months) {
|
||||||
|
if (!dateString || !months) return '';
|
||||||
|
|
||||||
|
const date = new Date(dateString + 'T00:00:00');
|
||||||
|
if (isNaN(date.getTime())) return '';
|
||||||
|
|
||||||
|
date.setMonth(date.getMonth() + parseInt(months, 10));
|
||||||
|
|
||||||
|
const y = date.getFullYear();
|
||||||
|
const m = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const d = String(date.getDate()).padStart(2, '0');
|
||||||
|
|
||||||
|
return `${y}-${m}-${d}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#ppeItemId, #ppeAssignedDate').on('change', function() {
|
||||||
|
const validityMonths = $('#ppeItemId option:selected').data('validity_months');
|
||||||
|
const assignedDate = $('#ppeAssignedDate').val();
|
||||||
|
|
||||||
|
if (validityMonths && assignedDate && !$('#ppeExpiryDate').val()) {
|
||||||
|
$('#ppeExpiryDate').val(addMonthsToDate(assignedDate, validityMonths));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ---- PPE: open modal add ----
|
||||||
window.openPpeModal = function() {
|
window.openPpeModal = function() {
|
||||||
$("#ppeId").val('');
|
$("#ppeId").val('');
|
||||||
$("#ppeItemName").val('');
|
$("#ppeItemId").val('').trigger('change');
|
||||||
$("#ppeDeliveryDate").val('');
|
$("#ppeAssignedDate").val('<?= date('Y-m-d') ?>');
|
||||||
|
$("#ppeExpiryDate").val('');
|
||||||
$("#ppeDeliveredBy").val('');
|
$("#ppeDeliveredBy").val('');
|
||||||
|
$("#ppeStatus").val('assigned');
|
||||||
$("#ppeNotes").val('');
|
$("#ppeNotes").val('');
|
||||||
$("#ppeModalTitle").text('Aggiungi DPI');
|
$("#ppeModalTitle").text('Aggiungi DPI');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ---- PPE: open modal edit ----
|
||||||
$(document).on("click", ".edit-ppe", function() {
|
$(document).on("click", ".edit-ppe", function() {
|
||||||
const b = $(this);
|
const b = $(this);
|
||||||
|
|
||||||
$("#ppeId").val(b.data("id"));
|
$("#ppeId").val(b.data("id"));
|
||||||
$("#ppeItemName").val(b.data("item_name"));
|
$("#ppeItemId").val(String(b.data("ppe_item_id"))).trigger('change');
|
||||||
$("#ppeDeliveryDate").val(b.data("delivery_date"));
|
$("#ppeAssignedDate").val(b.data("assigned_date"));
|
||||||
|
$("#ppeExpiryDate").val(b.data("expiry_date"));
|
||||||
$("#ppeDeliveredBy").val(b.data("delivered_by"));
|
$("#ppeDeliveredBy").val(b.data("delivered_by"));
|
||||||
|
$("#ppeStatus").val(b.data("status") || 'assigned');
|
||||||
$("#ppeNotes").val(b.data("notes"));
|
$("#ppeNotes").val(b.data("notes"));
|
||||||
$("#ppeModalTitle").text('Modifica DPI');
|
$("#ppeModalTitle").text('Modifica DPI');
|
||||||
|
|
||||||
$("#ppeModal").modal("show");
|
$("#ppeModal").modal("show");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---- PPE: save ----
|
||||||
$("#ppeForm").on("submit", function(e) {
|
$("#ppeForm").on("submit", function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const p = new URLSearchParams();
|
const p = new URLSearchParams();
|
||||||
p.append('id', $("#ppeId").val());
|
p.append('id', $("#ppeId").val());
|
||||||
p.append('employee_id', $("#ppeEmployeeId").val());
|
p.append('employee_id', $("#ppeEmployeeId").val());
|
||||||
p.append('item_name', $("#ppeItemName").val().trim());
|
p.append('ppe_item_id', $("#ppeItemId").val());
|
||||||
p.append('delivery_date', $("#ppeDeliveryDate").val());
|
p.append('assigned_date', $("#ppeAssignedDate").val());
|
||||||
|
p.append('expiry_date', $("#ppeExpiryDate").val());
|
||||||
p.append('delivered_by', $("#ppeDeliveredBy").val().trim());
|
p.append('delivered_by', $("#ppeDeliveredBy").val().trim());
|
||||||
|
p.append('status', $("#ppeStatus").val());
|
||||||
p.append('notes', $("#ppeNotes").val().trim());
|
p.append('notes', $("#ppeNotes").val().trim());
|
||||||
|
|
||||||
fetch("ajax/employee_profile/save_ppe.php", {
|
fetch("ajax/employee_profile/save_ppe.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -2378,22 +3002,26 @@ function fmtFileSize(?int $bytes): string
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---- PPE: remove / mark as returned ----
|
||||||
$(document).on("click", ".delete-ppe", function() {
|
$(document).on("click", ".delete-ppe", function() {
|
||||||
const id = $(this).data("id");
|
const id = $(this).data("id");
|
||||||
const name = $(this).data("name");
|
const name = $(this).data("name");
|
||||||
|
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Confermi la cancellazione?",
|
title: "Confermi la rimozione?",
|
||||||
text: name ? ("DPI: " + name) : "Il DPI verrà cancellato.",
|
text: name ? ("DPI: " + name) : "Il DPI verrà segnato come restituito.",
|
||||||
icon: "warning",
|
icon: "warning",
|
||||||
showCancelButton: true,
|
showCancelButton: true,
|
||||||
confirmButtonColor: "#d33",
|
confirmButtonColor: "#d33",
|
||||||
cancelButtonColor: "#6c757d",
|
cancelButtonColor: "#6c757d",
|
||||||
confirmButtonText: "Sì, cancella",
|
confirmButtonText: "Sì, rimuovi",
|
||||||
cancelButtonText: "Annulla"
|
cancelButtonText: "Annulla"
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
if (!result.isConfirmed) return;
|
if (!result.isConfirmed) return;
|
||||||
|
|
||||||
const p = new URLSearchParams();
|
const p = new URLSearchParams();
|
||||||
p.append('id', id);
|
p.append('id', id);
|
||||||
|
|
||||||
fetch("ajax/employee_profile/delete_ppe.php", {
|
fetch("ajax/employee_profile/delete_ppe.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -2406,7 +3034,7 @@ function fmtFileSize(?int $bytes): string
|
|||||||
if (data.success) {
|
if (data.success) {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
icon: "success",
|
icon: "success",
|
||||||
title: "Cancellato!",
|
title: "Rimosso!",
|
||||||
confirmButtonColor: "#3085d6"
|
confirmButtonColor: "#3085d6"
|
||||||
})
|
})
|
||||||
.then(() => location.reload());
|
.then(() => location.reload());
|
||||||
@@ -2414,7 +3042,7 @@ function fmtFileSize(?int $bytes): string
|
|||||||
Swal.fire({
|
Swal.fire({
|
||||||
icon: "error",
|
icon: "error",
|
||||||
title: "Errore",
|
title: "Errore",
|
||||||
text: data.message || "Impossibile cancellare."
|
text: data.message || "Impossibile rimuovere."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -2510,7 +3138,19 @@ function fmtFileSize(?int $bytes): string
|
|||||||
p.append('phone', $("#editPhone").val().trim());
|
p.append('phone', $("#editPhone").val().trim());
|
||||||
p.append('email', $("#editEmail").val().trim());
|
p.append('email', $("#editEmail").val().trim());
|
||||||
p.append('department_id', $("#editDepartmentId").val());
|
p.append('department_id', $("#editDepartmentId").val());
|
||||||
p.append('job_role_id', $("#editJobRoleId").val());
|
|
||||||
|
const selectedSubRoles = $("#editJobSubRoleIds").val() || [];
|
||||||
|
selectedSubRoles.forEach(function(subRoleId) {
|
||||||
|
p.append('job_sub_role_ids[]', subRoleId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Backward compatibility for ajax/employee_profile/save_personal.php:
|
||||||
|
// keep sending a legacy primary job_role_id/job_sub_role_id based on the first selected sub role.
|
||||||
|
const primarySubRoleId = selectedSubRoles.length ? selectedSubRoles[0] : '';
|
||||||
|
const primaryJobRoleId = primarySubRoleId && jobSubRoleToRoleMap[primarySubRoleId] ? jobSubRoleToRoleMap[primarySubRoleId] : '';
|
||||||
|
p.append('job_role_id', primaryJobRoleId);
|
||||||
|
p.append('job_sub_role_id', primarySubRoleId);
|
||||||
|
|
||||||
p.append('status', $("#editStatus").val());
|
p.append('status', $("#editStatus").val());
|
||||||
p.append('auth_user_id', $("#editAuthUserId").val());
|
p.append('auth_user_id', $("#editAuthUserId").val());
|
||||||
p.append('role_id', $("#editAuthUserId").val() ? ($("#editRoleId").val() || '') : '');
|
p.append('role_id', $("#editAuthUserId").val() ? ($("#editRoleId").val() || '') : '');
|
||||||
|
|||||||
+1306
-370
File diff suppressed because it is too large
Load Diff
@@ -307,25 +307,13 @@
|
|||||||
</li>
|
</li>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (userCan('hr.training_topics.view')) : ?>
|
|
||||||
<li>
|
|
||||||
<a href="training_topics.php">
|
|
||||||
<i class='bx bx-radio-circle'></i>Corsi di Formazione
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php if (userCan('hr.trainings.view')) : ?>
|
<?php if (userCan('hr.trainings.view')) : ?>
|
||||||
<li>
|
<li>
|
||||||
<a href="trainings.php">
|
<a href="trainings.php">
|
||||||
<i class='bx bx-radio-circle'></i>Storico Formazione
|
<i class='bx bx-radio-circle'></i>Gestione Formazione
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="training_calendar.php">
|
|
||||||
<i class='bx bx-radio-circle'></i>Calendario Formazione
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (userCan('hr.skills.view')) : ?>
|
<?php if (userCan('hr.skills.view')) : ?>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -155,12 +155,19 @@ $dashboardSections = [
|
|||||||
'open' => false,
|
'open' => false,
|
||||||
'buttons' => [
|
'buttons' => [
|
||||||
[
|
[
|
||||||
'label' => 'Employees',
|
'label' => 'Dipendenti',
|
||||||
'icon' => '👥',
|
'icon' => '👥',
|
||||||
'class' => 'btn-employees',
|
'class' => 'btn-employees',
|
||||||
'url' => 'employees.php',
|
'url' => 'employees.php',
|
||||||
'permission' => 'hr.employees.view',
|
'permission' => 'hr.employees.view',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'label' => 'Mansioni',
|
||||||
|
'icon' => '🧩',
|
||||||
|
'class' => 'btn-setup',
|
||||||
|
'url' => 'job-roles.php',
|
||||||
|
'permission' => 'hr.employees.view',
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'label' => 'Departments',
|
'label' => 'Departments',
|
||||||
'icon' => '🏢',
|
'icon' => '🏢',
|
||||||
@@ -169,14 +176,14 @@ $dashboardSections = [
|
|||||||
'permission' => 'hr.departments.view',
|
'permission' => 'hr.departments.view',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'label' => 'Corsi Formazione',
|
'label' => 'DPI',
|
||||||
'icon' => '📚',
|
'icon' => '🦺',
|
||||||
'class' => 'btn-setup',
|
'class' => 'btn-setup',
|
||||||
'url' => 'training_topics.php',
|
'url' => 'ppe-items.php',
|
||||||
'permission' => 'hr.training_topics.view',
|
'permission' => 'hr.employees.view',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'label' => 'Storico Formazione',
|
'label' => 'Gestione Formazione',
|
||||||
'icon' => '🎓',
|
'icon' => '🎓',
|
||||||
'class' => 'btn-setup',
|
'class' => 'btn-setup',
|
||||||
'url' => 'trainings.php',
|
'url' => 'trainings.php',
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
<?php
|
||||||
|
include('../../include/headscript.php');
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
function jsonResponse(array $data): void
|
||||||
|
{
|
||||||
|
echo json_encode($data);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeNullableInt($value): ?int
|
||||||
|
{
|
||||||
|
return (isset($value) && $value !== '') ? (int)$value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$isHrManager = Auth::user()->hasRole('Admin')
|
||||||
|
|| Auth::user()->hasRole('Superuser')
|
||||||
|
|| Auth::user()->hasRole('employee-hr')
|
||||||
|
|| Auth::user()->hasRole('manager');
|
||||||
|
|
||||||
|
if (!$isHrManager) {
|
||||||
|
jsonResponse(['success' => false, 'message' => 'Non autorizzato.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$employeeId = (int)($_POST['employee_id'] ?? 0);
|
||||||
|
$firstName = trim($_POST['first_name'] ?? '');
|
||||||
|
$lastName = trim($_POST['last_name'] ?? '');
|
||||||
|
$employeeCode = trim($_POST['employee_code'] ?? '');
|
||||||
|
$hireDate = trim($_POST['hire_date'] ?? '');
|
||||||
|
$address = trim($_POST['address'] ?? '');
|
||||||
|
$phone = trim($_POST['phone'] ?? '');
|
||||||
|
$email = trim($_POST['email'] ?? '');
|
||||||
|
$departmentId = normalizeNullableInt($_POST['department_id'] ?? '');
|
||||||
|
$status = trim($_POST['status'] ?? 'active');
|
||||||
|
$authUserId = normalizeNullableInt($_POST['auth_user_id'] ?? '');
|
||||||
|
$roleId = normalizeNullableInt($_POST['role_id'] ?? '');
|
||||||
|
|
||||||
|
$jobSubRoleIds = $_POST['job_sub_role_ids'] ?? [];
|
||||||
|
if (!is_array($jobSubRoleIds)) {
|
||||||
|
$jobSubRoleIds = [$jobSubRoleIds];
|
||||||
|
}
|
||||||
|
|
||||||
|
$jobSubRoleIds = array_values(array_unique(array_filter(array_map('intval', $jobSubRoleIds))));
|
||||||
|
|
||||||
|
if ($employeeId <= 0) {
|
||||||
|
jsonResponse(['success' => false, 'message' => 'ID dipendente non valido.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($firstName === '' || $lastName === '') {
|
||||||
|
jsonResponse(['success' => false, 'message' => 'Nome e cognome sono obbligatori.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
jsonResponse(['success' => false, 'message' => 'Email non valida.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array($status, ['active', 'inactive', 'suspended'], true)) {
|
||||||
|
$status = 'active';
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmtEmployee = $pdo->prepare('SELECT id FROM employees WHERE id = ? LIMIT 1');
|
||||||
|
$stmtEmployee->execute([$employeeId]);
|
||||||
|
if (!$stmtEmployee->fetchColumn()) {
|
||||||
|
jsonResponse(['success' => false, 'message' => 'Dipendente non trovato.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$primaryJobRoleId = null;
|
||||||
|
$primaryJobSubRoleId = null;
|
||||||
|
|
||||||
|
if ($jobSubRoleIds) {
|
||||||
|
$placeholders = implode(',', array_fill(0, count($jobSubRoleIds), '?'));
|
||||||
|
$stmtSubRoles = $pdo->prepare("\n SELECT id, job_role_id\n FROM job_sub_roles\n WHERE id IN ($placeholders)\n AND is_active = 1\n ");
|
||||||
|
$stmtSubRoles->execute($jobSubRoleIds);
|
||||||
|
$validRows = $stmtSubRoles->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$validMap = [];
|
||||||
|
foreach ($validRows as $row) {
|
||||||
|
$validMap[(int)$row['id']] = (int)$row['job_role_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$jobSubRoleIds = array_values(array_filter($jobSubRoleIds, static function ($id) use ($validMap) {
|
||||||
|
return isset($validMap[(int)$id]);
|
||||||
|
}));
|
||||||
|
|
||||||
|
if ($jobSubRoleIds) {
|
||||||
|
$primaryJobSubRoleId = (int)$jobSubRoleIds[0];
|
||||||
|
$primaryJobRoleId = $validMap[$primaryJobSubRoleId] ?? null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("\n UPDATE employees\n SET first_name = :first_name,\n last_name = :last_name,\n employee_code = :employee_code,\n hire_date = :hire_date,\n address = :address,\n phone = :phone,\n email = :email,\n department_id = :department_id,\n job_role_id = :job_role_id,\n job_sub_role_id = :job_sub_role_id,\n status = :status,\n auth_user_id = :auth_user_id,\n updated_at = NOW()\n WHERE id = :employee_id\n ");
|
||||||
|
$stmt->execute([
|
||||||
|
'first_name' => $firstName,
|
||||||
|
'last_name' => $lastName,
|
||||||
|
'employee_code' => $employeeCode !== '' ? $employeeCode : null,
|
||||||
|
'hire_date' => $hireDate !== '' ? $hireDate : null,
|
||||||
|
'address' => $address !== '' ? $address : null,
|
||||||
|
'phone' => $phone !== '' ? $phone : null,
|
||||||
|
'email' => $email !== '' ? $email : null,
|
||||||
|
'department_id' => $departmentId,
|
||||||
|
'job_role_id' => $primaryJobRoleId,
|
||||||
|
'job_sub_role_id' => $primaryJobSubRoleId,
|
||||||
|
'status' => $status,
|
||||||
|
'auth_user_id' => $authUserId,
|
||||||
|
'employee_id' => $employeeId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$stmtDelete = $pdo->prepare('DELETE FROM employee_job_sub_roles WHERE employee_id = ?');
|
||||||
|
$stmtDelete->execute([$employeeId]);
|
||||||
|
|
||||||
|
if ($jobSubRoleIds) {
|
||||||
|
$stmtInsert = $pdo->prepare("\n INSERT INTO employee_job_sub_roles\n (employee_id, job_sub_role_id, is_primary, created_at)\n VALUES\n (:employee_id, :job_sub_role_id, :is_primary, NOW())\n ");
|
||||||
|
|
||||||
|
foreach ($jobSubRoleIds as $index => $jobSubRoleId) {
|
||||||
|
$stmtInsert->execute([
|
||||||
|
'employee_id' => $employeeId,
|
||||||
|
'job_sub_role_id' => (int)$jobSubRoleId,
|
||||||
|
'is_primary' => $index === 0 ? 1 : 0,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($authUserId !== null && $roleId !== null) {
|
||||||
|
$checkRole = $pdo->prepare('SELECT COUNT(*) FROM auth_roles WHERE id = ?');
|
||||||
|
$checkRole->execute([$roleId]);
|
||||||
|
|
||||||
|
if ((int)$checkRole->fetchColumn() > 0) {
|
||||||
|
$stmtRole = $pdo->prepare('UPDATE auth_users SET role_id = :role_id, updated_at = NOW() WHERE id = :auth_user_id');
|
||||||
|
$stmtRole->execute([
|
||||||
|
'role_id' => $roleId,
|
||||||
|
'auth_user_id' => $authUserId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
jsonResponse(['success' => true]);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
if (isset($pdo) && $pdo->inTransaction()) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonResponse([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Group 3: Responsabili -->
|
<!-- Group 3: Responsabili -->
|
||||||
<div class="form-section-title">Responsabili</div>
|
<div class="form-section-title">Esecutore</div>
|
||||||
<div class="row g-3 mb-4">
|
<div class="row g-3 mb-4">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<label for="dlDepartments" class="form-label fw-semibold">Reparti</label>
|
<label for="dlDepartments" class="form-label fw-semibold">Reparti</label>
|
||||||
|
|||||||
@@ -967,7 +967,7 @@ function getContrastTextColor($hexColor)
|
|||||||
<th>Scadenza</th>
|
<th>Scadenza</th>
|
||||||
<th class="d-none d-lg-table-cell">Verifica</th>
|
<th class="d-none d-lg-table-cell">Verifica</th>
|
||||||
<th>Funzione</th>
|
<th>Funzione</th>
|
||||||
<th>Responsabili</th>
|
<th>Esecutore</th>
|
||||||
<th>Stato</th>
|
<th>Stato</th>
|
||||||
<th class="text-center" style="width:120px">Azioni</th>
|
<th class="text-center" style="width:120px">Azioni</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
Reference in New Issue
Block a user