Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1249ad3079 | ||
|
|
48ee511166 | ||
|
|
6718e08d1f | ||
|
|
b8b01cd5d5 | ||
|
|
f31ba24008 | ||
|
|
bb2ebcc122 | ||
|
|
955c760756 | ||
|
|
5c12d97d0e | ||
|
|
9bed740223 | ||
|
|
8be7ea0b85 | ||
|
|
dc0209dcf1 | ||
|
|
9cce361b90 | ||
|
|
c966096060 | ||
|
|
55da10cea4 | ||
|
|
0d3ed2505f | ||
|
|
def7377447 | ||
|
|
9453e7a01a | ||
|
|
8f4ac3d53e | ||
|
|
34f7490e94 | ||
|
|
d9d6a7ab58 |
+10
@@ -62,3 +62,13 @@ public/userarea/schemi_base_response.json
|
|||||||
|
|
||||||
public/userarea/cache/
|
public/userarea/cache/
|
||||||
public/userarea/error_log.txt
|
public/userarea/error_log.txt
|
||||||
|
|
||||||
|
# PDF/QR generati dai template (userarea)
|
||||||
|
/public/userarea/pdftemplates/
|
||||||
|
# Runtime / rigenerati - non tracciare
|
||||||
|
public/userarea/error_log.txt
|
||||||
|
public/userarea/schemi_base_response.json
|
||||||
|
public/userarea/logs/
|
||||||
|
public/userarea/imported_trf/
|
||||||
|
public/userarea/xlstemplates/
|
||||||
|
.well-known/
|
||||||
|
|||||||
+1
-1
@@ -41,7 +41,7 @@
|
|||||||
"laravel/socialite": "^5.16",
|
"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": "^7.1",
|
||||||
"phpoffice/phpspreadsheet": "^4.1",
|
"phpoffice/phpspreadsheet": "^4.1",
|
||||||
"proengsoft/laravel-jsvalidation": "^4.0.0",
|
"proengsoft/laravel-jsvalidation": "^4.0.0",
|
||||||
"robmorgan/phinx": "^0.16.12",
|
"robmorgan/phinx": "^0.16.12",
|
||||||
|
|||||||
Generated
+11
-10
@@ -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": "557df76fdde549210349be369d479f5d",
|
"content-hash": "e54cf3e0bbc04b94b563b01620ac3416",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "akaunting/laravel-setting",
|
"name": "akaunting/laravel-setting",
|
||||||
@@ -4166,16 +4166,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpmailer/phpmailer",
|
"name": "phpmailer/phpmailer",
|
||||||
"version": "v6.9.3",
|
"version": "v7.1.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/PHPMailer/PHPMailer.git",
|
"url": "https://github.com/PHPMailer/PHPMailer.git",
|
||||||
"reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e"
|
"reference": "1bc1716a507a65e039d4ac9d9adebbbd0d346e15"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2f5c94fe7493efc213f643c23b1b1c249d40f47e",
|
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/1bc1716a507a65e039d4ac9d9adebbbd0d346e15",
|
||||||
"reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e",
|
"reference": "1bc1716a507a65e039d4ac9d9adebbbd0d346e15",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -4189,13 +4189,14 @@
|
|||||||
"doctrine/annotations": "^1.2.6 || ^1.13.3",
|
"doctrine/annotations": "^1.2.6 || ^1.13.3",
|
||||||
"php-parallel-lint/php-console-highlighter": "^1.0.0",
|
"php-parallel-lint/php-console-highlighter": "^1.0.0",
|
||||||
"php-parallel-lint/php-parallel-lint": "^1.3.2",
|
"php-parallel-lint/php-parallel-lint": "^1.3.2",
|
||||||
"phpcompatibility/php-compatibility": "^9.3.5",
|
"phpcompatibility/php-compatibility": "^10.0.0@dev",
|
||||||
"roave/security-advisories": "dev-latest",
|
"squizlabs/php_codesniffer": "^3.13.5",
|
||||||
"squizlabs/php_codesniffer": "^3.7.2",
|
|
||||||
"yoast/phpunit-polyfills": "^1.0.4"
|
"yoast/phpunit-polyfills": "^1.0.4"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
|
"decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
|
||||||
|
"directorytree/imapengine": "For uploading sent messages via IMAP, see gmail example",
|
||||||
|
"ext-imap": "Needed to support advanced email address parsing according to RFC822",
|
||||||
"ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
|
"ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
|
||||||
"ext-openssl": "Needed for secure SMTP sending and DKIM signing",
|
"ext-openssl": "Needed for secure SMTP sending and DKIM signing",
|
||||||
"greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
|
"greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
|
||||||
@@ -4235,7 +4236,7 @@
|
|||||||
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
|
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/PHPMailer/PHPMailer/issues",
|
"issues": "https://github.com/PHPMailer/PHPMailer/issues",
|
||||||
"source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.3"
|
"source": "https://github.com/PHPMailer/PHPMailer/tree/v7.1.1"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -4243,7 +4244,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-11-24T18:04:13+00:00"
|
"time": "2026-05-18T08:06:14+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpoffice/phpspreadsheet",
|
"name": "phpoffice/phpspreadsheet",
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aggiunge il flag `engine` alla tabella `routine` esistente (0 = legacy su file,
|
||||||
|
* 1 = motore generico basato su routine_step) e crea la tabella `routine_step`.
|
||||||
|
*
|
||||||
|
* Migrazione puramente additiva: le routine legacy esistenti (id 1..4) restano
|
||||||
|
* intatte e con engine = 0 continuano a funzionare come prima.
|
||||||
|
*/
|
||||||
|
final class AddEngineAndRoutineStep extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
// 1) Flag engine sulla tabella esistente `routine`
|
||||||
|
$routine = $this->table('routine');
|
||||||
|
if (!$routine->hasColumn('engine')) {
|
||||||
|
$routine
|
||||||
|
->addColumn('engine', 'boolean', [
|
||||||
|
'default' => 0,
|
||||||
|
'null' => false,
|
||||||
|
'comment' => '0 = routine legacy su file, 1 = motore generico (routine_step)',
|
||||||
|
'after' => 'filename',
|
||||||
|
])
|
||||||
|
->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Nuova tabella routine_step
|
||||||
|
if (!$this->hasTable('routine_step')) {
|
||||||
|
$table = $this->table('routine_step', [
|
||||||
|
'id' => 'idstep',
|
||||||
|
'signed' => false,
|
||||||
|
'engine' => 'InnoDB',
|
||||||
|
'collation' => 'utf8mb4_general_ci',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$table
|
||||||
|
->addColumn('idroutine', 'integer', [
|
||||||
|
'null' => false,
|
||||||
|
'comment' => 'FK verso routine.idroutine',
|
||||||
|
])
|
||||||
|
->addColumn('step_order', 'integer', [
|
||||||
|
'default' => 1,
|
||||||
|
'null' => false,
|
||||||
|
'comment' => 'Ordine di esecuzione dello step nella routine',
|
||||||
|
])
|
||||||
|
->addColumn('operation', 'string', [
|
||||||
|
'limit' => 50,
|
||||||
|
'null' => false,
|
||||||
|
'comment' => 'Verbo operazione: merge, replace, trim, copy, clear, uppercase, lowercase',
|
||||||
|
])
|
||||||
|
->addColumn('params', 'text', [
|
||||||
|
'null' => true,
|
||||||
|
'comment' => 'Parametri operazione in formato JSON',
|
||||||
|
])
|
||||||
|
->addColumn('active', 'boolean', [
|
||||||
|
'default' => 1,
|
||||||
|
'null' => false,
|
||||||
|
'comment' => 'Se 0 lo step viene saltato',
|
||||||
|
])
|
||||||
|
->addIndex(['idroutine', 'step_order'], ['name' => 'idx_routine_order'])
|
||||||
|
->create();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class AddLogFileToDatadb extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
$table = $this->table('datadb');
|
||||||
|
if (!$table->hasColumn('log_file')) {
|
||||||
|
$table->addColumn('log_file', 'string', [
|
||||||
|
'limit' => 255,
|
||||||
|
'null' => true,
|
||||||
|
'default' => null,
|
||||||
|
'after' => 'commessaweb', // aggiusta se la colonna non esiste
|
||||||
|
])->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class AddErrorStatusToDatadb extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
// Aggiunge 'x' (errore export) all'enum status, preservando i valori esistenti
|
||||||
|
$this->execute("ALTER TABLE `datadb`
|
||||||
|
MODIFY `status` ENUM('i','P','l','x') NULL DEFAULT NULL");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
// Rollback: riporta le eventuali 'x' a NULL prima di togliere il valore
|
||||||
|
$this->execute("UPDATE `datadb` SET `status` = NULL WHERE `status` = 'x'");
|
||||||
|
$this->execute("ALTER TABLE `datadb`
|
||||||
|
MODIFY `status` ENUM('i','P','l') NULL DEFAULT NULL");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class AddNotifyExportEmailToAuthUsers extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
$table = $this->table('auth_users');
|
||||||
|
if (!$table->hasColumn('notify_export_email')) {
|
||||||
|
$table->addColumn('notify_export_email', 'boolean', [
|
||||||
|
'default' => true, // di default ricevono la mail
|
||||||
|
'null' => false,
|
||||||
|
'after' => 'lims_global_user_id',
|
||||||
|
])->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class AddAnalysisPriceToPartAnalyses extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
$this->table('identification_parts_analyses')
|
||||||
|
->addColumn('analysis_price', 'decimal', [
|
||||||
|
'precision' => 10,
|
||||||
|
'scale' => 2,
|
||||||
|
'null' => true,
|
||||||
|
'default' => null,
|
||||||
|
'after' => 'analysis_level',
|
||||||
|
])
|
||||||
|
->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ex public/userarea/sql/lims_value_binding.sql (feature/xls-mapping)
|
||||||
|
*/
|
||||||
|
final class CreateLimsValueBinding extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
if ($this->hasTable('lims_value_binding')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->execute("
|
||||||
|
CREATE TABLE `lims_value_binding` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`template_id` int(11) NOT NULL,
|
||||||
|
`mapping_id` int(11) NOT NULL,
|
||||||
|
`field_id` int(11) NOT NULL,
|
||||||
|
`source_value` varchar(500) NOT NULL,
|
||||||
|
`source_key` varchar(500) NOT NULL,
|
||||||
|
`lims_value_id` int(11) NOT NULL,
|
||||||
|
`lims_value` varchar(500) DEFAULT NULL,
|
||||||
|
`created_by` int(11) DEFAULT NULL,
|
||||||
|
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uq_mapping_source` (`mapping_id`, `source_key`),
|
||||||
|
KEY `idx_template` (`template_id`),
|
||||||
|
KEY `idx_field_source` (`field_id`, `source_key`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$this->execute("DROP TABLE IF EXISTS `lims_value_binding`");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ex public/userarea/sql/fixed_field_binding.sql (feature/xls-mapping)
|
||||||
|
* Idempotente: salta colonne/indici gia' presenti.
|
||||||
|
*/
|
||||||
|
final class XlsFixedFieldBinding extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
// --- template_fixed_mapping ---
|
||||||
|
$tfm = $this->table('template_fixed_mapping');
|
||||||
|
|
||||||
|
if (!$tfm->hasColumn('json_node')) {
|
||||||
|
$this->execute("ALTER TABLE `template_fixed_mapping`
|
||||||
|
ADD COLUMN `json_node` varchar(500) DEFAULT NULL");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$tfm->hasColumn('default_source')) {
|
||||||
|
$this->execute("ALTER TABLE `template_fixed_mapping`
|
||||||
|
ADD COLUMN `default_source` enum('manual','xls','json') NOT NULL DEFAULT 'manual'");
|
||||||
|
} else {
|
||||||
|
$this->execute("ALTER TABLE `template_fixed_mapping`
|
||||||
|
MODIFY COLUMN `default_source` enum('manual','xls','json') NOT NULL DEFAULT 'manual'");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$tfm->hasColumn('excel_column')) {
|
||||||
|
$this->execute("ALTER TABLE `template_fixed_mapping`
|
||||||
|
ADD COLUMN `excel_column` varchar(255) DEFAULT NULL AFTER `json_node`");
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- lims_value_binding ---
|
||||||
|
$lvb = $this->table('lims_value_binding');
|
||||||
|
|
||||||
|
if (!$lvb->hasColumn('binding_kind')) {
|
||||||
|
$this->execute("ALTER TABLE `lims_value_binding`
|
||||||
|
ADD COLUMN `binding_kind` enum('custom','fixed') NOT NULL DEFAULT 'custom' AFTER `template_id`");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->execute("ALTER TABLE `lims_value_binding`
|
||||||
|
MODIFY COLUMN `field_id` int(11) NOT NULL DEFAULT 0");
|
||||||
|
|
||||||
|
if ($lvb->hasIndexByName('uq_mapping_source')) {
|
||||||
|
$this->execute("ALTER TABLE `lims_value_binding` DROP INDEX `uq_mapping_source`");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$lvb->hasIndexByName('uq_kind_mapping_source')) {
|
||||||
|
$this->execute("ALTER TABLE `lims_value_binding`
|
||||||
|
ADD UNIQUE KEY `uq_kind_mapping_source` (`binding_kind`, `mapping_id`, `source_key`)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$lvb = $this->table('lims_value_binding');
|
||||||
|
|
||||||
|
if ($lvb->hasIndexByName('uq_kind_mapping_source')) {
|
||||||
|
$this->execute("ALTER TABLE `lims_value_binding` DROP INDEX `uq_kind_mapping_source`");
|
||||||
|
}
|
||||||
|
if (!$lvb->hasIndexByName('uq_mapping_source')) {
|
||||||
|
$this->execute("ALTER TABLE `lims_value_binding`
|
||||||
|
ADD UNIQUE KEY `uq_mapping_source` (`mapping_id`, `source_key`)");
|
||||||
|
}
|
||||||
|
if ($lvb->hasColumn('binding_kind')) {
|
||||||
|
$this->execute("ALTER TABLE `lims_value_binding` DROP COLUMN `binding_kind`");
|
||||||
|
}
|
||||||
|
$this->execute("ALTER TABLE `lims_value_binding`
|
||||||
|
MODIFY COLUMN `field_id` int(11) NOT NULL");
|
||||||
|
|
||||||
|
$tfm = $this->table('template_fixed_mapping');
|
||||||
|
foreach (['excel_column', 'default_source', 'json_node'] as $col) {
|
||||||
|
if ($tfm->hasColumn($col)) {
|
||||||
|
$this->execute("ALTER TABLE `template_fixed_mapping` DROP COLUMN `$col`");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Seed\AbstractSeed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed dimostrativo del motore generico.
|
||||||
|
*
|
||||||
|
* - Assicura che le routine legacy esistenti abbiano engine = 0 (nessun impatto).
|
||||||
|
* - Crea una routine di esempio "merge_D_F_H_into_J" con engine = 1
|
||||||
|
* e il relativo step: unisce le colonne D, F, H nella colonna J.
|
||||||
|
*
|
||||||
|
* Nota: filename per le routine engine punta al file motore condiviso _engine.php,
|
||||||
|
* così apply_routine.php lo risolve correttamente anche se il flusso invia il filename.
|
||||||
|
*/
|
||||||
|
final class RoutineEngineSeed extends AbstractSeed
|
||||||
|
{
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$pdo = $this->getAdapter()->getConnection();
|
||||||
|
|
||||||
|
// 1) Le legacy restano engine = 0 (idempotente).
|
||||||
|
$pdo->exec("UPDATE routine SET engine = 0 WHERE engine IS NULL");
|
||||||
|
|
||||||
|
// 2) Crea/aggiorna la routine engine di esempio.
|
||||||
|
$name = 'merge_D_F_H_into_J';
|
||||||
|
$description = 'Unisce le colonne D, F, H nella colonna J durante l\'importazione';
|
||||||
|
$filename = '_engine.php';
|
||||||
|
|
||||||
|
$check = $pdo->prepare("SELECT idroutine FROM routine WHERE name = :name LIMIT 1");
|
||||||
|
$check->execute([':name' => $name]);
|
||||||
|
$existing = $check->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($existing) {
|
||||||
|
$idroutine = (int)$existing['idroutine'];
|
||||||
|
$upd = $pdo->prepare(
|
||||||
|
"UPDATE routine
|
||||||
|
SET description = :descr, filename = :fn, engine = 1, instruction = :instr
|
||||||
|
WHERE idroutine = :id"
|
||||||
|
);
|
||||||
|
$upd->execute([
|
||||||
|
':descr' => $description,
|
||||||
|
':fn' => $filename,
|
||||||
|
':instr' => 'Routine gestita dal motore generico (routine_step).',
|
||||||
|
':id' => $idroutine,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$ins = $pdo->prepare(
|
||||||
|
"INSERT INTO routine (name, description, instruction, filename, engine, headerrow)
|
||||||
|
VALUES (:name, :descr, :instr, :fn, 1, 1)"
|
||||||
|
);
|
||||||
|
$ins->execute([
|
||||||
|
':name' => $name,
|
||||||
|
':descr' => $description,
|
||||||
|
':instr' => 'Routine gestita dal motore generico (routine_step).',
|
||||||
|
':fn' => $filename,
|
||||||
|
]);
|
||||||
|
$idroutine = (int)$pdo->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Ripulisci eventuali step precedenti di questa routine e reinserisci.
|
||||||
|
$del = $pdo->prepare("DELETE FROM routine_step WHERE idroutine = :id");
|
||||||
|
$del->execute([':id' => $idroutine]);
|
||||||
|
|
||||||
|
// Step unico: merge D,F,H -> J. Colonne espresse in lettere per leggibilità.
|
||||||
|
$params = json_encode([
|
||||||
|
'source_columns' => ['D', 'F', 'H'],
|
||||||
|
'target_column' => 'J',
|
||||||
|
'separator' => ' ',
|
||||||
|
'skip_empty' => true,
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
|
$insStep = $pdo->prepare(
|
||||||
|
"INSERT INTO routine_step (idroutine, step_order, operation, params, active)
|
||||||
|
VALUES (:id, 1, 'merge', :params, 1)"
|
||||||
|
);
|
||||||
|
$insStep->execute([':id' => $idroutine, ':params' => $params]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ $templatedashboard = "Template Dashboard";
|
|||||||
$newtemplate = "New Template";
|
$newtemplate = "New Template";
|
||||||
$dashtemplate = "Dashboard Templates";
|
$dashtemplate = "Dashboard Templates";
|
||||||
$insertnewtemplatexls = "Insert New Template XLS";
|
$insertnewtemplatexls = "Insert New Template XLS";
|
||||||
|
$bindingsmanage = "Value Bindings";
|
||||||
// Datatables
|
// Datatables
|
||||||
$langdatatables = [
|
$langdatatables = [
|
||||||
"paginate_first" => "First",
|
"paginate_first" => "First",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
is_web_selectable: rowEl.getAttribute("data-web") === "1" ? 1 : 0,
|
is_web_selectable: rowEl.getAttribute("data-web") === "1" ? 1 : 0,
|
||||||
is_accredited:
|
is_accredited:
|
||||||
rowEl.getAttribute("data-accredited") === "1" ? 1 : 0,
|
rowEl.getAttribute("data-accredited") === "1" ? 1 : 0,
|
||||||
|
analysis_price: rowEl.getAttribute("data-price") || "0",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,6 +197,10 @@
|
|||||||
: null,
|
: null,
|
||||||
is_web_selectable: payload.is_web_selectable,
|
is_web_selectable: payload.is_web_selectable,
|
||||||
is_accredited: payload.is_accredited,
|
is_accredited: payload.is_accredited,
|
||||||
|
analysis_price:
|
||||||
|
payload.analysis_price !== ""
|
||||||
|
? parseFloat(payload.analysis_price)
|
||||||
|
: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,6 +252,7 @@
|
|||||||
analysis_level: payload.analysis_level,
|
analysis_level: payload.analysis_level,
|
||||||
is_web_selectable: payload.is_web_selectable,
|
is_web_selectable: payload.is_web_selectable,
|
||||||
is_accredited: payload.is_accredited,
|
is_accredited: payload.is_accredited,
|
||||||
|
analysis_price: payload.analysis_price,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.done(function (resp) {
|
.done(function (resp) {
|
||||||
@@ -389,6 +395,17 @@
|
|||||||
const selectable = item.SelezionabileSuWeb === true;
|
const selectable = item.SelezionabileSuWeb === true;
|
||||||
const accredited = item.Accreditato === true;
|
const accredited = item.Accreditato === true;
|
||||||
const level = item.Livello ?? "";
|
const level = item.Livello ?? "";
|
||||||
|
|
||||||
|
const rawPrice = item.Prezzo;
|
||||||
|
const priceNum =
|
||||||
|
rawPrice === null ||
|
||||||
|
rawPrice === undefined ||
|
||||||
|
rawPrice === ""
|
||||||
|
? 0
|
||||||
|
: parseFloat(String(rawPrice).replace(",", "."));
|
||||||
|
const price = isNaN(priceNum) ? 0 : priceNum;
|
||||||
|
const hasPrice = price > 0;
|
||||||
|
|
||||||
const searchText = (
|
const searchText = (
|
||||||
analysisName +
|
analysisName +
|
||||||
" " +
|
" " +
|
||||||
@@ -414,6 +431,10 @@
|
|||||||
"</span>";
|
"</span>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hasPrice) {
|
||||||
|
badges += '<span class="badge bg-danger">No price</span>';
|
||||||
|
}
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div
|
<div
|
||||||
class="list-group-item analysis-analysis-item"
|
class="list-group-item analysis-analysis-item"
|
||||||
@@ -423,6 +444,7 @@
|
|||||||
data-analysis-level="${escapeHtml(level)}"
|
data-analysis-level="${escapeHtml(level)}"
|
||||||
data-web="${selectable ? "1" : "0"}"
|
data-web="${selectable ? "1" : "0"}"
|
||||||
data-accredited="${accredited ? "1" : "0"}"
|
data-accredited="${accredited ? "1" : "0"}"
|
||||||
|
data-price="${escapeHtml(price)}"
|
||||||
data-search="${escapeHtml(searchText)}"
|
data-search="${escapeHtml(searchText)}"
|
||||||
>
|
>
|
||||||
<div class="fw-semibold">${escapeHtml(analysisName)}</div>
|
<div class="fw-semibold">${escapeHtml(analysisName)}</div>
|
||||||
@@ -440,7 +462,6 @@
|
|||||||
filterAnalysisList();
|
filterAnalysisList();
|
||||||
syncSelectedAnalysisRows();
|
syncSelectedAnalysisRows();
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterAnalysisList() {
|
function filterAnalysisList() {
|
||||||
const modal = document.getElementById("analysisModal");
|
const modal = document.getElementById("analysisModal");
|
||||||
if (!modal) return;
|
if (!modal) return;
|
||||||
@@ -552,6 +573,20 @@
|
|||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
analyses.sort(function (a, b) {
|
||||||
|
const nameA = (
|
||||||
|
a.NomeAnalisiTraduzione ||
|
||||||
|
a.NomeAnalisi ||
|
||||||
|
""
|
||||||
|
).toLowerCase();
|
||||||
|
const nameB = (
|
||||||
|
b.NomeAnalisiTraduzione ||
|
||||||
|
b.NomeAnalisi ||
|
||||||
|
""
|
||||||
|
).toLowerCase();
|
||||||
|
return nameA.localeCompare(nameB);
|
||||||
|
});
|
||||||
|
|
||||||
analysisLoadedCache[cacheKey] = analyses;
|
analysisLoadedCache[cacheKey] = analyses;
|
||||||
renderAnalysesList(analyses);
|
renderAnalysesList(analyses);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -21,12 +21,50 @@ try {
|
|||||||
throw new Exception("Dati della routine mancanti.");
|
throw new Exception("Dati della routine mancanti.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$routineFile = __DIR__ . '/routines/' . $filename;
|
// --- NUOVO: risolvi la routine dal DB per decidere legacy vs motore ---
|
||||||
|
// Riusa la connessione PDO del progetto.
|
||||||
|
require_once __DIR__ . '/class/db-functions.php';
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
// Identifica la routine: preferisci idroutine se passato, altrimenti risali dal filename.
|
||||||
|
$idroutine = isset($input['routine_data']['idroutine'])
|
||||||
|
? (int)$input['routine_data']['idroutine']
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
if ($idroutine > 0) {
|
||||||
|
$stmt = $pdo->prepare("SELECT idroutine, name, filename, engine FROM routine WHERE idroutine = :id LIMIT 1");
|
||||||
|
$stmt->execute([':id' => $idroutine]);
|
||||||
|
} else {
|
||||||
|
// Il matching per filename è ambiguo per le routine a motore: tutte
|
||||||
|
// condividono _engine.php. Senza idroutine non possiamo sapere quale
|
||||||
|
// routine engine applicare -> meglio fermarsi che eseguire quella sbagliata.
|
||||||
|
if ($filename === '_engine.php') {
|
||||||
|
throw new Exception("Routine a motore senza idroutine: impossibile determinare quale routine applicare. Verifica che il template inoltri routine_data.idroutine.");
|
||||||
|
}
|
||||||
|
$stmt = $pdo->prepare("SELECT idroutine, name, filename, engine FROM routine WHERE filename = :fn LIMIT 1");
|
||||||
|
$stmt->execute([':fn' => $filename]);
|
||||||
|
}
|
||||||
|
$routineRow = $stmt->fetch();
|
||||||
|
|
||||||
|
// engine = 1 -> motore generico (_engine.php); 0 o record assente -> legacy come oggi.
|
||||||
|
$useEngine = $routineRow && (int)$routineRow['engine'] === 1;
|
||||||
|
|
||||||
|
if ($useEngine) {
|
||||||
|
// Il motore ha bisogno di sapere quale routine caricare dagli step.
|
||||||
|
$routineData['idroutine'] = (int)$routineRow['idroutine'];
|
||||||
|
$routineData['name'] = $routineData['name'] ?? ($routineRow['name'] ?? '');
|
||||||
|
$routineFile = __DIR__ . '/routines/_engine.php';
|
||||||
|
} else {
|
||||||
|
// Percorso legacy: il vecchio file della routine, identico a prima.
|
||||||
|
$legacyFilename = $routineRow['filename'] ?? $filename;
|
||||||
|
$routineFile = __DIR__ . '/routines/' . $legacyFilename;
|
||||||
|
}
|
||||||
|
|
||||||
if (file_exists($routineFile)) {
|
if (file_exists($routineFile)) {
|
||||||
include_once $routineFile;
|
include_once $routineFile;
|
||||||
$routineData['xls_headers'] = $_SESSION['headers'] ?? [];
|
$routineData['xls_headers'] = $_SESSION['headers'] ?? [];
|
||||||
applyRoutine($excelData, $routineData); // Modifica $excelData in place
|
applyRoutine($excelData, $routineData); // Modifica $excelData in place
|
||||||
error_log("Routine {$routineData['name']} applicata (file: {$filename}) per template {$template_id}, header row: {$headerrow}");
|
error_log("Routine " . ($routineData['name'] ?? '') . " applicata (" . ($useEngine ? "motore" : "file: {$filename}") . ") per template {$template_id}, header row: {$headerrow}");
|
||||||
} else {
|
} else {
|
||||||
throw new Exception("File della routine non trovato: $routineFile");
|
throw new Exception("File della routine non trovato: $routineFile");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,299 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
include('include/headscript.php');
|
||||||
|
require_once __DIR__ . '/class/binding-functions.php';
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$templateFilter = isset($_GET['template_id']) ? (int) $_GET['template_id'] : 0;
|
||||||
|
$search = trim($_GET['q'] ?? '');
|
||||||
|
|
||||||
|
$templates = $pdo->query("SELECT id, name FROM excel_templates ORDER BY name")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$conds = [];
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
if ($templateFilter > 0) {
|
||||||
|
$conds[] = 'b.template_id = ?';
|
||||||
|
$params[] = $templateFilter;
|
||||||
|
}
|
||||||
|
if ($search !== '') {
|
||||||
|
$conds[] = '(b.source_value LIKE ? OR b.lims_value LIKE ? OR m.field_label LIKE ? OR f.fixed_field_key LIKE ?)';
|
||||||
|
$like = '%' . $search . '%';
|
||||||
|
array_push($params, $like, $like, $like, $like);
|
||||||
|
}
|
||||||
|
$where = $conds ? ('WHERE ' . implode(' AND ', $conds)) : '';
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
"SELECT b.id, b.template_id, b.binding_kind, b.mapping_id, b.field_id, b.source_value,
|
||||||
|
b.lims_value_id, b.lims_value, b.updated_at,
|
||||||
|
t.name AS template_name,
|
||||||
|
COALESCE(m.field_label, f.fixed_field_key) AS field_label,
|
||||||
|
COALESCE(m.excel_column, f.excel_column) AS excel_column,
|
||||||
|
f.fixed_field_key
|
||||||
|
FROM lims_value_binding b
|
||||||
|
LEFT JOIN excel_templates t ON t.id = b.template_id
|
||||||
|
LEFT JOIN template_mapping m ON m.id = b.mapping_id AND b.binding_kind = 'custom'
|
||||||
|
LEFT JOIN template_fixed_mapping f ON f.id = b.mapping_id AND b.binding_kind = 'fixed'
|
||||||
|
$where
|
||||||
|
ORDER BY t.name ASC, field_label ASC, b.source_value ASC"
|
||||||
|
);
|
||||||
|
$stmt->execute($params);
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$groups = [];
|
||||||
|
foreach ($rows as $r) {
|
||||||
|
$kind = $r['binding_kind'] ?? 'custom';
|
||||||
|
$gkey = $kind . ':' . (int) $r['mapping_id'];
|
||||||
|
if (!isset($groups[$gkey])) {
|
||||||
|
$groups[$gkey] = [
|
||||||
|
'kind' => $kind,
|
||||||
|
'label' => $kind === 'fixed'
|
||||||
|
? binding_fixed_label((string) $r['fixed_field_key'])
|
||||||
|
: ($r['field_label'] ?: ('Mapping ' . $gkey)),
|
||||||
|
'excel_column' => $r['excel_column'] ?: '',
|
||||||
|
'template_name' => $r['template_name'] ?: ('Template ' . $r['template_id']),
|
||||||
|
'template_id' => (int) $r['template_id'],
|
||||||
|
'rows' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$groups[$gkey]['rows'][] = $r;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!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>
|
||||||
|
.source-value {
|
||||||
|
font-family: Consolas, Monaco, monospace;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
td .select2-container {
|
||||||
|
min-width: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-selection--single {
|
||||||
|
height: 38px;
|
||||||
|
border-color: #ced4da;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-selection--single .select2-selection__rendered {
|
||||||
|
line-height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-selection--single .select2-selection__arrow {
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<title>Gestione binding - <?= 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">
|
||||||
|
<h6 class="mb-0">Binding valori importati → LIMS</h6>
|
||||||
|
<small class="text-muted">
|
||||||
|
Le modifiche valgono dalla prossima importazione: i record gia' importati non vengono aggiornati.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<form class="row g-2 align-items-end mb-3" method="get">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label mb-1">Template</label>
|
||||||
|
<select class="form-select" name="template_id" onchange="this.form.submit()">
|
||||||
|
<option value="">Tutti</option>
|
||||||
|
<?php foreach ($templates as $t): ?>
|
||||||
|
<option value="<?= (int) $t['id'] ?>" <?= $templateFilter === (int) $t['id'] ? 'selected' : '' ?>>
|
||||||
|
<?= htmlspecialchars($t['name']) ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label mb-1">Cerca</label>
|
||||||
|
<input type="text" class="form-control" name="q" value="<?= htmlspecialchars($search) ?>"
|
||||||
|
placeholder="valore importato, valore LIMS, campo...">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<button type="submit" class="btn btn-primary">Filtra</button>
|
||||||
|
<a href="bindings_manage.php" class="btn btn-outline-secondary">Azzera</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div id="bindingError" class="alert alert-danger" style="display:none;"></div>
|
||||||
|
|
||||||
|
<?php if (empty($rows)): ?>
|
||||||
|
<div class="alert alert-info mb-0">Nessun binding salvato per i filtri selezionati.</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered align-middle">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:32px;"></th>
|
||||||
|
<th>Valore importato</th>
|
||||||
|
<th>Valore LIMS</th>
|
||||||
|
<th style="width:150px;">Aggiornato</th>
|
||||||
|
<th style="width:110px;">Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($groups as $gkey => $g): ?>
|
||||||
|
<tr class="table-light">
|
||||||
|
<td colspan="5">
|
||||||
|
<strong><?= htmlspecialchars($g['label']) ?></strong>
|
||||||
|
<?php if (($g['kind'] ?? 'custom') === 'fixed'): ?><span class="badge bg-light text-dark border">campo fisso</span><?php endif; ?>
|
||||||
|
<span class="text-muted">
|
||||||
|
· <?= htmlspecialchars($g['template_name']) ?>
|
||||||
|
<?php if ($g['excel_column'] !== ''): ?>
|
||||||
|
· colonna «<?= htmlspecialchars($g['excel_column']) ?>»
|
||||||
|
<?php endif; ?>
|
||||||
|
· <?= count($g['rows']) ?> valore/i
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php foreach ($g['rows'] as $r): ?>
|
||||||
|
<tr class="binding-row"
|
||||||
|
data-binding-id="<?= (int) $r['id'] ?>"
|
||||||
|
data-kind="<?= htmlspecialchars($r['binding_kind'] ?? 'custom', ENT_QUOTES) ?>"
|
||||||
|
data-fixed-key="<?= htmlspecialchars($r['fixed_field_key'] ?? '', ENT_QUOTES) ?>"
|
||||||
|
data-template-id="<?= (int) $r['template_id'] ?>"
|
||||||
|
data-mapping-id="<?= (int) $r['mapping_id'] ?>"
|
||||||
|
data-field-id="<?= (int) $r['field_id'] ?>"
|
||||||
|
data-source-value="<?= htmlspecialchars($r['source_value'], ENT_QUOTES) ?>">
|
||||||
|
<td class="text-muted ps-4">›</td>
|
||||||
|
<td class="source-value"><?= htmlspecialchars($r['source_value']) ?></td>
|
||||||
|
<td>
|
||||||
|
<select class="form-select binding-select">
|
||||||
|
<option value="<?= (int) $r['lims_value_id'] ?>" selected>
|
||||||
|
<?= htmlspecialchars($r['lims_value'] ?: ('#' . $r['lims_value_id'])) ?>
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<div class="binding-status small mt-1"></div>
|
||||||
|
</td>
|
||||||
|
<td class="small text-muted"><?= htmlspecialchars((string) $r['updated_at']) ?></td>
|
||||||
|
<td>
|
||||||
|
<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 endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?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 $error = $('#bindingError');
|
||||||
|
|
||||||
|
$('.binding-select').each(function() {
|
||||||
|
const $row = $(this).closest('.binding-row');
|
||||||
|
const isFixed = $row.data('kind') === 'fixed';
|
||||||
|
|
||||||
|
$(this).select2({
|
||||||
|
placeholder: 'Seleziona valore LIMS...',
|
||||||
|
width: '100%',
|
||||||
|
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
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.binding-select').on('change', function() {
|
||||||
|
const $select = $(this);
|
||||||
|
const $row = $select.closest('.binding-row');
|
||||||
|
const $status = $row.find('.binding-status');
|
||||||
|
const selected = $select.select2('data')[0] || {};
|
||||||
|
|
||||||
|
if (!$select.val()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$status.removeClass('text-success text-danger').addClass('text-muted').text('Salvataggio...');
|
||||||
|
|
||||||
|
$.post('save_binding.php', {
|
||||||
|
kind: $row.data('kind') || 'custom',
|
||||||
|
template_id: $row.data('template-id'),
|
||||||
|
mapping_id: $row.data('mapping-id'),
|
||||||
|
field_id: $row.data('field-id'),
|
||||||
|
source_value: String($row.data('source-value')),
|
||||||
|
lims_value_id: $select.val(),
|
||||||
|
lims_value: selected.text || ''
|
||||||
|
}).then(resp => {
|
||||||
|
if (resp && resp.success) {
|
||||||
|
$status.removeClass('text-muted').addClass('text-success').text('Salvato');
|
||||||
|
} else {
|
||||||
|
$status.removeClass('text-muted').addClass('text-danger').text((resp && resp.error) || 'Errore');
|
||||||
|
}
|
||||||
|
}).fail(() => {
|
||||||
|
$status.removeClass('text-muted').addClass('text-danger').text('Errore di rete');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.delete-binding-btn').on('click', function() {
|
||||||
|
const $row = $(this).closest('.binding-row');
|
||||||
|
|
||||||
|
$error.hide();
|
||||||
|
|
||||||
|
$.post('delete_binding.php', {
|
||||||
|
binding_id: $row.data('binding-id')
|
||||||
|
}).then(resp => {
|
||||||
|
if (resp && resp.success) {
|
||||||
|
$row.remove();
|
||||||
|
} else {
|
||||||
|
$error.text((resp && resp.error) || 'Errore durante l\'eliminazione.').show();
|
||||||
|
}
|
||||||
|
}).fail(() => {
|
||||||
|
$error.text('Errore di rete durante l\'eliminazione.').show();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use PHPMailer\PHPMailer\PHPMailer;
|
||||||
|
use PHPMailer\PHPMailer\Exception as PHPMailerException;
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../../vendor/autoload.php'; // aggiusta il path se serve
|
||||||
|
|
||||||
|
class Mailer
|
||||||
|
{
|
||||||
|
public static function send(array $to, string $subject, string $htmlBody, ?string $altBody = null): array
|
||||||
|
{
|
||||||
|
$mail = new PHPMailer(true);
|
||||||
|
try {
|
||||||
|
$mail->isSMTP();
|
||||||
|
$mail->Host = env('MAIL_HOST');
|
||||||
|
$mail->SMTPAuth = true;
|
||||||
|
$mail->Username = env('MAIL_USERNAME');
|
||||||
|
$mail->Password = env('MAIL_PASSWORD');
|
||||||
|
$mail->Port = (int) env('MAIL_PORT', 465);
|
||||||
|
$enc = env('MAIL_ENCRYPTION', 'ssl');
|
||||||
|
$mail->SMTPSecure = ($enc === 'tls')
|
||||||
|
? PHPMailer::ENCRYPTION_STARTTLS
|
||||||
|
: PHPMailer::ENCRYPTION_SMTPS;
|
||||||
|
$mail->CharSet = 'UTF-8';
|
||||||
|
|
||||||
|
$mail->setFrom(env('MAIL_FROM_ADDRESS'), env('MAIL_FROM_NAME', 'SmartTRF'));
|
||||||
|
|
||||||
|
foreach ($to as $addr) {
|
||||||
|
$addr = trim((string)$addr);
|
||||||
|
if ($addr !== '' && filter_var($addr, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
$mail->addAddress($addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$mail->getToAddresses()) {
|
||||||
|
return ['ok' => false, 'error' => 'Nessun destinatario valido'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$mail->isHTML(true);
|
||||||
|
$mail->Subject = $subject;
|
||||||
|
$mail->Body = $htmlBody;
|
||||||
|
$mail->AltBody = $altBody ?: strip_tags($htmlBody);
|
||||||
|
|
||||||
|
$mail->send();
|
||||||
|
return ['ok' => true, 'error' => null];
|
||||||
|
} catch (PHPMailerException $e) {
|
||||||
|
error_log('Mailer error: ' . $mail->ErrorInfo);
|
||||||
|
return ['ok' => false, 'error' => $mail->ErrorInfo];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,716 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
function binding_normalize_value($value): string
|
||||||
|
{
|
||||||
|
$s = (string) $value;
|
||||||
|
$s = str_replace(
|
||||||
|
["\xC2\xA0", "\xE2\x80\xAF", "\xE2\x80\x87", "\xE2\x80\x89", "\xE2\x80\x8A", "\xEF\xBB\xBF"],
|
||||||
|
' ',
|
||||||
|
$s
|
||||||
|
);
|
||||||
|
$s = preg_replace('/\s+/u', ' ', $s);
|
||||||
|
|
||||||
|
return mb_strtolower(trim((string) $s));
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_is_list_field(array $mapping): bool
|
||||||
|
{
|
||||||
|
return strcasecmp(trim((string) ($mapping['data_type'] ?? '')), 'SceltaMultipla') === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_lims_value_index(int $fieldId): array
|
||||||
|
{
|
||||||
|
static $memo = [];
|
||||||
|
|
||||||
|
if (array_key_exists($fieldId, $memo)) {
|
||||||
|
return $memo[$fieldId];
|
||||||
|
}
|
||||||
|
|
||||||
|
$index = [];
|
||||||
|
foreach (binding_get_lims_values($fieldId) as $v) {
|
||||||
|
$id = (int) ($v['IdCustomFieldsValue'] ?? 0);
|
||||||
|
if ($id > 0) {
|
||||||
|
$index[$id] = (string) ($v['Valore'] ?? '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $memo[$fieldId] = $index;
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_has_lims_values(int $fieldId): bool
|
||||||
|
{
|
||||||
|
return binding_get_lims_values($fieldId) !== [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_is_lims_id(int $fieldId, $value): bool
|
||||||
|
{
|
||||||
|
$raw = trim((string) $value);
|
||||||
|
if ($raw === '' || !ctype_digit($raw)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$index = binding_lims_value_index($fieldId);
|
||||||
|
|
||||||
|
return $index === [] ? false : isset($index[(int) $raw]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_auto_match(int $fieldId, string $sourceValue): ?array
|
||||||
|
{
|
||||||
|
$needle = binding_normalize_value($sourceValue);
|
||||||
|
if ($needle === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$matches = [];
|
||||||
|
foreach (binding_get_lims_values($fieldId) as $v) {
|
||||||
|
if (binding_normalize_value($v['Valore'] ?? '') === $needle) {
|
||||||
|
$matches[] = [
|
||||||
|
'id' => (int) ($v['IdCustomFieldsValue'] ?? 0),
|
||||||
|
'text' => (string) ($v['Valore'] ?? ''),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count($matches) === 1 && $matches[0]['id'] > 0 ? $matches[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_lookup(PDO $pdo, int $mappingId, string $sourceValue, string $kind = 'custom'): ?array
|
||||||
|
{
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
"SELECT id, lims_value_id, lims_value, source_value
|
||||||
|
FROM lims_value_binding
|
||||||
|
WHERE binding_kind = ? AND mapping_id = ? AND source_key = ?
|
||||||
|
LIMIT 1"
|
||||||
|
);
|
||||||
|
$stmt->execute([$kind, $mappingId, binding_normalize_value($sourceValue)]);
|
||||||
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
return $row ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_upsert(
|
||||||
|
PDO $pdo,
|
||||||
|
int $templateId,
|
||||||
|
int $mappingId,
|
||||||
|
int $fieldId,
|
||||||
|
string $sourceValue,
|
||||||
|
int $limsValueId,
|
||||||
|
string $limsValue,
|
||||||
|
?int $createdBy,
|
||||||
|
string $kind = 'custom'
|
||||||
|
): void {
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
"INSERT INTO lims_value_binding
|
||||||
|
(template_id, binding_kind, mapping_id, field_id, source_value, source_key, 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)"
|
||||||
|
);
|
||||||
|
$stmt->execute([
|
||||||
|
$templateId,
|
||||||
|
$kind,
|
||||||
|
$mappingId,
|
||||||
|
$fieldId,
|
||||||
|
mb_substr(trim($sourceValue), 0, 500),
|
||||||
|
mb_substr(binding_normalize_value($sourceValue), 0, 500),
|
||||||
|
$limsValueId,
|
||||||
|
mb_substr($limsValue, 0, 500),
|
||||||
|
$createdBy,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_delete(PDO $pdo, int $mappingId, string $sourceValue, string $kind = 'custom'): int
|
||||||
|
{
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM lims_value_binding WHERE binding_kind = ? AND mapping_id = ? AND source_key = ?");
|
||||||
|
$stmt->execute([$kind, $mappingId, binding_normalize_value($sourceValue)]);
|
||||||
|
|
||||||
|
return $stmt->rowCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_delete_by_id(PDO $pdo, int $bindingId): int
|
||||||
|
{
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM lims_value_binding WHERE id = ?");
|
||||||
|
$stmt->execute([$bindingId]);
|
||||||
|
|
||||||
|
return $stmt->rowCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_suggest(PDO $pdo, int $fieldId, string $sourceValue, int $excludeMappingId): ?array
|
||||||
|
{
|
||||||
|
if ($fieldId <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
"SELECT b.lims_value_id, b.lims_value, b.template_id, t.name AS template_name
|
||||||
|
FROM lims_value_binding b
|
||||||
|
LEFT JOIN excel_templates t ON t.id = b.template_id
|
||||||
|
WHERE b.binding_kind = 'custom' AND b.field_id = ? AND b.source_key = ? AND b.mapping_id <> ?
|
||||||
|
ORDER BY b.updated_at DESC
|
||||||
|
LIMIT 1"
|
||||||
|
);
|
||||||
|
$stmt->execute([$fieldId, binding_normalize_value($sourceValue), $excludeMappingId]);
|
||||||
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
return $row ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_apply_to_details(PDO $pdo, int $mappingId, string $value, array $datadbIds): int
|
||||||
|
{
|
||||||
|
$datadbIds = array_values(array_filter(array_map('intval', $datadbIds)));
|
||||||
|
if (empty($datadbIds)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$placeholders = implode(',', array_fill(0, count($datadbIds), '?'));
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
"UPDATE import_data_details
|
||||||
|
SET field_value = ?
|
||||||
|
WHERE mapping_id = ? AND id IN ($placeholders)"
|
||||||
|
);
|
||||||
|
$stmt->execute(array_merge([$value, $mappingId], $datadbIds));
|
||||||
|
|
||||||
|
return $stmt->rowCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_find_column_index(string $column, array $columns)
|
||||||
|
{
|
||||||
|
$needle = binding_normalize_value($column);
|
||||||
|
if ($needle === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($columns as $i => $c) {
|
||||||
|
if (binding_normalize_value($c) === $needle) {
|
||||||
|
return $i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_fixed_alias_map(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'ClienteResponsabile' => 'cliente_responsabile_id',
|
||||||
|
'ClienteFornitore' => 'cliente_fornitore_id',
|
||||||
|
'ClienteAnalisi' => 'clienteAnalisi',
|
||||||
|
'ClienteFatturazione' => 'ClienteFatturazione',
|
||||||
|
'MoltiplicatorePrezzo' => 'moltiplicatore_prezzo_id',
|
||||||
|
'AnagraficaCertestObject' => 'anagrafica_certest_object_id',
|
||||||
|
'AnagraficaCertestService' => 'anagrafica_certest_service_id',
|
||||||
|
'ConsegnaRichiesta' => 'consegna_richiesta',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_fixed_column(string $key): ?string
|
||||||
|
{
|
||||||
|
return binding_fixed_alias_map()[$key] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_fixed_is_list(string $key): bool
|
||||||
|
{
|
||||||
|
return in_array($key, [
|
||||||
|
'ClienteResponsabile',
|
||||||
|
'ClienteFornitore',
|
||||||
|
'ClienteAnalisi',
|
||||||
|
'ClienteFatturazione',
|
||||||
|
'MoltiplicatorePrezzo',
|
||||||
|
'AnagraficaCertestObject',
|
||||||
|
'AnagraficaCertestService',
|
||||||
|
], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_fixed_is_date(string $key): bool
|
||||||
|
{
|
||||||
|
return $key === 'ConsegnaRichiesta';
|
||||||
|
}
|
||||||
|
|
||||||
|
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' => 'Buyer',
|
||||||
|
'ClienteFatturazione' => 'Cliente Fatturazione',
|
||||||
|
'ConsegnaRichiesta' => 'Consegna Richiesta',
|
||||||
|
];
|
||||||
|
|
||||||
|
return $labels[$key] ?? $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_parse_date($value): ?string
|
||||||
|
{
|
||||||
|
$raw = trim((string) $value);
|
||||||
|
if ($raw === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (['Y-m-d', 'd/m/Y', 'd-m-Y', 'd.m.Y', 'Y/m/d', 'd/m/y', 'Y-m-d H:i:s'] as $format) {
|
||||||
|
$date = DateTime::createFromFormat($format, $raw);
|
||||||
|
if ($date && $date->format($format) === $raw) {
|
||||||
|
return $date->format('Y-m-d');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$ts = strtotime($raw);
|
||||||
|
|
||||||
|
return $ts ? date('Y-m-d', $ts) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_fixed_default(array $fixedMapping): string
|
||||||
|
{
|
||||||
|
$value = trim((string) ($fixedMapping['default_value'] ?? ''));
|
||||||
|
if ($value === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$isDate = strcasecmp((string) ($fixedMapping['data_type'] ?? ''), 'DATE') === 0
|
||||||
|
|| binding_fixed_is_date((string) ($fixedMapping['fixed_field_key'] ?? ''));
|
||||||
|
|
||||||
|
if ($isDate && preg_match('/^today\s*(?:\+\s*(\d+))?$/i', $value, $m)) {
|
||||||
|
return date('Y-m-d', strtotime('+' . (int) ($m[1] ?? 0) . ' days'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_cached_get($api, string $cacheFile, string $endpoint, int $ttl = 3600): array
|
||||||
|
{
|
||||||
|
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < $ttl)) {
|
||||||
|
$data = json_decode(file_get_contents($cacheFile), true);
|
||||||
|
if (is_array($data)) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $api->get($endpoint);
|
||||||
|
if (is_array($data)) {
|
||||||
|
$dir = dirname($cacheFile);
|
||||||
|
if (!is_dir($dir)) {
|
||||||
|
mkdir($dir, 0777, true);
|
||||||
|
}
|
||||||
|
file_put_contents($cacheFile, json_encode($data));
|
||||||
|
}
|
||||||
|
|
||||||
|
return is_array($data) ? $data : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
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((string) ($client['Nominativo'] ?? ''));
|
||||||
|
$code = trim((string) ($client['CodiceCliente'] ?? ''));
|
||||||
|
|
||||||
|
return $code !== '' ? ($name . ' - ' . $code) : $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
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'] ?? ''));
|
||||||
|
$name = (string) ($r['NomeAnagrafica'] ?? '');
|
||||||
|
$out[] = [
|
||||||
|
'id' => (int) ($r['IdAnagrafica'] ?? 0),
|
||||||
|
'text' => $name,
|
||||||
|
'aliases' => array_values(array_filter([$code, $code !== '' ? "$code - $name" : ''])),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
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':
|
||||||
|
case 'ClienteFatturazione':
|
||||||
|
$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] = array_values(array_filter($out, fn($v) => $v['id'] > 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_fixed_has_values(PDO $pdo, string $fieldKey, int $templateId): bool
|
||||||
|
{
|
||||||
|
return binding_get_fixed_values($pdo, $fieldKey, $templateId) !== [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_fixed_is_lims_id(PDO $pdo, string $fieldKey, int $templateId, $value): bool
|
||||||
|
{
|
||||||
|
$raw = trim((string) $value);
|
||||||
|
if ($raw === '' || !ctype_digit($raw)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (binding_get_fixed_values($pdo, $fieldKey, $templateId) as $v) {
|
||||||
|
if ((int) $v['id'] === (int) $raw) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_auto_match_fixed(array $values, string $sourceValue): ?array
|
||||||
|
{
|
||||||
|
$needle = binding_normalize_value($sourceValue);
|
||||||
|
if ($needle === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$matches = [];
|
||||||
|
foreach ($values as $v) {
|
||||||
|
$candidates = array_merge([$v['text'] ?? ''], $v['aliases'] ?? []);
|
||||||
|
foreach ($candidates as $candidate) {
|
||||||
|
if (binding_normalize_value($candidate) === $needle) {
|
||||||
|
$matches[] = $v;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count($matches) === 1 ? $matches[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_suggest_fixed(PDO $pdo, string $fieldKey, string $sourceValue, int $excludeMappingId): ?array
|
||||||
|
{
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
"SELECT b.lims_value_id, b.lims_value, b.template_id, t.name AS template_name
|
||||||
|
FROM lims_value_binding b
|
||||||
|
JOIN template_fixed_mapping f ON f.id = b.mapping_id
|
||||||
|
LEFT JOIN excel_templates t ON t.id = b.template_id
|
||||||
|
WHERE b.binding_kind = 'fixed' AND f.fixed_field_key = ?
|
||||||
|
AND b.source_key = ? AND b.mapping_id <> ?
|
||||||
|
ORDER BY b.updated_at DESC
|
||||||
|
LIMIT 1"
|
||||||
|
);
|
||||||
|
$stmt->execute([$fieldKey, binding_normalize_value($sourceValue), $excludeMappingId]);
|
||||||
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
return $row ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_apply_to_datadb(PDO $pdo, string $fieldKey, $value, array $datadbIds): int
|
||||||
|
{
|
||||||
|
$column = binding_fixed_column($fieldKey);
|
||||||
|
if ($column === null) {
|
||||||
|
throw new InvalidArgumentException("Campo fisso sconosciuto: $fieldKey");
|
||||||
|
}
|
||||||
|
|
||||||
|
$datadbIds = array_values(array_filter(array_map('intval', $datadbIds)));
|
||||||
|
if (empty($datadbIds)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$placeholders = implode(',', array_fill(0, count($datadbIds), '?'));
|
||||||
|
$stmt = $pdo->prepare("UPDATE datadb SET `$column` = ? WHERE iddatadb IN ($placeholders)");
|
||||||
|
$stmt->execute(array_merge([$value === '' ? null : $value], $datadbIds));
|
||||||
|
|
||||||
|
return $stmt->rowCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_write_fixed_columns(PDO $pdo, int $iddatadb, array $valuesByKey): int
|
||||||
|
{
|
||||||
|
$map = binding_fixed_alias_map();
|
||||||
|
$sets = [];
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
foreach ($valuesByKey as $key => $value) {
|
||||||
|
if (!isset($map[$key]) || $value === null || $value === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$sets[] = "`{$map[$key]}` = ?";
|
||||||
|
$params[] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($sets)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$params[] = $iddatadb;
|
||||||
|
$stmt = $pdo->prepare("UPDATE datadb SET " . implode(', ', $sets) . " WHERE iddatadb = ?");
|
||||||
|
$stmt->execute($params);
|
||||||
|
|
||||||
|
return $stmt->rowCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_resolve_for_import(
|
||||||
|
PDO $pdo,
|
||||||
|
array $mapping,
|
||||||
|
int $templateId,
|
||||||
|
string $sourceValue,
|
||||||
|
int $iddatadb,
|
||||||
|
?int $userId,
|
||||||
|
array &$saved,
|
||||||
|
array &$auto,
|
||||||
|
array &$pending
|
||||||
|
): string {
|
||||||
|
$mappingId = (int) $mapping['id'];
|
||||||
|
$fieldId = (int) $mapping['field_id'];
|
||||||
|
$key = 'cf:' . $mappingId . '|' . binding_normalize_value($sourceValue);
|
||||||
|
|
||||||
|
$collect = function (array &$bucket, array $extra = []) use ($key, $mapping, $mappingId, $fieldId, $sourceValue, $iddatadb) {
|
||||||
|
if (!isset($bucket[$key])) {
|
||||||
|
$bucket[$key] = array_merge([
|
||||||
|
'kind' => 'custom',
|
||||||
|
'mapping_id' => $mappingId,
|
||||||
|
'field_id' => $fieldId,
|
||||||
|
'field_label' => $mapping['field_label'] ?? ('Mapping ' . $mappingId),
|
||||||
|
'source_value' => $sourceValue,
|
||||||
|
'datadb_ids' => [],
|
||||||
|
], $extra);
|
||||||
|
}
|
||||||
|
$bucket[$key]['datadb_ids'][] = $iddatadb;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!binding_has_lims_values($fieldId)) {
|
||||||
|
return $sourceValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binding_is_lims_id($fieldId, $sourceValue)) {
|
||||||
|
return $sourceValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($existing = binding_lookup($pdo, $mappingId, $sourceValue)) {
|
||||||
|
$collect($saved, [
|
||||||
|
'lims_value_id' => (int) $existing['lims_value_id'],
|
||||||
|
'lims_value' => (string) $existing['lims_value'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (string) $existing['lims_value_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($match = binding_auto_match($fieldId, $sourceValue)) {
|
||||||
|
binding_upsert($pdo, $templateId, $mappingId, $fieldId, $sourceValue, $match['id'], $match['text'], $userId);
|
||||||
|
$collect($auto, [
|
||||||
|
'lims_value_id' => $match['id'],
|
||||||
|
'lims_value' => $match['text'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (string) $match['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$collect($pending);
|
||||||
|
|
||||||
|
return $sourceValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_resolve_fixed_for_import(
|
||||||
|
PDO $pdo,
|
||||||
|
array $fixedMapping,
|
||||||
|
int $templateId,
|
||||||
|
string $sourceValue,
|
||||||
|
int $iddatadb,
|
||||||
|
?int $userId,
|
||||||
|
array &$saved,
|
||||||
|
array &$auto,
|
||||||
|
array &$pending
|
||||||
|
): ?string {
|
||||||
|
$fieldKey = (string) $fixedMapping['fixed_field_key'];
|
||||||
|
$mappingId = (int) $fixedMapping['id'];
|
||||||
|
$key = 'fx:' . $mappingId . '|' . binding_normalize_value($sourceValue);
|
||||||
|
|
||||||
|
$collect = function (array &$bucket, array $extra = []) use ($key, $fieldKey, $mappingId, $sourceValue, $iddatadb) {
|
||||||
|
if (!isset($bucket[$key])) {
|
||||||
|
$bucket[$key] = array_merge([
|
||||||
|
'kind' => 'fixed',
|
||||||
|
'mapping_id' => $mappingId,
|
||||||
|
'fixed_field_key' => $fieldKey,
|
||||||
|
'field_id' => 0,
|
||||||
|
'field_label' => binding_fixed_label($fieldKey),
|
||||||
|
'source_value' => $sourceValue,
|
||||||
|
'datadb_ids' => [],
|
||||||
|
], $extra);
|
||||||
|
}
|
||||||
|
$bucket[$key]['datadb_ids'][] = $iddatadb;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (binding_fixed_is_date($fieldKey)) {
|
||||||
|
return binding_parse_date($sourceValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!binding_fixed_is_list($fieldKey)) {
|
||||||
|
return $sourceValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!binding_fixed_has_values($pdo, $fieldKey, $templateId)) {
|
||||||
|
error_log("[BINDING] campo fisso $fieldKey: lista LIMS non disponibile, valore non scritto");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binding_fixed_is_lims_id($pdo, $fieldKey, $templateId, $sourceValue)) {
|
||||||
|
return (string) (int) $sourceValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($existing = binding_lookup($pdo, $mappingId, $sourceValue, 'fixed')) {
|
||||||
|
$collect($saved, [
|
||||||
|
'lims_value_id' => (int) $existing['lims_value_id'],
|
||||||
|
'lims_value' => (string) $existing['lims_value'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (string) $existing['lims_value_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binding_fixed_auto_matchable($fieldKey)) {
|
||||||
|
$match = binding_auto_match_fixed(binding_get_fixed_values($pdo, $fieldKey, $templateId), $sourceValue);
|
||||||
|
if ($match) {
|
||||||
|
binding_upsert($pdo, $templateId, $mappingId, 0, $sourceValue, (int) $match['id'], (string) $match['text'], $userId, 'fixed');
|
||||||
|
$collect($auto, [
|
||||||
|
'lims_value_id' => (int) $match['id'],
|
||||||
|
'lims_value' => (string) $match['text'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (string) $match['id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$collect($pending);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_merge_buckets(array $bucket, array $items): array
|
||||||
|
{
|
||||||
|
foreach ($items as $key => $item) {
|
||||||
|
if (!isset($bucket[$key])) {
|
||||||
|
$bucket[$key] = $item;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$bucket[$key]['datadb_ids'] = array_values(array_unique(array_merge(
|
||||||
|
$bucket[$key]['datadb_ids'] ?? [],
|
||||||
|
$item['datadb_ids'] ?? []
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
function binding_list_for_template(PDO $pdo, int $templateId): array
|
||||||
|
{
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
"SELECT b.*,
|
||||||
|
COALESCE(m.field_label, f.fixed_field_key) AS field_label,
|
||||||
|
COALESCE(m.excel_column, f.excel_column) AS excel_column,
|
||||||
|
f.fixed_field_key
|
||||||
|
FROM lims_value_binding b
|
||||||
|
LEFT JOIN template_mapping m ON m.id = b.mapping_id AND b.binding_kind = 'custom'
|
||||||
|
LEFT JOIN template_fixed_mapping f ON f.id = b.mapping_id AND b.binding_kind = 'fixed'
|
||||||
|
WHERE b.template_id = ?
|
||||||
|
ORDER BY field_label ASC, b.source_value ASC"
|
||||||
|
);
|
||||||
|
$stmt->execute([$templateId]);
|
||||||
|
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||||
|
require_once __DIR__ . '/class/db-functions.php';
|
||||||
|
require_once __DIR__ . '/class/binding-functions.php';
|
||||||
|
include dirname(__DIR__) . '/../extra/auth.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
if (!Auth::check()) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Method not allowed']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bindingId = intval($_POST['binding_id'] ?? 0);
|
||||||
|
|
||||||
|
if ($bindingId <= 0) {
|
||||||
|
http_response_code(422);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Missing binding_id']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
$deleted = binding_delete_by_id($pdo, $bindingId);
|
||||||
|
|
||||||
|
echo json_encode(['success' => $deleted > 0, 'deleted' => $deleted]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
include('include/headscript.php');
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
$logDir = __DIR__ . '/logs/api/';
|
||||||
|
|
||||||
|
$iddatadb = (int)($_GET['iddatadb'] ?? 0);
|
||||||
|
if ($iddatadb <= 0) {
|
||||||
|
http_response_code(400);
|
||||||
|
exit('iddatadb mancante');
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT log_file, commessaweb FROM datadb WHERE iddatadb = :id LIMIT 1");
|
||||||
|
$stmt->execute(['id' => $iddatadb]);
|
||||||
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$row || empty($row['log_file'])) {
|
||||||
|
http_response_code(404);
|
||||||
|
exit('Nessun log per questa commessa');
|
||||||
|
}
|
||||||
|
|
||||||
|
// solo basename: niente path traversal
|
||||||
|
$path = $logDir . basename($row['log_file']);
|
||||||
|
if (!is_file($path)) {
|
||||||
|
http_response_code(404);
|
||||||
|
exit('File di log non trovato: ' . basename($row['log_file']));
|
||||||
|
}
|
||||||
|
|
||||||
|
while (ob_get_level() > 0) {
|
||||||
|
ob_end_clean();
|
||||||
|
}
|
||||||
|
$label = preg_replace('/[^A-Za-z0-9_\-]/', '_', (string)($row['commessaweb'] ?: $iddatadb));
|
||||||
|
header('Content-Type: text/plain; charset=utf-8');
|
||||||
|
header('Content-Disposition: attachment; filename="log_' . $label . '_' . $iddatadb . '.log"');
|
||||||
|
header('Content-Length: ' . filesize($path));
|
||||||
|
readfile($path);
|
||||||
|
exit;
|
||||||
@@ -1,376 +0,0 @@
|
|||||||
2025-06-10 12:21:44 - Errore nel recupero dati: HTTP 404, Risposta:
|
|
||||||
2025-06-16 11:15:30 - Errore nel recupero dati: HTTP 404, Risposta: {"title":"Not Found","status":404,"detail":"Not Found","instance":"GET /api/odata/Rapporto(2523026)","errorCode":"d25cbd678"}
|
|
||||||
2025-06-16 11:34:59 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to 93.43.5.102 port 443 after 21033 ms: Couldn't connect to server, Risposta:
|
|
||||||
2025-06-16 11:42:03 - Errore nella richiesta: Recv failure: Connection was reset
|
|
||||||
2025-07-04 10:42:49 - Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
|
||||||
2025-07-04 10:44:13 - Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
|
||||||
2025-07-04 10:48:07 - Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
|
||||||
2025-08-19 16:29:25 - Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
|
||||||
2025-08-26 16:47:19 - Risposta non JSON valida: <?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="DevExpress.ExpressApp.SystemModule" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="DashboardViewItemDescriptor"><Key><PropertyRef Name="ViewId" /></Key><Property Name="ViewId" Type="Edm.String" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem" BaseType="DevExpress.ExpressApp.NonPersistentLiteObject" Abstract="true"><Property Name="Visibility" Type="DevExpress.ExpressApp.Editors.ViewItemVisibility" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem_1OfIModelDashboardViewItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem" Abstract="true" /><EntityType Name="ViewDashboardOrganizationItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem_1OfIModelDashboardViewItem"><Property Name="ObjectType" Type="System.Type" /><Proper
|
|
||||||
2025-08-26 16:48:15 - Risposta non JSON valida: <?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="DevExpress.ExpressApp.SystemModule" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="DashboardViewItemDescriptor"><Key><PropertyRef Name="ViewId" /></Key><Property Name="ViewId" Type="Edm.String" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem" BaseType="DevExpress.ExpressApp.NonPersistentLiteObject" Abstract="true"><Property Name="Visibility" Type="DevExpress.ExpressApp.Editors.ViewItemVisibility" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem_1OfIModelDashboardViewItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem" Abstract="true" /><EntityType Name="ViewDashboardOrganizationItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem_1OfIModelDashboardViewItem"><Property Name="ObjectType" Type="System.Type" /><Proper
|
|
||||||
2025-08-26 16:48:44 - Risposta non JSON valida: <?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="DevExpress.ExpressApp.SystemModule" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="DashboardViewItemDescriptor"><Key><PropertyRef Name="ViewId" /></Key><Property Name="ViewId" Type="Edm.String" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem" BaseType="DevExpress.ExpressApp.NonPersistentLiteObject" Abstract="true"><Property Name="Visibility" Type="DevExpress.ExpressApp.Editors.ViewItemVisibility" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem_1OfIModelDashboardViewItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem" Abstract="true" /><EntityType Name="ViewDashboardOrganizationItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem_1OfIModelDashboardViewItem"><Property Name="ObjectType" Type="System.Type" /><Proper
|
|
||||||
2025-08-26 16:49:24 - Risposta non JSON valida: <?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="DevExpress.ExpressApp.SystemModule" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="DashboardViewItemDescriptor"><Key><PropertyRef Name="ViewId" /></Key><Property Name="ViewId" Type="Edm.String" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem" BaseType="DevExpress.ExpressApp.NonPersistentLiteObject" Abstract="true"><Property Name="Visibility" Type="DevExpress.ExpressApp.Editors.ViewItemVisibility" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem_1OfIModelDashboardViewItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem" Abstract="true" /><EntityType Name="ViewDashboardOrganizationItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem_1OfIModelDashboardViewItem"><Property Name="ObjectType" Type="System.Type" /><Proper
|
|
||||||
2025-08-26 16:50:23 - Risposta non JSON valida: <?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="DevExpress.ExpressApp.SystemModule" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="DashboardViewItemDescriptor"><Key><PropertyRef Name="ViewId" /></Key><Property Name="ViewId" Type="Edm.String" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem" BaseType="DevExpress.ExpressApp.NonPersistentLiteObject" Abstract="true"><Property Name="Visibility" Type="DevExpress.ExpressApp.Editors.ViewItemVisibility" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem_1OfIModelDashboardViewItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem" Abstract="true" /><EntityType Name="ViewDashboardOrganizationItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem_1OfIModelDashboardViewItem"><Property Name="ObjectType" Type="System.Type" /><Proper
|
|
||||||
2025-09-08 08:39:17 - Risposta non JSON valida: <?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="DevExpress.ExpressApp.SystemModule" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="DashboardViewItemDescriptor"><Key><PropertyRef Name="ViewId" /></Key><Property Name="ViewId" Type="Edm.String" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem" BaseType="DevExpress.ExpressApp.NonPersistentLiteObject" Abstract="true"><Property Name="Visibility" Type="DevExpress.ExpressApp.Editors.ViewItemVisibility" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem_1OfIModelDashboardViewItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem" Abstract="true" /><EntityType Name="ViewDashboardOrganizationItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem_1OfIModelDashboardViewItem"><Property Name="ObjectType" Type="System.Type" /><Proper
|
|
||||||
2026-01-27 15:33:53 - Errore nel recupero dati: HTTP 404, Risposta:
|
|
||||||
2026-01-27 15:34:06 - Errore nel recupero dati: HTTP 404, Risposta:
|
|
||||||
2026-01-27 15:34:10 - Errore nel recupero dati: HTTP 404, Risposta:
|
|
||||||
2026-01-27 15:35:13 - Errore nel recupero dati: HTTP 404, Risposta:
|
|
||||||
2026-01-29 14:33:38 [ClienteResponsabile] Errore nel recupero dati: HTTP 404, Risposta:
|
|
||||||
2026-01-29 14:33:39 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
|
||||||
2026-01-29 14:34:04 [ClienteResponsabile] Errore nel recupero dati: HTTP 404, Risposta:
|
|
||||||
2026-01-29 14:37:29 [ClienteResponsabile] Errore nel recupero dati: HTTP 404, Risposta:
|
|
||||||
2026-01-29 14:41:55 [ClienteResponsabile] Errore nel recupero dati: HTTP 404, Risposta:
|
|
||||||
2026-01-29 14:42:03 [ClienteResponsabile] Errore nel recupero dati: HTTP 404, Risposta:
|
|
||||||
2026-01-29 14:42:52 [ClienteResponsabile] Errore nel recupero dati: HTTP 404, Risposta:
|
|
||||||
2026-01-29 14:43:00 [ClienteResponsabile] Errore nel recupero dati: HTTP 404, Risposta:
|
|
||||||
2026-01-30 10:50:43 [AnagraficaCertestService] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
|
||||||
2026-01-30 10:50:43 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
|
||||||
2026-01-30 11:09:22 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
|
||||||
2026-02-19 10:00:13 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
|
||||||
2026-02-19 10:00:42 [AnagraficaCertestObject] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
|
||||||
2026-02-19 10:00:44 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
|
||||||
2026-02-23 10:44:04 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:42 [AnagraficaCertestService] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:42 [AnagraficaCertestObject] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:42 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:42 [AnagraficaCertestService] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:42 [AnagraficaCertestObject] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:42 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:42 [AnagraficaCertestService] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:42 [AnagraficaCertestObject] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:42 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:43 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:43 [AnagraficaCertestObject] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:43 [AnagraficaCertestService] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:52 [AnagraficaCertestObject] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:52 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:52 [AnagraficaCertestService] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:52 [AnagraficaCertestObject] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:52 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:52 [AnagraficaCertestService] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:52 [AnagraficaCertestObject] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:52 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:52 [AnagraficaCertestService] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:52 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:52 [AnagraficaCertestObject] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:44:53 [AnagraficaCertestService] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:48:26 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:48:26 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:48:27 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:48:27 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:48:27 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:49:04 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:49:04 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:49:04 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:49:05 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 10:49:05 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 14:26:52 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 14:26:59 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 15:54:16 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 16:22:45 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-23 16:32:17 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
|
|
||||||
<HTML><HEAD><TITLE>Not Found</TITLE>
|
|
||||||
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
|
|
||||||
<BODY><h2>Not Found</h2>
|
|
||||||
<hr><p>HTTP Error 404. The requested resource is not found.</p>
|
|
||||||
</BODY></HTML>
|
|
||||||
|
|
||||||
2026-02-25 15:23:12 [AnagraficaCertestObject] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
|
||||||
2026-02-26 15:01:32 [AnagraficaCertestObject] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
|
||||||
2026-02-28 21:01:27 [AnagraficaCertestService] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
|
||||||
2026-03-01 19:11:58 [AnagraficaCertestObject] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
|
||||||
2026-03-18 15:51:45 [AnagraficaCertestService] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
|
||||||
2026-03-19 09:50:34 [AnagraficaCertestService] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
|
||||||
2026-03-25 14:13:34 - Autenticazione fallita: HTTP 503, Errore cURL: , Risposta: The service is unavailable.
|
|
||||||
2026-03-25 14:13:34 - Autenticazione fallita: HTTP 503, Errore cURL: , Risposta: The service is unavailable.
|
|
||||||
2026-03-26 10:57:18 [AnagraficaCertestObject] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
|
||||||
2026-03-26 10:57:21 [AnagraficaCertestObject] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
|
||||||
2026-03-26 10:57:21 [AnagraficaCertestService] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
|
||||||
2026-03-26 10:57:21 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
|
||||||
2026-03-26 10:57:36 [AnagraficaCertestObject] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
|
||||||
2026-03-26 10:57:36 [AnagraficaCertestService] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
|
||||||
2026-03-26 10:57:36 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
|
||||||
2026-03-26 10:57:41 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
|
||||||
2026-03-26 10:57:56 [AnagraficaCertestObject] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
|
||||||
2026-03-26 10:58:11 [AnagraficaCertestService] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
|
||||||
2026-04-30 15:01:25 - Errore nel recupero dati: HTTP 404, Risposta: {"title":"Not Found","status":404,"detail":"Not Found","instance":"GET /api/odata/Rapporto(2621521)","errorCode":"d25cbd678"}
|
|
||||||
2026-04-30 15:02:04 - Errore nel recupero dati: HTTP 404, Risposta:
|
|
||||||
2026-04-30 15:03:19 - Errore nella richiesta: URL rejected: Malformed input to a URL function
|
|
||||||
2026-06-16 14:29:15 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21037 ms: Couldn't connect to server, Risposta:
|
|
||||||
|
|
||||||
|
|
||||||
2026-06-16 14:29:36 [ClienteResponsabile] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21099 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:30:03 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21076 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:30:03 [AnagraficaCertestObject] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21083 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:30:03 [AnagraficaCertestService] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21083 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:30:34 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21043 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:30:55 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21053 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:30:55 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21050 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:31:46 [AnagraficaCertestObject] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21048 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:31:46 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21043 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:31:46 [AnagraficaCertestService] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21042 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:32:07 [ClienteResponsabile] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21047 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:33:09 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21058 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:33:09 [AnagraficaCertestObject] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21057 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:33:09 [AnagraficaCertestService] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21057 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:33:30 [ClienteResponsabile] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21047 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:36:11 [AnagraficaCertestService] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21062 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:36:11 [AnagraficaCertestObject] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21064 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:36:11 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21065 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:36:33 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21049 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:36:33 [AnagraficaCertestService] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21057 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:36:33 [AnagraficaCertestObject] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21053 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:37:13 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21032 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:37:14 [AnagraficaCertestService] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21031 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:37:14 [AnagraficaCertestObject] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21027 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 14:37:35 [ClienteResponsabile] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21054 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 17:13:55 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21067 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 17:14:25 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21065 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 17:14:46 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21071 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-06-16 17:14:46 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21078 ms: Couldn't connect to server, Risposta:
|
|
||||||
2026-07-09 08:06:33 - Autenticazione fallita: HTTP 0, Errore cURL: Could not resolve host: bvcpsitaly-elims.com, Risposta:
|
|
||||||
2026-07-09 08:06:33 - Autenticazione fallita: HTTP 0, Errore cURL: Could not resolve host: bvcpsitaly-elims.com, Risposta:
|
|
||||||
2026-07-09 08:06:33 - Autenticazione fallita: HTTP 0, Errore cURL: Could not resolve host: bvcpsitaly-elims.com, Risposta:
|
|
||||||
2026-07-09 08:06:33 - Autenticazione fallita: HTTP 0, Errore cURL: Could not resolve host: bvcpsitaly-elims.com, Risposta:
|
|
||||||
2026-07-09 08:06:33 - Autenticazione fallita: HTTP 0, Errore cURL: Could not resolve host: bvcpsitaly-elims.com, Risposta:
|
|
||||||
2026-07-09 08:06:41 - Autenticazione fallita: HTTP 0, Errore cURL: Could not resolve host: bvcpsitaly-elims.com, Risposta:
|
|
||||||
2026-07-09 08:06:41 - Autenticazione fallita: HTTP 0, Errore cURL: Could not resolve host: bvcpsitaly-elims.com, Risposta:
|
|
||||||
2026-07-09 08:06:41 - Autenticazione fallita: HTTP 0, Errore cURL: Could not resolve host: bvcpsitaly-elims.com, Risposta:
|
|
||||||
2026-07-09 08:06:41 - Autenticazione fallita: HTTP 0, Errore cURL: Could not resolve host: bvcpsitaly-elims.com, Risposta:
|
|
||||||
2026-07-09 08:06:41 - Autenticazione fallita: HTTP 0, Errore cURL: Could not resolve host: bvcpsitaly-elims.com, Risposta:
|
|
||||||
2026-07-10 08:12:24 - Errore nella richiesta: SSL connection timeout
|
|
||||||
@@ -0,0 +1,363 @@
|
|||||||
|
<?php
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
$template_id = intval($_GET['id'] ?? 0);
|
||||||
|
if (!$template_id) {
|
||||||
|
header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Template ID mancante"));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user_id = $iduserlogin ?? 1;
|
||||||
|
$show_all_users = isset($_GET['all_users']) && $_GET['all_users'] == '1';
|
||||||
|
$allowedLimits = [20, 40, 60, 100];
|
||||||
|
$rawLimit = (int)($_GET['limit'] ?? 20);
|
||||||
|
$perPage = in_array($rawLimit, $allowedLimits) ? $rawLimit : 20;
|
||||||
|
$page = max(1, (int)($_GET['page'] ?? 1));
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
$logDir = __DIR__ . '/logs/api/';
|
||||||
|
|
||||||
|
// Estrae un riassunto breve dell'errore dal file di log della commessa
|
||||||
|
function extractErrorSummary(string $logDir, ?string $logFile): string
|
||||||
|
{
|
||||||
|
if (empty($logFile)) return '';
|
||||||
|
$path = $logDir . basename($logFile);
|
||||||
|
if (!is_file($path)) return '';
|
||||||
|
$content = @file_get_contents($path);
|
||||||
|
if ($content === false || $content === '') return '';
|
||||||
|
|
||||||
|
// 1) prova col "detail" del JSON del 500
|
||||||
|
if (preg_match('/"detail"\s*:\s*"([^"]+)"/', $content, $m)) {
|
||||||
|
return trim(stripcslashes($m[1]));
|
||||||
|
}
|
||||||
|
// 2) fallback: ultima riga MESSAGE:
|
||||||
|
if (preg_match_all('/^MESSAGE:\s*(.+)$/m', $content, $m) && !empty($m[1])) {
|
||||||
|
return trim(end($m[1]));
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query: solo le righe in errore per questo template
|
||||||
|
$userFilter = $show_all_users ? '' : 'AND d.user_id = ?';
|
||||||
|
$baseWhere = "WHERE d.templateid = ? AND d.status = 'x' {$userFilter}";
|
||||||
|
$baseParams = [$template_id];
|
||||||
|
if (!$show_all_users) $baseParams[] = $user_id;
|
||||||
|
|
||||||
|
$countStmt = $pdo->prepare("SELECT COUNT(*) FROM datadb d {$baseWhere}");
|
||||||
|
$countStmt->execute($baseParams);
|
||||||
|
$totalRows = (int)$countStmt->fetchColumn();
|
||||||
|
$totalPages = max(1, (int)ceil($totalRows / $perPage));
|
||||||
|
if ($page > $totalPages) $page = $totalPages;
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT d.*, CONCAT(u.first_name, ' ', u.last_name) AS user_name
|
||||||
|
FROM datadb d
|
||||||
|
LEFT JOIN auth_users u ON d.user_id = u.id
|
||||||
|
{$baseWhere}
|
||||||
|
ORDER BY d.export_date DESC, d.iddatadb DESC
|
||||||
|
LIMIT {$perPage} OFFSET " . (($page - 1) * $perPage) . "
|
||||||
|
");
|
||||||
|
$stmt->execute($baseParams);
|
||||||
|
$rowsData = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||||
|
<?php include('cssinclude.php'); ?>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||||
|
<style>
|
||||||
|
.err-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.err-table th,
|
||||||
|
.err-table td {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.err-table th {
|
||||||
|
background: #f8f9fa;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
.err-table tr:hover {
|
||||||
|
background: #fff8f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-x {
|
||||||
|
background: #dc3545;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.err-msg {
|
||||||
|
color: #b02a37;
|
||||||
|
max-width: 420px;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commessa-code {
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: #555;
|
||||||
|
display: block;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
padding: 5px 8px;
|
||||||
|
margin-right: 4px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-resend {
|
||||||
|
background: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-log {
|
||||||
|
background: #17a2b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-revert {
|
||||||
|
background: #fd7e14;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pager-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pager-num,
|
||||||
|
.pager-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
padding: 0 6px;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #495057;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pager-num.active {
|
||||||
|
background: #0d6efd;
|
||||||
|
color: #fff;
|
||||||
|
border-color: #0d6efd;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<title>Export Errors - <?= 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="mb-3 d-flex align-items-center gap-2">
|
||||||
|
<a href="imported.php?id=<?= $template_id ?>" class="btn btn-warning me-2">Imported (i)</a>
|
||||||
|
<a href="tolims.php?id=<?= $template_id ?>" class="btn btn-success me-2">To LIMS (l)</a>
|
||||||
|
<a href="errors.php?id=<?= $template_id ?>" class="btn btn-danger">Errors (x)</a>
|
||||||
|
<span class="ms-3">
|
||||||
|
<label class="form-check-label" style="font-size:13px; cursor:pointer;">
|
||||||
|
<?php $tp = $_GET;
|
||||||
|
unset($tp['all_users']);
|
||||||
|
$toggleBase = 'errors.php?' . http_build_query($tp); ?>
|
||||||
|
<input type="checkbox" class="form-check-input" <?= $show_all_users ? 'checked' : '' ?>
|
||||||
|
onchange="window.location.href='<?= htmlspecialchars($toggleBase) ?>' + (this.checked ? '&all_users=1' : '')">
|
||||||
|
Show all users
|
||||||
|
</label>
|
||||||
|
</span>
|
||||||
|
<span class="text-muted" style="font-size:12px;">(<?= count($rowsData) ?> of <?= $totalRows ?> errori<?= !$show_all_users ? ' — my records' : '' ?>)</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($totalPages > 1):
|
||||||
|
$pageQuery = $_GET;
|
||||||
|
unset($pageQuery['page']);
|
||||||
|
$pageBase = 'errors.php?' . http_build_query($pageQuery); ?>
|
||||||
|
<div class="pager-bar">
|
||||||
|
<span>Pagina <?= $page ?> di <?= $totalPages ?> — <?= $totalRows ?> righe</span>
|
||||||
|
<span>
|
||||||
|
<?php for ($p = 1; $p <= $totalPages; $p++): ?>
|
||||||
|
<a href="<?= htmlspecialchars($pageBase . '&page=' . $p) ?>" class="pager-num <?= $p === $page ? 'active' : '' ?>"><?= $p ?></a>
|
||||||
|
<?php endfor; ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="card radius-10">
|
||||||
|
<div class="card-header">
|
||||||
|
<span style="font-weight:600;"><i class="fas fa-triangle-exclamation" style="color:#dc3545;"></i> Export falliti verso il LIMS</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (empty($rowsData)): ?>
|
||||||
|
<p class="text-muted mb-0">Nessun errore di export. 🎉</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<div style="overflow-x:auto;">
|
||||||
|
<table class="err-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:170px;">Azioni</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Client</th>
|
||||||
|
<th>Errore</th>
|
||||||
|
<th style="width:150px;">Data export</th>
|
||||||
|
<th>File import</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($rowsData as $row):
|
||||||
|
$errMsg = extractErrorSummary($logDir, $row['log_file'] ?? null); ?>
|
||||||
|
<tr data-id="<?= (int)$row['iddatadb'] ?>">
|
||||||
|
<td>
|
||||||
|
<button type="button" class="action-btn btn-resend resend-btn" data-iddatadb="<?= (int)$row['iddatadb'] ?>" title="Reinvia al LIMS"><i class="fas fa-paper-plane"></i></button>
|
||||||
|
<?php if (!empty($row['log_file'])): ?>
|
||||||
|
<a href="download_logs.php?iddatadb=<?= (int)$row['iddatadb'] ?>" class="action-btn btn-log" title="Scarica log"><i class="fas fa-file-download"></i></a>
|
||||||
|
<?php endif; ?>
|
||||||
|
<button type="button" class="action-btn btn-revert revert-btn" data-iddatadb="<?= (int)$row['iddatadb'] ?>" title="Riporta a Imported"><i class="fas fa-rotate-left"></i></button>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="status-badge status-x">Error</span>
|
||||||
|
<?php if (!empty($row['commessaweb'])): ?>
|
||||||
|
<span class="commessa-code"><?= htmlspecialchars($row['commessaweb']) ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td><span class="client-text" data-client-id="<?= htmlspecialchars((string)($row['idclient'] ?? '')) ?>"><?= htmlspecialchars((string)($row['idclient'] ?? '')) ?></span></td>
|
||||||
|
<td class="err-msg"><?= htmlspecialchars($errMsg ?: '—') ?></td>
|
||||||
|
<td><?= htmlspecialchars((string)($row['export_date'] ?? '')) ?></td>
|
||||||
|
<td><?php if (!empty($row['filename_import'])): ?><a href="imported_trf/<?= htmlspecialchars($row['filename_import']) ?>" target="_blank">File</a><?php endif; ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="overlay toggle-icon"></div>
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(function() {
|
||||||
|
// Risoluzione nomi cliente (come in tolims.php)
|
||||||
|
(function() {
|
||||||
|
const ids = new Set();
|
||||||
|
document.querySelectorAll('.client-text').forEach(el => {
|
||||||
|
const id = el.dataset.clientId;
|
||||||
|
if (id) ids.add(id);
|
||||||
|
});
|
||||||
|
ids.forEach(id => {
|
||||||
|
fetch('search_clienti.php?id=' + encodeURIComponent(id))
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(j => {
|
||||||
|
const it = (j.results || [])[0];
|
||||||
|
if (it) document.querySelectorAll('.client-text[data-client-id="' + id + '"]').forEach(el => el.textContent = it.text);
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Reinvia: richiama export_to_lims.php da capo (create incluso)
|
||||||
|
$(document).on('click', '.resend-btn', function() {
|
||||||
|
const $btn = $(this);
|
||||||
|
const iddatadb = $btn.data('iddatadb');
|
||||||
|
if (!confirm('Reinviare questa commessa al LIMS? Verrà creata una nuova commessa nel LIMS.')) return;
|
||||||
|
|
||||||
|
const original = $btn.html();
|
||||||
|
$btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i>');
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: 'export_to_lims.php',
|
||||||
|
method: 'POST',
|
||||||
|
dataType: 'json',
|
||||||
|
data: {
|
||||||
|
iddatadb: iddatadb
|
||||||
|
},
|
||||||
|
success: function(res) {
|
||||||
|
if (res && res.success) {
|
||||||
|
const $tr = $('tr[data-id="' + iddatadb + '"]');
|
||||||
|
$tr.css('background', '#d4edda');
|
||||||
|
setTimeout(() => $tr.fadeOut(300, () => $tr.remove()), 500);
|
||||||
|
} else {
|
||||||
|
alert('Ancora errore: ' + ((res && res.message) ? res.message : 'invio non riuscito'));
|
||||||
|
$btn.prop('disabled', false).html(original);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
alert('Errore di rete durante il reinvio.');
|
||||||
|
$btn.prop('disabled', false).html(original);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Revert a Imported (i)
|
||||||
|
$(document).on('click', '.revert-btn', function() {
|
||||||
|
const $btn = $(this);
|
||||||
|
const iddatadb = $btn.data('iddatadb');
|
||||||
|
if (!confirm('Riportare questo record a Imported (i)?')) return;
|
||||||
|
$.ajax({
|
||||||
|
url: 'revert_status.php',
|
||||||
|
method: 'POST',
|
||||||
|
dataType: 'json',
|
||||||
|
data: {
|
||||||
|
iddatadb: iddatadb,
|
||||||
|
status: 'i'
|
||||||
|
},
|
||||||
|
success: function(res) {
|
||||||
|
if (res && res.success) {
|
||||||
|
const $tr = $('tr[data-id="' + iddatadb + '"]');
|
||||||
|
$tr.fadeOut(300, () => $tr.remove());
|
||||||
|
} else {
|
||||||
|
alert('Errore: ' + ((res && res.message) ? res.message : 'operazione non riuscita'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
alert('Errore di rete.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -24,6 +24,24 @@
|
|||||||
if (overlay) overlay.style.display = "none";
|
if (overlay) overlay.style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escHtml(v) {
|
||||||
|
return String(v ?? "")
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """)
|
||||||
|
.replace(/'/g, "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Badge "No price" con tooltip dei nomi analisi (vuoto se count = 0)
|
||||||
|
function noPriceBadgeHTML(count, names) {
|
||||||
|
const n = parseInt(count, 10) || 0;
|
||||||
|
if (n <= 0) return "";
|
||||||
|
const title = (names || []).join("\n");
|
||||||
|
return `<span class="badge bg-danger no-price-badge" title="${escHtml(title)}" style="margin-left:6px; font-size:0.85em; cursor:help; vertical-align:middle;"><i class="fas fa-exclamation-triangle"></i> No price (${n})</span>`;
|
||||||
|
}
|
||||||
|
window.noPriceBadgeHTML = noPriceBadgeHTML;
|
||||||
|
|
||||||
function getGridRow(iddatadb) {
|
function getGridRow(iddatadb) {
|
||||||
return document.querySelector(`.grid-row[data-id="${iddatadb}"]`);
|
return document.querySelector(`.grid-row[data-id="${iddatadb}"]`);
|
||||||
}
|
}
|
||||||
@@ -34,6 +52,29 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invia la mail di riepilogo per gli iddatadb processati (fire-and-forget).
|
||||||
|
// Una sola chiamata a fine export (singolo o batch).
|
||||||
|
function sendExportSummaryMail(iddatadbList) {
|
||||||
|
const ids = (iddatadbList || [])
|
||||||
|
.map((v) => parseInt(v, 10))
|
||||||
|
.filter(Boolean);
|
||||||
|
if (!ids.length) return;
|
||||||
|
|
||||||
|
const body = new URLSearchParams();
|
||||||
|
ids.forEach((id) => body.append("ids[]", id));
|
||||||
|
const tid = window.gridMeta && window.gridMeta.templateId;
|
||||||
|
if (tid) body.append("template_id", tid);
|
||||||
|
|
||||||
|
fetch("send_export_summary.php", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||||
|
body: body.toString(),
|
||||||
|
})
|
||||||
|
.then((r) => r.json())
|
||||||
|
.then((res) => console.log("Summary mail:", res))
|
||||||
|
.catch((err) => console.warn("Summary mail failed:", err));
|
||||||
|
}
|
||||||
|
|
||||||
// ── Validation ──────────────────────────────────────────────────────
|
// ── Validation ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
async function validateRows(rowsToValidate) {
|
async function validateRows(rowsToValidate) {
|
||||||
@@ -142,6 +183,9 @@
|
|||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
window.gridData[idx].status = "l";
|
window.gridData[idx].status = "l";
|
||||||
window.gridData[idx].commessaweb = data.commessaweb;
|
window.gridData[idx].commessaweb = data.commessaweb;
|
||||||
|
window.gridData[idx].noPriceCount = data.noPriceCount || 0;
|
||||||
|
window.gridData[idx].noPriceAnalyses =
|
||||||
|
data.noPriceAnalyses || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update visible DOM row
|
// Update visible DOM row
|
||||||
@@ -168,6 +212,11 @@
|
|||||||
statusCell.appendChild(cwSpan);
|
statusCell.appendChild(cwSpan);
|
||||||
}
|
}
|
||||||
cwSpan.textContent = data.commessaweb;
|
cwSpan.textContent = data.commessaweb;
|
||||||
|
const npHtml = noPriceBadgeHTML(
|
||||||
|
data.noPriceCount,
|
||||||
|
data.noPriceAnalyses,
|
||||||
|
);
|
||||||
|
if (npHtml) cwSpan.insertAdjacentHTML("beforeend", npHtml);
|
||||||
}
|
}
|
||||||
const exportBtn = gridRow.querySelector(".export-lims-btn");
|
const exportBtn = gridRow.querySelector(".export-lims-btn");
|
||||||
if (exportBtn) {
|
if (exportBtn) {
|
||||||
@@ -195,8 +244,17 @@
|
|||||||
msg.innerHTML =
|
msg.innerHTML =
|
||||||
`${data.message.replace(/\n/g, "<br>")}` +
|
`${data.message.replace(/\n/g, "<br>")}` +
|
||||||
`<br>ID CommessaWeb: ${data.idcommessaweb}` +
|
`<br>ID CommessaWeb: ${data.idcommessaweb}` +
|
||||||
`<br>Codice CommessaWeb: ${data.commessaweb}` +
|
`<br>Codice CommessaWeb: ${escHtml(data.commessaweb)}` +
|
||||||
(data.totalPhotos > 0 ? `<br>Foto: ${data.totalPhotos}` : "");
|
noPriceBadgeHTML(data.noPriceCount, data.noPriceAnalyses) +
|
||||||
|
(data.totalPhotos > 0 ? `<br>Foto: ${data.totalPhotos}` : "") +
|
||||||
|
(data.noPriceCount > 0
|
||||||
|
? `<div class="mt-2 small text-danger fw-semibold">Analisi senza prezzo:</div>` +
|
||||||
|
`<ul class="mb-0 small text-danger">` +
|
||||||
|
(data.noPriceAnalyses || [])
|
||||||
|
.map((n) => `<li>${escHtml(n)}</li>`)
|
||||||
|
.join("") +
|
||||||
|
`</ul>`
|
||||||
|
: "");
|
||||||
label.textContent = "Esportazione Completata";
|
label.textContent = "Esportazione Completata";
|
||||||
} else {
|
} else {
|
||||||
msg.textContent = `Errore: ${data.message}`;
|
msg.textContent = `Errore: ${data.message}`;
|
||||||
@@ -386,6 +444,8 @@
|
|||||||
data.message || "Unknown error",
|
data.message || "Unknown error",
|
||||||
);
|
);
|
||||||
showExportResult(data);
|
showExportResult(data);
|
||||||
|
// Mail di riepilogo per la singola commessa
|
||||||
|
sendExportSummaryMail([iddatadb]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (gridRow) {
|
if (gridRow) {
|
||||||
setRowExporting(gridRow, false);
|
setRowExporting(gridRow, false);
|
||||||
@@ -633,6 +693,8 @@
|
|||||||
let processed = 0,
|
let processed = 0,
|
||||||
succeeded = 0,
|
succeeded = 0,
|
||||||
failed = 0;
|
failed = 0;
|
||||||
|
const exportedList = []; // { commessaweb, noPriceCount, noPriceAnalyses }
|
||||||
|
const processedIds = []; // iddatadb processati (ok o errore) per la mail
|
||||||
|
|
||||||
disableAllRowButtons();
|
disableAllRowButtons();
|
||||||
|
|
||||||
@@ -666,11 +728,17 @@
|
|||||||
const gridRow = row || getGridRow(iddatadb);
|
const gridRow = row || getGridRow(iddatadb);
|
||||||
if (gridRow) setRowExporting(gridRow, true);
|
if (gridRow) setRowExporting(gridRow, true);
|
||||||
|
|
||||||
|
processedIds.push(iddatadb);
|
||||||
try {
|
try {
|
||||||
const data = await sendExport(iddatadb, batchUuid);
|
const data = await sendExport(iddatadb, batchUuid);
|
||||||
processed++;
|
processed++;
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
succeeded++;
|
succeeded++;
|
||||||
|
exportedList.push({
|
||||||
|
commessaweb: data.commessaweb,
|
||||||
|
noPriceCount: data.noPriceCount || 0,
|
||||||
|
noPriceAnalyses: data.noPriceAnalyses || [],
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
failed++;
|
failed++;
|
||||||
const errIdx = getRowIndexByIddatadb(iddatadb);
|
const errIdx = getRowIndexByIddatadb(iddatadb);
|
||||||
@@ -703,6 +771,9 @@
|
|||||||
enableAllRowButtons();
|
enableAllRowButtons();
|
||||||
if (bar) bar.style.display = "none";
|
if (bar) bar.style.display = "none";
|
||||||
|
|
||||||
|
// Mail di riepilogo unica per tutto il batch processato
|
||||||
|
sendExportSummaryMail(processedIds);
|
||||||
|
|
||||||
// Re-render to reflect status changes, then restore error indicators
|
// Re-render to reflect status changes, then restore error indicators
|
||||||
const gr = window.gridRenderer;
|
const gr = window.gridRenderer;
|
||||||
if (gr) gr.renderVisibleRows();
|
if (gr) gr.renderVisibleRows();
|
||||||
@@ -730,6 +801,31 @@
|
|||||||
msgEl.innerHTML = `Exported: <strong>${succeeded}</strong><br>Errors: <strong>${failed}</strong>`;
|
msgEl.innerHTML = `Exported: <strong>${succeeded}</strong><br>Errors: <strong>${failed}</strong>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (exportedList.length) {
|
||||||
|
const npCommesse = exportedList.filter(
|
||||||
|
(x) => x.noPriceCount > 0,
|
||||||
|
).length;
|
||||||
|
msgEl.innerHTML +=
|
||||||
|
`<hr class="my-2">` +
|
||||||
|
(npCommesse > 0
|
||||||
|
? `<div class="small text-danger fw-semibold mb-1"><i class="fas fa-exclamation-triangle"></i> ${npCommesse} commesse con analisi senza prezzo</div>`
|
||||||
|
: "") +
|
||||||
|
`<div class="small fw-semibold mb-1">Commesse esportate:</div>` +
|
||||||
|
`<ul class="mb-0 ps-3">` +
|
||||||
|
exportedList
|
||||||
|
.map(
|
||||||
|
(x) =>
|
||||||
|
`<li><span style="font-family:monospace;">${escHtml(x.commessaweb || "—")}</span>` +
|
||||||
|
noPriceBadgeHTML(
|
||||||
|
x.noPriceCount,
|
||||||
|
x.noPriceAnalyses,
|
||||||
|
) +
|
||||||
|
`</li>`,
|
||||||
|
)
|
||||||
|
.join("") +
|
||||||
|
`</ul>`;
|
||||||
|
}
|
||||||
|
|
||||||
const modalEl = document.getElementById("exportResponseModal");
|
const modalEl = document.getElementById("exportResponseModal");
|
||||||
new bootstrap.Modal(modalEl, { keyboard: false }).show();
|
new bootstrap.Modal(modalEl, { keyboard: false }).show();
|
||||||
modalEl.addEventListener("hidden.bs.modal", cleanupBackdrop, {
|
modalEl.addEventListener("hidden.bs.modal", cleanupBackdrop, {
|
||||||
|
|||||||
@@ -55,22 +55,29 @@ $apiBaseUrl = 'https://93.43.5.102/limsapi/api/odata/';
|
|||||||
// 🔹 Batch UUID — if present, all logs go to a single file
|
// 🔹 Batch UUID — if present, all logs go to a single file
|
||||||
$batchUuid = $_POST['batch_uuid'] ?? null;
|
$batchUuid = $_POST['batch_uuid'] ?? null;
|
||||||
|
|
||||||
$writeLog = (function () use ($batchUuid, $logDir) {
|
// File unico per questa commessa (nominato per iddatadb, che conosciamo sempre)
|
||||||
$batchLogFile = $batchUuid ? $logDir . "batch_export_{$batchUuid}.log" : null;
|
$iddatadbForLog = $_POST['iddatadb'] ?? 'unknown';
|
||||||
|
$commessaLogFile = $logDir . "commessa_{$iddatadbForLog}_" . time() . ".log";
|
||||||
|
|
||||||
return function ($individualPath, $content, $stepLabel = null) use ($batchLogFile) {
|
// File di lotto: resta come INDICE (quali commesse nel lotto), non più tutti gli step
|
||||||
if ($batchLogFile) {
|
$batchIndexFile = $batchUuid ? $logDir . "batch_export_{$batchUuid}.log" : null;
|
||||||
$header = "\n" . str_repeat("=", 60) . "\n";
|
|
||||||
if ($stepLabel) {
|
// $writeLog ora appende sempre sul file unico della commessa, con header di step
|
||||||
$header .= "[{$stepLabel}] " . date('Y-m-d H:i:s') . "\n";
|
$writeLog = function ($individualPath, $content, $stepLabel = null) use ($commessaLogFile) {
|
||||||
}
|
$header = "\n" . str_repeat("=", 60) . "\n";
|
||||||
$header .= str_repeat("=", 60) . "\n";
|
if ($stepLabel) {
|
||||||
file_put_contents($batchLogFile, $header . $content . "\n", FILE_APPEND);
|
$header .= "[{$stepLabel}] " . date('Y-m-d H:i:s') . "\n";
|
||||||
} else {
|
}
|
||||||
file_put_contents($individualPath, $content);
|
$header .= str_repeat("=", 60) . "\n";
|
||||||
}
|
file_put_contents($commessaLogFile, $header . $content . "\n", FILE_APPEND);
|
||||||
};
|
};
|
||||||
})();
|
|
||||||
|
// Helper per registrare una riga nell'indice di lotto
|
||||||
|
$writeBatchIndex = function ($line) use ($batchIndexFile) {
|
||||||
|
if ($batchIndexFile) {
|
||||||
|
file_put_contents($batchIndexFile, "[" . date('Y-m-d H:i:s') . "] " . $line . "\n", FILE_APPEND);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 🔹 Funzione per validare e convertire date
|
// 🔹 Funzione per validare e convertire date
|
||||||
function validateDate($value)
|
function validateDate($value)
|
||||||
@@ -272,11 +279,20 @@ try {
|
|||||||
return strcmp((string)($a['field_label'] ?? ''), (string)($b['field_label'] ?? ''));
|
return strcmp((string)($a['field_label'] ?? ''), (string)($b['field_label'] ?? ''));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Valori "vuoti convenzionali" da NON includere nelle note.
|
||||||
|
// Il cliente usa "/" (e simili) come placeholder di campo vuoto.
|
||||||
|
$addnotePlaceholders = ['/', '-', 'n/a', 'na', 'n.a.', '//'];
|
||||||
|
|
||||||
foreach ($addnoteRows as $r) {
|
foreach ($addnoteRows as $r) {
|
||||||
$val = trim((string)($r['field_value'] ?? ''));
|
$val = trim((string)($r['field_value'] ?? ''));
|
||||||
if ($val !== '') {
|
if ($val === '') {
|
||||||
$addnoteChunks[] = $val;
|
continue;
|
||||||
}
|
}
|
||||||
|
// Scarta i placeholder (confronto case-insensitive)
|
||||||
|
if (in_array(mb_strtolower($val), $addnotePlaceholders, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$addnoteChunks[] = $val;
|
||||||
}
|
}
|
||||||
|
|
||||||
$addnoteText = implode(' - ', $addnoteChunks);
|
$addnoteText = implode(' - ', $addnoteChunks);
|
||||||
@@ -310,7 +326,7 @@ try {
|
|||||||
"AnagraficaCertestService" => $anagraficaService,
|
"AnagraficaCertestService" => $anagraficaService,
|
||||||
"ClienteFornitore" => $clienteFornitore, // PLACEHOLDER — to be implemented
|
"ClienteFornitore" => $clienteFornitore, // PLACEHOLDER — to be implemented
|
||||||
"ClienteAnalisi" => $clienteAnalisi, // PLACEHOLDER — to be implemented
|
"ClienteAnalisi" => $clienteAnalisi, // PLACEHOLDER — to be implemented
|
||||||
// "ClienteFatturazione" => $clienteFatturazione, // ⚠️ TEMP DISABLED - problemi API lato LIMS
|
"ClienteFatturazione" => $clienteFatturazione, // ⚠️ TEMP DISABLED - problemi API lato LIMS
|
||||||
// DeliveryRequest goes to Campione, not CommessaWeb
|
// DeliveryRequest goes to Campione, not CommessaWeb
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -569,7 +585,7 @@ try {
|
|||||||
|
|
||||||
// 🔹 STEP 6.3: Add Analyses (AnalisiCampione) via Campione({id})/AddAnalisi bound action
|
// 🔹 STEP 6.3: Add Analyses (AnalisiCampione) via Campione({id})/AddAnalisi bound action
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
SELECT part_id, analysis_recordkey, analysis_name, analysis_method
|
SELECT part_id, analysis_recordkey, analysis_name, analysis_method, analysis_price
|
||||||
FROM identification_parts_analyses
|
FROM identification_parts_analyses
|
||||||
WHERE iddatadb = :iddatadb
|
WHERE iddatadb = :iddatadb
|
||||||
ORDER BY part_id, id
|
ORDER BY part_id, id
|
||||||
@@ -577,6 +593,17 @@ try {
|
|||||||
$stmt->execute(['iddatadb' => $iddatadb]);
|
$stmt->execute(['iddatadb' => $iddatadb]);
|
||||||
$analysesRows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$analysesRows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Analisi senza prezzo (NULL o 0), una sola volta per recordkey
|
||||||
|
$noPriceAnalyses = [];
|
||||||
|
foreach ($analysesRows as $a) {
|
||||||
|
$price = $a['analysis_price'];
|
||||||
|
if ($price === null || (float)$price <= 0) {
|
||||||
|
$rk = (string)($a['analysis_recordkey'] ?? '');
|
||||||
|
$noPriceAnalyses[$rk] = $a['analysis_name'] ?: 'Unnamed analysis';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$noPriceAnalyses = array_values($noPriceAnalyses);
|
||||||
|
|
||||||
$partIdToIndex = [];
|
$partIdToIndex = [];
|
||||||
foreach ($parts as $idx => $part) {
|
foreach ($parts as $idx => $part) {
|
||||||
$partIdToIndex[(int)$part['part_id']] = $idx;
|
$partIdToIndex[(int)$part['part_id']] = $idx;
|
||||||
@@ -684,16 +711,20 @@ try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔹 STEP 8: Update datadb with idcommessaweb, commessaweb, status and export date
|
// 🔹 STEP 8: Update datadb with idcommessaweb, commessaweb, log_file and export date
|
||||||
|
// NB: lo status 'l' viene impostato SOLO dopo invio+import ok (vedi più sotto),
|
||||||
|
// così una riga rimasta indietro non risulta "To LIMS" per errore.
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
UPDATE datadb
|
UPDATE datadb
|
||||||
SET idcommessaweb = :idcommessaweb, commessaweb = :commessaweb, status = 'l', export_date = NOW()
|
SET idcommessaweb = :idcommessaweb, commessaweb = :commessaweb,
|
||||||
|
log_file = :log_file, export_date = NOW()
|
||||||
WHERE iddatadb = :iddatadb
|
WHERE iddatadb = :iddatadb
|
||||||
");
|
");
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
'idcommessaweb' => $commessaId,
|
'idcommessaweb' => $commessaId,
|
||||||
'commessaweb' => $commessaWebCode,
|
'commessaweb' => $commessaWebCode,
|
||||||
'iddatadb' => $iddatadb
|
'log_file' => basename($commessaLogFile),
|
||||||
|
'iddatadb' => $iddatadb
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 🔹 STEP 9: Send CommessaWeb to laboratory (commentato come richiesto)
|
// 🔹 STEP 9: Send CommessaWeb to laboratory (commentato come richiesto)
|
||||||
@@ -761,9 +792,12 @@ try {
|
|||||||
");
|
");
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
'commessaweb' => substr($finalCodiceCommessa, 0, 30),
|
'commessaweb' => substr($finalCodiceCommessa, 0, 30),
|
||||||
'iddatadb' => $iddatadb
|
'iddatadb' => $iddatadb
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Indice di lotto: esito OK di questa commessa
|
||||||
|
$writeBatchIndex("OK iddatadb={$iddatadb} commessa={$commessaId} codice={$finalCodiceCommessa} log=" . basename($commessaLogFile));
|
||||||
// 🔹 STEP 11: Prepare final response
|
// 🔹 STEP 11: Prepare final response
|
||||||
$finalCommessa = [
|
$finalCommessa = [
|
||||||
"Cliente" => $clienteId,
|
"Cliente" => $clienteId,
|
||||||
@@ -787,6 +821,8 @@ try {
|
|||||||
"totalAnalyses" => $totalAnalyses,
|
"totalAnalyses" => $totalAnalyses,
|
||||||
"addedAnalyses" => $addedAnalyses,
|
"addedAnalyses" => $addedAnalyses,
|
||||||
"failedAnalyses" => $failedAnalyses,
|
"failedAnalyses" => $failedAnalyses,
|
||||||
|
"noPriceCount" => count($noPriceAnalyses),
|
||||||
|
"noPriceAnalyses" => $noPriceAnalyses,
|
||||||
"message" => "Export successful",
|
"message" => "Export successful",
|
||||||
"logFiles" => [
|
"logFiles" => [
|
||||||
"step5_create" => $logFileStep5,
|
"step5_create" => $logFileStep5,
|
||||||
@@ -799,11 +835,49 @@ try {
|
|||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
// --- LOG ERRORE DEDICATO (accanto agli altri log in logs/api/) ---
|
||||||
|
$errIddatadb = $iddatadb ?? 'unknown';
|
||||||
|
$errCommessaId = $commessaId ?? null;
|
||||||
|
|
||||||
|
$errContent = "TIMESTAMP: " . date('Y-m-d H:i:s') . "\n";
|
||||||
|
$errContent .= "iddatadb: {$errIddatadb}\n";
|
||||||
|
$errContent .= "IdCommessa: " . ($errCommessaId ?? 'non ancora creata') . "\n";
|
||||||
|
$errContent .= "MESSAGE: " . $e->getMessage() . "\n\n";
|
||||||
|
$errContent .= "TRACE:\n" . $e->getTraceAsString() . "\n";
|
||||||
|
|
||||||
|
// Appendi l'errore in coda al file unico della commessa (se esiste),
|
||||||
|
// così resta tutto in un posto solo.
|
||||||
|
if (isset($commessaLogFile)) {
|
||||||
|
$header = "\n" . str_repeat("=", 60) . "\n[ERROR] " . date('Y-m-d H:i:s') . "\n" . str_repeat("=", 60) . "\n";
|
||||||
|
@file_put_contents($commessaLogFile, $header . $errContent . "\n", FILE_APPEND);
|
||||||
|
$errFileToSave = basename($commessaLogFile);
|
||||||
|
} else {
|
||||||
|
// fallback: file d'errore dedicato
|
||||||
|
$errName = "commessa_ERROR_{$errIddatadb}_" . time() . ".txt";
|
||||||
|
@file_put_contents($logDir . $errName, $errContent);
|
||||||
|
$errFileToSave = $errName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Segna la riga come errore ('x') e registra il log_file, senza lasciarla 'l'
|
||||||
|
if ($errIddatadb !== 'unknown') {
|
||||||
|
try {
|
||||||
|
$up = $pdo->prepare("UPDATE datadb SET status = 'x', log_file = :log_file WHERE iddatadb = :iddatadb");
|
||||||
|
$up->execute(['log_file' => $errFileToSave, 'iddatadb' => $errIddatadb]);
|
||||||
|
} catch (Exception $ignore) { /* non bloccare la risposta d'errore */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indice di lotto: esito KO di questa commessa
|
||||||
|
if (isset($writeBatchIndex)) {
|
||||||
|
$writeBatchIndex("FAIL iddatadb={$errIddatadb} commessa=" . ($errCommessaId ?? '-') . " log={$errFileToSave} :: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
error_log("LIMS Export Error: " . $e->getMessage() . "\nTrace: " . $e->getTraceAsString());
|
error_log("LIMS Export Error: " . $e->getMessage() . "\nTrace: " . $e->getTraceAsString());
|
||||||
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
"success" => false,
|
"success" => false,
|
||||||
"message" => "Export failed: " . $e->getMessage(),
|
"message" => "Export failed: " . $e->getMessage(),
|
||||||
|
"errorLog" => $logDir . $errFileToSave,
|
||||||
"logFiles" => [
|
"logFiles" => [
|
||||||
"step5_create" => $logFileStep5 ?? null,
|
"step5_create" => $logFileStep5 ?? null,
|
||||||
"step5_2_photos" => $logFilePhotos ?? null,
|
"step5_2_photos" => $logFilePhotos ?? null,
|
||||||
|
|||||||
@@ -39,8 +39,14 @@ if (!empty($explicitIds)) {
|
|||||||
// Scoping di sicurezza: solo record del template/utente/import corretti.
|
// Scoping di sicurezza: solo record del template/utente/import corretti.
|
||||||
$conds = ['d.templateid = ?', 'd.status = ?'];
|
$conds = ['d.templateid = ?', 'd.status = ?'];
|
||||||
$params = [$templateId, $status];
|
$params = [$templateId, $status];
|
||||||
if (!$showAll) { $conds[] = 'd.user_id = ?'; $params[] = $userId; }
|
if (!$showAll) {
|
||||||
if ($importref !== '') { $conds[] = 'd.importreferencecode = ?'; $params[] = $importref; }
|
$conds[] = 'd.user_id = ?';
|
||||||
|
$params[] = $userId;
|
||||||
|
}
|
||||||
|
if ($importref !== '') {
|
||||||
|
$conds[] = 'd.importreferencecode = ?';
|
||||||
|
$params[] = $importref;
|
||||||
|
}
|
||||||
$ph = implode(',', array_fill(0, count($explicitIds), '?'));
|
$ph = implode(',', array_fill(0, count($explicitIds), '?'));
|
||||||
$conds[] = "d.iddatadb IN ($ph)";
|
$conds[] = "d.iddatadb IN ($ph)";
|
||||||
foreach ($explicitIds as $id) $params[] = $id;
|
foreach ($explicitIds as $id) $params[] = $id;
|
||||||
@@ -57,7 +63,7 @@ if (!empty($explicitIds)) {
|
|||||||
$idStmt = $pdo->prepare("
|
$idStmt = $pdo->prepare("
|
||||||
SELECT d.iddatadb FROM datadb d
|
SELECT d.iddatadb FROM datadb d
|
||||||
WHERE {$whereSql}
|
WHERE {$whereSql}
|
||||||
ORDER BY d.excelrow ASC, d.iddatadb ASC
|
ORDER BY CAST(d.excelrow AS UNSIGNED) ASC, d.excelrow ASC, d.iddatadb ASC
|
||||||
LIMIT {$perPage} OFFSET {$offset}
|
LIMIT {$perPage} OFFSET {$offset}
|
||||||
");
|
");
|
||||||
$idStmt->execute($params);
|
$idStmt->execute($params);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ try {
|
|||||||
$data = null;
|
$data = null;
|
||||||
$fromCache = false;
|
$fromCache = false;
|
||||||
|
|
||||||
if (!$simulate && !$refresh && file_exists($cache_file) && (time() - filemtime($cache_file) < 3600)) { // 1 ora
|
if (!$simulate && !$refresh && file_exists($cache_file) && (time() - filemtime($cache_file) < 7200)) { // 1 ora
|
||||||
$data = json_decode(file_get_contents($cache_file), true);
|
$data = json_decode(file_get_contents($cache_file), true);
|
||||||
$fromCache = is_array($data);
|
$fromCache = is_array($data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ try {
|
|||||||
|
|
||||||
// Cache file (1 hour TTL)
|
// Cache file (1 hour TTL)
|
||||||
$cacheFile = __DIR__ . '/cache/clienti.json';
|
$cacheFile = __DIR__ . '/cache/clienti.json';
|
||||||
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 7200)) {
|
||||||
readfile($cacheFile);
|
readfile($cacheFile);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ try {
|
|||||||
|
|
||||||
// Cache file (1 hour TTL)
|
// Cache file (1 hour TTL)
|
||||||
$cacheFile = __DIR__ . '/cache/clienti.json';
|
$cacheFile = __DIR__ . '/cache/clienti.json';
|
||||||
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 7200)) {
|
||||||
readfile($cacheFile);
|
readfile($cacheFile);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ try {
|
|||||||
|
|
||||||
// Cache file (1 hour TTL)
|
// Cache file (1 hour TTL)
|
||||||
$cacheFile = __DIR__ . '/cache/clienti.json';
|
$cacheFile = __DIR__ . '/cache/clienti.json';
|
||||||
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 7200)) {
|
||||||
readfile($cacheFile);
|
readfile($cacheFile);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ try {
|
|||||||
$cacheFile = $cacheDir . '/customfield_' . $customFieldId . '.json';
|
$cacheFile = $cacheDir . '/customfield_' . $customFieldId . '.json';
|
||||||
|
|
||||||
// Use cache if fresh (1 hour)
|
// Use cache if fresh (1 hour)
|
||||||
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 7200)) {
|
||||||
$results[$customFieldId] = json_decode(file_get_contents($cacheFile), true);
|
$results[$customFieldId] = json_decode(file_get_contents($cacheFile), true);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Caching skipped in simulate mode to avoid polluting real-data cache files
|
// Caching skipped in simulate mode to avoid polluting real-data cache files
|
||||||
if (!$simulate && $cache_file && file_exists($cache_file) && (time() - filemtime($cache_file) < 3600)) { // 1 ora
|
if (!$simulate && $cache_file && file_exists($cache_file) && (time() - filemtime($cache_file) < 7200)) { // 1 ora
|
||||||
$data = json_decode(file_get_contents($cache_file), true);
|
$data = json_decode(file_get_contents($cache_file), true);
|
||||||
} else {
|
} else {
|
||||||
$data = $api->get($endpoint, $options);
|
$data = $api->get($endpoint, $options);
|
||||||
|
|||||||
@@ -2,7 +2,11 @@
|
|||||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||||
require_once __DIR__ . '/class/db-functions.php';
|
require_once __DIR__ . '/class/db-functions.php';
|
||||||
include dirname(__DIR__) . '/../extra/auth.php';
|
include dirname(__DIR__) . '/../extra/auth.php';
|
||||||
if (!Auth::check()) { http_response_code(401); echo json_encode(['error' => 'Unauthorized']); exit; }
|
if (!Auth::check()) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['error' => 'Unauthorized']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
|
require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
|
||||||
|
|
||||||
@@ -13,7 +17,7 @@ error_reporting(E_ALL);
|
|||||||
try {
|
try {
|
||||||
// Cache file (1 hour TTL)
|
// Cache file (1 hour TTL)
|
||||||
$cacheFile = __DIR__ . '/cache/matrici.json';
|
$cacheFile = __DIR__ . '/cache/matrici.json';
|
||||||
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 7200)) {
|
||||||
readfile($cacheFile);
|
readfile($cacheFile);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -588,7 +588,14 @@
|
|||||||
: "To LIMS";
|
: "To LIMS";
|
||||||
let html = `<span class="status-badge status-${esc(st)}">${label}</span>`;
|
let html = `<span class="status-badge status-${esc(st)}">${label}</span>`;
|
||||||
if (row.commessaweb) {
|
if (row.commessaweb) {
|
||||||
html += `<span class="commessaweb-code" style="display:block; font-size:0.75em; color:#555; margin-top:2px;">${esc(row.commessaweb)}</span>`;
|
const npBadge =
|
||||||
|
typeof window.noPriceBadgeHTML === "function"
|
||||||
|
? window.noPriceBadgeHTML(
|
||||||
|
row.noPriceCount,
|
||||||
|
row.noPriceAnalyses,
|
||||||
|
)
|
||||||
|
: "";
|
||||||
|
html += `<span class="commessaweb-code" style="display:block; font-size:0.75em; color:#555; margin-top:2px;">${esc(row.commessaweb)}${npBadge}</span>`;
|
||||||
}
|
}
|
||||||
div.innerHTML = html;
|
div.innerHTML = html;
|
||||||
div.classList.remove("editable-cell");
|
div.classList.remove("editable-cell");
|
||||||
@@ -895,6 +902,76 @@
|
|||||||
updateDirtyIndicator();
|
updateDirtyIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Aggiorna in-place SOLO le celle di una colonna in tutte le righe visibili,
|
||||||
|
// senza distruggere/ricostruire l'intera griglia (evita il freeze in propagazione).
|
||||||
|
function updateColumnCells(colKey, colType) {
|
||||||
|
if (!rowContainer) return;
|
||||||
|
const cells = rowContainer.querySelectorAll(
|
||||||
|
`.grid-cell[data-col="${CSS.escape(String(colKey))}"][data-col-type="${colType}"]`,
|
||||||
|
);
|
||||||
|
cells.forEach((cell) => {
|
||||||
|
const rowIndex = parseInt(cell.dataset.row, 10);
|
||||||
|
if (Number.isNaN(rowIndex) || !data[rowIndex]) return;
|
||||||
|
|
||||||
|
// Ricava il valore corrente dal modello dati
|
||||||
|
let value = "";
|
||||||
|
if (colType === "detail" || colType === "main_field") {
|
||||||
|
value = data[rowIndex].details?.[String(colKey)] ?? "";
|
||||||
|
} else if (colType === "fixed") {
|
||||||
|
value = data[rowIndex].fixedFields?.[colKey] ?? "";
|
||||||
|
} else if (colType === "cliente_fornitore_id") {
|
||||||
|
value = data[rowIndex].cliente_fornitore_id ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const input = cell.querySelector(".cell-input");
|
||||||
|
if (!input) return;
|
||||||
|
|
||||||
|
if ($(input).hasClass("select2-hidden-accessible")) {
|
||||||
|
// Assicura che l'opzione selezionata esista, poi imposta il valore
|
||||||
|
if (
|
||||||
|
value &&
|
||||||
|
!input.querySelector(
|
||||||
|
`option[value="${CSS.escape(String(value))}"]`,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const label =
|
||||||
|
clientNameCache[value] ||
|
||||||
|
(input.dataset?.fieldId
|
||||||
|
? dropdownNameCache[
|
||||||
|
input.dataset.fieldId + "_" + value
|
||||||
|
]
|
||||||
|
: null) ||
|
||||||
|
value;
|
||||||
|
input.add(new Option(label, value, true, true));
|
||||||
|
}
|
||||||
|
$(input)
|
||||||
|
.val(value ? String(value) : "")
|
||||||
|
.trigger("change.select2");
|
||||||
|
} else if (input.tagName === "SELECT") {
|
||||||
|
// Select nativo: aggiungi opzione se manca
|
||||||
|
if (
|
||||||
|
value &&
|
||||||
|
!input.querySelector(
|
||||||
|
`option[value="${CSS.escape(String(value))}"]`,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const label =
|
||||||
|
(input.dataset?.fieldId
|
||||||
|
? dropdownNameCache[
|
||||||
|
input.dataset.fieldId + "_" + value
|
||||||
|
]
|
||||||
|
: null) || value;
|
||||||
|
input.add(new Option(label, value, true, true));
|
||||||
|
}
|
||||||
|
input.value = value;
|
||||||
|
} else {
|
||||||
|
input.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
cell.classList.add("cell-changed");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function renderSingleRow(rowIndex) {
|
function renderSingleRow(rowIndex) {
|
||||||
const existing = rowContainer.querySelector(
|
const existing = rowContainer.querySelector(
|
||||||
`.grid-row[data-id="${data[rowIndex].iddatadb}"]`,
|
`.grid-row[data-id="${data[rowIndex].iddatadb}"]`,
|
||||||
@@ -1546,7 +1623,10 @@
|
|||||||
row._dirty = true;
|
row._dirty = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
renderVisibleRows();
|
updateColumnCells(
|
||||||
|
"cliente_fornitore_id",
|
||||||
|
"cliente_fornitore_id",
|
||||||
|
);
|
||||||
updateDirtyIndicator();
|
updateDirtyIndicator();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1565,7 +1645,7 @@
|
|||||||
row._dirty = true;
|
row._dirty = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
renderVisibleRows();
|
updateColumnCells(fixedKey, "fixed");
|
||||||
updateDirtyIndicator();
|
updateDirtyIndicator();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1595,7 +1675,15 @@
|
|||||||
row._dirty = true;
|
row._dirty = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
renderVisibleRows();
|
updateColumnCells(col.key, col.type);
|
||||||
|
// Se esiste una colonna auto-copiata (es. Arrival→Unlock), aggiorna anche quella
|
||||||
|
if (autoPair) {
|
||||||
|
const pairCol = columns.find(
|
||||||
|
(c) => String(c.key) === String(autoPair.toKey),
|
||||||
|
);
|
||||||
|
if (pairCol) updateColumnCells(pairCol.key, pairCol.type);
|
||||||
|
}
|
||||||
|
|
||||||
updateDirtyIndicator();
|
updateDirtyIndicator();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ if (!file_exists(__DIR__ . '/import_debug.log')) {
|
|||||||
error_log("Inizio importazione alle " . date('Y-m-d H:i:s'));
|
error_log("Inizio importazione alle " . date('Y-m-d H:i:s'));
|
||||||
|
|
||||||
include('include/headscript.php');
|
include('include/headscript.php');
|
||||||
|
require_once __DIR__ . '/class/binding-functions.php';
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['template_id']) || !isset($_POST['selected_rows']) || !isset($_POST['filename'])) {
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['template_id']) || !isset($_POST['selected_rows']) || !isset($_POST['filename'])) {
|
||||||
header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Richiesta non valida"));
|
header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Richiesta non valida"));
|
||||||
@@ -70,7 +71,7 @@ $pdo = $db->getConnection();
|
|||||||
$importReferenceCode = date('YmdHis') . '-' . uniqid();
|
$importReferenceCode = date('YmdHis') . '-' . uniqid();
|
||||||
|
|
||||||
// Recupera tutti i mapping dal template
|
// Recupera tutti i mapping dal template
|
||||||
$stmt = $pdo->prepare("SELECT id, excel_column, data_type, is_required, manual_default, is_manual, field_label, field_id, main_field, auto_value FROM template_mapping WHERE template_id = ?");
|
$stmt = $pdo->prepare("SELECT id, excel_column, data_type, has_list, is_required, manual_default, is_manual, field_label, field_id, main_field, auto_value FROM template_mapping WHERE template_id = ?");
|
||||||
$stmt->execute([$template_id]);
|
$stmt->execute([$template_id]);
|
||||||
$allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
@@ -88,6 +89,23 @@ foreach ($allMappings as $mapping) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$fixedMappings = [];
|
||||||
|
try {
|
||||||
|
$fxStmt = $pdo->prepare(
|
||||||
|
"SELECT id, fixed_field_key, data_type, default_value, default_source, excel_column
|
||||||
|
FROM template_fixed_mapping
|
||||||
|
WHERE template_id = ?"
|
||||||
|
);
|
||||||
|
$fxStmt->execute([$template_id]);
|
||||||
|
$fixedMappings = $fxStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("[FIXED] lettura template_fixed_mapping fallita: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
$savedBindings = []; // risolti con un binding gia' salvato
|
||||||
|
$autoBindings = []; // collegati in automatico (corrispondenza esatta)
|
||||||
|
$pendingBindings = []; // senza corrispondenza: da risolvere a mano
|
||||||
|
|
||||||
// Inserisci le righe selezionate in datadb
|
// Inserisci le righe selezionate in datadb
|
||||||
$insertedIds = [];
|
$insertedIds = [];
|
||||||
foreach ($selected_rows as $rowIndex) {
|
foreach ($selected_rows as $rowIndex) {
|
||||||
@@ -172,6 +190,24 @@ foreach ($selected_rows as $rowIndex) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
binding_is_list_field($mapping)
|
||||||
|
&& $fieldValue !== null
|
||||||
|
&& trim((string) $fieldValue) !== ''
|
||||||
|
) {
|
||||||
|
$fieldValue = binding_resolve_for_import(
|
||||||
|
$pdo,
|
||||||
|
$mapping,
|
||||||
|
(int) $template_id,
|
||||||
|
(string) $fieldValue,
|
||||||
|
(int) $iddatadb,
|
||||||
|
(int) $user_id,
|
||||||
|
$savedBindings,
|
||||||
|
$autoBindings,
|
||||||
|
$pendingBindings
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if ($mapping['is_required'] && (is_null($fieldValue) || $fieldValue === '')) {
|
if ($mapping['is_required'] && (is_null($fieldValue) || $fieldValue === '')) {
|
||||||
error_log("Required field missing for mapping ID: " . $mapping['id'] . ", field: " . $mapping['field_label']);
|
error_log("Required field missing for mapping ID: " . $mapping['id'] . ", field: " . $mapping['field_label']);
|
||||||
}
|
}
|
||||||
@@ -180,6 +216,43 @@ foreach ($selected_rows as $rowIndex) {
|
|||||||
$stmt->execute([$iddatadb, $mapping['id'], $fieldValue]);
|
$stmt->execute([$iddatadb, $mapping['id'], $fieldValue]);
|
||||||
error_log("Inserted into import_data_details for ID $iddatadb, Mapping ID: " . $mapping['id'] . ", Field Value: " . var_export($fieldValue, true));
|
error_log("Inserted into import_data_details for ID $iddatadb, Mapping ID: " . $mapping['id'] . ", Field Value: " . var_export($fieldValue, true));
|
||||||
}
|
}
|
||||||
|
$fixedValues = [];
|
||||||
|
foreach ($fixedMappings as $fm) {
|
||||||
|
$fixedKey = (string) $fm['fixed_field_key'];
|
||||||
|
$value = null;
|
||||||
|
|
||||||
|
if (($fm['default_source'] ?? 'manual') === 'xls' && trim((string) ($fm['excel_column'] ?? '')) !== '') {
|
||||||
|
$idx = binding_find_column_index((string) $fm['excel_column'], $columns);
|
||||||
|
$raw = ($idx !== false && isset($row[$idx])) ? trim((string) $row[$idx]) : '';
|
||||||
|
|
||||||
|
if ($raw !== '') {
|
||||||
|
$value = binding_resolve_fixed_for_import(
|
||||||
|
$pdo,
|
||||||
|
$fm,
|
||||||
|
(int) $template_id,
|
||||||
|
$raw,
|
||||||
|
(int) $iddatadb,
|
||||||
|
(int) $user_id,
|
||||||
|
$savedBindings,
|
||||||
|
$autoBindings,
|
||||||
|
$pendingBindings
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value === null || $value === '') {
|
||||||
|
$default = binding_fixed_default($fm);
|
||||||
|
$value = $default !== '' ? $default : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value !== null && $value !== '') {
|
||||||
|
$fixedValues[$fixedKey] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($fixedValues)) {
|
||||||
|
binding_write_fixed_columns($pdo, (int) $iddatadb, $fixedValues);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
@@ -237,5 +310,20 @@ try {
|
|||||||
|
|
||||||
$_SESSION['inserted_ids'] = $insertedIds;
|
$_SESSION['inserted_ids'] = $insertedIds;
|
||||||
|
|
||||||
|
if (!empty($pendingBindings)) {
|
||||||
|
$_SESSION['pending_bindings'] = [
|
||||||
|
'template_id' => $template_id,
|
||||||
|
'importref' => $importReferenceCode,
|
||||||
|
'items' => array_values($pendingBindings),
|
||||||
|
'auto' => array_values($autoBindings),
|
||||||
|
'saved' => array_values($savedBindings),
|
||||||
|
];
|
||||||
|
|
||||||
|
header("Location: resolve_bindings.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($_SESSION['pending_bindings']);
|
||||||
|
|
||||||
header("Location: imported.php?id=" . urlencode($template_id) . "&importref=" . urlencode($importReferenceCode));
|
header("Location: imported.php?id=" . urlencode($template_id) . "&importref=" . urlencode($importReferenceCode));
|
||||||
exit;
|
exit;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ ini_set('log_errors', 1);
|
|||||||
ini_set('error_log', __DIR__ . '/import_debug.log');
|
ini_set('error_log', __DIR__ . '/import_debug.log');
|
||||||
|
|
||||||
include('include/headscript.php');
|
include('include/headscript.php');
|
||||||
|
require_once __DIR__ . '/class/binding-functions.php';
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
@@ -49,7 +50,7 @@ $db = DBHandlerSelect::getInstance();
|
|||||||
$pdo = $db->getConnection();
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
// Recupera i mapping
|
// Recupera i mapping
|
||||||
$stmt = $pdo->prepare("SELECT id, excel_column, data_type, is_required, manual_default, is_manual, field_label, field_id, main_field, auto_value FROM template_mapping WHERE template_id = ?");
|
$stmt = $pdo->prepare("SELECT id, excel_column, data_type, has_list, is_required, manual_default, is_manual, field_label, field_id, main_field, auto_value FROM template_mapping WHERE template_id = ?");
|
||||||
$stmt->execute([$template_id]);
|
$stmt->execute([$template_id]);
|
||||||
$allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
@@ -76,6 +77,23 @@ $mappingId244 = (int)$stmtMap->fetchColumn();
|
|||||||
|
|
||||||
$insertedIds = [];
|
$insertedIds = [];
|
||||||
|
|
||||||
|
$fixedMappings = [];
|
||||||
|
try {
|
||||||
|
$fxStmt = $pdo->prepare(
|
||||||
|
"SELECT id, fixed_field_key, data_type, default_value, default_source, excel_column
|
||||||
|
FROM template_fixed_mapping
|
||||||
|
WHERE template_id = ?"
|
||||||
|
);
|
||||||
|
$fxStmt->execute([$template_id]);
|
||||||
|
$fixedMappings = $fxStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("[FIXED] lettura template_fixed_mapping fallita: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
$savedBindings = [];
|
||||||
|
$autoBindings = [];
|
||||||
|
$pendingBindings = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$pdo->beginTransaction();
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
@@ -146,9 +164,65 @@ try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
binding_is_list_field($mapping)
|
||||||
|
&& $fieldValue !== null
|
||||||
|
&& trim((string) $fieldValue) !== ''
|
||||||
|
) {
|
||||||
|
$fieldValue = binding_resolve_for_import(
|
||||||
|
$pdo,
|
||||||
|
$mapping,
|
||||||
|
$template_id,
|
||||||
|
(string) $fieldValue,
|
||||||
|
(int) $iddatadb,
|
||||||
|
(int) $user_id,
|
||||||
|
$savedBindings,
|
||||||
|
$autoBindings,
|
||||||
|
$pendingBindings
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$insDetail->execute([$iddatadb, $mapping['id'], $fieldValue]);
|
$insDetail->execute([$iddatadb, $mapping['id'], $fieldValue]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$fixedValues = [];
|
||||||
|
foreach ($fixedMappings as $fm) {
|
||||||
|
$fixedKey = (string) $fm['fixed_field_key'];
|
||||||
|
$value = null;
|
||||||
|
|
||||||
|
if (($fm['default_source'] ?? 'manual') === 'xls' && trim((string) ($fm['excel_column'] ?? '')) !== '') {
|
||||||
|
$idx = binding_find_column_index((string) $fm['excel_column'], $columns);
|
||||||
|
$raw = ($idx !== false && isset($row[$idx])) ? trim((string) $row[$idx]) : '';
|
||||||
|
|
||||||
|
if ($raw !== '') {
|
||||||
|
$value = binding_resolve_fixed_for_import(
|
||||||
|
$pdo,
|
||||||
|
$fm,
|
||||||
|
(int) $template_id,
|
||||||
|
$raw,
|
||||||
|
(int) $iddatadb,
|
||||||
|
(int) $user_id,
|
||||||
|
$savedBindings,
|
||||||
|
$autoBindings,
|
||||||
|
$pendingBindings
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value === null || $value === '') {
|
||||||
|
$default = binding_fixed_default($fm);
|
||||||
|
$value = $default !== '' ? $default : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value !== null && $value !== '') {
|
||||||
|
$fixedValues[$fixedKey] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($fixedValues)) {
|
||||||
|
binding_write_fixed_columns($pdo, (int) $iddatadb, $fixedValues);
|
||||||
|
}
|
||||||
|
|
||||||
// Campo 244 (Accettatore) con lims_user_id
|
// Campo 244 (Accettatore) con lims_user_id
|
||||||
if ($limsUserId !== '' && $mappingId244 > 0) {
|
if ($limsUserId !== '' && $mappingId244 > 0) {
|
||||||
$insDetail->execute([$iddatadb, $mappingId244, $limsUserId]);
|
$insDetail->execute([$iddatadb, $mappingId244, $limsUserId]);
|
||||||
@@ -157,9 +231,27 @@ try {
|
|||||||
|
|
||||||
$pdo->commit();
|
$pdo->commit();
|
||||||
|
|
||||||
|
$state = $_SESSION['pending_bindings'] ?? null;
|
||||||
|
if (!is_array($state) || ($state['importref'] ?? null) !== $importReferenceCode) {
|
||||||
|
$state = [
|
||||||
|
'template_id' => $template_id,
|
||||||
|
'importref' => $importReferenceCode,
|
||||||
|
'items' => [],
|
||||||
|
'auto' => [],
|
||||||
|
'saved' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$state['items'] = binding_merge_buckets($state['items'], $pendingBindings);
|
||||||
|
$state['auto'] = binding_merge_buckets($state['auto'], $autoBindings);
|
||||||
|
$state['saved'] = binding_merge_buckets($state['saved'], $savedBindings);
|
||||||
|
|
||||||
|
$_SESSION['pending_bindings'] = $state;
|
||||||
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'ok' => true,
|
'ok' => true,
|
||||||
'inserted' => count($insertedIds)
|
'inserted' => count($insertedIds),
|
||||||
|
'pending_bindings' => count($state['items'])
|
||||||
]);
|
]);
|
||||||
exit;
|
exit;
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ error_log("Loaded template: " . print_r($template, true));
|
|||||||
<div class="mb-3 text">
|
<div class="mb-3 text">
|
||||||
<a href="imported.php?id=<?= $id ?>" class="btn btn-warning me-2">Imported (i)</a>
|
<a href="imported.php?id=<?= $id ?>" class="btn btn-warning me-2">Imported (i)</a>
|
||||||
<a href="tolims.php?id=<?= $id ?>" class="btn btn-success">To LIMS (l)</a>
|
<a href="tolims.php?id=<?= $id ?>" class="btn btn-success">To LIMS (l)</a>
|
||||||
|
<a href="errors.php?id=<?= $id ?>" class="btn btn-danger">Errors (x)</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="card radius-10">
|
<div class="card radius-10">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@@ -492,6 +493,7 @@ error_log("Loaded template: " . print_r($template, true));
|
|||||||
const progressErr = document.getElementById('importProgressError');
|
const progressErr = document.getElementById('importProgressError');
|
||||||
|
|
||||||
let insertedTotal = 0;
|
let insertedTotal = 0;
|
||||||
|
let pendingBindings = 0; // valori a lista senza corrispondenza LIMS
|
||||||
|
|
||||||
// Invia un batch e ritorna la risposta JSON
|
// Invia un batch e ritorna la risposta JSON
|
||||||
async function sendBatch(startIndex) {
|
async function sendBatch(startIndex) {
|
||||||
@@ -550,6 +552,7 @@ error_log("Loaded template: " . print_r($template, true));
|
|||||||
}
|
}
|
||||||
|
|
||||||
insertedTotal += (result.inserted || 0);
|
insertedTotal += (result.inserted || 0);
|
||||||
|
pendingBindings = result.pending_bindings || pendingBindings;
|
||||||
const done = Math.min(start + BATCH_SIZE, total);
|
const done = Math.min(start + BATCH_SIZE, total);
|
||||||
const pct = Math.round((done / total) * 100);
|
const pct = Math.round((done / total) * 100);
|
||||||
progressBar.style.width = pct + '%';
|
progressBar.style.width = pct + '%';
|
||||||
@@ -565,7 +568,9 @@ error_log("Loaded template: " . print_r($template, true));
|
|||||||
// PAUSA temporanea di 2 secondi per vedere la barra completa (da rimuovere dopo il test)
|
// PAUSA temporanea di 2 secondi per vedere la barra completa (da rimuovere dopo il test)
|
||||||
await new Promise(r => setTimeout(r, 2000));
|
await new Promise(r => setTimeout(r, 2000));
|
||||||
|
|
||||||
window.location.href = 'imported.php?id=' +
|
window.location.href = pendingBindings > 0
|
||||||
|
? 'resolve_bindings.php'
|
||||||
|
: 'imported.php?id=' +
|
||||||
encodeURIComponent(data.template_id) +
|
encodeURIComponent(data.template_id) +
|
||||||
'&importref=' + encodeURIComponent(importReferenceCode);
|
'&importref=' + encodeURIComponent(importReferenceCode);
|
||||||
});
|
});
|
||||||
|
|||||||
+209
-15
@@ -1411,6 +1411,84 @@ $gridMeta = [
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Card-header azioni sticky sotto la topbar */
|
||||||
|
:root {
|
||||||
|
--actions-top: 0px;
|
||||||
|
/* posizione verticale barra azioni: abbassa/alza questo */
|
||||||
|
--actions-height: 58px;
|
||||||
|
/* altezza reale del card-header azioni */
|
||||||
|
--scrollbar-height: 18px;
|
||||||
|
/* altezza della .top-scrollbar */
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header.sticky-actions {
|
||||||
|
position: sticky;
|
||||||
|
top: var(--actions-top);
|
||||||
|
z-index: 40;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* La barra di scroll superiore segue sempre il card-header sticky */
|
||||||
|
.top-scrollbar {
|
||||||
|
top: calc(var(--actions-top) + var(--actions-height)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wrapper del grid-top: sticky verticale + scroll orizzontale proprio */
|
||||||
|
.grid-top-scroller {
|
||||||
|
position: sticky;
|
||||||
|
top: calc(var(--actions-top) + var(--actions-height) + var(--scrollbar-height));
|
||||||
|
z-index: 25;
|
||||||
|
background: #fff;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
/* nasconde la barra di scroll di questo wrapper: usiamo la top-scrollbar e la griglia */
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-top-scroller::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tastino toggle sticky */
|
||||||
|
.sticky-toggle-btn {
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
background: #fff;
|
||||||
|
color: #adb5bd;
|
||||||
|
transition: all .15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky-toggle-btn:hover {
|
||||||
|
background: #f1f3f5;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky-toggle-btn.active {
|
||||||
|
background: #0d6efd;
|
||||||
|
border-color: #0d6efd;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky-toggle-btn.active:hover {
|
||||||
|
background: #0b5ed7;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icona ruotata quando sbloccato (puntina "storta") */
|
||||||
|
.sticky-toggle-btn:not(.active) i {
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Quando sbloccato, card-header e grid-top non sono più sticky verticalmente */
|
||||||
|
.card-header.actions-unpinned {
|
||||||
|
position: static !important;
|
||||||
|
top: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-top-scroller.actions-unpinned {
|
||||||
|
position: static !important;
|
||||||
|
top: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
<?php if (isset($mainFieldMappings) && count($mainFieldMappings) >= 2): ?>.grid-top .grid-cell:nth-child(3) {
|
<?php if (isset($mainFieldMappings) && count($mainFieldMappings) >= 2): ?>.grid-top .grid-cell:nth-child(3) {
|
||||||
position: sticky !important;
|
position: sticky !important;
|
||||||
left: 360px;
|
left: 360px;
|
||||||
@@ -1452,7 +1530,8 @@ $gridMeta = [
|
|||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
<div class="mb-3 text d-flex align-items-center gap-2">
|
<div class="mb-3 text d-flex align-items-center gap-2">
|
||||||
<a href="imported.php?id=<?= $template_id ?>" class="btn btn-warning me-2">Imported (i)</a>
|
<a href="imported.php?id=<?= $template_id ?>" class="btn btn-warning me-2">Imported (i)</a>
|
||||||
<a href="tolims.php?id=<?= $template_id ?>" class="btn btn-success">To LIMS (l)</a>
|
<a href="tolims.php?id=<?= $template_id ?>" class="btn btn-success me-2">To LIMS (l)</a>
|
||||||
|
<a href="errors.php?id=<?= $template_id ?>" class="btn btn-danger">Errors (x)</a>
|
||||||
<?php if ($importref === ''): ?>
|
<?php if ($importref === ''): ?>
|
||||||
<span class="ms-3">
|
<span class="ms-3">
|
||||||
<label class="form-check-label" style="font-size: 13px; cursor: pointer;">
|
<label class="form-check-label" style="font-size: 13px; cursor: pointer;">
|
||||||
@@ -1514,8 +1593,9 @@ $gridMeta = [
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<div class="card radius-10">
|
<div class="card radius-10">
|
||||||
<div class="card-header">
|
<div class="card-header sticky-actions">
|
||||||
<div class="d-flex align-items-center" style="min-height: 42px; gap: 12px;">
|
<div class="d-flex align-items-center" style="min-height: 42px; gap: 12px;">
|
||||||
|
<button type="button" id="toggleStickyBtn" class="btn btn-sm sticky-toggle-btn active" style="flex-shrink:0;" title="Blocca/sblocca barra azioni"><i class="fas fa-thumbtack"></i></button>
|
||||||
<a href="#" class="btn btn-outline-danger btn-sm export-all-lims-btn" style="flex-shrink:0;"><i class="fas fa-upload"></i> Export All</a>
|
<a href="#" class="btn btn-outline-danger btn-sm export-all-lims-btn" style="flex-shrink:0;"><i class="fas fa-upload"></i> Export All</a>
|
||||||
<a href="#" class="btn btn-outline-success btn-sm save-all-btn" style="flex-shrink:0;"><i class="fas fa-save"></i> Save All</a>
|
<a href="#" class="btn btn-outline-success btn-sm save-all-btn" style="flex-shrink:0;"><i class="fas fa-save"></i> Save All</a>
|
||||||
<button type="button" class="btn btn-outline-success btn-sm" id="addRowBtn" style="flex-shrink:0;"><i class="fas fa-plus"></i> Add Row</button>
|
<button type="button" class="btn btn-outline-success btn-sm" id="addRowBtn" style="flex-shrink:0;"><i class="fas fa-plus"></i> Add Row</button>
|
||||||
@@ -1538,8 +1618,11 @@ $gridMeta = [
|
|||||||
<div class="top-scrollbar-inner" id="topScrollbarInner"></div>
|
<div class="top-scrollbar-inner" id="topScrollbarInner"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid-container" id="gridContainer">
|
<div class="grid-top-scroller" id="gridTopScroller">
|
||||||
<div class="grid-top" id="gridTopContainer"></div>
|
<div class="grid-top" id="gridTopContainer"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid-container" id="gridContainer">
|
||||||
<div class="grid-row" id="gridHeaderContainer" style="font-weight:600;"></div>
|
<div class="grid-row" id="gridHeaderContainer" style="font-weight:600;"></div>
|
||||||
<div id="gridRowContainer"></div>
|
<div id="gridRowContainer"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1610,21 +1693,43 @@ $gridMeta = [
|
|||||||
topScrollbar.scrollLeft = gridContainer.scrollLeft;
|
topScrollbar.scrollLeft = gridContainer.scrollLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const gridTopScroller = document.getElementById('gridTopScroller');
|
||||||
|
let syncing = false;
|
||||||
|
|
||||||
|
function syncScroll(source) {
|
||||||
|
if (syncing) return;
|
||||||
|
syncing = true;
|
||||||
|
const x = source.scrollLeft;
|
||||||
|
if (source !== topScrollbar) topScrollbar.scrollLeft = x;
|
||||||
|
if (source !== gridContainer) gridContainer.scrollLeft = x;
|
||||||
|
if (gridTopScroller && source !== gridTopScroller) gridTopScroller.scrollLeft = x;
|
||||||
|
syncing = false;
|
||||||
|
}
|
||||||
|
|
||||||
topScrollbar.addEventListener('scroll', function() {
|
topScrollbar.addEventListener('scroll', function() {
|
||||||
if (syncingFromGrid) return;
|
syncScroll(topScrollbar);
|
||||||
syncingFromTop = true;
|
|
||||||
gridContainer.scrollLeft = topScrollbar.scrollLeft;
|
|
||||||
syncingFromTop = false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
gridContainer.addEventListener('scroll', function() {
|
gridContainer.addEventListener('scroll', function() {
|
||||||
if (syncingFromTop) return;
|
syncScroll(gridContainer);
|
||||||
syncingFromGrid = true;
|
|
||||||
topScrollbar.scrollLeft = gridContainer.scrollLeft;
|
|
||||||
syncingFromGrid = false;
|
|
||||||
});
|
});
|
||||||
|
if (gridTopScroller) {
|
||||||
|
gridTopScroller.addEventListener('scroll', function() {
|
||||||
|
syncScroll(gridTopScroller);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener('resize', updateTopScrollbarWidth);
|
let rafPending = false;
|
||||||
|
|
||||||
|
function scheduleUpdate() {
|
||||||
|
if (rafPending) return;
|
||||||
|
rafPending = true;
|
||||||
|
requestAnimationFrame(function() {
|
||||||
|
rafPending = false;
|
||||||
|
updateTopScrollbarWidth();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', scheduleUpdate);
|
||||||
|
|
||||||
// Recalculate after JS grid rendering
|
// Recalculate after JS grid rendering
|
||||||
setTimeout(updateTopScrollbarWidth, 100);
|
setTimeout(updateTopScrollbarWidth, 100);
|
||||||
@@ -1633,13 +1738,13 @@ $gridMeta = [
|
|||||||
setTimeout(updateTopScrollbarWidth, 1500);
|
setTimeout(updateTopScrollbarWidth, 1500);
|
||||||
|
|
||||||
// Recalculate automatically when rows/header/top controls are rendered or changed
|
// Recalculate automatically when rows/header/top controls are rendered or changed
|
||||||
const observer = new MutationObserver(updateTopScrollbarWidth);
|
const observer = new MutationObserver(scheduleUpdate);
|
||||||
|
|
||||||
if (gridContainer) {
|
if (gridContainer) {
|
||||||
observer.observe(gridContainer, {
|
observer.observe(gridContainer, {
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: true,
|
subtree: true,
|
||||||
attributes: true
|
attributes: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1804,7 +1909,96 @@ $gridMeta = [
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="sessionModal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:99999;align-items:center;justify-content:center">
|
||||||
|
<div style="background:#fff;padding:28px 32px;border-radius:14px;max-width:420px;text-align:center;box-shadow:0 20px 60px rgba(0,0,0,.3)">
|
||||||
|
<h5 style="margin:0 0 10px">La sessione sta per scadere</h5>
|
||||||
|
<p style="color:#64748b;margin:0 0 6px">Per la tua sicurezza verrai disconnesso tra</p>
|
||||||
|
<div id="sessCountdownBig" style="font:700 34px monospace;color:#dc2626;margin:8px 0 18px">00:30</div>
|
||||||
|
<button id="sessStayBtn" class="btn btn-primary btn-lg px-4">Resta connesso</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
// ── TEST: tempi compressi. In produzione: SESSION_MINUTES=120, WARN_SECONDS=120
|
||||||
|
const SESSION_MINUTES = 110; // durata simulata
|
||||||
|
const WARN_SECONDS = 30; // preavviso prima della scadenza
|
||||||
|
const LOGIN_URL = '../login'; // adatta al tuo path reale
|
||||||
|
|
||||||
|
const sessionMs = SESSION_MINUTES * 60 * 1000;
|
||||||
|
let lastActivity = Date.now();
|
||||||
|
let modalShown = false;
|
||||||
|
|
||||||
|
// ogni attività reale resetta il timer locale
|
||||||
|
['mousemove', 'keydown', 'click', 'scroll', 'touchstart'].forEach(ev =>
|
||||||
|
document.addEventListener(ev, () => {
|
||||||
|
lastActivity = Date.now();
|
||||||
|
if (!modalShown) return; // se il modal è aperto, l'attività NON conta: serve il click esplicito
|
||||||
|
}, {
|
||||||
|
passive: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const modal = document.getElementById('sessionModal');
|
||||||
|
const bigCd = document.getElementById('sessCountdownBig');
|
||||||
|
|
||||||
|
document.getElementById('sessStayBtn').addEventListener('click', async () => {
|
||||||
|
try {
|
||||||
|
const r = await fetch('session_keepalive.php', {
|
||||||
|
cache: 'no-store'
|
||||||
|
});
|
||||||
|
// se la sessione era già morta, headscript reindirizza: la risposta non sarà il JSON atteso
|
||||||
|
const ct = r.headers.get('content-type') || '';
|
||||||
|
if (!ct.includes('application/json')) {
|
||||||
|
window.location.href = LOGIN_URL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await r.json();
|
||||||
|
lastActivity = Date.now(); // timer riparte
|
||||||
|
modalShown = false;
|
||||||
|
modal.style.display = 'none';
|
||||||
|
} catch (e) {
|
||||||
|
window.location.href = LOGIN_URL;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
const elapsed = Date.now() - lastActivity;
|
||||||
|
const remaining = Math.ceil((sessionMs - elapsed) / 1000);
|
||||||
|
|
||||||
|
if (remaining <= 0) {
|
||||||
|
window.location.href = LOGIN_URL; // scaduta
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (remaining <= WARN_SECONDS && !modalShown) {
|
||||||
|
modalShown = true;
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
}
|
||||||
|
if (modalShown) {
|
||||||
|
const m = Math.floor(remaining / 60),
|
||||||
|
s = remaining % 60;
|
||||||
|
bigCd.textContent = String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0');
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
const btn = document.getElementById('toggleStickyBtn');
|
||||||
|
const cardHeader = document.querySelector('.card-header.sticky-actions');
|
||||||
|
if (!btn || !cardHeader) return;
|
||||||
|
|
||||||
|
const gridTopScroller = document.getElementById('gridTopScroller');
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
const pinned = btn.classList.toggle('active');
|
||||||
|
cardHeader.classList.toggle('actions-unpinned', !pinned);
|
||||||
|
if (gridTopScroller) gridTopScroller.classList.toggle('actions-unpinned', !pinned);
|
||||||
|
btn.title = pinned ?
|
||||||
|
'Sblocca barra azioni' :
|
||||||
|
'Blocca barra azioni';
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -56,4 +56,12 @@ $photousername = basename($avatar);
|
|||||||
//include files
|
//include files
|
||||||
require_once(__DIR__ . '/../../languages/en/general.php');
|
require_once(__DIR__ . '/../../languages/en/general.php');
|
||||||
|
|
||||||
//include("generalsettings.php");
|
//include("generalsettings.php");
|
||||||
|
|
||||||
|
// Timbra la sessione dell'utente a ogni pagina (rolling manuale)
|
||||||
|
try {
|
||||||
|
$pdoSess = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
$pdoSess->prepare("UPDATE auth_sessions SET last_activity = ? WHERE user_id = ? ORDER BY last_activity DESC LIMIT 1")
|
||||||
|
->execute([time(), $iduserlogin]);
|
||||||
|
} catch (\Throwable $e) { /* non bloccare la pagina */
|
||||||
|
}
|
||||||
|
|||||||
@@ -64,6 +64,11 @@
|
|||||||
<i class='bx bx-radio-circle'></i><?= htmlspecialchars($insertnewtemplatexls, ENT_QUOTES, 'UTF-8'); ?>
|
<i class='bx bx-radio-circle'></i><?= htmlspecialchars($insertnewtemplatexls, ENT_QUOTES, 'UTF-8'); ?>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="bindings_manage.php">
|
||||||
|
<i class='bx bx-radio-circle'></i><?= htmlspecialchars($bindingsmanage ?? 'Value Bindings', ENT_QUOTES, 'UTF-8'); ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -103,6 +108,14 @@
|
|||||||
<div class="menu-title">Stats</div>
|
<div class="menu-title">Stats</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="routine_admin.php">
|
||||||
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-bar-chart-alt-2"></i>
|
||||||
|
</div>
|
||||||
|
<div class="menu-title">Routines</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="warm_cache_run.php">
|
<a href="warm_cache_run.php">
|
||||||
<div class="parent-icon">
|
<div class="parent-icon">
|
||||||
|
|||||||
@@ -108,6 +108,8 @@
|
|||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li><a class="dropdown-item d-flex align-items-center" href="user-profile.php"><i class="bx bx-user fs-5"></i><span>Profile</span></a>
|
<li><a class="dropdown-item d-flex align-items-center" href="user-profile.php"><i class="bx bx-user fs-5"></i><span>Profile</span></a>
|
||||||
</li>
|
</li>
|
||||||
|
<li><a class="dropdown-item d-flex align-items-center" href="notification_settings.php"><i class="bx bx-user fs-5"></i><span>Notifications</span></a>
|
||||||
|
</li>
|
||||||
<?php if ($user->hasRole('Admin') || $user->hasRole('SuperUser')): ?>
|
<?php if ($user->hasRole('Admin') || $user->hasRole('SuperUser')): ?>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item d-flex align-items-center" href="user-admin.php">
|
<a class="dropdown-item d-flex align-items-center" href="user-admin.php">
|
||||||
|
|||||||
@@ -88,7 +88,8 @@ $mappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
|
|
||||||
// Recupera i fixed fields dalla tabella template_fixed_mapping
|
// Recupera i fixed fields dalla tabella template_fixed_mapping
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
SELECT id, fixed_field_key, is_manual, data_type, is_required, default_value, is_visible_import
|
SELECT id, fixed_field_key, is_manual, data_type, is_required, default_value, is_visible_import,
|
||||||
|
default_source, excel_column
|
||||||
FROM template_fixed_mapping
|
FROM template_fixed_mapping
|
||||||
WHERE template_id = ?
|
WHERE template_id = ?
|
||||||
ORDER BY id ASC
|
ORDER BY id ASC
|
||||||
@@ -459,9 +460,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
$autoValue = $mapping['auto_value'] ?? 'none';
|
$autoValue = $mapping['auto_value'] ?? 'none';
|
||||||
$hasAuto = ($autoValue && $autoValue !== 'none');
|
$hasAuto = ($autoValue && $autoValue !== 'none');
|
||||||
|
|
||||||
if ($isSceltaMultipla) {
|
if ($hasAuto && !$isSceltaMultipla) {
|
||||||
$mappingValue = 'manual';
|
|
||||||
} elseif ($hasAuto) {
|
|
||||||
$mappingValue = 'auto';
|
$mappingValue = 'auto';
|
||||||
} elseif ($sourceType === 'XLS' && !empty($mapping['excel_column'])) {
|
} elseif ($sourceType === 'XLS' && !empty($mapping['excel_column'])) {
|
||||||
$mappingValue = 'xls';
|
$mappingValue = 'xls';
|
||||||
@@ -470,24 +469,26 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
} elseif ((int)$mapping['is_manual'] === 1) {
|
} elseif ((int)$mapping['is_manual'] === 1) {
|
||||||
$mappingValue = 'manual';
|
$mappingValue = 'manual';
|
||||||
} else {
|
} else {
|
||||||
$mappingValue = '';
|
// SceltaMultipla senza colonna: valore di default dal dropdown
|
||||||
|
$mappingValue = $isSceltaMultipla ? 'manual' : '';
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<select class="form-select mapping-select"
|
<select class="form-select mapping-select"
|
||||||
data-id="<?php echo (int)$mapping['id']; ?>"
|
data-id="<?php echo (int)$mapping['id']; ?>"
|
||||||
data-field-id="<?php echo (int)$mapping['field_id']; ?>"
|
data-field-id="<?php echo (int)$mapping['field_id']; ?>">
|
||||||
<?php echo $isSceltaMultipla ? 'disabled' : ''; ?>>
|
|
||||||
|
|
||||||
<?php if (!$isSceltaMultipla): ?>
|
<?php if (!$isSceltaMultipla): ?>
|
||||||
<option value="">Select Option</option>
|
<option value="">Select Option</option>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($sourceType === 'XLS'): ?>
|
<?php if ($sourceType === 'XLS'): ?>
|
||||||
<option value="xls" <?php echo ($mappingValue === 'xls') ? 'selected' : ''; ?>>Map to XLS Column</option>
|
<option value="xls" <?php echo ($mappingValue === 'xls') ? 'selected' : ''; ?>>Map to XLS Column</option>
|
||||||
<?php elseif ($sourceType === 'API'): ?>
|
<?php elseif ($sourceType === 'API'): ?>
|
||||||
<option value="json" <?php echo ($mappingValue === 'json') ? 'selected' : ''; ?>>Map to JSON Node</option>
|
<option value="json" <?php echo ($mappingValue === 'json') ? 'selected' : ''; ?>>Map to JSON Node</option>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!$isSceltaMultipla): ?>
|
||||||
<option value="auto" <?php echo ($mappingValue === 'auto') ? 'selected' : ''; ?>>Auto value</option>
|
<option value="auto" <?php echo ($mappingValue === 'auto') ? 'selected' : ''; ?>>Auto value</option>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
@@ -543,6 +544,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
|
|
||||||
<td>
|
<td>
|
||||||
<?php if ($mapping['data_type'] === 'SceltaMultipla'): ?>
|
<?php if ($mapping['data_type'] === 'SceltaMultipla'): ?>
|
||||||
|
<div class="scelta-default-wrap" style="display:<?php echo ($mappingValue === 'xls' || $mappingValue === 'json') ? 'none' : 'block'; ?>;">
|
||||||
<select class="form-select dropdown-select manual-default"
|
<select class="form-select dropdown-select manual-default"
|
||||||
data-id="<?php echo (int)$mapping['id']; ?>"
|
data-id="<?php echo (int)$mapping['id']; ?>"
|
||||||
data-field-id="<?php echo (int)$mapping['field_id']; ?>"
|
data-field-id="<?php echo (int)$mapping['field_id']; ?>"
|
||||||
@@ -550,6 +552,10 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
style="display:block;">
|
style="display:block;">
|
||||||
<option value="">Seleziona...</option>
|
<option value="">Seleziona...</option>
|
||||||
</select>
|
</select>
|
||||||
|
</div>
|
||||||
|
<small class="text-muted scelta-binding-hint" style="display:<?php echo ($mappingValue === 'xls' || $mappingValue === 'json') ? 'block' : 'none'; ?>;">
|
||||||
|
Values from the file are linked to the LIMS list via Value Bindings
|
||||||
|
</small>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
class="form-control manual-default"
|
class="form-control manual-default"
|
||||||
@@ -594,6 +600,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
<th style="width:120px; text-align:center;">Required</th>
|
<th style="width:120px; text-align:center;">Required</th>
|
||||||
<th>Field</th>
|
<th>Field</th>
|
||||||
<th style="width:140px;">Type</th>
|
<th style="width:140px;">Type</th>
|
||||||
|
<th style="width:230px;">Source</th>
|
||||||
<th>Default Value</th>
|
<th>Default Value</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -626,6 +633,28 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
|
|
||||||
<td><?php echo htmlspecialchars($fm['data_type']); ?></td>
|
<td><?php echo htmlspecialchars($fm['data_type']); ?></td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<?php $fixedSource = $fm['default_source'] ?? 'manual'; ?>
|
||||||
|
<select class="form-select fixed-source-select"
|
||||||
|
data-fixed-id="<?php echo (int)$fm['id']; ?>">
|
||||||
|
<option value="manual" <?php echo $fixedSource === 'manual' ? 'selected' : ''; ?>>Default value</option>
|
||||||
|
<?php if ($sourceType === 'XLS'): ?>
|
||||||
|
<option value="xls" <?php echo $fixedSource === 'xls' ? 'selected' : ''; ?>>Map to XLS Column</option>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ($sourceType === 'API'): ?>
|
||||||
|
<option value="json" <?php echo $fixedSource === 'json' ? 'selected' : ''; ?>>Map to JSON Node</option>
|
||||||
|
<?php endif; ?>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<?php if ($sourceType === 'XLS'): ?>
|
||||||
|
<select class="form-select xls-columns fixed-xls-column mt-1"
|
||||||
|
style="display:<?php echo $fixedSource === 'xls' ? 'block' : 'none'; ?>"
|
||||||
|
data-fixed-id="<?php echo (int)$fm['id']; ?>"
|
||||||
|
<?php echo !empty($fm['excel_column']) ? 'data-current-xls="' . htmlspecialchars($fm['excel_column'], ENT_QUOTES, 'UTF-8') . '"' : ''; ?>>
|
||||||
|
</select>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
|
|
||||||
<?php if ($fm['data_type'] === 'DATE'): ?>
|
<?php if ($fm['data_type'] === 'DATE'): ?>
|
||||||
@@ -1279,8 +1308,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function saveJsonNodes(sampleJson, nodes) {
|
function saveJsonNodes(sampleJson, nodes) {
|
||||||
return fetch('update_api_json_nodes.php', {
|
return fetch('update_api_json_nodes.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -1363,6 +1390,41 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
});
|
});
|
||||||
|
|
||||||
// fallback (nel caso cambi senza select2)
|
// fallback (nel caso cambi senza select2)
|
||||||
|
$(document).on('change', '.fixed-source-select', function() {
|
||||||
|
const id = this.dataset.fixedId;
|
||||||
|
const value = this.value;
|
||||||
|
const $colSelect = $(this).closest('td').find('.fixed-xls-column');
|
||||||
|
|
||||||
|
$colSelect.toggle(value === 'xls');
|
||||||
|
if (value !== 'xls') {
|
||||||
|
$colSelect.val('');
|
||||||
|
}
|
||||||
|
|
||||||
|
saveFixedField(id, 'default_source', value);
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('change', '.fixed-xls-column', function() {
|
||||||
|
saveFixedField(this.dataset.fixedId, 'excel_column', this.value || null);
|
||||||
|
});
|
||||||
|
|
||||||
|
function saveFixedField(id, field, value) {
|
||||||
|
const status = document.getElementById('fixedSaveStatus');
|
||||||
|
if (status) status.textContent = 'Salvataggio...';
|
||||||
|
|
||||||
|
fetch('update_fixed_field.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ id: parseInt(id, 10), field: field, value: value })
|
||||||
|
})
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(d => {
|
||||||
|
if (status) status.textContent = d.success ? 'Salvato' : ('Errore: ' + (d.message || ''));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (status) status.textContent = 'Errore di rete';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$(document).on('change', '.fixed-default-select', function() {
|
$(document).on('change', '.fixed-default-select', function() {
|
||||||
saveFixedDefaultSelect(this);
|
saveFixedDefaultSelect(this);
|
||||||
});
|
});
|
||||||
@@ -1694,6 +1756,17 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SceltaMultipla: il default è un dropdown (Select2) nel wrapper; con colonna/nodo
|
||||||
|
// mappato si nasconde (il valore arriva dal file e passa dal binding).
|
||||||
|
const sceltaWrap = tr.querySelector('.scelta-default-wrap');
|
||||||
|
const sceltaHint = tr.querySelector('.scelta-binding-hint');
|
||||||
|
if (sceltaWrap) {
|
||||||
|
const fromFile = mappingSelect.value === 'xls' || mappingSelect.value === 'json';
|
||||||
|
sceltaWrap.style.display = fromFile ? 'none' : 'block';
|
||||||
|
if (sceltaHint) sceltaHint.style.display = fromFile ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
const isScelta = !!sceltaWrap;
|
||||||
|
|
||||||
if (mappingSelect.value === 'xls') {
|
if (mappingSelect.value === 'xls') {
|
||||||
if (xlsSelect) xlsSelect.style.display = 'block';
|
if (xlsSelect) xlsSelect.style.display = 'block';
|
||||||
|
|
||||||
@@ -1705,7 +1778,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
|
|
||||||
if (autoSelect) autoSelect.style.display = 'none';
|
if (autoSelect) autoSelect.style.display = 'none';
|
||||||
|
|
||||||
if (manualInput) {
|
if (manualInput && !isScelta) {
|
||||||
manualInput.style.display = 'none';
|
manualInput.style.display = 'none';
|
||||||
manualInput.value = '';
|
manualInput.value = '';
|
||||||
}
|
}
|
||||||
@@ -1728,7 +1801,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
|
|
||||||
if (autoSelect) autoSelect.style.display = 'none';
|
if (autoSelect) autoSelect.style.display = 'none';
|
||||||
|
|
||||||
if (manualInput) {
|
if (manualInput && !isScelta) {
|
||||||
manualInput.style.display = 'none';
|
manualInput.style.display = 'none';
|
||||||
manualInput.value = '';
|
manualInput.value = '';
|
||||||
}
|
}
|
||||||
@@ -1774,7 +1847,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
jsonSelect.value = '';
|
jsonSelect.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (manualInput) {
|
if (manualInput && !isScelta) {
|
||||||
manualInput.style.display = 'none';
|
manualInput.style.display = 'none';
|
||||||
manualInput.value = '';
|
manualInput.value = '';
|
||||||
}
|
}
|
||||||
@@ -1798,7 +1871,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
|
|
||||||
if (autoSelect) autoSelect.style.display = 'none';
|
if (autoSelect) autoSelect.style.display = 'none';
|
||||||
|
|
||||||
if (manualInput) {
|
if (manualInput && !isScelta) {
|
||||||
manualInput.style.display = 'none';
|
manualInput.style.display = 'none';
|
||||||
manualInput.value = '';
|
manualInput.value = '';
|
||||||
}
|
}
|
||||||
@@ -1882,7 +1955,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Save original Main checkbox state
|
// Save original Main checkbox state
|
||||||
document.querySelectorAll('#schemaFieldsBody .main-field-checkbox').forEach(cb => {
|
document.querySelectorAll('#schemaFieldsBody .main-field-checkbox').forEach(cb => {
|
||||||
cb.dataset.originalChecked = cb.checked ? 'true' : 'false';
|
cb.dataset.originalChecked = cb.checked ? 'true' : 'false';
|
||||||
@@ -2008,7 +2080,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
mappingSelect.value = '';
|
mappingSelect.value = '';
|
||||||
if (mappedColumn) mappedColumn.style.display = 'none';
|
if (mappedColumn) mappedColumn.style.display = 'none';
|
||||||
e.target.style.display = 'none';
|
e.target.style.display = 'none';
|
||||||
saveMapping(mappingId, '', '', null, null, null);
|
if (!resetSceltaToManual(tr, mappingId)) saveMapping(mappingId, '', '', null, null, null);
|
||||||
updateXlsDropdowns();
|
updateXlsDropdowns();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -2031,7 +2103,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
|
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
|
||||||
if (event.target.classList.contains('manual-default') && event.target.tagName === 'SELECT') {
|
if (event.target.classList.contains('manual-default') && event.target.tagName === 'SELECT') {
|
||||||
let tr = event.target.closest('tr');
|
let tr = event.target.closest('tr');
|
||||||
@@ -2089,7 +2160,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
console.log("Removing XLS mapping:", {
|
console.log("Removing XLS mapping:", {
|
||||||
id: mappingId
|
id: mappingId
|
||||||
});
|
});
|
||||||
saveMapping(mappingId, '', '', null, null, null);
|
if (!resetSceltaToManual(tr, mappingId)) saveMapping(mappingId, '', '', null, null, null);
|
||||||
updateXlsDropdowns();
|
updateXlsDropdowns();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -2119,11 +2190,26 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
|
|
||||||
event.target.style.display = 'none';
|
event.target.style.display = 'none';
|
||||||
|
|
||||||
saveMapping(mappingId, '', '', null, null, null);
|
if (!resetSceltaToManual(tr, mappingId)) saveMapping(mappingId, '', '', null, null, null);
|
||||||
updateJsonDropdowns();
|
updateJsonDropdowns();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Rimozione colonna/nodo: un campo SceltaMultipla torna a "Manual Entry" col suo dropdown default
|
||||||
|
// (non ha l'opzione vuota). Ritorna true se gestito.
|
||||||
|
function resetSceltaToManual(tr, mappingId) {
|
||||||
|
const wrap = tr.querySelector('.scelta-default-wrap');
|
||||||
|
if (!wrap) return false;
|
||||||
|
const sel = tr.querySelector('.mapping-select');
|
||||||
|
if (sel) sel.value = 'manual';
|
||||||
|
wrap.style.display = 'block';
|
||||||
|
const hint = tr.querySelector('.scelta-binding-hint');
|
||||||
|
if (hint) hint.style.display = 'none';
|
||||||
|
const dd = tr.querySelector('select.manual-default.dropdown-select');
|
||||||
|
saveMapping(mappingId, 'manual', dd ? dd.value : '', null, null, null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function saveMapping(mappingId, mappingType, defaultValue, excelColumn, autoValue, jsonNode) {
|
function saveMapping(mappingId, mappingType, defaultValue, excelColumn, autoValue, jsonNode) {
|
||||||
console.log("Saving mapping:", {
|
console.log("Saving mapping:", {
|
||||||
id: mappingId,
|
id: mappingId,
|
||||||
@@ -2233,7 +2319,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
|
|
||||||
<td><strong>${escapeHtml(fixedFieldLabels[r.fixed_field_key] || r.fixed_field_key)}</strong></td>
|
<td><strong>${escapeHtml(fixedFieldLabels[r.fixed_field_key] || r.fixed_field_key)}</strong></td>
|
||||||
|
|
||||||
|
|
||||||
<td>${escapeHtml(r.data_type || '')}</td>
|
<td>${escapeHtml(r.data_type || '')}</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$userId = (int)($iduserlogin ?? 0);
|
||||||
|
|
||||||
|
// Carica le preferenze correnti dell'utente
|
||||||
|
$prefs = ['notify_export_email' => 1];
|
||||||
|
if ($userId > 0) {
|
||||||
|
$s = $pdo->prepare("SELECT notify_export_email FROM auth_users WHERE id = ? LIMIT 1");
|
||||||
|
$s->execute([$userId]);
|
||||||
|
$row = $s->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if ($row) {
|
||||||
|
$prefs['notify_export_email'] = (int)($row['notify_export_email'] ?? 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flash message post-save (via querystring)
|
||||||
|
$saved = isset($_GET['saved']) && $_GET['saved'] === '1';
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||||
|
<?php include('cssinclude.php'); ?>
|
||||||
|
<title>SmartTRF - Notifiche</title>
|
||||||
|
<style>
|
||||||
|
.notif-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 16px 0;
|
||||||
|
border-bottom: 1px solid #eef0f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notif-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notif-text {
|
||||||
|
max-width: 640px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notif-text h6 {
|
||||||
|
margin: 0 0 4px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notif-text p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-switch .form-check-input {
|
||||||
|
width: 2.6em;
|
||||||
|
height: 1.4em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<?php include('include/navbar.php'); ?>
|
||||||
|
<?php include('include/topbar.php'); ?>
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
|
||||||
|
<div class="card radius-10">
|
||||||
|
<div class="card-header">
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-0">Preferenze notifiche</h6>
|
||||||
|
<small class="text-muted">Scegli quali email vuoi ricevere da SmartTRF.</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<?php if ($saved): ?>
|
||||||
|
<div class="alert alert-success py-2" role="alert">
|
||||||
|
<i class="fas fa-check-circle me-1"></i> Preferenze salvate.
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form action="update_notification_prefs.php" method="POST">
|
||||||
|
<input type="hidden" name="iduserlogin" value="<?= htmlspecialchars((string)$userId, ENT_QUOTES, 'UTF-8') ?>">
|
||||||
|
|
||||||
|
<!-- Notifica: commesse esportate -->
|
||||||
|
<div class="notif-row">
|
||||||
|
<div class="notif-text">
|
||||||
|
<h6>Ricezione email commesse esportate</h6>
|
||||||
|
<p>Ricevi un riepilogo via email al termine di ogni esportazione verso il LIMS (singola o in blocco), con l'esito di ciascuna commessa.</p>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch"
|
||||||
|
id="notify_export_email" name="notify_export_email" value="1"
|
||||||
|
<?= $prefs['notify_export_email'] === 1 ? 'checked' : '' ?>>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Qui in futuro altre notifiche, stessa struttura .notif-row -->
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-save me-1"></i> Salva preferenze
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="overlay toggle-icon"></div>
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -506,6 +506,7 @@ try {
|
|||||||
if ($routineData) {
|
if ($routineData) {
|
||||||
$response['apply_routine'] = true;
|
$response['apply_routine'] = true;
|
||||||
$response['routine_data'] = [
|
$response['routine_data'] = [
|
||||||
|
'idroutine' => (int)$routineData['idroutine'],
|
||||||
'name' => $routineData['name'] ?? 'Routine Sconosciuta',
|
'name' => $routineData['name'] ?? 'Routine Sconosciuta',
|
||||||
'instruction' => $routineData['instruction'] ?? 'Nessuna descrizione disponibile',
|
'instruction' => $routineData['instruction'] ?? 'Nessuna descrizione disponibile',
|
||||||
'filename' => $routineData['filename'] ?? '',
|
'filename' => $routineData['filename'] ?? '',
|
||||||
|
|||||||
@@ -0,0 +1,417 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
include('include/headscript.php');
|
||||||
|
require_once __DIR__ . '/class/binding-functions.php';
|
||||||
|
|
||||||
|
$pending = $_SESSION['pending_bindings'] ?? null;
|
||||||
|
|
||||||
|
if (isset($_GET['done'])) {
|
||||||
|
$tid = $pending['template_id'] ?? ($_SESSION['template_id'] ?? null);
|
||||||
|
$ref = $pending['importref'] ?? '';
|
||||||
|
unset($_SESSION['pending_bindings']);
|
||||||
|
|
||||||
|
header($tid
|
||||||
|
? "Location: imported.php?id=" . urlencode($tid) . "&importref=" . urlencode($ref)
|
||||||
|
: "Location: xlstemplates_grid.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($pending) || empty($pending['items'])) {
|
||||||
|
$tid = $pending['template_id'] ?? ($_SESSION['template_id'] ?? null);
|
||||||
|
$ref = $pending['importref'] ?? '';
|
||||||
|
unset($_SESSION['pending_bindings']);
|
||||||
|
|
||||||
|
if ($tid) {
|
||||||
|
header("Location: imported.php?id=" . urlencode($tid) . "&importref=" . urlencode($ref));
|
||||||
|
} else {
|
||||||
|
header("Location: xlstemplates_grid.php");
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$templateId = (int) $pending['template_id'];
|
||||||
|
$importRef = (string) $pending['importref'];
|
||||||
|
$items = array_values($pending['items'] ?? []);
|
||||||
|
$autoItems = array_values($pending['auto'] ?? []);
|
||||||
|
$savedItems = array_values($pending['saved'] ?? []);
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT name FROM excel_templates WHERE id = ?");
|
||||||
|
$stmt->execute([$templateId]);
|
||||||
|
$templateName = $stmt->fetchColumn() ?: ('Template ' . $templateId);
|
||||||
|
|
||||||
|
foreach ($items as $i => $item) {
|
||||||
|
$items[$i]['suggestion'] = ($item['kind'] ?? 'custom') === 'fixed'
|
||||||
|
? binding_suggest_fixed(
|
||||||
|
$pdo,
|
||||||
|
(string) ($item['fixed_field_key'] ?? ''),
|
||||||
|
(string) $item['source_value'],
|
||||||
|
(int) $item['mapping_id']
|
||||||
|
)
|
||||||
|
: binding_suggest(
|
||||||
|
$pdo,
|
||||||
|
(int) $item['field_id'],
|
||||||
|
(string) $item['source_value'],
|
||||||
|
(int) $item['mapping_id']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$resolvedItems = [];
|
||||||
|
foreach ($autoItems as $a) {
|
||||||
|
$a['badge'] = 'automatico';
|
||||||
|
$a['badge_class'] = 'bg-success';
|
||||||
|
$resolvedItems[] = $a;
|
||||||
|
}
|
||||||
|
foreach ($savedItems as $s) {
|
||||||
|
$s['badge'] = 'binding salvato';
|
||||||
|
$s['badge_class'] = 'bg-secondary';
|
||||||
|
$resolvedItems[] = $s;
|
||||||
|
}
|
||||||
|
|
||||||
|
$groups = [];
|
||||||
|
$groupOrder = [];
|
||||||
|
$pushToGroup = function (array $row, string $type, $idx = null) use (&$groups, &$groupOrder) {
|
||||||
|
$kind = $row['kind'] ?? 'custom';
|
||||||
|
$gkey = $kind . ':' . (int) ($row['mapping_id'] ?? 0);
|
||||||
|
if (!isset($groups[$gkey])) {
|
||||||
|
$groups[$gkey] = [
|
||||||
|
'label' => $row['field_label'] ?? ('Mapping ' . $gkey),
|
||||||
|
'kind' => $kind,
|
||||||
|
'pending' => [],
|
||||||
|
'resolved' => [],
|
||||||
|
];
|
||||||
|
$groupOrder[] = $gkey;
|
||||||
|
}
|
||||||
|
if ($type === 'pending') {
|
||||||
|
$groups[$gkey]['pending'][] = ['idx' => $idx, 'data' => $row];
|
||||||
|
} else {
|
||||||
|
$groups[$gkey]['resolved'][] = ['data' => $row];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
foreach ($items as $idx => $item) {
|
||||||
|
$pushToGroup($item, 'pending', $idx);
|
||||||
|
}
|
||||||
|
foreach ($resolvedItems as $res) {
|
||||||
|
$pushToGroup($res, 'resolved');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pendingRecords = 0;
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$pendingRecords += count($item['datadb_ids'] ?? []);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!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>
|
||||||
|
.source-value {
|
||||||
|
font-family: Consolas, Monaco, monospace;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.binding-status {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td .select2-container {
|
||||||
|
min-width: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allinea l'altezza di select2 a form-select/btn di Bootstrap */
|
||||||
|
.select2-container--default .select2-selection--single {
|
||||||
|
height: 38px;
|
||||||
|
border-color: #ced4da;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-selection--single .select2-selection__rendered {
|
||||||
|
line-height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-selection--single .select2-selection__arrow {
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<title>Binding valori → 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">
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-0">Binding valori importati → LIMS</h6>
|
||||||
|
<small><?= htmlspecialchars($templateName) ?> · Template ID: <?= $templateId ?></small>
|
||||||
|
</div>
|
||||||
|
<a href="bindings_manage.php?template_id=<?= $templateId ?>" class="btn btn-sm btn-outline-secondary">
|
||||||
|
<i class="fas fa-cog"></i> Gestione binding
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="alert alert-info">
|
||||||
|
Alcuni valori importati dal file non corrispondono a nessun valore delle liste LIMS.
|
||||||
|
Finche' non vengono collegati, questi campi non possono essere esportati.
|
||||||
|
Scegli il valore LIMS corretto per ciascuno: il binding viene salvato e
|
||||||
|
riutilizzato nelle importazioni successive.
|
||||||
|
<div class="mt-1">
|
||||||
|
<strong><?= count($items) ?></strong> valore/i da risolvere su
|
||||||
|
<strong><?= $pendingRecords ?></strong> record importati.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php if (!empty($autoItems)): ?>
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<?= count($autoItems) ?> valore/i collegato/i automaticamente (corrispondenza esatta con il LIMS).
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div id="bindingError" class="alert alert-danger" style="display:none;"></div>
|
||||||
|
|
||||||
|
<form id="bindingForm">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered align-middle">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:32px;"></th>
|
||||||
|
<th>Valore importato</th>
|
||||||
|
<th style="width:90px;">Record</th>
|
||||||
|
<th>Valore LIMS</th>
|
||||||
|
<th style="width:210px;">Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($groupOrder as $gkey): $g = $groups[$gkey];
|
||||||
|
$pendCount = count($g['pending']);
|
||||||
|
$totalCount = $pendCount + count($g['resolved']); ?>
|
||||||
|
<tr class="binding-group-header table-light">
|
||||||
|
<td colspan="5">
|
||||||
|
<strong><?= htmlspecialchars($g['label']) ?></strong>
|
||||||
|
<?php if (($g['kind'] ?? 'custom') === 'fixed'): ?><span class="badge bg-light text-dark border">campo fisso</span><?php endif; ?>
|
||||||
|
<span class="text-muted">· <?= $totalCount ?> valore/i<?php if ($pendCount): ?>, <?= $pendCount ?> da risolvere<?php endif; ?></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<?php foreach ($g['pending'] as $p): $idx = $p['idx']; $item = $p['data'];
|
||||||
|
$suggestion = $item['suggestion'] ?? null; ?>
|
||||||
|
<tr class="binding-row"
|
||||||
|
data-index="<?= $idx ?>"
|
||||||
|
data-kind="<?= htmlspecialchars($item['kind'] ?? 'custom', ENT_QUOTES) ?>"
|
||||||
|
data-fixed-key="<?= htmlspecialchars($item['fixed_field_key'] ?? '', ENT_QUOTES) ?>"
|
||||||
|
data-mapping-id="<?= (int) $item['mapping_id'] ?>"
|
||||||
|
data-field-id="<?= (int) $item['field_id'] ?>"
|
||||||
|
data-source-value="<?= htmlspecialchars($item['source_value'], ENT_QUOTES) ?>"
|
||||||
|
data-datadb-ids="<?= htmlspecialchars(json_encode($item['datadb_ids']), ENT_QUOTES) ?>">
|
||||||
|
<td class="text-muted ps-4">›</td>
|
||||||
|
<td class="source-value"><?= htmlspecialchars($item['source_value']) ?></td>
|
||||||
|
<td><?= count($item['datadb_ids'] ?? []) ?></td>
|
||||||
|
<td>
|
||||||
|
<select class="form-select binding-select">
|
||||||
|
<?php if ($suggestion): ?>
|
||||||
|
<option value="<?= (int) $suggestion['lims_value_id'] ?>" selected><?= htmlspecialchars($suggestion['lims_value']) ?></option>
|
||||||
|
<?php else: ?>
|
||||||
|
<option value="">Seleziona valore LIMS...</option>
|
||||||
|
<?php endif; ?>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary skip-binding-btn">Nessuna corrispondenza</button>
|
||||||
|
<div class="binding-status mt-1">
|
||||||
|
<?php if ($suggestion): ?>
|
||||||
|
<span class="badge bg-warning text-dark" title="Stesso valore gia' collegato in un altro template">
|
||||||
|
suggerito · <?= htmlspecialchars($suggestion['template_name'] ?: ('template ' . $suggestion['template_id'])) ?>
|
||||||
|
</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="text-muted">In attesa</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<?php foreach ($g['resolved'] as $r): $res = $r['data']; ?>
|
||||||
|
<tr class="binding-row"
|
||||||
|
data-kind="<?= htmlspecialchars($res['kind'] ?? 'custom', ENT_QUOTES) ?>"
|
||||||
|
data-fixed-key="<?= htmlspecialchars($res['fixed_field_key'] ?? '', ENT_QUOTES) ?>"
|
||||||
|
data-mapping-id="<?= (int) $res['mapping_id'] ?>"
|
||||||
|
data-field-id="<?= (int) $res['field_id'] ?>"
|
||||||
|
data-source-value="<?= htmlspecialchars($res['source_value'], ENT_QUOTES) ?>"
|
||||||
|
data-datadb-ids="<?= htmlspecialchars(json_encode($res['datadb_ids']), ENT_QUOTES) ?>">
|
||||||
|
<td class="text-muted ps-4">›</td>
|
||||||
|
<td class="source-value"><?= htmlspecialchars($res['source_value']) ?></td>
|
||||||
|
<td><?= count($res['datadb_ids'] ?? []) ?></td>
|
||||||
|
<td>
|
||||||
|
<select class="form-select binding-select">
|
||||||
|
<option value="<?= (int) $res['lims_value_id'] ?>" selected><?= htmlspecialchars($res['lims_value']) ?></option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary skip-binding-btn">Nessuna corrispondenza</button>
|
||||||
|
<div class="binding-status mt-1"><span class="badge <?= $res['badge_class'] ?>"><?= $res['badge'] ?></span></div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-end gap-2 mt-3">
|
||||||
|
<a href="resolve_bindings.php?done=1"
|
||||||
|
class="btn btn-outline-secondary">Salta per ora</a>
|
||||||
|
<button type="submit" class="btn btn-primary" id="confirmBindingsBtn" disabled>
|
||||||
|
Conferma e prosegui
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</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 TEMPLATE_ID = <?= $templateId ?>;
|
||||||
|
const IMPORT_REF = <?= json_encode($importRef) ?>;
|
||||||
|
const CONTINUE_URL = 'resolve_bindings.php?done=1';
|
||||||
|
|
||||||
|
const $form = $('#bindingForm');
|
||||||
|
const $btn = $('#confirmBindingsBtn');
|
||||||
|
const $error = $('#bindingError');
|
||||||
|
|
||||||
|
$('.binding-select').each(function() {
|
||||||
|
const $row = $(this).closest('.binding-row');
|
||||||
|
const isFixed = $row.data('kind') === 'fixed';
|
||||||
|
|
||||||
|
$(this).select2({
|
||||||
|
placeholder: 'Seleziona valore LIMS...',
|
||||||
|
width: '100%',
|
||||||
|
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: 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
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function refreshButton() {
|
||||||
|
const allReady = $('.binding-row').toArray().every(row => {
|
||||||
|
const $row = $(row);
|
||||||
|
return $row.hasClass('is-skipped') || $row.find('.binding-select').val();
|
||||||
|
});
|
||||||
|
$btn.prop('disabled', !allReady);
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.binding-select').on('change', refreshButton);
|
||||||
|
refreshButton();
|
||||||
|
|
||||||
|
$('.skip-binding-btn').on('click', function() {
|
||||||
|
const $row = $(this).closest('.binding-row');
|
||||||
|
const $select = $row.find('.binding-select');
|
||||||
|
const $status = $row.find('.binding-status');
|
||||||
|
const skipped = $row.toggleClass('is-skipped').hasClass('is-skipped');
|
||||||
|
|
||||||
|
$(this).toggleClass('btn-outline-secondary', !skipped).toggleClass('btn-secondary', skipped);
|
||||||
|
$select.val(null).trigger('change').prop('disabled', skipped);
|
||||||
|
$status.html(skipped ? 'Nessuna corrispondenza' : '<span class="text-muted">In attesa</span>');
|
||||||
|
refreshButton();
|
||||||
|
});
|
||||||
|
|
||||||
|
$form.on('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$error.hide();
|
||||||
|
$btn.prop('disabled', true).text('Salvataggio...');
|
||||||
|
|
||||||
|
const tasks = $('.binding-row').toArray().map(row => {
|
||||||
|
const $row = $(row);
|
||||||
|
const $status = $row.find('.binding-status');
|
||||||
|
const datadbIds = JSON.stringify($row.data('datadb-ids') || []);
|
||||||
|
const sourceValue = String($row.data('source-value'));
|
||||||
|
const mappingId = $row.data('mapping-id');
|
||||||
|
|
||||||
|
if ($row.hasClass('is-skipped')) {
|
||||||
|
return $.post('skip_binding.php', {
|
||||||
|
kind: $row.data('kind') || 'custom',
|
||||||
|
mapping_id: mappingId,
|
||||||
|
template_id: TEMPLATE_ID,
|
||||||
|
source_value: sourceValue,
|
||||||
|
datadb_ids: datadbIds
|
||||||
|
}).then(resp => {
|
||||||
|
if (resp && resp.success) {
|
||||||
|
$status.html('<span class="text-success">Azzerato</span>');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
throw new Error((resp && resp.error) || 'Errore azzeramento valore');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const $select = $row.find('.binding-select');
|
||||||
|
const selectedData = $select.select2('data')[0] || {};
|
||||||
|
|
||||||
|
return $.post('save_binding.php', {
|
||||||
|
kind: $row.data('kind') || 'custom',
|
||||||
|
mapping_id: mappingId,
|
||||||
|
field_id: $row.data('field-id'),
|
||||||
|
template_id: TEMPLATE_ID,
|
||||||
|
source_value: sourceValue,
|
||||||
|
lims_value_id: $select.val(),
|
||||||
|
lims_value: selectedData.text || '',
|
||||||
|
datadb_ids: datadbIds
|
||||||
|
}).then(resp => {
|
||||||
|
if (resp && resp.success) {
|
||||||
|
$status.html('<span class="text-success">Salvato</span>');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
throw new Error((resp && resp.error) || 'Errore salvataggio binding');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Promise.all(tasks)
|
||||||
|
.then(() => {
|
||||||
|
window.location.href = CONTINUE_URL;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
$error.text(err.message || 'Errore durante il salvataggio dei binding.').show();
|
||||||
|
$btn.prop('disabled', false).text('Conferma e prosegui');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
// Evita che eventuale output di headscript rompa il JSON
|
||||||
|
ob_start();
|
||||||
|
include('include/headscript.php');
|
||||||
|
ob_end_clean();
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$iddatadb = (int)($_POST['iddatadb'] ?? 0);
|
||||||
|
$newStatus = $_POST['status'] ?? 'i';
|
||||||
|
|
||||||
|
if ($iddatadb <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID record mancante']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Da questa azione consentiamo solo il ritorno a 'i'
|
||||||
|
if ($newStatus !== 'i') {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Stato non consentito']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
// Aggiorna solo se attualmente è in stato 'l' (To LIMS)
|
||||||
|
$stmt = $pdo->prepare("UPDATE datadb SET status = ? WHERE iddatadb = ? AND status = 'l'");
|
||||||
|
$stmt->execute([$newStatus, $iddatadb]);
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Record non trovato o già modificato']);
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,549 @@
|
|||||||
|
<?php
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$user->hasRole('Admin') && !$user->hasRole('SuperUser')) {
|
||||||
|
header('Location: import_dashboard.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Nome file del motore generico condiviso da tutte le routine engine.
|
||||||
|
* Deve corrispondere al file presente in routines/.
|
||||||
|
*/
|
||||||
|
$engineFilename = '_engine.php';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CSRF token.
|
||||||
|
*/
|
||||||
|
if (empty($_SESSION['routine_admin_csrf'])) {
|
||||||
|
$_SESSION['routine_admin_csrf'] = bin2hex(random_bytes(32));
|
||||||
|
}
|
||||||
|
$csrfToken = $_SESSION['routine_admin_csrf'];
|
||||||
|
|
||||||
|
function cleanInput($value)
|
||||||
|
{
|
||||||
|
return trim((string)($value ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
function redirectWithMessage($type, $message)
|
||||||
|
{
|
||||||
|
$_SESSION['routine_admin_flash'] = [
|
||||||
|
'type' => $type,
|
||||||
|
'message' => $message
|
||||||
|
];
|
||||||
|
header('Location: ' . $_SERVER['PHP_SELF']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Gestione azioni POST.
|
||||||
|
*/
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
try {
|
||||||
|
$postedToken = $_POST['csrf_token'] ?? '';
|
||||||
|
if (!hash_equals($csrfToken, $postedToken)) {
|
||||||
|
throw new Exception('Token di sicurezza non valido.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$action = cleanInput($_POST['action'] ?? '');
|
||||||
|
|
||||||
|
if ($action === 'create' || $action === 'update') {
|
||||||
|
$name = cleanInput($_POST['name'] ?? '');
|
||||||
|
$description = cleanInput($_POST['description'] ?? '');
|
||||||
|
$engine = (int)($_POST['engine'] ?? 0) === 1 ? 1 : 0;
|
||||||
|
$headerrow = (int)($_POST['headerrow'] ?? 1);
|
||||||
|
$filename = cleanInput($_POST['filename'] ?? '');
|
||||||
|
|
||||||
|
if ($name === '') {
|
||||||
|
throw new Exception('Il nome della routine è obbligatorio.');
|
||||||
|
}
|
||||||
|
if ($headerrow < 1) {
|
||||||
|
$headerrow = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Se è una routine a motore, il filename è forzato al file del motore.
|
||||||
|
* Se è legacy, serve un filename di file reale.
|
||||||
|
*/
|
||||||
|
if ($engine === 1) {
|
||||||
|
$filename = $GLOBALS['engineFilename'];
|
||||||
|
} else {
|
||||||
|
if ($filename === '') {
|
||||||
|
throw new Exception('Per una routine di tipo File indica il nome del file (es. fendi.php).');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'create') {
|
||||||
|
// instruction è NOT NULL nello schema: valorizziamo sempre.
|
||||||
|
$instruction = $engine === 1
|
||||||
|
? 'Routine gestita dal motore generico (routine_step).'
|
||||||
|
: ('Routine su file: ' . $filename);
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO routine (name, description, instruction, filename, engine, headerrow)
|
||||||
|
VALUES (:name, :descr, :instr, :fn, :engine, :hr)
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':name' => $name,
|
||||||
|
':descr' => ($description !== '' ? $description : null),
|
||||||
|
':instr' => $instruction,
|
||||||
|
':fn' => $filename,
|
||||||
|
':engine' => $engine,
|
||||||
|
':hr' => $headerrow,
|
||||||
|
]);
|
||||||
|
|
||||||
|
redirectWithMessage('success', 'Routine creata correttamente.');
|
||||||
|
} else {
|
||||||
|
$idroutine = (int)($_POST['idroutine'] ?? 0);
|
||||||
|
if ($idroutine <= 0) {
|
||||||
|
throw new Exception('ID routine non valido.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE routine
|
||||||
|
SET name = :name,
|
||||||
|
description = :descr,
|
||||||
|
filename = :fn,
|
||||||
|
engine = :engine,
|
||||||
|
headerrow = :hr
|
||||||
|
WHERE idroutine = :id
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':name' => $name,
|
||||||
|
':descr' => ($description !== '' ? $description : null),
|
||||||
|
':fn' => $filename,
|
||||||
|
':engine' => $engine,
|
||||||
|
':hr' => $headerrow,
|
||||||
|
':id' => $idroutine,
|
||||||
|
]);
|
||||||
|
|
||||||
|
redirectWithMessage('success', 'Routine aggiornata correttamente.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'delete') {
|
||||||
|
$idroutine = (int)($_POST['idroutine'] ?? 0);
|
||||||
|
if ($idroutine <= 0) {
|
||||||
|
throw new Exception('ID routine non valido.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rimuove prima gli eventuali step collegati, poi la routine.
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM routine_step WHERE idroutine = :id");
|
||||||
|
$stmt->execute([':id' => $idroutine]);
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM routine WHERE idroutine = :id");
|
||||||
|
$stmt->execute([':id' => $idroutine]);
|
||||||
|
|
||||||
|
redirectWithMessage('success', 'Routine eliminata correttamente.');
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception('Azione non valida.');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
redirectWithMessage('error', $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Caricamento routine + conteggio step.
|
||||||
|
*/
|
||||||
|
$stmt = $pdo->query("
|
||||||
|
SELECT
|
||||||
|
r.idroutine,
|
||||||
|
r.name,
|
||||||
|
r.description,
|
||||||
|
r.filename,
|
||||||
|
r.engine,
|
||||||
|
r.headerrow,
|
||||||
|
(SELECT COUNT(*) FROM routine_step s WHERE s.idroutine = r.idroutine) AS step_count
|
||||||
|
FROM routine r
|
||||||
|
ORDER BY r.name ASC
|
||||||
|
");
|
||||||
|
$routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$flash = $_SESSION['routine_admin_flash'] ?? null;
|
||||||
|
unset($_SESSION['routine_admin_flash']);
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||||
|
<?php include('cssinclude.php'); ?>
|
||||||
|
|
||||||
|
<title>Gestione Routine - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.small-muted {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* La tabella non deve sforare la pagina */
|
||||||
|
#routinesTable {
|
||||||
|
table-layout: fixed;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#routinesTable td,
|
||||||
|
#routinesTable th {
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
white-space: normal;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Colonna descrizione: va a capo invece di allargare la tabella */
|
||||||
|
#routinesTable td.desc-cell {
|
||||||
|
white-space: normal;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
max-width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Le azioni restano su una riga */
|
||||||
|
#routinesTable td.table-actions {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-engine {
|
||||||
|
background: #6f42c1;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-file {
|
||||||
|
background: #6c757d;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-actions {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filename-chip {
|
||||||
|
font-family: Consolas, Monaco, monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
background: #f1f5f9;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-count-pill {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 26px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 2px 10px;
|
||||||
|
background: #eef2ff;
|
||||||
|
color: #4338ca;
|
||||||
|
border: 1px solid #e0e7ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<?php include('include/navbar.php'); ?>
|
||||||
|
<?php include('include/topbar.php'); ?>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
<?php include('top_stat_widget.php'); ?>
|
||||||
|
|
||||||
|
<div class="card radius-10">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="d-flex align-items-center justify-content-between">
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-0">Gestione Routine</h6>
|
||||||
|
<small class="text-muted">Crea e configura le routine di importazione. Le routine di tipo Motore sono configurabili a step senza codice.</small>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-primary" id="btnAddRoutine">
|
||||||
|
<i class="bx bx-plus"></i> Nuova Routine
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="routinesTable" class="table table-striped table-bordered align-middle">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Nome</th>
|
||||||
|
<th>Descrizione</th>
|
||||||
|
<th style="width: 120px;">Tipo</th>
|
||||||
|
<th style="width: 160px;">File</th>
|
||||||
|
<th style="width: 90px;">Header row</th>
|
||||||
|
<th style="width: 80px;">Step</th>
|
||||||
|
<th style="width: 130px;">Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($routines as $row): ?>
|
||||||
|
<?php $isEngine = ((int)$row['engine'] === 1); ?>
|
||||||
|
<tr>
|
||||||
|
<td><?= htmlspecialchars($row['name'] ?? '', ENT_QUOTES, 'UTF-8'); ?></td>
|
||||||
|
<td class="small-muted desc-cell"><?= htmlspecialchars($row['description'] ?? '', ENT_QUOTES, 'UTF-8'); ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if ($isEngine): ?>
|
||||||
|
<span class="badge badge-engine">Motore</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge badge-file">File</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td><span class="filename-chip"><?= htmlspecialchars($row['filename'] ?? '', ENT_QUOTES, 'UTF-8'); ?></span></td>
|
||||||
|
<td class="text-center"><?= (int)$row['headerrow']; ?></td>
|
||||||
|
<td class="text-center">
|
||||||
|
<?php if ($isEngine): ?>
|
||||||
|
<span class="step-count-pill"><?= (int)$row['step_count']; ?></span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="small-muted">—</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td class="table-actions">
|
||||||
|
<?php if ($isEngine): ?>
|
||||||
|
<a class="btn btn-sm btn-outline-success"
|
||||||
|
href="routine_steps.php?idroutine=<?= (int)$row['idroutine']; ?>"
|
||||||
|
title="Gestisci step" data-bs-toggle="tooltip">
|
||||||
|
<i class="bx bx-list-ul"></i>
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-sm btn-outline-primary btnEditRoutine"
|
||||||
|
data-id="<?= (int)$row['idroutine']; ?>"
|
||||||
|
data-name="<?= htmlspecialchars($row['name'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
|
data-description="<?= htmlspecialchars($row['description'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
|
data-engine="<?= (int)$row['engine']; ?>"
|
||||||
|
data-filename="<?= htmlspecialchars($row['filename'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
|
data-headerrow="<?= (int)$row['headerrow']; ?>"
|
||||||
|
title="Modifica" data-bs-toggle="tooltip">
|
||||||
|
<i class="bx bx-edit"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-sm btn-outline-danger btnDeleteRoutine"
|
||||||
|
data-id="<?= (int)$row['idroutine']; ?>"
|
||||||
|
data-name="<?= htmlspecialchars($row['name'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
|
title="Elimina" data-bs-toggle="tooltip">
|
||||||
|
<i class="bx bx-trash"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="overlay toggle-icon"></div>
|
||||||
|
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Routine -->
|
||||||
|
<div class="modal fade" id="routineModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||||
|
<form method="POST" class="modal-content" id="routineForm">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8'); ?>">
|
||||||
|
<input type="hidden" name="action" id="formAction" value="create">
|
||||||
|
<input type="hidden" name="idroutine" id="idroutine" value="">
|
||||||
|
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="routineModalTitle">Nuova Routine</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<label for="name" class="form-label">Nome *</label>
|
||||||
|
<input type="text" class="form-control" id="name" name="name" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-12">
|
||||||
|
<label for="description" class="form-label">Descrizione</label>
|
||||||
|
<input type="text" class="form-control" id="description" name="description" maxlength="150">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="engine" class="form-label">Tipo routine *</label>
|
||||||
|
<select class="form-select" id="engine" name="engine" required>
|
||||||
|
<option value="1">Motore (step configurabili)</option>
|
||||||
|
<option value="0">File (legacy)</option>
|
||||||
|
</select>
|
||||||
|
<div class="small-muted mt-1">
|
||||||
|
Motore: configurabile a step da questa interfaccia. File: logica in un file PHP dedicato.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="headerrow" class="form-label">Riga intestazione (header row) *</label>
|
||||||
|
<input type="number" class="form-control" id="headerrow" name="headerrow" min="1" value="1" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-12" id="filenameWrapper">
|
||||||
|
<label for="filename" class="form-label">Nome file</label>
|
||||||
|
<input type="text" class="form-control" id="filename" name="filename" placeholder="es. fendi.php">
|
||||||
|
<div class="small-muted mt-1" id="filenameHelp">
|
||||||
|
Per le routine di tipo File indica il nome del file PHP presente nella cartella routines/.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Annulla</button>
|
||||||
|
<button type="submit" class="btn btn-primary" id="saveRoutineButton">Salva Routine</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Delete Form -->
|
||||||
|
<form method="POST" id="deleteRoutineForm" style="display:none;">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8'); ?>">
|
||||||
|
<input type="hidden" name="action" value="delete">
|
||||||
|
<input type="hidden" name="idroutine" id="deleteIdRoutine" value="">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const routineModal = new bootstrap.Modal(document.getElementById('routineModal'));
|
||||||
|
|
||||||
|
const formAction = document.getElementById('formAction');
|
||||||
|
const idroutine = document.getElementById('idroutine');
|
||||||
|
const routineModalTitle = document.getElementById('routineModalTitle');
|
||||||
|
const saveRoutineButton = document.getElementById('saveRoutineButton');
|
||||||
|
|
||||||
|
const name = document.getElementById('name');
|
||||||
|
const description = document.getElementById('description');
|
||||||
|
const engine = document.getElementById('engine');
|
||||||
|
const headerrow = document.getElementById('headerrow');
|
||||||
|
const filename = document.getElementById('filename');
|
||||||
|
const filenameWrapper = document.getElementById('filenameWrapper');
|
||||||
|
const filenameHelp = document.getElementById('filenameHelp');
|
||||||
|
|
||||||
|
const engineFilename = "<?= htmlspecialchars($engineFilename, ENT_QUOTES, 'UTF-8'); ?>";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Mostra/nasconde il campo filename in base al tipo di routine.
|
||||||
|
* Motore -> filename forzato al file del motore e campo di sola lettura.
|
||||||
|
*/
|
||||||
|
function applyEngineUi() {
|
||||||
|
if (engine.value === '1') {
|
||||||
|
filename.value = engineFilename;
|
||||||
|
filename.readOnly = true;
|
||||||
|
filenameHelp.textContent = 'Le routine di tipo Motore usano automaticamente il file ' + engineFilename + '.';
|
||||||
|
} else {
|
||||||
|
if (filename.value === engineFilename) {
|
||||||
|
filename.value = '';
|
||||||
|
}
|
||||||
|
filename.readOnly = false;
|
||||||
|
filenameHelp.textContent = 'Per le routine di tipo File indica il nome del file PHP presente nella cartella routines/.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.addEventListener('change', applyEngineUi);
|
||||||
|
|
||||||
|
function resetModalForCreate() {
|
||||||
|
formAction.value = 'create';
|
||||||
|
idroutine.value = '';
|
||||||
|
routineModalTitle.textContent = 'Nuova Routine';
|
||||||
|
saveRoutineButton.textContent = 'Crea Routine';
|
||||||
|
|
||||||
|
name.value = '';
|
||||||
|
description.value = '';
|
||||||
|
engine.value = '1';
|
||||||
|
headerrow.value = '1';
|
||||||
|
filename.value = '';
|
||||||
|
applyEngineUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setModalForEdit(button) {
|
||||||
|
formAction.value = 'update';
|
||||||
|
idroutine.value = button.dataset.id || '';
|
||||||
|
routineModalTitle.textContent = 'Modifica Routine';
|
||||||
|
saveRoutineButton.textContent = 'Aggiorna Routine';
|
||||||
|
|
||||||
|
name.value = button.dataset.name || '';
|
||||||
|
description.value = button.dataset.description || '';
|
||||||
|
engine.value = button.dataset.engine === '1' ? '1' : '0';
|
||||||
|
headerrow.value = button.dataset.headerrow || '1';
|
||||||
|
filename.value = button.dataset.filename || '';
|
||||||
|
applyEngineUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('btnAddRoutine').addEventListener('click', function() {
|
||||||
|
resetModalForCreate();
|
||||||
|
routineModal.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.btnEditRoutine').forEach(function(button) {
|
||||||
|
button.addEventListener('click', function() {
|
||||||
|
setModalForEdit(button);
|
||||||
|
routineModal.show();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.btnDeleteRoutine').forEach(function(button) {
|
||||||
|
button.addEventListener('click', function() {
|
||||||
|
const id = button.dataset.id;
|
||||||
|
const nm = button.dataset.name || 'questa routine';
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Eliminare la routine?',
|
||||||
|
text: 'Vuoi eliminare "' + nm + '"? Verranno rimossi anche gli step collegati. Operazione irreversibile.',
|
||||||
|
icon: 'warning',
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonText: 'Sì, elimina',
|
||||||
|
cancelButtonText: 'Annulla',
|
||||||
|
confirmButtonColor: '#d33'
|
||||||
|
}).then(function(result) {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
document.getElementById('deleteIdRoutine').value = id;
|
||||||
|
document.getElementById('deleteRoutineForm').submit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof $ !== 'undefined' && $.fn.DataTable) {
|
||||||
|
$('#routinesTable').DataTable({
|
||||||
|
pageLength: 25,
|
||||||
|
order: [
|
||||||
|
[0, 'asc']
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
<?php if ($flash): ?>
|
||||||
|
Swal.fire({
|
||||||
|
icon: '<?= htmlspecialchars($flash['type'], ENT_QUOTES, 'UTF-8'); ?>',
|
||||||
|
title: '<?= $flash['type'] === 'success' ? 'Fatto' : ($flash['type'] === 'warning' ? 'Attenzione' : 'Errore'); ?>',
|
||||||
|
text: '<?= htmlspecialchars($flash['message'], ENT_QUOTES, 'UTF-8'); ?>'
|
||||||
|
});
|
||||||
|
<?php endif; ?>
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* routine_preview.php
|
||||||
|
* -------------------
|
||||||
|
* Anteprima di una routine a motore su un XLS caricato temporaneamente.
|
||||||
|
*
|
||||||
|
* - Riceve via multipart/form-data: `file` (XLS/XLSX) e `idroutine`.
|
||||||
|
* - NON salva nulla: il file viene letto dal tmp di PHP ed elaborato in RAM.
|
||||||
|
* - Legge la PRIMA riga dati subito dopo l'header row (routine.headerrow).
|
||||||
|
* - Impacchetta la riga nel formato excelData atteso dal motore:
|
||||||
|
* [ ['excelrow' => N, 'data' => [0=>.., 1=>.., ...]] ]
|
||||||
|
* - Include _engine.php e applica applyRoutine().
|
||||||
|
* - Risponde JSON con la riga elaborata + intestazioni + lettere colonna.
|
||||||
|
*/
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
session_start();
|
||||||
|
require_once '../../vendor/autoload.php';
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||||
|
|
||||||
|
Dotenv\Dotenv::createImmutable(dirname(__DIR__, 2))->safeLoad();
|
||||||
|
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$response = [
|
||||||
|
'error' => '',
|
||||||
|
'headers' => [], // intestazioni prese dalla header row
|
||||||
|
'letters' => [], // lettere colonna (A, B, C, ...) allineate ai valori
|
||||||
|
'row' => [], // valori della riga DOPO l'applicazione del motore
|
||||||
|
'excelrow' => 0, // numero riga excel elaborata
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indice zero-based -> lettera colonna Excel (0 -> A, 25 -> Z, 26 -> AA...).
|
||||||
|
*/
|
||||||
|
function previewIndexToCol($index)
|
||||||
|
{
|
||||||
|
$index = (int)$index;
|
||||||
|
if ($index < 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
$letters = '';
|
||||||
|
$index += 1;
|
||||||
|
while ($index > 0) {
|
||||||
|
$rem = ($index - 1) % 26;
|
||||||
|
$letters = chr(ord('A') + $rem) . $letters;
|
||||||
|
$index = intdiv($index - 1, 26);
|
||||||
|
}
|
||||||
|
return $letters;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
throw new Exception('Richiesta non valida.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$idroutine = (int)($_POST['idroutine'] ?? 0);
|
||||||
|
if ($idroutine <= 0) {
|
||||||
|
throw new Exception('ID routine mancante.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
empty($_FILES['file']) ||
|
||||||
|
!isset($_FILES['file']['tmp_name']) ||
|
||||||
|
$_FILES['file']['error'] !== UPLOAD_ERR_OK
|
||||||
|
) {
|
||||||
|
throw new Exception('Nessun file caricato o upload non riuscito.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Recupera la routine e verifica che sia a motore ---
|
||||||
|
require_once __DIR__ . '/class/db-functions.php';
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT idroutine, name, engine, headerrow FROM routine WHERE idroutine = :id LIMIT 1");
|
||||||
|
$stmt->execute([':id' => $idroutine]);
|
||||||
|
$routine = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$routine) {
|
||||||
|
throw new Exception('Routine non trovata.');
|
||||||
|
}
|
||||||
|
if ((int)$routine['engine'] !== 1) {
|
||||||
|
throw new Exception('Questa routine non è di tipo Motore: anteprima non disponibile.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$headerRow = max(1, (int)$routine['headerrow']); // riga 1-based dell'intestazione
|
||||||
|
|
||||||
|
// --- Legge l'XLS dal file temporaneo (nessun salvataggio) ---
|
||||||
|
$tmpPath = $_FILES['file']['tmp_name'];
|
||||||
|
|
||||||
|
$reader = IOFactory::createReaderForFile($tmpPath);
|
||||||
|
$reader->setReadDataOnly(true);
|
||||||
|
$spreadsheet = $reader->load($tmpPath);
|
||||||
|
$sheet = $spreadsheet->getActiveSheet();
|
||||||
|
|
||||||
|
$highestColIndex = Coordinate::columnIndexFromString($sheet->getHighestDataColumn()); // 1-based
|
||||||
|
$highestRow = $sheet->getHighestDataRow();
|
||||||
|
|
||||||
|
// --- Intestazioni dalla header row ---
|
||||||
|
$headers = [];
|
||||||
|
$letters = [];
|
||||||
|
for ($c = 1; $c <= $highestColIndex; $c++) {
|
||||||
|
$val = $sheet->getCell([$c, $headerRow])->getValue();
|
||||||
|
$headers[] = ($val === null ? '' : (string)$val);
|
||||||
|
$letters[] = previewIndexToCol($c - 1); // zero-based -> lettera
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Trova la prima riga dati valorizzata dopo l'header ---
|
||||||
|
$dataRowNumber = null;
|
||||||
|
$rowValues = null;
|
||||||
|
|
||||||
|
for ($r = $headerRow + 1; $r <= $highestRow; $r++) {
|
||||||
|
$vals = [];
|
||||||
|
$hasValue = false;
|
||||||
|
for ($c = 1; $c <= $highestColIndex; $c++) {
|
||||||
|
$v = $sheet->getCell([$c, $r])->getValue();
|
||||||
|
$v = ($v === null ? '' : (string)$v);
|
||||||
|
if ($v !== '') {
|
||||||
|
$hasValue = true;
|
||||||
|
}
|
||||||
|
$vals[$c - 1] = $v; // indice zero-based, allineato alle colonne
|
||||||
|
}
|
||||||
|
if ($hasValue) {
|
||||||
|
$dataRowNumber = $r;
|
||||||
|
$rowValues = $vals;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dataRowNumber === null) {
|
||||||
|
throw new Exception('Nessuna riga dati valorizzata trovata dopo la riga di intestazione (' . $headerRow . ').');
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Impacchetta nel formato excelData e applica il motore ---
|
||||||
|
$excelData = [
|
||||||
|
[
|
||||||
|
'excelrow' => $dataRowNumber,
|
||||||
|
'data' => $rowValues,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Il motore legge gli step dal DB in base a idroutine.
|
||||||
|
$routineData = [
|
||||||
|
'idroutine' => $idroutine,
|
||||||
|
'name' => $routine['name'],
|
||||||
|
'headerrow' => $headerRow,
|
||||||
|
'xls_headers' => $headers,
|
||||||
|
];
|
||||||
|
|
||||||
|
require_once __DIR__ . '/routines/_engine.php';
|
||||||
|
applyRoutine($excelData, $routineData); // modifica in place
|
||||||
|
|
||||||
|
$processed = $excelData[0]['data'] ?? [];
|
||||||
|
|
||||||
|
// Riallinea l'output all'ampiezza delle intestazioni (il merge può creare indici nuovi).
|
||||||
|
$maxIndex = max(
|
||||||
|
count($headers) - 1,
|
||||||
|
empty($processed) ? 0 : max(array_keys($processed))
|
||||||
|
);
|
||||||
|
|
||||||
|
$outRow = [];
|
||||||
|
$outLetters = [];
|
||||||
|
$outHeaders = [];
|
||||||
|
for ($i = 0; $i <= $maxIndex; $i++) {
|
||||||
|
$outRow[] = array_key_exists($i, $processed) ? (string)$processed[$i] : '';
|
||||||
|
$outLetters[] = previewIndexToCol($i);
|
||||||
|
$outHeaders[] = array_key_exists($i, $headers) ? $headers[$i] : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$response['headers'] = $outHeaders;
|
||||||
|
$response['letters'] = $outLetters;
|
||||||
|
$response['row'] = $outRow;
|
||||||
|
$response['excelrow'] = $dataRowNumber;
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$response['error'] = 'Errore anteprima: ' . $e->getMessage();
|
||||||
|
error_log('routine_preview.php: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
ob_end_clean();
|
||||||
|
echo json_encode($response, JSON_UNESCAPED_UNICODE);
|
||||||
|
exit;
|
||||||
@@ -0,0 +1,976 @@
|
|||||||
|
<?php
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$user->hasRole('Admin') && !$user->hasRole('SuperUser')) {
|
||||||
|
header('Location: import_dashboard.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CSRF token.
|
||||||
|
*/
|
||||||
|
if (empty($_SESSION['routine_steps_csrf'])) {
|
||||||
|
$_SESSION['routine_steps_csrf'] = bin2hex(random_bytes(32));
|
||||||
|
}
|
||||||
|
$csrfToken = $_SESSION['routine_steps_csrf'];
|
||||||
|
|
||||||
|
function cleanInput($value)
|
||||||
|
{
|
||||||
|
return trim((string)($value ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Determina l'id routine dalla querystring (GET) o dal POST.
|
||||||
|
*/
|
||||||
|
$idroutine = 0;
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$idroutine = (int)($_POST['idroutine'] ?? 0);
|
||||||
|
} else {
|
||||||
|
$idroutine = (int)($_GET['idroutine'] ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function redirectToSteps($idroutine, $type, $message)
|
||||||
|
{
|
||||||
|
$_SESSION['routine_steps_flash'] = [
|
||||||
|
'type' => $type,
|
||||||
|
'message' => $message
|
||||||
|
];
|
||||||
|
header('Location: routine_steps.php?idroutine=' . (int)$idroutine);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Converte lettera colonna Excel in indice zero-based. Accetta anche numeri.
|
||||||
|
* A=0, B=1, ... Z=25, AA=26, ...
|
||||||
|
*/
|
||||||
|
function colToIndex($ref)
|
||||||
|
{
|
||||||
|
$ref = strtoupper(trim((string)$ref));
|
||||||
|
if ($ref === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (ctype_digit($ref)) {
|
||||||
|
return (int)$ref;
|
||||||
|
}
|
||||||
|
$index = 0;
|
||||||
|
$len = strlen($ref);
|
||||||
|
for ($i = 0; $i < $len; $i++) {
|
||||||
|
$c = $ref[$i];
|
||||||
|
if ($c < 'A' || $c > 'Z') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$index = $index * 26 + (ord($c) - ord('A') + 1);
|
||||||
|
}
|
||||||
|
return $index - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Converte indice zero-based in lettera Excel (per mostrare gli step salvati).
|
||||||
|
*/
|
||||||
|
function indexToCol($index)
|
||||||
|
{
|
||||||
|
$index = (int)$index;
|
||||||
|
if ($index < 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
$letters = '';
|
||||||
|
$index += 1;
|
||||||
|
while ($index > 0) {
|
||||||
|
$rem = ($index - 1) % 26;
|
||||||
|
$letters = chr(ord('A') + $rem) . $letters;
|
||||||
|
$index = intdiv($index - 1, 26);
|
||||||
|
}
|
||||||
|
return $letters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Normalizza una stringa di colonne separate da virgola ("D, F, H") in
|
||||||
|
* un array di indici zero-based, validando ogni riferimento.
|
||||||
|
*/
|
||||||
|
function parseColumnList($raw)
|
||||||
|
{
|
||||||
|
$raw = trim((string)$raw);
|
||||||
|
if ($raw === '') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
$parts = preg_split('/[\s,;]+/', $raw);
|
||||||
|
$out = [];
|
||||||
|
foreach ($parts as $p) {
|
||||||
|
if ($p === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$idx = colToIndex($p);
|
||||||
|
if ($idx === null) {
|
||||||
|
throw new Exception('Riferimento colonna non valido: "' . $p . '". Usa lettere come D, F, H.');
|
||||||
|
}
|
||||||
|
$out[] = $idx;
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Verifica esistenza routine ed engine = 1.
|
||||||
|
*/
|
||||||
|
$routine = null;
|
||||||
|
if ($idroutine > 0) {
|
||||||
|
$stmt = $pdo->prepare("SELECT idroutine, name, description, engine, headerrow FROM routine WHERE idroutine = :id LIMIT 1");
|
||||||
|
$stmt->execute([':id' => $idroutine]);
|
||||||
|
$routine = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$routine) {
|
||||||
|
$_SESSION['routine_admin_flash'] = ['type' => 'error', 'message' => 'Routine non trovata.'];
|
||||||
|
header('Location: routine_admin.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((int)$routine['engine'] !== 1) {
|
||||||
|
$_SESSION['routine_admin_flash'] = ['type' => 'error', 'message' => 'Questa routine è di tipo File: gli step non sono configurabili da qui.'];
|
||||||
|
header('Location: routine_admin.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Vocabolario operazioni disponibili (deve combaciare con gli handler di _engine.php).
|
||||||
|
*/
|
||||||
|
$operations = ['merge', 'copy', 'clear', 'trim', 'replace', 'uppercase', 'lowercase'];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Gestione azioni POST.
|
||||||
|
*/
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
try {
|
||||||
|
$postedToken = $_POST['csrf_token'] ?? '';
|
||||||
|
if (!hash_equals($csrfToken, $postedToken)) {
|
||||||
|
throw new Exception('Token di sicurezza non valido.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$action = cleanInput($_POST['action'] ?? '');
|
||||||
|
|
||||||
|
if ($action === 'create' || $action === 'update') {
|
||||||
|
$operation = strtolower(cleanInput($_POST['operation'] ?? ''));
|
||||||
|
$active = (int)($_POST['active'] ?? 1) === 1 ? 1 : 0;
|
||||||
|
|
||||||
|
if (!in_array($operation, $operations, true)) {
|
||||||
|
throw new Exception('Operazione non valida.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Costruzione params in base all'operazione.
|
||||||
|
* Le colonne arrivano come lettere e vengono convertite in indici.
|
||||||
|
*/
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
if ($operation === 'merge') {
|
||||||
|
$sources = parseColumnList($_POST['source_columns'] ?? '');
|
||||||
|
$target = colToIndex($_POST['target_column'] ?? '');
|
||||||
|
if (empty($sources)) {
|
||||||
|
throw new Exception('Indica almeno una colonna sorgente (es. D, F, H).');
|
||||||
|
}
|
||||||
|
if ($target === null) {
|
||||||
|
throw new Exception('Indica la colonna di destinazione (es. J).');
|
||||||
|
}
|
||||||
|
$params = [
|
||||||
|
'source_columns' => $sources,
|
||||||
|
'target_column' => $target,
|
||||||
|
'separator' => (string)($_POST['separator'] ?? ' '),
|
||||||
|
'skip_empty' => isset($_POST['skip_empty']) ? true : false,
|
||||||
|
];
|
||||||
|
} elseif ($operation === 'copy') {
|
||||||
|
$src = colToIndex($_POST['source_column'] ?? '');
|
||||||
|
$dst = colToIndex($_POST['target_column'] ?? '');
|
||||||
|
if ($src === null || $dst === null) {
|
||||||
|
throw new Exception('Indica colonna sorgente e destinazione (es. D e J).');
|
||||||
|
}
|
||||||
|
$params = ['source_column' => $src, 'target_column' => $dst];
|
||||||
|
} elseif ($operation === 'clear' || $operation === 'trim' || $operation === 'uppercase' || $operation === 'lowercase') {
|
||||||
|
$cols = parseColumnList($_POST['columns'] ?? '');
|
||||||
|
if (empty($cols)) {
|
||||||
|
throw new Exception('Indica almeno una colonna (es. D, F).');
|
||||||
|
}
|
||||||
|
$params = ['columns' => $cols];
|
||||||
|
if ($operation === 'trim') {
|
||||||
|
$chars = (string)($_POST['chars'] ?? '');
|
||||||
|
if ($chars !== '') {
|
||||||
|
$params['chars'] = $chars;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif ($operation === 'replace') {
|
||||||
|
$cols = parseColumnList($_POST['columns'] ?? '');
|
||||||
|
if (empty($cols)) {
|
||||||
|
throw new Exception('Indica almeno una colonna (es. D, F).');
|
||||||
|
}
|
||||||
|
$params = [
|
||||||
|
'columns' => $cols,
|
||||||
|
'search' => (string)($_POST['search'] ?? ''),
|
||||||
|
'replace' => (string)($_POST['replace'] ?? ''),
|
||||||
|
'regex' => isset($_POST['regex']) ? true : false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$paramsJson = json_encode($params, JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
|
if ($action === 'create') {
|
||||||
|
// step_order = max + 1
|
||||||
|
$stmt = $pdo->prepare("SELECT COALESCE(MAX(step_order), 0) + 1 AS next_order FROM routine_step WHERE idroutine = :id");
|
||||||
|
$stmt->execute([':id' => $idroutine]);
|
||||||
|
$nextOrder = (int)$stmt->fetchColumn();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO routine_step (idroutine, step_order, operation, params, active)
|
||||||
|
VALUES (:id, :ord, :op, :params, :active)
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':id' => $idroutine,
|
||||||
|
':ord' => $nextOrder,
|
||||||
|
':op' => $operation,
|
||||||
|
':params' => $paramsJson,
|
||||||
|
':active' => $active,
|
||||||
|
]);
|
||||||
|
|
||||||
|
redirectToSteps($idroutine, 'success', 'Step aggiunto correttamente.');
|
||||||
|
} else {
|
||||||
|
$idstep = (int)($_POST['idstep'] ?? 0);
|
||||||
|
if ($idstep <= 0) {
|
||||||
|
throw new Exception('ID step non valido.');
|
||||||
|
}
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE routine_step
|
||||||
|
SET operation = :op, params = :params, active = :active
|
||||||
|
WHERE idstep = :idstep AND idroutine = :id
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':op' => $operation,
|
||||||
|
':params' => $paramsJson,
|
||||||
|
':active' => $active,
|
||||||
|
':idstep' => $idstep,
|
||||||
|
':id' => $idroutine,
|
||||||
|
]);
|
||||||
|
|
||||||
|
redirectToSteps($idroutine, 'success', 'Step aggiornato correttamente.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'delete') {
|
||||||
|
$idstep = (int)($_POST['idstep'] ?? 0);
|
||||||
|
if ($idstep <= 0) {
|
||||||
|
throw new Exception('ID step non valido.');
|
||||||
|
}
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM routine_step WHERE idstep = :idstep AND idroutine = :id");
|
||||||
|
$stmt->execute([':idstep' => $idstep, ':id' => $idroutine]);
|
||||||
|
redirectToSteps($idroutine, 'success', 'Step eliminato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'move') {
|
||||||
|
$idstep = (int)($_POST['idstep'] ?? 0);
|
||||||
|
$dir = cleanInput($_POST['direction'] ?? '');
|
||||||
|
if ($idstep <= 0 || !in_array($dir, ['up', 'down'], true)) {
|
||||||
|
throw new Exception('Parametri di spostamento non validi.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carica step correnti ordinati.
|
||||||
|
$stmt = $pdo->prepare("SELECT idstep, step_order FROM routine_step WHERE idroutine = :id ORDER BY step_order ASC, idstep ASC");
|
||||||
|
$stmt->execute([':id' => $idroutine]);
|
||||||
|
$all = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Trova posizione corrente.
|
||||||
|
$pos = null;
|
||||||
|
foreach ($all as $i => $s) {
|
||||||
|
if ((int)$s['idstep'] === $idstep) {
|
||||||
|
$pos = $i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($pos !== null) {
|
||||||
|
$swapWith = $dir === 'up' ? $pos - 1 : $pos + 1;
|
||||||
|
if ($swapWith >= 0 && $swapWith < count($all)) {
|
||||||
|
$a = $all[$pos];
|
||||||
|
$b = $all[$swapWith];
|
||||||
|
// Scambia gli step_order dei due record.
|
||||||
|
$upd = $pdo->prepare("UPDATE routine_step SET step_order = :ord WHERE idstep = :idstep AND idroutine = :id");
|
||||||
|
$upd->execute([':ord' => (int)$b['step_order'], ':idstep' => (int)$a['idstep'], ':id' => $idroutine]);
|
||||||
|
$upd->execute([':ord' => (int)$a['step_order'], ':idstep' => (int)$b['idstep'], ':id' => $idroutine]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
redirectToSteps($idroutine, 'success', 'Ordine aggiornato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception('Azione non valida.');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
redirectToSteps($idroutine, 'error', $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Caricamento step della routine.
|
||||||
|
*/
|
||||||
|
$stmt = $pdo->prepare("SELECT idstep, step_order, operation, params, active FROM routine_step WHERE idroutine = :id ORDER BY step_order ASC, idstep ASC");
|
||||||
|
$stmt->execute([':id' => $idroutine]);
|
||||||
|
$steps = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Costruisce una descrizione leggibile dei params per la tabella.
|
||||||
|
*/
|
||||||
|
function describeStep($operation, array $p)
|
||||||
|
{
|
||||||
|
$colsToLetters = function ($arr) {
|
||||||
|
return implode(', ', array_map('indexToCol', $arr));
|
||||||
|
};
|
||||||
|
switch ($operation) {
|
||||||
|
case 'merge':
|
||||||
|
return 'Unisce ' . $colsToLetters($p['source_columns'] ?? []) .
|
||||||
|
' → ' . indexToCol($p['target_column'] ?? 0) .
|
||||||
|
' (sep: "' . ($p['separator'] ?? ' ') . '")';
|
||||||
|
case 'copy':
|
||||||
|
return 'Copia ' . indexToCol($p['source_column'] ?? 0) . ' → ' . indexToCol($p['target_column'] ?? 0);
|
||||||
|
case 'clear':
|
||||||
|
return 'Svuota ' . $colsToLetters($p['columns'] ?? []);
|
||||||
|
case 'trim':
|
||||||
|
return 'Trim ' . $colsToLetters($p['columns'] ?? []) . (isset($p['chars']) ? ' (chars: "' . $p['chars'] . '")' : '');
|
||||||
|
case 'uppercase':
|
||||||
|
return 'Maiuscolo ' . $colsToLetters($p['columns'] ?? []);
|
||||||
|
case 'lowercase':
|
||||||
|
return 'Minuscolo ' . $colsToLetters($p['columns'] ?? []);
|
||||||
|
case 'replace':
|
||||||
|
return 'Sostituisci in ' . $colsToLetters($p['columns'] ?? []) .
|
||||||
|
': "' . ($p['search'] ?? '') . '" → "' . ($p['replace'] ?? '') . '"' .
|
||||||
|
(!empty($p['regex']) ? ' (regex)' : '');
|
||||||
|
default:
|
||||||
|
return $operation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prepara i dati step per il JS (params già in lettere per il form di modifica).
|
||||||
|
*/
|
||||||
|
$stepsForJs = [];
|
||||||
|
foreach ($steps as $s) {
|
||||||
|
$p = json_decode($s['params'] ?? '[]', true);
|
||||||
|
if (!is_array($p)) {
|
||||||
|
$p = [];
|
||||||
|
}
|
||||||
|
// Converte indici -> lettere per il form.
|
||||||
|
$letters = function ($arr) {
|
||||||
|
return implode(', ', array_map('indexToCol', $arr));
|
||||||
|
};
|
||||||
|
$formData = ['operation' => $s['operation'], 'active' => (int)$s['active']];
|
||||||
|
if ($s['operation'] === 'merge') {
|
||||||
|
$formData['source_columns'] = $letters($p['source_columns'] ?? []);
|
||||||
|
$formData['target_column'] = indexToCol($p['target_column'] ?? 0);
|
||||||
|
$formData['separator'] = $p['separator'] ?? ' ';
|
||||||
|
$formData['skip_empty'] = !empty($p['skip_empty']);
|
||||||
|
} elseif ($s['operation'] === 'copy') {
|
||||||
|
$formData['source_column'] = indexToCol($p['source_column'] ?? 0);
|
||||||
|
$formData['target_column'] = indexToCol($p['target_column'] ?? 0);
|
||||||
|
} elseif (in_array($s['operation'], ['clear', 'trim', 'uppercase', 'lowercase'], true)) {
|
||||||
|
$formData['columns'] = $letters($p['columns'] ?? []);
|
||||||
|
if ($s['operation'] === 'trim') {
|
||||||
|
$formData['chars'] = $p['chars'] ?? '';
|
||||||
|
}
|
||||||
|
} elseif ($s['operation'] === 'replace') {
|
||||||
|
$formData['columns'] = $letters($p['columns'] ?? []);
|
||||||
|
$formData['search'] = $p['search'] ?? '';
|
||||||
|
$formData['replace'] = $p['replace'] ?? '';
|
||||||
|
$formData['regex'] = !empty($p['regex']);
|
||||||
|
}
|
||||||
|
$stepsForJs[(int)$s['idstep']] = $formData;
|
||||||
|
}
|
||||||
|
|
||||||
|
$flash = $_SESSION['routine_steps_flash'] ?? null;
|
||||||
|
unset($_SESSION['routine_steps_flash']);
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||||
|
<?php include('cssinclude.php'); ?>
|
||||||
|
|
||||||
|
<title>Step Routine - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.small-muted {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-actions {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.op-badge {
|
||||||
|
font-family: Consolas, Monaco, monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
background: #eef2ff;
|
||||||
|
color: #4338ca;
|
||||||
|
border: 1px solid #e0e7ff;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-inactive {
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-hint {
|
||||||
|
background: #f0fdf4;
|
||||||
|
border: 1px solid #bbf7d0;
|
||||||
|
color: #166534;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-btns .btn {
|
||||||
|
padding: 2px 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<?php include('include/navbar.php'); ?>
|
||||||
|
<?php include('include/topbar.php'); ?>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
<?php include('top_stat_widget.php'); ?>
|
||||||
|
|
||||||
|
<div class="card radius-10">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="d-flex align-items-center justify-content-between">
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-0">Step routine: <?= htmlspecialchars($routine['name'], ENT_QUOTES, 'UTF-8'); ?></h6>
|
||||||
|
<small class="text-muted">
|
||||||
|
Configura le operazioni applicate all'XLS. Vengono eseguite dall'alto verso il basso.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="routine_admin.php" class="btn btn-light">
|
||||||
|
<i class="bx bx-arrow-back"></i> Torna alle routine
|
||||||
|
</a>
|
||||||
|
<button type="button" class="btn btn-primary" id="btnAddStep">
|
||||||
|
<i class="bx bx-plus"></i> Aggiungi step
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="col-hint mb-3">
|
||||||
|
Le colonne si indicano con le <strong>lettere dell'Excel</strong> (A, B, C, …). Per più colonne separale con la virgola: <strong>D, F, H</strong>.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered align-middle">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 70px;">Ordine</th>
|
||||||
|
<th style="width: 120px;">Operazione</th>
|
||||||
|
<th>Dettaglio</th>
|
||||||
|
<th style="width: 90px;">Stato</th>
|
||||||
|
<th style="width: 240px;">Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($steps)): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" class="text-center small-muted py-4">
|
||||||
|
Nessuno step configurato. Usa "Aggiungi step" per iniziare.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($steps as $i => $s): ?>
|
||||||
|
<?php
|
||||||
|
$p = json_decode($s['params'] ?? '[]', true);
|
||||||
|
if (!is_array($p)) {
|
||||||
|
$p = [];
|
||||||
|
}
|
||||||
|
$isFirst = ($i === 0);
|
||||||
|
$isLast = ($i === count($steps) - 1);
|
||||||
|
$rowClass = ((int)$s['active'] === 1) ? '' : 'step-inactive';
|
||||||
|
?>
|
||||||
|
<tr class="<?= $rowClass; ?>">
|
||||||
|
<td class="text-center">
|
||||||
|
<div class="order-btns d-inline-flex flex-column gap-1">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary btnMove"
|
||||||
|
data-idstep="<?= (int)$s['idstep']; ?>" data-direction="up"
|
||||||
|
<?= $isFirst ? 'disabled' : ''; ?>>
|
||||||
|
<i class="bx bx-chevron-up"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary btnMove"
|
||||||
|
data-idstep="<?= (int)$s['idstep']; ?>" data-direction="down"
|
||||||
|
<?= $isLast ? 'disabled' : ''; ?>>
|
||||||
|
<i class="bx bx-chevron-down"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td><span class="op-badge"><?= htmlspecialchars($s['operation'], ENT_QUOTES, 'UTF-8'); ?></span></td>
|
||||||
|
<td><?= htmlspecialchars(describeStep($s['operation'], $p), ENT_QUOTES, 'UTF-8'); ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if ((int)$s['active'] === 1): ?>
|
||||||
|
<span class="badge bg-success">Attivo</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-secondary">Disattivo</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td class="table-actions">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-primary btnEditStep"
|
||||||
|
data-idstep="<?= (int)$s['idstep']; ?>">
|
||||||
|
Modifica
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-danger btnDeleteStep"
|
||||||
|
data-idstep="<?= (int)$s['idstep']; ?>">
|
||||||
|
Elimina
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-4">
|
||||||
|
|
||||||
|
<div class="d-flex align-items-center justify-content-between mb-2">
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-0">Prova la routine</h6>
|
||||||
|
<small class="text-muted">Carica un XLS di esempio: viene mostrata la prima riga dati dopo l'intestazione, elaborata con gli step qui sopra. Il file non viene salvato.</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-2 align-items-end">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<label for="previewFile" class="form-label">File XLS / XLSX di prova</label>
|
||||||
|
<input type="file" class="form-control" id="previewFile" accept=".xls,.xlsx">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<button type="button" class="btn btn-outline-primary w-100" id="btnRunPreview">
|
||||||
|
<i class="bx bx-play-circle"></i> Esegui anteprima
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="previewStatus" class="small-muted mt-2"></div>
|
||||||
|
|
||||||
|
<div class="table-responsive mt-3" id="previewResultWrapper" style="display:none;">
|
||||||
|
<table class="table table-bordered table-sm align-middle" id="previewTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 90px;">Colonna</th>
|
||||||
|
<th style="width: 40%;">Intestazione</th>
|
||||||
|
<th>Valore dopo la routine</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="overlay toggle-icon"></div>
|
||||||
|
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Step -->
|
||||||
|
<div class="modal fade" id="stepModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||||
|
<form method="POST" class="modal-content" id="stepForm">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8'); ?>">
|
||||||
|
<input type="hidden" name="idroutine" value="<?= (int)$idroutine; ?>">
|
||||||
|
<input type="hidden" name="action" id="stepFormAction" value="create">
|
||||||
|
<input type="hidden" name="idstep" id="idstep" value="">
|
||||||
|
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="stepModalTitle">Aggiungi step</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<label for="operation" class="form-label">Operazione *</label>
|
||||||
|
<select class="form-select" id="operation" name="operation" required>
|
||||||
|
<option value="merge">merge — unisci più colonne in una</option>
|
||||||
|
<option value="copy">copy — copia una colonna in un'altra</option>
|
||||||
|
<option value="clear">clear — svuota colonne</option>
|
||||||
|
<option value="trim">trim — rimuovi spazi</option>
|
||||||
|
<option value="replace">replace — sostituisci testo</option>
|
||||||
|
<option value="uppercase">uppercase — MAIUSCOLO</option>
|
||||||
|
<option value="lowercase">lowercase — minuscolo</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label for="active" class="form-label">Stato *</label>
|
||||||
|
<select class="form-select" id="active" name="active" required>
|
||||||
|
<option value="1">Attivo</option>
|
||||||
|
<option value="0">Disattivo</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Campi MERGE -->
|
||||||
|
<div class="col-md-8 op-field op-merge">
|
||||||
|
<label class="form-label">Colonne sorgente *</label>
|
||||||
|
<input type="text" class="form-control" name="source_columns" id="merge_source" placeholder="es. D, F, H">
|
||||||
|
<div class="small-muted mt-1">Lettere separate da virgola.</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 op-field op-merge">
|
||||||
|
<label class="form-label">Colonna destinazione *</label>
|
||||||
|
<input type="text" class="form-control" name="target_column" id="merge_target" placeholder="es. J">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8 op-field op-merge">
|
||||||
|
<label class="form-label">Separatore</label>
|
||||||
|
<input type="text" class="form-control" name="separator" id="merge_sep" value=" " placeholder="spazio">
|
||||||
|
<div class="small-muted mt-1">Carattere inserito tra i valori uniti. Default: spazio.</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 op-field op-merge d-flex align-items-end">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" name="skip_empty" id="merge_skip" checked>
|
||||||
|
<label class="form-check-label" for="merge_skip">Ignora valori vuoti</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Campi COPY -->
|
||||||
|
<div class="col-md-6 op-field op-copy">
|
||||||
|
<label class="form-label">Colonna sorgente *</label>
|
||||||
|
<input type="text" class="form-control" name="source_column" id="copy_source" placeholder="es. D">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 op-field op-copy">
|
||||||
|
<label class="form-label">Colonna destinazione *</label>
|
||||||
|
<input type="text" class="form-control" name="target_column_copy" id="copy_target" placeholder="es. J">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Campi COLONNE (clear/trim/uppercase/lowercase/replace) -->
|
||||||
|
<div class="col-md-12 op-field op-columns">
|
||||||
|
<label class="form-label">Colonne *</label>
|
||||||
|
<input type="text" class="form-control" name="columns" id="columns_input" placeholder="es. D, F">
|
||||||
|
<div class="small-muted mt-1">Una o più lettere separate da virgola.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Campo TRIM chars -->
|
||||||
|
<div class="col-md-12 op-field op-trim">
|
||||||
|
<label class="form-label">Caratteri da rimuovere (opzionale)</label>
|
||||||
|
<input type="text" class="form-control" name="chars" id="trim_chars" placeholder="lascia vuoto per spazi">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Campi REPLACE -->
|
||||||
|
<div class="col-md-6 op-field op-replace">
|
||||||
|
<label class="form-label">Testo da cercare</label>
|
||||||
|
<input type="text" class="form-control" name="search" id="replace_search">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 op-field op-replace">
|
||||||
|
<label class="form-label">Testo sostitutivo</label>
|
||||||
|
<input type="text" class="form-control" name="replace" id="replace_replace">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-12 op-field op-replace">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" name="regex" id="replace_regex">
|
||||||
|
<label class="form-check-label" for="replace_regex">
|
||||||
|
Usa espressione regolare (regex) — solo se sai cosa stai facendo
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Annulla</button>
|
||||||
|
<button type="submit" class="btn btn-primary" id="saveStepButton">Salva step</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Form nascosti -->
|
||||||
|
<form method="POST" id="deleteStepForm" style="display:none;">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8'); ?>">
|
||||||
|
<input type="hidden" name="idroutine" value="<?= (int)$idroutine; ?>">
|
||||||
|
<input type="hidden" name="action" value="delete">
|
||||||
|
<input type="hidden" name="idstep" id="deleteIdStep" value="">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form method="POST" id="moveStepForm" style="display:none;">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8'); ?>">
|
||||||
|
<input type="hidden" name="idroutine" value="<?= (int)$idroutine; ?>">
|
||||||
|
<input type="hidden" name="action" value="move">
|
||||||
|
<input type="hidden" name="idstep" id="moveIdStep" value="">
|
||||||
|
<input type="hidden" name="direction" id="moveDirection" value="">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const stepsData = <?= json_encode($stepsForJs, JSON_UNESCAPED_UNICODE); ?>;
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const stepModal = new bootstrap.Modal(document.getElementById('stepModal'));
|
||||||
|
|
||||||
|
const stepFormAction = document.getElementById('stepFormAction');
|
||||||
|
const idstep = document.getElementById('idstep');
|
||||||
|
const stepModalTitle = document.getElementById('stepModalTitle');
|
||||||
|
const saveStepButton = document.getElementById('saveStepButton');
|
||||||
|
const operation = document.getElementById('operation');
|
||||||
|
const active = document.getElementById('active');
|
||||||
|
|
||||||
|
// Mappa quali gruppi di campi mostrare per ogni operazione.
|
||||||
|
const fieldGroups = {
|
||||||
|
merge: ['op-merge'],
|
||||||
|
copy: ['op-copy'],
|
||||||
|
clear: ['op-columns'],
|
||||||
|
trim: ['op-columns', 'op-trim'],
|
||||||
|
replace: ['op-columns', 'op-replace'],
|
||||||
|
uppercase: ['op-columns'],
|
||||||
|
lowercase: ['op-columns']
|
||||||
|
};
|
||||||
|
|
||||||
|
function showFieldsForOperation(op) {
|
||||||
|
// Nasconde tutti i gruppi.
|
||||||
|
document.querySelectorAll('.op-field').forEach(function(el) {
|
||||||
|
el.style.display = 'none';
|
||||||
|
});
|
||||||
|
// Mostra i gruppi dell'operazione scelta.
|
||||||
|
(fieldGroups[op] || []).forEach(function(cls) {
|
||||||
|
document.querySelectorAll('.' + cls).forEach(function(el) {
|
||||||
|
el.style.display = '';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
operation.addEventListener('change', function() {
|
||||||
|
showFieldsForOperation(operation.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
function resetStepForm() {
|
||||||
|
document.getElementById('stepForm').reset();
|
||||||
|
// reset non ripristina i checkbox "checked" di default in modo affidabile: li forziamo
|
||||||
|
document.getElementById('merge_skip').checked = true;
|
||||||
|
document.getElementById('replace_regex').checked = false;
|
||||||
|
document.getElementById('merge_sep').value = ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
function openForCreate() {
|
||||||
|
stepFormAction.value = 'create';
|
||||||
|
idstep.value = '';
|
||||||
|
stepModalTitle.textContent = 'Aggiungi step';
|
||||||
|
saveStepButton.textContent = 'Aggiungi step';
|
||||||
|
|
||||||
|
resetStepForm();
|
||||||
|
operation.value = 'merge';
|
||||||
|
active.value = '1';
|
||||||
|
showFieldsForOperation('merge');
|
||||||
|
stepModal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function openForEdit(id) {
|
||||||
|
const data = stepsData[id];
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stepFormAction.value = 'update';
|
||||||
|
idstep.value = id;
|
||||||
|
stepModalTitle.textContent = 'Modifica step';
|
||||||
|
saveStepButton.textContent = 'Aggiorna step';
|
||||||
|
|
||||||
|
resetStepForm();
|
||||||
|
operation.value = data.operation;
|
||||||
|
active.value = String(data.active);
|
||||||
|
|
||||||
|
// Popola i campi in base all'operazione.
|
||||||
|
if (data.operation === 'merge') {
|
||||||
|
document.getElementById('merge_source').value = data.source_columns || '';
|
||||||
|
document.getElementById('merge_target').value = data.target_column || '';
|
||||||
|
document.getElementById('merge_sep').value = (data.separator !== undefined ? data.separator : ' ');
|
||||||
|
document.getElementById('merge_skip').checked = !!data.skip_empty;
|
||||||
|
} else if (data.operation === 'copy') {
|
||||||
|
document.getElementById('copy_source').value = data.source_column || '';
|
||||||
|
document.getElementById('copy_target').value = data.target_column || '';
|
||||||
|
} else if (['clear', 'trim', 'uppercase', 'lowercase'].includes(data.operation)) {
|
||||||
|
document.getElementById('columns_input').value = data.columns || '';
|
||||||
|
if (data.operation === 'trim') {
|
||||||
|
document.getElementById('trim_chars').value = data.chars || '';
|
||||||
|
}
|
||||||
|
} else if (data.operation === 'replace') {
|
||||||
|
document.getElementById('columns_input').value = data.columns || '';
|
||||||
|
document.getElementById('replace_search').value = data.search || '';
|
||||||
|
document.getElementById('replace_replace').value = data.replace || '';
|
||||||
|
document.getElementById('replace_regex').checked = !!data.regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
showFieldsForOperation(data.operation);
|
||||||
|
stepModal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NOTA sul campo destinazione di copy:
|
||||||
|
* l'input HTML si chiama target_column_copy per non collidere con merge,
|
||||||
|
* ma il backend si aspetta target_column. Rinominiamo al submit.
|
||||||
|
*/
|
||||||
|
document.getElementById('stepForm').addEventListener('submit', function(e) {
|
||||||
|
if (operation.value === 'copy') {
|
||||||
|
const copyTarget = document.getElementById('copy_target');
|
||||||
|
// Crea/aggiorna un campo hidden target_column col valore di copy.
|
||||||
|
let hidden = document.getElementById('copy_target_hidden');
|
||||||
|
if (!hidden) {
|
||||||
|
hidden = document.createElement('input');
|
||||||
|
hidden.type = 'hidden';
|
||||||
|
hidden.name = 'target_column';
|
||||||
|
hidden.id = 'copy_target_hidden';
|
||||||
|
this.appendChild(hidden);
|
||||||
|
}
|
||||||
|
hidden.value = copyTarget.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('btnAddStep').addEventListener('click', openForCreate);
|
||||||
|
|
||||||
|
document.querySelectorAll('.btnEditStep').forEach(function(btn) {
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
openForEdit(btn.dataset.idstep);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.btnDeleteStep').forEach(function(btn) {
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Eliminare lo step?',
|
||||||
|
text: 'Operazione irreversibile.',
|
||||||
|
icon: 'warning',
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonText: 'Sì, elimina',
|
||||||
|
cancelButtonText: 'Annulla',
|
||||||
|
confirmButtonColor: '#d33'
|
||||||
|
}).then(function(result) {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
document.getElementById('deleteIdStep').value = btn.dataset.idstep;
|
||||||
|
document.getElementById('deleteStepForm').submit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.btnMove').forEach(function(btn) {
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
document.getElementById('moveIdStep').value = btn.dataset.idstep;
|
||||||
|
document.getElementById('moveDirection').value = btn.dataset.direction;
|
||||||
|
document.getElementById('moveStepForm').submit();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Anteprima routine su XLS caricato (nessun salvataggio) ---
|
||||||
|
const previewFile = document.getElementById('previewFile');
|
||||||
|
const btnRunPreview = document.getElementById('btnRunPreview');
|
||||||
|
const previewStatus = document.getElementById('previewStatus');
|
||||||
|
const previewResultWrapper = document.getElementById('previewResultWrapper');
|
||||||
|
const previewTableBody = document.querySelector('#previewTable tbody');
|
||||||
|
const currentIdRoutine = "<?= (int)$idroutine; ?>";
|
||||||
|
|
||||||
|
btnRunPreview.addEventListener('click', function() {
|
||||||
|
if (!previewFile.files || previewFile.files.length === 0) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'info',
|
||||||
|
title: 'Nessun file',
|
||||||
|
text: 'Seleziona un file XLS di prova.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('idroutine', currentIdRoutine);
|
||||||
|
formData.append('file', previewFile.files[0]);
|
||||||
|
|
||||||
|
previewStatus.textContent = 'Elaborazione in corso...';
|
||||||
|
previewResultWrapper.style.display = 'none';
|
||||||
|
previewTableBody.innerHTML = '';
|
||||||
|
btnRunPreview.disabled = true;
|
||||||
|
|
||||||
|
fetch('routine_preview.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
cache: 'no-store'
|
||||||
|
})
|
||||||
|
.then(function(res) {
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.then(function(data) {
|
||||||
|
btnRunPreview.disabled = false;
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
previewStatus.textContent = '';
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'warning',
|
||||||
|
title: 'Anteprima non riuscita',
|
||||||
|
text: data.error
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
previewStatus.textContent = 'Riga elaborata: riga Excel ' + data.excelrow + '.';
|
||||||
|
|
||||||
|
const letters = data.letters || [];
|
||||||
|
const headers = data.headers || [];
|
||||||
|
const row = data.row || [];
|
||||||
|
|
||||||
|
for (let i = 0; i < row.length; i++) {
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
|
||||||
|
const tdCol = document.createElement('td');
|
||||||
|
tdCol.innerHTML = '<strong>' + (letters[i] || '') + '</strong>';
|
||||||
|
|
||||||
|
const tdHead = document.createElement('td');
|
||||||
|
tdHead.className = 'small-muted';
|
||||||
|
tdHead.textContent = headers[i] || '';
|
||||||
|
|
||||||
|
const tdVal = document.createElement('td');
|
||||||
|
tdVal.textContent = row[i] || '';
|
||||||
|
|
||||||
|
tr.appendChild(tdCol);
|
||||||
|
tr.appendChild(tdHead);
|
||||||
|
tr.appendChild(tdVal);
|
||||||
|
previewTableBody.appendChild(tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
previewResultWrapper.style.display = '';
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
btnRunPreview.disabled = false;
|
||||||
|
previewStatus.textContent = '';
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Errore',
|
||||||
|
text: 'Impossibile eseguire l\'anteprima.'
|
||||||
|
});
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
<?php if ($flash): ?>
|
||||||
|
Swal.fire({
|
||||||
|
icon: '<?= htmlspecialchars($flash['type'], ENT_QUOTES, 'UTF-8'); ?>',
|
||||||
|
title: '<?= $flash['type'] === 'success' ? 'Fatto' : ($flash['type'] === 'warning' ? 'Attenzione' : 'Errore'); ?>',
|
||||||
|
text: '<?= htmlspecialchars($flash['message'], ENT_QUOTES, 'UTF-8'); ?>'
|
||||||
|
});
|
||||||
|
<?php endif; ?>
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,296 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Routine motore generico (_engine.php)
|
||||||
|
* -------------------------------------
|
||||||
|
* File UNICO condiviso da tutte le routine con engine = 1.
|
||||||
|
*
|
||||||
|
* Espone la stessa firma delle routine legacy:
|
||||||
|
*
|
||||||
|
* applyRoutine(&$excelData, $routineData)
|
||||||
|
*
|
||||||
|
* così apply_routine.php lo include allo stesso modo. La differenza è che qui
|
||||||
|
* NON c'è logica hardcoded: gli step vengono letti dalla tabella routine_step
|
||||||
|
* (in base a routineData['idroutine']) ed eseguiti in ordine.
|
||||||
|
*
|
||||||
|
* Ogni step ha una `operation` (verbo del vocabolario) e `params` JSON.
|
||||||
|
* Le colonne nei params possono essere indici zero-based (3) o lettere Excel ("D").
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!function_exists('applyRoutine')) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converte un riferimento colonna in indice zero-based.
|
||||||
|
* Accetta int/indici numerici (3) o lettere Excel ("D", "AA", ...).
|
||||||
|
*/
|
||||||
|
function routineEngineColToIndex($ref): int
|
||||||
|
{
|
||||||
|
if (is_int($ref)) {
|
||||||
|
return $ref;
|
||||||
|
}
|
||||||
|
$ref = trim((string)$ref);
|
||||||
|
if ($ref === '') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// Numerico puro -> indice diretto
|
||||||
|
if (ctype_digit($ref)) {
|
||||||
|
return (int)$ref;
|
||||||
|
}
|
||||||
|
// Lettere Excel -> indice zero-based (A=0, B=1, ..., Z=25, AA=26, ...)
|
||||||
|
$ref = strtoupper($ref);
|
||||||
|
$index = 0;
|
||||||
|
$len = strlen($ref);
|
||||||
|
for ($i = 0; $i < $len; $i++) {
|
||||||
|
$c = $ref[$i];
|
||||||
|
if ($c < 'A' || $c > 'Z') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$index = $index * 26 + (ord($c) - ord('A') + 1);
|
||||||
|
}
|
||||||
|
return $index - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Normalizza una lista di riferimenti colonna in indici zero-based. */
|
||||||
|
function routineEngineColsToIndexes(array $refs): array
|
||||||
|
{
|
||||||
|
return array_map('routineEngineColToIndex', $refs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Carica gli step attivi di una routine dal DB, ordinati per step_order.
|
||||||
|
* Ritorna array di ['operation' => string, 'params' => array].
|
||||||
|
*/
|
||||||
|
function routineEngineLoadSteps(int $idroutine): array
|
||||||
|
{
|
||||||
|
// Riusa la connessione PDO del progetto.
|
||||||
|
// db-functions.php sta in .../class/ rispetto alla cartella routines/.
|
||||||
|
// Aggiusta il require se nel tuo albero il path differisce.
|
||||||
|
$dbFile = dirname(__DIR__) . '/class/db-functions.php';
|
||||||
|
if (!class_exists('DBHandlerSelect') && file_exists($dbFile)) {
|
||||||
|
require_once $dbFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists('DBHandlerSelect')) {
|
||||||
|
throw new Exception("Motore routine: classe DBHandlerSelect non trovata (path db-functions.php da verificare).");
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
"SELECT operation, params
|
||||||
|
FROM routine_step
|
||||||
|
WHERE idroutine = :id AND active = 1
|
||||||
|
ORDER BY step_order ASC, idstep ASC"
|
||||||
|
);
|
||||||
|
$stmt->execute([':id' => $idroutine]);
|
||||||
|
$rows = $stmt->fetchAll();
|
||||||
|
|
||||||
|
$steps = [];
|
||||||
|
foreach ($rows as $r) {
|
||||||
|
$params = [];
|
||||||
|
if (!empty($r['params'])) {
|
||||||
|
$decoded = json_decode($r['params'], true);
|
||||||
|
if (is_array($decoded)) {
|
||||||
|
$params = $decoded;
|
||||||
|
} else {
|
||||||
|
error_log("Motore routine: params JSON non valido per operazione {$r['operation']} (routine {$idroutine}).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$steps[] = ['operation' => strtolower(trim($r['operation'])), 'params' => $params];
|
||||||
|
}
|
||||||
|
return $steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ *
|
||||||
|
* HANDLERS – ogni operazione modifica $excelData in place.
|
||||||
|
* Aggiungere una capacità nuova = aggiungere un handler qui, una volta.
|
||||||
|
* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* merge: unisce N colonne sorgente in una destinazione.
|
||||||
|
* params: source_columns [], target_column, separator=" ", skip_empty=true
|
||||||
|
*/
|
||||||
|
function routineOpMerge(array &$excelData, array $params): void
|
||||||
|
{
|
||||||
|
$sources = routineEngineColsToIndexes($params['source_columns'] ?? []);
|
||||||
|
$target = routineEngineColToIndex($params['target_column'] ?? 0);
|
||||||
|
$sep = array_key_exists('separator', $params) ? (string)$params['separator'] : ' ';
|
||||||
|
$skip = $params['skip_empty'] ?? true;
|
||||||
|
|
||||||
|
foreach ($excelData as $rowIndex => &$row) {
|
||||||
|
if (!isset($row['data']) || !is_array($row['data'])) {
|
||||||
|
error_log("merge: struttura riga non valida all'indice {$rowIndex}.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$parts = [];
|
||||||
|
foreach ($sources as $col) {
|
||||||
|
$val = trim((string)($row['data'][$col] ?? ''));
|
||||||
|
if ($skip && $val === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$parts[] = $val;
|
||||||
|
}
|
||||||
|
$row['data'][$target] = implode($sep, $parts);
|
||||||
|
}
|
||||||
|
unset($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* copy: copia una colonna sorgente in una destinazione.
|
||||||
|
* params: source_column, target_column
|
||||||
|
*/
|
||||||
|
function routineOpCopy(array &$excelData, array $params): void
|
||||||
|
{
|
||||||
|
$src = routineEngineColToIndex($params['source_column'] ?? 0);
|
||||||
|
$dst = routineEngineColToIndex($params['target_column'] ?? 0);
|
||||||
|
foreach ($excelData as &$row) {
|
||||||
|
if (!isset($row['data']) || !is_array($row['data'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$row['data'][$dst] = $row['data'][$src] ?? '';
|
||||||
|
}
|
||||||
|
unset($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clear: svuota una o più colonne.
|
||||||
|
* params: columns [] (oppure column singola)
|
||||||
|
*/
|
||||||
|
function routineOpClear(array &$excelData, array $params): void
|
||||||
|
{
|
||||||
|
$cols = $params['columns'] ?? (isset($params['column']) ? [$params['column']] : []);
|
||||||
|
$cols = routineEngineColsToIndexes($cols);
|
||||||
|
foreach ($excelData as &$row) {
|
||||||
|
if (!isset($row['data']) || !is_array($row['data'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($cols as $c) {
|
||||||
|
$row['data'][$c] = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* trim: rimuove spazi (o caratteri indicati) da una o più colonne.
|
||||||
|
* params: columns [] (o column), chars (opzionale)
|
||||||
|
*/
|
||||||
|
function routineOpTrim(array &$excelData, array $params): void
|
||||||
|
{
|
||||||
|
$cols = $params['columns'] ?? (isset($params['column']) ? [$params['column']] : []);
|
||||||
|
$cols = routineEngineColsToIndexes($cols);
|
||||||
|
$chars = $params['chars'] ?? null;
|
||||||
|
foreach ($excelData as &$row) {
|
||||||
|
if (!isset($row['data']) || !is_array($row['data'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($cols as $c) {
|
||||||
|
$v = (string)($row['data'][$c] ?? '');
|
||||||
|
$row['data'][$c] = $chars === null ? trim($v) : trim($v, $chars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* replace: sostituzione testo in una o più colonne.
|
||||||
|
* params: columns [] (o column), search, replace="", regex=false
|
||||||
|
*/
|
||||||
|
function routineOpReplace(array &$excelData, array $params): void
|
||||||
|
{
|
||||||
|
$cols = $params['columns'] ?? (isset($params['column']) ? [$params['column']] : []);
|
||||||
|
$cols = routineEngineColsToIndexes($cols);
|
||||||
|
$search = $params['search'] ?? '';
|
||||||
|
$replace = $params['replace'] ?? '';
|
||||||
|
$regex = $params['regex'] ?? false;
|
||||||
|
|
||||||
|
foreach ($excelData as &$row) {
|
||||||
|
if (!isset($row['data']) || !is_array($row['data'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($cols as $c) {
|
||||||
|
$v = (string)($row['data'][$c] ?? '');
|
||||||
|
if ($regex) {
|
||||||
|
$out = @preg_replace($search, $replace, $v);
|
||||||
|
$row['data'][$c] = $out === null ? $v : $out;
|
||||||
|
} else {
|
||||||
|
$row['data'][$c] = str_replace($search, $replace, $v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** uppercase: maiuscolo su una o più colonne. params: columns [] (o column) */
|
||||||
|
function routineOpUppercase(array &$excelData, array $params): void
|
||||||
|
{
|
||||||
|
$cols = $params['columns'] ?? (isset($params['column']) ? [$params['column']] : []);
|
||||||
|
$cols = routineEngineColsToIndexes($cols);
|
||||||
|
foreach ($excelData as &$row) {
|
||||||
|
if (!isset($row['data']) || !is_array($row['data'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($cols as $c) {
|
||||||
|
$row['data'][$c] = mb_strtoupper((string)($row['data'][$c] ?? ''), 'UTF-8');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** lowercase: minuscolo su una o più colonne. params: columns [] (o column) */
|
||||||
|
function routineOpLowercase(array &$excelData, array $params): void
|
||||||
|
{
|
||||||
|
$cols = $params['columns'] ?? (isset($params['column']) ? [$params['column']] : []);
|
||||||
|
$cols = routineEngineColsToIndexes($cols);
|
||||||
|
foreach ($excelData as &$row) {
|
||||||
|
if (!isset($row['data']) || !is_array($row['data'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($cols as $c) {
|
||||||
|
$row['data'][$c] = mb_strtolower((string)($row['data'][$c] ?? ''), 'UTF-8');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry point richiamato da apply_routine.php.
|
||||||
|
* Stessa firma delle routine legacy.
|
||||||
|
*/
|
||||||
|
function applyRoutine(&$excelData, $routineData = [])
|
||||||
|
{
|
||||||
|
$idroutine = isset($routineData['idroutine']) ? (int)$routineData['idroutine'] : 0;
|
||||||
|
if ($idroutine <= 0) {
|
||||||
|
throw new Exception("Motore routine: idroutine mancante in routineData.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$steps = routineEngineLoadSteps($idroutine);
|
||||||
|
if (empty($steps)) {
|
||||||
|
error_log("Motore routine: nessuno step attivo per routine {$idroutine}.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch table verbo -> handler
|
||||||
|
$handlers = [
|
||||||
|
'merge' => 'routineOpMerge',
|
||||||
|
'copy' => 'routineOpCopy',
|
||||||
|
'clear' => 'routineOpClear',
|
||||||
|
'trim' => 'routineOpTrim',
|
||||||
|
'replace' => 'routineOpReplace',
|
||||||
|
'uppercase' => 'routineOpUppercase',
|
||||||
|
'lowercase' => 'routineOpLowercase',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($steps as $i => $step) {
|
||||||
|
$op = $step['operation'];
|
||||||
|
if (!isset($handlers[$op])) {
|
||||||
|
error_log("Motore routine: operazione sconosciuta '{$op}' (routine {$idroutine}, step " . ($i + 1) . ") — saltata.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$handlers[$op]($excelData, $step['params']);
|
||||||
|
error_log("Motore routine: applicata operazione '{$op}' (routine {$idroutine}, step " . ($i + 1) . ").");
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("Motore routine {$idroutine} completato (" . count($steps) . " step).");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||||
|
require_once __DIR__ . '/class/db-functions.php';
|
||||||
|
require_once __DIR__ . '/class/binding-functions.php';
|
||||||
|
include dirname(__DIR__) . '/../extra/auth.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
if (!Auth::check()) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Method not allowed']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$kind = ($_POST['kind'] ?? 'custom') === 'fixed' ? 'fixed' : 'custom';
|
||||||
|
$templateId = intval($_POST['template_id'] ?? 0);
|
||||||
|
$mappingId = intval($_POST['mapping_id'] ?? 0);
|
||||||
|
$fieldId = intval($_POST['field_id'] ?? 0);
|
||||||
|
$sourceValue = (string) ($_POST['source_value'] ?? '');
|
||||||
|
$limsValueId = intval($_POST['lims_value_id'] ?? 0);
|
||||||
|
$limsValue = (string) ($_POST['lims_value'] ?? '');
|
||||||
|
|
||||||
|
$datadbIds = [];
|
||||||
|
if (isset($_POST['datadb_ids'])) {
|
||||||
|
$decoded = json_decode($_POST['datadb_ids'], true);
|
||||||
|
if (is_array($decoded)) {
|
||||||
|
$datadbIds = $decoded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($templateId <= 0 || $mappingId <= 0 || trim($sourceValue) === '' || $limsValueId <= 0) {
|
||||||
|
http_response_code(422);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Missing required parameters']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
$createdBy = Auth::user() ? (int) Auth::user()->present()->id : null;
|
||||||
|
|
||||||
|
if ($kind === 'fixed') {
|
||||||
|
$stmt = $pdo->prepare("SELECT fixed_field_key FROM template_fixed_mapping WHERE id = ?");
|
||||||
|
$stmt->execute([$mappingId]);
|
||||||
|
$fixedKey = (string) ($stmt->fetchColumn() ?: '');
|
||||||
|
|
||||||
|
if ($fixedKey === '' || !binding_fixed_is_list($fixedKey)) {
|
||||||
|
http_response_code(422);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Invalid fixed field']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
binding_upsert($pdo, $templateId, $mappingId, 0, $sourceValue, $limsValueId, $limsValue, $createdBy, 'fixed');
|
||||||
|
|
||||||
|
$applied = !empty($datadbIds)
|
||||||
|
? binding_apply_to_datadb($pdo, $fixedKey, (string) $limsValueId, $datadbIds)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'applied_rows' => $applied,
|
||||||
|
'kind' => 'fixed',
|
||||||
|
'source_value' => $sourceValue,
|
||||||
|
'lims_value' => $limsValue,
|
||||||
|
'lims_value_id' => $limsValueId,
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($fieldId <= 0) {
|
||||||
|
$stmt = $pdo->prepare("SELECT field_id FROM template_mapping WHERE id = ?");
|
||||||
|
$stmt->execute([$mappingId]);
|
||||||
|
$fieldId = (int) ($stmt->fetchColumn() ?: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
binding_upsert($pdo, $templateId, $mappingId, $fieldId, $sourceValue, $limsValueId, $limsValue, $createdBy);
|
||||||
|
|
||||||
|
$applied = !empty($datadbIds)
|
||||||
|
? binding_apply_to_details($pdo, $mappingId, (string) $limsValueId, $datadbIds)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'applied_rows' => $applied,
|
||||||
|
'kind' => 'custom',
|
||||||
|
'source_value' => $sourceValue,
|
||||||
|
'lims_value' => $limsValue,
|
||||||
|
'lims_value_id' => $limsValueId,
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -22,6 +22,8 @@ try {
|
|||||||
$analysisLevel = isset($_POST['analysis_level']) && $_POST['analysis_level'] !== '' ? (int)$_POST['analysis_level'] : null;
|
$analysisLevel = isset($_POST['analysis_level']) && $_POST['analysis_level'] !== '' ? (int)$_POST['analysis_level'] : null;
|
||||||
$isWebSelectable = isset($_POST['is_web_selectable']) ? (int)$_POST['is_web_selectable'] : 0;
|
$isWebSelectable = isset($_POST['is_web_selectable']) ? (int)$_POST['is_web_selectable'] : 0;
|
||||||
$isAccredited = isset($_POST['is_accredited']) ? (int)$_POST['is_accredited'] : 0;
|
$isAccredited = isset($_POST['is_accredited']) ? (int)$_POST['is_accredited'] : 0;
|
||||||
|
$analysisPriceRaw = str_replace(',', '.', trim((string)($_POST['analysis_price'] ?? '')));
|
||||||
|
$analysisPrice = is_numeric($analysisPriceRaw) ? round((float)$analysisPriceRaw, 2) : null;
|
||||||
|
|
||||||
if ($partId <= 0 || $analysisRecordkey === '') {
|
if ($partId <= 0 || $analysisRecordkey === '') {
|
||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
@@ -42,7 +44,8 @@ try {
|
|||||||
analysis_method,
|
analysis_method,
|
||||||
analysis_level,
|
analysis_level,
|
||||||
is_web_selectable,
|
is_web_selectable,
|
||||||
is_accredited
|
is_accredited,
|
||||||
|
analysis_price
|
||||||
) VALUES (
|
) VALUES (
|
||||||
:part_id,
|
:part_id,
|
||||||
:iddatadb,
|
:iddatadb,
|
||||||
@@ -52,7 +55,8 @@ try {
|
|||||||
:analysis_method,
|
:analysis_method,
|
||||||
:analysis_level,
|
:analysis_level,
|
||||||
:is_web_selectable,
|
:is_web_selectable,
|
||||||
:is_accredited
|
:is_accredited,
|
||||||
|
:analysis_price
|
||||||
)
|
)
|
||||||
");
|
");
|
||||||
|
|
||||||
@@ -66,6 +70,7 @@ try {
|
|||||||
':analysis_level' => $analysisLevel,
|
':analysis_level' => $analysisLevel,
|
||||||
':is_web_selectable' => $isWebSelectable,
|
':is_web_selectable' => $isWebSelectable,
|
||||||
':is_accredited' => $isAccredited,
|
':is_accredited' => $isAccredited,
|
||||||
|
':analysis_price' => $analysisPrice,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
|
|||||||
@@ -1,917 +0,0 @@
|
|||||||
{
|
|
||||||
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#SchemaCustomField",
|
|
||||||
"value": [
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 41,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Labostudio",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 42,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Schema 1",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 43,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Schema1",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 44,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Cuoio\/Pelle Standard \/ Leather",
|
|
||||||
"Descrizione": "Schema per tutti i campioni in cuoio\/pelle per i quali non \u00e8 specificato un destinatario.\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 45,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Borse",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 46,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Borse Burberry",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 47,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "pelle calzatura",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 48,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Standard \/ Generico",
|
|
||||||
"Descrizione": "\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 49,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Accessori Metallici \/ Metallic Accessories",
|
|
||||||
"Descrizione": "Schema per tutti gli Accessori Metallici\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 50,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Tessile \/ Textiles",
|
|
||||||
"Descrizione": "Schema per tutti i Tessuti\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 52,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Brasport",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 54,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Chanel Calzatura",
|
|
||||||
"Descrizione": "\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 55,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Burberry",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 57,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Z_Ralph Lauren - Tessile",
|
|
||||||
"Descrizione": "Schema per tutti i campioni in tessuto per Ralph Lauren.\r\n\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 58,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "LIU JO",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 59,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Migrazione tra Componenti",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 60,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Prova Outsourcing",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 61,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Marc Jacobs",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 62,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "A. TESTONI",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 63,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Chanel CHINA PROJECT",
|
|
||||||
"Descrizione": "\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 66,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "GUESS",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 68,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Valentino",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 69,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "DESIGUAL",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 70,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "FENDI PELLETTERIA",
|
|
||||||
"Descrizione": "Solo per FENDI Pelletteria_INGLESE\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 71,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Z_Cuoio\/Pelle Standard ACCREDIA",
|
|
||||||
"Descrizione": "Schema per tutti i campioni in cuoio\/pelle per i quali non \u00e8 specificato un destinatario.\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 72,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Z_BALLY",
|
|
||||||
"Descrizione": "sia per calzatura che pelletteria. Aggiunto campo TEST DESCRIPTION, compreso nella fatturazione per le divisioni pelletteria.\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 73,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Z_Ralph Lauren - Leather",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 74,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Z_Ralph Lauren - Fabric",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 76,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "NORDSTROM",
|
|
||||||
"Descrizione": "\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 77,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "ONWARD LUXURY GROUP",
|
|
||||||
"Descrizione": "\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 78,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "P&G SRL_Progetto Durability",
|
|
||||||
"Descrizione": "\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 79,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Bally qualsiasi divisione",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 80,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "FENDI CALZATURA",
|
|
||||||
"Descrizione": "\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 81,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "LouBoutin",
|
|
||||||
"Descrizione": "\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 82,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "MONCLER Brand",
|
|
||||||
"Descrizione": "Da utilizzare solo per il Brand Diretto\r\nGR 19\/03\/2024"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 83,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "JIMMY CHOO",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 84,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Yves Saint Laurent",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 85,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "BENETTON",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 86,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "UPLOAD REPORT",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 87,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Hermes Calzatura",
|
|
||||||
"Descrizione": "\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 88,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "C&A",
|
|
||||||
"Descrizione": "Schema per fornitori e C&A diretto\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 89,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "AMERICAN EAGLE",
|
|
||||||
"Descrizione": "Schema per American Eagle\/Todd snyder\r\n\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 91,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Ferragamo",
|
|
||||||
"Descrizione": "\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 93,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Z_Alexander McQueen",
|
|
||||||
"Descrizione": "\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 95,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Balenciaga Pelletteria",
|
|
||||||
"Descrizione": "\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 96,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "DUNHILL",
|
|
||||||
"Descrizione": "SCHEMA DUNHILL tutti i tipi di campioni\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 97,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "PRELIEVI ACQUE",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 98,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "LouBoutin_Tossicologico",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 99,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "ROBAN'S ",
|
|
||||||
"Descrizione": "tutte le divisioni\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 100,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "PROGETTO LUGGAGE",
|
|
||||||
"Descrizione": "Progetto Luggage Hermes\r\n\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 102,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Balmain",
|
|
||||||
"Descrizione": "Schema per tutti i campioni Balmain"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 103,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "ZDHC",
|
|
||||||
"Descrizione": "Schema di Riferimento ZDHC Wastewaters"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 104,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "GUCCI CDC 4172",
|
|
||||||
"Descrizione": "\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 105,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Z_ASOS",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 106,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Z_BOUX AVENUE",
|
|
||||||
"Descrizione": "28\/09 no indicazioni su esigenze di fatturazione (detto da Frosini)\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 107,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "BONPOINT",
|
|
||||||
"Descrizione": "05\/10 creazione\r\n\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 108,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Canada Goose",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 109,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "DIESEL",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 110,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "AQC_Leather",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 111,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "BEAUMANOIR (CC-Mor-Bon-Breal)",
|
|
||||||
"Descrizione": "04\/02\/21_ AGGIORNATO 05\/08\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 112,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "BooHoo",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 114,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Golden Goose",
|
|
||||||
"Descrizione": "Golden goose\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 115,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Z_ZLABEL",
|
|
||||||
"Descrizione": "Schema per ZLABEL"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 116,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Zadig & Voltaire",
|
|
||||||
"Descrizione": "Schema per Zadig & Voltaire"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 117,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Gemo",
|
|
||||||
"Descrizione": "Schema solo per Gemo\r\n\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 118,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "PROMOD",
|
|
||||||
"Descrizione": "CHF 06\/08\r\n\r\n\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 119,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "LA HALLE (BEAUMANOIR)",
|
|
||||||
"Descrizione": "13\/08\/2021 CHF\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 120,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "LA REDOUTE",
|
|
||||||
"Descrizione": "14\/09\/2021 AD\r\n\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 121,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "TOD'S",
|
|
||||||
"Descrizione": "Schema per materiale gb18401 e GB20400\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 122,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "ASOS rev 1",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 123,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Monoprix",
|
|
||||||
"Descrizione": "Schema per Monoprix\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 124,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "DIOR Ita",
|
|
||||||
"Descrizione": "CHF 21\/03\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 125,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "GEOX - Raw Material",
|
|
||||||
"Descrizione": "SCHEMA DA USARE SOLO PER GEOX - SOLO PER LE SUOLE E MATERIALI DENOMINATI RPU \/ RAW MATERIAL"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 126,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "CAMAIEU",
|
|
||||||
"Descrizione": "SCHEMA DA USARE SOLO PER CAMAIEU\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 127,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Z_Golden Goose FTW",
|
|
||||||
"Descrizione": "Golden goose\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 128,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "STEVE MADDEN - FOOTWEAR",
|
|
||||||
"Descrizione": "DA UTILIZZARE SOLO PER DESTINAZIONE D'USO CALZATURA\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 129,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Z_Carhartt",
|
|
||||||
"Descrizione": "SCHEMA DA UTILIZZARE SOLO PER RICHIESTE UFFICIALI CARHARTT\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 131,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "DAMART",
|
|
||||||
"Descrizione": "\r\n\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 132,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Gruppo OTB",
|
|
||||||
"Descrizione": "Schema per Staff International, Diesel e Brave kid\r\n\r\n\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 133,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "FERRARI",
|
|
||||||
"Descrizione": "Schema da usare per Ferrari e fornitori\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 134,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "GAP - ABBIGLIAMENTO",
|
|
||||||
"Descrizione": "Schema GAP da utilizzare solo per destinazione abbigliamento\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 135,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "GAP - CALZATURA",
|
|
||||||
"Descrizione": "Schema GAP da utilizzare solo per destinazione calzatura\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 136,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "SALLING GROUP",
|
|
||||||
"Descrizione": "Schema per Salling Group\r\n\r\n\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 137,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "SOFT SURROUNDINGS",
|
|
||||||
"Descrizione": "Schema da utilizzare per Soft Surroundings\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 138,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "E-LIMS - Cuoio\/Pelle Standard \/ Leather",
|
|
||||||
"Descrizione": "Schema da utilizzare solo per accettazioni online\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 139,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Z_ZALANDO",
|
|
||||||
"Descrizione": "Schema per ZALANDO ------ (NON per ZLABEL che ha il suo)\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 140,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "LVMH",
|
|
||||||
"Descrizione": "gennaio 2023\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 141,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Valentino - eLims",
|
|
||||||
"Descrizione": "SCHEMA DA UTILIZZARE SOLO PER L'ACCETTAZIONE ONLINE DI VALENTINO"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 142,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "FENDI PELLETTERIA COMPLIANCE",
|
|
||||||
"Descrizione": "Solo per FENDI Pelletteria_INGLESE 04\/05\/2023\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 143,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "TAPESTRY",
|
|
||||||
"Descrizione": "04\/05\/2023, agg secondo procedura 08\/11\/23\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 144,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "ALEXANDER MC QUEEN",
|
|
||||||
"Descrizione": "ALEXANDER MC QUEEN 15\/05\/2023\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 145,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Hermes_Chimici_Parigi",
|
|
||||||
"Descrizione": "\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 146,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "CELINE ABBIGLIAMENTO_Progetto Speciale",
|
|
||||||
"Descrizione": "IO 202 CELINE ABBIGLIAMENTO_06\/06\/203_CHF\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 147,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "GEOX - Prodotto Finito",
|
|
||||||
"Descrizione": "SCHEMA DA USARE SOLO PER GEOX - SOLO PER PRODOTTO FINITO"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 149,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Hermes\/IDO",
|
|
||||||
"Descrizione": "creato il 05\/07\/2023 CHF\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 150,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Tessile \/ Textiles PER CLIENTI CON OBLO",
|
|
||||||
"Descrizione": "Schema per tutti i Tessuti QUANDO IL CLIENTE NECESSITA DI NR OBLO\r\n\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 151,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "J JIll",
|
|
||||||
"Descrizione": "Schema per cliente J JIll, necessario riportare la recommended fiber content in schema verde\r\n\r\n\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 152,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "AUCHAN - TAPE A L'OEIL",
|
|
||||||
"Descrizione": "Schema AUCHAN da utilizzare solo per la divisione TAPE A L'OEIL SA, successivamente se uguale da usare anche per le altre divisioni ed uniformarlo come generico"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 153,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "ALL SAINTS - BUSCEMI",
|
|
||||||
"Descrizione": "SCHE DA USARE SOLO PER ALL SAINTS E BUSCEMI\r\n\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 154,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "CELINE - LVMH",
|
|
||||||
"Descrizione": "Schema con aggiunta campi LVMH per estrazioni\r\nAD-29\/03\/2024"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 155,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "SeyMeChamLou",
|
|
||||||
"Descrizione": "DA USARE SOLO PER SEYMECHAMLOU (LOUBOUTIN)\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 156,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "PRADA",
|
|
||||||
"Descrizione": "PER ADESSO DA USARE PER ABBIGLIAMENTO E CALZATURA\r\n\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 157,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Hermes_HCP",
|
|
||||||
"Descrizione": "Schema per unicamente HCP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 158,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "STONE ISLAND",
|
|
||||||
"Descrizione": "Schema per tutti i campioni in cuoio\/pelle per i quali non \u00e8 specificato un destinatario.\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 159,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Chanel_Pierre Damien",
|
|
||||||
"Descrizione": "Schema da usare per Campioni ricevuti da Pierre-Damien Verine e suo staff CHANEL.\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 160,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Z_ACNE",
|
|
||||||
"Descrizione": "\r\n.\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 161,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "OBERALP GROUP",
|
|
||||||
"Descrizione": "Schema da usare solo per Oberalp Group\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 162,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Club Monaco",
|
|
||||||
"Descrizione": "da usare solo per Club Monaco GR 20\/02\/2024"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 163,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Devred",
|
|
||||||
"Descrizione": "Schema creato per cliente DEVRED\r\nGR 18\/03\/2024 aggiornato il 23\/04\/2026\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 164,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "MONCLER Supplier",
|
|
||||||
"Descrizione": "Da utilizzare per fornitori Moncler \r\nGR 19\/03\/2024"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 165,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Kering Sunglasses",
|
|
||||||
"Descrizione": "Schema per tutti i campioni di qualsiasi matrice escluso cuoio\/pelle\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 166,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Giorgio Armani",
|
|
||||||
"Descrizione": "\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 167,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Chanel USA",
|
|
||||||
"Descrizione": "\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 168,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Staff Calzatura",
|
|
||||||
"Descrizione": "Schema per tutti i campioni di qualsiasi matrice escluso cuoio\/pelle\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 169,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "P&G SRL",
|
|
||||||
"Descrizione": "\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 170,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "INCOM",
|
|
||||||
"Descrizione": "Schema da utilizzare per INCOM"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 171,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "RIVER ISLAND",
|
|
||||||
"Descrizione": "SCHEDA DA USARE SOLO PER RIVER ISLAND - NO CALZATURA\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 172,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Longchamp",
|
|
||||||
"Descrizione": "Schema per tutti i campioni con RIchiesta Longchamp ufficiale\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 173,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Bottega Veneta",
|
|
||||||
"Descrizione": "Schema per tutti i campioni di Bottega Veneta di Stella"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 174,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Mint Velvet",
|
|
||||||
"Descrizione": "Schema per tutti i campioni di Mint Velvel con richiesta ufficiale.\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 176,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "RIVER ISLAND FOOTWEAR",
|
|
||||||
"Descrizione": "SCHEDA DA USARE SOLO PER RIVER ISLAND - USARE SOLO PER\r\n CALZATURA\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 177,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Phoebe Philo ",
|
|
||||||
"Descrizione": "\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 178,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "New Guards Group",
|
|
||||||
"Descrizione": "\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 179,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Giorgio Armani Operation",
|
|
||||||
"Descrizione": "\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 180,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Chloe'",
|
|
||||||
"Descrizione": "\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 181,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "LVMH non diretti",
|
|
||||||
"Descrizione": "utilizzato ogni qualvolta siano caricati pacchetti \/ prove singole che non rientrano nel programma LVMH \r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 182,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Ralph Lauren - All testing V.10",
|
|
||||||
"Descrizione": "AGGIORNAMENTO AL 16\/03\/2026 per estrazione fatturazione"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 183,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "TWINSET",
|
|
||||||
"Descrizione": "Schema da usare per Twinset - come da mail di EP del 20\/05\/2025\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 184,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "ACNE STUDIOS rev 01",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 185,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Brunello cucinelli",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 186,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "TWINSET",
|
|
||||||
"Descrizione": "Schema per tutti i campioni di qualsiasi matrice escluso cuoio\/pelle\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 187,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "LORO PIANA",
|
|
||||||
"Descrizione": "Schema per tutti i campioni Loro Piana\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 188,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "LEMAIRE",
|
|
||||||
"Descrizione": "Schema per tutti i campioni in cuoio\/pelle per i quali non \u00e8 specificato un destinatario.\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 189,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "TEST_IT_SCHEMA",
|
|
||||||
"Descrizione": "Test per IT"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 190,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "LBS Interscambio",
|
|
||||||
"Descrizione": "Schema per tutti i campioni interscambio LBS\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 191,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "KIABI",
|
|
||||||
"Descrizione": "Schema per il cliente Kiabi\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 192,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "INDITEX",
|
|
||||||
"Descrizione": "Schema da usare solo per richieste INDITEX"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 193,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "AQC_Other materials",
|
|
||||||
"Descrizione": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 194,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Hermes ADM & Silk",
|
|
||||||
"Descrizione": "SCHEMA DA USARE SOLO PER LA DIVISIONE ADM & SILK\r\n\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 195,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Chloe'_APPAREL",
|
|
||||||
"Descrizione": "Schema aggiornato il 18\/12\/25 secondo modulo excel x codifica Elims\r\nGR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 196,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "DUNHILL_DRAFT NON USARE",
|
|
||||||
"Descrizione": "SCHEMA DUNHILL NON USARE STUDIO NUOVA PROCEDURA NOVEMBRE 2025\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 197,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "BERLUTI - LVMH",
|
|
||||||
"Descrizione": "Creato per accettazione Elims\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 198,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "PIAGET (gruppo Cartier)",
|
|
||||||
"Descrizione": "Schema per tutti i campioni PIAGET (gruppo Cartier)\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 199,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "HERMES PAP FEMME",
|
|
||||||
"Descrizione": "Schema per tutti i campioni di HERMES PAP FEMME \r\n15\/01\/2025\r\n\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 200,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "CARHARTT ( provvisiorio in revisione)",
|
|
||||||
"Descrizione": "Schema per tutti i campioni CARHARTT provvissorio\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 201,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "ROSSIMODA",
|
|
||||||
"Descrizione": "Per tutti i campioni di ROSSIMODA\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 202,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "LIMS-CIM - MAX MARA",
|
|
||||||
"Descrizione": "Schema per MAX MARA scambio dati Database"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 203,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Vince",
|
|
||||||
"Descrizione": "Schema per tutti i campioni di VINCE\r\n\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 204,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Max Mara",
|
|
||||||
"Descrizione": "Schema da usare per Max Mara\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 205,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "Chanel Flammability",
|
|
||||||
"Descrizione": "Schema per Chanel Flammability\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 206,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "COLDWATER CREEK",
|
|
||||||
"Descrizione": "\r\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"IdSchemaCustomFields": 208,
|
|
||||||
"ConteggioClienti": 0,
|
|
||||||
"Nome": "ETON",
|
|
||||||
"Descrizione": "Schema da usare per ETON\r\n"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -51,7 +51,7 @@ try {
|
|||||||
// Load from cache or API
|
// Load from cache or API
|
||||||
$cacheFile = __DIR__ . '/cache/clienti.json';
|
$cacheFile = __DIR__ . '/cache/clienti.json';
|
||||||
|
|
||||||
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 7200)) {
|
||||||
$data = json_decode(file_get_contents($cacheFile), true);
|
$data = json_decode(file_get_contents($cacheFile), true);
|
||||||
} else {
|
} else {
|
||||||
$api = VisualLimsApiClient::getInstance();
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ try {
|
|||||||
$cacheDir = __DIR__ . '/cache';
|
$cacheDir = __DIR__ . '/cache';
|
||||||
$cacheFile = $cacheDir . '/customfield_' . $fieldId . '.json';
|
$cacheFile = $cacheDir . '/customfield_' . $fieldId . '.json';
|
||||||
|
|
||||||
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 7200)) {
|
||||||
$values = json_decode(file_get_contents($cacheFile), true);
|
$values = json_decode(file_get_contents($cacheFile), true);
|
||||||
} else {
|
} else {
|
||||||
$api = VisualLimsApiClient::getInstance();
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||||
|
require_once __DIR__ . '/class/db-functions.php';
|
||||||
|
require_once __DIR__ . '/class/binding-functions.php';
|
||||||
|
include dirname(__DIR__) . '/../extra/auth.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
if (!Auth::check()) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['error' => 'Unauthorized']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fieldKey = trim($_GET['field_key'] ?? '');
|
||||||
|
$templateId = (int) ($_GET['template_id'] ?? 0);
|
||||||
|
$q = binding_normalize_value($_GET['q'] ?? '');
|
||||||
|
$rawLimit = (int) ($_GET['limit'] ?? 50);
|
||||||
|
$limit = $rawLimit <= 0 ? 0 : max(1, min(500, $rawLimit));
|
||||||
|
|
||||||
|
if ($fieldKey === '' || !binding_fixed_is_list($fieldKey)) {
|
||||||
|
echo json_encode(['results' => []]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
$values = binding_get_fixed_values($pdo, $fieldKey, $templateId);
|
||||||
|
|
||||||
|
if ($q !== '') {
|
||||||
|
$terms = array_values(array_filter(preg_split('/\s+/u', $q, -1, PREG_SPLIT_NO_EMPTY)));
|
||||||
|
$values = array_values(array_filter($values, function ($v) use ($terms) {
|
||||||
|
$hay = binding_normalize_value($v['text'] ?? '');
|
||||||
|
foreach ($terms as $t) {
|
||||||
|
if (mb_strpos($hay, $t) === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($limit > 0) {
|
||||||
|
$values = array_slice($values, 0, $limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['results' => $values]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage(), 'results' => []]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
<?php
|
||||||
|
include('include/headscript.php');
|
||||||
|
require_once __DIR__ . '/class/Mailer.class.php';
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Risolve [IdCliente => Nominativo] leggendo la stessa cache di search_clienti.php.
|
||||||
|
* Non chiama l'API: usa cache/clienti.json (già mantenuto aggiornato dalla ricerca clienti).
|
||||||
|
*/
|
||||||
|
function resolveClientNames(array $ids): array
|
||||||
|
{
|
||||||
|
$ids = array_values(array_filter(array_map('intval', $ids), fn($v) => $v > 0));
|
||||||
|
if (empty($ids)) return [];
|
||||||
|
|
||||||
|
$cacheFile = __DIR__ . '/cache/clienti.json';
|
||||||
|
if (!file_exists($cacheFile)) return [];
|
||||||
|
|
||||||
|
$data = json_decode((string)@file_get_contents($cacheFile), true);
|
||||||
|
$clients = $data['value'] ?? [];
|
||||||
|
if (empty($clients)) return [];
|
||||||
|
|
||||||
|
// indicizza per IdCliente
|
||||||
|
$byId = [];
|
||||||
|
foreach ($clients as $c) {
|
||||||
|
$cid = (int)($c['IdCliente'] ?? 0);
|
||||||
|
if ($cid > 0) {
|
||||||
|
$byId[$cid] = trim((string)($c['Nominativo'] ?? ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$out = [];
|
||||||
|
foreach ($ids as $id) {
|
||||||
|
if (isset($byId[$id]) && $byId[$id] !== '') {
|
||||||
|
$out[$id] = $byId[$id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
// input: solo POST (endpoint che invia email — niente attivazione via URL)
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Metodo non consentito']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$ids = $_POST['ids'] ?? [];
|
||||||
|
if (!is_array($ids)) $ids = explode(',', (string)$ids);
|
||||||
|
$ids = array_values(array_filter(array_map('intval', $ids), fn($v) => $v > 0));
|
||||||
|
$templateId = (int)($_POST['template_id'] ?? 0);
|
||||||
|
|
||||||
|
if (empty($ids)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Nessun id']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: se il template_id non arriva, ricavalo dalla prima commessa
|
||||||
|
if ($templateId <= 0) {
|
||||||
|
$tq = $pdo->prepare("SELECT templateid FROM datadb WHERE iddatadb = ? LIMIT 1");
|
||||||
|
$tq->execute([$ids[0]]);
|
||||||
|
$templateId = (int)$tq->fetchColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
// tutti i main field del template (possono essere fino a 2), in ordine
|
||||||
|
$mainMappingIds = [];
|
||||||
|
$templateName = '';
|
||||||
|
if ($templateId > 0) {
|
||||||
|
$s = $pdo->prepare("
|
||||||
|
SELECT id FROM template_mapping
|
||||||
|
WHERE template_id = ? AND main_field = '1' AND is_visible_import = 1
|
||||||
|
ORDER BY field_order ASC, id ASC
|
||||||
|
");
|
||||||
|
$s->execute([$templateId]);
|
||||||
|
$mainMappingIds = array_map('intval', $s->fetchAll(PDO::FETCH_COLUMN));
|
||||||
|
|
||||||
|
// nome del template per l'intestazione mail
|
||||||
|
$t = $pdo->prepare("SELECT name FROM excel_templates WHERE id = ? LIMIT 1");
|
||||||
|
$t->execute([$templateId]);
|
||||||
|
$templateName = (string)$t->fetchColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
// esiti delle commesse
|
||||||
|
$ph = implode(',', array_fill(0, count($ids), '?'));
|
||||||
|
$sql = "
|
||||||
|
SELECT d.iddatadb, d.commessaweb, d.status, d.user_id,
|
||||||
|
d.idclient, d.ClienteFatturazione,
|
||||||
|
CONCAT(u.first_name,' ',u.last_name) AS user_name,
|
||||||
|
u.email AS user_email, u.notify_export_email
|
||||||
|
FROM datadb d
|
||||||
|
LEFT JOIN auth_users u ON d.user_id = u.id
|
||||||
|
WHERE d.iddatadb IN ($ph)
|
||||||
|
";
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute($ids);
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
if (empty($rows)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Righe non trovate']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Risoluzione nomi cliente (idclient + ClienteFatturazione) ---
|
||||||
|
$clientIdsToResolve = [];
|
||||||
|
foreach ($rows as $r) {
|
||||||
|
if (!empty($r['idclient'])) $clientIdsToResolve[(int)$r['idclient']] = true;
|
||||||
|
if (!empty($r['ClienteFatturazione'])) $clientIdsToResolve[(int)$r['ClienteFatturazione']] = true;
|
||||||
|
}
|
||||||
|
$clientNames = resolveClientNames(array_keys($clientIdsToResolve)); // [id => nome]
|
||||||
|
|
||||||
|
// helper: id cliente → nome, con fallback all'id se non risolto
|
||||||
|
$clientLabel = function ($id) use ($clientNames) {
|
||||||
|
$id = (int)$id;
|
||||||
|
if ($id <= 0) return '—';
|
||||||
|
return htmlspecialchars($clientNames[$id] ?? (string)$id);
|
||||||
|
};
|
||||||
|
|
||||||
|
// valori dei main field per ogni iddatadb — concatena i main valorizzati
|
||||||
|
$mainValues = [];
|
||||||
|
if (!empty($mainMappingIds)) {
|
||||||
|
$mph = implode(',', array_fill(0, count($mainMappingIds), '?'));
|
||||||
|
$s = $pdo->prepare("
|
||||||
|
SELECT id AS iddatadb, mapping_id, field_value
|
||||||
|
FROM import_data_details
|
||||||
|
WHERE id IN ($ph) AND mapping_id IN ($mph)
|
||||||
|
");
|
||||||
|
$s->execute(array_merge($ids, $mainMappingIds));
|
||||||
|
|
||||||
|
// raggruppa per iddatadb, mantenendo l'ordine dei main
|
||||||
|
$byRow = []; // iddatadb => [mapping_id => value]
|
||||||
|
foreach ($s->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
||||||
|
$byRow[(int)$r['iddatadb']][(int)$r['mapping_id']] = $r['field_value'];
|
||||||
|
}
|
||||||
|
foreach ($byRow as $idd => $vals) {
|
||||||
|
$parts = [];
|
||||||
|
foreach ($mainMappingIds as $mid) { // ordine dei main
|
||||||
|
$v = trim((string)($vals[$mid] ?? ''));
|
||||||
|
if ($v !== '') $parts[] = $v;
|
||||||
|
}
|
||||||
|
$mainValues[$idd] = implode(' — ', $parts); // es. "descrizione — style code"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// analisi senza prezzo (NULL o 0) per ogni iddatadb
|
||||||
|
$noPriceByRow = []; // iddatadb => [ [part_number, part_description, analysis_name], ... ]
|
||||||
|
$s = $pdo->prepare("
|
||||||
|
SELECT a.iddatadb, MAX(a.analysis_name) AS analysis_name
|
||||||
|
FROM identification_parts_analyses a
|
||||||
|
WHERE a.iddatadb IN ($ph)
|
||||||
|
AND (a.analysis_price IS NULL OR a.analysis_price <= 0)
|
||||||
|
GROUP BY a.iddatadb, a.analysis_recordkey
|
||||||
|
ORDER BY a.iddatadb, analysis_name
|
||||||
|
");
|
||||||
|
$s->execute($ids);
|
||||||
|
foreach ($s->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
||||||
|
$noPriceByRow[(int)$r['iddatadb']][] = $r;
|
||||||
|
}
|
||||||
|
$noPriceTotal = array_sum(array_map('count', $noPriceByRow));
|
||||||
|
|
||||||
|
// destinatari: utente/i dell'export che hanno la notifica attiva + admin fisso
|
||||||
|
$recipients = [];
|
||||||
|
foreach ($rows as $r) {
|
||||||
|
if (!empty($r['user_email']) && (int)($r['notify_export_email'] ?? 1) === 1) {
|
||||||
|
$recipients[$r['user_email']] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$adminEmail = env('EXPORT_SUMMARY_ADMIN', 'admin@cesoft.io'); // sempre, indipendente dalla preferenza
|
||||||
|
if ($adminEmail) $recipients[$adminEmail] = true;
|
||||||
|
$recipients = array_keys($recipients);
|
||||||
|
|
||||||
|
// Se dopo il filtro non resta nessuno, non inviare
|
||||||
|
if (empty($recipients)) {
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Nessun destinatario (notifiche disattivate)', 'recipients' => []]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// conteggi
|
||||||
|
$okCount = 0;
|
||||||
|
$errCount = 0;
|
||||||
|
foreach ($rows as $r) {
|
||||||
|
if ($r['status'] === 'l') $okCount++;
|
||||||
|
elseif ($r['status'] === 'x') $errCount++;
|
||||||
|
}
|
||||||
|
$hasError = $errCount > 0;
|
||||||
|
|
||||||
|
// --- tabella righe ---
|
||||||
|
$rowsHtml = '';
|
||||||
|
foreach ($rows as $r) {
|
||||||
|
$st = $r['status'];
|
||||||
|
if ($st === 'l') {
|
||||||
|
$label = 'Inviata';
|
||||||
|
$bg = '#e6f4ea';
|
||||||
|
$col = '#1e7e34';
|
||||||
|
} elseif ($st === 'x') {
|
||||||
|
$label = 'ERRORE';
|
||||||
|
$bg = '#fdecea';
|
||||||
|
$col = '#c0392b';
|
||||||
|
} else {
|
||||||
|
$label = htmlspecialchars((string)$st);
|
||||||
|
$bg = '#fff8e1';
|
||||||
|
$col = '#8a6d3b';
|
||||||
|
}
|
||||||
|
|
||||||
|
$commessa = htmlspecialchars($r['commessaweb'] ?: '—');
|
||||||
|
$main = htmlspecialchars($mainValues[(int)$r['iddatadb']] ?? '—');
|
||||||
|
|
||||||
|
$committente = $clientLabel($r['idclient'] ?? 0);
|
||||||
|
$fatturazione = $clientLabel($r['ClienteFatturazione'] ?? 0);
|
||||||
|
|
||||||
|
$rowsHtml .= "<tr>
|
||||||
|
<td style='padding:10px 10px;border-bottom:1px solid #eee;font-family:monospace;white-space:nowrap;vertical-align:top;'>{$commessa}</td>
|
||||||
|
<td style='padding:10px 10px;border-bottom:1px solid #eee;word-break:break-word;vertical-align:top;'>{$committente}</td>
|
||||||
|
<td style='padding:10px 10px;border-bottom:1px solid #eee;word-break:break-word;vertical-align:top;'>{$fatturazione}</td>
|
||||||
|
<td style='padding:10px 10px;border-bottom:1px solid #eee;word-break:break-word;vertical-align:top;'>{$main}</td>
|
||||||
|
<td style='padding:10px 10px;border-bottom:1px solid #eee;text-align:center;white-space:nowrap;vertical-align:top;'>
|
||||||
|
<span style='display:inline-block;padding:3px 10px;border-radius:12px;background:{$bg};color:{$col};font-weight:600;font-size:13px;'>{$label}</span>
|
||||||
|
</td>
|
||||||
|
</tr>";
|
||||||
|
|
||||||
|
// sotto-riga: solo le analisi No Price di questa commessa
|
||||||
|
$np = $noPriceByRow[(int)$r['iddatadb']] ?? [];
|
||||||
|
if (!empty($np)) {
|
||||||
|
$items = '';
|
||||||
|
foreach ($np as $n) {
|
||||||
|
$analysisName = htmlspecialchars((string)($n['analysis_name'] ?: 'Unnamed analysis'));
|
||||||
|
$items .= "<li style='margin:2px 0;'>{$analysisName}</li>";
|
||||||
|
}
|
||||||
|
$npCount = count($np);
|
||||||
|
|
||||||
|
$rowsHtml .= "<tr>
|
||||||
|
<td colspan='5' style='padding:8px 12px 12px 24px;border-bottom:1px solid #eee;background:#fff5f5;'>
|
||||||
|
<div style='margin-bottom:4px;font-size:12px;color:#c0392b;font-weight:600;'>
|
||||||
|
<span style='display:inline-block;padding:2px 8px;border-radius:10px;background:#c0392b;color:#fff;font-size:11px;'>No price</span>
|
||||||
|
Analisi senza prezzo ({$npCount})
|
||||||
|
</div>
|
||||||
|
<ul style='margin:0;padding-left:18px;font-size:13px;color:#555;'>{$items}</ul>
|
||||||
|
</td>
|
||||||
|
</tr>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$headerColor = $hasError ? '#c0392b' : '#1e7e34';
|
||||||
|
$headerText = $hasError ? 'Export completato con errori' : 'Export completato con successo';
|
||||||
|
$total = count($rows);
|
||||||
|
|
||||||
|
$html = "
|
||||||
|
<div style='font-family:Arial,Helvetica,sans-serif;max-width:820px;margin:0 auto;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden;'>
|
||||||
|
<div style='background:{$headerColor};color:#fff;padding:18px 24px;'>
|
||||||
|
<h2 style='margin:0;font-size:18px;'>{$headerText}</h2>
|
||||||
|
<p style='margin:6px 0 0;font-size:13px;opacity:.9;'>Riepilogo esportazione verso LIMS" .
|
||||||
|
($templateName !== '' ? " · Template: <strong>" . htmlspecialchars($templateName) . "</strong>" : "") . "</p>
|
||||||
|
<p style='margin:6px 0 0;font-size:13px;opacity:.95;'>
|
||||||
|
Committente: <strong>" . $clientLabel($rows[0]['idclient'] ?? 0) . "</strong>
|
||||||
|
·
|
||||||
|
FatturazioneAvanzata: <strong>" . $clientLabel($rows[0]['ClienteFatturazione'] ?? 0) . "</strong>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style='padding:20px 24px;'>
|
||||||
|
<p style='margin:0 0 14px;font-size:14px;color:#333;'>
|
||||||
|
Totale commesse: <strong>{$total}</strong> •
|
||||||
|
<span style='color:#1e7e34;'>Inviate: {$okCount}</span> •
|
||||||
|
<span style='color:#c0392b;'>Errori: {$errCount}</span>" .
|
||||||
|
($noPriceTotal > 0 ? " • <span style='color:#c0392b;'>Analisi No price: {$noPriceTotal}</span>" : "") . "
|
||||||
|
</p>
|
||||||
|
<table style='width:100%;border-collapse:collapse;font-size:14px;table-layout:fixed;'>
|
||||||
|
<thead>
|
||||||
|
<tr style='background:#f5f5f5;'>
|
||||||
|
<th style='padding:10px 10px;text-align:left;border-bottom:2px solid #ddd;width:14%;'>Commessa</th>
|
||||||
|
<th style='padding:10px 10px;text-align:left;border-bottom:2px solid #ddd;width:24%;'>Committente</th>
|
||||||
|
<th style='padding:10px 10px;text-align:left;border-bottom:2px solid #ddd;width:24%;'>Fatturazione</th>
|
||||||
|
<th style='padding:10px 10px;text-align:left;border-bottom:2px solid #ddd;width:24%;'>Riferimento</th>
|
||||||
|
<th style='padding:10px 10px;text-align:center;border-bottom:2px solid #ddd;width:14%;'>Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>{$rowsHtml}</tbody>
|
||||||
|
</table>
|
||||||
|
" . ($hasError ? "<p style='margin:18px 0 0;padding:12px;background:#fdecea;border-left:4px solid #c0392b;color:#c0392b;font-size:13px;border-radius:4px;'>⚠️ Alcune commesse non sono state inviate al LIMS. Controlla la sezione <strong>Errors</strong> nell'applicazione per i dettagli e per rilanciarle.</p>" : "") . "
|
||||||
|
</div>
|
||||||
|
<div style='background:#fafafa;padding:14px 24px;font-size:12px;color:#999;border-top:1px solid #eee;'>
|
||||||
|
Messaggio automatico da SmartTRF — non rispondere a questa email.
|
||||||
|
</div>
|
||||||
|
</div>";
|
||||||
|
|
||||||
|
$subjectPrefix = $hasError ? '⚠️ Errori Export LIMS' : '✅ Export LIMS OK';
|
||||||
|
$subject = "{$subjectPrefix} — {$total} commesse ({$okCount} ok, {$errCount} err)";
|
||||||
|
|
||||||
|
$res = Mailer::send($recipients, $subject, $html);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => $res['ok'],
|
||||||
|
'message' => $res['ok'] ? 'Mail inviata' : ('Mail non inviata: ' . $res['error']),
|
||||||
|
'recipients' => $recipients,
|
||||||
|
'counts' => ['ok' => $okCount, 'err' => $errCount, 'total' => $total],
|
||||||
|
]);
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/include/headscript.php'; // autentica e dà $iduserlogin
|
||||||
|
require_once __DIR__ . '/class/db-functions.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
"UPDATE auth_sessions SET last_activity = ? WHERE user_id = ? ORDER BY last_activity DESC LIMIT 1"
|
||||||
|
);
|
||||||
|
$stmt->execute([time(), $iduserlogin]);
|
||||||
|
echo json_encode(['ok' => true, 'rows' => $stmt->rowCount(), 'ts' => time()]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo json_encode(['ok' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||||
|
require_once __DIR__ . '/class/db-functions.php';
|
||||||
|
require_once __DIR__ . '/class/binding-functions.php';
|
||||||
|
include dirname(__DIR__) . '/../extra/auth.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
if (!Auth::check()) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Method not allowed']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$kind = ($_POST['kind'] ?? 'custom') === 'fixed' ? 'fixed' : 'custom';
|
||||||
|
$mappingId = intval($_POST['mapping_id'] ?? 0);
|
||||||
|
$sourceValue = (string) ($_POST['source_value'] ?? '');
|
||||||
|
|
||||||
|
$datadbIds = [];
|
||||||
|
if (isset($_POST['datadb_ids'])) {
|
||||||
|
$decoded = json_decode($_POST['datadb_ids'], true);
|
||||||
|
if (is_array($decoded)) {
|
||||||
|
$datadbIds = $decoded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($mappingId <= 0 || trim($sourceValue) === '') {
|
||||||
|
http_response_code(422);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Missing required parameters']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
if ($kind === 'fixed') {
|
||||||
|
$stmt = $pdo->prepare("SELECT fixed_field_key FROM template_fixed_mapping WHERE id = ?");
|
||||||
|
$stmt->execute([$mappingId]);
|
||||||
|
$fixedKey = (string) ($stmt->fetchColumn() ?: '');
|
||||||
|
|
||||||
|
if ($fixedKey === '') {
|
||||||
|
http_response_code(422);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Invalid fixed field']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cleared = !empty($datadbIds)
|
||||||
|
? binding_apply_to_datadb($pdo, $fixedKey, null, $datadbIds)
|
||||||
|
: 0;
|
||||||
|
} else {
|
||||||
|
$cleared = !empty($datadbIds)
|
||||||
|
? binding_apply_to_details($pdo, $mappingId, '', $datadbIds)
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
binding_delete($pdo, $mappingId, $sourceValue, $kind);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'cleared_rows' => $cleared]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
-- json_node e default_source possono gia' esistere: in quel caso saltare le ADD COLUMN.
|
||||||
|
|
||||||
|
ALTER TABLE `template_fixed_mapping`
|
||||||
|
ADD COLUMN `json_node` varchar(500) DEFAULT NULL,
|
||||||
|
ADD COLUMN `default_source` enum('manual','xls','json') NOT NULL DEFAULT 'manual';
|
||||||
|
|
||||||
|
ALTER TABLE `template_fixed_mapping`
|
||||||
|
ADD COLUMN `excel_column` varchar(255) DEFAULT NULL AFTER `json_node`;
|
||||||
|
|
||||||
|
ALTER TABLE `template_fixed_mapping`
|
||||||
|
MODIFY COLUMN `default_source` enum('manual','xls','json') NOT NULL DEFAULT 'manual';
|
||||||
|
|
||||||
|
ALTER TABLE `lims_value_binding`
|
||||||
|
ADD COLUMN `binding_kind` enum('custom','fixed') NOT NULL DEFAULT 'custom' AFTER `template_id`,
|
||||||
|
MODIFY COLUMN `field_id` int(11) NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE `lims_value_binding` DROP INDEX `uq_mapping_source`;
|
||||||
|
ALTER TABLE `lims_value_binding`
|
||||||
|
ADD UNIQUE KEY `uq_kind_mapping_source` (`binding_kind`, `mapping_id`, `source_key`);
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `lims_value_binding` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`template_id` int(11) NOT NULL,
|
||||||
|
`mapping_id` int(11) NOT NULL,
|
||||||
|
`field_id` int(11) NOT NULL,
|
||||||
|
`source_value` varchar(500) NOT NULL,
|
||||||
|
`source_key` varchar(500) NOT NULL,
|
||||||
|
`lims_value_id` int(11) NOT NULL,
|
||||||
|
`lims_value` varchar(500) DEFAULT NULL,
|
||||||
|
`created_by` int(11) DEFAULT NULL,
|
||||||
|
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uq_mapping_source` (`mapping_id`, `source_key`),
|
||||||
|
KEY `idx_template` (`template_id`),
|
||||||
|
KEY `idx_field_source` (`field_id`, `source_key`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
+284
-82
@@ -227,8 +227,9 @@ function fixedDefaultValue(array $f): string
|
|||||||
// ── Build lookup maps: id → label for fixed fields ──
|
// ── Build lookup maps: id → label for fixed fields ──
|
||||||
$fixedLookup = []; // e.g. $fixedLookup['MoltiplicatorePrezzo'][2] = 'Urgente (1.5x)'
|
$fixedLookup = []; // e.g. $fixedLookup['MoltiplicatorePrezzo'][2] = 'Urgente (1.5x)'
|
||||||
|
|
||||||
function loadCacheJson(string $path): ?array {
|
function loadCacheJson(string $path): ?array
|
||||||
if (file_exists($path) && (time() - filemtime($path) < 3600)) {
|
{
|
||||||
|
if (file_exists($path) && (time() - filemtime($path) < 7200)) {
|
||||||
return json_decode(file_get_contents($path), true);
|
return json_decode(file_get_contents($path), true);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -278,7 +279,8 @@ foreach ($clienteIds as $cid) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper: resolve fixed field id → label
|
// Helper: resolve fixed field id → label
|
||||||
function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
function resolveFixedValue(string $key, $val, array $fixedLookup): string
|
||||||
|
{
|
||||||
if ($val === '' || $val === null) return '';
|
if ($val === '' || $val === null) return '';
|
||||||
if (isset($fixedLookup[$key][(int)$val])) return $fixedLookup[$key][(int)$val];
|
if (isset($fixedLookup[$key][(int)$val])) return $fixedLookup[$key][(int)$val];
|
||||||
return (string)$val;
|
return (string)$val;
|
||||||
@@ -649,6 +651,7 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
.grid-top {
|
.grid-top {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-top-original {
|
.grid-top-original {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
@@ -819,12 +822,12 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
padding: 6px 8px;
|
padding: 6px 4px;
|
||||||
margin-right: 5px;
|
margin-right: 3px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 35px;
|
width: 26px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -952,15 +955,35 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Hide grid-top (propagate row) */
|
/* Hide grid-top (propagate row) */
|
||||||
.grid-top { display: none !important; }
|
.grid-top {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Text cells styling */
|
/* Text cells styling */
|
||||||
.grid-row .grid-cell span { font-size: 12px; padding: 2px 4px; }
|
.grid-row .grid-cell span {
|
||||||
.propagate-btn { display: none !important; }
|
font-size: 12px;
|
||||||
.button-cell { flex: 0 0 120px !important; }
|
padding: 2px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.propagate-btn {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-cell {
|
||||||
|
flex: 0 0 120px !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* View modals — ensure they sit above backdrop */
|
/* View modals — ensure they sit above backdrop */
|
||||||
#partsViewModal, #photosViewModal { z-index: 1060 !important; }
|
#partsViewModal,
|
||||||
.modal-backdrop { z-index: 1050 !important; }
|
#photosViewModal,
|
||||||
|
#revertStatusModal {
|
||||||
|
z-index: 1060 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-backdrop {
|
||||||
|
z-index: 1050 !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Grid filter row ── */
|
/* ── Grid filter row ── */
|
||||||
.grid-filter-row {
|
.grid-filter-row {
|
||||||
background: #f0f4f8 !important;
|
background: #f0f4f8 !important;
|
||||||
@@ -969,12 +992,15 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
top: 0;
|
top: 0;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-filter-row:hover {
|
.grid-filter-row:hover {
|
||||||
background: #f0f4f8 !important;
|
background: #f0f4f8 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-filter-row .grid-cell {
|
.grid-filter-row .grid-cell {
|
||||||
padding: 4px 6px !important;
|
padding: 4px 6px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-filter-input {
|
.grid-filter-input {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
padding: 3px 6px !important;
|
padding: 3px 6px !important;
|
||||||
@@ -985,30 +1011,36 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
color: #333 !important;
|
color: #333 !important;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-filter-input:focus {
|
.grid-filter-input:focus {
|
||||||
border-color: #80bdff !important;
|
border-color: #80bdff !important;
|
||||||
box-shadow: 0 0 0 2px rgba(0,123,255,.15) !important;
|
box-shadow: 0 0 0 2px rgba(0, 123, 255, .15) !important;
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-filter-input::placeholder {
|
.grid-filter-input::placeholder {
|
||||||
color: #adb5bd;
|
color: #adb5bd;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-filter-row .grid-filter-input.has-value {
|
.grid-filter-row .grid-filter-input.has-value {
|
||||||
border-color: #0d6efd !important;
|
border-color: #0d6efd !important;
|
||||||
background: #f0f7ff !important;
|
background: #f0f7ff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
select.grid-filter-input {
|
select.grid-filter-input {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding-right: 18px !important;
|
padding-right: 18px !important;
|
||||||
appearance: auto;
|
appearance: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Select2 inside filter row */
|
/* Select2 inside filter row */
|
||||||
.grid-filter-row .select2-container {
|
.grid-filter-row .select2-container {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-filter-row .select2-container .select2-selection--single {
|
.grid-filter-row .select2-container .select2-selection--single {
|
||||||
height: 26px !important;
|
height: 26px !important;
|
||||||
min-height: 26px !important;
|
min-height: 26px !important;
|
||||||
@@ -1020,6 +1052,7 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-filter-row .select2-container .select2-selection__rendered {
|
.grid-filter-row .select2-container .select2-selection__rendered {
|
||||||
line-height: 1 !important;
|
line-height: 1 !important;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
@@ -1029,32 +1062,39 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-filter-row .select2-container .select2-selection__arrow {
|
.grid-filter-row .select2-container .select2-selection__arrow {
|
||||||
height: 26px !important;
|
height: 26px !important;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 2px;
|
right: 2px;
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-filter-row .filter-wrap {
|
.grid-filter-row .filter-wrap {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide our clear button when Select2 is active — Select2 has its own allowClear */
|
/* Hide our clear button when Select2 is active — Select2 has its own allowClear */
|
||||||
.filter-wrap:has(.select2-container) .filter-clear-btn {
|
.filter-wrap:has(.select2-container) .filter-clear-btn {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-filter-row .grid-cell {
|
.grid-filter-row .grid-cell {
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-wrap {
|
.filter-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-wrap .grid-filter-input {
|
.filter-wrap .grid-filter-input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-clear-btn {
|
.filter-clear-btn {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -1066,12 +1106,15 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-clear-btn:hover {
|
.filter-clear-btn:hover {
|
||||||
background: #c82333;
|
background: #c82333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-filter-row .grid-cell {
|
.grid-filter-row .grid-cell {
|
||||||
background: #f0f4f8 !important;
|
background: #f0f4f8 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* First sticky cell fills full row height */
|
/* First sticky cell fills full row height */
|
||||||
.grid-filter-row .grid-cell.button-cell {
|
.grid-filter-row .grid-cell.button-cell {
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
@@ -1089,21 +1132,25 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #495057;
|
color: #495057;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pager-rows-per-page {
|
.pager-rows-per-page {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pager-label {
|
.pager-label {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pager-limit-group {
|
.pager-limit-group {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
border: 1px solid #ced4da;
|
border: 1px solid #ced4da;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pager-limit-btn {
|
.pager-limit-btn {
|
||||||
padding: 3px 10px;
|
padding: 3px 10px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@@ -1112,24 +1159,41 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
transition: background .15s, color .15s;
|
transition: background .15s, color .15s;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
.pager-limit-btn:last-child { border-right: none; }
|
|
||||||
.pager-limit-btn:hover { background: #e9ecef; text-decoration: none; color: #212529; }
|
.pager-limit-btn:last-child {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pager-limit-btn:hover {
|
||||||
|
background: #e9ecef;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
|
||||||
.pager-limit-btn.active {
|
.pager-limit-btn.active {
|
||||||
background: #0d6efd;
|
background: #0d6efd;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.pager-limit-btn.active:hover { background: #0b5ed7; color: #fff; }
|
|
||||||
|
.pager-limit-btn.active:hover {
|
||||||
|
background: #0b5ed7;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.pager-nav {
|
.pager-nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pager-info {
|
.pager-info {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.pager-btn, .pager-num {
|
|
||||||
|
.pager-btn,
|
||||||
|
.pager-num {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -1143,16 +1207,25 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition: background .15s, color .15s, border-color .15s;
|
transition: background .15s, color .15s, border-color .15s;
|
||||||
}
|
}
|
||||||
.pager-btn:hover, .pager-num:hover { background: #e9ecef; text-decoration: none; color: #212529; }
|
|
||||||
|
.pager-btn:hover,
|
||||||
|
.pager-num:hover {
|
||||||
|
background: #e9ecef;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
|
||||||
.pager-num.active {
|
.pager-num.active {
|
||||||
background: #0d6efd;
|
background: #0d6efd;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border-color: #0d6efd;
|
border-color: #0d6efd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pager-btn.disabled {
|
.pager-btn.disabled {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: .4;
|
opacity: .4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pager-dots {
|
.pager-dots {
|
||||||
padding: 0 2px;
|
padding: 0 2px;
|
||||||
color: #adb5bd;
|
color: #adb5bd;
|
||||||
@@ -1170,7 +1243,8 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
<div class="mb-3 text d-flex align-items-center gap-2">
|
<div class="mb-3 text d-flex align-items-center gap-2">
|
||||||
<a href="imported.php?id=<?= $template_id ?>" class="btn btn-warning me-2">Imported (i)</a>
|
<a href="imported.php?id=<?= $template_id ?>" class="btn btn-warning me-2">Imported (i)</a>
|
||||||
<a href="tolims.php?id=<?= $template_id ?>" class="btn btn-success">To LIMS (l)</a>
|
<a href="tolims.php?id=<?= $template_id ?>" class="btn btn-success me-2">To LIMS (l)</a>
|
||||||
|
<a href="errors.php?id=<?= $template_id ?>" class="btn btn-danger">Errors (x)</a>
|
||||||
<span class="ms-3">
|
<span class="ms-3">
|
||||||
<label class="form-check-label" style="font-size: 13px; cursor: pointer;">
|
<label class="form-check-label" style="font-size: 13px; cursor: pointer;">
|
||||||
<?php
|
<?php
|
||||||
@@ -1188,48 +1262,48 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<?php
|
<?php
|
||||||
$baseQuery = $_GET;
|
$baseQuery = $_GET;
|
||||||
unset($baseQuery['limit'], $baseQuery['page']);
|
unset($baseQuery['limit'], $baseQuery['page']);
|
||||||
$pageQuery = $_GET;
|
$pageQuery = $_GET;
|
||||||
unset($pageQuery['page']);
|
unset($pageQuery['page']);
|
||||||
$pageBase = 'tolims.php?' . http_build_query($pageQuery);
|
$pageBase = 'tolims.php?' . http_build_query($pageQuery);
|
||||||
$fromRow = ($page - 1) * $perPage + 1;
|
$fromRow = ($page - 1) * $perPage + 1;
|
||||||
$toRow = min($page * $perPage, $totalRows);
|
$toRow = min($page * $perPage, $totalRows);
|
||||||
?>
|
?>
|
||||||
<div class="pager-bar mb-2">
|
<div class="pager-bar mb-2">
|
||||||
<div class="pager-rows-per-page">
|
<div class="pager-rows-per-page">
|
||||||
<span class="pager-label">Rows per page</span>
|
<span class="pager-label">Rows per page</span>
|
||||||
<div class="pager-limit-group">
|
<div class="pager-limit-group">
|
||||||
<?php foreach ($allowedLimits as $lim):
|
<?php foreach ($allowedLimits as $lim):
|
||||||
$isActive = ($perPage === $lim);
|
$isActive = ($perPage === $lim);
|
||||||
$url = 'tolims.php?' . http_build_query(array_merge($baseQuery, ['limit' => $lim]));
|
$url = 'tolims.php?' . http_build_query(array_merge($baseQuery, ['limit' => $lim]));
|
||||||
?>
|
?>
|
||||||
<a href="<?= htmlspecialchars($url) ?>" class="pager-limit-btn <?= $isActive ? 'active' : '' ?>"><?= $lim ?></a>
|
<a href="<?= htmlspecialchars($url) ?>" class="pager-limit-btn <?= $isActive ? 'active' : '' ?>"><?= $lim ?></a>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php if ($totalPages > 1): ?>
|
<?php if ($totalPages > 1): ?>
|
||||||
<div class="pager-nav">
|
<div class="pager-nav">
|
||||||
<span class="pager-info"><?= $fromRow ?>–<?= $toRow ?> of <?= $totalRows ?></span>
|
<span class="pager-info"><?= $fromRow ?>–<?= $toRow ?> of <?= $totalRows ?></span>
|
||||||
<a href="<?= htmlspecialchars($pageBase . '&page=1') ?>" class="pager-btn <?= $page <= 1 ? 'disabled' : '' ?>" title="First"><i class="fas fa-angle-double-left"></i></a>
|
<a href="<?= htmlspecialchars($pageBase . '&page=1') ?>" class="pager-btn <?= $page <= 1 ? 'disabled' : '' ?>" title="First"><i class="fas fa-angle-double-left"></i></a>
|
||||||
<a href="<?= htmlspecialchars($pageBase . '&page=' . ($page - 1)) ?>" class="pager-btn <?= $page <= 1 ? 'disabled' : '' ?>" title="Previous"><i class="fas fa-angle-left"></i></a>
|
<a href="<?= htmlspecialchars($pageBase . '&page=' . ($page - 1)) ?>" class="pager-btn <?= $page <= 1 ? 'disabled' : '' ?>" title="Previous"><i class="fas fa-angle-left"></i></a>
|
||||||
<?php
|
<?php
|
||||||
$startPage = max(1, $page - 2);
|
$startPage = max(1, $page - 2);
|
||||||
$endPage = min($totalPages, $page + 2);
|
$endPage = min($totalPages, $page + 2);
|
||||||
if ($startPage > 1): ?>
|
if ($startPage > 1): ?>
|
||||||
<a href="<?= htmlspecialchars($pageBase . '&page=1') ?>" class="pager-num">1</a>
|
<a href="<?= htmlspecialchars($pageBase . '&page=1') ?>" class="pager-num">1</a>
|
||||||
<?php if ($startPage > 2): ?><span class="pager-dots">...</span><?php endif; ?>
|
<?php if ($startPage > 2): ?><span class="pager-dots">...</span><?php endif; ?>
|
||||||
<?php endif;
|
<?php endif;
|
||||||
for ($p = $startPage; $p <= $endPage; $p++): ?>
|
for ($p = $startPage; $p <= $endPage; $p++): ?>
|
||||||
<a href="<?= htmlspecialchars($pageBase . '&page=' . $p) ?>" class="pager-num <?= $p === $page ? 'active' : '' ?>"><?= $p ?></a>
|
<a href="<?= htmlspecialchars($pageBase . '&page=' . $p) ?>" class="pager-num <?= $p === $page ? 'active' : '' ?>"><?= $p ?></a>
|
||||||
<?php endfor;
|
<?php endfor;
|
||||||
if ($endPage < $totalPages): ?>
|
if ($endPage < $totalPages): ?>
|
||||||
<?php if ($endPage < $totalPages - 1): ?><span class="pager-dots">...</span><?php endif; ?>
|
<?php if ($endPage < $totalPages - 1): ?><span class="pager-dots">...</span><?php endif; ?>
|
||||||
<a href="<?= htmlspecialchars($pageBase . '&page=' . $totalPages) ?>" class="pager-num"><?= $totalPages ?></a>
|
<a href="<?= htmlspecialchars($pageBase . '&page=' . $totalPages) ?>" class="pager-num"><?= $totalPages ?></a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<a href="<?= htmlspecialchars($pageBase . '&page=' . ($page + 1)) ?>" class="pager-btn <?= $page >= $totalPages ? 'disabled' : '' ?>" title="Next"><i class="fas fa-angle-right"></i></a>
|
<a href="<?= htmlspecialchars($pageBase . '&page=' . ($page + 1)) ?>" class="pager-btn <?= $page >= $totalPages ? 'disabled' : '' ?>" title="Next"><i class="fas fa-angle-right"></i></a>
|
||||||
<a href="<?= htmlspecialchars($pageBase . '&page=' . $totalPages) ?>" class="pager-btn <?= $page >= $totalPages ? 'disabled' : '' ?>" title="Last"><i class="fas fa-angle-double-right"></i></a>
|
<a href="<?= htmlspecialchars($pageBase . '&page=' . $totalPages) ?>" class="pager-btn <?= $page >= $totalPages ? 'disabled' : '' ?>" title="Last"><i class="fas fa-angle-double-right"></i></a>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="card radius-10">
|
<div class="card radius-10">
|
||||||
@@ -1512,14 +1586,16 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
<!-- Filter row (server-side) -->
|
<!-- Filter row (server-side) -->
|
||||||
<?php
|
<?php
|
||||||
// Helper: wrap filter element with clear button
|
// Helper: wrap filter element with clear button
|
||||||
function wrapFilter(string $inner, bool $hasValue): string {
|
function wrapFilter(string $inner, bool $hasValue): string
|
||||||
|
{
|
||||||
$btn = $hasValue
|
$btn = $hasValue
|
||||||
? "<button type='button' class='filter-clear-btn' onclick='clearOneFilter(this)' title='Clear'>×</button>"
|
? "<button type='button' class='filter-clear-btn' onclick='clearOneFilter(this)' title='Clear'>×</button>"
|
||||||
: '';
|
: '';
|
||||||
return "<div class='filter-wrap'>{$inner}{$btn}</div>";
|
return "<div class='filter-wrap'>{$inner}{$btn}</div>";
|
||||||
}
|
}
|
||||||
// Helper: render a select filter from a lookup array
|
// Helper: render a select filter from a lookup array
|
||||||
function renderFilterSelect(string $name, array $options, string $currentVal, string $style = ''): string {
|
function renderFilterSelect(string $name, array $options, string $currentVal, string $style = ''): string
|
||||||
|
{
|
||||||
$html = "<select class='grid-filter-input' name='" . htmlspecialchars($name) . "' style='" . htmlspecialchars($style) . "' onchange='submitFilters()'>";
|
$html = "<select class='grid-filter-input' name='" . htmlspecialchars($name) . "' style='" . htmlspecialchars($style) . "' onchange='submitFilters()'>";
|
||||||
$html .= "<option value=''>All</option>";
|
$html .= "<option value=''>All</option>";
|
||||||
foreach ($options as $id => $label) {
|
foreach ($options as $id => $label) {
|
||||||
@@ -1530,12 +1606,14 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
return wrapFilter($html, $currentVal !== '');
|
return wrapFilter($html, $currentVal !== '');
|
||||||
}
|
}
|
||||||
// Helper: render a text filter
|
// Helper: render a text filter
|
||||||
function renderFilterInput(string $name, string $currentVal): string {
|
function renderFilterInput(string $name, string $currentVal): string
|
||||||
|
{
|
||||||
$inner = "<input type='text' class='grid-filter-input' name='" . htmlspecialchars($name) . "' value='" . htmlspecialchars($currentVal) . "' placeholder='⌕'>";
|
$inner = "<input type='text' class='grid-filter-input' name='" . htmlspecialchars($name) . "' value='" . htmlspecialchars($currentVal) . "' placeholder='⌕'>";
|
||||||
return wrapFilter($inner, $currentVal !== '');
|
return wrapFilter($inner, $currentVal !== '');
|
||||||
}
|
}
|
||||||
// Helper: render a select filter populated by JS
|
// Helper: render a select filter populated by JS
|
||||||
function renderFilterSelectJS(string $name, string $currentVal, string $jsClass, string $dataAttr = ''): string {
|
function renderFilterSelectJS(string $name, string $currentVal, string $jsClass, string $dataAttr = ''): string
|
||||||
|
{
|
||||||
$html = "<select class='grid-filter-input {$jsClass}' name='" . htmlspecialchars($name) . "' {$dataAttr} onchange='submitFilters()'>";
|
$html = "<select class='grid-filter-input {$jsClass}' name='" . htmlspecialchars($name) . "' {$dataAttr} onchange='submitFilters()'>";
|
||||||
$html .= "<option value=''>All</option>";
|
$html .= "<option value=''>All</option>";
|
||||||
if ($currentVal !== '') {
|
if ($currentVal !== '') {
|
||||||
@@ -1639,6 +1717,8 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
<div class="grid-cell button-cell" style="flex: 0 0 120px; position: relative;">
|
<div class="grid-cell button-cell" style="flex: 0 0 120px; position: relative;">
|
||||||
<button type="button" class="photos-btn action-btn" data-row="<?= $index ?>" data-iddatadb="<?= $row['iddatadb'] ?>" title="Photos" style="background: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer;"><i class="fas fa-camera"></i></button>
|
<button type="button" class="photos-btn action-btn" data-row="<?= $index ?>" data-iddatadb="<?= $row['iddatadb'] ?>" title="Photos" style="background: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer;"><i class="fas fa-camera"></i></button>
|
||||||
<button type="button" class="parts-btn action-btn" data-row="<?= $index ?>" data-iddatadb="<?= $row['iddatadb'] ?>" title="Parts" style="background: #ffc107; color: white; border: none; border-radius: 5px; cursor: pointer;"><i class="fas fa-puzzle-piece"></i></button>
|
<button type="button" class="parts-btn action-btn" data-row="<?= $index ?>" data-iddatadb="<?= $row['iddatadb'] ?>" title="Parts" style="background: #ffc107; color: white; border: none; border-radius: 5px; cursor: pointer;"><i class="fas fa-puzzle-piece"></i></button>
|
||||||
|
<button type="button" class="revert-btn action-btn" data-row="<?= $index ?>" data-iddatadb="<?= $row['iddatadb'] ?>" data-commessaweb="<?= htmlspecialchars($row['commessaweb'] ?? '', ENT_QUOTES) ?>" title="Riporta a Imported (i)" style="background: #fd7e14; color: white; border: none; border-radius: 5px; cursor: pointer;"><i class="fas fa-rotate-left"></i></button>
|
||||||
|
<a href="download_logs.php?iddatadb=<?= (int)$row['iddatadb'] ?>" class="logs-btn action-btn" title="Scarica log LIMS" style="background:#17a2b8; color:white; border:none; border-radius:5px; cursor:pointer; display:inline-flex; align-items:center; justify-content:center; text-decoration:none;"><i class="fas fa-file-download"></i></a>
|
||||||
</div>
|
</div>
|
||||||
<?php if ($mainFieldMapping):
|
<?php if ($mainFieldMapping):
|
||||||
$detail = array_filter($manualDetails, fn($d) => $d['mapping_id'] == $mainFieldMapping['id'] && $d['datadb_id'] == $row['iddatadb']);
|
$detail = array_filter($manualDetails, fn($d) => $d['mapping_id'] == $mainFieldMapping['id'] && $d['datadb_id'] == $row['iddatadb']);
|
||||||
@@ -1775,6 +1855,25 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Revert to Imported Modal -->
|
||||||
|
<div class="modal fade" id="revertStatusModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title"><i class="fas fa-rotate-left" style="color:#fd7e14;"></i> Riporta a Imported</h5>
|
||||||
|
<button type="button" class="btn-close close-revert-modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Vuoi riportare questo record allo stato <strong>Imported (i)</strong>?</p>
|
||||||
|
<p class="text-muted" id="revertRecordInfo" style="font-size:13px;"></p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary btn-sm close-revert-modal">Annulla</button>
|
||||||
|
<button type="button" class="btn btn-warning btn-sm" id="confirmRevertBtn"><i class="fas fa-check"></i> Conferma</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1792,10 +1891,14 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
(function() {
|
(function() {
|
||||||
const sceltaEls = document.querySelectorAll('.scelta-value');
|
const sceltaEls = document.querySelectorAll('.scelta-value');
|
||||||
const fieldIds = new Set();
|
const fieldIds = new Set();
|
||||||
sceltaEls.forEach(el => { if (el.dataset.fieldId) fieldIds.add(el.dataset.fieldId); });
|
sceltaEls.forEach(el => {
|
||||||
|
if (el.dataset.fieldId) fieldIds.add(el.dataset.fieldId);
|
||||||
|
});
|
||||||
if (!fieldIds.size) return;
|
if (!fieldIds.size) return;
|
||||||
|
|
||||||
$.getJSON('get_customfield_values.php', { field_ids: [...fieldIds].join(',') }, function(data) {
|
$.getJSON('get_customfield_values.php', {
|
||||||
|
field_ids: [...fieldIds].join(',')
|
||||||
|
}, function(data) {
|
||||||
const lookup = {};
|
const lookup = {};
|
||||||
for (const [fid, values] of Object.entries(data)) {
|
for (const [fid, values] of Object.entries(data)) {
|
||||||
lookup[fid] = {};
|
lookup[fid] = {};
|
||||||
@@ -1836,8 +1939,18 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
url: 'search_customfield_values.php',
|
url: 'search_customfield_values.php',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
delay: 150,
|
delay: 150,
|
||||||
data: function(params) { return { field_id: fieldId, q: params.term || '', limit: 20 }; },
|
data: function(params) {
|
||||||
processResults: function(data) { return { results: data.results || [] }; },
|
return {
|
||||||
|
field_id: fieldId,
|
||||||
|
q: params.term || '',
|
||||||
|
limit: 20
|
||||||
|
};
|
||||||
|
},
|
||||||
|
processResults: function(data) {
|
||||||
|
return {
|
||||||
|
results: data.results || []
|
||||||
|
};
|
||||||
|
},
|
||||||
cache: true
|
cache: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1859,16 +1972,16 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
|
|
||||||
const promises = [...ids].map(id =>
|
const promises = [...ids].map(id =>
|
||||||
fetch('search_clienti.php?id=' + encodeURIComponent(id))
|
fetch('search_clienti.php?id=' + encodeURIComponent(id))
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(json => {
|
.then(json => {
|
||||||
const item = (json.results || [])[0];
|
const item = (json.results || [])[0];
|
||||||
if (item) {
|
if (item) {
|
||||||
document.querySelectorAll(`.client-text[data-client-id="${id}"]`).forEach(el => {
|
document.querySelectorAll(`.client-text[data-client-id="${id}"]`).forEach(el => {
|
||||||
el.textContent = item.text;
|
el.textContent = item.text;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
);
|
);
|
||||||
Promise.all(promises);
|
Promise.all(promises);
|
||||||
})();
|
})();
|
||||||
@@ -1893,8 +2006,17 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
url: 'search_clienti.php',
|
url: 'search_clienti.php',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
delay: 150,
|
delay: 150,
|
||||||
data: function(params) { return { q: params.term || '', limit: 20 }; },
|
data: function(params) {
|
||||||
processResults: function(data) { return { results: data.results || [] }; },
|
return {
|
||||||
|
q: params.term || '',
|
||||||
|
limit: 20
|
||||||
|
};
|
||||||
|
},
|
||||||
|
processResults: function(data) {
|
||||||
|
return {
|
||||||
|
results: data.results || []
|
||||||
|
};
|
||||||
|
},
|
||||||
cache: true
|
cache: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1909,8 +2031,17 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
url: 'search_clienti.php',
|
url: 'search_clienti.php',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
delay: 150,
|
delay: 150,
|
||||||
data: function(params) { return { q: params.term || '', limit: 20 }; },
|
data: function(params) {
|
||||||
processResults: function(data) { return { results: data.results || [] }; },
|
return {
|
||||||
|
q: params.term || '',
|
||||||
|
limit: 20
|
||||||
|
};
|
||||||
|
},
|
||||||
|
processResults: function(data) {
|
||||||
|
return {
|
||||||
|
results: data.results || []
|
||||||
|
};
|
||||||
|
},
|
||||||
cache: true
|
cache: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1942,14 +2073,16 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
$('#partsViewContent').html('<i class="fas fa-spinner fa-spin"></i> Loading...');
|
$('#partsViewContent').html('<i class="fas fa-spinner fa-spin"></i> Loading...');
|
||||||
new bootstrap.Modal(document.getElementById('partsViewModal')).show();
|
new bootstrap.Modal(document.getElementById('partsViewModal')).show();
|
||||||
|
|
||||||
$.getJSON('load_parts.php', { iddatadb: iddatadb }, function(data) {
|
$.getJSON('load_parts.php', {
|
||||||
|
iddatadb: iddatadb
|
||||||
|
}, function(data) {
|
||||||
if (!data.parts || data.parts.length === 0) {
|
if (!data.parts || data.parts.length === 0) {
|
||||||
$('#partsViewContent').html('<p class="text-muted">No parts found.</p>');
|
$('#partsViewContent').html('<p class="text-muted">No parts found.</p>');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let html = '<table class="table table-sm table-bordered"><thead><tr><th>#</th><th>Description</th><th>Material</th><th>Color</th><th>Matrice</th><th>Note</th></tr></thead><tbody>';
|
let html = '<table class="table table-sm table-bordered"><thead><tr><th>#</th><th>Description</th><th>Material</th><th>Color</th><th>Matrice</th><th>Note</th></tr></thead><tbody>';
|
||||||
data.parts.forEach(p => {
|
data.parts.forEach(p => {
|
||||||
html += '<tr><td>' + (p.part_number||'') + '</td><td>' + (p.part_description||'') + '</td><td>' + (p.material||'') + '</td><td>' + (p.color||'') + '</td><td>' + (p.idmatrice||'') + '</td><td>' + (p.note||'') + '</td></tr>';
|
html += '<tr><td>' + (p.part_number || '') + '</td><td>' + (p.part_description || '') + '</td><td>' + (p.material || '') + '</td><td>' + (p.color || '') + '</td><td>' + (p.idmatrice || '') + '</td><td>' + (p.note || '') + '</td></tr>';
|
||||||
});
|
});
|
||||||
html += '</tbody></table>';
|
html += '</tbody></table>';
|
||||||
$('#partsViewContent').html(html);
|
$('#partsViewContent').html(html);
|
||||||
@@ -1964,7 +2097,9 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
$('#photosViewContent').html('<i class="fas fa-spinner fa-spin"></i> Loading...');
|
$('#photosViewContent').html('<i class="fas fa-spinner fa-spin"></i> Loading...');
|
||||||
new bootstrap.Modal(document.getElementById('photosViewModal')).show();
|
new bootstrap.Modal(document.getElementById('photosViewModal')).show();
|
||||||
|
|
||||||
$.getJSON('load_photo.php', { iddatadb: iddatadb }, function(data) {
|
$.getJSON('load_photo.php', {
|
||||||
|
iddatadb: iddatadb
|
||||||
|
}, function(data) {
|
||||||
if (!data.success || !data.photos || data.photos.length === 0) {
|
if (!data.success || !data.photos || data.photos.length === 0) {
|
||||||
$('#photosViewContent').html('<p class="text-muted">No photos found.</p>');
|
$('#photosViewContent').html('<p class="text-muted">No photos found.</p>');
|
||||||
return;
|
return;
|
||||||
@@ -1979,14 +2114,74 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
$('#photosViewContent').html('<p class="text-danger">Error loading photos.</p>');
|
$('#photosViewContent').html('<p class="text-danger">Error loading photos.</p>');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Revert status to Imported (i) ──
|
||||||
|
let revertIddatadb = null;
|
||||||
|
|
||||||
|
$(document).on('click', '.revert-btn', function() {
|
||||||
|
revertIddatadb = $(this).data('iddatadb');
|
||||||
|
const cw = $(this).data('commessaweb') || '';
|
||||||
|
$('#revertRecordInfo').text(cw ? ('Commessa: ' + cw) : ('ID: ' + revertIddatadb));
|
||||||
|
new bootstrap.Modal(document.getElementById('revertStatusModal')).show();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.close-revert-modal', function() {
|
||||||
|
const modal = bootstrap.Modal.getInstance(document.getElementById('revertStatusModal'));
|
||||||
|
if (modal) modal.hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('hidden.bs.modal', '#revertStatusModal', function() {
|
||||||
|
$('.modal-backdrop').remove();
|
||||||
|
$('body').removeClass('modal-open').css('padding-right', '');
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '#confirmRevertBtn', function() {
|
||||||
|
if (!revertIddatadb) return;
|
||||||
|
const $btn = $(this);
|
||||||
|
$btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> Attendere...');
|
||||||
|
$.ajax({
|
||||||
|
url: 'revert_status.php',
|
||||||
|
method: 'POST',
|
||||||
|
dataType: 'json',
|
||||||
|
data: {
|
||||||
|
iddatadb: revertIddatadb,
|
||||||
|
status: 'i'
|
||||||
|
},
|
||||||
|
success: function(res) {
|
||||||
|
if (res && res.success) {
|
||||||
|
const modal = bootstrap.Modal.getInstance(document.getElementById('revertStatusModal'));
|
||||||
|
if (modal) modal.hide();
|
||||||
|
const $row = $('.grid-row[data-id="' + revertIddatadb + '"]');
|
||||||
|
$row.addClass('flash-success');
|
||||||
|
setTimeout(function() {
|
||||||
|
$row.fadeOut(300, function() {
|
||||||
|
$row.remove();
|
||||||
|
});
|
||||||
|
}, 400);
|
||||||
|
} else {
|
||||||
|
alert('Errore: ' + ((res && res.message) ? res.message : 'operazione non riuscita'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
alert('Errore di rete durante l\'operazione.');
|
||||||
|
},
|
||||||
|
complete: function() {
|
||||||
|
$btn.prop('disabled', false).html('<i class="fas fa-check"></i> Conferma');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Column filters ──
|
// ── Column filters ──
|
||||||
function clearOneFilter(btn) {
|
function clearOneFilter(btn) {
|
||||||
const wrap = btn.closest('.filter-wrap');
|
const wrap = btn.closest('.filter-wrap');
|
||||||
const el = wrap.querySelector('.grid-filter-input');
|
const el = wrap.querySelector('.grid-filter-input');
|
||||||
if (el) { el.value = ''; submitFilters(); }
|
if (el) {
|
||||||
|
el.value = '';
|
||||||
|
submitFilters();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function submitFilters() {
|
function submitFilters() {
|
||||||
const filterRow = document.getElementById('gridFilterRow');
|
const filterRow = document.getElementById('gridFilterRow');
|
||||||
if (!filterRow) return;
|
if (!filterRow) return;
|
||||||
@@ -2004,7 +2199,10 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
const filterRow = document.getElementById('gridFilterRow');
|
const filterRow = document.getElementById('gridFilterRow');
|
||||||
if (!filterRow) return;
|
if (!filterRow) return;
|
||||||
filterRow.addEventListener('keydown', function(e) {
|
filterRow.addEventListener('keydown', function(e) {
|
||||||
if (e.key === 'Enter') { e.preventDefault(); submitFilters(); }
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
submitFilters();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
filterRow.querySelectorAll('.grid-filter-input').forEach(el => {
|
filterRow.querySelectorAll('.grid-filter-input').forEach(el => {
|
||||||
el.classList.toggle('has-value', el.value.trim() !== '');
|
el.classList.toggle('has-value', el.value.trim() !== '');
|
||||||
@@ -2016,7 +2214,10 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
// ── Column resize ──
|
// ── Column resize ──
|
||||||
(function() {
|
(function() {
|
||||||
const resizers = document.querySelectorAll('.resizer');
|
const resizers = document.querySelectorAll('.resizer');
|
||||||
let currentResizer = null, startX = 0, startWidth = 0, columnIndex = null;
|
let currentResizer = null,
|
||||||
|
startX = 0,
|
||||||
|
startWidth = 0,
|
||||||
|
columnIndex = null;
|
||||||
|
|
||||||
function resize(e) {
|
function resize(e) {
|
||||||
if (!currentResizer || columnIndex === null) return;
|
if (!currentResizer || columnIndex === null) return;
|
||||||
@@ -2059,4 +2260,5 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once __DIR__ . '/class/db-functions.php';
|
||||||
|
$sid = session_id(); // oppure l'id Laravel, da verificare
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
$pdo->prepare("UPDATE auth_sessions SET last_activity = ? WHERE id = ?")
|
||||||
|
->execute([time(), $sid]);
|
||||||
|
echo json_encode(['ok' => true, 'sid' => $sid, 'ts' => time()]);
|
||||||
@@ -17,7 +17,7 @@ if ($id <= 0) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$allowed = ['default_value', 'is_visible_import', 'is_required'];
|
$allowed = ['default_value', 'is_visible_import', 'is_required', 'default_source', 'excel_column'];
|
||||||
if (!in_array($field, $allowed, true)) {
|
if (!in_array($field, $allowed, true)) {
|
||||||
echo json_encode(['success' => false, 'message' => 'Invalid field']);
|
echo json_encode(['success' => false, 'message' => 'Invalid field']);
|
||||||
exit;
|
exit;
|
||||||
@@ -27,6 +27,21 @@ if ($field === 'is_visible_import' || $field === 'is_required') {
|
|||||||
$value = ((int)$value === 1) ? 1 : 0;
|
$value = ((int)$value === 1) ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($field === 'default_source') {
|
||||||
|
$value = in_array($value, ['manual', 'xls', 'json'], true) ? $value : 'manual';
|
||||||
|
|
||||||
|
if ($value !== 'xls') {
|
||||||
|
$pdo->prepare("UPDATE template_fixed_mapping SET excel_column = NULL WHERE id = :id")
|
||||||
|
->execute([':id' => $id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($field === 'excel_column') {
|
||||||
|
$value = trim((string)$value);
|
||||||
|
if ($value === '') {
|
||||||
|
$value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$sql = "UPDATE template_fixed_mapping SET {$field} = :val WHERE id = :id";
|
$sql = "UPDATE template_fixed_mapping SET {$field} = :val WHERE id = :id";
|
||||||
$stmt = $pdo->prepare($sql);
|
$stmt = $pdo->prepare($sql);
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$userId = (int)($iduserlogin ?? 0);
|
||||||
|
if ($userId <= 0) {
|
||||||
|
header("Location: login.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ogni preferenza: colonna DB => 1 se checkbox spuntata, 0 altrimenti
|
||||||
|
$prefsMap = [
|
||||||
|
'notify_export_email' => isset($_POST['notify_export_email']) ? 1 : 0,
|
||||||
|
// in futuro: 'notify_altra_cosa' => isset($_POST['notify_altra_cosa']) ? 1 : 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sets = [];
|
||||||
|
$params = [];
|
||||||
|
foreach ($prefsMap as $col => $val) {
|
||||||
|
$sets[] = "`{$col}` = :{$col}";
|
||||||
|
$params[$col] = $val;
|
||||||
|
}
|
||||||
|
$params['id'] = $userId;
|
||||||
|
|
||||||
|
$sql = "UPDATE auth_users SET " . implode(', ', $sets) . " WHERE id = :id";
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute($params);
|
||||||
|
|
||||||
|
header("Location: notification_settings.php?saved=1");
|
||||||
|
exit;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Notification prefs update error: " . $e->getMessage());
|
||||||
|
header("Location: notification_settings.php?saved=0");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
* To add a new rule — just append another closure to $validators.
|
* To add a new rule — just append another closure to $validators.
|
||||||
*/
|
*/
|
||||||
include('include/headscript.php');
|
include('include/headscript.php');
|
||||||
|
require_once __DIR__ . '/class/binding-functions.php';
|
||||||
|
|
||||||
$dbHandler = DBHandlerSelect::getInstance();
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
$pdo = $dbHandler->getConnection();
|
$pdo = $dbHandler->getConnection();
|
||||||
@@ -124,6 +125,29 @@ $validators[] = function (int $iddatadb, array $ctx): array {
|
|||||||
return $errors;
|
return $errors;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$validators[] = function (int $iddatadb, array $ctx): array {
|
||||||
|
$errors = [];
|
||||||
|
|
||||||
|
foreach (($ctx['listCustom'] ?? []) as $cf) {
|
||||||
|
$val = trim((string) ($ctx['customValues'][(int) $cf['mapping_id']] ?? ''));
|
||||||
|
if ($val === '' || ctype_digit($val)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!binding_has_lims_values((int) $cf['field_id'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$label = rtrim($cf['field_label'], ': ');
|
||||||
|
$errors[] = [
|
||||||
|
'field' => 'field_label:' . $cf['field_label'],
|
||||||
|
'message' => $label . ': il valore importato "' . $val . '" non e\' collegato a nessun valore '
|
||||||
|
. 'della lista LIMS. Aprire Gestione binding o ri-importare per collegarlo.',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $errors;
|
||||||
|
};
|
||||||
|
|
||||||
// Logical fixed_field_key - real datadb column (mirrors imported.php $fixedAliasMap)
|
// Logical fixed_field_key - real datadb column (mirrors imported.php $fixedAliasMap)
|
||||||
$fixedAliasMap = [
|
$fixedAliasMap = [
|
||||||
'ClienteResponsabile' => 'cliente_responsabile_id',
|
'ClienteResponsabile' => 'cliente_responsabile_id',
|
||||||
@@ -197,6 +221,7 @@ try {
|
|||||||
|
|
||||||
$requiredFixedByTemplate = []; // template_id => [ fixed_field_key => label ]
|
$requiredFixedByTemplate = []; // template_id => [ fixed_field_key => label ]
|
||||||
$requiredCustomByTemplate = []; // template_id => [ { mapping_id, field_label }, ... ]
|
$requiredCustomByTemplate = []; // template_id => [ { mapping_id, field_label }, ... ]
|
||||||
|
$listCustomByTemplate = []; // template_id => [ { mapping_id, field_label }, ... ]
|
||||||
|
|
||||||
if (!empty($templateIds)) {
|
if (!empty($templateIds)) {
|
||||||
$tplPlaceholders = implode(',', array_fill(0, count($templateIds), '?'));
|
$tplPlaceholders = implode(',', array_fill(0, count($templateIds), '?'));
|
||||||
@@ -237,6 +262,21 @@ try {
|
|||||||
'field_label' => $r['field_label'],
|
'field_label' => $r['field_label'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT id AS mapping_id, template_id, field_id, field_label
|
||||||
|
FROM template_mapping
|
||||||
|
WHERE template_id IN ($tplPlaceholders)
|
||||||
|
AND data_type = 'SceltaMultipla'
|
||||||
|
");
|
||||||
|
$stmt->execute($templateIds);
|
||||||
|
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
||||||
|
$listCustomByTemplate[(int)$r['template_id']][] = [
|
||||||
|
'mapping_id' => (int)$r['mapping_id'],
|
||||||
|
'field_id' => (int)$r['field_id'],
|
||||||
|
'field_label' => $r['field_label'],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom field values per record (import_data_details.id is the FK to datadb)
|
// Custom field values per record (import_data_details.id is the FK to datadb)
|
||||||
@@ -268,6 +308,7 @@ try {
|
|||||||
'fixedAliasMap' => $fixedAliasMap,
|
'fixedAliasMap' => $fixedAliasMap,
|
||||||
'requiredFixed' => $requiredFixedByTemplate[$templateId] ?? [],
|
'requiredFixed' => $requiredFixedByTemplate[$templateId] ?? [],
|
||||||
'requiredCustom' => $requiredCustomByTemplate[$templateId] ?? [],
|
'requiredCustom' => $requiredCustomByTemplate[$templateId] ?? [],
|
||||||
|
'listCustom' => $listCustomByTemplate[$templateId] ?? [],
|
||||||
'customValues' => $customValuesByRecord[$iddatadb] ?? [],
|
'customValues' => $customValuesByRecord[$iddatadb] ?? [],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
Vendored
+11
-10
@@ -5806,17 +5806,17 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpmailer/phpmailer",
|
"name": "phpmailer/phpmailer",
|
||||||
"version": "v6.9.3",
|
"version": "v7.1.1",
|
||||||
"version_normalized": "6.9.3.0",
|
"version_normalized": "7.1.1.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/PHPMailer/PHPMailer.git",
|
"url": "https://github.com/PHPMailer/PHPMailer.git",
|
||||||
"reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e"
|
"reference": "1bc1716a507a65e039d4ac9d9adebbbd0d346e15"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2f5c94fe7493efc213f643c23b1b1c249d40f47e",
|
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/1bc1716a507a65e039d4ac9d9adebbbd0d346e15",
|
||||||
"reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e",
|
"reference": "1bc1716a507a65e039d4ac9d9adebbbd0d346e15",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -5830,13 +5830,14 @@
|
|||||||
"doctrine/annotations": "^1.2.6 || ^1.13.3",
|
"doctrine/annotations": "^1.2.6 || ^1.13.3",
|
||||||
"php-parallel-lint/php-console-highlighter": "^1.0.0",
|
"php-parallel-lint/php-console-highlighter": "^1.0.0",
|
||||||
"php-parallel-lint/php-parallel-lint": "^1.3.2",
|
"php-parallel-lint/php-parallel-lint": "^1.3.2",
|
||||||
"phpcompatibility/php-compatibility": "^9.3.5",
|
"phpcompatibility/php-compatibility": "^10.0.0@dev",
|
||||||
"roave/security-advisories": "dev-latest",
|
"squizlabs/php_codesniffer": "^3.13.5",
|
||||||
"squizlabs/php_codesniffer": "^3.7.2",
|
|
||||||
"yoast/phpunit-polyfills": "^1.0.4"
|
"yoast/phpunit-polyfills": "^1.0.4"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
|
"decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
|
||||||
|
"directorytree/imapengine": "For uploading sent messages via IMAP, see gmail example",
|
||||||
|
"ext-imap": "Needed to support advanced email address parsing according to RFC822",
|
||||||
"ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
|
"ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
|
||||||
"ext-openssl": "Needed for secure SMTP sending and DKIM signing",
|
"ext-openssl": "Needed for secure SMTP sending and DKIM signing",
|
||||||
"greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
|
"greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
|
||||||
@@ -5846,7 +5847,7 @@
|
|||||||
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)",
|
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)",
|
||||||
"thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication"
|
"thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication"
|
||||||
},
|
},
|
||||||
"time": "2024-11-24T18:04:13+00:00",
|
"time": "2026-05-18T08:06:14+00:00",
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"installation-source": "dist",
|
"installation-source": "dist",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -5878,7 +5879,7 @@
|
|||||||
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
|
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/PHPMailer/PHPMailer/issues",
|
"issues": "https://github.com/PHPMailer/PHPMailer/issues",
|
||||||
"source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.3"
|
"source": "https://github.com/PHPMailer/PHPMailer/tree/v7.1.1"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|||||||
Vendored
+5
-5
@@ -3,7 +3,7 @@
|
|||||||
'name' => 'loshmis/vanguard',
|
'name' => 'loshmis/vanguard',
|
||||||
'pretty_version' => 'dev-main',
|
'pretty_version' => 'dev-main',
|
||||||
'version' => 'dev-main',
|
'version' => 'dev-main',
|
||||||
'reference' => 'b19963808e5545f0b99bf0eba214b0c85b2f4b7b',
|
'reference' => 'bb2ebcc1223dd084e59588774fecbca9875d9daf',
|
||||||
'type' => 'project',
|
'type' => 'project',
|
||||||
'install_path' => __DIR__ . '/../../',
|
'install_path' => __DIR__ . '/../../',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
@@ -742,7 +742,7 @@
|
|||||||
'loshmis/vanguard' => array(
|
'loshmis/vanguard' => array(
|
||||||
'pretty_version' => 'dev-main',
|
'pretty_version' => 'dev-main',
|
||||||
'version' => 'dev-main',
|
'version' => 'dev-main',
|
||||||
'reference' => 'b19963808e5545f0b99bf0eba214b0c85b2f4b7b',
|
'reference' => 'bb2ebcc1223dd084e59588774fecbca9875d9daf',
|
||||||
'type' => 'project',
|
'type' => 'project',
|
||||||
'install_path' => __DIR__ . '/../../',
|
'install_path' => __DIR__ . '/../../',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
@@ -941,9 +941,9 @@
|
|||||||
'dev_requirement' => true,
|
'dev_requirement' => true,
|
||||||
),
|
),
|
||||||
'phpmailer/phpmailer' => array(
|
'phpmailer/phpmailer' => array(
|
||||||
'pretty_version' => 'v6.9.3',
|
'pretty_version' => 'v7.1.1',
|
||||||
'version' => '6.9.3.0',
|
'version' => '7.1.1.0',
|
||||||
'reference' => '2f5c94fe7493efc213f643c23b1b1c249d40f47e',
|
'reference' => '1bc1716a507a65e039d4ac9d9adebbbd0d346e15',
|
||||||
'type' => 'library',
|
'type' => 'library',
|
||||||
'install_path' => __DIR__ . '/../phpmailer/phpmailer',
|
'install_path' => __DIR__ . '/../phpmailer/phpmailer',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
|
|||||||
-15
@@ -1,15 +0,0 @@
|
|||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
charset = utf-8
|
|
||||||
indent_size = 4
|
|
||||||
indent_style = space
|
|
||||||
end_of_line = lf
|
|
||||||
insert_final_newline = true
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
|
|
||||||
[*.md]
|
|
||||||
trim_trailing_whitespace = false
|
|
||||||
|
|
||||||
[*.{yml,yaml}]
|
|
||||||
indent_size = 2
|
|
||||||
Vendored
+13
-12
@@ -6,9 +6,9 @@
|
|||||||
|
|
||||||
[](https://github.com/PHPMailer/PHPMailer/actions)
|
[](https://github.com/PHPMailer/PHPMailer/actions)
|
||||||
[](https://codecov.io/gh/PHPMailer/PHPMailer)
|
[](https://codecov.io/gh/PHPMailer/PHPMailer)
|
||||||
[](https://packagist.org/packages/phpmailer/phpmailer)
|
[](https://packagist.org/packages/phpmailer/phpmailer)
|
||||||
[](https://packagist.org/packages/phpmailer/phpmailer)
|
[](https://packagist.org/packages/phpmailer/phpmailer)
|
||||||
[](https://packagist.org/packages/phpmailer/phpmailer)
|
[](https://packagist.org/packages/phpmailer/phpmailer)
|
||||||
[](https://phpmailer.github.io/PHPMailer/)
|
[](https://phpmailer.github.io/PHPMailer/)
|
||||||
[](https://api.securityscorecards.dev/projects/github.com/PHPMailer/PHPMailer)
|
[](https://api.securityscorecards.dev/projects/github.com/PHPMailer/PHPMailer)
|
||||||
|
|
||||||
@@ -20,25 +20,26 @@
|
|||||||
- Multipart/alternative emails for mail clients that do not read HTML email
|
- Multipart/alternative emails for mail clients that do not read HTML email
|
||||||
- Add attachments, including inline
|
- Add attachments, including inline
|
||||||
- Support for UTF-8 content and 8bit, base64, binary, and quoted-printable encodings
|
- Support for UTF-8 content and 8bit, base64, binary, and quoted-printable encodings
|
||||||
- SMTP authentication with LOGIN, PLAIN, CRAM-MD5, and XOAUTH2 mechanisms over SMTPS and SMTP+STARTTLS transports
|
- Full UTF-8 support when using servers that support `SMTPUTF8`.
|
||||||
|
- Support for iCal events in multiparts and attachments
|
||||||
|
- SMTP authentication with `LOGIN`, `PLAIN`, `CRAM-MD5`, and `XOAUTH2` mechanisms over SMTPS and SMTP+STARTTLS transports
|
||||||
- Validates email addresses automatically
|
- Validates email addresses automatically
|
||||||
- Protects against header injection attacks
|
- Protects against header injection attacks
|
||||||
- Error messages in over 50 languages!
|
- Error messages in over 50 languages!
|
||||||
- DKIM and S/MIME signing support
|
- DKIM and S/MIME signing support
|
||||||
- Compatible with PHP 5.5 and later, including PHP 8.2
|
- Compatible with PHP 5.5 and later, including PHP 8.5
|
||||||
- Namespaced to prevent name clashes
|
- Namespaced to prevent name clashes
|
||||||
- Much more!
|
- Much more!
|
||||||
|
|
||||||
## Why you might need it
|
## Why you might need it
|
||||||
Many PHP developers need to send email from their code. The only PHP function that supports this directly is [`mail()`](https://www.php.net/manual/en/function.mail.php). However, it does not provide any assistance for making use of popular features such as encryption, authentication, HTML messages, and attachments.
|
Many PHP developers need to send email from their code. The only PHP function that supports this directly is [`mail()`](https://www.php.net/manual/en/function.mail.php). However, it does not provide any assistance for making use of popular features such as authentication, HTML messages, and attachments.
|
||||||
|
|
||||||
Formatting email correctly is surprisingly difficult. There are myriad overlapping (and conflicting) standards, requiring tight adherence to horribly complicated formatting and encoding rules – the vast majority of code that you'll find online that uses the `mail()` function directly is just plain wrong, if not unsafe!
|
Formatting email correctly is surprisingly difficult. There are myriad overlapping (and conflicting) standards, requiring tight adherence to horribly complicated formatting and encoding rules – the vast majority of code that you'll find online that uses the `mail()` function directly is just plain wrong, if not unsafe!
|
||||||
|
|
||||||
The PHP `mail()` function usually sends via a local mail server, typically fronted by a `sendmail` binary on Linux, BSD, and macOS platforms, however, Windows usually doesn't include a local mail server; PHPMailer's integrated SMTP client allows email sending on all platforms without needing a local mail server. Be aware though, that the `mail()` function should be avoided when possible; it's both faster and [safer](https://exploitbox.io/paper/Pwning-PHP-Mail-Function-For-Fun-And-RCE.html) to use SMTP to localhost.
|
The PHP `mail()` function usually sends via a local mail server, typically fronted by a `sendmail` binary on Linux, BSD, and macOS platforms, however, Windows usually doesn't include a local mail server; PHPMailer's integrated SMTP client allows email sending on all platforms without needing a local mail server. Be aware though, that the `mail()` function should be avoided when possible; it's both faster and [safer](https://exploitbox.io/paper/Pwning-PHP-Mail-Function-For-Fun-And-RCE.html) to use SMTP to localhost.
|
||||||
|
|
||||||
*Please* don't be tempted to do it yourself – if you don't use PHPMailer, there are many other excellent libraries that
|
*Please* don't be tempted to do it yourself – if you don't use PHPMailer, there are many other excellent libraries that
|
||||||
you should look at before rolling your own. Try [SwiftMailer](https://swiftmailer.symfony.com/)
|
you should look at before rolling your own. Try [Symfony Mailer](https://symfony.com/doc/current/mailer.html), [Laminas/Mail](https://docs.laminas.dev/laminas-mail/), [ZetaComponents](https://github.com/zetacomponents/Mail), etc.
|
||||||
, [Laminas/Mail](https://docs.laminas.dev/laminas-mail/), [ZetaComponents](https://github.com/zetacomponents/Mail), etc.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
This software is distributed under the [LGPL 2.1](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html) license, along with the [GPL Cooperation Commitment](https://gplcc.github.io/gplcc/). Please read [LICENSE](https://github.com/PHPMailer/PHPMailer/blob/master/LICENSE) for information on the software availability and distribution.
|
This software is distributed under the [LGPL 2.1](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html) license, along with the [GPL Cooperation Commitment](https://gplcc.github.io/gplcc/). Please read [LICENSE](https://github.com/PHPMailer/PHPMailer/blob/master/LICENSE) for information on the software availability and distribution.
|
||||||
@@ -47,7 +48,7 @@ This software is distributed under the [LGPL 2.1](https://www.gnu.org/licenses/o
|
|||||||
PHPMailer is available on [Packagist](https://packagist.org/packages/phpmailer/phpmailer) (using semantic versioning), and installation via [Composer](https://getcomposer.org) is the recommended way to install PHPMailer. Just add this line to your `composer.json` file:
|
PHPMailer is available on [Packagist](https://packagist.org/packages/phpmailer/phpmailer) (using semantic versioning), and installation via [Composer](https://getcomposer.org) is the recommended way to install PHPMailer. Just add this line to your `composer.json` file:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"phpmailer/phpmailer": "^6.9.2"
|
"phpmailer/phpmailer": "^7.0.0"
|
||||||
```
|
```
|
||||||
|
|
||||||
or run
|
or run
|
||||||
@@ -74,10 +75,10 @@ require 'path/to/PHPMailer/src/PHPMailer.php';
|
|||||||
require 'path/to/PHPMailer/src/SMTP.php';
|
require 'path/to/PHPMailer/src/SMTP.php';
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're not using the `SMTP` class explicitly (you're probably not), you don't need a `use` line for the SMTP class. Even if you're not using exceptions, you do still need to load the `Exception` class as it is used internally.
|
If you're not using the `SMTP` class explicitly (you're probably not), you don't need a `use` line for it. Even if you're not using exceptions, you do still need to load the `Exception` class as it is used internally.
|
||||||
|
|
||||||
## Legacy versions
|
## Legacy versions
|
||||||
PHPMailer 5.2 (which is compatible with PHP 5.0 — 7.0) is no longer supported, even for security updates. You will find the latest version of 5.2 in the [5.2-stable branch](https://github.com/PHPMailer/PHPMailer/tree/5.2-stable). If you're using PHP 5.5 or later (which you should be), switch to the 6.x releases.
|
PHPMailer 5.2 (which is compatible with PHP 5.0 — 7.0) is no longer supported, even for security updates. You will find the latest version of 5.2 in the [5.2-stable branch](https://github.com/PHPMailer/PHPMailer/tree/5.2-stable). If you're using PHP 5.5 or later (which you should be), upgrade to a later release.
|
||||||
|
|
||||||
### Upgrading from 5.2
|
### Upgrading from 5.2
|
||||||
The biggest changes are that source files are now in the `src/` folder, and PHPMailer now declares the namespace `PHPMailer\PHPMailer`. This has several important effects – [read the upgrade guide](https://github.com/PHPMailer/PHPMailer/tree/master/UPGRADING.md) for more details.
|
The biggest changes are that source files are now in the `src/` folder, and PHPMailer now declares the namespace `PHPMailer\PHPMailer`. This has several important effects – [read the upgrade guide](https://github.com/PHPMailer/PHPMailer/tree/master/UPGRADING.md) for more details.
|
||||||
@@ -95,7 +96,7 @@ use PHPMailer\PHPMailer\PHPMailer;
|
|||||||
use PHPMailer\PHPMailer\SMTP;
|
use PHPMailer\PHPMailer\SMTP;
|
||||||
use PHPMailer\PHPMailer\Exception;
|
use PHPMailer\PHPMailer\Exception;
|
||||||
|
|
||||||
//Load Composer's autoloader
|
//Load Composer's autoloader (created by composer, not included with PHPMailer)
|
||||||
require 'vendor/autoload.php';
|
require 'vendor/autoload.php';
|
||||||
|
|
||||||
//Create an instance; passing `true` enables exceptions
|
//Create an instance; passing `true` enables exceptions
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
6.9.3
|
7.1.1
|
||||||
|
|||||||
+8
-4
@@ -42,22 +42,25 @@
|
|||||||
"doctrine/annotations": "^1.2.6 || ^1.13.3",
|
"doctrine/annotations": "^1.2.6 || ^1.13.3",
|
||||||
"php-parallel-lint/php-console-highlighter": "^1.0.0",
|
"php-parallel-lint/php-console-highlighter": "^1.0.0",
|
||||||
"php-parallel-lint/php-parallel-lint": "^1.3.2",
|
"php-parallel-lint/php-parallel-lint": "^1.3.2",
|
||||||
"phpcompatibility/php-compatibility": "^9.3.5",
|
"phpcompatibility/php-compatibility": "^10.0.0@dev",
|
||||||
"roave/security-advisories": "dev-latest",
|
"squizlabs/php_codesniffer": "^3.13.5",
|
||||||
"squizlabs/php_codesniffer": "^3.7.2",
|
|
||||||
"yoast/phpunit-polyfills": "^1.0.4"
|
"yoast/phpunit-polyfills": "^1.0.4"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
|
"decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
|
||||||
|
"ext-imap": "Needed to support advanced email address parsing according to RFC822",
|
||||||
"ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
|
"ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
|
||||||
"ext-openssl": "Needed for secure SMTP sending and DKIM signing",
|
"ext-openssl": "Needed for secure SMTP sending and DKIM signing",
|
||||||
"greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
|
"greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
|
||||||
"hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
|
"hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
|
||||||
"league/oauth2-google": "Needed for Google XOAUTH2 authentication",
|
"league/oauth2-google": "Needed for Google XOAUTH2 authentication",
|
||||||
"psr/log": "For optional PSR-3 debug logging",
|
"psr/log": "For optional PSR-3 debug logging",
|
||||||
|
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)",
|
||||||
"thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication",
|
"thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication",
|
||||||
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)"
|
"directorytree/imapengine": "For uploading sent messages via IMAP, see gmail example"
|
||||||
},
|
},
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"prefer-stable": true,
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"PHPMailer\\PHPMailer\\": "src/"
|
"PHPMailer\\PHPMailer\\": "src/"
|
||||||
@@ -71,6 +74,7 @@
|
|||||||
"license": "LGPL-2.1-only",
|
"license": "LGPL-2.1-only",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"check": "./vendor/bin/phpcs",
|
"check": "./vendor/bin/phpcs",
|
||||||
|
"style": "./vendor/bin/phpcbf",
|
||||||
"test": "./vendor/bin/phpunit --no-coverage",
|
"test": "./vendor/bin/phpunit --no-coverage",
|
||||||
"coverage": "./vendor/bin/phpunit",
|
"coverage": "./vendor/bin/phpunit",
|
||||||
"lint": [
|
"lint": [
|
||||||
|
|||||||
+1
-1
@@ -178,5 +178,5 @@ if (!isset($_GET['code'])) {
|
|||||||
);
|
);
|
||||||
//Use this to interact with an API on the users behalf
|
//Use this to interact with an API on the users behalf
|
||||||
//Use this to get a new access token if the old one expires
|
//Use this to get a new access token if the old one expires
|
||||||
echo 'Refresh Token: ', htmlspecialchars($token->getRefreshToken());
|
echo 'Refresh Token: ', htmlspecialchars($token->getRefreshToken(), ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,3 +34,6 @@ $PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() fejlede.';
|
|||||||
$PHPMAILER_LANG['smtp_detail'] = 'Detalje: ';
|
$PHPMAILER_LANG['smtp_detail'] = 'Detalje: ';
|
||||||
$PHPMAILER_LANG['smtp_error'] = 'SMTP server fejl: ';
|
$PHPMAILER_LANG['smtp_error'] = 'SMTP server fejl: ';
|
||||||
$PHPMAILER_LANG['variable_set'] = 'Kunne ikke definere eller nulstille variablen: ';
|
$PHPMAILER_LANG['variable_set'] = 'Kunne ikke definere eller nulstille variablen: ';
|
||||||
|
$PHPMAILER_LANG['no_smtputf8'] = 'Serveren understøtter ikke SMTPUTF8 som påkrævet for at sende til Unicode adresser';
|
||||||
|
$PHPMAILER_LANG['imap_recommended'] = 'Brug af forenklet adresseparser anbefales ikke. Installer PHP IMAP udvidelsen for fuld RFC822 parsing.';
|
||||||
|
$PHPMAILER_LANG['deprecated_argument'] = 'Udfaset argument: ';
|
||||||
|
|||||||
+29
-18
@@ -3,24 +3,35 @@
|
|||||||
/**
|
/**
|
||||||
* Esperanto PHPMailer language file: refer to English translation for definitive list
|
* Esperanto PHPMailer language file: refer to English translation for definitive list
|
||||||
* @package PHPMailer
|
* @package PHPMailer
|
||||||
|
* @author Robin van der Vliet <info@robinvandervliet.com>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$PHPMAILER_LANG['authenticate'] = 'Eraro de servilo SMTP : aŭtentigo malsukcesis.';
|
$PHPMAILER_LANG['authenticate'] = 'SMTP-eraro: Ne eblis aŭtentigi.';
|
||||||
$PHPMAILER_LANG['connect_host'] = 'Eraro de servilo SMTP : konektado al servilo malsukcesis.';
|
$PHPMAILER_LANG['buggy_php'] = 'Via versio de PHP estas trafita de cimo, kiu povas kaŭzi difektitajn mesaĝojn. Por ripari tion, ŝanĝu al sendado per SMTP, malŝaltu la opcion mail.add_x_header en via php.ini, ŝanĝu al MacOS aŭ Linux, aŭ ĝisdatigu vian PHP al versio 7.0.17+ aŭ 7.1.3+.';
|
||||||
$PHPMAILER_LANG['data_not_accepted'] = 'Eraro de servilo SMTP : neĝustaj datumoj.';
|
$PHPMAILER_LANG['connect_host'] = 'SMTP-eraro: Ne eblis konektiĝi al la SMTP-gastiganto.';
|
||||||
$PHPMAILER_LANG['empty_message'] = 'Teksto de mesaĝo mankas.';
|
$PHPMAILER_LANG['data_not_accepted'] = 'SMTP-eraro: Datumoj ne akceptitaj.';
|
||||||
|
$PHPMAILER_LANG['empty_message'] = 'Mesaĝokorpo malplena';
|
||||||
$PHPMAILER_LANG['encoding'] = 'Nekonata kodoprezento: ';
|
$PHPMAILER_LANG['encoding'] = 'Nekonata kodoprezento: ';
|
||||||
$PHPMAILER_LANG['execute'] = 'Lanĉi rulumadon ne eblis: ';
|
$PHPMAILER_LANG['execute'] = 'Ne eblis plenumi: ';
|
||||||
$PHPMAILER_LANG['file_access'] = 'Aliro al dosiero ne sukcesis: ';
|
$PHPMAILER_LANG['extension_missing'] = 'Kromprogramo mankas: ';
|
||||||
$PHPMAILER_LANG['file_open'] = 'Eraro de dosiero: malfermo neeblas: ';
|
$PHPMAILER_LANG['file_access'] = 'Ne eblis aliri la dosieron: ';
|
||||||
$PHPMAILER_LANG['from_failed'] = 'Jena adreso de sendinto malsukcesis: ';
|
$PHPMAILER_LANG['file_open'] = 'Dosiera eraro: Ne eblis malfermi la dosieron: ';
|
||||||
$PHPMAILER_LANG['instantiate'] = 'Genero de retmesaĝa funkcio neeblis.';
|
$PHPMAILER_LANG['from_failed'] = 'La sekva(j) sendinto(j) malsukcesis: ';
|
||||||
$PHPMAILER_LANG['invalid_address'] = 'Retadreso ne validas: ';
|
$PHPMAILER_LANG['instantiate'] = 'Ne eblis funkciigi la retpoŝtan funkcion.';
|
||||||
$PHPMAILER_LANG['mailer_not_supported'] = ' mesaĝilo ne subtenata.';
|
$PHPMAILER_LANG['invalid_address'] = 'Nevalida adreso: ';
|
||||||
$PHPMAILER_LANG['provide_address'] = 'Vi devas tajpi almenaŭ unu recevontan retadreson.';
|
$PHPMAILER_LANG['invalid_header'] = 'Nevalida kaplinia nomo aŭ valoro';
|
||||||
$PHPMAILER_LANG['recipients_failed'] = 'Eraro de servilo SMTP : la jenaj poŝtrecivuloj kaŭzis eraron: ';
|
$PHPMAILER_LANG['invalid_hostentry'] = 'Nevalida enigo de gastiganto: ';
|
||||||
$PHPMAILER_LANG['signing'] = 'Eraro de subskribo: ';
|
$PHPMAILER_LANG['invalid_host'] = 'Nevalida gastiganto: ';
|
||||||
$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP konektado malsukcesis.';
|
$PHPMAILER_LANG['mailer_not_supported'] = ' retpoŝtilo ne estas subtenata.';
|
||||||
$PHPMAILER_LANG['smtp_error'] = 'Eraro de servilo SMTP : ';
|
$PHPMAILER_LANG['provide_address'] = 'Vi devas provizi almenaŭ unu retpoŝtadreson de ricevonto.';
|
||||||
$PHPMAILER_LANG['variable_set'] = 'Variablo ne pravalorizeblas aŭ ne repravalorizeblas: ';
|
$PHPMAILER_LANG['recipients_failed'] = 'SMTP-eraro: La sekva(j) ricevonto(j) malsukcesis: ';
|
||||||
$PHPMAILER_LANG['extension_missing'] = 'Mankas etendo: ';
|
$PHPMAILER_LANG['signing'] = 'Subskriba eraro: ';
|
||||||
|
$PHPMAILER_LANG['smtp_code'] = 'SMTP-kodo: ';
|
||||||
|
$PHPMAILER_LANG['smtp_code_ex'] = 'Pliaj SMTP-informoj: ';
|
||||||
|
$PHPMAILER_LANG['smtp_connect_failed'] = 'La SMTP-konektiĝo malsukcesis.';
|
||||||
|
$PHPMAILER_LANG['smtp_detail'] = 'Informoj: ';
|
||||||
|
$PHPMAILER_LANG['smtp_error'] = 'Eraro de SMTP-servilo: ';
|
||||||
|
$PHPMAILER_LANG['variable_set'] = 'Ne eblas agordi aŭ reagordi la variablon: ';
|
||||||
|
$PHPMAILER_LANG['no_smtputf8'] = 'La servilo ne subtenas SMTPUTF8, kiu estas bezonata por sendi al Unicode-adresoj.';
|
||||||
|
$PHPMAILER_LANG['imap_recommended'] = 'Uzado de la simpligita adresanalizilo ne estas rekomendita. Instalu la IMAP-kromprogramon por PHP por plena RFC822-analizado.';
|
||||||
|
$PHPMAILER_LANG['deprecated_argument'] = 'Malrekomendita argumento: ';
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
$PHPMAILER_LANG['authenticate'] = 'Error SMTP: Imposible autentificar.';
|
$PHPMAILER_LANG['authenticate'] = 'Error SMTP: Imposible autentificar.';
|
||||||
$PHPMAILER_LANG['buggy_php'] = 'Tu versión de PHP está afectada por un bug que puede resultar en mensajes corruptos. Para arreglarlo, cambia a enviar usando SMTP, deshabilita la opción mail.add_x_header en tu php.ini, cambia a MacOS o Linux, o actualiza tu PHP a la versión 7.0.17+ o 7.1.3+.';
|
$PHPMAILER_LANG['buggy_php'] = 'Tu versión de PHP ha sido afectada por un bug que puede resultar en mensajes corruptos. Para arreglarlo, cambia a enviar usando SMTP, deshabilita la opción mail.add_x_header en tu php.ini, cambia a MacOS o Linux, o actualiza tu PHP a la versión 7.0.17+ o 7.1.3+.';
|
||||||
$PHPMAILER_LANG['connect_host'] = 'Error SMTP: Imposible conectar al servidor SMTP.';
|
$PHPMAILER_LANG['connect_host'] = 'Error SMTP: Imposible conectar al servidor SMTP.';
|
||||||
$PHPMAILER_LANG['data_not_accepted'] = 'Error SMTP: Datos no aceptados.';
|
$PHPMAILER_LANG['data_not_accepted'] = 'Error SMTP: Datos no aceptados.';
|
||||||
$PHPMAILER_LANG['empty_message'] = 'El cuerpo del mensaje está vacío.';
|
$PHPMAILER_LANG['empty_message'] = 'El cuerpo del mensaje está vacío.';
|
||||||
@@ -18,7 +18,7 @@ $PHPMAILER_LANG['execute'] = 'Imposible ejecutar: ';
|
|||||||
$PHPMAILER_LANG['extension_missing'] = 'Extensión faltante: ';
|
$PHPMAILER_LANG['extension_missing'] = 'Extensión faltante: ';
|
||||||
$PHPMAILER_LANG['file_access'] = 'Imposible acceder al archivo: ';
|
$PHPMAILER_LANG['file_access'] = 'Imposible acceder al archivo: ';
|
||||||
$PHPMAILER_LANG['file_open'] = 'Error de Archivo: Imposible abrir el archivo: ';
|
$PHPMAILER_LANG['file_open'] = 'Error de Archivo: Imposible abrir el archivo: ';
|
||||||
$PHPMAILER_LANG['from_failed'] = 'La(s) siguiente(s) direcciones de remitente fallaron: ';
|
$PHPMAILER_LANG['from_failed'] = 'La siguiente dirección de remitente falló: ';
|
||||||
$PHPMAILER_LANG['instantiate'] = 'Imposible crear una instancia de la función Mail.';
|
$PHPMAILER_LANG['instantiate'] = 'Imposible crear una instancia de la función Mail.';
|
||||||
$PHPMAILER_LANG['invalid_address'] = 'Imposible enviar: dirección de email inválido: ';
|
$PHPMAILER_LANG['invalid_address'] = 'Imposible enviar: dirección de email inválido: ';
|
||||||
$PHPMAILER_LANG['invalid_header'] = 'Nombre o valor de encabezado no válido';
|
$PHPMAILER_LANG['invalid_header'] = 'Nombre o valor de encabezado no válido';
|
||||||
@@ -34,3 +34,5 @@ $PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() falló.';
|
|||||||
$PHPMAILER_LANG['smtp_detail'] = 'Detalle: ';
|
$PHPMAILER_LANG['smtp_detail'] = 'Detalle: ';
|
||||||
$PHPMAILER_LANG['smtp_error'] = 'Error del servidor SMTP: ';
|
$PHPMAILER_LANG['smtp_error'] = 'Error del servidor SMTP: ';
|
||||||
$PHPMAILER_LANG['variable_set'] = 'No se pudo configurar la variable: ';
|
$PHPMAILER_LANG['variable_set'] = 'No se pudo configurar la variable: ';
|
||||||
|
$PHPMAILER_LANG['imap_recommended'] = 'No se recomienda usar el analizador de direcciones simplificado. Instala la extensión IMAP de PHP para un análisis RFC822 más completo.';
|
||||||
|
$PHPMAILER_LANG['deprecated_argument'] = 'Argumento obsoleto: ';
|
||||||
|
|||||||
+10
-6
@@ -3,27 +3,28 @@
|
|||||||
/**
|
/**
|
||||||
* Norwegian Bokmål PHPMailer language file: refer to English translation for definitive list
|
* Norwegian Bokmål PHPMailer language file: refer to English translation for definitive list
|
||||||
* @package PHPMailer
|
* @package PHPMailer
|
||||||
|
* @author Wera AS <wordpress@wera.no>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$PHPMAILER_LANG['authenticate'] = 'SMTP-feil: Kunne ikke autentiseres.';
|
$PHPMAILER_LANG['authenticate'] = 'SMTP-feil: Kunne ikke autentisere.';
|
||||||
$PHPMAILER_LANG['buggy_php'] = 'Din versjon av PHP er berørt av en feil som kan føre til ødelagte meldinger. For å løse problemet kan du bytte til SMTP, deaktivere alternativet mail.add_x_header i php.ini, bytte til MacOS eller Linux eller oppgradere PHP til versjon 7.0.17+ eller 7.1.3+.';
|
$PHPMAILER_LANG['buggy_php'] = 'Din versjon av PHP er påvirket av en feil som kan føre til ødelagte meldinger. For å løse problemet kan du bytte til sending via SMTP, deaktivere mail.add_x_header-alternativet i php.ini, bytte til MacOS eller Linux, eller oppgradere PHP til versjon 7.0.17+ eller 7.1.3+.';
|
||||||
$PHPMAILER_LANG['connect_host'] = 'SMTP-feil: Kunne ikke koble til SMTP-vert.';
|
$PHPMAILER_LANG['connect_host'] = 'SMTP-feil: Kunne ikke koble til SMTP-vert.';
|
||||||
$PHPMAILER_LANG['data_not_accepted'] = 'SMTP-feil: data ikke akseptert.';
|
$PHPMAILER_LANG['data_not_accepted'] = 'SMTP-feil: data ikke akseptert.';
|
||||||
$PHPMAILER_LANG['empty_message'] = 'Meldingstekst mangler';
|
$PHPMAILER_LANG['empty_message'] = 'Meldingsinnholdet er tomt';
|
||||||
$PHPMAILER_LANG['encoding'] = 'Ukjent koding: ';
|
$PHPMAILER_LANG['encoding'] = 'Ukjent koding: ';
|
||||||
$PHPMAILER_LANG['execute'] = 'Kunne ikke utføres: ';
|
$PHPMAILER_LANG['execute'] = 'Kunne ikke utføres: ';
|
||||||
$PHPMAILER_LANG['extension_missing'] = 'Utvidelse mangler: ';
|
$PHPMAILER_LANG['extension_missing'] = 'Utvidelse mangler: ';
|
||||||
$PHPMAILER_LANG['file_access'] = 'Kunne ikke få tilgang til filen: ';
|
$PHPMAILER_LANG['file_access'] = 'Kunne ikke få tilgang til filen: ';
|
||||||
$PHPMAILER_LANG['file_open'] = 'Feil i fil: Kunne ikke åpne filen: ';
|
$PHPMAILER_LANG['file_open'] = 'Feil i fil: Kunne ikke åpne filen: ';
|
||||||
$PHPMAILER_LANG['from_failed'] = 'Følgende Fra-adresse mislyktes: ';
|
$PHPMAILER_LANG['from_failed'] = 'Følgende avsenderadresse mislyktes: ';
|
||||||
$PHPMAILER_LANG['instantiate'] = 'Kunne ikke instansiere e-postfunksjonen.';
|
$PHPMAILER_LANG['instantiate'] = 'Kunne ikke starte e-postfunksjonen.';
|
||||||
$PHPMAILER_LANG['invalid_address'] = 'Ugyldig adresse: ';
|
$PHPMAILER_LANG['invalid_address'] = 'Ugyldig adresse: ';
|
||||||
$PHPMAILER_LANG['invalid_header'] = 'Ugyldig headernavn eller verdi';
|
$PHPMAILER_LANG['invalid_header'] = 'Ugyldig headernavn eller verdi';
|
||||||
$PHPMAILER_LANG['invalid_hostentry'] = 'Ugyldig vertsinngang: ';
|
$PHPMAILER_LANG['invalid_hostentry'] = 'Ugyldig vertsinngang: ';
|
||||||
$PHPMAILER_LANG['invalid_host'] = 'Ugyldig vert: ';
|
$PHPMAILER_LANG['invalid_host'] = 'Ugyldig vert: ';
|
||||||
$PHPMAILER_LANG['mailer_not_supported'] = ' sender er ikke støttet.';
|
$PHPMAILER_LANG['mailer_not_supported'] = ' sender er ikke støttet.';
|
||||||
$PHPMAILER_LANG['provide_address'] = 'Du må oppgi minst én mottaker-e-postadresse.';
|
$PHPMAILER_LANG['provide_address'] = 'Du må oppgi minst én mottaker-e-postadresse.';
|
||||||
$PHPMAILER_LANG['recipients_failed'] = 'SMTP Feil: Følgende mottakeradresse feilet: ';
|
$PHPMAILER_LANG['recipients_failed'] = 'SMTP-feil: Følgende mottakeradresser feilet: ';
|
||||||
$PHPMAILER_LANG['signing'] = 'Signeringsfeil: ';
|
$PHPMAILER_LANG['signing'] = 'Signeringsfeil: ';
|
||||||
$PHPMAILER_LANG['smtp_code'] = 'SMTP-kode: ';
|
$PHPMAILER_LANG['smtp_code'] = 'SMTP-kode: ';
|
||||||
$PHPMAILER_LANG['smtp_code_ex'] = 'Ytterligere SMTP-info: ';
|
$PHPMAILER_LANG['smtp_code_ex'] = 'Ytterligere SMTP-info: ';
|
||||||
@@ -31,3 +32,6 @@
|
|||||||
$PHPMAILER_LANG['smtp_detail'] = 'Detaljer: ';
|
$PHPMAILER_LANG['smtp_detail'] = 'Detaljer: ';
|
||||||
$PHPMAILER_LANG['smtp_error'] = 'SMTP-serverfeil: ';
|
$PHPMAILER_LANG['smtp_error'] = 'SMTP-serverfeil: ';
|
||||||
$PHPMAILER_LANG['variable_set'] = 'Kan ikke angi eller tilbakestille variabel: ';
|
$PHPMAILER_LANG['variable_set'] = 'Kan ikke angi eller tilbakestille variabel: ';
|
||||||
|
$PHPMAILER_LANG['no_smtputf8'] = 'Serveren støtter ikke SMTPUTF8, som er nødvendig for å sende til Unicode-adresser.';
|
||||||
|
$PHPMAILER_LANG['imap_recommended'] = 'Det anbefales ikke å bruke forenklet adresseanalyse. Installer PHP IMAP-utvidelsen for full RFC822-analyse.';
|
||||||
|
$PHPMAILER_LANG['deprecated_argument'] = 'Avviklet argument: ';
|
||||||
|
|||||||
+11
-7
@@ -4,10 +4,11 @@
|
|||||||
* Dutch PHPMailer language file: refer to PHPMailer.php for definitive list.
|
* Dutch PHPMailer language file: refer to PHPMailer.php for definitive list.
|
||||||
* @package PHPMailer
|
* @package PHPMailer
|
||||||
* @author Tuxion <team@tuxion.nl>
|
* @author Tuxion <team@tuxion.nl>
|
||||||
|
* @author Robin van der Vliet <info@robinvandervliet.com>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$PHPMAILER_LANG['authenticate'] = 'SMTP-fout: authenticatie mislukt.';
|
$PHPMAILER_LANG['authenticate'] = 'SMTP-fout: authenticatie mislukt.';
|
||||||
$PHPMAILER_LANG['buggy_php'] = 'PHP versie gededecteerd die onderhavig is aan een bug die kan resulteren in gecorrumpeerde berichten. Om dit te voorkomen, gebruik SMTP voor het verzenden van berichten, zet de mail.add_x_header optie in uw php.ini file uit, gebruik MacOS of Linux, of pas de gebruikte PHP versie aan naar versie 7.0.17+ or 7.1.3+.';
|
$PHPMAILER_LANG['buggy_php'] = 'PHP-versie gedetecteerd die onderhevig is aan een bug die kan resulteren in gecorrumpeerde berichten. Om dit te voorkomen, gebruik SMTP voor het verzenden van berichten, zet de optie mail.add_x_header in uw php.ini uit, gebruik MacOS of Linux, of pas de gebruikte PHP-versie aan naar versie 7.0.17+ or 7.1.3+.';
|
||||||
$PHPMAILER_LANG['connect_host'] = 'SMTP-fout: kon niet verbinden met SMTP-host.';
|
$PHPMAILER_LANG['connect_host'] = 'SMTP-fout: kon niet verbinden met SMTP-host.';
|
||||||
$PHPMAILER_LANG['data_not_accepted'] = 'SMTP-fout: data niet geaccepteerd.';
|
$PHPMAILER_LANG['data_not_accepted'] = 'SMTP-fout: data niet geaccepteerd.';
|
||||||
$PHPMAILER_LANG['empty_message'] = 'Berichttekst is leeg';
|
$PHPMAILER_LANG['empty_message'] = 'Berichttekst is leeg';
|
||||||
@@ -16,19 +17,22 @@ $PHPMAILER_LANG['execute'] = 'Kon niet uitvoeren: ';
|
|||||||
$PHPMAILER_LANG['extension_missing'] = 'Extensie afwezig: ';
|
$PHPMAILER_LANG['extension_missing'] = 'Extensie afwezig: ';
|
||||||
$PHPMAILER_LANG['file_access'] = 'Kreeg geen toegang tot bestand: ';
|
$PHPMAILER_LANG['file_access'] = 'Kreeg geen toegang tot bestand: ';
|
||||||
$PHPMAILER_LANG['file_open'] = 'Bestandsfout: kon bestand niet openen: ';
|
$PHPMAILER_LANG['file_open'] = 'Bestandsfout: kon bestand niet openen: ';
|
||||||
$PHPMAILER_LANG['from_failed'] = 'Het volgende afzendersadres is mislukt: ';
|
$PHPMAILER_LANG['from_failed'] = 'Het volgende afzenderadres is mislukt: ';
|
||||||
$PHPMAILER_LANG['instantiate'] = 'Kon mailfunctie niet initialiseren.';
|
$PHPMAILER_LANG['instantiate'] = 'Kon mailfunctie niet initialiseren.';
|
||||||
$PHPMAILER_LANG['invalid_address'] = 'Ongeldig adres: ';
|
$PHPMAILER_LANG['invalid_address'] = 'Ongeldig adres: ';
|
||||||
$PHPMAILER_LANG['invalid_header'] = 'Ongeldige header naam of waarde';
|
$PHPMAILER_LANG['invalid_header'] = 'Ongeldige headernaam of -waarde';
|
||||||
$PHPMAILER_LANG['invalid_hostentry'] = 'Ongeldige hostentry: ';
|
$PHPMAILER_LANG['invalid_hostentry'] = 'Ongeldige hostentry: ';
|
||||||
$PHPMAILER_LANG['invalid_host'] = 'Ongeldige host: ';
|
$PHPMAILER_LANG['invalid_host'] = 'Ongeldige host: ';
|
||||||
$PHPMAILER_LANG['mailer_not_supported'] = ' mailer wordt niet ondersteund.';
|
$PHPMAILER_LANG['mailer_not_supported'] = ' mailer wordt niet ondersteund.';
|
||||||
$PHPMAILER_LANG['provide_address'] = 'Er moet minstens één ontvanger worden opgegeven.';
|
$PHPMAILER_LANG['provide_address'] = 'Er moet minstens één ontvanger worden opgegeven.';
|
||||||
$PHPMAILER_LANG['recipients_failed'] = 'SMTP-fout: de volgende ontvangers zijn mislukt: ';
|
$PHPMAILER_LANG['recipients_failed'] = 'SMTP-fout: de volgende ontvangers zijn mislukt: ';
|
||||||
$PHPMAILER_LANG['signing'] = 'Signeerfout: ';
|
$PHPMAILER_LANG['signing'] = 'Signeerfout: ';
|
||||||
$PHPMAILER_LANG['smtp_code'] = 'SMTP code: ';
|
$PHPMAILER_LANG['smtp_code'] = 'SMTP-code: ';
|
||||||
$PHPMAILER_LANG['smtp_code_ex'] = 'Aanvullende SMTP informatie: ';
|
$PHPMAILER_LANG['smtp_code_ex'] = 'Aanvullende SMTP-informatie: ';
|
||||||
$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Verbinding mislukt.';
|
$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP-verbinding mislukt.';
|
||||||
$PHPMAILER_LANG['smtp_detail'] = 'Detail: ';
|
$PHPMAILER_LANG['smtp_detail'] = 'Detail: ';
|
||||||
$PHPMAILER_LANG['smtp_error'] = 'SMTP-serverfout: ';
|
$PHPMAILER_LANG['smtp_error'] = 'SMTP-serverfout: ';
|
||||||
$PHPMAILER_LANG['variable_set'] = 'Kan de volgende variabele niet instellen of resetten: ';
|
$PHPMAILER_LANG['variable_set'] = 'Kan de volgende variabele niet instellen of herstellen: ';
|
||||||
|
$PHPMAILER_LANG['no_smtputf8'] = 'De server ondersteunt geen SMTPUTF8 dat nodig is om naar Unicode-adressen te sturen.';
|
||||||
|
$PHPMAILER_LANG['imap_recommended'] = 'Het gebruik van de vereenvoudigde adresparser is niet aanbevolen. Installeer de IMAP-extensie voor PHP voor volledige RFC822-ondersteuning.';
|
||||||
|
$PHPMAILER_LANG['deprecated_argument'] = 'Verouderd argument: ';
|
||||||
|
|||||||
+22
-22
@@ -5,29 +5,29 @@
|
|||||||
* @package PHPMailer
|
* @package PHPMailer
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$PHPMAILER_LANG['authenticate'] = 'Błąd SMTP: Nie można przeprowadzić uwierzytelnienia.';
|
$PHPMAILER_LANG['authenticate'] = 'Błąd SMTP: nie udało się przeprowadzić uwierzytelnienia.';
|
||||||
$PHPMAILER_LANG['buggy_php'] = 'Twoja wersja PHP zawiera błąd, który może powodować uszkodzenie wiadomości. Aby go naprawić, przełącz się na wysyłanie za pomocą SMTP, wyłącz opcję mail.add_x_header w php.ini, przełącz się na MacOS lub Linux lub zaktualizuj PHP do wersji 7.0.17+ lub 7.1.3+.';
|
$PHPMAILER_LANG['buggy_php'] = 'Używana wersja PHP zawiera błąd, który może powodować uszkodzenie wiadomości. Aby temu zapobiec, użyj wysyłki przez SMTP, wyłącz opcję mail.add_x_header w php.ini, przejdź na macOS lub Linux, lub zaktualizuj PHP do wersji 7.0.17+ albo 7.1.3+.';
|
||||||
$PHPMAILER_LANG['connect_host'] = 'Błąd SMTP: Nie można połączyć się z wybranym hostem.';
|
$PHPMAILER_LANG['connect_host'] = 'Błąd SMTP: nie udało się połączyć z serwerem (hostem).';
|
||||||
$PHPMAILER_LANG['data_not_accepted'] = 'Błąd SMTP: Dane nie zostały przyjęte.';
|
$PHPMAILER_LANG['data_not_accepted'] = 'Błąd SMTP: dane wiadomości nie zostały przyjęte przez serwer.';
|
||||||
$PHPMAILER_LANG['empty_message'] = 'Wiadomość jest pusta.';
|
$PHPMAILER_LANG['empty_message'] = 'Nie można wysłać pustej wiadomości.';
|
||||||
$PHPMAILER_LANG['encoding'] = 'Błędny sposób kodowania znaków: ';
|
$PHPMAILER_LANG['encoding'] = 'Nieobsługiwane kodowanie znaków: ';
|
||||||
$PHPMAILER_LANG['execute'] = 'Nie można uruchomić: ';
|
$PHPMAILER_LANG['execute'] = 'Nie udało się uruchomić polecenia: ';
|
||||||
$PHPMAILER_LANG['extension_missing'] = 'Brakujące rozszerzenie: ';
|
$PHPMAILER_LANG['extension_missing'] = 'Brak wymaganego rozszerzenia PHP: ';
|
||||||
$PHPMAILER_LANG['file_access'] = 'Brak dostępu do pliku: ';
|
$PHPMAILER_LANG['file_access'] = 'Brak dostępu do pliku: ';
|
||||||
$PHPMAILER_LANG['file_open'] = 'Nie można otworzyć pliku: ';
|
$PHPMAILER_LANG['file_open'] = 'Nie udało się otworzyć pliku: ';
|
||||||
$PHPMAILER_LANG['from_failed'] = 'Następujący adres nadawcy jest nieprawidłowy lub nie istnieje: ';
|
$PHPMAILER_LANG['from_failed'] = 'Nieprawidłowy adres nadawcy: ';
|
||||||
$PHPMAILER_LANG['instantiate'] = 'Nie można wywołać funkcji mail(). Sprawdź konfigurację serwera.';
|
$PHPMAILER_LANG['instantiate'] = 'Nie można zainicjować funkcji mail(). Sprawdź konfigurację serwera.';
|
||||||
$PHPMAILER_LANG['invalid_address'] = 'Nie można wysłać wiadomości, ' . 'następujący adres odbiorcy jest nieprawidłowy lub nie istnieje: ';
|
$PHPMAILER_LANG['invalid_address'] = 'Nie można wysłać wiadomości. Nieprawidłowy adres odbiorcy: ';
|
||||||
$PHPMAILER_LANG['invalid_header'] = 'Nieprawidłowa nazwa lub wartość nagłówka';
|
$PHPMAILER_LANG['invalid_header'] = 'Nieprawidłowa nazwa lub wartość nagłówka.';
|
||||||
$PHPMAILER_LANG['invalid_hostentry'] = 'Nieprawidłowy wpis hosta: ';
|
$PHPMAILER_LANG['invalid_hostentry'] = 'Nieprawidłowy wpis hosta: ';
|
||||||
$PHPMAILER_LANG['invalid_host'] = 'Nieprawidłowy host: ';
|
$PHPMAILER_LANG['invalid_host'] = 'Nieprawidłowa nazwa hosta: ';
|
||||||
$PHPMAILER_LANG['provide_address'] = 'Należy podać prawidłowy adres email odbiorcy.';
|
$PHPMAILER_LANG['provide_address'] = 'Musisz podać co najmniej jeden prawidłowy adres e-mail odbiorcy.';
|
||||||
$PHPMAILER_LANG['mailer_not_supported'] = 'Wybrana metoda wysyłki wiadomości nie jest obsługiwana.';
|
$PHPMAILER_LANG['mailer_not_supported'] = 'Wybrana metoda wysyłki wiadomości nie jest obsługiwana.';
|
||||||
$PHPMAILER_LANG['recipients_failed'] = 'Błąd SMTP: Następujący odbiorcy są nieprawidłowi lub nie istnieją: ';
|
$PHPMAILER_LANG['recipients_failed'] = 'Błąd SMTP: nie udało się wysłać do następujących odbiorców: ';
|
||||||
$PHPMAILER_LANG['signing'] = 'Błąd podpisywania wiadomości: ';
|
$PHPMAILER_LANG['signing'] = 'Błąd podpisywania wiadomości cyfrowo: ';
|
||||||
$PHPMAILER_LANG['smtp_code'] = 'Kod SMTP: ';
|
$PHPMAILER_LANG['smtp_code'] = 'Kod odpowiedzi SMTP: ';
|
||||||
$PHPMAILER_LANG['smtp_code_ex'] = 'Dodatkowe informacje SMTP: ';
|
$PHPMAILER_LANG['smtp_code_ex'] = 'Dodatkowe informacje serwera SMTP: ';
|
||||||
$PHPMAILER_LANG['smtp_connect_failed'] = 'Wywołanie funkcji SMTP Connect() zostało zakończone niepowodzeniem.';
|
$PHPMAILER_LANG['smtp_connect_failed'] = 'Nie udało się nawiązać połączenia za pomocą SMTP Connect().';
|
||||||
$PHPMAILER_LANG['smtp_detail'] = 'Szczegóły: ';
|
$PHPMAILER_LANG['smtp_detail'] = 'Szczegóły błędu: ';
|
||||||
$PHPMAILER_LANG['smtp_error'] = 'Błąd SMTP: ';
|
$PHPMAILER_LANG['smtp_error'] = 'Błąd serwera SMTP: ';
|
||||||
$PHPMAILER_LANG['variable_set'] = 'Nie można ustawić lub zmodyfikować zmiennej: ';
|
$PHPMAILER_LANG['variable_set'] = 'Nie można ustawić lub zmodyfikować zmiennej: ';
|
||||||
|
|||||||
+24
-17
@@ -3,25 +3,32 @@
|
|||||||
/**
|
/**
|
||||||
* Portuguese (European) PHPMailer language file: refer to English translation for definitive list
|
* Portuguese (European) PHPMailer language file: refer to English translation for definitive list
|
||||||
* @package PHPMailer
|
* @package PHPMailer
|
||||||
* @author Jonadabe <jonadabe@hotmail.com>
|
* @author João Vieira <mail@joaovieira.eu>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$PHPMAILER_LANG['authenticate'] = 'Erro do SMTP: Não foi possível realizar a autenticação.';
|
$PHPMAILER_LANG['authenticate'] = 'Erro SMTP: Falha na autenticação.';
|
||||||
$PHPMAILER_LANG['connect_host'] = 'Erro do SMTP: Não foi possível realizar ligação com o servidor SMTP.';
|
$PHPMAILER_LANG['buggy_php'] = 'A sua versão do PHP tem um bug que pode causar mensagens corrompidas. Para resolver, utilize o envio por SMTP, desative a opção mail.add_x_header no ficheiro php.ini, mude para MacOS ou Linux, ou atualize o PHP para a versão 7.0.17+ ou 7.1.3+.';
|
||||||
$PHPMAILER_LANG['data_not_accepted'] = 'Erro do SMTP: Os dados foram rejeitados.';
|
$PHPMAILER_LANG['connect_host'] = 'Erro SMTP: Não foi possível ligar ao servidor SMTP.';
|
||||||
$PHPMAILER_LANG['empty_message'] = 'A mensagem no e-mail está vazia.';
|
$PHPMAILER_LANG['data_not_accepted'] = 'Erro SMTP: Dados não aceites.';
|
||||||
|
$PHPMAILER_LANG['empty_message'] = 'A mensagem de e-mail está vazia.';
|
||||||
$PHPMAILER_LANG['encoding'] = 'Codificação desconhecida: ';
|
$PHPMAILER_LANG['encoding'] = 'Codificação desconhecida: ';
|
||||||
$PHPMAILER_LANG['execute'] = 'Não foi possível executar: ';
|
$PHPMAILER_LANG['execute'] = 'Não foi possível executar: ';
|
||||||
$PHPMAILER_LANG['file_access'] = 'Não foi possível aceder o ficheiro: ';
|
|
||||||
$PHPMAILER_LANG['file_open'] = 'Abertura do ficheiro: Não foi possível abrir o ficheiro: ';
|
|
||||||
$PHPMAILER_LANG['from_failed'] = 'Ocorreram falhas nos endereços dos seguintes remententes: ';
|
|
||||||
$PHPMAILER_LANG['instantiate'] = 'Não foi possível iniciar uma instância da função mail.';
|
|
||||||
$PHPMAILER_LANG['invalid_address'] = 'Não foi enviado nenhum e-mail para o endereço de e-mail inválido: ';
|
|
||||||
$PHPMAILER_LANG['mailer_not_supported'] = ' mailer não é suportado.';
|
|
||||||
$PHPMAILER_LANG['provide_address'] = 'Tem de fornecer pelo menos um endereço como destinatário do e-mail.';
|
|
||||||
$PHPMAILER_LANG['recipients_failed'] = 'Erro do SMTP: O endereço do seguinte destinatário falhou: ';
|
|
||||||
$PHPMAILER_LANG['signing'] = 'Erro ao assinar: ';
|
|
||||||
$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() falhou.';
|
|
||||||
$PHPMAILER_LANG['smtp_error'] = 'Erro de servidor SMTP: ';
|
|
||||||
$PHPMAILER_LANG['variable_set'] = 'Não foi possível definir ou redefinir a variável: ';
|
|
||||||
$PHPMAILER_LANG['extension_missing'] = 'Extensão em falta: ';
|
$PHPMAILER_LANG['extension_missing'] = 'Extensão em falta: ';
|
||||||
|
$PHPMAILER_LANG['file_access'] = 'Não foi possível aceder ao ficheiro: ';
|
||||||
|
$PHPMAILER_LANG['file_open'] = 'Erro ao abrir o ficheiro: ';
|
||||||
|
$PHPMAILER_LANG['from_failed'] = 'O envio falhou para o seguinte endereço do remetente: ';
|
||||||
|
$PHPMAILER_LANG['instantiate'] = 'Não foi possível instanciar a função mail.';
|
||||||
|
$PHPMAILER_LANG['invalid_address'] = 'Endereço de e-mail inválido: ';
|
||||||
|
$PHPMAILER_LANG['invalid_header'] = 'Nome ou valor do cabeçalho inválido.';
|
||||||
|
$PHPMAILER_LANG['invalid_hostentry'] = 'Entrada de host inválida: ';
|
||||||
|
$PHPMAILER_LANG['invalid_host'] = 'Host inválido: ';
|
||||||
|
$PHPMAILER_LANG['mailer_not_supported'] = 'O cliente de e-mail não é suportado.';
|
||||||
|
$PHPMAILER_LANG['provide_address'] = 'Deve fornecer pelo menos um endereço de destinatário.';
|
||||||
|
$PHPMAILER_LANG['recipients_failed'] = 'Erro SMTP: Falha no envio para os seguintes destinatários: ';
|
||||||
|
$PHPMAILER_LANG['signing'] = 'Erro ao assinar: ';
|
||||||
|
$PHPMAILER_LANG['smtp_code'] = 'Código SMTP: ';
|
||||||
|
$PHPMAILER_LANG['smtp_code_ex'] = 'Informações adicionais SMTP: ';
|
||||||
|
$PHPMAILER_LANG['smtp_connect_failed'] = 'Falha na função SMTP connect().';
|
||||||
|
$PHPMAILER_LANG['smtp_detail'] = 'Detalhes: ';
|
||||||
|
$PHPMAILER_LANG['smtp_error'] = 'Erro do servidor SMTP: ';
|
||||||
|
$PHPMAILER_LANG['variable_set'] = 'Não foi possível definir ou redefinir a variável: ';
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
* @author Mehmet Benlioğlu
|
* @author Mehmet Benlioğlu
|
||||||
* @author @yasinaydin
|
* @author @yasinaydin
|
||||||
* @author Ogün Karakuş
|
* @author Ogün Karakuş
|
||||||
|
* @author Mustafa Deniz Buksur
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$PHPMAILER_LANG['authenticate'] = 'SMTP Hatası: Oturum açılamadı.';
|
$PHPMAILER_LANG['authenticate'] = 'SMTP Hatası: Oturum açılamadı.';
|
||||||
@@ -36,3 +37,7 @@ $PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP connect() fonksiyonu başarısı
|
|||||||
$PHPMAILER_LANG['smtp_detail'] = 'SMTP SMTP Detayı: ';
|
$PHPMAILER_LANG['smtp_detail'] = 'SMTP SMTP Detayı: ';
|
||||||
$PHPMAILER_LANG['smtp_error'] = 'SMTP sunucu hatası: ';
|
$PHPMAILER_LANG['smtp_error'] = 'SMTP sunucu hatası: ';
|
||||||
$PHPMAILER_LANG['variable_set'] = 'Değişken ayarlanamadı ya da sıfırlanamadı: ';
|
$PHPMAILER_LANG['variable_set'] = 'Değişken ayarlanamadı ya da sıfırlanamadı: ';
|
||||||
|
$PHPMAILER_LANG['no_smtputf8'] = 'Unicode adreslere gönderim için gereken SMTPUTF8 desteği sunucu tarafından desteklenmiyor.';
|
||||||
|
$PHPMAILER_LANG['imap_recommended'] = 'Basitleştirilmiş adres ayrıştırıcısını kullanmanız önerilmez. ' .
|
||||||
|
'Tam RFC822 ayrıştırma için PHP IMAP eklentisini yükleyin.';
|
||||||
|
$PHPMAILER_LANG['deprecated_argument'] = 'Kullanımdan kaldırılmış argüman: ';
|
||||||
|
|||||||
+525
-187
File diff suppressed because it is too large
Load Diff
+20
-6
@@ -45,8 +45,9 @@ class POP3
|
|||||||
* The POP3 PHPMailer Version number.
|
* The POP3 PHPMailer Version number.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
|
* @deprecated This constant will be removed in PHPMailer 8.0. Use `PHPMailer::VERSION` instead.
|
||||||
*/
|
*/
|
||||||
const VERSION = '6.9.3';
|
const VERSION = '7.1.1';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default POP3 port number.
|
* Default POP3 port number.
|
||||||
@@ -211,9 +212,9 @@ class POP3
|
|||||||
} else {
|
} else {
|
||||||
$this->tval = (int) $timeout;
|
$this->tval = (int) $timeout;
|
||||||
}
|
}
|
||||||
$this->do_debug = $debug_level;
|
$this->do_debug = (int) $debug_level;
|
||||||
$this->username = $username;
|
$this->username = self::stripControls($username);
|
||||||
$this->password = $password;
|
$this->password = self::stripControls($password);
|
||||||
//Reset the error log
|
//Reset the error log
|
||||||
$this->errors = [];
|
$this->errors = [];
|
||||||
//Connect
|
//Connect
|
||||||
@@ -318,7 +319,8 @@ class POP3
|
|||||||
if (empty($password)) {
|
if (empty($password)) {
|
||||||
$password = $this->password;
|
$password = $this->password;
|
||||||
}
|
}
|
||||||
|
$username = self::stripControls($username);
|
||||||
|
$password = self::stripControls($password);
|
||||||
//Send the Username
|
//Send the Username
|
||||||
$this->sendString("USER $username" . static::LE);
|
$this->sendString("USER $username" . static::LE);
|
||||||
$pop3_response = $this->getResponse();
|
$pop3_response = $this->getResponse();
|
||||||
@@ -406,7 +408,7 @@ class POP3
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the POP3 server response.
|
* Checks the POP3 server response.
|
||||||
* Looks for for +OK or -ERR.
|
* Looks for +OK or -ERR.
|
||||||
*
|
*
|
||||||
* @param string $string
|
* @param string $string
|
||||||
*
|
*
|
||||||
@@ -466,4 +468,16 @@ class POP3
|
|||||||
"errno: $errno errstr: $errstr; errfile: $errfile; errline: $errline"
|
"errno: $errno errstr: $errstr; errfile: $errfile; errline: $errline"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strip all control chars from a string.
|
||||||
|
*
|
||||||
|
* @param $string
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function stripControls($string)
|
||||||
|
{
|
||||||
|
return preg_replace('/[\x00-\x1F\x7F]/u', '', $string);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+121
-13
@@ -34,8 +34,9 @@ class SMTP
|
|||||||
* The PHPMailer SMTP version number.
|
* The PHPMailer SMTP version number.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
|
* @deprecated This constant will be removed in PHPMailer 8.0. Use `PHPMailer::VERSION` instead.
|
||||||
*/
|
*/
|
||||||
const VERSION = '6.9.3';
|
const VERSION = '7.1.1';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SMTP line break constant.
|
* SMTP line break constant.
|
||||||
@@ -159,6 +160,15 @@ class SMTP
|
|||||||
*/
|
*/
|
||||||
public $do_verp = false;
|
public $do_verp = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to use SMTPUTF8.
|
||||||
|
*
|
||||||
|
* @see https://www.rfc-editor.org/rfc/rfc6531
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $do_smtputf8 = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The timeout value for connection, in seconds.
|
* The timeout value for connection, in seconds.
|
||||||
* Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
|
* Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
|
||||||
@@ -196,6 +206,7 @@ class SMTP
|
|||||||
'Haraka' => '/[\d]{3} Message Queued \((.*)\)/',
|
'Haraka' => '/[\d]{3} Message Queued \((.*)\)/',
|
||||||
'ZoneMTA' => '/[\d]{3} Message queued as (.*)/',
|
'ZoneMTA' => '/[\d]{3} Message queued as (.*)/',
|
||||||
'Mailjet' => '/[\d]{3} OK queued as (.*)/',
|
'Mailjet' => '/[\d]{3} OK queued as (.*)/',
|
||||||
|
'Gsmtp' => '/[\d]{3} 2\.0\.0 OK (.*) - gsmtp/',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -484,7 +495,9 @@ class SMTP
|
|||||||
//PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
|
//PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
|
||||||
//so add them back in manually if we can
|
//so add them back in manually if we can
|
||||||
if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
|
if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
|
||||||
|
// phpcs:ignore PHPCompatibility.Constants.NewConstants.stream_crypto_method_tlsv1_2_clientFound
|
||||||
$crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
|
$crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
|
||||||
|
// phpcs:ignore PHPCompatibility.Constants.NewConstants.stream_crypto_method_tlsv1_1_clientFound
|
||||||
$crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
|
$crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,11 +636,48 @@ class SMTP
|
|||||||
if (null === $OAuth) {
|
if (null === $OAuth) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$oauth = $OAuth->getOauth64();
|
try {
|
||||||
|
$oauth = $OAuth->getOauth64();
|
||||||
//Start authentication
|
} catch (\Exception $e) {
|
||||||
if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
|
// We catch all exceptions and convert them to PHPMailer exceptions to be able to
|
||||||
return false;
|
// handle them correctly later
|
||||||
|
throw new Exception("SMTP authentication error", 0, $e);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* An SMTP command line can have a maximum length of 512 bytes, including the command name,
|
||||||
|
* so the base64-encoded OAUTH token has a maximum length of:
|
||||||
|
* 512 - 13 (AUTH XOAUTH2) - 2 (CRLF) = 497 bytes
|
||||||
|
* If the token is longer than that, the command and the token must be sent separately as described in
|
||||||
|
* https://www.rfc-editor.org/rfc/rfc4954#section-4
|
||||||
|
*/
|
||||||
|
if ($oauth === '') {
|
||||||
|
//Sending an empty auth token is legitimate, but it must be encoded as '='
|
||||||
|
//to indicate it's not a 2-part command
|
||||||
|
if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 =', 235)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} elseif (strlen($oauth) <= 497) {
|
||||||
|
//Authenticate using a token in the initial-response part
|
||||||
|
if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//The token is too long, so we need to send it in two parts.
|
||||||
|
//Send the auth command without a token and expect a 334
|
||||||
|
if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2', 334)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//Send the token
|
||||||
|
if (!$this->sendCommand('OAuth TOKEN', $oauth, [235, 334])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//If the server answers with 334, send an empty line and wait for a 235
|
||||||
|
if (
|
||||||
|
substr($this->last_reply, 0, 3) === '334'
|
||||||
|
&& $this->sendCommand('AUTH End', '', 235)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -720,6 +770,25 @@ class SMTP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function iterateLines($s)
|
||||||
|
{
|
||||||
|
$start = 0;
|
||||||
|
$length = strlen($s);
|
||||||
|
|
||||||
|
for ($i = 0; $i < $length; $i++) {
|
||||||
|
$c = $s[$i];
|
||||||
|
if ($c === "\n" || $c === "\r") {
|
||||||
|
yield substr($s, $start, $i - $start);
|
||||||
|
if ($c === "\r" && $i + 1 < $length && $s[$i + 1] === "\n") {
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
$start = $i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
yield substr($s, $start);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send an SMTP DATA command.
|
* Send an SMTP DATA command.
|
||||||
* Issues a data command and sends the msg_data to the server,
|
* Issues a data command and sends the msg_data to the server,
|
||||||
@@ -748,15 +817,16 @@ class SMTP
|
|||||||
* NOTE: this does not count towards line-length limit.
|
* NOTE: this does not count towards line-length limit.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//Normalize line breaks before exploding
|
//Iterate over lines with normalized line breaks
|
||||||
$lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data));
|
$lines = $this->iterateLines($msg_data);
|
||||||
|
|
||||||
/* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
|
/* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
|
||||||
* of the first line (':' separated) does not contain a space then it _should_ be a header, and we will
|
* of the first line (':' separated) does not contain a space then it _should_ be a header, and we will
|
||||||
* process all lines before a blank line as headers.
|
* process all lines before a blank line as headers.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$field = substr($lines[0], 0, strpos($lines[0], ':'));
|
$first_line = $lines->current();
|
||||||
|
$field = substr($first_line, 0, strpos($first_line, ':'));
|
||||||
$in_headers = false;
|
$in_headers = false;
|
||||||
if (!empty($field) && strpos($field, ' ') === false) {
|
if (!empty($field) && strpos($field, ' ') === false) {
|
||||||
$in_headers = true;
|
$in_headers = true;
|
||||||
@@ -913,7 +983,15 @@ class SMTP
|
|||||||
* $from. Returns true if successful or false otherwise. If True
|
* $from. Returns true if successful or false otherwise. If True
|
||||||
* the mail transaction is started and then one or more recipient
|
* the mail transaction is started and then one or more recipient
|
||||||
* commands may be called followed by a data command.
|
* commands may be called followed by a data command.
|
||||||
* Implements RFC 821: MAIL <SP> FROM:<reverse-path> <CRLF>.
|
* Implements RFC 821: MAIL <SP> FROM:<reverse-path> <CRLF> and
|
||||||
|
* two extensions, namely XVERP and SMTPUTF8.
|
||||||
|
*
|
||||||
|
* The server's EHLO response is not checked. If use of either
|
||||||
|
* extensions is enabled even though the server does not support
|
||||||
|
* that, mail submission will fail.
|
||||||
|
*
|
||||||
|
* XVERP is documented at https://www.postfix.org/VERP_README.html
|
||||||
|
* and SMTPUTF8 is specified in RFC 6531.
|
||||||
*
|
*
|
||||||
* @param string $from Source address of this message
|
* @param string $from Source address of this message
|
||||||
*
|
*
|
||||||
@@ -922,10 +1000,11 @@ class SMTP
|
|||||||
public function mail($from)
|
public function mail($from)
|
||||||
{
|
{
|
||||||
$useVerp = ($this->do_verp ? ' XVERP' : '');
|
$useVerp = ($this->do_verp ? ' XVERP' : '');
|
||||||
|
$useSmtputf8 = ($this->do_smtputf8 ? ' SMTPUTF8' : '');
|
||||||
|
|
||||||
return $this->sendCommand(
|
return $this->sendCommand(
|
||||||
'MAIL FROM',
|
'MAIL FROM',
|
||||||
'MAIL FROM:<' . $from . '>' . $useVerp,
|
'MAIL FROM:<' . $from . '>' . $useSmtputf8 . $useVerp,
|
||||||
250
|
250
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1210,7 +1289,7 @@ class SMTP
|
|||||||
* 3. EHLO has been sent -
|
* 3. EHLO has been sent -
|
||||||
* $name == 'HELO'|'EHLO': returns the server name
|
* $name == 'HELO'|'EHLO': returns the server name
|
||||||
* $name == any other string: if extension $name exists, returns True
|
* $name == any other string: if extension $name exists, returns True
|
||||||
* or its options (e.g. AUTH mechanisms supported). Otherwise returns False.
|
* or its options (e.g. AUTH mechanisms supported). Otherwise, returns False.
|
||||||
*
|
*
|
||||||
* @param string $name Name of SMTP extension or 'HELO'|'EHLO'
|
* @param string $name Name of SMTP extension or 'HELO'|'EHLO'
|
||||||
*
|
*
|
||||||
@@ -1291,7 +1370,16 @@ class SMTP
|
|||||||
|
|
||||||
//stream_select returns false when the `select` system call is interrupted
|
//stream_select returns false when the `select` system call is interrupted
|
||||||
//by an incoming signal, try the select again
|
//by an incoming signal, try the select again
|
||||||
if (stripos($message, 'interrupted system call') !== false) {
|
if (
|
||||||
|
stripos($message, 'interrupted system call') !== false ||
|
||||||
|
(
|
||||||
|
// on applications with a different locale than english, the message above is not found because
|
||||||
|
// it's translated. So we also check for the SOCKET_EINTR constant which is defined under
|
||||||
|
// Windows and UNIX-like platforms (if available on the platform).
|
||||||
|
defined('SOCKET_EINTR') &&
|
||||||
|
stripos($message, 'stream_select(): Unable to select [' . SOCKET_EINTR . ']') !== false
|
||||||
|
)
|
||||||
|
) {
|
||||||
$this->edebug(
|
$this->edebug(
|
||||||
'SMTP -> get_lines(): retrying stream_select',
|
'SMTP -> get_lines(): retrying stream_select',
|
||||||
self::DEBUG_LOWLEVEL
|
self::DEBUG_LOWLEVEL
|
||||||
@@ -1364,6 +1452,26 @@ class SMTP
|
|||||||
return $this->do_verp;
|
return $this->do_verp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable or disable use of SMTPUTF8.
|
||||||
|
*
|
||||||
|
* @param bool $enabled
|
||||||
|
*/
|
||||||
|
public function setSMTPUTF8($enabled = false)
|
||||||
|
{
|
||||||
|
$this->do_smtputf8 = $enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get SMTPUTF8 use.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getSMTPUTF8()
|
||||||
|
{
|
||||||
|
return $this->do_smtputf8;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set error messages and codes.
|
* Set error messages and codes.
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user