Compare commits
111 Commits
4eae855e23
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 27cbc9f449 | |||
| 4c09a0dcb4 | |||
| 8bb23ee563 | |||
| 20571c9e4b | |||
| fdde16b113 | |||
| 33b627f328 | |||
| d96b4be9e0 | |||
| 088e518db1 | |||
| 789c547bc7 | |||
| e5bf546ae7 | |||
| 6dd13e5d7d | |||
| b1f2bb60e3 | |||
| f7e97f55e9 | |||
| 70b712ff3b | |||
| fdc3af01f3 | |||
| 3d54140280 | |||
| bfdbbbfc8f | |||
| 40a5771a4b | |||
| 9f5a585717 | |||
| 9ec5419a86 | |||
| c05091e020 | |||
| 0b470f290e | |||
| e74870c8d3 | |||
| 9001eff317 | |||
| 7cbd74111d | |||
| 650676037a | |||
| 2fc34c3cf4 | |||
| 955a7ed9e9 | |||
| cb221a8039 | |||
| ece1beb87f | |||
| e6a805f1f7 | |||
| fe84d446e7 | |||
| 2ddf575191 | |||
| d73a8bb8d3 | |||
| d155d1cbab | |||
| fa2f293835 | |||
| fc35adc7f9 | |||
| ac942dcdc8 | |||
| 5728afa788 | |||
| 1946648b1b | |||
| a1bcab3188 | |||
| 2bbeb11726 | |||
| dd5edab2f3 | |||
| 1fadc22178 | |||
| d3ee9a3790 | |||
| c387b71cae | |||
| b2cfec77df | |||
| 73b9a3d890 | |||
| 0550ffe923 | |||
| d2e5cc8b2b | |||
| d7b6a58407 | |||
| 174fa73c2c | |||
| 0bd41b8eb0 | |||
| 248ae63875 | |||
| d39e997beb | |||
| 1b23885659 | |||
| ad16d84b2e | |||
| 8cf74608b8 | |||
| 2642906a9b | |||
| d2f2a9089e | |||
| 53b990ff40 | |||
| f477f393ba | |||
| bc806f37f4 | |||
| f043b43791 | |||
| 245750f057 | |||
| 22e5b90fe4 | |||
| 31f22b4d92 | |||
| 340ebdcbce | |||
| 1edf7b7239 | |||
| 51cca3448a | |||
| 8732f21af8 | |||
| 3043522465 | |||
| 49435b8e44 | |||
| e876cb9775 | |||
| 7f78a61808 | |||
| 50c808e605 | |||
| 978b38c669 | |||
| 4683c4f40c | |||
| d9cbaf8df1 | |||
| 9649751ad8 | |||
| 824ae278d1 | |||
| 37909e8175 | |||
| 86782d26b2 | |||
| 329b3ffdeb | |||
| 9447f3cf00 | |||
| 5b7a8b57d5 | |||
| 7a2cac27cd | |||
| f9737fdf73 | |||
| 77e5820dcc | |||
| dd9d109dee | |||
| a1c9d9f789 | |||
| 4c63f16a54 | |||
| 6f3b933a71 | |||
| 779821a08b | |||
| 8edccbdfef | |||
| eeb1d0d5de | |||
| 711d3d5f73 | |||
| 85c8ddc985 | |||
| edcedb8f91 | |||
| a4b1fb9b1f | |||
| 92ec026afe | |||
| 29e4b41874 | |||
| eef9ae8d36 | |||
| 68c867a3f4 | |||
| a9827e4e81 | |||
| b51936f784 | |||
| 15b6f38e8b | |||
| 12c6cc5f95 | |||
| a0b12463c0 | |||
| 07ddcafd3f | |||
| 7843d4b1fc |
@@ -31,6 +31,8 @@ MAIL_USERNAME=null
|
|||||||
MAIL_PASSWORD=null
|
MAIL_PASSWORD=null
|
||||||
MAIL_ENCRYPTION=null
|
MAIL_ENCRYPTION=null
|
||||||
|
|
||||||
|
MANAGER_USER_ID=
|
||||||
|
|
||||||
PUSHER_APP_ID=
|
PUSHER_APP_ID=
|
||||||
PUSHER_APP_KEY=
|
PUSHER_APP_KEY=
|
||||||
PUSHER_APP_SECRET=
|
PUSHER_APP_SECRET=
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ auth.json
|
|||||||
# File XLSX temporanei importati
|
# File XLSX temporanei importati
|
||||||
/public/userarea/imported_trf/*.xlsx
|
/public/userarea/imported_trf/*.xlsx
|
||||||
/public/userarea/xlstemplates/*.xlsx
|
/public/userarea/xlstemplates/*.xlsx
|
||||||
|
/public/userarea/photos/matrici/allegati/
|
||||||
|
/public/userarea/photos/matrici/allegati/*
|
||||||
|
|
||||||
# Ignora cartelle di foto generate
|
# Ignora cartelle di foto generate
|
||||||
/public/photostrf/
|
/public/photostrf/
|
||||||
@@ -44,6 +46,7 @@ public/userarea/last_url.txt
|
|||||||
public/userarea/class/curl_auth_debug.log
|
public/userarea/class/curl_auth_debug.log
|
||||||
public/userarea/class/curl_request_debug.log
|
public/userarea/class/curl_request_debug.log
|
||||||
|
|
||||||
|
public/userarea/uploads/cad_area/originals/*
|
||||||
# Ignora tutti i log
|
# Ignora tutti i log
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
@@ -64,3 +67,6 @@ public/userarea/logsapi/commessaweb_customfields_763.json
|
|||||||
public/userarea/logsapi/commessaweb_invia_762.json
|
public/userarea/logsapi/commessaweb_invia_762.json
|
||||||
public/userarea/logsapi/commessaweb_invia_763.json
|
public/userarea/logsapi/commessaweb_invia_763.json
|
||||||
public/userarea/logsapi/last_auth_url.txt
|
public/userarea/logsapi/last_auth_url.txt
|
||||||
|
|
||||||
|
# User uploaded files
|
||||||
|
/public/userarea/files/
|
||||||
@@ -108,9 +108,17 @@ class LoginController extends Controller
|
|||||||
|
|
||||||
// Reindirizza in base al ruolo
|
// Reindirizza in base al ruolo
|
||||||
if ($user->hasRole('Admin')) {
|
if ($user->hasRole('Admin')) {
|
||||||
return redirect()->to('userarea/import_dashboard.php');
|
return redirect()->to('userarea/production_dashboard.php');
|
||||||
} elseif ($user->hasRole('User')) {
|
} elseif ($user->hasRole('User')) {
|
||||||
return redirect()->to('userarea/import_dashboard.php');
|
return redirect()->to('userarea/production_dashboard.php');
|
||||||
|
} elseif ($user->hasRole('HR')) {
|
||||||
|
return redirect()->to('userarea/production_dashboard.php');
|
||||||
|
} elseif ($user->hasRole('SuperUser')) {
|
||||||
|
return redirect()->to('userarea/production_dashboard.php');
|
||||||
|
} elseif ($user->hasRole('Management')) {
|
||||||
|
return redirect()->to('userarea/production_dashboard.php');
|
||||||
|
} elseif ($user->hasRole('Quality')) {
|
||||||
|
return redirect()->to('userarea/production_dashboard.php');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se il ruolo non è specificato, reindirizza alla home predefinita
|
// Se il ruolo non è specificato, reindirizza alla home predefinita
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
"phpmailer/phpmailer": "^6.9",
|
"phpmailer/phpmailer": "^6.9",
|
||||||
"phpoffice/phpspreadsheet": "^4.1",
|
"phpoffice/phpspreadsheet": "^4.1",
|
||||||
"proengsoft/laravel-jsvalidation": "^4.0.0",
|
"proengsoft/laravel-jsvalidation": "^4.0.0",
|
||||||
|
"robmorgan/phinx": "^0.16.11",
|
||||||
"socialiteproviders/microsoft": "^4.7",
|
"socialiteproviders/microsoft": "^4.7",
|
||||||
"spatie/laravel-query-builder": "^5.0",
|
"spatie/laravel-query-builder": "^5.0",
|
||||||
"vanguardapp/activity-log": "^6.0",
|
"vanguardapp/activity-log": "^6.0",
|
||||||
|
|||||||
Generated
+646
-2
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "9c4f1e3bc3ee2180211c055e70635aef",
|
"content-hash": "076e7721d08cfea8b06ce75dd8c6c576",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "akaunting/laravel-setting",
|
"name": "akaunting/laravel-setting",
|
||||||
@@ -251,6 +251,330 @@
|
|||||||
],
|
],
|
||||||
"time": "2023-11-29T23:19:16+00:00"
|
"time": "2023-11-29T23:19:16+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "cakephp/chronos",
|
||||||
|
"version": "3.5.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/cakephp/chronos.git",
|
||||||
|
"reference": "e6e777b534244911566face8a5dbdbd7f7bda5a6"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/cakephp/chronos/zipball/e6e777b534244911566face8a5dbdbd7f7bda5a6",
|
||||||
|
"reference": "e6e777b534244911566face8a5dbdbd7f7bda5a6",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.1",
|
||||||
|
"psr/clock": "^1.0"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"psr/clock-implementation": "1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"cakephp/cakephp-codesniffer": "^5.0",
|
||||||
|
"phpunit/phpunit": "^10.5.58 || ^11.5.3 || ^12.1.3"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Cake\\Chronos\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Brian Nesbitt",
|
||||||
|
"email": "brian@nesbot.com",
|
||||||
|
"homepage": "http://nesbot.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "The CakePHP Team",
|
||||||
|
"homepage": "https://cakephp.org"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A simple API extension for DateTime.",
|
||||||
|
"homepage": "https://cakephp.org",
|
||||||
|
"keywords": [
|
||||||
|
"date",
|
||||||
|
"datetime",
|
||||||
|
"time"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/cakephp/chronos/issues",
|
||||||
|
"source": "https://github.com/cakephp/chronos"
|
||||||
|
},
|
||||||
|
"time": "2026-04-10T02:50:39+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cakephp/core",
|
||||||
|
"version": "5.3.5",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/cakephp/core.git",
|
||||||
|
"reference": "eb012517900ed288f580aa3487e9a09f28ea85f9"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/cakephp/core/zipball/eb012517900ed288f580aa3487e9a09f28ea85f9",
|
||||||
|
"reference": "eb012517900ed288f580aa3487e9a09f28ea85f9",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"cakephp/utility": "^5.3.0",
|
||||||
|
"league/container": "^5.1",
|
||||||
|
"php": ">=8.2",
|
||||||
|
"psr/container": "^1.1 || ^2.0"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"psr/container-implementation": "^2.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"cakephp/cache": "To use Configure::store() and restore().",
|
||||||
|
"cakephp/event": "To use PluginApplicationInterface or plugin applications.",
|
||||||
|
"league/container": "To use Container and ServiceProvider classes"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-5.next": "5.4.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"functions.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Cake\\Core\\": "."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "CakePHP Community",
|
||||||
|
"homepage": "https://github.com/cakephp/core/graphs/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "CakePHP Framework Core classes",
|
||||||
|
"homepage": "https://cakephp.org",
|
||||||
|
"keywords": [
|
||||||
|
"cakephp",
|
||||||
|
"core",
|
||||||
|
"framework"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"forum": "https://stackoverflow.com/tags/cakephp",
|
||||||
|
"irc": "irc://irc.freenode.org/cakephp",
|
||||||
|
"issues": "https://github.com/cakephp/cakephp/issues",
|
||||||
|
"source": "https://github.com/cakephp/core"
|
||||||
|
},
|
||||||
|
"time": "2026-03-31T06:25:23+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cakephp/database",
|
||||||
|
"version": "5.3.5",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/cakephp/database.git",
|
||||||
|
"reference": "cf94dcb57c54a1a308fd866b038cd6995910e36e"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/cakephp/database/zipball/cf94dcb57c54a1a308fd866b038cd6995910e36e",
|
||||||
|
"reference": "cf94dcb57c54a1a308fd866b038cd6995910e36e",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"cakephp/chronos": "^3.3",
|
||||||
|
"cakephp/core": "^5.3.0",
|
||||||
|
"cakephp/datasource": "^5.3.0",
|
||||||
|
"php": ">=8.2",
|
||||||
|
"psr/log": "^3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"cakephp/i18n": "^5.3.0",
|
||||||
|
"cakephp/log": "^5.3.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"cakephp/i18n": "If you are using locale-aware datetime formats.",
|
||||||
|
"cakephp/log": "If you want to use query logging without providing a logger yourself."
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-5.next": "5.4.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Cake\\Database\\": "."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "CakePHP Community",
|
||||||
|
"homepage": "https://github.com/cakephp/database/graphs/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Flexible and powerful Database abstraction library with a familiar PDO-like API",
|
||||||
|
"homepage": "https://cakephp.org",
|
||||||
|
"keywords": [
|
||||||
|
"abstraction",
|
||||||
|
"cakephp",
|
||||||
|
"database",
|
||||||
|
"database abstraction",
|
||||||
|
"pdo"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"forum": "https://stackoverflow.com/tags/cakephp",
|
||||||
|
"irc": "irc://irc.freenode.org/cakephp",
|
||||||
|
"issues": "https://github.com/cakephp/cakephp/issues",
|
||||||
|
"source": "https://github.com/cakephp/database"
|
||||||
|
},
|
||||||
|
"time": "2026-03-31T06:25:23+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cakephp/datasource",
|
||||||
|
"version": "5.3.5",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/cakephp/datasource.git",
|
||||||
|
"reference": "512464eb27b19316b515ec338089b83822c9ab5a"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/cakephp/datasource/zipball/512464eb27b19316b515ec338089b83822c9ab5a",
|
||||||
|
"reference": "512464eb27b19316b515ec338089b83822c9ab5a",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"cakephp/core": "^5.3.0",
|
||||||
|
"php": ">=8.2",
|
||||||
|
"psr/simple-cache": "^2.0 || ^3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"cakephp/cache": "^5.3.0",
|
||||||
|
"cakephp/collection": "^5.3.0",
|
||||||
|
"cakephp/utility": "^5.3.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"cakephp/cache": "If you decide to use Query caching.",
|
||||||
|
"cakephp/collection": "If you decide to use ResultSetInterface.",
|
||||||
|
"cakephp/utility": "If you decide to use EntityTrait."
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-5.next": "5.4.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Cake\\Datasource\\": "."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "CakePHP Community",
|
||||||
|
"homepage": "https://github.com/cakephp/datasource/graphs/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Provides connection managing and traits for Entities and Queries that can be reused for different datastores",
|
||||||
|
"homepage": "https://cakephp.org",
|
||||||
|
"keywords": [
|
||||||
|
"cakephp",
|
||||||
|
"connection management",
|
||||||
|
"datasource",
|
||||||
|
"entity",
|
||||||
|
"query"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"forum": "https://stackoverflow.com/tags/cakephp",
|
||||||
|
"irc": "irc://irc.freenode.org/cakephp",
|
||||||
|
"issues": "https://github.com/cakephp/cakephp/issues",
|
||||||
|
"source": "https://github.com/cakephp/datasource"
|
||||||
|
},
|
||||||
|
"time": "2026-04-04T08:08:42+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cakephp/utility",
|
||||||
|
"version": "5.3.5",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/cakephp/utility.git",
|
||||||
|
"reference": "4ac9826fe5faa1505ec5aa3c171d6b58b6ab4e99"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/cakephp/utility/zipball/4ac9826fe5faa1505ec5aa3c171d6b58b6ab4e99",
|
||||||
|
"reference": "4ac9826fe5faa1505ec5aa3c171d6b58b6ab4e99",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"cakephp/core": "^5.3.0",
|
||||||
|
"php": ">=8.2"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-intl": "To use Text::transliterate() or Text::slug()",
|
||||||
|
"lib-ICU": "To use Text::transliterate() or Text::slug()"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-5.next": "5.4.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Cake\\Utility\\": "."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "CakePHP Community",
|
||||||
|
"homepage": "https://github.com/cakephp/utility/graphs/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "CakePHP Utility classes such as Inflector, String, Hash, and Security",
|
||||||
|
"homepage": "https://cakephp.org",
|
||||||
|
"keywords": [
|
||||||
|
"cakephp",
|
||||||
|
"hash",
|
||||||
|
"inflector",
|
||||||
|
"security",
|
||||||
|
"string",
|
||||||
|
"utility"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"forum": "https://stackoverflow.com/tags/cakephp",
|
||||||
|
"irc": "irc://irc.freenode.org/cakephp",
|
||||||
|
"issues": "https://github.com/cakephp/cakephp/issues",
|
||||||
|
"source": "https://github.com/cakephp/utility"
|
||||||
|
},
|
||||||
|
"time": "2026-03-09T09:38:36+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "carbonphp/carbon-doctrine-types",
|
"name": "carbonphp/carbon-doctrine-types",
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
@@ -2627,6 +2951,90 @@
|
|||||||
],
|
],
|
||||||
"time": "2022-12-11T20:36:23+00:00"
|
"time": "2022-12-11T20:36:23+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "league/container",
|
||||||
|
"version": "5.2.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/thephpleague/container.git",
|
||||||
|
"reference": "58accbc032f0090a9bd08326f93062c5a658b2c5"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/thephpleague/container/zipball/58accbc032f0090a9bd08326f93062c5a658b2c5",
|
||||||
|
"reference": "58accbc032f0090a9bd08326f93062c5a658b2c5",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"psr/container": "^2.0.2",
|
||||||
|
"psr/event-dispatcher": "^1.0"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"psr/container-implementation": "^1.0"
|
||||||
|
},
|
||||||
|
"replace": {
|
||||||
|
"orno/di": "~2.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"nette/php-generator": "^4.1",
|
||||||
|
"nikic/php-parser": "^5.0",
|
||||||
|
"phpstan/phpstan": "^2.1.11",
|
||||||
|
"phpunit/phpunit": "^10.5.45|^11.5.15|^12.0",
|
||||||
|
"roave/security-advisories": "dev-latest",
|
||||||
|
"scrutinizer/ocular": "^1.9",
|
||||||
|
"squizlabs/php_codesniffer": "^3.9"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-1.x": "1.x-dev",
|
||||||
|
"dev-2.x": "2.x-dev",
|
||||||
|
"dev-3.x": "3.x-dev",
|
||||||
|
"dev-4.x": "4.x-dev",
|
||||||
|
"dev-5.x": "5.x-dev",
|
||||||
|
"dev-master": "5.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"League\\Container\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Phil Bennett",
|
||||||
|
"email": "mail@philbennett.co.uk",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A fast and intuitive dependency injection container.",
|
||||||
|
"homepage": "https://github.com/thephpleague/container",
|
||||||
|
"keywords": [
|
||||||
|
"container",
|
||||||
|
"dependency",
|
||||||
|
"di",
|
||||||
|
"injection",
|
||||||
|
"league",
|
||||||
|
"provider",
|
||||||
|
"service"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/thephpleague/container/issues",
|
||||||
|
"source": "https://github.com/thephpleague/container/tree/5.2.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/philipobenito",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-03-19T18:52:39+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "league/flysystem",
|
"name": "league/flysystem",
|
||||||
"version": "3.28.0",
|
"version": "3.28.0",
|
||||||
@@ -4980,6 +5388,93 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-04-27T21:32:50+00:00"
|
"time": "2024-04-27T21:32:50+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "robmorgan/phinx",
|
||||||
|
"version": "0.16.11",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/cakephp/phinx.git",
|
||||||
|
"reference": "a03014fea316ba021fc0776982e5bed2d10228d4"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/cakephp/phinx/zipball/a03014fea316ba021fc0776982e5bed2d10228d4",
|
||||||
|
"reference": "a03014fea316ba021fc0776982e5bed2d10228d4",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"cakephp/database": "^5.0.2",
|
||||||
|
"composer-runtime-api": "^2.0",
|
||||||
|
"php-64bit": ">=8.1",
|
||||||
|
"psr/container": "^1.1|^2.0",
|
||||||
|
"symfony/config": "^4.0|^5.0|^6.0|^7.0|^8.0",
|
||||||
|
"symfony/console": "^6.0|^7.0|^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"cakephp/cakephp-codesniffer": "^5.0",
|
||||||
|
"cakephp/i18n": "^5.0",
|
||||||
|
"ext-json": "*",
|
||||||
|
"ext-pdo": "*",
|
||||||
|
"phpunit/phpunit": "^10.5",
|
||||||
|
"symfony/yaml": "^4.0|^5.0|^6.0|^7.0|^8.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-json": "Install if using JSON configuration format",
|
||||||
|
"ext-pdo": "PDO extension is needed",
|
||||||
|
"symfony/yaml": "Install if using YAML configuration format"
|
||||||
|
},
|
||||||
|
"bin": [
|
||||||
|
"bin/phinx"
|
||||||
|
],
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phinx\\": "src/Phinx/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Rob Morgan",
|
||||||
|
"email": "robbym@gmail.com",
|
||||||
|
"homepage": "https://robmorgan.id.au",
|
||||||
|
"role": "Lead Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Woody Gilk",
|
||||||
|
"email": "woody.gilk@gmail.com",
|
||||||
|
"homepage": "https://shadowhand.me",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Richard Quadling",
|
||||||
|
"email": "rquadling@gmail.com",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CakePHP Community",
|
||||||
|
"homepage": "https://github.com/cakephp/phinx/graphs/contributors",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Phinx makes it ridiculously easy to manage the database migrations for your PHP app.",
|
||||||
|
"homepage": "https://phinx.org",
|
||||||
|
"keywords": [
|
||||||
|
"database",
|
||||||
|
"database migrations",
|
||||||
|
"db",
|
||||||
|
"migrations",
|
||||||
|
"phinx"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/cakephp/phinx/issues",
|
||||||
|
"source": "https://github.com/cakephp/phinx/tree/0.16.11"
|
||||||
|
},
|
||||||
|
"time": "2026-03-15T00:04:32+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "socialiteproviders/manager",
|
"name": "socialiteproviders/manager",
|
||||||
"version": "v4.8.1",
|
"version": "v4.8.1",
|
||||||
@@ -5312,6 +5807,85 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-05-31T14:57:53+00:00"
|
"time": "2024-05-31T14:57:53+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/config",
|
||||||
|
"version": "v7.4.10",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/config.git",
|
||||||
|
"reference": "d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/config/zipball/d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57",
|
||||||
|
"reference": "d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.2",
|
||||||
|
"symfony/deprecation-contracts": "^2.5|^3",
|
||||||
|
"symfony/filesystem": "^7.1|^8.0",
|
||||||
|
"symfony/polyfill-ctype": "~1.8"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"symfony/finder": "<6.4",
|
||||||
|
"symfony/service-contracts": "<2.5"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/event-dispatcher": "^6.4|^7.0|^8.0",
|
||||||
|
"symfony/finder": "^6.4|^7.0|^8.0",
|
||||||
|
"symfony/messenger": "^6.4|^7.0|^8.0",
|
||||||
|
"symfony/service-contracts": "^2.5|^3",
|
||||||
|
"symfony/yaml": "^6.4|^7.0|^8.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\Config\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/config/tree/v7.4.10"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nicolas-grekas",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-05-03T14:20:49+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/console",
|
"name": "symfony/console",
|
||||||
"version": "v7.1.3",
|
"version": "v7.1.3",
|
||||||
@@ -5768,6 +6342,76 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-04-18T09:32:20+00:00"
|
"time": "2024-04-18T09:32:20+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/filesystem",
|
||||||
|
"version": "v7.4.11",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/filesystem.git",
|
||||||
|
"reference": "d721ea61b4a5fba8c5b6e7c1feda19efea144b50"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/filesystem/zipball/d721ea61b4a5fba8c5b6e7c1feda19efea144b50",
|
||||||
|
"reference": "d721ea61b4a5fba8c5b6e7c1feda19efea144b50",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.2",
|
||||||
|
"symfony/polyfill-ctype": "~1.8",
|
||||||
|
"symfony/polyfill-mbstring": "~1.8"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/process": "^6.4|^7.0|^8.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\Filesystem\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Provides basic utilities for the filesystem",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/filesystem/tree/v7.4.11"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nicolas-grekas",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-05-11T16:38:44+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/finder",
|
"name": "symfony/finder",
|
||||||
"version": "v7.1.3",
|
"version": "v7.1.3",
|
||||||
@@ -11355,6 +11999,6 @@
|
|||||||
"php": "^8.2.0",
|
"php": "^8.2.0",
|
||||||
"ext-json": "*"
|
"ext-json": "*"
|
||||||
},
|
},
|
||||||
"platform-dev": [],
|
"platform-dev": {},
|
||||||
"plugin-api-version": "2.6.0"
|
"plugin-api-version": "2.6.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class BaselineExistingDatabase extends AbstractMigration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Change Method.
|
||||||
|
*
|
||||||
|
* Write your reversible migrations using this method.
|
||||||
|
*
|
||||||
|
* More information on writing migrations is available here:
|
||||||
|
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
|
||||||
|
*
|
||||||
|
* Remember to call "create()" or "update()" and NOT "save()" when working
|
||||||
|
* with the Table class.
|
||||||
|
*/
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
// Baseline migration.
|
||||||
|
// Existing database structure starts being tracked from this point.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class CreatePhinxTestTable extends AbstractMigration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Change Method.
|
||||||
|
*
|
||||||
|
* Write your reversible migrations using this method.
|
||||||
|
*
|
||||||
|
* More information on writing migrations is available here:
|
||||||
|
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
|
||||||
|
*
|
||||||
|
* Remember to call "create()" or "update()" and NOT "save()" when working
|
||||||
|
* with the Table class.
|
||||||
|
*/
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
$table = $this->table('phinx_test_table');
|
||||||
|
|
||||||
|
$table
|
||||||
|
->addColumn('name', 'string', [
|
||||||
|
'limit' => 100,
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->addColumn('created_at', 'timestamp', [
|
||||||
|
'default' => 'CURRENT_TIMESTAMP',
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->create();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class AddFunctionsToScadDeadlines extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
$this->table('scad_functions', [
|
||||||
|
'id' => false,
|
||||||
|
'primary_key' => ['id'],
|
||||||
|
'collation' => 'utf8mb4_unicode_ci',
|
||||||
|
'encoding' => 'utf8mb4',
|
||||||
|
])
|
||||||
|
->addColumn('id', 'integer', [
|
||||||
|
'identity' => true,
|
||||||
|
'signed' => false,
|
||||||
|
])
|
||||||
|
->addColumn('name', 'string', [
|
||||||
|
'limit' => 255,
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->addColumn('description', 'text', [
|
||||||
|
'null' => true,
|
||||||
|
])
|
||||||
|
->addColumn('status', 'string', [
|
||||||
|
'limit' => 20,
|
||||||
|
'null' => false,
|
||||||
|
'default' => 'active',
|
||||||
|
])
|
||||||
|
->addColumn('created_at', 'timestamp', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => 'CURRENT_TIMESTAMP',
|
||||||
|
])
|
||||||
|
->addColumn('updated_at', 'timestamp', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => 'CURRENT_TIMESTAMP',
|
||||||
|
'update' => 'CURRENT_TIMESTAMP',
|
||||||
|
])
|
||||||
|
->addIndex(['name'], [
|
||||||
|
'unique' => true,
|
||||||
|
'name' => 'uniq_scad_functions_name',
|
||||||
|
])
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$this->table('scad_deadlines')
|
||||||
|
->addColumn('function_id', 'integer', [
|
||||||
|
'signed' => false,
|
||||||
|
'null' => true,
|
||||||
|
'after' => 'subject_id',
|
||||||
|
])
|
||||||
|
->addIndex(['function_id'], [
|
||||||
|
'name' => 'idx_scad_deadlines_function_id',
|
||||||
|
])
|
||||||
|
->addForeignKey('function_id', 'scad_functions', 'id', [
|
||||||
|
'delete' => 'SET_NULL',
|
||||||
|
'update' => 'CASCADE',
|
||||||
|
'constraint' => 'fk_scad_deadlines_function',
|
||||||
|
])
|
||||||
|
->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class CreateJobSubRolesTable extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
if (!$this->hasTable('job_roles')) {
|
||||||
|
$rolesTable = $this->table('job_roles', [
|
||||||
|
'id' => false,
|
||||||
|
'primary_key' => ['id'],
|
||||||
|
'collation' => 'utf8mb4_unicode_ci',
|
||||||
|
'encoding' => 'utf8mb4',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$rolesTable
|
||||||
|
->addColumn('id', 'integer', [
|
||||||
|
'identity' => true,
|
||||||
|
'signed' => 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(['is_active'], [
|
||||||
|
'name' => 'idx_job_roles_is_active',
|
||||||
|
])
|
||||||
|
->addIndex(['sort_order'], [
|
||||||
|
'name' => 'idx_job_roles_sort_order',
|
||||||
|
])
|
||||||
|
->create();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->hasTable('job_sub_roles')) {
|
||||||
|
$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,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class AddJobSubRoleIdToEmployeesTable extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
if (!$this->hasTable('employees')) {
|
||||||
|
throw new RuntimeException('Table employees does not exist.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = $this->table('employees');
|
||||||
|
|
||||||
|
if (!$table->hasColumn('job_role_id')) {
|
||||||
|
$table
|
||||||
|
->addColumn('job_role_id', 'integer', [
|
||||||
|
'signed' => false,
|
||||||
|
'null' => true,
|
||||||
|
'after' => 'department_id',
|
||||||
|
])
|
||||||
|
->addIndex(['job_role_id'], [
|
||||||
|
'name' => 'idx_employees_job_role_id',
|
||||||
|
])
|
||||||
|
->addForeignKey(
|
||||||
|
'job_role_id',
|
||||||
|
'job_roles',
|
||||||
|
'id',
|
||||||
|
[
|
||||||
|
'delete' => 'SET_NULL',
|
||||||
|
'update' => 'CASCADE',
|
||||||
|
'constraint' => 'fk_employees_job_role',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = $this->table('employees');
|
||||||
|
|
||||||
|
if (!$table->hasColumn('job_sub_role_id')) {
|
||||||
|
$afterColumn = $table->hasColumn('job_role_id') ? 'job_role_id' : 'department_id';
|
||||||
|
|
||||||
|
$table
|
||||||
|
->addColumn('job_sub_role_id', 'integer', [
|
||||||
|
'signed' => false,
|
||||||
|
'null' => true,
|
||||||
|
'after' => $afterColumn,
|
||||||
|
])
|
||||||
|
->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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
if (!$this->hasTable('employees')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = $this->table('employees');
|
||||||
|
|
||||||
|
if ($table->hasForeignKey('job_sub_role_id')) {
|
||||||
|
$table->dropForeignKey('job_sub_role_id')->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($table->hasForeignKey('job_role_id')) {
|
||||||
|
$table->dropForeignKey('job_role_id')->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = $this->table('employees');
|
||||||
|
|
||||||
|
if ($table->hasIndexByName('idx_employees_job_sub_role_id')) {
|
||||||
|
$table->removeIndexByName('idx_employees_job_sub_role_id')->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($table->hasIndexByName('idx_employees_job_role_id')) {
|
||||||
|
$table->removeIndexByName('idx_employees_job_role_id')->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = $this->table('employees');
|
||||||
|
|
||||||
|
if ($table->hasColumn('job_sub_role_id')) {
|
||||||
|
$table->removeColumn('job_sub_role_id')->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($table->hasColumn('job_role_id')) {
|
||||||
|
$table->removeColumn('job_role_id')->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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class CreateCompanyFunctionsTable extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
if (!$this->hasTable('company_functions')) {
|
||||||
|
$table = $this->table('company_functions', [
|
||||||
|
'id' => false,
|
||||||
|
'primary_key' => ['id'],
|
||||||
|
'signed' => false,
|
||||||
|
'collation' => 'utf8mb4_general_ci',
|
||||||
|
'encoding' => 'utf8mb4',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$table
|
||||||
|
->addColumn('id', 'integer', [
|
||||||
|
'identity' => true,
|
||||||
|
'signed' => false,
|
||||||
|
])
|
||||||
|
->addColumn('function_name', 'string', [
|
||||||
|
'limit' => 150,
|
||||||
|
'null' => false,
|
||||||
|
'comment' => 'Function name, for example RSPP, Medico del lavoro, RLS',
|
||||||
|
])
|
||||||
|
->addColumn('person_full_name', 'string', [
|
||||||
|
'limit' => 200,
|
||||||
|
'null' => false,
|
||||||
|
'comment' => 'Full name and surname of the person assigned to the function',
|
||||||
|
])
|
||||||
|
->addColumn('phone', 'string', [
|
||||||
|
'limit' => 80,
|
||||||
|
'null' => true,
|
||||||
|
])
|
||||||
|
->addColumn('email', 'string', [
|
||||||
|
'limit' => 190,
|
||||||
|
'null' => true,
|
||||||
|
])
|
||||||
|
->addColumn('notes', 'text', [
|
||||||
|
'null' => true,
|
||||||
|
])
|
||||||
|
->addColumn('sort_order', 'integer', [
|
||||||
|
'signed' => false,
|
||||||
|
'null' => false,
|
||||||
|
'default' => 0,
|
||||||
|
])
|
||||||
|
->addColumn('is_active', 'boolean', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => true,
|
||||||
|
])
|
||||||
|
->addColumn('created_at', 'timestamp', [
|
||||||
|
'null' => true,
|
||||||
|
'default' => 'CURRENT_TIMESTAMP',
|
||||||
|
])
|
||||||
|
->addColumn('updated_at', 'timestamp', [
|
||||||
|
'null' => true,
|
||||||
|
'default' => null,
|
||||||
|
'update' => 'CURRENT_TIMESTAMP',
|
||||||
|
])
|
||||||
|
->addIndex(['function_name'], [
|
||||||
|
'name' => 'idx_company_functions_function_name',
|
||||||
|
])
|
||||||
|
->addIndex(['person_full_name'], [
|
||||||
|
'name' => 'idx_company_functions_person_full_name',
|
||||||
|
])
|
||||||
|
->addIndex(['email'], [
|
||||||
|
'name' => 'idx_company_functions_email',
|
||||||
|
])
|
||||||
|
->addIndex(['is_active', 'sort_order'], [
|
||||||
|
'name' => 'idx_company_functions_active_sort',
|
||||||
|
])
|
||||||
|
->create();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->execute("
|
||||||
|
INSERT INTO company_functions
|
||||||
|
(function_name, person_full_name, phone, email, notes, sort_order, is_active, created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
('RSPP', '', NULL, NULL, NULL, 10, 1, NOW(), NOW()),
|
||||||
|
('Medico del lavoro', '', NULL, NULL, NULL, 20, 1, NOW(), NOW()),
|
||||||
|
('RLS', '', NULL, NULL, NULL, 30, 1, NOW(), NOW())
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
if ($this->hasTable('company_functions')) {
|
||||||
|
$this->table('company_functions')->drop()->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class AlterScadFunctionsAddContactFields extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
if (!$this->hasTable('scad_functions')) {
|
||||||
|
throw new RuntimeException('Table scad_functions does not exist.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = $this->table('scad_functions');
|
||||||
|
|
||||||
|
if (!$table->hasColumn('person_full_name')) {
|
||||||
|
$table->addColumn('person_full_name', 'string', [
|
||||||
|
'limit' => 200,
|
||||||
|
'null' => true,
|
||||||
|
'after' => 'description',
|
||||||
|
'comment' => 'Full name and surname of the person assigned to the function',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('phone')) {
|
||||||
|
$table->addColumn('phone', 'string', [
|
||||||
|
'limit' => 80,
|
||||||
|
'null' => true,
|
||||||
|
'after' => 'person_full_name',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('email')) {
|
||||||
|
$table->addColumn('email', 'string', [
|
||||||
|
'limit' => 190,
|
||||||
|
'null' => true,
|
||||||
|
'after' => 'phone',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('notes')) {
|
||||||
|
$table->addColumn('notes', 'text', [
|
||||||
|
'null' => true,
|
||||||
|
'after' => 'email',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('sort_order')) {
|
||||||
|
$table->addColumn('sort_order', 'integer', [
|
||||||
|
'signed' => false,
|
||||||
|
'null' => false,
|
||||||
|
'default' => 0,
|
||||||
|
'after' => 'status',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasIndexByName('idx_scad_functions_name')) {
|
||||||
|
$table->addIndex(['name'], [
|
||||||
|
'name' => 'idx_scad_functions_name',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasIndexByName('idx_scad_functions_person_full_name')) {
|
||||||
|
$table->addIndex(['person_full_name'], [
|
||||||
|
'name' => 'idx_scad_functions_person_full_name',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasIndexByName('idx_scad_functions_email')) {
|
||||||
|
$table->addIndex(['email'], [
|
||||||
|
'name' => 'idx_scad_functions_email',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasIndexByName('idx_scad_functions_status_sort')) {
|
||||||
|
$table->addIndex(['status', 'sort_order'], [
|
||||||
|
'name' => 'idx_scad_functions_status_sort',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$table->update();
|
||||||
|
|
||||||
|
// Set a default order for existing rows without changing their names.
|
||||||
|
$this->execute("
|
||||||
|
UPDATE scad_functions
|
||||||
|
SET sort_order = id * 10
|
||||||
|
WHERE sort_order = 0
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
if (!$this->hasTable('scad_functions')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = $this->table('scad_functions');
|
||||||
|
|
||||||
|
if ($table->hasIndexByName('idx_scad_functions_status_sort')) {
|
||||||
|
$table->removeIndexByName('idx_scad_functions_status_sort');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($table->hasIndexByName('idx_scad_functions_email')) {
|
||||||
|
$table->removeIndexByName('idx_scad_functions_email');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($table->hasIndexByName('idx_scad_functions_person_full_name')) {
|
||||||
|
$table->removeIndexByName('idx_scad_functions_person_full_name');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($table->hasIndexByName('idx_scad_functions_name')) {
|
||||||
|
$table->removeIndexByName('idx_scad_functions_name');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($table->hasColumn('sort_order')) {
|
||||||
|
$table->removeColumn('sort_order');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($table->hasColumn('notes')) {
|
||||||
|
$table->removeColumn('notes');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($table->hasColumn('email')) {
|
||||||
|
$table->removeColumn('email');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($table->hasColumn('phone')) {
|
||||||
|
$table->removeColumn('phone');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($table->hasColumn('person_full_name')) {
|
||||||
|
$table->removeColumn('person_full_name');
|
||||||
|
}
|
||||||
|
|
||||||
|
$table->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class CreateCadAreaJobsTable extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
$table = $this->table('cad_area_jobs');
|
||||||
|
|
||||||
|
$table
|
||||||
|
->addColumn('iduser', 'integer', [
|
||||||
|
'null' => true,
|
||||||
|
'signed' => false,
|
||||||
|
'limit' => 10,
|
||||||
|
])
|
||||||
|
->addColumn('original_filename', 'string', [
|
||||||
|
'limit' => 255,
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->addColumn('stored_filename', 'string', [
|
||||||
|
'limit' => 255,
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->addColumn('file_path', 'string', [
|
||||||
|
'limit' => 500,
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->addColumn('file_url', 'string', [
|
||||||
|
'limit' => 500,
|
||||||
|
'null' => true,
|
||||||
|
])
|
||||||
|
->addColumn('file_size', 'integer', [
|
||||||
|
'null' => true,
|
||||||
|
'signed' => false,
|
||||||
|
])
|
||||||
|
->addColumn('status', 'enum', [
|
||||||
|
'values' => [
|
||||||
|
'uploaded',
|
||||||
|
'processing',
|
||||||
|
'completed',
|
||||||
|
'error',
|
||||||
|
],
|
||||||
|
'default' => 'uploaded',
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->addColumn('area_mm2', 'decimal', [
|
||||||
|
'precision' => 18,
|
||||||
|
'scale' => 6,
|
||||||
|
'null' => true,
|
||||||
|
])
|
||||||
|
->addColumn('area_cm2', 'decimal', [
|
||||||
|
'precision' => 18,
|
||||||
|
'scale' => 6,
|
||||||
|
'null' => true,
|
||||||
|
])
|
||||||
|
->addColumn('area_m2', 'decimal', [
|
||||||
|
'precision' => 18,
|
||||||
|
'scale' => 9,
|
||||||
|
'null' => true,
|
||||||
|
])
|
||||||
|
->addColumn('scale_detected', 'string', [
|
||||||
|
'limit' => 50,
|
||||||
|
'null' => true,
|
||||||
|
])
|
||||||
|
->addColumn('confidence', 'string', [
|
||||||
|
'limit' => 50,
|
||||||
|
'null' => true,
|
||||||
|
])
|
||||||
|
->addColumn('message', 'text', [
|
||||||
|
'null' => true,
|
||||||
|
])
|
||||||
|
->addColumn('python_response', 'text', [
|
||||||
|
'null' => true,
|
||||||
|
])
|
||||||
|
->addColumn('created_at', 'timestamp', [
|
||||||
|
'default' => 'CURRENT_TIMESTAMP',
|
||||||
|
'null' => true,
|
||||||
|
])
|
||||||
|
->addColumn('updated_at', 'timestamp', [
|
||||||
|
'default' => 'CURRENT_TIMESTAMP',
|
||||||
|
'update' => 'CURRENT_TIMESTAMP',
|
||||||
|
'null' => true,
|
||||||
|
])
|
||||||
|
->addIndex(['iduser'], [
|
||||||
|
'name' => 'idx_cad_area_jobs_iduser',
|
||||||
|
])
|
||||||
|
->addIndex(['status'], [
|
||||||
|
'name' => 'idx_cad_area_jobs_status',
|
||||||
|
])
|
||||||
|
->create();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class AddNotifyFunctionToScadDeadlines extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
if (!$this->hasTable('scad_deadlines')) {
|
||||||
|
throw new RuntimeException('Table scad_deadlines does not exist.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = $this->table('scad_deadlines');
|
||||||
|
|
||||||
|
if (!$table->hasColumn('notify_function')) {
|
||||||
|
$table
|
||||||
|
->addColumn('notify_function', 'boolean', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => false,
|
||||||
|
'after' => 'function_id',
|
||||||
|
'comment' => 'Send deadline reminder also to the linked function email',
|
||||||
|
])
|
||||||
|
->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
if (!$this->hasTable('scad_deadlines')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = $this->table('scad_deadlines');
|
||||||
|
|
||||||
|
if ($table->hasColumn('notify_function')) {
|
||||||
|
$table
|
||||||
|
->removeColumn('notify_function')
|
||||||
|
->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class AddRoiFieldsToCadAreaJobsTable extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
$table = $this->table('cad_area_jobs');
|
||||||
|
|
||||||
|
if (!$table->hasColumn('roi_x')) {
|
||||||
|
$table->addColumn('roi_x', 'decimal', [
|
||||||
|
'precision' => 12,
|
||||||
|
'scale' => 6,
|
||||||
|
'null' => true,
|
||||||
|
'after' => 'file_size',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('roi_y')) {
|
||||||
|
$table->addColumn('roi_y', 'decimal', [
|
||||||
|
'precision' => 12,
|
||||||
|
'scale' => 6,
|
||||||
|
'null' => true,
|
||||||
|
'after' => 'roi_x',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('roi_width')) {
|
||||||
|
$table->addColumn('roi_width', 'decimal', [
|
||||||
|
'precision' => 12,
|
||||||
|
'scale' => 6,
|
||||||
|
'null' => true,
|
||||||
|
'after' => 'roi_y',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('roi_height')) {
|
||||||
|
$table->addColumn('roi_height', 'decimal', [
|
||||||
|
'precision' => 12,
|
||||||
|
'scale' => 6,
|
||||||
|
'null' => true,
|
||||||
|
'after' => 'roi_width',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('roi_page')) {
|
||||||
|
$table->addColumn('roi_page', 'integer', [
|
||||||
|
'null' => true,
|
||||||
|
'default' => 1,
|
||||||
|
'after' => 'roi_height',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('calculation_mode')) {
|
||||||
|
$table->addColumn('calculation_mode', 'string', [
|
||||||
|
'limit' => 50,
|
||||||
|
'null' => true,
|
||||||
|
'default' => 'auto_roi',
|
||||||
|
'after' => 'roi_page',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$table->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class AddResultDetailFieldsToCadAreaJobsTable extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
$table = $this->table('cad_area_jobs');
|
||||||
|
|
||||||
|
if (!$table->hasColumn('width_mm')) {
|
||||||
|
$table->addColumn('width_mm', 'decimal', [
|
||||||
|
'precision' => 18,
|
||||||
|
'scale' => 6,
|
||||||
|
'null' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('height_mm')) {
|
||||||
|
$table->addColumn('height_mm', 'decimal', [
|
||||||
|
'precision' => 18,
|
||||||
|
'scale' => 6,
|
||||||
|
'null' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('scale_used')) {
|
||||||
|
$table->addColumn('scale_used', 'decimal', [
|
||||||
|
'precision' => 12,
|
||||||
|
'scale' => 6,
|
||||||
|
'null' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('strategy_used')) {
|
||||||
|
$table->addColumn('strategy_used', 'string', [
|
||||||
|
'limit' => 100,
|
||||||
|
'null' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$table->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class AddManualTracingFieldsToCadAreaJobsTable extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
$table = $this->table('cad_area_jobs');
|
||||||
|
|
||||||
|
if (!$table->hasColumn('width_mm')) {
|
||||||
|
$table->addColumn('width_mm', 'decimal', [
|
||||||
|
'precision' => 18,
|
||||||
|
'scale' => 6,
|
||||||
|
'null' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('height_mm')) {
|
||||||
|
$table->addColumn('height_mm', 'decimal', [
|
||||||
|
'precision' => 18,
|
||||||
|
'scale' => 6,
|
||||||
|
'null' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('scale_used')) {
|
||||||
|
$table->addColumn('scale_used', 'decimal', [
|
||||||
|
'precision' => 12,
|
||||||
|
'scale' => 6,
|
||||||
|
'null' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('strategy_used')) {
|
||||||
|
$table->addColumn('strategy_used', 'string', [
|
||||||
|
'limit' => 100,
|
||||||
|
'null' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('manual_calibration_px')) {
|
||||||
|
$table->addColumn('manual_calibration_px', 'decimal', [
|
||||||
|
'precision' => 18,
|
||||||
|
'scale' => 6,
|
||||||
|
'null' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('manual_calibration_mm')) {
|
||||||
|
$table->addColumn('manual_calibration_mm', 'decimal', [
|
||||||
|
'precision' => 18,
|
||||||
|
'scale' => 6,
|
||||||
|
'null' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('manual_mm_per_px')) {
|
||||||
|
$table->addColumn('manual_mm_per_px', 'decimal', [
|
||||||
|
'precision' => 18,
|
||||||
|
'scale' => 10,
|
||||||
|
'null' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('manual_polygon_json')) {
|
||||||
|
$table->addColumn('manual_polygon_json', 'text', [
|
||||||
|
'null' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('manual_area_mm2')) {
|
||||||
|
$table->addColumn('manual_area_mm2', 'decimal', [
|
||||||
|
'precision' => 18,
|
||||||
|
'scale' => 6,
|
||||||
|
'null' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('manual_area_cm2')) {
|
||||||
|
$table->addColumn('manual_area_cm2', 'decimal', [
|
||||||
|
'precision' => 18,
|
||||||
|
'scale' => 6,
|
||||||
|
'null' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('manual_width_mm')) {
|
||||||
|
$table->addColumn('manual_width_mm', 'decimal', [
|
||||||
|
'precision' => 18,
|
||||||
|
'scale' => 6,
|
||||||
|
'null' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('manual_height_mm')) {
|
||||||
|
$table->addColumn('manual_height_mm', 'decimal', [
|
||||||
|
'precision' => 18,
|
||||||
|
'scale' => 6,
|
||||||
|
'null' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('manual_status')) {
|
||||||
|
$table->addColumn('manual_status', 'string', [
|
||||||
|
'limit' => 50,
|
||||||
|
'null' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$table->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class AddManualHoleFieldsToCadAreaJobsTable extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
$table = $this->table('cad_area_jobs');
|
||||||
|
|
||||||
|
if (!$table->hasColumn('manual_outer_area_mm2')) {
|
||||||
|
$table->addColumn('manual_outer_area_mm2', 'decimal', [
|
||||||
|
'precision' => 18,
|
||||||
|
'scale' => 6,
|
||||||
|
'null' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('manual_holes_area_mm2')) {
|
||||||
|
$table->addColumn('manual_holes_area_mm2', 'decimal', [
|
||||||
|
'precision' => 18,
|
||||||
|
'scale' => 6,
|
||||||
|
'null' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table->hasColumn('manual_holes_json')) {
|
||||||
|
$table->addColumn('manual_holes_json', 'text', [
|
||||||
|
'null' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$table->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
# 1. Database migration
|
||||||
|
|
||||||
|
```mysql
|
||||||
|
ALTER TABLE employees
|
||||||
|
ADD COLUMN address varchar(500) DEFAULT NULL AFTER last_name,
|
||||||
|
ADD COLUMN phone varchar(255) DEFAULT NULL AFTER address,
|
||||||
|
ADD COLUMN email varchar(255) DEFAULT NULL AFTER phone,
|
||||||
|
ADD COLUMN job_role_id int(10) UNSIGNED DEFAULT NULL AFTER department_id;
|
||||||
|
|
||||||
|
-- Replace ENUM status with plain VARCHAR for easier maintenance.
|
||||||
|
ALTER TABLE employees
|
||||||
|
MODIFY status varchar(255) NOT NULL DEFAULT 'active';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS job_roles (
|
||||||
|
id int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
name varchar(255) NOT NULL,
|
||||||
|
description text DEFAULT NULL,
|
||||||
|
sort_order int(10) UNSIGNED NOT NULL DEFAULT 999,
|
||||||
|
is_active tinyint(1) NOT NULL DEFAULT 1,
|
||||||
|
created_at timestamp NULL DEFAULT current_timestamp(),
|
||||||
|
updated_at timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uniq_job_roles_name (name),
|
||||||
|
KEY idx_job_roles_active (is_active),
|
||||||
|
KEY idx_job_roles_sort_order (sort_order)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
ALTER TABLE employees
|
||||||
|
ADD KEY idx_employees_job_role_id (job_role_id);
|
||||||
|
|
||||||
|
ALTER TABLE employees
|
||||||
|
ADD CONSTRAINT fk_employees_job_role
|
||||||
|
FOREIGN KEY (job_role_id) REFERENCES job_roles (id)
|
||||||
|
ON DELETE SET NULL
|
||||||
|
ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- 1) Seed job_roles with every distinct non-empty value of employees.position.
|
||||||
|
INSERT IGNORE INTO job_roles (name, is_active, sort_order, created_at, updated_at)
|
||||||
|
SELECT DISTINCT TRIM(position), 1, 999, NOW(), NOW()
|
||||||
|
FROM employees
|
||||||
|
WHERE position IS NOT NULL AND TRIM(position) <> '';
|
||||||
|
|
||||||
|
-- 2) Backfill employees.job_role_id by matching position text to job_roles.name.
|
||||||
|
UPDATE employees e
|
||||||
|
JOIN job_roles jr ON jr.name = TRIM(e.position)
|
||||||
|
SET e.job_role_id = jr.id
|
||||||
|
WHERE e.position IS NOT NULL AND TRIM(e.position) <> '';
|
||||||
|
|
||||||
|
-- 3) Drop the legacy column.
|
||||||
|
ALTER TABLE employees DROP COLUMN position;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS training_topics (
|
||||||
|
id int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
name varchar(255) NOT NULL,
|
||||||
|
description text DEFAULT NULL,
|
||||||
|
default_frequency_months int(10) UNSIGNED DEFAULT NULL,
|
||||||
|
default_reminder_days int(10) UNSIGNED NOT NULL DEFAULT 30,
|
||||||
|
sort_order int(10) UNSIGNED NOT NULL DEFAULT 999,
|
||||||
|
is_active tinyint(1) NOT NULL DEFAULT 1,
|
||||||
|
is_mandatory tinyint(1) NOT NULL DEFAULT 0,
|
||||||
|
created_at timestamp NULL DEFAULT current_timestamp(),
|
||||||
|
updated_at timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uniq_training_topics_name (name),
|
||||||
|
KEY idx_training_topics_active (is_active),
|
||||||
|
KEY idx_training_topics_mandatory (is_mandatory),
|
||||||
|
KEY idx_training_topics_sort_order (sort_order)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS employee_documents (
|
||||||
|
id int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
employee_id int(10) UNSIGNED NOT NULL,
|
||||||
|
category varchar(255) NOT NULL DEFAULT 'other',
|
||||||
|
original_name varchar(500) NOT NULL,
|
||||||
|
stored_name varchar(500) NOT NULL,
|
||||||
|
mime_type varchar(255) DEFAULT NULL,
|
||||||
|
size int(10) UNSIGNED DEFAULT NULL,
|
||||||
|
notes text DEFAULT NULL,
|
||||||
|
uploaded_by int(10) UNSIGNED DEFAULT NULL,
|
||||||
|
created_at timestamp NULL DEFAULT current_timestamp(),
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
KEY idx_employee_documents_employee (employee_id),
|
||||||
|
KEY idx_employee_documents_category (category),
|
||||||
|
KEY idx_employee_documents_uploaded_by (uploaded_by),
|
||||||
|
CONSTRAINT fk_employee_documents_employee
|
||||||
|
FOREIGN KEY (employee_id) REFERENCES employees (id)
|
||||||
|
ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT fk_employee_documents_uploaded_by
|
||||||
|
FOREIGN KEY (uploaded_by) REFERENCES auth_users (id)
|
||||||
|
ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS employee_ppe (
|
||||||
|
id int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
employee_id int(10) UNSIGNED NOT NULL,
|
||||||
|
item_name varchar(255) NOT NULL,
|
||||||
|
delivery_date date DEFAULT NULL,
|
||||||
|
delivered_by varchar(255) DEFAULT NULL,
|
||||||
|
notes text DEFAULT NULL,
|
||||||
|
created_by int(10) UNSIGNED DEFAULT NULL,
|
||||||
|
created_at timestamp NULL DEFAULT current_timestamp(),
|
||||||
|
updated_at timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
KEY idx_employee_ppe_employee (employee_id),
|
||||||
|
KEY idx_employee_ppe_delivery_date (delivery_date),
|
||||||
|
CONSTRAINT fk_employee_ppe_employee
|
||||||
|
FOREIGN KEY (employee_id) REFERENCES employees (id)
|
||||||
|
ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT fk_employee_ppe_created_by
|
||||||
|
FOREIGN KEY (created_by) REFERENCES auth_users (id)
|
||||||
|
ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS employee_trainings (
|
||||||
|
id int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
employee_id int(10) UNSIGNED NOT NULL,
|
||||||
|
training_topic_id int(10) UNSIGNED NOT NULL,
|
||||||
|
completed_date date NOT NULL,
|
||||||
|
delivered_by varchar(255) DEFAULT NULL,
|
||||||
|
description text DEFAULT NULL,
|
||||||
|
training_type varchar(255) NOT NULL DEFAULT 'initial',
|
||||||
|
update_frequency_months int(10) UNSIGNED DEFAULT NULL,
|
||||||
|
reminder_days int(10) UNSIGNED DEFAULT NULL,
|
||||||
|
next_due_date date DEFAULT NULL,
|
||||||
|
created_by int(10) UNSIGNED DEFAULT NULL,
|
||||||
|
created_at timestamp NULL DEFAULT current_timestamp(),
|
||||||
|
updated_at timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
KEY idx_employee_trainings_employee (employee_id),
|
||||||
|
KEY idx_employee_trainings_topic (training_topic_id),
|
||||||
|
KEY idx_employee_trainings_next_due (next_due_date),
|
||||||
|
KEY idx_employee_trainings_employee_topic (employee_id, training_topic_id),
|
||||||
|
KEY idx_employee_trainings_created_by (created_by),
|
||||||
|
CONSTRAINT fk_employee_trainings_employee
|
||||||
|
FOREIGN KEY (employee_id) REFERENCES employees (id)
|
||||||
|
ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT fk_employee_trainings_topic
|
||||||
|
FOREIGN KEY (training_topic_id) REFERENCES training_topics (id)
|
||||||
|
ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT fk_employee_trainings_created_by
|
||||||
|
FOREIGN KEY (created_by) REFERENCES auth_users (id)
|
||||||
|
ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS employee_training_attachments (
|
||||||
|
id int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
training_id int(10) UNSIGNED NOT NULL,
|
||||||
|
original_name varchar(500) NOT NULL,
|
||||||
|
stored_name varchar(500) NOT NULL,
|
||||||
|
mime_type varchar(255) DEFAULT NULL,
|
||||||
|
size int(10) UNSIGNED DEFAULT NULL,
|
||||||
|
uploaded_by int(10) UNSIGNED DEFAULT NULL,
|
||||||
|
created_at timestamp NULL DEFAULT current_timestamp(),
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
KEY idx_employee_training_attachments_training (training_id),
|
||||||
|
KEY idx_employee_training_attachments_uploaded_by (uploaded_by),
|
||||||
|
CONSTRAINT fk_employee_training_attachments_training
|
||||||
|
FOREIGN KEY (training_id) REFERENCES employee_trainings (id)
|
||||||
|
ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT fk_employee_training_attachments_uploaded_by
|
||||||
|
FOREIGN KEY (uploaded_by) REFERENCES auth_users (id)
|
||||||
|
ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS employee_training_log (
|
||||||
|
id int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
employee_id int(10) UNSIGNED DEFAULT NULL,
|
||||||
|
training_id int(10) UNSIGNED DEFAULT NULL,
|
||||||
|
action varchar(255) NOT NULL,
|
||||||
|
field varchar(255) DEFAULT NULL,
|
||||||
|
old_value text DEFAULT NULL,
|
||||||
|
new_value text DEFAULT NULL,
|
||||||
|
changed_by int(10) UNSIGNED DEFAULT NULL,
|
||||||
|
changed_at timestamp NULL DEFAULT current_timestamp(),
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
KEY idx_employee_training_log_employee (employee_id),
|
||||||
|
KEY idx_employee_training_log_training (training_id),
|
||||||
|
KEY idx_employee_training_log_changed_at (changed_at),
|
||||||
|
CONSTRAINT fk_employee_training_log_employee
|
||||||
|
FOREIGN KEY (employee_id) REFERENCES employees (id)
|
||||||
|
ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT fk_employee_training_log_training
|
||||||
|
FOREIGN KEY (training_id) REFERENCES employee_trainings (id)
|
||||||
|
ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT fk_employee_training_log_changed_by
|
||||||
|
FOREIGN KEY (changed_by) REFERENCES auth_users (id)
|
||||||
|
ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
INSERT INTO auth_roles (name, display_name, description, removable, created_at, updated_at) VALUES
|
||||||
|
('employee', 'Employee', 'Read-only access to own employee profile.', 1, NOW(), NOW()),
|
||||||
|
('employee-hr', 'HR Manager', 'Can manage employee profiles, documents, PPE and training records.', 1, NOW(), NOW()),
|
||||||
|
('manager', 'Manager', 'Same permissions as HR Manager.', 1, NOW(), NOW())
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
display_name = VALUES(display_name),
|
||||||
|
description = VALUES(description),
|
||||||
|
updated_at = NOW();
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS training_reminder_log (
|
||||||
|
id int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
training_id int(10) UNSIGNED DEFAULT NULL,
|
||||||
|
employee_id int(10) UNSIGNED DEFAULT NULL,
|
||||||
|
training_topic_id int(10) UNSIGNED DEFAULT NULL,
|
||||||
|
addressee_email varchar(255) NOT NULL,
|
||||||
|
next_due_date date DEFAULT NULL,
|
||||||
|
status_at_send varchar(255) NOT NULL,
|
||||||
|
sent_at timestamp NULL DEFAULT current_timestamp(),
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
KEY idx_training_reminder_log_dedup (training_id, addressee_email, next_due_date),
|
||||||
|
KEY idx_training_reminder_log_dedup_missing (employee_id, training_topic_id, addressee_email),
|
||||||
|
KEY idx_training_reminder_log_sent_at (sent_at),
|
||||||
|
CONSTRAINT fk_training_reminder_log_training
|
||||||
|
FOREIGN KEY (training_id) REFERENCES employee_trainings (id)
|
||||||
|
ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT fk_training_reminder_log_employee
|
||||||
|
FOREIGN KEY (employee_id) REFERENCES employees (id)
|
||||||
|
ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT fk_training_reminder_log_topic
|
||||||
|
FOREIGN KEY (training_topic_id) REFERENCES training_topics (id)
|
||||||
|
ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
```
|
||||||
|
|
||||||
|
# 2. Upload storage folder
|
||||||
|
|
||||||
|
Create the storage directory with the correct permissions for the web server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p /var/www/zibo-dashboard/public/userarea/files/employees
|
||||||
|
chown -R www-data:www-data /var/www/zibo-dashboard/public/userarea/files
|
||||||
|
chmod -R 775 /var/www/zibo-dashboard/public/userarea/files
|
||||||
|
```
|
||||||
|
|
||||||
|
Uploaded files will be organized as:
|
||||||
|
|
||||||
|
```
|
||||||
|
files/employees/{employee_id}/documents/ # File Repository (HR)
|
||||||
|
files/employees/{employee_id}/trainings/ # Training certificates
|
||||||
|
```
|
||||||
|
|
||||||
|
# 3. Cron for automated emails
|
||||||
|
|
||||||
|
```cron
|
||||||
|
0 7 * * * /usr/bin/php /var/www/zibo-dashboard/public/userarea/cron/send_training_reminders.php \
|
||||||
|
>> /var/www/zibo-dashboard/storage/logs/training_reminders.log 2>&1
|
||||||
|
```
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
if (file_exists(__DIR__ . '/.env')) {
|
||||||
|
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
|
||||||
|
$dotenv->safeLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'paths' => [
|
||||||
|
'migrations' => __DIR__ . '/db/migrations',
|
||||||
|
'seeds' => __DIR__ . '/db/seeds',
|
||||||
|
],
|
||||||
|
|
||||||
|
'environments' => [
|
||||||
|
'default_migration_table' => 'phinxlog',
|
||||||
|
'default_environment' => 'development',
|
||||||
|
|
||||||
|
'development' => [
|
||||||
|
'adapter' => $_ENV['DB_CONNECTION'] ?? 'mysql',
|
||||||
|
'host' => $_ENV['DB_HOST'] ?? 'localhost',
|
||||||
|
'name' => $_ENV['DB_DATABASE'] ?? '',
|
||||||
|
'user' => $_ENV['DB_USERNAME'] ?? '',
|
||||||
|
'pass' => $_ENV['DB_PASSWORD'] ?? '',
|
||||||
|
'port' => $_ENV['DB_PORT'] ?? 3306,
|
||||||
|
'charset' => 'utf8mb4',
|
||||||
|
'collation' => 'utf8mb4_unicode_ci',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'version_order' => 'creation',
|
||||||
|
];
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 456 KiB |
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Auth check for AJAX endpoints under /userarea/ajax/.
|
||||||
|
* Include this at the top of every ajax handler.
|
||||||
|
* Sets $currentUserId from session or returns 401 JSON.
|
||||||
|
*/
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($_SESSION['iduserlogin'])) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Non autorizzato. Effettua il login.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentUserId = (int)$_SESSION['iduserlogin'];
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
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 = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID documento non valido.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT employee_id, stored_name FROM employee_documents WHERE id = :id LIMIT 1");
|
||||||
|
$stmt->execute(['id' => $id]);
|
||||||
|
$doc = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!$doc) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Documento non trovato.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$del = $pdo->prepare("DELETE FROM employee_documents WHERE id = :id");
|
||||||
|
$del->execute(['id' => $id]);
|
||||||
|
|
||||||
|
$path = __DIR__ . '/../../files/employees/' . (int)$doc['employee_id'] . '/documents/' . $doc['stored_name'];
|
||||||
|
if (is_file($path)) {
|
||||||
|
@unlink($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
include('../../include/headscript.php');
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'ID DPI non valido.'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE employee_ppe_items
|
||||||
|
SET status = 'returned',
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = ?
|
||||||
|
");
|
||||||
|
$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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
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 = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID formazione non valido.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
$row = $pdo->prepare("SELECT employee_id FROM employee_trainings WHERE id = :id");
|
||||||
|
$row->execute(['id' => $id]);
|
||||||
|
$tr = $row->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!$tr) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Formazione non trovata.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect attached files BEFORE deletion so we can unlink them after
|
||||||
|
$files = $pdo->prepare("SELECT stored_name FROM employee_training_attachments WHERE training_id = :id");
|
||||||
|
$files->execute(['id' => $id]);
|
||||||
|
$stored = $files->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
// Log BEFORE delete (FK on log allows SET NULL on training delete but we want a clean record)
|
||||||
|
$pdo->prepare("
|
||||||
|
INSERT INTO employee_training_log
|
||||||
|
(employee_id, training_id, action, field, old_value, new_value, changed_by, changed_at)
|
||||||
|
VALUES
|
||||||
|
(:eid, NULL, 'deleted', NULL, NULL, NULL, :cb, NOW())
|
||||||
|
")->execute(['eid' => $tr['employee_id'], 'cb' => $currentUserId]);
|
||||||
|
|
||||||
|
$pdo->prepare("DELETE FROM employee_trainings WHERE id = :id")->execute(['id' => $id]);
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
foreach ($stored as $name) {
|
||||||
|
$path = __DIR__ . '/../../files/employees/' . (int)$tr['employee_id'] . '/trainings/' . $name;
|
||||||
|
if (is_file($path)) {
|
||||||
|
@unlink($path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
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 = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID allegato non valido.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = $pdo->prepare("
|
||||||
|
SELECT a.stored_name, a.original_name, a.training_id, t.employee_id
|
||||||
|
FROM employee_training_attachments a
|
||||||
|
JOIN employee_trainings t ON t.id = a.training_id
|
||||||
|
WHERE a.id = :id
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$row->execute(['id' => $id]);
|
||||||
|
$att = $row->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!$att) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Allegato non trovato.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
$pdo->prepare("DELETE FROM employee_training_attachments WHERE id = :id")->execute(['id' => $id]);
|
||||||
|
$pdo->prepare("
|
||||||
|
INSERT INTO employee_training_log
|
||||||
|
(employee_id, training_id, action, field, old_value, new_value, changed_by, changed_at)
|
||||||
|
VALUES
|
||||||
|
(:eid, :tid, 'attachment_deleted', 'attachment', :name, NULL, :cb, NOW())
|
||||||
|
")->execute([
|
||||||
|
'eid' => $att['employee_id'],
|
||||||
|
'tid' => $att['training_id'],
|
||||||
|
'name' => $att['original_name'],
|
||||||
|
'cb' => $currentUserId,
|
||||||
|
]);
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
$path = __DIR__ . '/../../files/employees/' . (int)$att['employee_id'] . '/trainings/' . $att['stored_name'];
|
||||||
|
if (is_file($path)) {
|
||||||
|
@unlink($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
require_once(__DIR__ . '/../auth_check.php');
|
||||||
|
require_once(__DIR__ . '/../../class/db-functions.php');
|
||||||
|
|
||||||
|
$id = (int)($_GET['id'] ?? 0);
|
||||||
|
if ($id <= 0) {
|
||||||
|
http_response_code(400);
|
||||||
|
exit('ID non valido.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT d.*, e.auth_user_id
|
||||||
|
FROM employee_documents d
|
||||||
|
JOIN employees e ON e.id = d.employee_id
|
||||||
|
WHERE d.id = :id
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute(['id' => $id]);
|
||||||
|
$doc = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$doc) {
|
||||||
|
http_response_code(404);
|
||||||
|
exit('Documento non trovato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Access check: HR roles can download any; otherwise only own employee */
|
||||||
|
$roleStmt = $pdo->prepare("
|
||||||
|
SELECT r.name
|
||||||
|
FROM auth_users u
|
||||||
|
LEFT JOIN auth_roles r ON r.id = u.role_id
|
||||||
|
WHERE u.id = :id LIMIT 1
|
||||||
|
");
|
||||||
|
$roleStmt->execute(['id' => $currentUserId]);
|
||||||
|
$role = (string)$roleStmt->fetchColumn();
|
||||||
|
$hrRoles = ['Admin', 'Superuser', 'employee-hr', 'manager'];
|
||||||
|
$isHr = in_array($role, $hrRoles, true);
|
||||||
|
|
||||||
|
if (!$isHr && (int)$doc['auth_user_id'] !== $currentUserId) {
|
||||||
|
http_response_code(403);
|
||||||
|
exit('Accesso negato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = __DIR__ . '/../../files/employees/' . (int)$doc['employee_id'] . '/documents/' . $doc['stored_name'];
|
||||||
|
if (!is_file($path)) {
|
||||||
|
http_response_code(404);
|
||||||
|
exit('File non trovato sul server.');
|
||||||
|
}
|
||||||
|
|
||||||
|
while (ob_get_level() > 0) { ob_end_clean(); }
|
||||||
|
header('Content-Type: ' . (!empty($doc['mime_type']) ? $doc['mime_type'] : 'application/octet-stream'));
|
||||||
|
header('Content-Disposition: attachment; filename="' . rawurlencode($doc['original_name']) . '"');
|
||||||
|
header('Content-Length: ' . filesize($path));
|
||||||
|
header('Cache-Control: private, max-age=0, must-revalidate');
|
||||||
|
readfile($path);
|
||||||
|
exit;
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
require_once(__DIR__ . '/../auth_check.php');
|
||||||
|
require_once(__DIR__ . '/../../class/db-functions.php');
|
||||||
|
|
||||||
|
$id = (int)($_GET['id'] ?? 0);
|
||||||
|
if ($id <= 0) {
|
||||||
|
http_response_code(400);
|
||||||
|
exit('ID non valido.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT a.*, t.employee_id, e.auth_user_id
|
||||||
|
FROM employee_training_attachments a
|
||||||
|
JOIN employee_trainings t ON t.id = a.training_id
|
||||||
|
JOIN employees e ON e.id = t.employee_id
|
||||||
|
WHERE a.id = :id
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute(['id' => $id]);
|
||||||
|
$att = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!$att) {
|
||||||
|
http_response_code(404);
|
||||||
|
exit('Allegato non trovato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Access: HR or owning employee */
|
||||||
|
$roleStmt = $pdo->prepare("
|
||||||
|
SELECT r.name FROM auth_users u
|
||||||
|
LEFT JOIN auth_roles r ON r.id = u.role_id
|
||||||
|
WHERE u.id = :id LIMIT 1
|
||||||
|
");
|
||||||
|
$roleStmt->execute(['id' => $currentUserId]);
|
||||||
|
$role = (string)$roleStmt->fetchColumn();
|
||||||
|
$hrRoles = ['Admin', 'Superuser', 'employee-hr', 'manager'];
|
||||||
|
$isHr = in_array($role, $hrRoles, true);
|
||||||
|
|
||||||
|
if (!$isHr && (int)$att['auth_user_id'] !== $currentUserId) {
|
||||||
|
http_response_code(403);
|
||||||
|
exit('Accesso negato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = __DIR__ . '/../../files/employees/' . (int)$att['employee_id'] . '/trainings/' . $att['stored_name'];
|
||||||
|
if (!is_file($path)) {
|
||||||
|
http_response_code(404);
|
||||||
|
exit('File non trovato sul server.');
|
||||||
|
}
|
||||||
|
|
||||||
|
while (ob_get_level() > 0) { ob_end_clean(); }
|
||||||
|
header('Content-Type: ' . (!empty($att['mime_type']) ? $att['mime_type'] : 'application/octet-stream'));
|
||||||
|
header('Content-Disposition: attachment; filename="' . rawurlencode($att['original_name']) . '"');
|
||||||
|
header('Content-Length: ' . filesize($path));
|
||||||
|
header('Cache-Control: private, max-age=0, must-revalidate');
|
||||||
|
readfile($path);
|
||||||
|
exit;
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
require_once(__DIR__ . '/../auth_check.php');
|
||||||
|
require_once(__DIR__ . '/../../class/db-functions.php');
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$trainingId = (int)($_GET['training_id'] ?? 0);
|
||||||
|
if ($trainingId <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID formazione non valido.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
/* Access: HR or owner */
|
||||||
|
$ownerStmt = $pdo->prepare("
|
||||||
|
SELECT e.auth_user_id
|
||||||
|
FROM employee_trainings t
|
||||||
|
JOIN employees e ON e.id = t.employee_id
|
||||||
|
WHERE t.id = :id LIMIT 1
|
||||||
|
");
|
||||||
|
$ownerStmt->execute(['id' => $trainingId]);
|
||||||
|
$ownerAuthUserId = $ownerStmt->fetchColumn();
|
||||||
|
if ($ownerAuthUserId === false) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Formazione non trovata.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$roleStmt = $pdo->prepare("
|
||||||
|
SELECT r.name FROM auth_users u
|
||||||
|
LEFT JOIN auth_roles r ON r.id = u.role_id
|
||||||
|
WHERE u.id = :id LIMIT 1
|
||||||
|
");
|
||||||
|
$roleStmt->execute(['id' => $currentUserId]);
|
||||||
|
$role = (string)$roleStmt->fetchColumn();
|
||||||
|
$hrRoles = ['Admin', 'Superuser', 'employee-hr', 'manager'];
|
||||||
|
$isHr = in_array($role, $hrRoles, true);
|
||||||
|
|
||||||
|
if (!$isHr && (int)$ownerAuthUserId !== $currentUserId) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Accesso negato.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT id, original_name, mime_type, size, created_at
|
||||||
|
FROM employee_training_attachments
|
||||||
|
WHERE training_id = :tid
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
");
|
||||||
|
$stmt->execute(['tid' => $trainingId]);
|
||||||
|
$attachments = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'attachments' => $attachments,
|
||||||
|
'can_edit' => $isHr,
|
||||||
|
]);
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
require_once(__DIR__ . '/../auth_check.php');
|
||||||
|
require_once(__DIR__ . '/../../class/db-functions.php');
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$trainingId = (int)($_GET['training_id'] ?? 0);
|
||||||
|
if ($trainingId <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID formazione non valido.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
/* Access: HR or owner */
|
||||||
|
$ownerStmt = $pdo->prepare("
|
||||||
|
SELECT e.auth_user_id
|
||||||
|
FROM employee_trainings t
|
||||||
|
JOIN employees e ON e.id = t.employee_id
|
||||||
|
WHERE t.id = :id LIMIT 1
|
||||||
|
");
|
||||||
|
$ownerStmt->execute(['id' => $trainingId]);
|
||||||
|
$ownerAuthUserId = $ownerStmt->fetchColumn();
|
||||||
|
if ($ownerAuthUserId === false) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Formazione non trovata.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$roleStmt = $pdo->prepare("
|
||||||
|
SELECT r.name FROM auth_users u
|
||||||
|
LEFT JOIN auth_roles r ON r.id = u.role_id
|
||||||
|
WHERE u.id = :id LIMIT 1
|
||||||
|
");
|
||||||
|
$roleStmt->execute(['id' => $currentUserId]);
|
||||||
|
$role = (string)$roleStmt->fetchColumn();
|
||||||
|
$hrRoles = ['Admin', 'Superuser', 'employee-hr', 'manager'];
|
||||||
|
$isHr = in_array($role, $hrRoles, true);
|
||||||
|
|
||||||
|
if (!$isHr && (int)$ownerAuthUserId !== $currentUserId) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Accesso negato.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT l.id, l.action, l.field, l.old_value, l.new_value, l.changed_at,
|
||||||
|
TRIM(CONCAT(COALESCE(u.first_name,''),' ',COALESCE(u.last_name,''))) AS changed_by_name,
|
||||||
|
u.email AS changed_by_email
|
||||||
|
FROM employee_training_log l
|
||||||
|
LEFT JOIN auth_users u ON u.id = l.changed_by
|
||||||
|
WHERE l.training_id = :tid
|
||||||
|
ORDER BY l.changed_at DESC, l.id DESC
|
||||||
|
");
|
||||||
|
$stmt->execute(['tid' => $trainingId]);
|
||||||
|
$entries = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'entries' => $entries]);
|
||||||
@@ -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()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
<?php
|
||||||
|
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 = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
$employeeId = (int)($_POST['employee_id'] ?? 0);
|
||||||
|
$firstName = trim($_POST['first_name'] ?? '');
|
||||||
|
$lastName = trim($_POST['last_name'] ?? '');
|
||||||
|
$employeeCode = trim($_POST['employee_code'] ?? '');
|
||||||
|
$address = trim($_POST['address'] ?? '');
|
||||||
|
$phone = trim($_POST['phone'] ?? '');
|
||||||
|
$email = trim($_POST['email'] ?? '');
|
||||||
|
$hireDate = trim($_POST['hire_date'] ?? '');
|
||||||
|
$departmentId = $_POST['department_id'] ?? '';
|
||||||
|
$jobRoleId = $_POST['job_role_id'] ?? '';
|
||||||
|
$status = trim($_POST['status'] ?? '');
|
||||||
|
$authUserId = $_POST['auth_user_id'] ?? '';
|
||||||
|
$roleId = $_POST['role_id'] ?? '';
|
||||||
|
|
||||||
|
if ($employeeId <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID dipendente non valido.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if ($firstName === '' || $lastName === '') {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Nome e cognome sono obbligatori.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowedStatus = ['active', 'inactive', 'suspended'];
|
||||||
|
if (!in_array($status, $allowedStatus, true)) {
|
||||||
|
$status = 'active';
|
||||||
|
}
|
||||||
|
|
||||||
|
$departmentId = ($departmentId === '' || $departmentId === null) ? null : (int)$departmentId;
|
||||||
|
$jobRoleId = ($jobRoleId === '' || $jobRoleId === null) ? null : (int)$jobRoleId;
|
||||||
|
$authUserId = ($authUserId === '' || $authUserId === null) ? null : (int)$authUserId;
|
||||||
|
$roleId = ($roleId === '' || $roleId === null) ? null : (int)$roleId;
|
||||||
|
$hireDate = $hireDate === '' ? null : $hireDate;
|
||||||
|
|
||||||
|
if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Email non valida.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($employeeCode !== '') {
|
||||||
|
$check = $pdo->prepare("SELECT COUNT(*) FROM employees WHERE employee_code = :code AND id <> :id");
|
||||||
|
$check->execute(['code' => $employeeCode, 'id' => $employeeId]);
|
||||||
|
if ((int)$check->fetchColumn() > 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Codice dipendente già in uso.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($authUserId !== null) {
|
||||||
|
$check = $pdo->prepare("SELECT COUNT(*) FROM employees WHERE auth_user_id = :uid AND id <> :id");
|
||||||
|
$check->execute(['uid' => $authUserId, 'id' => $employeeId]);
|
||||||
|
if ((int)$check->fetchColumn() > 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Questo utente è già associato ad un altro dipendente.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE employees
|
||||||
|
SET first_name = :first_name,
|
||||||
|
last_name = :last_name,
|
||||||
|
employee_code = :employee_code,
|
||||||
|
address = :address,
|
||||||
|
phone = :phone,
|
||||||
|
email = :email,
|
||||||
|
hire_date = :hire_date,
|
||||||
|
department_id = :department_id,
|
||||||
|
job_role_id = :job_role_id,
|
||||||
|
status = :status,
|
||||||
|
auth_user_id = :auth_user_id,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = :id
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
'first_name' => $firstName,
|
||||||
|
'last_name' => $lastName,
|
||||||
|
'employee_code' => $employeeCode !== '' ? $employeeCode : null,
|
||||||
|
'address' => $address !== '' ? $address : null,
|
||||||
|
'phone' => $phone !== '' ? $phone : null,
|
||||||
|
'email' => $email !== '' ? $email : null,
|
||||||
|
'hire_date' => $hireDate,
|
||||||
|
'department_id' => $departmentId,
|
||||||
|
'job_role_id' => $jobRoleId,
|
||||||
|
'status' => $status,
|
||||||
|
'auth_user_id' => $authUserId,
|
||||||
|
'id' => $employeeId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Optionally update Vanguard role for the linked auth_user
|
||||||
|
if ($authUserId !== null && $roleId !== null) {
|
||||||
|
$check = $pdo->prepare("SELECT COUNT(*) FROM auth_roles WHERE id = ?");
|
||||||
|
$check->execute([$roleId]);
|
||||||
|
if ((int)$check->fetchColumn() > 0) {
|
||||||
|
$upd = $pdo->prepare("UPDATE auth_users SET role_id = :role_id, updated_at = NOW() WHERE id = :uid");
|
||||||
|
$upd->execute(['role_id' => $roleId, 'uid' => $authUserId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
<?php
|
||||||
|
include('../../include/headscript.php');
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$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("
|
||||||
|
UPDATE employee_ppe_items
|
||||||
|
SET ppe_item_id = :ppe_item_id,
|
||||||
|
assigned_date = :assigned_date,
|
||||||
|
expiry_date = :expiry_date,
|
||||||
|
delivered_by = :delivered_by,
|
||||||
|
status = :status,
|
||||||
|
notes = :notes,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = :id
|
||||||
|
AND employee_id = :employee_id
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
'ppe_item_id' => $ppeItemId,
|
||||||
|
'assigned_date' => $assignedDate !== '' ? $assignedDate : null,
|
||||||
|
'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;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO employee_ppe_items
|
||||||
|
(
|
||||||
|
employee_id,
|
||||||
|
ppe_item_id,
|
||||||
|
assigned_date,
|
||||||
|
expiry_date,
|
||||||
|
delivered_by,
|
||||||
|
quantity,
|
||||||
|
status,
|
||||||
|
notes,
|
||||||
|
created_by,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
:employee_id,
|
||||||
|
:ppe_item_id,
|
||||||
|
:assigned_date,
|
||||||
|
:expiry_date,
|
||||||
|
:delivered_by,
|
||||||
|
1,
|
||||||
|
:status,
|
||||||
|
:notes,
|
||||||
|
:created_by,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
)
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
'employee_id' => $employeeId,
|
||||||
|
'ppe_item_id' => $ppeItemId,
|
||||||
|
'assigned_date' => $assignedDate !== '' ? $assignedDate : null,
|
||||||
|
'expiry_date' => $expiryDate !== '' ? $expiryDate : null,
|
||||||
|
'delivered_by' => $deliveredBy !== '' ? $deliveredBy : null,
|
||||||
|
'status' => $status,
|
||||||
|
'notes' => $notes !== '' ? $notes : null,
|
||||||
|
'created_by' => isset($iduserlogin) ? (int)$iduserlogin : null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'DPI assegnato.'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
<?php
|
||||||
|
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 = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
$employeeId = (int)($_POST['employee_id'] ?? 0);
|
||||||
|
$topicId = (int)($_POST['training_topic_id'] ?? 0);
|
||||||
|
$completedDate = trim($_POST['completed_date'] ?? '');
|
||||||
|
$deliveredBy = trim($_POST['delivered_by'] ?? '');
|
||||||
|
$description = trim($_POST['description'] ?? '');
|
||||||
|
$trainingType = trim($_POST['training_type'] ?? 'initial');
|
||||||
|
$freqRaw = $_POST['update_frequency_months'] ?? '';
|
||||||
|
$remRaw = $_POST['reminder_days'] ?? '';
|
||||||
|
|
||||||
|
if ($employeeId <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID dipendente non valido.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if ($topicId <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Selezionare un corso.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if ($completedDate === '') {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'La data di completamento è obbligatoria.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if (!in_array($trainingType, ['initial', 'refresher'], true)) {
|
||||||
|
$trainingType = 'initial';
|
||||||
|
}
|
||||||
|
|
||||||
|
$topicStmt = $pdo->prepare("SELECT default_frequency_months, default_reminder_days FROM training_topics WHERE id = :id");
|
||||||
|
$topicStmt->execute(['id' => $topicId]);
|
||||||
|
$topic = $topicStmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!$topic) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Corso non trovato.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$freq = ($freqRaw === '' || $freqRaw === null) ? null : max(0, (int)$freqRaw);
|
||||||
|
$rem = ($remRaw === '' || $remRaw === null) ? null : max(0, (int)$remRaw);
|
||||||
|
|
||||||
|
/* Effective frequency for next_due_date: explicit override or topic default */
|
||||||
|
$effFreq = $freq !== null ? $freq : ($topic['default_frequency_months'] !== null ? (int)$topic['default_frequency_months'] : null);
|
||||||
|
|
||||||
|
$nextDue = null;
|
||||||
|
if ($effFreq !== null && $effFreq > 0) {
|
||||||
|
$d = DateTime::createFromFormat('Y-m-d', $completedDate);
|
||||||
|
if ($d) {
|
||||||
|
$d->modify('+' . (int)$effFreq . ' months');
|
||||||
|
$nextDue = $d->format('Y-m-d');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$deliveredBy = $deliveredBy !== '' ? $deliveredBy : null;
|
||||||
|
$description = $description !== '' ? $description : null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
if ($id > 0) {
|
||||||
|
$old = $pdo->prepare("SELECT * FROM employee_trainings WHERE id = :id");
|
||||||
|
$old->execute(['id' => $id]);
|
||||||
|
$oldRow = $old->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!$oldRow) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Formazione non trovata.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$upd = $pdo->prepare("
|
||||||
|
UPDATE employee_trainings
|
||||||
|
SET training_topic_id = :topic_id,
|
||||||
|
completed_date = :completed_date,
|
||||||
|
delivered_by = :delivered_by,
|
||||||
|
description = :description,
|
||||||
|
training_type = :training_type,
|
||||||
|
update_frequency_months = :freq,
|
||||||
|
reminder_days = :rem,
|
||||||
|
next_due_date = :next_due,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = :id
|
||||||
|
");
|
||||||
|
$upd->execute([
|
||||||
|
'topic_id' => $topicId,
|
||||||
|
'completed_date' => $completedDate,
|
||||||
|
'delivered_by' => $deliveredBy,
|
||||||
|
'description' => $description,
|
||||||
|
'training_type' => $trainingType,
|
||||||
|
'freq' => $freq,
|
||||||
|
'rem' => $rem,
|
||||||
|
'next_due' => $nextDue,
|
||||||
|
'id' => $id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$fields = [
|
||||||
|
'training_topic_id' => $topicId,
|
||||||
|
'completed_date' => $completedDate,
|
||||||
|
'delivered_by' => $deliveredBy,
|
||||||
|
'description' => $description,
|
||||||
|
'training_type' => $trainingType,
|
||||||
|
'update_frequency_months' => $freq,
|
||||||
|
'reminder_days' => $rem,
|
||||||
|
'next_due_date' => $nextDue,
|
||||||
|
];
|
||||||
|
$logStmt = $pdo->prepare("
|
||||||
|
INSERT INTO employee_training_log
|
||||||
|
(employee_id, training_id, action, field, old_value, new_value, changed_by, changed_at)
|
||||||
|
VALUES
|
||||||
|
(:eid, :tid, 'updated', :field, :old_v, :new_v, :cb, NOW())
|
||||||
|
");
|
||||||
|
foreach ($fields as $f => $newV) {
|
||||||
|
$oldV = $oldRow[$f] ?? null;
|
||||||
|
if ((string)$oldV !== (string)$newV) {
|
||||||
|
$logStmt->execute([
|
||||||
|
'eid' => $employeeId,
|
||||||
|
'tid' => $id,
|
||||||
|
'field' => $f,
|
||||||
|
'old_v' => $oldV,
|
||||||
|
'new_v' => $newV,
|
||||||
|
'cb' => $currentUserId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
echo json_encode(['success' => true, 'id' => $id]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ins = $pdo->prepare("
|
||||||
|
INSERT INTO employee_trainings
|
||||||
|
(employee_id, training_topic_id, completed_date,
|
||||||
|
delivered_by, description,
|
||||||
|
training_type, update_frequency_months, reminder_days, next_due_date,
|
||||||
|
created_by, created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
(:eid, :tid, :completed_date,
|
||||||
|
:delivered_by, :description,
|
||||||
|
:training_type, :freq, :rem, :next_due,
|
||||||
|
:cb, NOW(), NOW())
|
||||||
|
");
|
||||||
|
$ins->execute([
|
||||||
|
'eid' => $employeeId,
|
||||||
|
'tid' => $topicId,
|
||||||
|
'completed_date' => $completedDate,
|
||||||
|
'delivered_by' => $deliveredBy,
|
||||||
|
'description' => $description,
|
||||||
|
'training_type' => $trainingType,
|
||||||
|
'freq' => $freq,
|
||||||
|
'rem' => $rem,
|
||||||
|
'next_due' => $nextDue,
|
||||||
|
'cb' => $currentUserId,
|
||||||
|
]);
|
||||||
|
$newId = (int)$pdo->lastInsertId();
|
||||||
|
|
||||||
|
$pdo->prepare("
|
||||||
|
INSERT INTO employee_training_log
|
||||||
|
(employee_id, training_id, action, field, old_value, new_value, changed_by, changed_at)
|
||||||
|
VALUES
|
||||||
|
(:eid, :tid, 'created', NULL, NULL, NULL, :cb, NOW())
|
||||||
|
")->execute(['eid' => $employeeId, 'tid' => $newId, 'cb' => $currentUserId]);
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
echo json_encode(['success' => true, 'id' => $newId]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
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 = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
$employeeId = (int)($_POST['employee_id'] ?? 0);
|
||||||
|
$category = trim($_POST['category'] ?? 'other');
|
||||||
|
$notes = trim($_POST['notes'] ?? '');
|
||||||
|
|
||||||
|
$allowedCategories = ['job_description', 'contract', 'rules', 'other'];
|
||||||
|
if (!in_array($category, $allowedCategories, true)) {
|
||||||
|
$category = 'other';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($employeeId <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID dipendente non valido.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$check = $pdo->prepare("SELECT COUNT(*) FROM employees WHERE id = :id");
|
||||||
|
$check->execute(['id' => $employeeId]);
|
||||||
|
if ((int)$check->fetchColumn() === 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Dipendente non trovato.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
|
||||||
|
$errCode = $_FILES['file']['error'] ?? -1;
|
||||||
|
$msg = 'Errore nel caricamento del file.';
|
||||||
|
if ($errCode === UPLOAD_ERR_INI_SIZE || $errCode === UPLOAD_ERR_FORM_SIZE) {
|
||||||
|
$msg = 'Il file supera la dimensione massima consentita.';
|
||||||
|
}
|
||||||
|
echo json_encode(['success' => false, 'message' => $msg]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$originalName = $_FILES['file']['name'];
|
||||||
|
$tmpPath = $_FILES['file']['tmp_name'];
|
||||||
|
$size = (int)$_FILES['file']['size'];
|
||||||
|
$mimeType = mime_content_type($tmpPath) ?: ($_FILES['file']['type'] ?? null);
|
||||||
|
|
||||||
|
$dir = __DIR__ . '/../../files/employees/' . $employeeId . '/documents';
|
||||||
|
if (!is_dir($dir)) {
|
||||||
|
if (!mkdir($dir, 0775, true) && !is_dir($dir)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Impossibile creare la cartella di destinazione.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$safeOriginal = preg_replace('/[^a-zA-Z0-9._-]/', '_', $originalName);
|
||||||
|
$storedName = uniqid('doc_') . '_' . $safeOriginal;
|
||||||
|
$destPath = $dir . '/' . $storedName;
|
||||||
|
|
||||||
|
if (!move_uploaded_file($tmpPath, $destPath)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Impossibile salvare il file su disco.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO employee_documents
|
||||||
|
(employee_id, category, original_name, stored_name, mime_type, size, notes, uploaded_by, created_at)
|
||||||
|
VALUES
|
||||||
|
(:employee_id, :category, :original_name, :stored_name, :mime_type, :size, :notes, :uploaded_by, NOW())
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
'employee_id' => $employeeId,
|
||||||
|
'category' => $category,
|
||||||
|
'original_name' => $originalName,
|
||||||
|
'stored_name' => $storedName,
|
||||||
|
'mime_type' => $mimeType,
|
||||||
|
'size' => $size,
|
||||||
|
'notes' => $notes !== '' ? $notes : null,
|
||||||
|
'uploaded_by' => $currentUserId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'id' => (int)$pdo->lastInsertId()]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
@unlink($destPath);
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
<?php
|
||||||
|
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 = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
$trainingId = (int)($_POST['training_id'] ?? 0);
|
||||||
|
if ($trainingId <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID formazione non valido.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tr = $pdo->prepare("SELECT employee_id FROM employee_trainings WHERE id = :id");
|
||||||
|
$tr->execute(['id' => $trainingId]);
|
||||||
|
$trainingRow = $tr->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!$trainingRow) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Formazione non trovata.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$employeeId = (int)$trainingRow['employee_id'];
|
||||||
|
|
||||||
|
if (empty($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
|
||||||
|
$errCode = $_FILES['file']['error'] ?? -1;
|
||||||
|
$msg = 'Errore nel caricamento del file.';
|
||||||
|
if ($errCode === UPLOAD_ERR_INI_SIZE || $errCode === UPLOAD_ERR_FORM_SIZE) {
|
||||||
|
$msg = 'Il file supera la dimensione massima consentita.';
|
||||||
|
}
|
||||||
|
echo json_encode(['success' => false, 'message' => $msg]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$originalName = $_FILES['file']['name'];
|
||||||
|
$tmpPath = $_FILES['file']['tmp_name'];
|
||||||
|
$size = (int)$_FILES['file']['size'];
|
||||||
|
$mimeType = mime_content_type($tmpPath) ?: ($_FILES['file']['type'] ?? null);
|
||||||
|
|
||||||
|
$dir = __DIR__ . '/../../files/employees/' . $employeeId . '/trainings';
|
||||||
|
if (!is_dir($dir)) {
|
||||||
|
if (!mkdir($dir, 0775, true) && !is_dir($dir)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Impossibile creare la cartella di destinazione.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$safeOriginal = preg_replace('/[^a-zA-Z0-9._-]/', '_', $originalName);
|
||||||
|
$storedName = uniqid('tr_') . '_' . $safeOriginal;
|
||||||
|
$destPath = $dir . '/' . $storedName;
|
||||||
|
|
||||||
|
if (!move_uploaded_file($tmpPath, $destPath)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Impossibile salvare il file su disco.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
$ins = $pdo->prepare("
|
||||||
|
INSERT INTO employee_training_attachments
|
||||||
|
(training_id, original_name, stored_name, mime_type, size, uploaded_by, created_at)
|
||||||
|
VALUES
|
||||||
|
(:tid, :original_name, :stored_name, :mime_type, :size, :uploaded_by, NOW())
|
||||||
|
");
|
||||||
|
$ins->execute([
|
||||||
|
'tid' => $trainingId,
|
||||||
|
'original_name' => $originalName,
|
||||||
|
'stored_name' => $storedName,
|
||||||
|
'mime_type' => $mimeType,
|
||||||
|
'size' => $size,
|
||||||
|
'uploaded_by' => $currentUserId,
|
||||||
|
]);
|
||||||
|
$attachmentId = (int)$pdo->lastInsertId();
|
||||||
|
|
||||||
|
$pdo->prepare("
|
||||||
|
INSERT INTO employee_training_log
|
||||||
|
(employee_id, training_id, action, field, old_value, new_value, changed_by, changed_at)
|
||||||
|
VALUES
|
||||||
|
(:eid, :tid, 'attachment_added', 'attachment', NULL, :name, :cb, NOW())
|
||||||
|
")->execute([
|
||||||
|
'eid' => $employeeId,
|
||||||
|
'tid' => $trainingId,
|
||||||
|
'name' => $originalName,
|
||||||
|
'cb' => $currentUserId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
echo json_encode(['success' => true, 'id' => $attachmentId]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||||
|
@unlink($destPath);
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* HR auth check for AJAX endpoints that require HR-management permissions.
|
||||||
|
* Allowed roles: Admin, User, Superuser, employee-hr, manager.
|
||||||
|
* Sets $currentUserId and $currentUserRole, or returns 401/403 JSON.
|
||||||
|
*/
|
||||||
|
require_once(__DIR__ . '/auth_check.php');
|
||||||
|
require_once(__DIR__ . '/../class/db-functions.php');
|
||||||
|
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT r.name AS role_name
|
||||||
|
FROM auth_users u
|
||||||
|
LEFT JOIN auth_roles r ON r.id = u.role_id
|
||||||
|
WHERE u.id = :id
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute(['id' => $currentUserId]);
|
||||||
|
$currentUserRole = (string)$stmt->fetchColumn();
|
||||||
|
|
||||||
|
$allowedHrRoles = ['Admin', 'Superuser', 'employee-hr', 'manager'];
|
||||||
|
|
||||||
|
if (!in_array($currentUserRole, $allowedHrRoles, true)) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Permessi insufficienti per questa operazione.',
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
require_once(__DIR__ . '/../auth_check.php');
|
||||||
|
require_once(__DIR__ . '/../../class/db-functions.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 = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID mansione non valido.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$usage = $pdo->prepare("SELECT COUNT(*) FROM employees WHERE job_role_id = :id");
|
||||||
|
$usage->execute(['id' => $id]);
|
||||||
|
if ((int)$usage->fetchColumn() > 0) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Questa mansione è associata a uno o più dipendenti e non può essere cancellata.',
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM job_roles WHERE id = :id");
|
||||||
|
$stmt->execute(['id' => $id]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
require_once(__DIR__ . '/../auth_check.php');
|
||||||
|
require_once(__DIR__ . '/../../class/db-functions.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 = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
$name = trim($_POST['name'] ?? '');
|
||||||
|
$description = trim($_POST['description'] ?? '');
|
||||||
|
$sort_order = isset($_POST['sort_order']) && $_POST['sort_order'] !== '' ? (int)$_POST['sort_order'] : 999;
|
||||||
|
$is_active = isset($_POST['is_active']) ? ((int)$_POST['is_active'] === 1 ? 1 : 0) : 1;
|
||||||
|
|
||||||
|
if ($name === '') {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Il nome della mansione è obbligatorio.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($id > 0) {
|
||||||
|
$check = $pdo->prepare("SELECT COUNT(*) FROM job_roles WHERE name = :name AND id <> :id");
|
||||||
|
$check->execute(['name' => $name, 'id' => $id]);
|
||||||
|
if ((int)$check->fetchColumn() > 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Esiste già un\'altra mansione con questo nome.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE job_roles
|
||||||
|
SET name = :name,
|
||||||
|
description = :description,
|
||||||
|
sort_order = :sort_order,
|
||||||
|
is_active = :is_active,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = :id
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
'name' => $name,
|
||||||
|
'description' => $description !== '' ? $description : null,
|
||||||
|
'sort_order' => $sort_order,
|
||||||
|
'is_active' => $is_active,
|
||||||
|
'id' => $id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'id' => $id]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$check = $pdo->prepare("SELECT COUNT(*) FROM job_roles WHERE name = :name");
|
||||||
|
$check->execute(['name' => $name]);
|
||||||
|
if ((int)$check->fetchColumn() > 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Esiste già una mansione con questo nome.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO job_roles (name, description, sort_order, is_active, created_at, updated_at)
|
||||||
|
VALUES (:name, :description, :sort_order, :is_active, NOW(), NOW())
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
'name' => $name,
|
||||||
|
'description' => $description !== '' ? $description : null,
|
||||||
|
'sort_order' => $sort_order,
|
||||||
|
'is_active' => $is_active,
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'id' => (int)$pdo->lastInsertId()]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
require_once(__DIR__ . '/../auth_check.php');
|
||||||
|
require_once(__DIR__ . '/../../class/db-functions.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 = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID corso non valido.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$usage = $pdo->prepare("SELECT COUNT(*) FROM employee_trainings WHERE training_topic_id = :id");
|
||||||
|
$usage->execute(['id' => $id]);
|
||||||
|
if ((int)$usage->fetchColumn() > 0) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Questo corso ha già delle registrazioni di formazione e non può essere cancellato.',
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM training_topics WHERE id = :id");
|
||||||
|
$stmt->execute(['id' => $id]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
<?php
|
||||||
|
require_once(__DIR__ . '/../auth_check.php');
|
||||||
|
require_once(__DIR__ . '/../../class/db-functions.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 = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
$name = trim($_POST['name'] ?? '');
|
||||||
|
$description = trim($_POST['description'] ?? '');
|
||||||
|
$freqRaw = $_POST['default_frequency_months'] ?? '';
|
||||||
|
$remRaw = $_POST['default_reminder_days'] ?? '';
|
||||||
|
$sort_order = isset($_POST['sort_order']) && $_POST['sort_order'] !== '' ? (int)$_POST['sort_order'] : 999;
|
||||||
|
$is_active = isset($_POST['is_active']) ? ((int)$_POST['is_active'] === 1 ? 1 : 0) : 1;
|
||||||
|
$is_mandatory = isset($_POST['is_mandatory']) && (int)$_POST['is_mandatory'] === 1 ? 1 : 0;
|
||||||
|
|
||||||
|
$freq = ($freqRaw === '' || $freqRaw === null) ? null : max(0, (int)$freqRaw);
|
||||||
|
$rem = ($remRaw === '' || $remRaw === null) ? 30 : max(0, (int)$remRaw);
|
||||||
|
|
||||||
|
if ($name === '') {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Il nome del corso è obbligatorio.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($id > 0) {
|
||||||
|
$check = $pdo->prepare("SELECT COUNT(*) FROM training_topics WHERE name = :name AND id <> :id");
|
||||||
|
$check->execute(['name' => $name, 'id' => $id]);
|
||||||
|
if ((int)$check->fetchColumn() > 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Esiste già un altro corso con questo nome.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE training_topics
|
||||||
|
SET name = :name,
|
||||||
|
description = :description,
|
||||||
|
default_frequency_months = :freq,
|
||||||
|
default_reminder_days = :rem,
|
||||||
|
sort_order = :sort_order,
|
||||||
|
is_active = :is_active,
|
||||||
|
is_mandatory = :is_mandatory,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = :id
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
'name' => $name,
|
||||||
|
'description' => $description !== '' ? $description : null,
|
||||||
|
'freq' => $freq,
|
||||||
|
'rem' => $rem,
|
||||||
|
'sort_order' => $sort_order,
|
||||||
|
'is_active' => $is_active,
|
||||||
|
'is_mandatory' => $is_mandatory,
|
||||||
|
'id' => $id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'id' => $id]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$check = $pdo->prepare("SELECT COUNT(*) FROM training_topics WHERE name = :name");
|
||||||
|
$check->execute(['name' => $name]);
|
||||||
|
if ((int)$check->fetchColumn() > 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Esiste già un corso con questo nome.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO training_topics
|
||||||
|
(name, description, default_frequency_months, default_reminder_days, sort_order, is_active, is_mandatory, created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
(:name, :description, :freq, :rem, :sort_order, :is_active, :is_mandatory, NOW(), NOW())
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
'name' => $name,
|
||||||
|
'description' => $description !== '' ? $description : null,
|
||||||
|
'freq' => $freq,
|
||||||
|
'rem' => $rem,
|
||||||
|
'sort_order' => $sort_order,
|
||||||
|
'is_active' => $is_active,
|
||||||
|
'is_mandatory' => $is_mandatory,
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'id' => (int)$pdo->lastInsertId()]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Bulk "renew": set a common completed_date on the selected training records
|
||||||
|
*/
|
||||||
|
require_once(__DIR__ . '/../hr_auth_check.php');
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Metodo non consentito.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// $pdo and $currentUserId from hr_auth_check.php
|
||||||
|
|
||||||
|
$completedDate = trim($_POST['completed_date'] ?? '');
|
||||||
|
$ids = $_POST['training_ids'] ?? [];
|
||||||
|
|
||||||
|
if (!is_array($ids)) {
|
||||||
|
$ids = [];
|
||||||
|
}
|
||||||
|
$ids = array_values(array_unique(array_filter(array_map('intval', $ids), fn($v) => $v > 0)));
|
||||||
|
|
||||||
|
if ($completedDate === '' || !DateTime::createFromFormat('Y-m-d', $completedDate)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Indicare una data valida.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if (empty($ids)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Selezionare almeno un record.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
// Load each record with its topic default frequency
|
||||||
|
$rowStmt = $pdo->prepare("
|
||||||
|
SELECT et.id, et.employee_id, et.completed_date, et.next_due_date,
|
||||||
|
et.update_frequency_months, tt.default_frequency_months
|
||||||
|
FROM employee_trainings et
|
||||||
|
JOIN training_topics tt ON tt.id = et.training_topic_id
|
||||||
|
WHERE et.id = :id
|
||||||
|
");
|
||||||
|
$upd = $pdo->prepare("
|
||||||
|
UPDATE employee_trainings
|
||||||
|
SET completed_date = :cd, next_due_date = :nd, updated_at = NOW()
|
||||||
|
WHERE id = :id
|
||||||
|
");
|
||||||
|
$logStmt = $pdo->prepare("
|
||||||
|
INSERT INTO employee_training_log
|
||||||
|
(employee_id, training_id, action, field, old_value, new_value, changed_by, changed_at)
|
||||||
|
VALUES
|
||||||
|
(:eid, :tid, 'updated', :field, :old_v, :new_v, :cb, NOW())
|
||||||
|
");
|
||||||
|
|
||||||
|
$updated = 0;
|
||||||
|
foreach ($ids as $id) {
|
||||||
|
$rowStmt->execute(['id' => $id]);
|
||||||
|
$row = $rowStmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!$row) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Effective frequency: per-record override, else topic default
|
||||||
|
$effFreq = $row['update_frequency_months'] !== null
|
||||||
|
? (int)$row['update_frequency_months']
|
||||||
|
: ($row['default_frequency_months'] !== null ? (int)$row['default_frequency_months'] : null);
|
||||||
|
|
||||||
|
$nextDue = null;
|
||||||
|
if ($effFreq !== null && $effFreq > 0) {
|
||||||
|
$d = DateTime::createFromFormat('Y-m-d', $completedDate);
|
||||||
|
if ($d) {
|
||||||
|
$d->modify('+' . $effFreq . ' months');
|
||||||
|
$nextDue = $d->format('Y-m-d');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$upd->execute(['cd' => $completedDate, 'nd' => $nextDue, 'id' => $id]);
|
||||||
|
|
||||||
|
if ((string)$row['completed_date'] !== (string)$completedDate) {
|
||||||
|
$logStmt->execute([
|
||||||
|
'eid' => $row['employee_id'], 'tid' => $id, 'field' => 'completed_date',
|
||||||
|
'old_v' => $row['completed_date'], 'new_v' => $completedDate, 'cb' => $currentUserId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ((string)$row['next_due_date'] !== (string)$nextDue) {
|
||||||
|
$logStmt->execute([
|
||||||
|
'eid' => $row['employee_id'], 'tid' => $id, 'field' => 'next_due_date',
|
||||||
|
'old_v' => $row['next_due_date'], 'new_v' => $nextDue, 'cb' => $currentUserId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$updated++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'updated' => $updated,
|
||||||
|
'message' => $updated . ' record aggiornat' . ($updated === 1 ? 'o' : 'i') . '.',
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Calendar events for the training calendar (training_calendar.php).
|
||||||
|
* Returns FullCalendar event objects for the *current* training record per
|
||||||
|
* (employee, topic) that has a next_due_date, colored by computed status.
|
||||||
|
* HR-only.
|
||||||
|
*/
|
||||||
|
require_once(__DIR__ . '/../hr_auth_check.php');
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// $pdo and $currentUserId provided by hr_auth_check.php
|
||||||
|
|
||||||
|
$start = $_GET['start'] ?? null;
|
||||||
|
$end = $_GET['end'] ?? null;
|
||||||
|
$fStatus = isset($_GET['status']) ? trim($_GET['status']) : '';
|
||||||
|
$fDept = isset($_GET['department_id']) && $_GET['department_id'] !== '' ? (int)$_GET['department_id'] : 0;
|
||||||
|
$fTopic = isset($_GET['topic_id']) && $_GET['topic_id'] !== '' ? (int)$_GET['topic_id'] : 0;
|
||||||
|
$fEmp = isset($_GET['employee_id']) && $_GET['employee_id'] !== '' ? (int)$_GET['employee_id'] : 0;
|
||||||
|
|
||||||
|
$where = [];
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
// Deadlines only (one-time trainings have no next_due_date)
|
||||||
|
$where[] = "et.next_due_date IS NOT NULL";
|
||||||
|
|
||||||
|
// Only the most recent record per (employee, topic)
|
||||||
|
$where[] = "NOT EXISTS (
|
||||||
|
SELECT 1 FROM employee_trainings et2
|
||||||
|
WHERE et2.employee_id = et.employee_id
|
||||||
|
AND et2.training_topic_id = et.training_topic_id
|
||||||
|
AND (et2.completed_date > et.completed_date
|
||||||
|
OR (et2.completed_date = et.completed_date AND et2.id > et.id))
|
||||||
|
)";
|
||||||
|
|
||||||
|
if ($start && $end) {
|
||||||
|
$where[] = "et.next_due_date >= :start AND et.next_due_date <= :end";
|
||||||
|
$params['start'] = $start;
|
||||||
|
$params['end'] = $end;
|
||||||
|
}
|
||||||
|
if ($fDept > 0) { $where[] = "e.department_id = :did"; $params['did'] = $fDept; }
|
||||||
|
if ($fTopic > 0) { $where[] = "et.training_topic_id = :tid"; $params['tid'] = $fTopic; }
|
||||||
|
if ($fEmp > 0) { $where[] = "et.employee_id = :eid"; $params['eid'] = $fEmp; }
|
||||||
|
|
||||||
|
$whereSql = 'WHERE ' . implode(' AND ', $where);
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT et.id, et.employee_id, et.next_due_date, et.reminder_days,
|
||||||
|
tt.name AS topic_name, tt.default_reminder_days AS topic_default_rem,
|
||||||
|
e.first_name, e.last_name
|
||||||
|
FROM employee_trainings et
|
||||||
|
JOIN training_topics tt ON tt.id = et.training_topic_id
|
||||||
|
JOIN employees e ON e.id = et.employee_id
|
||||||
|
$whereSql
|
||||||
|
");
|
||||||
|
$stmt->execute($params);
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$today = new DateTime('today');
|
||||||
|
$events = [];
|
||||||
|
|
||||||
|
foreach ($rows as $r) {
|
||||||
|
$rem = $r['reminder_days'] !== null
|
||||||
|
? (int)$r['reminder_days']
|
||||||
|
: ($r['topic_default_rem'] !== null ? (int)$r['topic_default_rem'] : 30);
|
||||||
|
|
||||||
|
$due = DateTime::createFromFormat('Y-m-d', $r['next_due_date']);
|
||||||
|
if (!$due) continue;
|
||||||
|
$daysLeft = (int)$today->diff($due)->format('%r%a');
|
||||||
|
|
||||||
|
if ($daysLeft < 0) { $code = 'expired'; $color = '#dc3545'; }
|
||||||
|
elseif ($daysLeft <= $rem){ $code = 'due_soon'; $color = '#e8930c'; }
|
||||||
|
else { $code = 'compliant'; $color = '#198754'; }
|
||||||
|
|
||||||
|
if ($fStatus !== '' && $fStatus !== $code) continue;
|
||||||
|
|
||||||
|
$name = trim($r['first_name'] . ' ' . $r['last_name']);
|
||||||
|
$events[] = [
|
||||||
|
'id' => (int)$r['id'],
|
||||||
|
'title' => $name . ' — ' . $r['topic_name'],
|
||||||
|
'start' => $r['next_due_date'],
|
||||||
|
'allDay' => true,
|
||||||
|
'backgroundColor' => $color,
|
||||||
|
'borderColor' => $color,
|
||||||
|
'url' => 'employee-profile.php?id=' . (int)$r['employee_id'] . '#tab-training',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($events);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode([]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Bulk-create training records: one employee_trainings row per selected employee,
|
||||||
|
* all sharing the same course + parameters (a single training "session").
|
||||||
|
* Mirrors the next_due_date logic of ajax/employee_profile/save_training.php.
|
||||||
|
* HR-only.
|
||||||
|
*/
|
||||||
|
require_once(__DIR__ . '/../hr_auth_check.php');
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Metodo non consentito.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// $pdo and $currentUserId from hr_auth_check.php
|
||||||
|
|
||||||
|
$topicId = (int)($_POST['training_topic_id'] ?? 0);
|
||||||
|
$completedDate = trim($_POST['completed_date'] ?? '');
|
||||||
|
$deliveredBy = trim($_POST['delivered_by'] ?? '');
|
||||||
|
$description = trim($_POST['description'] ?? '');
|
||||||
|
$trainingType = trim($_POST['training_type'] ?? 'initial');
|
||||||
|
$freqRaw = $_POST['update_frequency_months'] ?? '';
|
||||||
|
$remRaw = $_POST['reminder_days'] ?? '';
|
||||||
|
$employeeIds = $_POST['employee_ids'] ?? [];
|
||||||
|
|
||||||
|
if (!is_array($employeeIds)) {
|
||||||
|
$employeeIds = [];
|
||||||
|
}
|
||||||
|
$employeeIds = array_values(array_unique(array_filter(array_map('intval', $employeeIds), fn($v) => $v > 0)));
|
||||||
|
|
||||||
|
if ($topicId <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Selezionare un corso.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if ($completedDate === '' || !DateTime::createFromFormat('Y-m-d', $completedDate)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'La data di completamento è obbligatoria.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if (empty($employeeIds)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Selezionare almeno un dipendente.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if (!in_array($trainingType, ['initial', 'refresher'], true)) {
|
||||||
|
$trainingType = 'initial';
|
||||||
|
}
|
||||||
|
|
||||||
|
$topicStmt = $pdo->prepare("SELECT default_frequency_months, default_reminder_days FROM training_topics WHERE id = :id");
|
||||||
|
$topicStmt->execute(['id' => $topicId]);
|
||||||
|
$topic = $topicStmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!$topic) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Corso non trovato.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$freq = ($freqRaw === '' || $freqRaw === null) ? null : max(0, (int)$freqRaw);
|
||||||
|
$rem = ($remRaw === '' || $remRaw === null) ? null : max(0, (int)$remRaw);
|
||||||
|
|
||||||
|
/* Effective frequency → next_due_date (same for every employee: same date + same frequency) */
|
||||||
|
$effFreq = $freq !== null ? $freq : ($topic['default_frequency_months'] !== null ? (int)$topic['default_frequency_months'] : null);
|
||||||
|
$nextDue = null;
|
||||||
|
if ($effFreq !== null && $effFreq > 0) {
|
||||||
|
$d = DateTime::createFromFormat('Y-m-d', $completedDate);
|
||||||
|
if ($d) {
|
||||||
|
$d->modify('+' . (int)$effFreq . ' months');
|
||||||
|
$nextDue = $d->format('Y-m-d');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$deliveredBy = $deliveredBy !== '' ? $deliveredBy : null;
|
||||||
|
$description = $description !== '' ? $description : null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
// Only insert for employees that actually exist
|
||||||
|
$checkEmp = $pdo->prepare("SELECT id FROM employees WHERE id = :id");
|
||||||
|
|
||||||
|
$ins = $pdo->prepare("
|
||||||
|
INSERT INTO employee_trainings
|
||||||
|
(employee_id, training_topic_id, completed_date,
|
||||||
|
delivered_by, description,
|
||||||
|
training_type, update_frequency_months, reminder_days, next_due_date,
|
||||||
|
created_by, created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
(:eid, :tid, :completed_date,
|
||||||
|
:delivered_by, :description,
|
||||||
|
:training_type, :freq, :rem, :next_due,
|
||||||
|
:cb, NOW(), NOW())
|
||||||
|
");
|
||||||
|
$logStmt = $pdo->prepare("
|
||||||
|
INSERT INTO employee_training_log
|
||||||
|
(employee_id, training_id, action, field, old_value, new_value, changed_by, changed_at)
|
||||||
|
VALUES
|
||||||
|
(:eid, :tid, 'created', NULL, NULL, NULL, :cb, NOW())
|
||||||
|
");
|
||||||
|
|
||||||
|
$created = 0;
|
||||||
|
foreach ($employeeIds as $eid) {
|
||||||
|
$checkEmp->execute(['id' => $eid]);
|
||||||
|
if (!$checkEmp->fetchColumn()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$ins->execute([
|
||||||
|
'eid' => $eid,
|
||||||
|
'tid' => $topicId,
|
||||||
|
'completed_date' => $completedDate,
|
||||||
|
'delivered_by' => $deliveredBy,
|
||||||
|
'description' => $description,
|
||||||
|
'training_type' => $trainingType,
|
||||||
|
'freq' => $freq,
|
||||||
|
'rem' => $rem,
|
||||||
|
'next_due' => $nextDue,
|
||||||
|
'cb' => $currentUserId,
|
||||||
|
]);
|
||||||
|
$newId = (int)$pdo->lastInsertId();
|
||||||
|
$logStmt->execute(['eid' => $eid, 'tid' => $newId, 'cb' => $currentUserId]);
|
||||||
|
$created++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'created' => $created,
|
||||||
|
'message' => $created . ' formazion' . ($created === 1 ? 'e registrata' : 'i registrate') . '.',
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,254 @@
|
|||||||
|
<?php
|
||||||
|
ob_start();
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
require_once(__DIR__ . '/../../../extra/auth.php');
|
||||||
|
require_once(__DIR__ . '/../class/db-functions.php');
|
||||||
|
|
||||||
|
while (ob_get_level()) {
|
||||||
|
ob_end_clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
if (!class_exists('Auth')) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Classe Auth non disponibile'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Auth::check()) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Sessione non valida o utente non autenticato'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
|
||||||
|
function formatDateIT($d)
|
||||||
|
{
|
||||||
|
if (!$d || $d === '0000-00-00') return '';
|
||||||
|
return date("d/m/Y", strtotime($d));
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDateTimeIT($d)
|
||||||
|
{
|
||||||
|
if (!$d || $d === '0000-00-00 00:00:00') return '';
|
||||||
|
return date("d/m/Y H:i", strtotime($d));
|
||||||
|
}
|
||||||
|
|
||||||
|
function worksheetNumberLabel($n)
|
||||||
|
{
|
||||||
|
$n = (int)$n;
|
||||||
|
return $n > 0 ? 'FL' . $n : '—';
|
||||||
|
}
|
||||||
|
|
||||||
|
function revisionLabel($rev)
|
||||||
|
{
|
||||||
|
$rev = trim((string)$rev);
|
||||||
|
return $rev !== '' ? $rev : '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$action = $_POST['action'] ?? '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($action === 'get_matrice_worksheets') {
|
||||||
|
$idmatrice = isset($_POST['idmatrice']) ? (int)$_POST['idmatrice'] : 0;
|
||||||
|
if ($idmatrice <= 0) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'ID matrice non valido'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
ws.id,
|
||||||
|
ws.idmatrice,
|
||||||
|
ws.worksheet_number,
|
||||||
|
ws.revision_code,
|
||||||
|
ws.worksheet_status,
|
||||||
|
ws.worksheet_date,
|
||||||
|
ws.customer_name,
|
||||||
|
ws.profile_type_code,
|
||||||
|
ws.marking,
|
||||||
|
ws.approved_by,
|
||||||
|
ws.created_at,
|
||||||
|
ws.updated_at,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM work_sheet_mescole wsm
|
||||||
|
WHERE wsm.worksheet_id = ws.id
|
||||||
|
) AS mix_count
|
||||||
|
FROM work_sheets ws
|
||||||
|
WHERE ws.idmatrice = ?
|
||||||
|
ORDER BY
|
||||||
|
ws.worksheet_number DESC,
|
||||||
|
CASE
|
||||||
|
WHEN ws.revision_code IS NULL OR ws.revision_code = '' THEN 0
|
||||||
|
WHEN ws.revision_code REGEXP '^R[0-9]+$' THEN CAST(SUBSTRING(ws.revision_code, 2) AS UNSIGNED)
|
||||||
|
ELSE 0
|
||||||
|
END DESC,
|
||||||
|
ws.id DESC
|
||||||
|
");
|
||||||
|
$stmt->execute([$idmatrice]);
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
foreach ($rows as $r) {
|
||||||
|
$data[] = [
|
||||||
|
'id' => (int)$r['id'],
|
||||||
|
'idmatrice' => (int)$r['idmatrice'],
|
||||||
|
'worksheet_number' => (int)($r['worksheet_number'] ?? 0),
|
||||||
|
'worksheet_number_label' => worksheetNumberLabel($r['worksheet_number'] ?? 0),
|
||||||
|
'revision_code' => $r['revision_code'] ?? '',
|
||||||
|
'revision_label' => revisionLabel($r['revision_code'] ?? ''),
|
||||||
|
'worksheet_status' => $r['worksheet_status'] ?? 'active',
|
||||||
|
'worksheet_status_label' => (($r['worksheet_status'] ?? 'active') === 'inactive') ? 'Inattivo' : 'Attivo',
|
||||||
|
'worksheet_date' => $r['worksheet_date'],
|
||||||
|
'worksheet_date_it' => formatDateIT($r['worksheet_date']),
|
||||||
|
'customer_name' => $r['customer_name'] ?? '',
|
||||||
|
'profile_type_code' => $r['profile_type_code'] ?? '',
|
||||||
|
'marking' => $r['marking'] ?? '',
|
||||||
|
'approved_by' => $r['approved_by'] ?? '',
|
||||||
|
'created_at' => $r['created_at'] ?? '',
|
||||||
|
'created_at_it' => formatDateTimeIT($r['created_at'] ?? ''),
|
||||||
|
'updated_at' => $r['updated_at'] ?? '',
|
||||||
|
'updated_at_it' => formatDateTimeIT($r['updated_at'] ?? ''),
|
||||||
|
'mix_count' => (int)($r['mix_count'] ?? 0)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'worksheets' => $data
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'get_worksheet_detail') {
|
||||||
|
$worksheetId = isset($_POST['worksheet_id']) ? (int)$_POST['worksheet_id'] : 0;
|
||||||
|
if ($worksheetId <= 0) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'ID foglio non valido'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
ws.*,
|
||||||
|
m.nome AS matrice_nome,
|
||||||
|
m.cliente AS matrice_cliente
|
||||||
|
FROM work_sheets ws
|
||||||
|
LEFT JOIN matrice m ON m.id = ws.idmatrice
|
||||||
|
WHERE ws.id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$worksheetId]);
|
||||||
|
$ws = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$ws) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Foglio di lavoro non trovato'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmtMix = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
wsm.*,
|
||||||
|
me.nome AS mescola_nome,
|
||||||
|
me.nomeuscita AS mescola_uscita
|
||||||
|
FROM work_sheet_mescole wsm
|
||||||
|
LEFT JOIN mescole me ON me.id = wsm.idmescola
|
||||||
|
WHERE wsm.worksheet_id = ?
|
||||||
|
ORDER BY wsm.mix_position ASC, wsm.id ASC
|
||||||
|
");
|
||||||
|
$stmtMix->execute([$worksheetId]);
|
||||||
|
$mixRows = $stmtMix->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'worksheet' => [
|
||||||
|
'id' => (int)$ws['id'],
|
||||||
|
'worksheet_number' => (int)($ws['worksheet_number'] ?? 0),
|
||||||
|
'worksheet_number_label' => worksheetNumberLabel($ws['worksheet_number'] ?? 0),
|
||||||
|
'revision_code' => $ws['revision_code'] ?? '',
|
||||||
|
'revision_label' => revisionLabel($ws['revision_code'] ?? ''),
|
||||||
|
'worksheet_status' => $ws['worksheet_status'] ?? 'active',
|
||||||
|
'worksheet_status_label' => (($ws['worksheet_status'] ?? 'active') === 'inactive') ? 'Inattivo' : 'Attivo',
|
||||||
|
'idmatrice' => (int)$ws['idmatrice'],
|
||||||
|
'matrice_nome' => $ws['matrice_nome'] ?? '',
|
||||||
|
'matrice_cliente' => $ws['matrice_cliente'] ?? '',
|
||||||
|
'worksheet_date' => $ws['worksheet_date'] ?? '',
|
||||||
|
'worksheet_date_it' => formatDateIT($ws['worksheet_date'] ?? ''),
|
||||||
|
'customer_name' => $ws['customer_name'] ?? '',
|
||||||
|
'profile_type_code' => $ws['profile_type_code'] ?? '',
|
||||||
|
'marking' => $ws['marking'] ?? '',
|
||||||
|
'prod_control_measure_settings' => $ws['prod_control_measure_settings'] ?? '',
|
||||||
|
'control_frequency_cut' => $ws['control_frequency_cut'] ?? '',
|
||||||
|
'control_frequency_drawing' => $ws['control_frequency_drawing'] ?? '',
|
||||||
|
'control_frequency_jig' => $ws['control_frequency_jig'] ?? '',
|
||||||
|
'control_mode_jig' => $ws['control_mode_jig'] ?? '',
|
||||||
|
'requested_package_code' => $ws['requested_package_code'] ?? '',
|
||||||
|
'meters_per_package' => $ws['meters_per_package'] ?? '',
|
||||||
|
'meters_per_package_tolerance' => $ws['meters_per_package_tolerance'] ?? '',
|
||||||
|
'meters_per_package_notes' => $ws['meters_per_package_notes'] ?? '',
|
||||||
|
'box_type' => $ws['box_type'] ?? '',
|
||||||
|
'packages_or_pieces_per_box' => $ws['packages_or_pieces_per_box'] ?? '',
|
||||||
|
'meters_per_box' => $ws['meters_per_box'] ?? '',
|
||||||
|
'pallet_type' => $ws['pallet_type'] ?? '',
|
||||||
|
'boxes_or_packages_per_pallet' => $ws['boxes_or_packages_per_pallet'] ?? '',
|
||||||
|
'speed_expected_kg_h' => $ws['speed_expected_kg_h'] ?? '',
|
||||||
|
'speed_actual_kg_h' => $ws['speed_actual_kg_h'] ?? '',
|
||||||
|
'speed_expected_m_h' => $ws['speed_expected_m_h'] ?? '',
|
||||||
|
'speed_actual_m_h' => $ws['speed_actual_m_h'] ?? '',
|
||||||
|
'approved_by' => $ws['approved_by'] ?? '',
|
||||||
|
'notes' => $ws['notes'] ?? '',
|
||||||
|
'created_at' => $ws['created_at'] ?? '',
|
||||||
|
'created_at_it' => formatDateTimeIT($ws['created_at'] ?? ''),
|
||||||
|
'updated_at' => $ws['updated_at'] ?? '',
|
||||||
|
'updated_at_it' => formatDateTimeIT($ws['updated_at'] ?? '')
|
||||||
|
],
|
||||||
|
'mix_rows' => array_map(function ($r) {
|
||||||
|
return [
|
||||||
|
'id' => (int)$r['id'],
|
||||||
|
'mix_position' => (int)$r['mix_position'],
|
||||||
|
'mescola_nome' => $r['mescola_nome'] ?? '',
|
||||||
|
'mescola_uscita' => $r['mescola_uscita'] ?? '',
|
||||||
|
'mix_weight_g_m' => $r['mix_weight_g_m'] ?? '',
|
||||||
|
'required_density' => $r['required_density'] ?? '',
|
||||||
|
'required_hardness_shore_a' => $r['required_hardness_shore_a'] ?? '',
|
||||||
|
'lubrication_type' => $r['lubrication_type'] ?? '',
|
||||||
|
'lubrication_notes' => $r['lubrication_notes'] ?? ''
|
||||||
|
];
|
||||||
|
}, $mixRows)
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Azione AJAX sconosciuta'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
<?php
|
||||||
|
require_once(__DIR__ . "/class/db-functions.php");
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$STATUS_PROGRAMMATO = 6;
|
||||||
|
$STATUS_DA_PROGRAMMARE = 1;
|
||||||
|
|
||||||
|
$jsonFile = __DIR__ . "/data/production_priority.json";
|
||||||
|
|
||||||
|
/* -------------------------------
|
||||||
|
JSON HELPERS
|
||||||
|
---------------------------------*/
|
||||||
|
function loadPriority()
|
||||||
|
{
|
||||||
|
global $jsonFile;
|
||||||
|
if (!file_exists($jsonFile)) return [];
|
||||||
|
return json_decode(file_get_contents($jsonFile), true) ?: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function savePriority($arr)
|
||||||
|
{
|
||||||
|
global $jsonFile;
|
||||||
|
file_put_contents($jsonFile, json_encode($arr, JSON_PRETTY_PRINT));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------
|
||||||
|
LOAD: PROGRAMMATI
|
||||||
|
---------------------------------*/
|
||||||
|
if ($_GET['mode'] === 'planned') {
|
||||||
|
|
||||||
|
$priority = loadPriority();
|
||||||
|
$map = [];
|
||||||
|
foreach ($priority as $p) $map[$p['id']] = $p['priority'];
|
||||||
|
|
||||||
|
$sql = "SELECT p.*, m.nome AS matrice, ms.nome AS mescola,
|
||||||
|
l.name AS linea, c.nome AS cliente
|
||||||
|
FROM productiondata p
|
||||||
|
LEFT JOIN matrice m ON p.idmatrice = m.id
|
||||||
|
LEFT JOIN mescole ms ON p.idmescola = ms.id
|
||||||
|
LEFT JOIN production_lines l ON p.id_linea = l.id
|
||||||
|
LEFT JOIN clients c ON p.id_cliente = c.id
|
||||||
|
WHERE p.id_status = :s";
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute(['s' => $STATUS_PROGRAMMATO]);
|
||||||
|
$rows = $stmt->fetchAll();
|
||||||
|
|
||||||
|
// Ordina per priority
|
||||||
|
usort($rows, function ($a, $b) use ($map) {
|
||||||
|
return ($map[$a['id']] ?? 9999) <=> ($map[$b['id']] ?? 9999);
|
||||||
|
});
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
foreach ($rows as $r):
|
||||||
|
$prio = $map[$r['id']] ?? '-';
|
||||||
|
?>
|
||||||
|
<tr data-id="<?= $r['id'] ?>">
|
||||||
|
<td><strong><?= $prio ?></strong></td>
|
||||||
|
<td><?= date('d/m/Y', strtotime($r['Data'])) ?></td>
|
||||||
|
<td><?= $r['matrice'] ?></td>
|
||||||
|
<td><?= $r['mescola'] ?></td>
|
||||||
|
<td><?= $r['linea'] ?></td>
|
||||||
|
<td><?= $r['cliente'] ?? '-' ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php
|
||||||
|
endforeach;
|
||||||
|
|
||||||
|
echo json_encode(['html' => ob_get_clean()]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------
|
||||||
|
LOAD: DA PROGRAMMARE
|
||||||
|
---------------------------------*/
|
||||||
|
if ($_GET['mode'] === 'to_plan') {
|
||||||
|
|
||||||
|
$sql = "SELECT p.*, m.nome AS matrice, ms.nome AS mescola,
|
||||||
|
l.name AS linea, c.nome AS cliente
|
||||||
|
FROM productiondata p
|
||||||
|
LEFT JOIN matrice m ON p.idmatrice = m.id
|
||||||
|
LEFT JOIN mescole ms ON p.idmescola = ms.id
|
||||||
|
LEFT JOIN production_lines l ON p.id_linea = l.id
|
||||||
|
LEFT JOIN clients c ON p.id_cliente = c.id
|
||||||
|
WHERE p.id_status = :s
|
||||||
|
ORDER BY p.Data ASC";
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute(['s' => $STATUS_DA_PROGRAMMARE]);
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
foreach ($stmt as $r): ?>
|
||||||
|
<tr data-id="<?= $r['id'] ?>">
|
||||||
|
<td><?= date('d/m/Y', strtotime($r['Data'])) ?></td>
|
||||||
|
<td><?= $r['matrice'] ?></td>
|
||||||
|
<td><?= $r['mescola'] ?></td>
|
||||||
|
<td><?= $r['linea'] ?></td>
|
||||||
|
<td><?= $r['cliente'] ?? '-' ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach;
|
||||||
|
|
||||||
|
echo json_encode(['html' => ob_get_clean()]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------
|
||||||
|
UPDATE STATUS + PRIORITY
|
||||||
|
---------------------------------*/
|
||||||
|
if ($_POST['mode'] === 'save') {
|
||||||
|
|
||||||
|
$planned = json_decode($_POST['planned'], true);
|
||||||
|
$toPlan = json_decode($_POST['toPlan'], true);
|
||||||
|
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
// Aggiorna programmati
|
||||||
|
foreach ($planned as $i => $row) {
|
||||||
|
$id = $row['id'];
|
||||||
|
$stmt = $pdo->prepare("UPDATE productiondata SET id_status = :s WHERE id = :id");
|
||||||
|
$stmt->execute(['s' => $STATUS_PROGRAMMATO, 'id' => $id]);
|
||||||
|
$priorityArr[] = ['id' => $id, 'priority' => $i + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggiorna da programmare
|
||||||
|
foreach ($toPlan as $row) {
|
||||||
|
$id = $row['id'];
|
||||||
|
$stmt = $pdo->prepare("UPDATE productiondata SET id_status = :s WHERE id = :id");
|
||||||
|
$stmt->execute(['s' => $STATUS_DA_PROGRAMMARE, 'id' => $id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
savePriority($priorityArr);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
require_once("include/headscript.php");
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$programmati = json_decode($_POST["programmati"] ?? '[]', true);
|
||||||
|
$daProgrammare = json_decode($_POST["daProgrammare"] ?? '[]', true);
|
||||||
|
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
foreach ($programmati as $p) {
|
||||||
|
if (empty($p['id'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
"UPDATE productiondata SET id_status=6, priority=? WHERE id=?"
|
||||||
|
);
|
||||||
|
$stmt->execute([(int)$p["priority"], (int)$p["id"]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($daProgrammare as $p) {
|
||||||
|
if (empty($p['id'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
"UPDATE productiondata SET id_status=1, priority=? WHERE id=?"
|
||||||
|
);
|
||||||
|
$stmt->execute([(int)$p["priority"], (int)$p["id"]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
echo json_encode(["success" => true]);
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
<?php
|
|
||||||
ob_start();
|
|
||||||
session_start();
|
|
||||||
require_once '../../vendor/autoload.php';
|
|
||||||
|
|
||||||
$response = ['error' => '', 'rows' => [], 'columns' => [], 'template_id' => 0, 'filename' => '', 'excel_data' => []];
|
|
||||||
|
|
||||||
try {
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
$input = json_decode(file_get_contents('php://input'), true);
|
|
||||||
$template_id = isset($input['template_id']) ? intval($input['template_id']) : 0;
|
|
||||||
$filename = $input['routine_data']['filename'] ?? '';
|
|
||||||
$headerrow = $input['routine_data']['headerrow'] ?? 1;
|
|
||||||
$excelData = $input['excel_data'] ?? [];
|
|
||||||
$routineData = $input['routine_data'] ?? [];
|
|
||||||
|
|
||||||
if (!$filename || empty($excelData)) {
|
|
||||||
throw new Exception("Dati della routine mancanti.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$routineFile = __DIR__ . '/routines/' . $filename;
|
|
||||||
if (file_exists($routineFile)) {
|
|
||||||
include_once $routineFile;
|
|
||||||
$routineData['xls_headers'] = $_SESSION['headers'] ?? [];
|
|
||||||
applyRoutine($excelData, $routineData); // Modifica $excelData in place
|
|
||||||
error_log("Routine {$routineData['name']} applicata (file: {$filename}) per template {$template_id}, header row: {$headerrow}");
|
|
||||||
} else {
|
|
||||||
throw new Exception("File della routine non trovato: $routineFile");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Aggiorna la sessione con i dati modificati
|
|
||||||
$_SESSION['excel_data'] = $excelData;
|
|
||||||
|
|
||||||
$response['excel_data'] = $excelData;
|
|
||||||
$response['rows'] = array_column($excelData, 'data');
|
|
||||||
$response['columns'] = $_SESSION['headers'];
|
|
||||||
$response['template_id'] = $template_id;
|
|
||||||
$response['filename'] = $input['filename'] ?? '';
|
|
||||||
} else {
|
|
||||||
$response['error'] = "Richiesta non valida.";
|
|
||||||
}
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$response['error'] = "Errore durante l'applicazione della routine: " . $e->getMessage();
|
|
||||||
error_log("Exception in apply_routine.php: " . $e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
ob_end_clean();
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
echo json_encode($response);
|
|
||||||
exit;
|
|
||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 19 KiB |
@@ -37,6 +37,16 @@ $(function () {
|
|||||||
},
|
},
|
||||||
));
|
));
|
||||||
}),
|
}),
|
||||||
|
// NEW: se la pagina parte già con .wrapper.toggled, abilita subito l'hover
|
||||||
|
$(".wrapper").hasClass("toggled") &&
|
||||||
|
$(".sidebar-wrapper").hover(
|
||||||
|
function () {
|
||||||
|
$(".wrapper").addClass("sidebar-hovered");
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
$(".wrapper").removeClass("sidebar-hovered");
|
||||||
|
},
|
||||||
|
),
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$(window).on("scroll", function () {
|
$(window).on("scroll", function () {
|
||||||
$(this).scrollTop() > 300
|
$(this).scrollTop() > 300
|
||||||
|
|||||||
@@ -0,0 +1,362 @@
|
|||||||
|
(function () {
|
||||||
|
function escapeHtml(str) {
|
||||||
|
return String(str || "")
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """)
|
||||||
|
.replace(/'/g, "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
function valueOrDash(v) {
|
||||||
|
return v === null || v === undefined || String(v).trim() === ""
|
||||||
|
? "—"
|
||||||
|
: escapeHtml(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderReadonlyField(label, value) {
|
||||||
|
return `
|
||||||
|
<div class="readonly-label">${escapeHtml(label)}</div>
|
||||||
|
<div class="readonly-value">${valueOrDash(value)}</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.loadMatriceWorksheets = function (idmatrice, matriceNome, endpoint) {
|
||||||
|
endpoint = endpoint || "ajax/worksheet-linked-data.php";
|
||||||
|
|
||||||
|
$("#wl_idmatrice").val(idmatrice);
|
||||||
|
$("#wl_matrice_name").html(
|
||||||
|
`<span class="worksheet-chip"><i class="fa-solid fa-layer-group"></i>${escapeHtml(matriceNome || "")}</span>`,
|
||||||
|
);
|
||||||
|
$("#worksheetsListContainer").html(
|
||||||
|
'<div class="text-muted">Caricamento fogli di lavoro...</div>',
|
||||||
|
);
|
||||||
|
|
||||||
|
fetch(endpoint, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||||
|
body:
|
||||||
|
"action=get_matrice_worksheets&idmatrice=" +
|
||||||
|
encodeURIComponent(idmatrice),
|
||||||
|
})
|
||||||
|
.then(async (r) => {
|
||||||
|
const text = await r.text();
|
||||||
|
try {
|
||||||
|
return JSON.parse(text);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Risposta non JSON worksheets:", text);
|
||||||
|
throw new Error("Risposta non JSON, vedi console");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
if (!data.success) {
|
||||||
|
$("#worksheetsListContainer").html(
|
||||||
|
'<div class="text-danger">Errore nel caricamento dei fogli</div>',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = data.worksheets || [];
|
||||||
|
|
||||||
|
if (!rows.length) {
|
||||||
|
$("#worksheetsListContainer").html(
|
||||||
|
'<div class="text-muted">Nessun foglio di lavoro collegato a questo profilo</div>',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = `
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped align-middle worksheet-list-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:110px;">Foglio</th>
|
||||||
|
<th style="width:100px;">Rev.</th>
|
||||||
|
<th style="width:110px;">Stato</th>
|
||||||
|
<th style="width:140px;">Data foglio</th>
|
||||||
|
<th>Cliente</th>
|
||||||
|
<th>Codice profilo</th>
|
||||||
|
<th style="width:110px;">Mescole</th>
|
||||||
|
<th style="width:230px;">Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
`;
|
||||||
|
|
||||||
|
rows.forEach((r) => {
|
||||||
|
const statusBadge =
|
||||||
|
r.worksheet_status === "inactive"
|
||||||
|
? `<span class="worksheet-badge-status-inactive">Inattivo</span>`
|
||||||
|
: `<span class="worksheet-badge-status-active">Attivo</span>`;
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<tr>
|
||||||
|
<td><span class="worksheet-badge-fl">${escapeHtml(r.worksheet_number_label || "—")}</span></td>
|
||||||
|
<td><span class="worksheet-badge-rev">${escapeHtml(r.revision_label || "0")}</span></td>
|
||||||
|
<td>${statusBadge}</td>
|
||||||
|
<td>${escapeHtml(r.worksheet_date_it || "—")}</td>
|
||||||
|
<td title="${escapeHtml(r.customer_name || "")}">${escapeHtml(r.customer_name || "—")}</td>
|
||||||
|
<td>${escapeHtml(r.profile_type_code || "—")}</td>
|
||||||
|
<td>${escapeHtml(String(r.mix_count || 0))}</td>
|
||||||
|
<td class="text-nowrap">
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-view-worksheet open-worksheet-detail"
|
||||||
|
data-id="${r.id}"
|
||||||
|
data-endpoint="${escapeHtml(endpoint)}">
|
||||||
|
<i class="fa-solid fa-eye me-1"></i>Apri dettaglio
|
||||||
|
</button>
|
||||||
|
<a class="worksheet-open-link ms-1"
|
||||||
|
href="manage-worksheet.php?id=${r.id}"
|
||||||
|
target="_blank">
|
||||||
|
<i class="fa-solid fa-arrow-up-right-from-square"></i>Apri pagina
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
html += `
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
$("#worksheetsListContainer").html(html);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
$("#worksheetsListContainer").html(
|
||||||
|
'<div class="text-danger">Errore nel caricamento dei fogli</div>',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
window.loadWorksheetDetail = function (worksheetId, endpoint) {
|
||||||
|
endpoint = endpoint || "ajax/worksheet-linked-data.php";
|
||||||
|
|
||||||
|
$("#worksheetDetailContainer").html(
|
||||||
|
'<div class="text-muted">Caricamento dettaglio foglio...</div>',
|
||||||
|
);
|
||||||
|
|
||||||
|
fetch(endpoint, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||||
|
body:
|
||||||
|
"action=get_worksheet_detail&worksheet_id=" +
|
||||||
|
encodeURIComponent(worksheetId),
|
||||||
|
})
|
||||||
|
.then(async (r) => {
|
||||||
|
const text = await r.text();
|
||||||
|
try {
|
||||||
|
return JSON.parse(text);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Risposta non JSON worksheet detail:", text);
|
||||||
|
throw new Error("Risposta non JSON, vedi console");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
if (!data.success) {
|
||||||
|
console.error("Errore worksheets:", data);
|
||||||
|
|
||||||
|
$("#worksheetsListContainer").html(
|
||||||
|
'<div class="text-danger">Errore nel caricamento dei fogli: ' +
|
||||||
|
escapeHtml(
|
||||||
|
data.message || "nessun dettaglio restituito",
|
||||||
|
) +
|
||||||
|
"</div>",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ws = data.worksheet || {};
|
||||||
|
const mixRows = data.mix_rows || [];
|
||||||
|
|
||||||
|
let mixHtml = `<div class="text-muted">Nessuna mescola associata</div>`;
|
||||||
|
|
||||||
|
if (mixRows.length) {
|
||||||
|
mixHtml = `
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped align-middle mix-readonly-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:80px;">Pos</th>
|
||||||
|
<th>Mescola</th>
|
||||||
|
<th style="width:120px;">Peso g/m</th>
|
||||||
|
<th style="width:140px;">Densità</th>
|
||||||
|
<th style="width:150px;">Durezza</th>
|
||||||
|
<th style="width:130px;">Lubr.</th>
|
||||||
|
<th>Note lubr.</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
`;
|
||||||
|
|
||||||
|
mixRows.forEach((r) => {
|
||||||
|
const nomeMescola = r.mescola_uscita
|
||||||
|
? `${escapeHtml(r.mescola_nome || "—")} <div class="small text-muted">${escapeHtml(r.mescola_uscita)}</div>`
|
||||||
|
: escapeHtml(r.mescola_nome || "—");
|
||||||
|
|
||||||
|
mixHtml += `
|
||||||
|
<tr>
|
||||||
|
<td>${escapeHtml(String(r.mix_position || ""))}</td>
|
||||||
|
<td>${nomeMescola}</td>
|
||||||
|
<td>${valueOrDash(r.mix_weight_g_m)}</td>
|
||||||
|
<td>${valueOrDash(r.required_density)}</td>
|
||||||
|
<td>${valueOrDash(r.required_hardness_shore_a)}</td>
|
||||||
|
<td>${valueOrDash(r.lubrication_type)}</td>
|
||||||
|
<td>${valueOrDash(r.lubrication_notes)}</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
mixHtml += `
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = `
|
||||||
|
<div class="worksheet-title-box mb-4">
|
||||||
|
<div class="d-flex justify-content-between align-items-start gap-3 flex-wrap">
|
||||||
|
<div>
|
||||||
|
<h4 class="mb-1">${escapeHtml(ws.worksheet_number_label || "—")} / Rev. ${escapeHtml(ws.revision_label || "0")}</h4>
|
||||||
|
<small>
|
||||||
|
Stato: ${escapeHtml(ws.worksheet_status_label || "Attivo")}
|
||||||
|
${ws.matrice_nome ? " • Profilo: " + escapeHtml(ws.matrice_nome) : ""}
|
||||||
|
${ws.matrice_cliente ? " • Cliente matrice: " + escapeHtml(ws.matrice_cliente) : ""}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="manage-worksheet.php?id=${escapeHtml(String(ws.id || ""))}"
|
||||||
|
target="_blank"
|
||||||
|
class="worksheet-open-link">
|
||||||
|
<i class="fa-solid fa-arrow-up-right-from-square"></i>
|
||||||
|
Apri foglio completo
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="readonly-card">
|
||||||
|
<div class="readonly-card-header">Dati principali</div>
|
||||||
|
<div class="readonly-card-body">
|
||||||
|
<div class="readonly-grid">
|
||||||
|
${renderReadonlyField("Foglio", ws.worksheet_number_label)}
|
||||||
|
${renderReadonlyField("Revisione", ws.revision_label)}
|
||||||
|
${renderReadonlyField("Stato", ws.worksheet_status_label)}
|
||||||
|
${renderReadonlyField("Data foglio", ws.worksheet_date_it)}
|
||||||
|
${renderReadonlyField("Cliente override", ws.customer_name)}
|
||||||
|
${renderReadonlyField("Codice profilo", ws.profile_type_code)}
|
||||||
|
${renderReadonlyField("Marchiatura", ws.marking)}
|
||||||
|
${renderReadonlyField("Approvato da", ws.approved_by)}
|
||||||
|
${renderReadonlyField("Creato il", ws.created_at_it)}
|
||||||
|
${renderReadonlyField("Ultimo aggiornamento", ws.updated_at_it)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="readonly-card">
|
||||||
|
<div class="readonly-card-header">Controlli produzione</div>
|
||||||
|
<div class="readonly-card-body">
|
||||||
|
<div class="readonly-grid">
|
||||||
|
${renderReadonlyField("Taglio", ws.control_frequency_cut)}
|
||||||
|
${renderReadonlyField("Disegno", ws.control_frequency_drawing)}
|
||||||
|
${renderReadonlyField("Dima", ws.control_frequency_jig)}
|
||||||
|
${renderReadonlyField("Modalità dima", ws.control_mode_jig)}
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="readonly-label mb-2">Impostazione misure controllo produzione</div>
|
||||||
|
<div class="readonly-value" style="white-space:pre-wrap;">${valueOrDash(ws.prod_control_measure_settings)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="readonly-card">
|
||||||
|
<div class="readonly-card-header">Packaging / Confezionamento</div>
|
||||||
|
<div class="readonly-card-body">
|
||||||
|
<div class="readonly-grid">
|
||||||
|
${renderReadonlyField("Codice confezione", ws.requested_package_code)}
|
||||||
|
${renderReadonlyField("Metri per confezione", ws.meters_per_package)}
|
||||||
|
${renderReadonlyField("Tolleranza metri/conf.", ws.meters_per_package_tolerance)}
|
||||||
|
${renderReadonlyField("Scatola tipo", ws.box_type)}
|
||||||
|
${renderReadonlyField("Conf./pezzi per scatola", ws.packages_or_pieces_per_box)}
|
||||||
|
${renderReadonlyField("Metri per scatola", ws.meters_per_box)}
|
||||||
|
${renderReadonlyField("Bancale tipo", ws.pallet_type)}
|
||||||
|
${renderReadonlyField("Scatole/conf. per bancale", ws.boxes_or_packages_per_pallet)}
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="readonly-label mb-2">Note metri / confezione</div>
|
||||||
|
<div class="readonly-value" style="white-space:pre-wrap;">${valueOrDash(ws.meters_per_package_notes)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="readonly-card">
|
||||||
|
<div class="readonly-card-header">Velocità e note</div>
|
||||||
|
<div class="readonly-card-body">
|
||||||
|
<div class="readonly-grid">
|
||||||
|
${renderReadonlyField("Vel. prevista kg/h", ws.speed_expected_kg_h)}
|
||||||
|
${renderReadonlyField("Vel. effettiva kg/h", ws.speed_actual_kg_h)}
|
||||||
|
${renderReadonlyField("Vel. prevista m/h", ws.speed_expected_m_h)}
|
||||||
|
${renderReadonlyField("Vel. effettiva m/h", ws.speed_actual_m_h)}
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="readonly-label mb-2">Note</div>
|
||||||
|
<div class="readonly-value" style="white-space:pre-wrap;">${valueOrDash(ws.notes)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="readonly-card">
|
||||||
|
<div class="readonly-card-header">Mescole associate al foglio</div>
|
||||||
|
<div class="readonly-card-body">
|
||||||
|
${mixHtml}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
$("#worksheetDetailContainer").html(html);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Catch loadMatriceWorksheets:", err);
|
||||||
|
|
||||||
|
$("#worksheetsListContainer").html(
|
||||||
|
'<div class="text-danger">Errore nel caricamento dei fogli: ' +
|
||||||
|
escapeHtml(err.message || "errore JavaScript/fetch") +
|
||||||
|
"</div>",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$(document).on("click", ".worksheets, .show-worksheets", function () {
|
||||||
|
const idmatrice = $(this).data("id");
|
||||||
|
const nome = $(this).data("nome") || "";
|
||||||
|
const endpoint =
|
||||||
|
$(this).data("endpoint") || "ajax/worksheet-linked-data.php";
|
||||||
|
|
||||||
|
loadMatriceWorksheets(idmatrice, nome, endpoint);
|
||||||
|
new bootstrap.Modal(
|
||||||
|
document.getElementById("worksheetsListModal"),
|
||||||
|
).show();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on("click", ".open-worksheet-detail", function () {
|
||||||
|
const worksheetId = $(this).data("id");
|
||||||
|
const endpoint =
|
||||||
|
$(this).data("endpoint") || "ajax/worksheet-linked-data.php";
|
||||||
|
|
||||||
|
loadWorksheetDetail(worksheetId, endpoint);
|
||||||
|
new bootstrap.Modal(
|
||||||
|
document.getElementById("worksheetDetailModal"),
|
||||||
|
).show();
|
||||||
|
});
|
||||||
|
})();
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
@@ -1,58 +0,0 @@
|
|||||||
<?php
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
|
|
||||||
// URL dell'API e credenziali
|
|
||||||
$api_url = 'https://93.43.5.102/limsapi/api/authentication/authenticate';
|
|
||||||
$credentials = [
|
|
||||||
'Username' => 'WebApiUser',
|
|
||||||
'Password' => 'webapiuser01'
|
|
||||||
];
|
|
||||||
|
|
||||||
// Inizializza cURL
|
|
||||||
$ch = curl_init($api_url);
|
|
||||||
|
|
||||||
// Configura le opzioni di cURL
|
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
||||||
curl_setopt($ch, CURLOPT_POST, true);
|
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($credentials));
|
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
||||||
'Content-Type: application/json',
|
|
||||||
'Accept: application/json'
|
|
||||||
]);
|
|
||||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Solo per test
|
|
||||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // Solo per test
|
|
||||||
curl_setopt($ch, CURLOPT_VERBOSE, true);
|
|
||||||
$log = fopen('curl_auth_debug.log', 'w');
|
|
||||||
curl_setopt($ch, CURLOPT_STDERR, $log);
|
|
||||||
|
|
||||||
// Esegui la richiesta
|
|
||||||
$response = curl_exec($ch);
|
|
||||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
||||||
$curl_error = curl_error($ch);
|
|
||||||
fclose($log);
|
|
||||||
|
|
||||||
// Verifica errori
|
|
||||||
if ($response === false || $http_code != 200) {
|
|
||||||
http_response_code($http_code ? $http_code : 500);
|
|
||||||
echo json_encode([
|
|
||||||
'error' => 'Errore nella richiesta API',
|
|
||||||
'http_code' => $http_code,
|
|
||||||
'curl_error' => $curl_error,
|
|
||||||
'response' => substr($response, 0, 1000)
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
$decoded = json_decode($response);
|
|
||||||
if (json_last_error() === JSON_ERROR_NONE) {
|
|
||||||
http_response_code($http_code);
|
|
||||||
echo $response;
|
|
||||||
} else {
|
|
||||||
http_response_code(500);
|
|
||||||
echo json_encode([
|
|
||||||
'error' => 'Risposta non JSON valida',
|
|
||||||
'http_code' => $http_code,
|
|
||||||
'response' => substr($response, 0, 1000)
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
curl_close($ch);
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
require_once(__DIR__ . '/include/headscript.php');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$iduser = $iduserlogin ?? null;
|
||||||
|
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$id = (int)($input['id'] ?? 0);
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
throw new Exception('ID non valido.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($iduser === null) {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT *
|
||||||
|
FROM cad_area_jobs
|
||||||
|
WHERE id = :id
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
':id' => $id
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT *
|
||||||
|
FROM cad_area_jobs
|
||||||
|
WHERE id = :id
|
||||||
|
AND iduser = :iduser
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
':id' => $id,
|
||||||
|
':iduser' => $iduser
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$job = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$job) {
|
||||||
|
throw new Exception('Record non trovato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($job['file_path']) && file_exists($job['file_path'])) {
|
||||||
|
unlink($job['file_path']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmtDelete = $pdo->prepare("
|
||||||
|
DELETE FROM cad_area_jobs
|
||||||
|
WHERE id = :id
|
||||||
|
");
|
||||||
|
$stmtDelete->execute([':id' => $id]);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true
|
||||||
|
]);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log('CAD area delete error: ' . $e->getMessage());
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,338 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
require_once(__DIR__ . '/include/headscript.php');
|
||||||
|
|
||||||
|
function jsonResponse(array $data): void
|
||||||
|
{
|
||||||
|
echo json_encode($data);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateJobProcessing(PDO $pdo, int $id): void
|
||||||
|
{
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE cad_area_jobs
|
||||||
|
SET
|
||||||
|
status = 'processing',
|
||||||
|
message = 'Elaborazione in corso...',
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = ?
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateJobError(PDO $pdo, int $id, string $message, ?array $pythonResponse = null): void
|
||||||
|
{
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE cad_area_jobs
|
||||||
|
SET
|
||||||
|
status = 'error',
|
||||||
|
message = ?,
|
||||||
|
python_response = ?,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = ?
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
$message,
|
||||||
|
$pythonResponse ? json_encode($pythonResponse) : null,
|
||||||
|
$id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateJobCompleted(PDO $pdo, int $id, array $response): void
|
||||||
|
{
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE cad_area_jobs
|
||||||
|
SET
|
||||||
|
status = 'completed',
|
||||||
|
message = ?,
|
||||||
|
area_mm2 = ?,
|
||||||
|
area_cm2 = ?,
|
||||||
|
area_m2 = ?,
|
||||||
|
width_mm = ?,
|
||||||
|
height_mm = ?,
|
||||||
|
scale_detected = ?,
|
||||||
|
scale_used = ?,
|
||||||
|
confidence = ?,
|
||||||
|
python_response = ?,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = ?
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
$response['message'] ?? 'Area calcolata correttamente.',
|
||||||
|
$response['area_mm2'] ?? null,
|
||||||
|
$response['area_cm2'] ?? null,
|
||||||
|
$response['area_m2'] ?? null,
|
||||||
|
$response['width_mm'] ?? null,
|
||||||
|
$response['height_mm'] ?? null,
|
||||||
|
$response['scale_detected'] ?? null,
|
||||||
|
$response['scale_used'] ?? null,
|
||||||
|
$response['confidence'] ?? null,
|
||||||
|
json_encode($response),
|
||||||
|
$id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeCalculationMode(?string $mode): string
|
||||||
|
{
|
||||||
|
$allowed = [
|
||||||
|
'auto_roi',
|
||||||
|
'stitch_contour',
|
||||||
|
'filled_union',
|
||||||
|
'closed_path'
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!$mode || !in_array($mode, $allowed, true)) {
|
||||||
|
return 'auto_roi';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasValidRoi(array $job): bool
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
array_key_exists('roi_x', $job) &&
|
||||||
|
array_key_exists('roi_y', $job) &&
|
||||||
|
array_key_exists('roi_width', $job) &&
|
||||||
|
array_key_exists('roi_height', $job) &&
|
||||||
|
$job['roi_x'] !== null &&
|
||||||
|
$job['roi_y'] !== null &&
|
||||||
|
$job['roi_width'] !== null &&
|
||||||
|
$job['roi_height'] !== null &&
|
||||||
|
(float)$job['roi_width'] > 0 &&
|
||||||
|
(float)$job['roi_height'] > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function callPythonAreaService(string $url, array $job): array
|
||||||
|
{
|
||||||
|
$filePath = $job['file_path'] ?? '';
|
||||||
|
$originalFilename = $job['original_filename'] ?? basename($filePath);
|
||||||
|
|
||||||
|
if (!$filePath || !file_exists($filePath)) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'File PDF non trovato sul server: ' . $filePath
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$mode = normalizeCalculationMode($job['calculation_mode'] ?? 'auto_roi');
|
||||||
|
|
||||||
|
$scaleRatio = $job['scale_used'] ?? null;
|
||||||
|
|
||||||
|
if ($scaleRatio === null || $scaleRatio === '' || (float)$scaleRatio <= 0) {
|
||||||
|
$scaleRatio = '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
$curlFile = new CURLFile(
|
||||||
|
$filePath,
|
||||||
|
'application/pdf',
|
||||||
|
$originalFilename
|
||||||
|
);
|
||||||
|
|
||||||
|
$postFields = [
|
||||||
|
'file' => $curlFile,
|
||||||
|
'mode' => $mode,
|
||||||
|
'scale_ratio' => (string)$scaleRatio,
|
||||||
|
'roi_x' => (string)$job['roi_x'],
|
||||||
|
'roi_y' => (string)$job['roi_y'],
|
||||||
|
'roi_width' => (string)$job['roi_width'],
|
||||||
|
'roi_height' => (string)$job['roi_height'],
|
||||||
|
'roi_page' => (string)($job['roi_page'] ?? 1)
|
||||||
|
];
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_URL => $url,
|
||||||
|
CURLOPT_POST => true,
|
||||||
|
CURLOPT_POSTFIELDS => $postFields,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 180,
|
||||||
|
CURLOPT_CONNECTTIMEOUT => 10,
|
||||||
|
CURLOPT_HTTPHEADER => [
|
||||||
|
'Accept: application/json'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$rawResponse = curl_exec($ch);
|
||||||
|
$curlError = curl_error($ch);
|
||||||
|
$httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($rawResponse === false) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Errore cURL verso Python: ' . $curlError
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$decoded = json_decode($rawResponse, true);
|
||||||
|
|
||||||
|
if (!is_array($decoded)) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Risposta Python non JSON valida.',
|
||||||
|
'http_code' => $httpCode,
|
||||||
|
'raw_response' => $rawResponse
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($httpCode < 200 || $httpCode >= 300) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => $decoded['message'] ?? ('Servizio Python HTTP ' . $httpCode),
|
||||||
|
'http_code' => $httpCode,
|
||||||
|
'python_response' => $decoded,
|
||||||
|
'raw_response' => $rawResponse
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$iduser = $iduserlogin ?? null;
|
||||||
|
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
if (!is_array($input)) {
|
||||||
|
jsonResponse([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Payload JSON non valido.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ids = $input['ids'] ?? [];
|
||||||
|
|
||||||
|
if (!is_array($ids) || count($ids) === 0) {
|
||||||
|
jsonResponse([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Nessun ID ricevuto.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ids = array_values(array_unique(array_map('intval', $ids)));
|
||||||
|
$ids = array_filter($ids, fn($id) => $id > 0);
|
||||||
|
|
||||||
|
if (count($ids) === 0) {
|
||||||
|
jsonResponse([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Nessun ID valido ricevuto.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pythonServiceUrl = 'http://127.0.0.1:5055/calculate';
|
||||||
|
|
||||||
|
$results = [];
|
||||||
|
|
||||||
|
foreach ($ids as $id) {
|
||||||
|
if ($iduser === null || $iduser === '') {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT *
|
||||||
|
FROM cad_area_jobs
|
||||||
|
WHERE id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT *
|
||||||
|
FROM cad_area_jobs
|
||||||
|
WHERE id = ?
|
||||||
|
AND iduser = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([$id, $iduser]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$job = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$job) {
|
||||||
|
$results[] = [
|
||||||
|
'id' => $id,
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Record non trovato.'
|
||||||
|
];
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasValidRoi($job)) {
|
||||||
|
$message = 'Prima devi definire la sezione da misurare tramite il pulsante Sezione.';
|
||||||
|
|
||||||
|
updateJobError($pdo, $id, $message, [
|
||||||
|
'success' => false,
|
||||||
|
'message' => $message,
|
||||||
|
'job_roi_debug' => [
|
||||||
|
'roi_x' => $job['roi_x'] ?? null,
|
||||||
|
'roi_y' => $job['roi_y'] ?? null,
|
||||||
|
'roi_width' => $job['roi_width'] ?? null,
|
||||||
|
'roi_height' => $job['roi_height'] ?? null,
|
||||||
|
'roi_page' => $job['roi_page'] ?? null,
|
||||||
|
'calculation_mode' => $job['calculation_mode'] ?? null
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$results[] = [
|
||||||
|
'id' => $id,
|
||||||
|
'success' => false,
|
||||||
|
'message' => $message
|
||||||
|
];
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateJobProcessing($pdo, $id);
|
||||||
|
|
||||||
|
$pythonResponse = callPythonAreaService($pythonServiceUrl, $job);
|
||||||
|
|
||||||
|
if (!($pythonResponse['success'] ?? false)) {
|
||||||
|
$message = $pythonResponse['message'] ?? 'Errore durante il calcolo Python.';
|
||||||
|
|
||||||
|
updateJobError($pdo, $id, $message, $pythonResponse);
|
||||||
|
|
||||||
|
$results[] = [
|
||||||
|
'id' => $id,
|
||||||
|
'success' => false,
|
||||||
|
'message' => $message,
|
||||||
|
'python_response' => $pythonResponse
|
||||||
|
];
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateJobCompleted($pdo, $id, $pythonResponse);
|
||||||
|
|
||||||
|
$results[] = [
|
||||||
|
'id' => $id,
|
||||||
|
'success' => true,
|
||||||
|
'message' => $pythonResponse['message'] ?? 'Area calcolata.',
|
||||||
|
'area_mm2' => $pythonResponse['area_mm2'] ?? null,
|
||||||
|
'area_cm2' => $pythonResponse['area_cm2'] ?? null
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonResponse([
|
||||||
|
'success' => true,
|
||||||
|
'results' => $results
|
||||||
|
]);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log('CAD area process error: ' . $e->getMessage());
|
||||||
|
|
||||||
|
jsonResponse([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,297 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
require_once(__DIR__ . '/include/headscript.php');
|
||||||
|
|
||||||
|
function jsonResponse(array $data): void
|
||||||
|
{
|
||||||
|
echo json_encode($data);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$iduser = $iduserlogin ?? null;
|
||||||
|
|
||||||
|
$rawInput = file_get_contents('php://input');
|
||||||
|
$input = json_decode($rawInput, true);
|
||||||
|
|
||||||
|
if (!is_array($input)) {
|
||||||
|
jsonResponse([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Payload JSON non valido.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = (int)($input['id'] ?? 0);
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
jsonResponse([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'ID non valido.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$areaMm2 = isset($input['area_mm2']) ? (float)$input['area_mm2'] : 0;
|
||||||
|
$areaCm2 = isset($input['area_cm2']) ? (float)$input['area_cm2'] : 0;
|
||||||
|
|
||||||
|
$outerAreaMm2 = isset($input['manual_outer_area_mm2']) ? (float)$input['manual_outer_area_mm2'] : $areaMm2;
|
||||||
|
$holesAreaMm2 = isset($input['manual_holes_area_mm2']) ? (float)$input['manual_holes_area_mm2'] : 0;
|
||||||
|
|
||||||
|
$widthMm = isset($input['width_mm']) ? (float)$input['width_mm'] : null;
|
||||||
|
$heightMm = isset($input['height_mm']) ? (float)$input['height_mm'] : null;
|
||||||
|
|
||||||
|
$calibrationPx = isset($input['manual_calibration_px']) ? (float)$input['manual_calibration_px'] : 0;
|
||||||
|
$calibrationMm = isset($input['manual_calibration_mm']) ? (float)$input['manual_calibration_mm'] : 0;
|
||||||
|
$mmPerPx = isset($input['manual_mm_per_px']) ? (float)$input['manual_mm_per_px'] : 0;
|
||||||
|
|
||||||
|
$outerPolygon = $input['manual_polygon'] ?? null;
|
||||||
|
$holes = $input['manual_holes'] ?? [];
|
||||||
|
$roi = $input['roi'] ?? null;
|
||||||
|
|
||||||
|
if ($areaMm2 <= 0) {
|
||||||
|
jsonResponse([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Area finale non valida.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($outerAreaMm2 <= 0) {
|
||||||
|
jsonResponse([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Area esterna non valida.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($holesAreaMm2 < 0) {
|
||||||
|
jsonResponse([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Area fori non valida.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($calibrationPx <= 0 || $calibrationMm <= 0 || $mmPerPx <= 0) {
|
||||||
|
jsonResponse([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Calibrazione non valida.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_array($outerPolygon) || count($outerPolygon) < 3) {
|
||||||
|
jsonResponse([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Poligono esterno non valido. Servono almeno 3 punti.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_array($holes)) {
|
||||||
|
$holes = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$manualPolygonJson = json_encode([
|
||||||
|
'outer_polygon' => $outerPolygon,
|
||||||
|
'holes' => $holes,
|
||||||
|
'roi' => $roi,
|
||||||
|
'calibration' => $input['calibration'] ?? null,
|
||||||
|
'canvas' => $input['canvas'] ?? null,
|
||||||
|
'areas' => [
|
||||||
|
'outer_area_mm2' => $outerAreaMm2,
|
||||||
|
'holes_area_mm2' => $holesAreaMm2,
|
||||||
|
'final_area_mm2' => $areaMm2,
|
||||||
|
'final_area_cm2' => $areaCm2
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$manualHolesJson = json_encode($holes);
|
||||||
|
|
||||||
|
$roiX = null;
|
||||||
|
$roiY = null;
|
||||||
|
$roiW = null;
|
||||||
|
$roiH = null;
|
||||||
|
|
||||||
|
if (is_array($roi)) {
|
||||||
|
$roiX = isset($roi['x']) ? (float)$roi['x'] : null;
|
||||||
|
$roiY = isset($roi['y']) ? (float)$roi['y'] : null;
|
||||||
|
$roiW = isset($roi['width']) ? (float)$roi['width'] : null;
|
||||||
|
$roiH = isset($roi['height']) ? (float)$roi['height'] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($iduser === null || $iduser === '') {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE cad_area_jobs
|
||||||
|
SET
|
||||||
|
roi_x = COALESCE(?, roi_x),
|
||||||
|
roi_y = COALESCE(?, roi_y),
|
||||||
|
roi_width = COALESCE(?, roi_width),
|
||||||
|
roi_height = COALESCE(?, roi_height),
|
||||||
|
roi_page = 1,
|
||||||
|
|
||||||
|
status = 'completed',
|
||||||
|
message = 'Area calcolata tramite tracciamento manuale calibrato.',
|
||||||
|
|
||||||
|
area_mm2 = ?,
|
||||||
|
area_cm2 = ?,
|
||||||
|
area_m2 = ?,
|
||||||
|
|
||||||
|
manual_area_mm2 = ?,
|
||||||
|
manual_area_cm2 = ?,
|
||||||
|
manual_outer_area_mm2 = ?,
|
||||||
|
manual_holes_area_mm2 = ?,
|
||||||
|
manual_width_mm = ?,
|
||||||
|
manual_height_mm = ?,
|
||||||
|
|
||||||
|
width_mm = ?,
|
||||||
|
height_mm = ?,
|
||||||
|
|
||||||
|
manual_calibration_px = ?,
|
||||||
|
manual_calibration_mm = ?,
|
||||||
|
manual_mm_per_px = ?,
|
||||||
|
manual_polygon_json = ?,
|
||||||
|
manual_holes_json = ?,
|
||||||
|
manual_status = 'completed',
|
||||||
|
|
||||||
|
scale_used = ?,
|
||||||
|
scale_detected = ?,
|
||||||
|
confidence = 'manual_validated',
|
||||||
|
strategy_used = 'manual_tracing_with_exclusions',
|
||||||
|
|
||||||
|
python_response = NULL,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = ?
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
$roiX,
|
||||||
|
$roiY,
|
||||||
|
$roiW,
|
||||||
|
$roiH,
|
||||||
|
|
||||||
|
$areaMm2,
|
||||||
|
$areaCm2,
|
||||||
|
$areaMm2 / 1000000,
|
||||||
|
|
||||||
|
$areaMm2,
|
||||||
|
$areaCm2,
|
||||||
|
$outerAreaMm2,
|
||||||
|
$holesAreaMm2,
|
||||||
|
$widthMm,
|
||||||
|
$heightMm,
|
||||||
|
|
||||||
|
$widthMm,
|
||||||
|
$heightMm,
|
||||||
|
|
||||||
|
$calibrationPx,
|
||||||
|
$calibrationMm,
|
||||||
|
$mmPerPx,
|
||||||
|
$manualPolygonJson,
|
||||||
|
$manualHolesJson,
|
||||||
|
|
||||||
|
$mmPerPx,
|
||||||
|
'manual',
|
||||||
|
|
||||||
|
$id
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE cad_area_jobs
|
||||||
|
SET
|
||||||
|
roi_x = COALESCE(?, roi_x),
|
||||||
|
roi_y = COALESCE(?, roi_y),
|
||||||
|
roi_width = COALESCE(?, roi_width),
|
||||||
|
roi_height = COALESCE(?, roi_height),
|
||||||
|
roi_page = 1,
|
||||||
|
|
||||||
|
status = 'completed',
|
||||||
|
message = 'Area calcolata tramite tracciamento manuale calibrato.',
|
||||||
|
|
||||||
|
area_mm2 = ?,
|
||||||
|
area_cm2 = ?,
|
||||||
|
area_m2 = ?,
|
||||||
|
|
||||||
|
manual_area_mm2 = ?,
|
||||||
|
manual_area_cm2 = ?,
|
||||||
|
manual_outer_area_mm2 = ?,
|
||||||
|
manual_holes_area_mm2 = ?,
|
||||||
|
manual_width_mm = ?,
|
||||||
|
manual_height_mm = ?,
|
||||||
|
|
||||||
|
width_mm = ?,
|
||||||
|
height_mm = ?,
|
||||||
|
|
||||||
|
manual_calibration_px = ?,
|
||||||
|
manual_calibration_mm = ?,
|
||||||
|
manual_mm_per_px = ?,
|
||||||
|
manual_polygon_json = ?,
|
||||||
|
manual_holes_json = ?,
|
||||||
|
manual_status = 'completed',
|
||||||
|
|
||||||
|
scale_used = ?,
|
||||||
|
scale_detected = ?,
|
||||||
|
confidence = 'manual_validated',
|
||||||
|
strategy_used = 'manual_tracing_with_exclusions',
|
||||||
|
|
||||||
|
python_response = NULL,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = ?
|
||||||
|
AND iduser = ?
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
$roiX,
|
||||||
|
$roiY,
|
||||||
|
$roiW,
|
||||||
|
$roiH,
|
||||||
|
|
||||||
|
$areaMm2,
|
||||||
|
$areaCm2,
|
||||||
|
$areaMm2 / 1000000,
|
||||||
|
|
||||||
|
$areaMm2,
|
||||||
|
$areaCm2,
|
||||||
|
$outerAreaMm2,
|
||||||
|
$holesAreaMm2,
|
||||||
|
$widthMm,
|
||||||
|
$heightMm,
|
||||||
|
|
||||||
|
$widthMm,
|
||||||
|
$heightMm,
|
||||||
|
|
||||||
|
$calibrationPx,
|
||||||
|
$calibrationMm,
|
||||||
|
$mmPerPx,
|
||||||
|
$manualPolygonJson,
|
||||||
|
$manualHolesJson,
|
||||||
|
|
||||||
|
$mmPerPx,
|
||||||
|
'manual',
|
||||||
|
|
||||||
|
$id,
|
||||||
|
$iduser
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stmt->rowCount() === 0) {
|
||||||
|
jsonResponse([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Nessun record aggiornato. Controlla ID o utente.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonResponse([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Area manuale salvata correttamente.',
|
||||||
|
'area_mm2' => $areaMm2,
|
||||||
|
'area_cm2' => $areaCm2,
|
||||||
|
'outer_area_mm2' => $outerAreaMm2,
|
||||||
|
'holes_area_mm2' => $holesAreaMm2
|
||||||
|
]);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log('CAD manual area save error: ' . $e->getMessage());
|
||||||
|
|
||||||
|
jsonResponse([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
require_once(__DIR__ . '/include/headscript.php');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$iduser = $iduserlogin ?? null;
|
||||||
|
|
||||||
|
$rawInput = file_get_contents('php://input');
|
||||||
|
$input = json_decode($rawInput, true);
|
||||||
|
|
||||||
|
if (!is_array($input)) {
|
||||||
|
throw new Exception('Payload JSON non valido.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = (int)($input['id'] ?? 0);
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
throw new Exception('ID non valido.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$roiX = isset($input['roi_x']) ? (float)$input['roi_x'] : null;
|
||||||
|
$roiY = isset($input['roi_y']) ? (float)$input['roi_y'] : null;
|
||||||
|
$roiW = isset($input['roi_width']) ? (float)$input['roi_width'] : null;
|
||||||
|
$roiH = isset($input['roi_height']) ? (float)$input['roi_height'] : null;
|
||||||
|
$roiPage = isset($input['roi_page']) ? (int)$input['roi_page'] : 1;
|
||||||
|
$mode = $input['calculation_mode'] ?? 'auto_roi';
|
||||||
|
|
||||||
|
if ($roiX === null || $roiY === null || $roiW === null || $roiH === null) {
|
||||||
|
throw new Exception('ROI non valida.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($roiW <= 0 || $roiH <= 0) {
|
||||||
|
throw new Exception('Dimensioni ROI non valide.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($roiX < 0 || $roiY < 0 || $roiX > 1 || $roiY > 1 || $roiW > 1 || $roiH > 1) {
|
||||||
|
throw new Exception('Coordinate ROI fuori scala.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowedModes = [
|
||||||
|
'auto_roi',
|
||||||
|
'stitch_contour',
|
||||||
|
'filled_union',
|
||||||
|
'closed_path'
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!in_array($mode, $allowedModes, true)) {
|
||||||
|
$mode = 'auto_roi';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($iduser === null || $iduser === '') {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE cad_area_jobs
|
||||||
|
SET
|
||||||
|
roi_x = ?,
|
||||||
|
roi_y = ?,
|
||||||
|
roi_width = ?,
|
||||||
|
roi_height = ?,
|
||||||
|
roi_page = ?,
|
||||||
|
calculation_mode = ?,
|
||||||
|
status = 'uploaded',
|
||||||
|
message = NULL
|
||||||
|
WHERE id = ?
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
$roiX,
|
||||||
|
$roiY,
|
||||||
|
$roiW,
|
||||||
|
$roiH,
|
||||||
|
$roiPage,
|
||||||
|
$mode,
|
||||||
|
$id
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE cad_area_jobs
|
||||||
|
SET
|
||||||
|
roi_x = ?,
|
||||||
|
roi_y = ?,
|
||||||
|
roi_width = ?,
|
||||||
|
roi_height = ?,
|
||||||
|
roi_page = ?,
|
||||||
|
calculation_mode = ?,
|
||||||
|
status = 'uploaded',
|
||||||
|
message = NULL
|
||||||
|
WHERE id = ?
|
||||||
|
AND iduser = ?
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
$roiX,
|
||||||
|
$roiY,
|
||||||
|
$roiW,
|
||||||
|
$roiH,
|
||||||
|
$roiPage,
|
||||||
|
$mode,
|
||||||
|
$id,
|
||||||
|
$iduser
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stmt->rowCount() === 0) {
|
||||||
|
throw new Exception('Nessun record aggiornato. Controlla ID o utente.');
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'ROI salvata correttamente.'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log('CAD area save ROI error: ' . $e->getMessage());
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
require_once(__DIR__ . '/include/headscript.php');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$iduser = $iduserlogin ?? null;
|
||||||
|
|
||||||
|
$uploadDir = __DIR__ . '/uploads/cad_area/originals/';
|
||||||
|
$publicBaseUrl = 'uploads/cad_area/originals/';
|
||||||
|
|
||||||
|
if (!is_dir($uploadDir)) {
|
||||||
|
mkdir($uploadDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($_FILES['pdf_files'])) {
|
||||||
|
throw new Exception('Nessun file ricevuto.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = $_FILES['pdf_files'];
|
||||||
|
$insertedIds = [];
|
||||||
|
|
||||||
|
for ($i = 0; $i < count($files['name']); $i++) {
|
||||||
|
if ($files['error'][$i] !== UPLOAD_ERR_OK) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$originalName = $files['name'][$i];
|
||||||
|
$tmpName = $files['tmp_name'][$i];
|
||||||
|
$size = (int)$files['size'][$i];
|
||||||
|
|
||||||
|
$extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
if ($extension !== 'pdf') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($size > 25 * 1024 * 1024) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$safeBaseName = preg_replace('/[^a-zA-Z0-9_\-]/', '_', pathinfo($originalName, PATHINFO_FILENAME));
|
||||||
|
$storedName = date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '_' . $safeBaseName . '.pdf';
|
||||||
|
|
||||||
|
$targetPath = $uploadDir . $storedName;
|
||||||
|
|
||||||
|
if (!move_uploaded_file($tmpName, $targetPath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$relativeUrl = $publicBaseUrl . $storedName;
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO cad_area_jobs
|
||||||
|
(
|
||||||
|
iduser,
|
||||||
|
original_filename,
|
||||||
|
stored_filename,
|
||||||
|
file_path,
|
||||||
|
file_url,
|
||||||
|
file_size,
|
||||||
|
status
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
:iduser,
|
||||||
|
:original_filename,
|
||||||
|
:stored_filename,
|
||||||
|
:file_path,
|
||||||
|
:file_url,
|
||||||
|
:file_size,
|
||||||
|
'uploaded'
|
||||||
|
)
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
':iduser' => $iduser,
|
||||||
|
':original_filename' => $originalName,
|
||||||
|
':stored_filename' => $storedName,
|
||||||
|
':file_path' => $targetPath,
|
||||||
|
':file_url' => $relativeUrl,
|
||||||
|
':file_size' => $size
|
||||||
|
]);
|
||||||
|
|
||||||
|
$insertedIds[] = (int)$pdo->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($insertedIds)) {
|
||||||
|
throw new Exception('Nessun PDF valido caricato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'ids' => $insertedIds
|
||||||
|
]);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log('CAD area upload error: ' . $e->getMessage());
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once "class/VisualLimsApiClient.class.php";
|
|
||||||
include('include/headscript.php');
|
|
||||||
|
|
||||||
$dbHandler = DBHandlerSelect::getInstance();
|
|
||||||
$pdo = $dbHandler->getConnection();
|
|
||||||
|
|
||||||
header("Content-Type: application/json");
|
|
||||||
|
|
||||||
// 🔹 Configura directory log (creala se non esiste)
|
|
||||||
$logDir = __DIR__ . '/logs/api/';
|
|
||||||
if (!is_dir($logDir)) {
|
|
||||||
mkdir($logDir, 0755, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🔹 Base URL API
|
|
||||||
$apiBaseUrl = 'https://93.43.5.102/limsapi/api/odata/';
|
|
||||||
|
|
||||||
// 🔹 Hardcoded values
|
|
||||||
$iddatadb = 846;
|
|
||||||
$commessaId = 533357;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 🔹 STEP 4: Fetch Field Values with Labels (usa dati reali per iddatadb=845)
|
|
||||||
$stmt = $pdo->prepare("
|
|
||||||
SELECT
|
|
||||||
idd.field_value,
|
|
||||||
m.field_label,
|
|
||||||
m.schema_id,
|
|
||||||
m.field_id
|
|
||||||
FROM
|
|
||||||
import_data_details as idd
|
|
||||||
JOIN template_mapping m ON idd.mapping_id = m.id
|
|
||||||
WHERE idd.id = :iddatadb
|
|
||||||
");
|
|
||||||
$stmt->execute(['iddatadb' => $iddatadb]);
|
|
||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
$fieldValues = [];
|
|
||||||
$valueMap = []; // Mappa per field_id -> valore
|
|
||||||
foreach ($rows as $row) {
|
|
||||||
$fieldValues[] = [
|
|
||||||
"IdCommesseCustomFields" => (int) $row['field_id'],
|
|
||||||
"Valore" => $row['field_value'],
|
|
||||||
"FieldLabel" => $row['field_label']
|
|
||||||
];
|
|
||||||
$valueMap[(int) $row['field_id']] = $row['field_value']; // Mappa per ID definizione
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logga i fieldValues in error_log
|
|
||||||
$logFieldValues = "FieldValues dal DB (iddatadb={$iddatadb}):\n" . json_encode($fieldValues, JSON_PRETTY_PRINT);
|
|
||||||
error_log($logFieldValues);
|
|
||||||
|
|
||||||
// 🔹 Initialize API client
|
|
||||||
$api = VisualLimsApiClient::getInstance();
|
|
||||||
|
|
||||||
// 🔹 STEP A: GET iniziale per CommesseCustomFields con espansione CustomField
|
|
||||||
$expand = "CommesseCustomFields(\$expand=CustomField)"; // Espansione come da istruzioni fornitore
|
|
||||||
$commessaWithFields = $api->get("CommessaWeb(" . $commessaId . ")?\$expand=" . $expand);
|
|
||||||
|
|
||||||
// 🔹 STEP B: Prepara payload PATCH (tutti i campi, sovrascrivi se match su CustomField.IdCustomField == field_id)
|
|
||||||
$commessaCustomFields = [];
|
|
||||||
foreach ($commessaWithFields["CommesseCustomFields"] as $customField) {
|
|
||||||
$definitionId = (int) ($customField["CustomField"]["IdCustomField"] ?? 0); // Usa IdCustomField dal CustomField
|
|
||||||
$currentValue = $customField["Valore"] ?? '';
|
|
||||||
$newValue = isset($valueMap[$definitionId]) ? $valueMap[$definitionId] : $currentValue;
|
|
||||||
|
|
||||||
$commessaCustomFields[] = [
|
|
||||||
"IdCommesseCustomFields" => (int) $customField["IdCommesseCustomFields"],
|
|
||||||
"Valore" => $newValue
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🔹 Unico file di log per tutto
|
|
||||||
$logFile = $logDir . "commessa_{$commessaId}_patch_and_get_" . time() . ".txt";
|
|
||||||
$logContent = "FieldValues dal DB (iddatadb={$iddatadb}):\n" . json_encode($fieldValues, JSON_PRETTY_PRINT) . "\n\n";
|
|
||||||
|
|
||||||
// Log curl-like per GET iniziale
|
|
||||||
$logContent .= "GET iniziale:\n" .
|
|
||||||
"curl --location --request GET '{$apiBaseUrl}CommessaWeb({$commessaId})?\$expand=CommesseCustomFields(\$expand=CustomField)' \\\n" .
|
|
||||||
"--header 'Authorization: Bearer ••••••'\n\n" .
|
|
||||||
"RESPONSE:\n" . json_encode($commessaWithFields, JSON_PRETTY_PRINT) . "\n\n---\n";
|
|
||||||
|
|
||||||
if (!empty($commessaCustomFields)) {
|
|
||||||
$updatePayload = ["CommesseCustomFields" => $commessaCustomFields];
|
|
||||||
|
|
||||||
// Log curl-like per PATCH
|
|
||||||
$jsonPayload = json_encode($updatePayload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
|
||||||
$logContent .= "PATCH:\n" .
|
|
||||||
"curl --location --request PATCH '{$apiBaseUrl}CommessaWeb({$commessaId})' \\\n" .
|
|
||||||
"--header 'Content-Type: application/json' \\\n" .
|
|
||||||
"--header 'Authorization: Bearer ••••••' \\\n" .
|
|
||||||
"--data '{$jsonPayload}'\n\n";
|
|
||||||
|
|
||||||
$patchResponse = $api->patch("CommessaWeb({$commessaId})", $updatePayload);
|
|
||||||
|
|
||||||
$logContent .= "PATCH RESPONSE:\n" . json_encode($patchResponse, JSON_PRETTY_PRINT) . "\n\n---\n";
|
|
||||||
} else {
|
|
||||||
$logContent .= "PATCH: Nessun campo custom da aggiornare\n\n---\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🔹 STEP C: GET di controllo post-PATCH
|
|
||||||
$commessaAfterPatch = $api->get("CommessaWeb(" . $commessaId . ")?\$expand=" . $expand);
|
|
||||||
|
|
||||||
// Log curl-like per GET di controllo
|
|
||||||
$logContent .= "GET di controllo:\n" .
|
|
||||||
"curl --location --request GET '{$apiBaseUrl}CommessaWeb({$commessaId})?\$expand=CommesseCustomFields(\$expand=CustomField)' \\\n" .
|
|
||||||
"--header 'Authorization: Bearer ••••••'\n\n" .
|
|
||||||
"RESPONSE:\n" . json_encode($commessaAfterPatch, JSON_PRETTY_PRINT);
|
|
||||||
|
|
||||||
// Salva log unico
|
|
||||||
file_put_contents($logFile, $logContent);
|
|
||||||
|
|
||||||
// 🔹 Output a schermo
|
|
||||||
echo json_encode([
|
|
||||||
"success" => true,
|
|
||||||
"message" => "PATCH eseguito su commessa {$commessaId} con dati da iddatadb {$iddatadb}",
|
|
||||||
"commessaAfterPatch" => $commessaAfterPatch,
|
|
||||||
"totalCustomFieldsUpdated" => count($commessaCustomFields),
|
|
||||||
"fieldValues" => $fieldValues,
|
|
||||||
"logFile" => $logFile
|
|
||||||
]);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
error_log("Patch Error: " . $e->getMessage() . "\nTrace: " . $e->getTraceAsString());
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
"success" => false,
|
|
||||||
"message" => "Patch failed: " . $e->getMessage(),
|
|
||||||
"logFile" => $logFile ?? 'Nessun log generato'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
@@ -29,7 +29,7 @@ class VisualLimsApiClient
|
|||||||
return self::$instance;
|
return self::$instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function authenticate()
|
private function authenticate($retryCount = 0, $maxRetries = 3)
|
||||||
{
|
{
|
||||||
$ch = curl_init("{$this->baseUrl}/api/authentication/authenticate");
|
$ch = curl_init("{$this->baseUrl}/api/authentication/authenticate");
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
@@ -45,16 +45,22 @@ class VisualLimsApiClient
|
|||||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
curl_setopt($ch, CURLOPT_VERBOSE, true);
|
curl_setopt($ch, CURLOPT_VERBOSE, true);
|
||||||
$log = fopen(__DIR__ . '/curl_auth_debug.log', 'w') ?: fopen('php://stderr', 'w');
|
$log = fopen(__DIR__ . '/curl_auth_debug.log', 'a') ?: fopen('php://stderr', 'w');
|
||||||
curl_setopt($ch, CURLOPT_STDERR, $log);
|
curl_setopt($ch, CURLOPT_STDERR, $log);
|
||||||
|
|
||||||
$response = curl_exec($ch);
|
$response = curl_exec($ch);
|
||||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
$curl_error = curl_error($ch);
|
$curl_error = curl_error($ch);
|
||||||
|
$log_message = date('Y-m-d H:i:s') . " - Auth attempt {$retryCount}: HTTP {$http_code}, Error: {$curl_error}, Response: " . substr($response, 0, 1000) . "\n";
|
||||||
|
fwrite($log, $log_message);
|
||||||
fclose($log);
|
fclose($log);
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
|
|
||||||
if ($response === false || $http_code != 200) {
|
if ($response === false || $http_code != 200) {
|
||||||
|
if ($http_code === 400 && strpos($response, 'Cannot persist the object') !== false && $retryCount < $maxRetries) {
|
||||||
|
usleep(500000); // Ritardo di 500ms
|
||||||
|
return $this->authenticate($retryCount + 1, $maxRetries); // Riprova
|
||||||
|
}
|
||||||
throw new Exception("Autenticazione fallita: HTTP {$http_code}, Errore cURL: {$curl_error}, Risposta: " . substr($response, 0, 1000));
|
throw new Exception("Autenticazione fallita: HTTP {$http_code}, Errore cURL: {$curl_error}, Risposta: " . substr($response, 0, 1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,5 +197,4 @@ class VisualLimsApiClient
|
|||||||
{
|
{
|
||||||
return $this->baseUrl;
|
return $this->baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,617 @@
|
|||||||
|
<?php
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
function jsonResponse(array $data): void
|
||||||
|
{
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
echo json_encode($data);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeNullableInt($value): ?int
|
||||||
|
{
|
||||||
|
return (isset($value) && $value !== '') ? (int)$value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeBoolValue($value): int
|
||||||
|
{
|
||||||
|
return ((string)$value === '0') ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanString(?string $value): string
|
||||||
|
{
|
||||||
|
return trim((string)$value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================
|
||||||
|
AJAX HANDLERS
|
||||||
|
========================================== */
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['ajax']) && $_POST['ajax'] == '1') {
|
||||||
|
$action = $_POST['action'] ?? '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($action === 'add') {
|
||||||
|
$functionName = cleanString($_POST['function_name'] ?? '');
|
||||||
|
$personFullName = cleanString($_POST['person_full_name'] ?? '');
|
||||||
|
$phone = cleanString($_POST['phone'] ?? '');
|
||||||
|
$email = cleanString($_POST['email'] ?? '');
|
||||||
|
$notes = cleanString($_POST['notes'] ?? '');
|
||||||
|
$sortOrder = normalizeNullableInt($_POST['sort_order'] ?? '0') ?? 0;
|
||||||
|
$isActive = normalizeBoolValue($_POST['is_active'] ?? '1');
|
||||||
|
|
||||||
|
if ($functionName === '') {
|
||||||
|
jsonResponse(['success' => false, 'message' => 'Il nome funzione è obbligatorio.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
jsonResponse(['success' => false, 'message' => 'Email non valida.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("\n INSERT INTO company_functions\n (function_name, person_full_name, phone, email, notes, sort_order, is_active, created_at, updated_at)\n VALUES\n (:function_name, :person_full_name, :phone, :email, :notes, :sort_order, :is_active, NOW(), NOW())\n ");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
'function_name' => $functionName,
|
||||||
|
'person_full_name' => $personFullName !== '' ? $personFullName : '',
|
||||||
|
'phone' => $phone !== '' ? $phone : null,
|
||||||
|
'email' => $email !== '' ? $email : null,
|
||||||
|
'notes' => $notes !== '' ? $notes : null,
|
||||||
|
'sort_order' => $sortOrder,
|
||||||
|
'is_active' => $isActive,
|
||||||
|
]);
|
||||||
|
|
||||||
|
jsonResponse(['success' => true, 'message' => 'Funzione salvata correttamente.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'edit') {
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
$functionName = cleanString($_POST['function_name'] ?? '');
|
||||||
|
$personFullName = cleanString($_POST['person_full_name'] ?? '');
|
||||||
|
$phone = cleanString($_POST['phone'] ?? '');
|
||||||
|
$email = cleanString($_POST['email'] ?? '');
|
||||||
|
$notes = cleanString($_POST['notes'] ?? '');
|
||||||
|
$sortOrder = normalizeNullableInt($_POST['sort_order'] ?? '0') ?? 0;
|
||||||
|
$isActive = normalizeBoolValue($_POST['is_active'] ?? '1');
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
jsonResponse(['success' => false, 'message' => 'ID funzione non valido.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($functionName === '') {
|
||||||
|
jsonResponse(['success' => false, 'message' => 'Il nome funzione è obbligatorio.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
jsonResponse(['success' => false, 'message' => 'Email non valida.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("\n UPDATE company_functions\n SET function_name = :function_name,\n person_full_name = :person_full_name,\n phone = :phone,\n email = :email,\n notes = :notes,\n sort_order = :sort_order,\n is_active = :is_active,\n updated_at = NOW()\n WHERE id = :id\n ");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
'function_name' => $functionName,
|
||||||
|
'person_full_name' => $personFullName !== '' ? $personFullName : '',
|
||||||
|
'phone' => $phone !== '' ? $phone : null,
|
||||||
|
'email' => $email !== '' ? $email : null,
|
||||||
|
'notes' => $notes !== '' ? $notes : null,
|
||||||
|
'sort_order' => $sortOrder,
|
||||||
|
'is_active' => $isActive,
|
||||||
|
'id' => $id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
jsonResponse(['success' => true, 'message' => 'Funzione aggiornata correttamente.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'delete') {
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
jsonResponse(['success' => false, 'message' => 'ID funzione non valido.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM company_functions WHERE id = :id");
|
||||||
|
$stmt->execute(['id' => $id]);
|
||||||
|
|
||||||
|
jsonResponse(['success' => true, 'message' => 'Funzione cancellata correttamente.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonResponse(['success' => false, 'message' => 'Azione non riconosciuta.']);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
jsonResponse(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================
|
||||||
|
PAGE DATA
|
||||||
|
========================================== */
|
||||||
|
$stmtFunctions = $pdo->query("\n SELECT id, function_name, person_full_name, phone, email, notes, sort_order, is_active, created_at, updated_at\n FROM company_functions\n ORDER BY is_active DESC, sort_order ASC, function_name ASC, person_full_name ASC\n");
|
||||||
|
$functions = $stmtFunctions->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||||
|
<?php include('cssinclude.php'); ?>
|
||||||
|
<title>Funzioni Aziendali - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||||
|
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css">
|
||||||
|
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
|
||||||
|
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-size: 1.05rem;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-dashboard {
|
||||||
|
background-color: #cfe3ff !important;
|
||||||
|
color: #1f2d3d !important;
|
||||||
|
border: 1px solid #bcd4f4 !important;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 10px 18px;
|
||||||
|
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-dashboard:hover {
|
||||||
|
background-color: #b9d3ff !important;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-main-action,
|
||||||
|
.btn-add {
|
||||||
|
background-color: #0d6efd;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-main-action:hover,
|
||||||
|
.btn-add:hover {
|
||||||
|
background-color: #0b5ed7;
|
||||||
|
color: #fff;
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table thead {
|
||||||
|
background-color: #cfe3ff;
|
||||||
|
color: #1f2d3d;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tabCompanyFunctions thead th {
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-name {
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.person-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-line {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notes-small {
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
max-width: 420px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-status {
|
||||||
|
padding: 0.25rem 0.65rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-status.active {
|
||||||
|
background-color: #d1fae5;
|
||||||
|
color: #065f46;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-status.inactive {
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
color: #94a3b8;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767.98px) {
|
||||||
|
.card-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start !important;
|
||||||
|
gap: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-dashboard,
|
||||||
|
.btn-main-action {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="wrapper" id="appWrapper">
|
||||||
|
<?php include('include/navbar.php'); ?>
|
||||||
|
<?php include('include/topbar.php'); ?>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
<div class="card p-3">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
|
||||||
|
<h5 class="mb-0">Funzioni Aziendali</h5>
|
||||||
|
<button type="button" class="btn back-dashboard" onclick="location.href='production_dashboard.php'">
|
||||||
|
↩️ Torna alla Dashboard
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap gap-2">
|
||||||
|
<div>
|
||||||
|
<h6 class="fw-semibold mb-1">Elenco Funzioni</h6>
|
||||||
|
<div class="text-muted small">Gestione di RSPP, medico del lavoro, RLS e altre funzioni aziendali.</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-main-action" data-bs-toggle="modal" data-bs-target="#companyFunctionModal" onclick="openCompanyFunctionModal()">
|
||||||
|
➕ Aggiungi Funzione
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="tabCompanyFunctions" class="table table-striped align-middle text-center" style="width:100%;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Funzione</th>
|
||||||
|
<th>Nominativo</th>
|
||||||
|
<th>Contatti</th>
|
||||||
|
<th>Note</th>
|
||||||
|
<th>Ordine</th>
|
||||||
|
<th>Stato</th>
|
||||||
|
<th>Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($functions as $row): ?>
|
||||||
|
<?php
|
||||||
|
$id = (int)$row['id'];
|
||||||
|
$functionName = (string)($row['function_name'] ?? '');
|
||||||
|
$personFullName = (string)($row['person_full_name'] ?? '');
|
||||||
|
$phone = (string)($row['phone'] ?? '');
|
||||||
|
$email = (string)($row['email'] ?? '');
|
||||||
|
$notes = (string)($row['notes'] ?? '');
|
||||||
|
$sortOrder = (int)($row['sort_order'] ?? 0);
|
||||||
|
$isActive = (int)($row['is_active'] ?? 1);
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td class="text-start">
|
||||||
|
<div class="function-name"><?= htmlspecialchars($functionName, ENT_QUOTES, 'UTF-8') ?></div>
|
||||||
|
</td>
|
||||||
|
<td class="text-start">
|
||||||
|
<?php if ($personFullName !== ''): ?>
|
||||||
|
<div class="person-name"><?= htmlspecialchars($personFullName, ENT_QUOTES, 'UTF-8') ?></div>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="empty-text">Da definire</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td class="text-start">
|
||||||
|
<?php if ($phone !== ''): ?>
|
||||||
|
<a class="contact-line" href="tel:<?= htmlspecialchars($phone, ENT_QUOTES, 'UTF-8') ?>">
|
||||||
|
📞 <?= htmlspecialchars($phone, ENT_QUOTES, 'UTF-8') ?>
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($email !== ''): ?>
|
||||||
|
<a class="contact-line" href="mailto:<?= htmlspecialchars($email, ENT_QUOTES, 'UTF-8') ?>">
|
||||||
|
✉️ <?= htmlspecialchars($email, ENT_QUOTES, 'UTF-8') ?>
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($phone === '' && $email === ''): ?>
|
||||||
|
<span class="empty-text">Nessun contatto</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td class="text-start">
|
||||||
|
<?php if ($notes !== ''): ?>
|
||||||
|
<div class="notes-small" title="<?= htmlspecialchars($notes, ENT_QUOTES, 'UTF-8') ?>">
|
||||||
|
<?= htmlspecialchars($notes, ENT_QUOTES, 'UTF-8') ?>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="empty-text">—</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td><?= $sortOrder ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if ($isActive === 1): ?>
|
||||||
|
<span class="badge-status active">Attiva</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge-status inactive">Non attiva</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-sm btn-outline-secondary edit-function mb-1"
|
||||||
|
data-id="<?= $id ?>"
|
||||||
|
data-function_name="<?= htmlspecialchars($functionName, ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
data-person_full_name="<?= htmlspecialchars($personFullName, ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
data-phone="<?= htmlspecialchars($phone, ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
data-email="<?= htmlspecialchars($email, ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
data-notes="<?= htmlspecialchars($notes, ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
data-sort_order="<?= $sortOrder ?>"
|
||||||
|
data-is_active="<?= $isActive ?>">
|
||||||
|
✏️ Modifica
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-sm btn-outline-danger delete-function mb-1"
|
||||||
|
data-id="<?= $id ?>"
|
||||||
|
data-name="<?= htmlspecialchars($functionName, ENT_QUOTES, 'UTF-8') ?>">
|
||||||
|
🗑️ Cancella
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MODALE ADD / EDIT FUNZIONE -->
|
||||||
|
<div class="modal fade" id="companyFunctionModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header" style="background-color:#cfe3ff;">
|
||||||
|
<h5 class="modal-title" id="companyFunctionModalTitle">Aggiungi Funzione</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="companyFunctionForm">
|
||||||
|
<input type="hidden" id="functionId">
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Nome funzione <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" id="functionName" placeholder="Es. RSPP, Medico del lavoro, RLS" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Nome e Cognome persona</label>
|
||||||
|
<input type="text" class="form-control" id="personFullName" placeholder="Es. Mario Rossi">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-semibold">Telefono</label>
|
||||||
|
<input type="text" class="form-control" id="phone" placeholder="Es. +39 333 1234567">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-semibold">Email</label>
|
||||||
|
<input type="email" class="form-control" id="email" placeholder="nome@azienda.it">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-semibold">Ordine</label>
|
||||||
|
<input type="number" class="form-control" id="sortOrder" value="0" min="0" step="1">
|
||||||
|
<small class="text-muted">Serve solo per ordinare la visualizzazione.</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-semibold">Stato</label>
|
||||||
|
<select class="form-select" id="isActive">
|
||||||
|
<option value="1">Attiva</option>
|
||||||
|
<option value="0">Non attiva</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Note</label>
|
||||||
|
<textarea class="form-control" id="notes" rows="3" placeholder="Note interne, riferimenti, disponibilità, ecc."></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<button type="submit" class="btn btn-add">💾 Salva</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function escapeHtml(value) {
|
||||||
|
return $('<div>').text(value || '').html();
|
||||||
|
}
|
||||||
|
|
||||||
|
function openCompanyFunctionModal() {
|
||||||
|
$('#functionId').val('');
|
||||||
|
$('#functionName').val('');
|
||||||
|
$('#personFullName').val('');
|
||||||
|
$('#phone').val('');
|
||||||
|
$('#email').val('');
|
||||||
|
$('#notes').val('');
|
||||||
|
$('#sortOrder').val('0');
|
||||||
|
$('#isActive').val('1');
|
||||||
|
$('#companyFunctionModalTitle').text('Aggiungi Funzione');
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#tabCompanyFunctions').DataTable({
|
||||||
|
order: [
|
||||||
|
[5, 'asc'],
|
||||||
|
[4, 'asc'],
|
||||||
|
[0, 'asc']
|
||||||
|
],
|
||||||
|
pageLength: 25,
|
||||||
|
language: {
|
||||||
|
url: 'https://cdn.datatables.net/plug-ins/1.13.6/i18n/it-IT.json',
|
||||||
|
emptyTable: 'Nessuna funzione presente'
|
||||||
|
},
|
||||||
|
columnDefs: [{
|
||||||
|
targets: -1,
|
||||||
|
orderable: false,
|
||||||
|
searchable: false
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.edit-function', function() {
|
||||||
|
const btn = $(this);
|
||||||
|
|
||||||
|
$('#functionId').val(btn.data('id'));
|
||||||
|
$('#functionName').val(btn.data('function_name'));
|
||||||
|
$('#personFullName').val(btn.data('person_full_name'));
|
||||||
|
$('#phone').val(btn.data('phone'));
|
||||||
|
$('#email').val(btn.data('email'));
|
||||||
|
$('#notes').val(btn.data('notes'));
|
||||||
|
$('#sortOrder').val(btn.data('sort_order'));
|
||||||
|
$('#isActive').val(String(btn.data('is_active')));
|
||||||
|
$('#companyFunctionModalTitle').text('Modifica Funzione');
|
||||||
|
|
||||||
|
$('#companyFunctionModal').modal('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#companyFunctionForm').on('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const id = $('#functionId').val();
|
||||||
|
const payload = new URLSearchParams();
|
||||||
|
payload.append('ajax', '1');
|
||||||
|
payload.append('action', id ? 'edit' : 'add');
|
||||||
|
payload.append('id', id);
|
||||||
|
payload.append('function_name', $('#functionName').val().trim());
|
||||||
|
payload.append('person_full_name', $('#personFullName').val().trim());
|
||||||
|
payload.append('phone', $('#phone').val().trim());
|
||||||
|
payload.append('email', $('#email').val().trim());
|
||||||
|
payload.append('notes', $('#notes').val().trim());
|
||||||
|
payload.append('sort_order', $('#sortOrder').val() || '0');
|
||||||
|
payload.append('is_active', $('#isActive').val());
|
||||||
|
|
||||||
|
fetch('', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
},
|
||||||
|
body: payload.toString()
|
||||||
|
})
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Salvato!',
|
||||||
|
text: data.message || 'Operazione completata.',
|
||||||
|
confirmButtonColor: '#3085d6'
|
||||||
|
}).then(() => location.reload());
|
||||||
|
} else {
|
||||||
|
Swal.fire('Errore', data.message || 'Impossibile salvare.', 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
Swal.fire('Errore', 'Errore di comunicazione.', 'error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.delete-function', function() {
|
||||||
|
const id = $(this).data('id');
|
||||||
|
const name = $(this).data('name');
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Confermi la cancellazione?',
|
||||||
|
text: name ? ('Funzione: ' + name) : 'La funzione verrà cancellata.',
|
||||||
|
icon: 'warning',
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonColor: '#d33',
|
||||||
|
cancelButtonColor: '#6c757d',
|
||||||
|
confirmButtonText: 'Sì, cancella',
|
||||||
|
cancelButtonText: 'Annulla'
|
||||||
|
}).then((result) => {
|
||||||
|
if (!result.isConfirmed) return;
|
||||||
|
|
||||||
|
const payload = new URLSearchParams();
|
||||||
|
payload.append('ajax', '1');
|
||||||
|
payload.append('action', 'delete');
|
||||||
|
payload.append('id', id);
|
||||||
|
|
||||||
|
fetch('', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
},
|
||||||
|
body: payload.toString()
|
||||||
|
})
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Cancellato!',
|
||||||
|
text: data.message || 'Funzione cancellata.',
|
||||||
|
confirmButtonColor: '#3085d6'
|
||||||
|
}).then(() => location.reload());
|
||||||
|
} else {
|
||||||
|
Swal.fire('Errore', data.message || 'Impossibile cancellare.', 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
Swal.fire('Errore', 'Errore di comunicazione.', 'error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
// Recupero foto esistenti
|
||||||
|
$photosSlots = [];
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT param_position, filename
|
||||||
|
FROM production_photos
|
||||||
|
WHERE production_id = ?
|
||||||
|
AND photo_type = 'parametri_macchina'
|
||||||
|
");
|
||||||
|
$stmt->execute([$r['id']]);
|
||||||
|
|
||||||
|
foreach ($stmt->fetchAll() as $p) {
|
||||||
|
$photosSlots[(int)$p['param_position']] = $p['filename'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slot dinamici per la linea
|
||||||
|
$slots = $r['param_slots'] ?? [];
|
||||||
|
|
||||||
|
if (empty($slots)) {
|
||||||
|
echo "<div style='color:#999; padding:8px;'>Nessun parametro configurato per questa linea.</div>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="param-grid" style="grid-template-columns: repeat(<?= count($slots) ?>, 1fr);">
|
||||||
|
<?php foreach ($slots as $slot): ?>
|
||||||
|
<?php
|
||||||
|
$pos = (int)$slot['position'];
|
||||||
|
$label = htmlspecialchars($slot['short_label']);
|
||||||
|
$img = $photosSlots[$pos] ?? null;
|
||||||
|
?>
|
||||||
|
<div class="param-slot" data-slot="<?= $pos ?>" data-production="<?= $r['id'] ?>">
|
||||||
|
<div class="thumb">
|
||||||
|
<?php if ($img): ?>
|
||||||
|
<img src="photos/<?= htmlspecialchars($img) ?>" class="thumb-img">
|
||||||
|
<?php else: ?>
|
||||||
|
<img src="assets/placeholder-photo.png" class="thumb-img">
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<div class="param-label"><?= $label ?></div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<div class="photo-actions" style="margin-top:12px; display:flex; gap:12px; width:100%; align-items:center;">
|
||||||
|
|
||||||
|
<!-- Lotto mescola -->
|
||||||
|
<button class="photo-btn"
|
||||||
|
data-type="lotto_mescola"
|
||||||
|
data-production="<?= $r['id'] ?>"
|
||||||
|
title="Foto Lotti Mescola">
|
||||||
|
<i class="bi bi-box-seam" style="font-size:1.8rem; color:#334155;"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Problemi -->
|
||||||
|
<button class="photo-btn"
|
||||||
|
data-type="problema"
|
||||||
|
data-production="<?= $r['id'] ?>"
|
||||||
|
title="Foto Problemi">
|
||||||
|
<i class="bi bi-exclamation-triangle" style="font-size:1.8rem; color:#b91c1c;"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- 🔥 SPAZIO PER PORTARE A DESTRA -->
|
||||||
|
<div style="flex-grow:1;"></div>
|
||||||
|
|
||||||
|
<!-- 🔵 ICONA QUALITÀ -->
|
||||||
|
<button class="photo-btn qc-btn"
|
||||||
|
data-production="<?= $r['id'] ?>"
|
||||||
|
title="Controlli Qualità">
|
||||||
|
<i class="bi bi-droplet-half"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<div id="photoModal" class="custom-modal">
|
||||||
|
<div class="modal-content final-wide" style="max-width:800px; position:relative;">
|
||||||
|
|
||||||
|
<!-- X per chiudere -->
|
||||||
|
<button id="photoModalCloseX"
|
||||||
|
style="position:absolute; top:10px; right:15px; font-size:1.7rem; background:none; border:none; cursor:pointer;">
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<h3 id="photoModalTitle">Carica Foto</h3>
|
||||||
|
<p id="photoModalSubtitle"></p>
|
||||||
|
|
||||||
|
<div id="photoMessageSuccess"
|
||||||
|
style="display:none; margin-bottom:10px; padding:8px 12px; border-radius:8px; background:#dcfce7; color:#166534; font-weight:500; text-align:left;">
|
||||||
|
✅ Foto caricata correttamente.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="photoMessageError"
|
||||||
|
style="display:none; margin-bottom:10px; padding:8px 12px; border-radius:8px; background:#fee2e2; color:#b91c1c; font-weight:500; text-align:left;">
|
||||||
|
⚠️ Errore durante il caricamento.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="photoForm" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" name="production_id" id="photoProductionId">
|
||||||
|
<input type="hidden" name="photo_type" id="photoType">
|
||||||
|
<input type="hidden" name="param_position" id="photoParamPosition">
|
||||||
|
|
||||||
|
<!-- ✅ input file DENTRO al form -->
|
||||||
|
<input type="file" name="photo" id="photoInput"
|
||||||
|
accept="image/*;capture=camera"
|
||||||
|
style="display:none;">
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label><strong>Carica o scatta una foto:</strong></label>
|
||||||
|
<button type="button" class="modal-btn" id="choosePhotoBtn">Scegli foto</button>
|
||||||
|
<span id="selectedPhotoName" style="margin-left:8px; font-size:0.85rem; color:#64748b;"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 🔥 loader -->
|
||||||
|
<div id="photoLoading"
|
||||||
|
style="display:none; margin-top:10px; font-size:0.9rem; color:#64748b;">
|
||||||
|
⏳ Caricamento in corso...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="singlePhotoInfo"
|
||||||
|
style="display:none; margin-top:10px; font-size:0.9rem; color:#b91c1c; text-align:left;">
|
||||||
|
Per questo parametro è già presente una foto.
|
||||||
|
Se vuoi sostituirla, chiedi all'amministratore di cancellare quella esistente.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="modal-buttons" style="margin-top:1.5rem;">
|
||||||
|
<button type="button" id="photoCancel" class="modal-btn modal-cancel">Annulla</button>
|
||||||
|
<button type="submit" class="modal-btn modal-confirm">Carica</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr style="margin:1.5rem 0;">
|
||||||
|
|
||||||
|
<h4 style="font-size:1rem; margin-bottom:0.7rem;">Foto già registrate per questa tipologia</h4>
|
||||||
|
|
||||||
|
<div id="photoGallery" class="photo-gallery"
|
||||||
|
style="display:flex; flex-wrap:wrap; gap:10px; max-height:220px; overflow-y:auto; padding:4px 0;">
|
||||||
|
<!-- thumbnails caricati via JS -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Questo file può essere vuoto o contenere logica PHP aggiuntiva se necessario
|
|
||||||
?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="it">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Autenticazione VisualLims</title>
|
|
||||||
<!-- Includi Select2 CSS -->
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 20px auto;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#authButton {
|
|
||||||
padding: 10px 20px;
|
|
||||||
background-color: #007bff;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
#authButton:hover {
|
|
||||||
background-color: #0056b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
#result {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
#schemiResult {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container {
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h1>Autenticazione VisualLims</h1>
|
|
||||||
<button id="authButton">Autentica</button>
|
|
||||||
<div id="result"></div>
|
|
||||||
|
|
||||||
<!-- Tendina per i clienti -->
|
|
||||||
<h3>Seleziona un cliente:</h3>
|
|
||||||
<select id="clientiSelect" style="width: 100%;">
|
|
||||||
<option value="">Seleziona un cliente...</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<!-- Area per mostrare gli schemi -->
|
|
||||||
<div id="schemiResult"></div>
|
|
||||||
|
|
||||||
<!-- Includi jQuery (necessario per Select2) -->
|
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
|
||||||
<!-- Includi Select2 JS -->
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Inizializza Select2 sulla tendina
|
|
||||||
$(document).ready(function() {
|
|
||||||
$('#clientiSelect').select2({
|
|
||||||
placeholder: "Cerca un cliente...",
|
|
||||||
allowClear: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Carica i clienti al caricamento della pagina
|
|
||||||
loadClienti();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Autenticazione
|
|
||||||
document.getElementById('authButton').addEventListener('click', async () => {
|
|
||||||
const resultDiv = document.getElementById('result');
|
|
||||||
resultDiv.textContent = 'Autenticazione in corso...';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('auth_proxy.php', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Errore HTTP! Stato: ${response.status}, Dettagli: ${data.error || 'Nessun dettaglio disponibile'}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof data === 'string' && data.length > 0) {
|
|
||||||
resultDiv.textContent = `Token: ${data}`;
|
|
||||||
} else if (data && data.token) {
|
|
||||||
resultDiv.textContent = `Token: ${data.token}`;
|
|
||||||
} else {
|
|
||||||
resultDiv.textContent = `Autenticazione fallita: Nessun token ricevuto. Dettagli: ${JSON.stringify(data)}`;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
resultDiv.textContent = `Errore: ${error.message}`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Funzione per caricare i clienti nella tendina
|
|
||||||
async function loadClienti() {
|
|
||||||
const resultDiv = document.getElementById('result');
|
|
||||||
resultDiv.textContent = 'Caricamento clienti...';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('get_clienti.php', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({})
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(data.error || `Errore HTTP: ${response.status}, Dettagli: ${JSON.stringify(data)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.value && Array.isArray(data.value)) {
|
|
||||||
const select = document.getElementById('clientiSelect');
|
|
||||||
data.value.forEach(c => {
|
|
||||||
const nome = c.Nominativo || 'Nome non disponibile';
|
|
||||||
const id = c.IdCliente || 'ID non disponibile';
|
|
||||||
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
|
|
||||||
select.add(option);
|
|
||||||
});
|
|
||||||
resultDiv.textContent = 'Clienti caricati con successo.';
|
|
||||||
} else {
|
|
||||||
resultDiv.textContent = 'Nessun cliente trovato o formato dati non valido.';
|
|
||||||
console.log('Risposta API:', data);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
resultDiv.textContent = 'Errore: ' + err.message;
|
|
||||||
console.error('Dettagli errore:', err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evento per gestire la selezione di un cliente e recuperare gli schemi
|
|
||||||
$('#clientiSelect').on('select2:select', async function(e) {
|
|
||||||
const clienteId = e.target.value; // Oppure: $(this).val()
|
|
||||||
const schemiDiv = document.getElementById('schemiResult'); // Correzione del nome variabile
|
|
||||||
|
|
||||||
// Log per debug
|
|
||||||
console.log('Cliente selezionato:', clienteId);
|
|
||||||
|
|
||||||
if (!clienteId) {
|
|
||||||
schemiDiv.textContent = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
schemiDiv.textContent = 'Caricamento schemi...';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('get_schemi.php', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
clienteId
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(data.error || `Errore HTTP: ${response.status}, Dettagli: ${JSON.stringify(data)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.SchemiAbilitati && Array.isArray(data.SchemiAbilitati)) {
|
|
||||||
let html = '<h3>Schemi Abilitati:</h3><ul>';
|
|
||||||
data.SchemiAbilitati.forEach(s => {
|
|
||||||
const nomeSchema = s.NomeSchema || s.Descrizione || 'Schema non specificato';
|
|
||||||
html += `<li>${nomeSchema}</li>`;
|
|
||||||
});
|
|
||||||
html += '</ul>';
|
|
||||||
schemiDiv.innerHTML = html;
|
|
||||||
} else {
|
|
||||||
schemiDiv.textContent = 'Nessuno schema trovato per questo cliente.';
|
|
||||||
console.log('Risposta Schemi:', data);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
schemiDiv.textContent = 'Errore: ' + err.message;
|
|
||||||
console.error('Dettagli errore:', err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Gestisci la deselezione (opzionale)
|
|
||||||
$('#clientiSelect').on('select2:unselect', function(e) {
|
|
||||||
const schemiDiv = document.getElementById('schemiResult'); // Correzione del nome variabile
|
|
||||||
schemiDiv.textContent = '';
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,355 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Formazione — Email reminder cron script
|
||||||
|
* Run daily: 0 7 * * * php /var/www/html/public/userarea/cron/send_training_reminders.php
|
||||||
|
*
|
||||||
|
* Sends "due_soon" emails when next_due_date is within the reminder window
|
||||||
|
* (override reminder_days > topic default > 30 days).
|
||||||
|
* Sends "expired" emails when next_due_date is in the past.
|
||||||
|
* Skips rows with next_due_date IS NULL (one-off trainings).
|
||||||
|
* Skips already-sent notifications (same training + addressee + next_due_date).
|
||||||
|
* Recipients: the employee (employees.email or auth_users.email) + every HR user
|
||||||
|
* with role Admin / Superuser / employee-hr / manager.
|
||||||
|
*
|
||||||
|
* Optional CLI flags:
|
||||||
|
* --dry-run — log only, no SMTP, no DB write
|
||||||
|
* --only-email=foo@bar — restrict to a single addressee (for testing)
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../class/db-functions.php';
|
||||||
|
require_once __DIR__ . '/../../../vendor/autoload.php';
|
||||||
|
|
||||||
|
use Dotenv\Dotenv;
|
||||||
|
use PHPMailer\PHPMailer\PHPMailer;
|
||||||
|
use PHPMailer\PHPMailer\Exception;
|
||||||
|
|
||||||
|
$dotenv = Dotenv::createImmutable(__DIR__ . '/../../../');
|
||||||
|
$dotenv->load();
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$today = date('Y-m-d');
|
||||||
|
$appUrl = rtrim($_ENV['APP_URL'] ?? 'http://localhost:8001', '/');
|
||||||
|
|
||||||
|
/* CLI flags */
|
||||||
|
$dryRun = false;
|
||||||
|
$onlyEmail = null;
|
||||||
|
foreach (array_slice($argv ?? [], 1) as $a) {
|
||||||
|
if ($a === '--dry-run' || $a === '-n') {
|
||||||
|
$dryRun = true;
|
||||||
|
} elseif (strpos($a, '--only-email=') === 0) {
|
||||||
|
$onlyEmail = substr($a, strlen('--only-email='));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sent = 0;
|
||||||
|
$skipped = 0;
|
||||||
|
$errors = 0;
|
||||||
|
|
||||||
|
/* Candidate trainings (with optional override reminder + topic default).
|
||||||
|
Only the most recent record per (employee, topic) — older history rows skipped. */
|
||||||
|
$stmt = $pdo->query("
|
||||||
|
SELECT et.id, et.employee_id, et.completed_date, et.next_due_date,
|
||||||
|
et.reminder_days, et.delivered_by,
|
||||||
|
tt.name AS topic_name, tt.default_reminder_days AS topic_default_rem,
|
||||||
|
e.first_name, e.last_name, e.employee_code,
|
||||||
|
e.email AS employee_email_direct,
|
||||||
|
au.email AS employee_email_auth
|
||||||
|
FROM employee_trainings et
|
||||||
|
JOIN training_topics tt ON tt.id = et.training_topic_id
|
||||||
|
JOIN employees e ON e.id = et.employee_id
|
||||||
|
LEFT JOIN auth_users au ON au.id = e.auth_user_id
|
||||||
|
WHERE et.next_due_date IS NOT NULL
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM employee_trainings et2
|
||||||
|
WHERE et2.employee_id = et.employee_id
|
||||||
|
AND et2.training_topic_id = et.training_topic_id
|
||||||
|
AND (et2.completed_date > et.completed_date
|
||||||
|
OR (et2.completed_date = et.completed_date AND et2.id > et.id))
|
||||||
|
)
|
||||||
|
");
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (empty($rows)) {
|
||||||
|
echo date('Y-m-d H:i:s') . " — Nessuna formazione da notificare.\n";
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HR addressees (one query, reused per training) */
|
||||||
|
$hrUsers = $pdo->query("
|
||||||
|
SELECT u.id, u.email, TRIM(CONCAT(COALESCE(u.first_name,''),' ',COALESCE(u.last_name,''))) AS name
|
||||||
|
FROM auth_users u
|
||||||
|
JOIN auth_roles r ON r.id = u.role_id
|
||||||
|
WHERE r.name IN ('Admin','Superuser','employee-hr','manager')
|
||||||
|
AND u.email IS NOT NULL AND u.email <> ''
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$checkSent = $pdo->prepare("
|
||||||
|
SELECT COUNT(*) FROM training_reminder_log
|
||||||
|
WHERE training_id = ? AND addressee_email = ? AND next_due_date = ?
|
||||||
|
");
|
||||||
|
$insertLog = $pdo->prepare("
|
||||||
|
INSERT INTO training_reminder_log
|
||||||
|
(training_id, addressee_email, next_due_date, status_at_send, sent_at)
|
||||||
|
VALUES (?, ?, ?, ?, NOW())
|
||||||
|
");
|
||||||
|
|
||||||
|
foreach ($rows as $r) {
|
||||||
|
$rem = $r['reminder_days'] !== null
|
||||||
|
? (int)$r['reminder_days']
|
||||||
|
: ($r['topic_default_rem'] !== null ? (int)$r['topic_default_rem'] : 30);
|
||||||
|
$isOverdue = $r['next_due_date'] < $today;
|
||||||
|
$daysLeft = (int)((strtotime($r['next_due_date']) - strtotime($today)) / 86400);
|
||||||
|
|
||||||
|
if (!$isOverdue && $daysLeft > $rem) {
|
||||||
|
continue; // not yet in the reminder window
|
||||||
|
}
|
||||||
|
$type = $isOverdue ? 'expired' : 'update_to_be_scheduled';
|
||||||
|
|
||||||
|
$employeeFullName = trim($r['first_name'] . ' ' . $r['last_name']);
|
||||||
|
$employeeEmail = !empty($r['employee_email_direct'])
|
||||||
|
? $r['employee_email_direct']
|
||||||
|
: (!empty($r['employee_email_auth']) ? $r['employee_email_auth'] : null);
|
||||||
|
|
||||||
|
/* Collect addressees (employee + HR), deduplicated by lowercased email */
|
||||||
|
$recipients = [];
|
||||||
|
if ($employeeEmail) {
|
||||||
|
$key = strtolower(trim($employeeEmail));
|
||||||
|
$recipients[$key] = ['email' => $employeeEmail, 'name' => $employeeFullName, 'is_hr' => false];
|
||||||
|
}
|
||||||
|
foreach ($hrUsers as $hr) {
|
||||||
|
$key = strtolower(trim((string)$hr['email']));
|
||||||
|
if ($key === '' || isset($recipients[$key])) continue;
|
||||||
|
$recipients[$key] = ['email' => $hr['email'], 'name' => trim((string)$hr['name']), 'is_hr' => true];
|
||||||
|
}
|
||||||
|
if (empty($recipients)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($recipients as $email => $rec) {
|
||||||
|
if ($onlyEmail !== null && strcasecmp($rec['email'], $onlyEmail) !== 0) continue;
|
||||||
|
|
||||||
|
$checkSent->execute([$r['id'], $rec['email'], $r['next_due_date']]);
|
||||||
|
if ($checkSent->fetchColumn() > 0) {
|
||||||
|
$skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$mail = new PHPMailer(true);
|
||||||
|
|
||||||
|
// SMTP config from .env
|
||||||
|
$mailer = $_ENV['MAIL_MAILER'] ?? 'mail';
|
||||||
|
if ($mailer === 'smtp') {
|
||||||
|
$mail->isSMTP();
|
||||||
|
$mail->Host = $_ENV['MAIL_HOST'] ?? 'localhost';
|
||||||
|
$mail->Port = (int)($_ENV['MAIL_PORT'] ?? 587);
|
||||||
|
if (!empty($_ENV['MAIL_USERNAME']) && $_ENV['MAIL_USERNAME'] !== 'null') {
|
||||||
|
$mail->SMTPAuth = true;
|
||||||
|
$mail->Username = $_ENV['MAIL_USERNAME'];
|
||||||
|
$mail->Password = $_ENV['MAIL_PASSWORD'] ?? '';
|
||||||
|
}
|
||||||
|
$enc = $_ENV['MAIL_ENCRYPTION'] ?? '';
|
||||||
|
if ($enc && $enc !== 'null') {
|
||||||
|
$mail->SMTPSecure = $enc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$mail->CharSet = 'UTF-8';
|
||||||
|
$mail->setFrom(
|
||||||
|
$_ENV['MAIL_FROM_ADDRESS'] ?? 'noreply@zibogomma.it',
|
||||||
|
$_ENV['MAIL_FROM_NAME'] ?? 'Formazione ZIBOGOMMA'
|
||||||
|
);
|
||||||
|
$mail->addAddress($rec['email'], $rec['name'] ?: $rec['email']);
|
||||||
|
|
||||||
|
$profileUrl = $appUrl . '/userarea/employee-profile.php?id=' . (int)$r['employee_id'] . '#tab-training';
|
||||||
|
$topicText = $r['topic_name'] . ' — ' . $employeeFullName
|
||||||
|
. (!empty($r['employee_code']) ? ' (' . $r['employee_code'] . ')' : '');
|
||||||
|
|
||||||
|
if ($isOverdue) {
|
||||||
|
$mail->Subject = '⚠️ Formazione scaduta: ' . $r['topic_name'];
|
||||||
|
$mail->Body = buildHtml(
|
||||||
|
'Formazione scaduta',
|
||||||
|
$topicText,
|
||||||
|
'Completata il <strong>' . date('d/m/Y', strtotime($r['completed_date'])) . '</strong>. '
|
||||||
|
. 'Il prossimo aggiornamento era previsto per <strong>' . date('d/m/Y', strtotime($r['next_due_date'])) . '</strong>'
|
||||||
|
. ' (scaduta da <strong>' . abs($daysLeft) . ' giorni</strong>).',
|
||||||
|
'#dc3545',
|
||||||
|
$profileUrl,
|
||||||
|
$rec['is_hr']
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$mail->Subject = '📚 Formazione in scadenza: ' . $r['topic_name'];
|
||||||
|
$daysText = $daysLeft === 0 ? 'oggi' : 'tra <strong>' . $daysLeft . ' giorni</strong>';
|
||||||
|
$mail->Body = buildHtml(
|
||||||
|
'Formazione in scadenza',
|
||||||
|
$topicText,
|
||||||
|
'Completata il <strong>' . date('d/m/Y', strtotime($r['completed_date'])) . '</strong>. '
|
||||||
|
. 'Prossimo aggiornamento previsto per <strong>' . date('d/m/Y', strtotime($r['next_due_date'])) . '</strong>'
|
||||||
|
. ' (' . $daysText . ').',
|
||||||
|
'#e8930c',
|
||||||
|
$profileUrl,
|
||||||
|
$rec['is_hr']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$mail->isHTML(true);
|
||||||
|
$mail->AltBody = strip_tags(str_replace('<br>', "\n", $mail->Body));
|
||||||
|
|
||||||
|
if ($dryRun) {
|
||||||
|
echo date('H:i:s') . " ◌ DRY {$type} → {$rec['email']} — {$r['topic_name']}\n";
|
||||||
|
$sent++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mail->send();
|
||||||
|
$insertLog->execute([$r['id'], $rec['email'], $r['next_due_date'], $type]);
|
||||||
|
$sent++;
|
||||||
|
echo date('H:i:s') . " ✓ {$type} → {$rec['email']} — {$r['topic_name']}\n";
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$errors++;
|
||||||
|
echo date('H:i:s') . " ✗ Errore {$rec['email']}: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
NOT-PRESENT reminders — mandatory topics with no record for an employee.
|
||||||
|
Notify HR only.
|
||||||
|
De-dup by (employee_id, training_topic_id, addressee_email).
|
||||||
|
============================================================================ */
|
||||||
|
$missingStmt = $pdo->query("
|
||||||
|
SELECT e.id AS employee_id, e.first_name, e.last_name, e.employee_code,
|
||||||
|
tt.id AS topic_id, tt.name AS topic_name
|
||||||
|
FROM employees e
|
||||||
|
CROSS JOIN training_topics tt
|
||||||
|
WHERE tt.is_active = 1 AND tt.is_mandatory = 1
|
||||||
|
AND (e.status IS NULL OR e.status = 'active')
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM employee_trainings et
|
||||||
|
WHERE et.employee_id = e.id AND et.training_topic_id = tt.id
|
||||||
|
)
|
||||||
|
ORDER BY e.last_name, e.first_name, tt.name
|
||||||
|
");
|
||||||
|
$missingRows = $missingStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$checkMissingSent = $pdo->prepare("
|
||||||
|
SELECT COUNT(*) FROM training_reminder_log
|
||||||
|
WHERE employee_id = ? AND training_topic_id = ? AND addressee_email = ?
|
||||||
|
AND status_at_send = 'not_present'
|
||||||
|
");
|
||||||
|
$insertMissingLog = $pdo->prepare("
|
||||||
|
INSERT INTO training_reminder_log
|
||||||
|
(training_id, employee_id, training_topic_id, addressee_email, next_due_date, status_at_send, sent_at)
|
||||||
|
VALUES (NULL, ?, ?, ?, NULL, 'not_present', NOW())
|
||||||
|
");
|
||||||
|
|
||||||
|
foreach ($missingRows as $m) {
|
||||||
|
$employeeFullName = trim($m['first_name'] . ' ' . $m['last_name']);
|
||||||
|
|
||||||
|
foreach ($hrUsers as $hr) {
|
||||||
|
$email = trim((string)$hr['email']);
|
||||||
|
if ($email === '') continue;
|
||||||
|
if ($onlyEmail !== null && strcasecmp($email, $onlyEmail) !== 0) continue;
|
||||||
|
|
||||||
|
$checkMissingSent->execute([$m['employee_id'], $m['topic_id'], $email]);
|
||||||
|
if ($checkMissingSent->fetchColumn() > 0) {
|
||||||
|
$skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$mail = new PHPMailer(true);
|
||||||
|
$mailer = $_ENV['MAIL_MAILER'] ?? 'mail';
|
||||||
|
if ($mailer === 'smtp') {
|
||||||
|
$mail->isSMTP();
|
||||||
|
$mail->Host = $_ENV['MAIL_HOST'] ?? 'localhost';
|
||||||
|
$mail->Port = (int)($_ENV['MAIL_PORT'] ?? 587);
|
||||||
|
if (!empty($_ENV['MAIL_USERNAME']) && $_ENV['MAIL_USERNAME'] !== 'null') {
|
||||||
|
$mail->SMTPAuth = true;
|
||||||
|
$mail->Username = $_ENV['MAIL_USERNAME'];
|
||||||
|
$mail->Password = $_ENV['MAIL_PASSWORD'] ?? '';
|
||||||
|
}
|
||||||
|
$enc = $_ENV['MAIL_ENCRYPTION'] ?? '';
|
||||||
|
if ($enc && $enc !== 'null') {
|
||||||
|
$mail->SMTPSecure = $enc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$mail->CharSet = 'UTF-8';
|
||||||
|
$mail->setFrom(
|
||||||
|
$_ENV['MAIL_FROM_ADDRESS'] ?? 'noreply@zibogomma.it',
|
||||||
|
$_ENV['MAIL_FROM_NAME'] ?? 'Formazione ZIBOGOMMA'
|
||||||
|
);
|
||||||
|
$mail->addAddress($email, trim((string)$hr['name']) ?: $email);
|
||||||
|
|
||||||
|
$profileUrl = $appUrl . '/userarea/employee-profile.php?id=' . (int)$m['employee_id'] . '#tab-training';
|
||||||
|
$topicText = $m['topic_name'] . ' — ' . $employeeFullName
|
||||||
|
. (!empty($m['employee_code']) ? ' (' . $m['employee_code'] . ')' : '');
|
||||||
|
|
||||||
|
$mail->Subject = '🔔 Formazione obbligatoria non presente: ' . $m['topic_name'];
|
||||||
|
$mail->Body = buildHtml(
|
||||||
|
'Formazione obbligatoria non presente',
|
||||||
|
$topicText,
|
||||||
|
'Il dipendente <strong>' . htmlspecialchars($employeeFullName) . '</strong> non ha nessuna registrazione per il corso obbligatorio <strong>' . htmlspecialchars($m['topic_name']) . '</strong>. Programma la prima erogazione.',
|
||||||
|
'#6b7280',
|
||||||
|
$profileUrl,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
$mail->isHTML(true);
|
||||||
|
$mail->AltBody = strip_tags(str_replace('<br>', "\n", $mail->Body));
|
||||||
|
|
||||||
|
if ($dryRun) {
|
||||||
|
echo date('H:i:s') . " ◌ DRY not_present → {$email} — {$m['topic_name']} / {$employeeFullName}\n";
|
||||||
|
$sent++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mail->send();
|
||||||
|
$insertMissingLog->execute([$m['employee_id'], $m['topic_id'], $email]);
|
||||||
|
$sent++;
|
||||||
|
echo date('H:i:s') . " ✓ not_present → {$email} — {$m['topic_name']} / {$employeeFullName}\n";
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$errors++;
|
||||||
|
echo date('H:i:s') . " ✗ Errore {$email}: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n" . date('Y-m-d H:i:s') . " — Completato. Inviate: {$sent}, Saltate: {$skipped}, Errori: {$errors}\n";
|
||||||
|
|
||||||
|
// --- HTML email template ---
|
||||||
|
function buildHtml(string $title, string $topic, string $message, string $accentColor, string $url, bool $isForHr): string
|
||||||
|
{
|
||||||
|
$greeting = $isForHr
|
||||||
|
? 'Una formazione richiede attenzione.'
|
||||||
|
: 'Una delle tue formazioni richiede attenzione.';
|
||||||
|
return '
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head><meta charset="UTF-8"></head>
|
||||||
|
<body style="margin:0;padding:0;background:#f4f6f9;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif">
|
||||||
|
<table width="100%" cellpadding="0" cellspacing="0" style="padding:30px 0">
|
||||||
|
<tr><td align="center">
|
||||||
|
<table width="560" cellpadding="0" cellspacing="0" style="background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.06)">
|
||||||
|
<tr><td style="background:' . $accentColor . ';padding:20px 30px">
|
||||||
|
<h1 style="margin:0;color:#fff;font-size:18px">' . htmlspecialchars($title) . '</h1>
|
||||||
|
</td></tr>
|
||||||
|
<tr><td style="padding:30px">
|
||||||
|
<p style="margin:0 0 12px;color:#444;font-size:14px">' . htmlspecialchars($greeting) . '</p>
|
||||||
|
<h2 style="margin:0 0 15px;color:#2c3e6b;font-size:16px">' . htmlspecialchars($topic) . '</h2>
|
||||||
|
<p style="margin:0 0 20px;color:#444;font-size:14px;line-height:1.6">' . $message . '</p>
|
||||||
|
<a href="' . htmlspecialchars($url) . '" style="display:inline-block;background:#5a8fd8;color:#fff;padding:10px 24px;border-radius:6px;text-decoration:none;font-weight:600;font-size:14px">Apri profilo</a>
|
||||||
|
</td></tr>
|
||||||
|
<tr><td style="padding:15px 30px;background:#f8f9fb;border-top:1px solid #eee">
|
||||||
|
<p style="margin:0;color:#999;font-size:11px">ZIBOGOMMA — Formazione</p>
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>';
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
require_once dirname(__DIR__, 3) . '/vendor/autoload.php'; // Risale a root/vendor/
|
||||||
|
require_once dirname(__FILE__) . '/../class/VisualLimsApiClient.class.php'; // In root/public/userarea/class/
|
||||||
|
|
||||||
|
use Dotenv\Dotenv;
|
||||||
|
|
||||||
|
// Debug: Log the path where we expect the .env file
|
||||||
|
$envPath = dirname(__DIR__, 3);
|
||||||
|
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . ' - Expected .env path: ' . $envPath . PHP_EOL, FILE_APPEND);
|
||||||
|
|
||||||
|
// Carica il file .env dalla root del progetto
|
||||||
|
try {
|
||||||
|
$dotenv = Dotenv::createImmutable($envPath);
|
||||||
|
$dotenv->load();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - Errore caricamento .env: ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recupera le variabili d'ambiente
|
||||||
|
$dbHost = $_ENV['DB_HOST'];
|
||||||
|
$dbName = $_ENV['DB_DATABASE'];
|
||||||
|
$dbUser = $_ENV['DB_USERNAME'];
|
||||||
|
$dbPass = $_ENV['DB_PASSWORD'];
|
||||||
|
$dbPrefix = $_ENV['DB_PREFIX'];
|
||||||
|
|
||||||
|
// Debug: Log database connection details (excluding password)
|
||||||
|
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . " - DB Connection: host=$dbHost, dbname=$dbName, user=$dbUser, prefix=$dbPrefix" . PHP_EOL, FILE_APPEND);
|
||||||
|
|
||||||
|
// Connessione al database MySQL
|
||||||
|
try {
|
||||||
|
$pdo = new PDO("mysql:host=$dbHost;dbname=$dbName;charset=utf8mb4", $dbUser, $dbPass);
|
||||||
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - Errore connessione DB: ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
|
||||||
|
// Endpoint per recuperare le Matrici
|
||||||
|
$endpoint = 'Matrice';
|
||||||
|
|
||||||
|
// (Opzionale) aggiungi parametri
|
||||||
|
$options = []; // es. ['$top' => 100]
|
||||||
|
|
||||||
|
// Debug: salva URL usato
|
||||||
|
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
|
||||||
|
$query = http_build_query($options);
|
||||||
|
$full_url = $base_url . $endpoint . ($query ? '?' . $query : '');
|
||||||
|
file_put_contents(__DIR__ . '/last_url.txt', $full_url . PHP_EOL, FILE_APPEND);
|
||||||
|
|
||||||
|
// Chiamata API
|
||||||
|
$data = $api->get($endpoint, $options);
|
||||||
|
|
||||||
|
// Debug: Log the API response size
|
||||||
|
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . ' - API response received, value count: ' . (isset($data['value']) ? count($data['value']) : 0) . PHP_EOL, FILE_APPEND);
|
||||||
|
|
||||||
|
// Salva il JSON in locale (opzionale, per debug)
|
||||||
|
file_put_contents(__DIR__ . '/matrici_response.json', json_encode($data, JSON_PRETTY_PRINT));
|
||||||
|
|
||||||
|
// Svuota la tabella (dump rapido)
|
||||||
|
$pdo->exec("TRUNCATE TABLE {$dbPrefix}matrici");
|
||||||
|
|
||||||
|
// Debug: Log after truncate
|
||||||
|
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . ' - Table truncated: ' . $dbPrefix . 'matrici' . PHP_EOL, FILE_APPEND);
|
||||||
|
|
||||||
|
// Prepara l'insert
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO {$dbPrefix}matrici (
|
||||||
|
IdMatrice, NomeMatriceTraduzione, DescrizioneTraduzione, MacroMatrice, NomeMatrice, Descrizione
|
||||||
|
) VALUES (
|
||||||
|
:IdMatrice, :NomeMatriceTraduzione, :DescrizioneTraduzione, :MacroMatrice, :NomeMatrice, :Descrizione
|
||||||
|
)
|
||||||
|
");
|
||||||
|
|
||||||
|
// Inserisci i dati
|
||||||
|
$insertedRows = 0;
|
||||||
|
if (isset($data['value']) && is_array($data['value'])) {
|
||||||
|
foreach ($data['value'] as $item) {
|
||||||
|
$stmt->execute([
|
||||||
|
':IdMatrice' => $item['IdMatrice'],
|
||||||
|
':NomeMatriceTraduzione' => $item['NomeMatriceTraduzione'],
|
||||||
|
':DescrizioneTraduzione' => $item['DescrizioneTraduzione'] ?? null,
|
||||||
|
':MacroMatrice' => $item['MacroMatrice'] ?? null,
|
||||||
|
':NomeMatrice' => $item['NomeMatrice'],
|
||||||
|
':Descrizione' => $item['Descrizione'] ?? null,
|
||||||
|
]);
|
||||||
|
$insertedRows++;
|
||||||
|
// Debug: Log each inserted row
|
||||||
|
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . ' - Inserted row with IdMatrice: ' . $item['IdMatrice'] . PHP_EOL, FILE_APPEND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log successo
|
||||||
|
file_put_contents(__DIR__ . '/success_log.txt', date('Y-m-d H:i:s') . ' - Aggiornamento completato: ' . $insertedRows . ' record inseriti.' . PHP_EOL, FILE_APPEND);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(0); // Esci con successo per cron
|
||||||
@@ -13,10 +13,145 @@
|
|||||||
<link href="assets/css/app.css" rel="stylesheet">
|
<link href="assets/css/app.css" rel="stylesheet">
|
||||||
<link href="assets/css/icons.css" rel="stylesheet">
|
<link href="assets/css/icons.css" rel="stylesheet">
|
||||||
<!-- Theme Style CSS -->
|
<!-- Theme Style CSS -->
|
||||||
<link rel="stylesheet" href="assets/css/dark-theme.css" />
|
<!-- <link rel="stylesheet" href="assets/css/dark-theme.css" />
|
||||||
<link rel="stylesheet" href="assets/css/semi-dark.css" />
|
<link rel="stylesheet" href="assets/css/semi-dark.css" />
|
||||||
<link rel="stylesheet" href="assets/css/header-colors.css" />
|
<link rel="stylesheet" href="assets/css/header-colors.css" /> -->
|
||||||
<!-- Font awesome -->
|
<!-- Font awesome -->
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
|
||||||
<!-- SweetAlert2 -->
|
<!-- SweetAlert2 -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||||
|
<!-- Override temporaneo per tema chiaro pastello -->
|
||||||
|
<style>
|
||||||
|
/* ======= HEADER (barra superiore) ======= */
|
||||||
|
.top-header {
|
||||||
|
background-color: #a4c4fdff !important;
|
||||||
|
/* azzurro pastello */
|
||||||
|
color: #1f2d3d !important;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-header .nav-link,
|
||||||
|
.top-header .navbar-brand,
|
||||||
|
.top-header .navbar-nav .nav-item a,
|
||||||
|
.top-header .dropdown-item {
|
||||||
|
color: #1a2a3b !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-header .nav-link:hover,
|
||||||
|
.top-header .navbar-nav .nav-item a:hover,
|
||||||
|
.top-header .dropdown-item:hover {
|
||||||
|
color: #0b5ed7 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======= SIDEBAR (menu laterale) ======= */
|
||||||
|
.sidebar-wrapper {
|
||||||
|
background-color: #dfebffff !important;
|
||||||
|
/* grigio chiaro con tono bluastro */
|
||||||
|
color: #2b3e50 !important;
|
||||||
|
box-shadow: inset -2px 0 5px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-wrapper .metismenu a {
|
||||||
|
color: #2b3e50 !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
border-radius: 6px !important;
|
||||||
|
padding: 10px 14px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-wrapper .metismenu a:hover,
|
||||||
|
.sidebar-wrapper .metismenu .mm-active>a {
|
||||||
|
background-color: #d6e4ff !important;
|
||||||
|
/* azzurro più deciso al passaggio */
|
||||||
|
color: #0d6efd !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======= CORPO PAGINA ======= */
|
||||||
|
body {
|
||||||
|
background-color: #f8fafc !important;
|
||||||
|
color: #2b3e50 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card,
|
||||||
|
.form-container,
|
||||||
|
.table-container {
|
||||||
|
background-color: #ffffff !important;
|
||||||
|
border: 1px solid #e2e8f0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pulsanti principali coerenti */
|
||||||
|
.btn-primary,
|
||||||
|
.btn-submit {
|
||||||
|
background-color: #0d6efd !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover,
|
||||||
|
.btn-submit:hover {
|
||||||
|
background-color: #0b5ed7 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navbar icone (se presenti) */
|
||||||
|
.top-header i {
|
||||||
|
color: #1f2d3d !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* ======= TOPBAR (barra superiore, con menu e profilo utente) ======= */
|
||||||
|
.topbar {
|
||||||
|
background-color: #a4c4fdff !important;
|
||||||
|
/* azzurro pastello */
|
||||||
|
color: #1f2d3d !important;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar .nav-link,
|
||||||
|
.topbar .navbar-nav .nav-item a,
|
||||||
|
.topbar .navbar-brand,
|
||||||
|
.topbar .dropdown-item {
|
||||||
|
color: #1f2d3d !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar .nav-link:hover,
|
||||||
|
.topbar .navbar-nav .nav-item a:hover,
|
||||||
|
.topbar .dropdown-item:hover {
|
||||||
|
color: #0d6efd !important;
|
||||||
|
background-color: rgba(255, 255, 255, 0.3) !important;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar .user-box .user-info p {
|
||||||
|
color: #1f2d3d !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar .dropdown-menu {
|
||||||
|
border-radius: 8px !important;
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======= SIDEBAR HEADER (logo + titolo) ======= */
|
||||||
|
.sidebar-header {
|
||||||
|
background-color: #a4c4fdff !important;
|
||||||
|
/* leggermente più scuro per separare dal resto */
|
||||||
|
color: #1f2d3d !important;
|
||||||
|
border-bottom: 1px solid #a8c5f9 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header .logo-text {
|
||||||
|
color: #1f2d3d !important;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header .toggle-icon i {
|
||||||
|
color: #1f2d3d !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======= ICONCINE GENERICHE ======= */
|
||||||
|
.bx,
|
||||||
|
.fa {
|
||||||
|
color: #1f2d3d !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
include('include/headscript.php');
|
||||||
|
require_once(__DIR__ . '/class/db-functions.php');
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID non valido.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = (int)$_GET['id'];
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM production_lines WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Linea non trovata o già eliminata.']);
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Errore: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
include('../class/db-functions.php');
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!isset($_GET['id'])) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID non fornito.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = intval($_GET['id']);
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
// Cancella eventuali associazioni
|
||||||
|
$pdo->prepare("DELETE FROM matrice_lines WHERE idmatrice = ?")->execute([$id]);
|
||||||
|
$pdo->prepare("DELETE FROM matrice_mescole WHERE idmatrice = ?")->execute([$id]);
|
||||||
|
|
||||||
|
// Cancella la matrice
|
||||||
|
$pdo->prepare("DELETE FROM matrice WHERE id = ?")->execute([$id]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Matrice eliminata con successo.']);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Errore: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
$response = [
|
||||||
|
'success' => false,
|
||||||
|
'message' => ''
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
throw new Exception('Metodo non consentito');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_POST['id']) || !is_numeric($_POST['id'])) {
|
||||||
|
throw new Exception('ID allegato non valido');
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = (int)$_POST['id'];
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT id, file_path FROM matrice_attachments WHERE id = :id LIMIT 1");
|
||||||
|
$stmt->execute([':id' => $id]);
|
||||||
|
$attachment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$attachment) {
|
||||||
|
throw new Exception('Allegato non trovato');
|
||||||
|
}
|
||||||
|
|
||||||
|
$filePathRelative = ltrim((string)$attachment['file_path'], '/\\');
|
||||||
|
$filePathAbsolute = __DIR__ . '/' . $filePathRelative;
|
||||||
|
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
$deleteStmt = $pdo->prepare("DELETE FROM matrice_attachments WHERE id = :id");
|
||||||
|
$deleteStmt->execute([':id' => $id]);
|
||||||
|
|
||||||
|
if ($deleteStmt->rowCount() <= 0) {
|
||||||
|
throw new Exception('Impossibile eliminare il record allegato');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($filePathRelative) && file_exists($filePathAbsolute) && is_file($filePathAbsolute)) {
|
||||||
|
@unlink($filePathAbsolute);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
$response['success'] = true;
|
||||||
|
$response['message'] = 'Allegato eliminato correttamente';
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
if (isset($pdo) && $pdo instanceof PDO && $pdo->inTransaction()) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
}
|
||||||
|
$response['message'] = $e->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($response, JSON_UNESCAPED_UNICODE);
|
||||||
|
exit;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
include('include/headscript.php');
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$id = isset($_POST['id']) ? (int)$_POST['id'] : 0;
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Invalid id']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM mescole_supplier_lots WHERE id = ?");
|
||||||
|
$ok = $stmt->execute([$id]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => (bool)$ok]);
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
include('include/headscript.php');
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$id = isset($_POST['id']) ? (int)$_POST['id'] : 0;
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Invalid id']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM packaging_stock_lots WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Invalid parameter id.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
// Check existence
|
||||||
|
$stmt = $pdo->prepare("SELECT id FROM production_line_params WHERE id = :id");
|
||||||
|
$stmt->execute([':id' => $id]);
|
||||||
|
|
||||||
|
if (!$stmt->fetchColumn()) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Parameter not found.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
$del = $pdo->prepare("DELETE FROM production_line_params WHERE id = :id");
|
||||||
|
$del->execute([':id' => $id]);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Parameter deleted successfully.'
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Server error: ' . $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<?php
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
|
|
||||||
include('include/headscript.php');
|
|
||||||
|
|
||||||
$dbHandler = DBHandlerSelect::getInstance();
|
|
||||||
$pdo = $dbHandler->getConnection();
|
|
||||||
|
|
||||||
$data = json_decode(file_get_contents('php://input'), true);
|
|
||||||
|
|
||||||
$partId = $data['part_id'] ?? null;
|
|
||||||
|
|
||||||
if (!$partId) {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'ID parte mancante']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$stmt = $pdo->prepare("DELETE FROM identification_parts WHERE id = :part_id");
|
|
||||||
$stmt->execute([':part_id' => $partId]);
|
|
||||||
$rowCount = $stmt->rowCount();
|
|
||||||
if ($rowCount > 0) {
|
|
||||||
echo json_encode(['success' => true, 'message' => 'Parte eliminata con successo']);
|
|
||||||
} else {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Nessuna parte trovata con ID ' . $partId]);
|
|
||||||
}
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Errore nell\'eliminazione: ' . $e->getMessage()]);
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
include('include/headscript.php');
|
|
||||||
|
|
||||||
$dbHandler = DBHandlerSelect::getInstance();
|
|
||||||
$pdo = $dbHandler->getConnection();
|
|
||||||
|
|
||||||
$data = json_decode(file_get_contents('php://input'), true);
|
|
||||||
|
|
||||||
$partId = $data['part_id'] ?? null;
|
|
||||||
|
|
||||||
if (!$partId) {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'ID parte mancante']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$stmt = $pdo->prepare("DELETE FROM identification_parts WHERE id = :part_id");
|
|
||||||
$stmt->execute([':part_id' => $partId]);
|
|
||||||
$rowCount = $stmt->rowCount();
|
|
||||||
if ($rowCount > 0) {
|
|
||||||
echo json_encode(['success' => true, 'message' => 'Parte eliminata con successo']);
|
|
||||||
} else {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Nessuna parte trovata con ID ' . $partId]);
|
|
||||||
}
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Errore nell\'eliminazione: ' . $e->getMessage()]);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/class/db-functions.php';
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$id = (int)($_GET['id'] ?? 0);
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID non valido.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Verifica se è usata in production_pauses
|
||||||
|
$check = $pdo->prepare("SELECT COUNT(*) FROM production_pauses WHERE reason_id = :id");
|
||||||
|
$check->execute([':id' => $id]);
|
||||||
|
$used = $check->fetchColumn();
|
||||||
|
|
||||||
|
if ($used > 0) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Impossibile eliminare: la causa è utilizzata in una o più pause.'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Elimina
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM pause_reasons WHERE id = :id");
|
||||||
|
$stmt->execute([':id' => $id]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
<?php
|
|
||||||
// delete_photo.php
|
|
||||||
include('include/headscript.php');
|
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['photo_id'])) {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Richiesta non valida']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$photoId = intval($_POST['photo_id']);
|
|
||||||
|
|
||||||
$db = DBHandlerSelect::getInstance();
|
|
||||||
$pdo = $db->getConnection();
|
|
||||||
|
|
||||||
// Recupera il percorso del file
|
|
||||||
$stmt = $pdo->prepare("SELECT file_path FROM datadb_photos WHERE id = ?");
|
|
||||||
$stmt->execute([$photoId]);
|
|
||||||
$photo = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if (!$photo) {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Foto non trovata']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Elimina il file dal server
|
|
||||||
$filePath = '../photostrf/' . $photo['file_path'];
|
|
||||||
if (file_exists($filePath)) {
|
|
||||||
unlink($filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Elimina il record dal database
|
|
||||||
$stmt = $pdo->prepare("DELETE FROM datadb_photos WHERE id = ?");
|
|
||||||
$stmt->execute([$photoId]);
|
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'message' => 'Foto eliminata con successo']);
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
include('include/headscript.php');
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
if (empty($_POST['id'])) {
|
||||||
|
echo json_encode(['success' => false, 'msg' => 'ID mancante']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM productiondata WHERE id = :id");
|
||||||
|
$stmt->execute(['id' => $_POST['id']]);
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'msg' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Enable errors for debugging
|
|
||||||
ini_set('display_errors', 1);
|
|
||||||
ini_set('display_startup_errors', 1);
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
ini_set('log_errors', 1);
|
|
||||||
ini_set('error_log', __DIR__ . '/delete_record_debug.log');
|
|
||||||
|
|
||||||
// Log iniziale
|
|
||||||
error_log("Inizio cancellazione record alle " . date('Y-m-d H:i:s'));
|
|
||||||
|
|
||||||
// Includi il file di configurazione del database
|
|
||||||
include('include/headscript.php');
|
|
||||||
|
|
||||||
// Ricevi l'ID dalla richiesta POST
|
|
||||||
$input = json_decode(file_get_contents('php://input'), true);
|
|
||||||
$iddatadb = isset($input['id']) ? intval($input['id']) : 0;
|
|
||||||
|
|
||||||
if ($iddatadb <= 0) {
|
|
||||||
http_response_code(400);
|
|
||||||
echo json_encode(['success' => false, 'message' => 'ID non valido']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connessione al database
|
|
||||||
try {
|
|
||||||
$db = DBHandlerSelect::getInstance();
|
|
||||||
$pdo = $db->getConnection();
|
|
||||||
} catch (Exception $e) {
|
|
||||||
http_response_code(500);
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Errore di connessione al database: ' . $e->getMessage()]);
|
|
||||||
error_log("Errore di connessione al database: " . $e->getMessage());
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inizia una transazione
|
|
||||||
$pdo->beginTransaction();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Elimina i dettagli associati dal tavolo import_data_details
|
|
||||||
$stmt = $pdo->prepare("DELETE FROM import_data_details WHERE id = ?");
|
|
||||||
$stmt->execute([$iddatadb]);
|
|
||||||
|
|
||||||
// Elimina il record principale dal tavolo datadb
|
|
||||||
$stmt = $pdo->prepare("DELETE FROM datadb WHERE iddatadb = ?");
|
|
||||||
$stmt->execute([$iddatadb]);
|
|
||||||
|
|
||||||
// Verifica se il record è stato eliminato
|
|
||||||
if ($stmt->rowCount() > 0) {
|
|
||||||
$pdo->commit();
|
|
||||||
echo json_encode(['success' => true, 'message' => 'Record eliminato con successo']);
|
|
||||||
error_log("Record con iddatadb=$iddatadb eliminato con successo");
|
|
||||||
} else {
|
|
||||||
$pdo->rollBack();
|
|
||||||
http_response_code(404);
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Record non trovato']);
|
|
||||||
error_log("Record con iddatadb=$iddatadb non trovato");
|
|
||||||
}
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$pdo->rollBack();
|
|
||||||
http_response_code(500);
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Errore durante la cancellazione: ' . $e->getMessage()]);
|
|
||||||
error_log("Errore durante la cancellazione del record con iddatadb=$iddatadb: " . $e->getMessage());
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$id = intval($_GET['id'] ?? 0);
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID non valido']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifica se status è usato in produzione
|
||||||
|
$check = $pdo->prepare("SELECT COUNT(*) FROM productiondata WHERE id_status = :id");
|
||||||
|
$check->execute([':id' => $id]);
|
||||||
|
$count = $check->fetchColumn();
|
||||||
|
|
||||||
|
if ($count > 0) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => "Impossibile eliminare: lo status è utilizzato in $count record di produzione."
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eliminazione
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM production_status WHERE id = :id");
|
||||||
|
$stmt->execute([':id' => $id]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Abilita la gestione degli errori
|
|
||||||
ini_set('display_errors', 1);
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
|
|
||||||
// Includi la connessione al database
|
|
||||||
require_once 'class/db-functions.php';
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Controlla se è stato passato un ID valido
|
|
||||||
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
|
|
||||||
throw new Exception('Invalid ID');
|
|
||||||
}
|
|
||||||
|
|
||||||
$id = intval($_GET['id']); // Sanifica l'ID
|
|
||||||
|
|
||||||
// Connessione al database
|
|
||||||
$db = DBHandlerSelect::getInstance();
|
|
||||||
$pdo = $db->getConnection();
|
|
||||||
|
|
||||||
if (!$pdo) {
|
|
||||||
throw new Exception('Database connection failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verifica se l'ID esiste
|
|
||||||
$stmtCheck = $pdo->prepare("SELECT id FROM excel_templates WHERE id = ?");
|
|
||||||
$stmtCheck->execute([$id]);
|
|
||||||
if ($stmtCheck->rowCount() === 0) {
|
|
||||||
throw new Exception('Template not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Esegui l'eliminazione
|
|
||||||
$stmt = $pdo->prepare("DELETE FROM excel_templates WHERE id = ?");
|
|
||||||
$stmt->execute([$id]);
|
|
||||||
|
|
||||||
if ($stmt->rowCount() > 0) {
|
|
||||||
$message = "Template deleted successfully!";
|
|
||||||
$status = "success";
|
|
||||||
} else {
|
|
||||||
throw new Exception('Failed to delete template');
|
|
||||||
}
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$message = $e->getMessage();
|
|
||||||
$status = "error";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reindirizza con un messaggio
|
|
||||||
header("Location: templates_dashboard.php?status=$status&message=" . urlencode($message));
|
|
||||||
exit;
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
require_once 'include/headscript.php';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
$name = trim($_POST['name'] ?? '');
|
||||||
|
$tool_type = trim($_POST['tool_type'] ?? '');
|
||||||
|
$description = trim($_POST['description'] ?? '');
|
||||||
|
$is_active = isset($_POST['is_active']) ? (int)$_POST['is_active'] : 1;
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Invalid ID.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($name === '') {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Name is required.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "UPDATE production_tools
|
||||||
|
SET name = :name,
|
||||||
|
tool_type = :tool_type,
|
||||||
|
description = :description,
|
||||||
|
is_active = :is_active
|
||||||
|
WHERE id = :id";
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute([
|
||||||
|
'name' => $name,
|
||||||
|
'tool_type' => $tool_type ?: null,
|
||||||
|
'description' => $description ?: null,
|
||||||
|
'is_active' => $is_active,
|
||||||
|
'id' => $id
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,798 @@
|
|||||||
|
<?php
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
/* ==========================================
|
||||||
|
AJAX HANDLERS
|
||||||
|
========================================== */
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['ajax']) && $_POST['ajax'] == '1') {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$action = $_POST['action'] ?? '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($action === 'add') {
|
||||||
|
$name = trim($_POST['name'] ?? '');
|
||||||
|
$code = trim($_POST['code'] ?? '');
|
||||||
|
$description = trim($_POST['description'] ?? '');
|
||||||
|
$color = trim($_POST['color'] ?? '#6c757d');
|
||||||
|
$sort_order = isset($_POST['sort_order']) && $_POST['sort_order'] !== '' ? (int)$_POST['sort_order'] : 999;
|
||||||
|
$is_active = isset($_POST['is_active']) ? (int)$_POST['is_active'] : 1;
|
||||||
|
|
||||||
|
if ($name === '') {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Department name is required.'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($code === '') {
|
||||||
|
$code = strtoupper(str_replace(' ', '_', $name));
|
||||||
|
$code = preg_replace('/[^A-Z0-9_]/', '', $code);
|
||||||
|
} else {
|
||||||
|
$code = strtoupper($code);
|
||||||
|
$code = preg_replace('/[^A-Z0-9_]/', '', $code);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!preg_match('/^#[0-9A-Fa-f]{6}$/', $color)) {
|
||||||
|
$color = '#6c757d';
|
||||||
|
}
|
||||||
|
|
||||||
|
$is_active = $is_active === 1 ? 1 : 0;
|
||||||
|
|
||||||
|
$check = $pdo->prepare("
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM departments
|
||||||
|
WHERE name = :name OR code = :code
|
||||||
|
");
|
||||||
|
$check->execute([
|
||||||
|
'name' => $name,
|
||||||
|
'code' => $code
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ((int)$check->fetchColumn() > 0) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'A department with the same name or code already exists.'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "INSERT INTO departments
|
||||||
|
(name, code, description, color, sort_order, is_active, created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
(:name, :code, :description, :color, :sort_order, :is_active, NOW(), NOW())";
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute([
|
||||||
|
'name' => $name,
|
||||||
|
'code' => $code !== '' ? $code : null,
|
||||||
|
'description' => $description !== '' ? $description : null,
|
||||||
|
'color' => $color,
|
||||||
|
'sort_order' => $sort_order,
|
||||||
|
'is_active' => $is_active
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'edit') {
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
$name = trim($_POST['name'] ?? '');
|
||||||
|
$code = trim($_POST['code'] ?? '');
|
||||||
|
$description = trim($_POST['description'] ?? '');
|
||||||
|
$color = trim($_POST['color'] ?? '#6c757d');
|
||||||
|
$sort_order = isset($_POST['sort_order']) && $_POST['sort_order'] !== '' ? (int)$_POST['sort_order'] : 999;
|
||||||
|
$is_active = isset($_POST['is_active']) ? (int)$_POST['is_active'] : 1;
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Invalid department ID.'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($name === '') {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Department name is required.'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($code === '') {
|
||||||
|
$code = strtoupper(str_replace(' ', '_', $name));
|
||||||
|
$code = preg_replace('/[^A-Z0-9_]/', '', $code);
|
||||||
|
} else {
|
||||||
|
$code = strtoupper($code);
|
||||||
|
$code = preg_replace('/[^A-Z0-9_]/', '', $code);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!preg_match('/^#[0-9A-Fa-f]{6}$/', $color)) {
|
||||||
|
$color = '#6c757d';
|
||||||
|
}
|
||||||
|
|
||||||
|
$is_active = $is_active === 1 ? 1 : 0;
|
||||||
|
|
||||||
|
$check = $pdo->prepare("
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM departments
|
||||||
|
WHERE (name = :name OR code = :code)
|
||||||
|
AND id <> :id
|
||||||
|
");
|
||||||
|
$check->execute([
|
||||||
|
'name' => $name,
|
||||||
|
'code' => $code,
|
||||||
|
'id' => $id
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ((int)$check->fetchColumn() > 0) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Another department with the same name or code already exists.'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "UPDATE departments
|
||||||
|
SET name = :name,
|
||||||
|
code = :code,
|
||||||
|
description = :description,
|
||||||
|
color = :color,
|
||||||
|
sort_order = :sort_order,
|
||||||
|
is_active = :is_active,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = :id";
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute([
|
||||||
|
'name' => $name,
|
||||||
|
'code' => $code !== '' ? $code : null,
|
||||||
|
'description' => $description !== '' ? $description : null,
|
||||||
|
'color' => $color,
|
||||||
|
'sort_order' => $sort_order,
|
||||||
|
'is_active' => $is_active,
|
||||||
|
'id' => $id
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'delete') {
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Invalid department ID.'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Future-proof check:
|
||||||
|
* If later you add employees.department_id, this prevents deleting
|
||||||
|
* a department already used by employees.
|
||||||
|
*/
|
||||||
|
$columnCheck = $pdo->prepare("
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
|
AND TABLE_NAME = 'employees'
|
||||||
|
AND COLUMN_NAME = 'department_id'
|
||||||
|
");
|
||||||
|
$columnCheck->execute();
|
||||||
|
$hasDepartmentId = (int)$columnCheck->fetchColumn() > 0;
|
||||||
|
|
||||||
|
if ($hasDepartmentId) {
|
||||||
|
$usageCheck = $pdo->prepare("
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM employees
|
||||||
|
WHERE department_id = :id
|
||||||
|
");
|
||||||
|
$usageCheck->execute(['id' => $id]);
|
||||||
|
|
||||||
|
if ((int)$usageCheck->fetchColumn() > 0) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'This department is linked to one or more employees and cannot be deleted.'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM departments WHERE id = :id");
|
||||||
|
$stmt->execute(['id' => $id]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Unknown action.'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================
|
||||||
|
PAGE DATA
|
||||||
|
========================================== */
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
SELECT *
|
||||||
|
FROM departments
|
||||||
|
ORDER BY sort_order ASC, name ASC
|
||||||
|
";
|
||||||
|
$stmtDepartments = $pdo->query($sql);
|
||||||
|
$departments = $stmtDepartments->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||||
|
<?php include('cssinclude.php'); ?>
|
||||||
|
<title>Gestione Departments - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||||
|
|
||||||
|
<!-- jQuery and Bootstrap -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||||
|
|
||||||
|
<!-- DataTables -->
|
||||||
|
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css">
|
||||||
|
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
|
||||||
|
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-size: 1.05rem;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-dashboard {
|
||||||
|
background-color: #cfe3ff !important;
|
||||||
|
color: #1f2d3d !important;
|
||||||
|
border: 1px solid #bcd4f4 !important;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 10px 18px;
|
||||||
|
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-dashboard:hover {
|
||||||
|
background-color: #b9d3ff !important;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add {
|
||||||
|
background-color: #0d6efd;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add:hover {
|
||||||
|
background-color: #0b5ed7;
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table thead {
|
||||||
|
background-color: #cfe3ff;
|
||||||
|
color: #1f2d3d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tabellaDepartments thead th {
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-status {
|
||||||
|
padding: 0.25rem 0.6rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-status.active {
|
||||||
|
background-color: #d1fae5;
|
||||||
|
color: #065f46;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-status.inactive {
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.department-color-dot {
|
||||||
|
display: inline-block;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.department-code {
|
||||||
|
font-family: Consolas, Monaco, monospace;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
background: #f1f5f9;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description-cell {
|
||||||
|
max-width: 320px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="wrapper" id="appWrapper">
|
||||||
|
<?php include('include/navbar.php'); ?>
|
||||||
|
<?php include('include/topbar.php'); ?>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
<div class="card p-3">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">Gestione Departments</h5>
|
||||||
|
<button type="button" class="btn back-dashboard" onclick="location.href='production_dashboard.php'">
|
||||||
|
↩️ Torna alla Dashboard
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<h6 class="fw-semibold mb-0">Elenco Reparti / Departments</h6>
|
||||||
|
|
||||||
|
<button class="btn btn-add" data-bs-toggle="modal" data-bs-target="#addDepartmentModal">
|
||||||
|
➕ Aggiungi Department
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="tabellaDepartments" class="table table-striped align-middle text-center" style="width:100%;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Color</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Code</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Order</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Created</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<?php if (!empty($departments)): ?>
|
||||||
|
<?php foreach ($departments as $row): ?>
|
||||||
|
<?php
|
||||||
|
$id = (int)$row['id'];
|
||||||
|
$name = $row['name'] ?? '';
|
||||||
|
$code = $row['code'] ?? '';
|
||||||
|
$description = $row['description'] ?? '';
|
||||||
|
$color = $row['color'] ?? '#6c757d';
|
||||||
|
$sortOrder = (int)($row['sort_order'] ?? 999);
|
||||||
|
$isActive = (int)($row['is_active'] ?? 1);
|
||||||
|
|
||||||
|
$statusClass = $isActive === 1 ? 'active' : 'inactive';
|
||||||
|
$statusLabel = $isActive === 1 ? 'Active' : 'Inactive';
|
||||||
|
|
||||||
|
$createdAt = !empty($row['created_at'])
|
||||||
|
? date('d/m/Y H:i', strtotime($row['created_at']))
|
||||||
|
: '-';
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td><?= $id ?></td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span class="department-color-dot" style="background-color: <?= htmlspecialchars($color, ENT_QUOTES) ?>;"></span>
|
||||||
|
<?= htmlspecialchars($color) ?>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="fw-semibold">
|
||||||
|
<?= htmlspecialchars($name) ?>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<?php if ($code !== ''): ?>
|
||||||
|
<span class="department-code"><?= htmlspecialchars($code) ?></span>
|
||||||
|
<?php else: ?>
|
||||||
|
-
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="description-cell" title="<?= htmlspecialchars($description, ENT_QUOTES) ?>">
|
||||||
|
<?= $description !== '' ? htmlspecialchars($description) : '-' ?>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td><?= $sortOrder ?></td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span class="badge-status <?= $statusClass ?>">
|
||||||
|
<?= htmlspecialchars($statusLabel) ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td><?= $createdAt ?></td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-outline-secondary edit-department"
|
||||||
|
data-id="<?= $id ?>"
|
||||||
|
data-name="<?= htmlspecialchars($name, ENT_QUOTES) ?>"
|
||||||
|
data-code="<?= htmlspecialchars($code, ENT_QUOTES) ?>"
|
||||||
|
data-description="<?= htmlspecialchars($description, ENT_QUOTES) ?>"
|
||||||
|
data-color="<?= htmlspecialchars($color, ENT_QUOTES) ?>"
|
||||||
|
data-sort_order="<?= $sortOrder ?>"
|
||||||
|
data-is_active="<?= $isActive ?>">
|
||||||
|
✏️ Modifica
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-outline-danger delete-department"
|
||||||
|
data-id="<?= $id ?>"
|
||||||
|
data-name="<?= htmlspecialchars($name, ENT_QUOTES) ?>">
|
||||||
|
🗑️ Cancella
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ADD DEPARTMENT MODAL -->
|
||||||
|
<div class="modal fade" id="addDepartmentModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header" style="background-color:#cfe3ff;">
|
||||||
|
<h5 class="modal-title">Aggiungi Department</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="addDepartmentForm">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Name</label>
|
||||||
|
<input type="text" class="form-control" id="addName" name="name" placeholder="e.g. Produzione" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Code</label>
|
||||||
|
<input type="text" class="form-control" id="addCode" name="code" placeholder="Optional, e.g. PRODUZIONE">
|
||||||
|
<small class="text-muted">If empty, it will be generated automatically from the name.</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Description</label>
|
||||||
|
<textarea class="form-control" id="addDescription" name="description" rows="3" placeholder="Optional notes"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-semibold">Color</label>
|
||||||
|
<input type="color" class="form-control form-control-color" id="addColor" name="color" value="#6c757d">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-semibold">Sort Order</label>
|
||||||
|
<input type="number" class="form-control" id="addSortOrder" name="sort_order" value="999" min="0">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Status</label>
|
||||||
|
<select class="form-select" id="addIsActive" name="is_active">
|
||||||
|
<option value="1" selected>Active</option>
|
||||||
|
<option value="0">Inactive</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<button type="submit" class="btn btn-add">💾 Save</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- EDIT DEPARTMENT MODAL -->
|
||||||
|
<div class="modal fade" id="editDepartmentModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header" style="background-color:#cfe3ff;">
|
||||||
|
<h5 class="modal-title">Modifica Department</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="editDepartmentForm">
|
||||||
|
<input type="hidden" id="editDepartmentId">
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Name</label>
|
||||||
|
<input type="text" class="form-control" id="editName" name="name" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Code</label>
|
||||||
|
<input type="text" class="form-control" id="editCode" name="code">
|
||||||
|
<small class="text-muted">If empty, it will be generated automatically from the name.</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Description</label>
|
||||||
|
<textarea class="form-control" id="editDescription" name="description" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-semibold">Color</label>
|
||||||
|
<input type="color" class="form-control form-control-color" id="editColor" name="color" value="#6c757d">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-semibold">Sort Order</label>
|
||||||
|
<input type="number" class="form-control" id="editSortOrder" name="sort_order" value="999" min="0">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Status</label>
|
||||||
|
<select class="form-select" id="editIsActive" name="is_active">
|
||||||
|
<option value="1">Active</option>
|
||||||
|
<option value="0">Inactive</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<button type="submit" class="btn btn-add">💾 Save Changes</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#tabellaDepartments').DataTable({
|
||||||
|
order: [
|
||||||
|
[5, 'asc'],
|
||||||
|
[2, 'asc']
|
||||||
|
],
|
||||||
|
pageLength: 25,
|
||||||
|
language: {
|
||||||
|
url: 'https://cdn.datatables.net/plug-ins/1.13.6/i18n/it-IT.json',
|
||||||
|
emptyTable: 'Nessun department presente'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* -------- ADD DEPARTMENT -------- */
|
||||||
|
$("#addDepartmentForm").on("submit", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const payload = new URLSearchParams();
|
||||||
|
payload.append('ajax', '1');
|
||||||
|
payload.append('action', 'add');
|
||||||
|
payload.append('name', $("#addName").val().trim());
|
||||||
|
payload.append('code', $("#addCode").val().trim());
|
||||||
|
payload.append('description', $("#addDescription").val().trim());
|
||||||
|
payload.append('color', $("#addColor").val());
|
||||||
|
payload.append('sort_order', $("#addSortOrder").val());
|
||||||
|
payload.append('is_active', $("#addIsActive").val());
|
||||||
|
|
||||||
|
fetch("", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
|
},
|
||||||
|
body: payload.toString()
|
||||||
|
})
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: "success",
|
||||||
|
title: "Saved!",
|
||||||
|
confirmButtonColor: "#3085d6"
|
||||||
|
}).then(() => location.reload());
|
||||||
|
} else {
|
||||||
|
Swal.fire({
|
||||||
|
icon: "error",
|
||||||
|
title: "Error",
|
||||||
|
text: data.message || "Unable to save department."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
Swal.fire({
|
||||||
|
icon: "error",
|
||||||
|
title: "Error",
|
||||||
|
text: "Communication error."
|
||||||
|
});
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/* -------- OPEN EDIT MODAL -------- */
|
||||||
|
$(document).on("click", ".edit-department", function() {
|
||||||
|
const btn = $(this);
|
||||||
|
|
||||||
|
$("#editDepartmentId").val(btn.data("id"));
|
||||||
|
$("#editName").val(btn.data("name"));
|
||||||
|
$("#editCode").val(btn.data("code"));
|
||||||
|
$("#editDescription").val(btn.data("description"));
|
||||||
|
$("#editColor").val(btn.data("color") || '#6c757d');
|
||||||
|
$("#editSortOrder").val(btn.data("sort_order"));
|
||||||
|
$("#editIsActive").val(String(btn.data("is_active")));
|
||||||
|
|
||||||
|
$("#editDepartmentModal").modal("show");
|
||||||
|
});
|
||||||
|
|
||||||
|
/* -------- SAVE EDIT -------- */
|
||||||
|
$("#editDepartmentForm").on("submit", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const payload = new URLSearchParams();
|
||||||
|
payload.append('ajax', '1');
|
||||||
|
payload.append('action', 'edit');
|
||||||
|
payload.append('id', $("#editDepartmentId").val());
|
||||||
|
payload.append('name', $("#editName").val().trim());
|
||||||
|
payload.append('code', $("#editCode").val().trim());
|
||||||
|
payload.append('description', $("#editDescription").val().trim());
|
||||||
|
payload.append('color', $("#editColor").val());
|
||||||
|
payload.append('sort_order', $("#editSortOrder").val());
|
||||||
|
payload.append('is_active', $("#editIsActive").val());
|
||||||
|
|
||||||
|
fetch("", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
|
},
|
||||||
|
body: payload.toString()
|
||||||
|
})
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: "success",
|
||||||
|
title: "Updated!",
|
||||||
|
confirmButtonColor: "#3085d6"
|
||||||
|
}).then(() => location.reload());
|
||||||
|
} else {
|
||||||
|
Swal.fire({
|
||||||
|
icon: "error",
|
||||||
|
title: "Error",
|
||||||
|
text: data.message || "Unable to update department."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
Swal.fire({
|
||||||
|
icon: "error",
|
||||||
|
title: "Error",
|
||||||
|
text: "Communication error."
|
||||||
|
});
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/* -------- DELETE DEPARTMENT -------- */
|
||||||
|
$(document).on("click", ".delete-department", function() {
|
||||||
|
const id = $(this).data("id");
|
||||||
|
const name = $(this).data("name");
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Confermi la cancellazione?",
|
||||||
|
text: name ? ("Department: " + name) : "This department will be deleted.",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonColor: "#d33",
|
||||||
|
cancelButtonColor: "#6c757d",
|
||||||
|
confirmButtonText: "Sì, cancella",
|
||||||
|
cancelButtonText: "Annulla"
|
||||||
|
}).then((result) => {
|
||||||
|
if (!result.isConfirmed) return;
|
||||||
|
|
||||||
|
const payload = new URLSearchParams();
|
||||||
|
payload.append('ajax', '1');
|
||||||
|
payload.append('action', 'delete');
|
||||||
|
payload.append('id', id);
|
||||||
|
|
||||||
|
fetch("", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
|
},
|
||||||
|
body: payload.toString()
|
||||||
|
})
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: "success",
|
||||||
|
title: "Deleted!",
|
||||||
|
confirmButtonColor: "#3085d6"
|
||||||
|
}).then(() => location.reload());
|
||||||
|
} else {
|
||||||
|
Swal.fire({
|
||||||
|
icon: "error",
|
||||||
|
title: "Error",
|
||||||
|
text: data.message || "Unable to delete department."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
Swal.fire({
|
||||||
|
icon: "error",
|
||||||
|
title: "Error",
|
||||||
|
text: "Communication error."
|
||||||
|
});
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
<?php include('include/headscript.php'); ?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||||
|
<?php include('cssinclude.php'); ?>
|
||||||
|
<title>Modifica Linea di Produzione</title>
|
||||||
|
|
||||||
|
<!-- jQuery + Bootstrap -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: #f8fafc;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save {
|
||||||
|
background-color: #0d6efd;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save:hover {
|
||||||
|
background-color: #0b5ed7;
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-dashboard {
|
||||||
|
background-color: #cfe3ff;
|
||||||
|
color: #1f2d3d;
|
||||||
|
border: 1px solid #bcd4f4;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 10px 18px;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-dashboard:hover {
|
||||||
|
background-color: #b9d3ff;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<?php include('include/navbar.php'); ?>
|
||||||
|
<?php include('include/topbar.php'); ?>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
<div class="card p-4 mx-auto" style="max-width: 600px;">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0 text-center w-100">Modifica Linea</h5>
|
||||||
|
<button type="button" class="btn back-dashboard position-absolute end-0 me-3" onclick="location.href='linee.php'">
|
||||||
|
↩️ Torna all'elenco
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<?php
|
||||||
|
require_once(__DIR__ . '/class/db-functions.php');
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
|
||||||
|
echo "<div class='alert alert-danger'>ID non valido.</div>";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = (int)$_GET['id'];
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM production_lines WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$line = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$line) {
|
||||||
|
echo "<div class='alert alert-warning'>Linea non trovata.</div>";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<form id="editLineaForm">
|
||||||
|
<input type="hidden" name="id" value="<?= htmlspecialchars($line['id']) ?>">
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="lineNumber" class="form-label fw-semibold">Numero Linea</label>
|
||||||
|
<input type="number" id="lineNumber" name="lineNumber" class="form-control" value="<?= htmlspecialchars($line['line_number']) ?>" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="lineName" class="form-label fw-semibold">Nome Linea</label>
|
||||||
|
<input type="text" id="lineName" name="lineName" class="form-control" value="<?= htmlspecialchars($line['name']) ?>" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="model" class="form-label fw-semibold">Modello</label>
|
||||||
|
<input type="text" id="model" name="model" class="form-control" value="<?= htmlspecialchars($line['model']) ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="brand" class="form-label fw-semibold">Marca</label>
|
||||||
|
<input type="text" id="brand" name="brand" class="form-control" value="<?= htmlspecialchars($line['brand']) ?>">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="color" class="form-label fw-semibold">Colore Linea</label>
|
||||||
|
<input type="color" id="color" name="color" class="form-control form-control-color"
|
||||||
|
value="<?= htmlspecialchars($line['color'] ?? '#dc2626') ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="status" class="form-label fw-semibold">Stato</label>
|
||||||
|
<select id="status" name="status" class="form-select">
|
||||||
|
<option value="active" <?= $line['status'] === 'active' ? 'selected' : '' ?>>Attiva</option>
|
||||||
|
<option value="inactive" <?= $line['status'] === 'inactive' ? 'selected' : '' ?>>Inattiva</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center mt-4">
|
||||||
|
<button type="submit" class="btn btn-save">💾 Salva Modifiche</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById("editLineaForm").addEventListener("submit", e => {
|
||||||
|
e.preventDefault();
|
||||||
|
const formData = new FormData(e.target);
|
||||||
|
|
||||||
|
fetch("update_linea.php", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: "success",
|
||||||
|
title: "Aggiornata!",
|
||||||
|
text: "La linea è stata modificata correttamente.",
|
||||||
|
confirmButtonColor: "#3085d6"
|
||||||
|
}).then(() => location.href = "linee.php");
|
||||||
|
} else {
|
||||||
|
Swal.fire("Errore", data.message || "Errore durante l'aggiornamento.", "error");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => Swal.fire("Errore", "Impossibile contattare il server.", "error"));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/class/db-functions.php';
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
$name = trim($_POST['name'] ?? '');
|
||||||
|
$description = trim($_POST['description'] ?? '');
|
||||||
|
$is_problem = isset($_POST['is_problem']) ? (int)$_POST['is_problem'] : 0;
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID non valido.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if ($name === '') {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Il nome è obbligatorio.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE pause_reasons
|
||||||
|
SET name = :name,
|
||||||
|
description = :description,
|
||||||
|
is_problem = :is_problem
|
||||||
|
WHERE id = :id
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
':name' => $name,
|
||||||
|
':description' => $description !== '' ? $description : null,
|
||||||
|
':is_problem' => $is_problem,
|
||||||
|
':id' => $id
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
// Validazione
|
||||||
|
$id = intval($_POST['id'] ?? 0);
|
||||||
|
$nome = trim($_POST['nome'] ?? '');
|
||||||
|
$ordinamento = intval($_POST['ordinamento'] ?? 0);
|
||||||
|
$badge = $_POST['badge_color'] ?? '#6c757d';
|
||||||
|
$line = $_POST['line_color'] ?? '#e9ecef';
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID non valido']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($nome === '') {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Il nome è obbligatorio']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE production_status
|
||||||
|
SET nome = :nome,
|
||||||
|
ordinamento = :ordinamento,
|
||||||
|
badge_color = :badge,
|
||||||
|
line_color = :line
|
||||||
|
WHERE id = :id
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
':nome' => $nome,
|
||||||
|
':ordinamento' => $ordinamento,
|
||||||
|
':badge' => $badge,
|
||||||
|
':line' => $line,
|
||||||
|
':id' => $id
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -1,434 +0,0 @@
|
|||||||
<?php include('include/headscript.php');
|
|
||||||
|
|
||||||
// Controlla se è stato passato un ID valido
|
|
||||||
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
|
|
||||||
header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Invalid ID"));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$id = intval($_GET['id']); // Sanifica l'ID
|
|
||||||
|
|
||||||
// Recupera il template dal database
|
|
||||||
$db = DBHandlerSelect::getInstance();
|
|
||||||
$pdo = $db->getConnection();
|
|
||||||
$stmt = $pdo->prepare("SELECT * FROM excel_templates WHERE id = ?");
|
|
||||||
$stmt->execute([$id]);
|
|
||||||
$template = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if (!$template) {
|
|
||||||
header("Location: template_dashboard.php?status=error&message=" . urlencode("Template not found"));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recupera tutte le routine dal database
|
|
||||||
$stmt = $pdo->prepare("SELECT * FROM routine");
|
|
||||||
$stmt->execute();
|
|
||||||
$routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
?>
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
|
||||||
<?php include('cssinclude.php'); ?>
|
|
||||||
<!-- Include jQuery prima di Select2 -->
|
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
|
||||||
<title>Edit Template <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="wrapper">
|
|
||||||
<?php include('include/navbar.php'); ?>
|
|
||||||
<?php include('include/topbar.php'); ?>
|
|
||||||
<div class="page-wrapper">
|
|
||||||
<div class="page-content">
|
|
||||||
<div class="card mb-4">
|
|
||||||
<div class="card-header">
|
|
||||||
<h5 class="mb-0">Update XLS Template</h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<p class="mb-2">Edit the following form in order to update the selected import XLS template</p>
|
|
||||||
<p class="mb-2">Mandatory Fields</p>
|
|
||||||
<ul class="mb-0">
|
|
||||||
<li>Template Name</li>
|
|
||||||
<li>Row Header and Column Header: where the title of the excel starts</li>
|
|
||||||
<li>Schema</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card radius-10">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<div>
|
|
||||||
<h6 class="mb-0">Edit Template: <?php echo htmlspecialchars($template['name']); ?></h6>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="col-12">
|
|
||||||
<form id="editTemplateForm" method="POST">
|
|
||||||
<input type="hidden" name="id" value="<?php echo $template['id']; ?>">
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= htmlspecialchars($templatename, ENT_QUOTES, 'UTF-8'); ?> *</label>
|
|
||||||
<input type="text" name="name" class="form-control" value="<?php echo htmlspecialchars($template['name']); ?>" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= htmlspecialchars($rowheader, ENT_QUOTES, 'UTF-8'); ?> *</label>
|
|
||||||
<input type="number" name="header_row" class="form-control" value="<?php echo $template['header_row']; ?>" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= htmlspecialchars($columnheader, ENT_QUOTES, 'UTF-8'); ?>*</label>
|
|
||||||
<input type="text" name="start_column" class="form-control" value="<?php echo htmlspecialchars($template['start_column']); ?>" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= htmlspecialchars($desctemplate, ENT_QUOTES, 'UTF-8'); ?></label>
|
|
||||||
<textarea name="description" class="form-control"><?php echo htmlspecialchars($template['description']); ?></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label"><?= htmlspecialchars($desttable, ENT_QUOTES, 'UTF-8'); ?>*</label>
|
|
||||||
<input type="text" name="target_table" class="form-control" value="<?php echo htmlspecialchars($template['target_table']); ?>" readonly required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">Button Size</label>
|
|
||||||
<select name="button_size" class="form-control">
|
|
||||||
<option value="small" <?php echo ($template['button_size'] ?? 'medium') === 'small' ? 'selected' : ''; ?>>Small</option>
|
|
||||||
<option value="medium" <?php echo ($template['button_size'] ?? 'medium') === 'medium' ? 'selected' : ''; ?>>Medium</option>
|
|
||||||
<option value="large" <?php echo ($template['button_size'] ?? 'medium') === 'large' ? 'selected' : ''; ?>>Large</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">Button Background Color</label>
|
|
||||||
<input type="color" name="button_bg_color" class="form-control" value="<?php echo htmlspecialchars($template['button_bg_color'] ?? '#007bff'); ?>">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">Button Text Color</label>
|
|
||||||
<input type="color" name="button_text_color" class="form-control" value="<?php echo htmlspecialchars($template['button_text_color'] ?? '#ffffff'); ?>">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">Button Label</label>
|
|
||||||
<input type="text" name="button_label" class="form-control" value="<?php echo htmlspecialchars($template['button_label'] ?? 'Click Me'); ?>">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">Select Client *</label>
|
|
||||||
<select name="client_id" id="clientSelect" class="form-control" required>
|
|
||||||
<option value="">Select a client...</option>
|
|
||||||
</select>
|
|
||||||
<span id="clientLoadingStatus" class="text-muted" style="margin-left: 10px; display: none;">Recupero clienti in corso...</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">Select Schema *</label>
|
|
||||||
<select name="schema_id" id="schemaSelect" class="form-control" required>
|
|
||||||
<option value="">Select a schema...</option>
|
|
||||||
</select>
|
|
||||||
<span id="schemaLoadingStatus" class="text-muted" style="margin-left: 10px; display: none;">Caricamento schemi in corso...</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">Select Routine</label>
|
|
||||||
<select name="idroutine" id="routineSelect" class="form-control">
|
|
||||||
<option value="">Select a routine...</option>
|
|
||||||
<?php foreach ($routines as $routine): ?>
|
|
||||||
<option value="<?php echo $routine['idroutine']; ?>" <?php echo ($template['idroutine'] ?? '') == $routine['idroutine'] ? 'selected' : ''; ?>>
|
|
||||||
<?php echo htmlspecialchars($routine['name']); ?>
|
|
||||||
</option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
<div id="routineDetails" class="mt-2" style="display: none;">
|
|
||||||
<h6>Routine Details</h6>
|
|
||||||
<p><strong>Name:</strong> <span id="routineName"></span></p>
|
|
||||||
<p><strong>Description:</strong> <span id="routineDescription"></span></p>
|
|
||||||
<p><strong>Action 1:</strong> <span id="routineAction1"></span></p>
|
|
||||||
<p><strong>Action 2:</strong> <span id="routineAction2"></span></p>
|
|
||||||
<p><strong>Action 3:</strong> <span id="routineAction3"></span></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br>
|
|
||||||
<button type="submit" class="btn btn-primary"><?= htmlspecialchars($savechanges, ENT_QUOTES, 'UTF-8'); ?></button>
|
|
||||||
<a href="templates_dashboard.php" class="btn btn-secondary">Cancel</a>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="overlay toggle-icon"></div>
|
|
||||||
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
|
||||||
<?php include('include/footer.php'); ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
|
||||||
// Verifica che jQuery sia caricato
|
|
||||||
if (typeof jQuery === 'undefined') {
|
|
||||||
alert("Errore: jQuery non è caricato. Contatta l'amministratore.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const form = document.getElementById("editTemplateForm");
|
|
||||||
const clientLoadingStatus = document.getElementById("clientLoadingStatus");
|
|
||||||
const schemaLoadingStatus = document.getElementById("schemaLoadingStatus");
|
|
||||||
const routineSelect = document.getElementById("routineSelect");
|
|
||||||
const routineDetails = document.getElementById("routineDetails");
|
|
||||||
const routineName = document.getElementById("routineName");
|
|
||||||
const routineDescription = document.getElementById("routineDescription");
|
|
||||||
const routineAction1 = document.getElementById("routineAction1");
|
|
||||||
const routineAction2 = document.getElementById("routineAction2");
|
|
||||||
const routineAction3 = document.getElementById("routineAction3");
|
|
||||||
|
|
||||||
if (!form || !clientLoadingStatus || !schemaLoadingStatus || !routineSelect || !routineDetails) {
|
|
||||||
alert("Errore: Uno o più elementi della pagina non sono stati trovati. Contatta l'amministratore.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inizializza Select2
|
|
||||||
$('#clientSelect').select2({
|
|
||||||
placeholder: "Search for a client...",
|
|
||||||
allowClear: true
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#schemaSelect').select2({
|
|
||||||
placeholder: "Search for a schema...",
|
|
||||||
allowClear: true
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#routineSelect').select2({
|
|
||||||
placeholder: "Select a routine...",
|
|
||||||
allowClear: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Carica i clienti
|
|
||||||
async function loadClients() {
|
|
||||||
try {
|
|
||||||
clientLoadingStatus.style.display = 'inline';
|
|
||||||
clientLoadingStatus.textContent = 'Recupero clienti in corso...';
|
|
||||||
const response = await fetch("get_clienti.php", {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
if (!response.ok) throw new Error(data.error || `Errore HTTP: ${response.status}`);
|
|
||||||
const select = document.getElementById("clientSelect");
|
|
||||||
select.innerHTML = '<option value="">Select a client...</option>';
|
|
||||||
data.value.forEach(client => {
|
|
||||||
const nome = client.Nominativo || "Nome non disponibile";
|
|
||||||
const id = client.IdCliente || "ID non disponibile";
|
|
||||||
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
|
|
||||||
if (parseInt(id) === parseInt(<?php echo json_encode($template['idclient'] ?? 0); ?>)) {
|
|
||||||
option.selected = true;
|
|
||||||
}
|
|
||||||
select.add(option);
|
|
||||||
});
|
|
||||||
$(select).trigger('change');
|
|
||||||
clientLoadingStatus.textContent = "Clienti caricati.";
|
|
||||||
} catch (error) {
|
|
||||||
clientLoadingStatus.textContent = "Errore nel caricamento.";
|
|
||||||
Swal.fire({
|
|
||||||
title: "Errore!",
|
|
||||||
text: "Impossibile caricare i clienti: " + error.message,
|
|
||||||
icon: "error",
|
|
||||||
confirmButtonText: "OK"
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setTimeout(() => clientLoadingStatus.style.display = 'none', 2000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Carica gli schemi
|
|
||||||
async function loadSchemas() {
|
|
||||||
try {
|
|
||||||
schemaLoadingStatus.style.display = 'inline';
|
|
||||||
schemaLoadingStatus.textContent = 'Caricamento schemi in corso...';
|
|
||||||
const response = await fetch("get_schemi.php", {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
if (!response.ok) throw new Error(data.error || `Errore HTTP: ${response.status}`);
|
|
||||||
const select = document.getElementById("schemaSelect");
|
|
||||||
select.innerHTML = '<option value="">Select a schema...</option>';
|
|
||||||
data.value.forEach(schema => {
|
|
||||||
const nome = schema.Nome || "Nome non disponibile";
|
|
||||||
const id = schema.IdSchemaCustomFields || "ID non disponibile";
|
|
||||||
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
|
|
||||||
if (parseInt(id) === parseInt(<?php echo json_encode($template['idschema'] ?? 0); ?>)) {
|
|
||||||
option.selected = true;
|
|
||||||
}
|
|
||||||
select.add(option);
|
|
||||||
});
|
|
||||||
$(select).trigger('change');
|
|
||||||
schemaLoadingStatus.textContent = "Schemi caricati.";
|
|
||||||
} catch (error) {
|
|
||||||
schemaLoadingStatus.textContent = "Errore nel caricamento.";
|
|
||||||
Swal.fire({
|
|
||||||
title: "Errore!",
|
|
||||||
text: "Impossibile caricare gli schemi: " + error.message,
|
|
||||||
icon: "error",
|
|
||||||
confirmButtonText: "OK"
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setTimeout(() => schemaLoadingStatus.style.display = 'none', 2000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Carica i dati
|
|
||||||
async function loadData() {
|
|
||||||
try {
|
|
||||||
await loadClients();
|
|
||||||
await loadSchemas();
|
|
||||||
} catch (error) {
|
|
||||||
Swal.fire({
|
|
||||||
title: "Errore!",
|
|
||||||
text: "Errore nel caricamento dei dati: " + error.message,
|
|
||||||
icon: "error",
|
|
||||||
confirmButtonText: "OK"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loadData();
|
|
||||||
|
|
||||||
// Routine dettagli
|
|
||||||
const routines = <?php echo json_encode($routines); ?>;
|
|
||||||
|
|
||||||
function updateRoutineDetails() {
|
|
||||||
const selectedId = routineSelect.value;
|
|
||||||
routineDetails.style.display = selectedId ? 'block' : 'none';
|
|
||||||
if (selectedId) {
|
|
||||||
const routine = routines.find(r => r.idroutine == selectedId);
|
|
||||||
if (routine) {
|
|
||||||
routineName.textContent = routine.name || 'N/A';
|
|
||||||
routineDescription.textContent = routine.description || 'N/A';
|
|
||||||
routineAction1.textContent = routine.action1 || 'N/A';
|
|
||||||
routineAction2.textContent = routine.action2 || 'N/A';
|
|
||||||
routineAction3.textContent = routine.action3 || 'N/A';
|
|
||||||
} else {
|
|
||||||
routineName.textContent = 'N/A';
|
|
||||||
routineDescription.textContent = 'N/A';
|
|
||||||
routineAction1.textContent = 'N/A';
|
|
||||||
routineAction2.textContent = 'N/A';
|
|
||||||
routineAction3.textContent = 'N/A';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
routineName.textContent = '';
|
|
||||||
routineDescription.textContent = '';
|
|
||||||
routineAction1.textContent = '';
|
|
||||||
routineAction2.textContent = '';
|
|
||||||
routineAction3.textContent = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
routineSelect.addEventListener('change', updateRoutineDetails);
|
|
||||||
updateRoutineDetails(); // Inizializza dettagli se una routine è preselezionata
|
|
||||||
|
|
||||||
// Submit del form
|
|
||||||
form.addEventListener("submit", function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
let formData = new FormData(this);
|
|
||||||
|
|
||||||
const clientSelect = document.getElementById("clientSelect");
|
|
||||||
const clientId = clientSelect.value;
|
|
||||||
const selectedClientOption = clientSelect.options[clientSelect.selectedIndex];
|
|
||||||
|
|
||||||
if (!clientId) {
|
|
||||||
Swal.fire({
|
|
||||||
title: "Errore!",
|
|
||||||
text: "Per favore seleziona un cliente.",
|
|
||||||
icon: "error",
|
|
||||||
confirmButtonText: "OK"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let clientName = "";
|
|
||||||
if (selectedClientOption) {
|
|
||||||
const optionText = selectedClientOption.text.trim();
|
|
||||||
const nameMatch = optionText.match(/^(.+?)(?:\s*\(ID:\s*\d+\))?$/);
|
|
||||||
clientName = nameMatch ? nameMatch[1].trim() : optionText;
|
|
||||||
}
|
|
||||||
formData.append("client_name", clientName);
|
|
||||||
|
|
||||||
const schemaSelect = document.getElementById("schemaSelect");
|
|
||||||
const schemaId = schemaSelect.value;
|
|
||||||
const selectedSchemaOption = schemaSelect.options[schemaSelect.selectedIndex];
|
|
||||||
|
|
||||||
if (!schemaId) {
|
|
||||||
Swal.fire({
|
|
||||||
title: "Errore!",
|
|
||||||
text: "Per favore seleziona uno schema.",
|
|
||||||
icon: "error",
|
|
||||||
confirmButtonText: "OK"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let schemaName = "";
|
|
||||||
if (selectedSchemaOption) {
|
|
||||||
const optionText = selectedSchemaOption.text.trim();
|
|
||||||
const nameMatch = optionText.match(/^(.+?)(?:\s*\(ID:\s*\d+\))?$/);
|
|
||||||
schemaName = nameMatch ? nameMatch[1].trim() : optionText;
|
|
||||||
}
|
|
||||||
formData.append("idschema", schemaId);
|
|
||||||
formData.append("schemaname", schemaName);
|
|
||||||
|
|
||||||
// Aggiungi idroutine
|
|
||||||
const routineId = routineSelect.value;
|
|
||||||
formData.append("idroutine", routineId);
|
|
||||||
|
|
||||||
fetch("process_edit_template_xls.php", {
|
|
||||||
method: "POST",
|
|
||||||
body: formData
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.success) {
|
|
||||||
Swal.fire({
|
|
||||||
title: "Successo!",
|
|
||||||
text: "Template aggiornato con successo!",
|
|
||||||
icon: "success",
|
|
||||||
confirmButtonText: "OK"
|
|
||||||
}).then(() => {
|
|
||||||
window.location.href = "templates_dashboard.php";
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Swal.fire({
|
|
||||||
title: "Errore!",
|
|
||||||
text: data.message,
|
|
||||||
icon: "error",
|
|
||||||
confirmButtonText: "OK"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
Swal.fire({
|
|
||||||
title: "Errore!",
|
|
||||||
text: "Si è verificato un errore imprevisto.",
|
|
||||||
icon: "error",
|
|
||||||
confirmButtonText: "OK"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
require_once 'include/headscript.php';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
$name = trim($_POST['name'] ?? '');
|
||||||
|
$registrationNumber = trim($_POST['registration_number'] ?? '');
|
||||||
|
$serialNumber = trim($_POST['serial_number'] ?? '');
|
||||||
|
$toolType = trim($_POST['tool_type'] ?? '');
|
||||||
|
$manufacturer = trim($_POST['manufacturer'] ?? '');
|
||||||
|
$description = trim($_POST['description'] ?? '');
|
||||||
|
$isActive = isset($_POST['is_active']) ? (int)$_POST['is_active'] : 1;
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Invalid ID.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($name === '') {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Name is required.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "UPDATE production_tools
|
||||||
|
SET name = :name,
|
||||||
|
registration_number = :registration_number,
|
||||||
|
serial_number = :serial_number,
|
||||||
|
tool_type = :tool_type,
|
||||||
|
manufacturer = :manufacturer,
|
||||||
|
description = :description,
|
||||||
|
is_active = :is_active
|
||||||
|
WHERE id = :id";
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute([
|
||||||
|
'name' => $name,
|
||||||
|
'registration_number' => $registrationNumber ?: null,
|
||||||
|
'serial_number' => $serialNumber ?: null,
|
||||||
|
'tool_type' => $toolType ?: null,
|
||||||
|
'manufacturer' => $manufacturer ?: null,
|
||||||
|
'description' => $description ?: null,
|
||||||
|
'is_active' => $isActive,
|
||||||
|
'id' => $id
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,14 +0,0 @@
|
|||||||
2025-06-10 12:21:44 - Errore nel recupero dati: HTTP 404, Risposta:
|
|
||||||
2025-06-16 11:15:30 - Errore nel recupero dati: HTTP 404, Risposta: {"title":"Not Found","status":404,"detail":"Not Found","instance":"GET /api/odata/Rapporto(2523026)","errorCode":"d25cbd678"}
|
|
||||||
2025-06-16 11:34:59 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to 93.43.5.102 port 443 after 21033 ms: Couldn't connect to server, Risposta:
|
|
||||||
2025-06-16 11:42:03 - Errore nella richiesta: Recv failure: Connection was reset
|
|
||||||
2025-07-04 10:42:49 - Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
|
||||||
2025-07-04 10:44:13 - Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
|
||||||
2025-07-04 10:48:07 - Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
|
||||||
2025-08-19 16:29:25 - Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
|
||||||
2025-08-26 16:47:19 - Risposta non JSON valida: <?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="DevExpress.ExpressApp.SystemModule" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="DashboardViewItemDescriptor"><Key><PropertyRef Name="ViewId" /></Key><Property Name="ViewId" Type="Edm.String" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem" BaseType="DevExpress.ExpressApp.NonPersistentLiteObject" Abstract="true"><Property Name="Visibility" Type="DevExpress.ExpressApp.Editors.ViewItemVisibility" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem_1OfIModelDashboardViewItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem" Abstract="true" /><EntityType Name="ViewDashboardOrganizationItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem_1OfIModelDashboardViewItem"><Property Name="ObjectType" Type="System.Type" /><Proper
|
|
||||||
2025-08-26 16:48:15 - Risposta non JSON valida: <?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="DevExpress.ExpressApp.SystemModule" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="DashboardViewItemDescriptor"><Key><PropertyRef Name="ViewId" /></Key><Property Name="ViewId" Type="Edm.String" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem" BaseType="DevExpress.ExpressApp.NonPersistentLiteObject" Abstract="true"><Property Name="Visibility" Type="DevExpress.ExpressApp.Editors.ViewItemVisibility" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem_1OfIModelDashboardViewItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem" Abstract="true" /><EntityType Name="ViewDashboardOrganizationItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem_1OfIModelDashboardViewItem"><Property Name="ObjectType" Type="System.Type" /><Proper
|
|
||||||
2025-08-26 16:48:44 - Risposta non JSON valida: <?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="DevExpress.ExpressApp.SystemModule" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="DashboardViewItemDescriptor"><Key><PropertyRef Name="ViewId" /></Key><Property Name="ViewId" Type="Edm.String" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem" BaseType="DevExpress.ExpressApp.NonPersistentLiteObject" Abstract="true"><Property Name="Visibility" Type="DevExpress.ExpressApp.Editors.ViewItemVisibility" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem_1OfIModelDashboardViewItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem" Abstract="true" /><EntityType Name="ViewDashboardOrganizationItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem_1OfIModelDashboardViewItem"><Property Name="ObjectType" Type="System.Type" /><Proper
|
|
||||||
2025-08-26 16:49:24 - Risposta non JSON valida: <?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="DevExpress.ExpressApp.SystemModule" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="DashboardViewItemDescriptor"><Key><PropertyRef Name="ViewId" /></Key><Property Name="ViewId" Type="Edm.String" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem" BaseType="DevExpress.ExpressApp.NonPersistentLiteObject" Abstract="true"><Property Name="Visibility" Type="DevExpress.ExpressApp.Editors.ViewItemVisibility" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem_1OfIModelDashboardViewItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem" Abstract="true" /><EntityType Name="ViewDashboardOrganizationItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem_1OfIModelDashboardViewItem"><Property Name="ObjectType" Type="System.Type" /><Proper
|
|
||||||
2025-08-26 16:50:23 - Risposta non JSON valida: <?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="DevExpress.ExpressApp.SystemModule" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="DashboardViewItemDescriptor"><Key><PropertyRef Name="ViewId" /></Key><Property Name="ViewId" Type="Edm.String" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem" BaseType="DevExpress.ExpressApp.NonPersistentLiteObject" Abstract="true"><Property Name="Visibility" Type="DevExpress.ExpressApp.Editors.ViewItemVisibility" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem_1OfIModelDashboardViewItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem" Abstract="true" /><EntityType Name="ViewDashboardOrganizationItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem_1OfIModelDashboardViewItem"><Property Name="ObjectType" Type="System.Type" /><Proper
|
|
||||||
2025-09-08 08:39:17 - Risposta non JSON valida: <?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="DevExpress.ExpressApp.SystemModule" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="DashboardViewItemDescriptor"><Key><PropertyRef Name="ViewId" /></Key><Property Name="ViewId" Type="Edm.String" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem" BaseType="DevExpress.ExpressApp.NonPersistentLiteObject" Abstract="true"><Property Name="Visibility" Type="DevExpress.ExpressApp.Editors.ViewItemVisibility" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem_1OfIModelDashboardViewItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem" Abstract="true" /><EntityType Name="ViewDashboardOrganizationItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem_1OfIModelDashboardViewItem"><Property Name="ObjectType" Type="System.Type" /><Proper
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<?php
|
|
||||||
ini_set('log_errors', 1);
|
|
||||||
ini_set('error_log', __DIR__ . '/import_debug.log');
|
|
||||||
|
|
||||||
|
|
||||||
error_log("TEST: errore manuale");
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user