Compare commits
278 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ad2e06dc2 | |||
| 25c3990753 | |||
| 9eb257d237 | |||
| d623ee797c | |||
| 4dd7b89c22 | |||
| dec42b4442 | |||
| d088364a0d | |||
| 6e43a178af | |||
| 25bd916221 | |||
| dab8d9aebf | |||
| 375a10a678 | |||
| 15990be884 | |||
| c3a6dd73b6 | |||
| 44ed1186e0 | |||
| 9050cb1006 | |||
| e6820fdb62 | |||
| 5da37a7836 | |||
| c5f27cb69a | |||
| 1d81d6c996 | |||
| 0c72dbf5ae | |||
| 8455be04e1 | |||
| e42d1b9c51 | |||
| 3e69e3c322 | |||
| 0eb4f7a2ad | |||
| 4f2cfc1930 | |||
| df075dd76a | |||
| 6460454201 | |||
| 574ddbbd32 | |||
| 41f414db5c | |||
| 4a863e8c16 | |||
| b431f1d4e9 | |||
| f97b52f158 | |||
| 836fc055ec | |||
| e8dd585df4 | |||
| 198b8c08ad | |||
| 28c467d55e | |||
| 56eee99a67 | |||
| f514b3d2c7 | |||
| a3eb0f0a57 | |||
| aa355905d7 | |||
| b38f3e1240 | |||
| cbd0c5b68a | |||
| f3e5cb4ffd | |||
| 0f0c6a04b7 | |||
| 813bd66f96 | |||
| ce00247d1c | |||
| 3a7dd266c8 | |||
| ba8dc4c721 | |||
| cfbbc36116 | |||
| 50d578eea1 | |||
| 6b0d2aa9b9 | |||
| 67bbd9bbbb | |||
| fa7997c980 | |||
| 66be442eb6 | |||
| 19a2d6e3f7 | |||
| a15ab08576 | |||
| f71e8a56b5 | |||
| cb38bfb75a | |||
| 28a708dad3 | |||
| 6b9cf20ab9 | |||
| d40fc7d177 | |||
| b812563023 | |||
| 39a821357e | |||
| 53c223ea5f | |||
| 9775a12d4a | |||
| 7dfb935e33 | |||
| abb4200215 | |||
| 0be7109df4 | |||
| 578671e013 | |||
| d24836e2b1 | |||
| d983659000 | |||
| 7c5aa7734f | |||
| 4f0dbc7e91 | |||
| fb09f033ae | |||
| 8bae2d7008 | |||
| 7463fc6726 | |||
| 0bc2ff7e9d | |||
| aa1c32b7ed | |||
| c573a46318 | |||
| bf18a904bd | |||
| b3ce489348 | |||
| 08b89e01cc | |||
| 3e66d67dc5 | |||
| 490786731a | |||
| e96f538a47 | |||
| cc96ecb67f | |||
| 4cf03ae742 | |||
| 7a486e9dcf | |||
| 2a7b1fae17 | |||
| a05d9fed2b | |||
| 65170a0a7c | |||
| 223688c372 | |||
| b562eb4033 | |||
| 0645a0c675 | |||
| a02a6b2c4c | |||
| 45dd8d6907 | |||
| 755f6812d4 | |||
| 5e59ae2162 | |||
| 71a19144c8 | |||
| 381a05341b | |||
| 5a58decd40 | |||
| eb21910ef3 | |||
| 5dedc779df | |||
| f4e0074a73 | |||
| 9c850e4ea6 | |||
| f300811341 | |||
| 48387a9945 | |||
| 0e90db8219 | |||
| eaf70d5a46 | |||
| cdd6551e9c | |||
| 1b97bf4362 | |||
| c516589483 | |||
| 817bbadf22 | |||
| 1f27bc48d4 | |||
| c9fba48d88 | |||
| 70d4c0759e | |||
| 5d7880160a | |||
| bbe74d1529 | |||
| 93930227a2 | |||
| 2598a4c91b | |||
| 5e677a8b9c | |||
| 540c44d89a | |||
| 2ee9f2ecb1 | |||
| c9122774b1 | |||
| 1fed113c5c | |||
| e3994d6f9f | |||
| 9af0df3cca | |||
| 48d2a3ff42 | |||
| cf44e67922 | |||
| 35021e9d9b | |||
| 497ebda65a | |||
| b0024edb70 | |||
| b9852ba226 | |||
| 407d6884a1 | |||
| 1a4beadbb2 | |||
| 4bb0445cff | |||
| 73dd8f4ce8 | |||
| bdc4e0e60c | |||
| ef8f2d8000 | |||
| 74c9a1d02a | |||
| cd3bccd183 | |||
| 78154e43a9 | |||
| 9fe9243e60 | |||
| a8330d4aba | |||
| 82d6a2ee18 | |||
| d8eddb3aa5 | |||
| f60dc64b2d | |||
| 4e4cae1df8 | |||
| 8838edf3a1 | |||
| e75be99e43 | |||
| a482d975da | |||
| 598a2cc84c | |||
| 6ec0c2062e | |||
| 5eb5bd1613 | |||
| 03771e3ca8 | |||
| 03642fdfab | |||
| f6ea17388c | |||
| 1c2b4ab7a6 | |||
| 31cb23b00e | |||
| d29563d20d | |||
| 82af925ac1 | |||
| 5d8360dd87 | |||
| 683073c244 | |||
| 8d6fe92481 | |||
| dbc66723a6 | |||
| 218fc14462 | |||
| 29e4b41874 | |||
| eef9ae8d36 | |||
| 68c867a3f4 | |||
| a9827e4e81 | |||
| b51936f784 | |||
| 15b6f38e8b | |||
| 12c6cc5f95 | |||
| a0b12463c0 | |||
| 07ddcafd3f | |||
| 7843d4b1fc | |||
| 4eae855e23 | |||
| c709f64a17 | |||
| d5f0690f59 | |||
| 6bbd3fcae9 | |||
| 9e19e9e1d4 | |||
| 7caee9c994 | |||
| f8320315f7 | |||
| 7397d86bc2 | |||
| 2deb1f101a | |||
| ed4467337f | |||
| 864714d198 | |||
| 33aacfb469 | |||
| e0e262fd32 | |||
| 5d6302fa9c | |||
| 3da8ff81c9 | |||
| a36dd02771 | |||
| 0a6fb98476 | |||
| 57ddd4bb5a | |||
| df5e6d5656 | |||
| 78495880ca | |||
| 960832efb1 | |||
| 447a0d1dea | |||
| 5b47416841 | |||
| 3e4a627ca7 | |||
| 420b0a0405 | |||
| b39d601ec9 | |||
| 89d13699b4 | |||
| 9826331545 | |||
| 9d8718d110 | |||
| 16e00f8573 | |||
| baf3f6da32 | |||
| 62bf4ebd92 | |||
| 6e465e3010 | |||
| 8b08969c69 | |||
| 25d4519684 | |||
| 34d4dc8660 | |||
| 1510ef03f1 | |||
| ce8c95921f | |||
| 095a6ae879 | |||
| 296143016a | |||
| 412dce8941 | |||
| 586226ceaf | |||
| ac09d8d0eb | |||
| 33e3ae059d | |||
| 3aa2504f3c | |||
| c1a396f246 | |||
| a45ba1c8b3 | |||
| 7a944a73f7 | |||
| 71595cc8de | |||
| f89dbd0c23 | |||
| 9ba859e15b | |||
| 672e448e9a | |||
| 0749032fbc | |||
| d692614f70 | |||
| 1303cff9fd | |||
| 6b2bd0964b | |||
| 0d2cf13524 | |||
| f6ef9c39d2 | |||
| 7e4ed56f28 | |||
| 06dd7883c2 | |||
| 4d0644f46c | |||
| 712042b8d8 | |||
| efee12740d | |||
| 14395810d0 | |||
| 03002a8938 | |||
| 21fcee8ff5 | |||
| 1361340928 | |||
| 1bda30e957 | |||
| a87423d879 | |||
| 24cda34681 | |||
| 22e4e652b5 | |||
| 2c514a8ab6 | |||
| b1ea728c15 | |||
| caf5568779 | |||
| 0728fd8f01 | |||
| 9d5c20113f | |||
| 47762a8557 | |||
| 434bb0d993 | |||
| 939a4fe03e | |||
| 493de65892 | |||
| d8eca66747 | |||
| 23ae8e1b1d | |||
| a14aa6eb98 | |||
| 99a30e4d9f | |||
| 7ad20993d9 | |||
| 8978980901 | |||
| 6d66c5cf97 | |||
| b3f19be47d | |||
| 13e73abc5d | |||
| 14d91b6d6e | |||
| c004636b6c | |||
| 4c4c6e3153 | |||
| 7d0824d01f | |||
| 32c0966801 | |||
| 57ab20ed1f | |||
| c533973420 | |||
| b092abf8c7 | |||
| 78089cadc1 | |||
| 3816bf5a20 | |||
| e8b15d8096 | |||
| d925726ecd | |||
| aaad0a6bda |
@@ -1,40 +0,0 @@
|
|||||||
APP_ENV=production
|
|
||||||
APP_DEBUG=true
|
|
||||||
APP_KEY=base64:C+sutHm6xP5sE4QXhoZFhYjArlVN11s2mDU1F8beUkM=
|
|
||||||
APP_URL=http://vanguard.test
|
|
||||||
|
|
||||||
LOG_CHANNEL=stack
|
|
||||||
|
|
||||||
DB_CONNECTION=mysql
|
|
||||||
DB_HOST="localhost"
|
|
||||||
DB_DATABASE="trfcertest"
|
|
||||||
DB_USERNAME="solocla"
|
|
||||||
DB_PASSWORD="!Massarosa2"
|
|
||||||
DB_PREFIX="auth_"
|
|
||||||
|
|
||||||
BROADCAST_DRIVER=log
|
|
||||||
CACHE_DRIVER=file
|
|
||||||
QUEUE_DRIVER=sync
|
|
||||||
SESSION_DRIVER=database
|
|
||||||
SESSION_LIFETIME=120
|
|
||||||
|
|
||||||
REDIS_HOST=127.0.0.1
|
|
||||||
REDIS_PASSWORD=null
|
|
||||||
REDIS_PORT=6379
|
|
||||||
|
|
||||||
MAIL_MAILER=mail
|
|
||||||
MAIL_FROM_NAME=Vanguard
|
|
||||||
MAIL_FROM_ADDRESS=vanguard@test.dev
|
|
||||||
MAIL_HOST=smtp.mailtrap.io
|
|
||||||
MAIL_PORT=2525
|
|
||||||
MAIL_USERNAME=null
|
|
||||||
MAIL_PASSWORD=null
|
|
||||||
MAIL_ENCRYPTION=null
|
|
||||||
|
|
||||||
PUSHER_APP_ID=
|
|
||||||
PUSHER_APP_KEY=
|
|
||||||
PUSHER_APP_SECRET=
|
|
||||||
PUSHER_APP_CLUSTER=mt1
|
|
||||||
|
|
||||||
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
|
||||||
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
APP_ENV=production
|
APP_ENV=production
|
||||||
APP_DEBUG=false
|
APP_DEBUG=true
|
||||||
APP_KEY=
|
APP_KEY=base64:C+sutHm6xP5sE4QXhoZFhYjArlVN11s2mDU1F8beUkM=
|
||||||
APP_URL=http://vanguard.test
|
APP_URL=http://vanguard.test
|
||||||
|
|
||||||
LOG_CHANNEL=stack
|
LOG_CHANNEL=stack
|
||||||
|
|
||||||
DB_CONNECTION=mysql
|
DB_CONNECTION=mysql
|
||||||
DB_HOST=localhost
|
DB_HOST="localhost"
|
||||||
DB_DATABASE=vanguard
|
DB_DATABASE="xxxx"
|
||||||
DB_USERNAME=homestead
|
DB_USERNAME="xxxx"
|
||||||
DB_PASSWORD=secret
|
DB_PASSWORD="xxxxx"
|
||||||
DB_PREFIX=vg_
|
DB_PREFIX="auth_"
|
||||||
|
|
||||||
BROADCAST_DRIVER=log
|
BROADCAST_DRIVER=log
|
||||||
CACHE_DRIVER=file
|
CACHE_DRIVER=file
|
||||||
@@ -39,3 +39,10 @@ PUSHER_APP_CLUSTER=mt1
|
|||||||
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||||
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||||
|
|
||||||
|
# Credenziali API VisualLims
|
||||||
|
SIMULATE_EXPORT_LIMS=true
|
||||||
|
API_BASE_URL=https://bvcpsitaly-elims.com/limsapi
|
||||||
|
API_USERNAME=WebApiUser
|
||||||
|
API_PASSWORD=webapiuser01
|
||||||
|
|
||||||
|
BASE_URL=http://localhost:8000/userarea/
|
||||||
@@ -1,23 +1,63 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
/node_modules
|
/node_modules
|
||||||
/public/hot
|
|
||||||
/public/storage
|
|
||||||
/storage/*.key
|
|
||||||
/vendor
|
/vendor
|
||||||
|
|
||||||
/.idea
|
/.idea
|
||||||
/.fleet
|
/.fleet
|
||||||
/.vscode
|
/.vscode
|
||||||
/.vagrant
|
/.vagrant
|
||||||
Homestead.json
|
|
||||||
Homestead.yaml
|
/public/hot
|
||||||
npm-debug.log
|
/public/storage
|
||||||
yarn-error.log
|
|
||||||
.env
|
|
||||||
.phpunit.result.cache
|
|
||||||
.php_cs.cache
|
|
||||||
/documentation
|
|
||||||
/.phpunit.cache
|
|
||||||
/public/build
|
/public/build
|
||||||
|
|
||||||
|
/storage/*.key
|
||||||
|
|
||||||
|
.env
|
||||||
.env.backup
|
.env.backup
|
||||||
.env.production
|
.env.production
|
||||||
auth.json
|
auth.json
|
||||||
|
|
||||||
|
.phpunit.result.cache
|
||||||
|
.php_cs.cache
|
||||||
|
/.phpunit.cache
|
||||||
|
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
|
||||||
|
/documentation
|
||||||
|
|
||||||
|
# --- Runtime / Debug (userarea) ---
|
||||||
|
/public/userarea/*.json
|
||||||
|
/public/userarea/*.log
|
||||||
|
/public/userarea/*.txt
|
||||||
|
/public/userarea/*_response.json
|
||||||
|
/public/userarea/customfield_values_response.json
|
||||||
|
/public/userarea/error_log.txt
|
||||||
|
/public/userarea/import_debug.log
|
||||||
|
/public/userarea/last_url.txt
|
||||||
|
/public/userarea/logaspi/
|
||||||
|
/public/userarea/logs/api/
|
||||||
|
/public/userarea/logs/api/**
|
||||||
|
/public/userarea/logs/
|
||||||
|
/public/userarea/logs/**
|
||||||
|
/public/userarea/photostrf/
|
||||||
|
/public/userarea/class/*.log
|
||||||
|
/public/userarea/class/curl_auth_debug.log
|
||||||
|
/public/userarea/class/curl_request_debug.log
|
||||||
|
/public/userarea/schema_dettagli_response.json
|
||||||
|
public/userarea/schemi_base_response.json
|
||||||
|
|
||||||
|
# File XLSX temporanei importati
|
||||||
|
/public/userarea/imported_trf/*.xlsx
|
||||||
|
/public/userarea/xlstemplates/*.xlsx
|
||||||
|
|
||||||
|
# Cartelle foto generate
|
||||||
|
/public/photostrf/
|
||||||
|
/public/photostrf/qrcodes/
|
||||||
|
|
||||||
|
# Ignora tutti i log ovunque
|
||||||
|
*.log
|
||||||
|
|
||||||
|
public/userarea/cache/
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Userarea;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class UploadPhotosMobileController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$iddatadb = $request->query('iddatadb');
|
||||||
|
|
||||||
|
if (empty($iddatadb)) {
|
||||||
|
return response('ID riga non fornito', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the upload form
|
||||||
|
return view('userarea.upload_photos_mobile', [
|
||||||
|
'iddatadb' => $iddatadb
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function upload(Request $request)
|
||||||
|
{
|
||||||
|
// Validation
|
||||||
|
$request->validate([
|
||||||
|
'photo' => 'required|file|mimes:jpeg,png,gif,heic,heif|max:5120', // 5MB
|
||||||
|
'iddatadb' => 'required|integer'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$iddatadb = $request->input('iddatadb');
|
||||||
|
$photo = $request->file('photo');
|
||||||
|
$iduserlogin = auth()->id(); // assuming Laravel authentication
|
||||||
|
|
||||||
|
// Check if user exists
|
||||||
|
$userExists = DB::table('auth_users')->where('id', $iduserlogin)->exists();
|
||||||
|
if (!$userExists) {
|
||||||
|
return response()->json(['success' => false, 'message' => 'Utente non valido']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload folder
|
||||||
|
$uploadDir = public_path('photostrf');
|
||||||
|
if (!is_dir($uploadDir)) {
|
||||||
|
mkdir($uploadDir, 0755, true);
|
||||||
|
}
|
||||||
|
if (!is_writable($uploadDir)) {
|
||||||
|
return response()->json(['success' => false, 'message' => 'La cartella photostrf non è scrivibile']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// New filename
|
||||||
|
$timestamp = now()->format('YmdHis');
|
||||||
|
$originalName = pathinfo($photo->getClientOriginalName(), PATHINFO_FILENAME);
|
||||||
|
$extension = strtolower($photo->getClientOriginalExtension());
|
||||||
|
|
||||||
|
$newFileName = "{$iddatadb}-{$timestamp}-{$originalName}.{$extension}";
|
||||||
|
$destination = $uploadDir . '/' . $newFileName;
|
||||||
|
|
||||||
|
// Move uploaded file
|
||||||
|
$photo->move($uploadDir, $newFileName);
|
||||||
|
|
||||||
|
// Save DB record
|
||||||
|
DB::table('datadb_photos')->insert([
|
||||||
|
'iddatadb' => $iddatadb,
|
||||||
|
'file_path' => $newFileName,
|
||||||
|
'file_name' => $newFileName,
|
||||||
|
'uploaded_by' => $iduserlogin
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json(['success' => true, 'message' => 'Foto caricata con successo']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -110,7 +110,7 @@ class LoginController extends Controller
|
|||||||
if ($user->hasRole('Admin')) {
|
if ($user->hasRole('Admin')) {
|
||||||
return redirect()->to('userarea/import_dashboard.php');
|
return redirect()->to('userarea/import_dashboard.php');
|
||||||
} elseif ($user->hasRole('User')) {
|
} elseif ($user->hasRole('User')) {
|
||||||
return redirect()->to('userarea/index.php');
|
return redirect()->to('userarea/import_dashboard.php');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se il ruolo non è specificato, reindirizza alla home predefinita
|
// Se il ruolo non è specificato, reindirizza alla home predefinita
|
||||||
|
|||||||
@@ -28,10 +28,21 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
\Illuminate\Database\Schema\Builder::defaultStringLength(191);
|
\Illuminate\Database\Schema\Builder::defaultStringLength(191);
|
||||||
|
|
||||||
Factory::guessFactoryNamesUsing(function (string $modelName) {
|
Factory::guessFactoryNamesUsing(function (string $modelName) {
|
||||||
return 'Database\Factories\\'.class_basename($modelName).'Factory';
|
return 'Database\Factories\\' . class_basename($modelName) . 'Factory';
|
||||||
});
|
});
|
||||||
|
|
||||||
\Illuminate\Pagination\Paginator::useBootstrap();
|
\Illuminate\Pagination\Paginator::useBootstrap();
|
||||||
|
|
||||||
|
// Register Microsoft Socialite driver
|
||||||
|
$this->app->make('Laravel\Socialite\Contracts\Factory')->extend('microsoft', function ($app) {
|
||||||
|
$config = $app['config']['services.microsoft'];
|
||||||
|
return new \SocialiteProviders\Microsoft\Provider(
|
||||||
|
$app['request'],
|
||||||
|
$config['client_id'],
|
||||||
|
$config['client_secret'],
|
||||||
|
$config['redirect']
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ class EventServiceProvider extends ServiceProvider
|
|||||||
Verified::class => [
|
Verified::class => [
|
||||||
ActivateUser::class,
|
ActivateUser::class,
|
||||||
],
|
],
|
||||||
|
\SocialiteProviders\Manager\SocialiteWasMapped::class => [
|
||||||
|
\SocialiteProviders\Microsoft\MicrosoftExtendSocialite::class,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
$app = new Illuminate\Foundation\Application(
|
$app = new Illuminate\Foundation\Application(
|
||||||
realpath(__DIR__.'/../')
|
realpath(__DIR__ . '/../')
|
||||||
);
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -38,12 +38,13 @@
|
|||||||
"laravel/fortify": "^1.21",
|
"laravel/fortify": "^1.21",
|
||||||
"laravel/framework": "^11.0",
|
"laravel/framework": "^11.0",
|
||||||
"laravel/sanctum": "^4.0",
|
"laravel/sanctum": "^4.0",
|
||||||
"laravel/socialite": "^5.0",
|
"laravel/socialite": "^5.16",
|
||||||
"laravel/tinker": "^2.7",
|
"laravel/tinker": "^2.7",
|
||||||
"laravel/ui": "^4.0",
|
"laravel/ui": "^4.0",
|
||||||
"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",
|
||||||
|
"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",
|
||||||
"vanguardapp/announcements": "^6.0",
|
"vanguardapp/announcements": "^6.0",
|
||||||
|
|||||||
@@ -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": "ef3e05e7260284f5b7c7b4b6f93b252b",
|
"content-hash": "9c4f1e3bc3ee2180211c055e70635aef",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "akaunting/laravel-setting",
|
"name": "akaunting/laravel-setting",
|
||||||
@@ -2240,16 +2240,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/socialite",
|
"name": "laravel/socialite",
|
||||||
"version": "v5.15.1",
|
"version": "v5.16.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/laravel/socialite.git",
|
"url": "https://github.com/laravel/socialite.git",
|
||||||
"reference": "cc02625f0bd1f95dc3688eb041cce0f1e709d029"
|
"reference": "40a2dc98c53d9dc6d55eadb0d490d3d72b73f1bf"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/laravel/socialite/zipball/cc02625f0bd1f95dc3688eb041cce0f1e709d029",
|
"url": "https://api.github.com/repos/laravel/socialite/zipball/40a2dc98c53d9dc6d55eadb0d490d3d72b73f1bf",
|
||||||
"reference": "cc02625f0bd1f95dc3688eb041cce0f1e709d029",
|
"reference": "40a2dc98c53d9dc6d55eadb0d490d3d72b73f1bf",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -2271,16 +2271,16 @@
|
|||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
|
||||||
"dev-master": "5.x-dev"
|
|
||||||
},
|
|
||||||
"laravel": {
|
"laravel": {
|
||||||
"providers": [
|
|
||||||
"Laravel\\Socialite\\SocialiteServiceProvider"
|
|
||||||
],
|
|
||||||
"aliases": {
|
"aliases": {
|
||||||
"Socialite": "Laravel\\Socialite\\Facades\\Socialite"
|
"Socialite": "Laravel\\Socialite\\Facades\\Socialite"
|
||||||
}
|
},
|
||||||
|
"providers": [
|
||||||
|
"Laravel\\Socialite\\SocialiteServiceProvider"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "5.x-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -2308,7 +2308,7 @@
|
|||||||
"issues": "https://github.com/laravel/socialite/issues",
|
"issues": "https://github.com/laravel/socialite/issues",
|
||||||
"source": "https://github.com/laravel/socialite"
|
"source": "https://github.com/laravel/socialite"
|
||||||
},
|
},
|
||||||
"time": "2024-06-28T20:09:34+00:00"
|
"time": "2024-09-03T09:46:57+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/tinker",
|
"name": "laravel/tinker",
|
||||||
@@ -4980,6 +4980,131 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-04-27T21:32:50+00:00"
|
"time": "2024-04-27T21:32:50+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "socialiteproviders/manager",
|
||||||
|
"version": "v4.8.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/SocialiteProviders/Manager.git",
|
||||||
|
"reference": "8180ec14bef230ec2351cff993d5d2d7ca470ef4"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/8180ec14bef230ec2351cff993d5d2d7ca470ef4",
|
||||||
|
"reference": "8180ec14bef230ec2351cff993d5d2d7ca470ef4",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0",
|
||||||
|
"laravel/socialite": "^5.5",
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"mockery/mockery": "^1.2",
|
||||||
|
"phpunit/phpunit": "^9.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"SocialiteProviders\\Manager\\ServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"SocialiteProviders\\Manager\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Andy Wendt",
|
||||||
|
"email": "andy@awendt.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Anton Komarev",
|
||||||
|
"email": "a.komarev@cybercog.su"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Miguel Piedrafita",
|
||||||
|
"email": "soy@miguelpiedrafita.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atymic",
|
||||||
|
"email": "atymicq@gmail.com",
|
||||||
|
"homepage": "https://atymic.dev"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Easily add new or override built-in providers in Laravel Socialite.",
|
||||||
|
"homepage": "https://socialiteproviders.com",
|
||||||
|
"keywords": [
|
||||||
|
"laravel",
|
||||||
|
"manager",
|
||||||
|
"oauth",
|
||||||
|
"providers",
|
||||||
|
"socialite"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/socialiteproviders/manager/issues",
|
||||||
|
"source": "https://github.com/socialiteproviders/manager"
|
||||||
|
},
|
||||||
|
"time": "2025-02-24T19:33:30+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "socialiteproviders/microsoft",
|
||||||
|
"version": "4.7.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/SocialiteProviders/Microsoft.git",
|
||||||
|
"reference": "824ef97a4f6e3f363c21702b76676d54e8265573"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/SocialiteProviders/Microsoft/zipball/824ef97a4f6e3f363c21702b76676d54e8265573",
|
||||||
|
"reference": "824ef97a4f6e3f363c21702b76676d54e8265573",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"firebase/php-jwt": "^6.8",
|
||||||
|
"php": "^8.0",
|
||||||
|
"socialiteproviders/manager": "^4.4"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"SocialiteProviders\\Microsoft\\": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Brian Faust",
|
||||||
|
"email": "hello@brianfaust.de"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Microsoft OAuth2 Provider for Laravel Socialite",
|
||||||
|
"keywords": [
|
||||||
|
"laravel",
|
||||||
|
"microsoft",
|
||||||
|
"oauth",
|
||||||
|
"provider",
|
||||||
|
"socialite"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"docs": "https://socialiteproviders.com/microsoft",
|
||||||
|
"issues": "https://github.com/socialiteproviders/providers/issues",
|
||||||
|
"source": "https://github.com/socialiteproviders/providers"
|
||||||
|
},
|
||||||
|
"time": "2025-07-06T00:25:25+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "spatie/laravel-package-tools",
|
"name": "spatie/laravel-package-tools",
|
||||||
"version": "1.16.4",
|
"version": "1.16.4",
|
||||||
|
|||||||
@@ -228,6 +228,7 @@ return [
|
|||||||
Proengsoft\JsValidation\JsValidationServiceProvider::class,
|
Proengsoft\JsValidation\JsValidationServiceProvider::class,
|
||||||
Anhskohbo\NoCaptcha\NoCaptchaServiceProvider::class,
|
Anhskohbo\NoCaptcha\NoCaptchaServiceProvider::class,
|
||||||
Laravel\Socialite\SocialiteServiceProvider::class,
|
Laravel\Socialite\SocialiteServiceProvider::class,
|
||||||
|
\SocialiteProviders\Manager\ServiceProvider::class,
|
||||||
Webpatser\Countries\CountriesServiceProvider::class,
|
Webpatser\Countries\CountriesServiceProvider::class,
|
||||||
Intervention\Image\ImageServiceProvider::class,
|
Intervention\Image\ImageServiceProvider::class,
|
||||||
Jenssegers\Agent\AgentServiceProvider::class,
|
Jenssegers\Agent\AgentServiceProvider::class,
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 'social' => [
|
'social' => [
|
||||||
// 'providers' => ['facebook', 'twitter', 'google'],
|
'providers' => ['microsoft'],
|
||||||
// ],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -64,9 +64,15 @@ return [
|
|||||||
'redirect' => env('GOOGLE_CALLBACK_URI'),
|
'redirect' => env('GOOGLE_CALLBACK_URI'),
|
||||||
],
|
],
|
||||||
|
|
||||||
// 'authy' => [
|
'microsoft' => [
|
||||||
// 'key' => env('AUTHY_KEY'),
|
'client_id' => env('MICROSOFT_CLIENT_ID'),
|
||||||
// ],
|
'client_secret' => env('MICROSOFT_CLIENT_SECRET'),
|
||||||
|
'redirect' => env('MICROSOFT_REDIRECT_URI'),
|
||||||
|
],
|
||||||
|
|
||||||
|
// 'authy' => [
|
||||||
|
// 'key' => env('AUTHY_KEY'),
|
||||||
|
// ],
|
||||||
|
|
||||||
'resend' => [
|
'resend' => [
|
||||||
'key' => env('RESEND_KEY'),
|
'key' => env('RESEND_KEY'),
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# DB LOCALE (Windows 11)
|
||||||
|
url=jdbc:mysql://localhost:3306/trfcertest
|
||||||
|
username=solocla
|
||||||
|
password=!Massarosa2
|
||||||
|
driver=com.mysql.cj.jdbc.Driver
|
||||||
|
changeLogFile=liquibase/changelog/db.changelog-master.yaml
|
||||||
@@ -32,3 +32,4 @@ $langdatatables = [
|
|||||||
"paginate_next" => "Next",
|
"paginate_next" => "Next",
|
||||||
"paginate_previous" => "Previous"
|
"paginate_previous" => "Previous"
|
||||||
];
|
];
|
||||||
|
$quotationstitle = "Quotations";
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 510 B |
|
Before Width: | Height: | Size: 511 B |
|
Before Width: | Height: | Size: 518 B |
|
Before Width: | Height: | Size: 511 B |
|
Before Width: | Height: | Size: 515 B |
|
Before Width: | Height: | Size: 509 B |
|
Before Width: | Height: | Size: 508 B |
|
Before Width: | Height: | Size: 518 B |
|
Before Width: | Height: | Size: 510 B |
|
Before Width: | Height: | Size: 516 B |
|
Before Width: | Height: | Size: 512 B |
|
Before Width: | Height: | Size: 518 B |
|
Before Width: | Height: | Size: 511 B |
|
Before Width: | Height: | Size: 517 B |
|
After Width: | Height: | Size: 451 B |
|
After Width: | Height: | Size: 510 B |
|
After Width: | Height: | Size: 462 B |
|
After Width: | Height: | Size: 443 B |
|
After Width: | Height: | Size: 460 B |
|
After Width: | Height: | Size: 454 B |
|
After Width: | Height: | Size: 456 B |
|
After Width: | Height: | Size: 460 B |
|
After Width: | Height: | Size: 457 B |
|
After Width: | Height: | Size: 454 B |
|
After Width: | Height: | Size: 455 B |
@@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
require_once(__DIR__ . '/include/headscript.php'); // Auth + $iduserlogin + DBHandlerSelect
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
throw new Exception('Invalid request method');
|
||||||
|
}
|
||||||
|
|
||||||
|
$iddatadb = isset($_POST['iddatadb']) ? (int)$_POST['iddatadb'] : 0;
|
||||||
|
|
||||||
|
if ($iddatadb <= 0) {
|
||||||
|
throw new Exception('Missing iddatadb');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Supporta sia singola stringa (con |) che array part_descriptions[]
|
||||||
|
$parts = [];
|
||||||
|
|
||||||
|
if (isset($_POST['part_descriptions']) && is_array($_POST['part_descriptions'])) {
|
||||||
|
$parts = $_POST['part_descriptions'];
|
||||||
|
} else {
|
||||||
|
$raw = isset($_POST['part_description']) ? (string)$_POST['part_description'] : '';
|
||||||
|
// split su "|" per multi-part
|
||||||
|
$parts = preg_split('/\s*\|\s*/', $raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalizza: trim + rimuovi vuoti + dedup
|
||||||
|
$cleanParts = [];
|
||||||
|
foreach ($parts as $p) {
|
||||||
|
$p = trim((string)$p);
|
||||||
|
if ($p === '') continue;
|
||||||
|
|
||||||
|
// limita lunghezza come da DB varchar(255)
|
||||||
|
if (mb_strlen($p) > 255) {
|
||||||
|
$p = mb_substr($p, 0, 255);
|
||||||
|
}
|
||||||
|
$cleanParts[] = $p;
|
||||||
|
}
|
||||||
|
$cleanParts = array_values(array_unique($cleanParts));
|
||||||
|
|
||||||
|
if (count($cleanParts) === 0) {
|
||||||
|
throw new Exception('Part description is empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// prende il prossimo part_number per iddatadb
|
||||||
|
$stmtMax = $pdo->prepare("SELECT COALESCE(MAX(part_number), 0) AS maxnum FROM identification_parts WHERE iddatadb = ?");
|
||||||
|
$stmtMax->execute([$iddatadb]);
|
||||||
|
$nextNumber = (int)$stmtMax->fetchColumn() + 1;
|
||||||
|
|
||||||
|
$stmtIns = $pdo->prepare("
|
||||||
|
INSERT INTO identification_parts (iddatadb, part_number, part_description)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
");
|
||||||
|
|
||||||
|
$insertedIds = [];
|
||||||
|
|
||||||
|
foreach ($cleanParts as $p) {
|
||||||
|
$ok = $stmtIns->execute([$iddatadb, $nextNumber, $p]);
|
||||||
|
if (!$ok) {
|
||||||
|
throw new Exception('Insert failed');
|
||||||
|
}
|
||||||
|
$insertedIds[] = $pdo->lastInsertId();
|
||||||
|
$nextNumber++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Parts added',
|
||||||
|
'count' => count($insertedIds),
|
||||||
|
'ids' => $insertedIds
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
include('include/headscript.php');
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$templateId = intval($input['template_id'] ?? 0);
|
||||||
|
$importRefFromClient = trim($input['importreferencecode'] ?? '');
|
||||||
|
|
||||||
|
if (!$templateId) {
|
||||||
|
throw new Exception('Template ID missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
$userId = $iduserlogin ?? 1;
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
// Get default idclient from template
|
||||||
|
$stmt = $pdo->prepare("SELECT idclient FROM excel_templates WHERE id = ?");
|
||||||
|
$stmt->execute([$templateId]);
|
||||||
|
$template = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
$idclient = $template['idclient'] ?? null;
|
||||||
|
|
||||||
|
// Use provided importreferencecode (from filtered page) or generate new
|
||||||
|
$importReferenceCode = $importRefFromClient !== '' ? $importRefFromClient : date('YmdHis') . '-' . uniqid();
|
||||||
|
|
||||||
|
// Insert empty record
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO datadb (templateid, user_id, status, idclient, importreferencecode, importdate)
|
||||||
|
VALUES (?, ?, 'i', ?, ?, NOW())
|
||||||
|
");
|
||||||
|
$stmt->execute([$templateId, $userId, $idclient, $importReferenceCode]);
|
||||||
|
$iddatadb = (int)$pdo->lastInsertId();
|
||||||
|
|
||||||
|
// Create import_data_details for all mappings (with auto_value support)
|
||||||
|
$mappingStmt = $pdo->prepare("SELECT id, auto_value, manual_default, data_type FROM template_mapping WHERE template_id = ?");
|
||||||
|
$mappingStmt->execute([$templateId]);
|
||||||
|
$mappings = $mappingStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!empty($mappings)) {
|
||||||
|
$insertStmt = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, ?)");
|
||||||
|
foreach ($mappings as $m) {
|
||||||
|
$val = '';
|
||||||
|
$auto = $m['auto_value'] ?? 'none';
|
||||||
|
if ($auto === 'import_date') {
|
||||||
|
$val = date('Y-m-d');
|
||||||
|
} elseif ($auto === 'import_time') {
|
||||||
|
$val = date('H:i');
|
||||||
|
} elseif ($m['data_type'] === 'DATE' && ($m['manual_default'] ?? '') === 'today') {
|
||||||
|
$val = date('Y-m-d');
|
||||||
|
} elseif (!empty($m['manual_default'])) {
|
||||||
|
$val = $m['manual_default'];
|
||||||
|
}
|
||||||
|
$insertStmt->execute([$iddatadb, $m['id'], $val]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user name
|
||||||
|
$userStmt = $pdo->prepare("SELECT CONCAT(first_name, ' ', last_name) AS user_name FROM auth_users WHERE id = ?");
|
||||||
|
$userStmt->execute([$userId]);
|
||||||
|
$userName = $userStmt->fetchColumn() ?: '';
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'iddatadb' => $iddatadb,
|
||||||
|
'importreferencecode' => $importReferenceCode,
|
||||||
|
'user_name' => $userName,
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,854 @@
|
|||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
let analysisMatriciMap = {};
|
||||||
|
let analysisLoadedCache = {};
|
||||||
|
let analysisAssignedState = {};
|
||||||
|
let analysisSelectedState = {};
|
||||||
|
|
||||||
|
function loadAnalysisMatrixNames() {
|
||||||
|
return $.ajax({
|
||||||
|
url: "get_matrici_db.php",
|
||||||
|
method: "GET",
|
||||||
|
dataType: "json",
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
analysisMatriciMap = {};
|
||||||
|
|
||||||
|
(data.value || []).forEach(function (matrice) {
|
||||||
|
analysisMatriciMap[String(matrice.IdMatrice)] =
|
||||||
|
matrice.NomeMatrice || "#" + matrice.IdMatrice;
|
||||||
|
});
|
||||||
|
|
||||||
|
applyAnalysisMatrixNames();
|
||||||
|
})
|
||||||
|
.fail(function () {
|
||||||
|
analysisMatriciMap = {};
|
||||||
|
applyAnalysisMatrixNames();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyAnalysisMatrixNames() {
|
||||||
|
const modal = document.getElementById("analysisModal");
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
modal.querySelectorAll(".analysis-matrix-item").forEach((item) => {
|
||||||
|
const matrixId = item.getAttribute("data-matrix-id");
|
||||||
|
const nameEl = item.querySelector(".analysis-matrix-name");
|
||||||
|
|
||||||
|
let matrixName = "No Matrix";
|
||||||
|
if (matrixId && matrixId !== "NO_MATRIX") {
|
||||||
|
matrixName =
|
||||||
|
analysisMatriciMap[String(matrixId)] || "#" + matrixId;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.setAttribute("data-matrix-name", matrixName);
|
||||||
|
|
||||||
|
if (nameEl) {
|
||||||
|
nameEl.textContent = matrixName;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
modal.querySelectorAll(".analysis-part-matrix-name").forEach((el) => {
|
||||||
|
const matrixId = el.getAttribute("data-matrix-id");
|
||||||
|
|
||||||
|
if (!matrixId || matrixId === "NO_MATRIX") {
|
||||||
|
el.textContent = "No Matrix";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
el.textContent =
|
||||||
|
analysisMatriciMap[String(matrixId)] || "#" + matrixId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function readInitialAssignedAnalyses() {
|
||||||
|
const jsonEl = document.getElementById("analysisAssignedInitialData");
|
||||||
|
if (!jsonEl) {
|
||||||
|
analysisAssignedState = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
analysisAssignedState =
|
||||||
|
JSON.parse(jsonEl.textContent || "{}") || {};
|
||||||
|
} catch (e) {
|
||||||
|
analysisAssignedState = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectedPartIds() {
|
||||||
|
const modal = document.getElementById("analysisModal");
|
||||||
|
if (!modal) return [];
|
||||||
|
|
||||||
|
return Array.from(
|
||||||
|
modal.querySelectorAll(".analysis-part-checkbox:checked"),
|
||||||
|
).map((el) => String(el.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentSelectedAnalysisRecordKeys() {
|
||||||
|
return Object.keys(analysisSelectedState).filter(function (key) {
|
||||||
|
return analysisSelectedState[key] === true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderAssignedAnalysesForPart(partId) {
|
||||||
|
const container = document.getElementById(
|
||||||
|
"analysisAssignedListPart" + partId,
|
||||||
|
);
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const items = Array.isArray(analysisAssignedState[String(partId)])
|
||||||
|
? analysisAssignedState[String(partId)]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
if (!items.length) {
|
||||||
|
container.innerHTML = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = items
|
||||||
|
.map(function (item) {
|
||||||
|
const recordKey = item.analysis_recordkey || "";
|
||||||
|
const title = item.analysis_name || "Unnamed analysis";
|
||||||
|
const method = item.analysis_method || "";
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="analysis-assigned-chip" data-recordkey="${escapeHtml(recordKey)}">
|
||||||
|
<div class="analysis-assigned-chip-text">
|
||||||
|
<div class="analysis-assigned-chip-title">${escapeHtml(title)}</div>
|
||||||
|
${method ? `<div class="analysis-assigned-chip-method">${escapeHtml(method)}</div>` : ""}
|
||||||
|
</div>
|
||||||
|
<button type="button"
|
||||||
|
class="analysis-remove-btn"
|
||||||
|
data-part-id="${escapeHtml(partId)}"
|
||||||
|
data-recordkey="${escapeHtml(recordKey)}"
|
||||||
|
title="Remove analysis">×</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderAssignedAnalysesForSelectedParts() {
|
||||||
|
const modal = document.getElementById("analysisModal");
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
modal.querySelectorAll(".analysis-part-item").forEach(function (item) {
|
||||||
|
const partId = item.getAttribute("data-part-id");
|
||||||
|
renderAssignedAnalysesForPart(partId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncSelectedAnalysisRows() {
|
||||||
|
const modal = document.getElementById("analysisModal");
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
modal
|
||||||
|
.querySelectorAll(".analysis-analysis-item")
|
||||||
|
.forEach(function (item) {
|
||||||
|
const recordKey = item.getAttribute("data-recordkey") || "";
|
||||||
|
if (recordKey && analysisSelectedState[recordKey]) {
|
||||||
|
item.classList.add("analysis-selected");
|
||||||
|
} else {
|
||||||
|
item.classList.remove("analysis-selected");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAnalysisPayloadFromRow(rowEl) {
|
||||||
|
return {
|
||||||
|
analysis_recordkey: rowEl.getAttribute("data-recordkey") || "",
|
||||||
|
analysis_name: rowEl.getAttribute("data-analysis-name") || "",
|
||||||
|
analysis_method: rowEl.getAttribute("data-analysis-method") || "",
|
||||||
|
analysis_level: rowEl.getAttribute("data-analysis-level") || "",
|
||||||
|
is_web_selectable: rowEl.getAttribute("data-web") === "1" ? 1 : 0,
|
||||||
|
is_accredited:
|
||||||
|
rowEl.getAttribute("data-accredited") === "1" ? 1 : 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function addAnalysisToLocalState(partId, payload, iddatadb, idmatrice) {
|
||||||
|
const key = String(partId);
|
||||||
|
if (!Array.isArray(analysisAssignedState[key])) {
|
||||||
|
analysisAssignedState[key] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const exists = analysisAssignedState[key].some(function (item) {
|
||||||
|
return item.analysis_recordkey === payload.analysis_recordkey;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
analysisAssignedState[key].push({
|
||||||
|
id: null,
|
||||||
|
part_id: parseInt(partId, 10),
|
||||||
|
iddatadb: iddatadb || null,
|
||||||
|
idmatrice: idmatrice || null,
|
||||||
|
analysis_recordkey: payload.analysis_recordkey,
|
||||||
|
analysis_name: payload.analysis_name,
|
||||||
|
analysis_method: payload.analysis_method,
|
||||||
|
analysis_level:
|
||||||
|
payload.analysis_level !== ""
|
||||||
|
? parseInt(payload.analysis_level, 10)
|
||||||
|
: null,
|
||||||
|
is_web_selectable: payload.is_web_selectable,
|
||||||
|
is_accredited: payload.is_accredited,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeAnalysisFromLocalState(partId, recordKey) {
|
||||||
|
const key = String(partId);
|
||||||
|
if (!Array.isArray(analysisAssignedState[key])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
analysisAssignedState[key] = analysisAssignedState[key].filter(
|
||||||
|
function (item) {
|
||||||
|
return item.analysis_recordkey !== recordKey;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveAnalysisAssociation(partId, payload, callback) {
|
||||||
|
const modal = document.getElementById("analysisModal");
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
const iddatadb =
|
||||||
|
modal.querySelector("#analysisModalIddatadb")?.value || "";
|
||||||
|
const partItem = modal.querySelector(
|
||||||
|
'.analysis-part-item[data-part-id="' + partId + '"]',
|
||||||
|
);
|
||||||
|
const idmatrice = partItem
|
||||||
|
? partItem.getAttribute("data-matrix-id") || ""
|
||||||
|
: "";
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "save_part_analysis.php",
|
||||||
|
method: "POST",
|
||||||
|
dataType: "json",
|
||||||
|
data: {
|
||||||
|
part_id: partId,
|
||||||
|
iddatadb: iddatadb,
|
||||||
|
idmatrice: idmatrice !== "NO_MATRIX" ? idmatrice : "",
|
||||||
|
analysis_recordkey: payload.analysis_recordkey,
|
||||||
|
analysis_name: payload.analysis_name,
|
||||||
|
analysis_method: payload.analysis_method,
|
||||||
|
analysis_level: payload.analysis_level,
|
||||||
|
is_web_selectable: payload.is_web_selectable,
|
||||||
|
is_accredited: payload.is_accredited,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.done(function () {
|
||||||
|
addAnalysisToLocalState(
|
||||||
|
partId,
|
||||||
|
payload,
|
||||||
|
iddatadb,
|
||||||
|
idmatrice !== "NO_MATRIX" ? idmatrice : null,
|
||||||
|
);
|
||||||
|
renderAssignedAnalysesForPart(partId);
|
||||||
|
if (typeof callback === "function") callback(true);
|
||||||
|
})
|
||||||
|
.fail(function (xhr) {
|
||||||
|
let message = "Error saving analysis association";
|
||||||
|
if (xhr && xhr.responseJSON && xhr.responseJSON.message) {
|
||||||
|
message = xhr.responseJSON.message;
|
||||||
|
}
|
||||||
|
alert(message);
|
||||||
|
if (typeof callback === "function") callback(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteAnalysisAssociation(partId, recordKey, callback) {
|
||||||
|
$.ajax({
|
||||||
|
url: "delete_part_analysis.php",
|
||||||
|
method: "POST",
|
||||||
|
dataType: "json",
|
||||||
|
data: {
|
||||||
|
part_id: partId,
|
||||||
|
analysis_recordkey: recordKey,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.done(function () {
|
||||||
|
removeAnalysisFromLocalState(partId, recordKey);
|
||||||
|
renderAssignedAnalysesForPart(partId);
|
||||||
|
if (typeof callback === "function") callback(true);
|
||||||
|
})
|
||||||
|
.fail(function (xhr) {
|
||||||
|
let message = "Error deleting analysis association";
|
||||||
|
if (xhr && xhr.responseJSON && xhr.responseJSON.message) {
|
||||||
|
message = xhr.responseJSON.message;
|
||||||
|
}
|
||||||
|
alert(message);
|
||||||
|
if (typeof callback === "function") callback(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function setAnalysisLoadingState(isLoading) {
|
||||||
|
const modal = document.getElementById("analysisModal");
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
const loadingEl = modal.querySelector("#analysisLoadingBox");
|
||||||
|
if (!loadingEl) return;
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
loadingEl.classList.remove("d-none");
|
||||||
|
} else {
|
||||||
|
loadingEl.classList.add("d-none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAnalysisError(message) {
|
||||||
|
const modal = document.getElementById("analysisModal");
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
const errorEl = modal.querySelector("#analysisErrorBox");
|
||||||
|
const emptyEl = modal.querySelector("#analysisEmptyBox");
|
||||||
|
const listEl = modal.querySelector("#analysisList");
|
||||||
|
|
||||||
|
if (listEl) listEl.innerHTML = "";
|
||||||
|
if (emptyEl) emptyEl.classList.add("d-none");
|
||||||
|
|
||||||
|
if (errorEl) {
|
||||||
|
errorEl.textContent = message || "Error loading analyses";
|
||||||
|
errorEl.classList.remove("d-none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAnalysisEmpty(message) {
|
||||||
|
const modal = document.getElementById("analysisModal");
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
const errorEl = modal.querySelector("#analysisErrorBox");
|
||||||
|
const emptyEl = modal.querySelector("#analysisEmptyBox");
|
||||||
|
const listEl = modal.querySelector("#analysisList");
|
||||||
|
|
||||||
|
if (listEl) listEl.innerHTML = "";
|
||||||
|
if (errorEl) errorEl.classList.add("d-none");
|
||||||
|
|
||||||
|
if (emptyEl) {
|
||||||
|
emptyEl.textContent =
|
||||||
|
message || "No analyses found for this matrix";
|
||||||
|
emptyEl.classList.remove("d-none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(value) {
|
||||||
|
return String(value ?? "")
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """)
|
||||||
|
.replace(/'/g, "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderAnalysesList(analyses) {
|
||||||
|
const modal = document.getElementById("analysisModal");
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
const errorEl = modal.querySelector("#analysisErrorBox");
|
||||||
|
const emptyEl = modal.querySelector("#analysisEmptyBox");
|
||||||
|
const listEl = modal.querySelector("#analysisList");
|
||||||
|
|
||||||
|
if (!listEl) return;
|
||||||
|
|
||||||
|
if (errorEl) errorEl.classList.add("d-none");
|
||||||
|
|
||||||
|
if (!Array.isArray(analyses) || analyses.length === 0) {
|
||||||
|
showAnalysisEmpty("No analyses found for this matrix");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emptyEl) emptyEl.classList.add("d-none");
|
||||||
|
|
||||||
|
listEl.innerHTML = analyses
|
||||||
|
.map(function (item) {
|
||||||
|
const recordKey = item.RecordKey || "";
|
||||||
|
const analysisName =
|
||||||
|
item.NomeAnalisiTraduzione ||
|
||||||
|
item.NomeAnalisi ||
|
||||||
|
"Unnamed analysis";
|
||||||
|
const methodName = item.MetodoNome || "";
|
||||||
|
const selectable = item.SelezionabileSuWeb === true;
|
||||||
|
const accredited = item.Accreditato === true;
|
||||||
|
const level = item.Livello ?? "";
|
||||||
|
const searchText = (
|
||||||
|
analysisName +
|
||||||
|
" " +
|
||||||
|
methodName
|
||||||
|
).toLowerCase();
|
||||||
|
|
||||||
|
let badges = "";
|
||||||
|
if (selectable) {
|
||||||
|
badges += '<span class="badge bg-success">Web</span>';
|
||||||
|
} else {
|
||||||
|
badges += '<span class="badge bg-secondary">Not web</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accredited) {
|
||||||
|
badges +=
|
||||||
|
'<span class="badge bg-info text-dark">Accredited</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level !== "") {
|
||||||
|
badges +=
|
||||||
|
'<span class="badge bg-light text-dark border">Level ' +
|
||||||
|
escapeHtml(level) +
|
||||||
|
"</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div
|
||||||
|
class="list-group-item analysis-analysis-item"
|
||||||
|
data-recordkey="${escapeHtml(recordKey)}"
|
||||||
|
data-analysis-name="${escapeHtml(analysisName)}"
|
||||||
|
data-analysis-method="${escapeHtml(methodName)}"
|
||||||
|
data-analysis-level="${escapeHtml(level)}"
|
||||||
|
data-web="${selectable ? "1" : "0"}"
|
||||||
|
data-accredited="${accredited ? "1" : "0"}"
|
||||||
|
data-search="${escapeHtml(searchText)}"
|
||||||
|
>
|
||||||
|
<div class="fw-semibold">${escapeHtml(analysisName)}</div>
|
||||||
|
|
||||||
|
${methodName ? `<div class="analysis-analysis-meta mt-1">${escapeHtml(methodName)}</div>` : ""}
|
||||||
|
|
||||||
|
<div class="analysis-analysis-badges mt-2">
|
||||||
|
${badges}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
filterAnalysisList();
|
||||||
|
syncSelectedAnalysisRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterAnalysisList() {
|
||||||
|
const modal = document.getElementById("analysisModal");
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
const webOnlyEl = modal.querySelector("#analysisWebOnly");
|
||||||
|
const searchEl = modal.querySelector("#analysisSearchInput");
|
||||||
|
const items = modal.querySelectorAll(".analysis-analysis-item");
|
||||||
|
const emptyEl = modal.querySelector("#analysisEmptyBox");
|
||||||
|
const errorEl = modal.querySelector("#analysisErrorBox");
|
||||||
|
|
||||||
|
const webOnly = true;
|
||||||
|
const searchValue = searchEl ? searchEl.value.trim().toLowerCase() : "";
|
||||||
|
|
||||||
|
let visibleCount = 0;
|
||||||
|
|
||||||
|
items.forEach((item) => {
|
||||||
|
const isWeb = item.getAttribute("data-web") === "1";
|
||||||
|
const searchText = (
|
||||||
|
item.getAttribute("data-search") || ""
|
||||||
|
).toLowerCase();
|
||||||
|
|
||||||
|
let visible = true;
|
||||||
|
|
||||||
|
if (webOnly && !isWeb) {
|
||||||
|
visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visible && searchValue && !searchText.includes(searchValue)) {
|
||||||
|
visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.style.display = visible ? "" : "none";
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
visibleCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (errorEl) {
|
||||||
|
errorEl.classList.add("d-none");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emptyEl) {
|
||||||
|
if (items.length === 0) {
|
||||||
|
emptyEl.textContent = "No analyses found for this matrix";
|
||||||
|
emptyEl.classList.remove("d-none");
|
||||||
|
} else if (visibleCount === 0) {
|
||||||
|
emptyEl.textContent = "No analyses match the current filters";
|
||||||
|
emptyEl.classList.remove("d-none");
|
||||||
|
} else {
|
||||||
|
emptyEl.classList.add("d-none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadAnalysesByMatrix(matrixId) {
|
||||||
|
const modal = document.getElementById("analysisModal");
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
if (!matrixId || matrixId === "NO_MATRIX") {
|
||||||
|
showAnalysisEmpty("No matrix selected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const listEl = modal.querySelector("#analysisList");
|
||||||
|
const errorEl = modal.querySelector("#analysisErrorBox");
|
||||||
|
const emptyEl = modal.querySelector("#analysisEmptyBox");
|
||||||
|
|
||||||
|
if (listEl) listEl.innerHTML = "";
|
||||||
|
if (errorEl) errorEl.classList.add("d-none");
|
||||||
|
if (emptyEl) {
|
||||||
|
emptyEl.textContent = "";
|
||||||
|
emptyEl.classList.add("d-none");
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheKey = String(matrixId) + "_WEB_ONLY";
|
||||||
|
|
||||||
|
if (analysisLoadedCache[cacheKey]) {
|
||||||
|
renderAnalysesList(analysisLoadedCache[cacheKey]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAnalysisLoadingState(true);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "get_analisi_matrice_filter.php",
|
||||||
|
method: "GET",
|
||||||
|
dataType: "json",
|
||||||
|
data: {
|
||||||
|
id_matrice: matrixId,
|
||||||
|
web_only: 1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.done(function (response) {
|
||||||
|
const analyses = Array.isArray(response.value)
|
||||||
|
? response.value.filter(function (item) {
|
||||||
|
return (
|
||||||
|
item.SelezionabileSuWeb === true ||
|
||||||
|
item.SelezionabileSuWeb === 1 ||
|
||||||
|
item.SelezionabileSuWeb === "1"
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
|
||||||
|
analysisLoadedCache[cacheKey] = analyses;
|
||||||
|
renderAnalysesList(analyses);
|
||||||
|
})
|
||||||
|
.fail(function (xhr) {
|
||||||
|
let message = "Error loading analyses";
|
||||||
|
if (xhr && xhr.responseJSON && xhr.responseJSON.error) {
|
||||||
|
message = xhr.responseJSON.error;
|
||||||
|
}
|
||||||
|
showAnalysisError(message);
|
||||||
|
})
|
||||||
|
.always(function () {
|
||||||
|
setAnalysisLoadingState(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSelectedPartsInfo() {
|
||||||
|
const modal = document.getElementById("analysisModal");
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
const checked = modal.querySelectorAll(
|
||||||
|
".analysis-part-checkbox:checked",
|
||||||
|
);
|
||||||
|
const ids = Array.from(checked).map((el) => el.value);
|
||||||
|
|
||||||
|
const countEl = modal.querySelector("#analysisSelectedPartsCount");
|
||||||
|
const idsEl = modal.querySelector("#analysisSelectedPartsIds");
|
||||||
|
|
||||||
|
if (countEl) {
|
||||||
|
countEl.textContent = `${ids.length} selected`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idsEl) {
|
||||||
|
idsEl.textContent = ids.length ? ids.join(", ") : "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
modal.querySelectorAll(".analysis-part-item").forEach((item) => {
|
||||||
|
const checkbox = item.querySelector(".analysis-part-checkbox");
|
||||||
|
if (checkbox && checkbox.checked) {
|
||||||
|
item.classList.add("part-checked");
|
||||||
|
} else {
|
||||||
|
item.classList.remove("part-checked");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectPartsByMatrix(matrixId, matrixLabel) {
|
||||||
|
const modal = document.getElementById("analysisModal");
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
const partItems = modal.querySelectorAll(".analysis-part-item");
|
||||||
|
const matrixLabelEl = modal.querySelector("#analysisCurrentMatrix");
|
||||||
|
|
||||||
|
partItems.forEach((item) => {
|
||||||
|
const itemMatrixId = item.getAttribute("data-matrix-id");
|
||||||
|
const checkbox = item.querySelector(".analysis-part-checkbox");
|
||||||
|
|
||||||
|
item.classList.remove("matrix-active");
|
||||||
|
|
||||||
|
if (itemMatrixId === String(matrixId)) {
|
||||||
|
item.classList.add("matrix-active");
|
||||||
|
if (checkbox) checkbox.checked = true;
|
||||||
|
} else {
|
||||||
|
if (checkbox) checkbox.checked = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (matrixLabelEl) {
|
||||||
|
matrixLabelEl.textContent = matrixLabel || "-";
|
||||||
|
}
|
||||||
|
analysisSelectedState = {};
|
||||||
|
|
||||||
|
const selectedPartIds = getSelectedPartIds();
|
||||||
|
selectedPartIds.forEach(function (partId) {
|
||||||
|
const assigned = Array.isArray(
|
||||||
|
analysisAssignedState[String(partId)],
|
||||||
|
)
|
||||||
|
? analysisAssignedState[String(partId)]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
assigned.forEach(function (item) {
|
||||||
|
if (item.analysis_recordkey) {
|
||||||
|
analysisSelectedState[item.analysis_recordkey] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
updateSelectedPartsInfo();
|
||||||
|
loadAnalysesByMatrix(matrixId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAnalysisSelection() {
|
||||||
|
const modal = document.getElementById("analysisModal");
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
modal.querySelectorAll(".analysis-matrix-item").forEach((item) => {
|
||||||
|
item.classList.remove("active");
|
||||||
|
});
|
||||||
|
|
||||||
|
modal.querySelectorAll(".analysis-part-item").forEach((item) => {
|
||||||
|
item.classList.remove("matrix-active", "part-checked");
|
||||||
|
});
|
||||||
|
|
||||||
|
modal.querySelectorAll(".analysis-part-checkbox").forEach((cb) => {
|
||||||
|
cb.checked = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const matrixLabelEl = modal.querySelector("#analysisCurrentMatrix");
|
||||||
|
if (matrixLabelEl) matrixLabelEl.textContent = "-";
|
||||||
|
|
||||||
|
const webOnlyEl = modal.querySelector("#analysisWebOnly");
|
||||||
|
if (webOnlyEl) webOnlyEl.checked = false;
|
||||||
|
|
||||||
|
const searchEl = modal.querySelector("#analysisSearchInput");
|
||||||
|
if (searchEl) searchEl.value = "";
|
||||||
|
|
||||||
|
const listEl = modal.querySelector("#analysisList");
|
||||||
|
if (listEl) listEl.innerHTML = "";
|
||||||
|
|
||||||
|
showAnalysisEmpty("Select a matrix to load analyses");
|
||||||
|
updateSelectedPartsInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
function initAnalysisModal() {
|
||||||
|
const modal = document.getElementById("analysisModal");
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
analysisLoadedCache = {};
|
||||||
|
analysisSelectedState = {};
|
||||||
|
readInitialAssignedAnalyses();
|
||||||
|
renderAssignedAnalysesForSelectedParts();
|
||||||
|
|
||||||
|
modal.querySelectorAll(".analysis-matrix-item").forEach((btn) => {
|
||||||
|
btn.addEventListener("click", function () {
|
||||||
|
modal
|
||||||
|
.querySelectorAll(".analysis-matrix-item")
|
||||||
|
.forEach((x) => x.classList.remove("active"));
|
||||||
|
this.classList.add("active");
|
||||||
|
|
||||||
|
const matrixId = this.getAttribute("data-matrix-id");
|
||||||
|
const matrixLabel =
|
||||||
|
this.getAttribute("data-matrix-name") ||
|
||||||
|
this.textContent.trim();
|
||||||
|
|
||||||
|
selectPartsByMatrix(matrixId, matrixLabel);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modal.querySelectorAll(".analysis-part-checkbox").forEach((cb) => {
|
||||||
|
cb.addEventListener("change", function () {
|
||||||
|
updateSelectedPartsInfo();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const clearBtn = modal.querySelector("#analysisClearSelectionBtn");
|
||||||
|
if (clearBtn) {
|
||||||
|
clearBtn.addEventListener("click", function () {
|
||||||
|
clearAnalysisSelection();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// WEB only is now fixed by default
|
||||||
|
|
||||||
|
const searchEl = modal.querySelector("#analysisSearchInput");
|
||||||
|
if (searchEl) {
|
||||||
|
searchEl.addEventListener("input", function () {
|
||||||
|
filterAnalysisList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
modal.addEventListener("click", function (e) {
|
||||||
|
const removeBtn = e.target.closest(".analysis-remove-btn");
|
||||||
|
if (removeBtn) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const partId = removeBtn.getAttribute("data-part-id");
|
||||||
|
const recordKey = removeBtn.getAttribute("data-recordkey");
|
||||||
|
|
||||||
|
deleteAnalysisAssociation(partId, recordKey, function () {
|
||||||
|
const stillUsed = Object.keys(analysisAssignedState).some(
|
||||||
|
function (pid) {
|
||||||
|
return (
|
||||||
|
Array.isArray(analysisAssignedState[pid]) &&
|
||||||
|
analysisAssignedState[pid].some(
|
||||||
|
function (item) {
|
||||||
|
return (
|
||||||
|
item.analysis_recordkey ===
|
||||||
|
recordKey
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!stillUsed) {
|
||||||
|
delete analysisSelectedState[recordKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
syncSelectedAnalysisRows();
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const row = e.target.closest(".analysis-analysis-item");
|
||||||
|
if (!row) return;
|
||||||
|
|
||||||
|
const selectedPartIds = getSelectedPartIds();
|
||||||
|
if (!selectedPartIds.length) {
|
||||||
|
alert("Select at least one part first");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = buildAnalysisPayloadFromRow(row);
|
||||||
|
if (!payload.analysis_recordkey) {
|
||||||
|
alert("Invalid analysis record key");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const recordKey = payload.analysis_recordkey;
|
||||||
|
const alreadySelected = !!analysisSelectedState[recordKey];
|
||||||
|
|
||||||
|
if (alreadySelected) {
|
||||||
|
selectedPartIds.forEach(function (partId) {
|
||||||
|
deleteAnalysisAssociation(partId, recordKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
delete analysisSelectedState[recordKey];
|
||||||
|
syncSelectedAnalysisRows();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pending = selectedPartIds.length;
|
||||||
|
let atLeastOneSaved = false;
|
||||||
|
|
||||||
|
selectedPartIds.forEach(function (partId) {
|
||||||
|
saveAnalysisAssociation(partId, payload, function (ok) {
|
||||||
|
if (ok) atLeastOneSaved = true;
|
||||||
|
|
||||||
|
pending--;
|
||||||
|
if (pending <= 0 && atLeastOneSaved) {
|
||||||
|
analysisSelectedState[recordKey] = true;
|
||||||
|
syncSelectedAnalysisRows();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const firstActiveMatrix = modal.querySelector(
|
||||||
|
".analysis-matrix-item.active",
|
||||||
|
);
|
||||||
|
if (firstActiveMatrix) {
|
||||||
|
const matrixId = firstActiveMatrix.getAttribute("data-matrix-id");
|
||||||
|
const matrixLabel =
|
||||||
|
firstActiveMatrix.getAttribute("data-matrix-name") ||
|
||||||
|
firstActiveMatrix.textContent.trim();
|
||||||
|
|
||||||
|
selectPartsByMatrix(matrixId, matrixLabel);
|
||||||
|
} else {
|
||||||
|
updateSelectedPartsInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OPEN ANALYSIS MODAL FROM PARTS MODAL BUTTON
|
||||||
|
$(document).on("click", ".open-analysis-modal-btn", function () {
|
||||||
|
const partsModal = document.getElementById("partsModal");
|
||||||
|
const iddatadb = $("#partsModal").data("iddatadb");
|
||||||
|
|
||||||
|
if (!iddatadb) {
|
||||||
|
console.error("iddatadb not found on #partsModal");
|
||||||
|
alert("iddatadb not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "modal_analysis.php",
|
||||||
|
method: "GET",
|
||||||
|
data: { iddatadb: iddatadb },
|
||||||
|
success: function (response) {
|
||||||
|
$("#analysisModalContainer").html(response);
|
||||||
|
|
||||||
|
const modalElement = document.getElementById("analysisModal");
|
||||||
|
if (!modalElement) {
|
||||||
|
console.error("Analysis modal not found: #analysisModal");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let modal = bootstrap.Modal.getInstance(modalElement);
|
||||||
|
if (!modal) {
|
||||||
|
modal = new bootstrap.Modal(modalElement, {
|
||||||
|
backdrop: true,
|
||||||
|
keyboard: true,
|
||||||
|
focus: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAnalysisMatrixNames().always(function () {
|
||||||
|
initAnalysisModal();
|
||||||
|
modal.show();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function (xhr, status, error) {
|
||||||
|
console.error("Error loading analysis modal:", error);
|
||||||
|
alert("Error loading analysis modal: " + error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// CLEANUP ON CLOSE
|
||||||
|
$(document).on("hidden.bs.modal", "#analysisModal", function () {
|
||||||
|
const modalElement = document.getElementById("analysisModal");
|
||||||
|
if (modalElement) {
|
||||||
|
const modal = bootstrap.Modal.getInstance(modalElement);
|
||||||
|
if (modal) modal.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#analysisModalContainer").empty();
|
||||||
|
|
||||||
|
// keep parts modal alive, but remove extra backdrop leftovers
|
||||||
|
$(".modal-backdrop").last().remove();
|
||||||
|
|
||||||
|
if ($("#partsModal").hasClass("show")) {
|
||||||
|
$("body").addClass("modal-open");
|
||||||
|
} else {
|
||||||
|
$("body").removeClass("modal-open").css("padding-right", "");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
ob_start();
|
||||||
|
session_start();
|
||||||
|
require_once '../../vendor/autoload.php';
|
||||||
|
|
||||||
|
Dotenv\Dotenv::createImmutable(dirname(__DIR__, 2))->safeLoad();
|
||||||
|
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
|
||||||
|
|
||||||
|
$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;
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<?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);
|
||||||
@@ -0,0 +1,449 @@
|
|||||||
|
<?php
|
||||||
|
include('include/headscript.php');
|
||||||
|
require_once(__DIR__ . '/class/binding-functions.php');
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
// Filtri comuni.
|
||||||
|
$templateFilter = isset($_GET['template_id']) ? intval($_GET['template_id']) : 0;
|
||||||
|
$search = trim($_GET['q'] ?? '');
|
||||||
|
$perPage = 50;
|
||||||
|
$page = max(1, intval($_GET['page'] ?? 1));
|
||||||
|
$dir = (strtolower($_GET['dir'] ?? 'asc') === 'desc') ? 'DESC' : 'ASC';
|
||||||
|
|
||||||
|
// Modalita': overview (gruppi per campo) oppure detail (valori di un campo).
|
||||||
|
$focusTarget = trim($_GET['target'] ?? '');
|
||||||
|
$mode = $focusTarget !== '' ? 'detail' : 'overview';
|
||||||
|
|
||||||
|
$templates = $pdo->query("SELECT id, name FROM excel_templates ORDER BY name")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Helper: URL che preserva i filtri correnti.
|
||||||
|
$bmUrl = function (array $ov) use ($templateFilter, $search, $dir, $page, $focusTarget) {
|
||||||
|
$base = [
|
||||||
|
'template_id' => $templateFilter ?: null,
|
||||||
|
'q' => $search !== '' ? $search : null,
|
||||||
|
'order' => $_GET['order'] ?? null,
|
||||||
|
'dir' => strtolower($dir),
|
||||||
|
'page' => $page,
|
||||||
|
'target' => $focusTarget !== '' ? $focusTarget : null,
|
||||||
|
];
|
||||||
|
$p = array_filter(array_merge($base, $ov), fn($v) => $v !== null && $v !== '');
|
||||||
|
return 'bindings_manage.php?' . http_build_query($p);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filtri WHERE comuni (template/kind/search).
|
||||||
|
$conds = [];
|
||||||
|
$params = [];
|
||||||
|
if ($templateFilter > 0) {
|
||||||
|
$conds[] = 'b.template_id = ?';
|
||||||
|
$params[] = $templateFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($mode === 'detail') {
|
||||||
|
// ---- DETAIL: valori (json_value -> lims) di un singolo campo (target_key) ----
|
||||||
|
$conds[] = 'b.target_key = ?';
|
||||||
|
$params[] = $focusTarget;
|
||||||
|
if ($search !== '') {
|
||||||
|
$conds[] = '(b.json_value LIKE ? OR b.lims_value LIKE ?)';
|
||||||
|
$like = '%' . $search . '%';
|
||||||
|
array_push($params, $like, $like);
|
||||||
|
}
|
||||||
|
$where = 'WHERE ' . implode(' AND ', $conds);
|
||||||
|
|
||||||
|
$orderCols = ['json' => 'b.json_value', 'lims' => 'b.lims_value'];
|
||||||
|
$order = array_key_exists($_GET['order'] ?? '', $orderCols) ? $_GET['order'] : 'json';
|
||||||
|
$orderSql = $orderCols[$order] . ' ' . $dir;
|
||||||
|
|
||||||
|
$countStmt = $pdo->prepare("SELECT COUNT(*) FROM json_lims_binding b $where");
|
||||||
|
$countStmt->execute($params);
|
||||||
|
$total = (int) $countStmt->fetchColumn();
|
||||||
|
$totalPages = max(1, (int) ceil($total / $perPage));
|
||||||
|
if ($page > $totalPages) $page = $totalPages;
|
||||||
|
$offset = ($page - 1) * $perPage;
|
||||||
|
|
||||||
|
$sql = "SELECT b.id, b.template_id, b.binding_kind, b.mapping_id, b.fixed_field_key, b.field_id,
|
||||||
|
b.json_value, b.lims_value_id, b.lims_value,
|
||||||
|
t.name AS template_name, m.field_label
|
||||||
|
FROM json_lims_binding b
|
||||||
|
LEFT JOIN excel_templates t ON t.id = b.template_id
|
||||||
|
LEFT JOIN template_mapping m ON m.id = b.mapping_id
|
||||||
|
$where
|
||||||
|
ORDER BY $orderSql, b.json_value ASC
|
||||||
|
LIMIT $perPage OFFSET $offset";
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute($params);
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Intestazione del gruppo (tutte le righe condividono campo/template/kind).
|
||||||
|
$head = $rows[0] ?? null;
|
||||||
|
if (!$head) {
|
||||||
|
// Target vuoto (es. dopo aver svuotato il campo): ricavo i meta dal target_key.
|
||||||
|
$head = ['binding_kind' => str_starts_with($focusTarget, 'fx:') ? 'fixed' : 'custom'];
|
||||||
|
}
|
||||||
|
$headKind = $head['binding_kind'] ?? 'custom';
|
||||||
|
if ($headKind === 'fixed') {
|
||||||
|
$headFixedKey = $head['fixed_field_key'] ?? (explode(':', $focusTarget)[2] ?? '');
|
||||||
|
$headLabel = binding_fixed_label((string) $headFixedKey);
|
||||||
|
$headTpl = (int) ($head['template_id'] ?? (explode(':', $focusTarget)[1] ?? 0));
|
||||||
|
} else {
|
||||||
|
$headLabel = $head['field_label'] ?? ('mapping ' . ($head['mapping_id'] ?? ''));
|
||||||
|
$headTpl = (int) ($head['template_id'] ?? 0);
|
||||||
|
}
|
||||||
|
$headTplName = $headTpl ? ($pdo->query("SELECT name FROM excel_templates WHERE id=" . $headTpl)->fetchColumn() ?: ('#' . $headTpl)) : '';
|
||||||
|
} else {
|
||||||
|
// ---- OVERVIEW: un gruppo per campo (target_key) con il conteggio ----
|
||||||
|
if ($search !== '') {
|
||||||
|
$conds[] = '(t.name LIKE ? OR m.field_label LIKE ? OR b.fixed_field_key LIKE ?)';
|
||||||
|
$like = '%' . $search . '%';
|
||||||
|
array_push($params, $like, $like, $like);
|
||||||
|
}
|
||||||
|
$where = $conds ? ('WHERE ' . implode(' AND ', $conds)) : '';
|
||||||
|
|
||||||
|
$orderCols = ['template' => 'template_name', 'field' => 'field_label', 'count' => 'cnt'];
|
||||||
|
$order = array_key_exists($_GET['order'] ?? '', $orderCols) ? $_GET['order'] : 'template';
|
||||||
|
$orderSql = $orderCols[$order] . ' ' . $dir;
|
||||||
|
|
||||||
|
$countStmt = $pdo->prepare("SELECT COUNT(DISTINCT b.target_key)
|
||||||
|
FROM json_lims_binding b
|
||||||
|
LEFT JOIN excel_templates t ON t.id = b.template_id
|
||||||
|
LEFT JOIN template_mapping m ON m.id = b.mapping_id $where");
|
||||||
|
$countStmt->execute($params);
|
||||||
|
$total = (int) $countStmt->fetchColumn();
|
||||||
|
$totalPages = max(1, (int) ceil($total / $perPage));
|
||||||
|
if ($page > $totalPages) $page = $totalPages;
|
||||||
|
$offset = ($page - 1) * $perPage;
|
||||||
|
|
||||||
|
$sql = "SELECT b.target_key, b.template_id, b.binding_kind, b.mapping_id, b.fixed_field_key,
|
||||||
|
MAX(t.name) AS template_name, MAX(m.field_label) AS field_label,
|
||||||
|
COUNT(*) AS cnt, MAX(b.updated_at) AS last_updated
|
||||||
|
FROM json_lims_binding b
|
||||||
|
LEFT JOIN excel_templates t ON t.id = b.template_id
|
||||||
|
LEFT JOIN template_mapping m ON m.id = b.mapping_id
|
||||||
|
$where
|
||||||
|
GROUP BY b.target_key, b.template_id, b.binding_kind, b.mapping_id, b.fixed_field_key
|
||||||
|
ORDER BY $orderSql, template_name ASC
|
||||||
|
LIMIT $perPage OFFSET $offset";
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute($params);
|
||||||
|
$groups = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sortLink = function (string $col, string $label) use ($bmUrl, $order, $dir) {
|
||||||
|
$nextDir = ($order === $col && $dir === 'ASC') ? 'desc' : 'asc';
|
||||||
|
$caret = $order === $col ? ($dir === 'ASC' ? ' ▲' : ' ▼') : '';
|
||||||
|
return '<a href="' . htmlspecialchars($bmUrl(['order' => $col, 'dir' => $nextDir, 'page' => 1]))
|
||||||
|
. '" class="text-decoration-none text-reset">' . $label . $caret . '</a>';
|
||||||
|
};
|
||||||
|
?>
|
||||||
|
<!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" />
|
||||||
|
<?php include('cssinclude.php'); ?>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
.json-value {
|
||||||
|
font-family: Consolas, Monaco, monospace;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
td .select2-container {
|
||||||
|
min-width: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-status {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-row {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-row:hover {
|
||||||
|
background-color: #f1f5ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<title>Gestione Binding JSON → LIMS - <?= 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 radius-10">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
|
||||||
|
<h6 class="mb-0">Gestione Binding JSON → LIMS</h6>
|
||||||
|
<form method="GET" class="d-flex align-items-center gap-2 flex-wrap mb-0">
|
||||||
|
<?php if ($mode === 'detail'): ?>
|
||||||
|
<input type="hidden" name="target" value="<?= htmlspecialchars($focusTarget) ?>">
|
||||||
|
<?php endif; ?>
|
||||||
|
<input type="hidden" name="dir" value="<?= htmlspecialchars(strtolower($dir)) ?>">
|
||||||
|
<input type="text" name="q" class="form-control form-control-sm" style="width:220px;"
|
||||||
|
placeholder="<?= $mode === 'detail' ? 'Cerca valore...' : 'Cerca campo/template...' ?>"
|
||||||
|
value="<?= htmlspecialchars($search) ?>">
|
||||||
|
<?php if ($mode === 'overview'): ?>
|
||||||
|
<select name="template_id" class="form-select form-select-sm" style="width:auto;" onchange="this.form.submit()">
|
||||||
|
<option value="0">Tutti i template</option>
|
||||||
|
<?php foreach ($templates as $t): ?>
|
||||||
|
<option value="<?= (int) $t['id'] ?>" <?= $templateFilter === (int) $t['id'] ? 'selected' : '' ?>>
|
||||||
|
<?= htmlspecialchars($t['name']) ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
<?php endif; ?>
|
||||||
|
<button type="submit" class="btn btn-sm btn-primary">Cerca</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<div id="globalError" class="alert alert-danger" style="display:none;"></div>
|
||||||
|
|
||||||
|
<?php if ($mode === 'detail'): ?>
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2 flex-wrap gap-2">
|
||||||
|
<div>
|
||||||
|
<a href="<?= htmlspecialchars($bmUrl(['target' => null, 'page' => 1, 'order' => null])) ?>" class="btn btn-sm btn-outline-secondary">
|
||||||
|
<i class="fas fa-arrow-left"></i> Tutti i campi
|
||||||
|
</a>
|
||||||
|
<span class="ms-2">
|
||||||
|
<strong><?= htmlspecialchars($headLabel) ?></strong>
|
||||||
|
<?php if ($headKind === 'fixed'): ?><span class="badge bg-light text-dark border">fixed</span><?php endif; ?>
|
||||||
|
<span class="text-muted">· <?= htmlspecialchars((string) $headTplName) ?></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span class="small text-muted"><?= $total ?> valore/i · pagina <?= $page ?>/<?= $totalPages ?></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-bordered align-middle" id="bindingsTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><?= $sortLink('json', 'Valore JSON') ?></th>
|
||||||
|
<th><?= $sortLink('lims', 'Valore LIMS') ?></th>
|
||||||
|
<th style="width:170px;">Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($rows)): ?>
|
||||||
|
<tr class="no-data-row">
|
||||||
|
<td colspan="3" class="text-center text-muted">Nessun valore per questo campo.</td>
|
||||||
|
</tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($rows as $b): ?>
|
||||||
|
<?php $bDangling = ($b['binding_kind'] === 'custom' && $b['field_label'] === null); ?>
|
||||||
|
<tr data-id="<?= (int) $b['id'] ?>"
|
||||||
|
data-kind="<?= $b['binding_kind'] ?>"
|
||||||
|
data-mapping-id="<?= (int) $b['mapping_id'] ?>"
|
||||||
|
data-field-id="<?= (int) $b['field_id'] ?>"
|
||||||
|
data-fixed-key="<?= htmlspecialchars((string) $b['fixed_field_key'], ENT_QUOTES) ?>"
|
||||||
|
data-template-id="<?= (int) $b['template_id'] ?>"
|
||||||
|
data-json-value="<?= htmlspecialchars($b['json_value'], ENT_QUOTES) ?>">
|
||||||
|
<td class="json-value"><?= htmlspecialchars($b['json_value']) ?></td>
|
||||||
|
<td>
|
||||||
|
<select class="form-select binding-select" <?= $bDangling ? 'disabled' : '' ?>>
|
||||||
|
<?php if ($b['lims_value_id']): ?>
|
||||||
|
<option value="<?= (int) $b['lims_value_id'] ?>" selected><?= htmlspecialchars($b['lims_value']) ?></option>
|
||||||
|
<?php endif; ?>
|
||||||
|
</select>
|
||||||
|
<span class="row-status text-muted"></span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" class="btn btn-sm btn-success save-binding-btn" disabled>
|
||||||
|
<i class="fas fa-save"></i> Salva
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-danger delete-binding-btn">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="alert alert-secondary small">
|
||||||
|
Scegli un campo per gestirne i valori. Le modifiche valgono per le <strong>importazioni future</strong>.
|
||||||
|
</div>
|
||||||
|
<div class="small text-muted mb-2"><?= $total ?> camp<?= $total === 1 ? 'o' : 'i' ?> con binding · pagina <?= $page ?>/<?= $totalPages ?></div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-bordered align-middle">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><?= $sortLink('template', 'Template') ?></th>
|
||||||
|
<th><?= $sortLink('field', 'Campo') ?></th>
|
||||||
|
<th style="width:120px;"><?= $sortLink('count', '# Binding') ?></th>
|
||||||
|
<th style="width:90px;"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($groups)): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-center text-muted">Nessun binding presente.</td>
|
||||||
|
</tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($groups as $g): ?>
|
||||||
|
<?php
|
||||||
|
$gKind = $g['binding_kind'];
|
||||||
|
$gDangling = ($gKind === 'custom' && $g['field_label'] === null);
|
||||||
|
$gLabel = $gKind === 'fixed'
|
||||||
|
? binding_fixed_label((string) $g['fixed_field_key'])
|
||||||
|
: ($g['field_label'] ?? ('mapping ' . $g['mapping_id']));
|
||||||
|
$gUrl = $bmUrl(['target' => $g['target_key'], 'page' => 1, 'order' => null, 'q' => null]);
|
||||||
|
?>
|
||||||
|
<tr class="group-row" onclick="window.location.href='<?= htmlspecialchars($gUrl) ?>'">
|
||||||
|
<td><?= htmlspecialchars($g['template_name'] ?? ('#' . $g['template_id'])) ?></td>
|
||||||
|
<td>
|
||||||
|
<?= htmlspecialchars($gLabel) ?>
|
||||||
|
<?php if ($gKind === 'fixed'): ?><span class="badge bg-light text-dark border">fixed</span><?php endif; ?>
|
||||||
|
<?php if ($gDangling): ?><span class="badge bg-warning text-dark" title="Il campo mappato non esiste piu'">campo rimosso</span><?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td><span class="badge bg-primary"><?= (int) $g['cnt'] ?></span></td>
|
||||||
|
<td><a href="<?= htmlspecialchars($gUrl) ?>" class="btn btn-sm btn-outline-primary">Apri</a></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($totalPages > 1): ?>
|
||||||
|
<nav class="d-flex justify-content-center mt-2">
|
||||||
|
<ul class="pagination pagination-sm mb-0">
|
||||||
|
<li class="page-item <?= $page <= 1 ? 'disabled' : '' ?>">
|
||||||
|
<a class="page-link" href="<?= htmlspecialchars($bmUrl(['page' => max(1, $page - 1)])) ?>">«</a>
|
||||||
|
</li>
|
||||||
|
<li class="page-item disabled"><span class="page-link"><?= $page ?> / <?= $totalPages ?></span></li>
|
||||||
|
<li class="page-item <?= $page >= $totalPages ? 'disabled' : '' ?>">
|
||||||
|
<a class="page-link" href="<?= htmlspecialchars($bmUrl(['page' => min($totalPages, $page + 1)])) ?>">»</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<?php endif; ?>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(function() {
|
||||||
|
const $globalError = $('#globalError');
|
||||||
|
|
||||||
|
// Select2 solo nelle righe valore della pagina corrente (max 50), saltando le disabilitate.
|
||||||
|
$('.binding-select').not(':disabled').each(function() {
|
||||||
|
const $row = $(this).closest('tr');
|
||||||
|
const isFixed = $row.data('kind') === 'fixed';
|
||||||
|
const initialVal = $(this).val();
|
||||||
|
|
||||||
|
$(this).select2({
|
||||||
|
width: '220px',
|
||||||
|
ajax: {
|
||||||
|
url: isFixed ? 'search_fixed_field_values.php' : 'search_customfield_values.php',
|
||||||
|
dataType: 'json',
|
||||||
|
delay: 200,
|
||||||
|
data: params => isFixed ? {
|
||||||
|
field_key: $row.data('fixed-key'),
|
||||||
|
template_id: $row.data('template-id'),
|
||||||
|
q: params.term || '',
|
||||||
|
limit: 50
|
||||||
|
} : {
|
||||||
|
field_id: $row.data('field-id'),
|
||||||
|
q: params.term || '',
|
||||||
|
limit: 50
|
||||||
|
},
|
||||||
|
processResults: data => ({
|
||||||
|
results: data.results || []
|
||||||
|
})
|
||||||
|
},
|
||||||
|
minimumInputLength: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
$(this).data('original-val', initialVal);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.binding-select').on('change', function() {
|
||||||
|
const $row = $(this).closest('tr');
|
||||||
|
const changed = String($(this).val()) !== String($(this).data('original-val'));
|
||||||
|
$row.find('.save-binding-btn').prop('disabled', !changed);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.save-binding-btn').on('click', function() {
|
||||||
|
const $row = $(this).closest('tr');
|
||||||
|
const $select = $row.find('.binding-select');
|
||||||
|
const selectedData = $select.select2('data')[0] || {};
|
||||||
|
const $status = $row.find('.row-status');
|
||||||
|
const $btn = $(this);
|
||||||
|
|
||||||
|
$globalError.hide();
|
||||||
|
$btn.prop('disabled', true);
|
||||||
|
$status.text('Salvataggio...').removeClass('text-success text-danger').addClass('text-muted');
|
||||||
|
|
||||||
|
const isFixed = $row.data('kind') === 'fixed';
|
||||||
|
const targetFields = isFixed
|
||||||
|
? { kind: 'fixed', fixed_field_key: $row.data('fixed-key') }
|
||||||
|
: { kind: 'custom', mapping_id: $row.data('mapping-id'), field_id: $row.data('field-id') };
|
||||||
|
|
||||||
|
$.post('save_binding.php', {
|
||||||
|
...targetFields,
|
||||||
|
template_id: $row.data('template-id'),
|
||||||
|
json_value: String($row.data('json-value')),
|
||||||
|
lims_value_id: $select.val(),
|
||||||
|
lims_value: selectedData.text || ''
|
||||||
|
}).then(resp => {
|
||||||
|
if (resp && resp.success) {
|
||||||
|
$status.text('Salvato').removeClass('text-muted').addClass('text-success');
|
||||||
|
$select.data('original-val', $select.val());
|
||||||
|
} else {
|
||||||
|
throw new Error((resp && resp.error) || 'Errore salvataggio');
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
$status.text('Errore').removeClass('text-muted').addClass('text-danger');
|
||||||
|
$globalError.text(err.message || 'Errore durante il salvataggio.').show();
|
||||||
|
$btn.prop('disabled', false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.delete-binding-btn').on('click', function() {
|
||||||
|
if (!confirm('Eliminare questo binding?')) return;
|
||||||
|
const $row = $(this).closest('tr');
|
||||||
|
const $btn = $(this);
|
||||||
|
$globalError.hide();
|
||||||
|
$btn.prop('disabled', true);
|
||||||
|
|
||||||
|
$.post('delete_binding.php', {
|
||||||
|
id: $row.data('id')
|
||||||
|
}).then(resp => {
|
||||||
|
if (resp && resp.success) {
|
||||||
|
$row.fadeOut(200, () => $row.remove());
|
||||||
|
} else {
|
||||||
|
throw new Error((resp && resp.error) || 'Errore eliminazione');
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
$globalError.text(err.message || 'Errore durante l\'eliminazione.').show();
|
||||||
|
$btn.prop('disabled', false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
<?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'
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
<?php
|
||||||
|
require_once dirname(__DIR__, 3) . '/vendor/autoload.php'; // Torna al livello di public
|
||||||
|
|
||||||
|
use Dotenv\Dotenv;
|
||||||
|
|
||||||
|
class VisualLimsApiClient
|
||||||
|
{
|
||||||
|
private static $instance = null;
|
||||||
|
private $baseUrl;
|
||||||
|
private $username;
|
||||||
|
private $password;
|
||||||
|
private $token = null;
|
||||||
|
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
$dotenv = Dotenv::createImmutable(dirname(__DIR__, 3)); // Torna al livello di public
|
||||||
|
$dotenv->load();
|
||||||
|
|
||||||
|
$this->baseUrl = $_ENV['API_BASE_URL'];
|
||||||
|
$this->username = $_ENV['API_USERNAME'];
|
||||||
|
$this->password = $_ENV['API_PASSWORD'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getInstance()
|
||||||
|
{
|
||||||
|
if (self::$instance === null) {
|
||||||
|
$dotenv = Dotenv::createImmutable(dirname(__DIR__, 3));
|
||||||
|
$dotenv->load();
|
||||||
|
|
||||||
|
$simulate = ($_ENV['SIMULATE_EXPORT_LIMS'] ?? '') === 'true';
|
||||||
|
|
||||||
|
if ($simulate) {
|
||||||
|
require_once __DIR__ . '/VisualLimsApiClientMock.class.php';
|
||||||
|
self::$instance = new VisualLimsApiClientMock();
|
||||||
|
} else {
|
||||||
|
self::$instance = new VisualLimsApiClient();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function authenticate($retryCount = 0, $maxRetries = 3)
|
||||||
|
{
|
||||||
|
$ch = curl_init("{$this->baseUrl}/api/authentication/authenticate");
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
|
||||||
|
'Username' => $this->username,
|
||||||
|
'Password' => $this->password
|
||||||
|
]));
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
'Content-Type: application/json',
|
||||||
|
'Accept: application/json'
|
||||||
|
]);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($ch, CURLOPT_VERBOSE, true);
|
||||||
|
$log = fopen(__DIR__ . '/curl_auth_debug.log', 'a') ?: fopen('php://stderr', 'w');
|
||||||
|
curl_setopt($ch, CURLOPT_STDERR, $log);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$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);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
$token_data = json_decode($response, true);
|
||||||
|
$this->token = null;
|
||||||
|
|
||||||
|
if (is_array($token_data) && isset($token_data['token'])) {
|
||||||
|
$this->token = $token_data['token'];
|
||||||
|
} elseif (is_string($token_data) && !empty($token_data)) {
|
||||||
|
$this->token = trim($token_data, '"');
|
||||||
|
} elseif (is_string($response) && !empty($response)) {
|
||||||
|
$this->token = trim($response, '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($this->token)) {
|
||||||
|
throw new Exception("Token non ricevuto: " . substr($response, 0, 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getToken()
|
||||||
|
{
|
||||||
|
if ($this->token === null) {
|
||||||
|
$this->authenticate();
|
||||||
|
}
|
||||||
|
return $this->token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get($endpoint)
|
||||||
|
{
|
||||||
|
$token = $this->getToken();
|
||||||
|
$url = "{$this->baseUrl}/api/odata/{$endpoint}";
|
||||||
|
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
"Authorization: Bearer {$token}",
|
||||||
|
"Accept: application/json"
|
||||||
|
]);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($ch, CURLOPT_VERBOSE, true);
|
||||||
|
$log = fopen(__DIR__ . '/curl_request_debug.log', 'w') ?: fopen('php://stderr', 'w');
|
||||||
|
curl_setopt($ch, CURLOPT_STDERR, $log);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$curl_error = curl_error($ch);
|
||||||
|
fclose($log);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
throw new Exception("Errore nella richiesta: {$curl_error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($http_code !== 200) {
|
||||||
|
throw new Exception("Errore nel recupero dati: HTTP {$http_code}, Risposta: " . substr($response, 0, 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($response, true);
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
throw new Exception("Risposta non JSON valida: " . substr($response, 0, 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function post($endpoint, $payload)
|
||||||
|
{
|
||||||
|
$token = $this->getToken();
|
||||||
|
$url = "{$this->baseUrl}/api/odata/{$endpoint}";
|
||||||
|
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
"Authorization: Bearer {$token}",
|
||||||
|
"Content-Type: application/json",
|
||||||
|
"Accept: application/json"
|
||||||
|
]);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$curl_error = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
throw new Exception("Errore nella richiesta POST: {$curl_error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($http_code < 200 || $http_code >= 300) {
|
||||||
|
throw new Exception("POST fallito: HTTP {$http_code}, Risposta: " . substr($response, 0, 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_decode($response, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function patch($endpoint, $payload)
|
||||||
|
{
|
||||||
|
$token = $this->getToken();
|
||||||
|
$url = "{$this->baseUrl}/api/odata/{$endpoint}";
|
||||||
|
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH");
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
"Authorization: Bearer {$token}",
|
||||||
|
"Content-Type: application/json",
|
||||||
|
"Accept: application/json"
|
||||||
|
]);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$curl_error = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
throw new Exception("Errore nella richiesta PATCH: {$curl_error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($http_code < 200 || $http_code >= 300) {
|
||||||
|
throw new Exception("PATCH fallito: HTTP {$http_code}, Risposta: " . substr($response, 0, 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_decode($response, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST a file as multipart/form-data (used for photo/attachment uploads).
|
||||||
|
*
|
||||||
|
* @param string $endpoint OData endpoint, e.g. "Campione(613388)/UploadCampioneFile"
|
||||||
|
* @param string $filePath Absolute path to the file on disk
|
||||||
|
* @param string $fileName Original file name to send
|
||||||
|
* @param array $extraFields Additional form fields to include
|
||||||
|
* @return array|null Decoded JSON response
|
||||||
|
*/
|
||||||
|
public function postMultipart($endpoint, $filePath, $fileName, array $extraFields = [])
|
||||||
|
{
|
||||||
|
$token = $this->getToken();
|
||||||
|
$url = "{$this->baseUrl}/api/odata/{$endpoint}";
|
||||||
|
|
||||||
|
$cfile = new CURLFile($filePath, mime_content_type($filePath) ?: 'application/octet-stream', $fileName);
|
||||||
|
|
||||||
|
$payload = array_merge($extraFields, [
|
||||||
|
'file' => $cfile,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
"Authorization: Bearer {$token}",
|
||||||
|
"Accept: application/json",
|
||||||
|
// Content-Type is set automatically to multipart/form-data by cURL
|
||||||
|
]);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$curl_error = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
throw new Exception("Errore nella richiesta POST multipart: {$curl_error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($http_code < 200 || $http_code >= 300) {
|
||||||
|
throw new Exception("POST multipart fallito: HTTP {$http_code}, Risposta: " . substr($response, 0, 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_decode($response, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBaseUrl()
|
||||||
|
{
|
||||||
|
return $this->baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get raw/binary content from VisualLims API.
|
||||||
|
* Used for PDF downloads from MediaFile/DownloadStream.
|
||||||
|
*/
|
||||||
|
public function getRaw($endpoint)
|
||||||
|
{
|
||||||
|
$token = $this->getToken();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Normal JSON OData calls use:
|
||||||
|
* {$this->baseUrl}/api/odata/...
|
||||||
|
*
|
||||||
|
* Media file downloads use:
|
||||||
|
* {$this->baseUrl}/api/MediaFile/DownloadStream...
|
||||||
|
*/
|
||||||
|
$url = rtrim($this->baseUrl, '/') . '/api/' . ltrim($endpoint, '/');
|
||||||
|
|
||||||
|
$ch = curl_init($url);
|
||||||
|
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_HTTPHEADER => [
|
||||||
|
"Authorization: Bearer {$token}",
|
||||||
|
"Accept: application/pdf,*/*"
|
||||||
|
],
|
||||||
|
CURLOPT_SSL_VERIFYPEER => false,
|
||||||
|
CURLOPT_SSL_VERIFYHOST => false,
|
||||||
|
CURLOPT_FOLLOWLOCATION => true,
|
||||||
|
CURLOPT_TIMEOUT => 60
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
|
||||||
|
$curlError = curl_error($ch);
|
||||||
|
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
throw new Exception("Errore cURL download raw: " . $curlError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($httpCode < 200 || $httpCode >= 300) {
|
||||||
|
throw new Exception(
|
||||||
|
"Errore HTTP {$httpCode} durante download raw. Content-Type: {$contentType}. Response: " .
|
||||||
|
substr($response, 0, 500)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($response)) {
|
||||||
|
throw new Exception("Risposta vuota dal download raw.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
<?php
|
||||||
|
require_once dirname(__DIR__, 3) . '/vendor/autoload.php'; // Torna al livello di public
|
||||||
|
|
||||||
|
use Dotenv\Dotenv;
|
||||||
|
|
||||||
|
class VisualLimsApiClient
|
||||||
|
{
|
||||||
|
private static $instance = null;
|
||||||
|
private $baseUrl;
|
||||||
|
private $username;
|
||||||
|
private $password;
|
||||||
|
private $token = null;
|
||||||
|
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
$dotenv = Dotenv::createImmutable(dirname(__DIR__, 3)); // Torna al livello di public
|
||||||
|
$dotenv->load();
|
||||||
|
|
||||||
|
$this->baseUrl = $_ENV['API_BASE_URL'];
|
||||||
|
$this->username = $_ENV['API_USERNAME'];
|
||||||
|
$this->password = $_ENV['API_PASSWORD'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getInstance()
|
||||||
|
{
|
||||||
|
if (self::$instance === null) {
|
||||||
|
self::$instance = new VisualLimsApiClient();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function authenticate()
|
||||||
|
{
|
||||||
|
$ch = curl_init("{$this->baseUrl}/api/authentication/authenticate");
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
|
||||||
|
'Username' => $this->username,
|
||||||
|
'Password' => $this->password
|
||||||
|
]));
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
'Content-Type: application/json',
|
||||||
|
'Accept: application/json'
|
||||||
|
]);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($ch, CURLOPT_VERBOSE, true);
|
||||||
|
$log = fopen(__DIR__ . '/curl_auth_debug.log', 'w') ?: fopen('php://stderr', 'w');
|
||||||
|
curl_setopt($ch, CURLOPT_STDERR, $log);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$curl_error = curl_error($ch);
|
||||||
|
fclose($log);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($response === false || $http_code != 200) {
|
||||||
|
throw new Exception("Autenticazione fallita: HTTP {$http_code}, Errore cURL: {$curl_error}, Risposta: " . substr($response, 0, 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
$token_data = json_decode($response, true);
|
||||||
|
$this->token = null;
|
||||||
|
|
||||||
|
if (is_array($token_data) && isset($token_data['token'])) {
|
||||||
|
$this->token = $token_data['token'];
|
||||||
|
} elseif (is_string($token_data) && !empty($token_data)) {
|
||||||
|
$this->token = trim($token_data, '"');
|
||||||
|
} elseif (is_string($response) && !empty($response)) {
|
||||||
|
$this->token = trim($response, '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($this->token)) {
|
||||||
|
throw new Exception("Token non ricevuto: " . substr($response, 0, 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getToken()
|
||||||
|
{
|
||||||
|
if ($this->token === null) {
|
||||||
|
$this->authenticate();
|
||||||
|
}
|
||||||
|
return $this->token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get($endpoint)
|
||||||
|
{
|
||||||
|
$token = $this->getToken();
|
||||||
|
$url = "{$this->baseUrl}/api/odata/{$endpoint}";
|
||||||
|
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
"Authorization: Bearer {$token}",
|
||||||
|
"Accept: application/json"
|
||||||
|
]);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($ch, CURLOPT_VERBOSE, true);
|
||||||
|
$log = fopen(__DIR__ . '/curl_request_debug.log', 'w') ?: fopen('php://stderr', 'w');
|
||||||
|
curl_setopt($ch, CURLOPT_STDERR, $log);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$curl_error = curl_error($ch);
|
||||||
|
fclose($log);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
throw new Exception("Errore nella richiesta: {$curl_error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($http_code !== 200) {
|
||||||
|
throw new Exception("Errore nel recupero dati: HTTP {$http_code}, Risposta: " . substr($response, 0, 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($response, true);
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
throw new Exception("Risposta non JSON valida: " . substr($response, 0, 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock implementation of VisualLimsApiClient.
|
||||||
|
* Activated when SIMULATE_EXPORT_LIMS=true in .env.
|
||||||
|
* All HTTP calls are skipped; fake but structurally valid data is returned.
|
||||||
|
* Every simulated call is logged via error_log() with a [SIMULATE] prefix.
|
||||||
|
*/
|
||||||
|
class VisualLimsApiClientMock
|
||||||
|
{
|
||||||
|
private int $fakeCommessaId;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
// Stable fake ID for the lifetime of a single request
|
||||||
|
$this->fakeCommessaId = mt_rand(90001, 99999);
|
||||||
|
error_log("[SIMULATE] VisualLimsApiClientMock initialised (fakeCommessaId={$this->fakeCommessaId})");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(string $endpoint): array
|
||||||
|
{
|
||||||
|
error_log("[SIMULATE] GET {$endpoint}");
|
||||||
|
|
||||||
|
// --- Fixed-field dropdown lists ---
|
||||||
|
|
||||||
|
if (str_starts_with($endpoint, 'MoltiplicatorePrezzi')) {
|
||||||
|
return ['value' => [
|
||||||
|
['IdMoltiplicatorePrezzo' => 1, 'Codice' => 'MP-01', 'Descrizione' => 'Standard (1x)'],
|
||||||
|
['IdMoltiplicatorePrezzo' => 2, 'Codice' => 'MP-02', 'Descrizione' => 'Urgente (1.5x)'],
|
||||||
|
['IdMoltiplicatorePrezzo' => 3, 'Codice' => 'MP-03', 'Descrizione' => 'Extra Urgente (2x)'],
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_starts_with($endpoint, 'AnagraficaCertestObject')) {
|
||||||
|
return ['value' => [
|
||||||
|
['IdAnagrafica' => 1, 'Codice' => 'OBJ-01', 'NomeAnagrafica' => 'Articolo Tessile'],
|
||||||
|
['IdAnagrafica' => 2, 'Codice' => 'OBJ-02', 'NomeAnagrafica' => 'Componente Meccanico'],
|
||||||
|
['IdAnagrafica' => 3, 'Codice' => 'OBJ-03', 'NomeAnagrafica' => 'Materiale Plastico'],
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_starts_with($endpoint, 'AnagraficaCertestService')) {
|
||||||
|
return ['value' => [
|
||||||
|
['IdAnagrafica' => 1, 'Codice' => 'SRV-01', 'NomeAnagrafica' => 'Analisi Chimica'],
|
||||||
|
['IdAnagrafica' => 2, 'Codice' => 'SRV-02', 'NomeAnagrafica' => 'Test Meccanico'],
|
||||||
|
['IdAnagrafica' => 3, 'Codice' => 'SRV-03', 'NomeAnagrafica' => 'Prova Ambientale'],
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cliente? list — get_clienti.php exits early in simulate mode, but guard here too
|
||||||
|
if (str_starts_with($endpoint, 'Cliente?')) {
|
||||||
|
return ['value' => []];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cliente(N)?$expand=Responsabili
|
||||||
|
if (str_starts_with($endpoint, 'Cliente(')) {
|
||||||
|
preg_match('/Cliente\((\d+)\)/', $endpoint, $m);
|
||||||
|
$clienteId = isset($m[1]) ? (int) $m[1] : 0;
|
||||||
|
return [
|
||||||
|
'IdCliente' => $clienteId,
|
||||||
|
'Responsabili' => [
|
||||||
|
['IdClienteResponsabile' => 1, 'Nominativo' => 'Marco Bianchi'],
|
||||||
|
['IdClienteResponsabile' => 2, 'Nominativo' => 'Giulia Ferrari'],
|
||||||
|
['IdClienteResponsabile' => 3, 'Nominativo' => 'Andrea Russo'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- CustomField dropdown values (get_customfield_values.php) ---
|
||||||
|
|
||||||
|
if (str_starts_with($endpoint, 'CustomField(')) {
|
||||||
|
preg_match('/CustomField\((\d+)\)/', $endpoint, $m);
|
||||||
|
$fieldId = isset($m[1]) ? (int) $m[1] : 0;
|
||||||
|
return [
|
||||||
|
'CustomFieldsValues' => [
|
||||||
|
['IdCustomFieldsValue' => $fieldId * 10 + 1, 'Valore' => 'Opzione A'],
|
||||||
|
['IdCustomFieldsValue' => $fieldId * 10 + 2, 'Valore' => 'Opzione B'],
|
||||||
|
['IdCustomFieldsValue' => $fieldId * 10 + 3, 'Valore' => 'Opzione C'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- CommessaWeb OData calls (STEP 7 GET + STEP 10 verification) ---
|
||||||
|
|
||||||
|
preg_match('/\((\d+)\)/', $endpoint, $m);
|
||||||
|
$id = isset($m[1]) ? (int) $m[1] : $this->fakeCommessaId;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'IdCommessa' => $id,
|
||||||
|
'CodiceCommessa' => "SIM-{$id}",
|
||||||
|
'CommesseCustomFields' => [], // Empty → PATCH step is skipped correctly
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function post(string $endpoint, array $payload): array
|
||||||
|
{
|
||||||
|
error_log("[SIMULATE] POST {$endpoint} payload=" . json_encode($payload));
|
||||||
|
|
||||||
|
// CommessaWeb creation
|
||||||
|
if ($endpoint === 'CommessaWeb') {
|
||||||
|
return [
|
||||||
|
'IdCommessa' => $this->fakeCommessaId,
|
||||||
|
'CodiceCommessa' => "SIM-{$this->fakeCommessaId}",
|
||||||
|
'Richiedente' => $payload['Richiedente'] ?? '',
|
||||||
|
'Descrizione' => $payload['Descrizione'] ?? '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Campione creation
|
||||||
|
if ($endpoint === 'Campione') {
|
||||||
|
return [
|
||||||
|
'IdCampione' => mt_rand(10001, 19999),
|
||||||
|
'Commessa' => $payload['Commessa'] ?? null,
|
||||||
|
'Matrice' => $payload['Matrice'] ?? null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// InviaCommessa / ImportaCommessa (currently commented out upstream)
|
||||||
|
return ['simulated' => true, 'endpoint' => $endpoint];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function patch(string $endpoint, array $payload): array
|
||||||
|
{
|
||||||
|
error_log("[SIMULATE] PATCH {$endpoint} payload=" . json_encode($payload));
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function postMultipart(string $endpoint, string $filePath, string $fileName, array $extraFields = []): array
|
||||||
|
{
|
||||||
|
error_log("[SIMULATE] POST multipart {$endpoint} file={$fileName}");
|
||||||
|
|
||||||
|
return ['simulated' => true, 'file' => $fileName];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
<?php
|
||||||
|
require_once dirname(__DIR__, 3) . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
use Dotenv\Dotenv;
|
||||||
|
|
||||||
|
class VisualLimsApiClientXml
|
||||||
|
{
|
||||||
|
private static $instance = null;
|
||||||
|
private $baseUrl;
|
||||||
|
private $username;
|
||||||
|
private $password;
|
||||||
|
private $token = null;
|
||||||
|
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
$dotenv = Dotenv::createImmutable(dirname(__DIR__, 3));
|
||||||
|
$dotenv->load();
|
||||||
|
|
||||||
|
$this->baseUrl = $_ENV['API_BASE_URL'];
|
||||||
|
$this->username = $_ENV['API_USERNAME'];
|
||||||
|
$this->password = $_ENV['API_PASSWORD'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getInstance()
|
||||||
|
{
|
||||||
|
if (self::$instance === null) {
|
||||||
|
self::$instance = new VisualLimsApiClientXml();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function authenticate()
|
||||||
|
{
|
||||||
|
$ch = curl_init("{$this->baseUrl}/api/authentication/authenticate");
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
|
||||||
|
'Username' => $this->username,
|
||||||
|
'Password' => $this->password
|
||||||
|
]));
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
'Content-Type: application/json',
|
||||||
|
'Accept: application/json'
|
||||||
|
]);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($ch, CURLOPT_VERBOSE, true);
|
||||||
|
$log = fopen(__DIR__ . '/curl_auth_debug_xml.log', 'w') ?: fopen('php://stderr', 'w');
|
||||||
|
curl_setopt($ch, CURLOPT_STDERR, $log);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$curl_error = curl_error($ch);
|
||||||
|
fclose($log);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($response === false || $http_code != 200) {
|
||||||
|
throw new Exception("Autenticazione fallita: HTTP {$http_code}, Errore cURL: {$curl_error}, Risposta: " . substr($response, 0, 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
$token_data = json_decode($response, true);
|
||||||
|
$this->token = null;
|
||||||
|
|
||||||
|
if (is_array($token_data) && isset($token_data['token'])) {
|
||||||
|
$this->token = $token_data['token'];
|
||||||
|
} elseif (is_string($token_data) && !empty($token_data)) {
|
||||||
|
$this->token = trim($token_data, '"');
|
||||||
|
} elseif (is_string($response) && !empty($response)) {
|
||||||
|
$this->token = trim($response, '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($this->token)) {
|
||||||
|
throw new Exception("Token non ricevuto: " . substr($response, 0, 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getToken()
|
||||||
|
{
|
||||||
|
if ($this->token === null) {
|
||||||
|
$this->authenticate();
|
||||||
|
}
|
||||||
|
return $this->token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get($endpoint, $options = [])
|
||||||
|
{
|
||||||
|
$token = $this->getToken();
|
||||||
|
$query = http_build_query($options);
|
||||||
|
$url = "{$this->baseUrl}/api/odata/{$endpoint}" . ($query ? '?' . $query : '');
|
||||||
|
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
"Authorization: Bearer {$token}",
|
||||||
|
"Accept: application/xml"
|
||||||
|
]);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($ch, CURLOPT_VERBOSE, true);
|
||||||
|
$log = fopen(__DIR__ . '/curl_request_debug_xml.log', 'w') ?: fopen('php://stderr', 'w');
|
||||||
|
curl_setopt($ch, CURLOPT_STDERR, $log);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$curl_error = curl_error($ch);
|
||||||
|
fclose($log);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
throw new Exception("Errore nella richiesta: {$curl_error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($http_code !== 200) {
|
||||||
|
throw new Exception("Errore nel recupero dati: HTTP {$http_code}, Risposta: " . substr($response, 0, 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifica che la risposta sia XML
|
||||||
|
if (strpos($response, '<?xml') !== 0) {
|
||||||
|
throw new Exception("Risposta non valida: atteso formato XML, ricevuto: " . substr($response, 0, 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
<?php
|
||||||
|
require_once dirname(__DIR__, 3) . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
use Dotenv\Dotenv;
|
||||||
|
|
||||||
|
class VisualLimsApiClientXml
|
||||||
|
{
|
||||||
|
private static $instance = null;
|
||||||
|
private $baseUrl;
|
||||||
|
private $username;
|
||||||
|
private $password;
|
||||||
|
private $token = null;
|
||||||
|
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
$dotenv = Dotenv::createImmutable(dirname(__DIR__, 3));
|
||||||
|
$dotenv->load();
|
||||||
|
|
||||||
|
$this->baseUrl = $_ENV['API_BASE_URL'];
|
||||||
|
$this->username = $_ENV['API_USERNAME'];
|
||||||
|
$this->password = $_ENV['API_PASSWORD'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getInstance()
|
||||||
|
{
|
||||||
|
if (self::$instance === null) {
|
||||||
|
self::$instance = new VisualLimsApiClientXml();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function authenticate()
|
||||||
|
{
|
||||||
|
$ch = curl_init("{$this->baseUrl}/api/authentication/authenticate");
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
|
||||||
|
'Username' => $this->username,
|
||||||
|
'Password' => $this->password
|
||||||
|
]));
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
'Content-Type: application/json',
|
||||||
|
'Accept: application/json'
|
||||||
|
]);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($ch, CURLOPT_VERBOSE, true);
|
||||||
|
$log = fopen(__DIR__ . '/curl_auth_debug_xml.log', 'w') ?: fopen('php://stderr', 'w');
|
||||||
|
curl_setopt($ch, CURLOPT_STDERR, $log);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$curl_error = curl_error($ch);
|
||||||
|
fclose($log);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($response === false || $http_code != 200) {
|
||||||
|
throw new Exception("Autenticazione fallita: HTTP {$http_code}, Errore cURL: {$curl_error}, Risposta: " . substr($response, 0, 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
$token_data = json_decode($response, true);
|
||||||
|
$this->token = null;
|
||||||
|
|
||||||
|
if (is_array($token_data) && isset($token_data['token'])) {
|
||||||
|
$this->token = $token_data['token'];
|
||||||
|
} elseif (is_string($token_data) && !empty($token_data)) {
|
||||||
|
$this->token = trim($token_data, '"');
|
||||||
|
} elseif (is_string($response) && !empty($response)) {
|
||||||
|
$this->token = trim($response, '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($this->token)) {
|
||||||
|
throw new Exception("Token non ricevuto: " . substr($response, 0, 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getToken()
|
||||||
|
{
|
||||||
|
if ($this->token === null) {
|
||||||
|
$this->authenticate();
|
||||||
|
}
|
||||||
|
return $this->token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get($endpoint, $options = [])
|
||||||
|
{
|
||||||
|
$token = $this->getToken();
|
||||||
|
$query = http_build_query($options);
|
||||||
|
$url = "{$this->baseUrl}/api/odata/{$endpoint}" . ($query ? '?' . $query : '');
|
||||||
|
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
"Authorization: Bearer {$token}",
|
||||||
|
"Accept: application/xml"
|
||||||
|
]);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($ch, CURLOPT_VERBOSE, true);
|
||||||
|
$log = fopen(__DIR__ . '/curl_request_debug_xml.log', 'w') ?: fopen('php://stderr', 'w');
|
||||||
|
curl_setopt($ch, CURLOPT_STDERR, $log);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$curl_error = curl_error($ch);
|
||||||
|
fclose($log);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
throw new Exception("Errore nella richiesta: {$curl_error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($http_code !== 200) {
|
||||||
|
throw new Exception("Errore nel recupero dati: HTTP {$http_code}, Risposta: " . substr($response, 0, 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifica che la risposta sia XML
|
||||||
|
if (strpos($response, '<?xml') !== 0) {
|
||||||
|
throw new Exception("Risposta non valida: atteso formato XML, ricevuto: " . substr($response, 0, 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,448 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// Helpers for JSON -> LIMS value bindings (table json_lims_binding).
|
||||||
|
// Supports two kinds: 'custom' (template_mapping list fields, value -> import_data_details)
|
||||||
|
// and 'fixed' (template_fixed_mapping list fields, id -> datadb column).
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Fixed-field metadata
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function binding_fixed_alias_map(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'ClienteResponsabile' => 'cliente_responsabile_id',
|
||||||
|
'ClienteFornitore' => 'cliente_fornitore_id',
|
||||||
|
'ClienteAnalisi' => 'clienteAnalisi',
|
||||||
|
'MoltiplicatorePrezzo' => 'moltiplicatore_prezzo_id',
|
||||||
|
'AnagraficaCertestObject' => 'anagrafica_certest_object_id',
|
||||||
|
'AnagraficaCertestService' => 'anagrafica_certest_service_id',
|
||||||
|
'ConsegnaRichiesta' => 'consegna_richiesta',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixed-field keys that are LIMS list dropdowns (bindable). ConsegnaRichiesta is a date.
|
||||||
|
function binding_fixed_is_list(string $key): bool
|
||||||
|
{
|
||||||
|
return in_array($key, [
|
||||||
|
'ClienteResponsabile',
|
||||||
|
'ClienteFornitore',
|
||||||
|
'ClienteAnalisi',
|
||||||
|
'MoltiplicatorePrezzo',
|
||||||
|
'AnagraficaCertestObject',
|
||||||
|
'AnagraficaCertestService',
|
||||||
|
], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_fixed_column(string $key): ?string
|
||||||
|
{
|
||||||
|
return binding_fixed_alias_map()[$key] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-match only the small global lists; the client-based ones are huge / client-specific.
|
||||||
|
function binding_fixed_auto_matchable(string $key): bool
|
||||||
|
{
|
||||||
|
return in_array($key, [
|
||||||
|
'MoltiplicatorePrezzo',
|
||||||
|
'AnagraficaCertestObject',
|
||||||
|
'AnagraficaCertestService',
|
||||||
|
], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_fixed_label(string $key): string
|
||||||
|
{
|
||||||
|
$labels = [
|
||||||
|
'AnagraficaCertestObject' => 'Anagrafica Certest Object',
|
||||||
|
'AnagraficaCertestService' => 'Anagrafica Certest Service',
|
||||||
|
'MoltiplicatorePrezzo' => 'Moltiplicatore Prezzo',
|
||||||
|
'ClienteResponsabile' => 'Cliente Responsabile',
|
||||||
|
'ClienteFornitore' => 'Cliente Fornitore',
|
||||||
|
'ClienteAnalisi' => 'Cliente Analisi',
|
||||||
|
];
|
||||||
|
return $labels[$key] ?? $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Bindable check (custom mapping row)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function binding_is_list_field(array $mapping): bool
|
||||||
|
{
|
||||||
|
$hasList = (int) ($mapping['has_list'] ?? 0) === 1;
|
||||||
|
$isMultiChoice = strcasecmp(trim((string) ($mapping['data_type'] ?? '')), 'SceltaMultipla') === 0;
|
||||||
|
return $hasList || $isMultiChoice;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Target keys + lookup / upsert
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function binding_target_custom(int $mappingId): string
|
||||||
|
{
|
||||||
|
return 'cf:' . $mappingId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_target_fixed(int $templateId, string $fixedKey): string
|
||||||
|
{
|
||||||
|
return 'fx:' . $templateId . ':' . $fixedKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_lookup_target(PDO $pdo, string $targetKey, string $jsonValue): ?array
|
||||||
|
{
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
"SELECT id, lims_value_id, lims_value
|
||||||
|
FROM json_lims_binding
|
||||||
|
WHERE target_key = ? AND json_value = ?
|
||||||
|
LIMIT 1"
|
||||||
|
);
|
||||||
|
$stmt->execute([$targetKey, $jsonValue]);
|
||||||
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
return $row ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_lookup(PDO $pdo, int $mappingId, string $jsonValue): ?array
|
||||||
|
{
|
||||||
|
return binding_lookup_target($pdo, binding_target_custom($mappingId), $jsonValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_lookup_fixed(PDO $pdo, int $templateId, string $fixedKey, string $jsonValue): ?array
|
||||||
|
{
|
||||||
|
return binding_lookup_target($pdo, binding_target_fixed($templateId, $fixedKey), $jsonValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_upsert_row(
|
||||||
|
PDO $pdo,
|
||||||
|
string $kind,
|
||||||
|
int $templateId,
|
||||||
|
?int $mappingId,
|
||||||
|
?string $fixedFieldKey,
|
||||||
|
string $targetKey,
|
||||||
|
int $fieldId,
|
||||||
|
string $jsonValue,
|
||||||
|
int $limsValueId,
|
||||||
|
string $limsValue,
|
||||||
|
?int $createdBy
|
||||||
|
): void {
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
"INSERT INTO json_lims_binding
|
||||||
|
(template_id, binding_kind, mapping_id, fixed_field_key, target_key, field_id, json_value, lims_value_id, lims_value, created_by)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
lims_value_id = VALUES(lims_value_id),
|
||||||
|
lims_value = VALUES(lims_value),
|
||||||
|
field_id = VALUES(field_id),
|
||||||
|
template_id = VALUES(template_id),
|
||||||
|
binding_kind = VALUES(binding_kind),
|
||||||
|
mapping_id = VALUES(mapping_id),
|
||||||
|
fixed_field_key = VALUES(fixed_field_key)"
|
||||||
|
);
|
||||||
|
$stmt->execute([
|
||||||
|
$templateId, $kind, $mappingId, $fixedFieldKey, $targetKey,
|
||||||
|
$fieldId, $jsonValue, $limsValueId, $limsValue, $createdBy,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_upsert(
|
||||||
|
PDO $pdo,
|
||||||
|
int $templateId,
|
||||||
|
int $mappingId,
|
||||||
|
int $fieldId,
|
||||||
|
string $jsonValue,
|
||||||
|
int $limsValueId,
|
||||||
|
string $limsValue,
|
||||||
|
?int $createdBy
|
||||||
|
): void {
|
||||||
|
binding_upsert_row(
|
||||||
|
$pdo, 'custom', $templateId, $mappingId, null,
|
||||||
|
binding_target_custom($mappingId), $fieldId, $jsonValue, $limsValueId, $limsValue, $createdBy
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_upsert_fixed(
|
||||||
|
PDO $pdo,
|
||||||
|
int $templateId,
|
||||||
|
string $fixedKey,
|
||||||
|
string $jsonValue,
|
||||||
|
int $limsValueId,
|
||||||
|
string $limsValue,
|
||||||
|
?int $createdBy
|
||||||
|
): void {
|
||||||
|
binding_upsert_row(
|
||||||
|
$pdo, 'fixed', $templateId, null, $fixedKey,
|
||||||
|
binding_target_fixed($templateId, $fixedKey), 0, $jsonValue, $limsValueId, $limsValue, $createdBy
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_delete_target(PDO $pdo, string $targetKey, string $jsonValue): int
|
||||||
|
{
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM json_lims_binding WHERE target_key = ? AND json_value = ?");
|
||||||
|
$stmt->execute([$targetKey, $jsonValue]);
|
||||||
|
return $stmt->rowCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Custom-field LIMS values (cache/customfield_{id}.json) + auto-match
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function binding_get_lims_values(int $fieldId): array
|
||||||
|
{
|
||||||
|
static $memo = [];
|
||||||
|
if ($fieldId <= 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (array_key_exists($fieldId, $memo)) {
|
||||||
|
return $memo[$fieldId];
|
||||||
|
}
|
||||||
|
|
||||||
|
$cacheDir = dirname(__DIR__) . '/cache';
|
||||||
|
$cacheFile = $cacheDir . '/customfield_' . $fieldId . '.json';
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
||||||
|
$values = json_decode(file_get_contents($cacheFile), true);
|
||||||
|
} else {
|
||||||
|
require_once __DIR__ . '/VisualLimsApiClient.class.php';
|
||||||
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
$data = $api->get("CustomField($fieldId)?\$expand=CustomFieldsValues");
|
||||||
|
$values = $data['CustomFieldsValues'] ?? [];
|
||||||
|
if (!is_dir($cacheDir)) {
|
||||||
|
mkdir($cacheDir, 0777, true);
|
||||||
|
}
|
||||||
|
file_put_contents($cacheFile, json_encode($values));
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("binding_get_lims_values failed for field $fieldId: " . $e->getMessage());
|
||||||
|
$values = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_array($values)) {
|
||||||
|
$values = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $memo[$fieldId] = $values;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exactly one case-insensitive match by Valore -> that value, otherwise null.
|
||||||
|
function binding_auto_match(array $limsValues, string $jsonValue): ?array
|
||||||
|
{
|
||||||
|
$needle = mb_strtolower(trim($jsonValue));
|
||||||
|
if ($needle === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$matches = [];
|
||||||
|
foreach ($limsValues as $v) {
|
||||||
|
$valore = (string) ($v['Valore'] ?? '');
|
||||||
|
if (mb_strtolower(trim($valore)) === $needle) {
|
||||||
|
$matches[] = $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count($matches) === 1 ? $matches[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Fixed-field LIMS values (per-field source) + auto-match
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function binding_template_idclient(PDO $pdo, int $templateId): int
|
||||||
|
{
|
||||||
|
$stmt = $pdo->prepare("SELECT idclient FROM excel_templates WHERE id = ?");
|
||||||
|
$stmt->execute([$templateId]);
|
||||||
|
return (int) ($stmt->fetchColumn() ?: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_client_label(array $client): string
|
||||||
|
{
|
||||||
|
$name = trim($client['Nominativo'] ?? '');
|
||||||
|
$id = trim((string) ($client['IdCliente'] ?? ''));
|
||||||
|
$code = trim((string) ($client['CodiceCliente'] ?? ''));
|
||||||
|
|
||||||
|
$parts = explode('_', $code);
|
||||||
|
$suffix = trim($parts[1] ?? '');
|
||||||
|
if ($suffix === '' && $code !== '') {
|
||||||
|
$suffix = substr($code, 0, 1);
|
||||||
|
}
|
||||||
|
if ($suffix === '') {
|
||||||
|
$suffix = '--';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $name . ' - ' . $suffix . ' (ID: ' . $id . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch an OData payload with a 1-hour file cache.
|
||||||
|
function binding_cached_get($api, string $cacheFile, string $endpoint): array
|
||||||
|
{
|
||||||
|
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
||||||
|
$data = json_decode(file_get_contents($cacheFile), true);
|
||||||
|
} else {
|
||||||
|
$data = $api->get($endpoint);
|
||||||
|
$dir = dirname($cacheFile);
|
||||||
|
if (!is_dir($dir)) {
|
||||||
|
mkdir($dir, 0777, true);
|
||||||
|
}
|
||||||
|
file_put_contents($cacheFile, json_encode($data));
|
||||||
|
}
|
||||||
|
return is_array($data) ? $data : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalized LIMS value list for a fixed field: [['id' => int, 'text' => string], ...].
|
||||||
|
function binding_get_fixed_values(PDO $pdo, string $fieldKey, int $templateId): array
|
||||||
|
{
|
||||||
|
static $memo = [];
|
||||||
|
$memoKey = $fieldKey . '|' . $templateId;
|
||||||
|
if (isset($memo[$memoKey])) {
|
||||||
|
return $memo[$memoKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
$cacheDir = dirname(__DIR__) . '/cache';
|
||||||
|
$out = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
require_once __DIR__ . '/VisualLimsApiClient.class.php';
|
||||||
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
|
||||||
|
switch ($fieldKey) {
|
||||||
|
case 'MoltiplicatorePrezzo':
|
||||||
|
$data = binding_cached_get($api, "$cacheDir/moltiplicatori_prezzo.json", 'MoltiplicatorePrezzi');
|
||||||
|
foreach (($data['value'] ?? []) as $r) {
|
||||||
|
$out[] = ['id' => (int) ($r['IdMoltiplicatorePrezzo'] ?? 0), 'text' => (string) ($r['Descrizione'] ?? '')];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'AnagraficaCertestObject':
|
||||||
|
case 'AnagraficaCertestService':
|
||||||
|
$file = $fieldKey === 'AnagraficaCertestObject' ? 'anagrafica_certest_object.json' : 'anagrafica_certest_service.json';
|
||||||
|
$data = binding_cached_get($api, "$cacheDir/$file", $fieldKey);
|
||||||
|
foreach (($data['value'] ?? []) as $r) {
|
||||||
|
$code = trim((string) ($r['Codice'] ?? ''));
|
||||||
|
$text = ($code !== '' ? $code . ' - ' : '') . (string) ($r['NomeAnagrafica'] ?? '');
|
||||||
|
$out[] = ['id' => (int) ($r['IdAnagrafica'] ?? 0), 'text' => $text];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ClienteResponsabile':
|
||||||
|
$idCliente = binding_template_idclient($pdo, $templateId);
|
||||||
|
if ($idCliente > 0) {
|
||||||
|
$data = binding_cached_get($api, "$cacheDir/cliente_responsabili_$idCliente.json", "Cliente($idCliente)?\$expand=Responsabili");
|
||||||
|
foreach (($data['Responsabili'] ?? []) as $r) {
|
||||||
|
$out[] = ['id' => (int) ($r['IdClienteResponsabile'] ?? 0), 'text' => (string) ($r['Nominativo'] ?? '')];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ClienteFornitore':
|
||||||
|
case 'ClienteAnalisi':
|
||||||
|
$endpoint = 'Cliente?' . http_build_query(['$select' => 'IdCliente,Nominativo,CodiceCliente', '$orderby' => 'Nominativo asc']);
|
||||||
|
$data = binding_cached_get($api, "$cacheDir/clienti.json", $endpoint);
|
||||||
|
foreach (($data['value'] ?? []) as $r) {
|
||||||
|
$out[] = ['id' => (int) ($r['IdCliente'] ?? 0), 'text' => binding_client_label($r)];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("binding_get_fixed_values($fieldKey) failed: " . $e->getMessage());
|
||||||
|
$out = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $memo[$memoKey] = $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exactly one case-insensitive match by text on a [{id,text}] list, otherwise null.
|
||||||
|
function binding_auto_match_fixed(array $values, string $jsonValue): ?array
|
||||||
|
{
|
||||||
|
$needle = mb_strtolower(trim($jsonValue));
|
||||||
|
if ($needle === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$matches = [];
|
||||||
|
foreach ($values as $v) {
|
||||||
|
if (mb_strtolower(trim((string) ($v['text'] ?? ''))) === $needle) {
|
||||||
|
$matches[] = $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count($matches) === 1 ? $matches[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// JSON node -> column matching (shared with import_insert custom logic)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function binding_find_column_index(string $sourceColumn, array $columns): int
|
||||||
|
{
|
||||||
|
$sourceColumn = trim($sourceColumn);
|
||||||
|
if ($sourceColumn === '') {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$columnsTrimmed = array_map('trim', $columns);
|
||||||
|
$candidates = [
|
||||||
|
$sourceColumn,
|
||||||
|
preg_replace('/^data\[\]\./', '', $sourceColumn),
|
||||||
|
preg_replace('/^data\.0\./', '', $sourceColumn),
|
||||||
|
str_replace('data[].', 'data.0.', $sourceColumn),
|
||||||
|
str_replace('data.0.', 'data[].', $sourceColumn),
|
||||||
|
];
|
||||||
|
$candidates = array_values(array_unique(array_filter(array_map('trim', $candidates), function ($v) {
|
||||||
|
return $v !== '';
|
||||||
|
})));
|
||||||
|
|
||||||
|
foreach ($candidates as $c) {
|
||||||
|
$i = array_search($c, $columnsTrimmed, true);
|
||||||
|
if ($i !== false) {
|
||||||
|
return (int) $i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Apply resolved values
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Custom: write the resolved LIMS text into import_data_details for the given datadb ids.
|
||||||
|
function binding_apply_to_details(
|
||||||
|
PDO $pdo,
|
||||||
|
int $mappingId,
|
||||||
|
string $limsValue,
|
||||||
|
array $datadbIds
|
||||||
|
): int {
|
||||||
|
$datadbIds = array_values(array_filter(array_map('intval', $datadbIds)));
|
||||||
|
if (empty($datadbIds)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$placeholders = implode(',', array_fill(0, count($datadbIds), '?'));
|
||||||
|
$sql = "UPDATE import_data_details
|
||||||
|
SET field_value = ?
|
||||||
|
WHERE mapping_id = ?
|
||||||
|
AND id IN ($placeholders)";
|
||||||
|
|
||||||
|
$params = array_merge([$limsValue, $mappingId], $datadbIds);
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute($params);
|
||||||
|
return $stmt->rowCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixed: write the resolved LIMS id into a whitelisted datadb column for the given ids.
|
||||||
|
// $limsValueId null clears the column.
|
||||||
|
function binding_apply_to_datadb(
|
||||||
|
PDO $pdo,
|
||||||
|
string $column,
|
||||||
|
?int $limsValueId,
|
||||||
|
array $datadbIds
|
||||||
|
): int {
|
||||||
|
if (!in_array($column, array_values(binding_fixed_alias_map()), true)) {
|
||||||
|
throw new InvalidArgumentException("Invalid fixed-field column: $column");
|
||||||
|
}
|
||||||
|
$datadbIds = array_values(array_filter(array_map('intval', $datadbIds)));
|
||||||
|
if (empty($datadbIds)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$placeholders = implode(',', array_fill(0, count($datadbIds), '?'));
|
||||||
|
$sql = "UPDATE datadb SET `$column` = ? WHERE iddatadb IN ($placeholders)";
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute(array_merge([$limsValueId], $datadbIds));
|
||||||
|
return $stmt->rowCount();
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>VisualLims Authentication</title>
|
||||||
|
<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;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>VisualLims Authentication</h1>
|
||||||
|
<button id="authButton">Authenticate</button>
|
||||||
|
<div id="result"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('authButton').addEventListener('click', async () => {
|
||||||
|
const resultDiv = document.getElementById('result');
|
||||||
|
resultDiv.textContent = 'Authenticating...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('https://93.43.5.102/limsapi/api/authentication/authenticate', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
Username: 'WebApiUserTest',
|
||||||
|
Password: 'WebApiUserClienteTest'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
if (data && data.token) {
|
||||||
|
resultDiv.textContent = `Token: ${data.token}`;
|
||||||
|
} else {
|
||||||
|
resultDiv.textContent = 'Authentication failed: No token received';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
resultDiv.textContent = `Error: ${error.message}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -3,6 +3,9 @@ require_once dirname(__DIR__, 3) . '/vendor/autoload.php';
|
|||||||
|
|
||||||
use Dotenv\Dotenv;
|
use Dotenv\Dotenv;
|
||||||
|
|
||||||
|
Dotenv::createImmutable(dirname(__DIR__, 3))->safeLoad();
|
||||||
|
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
|
||||||
|
|
||||||
class DBHandlerSelect
|
class DBHandlerSelect
|
||||||
{
|
{
|
||||||
private static $instance = null;
|
private static $instance = null;
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
$sourceIddatadb = isset($data['source_iddatadb']) ? (int)$data['source_iddatadb'] : 0;
|
||||||
|
$targetList = $data['target_iddatadb_list'] ?? [];
|
||||||
|
|
||||||
|
$targetIds = array_values(array_unique(array_filter(array_map('intval', (array)$targetList), function ($v) use ($sourceIddatadb) {
|
||||||
|
return $v > 0 && $v !== $sourceIddatadb;
|
||||||
|
})));
|
||||||
|
|
||||||
|
if ($sourceIddatadb <= 0 || empty($targetIds)) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Missing source or target records'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
// 1. Load source parts
|
||||||
|
$stmtParts = $pdo->prepare("
|
||||||
|
SELECT id, part_number, part_description, mix, idmatrice, note, dateexpiry
|
||||||
|
FROM identification_parts
|
||||||
|
WHERE iddatadb = ?
|
||||||
|
AND part_description IS NOT NULL
|
||||||
|
AND TRIM(part_description) <> ''
|
||||||
|
ORDER BY part_number ASC, id ASC
|
||||||
|
");
|
||||||
|
$stmtParts->execute([$sourceIddatadb]);
|
||||||
|
$sourceParts = $stmtParts->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (empty($sourceParts)) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'No parts found for source record'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Prepare statements
|
||||||
|
$stmtInsertPart = $pdo->prepare("
|
||||||
|
INSERT INTO identification_parts
|
||||||
|
(iddatadb, part_number, part_description, mix, idmatrice, note, dateexpiry, created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
(:iddatadb, :part_number, :part_description, :mix, :idmatrice, :note, :dateexpiry, NOW(), NOW())
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmtLoadCF = $pdo->prepare("
|
||||||
|
SELECT field_id, value_id, value_text
|
||||||
|
FROM identification_parts_customfields
|
||||||
|
WHERE part_id = ?
|
||||||
|
ORDER BY id ASC
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmtInsertCF = $pdo->prepare("
|
||||||
|
INSERT INTO identification_parts_customfields
|
||||||
|
(part_id, field_id, value_id, value_text, created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
(:part_id, :field_id, :value_id, :value_text, NOW(), NOW())
|
||||||
|
");
|
||||||
|
|
||||||
|
$details = [];
|
||||||
|
$totalClonedParts = 0;
|
||||||
|
|
||||||
|
// 3. Clone source parts to each target record
|
||||||
|
foreach ($targetIds as $targetIddatadb) {
|
||||||
|
$clonedCountForTarget = 0;
|
||||||
|
|
||||||
|
foreach ($sourceParts as $part) {
|
||||||
|
$stmtInsertPart->execute([
|
||||||
|
':iddatadb' => $targetIddatadb,
|
||||||
|
':part_number' => $part['part_number'],
|
||||||
|
':part_description' => $part['part_description'],
|
||||||
|
':mix' => $part['mix'] ?? 'N',
|
||||||
|
':idmatrice' => $part['idmatrice'] !== '' ? $part['idmatrice'] : null,
|
||||||
|
':note' => $part['note'] ?? null,
|
||||||
|
':dateexpiry' => $part['dateexpiry'] ?? null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$newPartId = (int)$pdo->lastInsertId();
|
||||||
|
|
||||||
|
// Load source custom fields for this part
|
||||||
|
$stmtLoadCF->execute([(int)$part['id']]);
|
||||||
|
$customFields = $stmtLoadCF->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
foreach ($customFields as $cf) {
|
||||||
|
$stmtInsertCF->execute([
|
||||||
|
':part_id' => $newPartId,
|
||||||
|
':field_id' => (int)$cf['field_id'],
|
||||||
|
':value_id' => ($cf['value_id'] !== null && $cf['value_id'] !== '') ? (int)$cf['value_id'] : null,
|
||||||
|
':value_text' => $cf['value_text'] !== '' ? $cf['value_text'] : null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$clonedCountForTarget++;
|
||||||
|
$totalClonedParts++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$details[] = [
|
||||||
|
'target_iddatadb' => $targetIddatadb,
|
||||||
|
'cloned_parts' => $clonedCountForTarget
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'source_iddatadb' => $sourceIddatadb,
|
||||||
|
'cloned_targets' => count($targetIds),
|
||||||
|
'total_cloned_parts' => $totalClonedParts,
|
||||||
|
'details' => $details
|
||||||
|
]);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
if ($pdo->inTransaction()) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Clone failed: ' . $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'class/db-functions.php';
|
||||||
|
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
|
||||||
|
die("Invalid template ID");
|
||||||
|
}
|
||||||
|
|
||||||
|
$sourceTemplateId = (int)$_GET['id'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
// 1. Get source template
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
name,
|
||||||
|
source_type,
|
||||||
|
header_row,
|
||||||
|
start_column,
|
||||||
|
description,
|
||||||
|
target_table,
|
||||||
|
sample_xlsx,
|
||||||
|
button_size,
|
||||||
|
button_bg_color,
|
||||||
|
button_text_color,
|
||||||
|
button_label,
|
||||||
|
status,
|
||||||
|
client_specific_fields,
|
||||||
|
idclient,
|
||||||
|
clientname,
|
||||||
|
schemaname,
|
||||||
|
idschema,
|
||||||
|
schemajson,
|
||||||
|
xls_headers,
|
||||||
|
api_sample_json,
|
||||||
|
json_nodes,
|
||||||
|
idroutine
|
||||||
|
FROM excel_templates
|
||||||
|
WHERE id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$sourceTemplateId]);
|
||||||
|
$template = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$template) {
|
||||||
|
throw new Exception("Template not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Generate unique clone name
|
||||||
|
$baseName = 'Copia di ' . $template['name'];
|
||||||
|
$newName = $baseName;
|
||||||
|
|
||||||
|
$checkStmt = $pdo->prepare("SELECT COUNT(*) FROM excel_templates WHERE name = ?");
|
||||||
|
$checkStmt->execute([$newName]);
|
||||||
|
|
||||||
|
if ((int)$checkStmt->fetchColumn() > 0) {
|
||||||
|
$counter = 2;
|
||||||
|
|
||||||
|
do {
|
||||||
|
$newName = $baseName . ' (' . $counter . ')';
|
||||||
|
$checkStmt->execute([$newName]);
|
||||||
|
$exists = (int)$checkStmt->fetchColumn() > 0;
|
||||||
|
$counter++;
|
||||||
|
} while ($exists);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Insert cloned template
|
||||||
|
$insertTemplate = $pdo->prepare("
|
||||||
|
INSERT INTO excel_templates (
|
||||||
|
name,
|
||||||
|
source_type,
|
||||||
|
created_at,
|
||||||
|
updated_at,
|
||||||
|
header_row,
|
||||||
|
start_column,
|
||||||
|
description,
|
||||||
|
target_table,
|
||||||
|
sample_xlsx,
|
||||||
|
button_size,
|
||||||
|
button_bg_color,
|
||||||
|
button_text_color,
|
||||||
|
button_label,
|
||||||
|
status,
|
||||||
|
client_specific_fields,
|
||||||
|
idclient,
|
||||||
|
clientname,
|
||||||
|
schemaname,
|
||||||
|
idschema,
|
||||||
|
schemajson,
|
||||||
|
xls_headers,
|
||||||
|
api_sample_json,
|
||||||
|
json_nodes,
|
||||||
|
idroutine
|
||||||
|
) VALUES (
|
||||||
|
:name,
|
||||||
|
:source_type,
|
||||||
|
NOW(),
|
||||||
|
NOW(),
|
||||||
|
:header_row,
|
||||||
|
:start_column,
|
||||||
|
:description,
|
||||||
|
:target_table,
|
||||||
|
:sample_xlsx,
|
||||||
|
:button_size,
|
||||||
|
:button_bg_color,
|
||||||
|
:button_text_color,
|
||||||
|
:button_label,
|
||||||
|
:status,
|
||||||
|
:client_specific_fields,
|
||||||
|
:idclient,
|
||||||
|
:clientname,
|
||||||
|
:schemaname,
|
||||||
|
:idschema,
|
||||||
|
:schemajson,
|
||||||
|
:xls_headers,
|
||||||
|
:api_sample_json,
|
||||||
|
:json_nodes,
|
||||||
|
:idroutine
|
||||||
|
)
|
||||||
|
");
|
||||||
|
|
||||||
|
$insertTemplate->execute([
|
||||||
|
':name' => $newName,
|
||||||
|
':source_type' => $template['source_type'],
|
||||||
|
':header_row' => $template['header_row'],
|
||||||
|
':start_column' => $template['start_column'],
|
||||||
|
':description' => $template['description'],
|
||||||
|
':target_table' => $template['target_table'],
|
||||||
|
':sample_xlsx' => $template['sample_xlsx'],
|
||||||
|
':button_size' => $template['button_size'],
|
||||||
|
':button_bg_color' => $template['button_bg_color'],
|
||||||
|
':button_text_color' => $template['button_text_color'],
|
||||||
|
':button_label' => $template['button_label'],
|
||||||
|
':status' => $template['status'],
|
||||||
|
':client_specific_fields' => $template['client_specific_fields'],
|
||||||
|
':idclient' => $template['idclient'],
|
||||||
|
':clientname' => $template['clientname'],
|
||||||
|
':schemaname' => $template['schemaname'],
|
||||||
|
':idschema' => $template['idschema'],
|
||||||
|
':schemajson' => $template['schemajson'],
|
||||||
|
':xls_headers' => $template['xls_headers'],
|
||||||
|
':api_sample_json' => $template['api_sample_json'],
|
||||||
|
':json_nodes' => $template['json_nodes'],
|
||||||
|
':idroutine' => $template['idroutine']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$newTemplateId = (int)$pdo->lastInsertId();
|
||||||
|
|
||||||
|
if ($newTemplateId <= 0) {
|
||||||
|
throw new Exception("Unable to create cloned template.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Clone template_mapping rows
|
||||||
|
$cloneMappings = $pdo->prepare("
|
||||||
|
INSERT INTO template_mapping (
|
||||||
|
template_id,
|
||||||
|
schema_id,
|
||||||
|
field_id,
|
||||||
|
excel_column,
|
||||||
|
json_node,
|
||||||
|
is_manual,
|
||||||
|
manual_default,
|
||||||
|
auto_value,
|
||||||
|
data_type,
|
||||||
|
is_required,
|
||||||
|
default_value,
|
||||||
|
has_list,
|
||||||
|
length,
|
||||||
|
decimals,
|
||||||
|
min_value,
|
||||||
|
max_value,
|
||||||
|
default_curr_date,
|
||||||
|
tablename,
|
||||||
|
field_label,
|
||||||
|
main_field,
|
||||||
|
is_visible_import,
|
||||||
|
is_visible_parts
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
:new_template_id,
|
||||||
|
schema_id,
|
||||||
|
field_id,
|
||||||
|
excel_column,
|
||||||
|
json_node,
|
||||||
|
is_manual,
|
||||||
|
manual_default,
|
||||||
|
auto_value,
|
||||||
|
data_type,
|
||||||
|
is_required,
|
||||||
|
default_value,
|
||||||
|
has_list,
|
||||||
|
length,
|
||||||
|
decimals,
|
||||||
|
min_value,
|
||||||
|
max_value,
|
||||||
|
default_curr_date,
|
||||||
|
tablename,
|
||||||
|
field_label,
|
||||||
|
main_field,
|
||||||
|
is_visible_import,
|
||||||
|
is_visible_parts
|
||||||
|
FROM template_mapping
|
||||||
|
WHERE template_id = :source_template_id
|
||||||
|
");
|
||||||
|
|
||||||
|
$cloneMappings->execute([
|
||||||
|
':new_template_id' => $newTemplateId,
|
||||||
|
':source_template_id' => $sourceTemplateId
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
header("Location: templates_dashboard.php?cloned=1&new_id=" . $newTemplateId);
|
||||||
|
exit;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
if (isset($pdo) && $pdo->inTransaction()) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
die("Clone error: " . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8'));
|
||||||
|
}
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
<?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,79 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
require_once(__DIR__ . '/include/headscript.php');
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$templateId = (int)($input['template_id'] ?? 0);
|
||||||
|
|
||||||
|
if ($templateId <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Invalid template_id']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If already exists, do nothing
|
||||||
|
$stmt = $pdo->prepare("SELECT COUNT(*) FROM template_fixed_mapping WHERE template_id = ?");
|
||||||
|
$stmt->execute([$templateId]);
|
||||||
|
if ((int)$stmt->fetchColumn() > 0) {
|
||||||
|
echo json_encode(['success' => true, 'created' => 0, 'message' => 'Fixed fields already exist']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FIXED FIELDS STANDARD (no UI selection)
|
||||||
|
* is_manual always 1
|
||||||
|
* is_visible_import default 1
|
||||||
|
*/
|
||||||
|
$fixedFields = [
|
||||||
|
// fixed_field_key, data_type
|
||||||
|
['ClienteResponsabile', 'INT'],
|
||||||
|
['ClienteFornitore', 'INT'],
|
||||||
|
['ClienteAnalisi', 'INT'],
|
||||||
|
['MoltiplicatorePrezzo', 'INT'],
|
||||||
|
['AnagraficaCertestObject', 'INT'],
|
||||||
|
['AnagraficaCertestService', 'INT'],
|
||||||
|
['ConsegnaRichiesta', 'DATE'],
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
$ins = $pdo->prepare("
|
||||||
|
INSERT INTO template_fixed_mapping
|
||||||
|
(template_id, fixed_field_key, is_manual, data_type, is_required, default_value, is_visible_import)
|
||||||
|
VALUES
|
||||||
|
(:template_id, :fixed_field_key, 1, :data_type, 1, NULL, 1)
|
||||||
|
");
|
||||||
|
|
||||||
|
|
||||||
|
foreach ($fixedFields as $f) {
|
||||||
|
$ins->execute([
|
||||||
|
':template_id' => $templateId,
|
||||||
|
':fixed_field_key' => $f[0],
|
||||||
|
':data_type' => $f[1],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
// Return rows (for client render)
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT id, template_id, fixed_field_key, is_manual, data_type, is_required, default_value, is_visible_import
|
||||||
|
FROM template_fixed_mapping
|
||||||
|
WHERE template_id = ?
|
||||||
|
ORDER BY id ASC
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([$templateId]);
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'created' => count($rows), 'rows' => $rows]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
<?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);
|
||||||
|
}
|
||||||
|
|
||||||
|
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
|
||||||
|
|
||||||
|
// 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
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
https://93.43.5.102/limsapi/api/odata/Matrice
|
||||||