Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1249ad3079 | ||
|
|
48ee511166 | ||
|
|
6718e08d1f | ||
|
|
b8b01cd5d5 |
@@ -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`");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -43,7 +43,6 @@ class VisualLimsApiClient
|
|||||||
{
|
{
|
||||||
$ch = curl_init("{$this->baseUrl}/api/authentication/authenticate");
|
$ch = curl_init("{$this->baseUrl}/api/authentication/authenticate");
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
curl_setopt($ch, CURLOPT_USERAGENT, 'VisualLims-Client/1.0');
|
|
||||||
curl_setopt($ch, CURLOPT_POST, true);
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
|
||||||
'Username' => $this->username,
|
'Username' => $this->username,
|
||||||
@@ -106,7 +105,6 @@ class VisualLimsApiClient
|
|||||||
|
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
curl_setopt($ch, CURLOPT_USERAGENT, 'VisualLims-Client/1.0');
|
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
"Authorization: Bearer {$token}",
|
"Authorization: Bearer {$token}",
|
||||||
"Accept: application/json"
|
"Accept: application/json"
|
||||||
@@ -146,7 +144,6 @@ class VisualLimsApiClient
|
|||||||
|
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
curl_setopt($ch, CURLOPT_USERAGENT, 'VisualLims-Client/1.0');
|
|
||||||
curl_setopt($ch, CURLOPT_POST, true);
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
@@ -180,7 +177,6 @@ class VisualLimsApiClient
|
|||||||
|
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
curl_setopt($ch, CURLOPT_USERAGENT, 'VisualLims-Client/1.0');
|
|
||||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH");
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH");
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
@@ -229,7 +225,6 @@ class VisualLimsApiClient
|
|||||||
|
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
curl_setopt($ch, CURLOPT_USERAGENT, 'VisualLims-Client/1.0');
|
|
||||||
curl_setopt($ch, CURLOPT_POST, true);
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
@@ -273,7 +268,6 @@ class VisualLimsApiClient
|
|||||||
|
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
curl_setopt($ch, CURLOPT_USERAGENT, 'VisualLims-Client/1.0');
|
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
"Authorization: Bearer {$token}",
|
"Authorization: Bearer {$token}",
|
||||||
"Accept: */*"
|
"Accept: */*"
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controllo blocco cliente.
|
|
||||||
*
|
|
||||||
* Il cliente usa il campo CodiceASL dell'anagrafica LIMS come nota di blocco
|
|
||||||
* (es. "BLOCCO PER PAGAMENTO"), perché i campi nativi del Blocco Accettazione
|
|
||||||
* non sono esposti dall'API.
|
|
||||||
*
|
|
||||||
* Regola: se è indicato il cliente di Fatturazione avanzata si controlla quello,
|
|
||||||
* altrimenti il Client.
|
|
||||||
*
|
|
||||||
* NESSUNA chiamata API: legge solo il file di cache scritto da get_clienti.php
|
|
||||||
* (cache/clienti.json), con la sua durata già esistente. Se il file non c'è,
|
|
||||||
* il controllo viene saltato.
|
|
||||||
*
|
|
||||||
* Restituisce null se non c'è blocco o se il controllo non è possibile:
|
|
||||||
* è solo un avviso e non deve mai far fallire l'export o la mail.
|
|
||||||
*/
|
|
||||||
if (!function_exists('getClientBlockAlert')) {
|
|
||||||
function getClientBlockAlert($clienteId, $clienteFatturazione = null): ?array
|
|
||||||
{
|
|
||||||
// Mappa IdCliente => [Nominativo, CodiceASL], letta una sola volta per richiesta
|
|
||||||
static $clienti = null;
|
|
||||||
|
|
||||||
$useFatturazione = !empty($clienteFatturazione);
|
|
||||||
$targetId = $useFatturazione ? (int) $clienteFatturazione : (int) $clienteId;
|
|
||||||
|
|
||||||
if ($targetId <= 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($clienti === null) {
|
|
||||||
$clienti = [];
|
|
||||||
$cacheFile = dirname(__DIR__) . '/cache/clienti.json';
|
|
||||||
|
|
||||||
if (!is_file($cacheFile)) {
|
|
||||||
error_log("getClientBlockAlert: cache clienti non trovata ({$cacheFile}), controllo saltato");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$json = json_decode((string) file_get_contents($cacheFile), true);
|
|
||||||
foreach (($json['value'] ?? []) as $c) {
|
|
||||||
$clienti[(int) ($c['IdCliente'] ?? 0)] = [
|
|
||||||
'nominativo' => trim((string) ($c['Nominativo'] ?? '')),
|
|
||||||
'codiceASL' => trim((string) ($c['CodiceASL'] ?? '')),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$cliente = $clienti[$targetId] ?? null;
|
|
||||||
if (!$cliente) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$motivo = $cliente['codiceASL'];
|
|
||||||
if ($motivo === '' || stripos($motivo, 'blocco') === false) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'idCliente' => $targetId,
|
|
||||||
'nominativo' => $cliente['nominativo'],
|
|
||||||
'motivo' => $motivo,
|
|
||||||
'campo' => $useFatturazione ? 'Fatturazione avanzata' : 'Client',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -69,7 +69,7 @@ try {
|
|||||||
|
|
||||||
$stmtLoadAnalyses = $pdo->prepare("
|
$stmtLoadAnalyses = $pdo->prepare("
|
||||||
SELECT idmatrice, analysis_recordkey, analysis_name, analysis_method,
|
SELECT idmatrice, analysis_recordkey, analysis_name, analysis_method,
|
||||||
analysis_level, analysis_price, is_web_selectable, is_accredited
|
analysis_level, is_web_selectable, is_accredited
|
||||||
FROM identification_parts_analyses
|
FROM identification_parts_analyses
|
||||||
WHERE part_id = ?
|
WHERE part_id = ?
|
||||||
ORDER BY id ASC
|
ORDER BY id ASC
|
||||||
@@ -78,10 +78,10 @@ try {
|
|||||||
$stmtInsertAnalysis = $pdo->prepare("
|
$stmtInsertAnalysis = $pdo->prepare("
|
||||||
INSERT INTO identification_parts_analyses
|
INSERT INTO identification_parts_analyses
|
||||||
(part_id, iddatadb, idmatrice, analysis_recordkey, analysis_name, analysis_method,
|
(part_id, iddatadb, idmatrice, analysis_recordkey, analysis_name, analysis_method,
|
||||||
analysis_level, analysis_price, is_web_selectable, is_accredited, created_at, updated_at)
|
analysis_level, is_web_selectable, is_accredited, created_at, updated_at)
|
||||||
VALUES
|
VALUES
|
||||||
(:part_id, :iddatadb, :idmatrice, :analysis_recordkey, :analysis_name, :analysis_method,
|
(:part_id, :iddatadb, :idmatrice, :analysis_recordkey, :analysis_name, :analysis_method,
|
||||||
:analysis_level, :analysis_price, :is_web_selectable, :is_accredited, NOW(), NOW())
|
:analysis_level, :is_web_selectable, :is_accredited, NOW(), NOW())
|
||||||
");
|
");
|
||||||
|
|
||||||
$stmtInsertCF = $pdo->prepare("
|
$stmtInsertCF = $pdo->prepare("
|
||||||
@@ -161,7 +161,6 @@ try {
|
|||||||
':analysis_name' => $an['analysis_name'] ?? null,
|
':analysis_name' => $an['analysis_name'] ?? null,
|
||||||
':analysis_method' => $an['analysis_method'] ?? null,
|
':analysis_method' => $an['analysis_method'] ?? null,
|
||||||
':analysis_level' => ($an['analysis_level'] !== null && $an['analysis_level'] !== '') ? (int)$an['analysis_level'] : null,
|
':analysis_level' => ($an['analysis_level'] !== null && $an['analysis_level'] !== '') ? (int)$an['analysis_level'] : null,
|
||||||
':analysis_price' => ($an['analysis_price'] !== null && $an['analysis_price'] !== '') ? $an['analysis_price'] : null,
|
|
||||||
':is_web_selectable' => (int)($an['is_web_selectable'] ?? 0),
|
':is_web_selectable' => (int)($an['is_web_selectable'] ?? 0),
|
||||||
':is_accredited' => (int)($an['is_accredited'] ?? 0),
|
':is_accredited' => (int)($an['is_accredited'] ?? 0),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -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()]);
|
||||||
|
}
|
||||||
@@ -146,10 +146,6 @@ $rowsData = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
background: #fd7e14;
|
background: #fd7e14;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-delete {
|
|
||||||
background: #dc3545;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pager-bar {
|
.pager-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -236,7 +232,7 @@ $rowsData = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
<table class="err-table">
|
<table class="err-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:210px;">Azioni</th>
|
<th style="width:170px;">Azioni</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Client</th>
|
<th>Client</th>
|
||||||
<th>Errore</th>
|
<th>Errore</th>
|
||||||
@@ -254,7 +250,6 @@ $rowsData = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
<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>
|
<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; ?>
|
<?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>
|
<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>
|
||||||
<button type="button" class="action-btn btn-delete delete-btn" data-iddatadb="<?= (int)$row['iddatadb'] ?>" title="Elimina record"><i class="fas fa-trash"></i></button>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="status-badge status-x">Error</span>
|
<span class="status-badge status-x">Error</span>
|
||||||
@@ -302,12 +297,11 @@ $rowsData = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Reinvia: richiama export_to_lims.php. Se la commessa era già stata creata,
|
// Reinvia: richiama export_to_lims.php da capo (create incluso)
|
||||||
// il CHECK PRELIMINARE la verifica nel LIMS e non ne crea una seconda.
|
|
||||||
$(document).on('click', '.resend-btn', function() {
|
$(document).on('click', '.resend-btn', function() {
|
||||||
const $btn = $(this);
|
const $btn = $(this);
|
||||||
const iddatadb = $btn.data('iddatadb');
|
const iddatadb = $btn.data('iddatadb');
|
||||||
if (!confirm('Reinviare questa commessa al LIMS? Se risulta già nel LIMS verrà solo riallineata, altrimenti ne verrà creata una nuova.')) return;
|
if (!confirm('Reinviare questa commessa al LIMS? Verrà creata una nuova commessa nel LIMS.')) return;
|
||||||
|
|
||||||
const original = $btn.html();
|
const original = $btn.html();
|
||||||
$btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i>');
|
$btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i>');
|
||||||
@@ -336,39 +330,6 @@ $rowsData = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Elimina record
|
|
||||||
$(document).on('click', '.delete-btn', function() {
|
|
||||||
const $btn = $(this);
|
|
||||||
const iddatadb = $btn.data('iddatadb');
|
|
||||||
if (!confirm('Eliminare definitivamente questo record? L\'operazione non è reversibile.')) return;
|
|
||||||
|
|
||||||
const original = $btn.html();
|
|
||||||
$btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i>');
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: 'revert_status.php',
|
|
||||||
method: 'POST',
|
|
||||||
dataType: 'json',
|
|
||||||
data: {
|
|
||||||
iddatadb: iddatadb,
|
|
||||||
action: 'delete'
|
|
||||||
},
|
|
||||||
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 : 'cancellazione non riuscita'));
|
|
||||||
$btn.prop('disabled', false).html(original);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function() {
|
|
||||||
alert('Errore di rete.');
|
|
||||||
$btn.prop('disabled', false).html(original);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Revert a Imported (i)
|
// Revert a Imported (i)
|
||||||
$(document).on('click', '.revert-btn', function() {
|
$(document).on('click', '.revert-btn', function() {
|
||||||
const $btn = $(this);
|
const $btn = $(this);
|
||||||
|
|||||||
@@ -42,23 +42,6 @@
|
|||||||
}
|
}
|
||||||
window.noPriceBadgeHTML = noPriceBadgeHTML;
|
window.noPriceBadgeHTML = noPriceBadgeHTML;
|
||||||
|
|
||||||
// Riquadro "cliente bloccato" per la finestra di export singolo
|
|
||||||
function blockAlertHTML(b) {
|
|
||||||
if (!b || !b.motivo) return "";
|
|
||||||
return (
|
|
||||||
`<div class="alert alert-danger py-2 px-3 mt-3 mb-0 small">` +
|
|
||||||
`<i class="fas fa-ban me-1"></i><strong>Cliente bloccato</strong> (${escHtml(b.campo)}): ` +
|
|
||||||
`${escHtml(b.nominativo)} - ${escHtml(b.idCliente)}<br>` +
|
|
||||||
`<strong>${escHtml(b.motivo)}</strong></div>`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Badge "Bloccato" per l'elenco commesse dell'Export All
|
|
||||||
function blockBadgeHTML(b) {
|
|
||||||
if (!b || !b.motivo) return "";
|
|
||||||
return `<span class="badge" title="${escHtml(b.nominativo + ": " + b.motivo)}" style="margin-left:6px; font-size:0.85em; cursor:help; vertical-align:middle; background:#f8d7da; color:#842029;"><i class="fas fa-ban"></i> Bloccato</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getGridRow(iddatadb) {
|
function getGridRow(iddatadb) {
|
||||||
return document.querySelector(`.grid-row[data-id="${iddatadb}"]`);
|
return document.querySelector(`.grid-row[data-id="${iddatadb}"]`);
|
||||||
}
|
}
|
||||||
@@ -186,28 +169,15 @@
|
|||||||
formData.append("iddatadb", iddatadb);
|
formData.append("iddatadb", iddatadb);
|
||||||
if (batchUuid) formData.append("batch_uuid", batchUuid);
|
if (batchUuid) formData.append("batch_uuid", batchUuid);
|
||||||
|
|
||||||
// Marca la riga come "in export" nei dati: se il grid virtuale la ri-renderizza
|
const response = await fetch("export_to_lims.php", {
|
||||||
// durante l'attesa, il pulsante Export resta disabilitato.
|
method: "POST",
|
||||||
const startIdx = getRowIndexByIddatadb(iddatadb);
|
body: formData,
|
||||||
if (startIdx >= 0) window.gridData[startIdx]._exporting = true;
|
});
|
||||||
|
if (!response.ok)
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
let data;
|
if (data.success) {
|
||||||
try {
|
|
||||||
const response = await fetch("export_to_lims.php", {
|
|
||||||
method: "POST",
|
|
||||||
body: formData,
|
|
||||||
});
|
|
||||||
if (!response.ok)
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
data = await response.json();
|
|
||||||
} finally {
|
|
||||||
const endIdx = getRowIndexByIddatadb(iddatadb);
|
|
||||||
if (endIdx >= 0) delete window.gridData[endIdx]._exporting;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rifiutato perché la riga risulta già esportata (pagina non aggiornata):
|
|
||||||
// allinea la griglia allo stato reale invece di lasciarla esportabile.
|
|
||||||
if (data.success || (data.rejected && data.status === "l")) {
|
|
||||||
// Update gridData
|
// Update gridData
|
||||||
const idx = getRowIndexByIddatadb(iddatadb);
|
const idx = getRowIndexByIddatadb(iddatadb);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
@@ -284,8 +254,7 @@
|
|||||||
.map((n) => `<li>${escHtml(n)}</li>`)
|
.map((n) => `<li>${escHtml(n)}</li>`)
|
||||||
.join("") +
|
.join("") +
|
||||||
`</ul>`
|
`</ul>`
|
||||||
: "") +
|
: "");
|
||||||
blockAlertHTML(data.blockAlert);
|
|
||||||
label.textContent = "Esportazione Completata";
|
label.textContent = "Esportazione Completata";
|
||||||
} else {
|
} else {
|
||||||
msg.textContent = `Errore: ${data.message}`;
|
msg.textContent = `Errore: ${data.message}`;
|
||||||
@@ -475,9 +444,8 @@
|
|||||||
data.message || "Unknown error",
|
data.message || "Unknown error",
|
||||||
);
|
);
|
||||||
showExportResult(data);
|
showExportResult(data);
|
||||||
// Mail di riepilogo per la singola commessa (non per un export rifiutato:
|
// Mail di riepilogo per la singola commessa
|
||||||
// non è stato inviato nulla e la riga può risultare già 'l')
|
sendExportSummaryMail([iddatadb]);
|
||||||
if (!data.rejected) sendExportSummaryMail([iddatadb]);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (gridRow) {
|
if (gridRow) {
|
||||||
setRowExporting(gridRow, false);
|
setRowExporting(gridRow, false);
|
||||||
@@ -485,9 +453,6 @@
|
|||||||
}
|
}
|
||||||
showRowError(gridRow, iddatadb, error.message);
|
showRowError(gridRow, iddatadb, error.message);
|
||||||
showExportResult({ success: false, message: error.message });
|
showExportResult({ success: false, message: error.message });
|
||||||
// Timeout/errore di rete: il server può aver completato lo stesso.
|
|
||||||
// La mail riporta lo stato reale della riga in DB.
|
|
||||||
sendExportSummaryMail([iddatadb]);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -727,8 +692,7 @@
|
|||||||
const total = eligibleRows.length;
|
const total = eligibleRows.length;
|
||||||
let processed = 0,
|
let processed = 0,
|
||||||
succeeded = 0,
|
succeeded = 0,
|
||||||
failed = 0,
|
failed = 0;
|
||||||
skipped = 0;
|
|
||||||
const exportedList = []; // { commessaweb, noPriceCount, noPriceAnalyses }
|
const exportedList = []; // { commessaweb, noPriceCount, noPriceAnalyses }
|
||||||
const processedIds = []; // iddatadb processati (ok o errore) per la mail
|
const processedIds = []; // iddatadb processati (ok o errore) per la mail
|
||||||
|
|
||||||
@@ -764,26 +728,19 @@
|
|||||||
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.rejected) {
|
if (data.success) {
|
||||||
// Già esportata / export in corso altrove: niente inviato, fuori dalla mail
|
|
||||||
skipped++;
|
|
||||||
if (data.status !== "l" && gridRow)
|
|
||||||
showRowError(gridRow, iddatadb, data.message);
|
|
||||||
} else if (data.success) {
|
|
||||||
processedIds.push(iddatadb);
|
|
||||||
succeeded++;
|
succeeded++;
|
||||||
exportedList.push({
|
exportedList.push({
|
||||||
commessaweb: data.commessaweb,
|
commessaweb: data.commessaweb,
|
||||||
noPriceCount: data.noPriceCount || 0,
|
noPriceCount: data.noPriceCount || 0,
|
||||||
noPriceAnalyses: data.noPriceAnalyses || [],
|
noPriceAnalyses: data.noPriceAnalyses || [],
|
||||||
blockAlert: data.blockAlert || null,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
failed++;
|
failed++;
|
||||||
processedIds.push(iddatadb);
|
|
||||||
const errIdx = getRowIndexByIddatadb(iddatadb);
|
const errIdx = getRowIndexByIddatadb(iddatadb);
|
||||||
if (errIdx >= 0)
|
if (errIdx >= 0)
|
||||||
window.gridData[errIdx]._exportError =
|
window.gridData[errIdx]._exportError =
|
||||||
@@ -798,7 +755,6 @@
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
processed++;
|
processed++;
|
||||||
failed++;
|
failed++;
|
||||||
processedIds.push(iddatadb);
|
|
||||||
const errIdx = getRowIndexByIddatadb(iddatadb);
|
const errIdx = getRowIndexByIddatadb(iddatadb);
|
||||||
if (errIdx >= 0)
|
if (errIdx >= 0)
|
||||||
window.gridData[errIdx]._exportError = error.message;
|
window.gridData[errIdx]._exportError = error.message;
|
||||||
@@ -837,34 +793,23 @@
|
|||||||
if (batchCancelled) {
|
if (batchCancelled) {
|
||||||
labelEl.textContent = "Export All — Cancelled";
|
labelEl.textContent = "Export All — Cancelled";
|
||||||
msgEl.innerHTML = `Exported: <strong>${succeeded}</strong><br>Errors: <strong>${failed}</strong><br>Not processed: <strong>${total - processed}</strong>`;
|
msgEl.innerHTML = `Exported: <strong>${succeeded}</strong><br>Errors: <strong>${failed}</strong><br>Not processed: <strong>${total - processed}</strong>`;
|
||||||
} else if (failed === 0 && skipped === 0) {
|
} else if (failed === 0) {
|
||||||
labelEl.textContent = "Export All — Complete";
|
labelEl.textContent = "Export All — Complete";
|
||||||
msgEl.innerHTML = `All <strong>${succeeded}</strong> rows exported successfully.`;
|
msgEl.innerHTML = `All <strong>${succeeded}</strong> rows exported successfully.`;
|
||||||
} else {
|
} else {
|
||||||
labelEl.textContent =
|
labelEl.textContent = "Export All — Completed with errors";
|
||||||
failed > 0
|
|
||||||
? "Export All — Completed with errors"
|
|
||||||
: "Export All — Complete";
|
|
||||||
msgEl.innerHTML = `Exported: <strong>${succeeded}</strong><br>Errors: <strong>${failed}</strong>`;
|
msgEl.innerHTML = `Exported: <strong>${succeeded}</strong><br>Errors: <strong>${failed}</strong>`;
|
||||||
}
|
}
|
||||||
if (skipped > 0)
|
|
||||||
msgEl.innerHTML += `<br>Skipped (already exported / in progress — reload the page): <strong>${skipped}</strong>`;
|
|
||||||
|
|
||||||
if (exportedList.length) {
|
if (exportedList.length) {
|
||||||
const npCommesse = exportedList.filter(
|
const npCommesse = exportedList.filter(
|
||||||
(x) => x.noPriceCount > 0,
|
(x) => x.noPriceCount > 0,
|
||||||
).length;
|
).length;
|
||||||
const blockedCommesse = exportedList.filter(
|
|
||||||
(x) => x.blockAlert,
|
|
||||||
).length;
|
|
||||||
msgEl.innerHTML +=
|
msgEl.innerHTML +=
|
||||||
`<hr class="my-2">` +
|
`<hr class="my-2">` +
|
||||||
(npCommesse > 0
|
(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 text-danger fw-semibold mb-1"><i class="fas fa-exclamation-triangle"></i> ${npCommesse} commesse con analisi senza prezzo</div>`
|
||||||
: "") +
|
: "") +
|
||||||
(blockedCommesse > 0
|
|
||||||
? `<div class="small fw-semibold mb-1" style="color:#842029;"><i class="fas fa-ban"></i> ${blockedCommesse} commesse con cliente bloccato</div>`
|
|
||||||
: "") +
|
|
||||||
`<div class="small fw-semibold mb-1">Commesse esportate:</div>` +
|
`<div class="small fw-semibold mb-1">Commesse esportate:</div>` +
|
||||||
`<ul class="mb-0 ps-3">` +
|
`<ul class="mb-0 ps-3">` +
|
||||||
exportedList
|
exportedList
|
||||||
@@ -875,7 +820,6 @@
|
|||||||
x.noPriceCount,
|
x.noPriceCount,
|
||||||
x.noPriceAnalyses,
|
x.noPriceAnalyses,
|
||||||
) +
|
) +
|
||||||
blockBadgeHTML(x.blockAlert) +
|
|
||||||
`</li>`,
|
`</li>`,
|
||||||
)
|
)
|
||||||
.join("") +
|
.join("") +
|
||||||
|
|||||||
@@ -43,23 +43,17 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
* Clear all validation-error highlights from the grid.
|
* Clear all validation-error highlights from the grid.
|
||||||
*/
|
*/
|
||||||
function clearValidationErrors() {
|
function clearValidationErrors() {
|
||||||
document
|
document.querySelectorAll(".grid-cell.validation-error").forEach((cell) => {
|
||||||
.querySelectorAll(".grid-cell.validation-error")
|
cell.classList.remove("validation-error");
|
||||||
.forEach((cell) => {
|
cell.querySelectorAll(".input-validation-error").forEach((el) => {
|
||||||
cell.classList.remove("validation-error");
|
el.classList.remove("input-validation-error");
|
||||||
cell.querySelectorAll(".input-validation-error").forEach(
|
|
||||||
(el) => {
|
|
||||||
el.classList.remove("input-validation-error");
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const tooltip = cell.querySelector(".validation-tooltip");
|
|
||||||
if (tooltip) tooltip.remove();
|
|
||||||
});
|
|
||||||
document
|
|
||||||
.querySelectorAll(".grid-row.validation-row-error")
|
|
||||||
.forEach((row) => {
|
|
||||||
row.classList.remove("validation-row-error");
|
|
||||||
});
|
});
|
||||||
|
const tooltip = cell.querySelector(".validation-tooltip");
|
||||||
|
if (tooltip) tooltip.remove();
|
||||||
|
});
|
||||||
|
document.querySelectorAll(".grid-row.validation-row-error").forEach((row) => {
|
||||||
|
row.classList.remove("validation-row-error");
|
||||||
|
});
|
||||||
// Also clear batch-row-error that came from validation
|
// Also clear batch-row-error that came from validation
|
||||||
clearAllRowErrors();
|
clearAllRowErrors();
|
||||||
}
|
}
|
||||||
@@ -96,24 +90,16 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (targetIndex) {
|
if (targetIndex) {
|
||||||
const rowIndex = row
|
const rowIndex = row.querySelector(".grid-cell[data-row]")?.getAttribute("data-row");
|
||||||
.querySelector(".grid-cell[data-row]")
|
|
||||||
?.getAttribute("data-row");
|
|
||||||
if (rowIndex !== null) {
|
if (rowIndex !== null) {
|
||||||
cell = row.querySelector(
|
cell = row.querySelector(`.grid-cell[data-row="${rowIndex}"][data-index="${targetIndex}"]`);
|
||||||
`.grid-cell[data-row="${rowIndex}"][data-index="${targetIndex}"]`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Direct data-col match
|
// Direct data-col match
|
||||||
const rowIndex = row
|
const rowIndex = row.querySelector(".grid-cell[data-row]")?.getAttribute("data-row");
|
||||||
.querySelector(".grid-cell[data-row]")
|
|
||||||
?.getAttribute("data-row");
|
|
||||||
if (rowIndex !== null) {
|
if (rowIndex !== null) {
|
||||||
cell = row.querySelector(
|
cell = row.querySelector(`.grid-cell[data-col="${err.field}"][data-row="${rowIndex}"]`);
|
||||||
`.grid-cell[data-col="${err.field}"][data-row="${rowIndex}"]`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,9 +194,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
* Show the result of a single export in the response modal.
|
* Show the result of a single export in the response modal.
|
||||||
*/
|
*/
|
||||||
function showExportResult(data) {
|
function showExportResult(data) {
|
||||||
const responseModalElement = document.getElementById(
|
const responseModalElement =
|
||||||
"exportResponseModal",
|
document.getElementById("exportResponseModal");
|
||||||
);
|
|
||||||
if (!responseModalElement) {
|
if (!responseModalElement) {
|
||||||
alert("Errore: Modale di risposta non trovato");
|
alert("Errore: Modale di risposta non trovato");
|
||||||
return;
|
return;
|
||||||
@@ -291,28 +276,20 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
errorEl.className = "batch-error-msg";
|
errorEl.className = "batch-error-msg";
|
||||||
errorEl.textContent = "⚠ Errore — clicca per dettagli";
|
errorEl.textContent = "⚠ Errore — clicca per dettagli";
|
||||||
errorEl.addEventListener("click", () => {
|
errorEl.addEventListener("click", () => {
|
||||||
document.getElementById("exportResponseMessage").innerHTML =
|
document.getElementById("exportResponseMessage").innerHTML = message.replace(/\n/g, "<br>");
|
||||||
message.replace(/\n/g, "<br>");
|
document.getElementById("exportResponseModalLabel").textContent = "Errore Validazione (id: " + iddatadb + ")";
|
||||||
document.getElementById(
|
new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show();
|
||||||
"exportResponseModalLabel",
|
|
||||||
).textContent = "Errore Validazione (id: " + iddatadb + ")";
|
|
||||||
new bootstrap.Modal(
|
|
||||||
document.getElementById("exportResponseModal"),
|
|
||||||
{ keyboard: false },
|
|
||||||
).show();
|
|
||||||
});
|
});
|
||||||
btnCell.appendChild(errorEl);
|
btnCell.appendChild(errorEl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearAllRowErrors() {
|
function clearAllRowErrors() {
|
||||||
document
|
document.querySelectorAll(".grid-row.batch-row-error").forEach((row) => {
|
||||||
.querySelectorAll(".grid-row.batch-row-error")
|
row.classList.remove("batch-row-error");
|
||||||
.forEach((row) => {
|
const msg = row.querySelector(".batch-error-msg");
|
||||||
row.classList.remove("batch-row-error");
|
if (msg) msg.remove();
|
||||||
const msg = row.querySelector(".batch-error-msg");
|
});
|
||||||
if (msg) msg.remove();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function disableAllRowButtons() {
|
function disableAllRowButtons() {
|
||||||
@@ -356,23 +333,15 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
// Show validating state on the row
|
// Show validating state on the row
|
||||||
setRowExporting(gridRow, true);
|
setRowExporting(gridRow, true);
|
||||||
const spinner = gridRow.querySelector(".batch-row-spinner");
|
const spinner = gridRow.querySelector(".batch-row-spinner");
|
||||||
if (spinner)
|
if (spinner) spinner.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Validating...';
|
||||||
spinner.innerHTML =
|
|
||||||
'<i class="fas fa-spinner fa-spin"></i> Validating...';
|
|
||||||
|
|
||||||
validateRows([
|
validateRows([{ iddatadb: parseInt(iddatadb), index: parseInt(rowIndex) }])
|
||||||
{ iddatadb: parseInt(iddatadb), index: parseInt(rowIndex) },
|
|
||||||
])
|
|
||||||
.then((validationData) => {
|
.then((validationData) => {
|
||||||
setRowExporting(gridRow, false);
|
setRowExporting(gridRow, false);
|
||||||
gridRow.classList.remove("batch-disabled");
|
gridRow.classList.remove("batch-disabled");
|
||||||
|
|
||||||
if (!validationData.success) {
|
if (!validationData.success) {
|
||||||
showExportResult({
|
showExportResult({ success: false, message: validationData.message || "Errore di validazione" });
|
||||||
success: false,
|
|
||||||
message:
|
|
||||||
validationData.message || "Errore di validazione",
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,10 +360,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
gridRow.classList.remove("batch-disabled");
|
gridRow.classList.remove("batch-disabled");
|
||||||
|
|
||||||
console.error("Validation error:", error);
|
console.error("Validation error:", error);
|
||||||
showExportResult({
|
showExportResult({ success: false, message: "Errore di validazione: " + error.message });
|
||||||
success: false,
|
|
||||||
message: "Errore di validazione: " + error.message,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,11 +402,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
gridRow.classList.remove("batch-disabled");
|
gridRow.classList.remove("batch-disabled");
|
||||||
|
|
||||||
if (!data.success) {
|
if (!data.success) {
|
||||||
showRowError(
|
showRowError(gridRow, iddatadb, data.message || "Errore sconosciuto");
|
||||||
gridRow,
|
|
||||||
iddatadb,
|
|
||||||
data.message || "Errore sconosciuto",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
showExportResult(data);
|
showExportResult(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -615,9 +577,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
document.getElementById("exportResponseMessage").innerHTML =
|
document.getElementById("exportResponseMessage").innerHTML =
|
||||||
`Nessuna riga valida per l'esportazione.<br>` +
|
`Nessuna riga valida per l'esportazione.<br>` +
|
||||||
`<strong>${invalidCount}</strong> righe con errori di validazione.`;
|
`<strong>${invalidCount}</strong> righe con errori di validazione.`;
|
||||||
document.getElementById(
|
document.getElementById("exportResponseModalLabel").textContent =
|
||||||
"exportResponseModalLabel",
|
"Validazione Fallita";
|
||||||
).textContent = "Validazione Fallita";
|
|
||||||
new bootstrap.Modal(
|
new bootstrap.Modal(
|
||||||
document.getElementById("exportResponseModal"),
|
document.getElementById("exportResponseModal"),
|
||||||
{ keyboard: false },
|
{ keyboard: false },
|
||||||
@@ -634,38 +595,27 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
if (invalidCount > 0) {
|
if (invalidCount > 0) {
|
||||||
countText += ` (${invalidCount} escluse per errori di validazione)`;
|
countText += ` (${invalidCount} escluse per errori di validazione)`;
|
||||||
}
|
}
|
||||||
document.getElementById("exportBatchCount").textContent =
|
document.getElementById("exportBatchCount").textContent = countText;
|
||||||
countText;
|
|
||||||
confirmModal.show();
|
confirmModal.show();
|
||||||
|
|
||||||
const confirmBtn = document.getElementById(
|
const confirmBtn = document.getElementById("exportBatchConfirmBtn");
|
||||||
"exportBatchConfirmBtn",
|
|
||||||
);
|
|
||||||
if (pendingBatchConfirmHandler) {
|
if (pendingBatchConfirmHandler) {
|
||||||
confirmBtn.removeEventListener(
|
confirmBtn.removeEventListener("click", pendingBatchConfirmHandler);
|
||||||
"click",
|
|
||||||
pendingBatchConfirmHandler,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
pendingBatchConfirmHandler = () => {
|
pendingBatchConfirmHandler = () => {
|
||||||
pendingBatchConfirmHandler = null;
|
pendingBatchConfirmHandler = null;
|
||||||
confirmModal.hide();
|
confirmModal.hide();
|
||||||
startBatchExport(validRows);
|
startBatchExport(validRows);
|
||||||
};
|
};
|
||||||
confirmBtn.addEventListener(
|
confirmBtn.addEventListener("click", pendingBatchConfirmHandler, { once: true });
|
||||||
"click",
|
|
||||||
pendingBatchConfirmHandler,
|
|
||||||
{ once: true },
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
showValidationSpinner(false);
|
showValidationSpinner(false);
|
||||||
console.error("Batch validation error:", error);
|
console.error("Batch validation error:", error);
|
||||||
document.getElementById("exportResponseMessage").textContent =
|
document.getElementById("exportResponseMessage").textContent =
|
||||||
"Errore di validazione: " + error.message;
|
"Errore di validazione: " + error.message;
|
||||||
document.getElementById(
|
document.getElementById("exportResponseModalLabel").textContent =
|
||||||
"exportResponseModalLabel",
|
"Errore Validazione";
|
||||||
).textContent = "Errore Validazione";
|
|
||||||
new bootstrap.Modal(
|
new bootstrap.Modal(
|
||||||
document.getElementById("exportResponseModal"),
|
document.getElementById("exportResponseModal"),
|
||||||
{ keyboard: false },
|
{ keyboard: false },
|
||||||
@@ -685,51 +635,54 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
);
|
);
|
||||||
unsavedModal.show();
|
unsavedModal.show();
|
||||||
|
|
||||||
document.getElementById("batchSaveAndExportBtn").addEventListener(
|
document
|
||||||
"click",
|
.getElementById("batchSaveAndExportBtn")
|
||||||
() => {
|
.addEventListener(
|
||||||
unsavedModal.hide();
|
"click",
|
||||||
// Trigger Save All, then proceed
|
() => {
|
||||||
const saveAllEl = document.querySelector(".save-all-btn");
|
unsavedModal.hide();
|
||||||
if (!saveAllEl) return;
|
// Trigger Save All, then proceed
|
||||||
|
const saveAllEl =
|
||||||
|
document.querySelector(".save-all-btn");
|
||||||
|
if (!saveAllEl) return;
|
||||||
|
|
||||||
// Watch for all .cell-changed to disappear
|
// Watch for all .cell-changed to disappear
|
||||||
const observer = new MutationObserver(() => {
|
const observer = new MutationObserver(() => {
|
||||||
if (!hasUnsavedChanges()) {
|
if (!hasUnsavedChanges()) {
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
const eligibleRows = collectEligibleRows();
|
const eligibleRows = collectEligibleRows();
|
||||||
if (eligibleRows.length === 0) {
|
if (eligibleRows.length === 0) {
|
||||||
document.getElementById(
|
|
||||||
"exportResponseMessage",
|
|
||||||
).textContent =
|
|
||||||
"Tutte le righe sono già state esportate al LIMS.";
|
|
||||||
document.getElementById(
|
|
||||||
"exportResponseModalLabel",
|
|
||||||
).textContent = "Export All";
|
|
||||||
new bootstrap.Modal(
|
|
||||||
document.getElementById(
|
document.getElementById(
|
||||||
"exportResponseModal",
|
"exportResponseMessage",
|
||||||
),
|
).textContent =
|
||||||
{ keyboard: false },
|
"Tutte le righe sono già state esportate al LIMS.";
|
||||||
).show();
|
document.getElementById(
|
||||||
return;
|
"exportResponseModalLabel",
|
||||||
|
).textContent = "Export All";
|
||||||
|
new bootstrap.Modal(
|
||||||
|
document.getElementById(
|
||||||
|
"exportResponseModal",
|
||||||
|
),
|
||||||
|
{ keyboard: false },
|
||||||
|
).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showBatchConfirm(eligibleRows);
|
||||||
}
|
}
|
||||||
showBatchConfirm(eligibleRows);
|
});
|
||||||
}
|
observer.observe(
|
||||||
});
|
document.querySelector(".grid-container"),
|
||||||
observer.observe(
|
{
|
||||||
document.querySelector(".grid-container"),
|
subtree: true,
|
||||||
{
|
attributes: true,
|
||||||
subtree: true,
|
attributeFilter: ["class"],
|
||||||
attributes: true,
|
},
|
||||||
attributeFilter: ["class"],
|
);
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
saveAllEl.click();
|
saveAllEl.click();
|
||||||
},
|
},
|
||||||
{ once: true },
|
{ once: true },
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -801,11 +754,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
succeeded++;
|
succeeded++;
|
||||||
} else {
|
} else {
|
||||||
failed++;
|
failed++;
|
||||||
showRowError(
|
showRowError(row, iddatadb, data.message || "Errore sconosciuto");
|
||||||
row,
|
|
||||||
iddatadb,
|
|
||||||
data.message || "Errore sconosciuto",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
processed++;
|
processed++;
|
||||||
@@ -845,9 +794,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
const modalEl = document.getElementById("exportResponseModal");
|
const modalEl = document.getElementById("exportResponseModal");
|
||||||
const modal = new bootstrap.Modal(modalEl, { keyboard: false });
|
const modal = new bootstrap.Modal(modalEl, { keyboard: false });
|
||||||
modal.show();
|
modal.show();
|
||||||
modalEl.addEventListener("hidden.bs.modal", cleanupBackdrop, {
|
modalEl.addEventListener("hidden.bs.modal", cleanupBackdrop, { once: true });
|
||||||
once: true,
|
|
||||||
});
|
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once "class/VisualLimsApiClient.class.php";
|
require_once "class/VisualLimsApiClient.class.php";
|
||||||
require_once "class/block_alert.php";
|
|
||||||
include('include/headscript.php');
|
include('include/headscript.php');
|
||||||
|
|
||||||
$dbHandler = DBHandlerSelect::getInstance();
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
@@ -144,100 +143,6 @@ function importaCommessaWithRetry($api, $commessaId, array $payload, $maxRetries
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔹 CHECK PRELIMINARE + LOCK: una sola esportazione per riga, mai una seconda commessa.
|
|
||||||
// Protegge da pagine non aggiornate (due schede, pagina Errors vecchia, doppio click,
|
|
||||||
// LIMS lento con richiesta ancora in corso).
|
|
||||||
$chkId = (int)($_POST['iddatadb'] ?? 0);
|
|
||||||
if ($chkId > 0) {
|
|
||||||
$rejectExport = function ($reason, $cur = null) use ($chkId, $commessaLogFile, $writeBatchIndex, $batchUuid) {
|
|
||||||
file_put_contents(
|
|
||||||
$commessaLogFile,
|
|
||||||
"\n[EXPORT RIFIUTATO] " . date('Y-m-d H:i:s') . " | {$reason}" .
|
|
||||||
" | user_id=" . ($GLOBALS['iduserlogin'] ?? 'n/d') .
|
|
||||||
" | IP=" . ($_SERVER['REMOTE_ADDR'] ?? '-') .
|
|
||||||
" | batch=" . ($batchUuid ?? '-') . "\n",
|
|
||||||
FILE_APPEND
|
|
||||||
);
|
|
||||||
$writeBatchIndex("SKIP iddatadb={$chkId} :: {$reason}");
|
|
||||||
echo json_encode([
|
|
||||||
"success" => false,
|
|
||||||
"rejected" => true,
|
|
||||||
"status" => $cur['status'] ?? null,
|
|
||||||
"commessaweb" => $cur['commessaweb'] ?? null,
|
|
||||||
"message" => "Commessa non inviata (riga {$chkId}): {$reason}. Ricarica la pagina.",
|
|
||||||
]);
|
|
||||||
exit;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Lock per riga: tenuto fino a fine script (rilasciato da MySQL alla chiusura connessione)
|
|
||||||
$gotLock = (int)$pdo->query("SELECT GET_LOCK(" . $pdo->quote("lims_export_{$chkId}") . ", 0)")->fetchColumn();
|
|
||||||
if ($gotLock !== 1) {
|
|
||||||
$rejectExport("esportazione già in corso");
|
|
||||||
}
|
|
||||||
|
|
||||||
$chk = $pdo->prepare("SELECT status, idcommessaweb, commessaweb FROM datadb WHERE iddatadb = ?");
|
|
||||||
$chk->execute([$chkId]);
|
|
||||||
$cur = $chk->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if (!$cur) {
|
|
||||||
$rejectExport("riga non trovata");
|
|
||||||
}
|
|
||||||
if (!in_array($cur['status'], ['i', 'x'], true)) {
|
|
||||||
$rejectExport("già esportata (status={$cur['status']}, commessa=" . ($cur['commessaweb'] ?: '-') . ")", $cur);
|
|
||||||
}
|
|
||||||
|
|
||||||
$existingId = (int)($cur['idcommessaweb'] ?? 0);
|
|
||||||
if ($existingId > 0) {
|
|
||||||
// Commessa già creata da un export precedente (fallito, o interrotto da un crash/timeout PHP):
|
|
||||||
// controlla lo stato nel LIMS invece di crearne una nuova (sarebbe un duplicato).
|
|
||||||
// NB: il revert a 'i' azzera idcommessaweb, quindi qui non arrivano le righe riportate a mano.
|
|
||||||
$api = VisualLimsApiClient::getInstance();
|
|
||||||
try {
|
|
||||||
$existing = $api->get("CommessaWeb({$existingId})");
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$existing = null;
|
|
||||||
if (strpos($e->getMessage(), 'HTTP 404') === false) {
|
|
||||||
$rejectExport("impossibile verificare la commessa {$existingId} nel LIMS: " . $e->getMessage(), $cur);
|
|
||||||
}
|
|
||||||
// 404: la commessa non esiste più nel LIMS → si può esportare da capo
|
|
||||||
}
|
|
||||||
|
|
||||||
$stato = $existing['StatoCommessaWeb'] ?? null;
|
|
||||||
$notSentYet = $existing !== null && $stato === 'Nuova' && empty($existing['DataInviatoWeb']);
|
|
||||||
|
|
||||||
$writeLog(null, "IdCommessa esistente: {$existingId}\nStatoCommessaWeb: " . ($stato ?? '404') .
|
|
||||||
"\nDataInviatoWeb: " . ($existing['DataInviatoWeb'] ?? '-'), "PRECHECK - commessa esistente (iddatadb={$chkId})");
|
|
||||||
|
|
||||||
if ($stato === 'Elaborata') {
|
|
||||||
// Già importata nel LIMS: riallinea la riga, nessuna nuova commessa
|
|
||||||
$codice = substr(trim((string)($existing['CodiceCommessa'] ?? '')), 0, 30);
|
|
||||||
$pdo->prepare("UPDATE datadb SET status = 'l', commessaweb = :c, log_file = :lf WHERE iddatadb = :id")
|
|
||||||
->execute(['c' => $codice, 'lf' => basename($commessaLogFile), 'id' => $chkId]);
|
|
||||||
|
|
||||||
$writeLog(null, "Riga riallineata: status='l', commessaweb={$codice}", "PRECHECK - RECOVERED (iddatadb={$chkId})");
|
|
||||||
$writeBatchIndex("RECOVERED iddatadb={$chkId} commessa={$existingId} codice={$codice}");
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
"success" => true,
|
|
||||||
"recovered" => true,
|
|
||||||
"idcommessaweb" => $existingId,
|
|
||||||
"commessaweb" => $codice,
|
|
||||||
"message" => "Commessa già presente nel LIMS: riga riallineata senza crearne una nuova",
|
|
||||||
]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($existing !== null && !$notSentYet) {
|
|
||||||
// Inviata ma non (ancora) importata: rifare l'export creerebbe un duplicato → verifica manuale
|
|
||||||
$rejectExport("commessa {$existingId} già inviata al LIMS ma non ancora importata (stato: {$stato}). " .
|
|
||||||
"Riprova tra qualche minuto o verifica nel LIMS", $cur);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 404 o mai inviata al laboratorio: si ricrea da capo (la vecchia CommessaWeb resta orfana in "Nuova")
|
|
||||||
$writeBatchIndex("RECREATE iddatadb={$chkId} old_commessa={$existingId} stato=" . ($stato ?? '404'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$iddatadb = $_POST['iddatadb'] ?? null;
|
$iddatadb = $_POST['iddatadb'] ?? null;
|
||||||
if (!$iddatadb) {
|
if (!$iddatadb) {
|
||||||
@@ -443,11 +348,6 @@ try {
|
|||||||
$commessaId = $commessaWeb["IdCommessa"];
|
$commessaId = $commessaWeb["IdCommessa"];
|
||||||
$commessaWebCode = substr($commessaWeb["CodiceCommessa"] ?? "TEST CommessaWeb", 0, 30); // Limite a 30 caratteri
|
$commessaWebCode = substr($commessaWeb["CodiceCommessa"] ?? "TEST CommessaWeb", 0, 30); // Limite a 30 caratteri
|
||||||
|
|
||||||
// 🔹 STEP 5.1: salva SUBITO l'id della commessa creata: se l'export fallisce più avanti,
|
|
||||||
// il CHECK PRELIMINARE sa che nel LIMS esiste già e non ne crea una seconda.
|
|
||||||
$pdo->prepare("UPDATE datadb SET idcommessaweb = :idc, commessaweb = :cw WHERE iddatadb = :id")
|
|
||||||
->execute(['idc' => $commessaId, 'cw' => $commessaWebCode, 'id' => $iddatadb]);
|
|
||||||
|
|
||||||
// 🔹 STEP 6: Create Campioni (Samples) for each part
|
// 🔹 STEP 6: Create Campioni (Samples) for each part
|
||||||
$campioni = [];
|
$campioni = [];
|
||||||
$logContentStep6 = "";
|
$logContentStep6 = "";
|
||||||
@@ -868,13 +768,6 @@ try {
|
|||||||
$logFileStep91 = $logDir . "commessa_{$commessaId}_importa_step91_" . time() . ".txt";
|
$logFileStep91 = $logDir . "commessa_{$commessaId}_importa_step91_" . time() . ".txt";
|
||||||
$writeLog($logFileStep91, $logContentStep91, "STEP 9.5 - ImportaCommessa (commessa={$commessaId}, succeeded=" . ($importSucceeded ? '1' : '0') . ")");
|
$writeLog($logFileStep91, $logContentStep91, "STEP 9.5 - ImportaCommessa (commessa={$commessaId}, succeeded=" . ($importSucceeded ? '1' : '0') . ")");
|
||||||
|
|
||||||
// Import non confermato → errore ('x'), non 'l'. Prima veniva comunque segnata 'l'
|
|
||||||
// perché CodiceCommessa (codice web) non è mai vuoto. Al reinvio il CHECK PRELIMINARE
|
|
||||||
// ricontrolla lo stato nel LIMS: se nel frattempo è "Elaborata" la riga viene solo riallineata.
|
|
||||||
if (!$importSucceeded) {
|
|
||||||
throw new Exception("ImportaCommessa non confermata dal LIMS (stato: " . ($importStato ?? 'null') . ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🔹 STEP 10: GET di controllo post-PATCH
|
// 🔹 STEP 10: GET di controllo post-PATCH
|
||||||
$expand = "CommesseCustomFields(\$expand=CustomField)";
|
$expand = "CommesseCustomFields(\$expand=CustomField)";
|
||||||
$commessaAfterPatch = $api->get("CommessaWeb(" . $commessaId . ")?\$expand=" . $expand);
|
$commessaAfterPatch = $api->get("CommessaWeb(" . $commessaId . ")?\$expand=" . $expand);
|
||||||
@@ -905,19 +798,6 @@ try {
|
|||||||
|
|
||||||
// Indice di lotto: esito OK di questa commessa
|
// Indice di lotto: esito OK di questa commessa
|
||||||
$writeBatchIndex("OK iddatadb={$iddatadb} commessa={$commessaId} codice={$finalCodiceCommessa} log=" . basename($commessaLogFile));
|
$writeBatchIndex("OK iddatadb={$iddatadb} commessa={$commessaId} codice={$finalCodiceCommessa} log=" . basename($commessaLogFile));
|
||||||
// 🔹 STEP 10.2: Controllo blocco cliente (solo avviso, non blocca mai l'export)
|
|
||||||
$blockAlert = null;
|
|
||||||
try {
|
|
||||||
$blockAlert = getClientBlockAlert($clienteId, $clienteFatturazione);
|
|
||||||
} catch (Throwable $e) {
|
|
||||||
error_log("Controllo blocco cliente fallito (iddatadb={$iddatadb}): " . $e->getMessage());
|
|
||||||
}
|
|
||||||
$writeLog(
|
|
||||||
null,
|
|
||||||
$blockAlert ? json_encode($blockAlert, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) : "Nessun blocco",
|
|
||||||
"STEP 10.2 - Controllo blocco cliente (iddatadb={$iddatadb})"
|
|
||||||
);
|
|
||||||
|
|
||||||
// 🔹 STEP 11: Prepare final response
|
// 🔹 STEP 11: Prepare final response
|
||||||
$finalCommessa = [
|
$finalCommessa = [
|
||||||
"Cliente" => $clienteId,
|
"Cliente" => $clienteId,
|
||||||
@@ -943,7 +823,6 @@ try {
|
|||||||
"failedAnalyses" => $failedAnalyses,
|
"failedAnalyses" => $failedAnalyses,
|
||||||
"noPriceCount" => count($noPriceAnalyses),
|
"noPriceCount" => count($noPriceAnalyses),
|
||||||
"noPriceAnalyses" => $noPriceAnalyses,
|
"noPriceAnalyses" => $noPriceAnalyses,
|
||||||
"blockAlert" => $blockAlert,
|
|
||||||
"message" => "Export successful",
|
"message" => "Export successful",
|
||||||
"logFiles" => [
|
"logFiles" => [
|
||||||
"step5_create" => $logFileStep5,
|
"step5_create" => $logFileStep5,
|
||||||
@@ -955,7 +834,7 @@ try {
|
|||||||
"step10_get" => $logFileStep10
|
"step10_get" => $logFileStep10
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
} catch (Throwable $e) {
|
} catch (Exception $e) {
|
||||||
// --- LOG ERRORE DEDICATO (accanto agli altri log in logs/api/) ---
|
// --- LOG ERRORE DEDICATO (accanto agli altri log in logs/api/) ---
|
||||||
$errIddatadb = $iddatadb ?? 'unknown';
|
$errIddatadb = $iddatadb ?? 'unknown';
|
||||||
$errCommessaId = $commessaId ?? null;
|
$errCommessaId = $commessaId ?? null;
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ try {
|
|||||||
SELECT d.iddatadb
|
SELECT d.iddatadb
|
||||||
FROM datadb d {$joinSql}
|
FROM datadb d {$joinSql}
|
||||||
WHERE {$whereSql}
|
WHERE {$whereSql}
|
||||||
ORDER BY CAST(d.excelrow AS UNSIGNED) ASC, d.excelrow ASC, d.iddatadb ASC
|
ORDER BY d.excelrow ASC, d.iddatadb ASC
|
||||||
LIMIT {$perPage} OFFSET {$offset}
|
LIMIT {$perPage} OFFSET {$offset}
|
||||||
");
|
");
|
||||||
$pageStmt->execute($params);
|
$pageStmt->execute($params);
|
||||||
@@ -385,7 +385,7 @@ try {
|
|||||||
$allStmt = $pdo->prepare("
|
$allStmt = $pdo->prepare("
|
||||||
SELECT d.iddatadb FROM datadb d {$joinSql}
|
SELECT d.iddatadb FROM datadb d {$joinSql}
|
||||||
WHERE {$whereSql}
|
WHERE {$whereSql}
|
||||||
ORDER BY CAST(d.excelrow AS UNSIGNED) ASC, d.excelrow ASC, d.iddatadb ASC
|
ORDER BY d.excelrow ASC, d.iddatadb ASC
|
||||||
LIMIT " . FILTER_ALLIDS_CAP . "
|
LIMIT " . FILTER_ALLIDS_CAP . "
|
||||||
");
|
");
|
||||||
$allStmt->execute($params);
|
$allStmt->execute($params);
|
||||||
|
|||||||
@@ -20,20 +20,17 @@ try {
|
|||||||
$stmt = $pdo->query("SELECT DISTINCT idclient FROM datadb WHERE idclient IS NOT NULL AND idclient > 0 ORDER BY idclient ASC");
|
$stmt = $pdo->query("SELECT DISTINCT idclient FROM datadb WHERE idclient IS NOT NULL AND idclient > 0 ORDER BY idclient ASC");
|
||||||
$ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
$ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
$fakeClients = array_map(fn($id) => [
|
$fakeClients = array_map(fn($id) => [
|
||||||
'IdCliente' => (int) $id,
|
'IdCliente' => (int) $id,
|
||||||
'Nominativo' => "Cliente Simulato {$id}",
|
'Nominativo' => "Cliente Simulato {$id}",
|
||||||
'CodiceCliente' => "SIM_{$id}",
|
'CodiceCliente' => "SIM_{$id}",
|
||||||
'CodiceASL' => null, // campo usato come nota di blocco (es. "BLOCCO PER PAGAMENTO")
|
|
||||||
], $ids);
|
], $ids);
|
||||||
echo json_encode(['value' => $fakeClients]);
|
echo json_encode(['value' => $fakeClients]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parametri OData
|
// Parametri OData
|
||||||
// CodiceASL viene usato dal cliente come nota di blocco (es. "BLOCCO PER PAGAMENTO"),
|
|
||||||
// perché i campi nativi del Blocco Accettazione non sono esposti dall'API.
|
|
||||||
$params = [
|
$params = [
|
||||||
'$select' => 'IdCliente,Nominativo,CodiceCliente,Inattivo,CodiceASL',
|
'$select' => 'IdCliente,Nominativo,CodiceCliente,Inattivo',
|
||||||
'$orderby' => 'Nominativo asc'
|
'$orderby' => 'Nominativo asc'
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -57,12 +54,10 @@ try {
|
|||||||
$errorMessage = $e->getMessage();
|
$errorMessage = $e->getMessage();
|
||||||
// Controlla se l'errore è legato all'autenticazione (HTTP 400 con messaggio specifico)
|
// Controlla se l'errore è legato all'autenticazione (HTTP 400 con messaggio specifico)
|
||||||
if (strpos($errorMessage, 'HTTP 400') !== false && strpos($errorMessage, 'Cannot persist the object') !== false) {
|
if (strpos($errorMessage, 'HTTP 400') !== false && strpos($errorMessage, 'Cannot persist the object') !== false) {
|
||||||
// Forza il refresh del token, solo se la classe lo supporta
|
// Forza il refresh del token
|
||||||
if (!method_exists($api, 'refreshToken')) {
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
$api->refreshToken();
|
// Assumi che VisualLimsApiClient abbia un metodo per il refresh del token
|
||||||
|
$api->refreshToken(); // Da implementare in VisualLimsApiClient se non esiste
|
||||||
error_log("Tentativo $retry: Refresh token eseguito per endpoint $endpoint");
|
error_log("Tentativo $retry: Refresh token eseguito per endpoint $endpoint");
|
||||||
} catch (Exception $refreshEx) {
|
} catch (Exception $refreshEx) {
|
||||||
error_log("Errore durante il refresh del token: " . $refreshEx->getMessage());
|
error_log("Errore durante il refresh del token: " . $refreshEx->getMessage());
|
||||||
@@ -79,7 +74,7 @@ try {
|
|||||||
throw new Exception("Massimo numero di tentativi raggiunto per $endpoint");
|
throw new Exception("Massimo numero di tentativi raggiunto per $endpoint");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache file (15 minuti TTL: così un blocco impostato nel LIMS compare entro 15 minuti)
|
// Cache file (1 hour TTL)
|
||||||
$cacheFile = __DIR__ . '/cache/clienti.json';
|
$cacheFile = __DIR__ . '/cache/clienti.json';
|
||||||
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 7200)) {
|
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 7200)) {
|
||||||
readfile($cacheFile);
|
readfile($cacheFile);
|
||||||
@@ -94,12 +89,12 @@ try {
|
|||||||
file_put_contents($cacheFile, $json);
|
file_put_contents($cacheFile, $json);
|
||||||
|
|
||||||
echo $json;
|
echo $json;
|
||||||
} catch (Throwable $e) {
|
} catch (Exception $e) {
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
$errorResponse = [
|
$errorResponse = [
|
||||||
'error' => $e->getMessage(),
|
'error' => $e->getMessage(),
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
'line' => $e->getLine(),
|
'line' => $e->getLine(),
|
||||||
'trace' => $e->getTraceAsString()
|
'trace' => $e->getTraceAsString()
|
||||||
];
|
];
|
||||||
error_log("Errore in get_clienti.php: " . json_encode($errorResponse));
|
error_log("Errore in get_clienti.php: " . json_encode($errorResponse));
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Script di TEST: recupera TUTTI i campi di pochi clienti (nessun $select, nessuna cache).
|
|
||||||
// Da mettere nella stessa cartella di get_clienti.php.
|
|
||||||
//
|
|
||||||
// Uso:
|
|
||||||
// test_clienti_full.php -> primi 3 clienti
|
|
||||||
// test_clienti_full.php?top=5 -> primi 5 clienti
|
|
||||||
// test_clienti_full.php?id=123 -> solo il cliente con IdCliente = 123
|
|
||||||
// test_clienti_full.php?expand=XYZ -> include anche la relazione XYZ
|
|
||||||
|
|
||||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
|
||||||
require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
|
|
||||||
|
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
|
||||||
ini_set('display_errors', '0');
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$api = VisualLimsApiClient::getInstance();
|
|
||||||
|
|
||||||
$top = isset($_GET['top']) ? max(1, min(20, (int) $_GET['top'])) : 3;
|
|
||||||
|
|
||||||
$params = [
|
|
||||||
'$top' => $top,
|
|
||||||
'$orderby' => 'Nominativo asc',
|
|
||||||
];
|
|
||||||
|
|
||||||
if (isset($_GET['id']) && ctype_digit($_GET['id'])) {
|
|
||||||
$params['$filter'] = 'IdCliente eq ' . (int) $_GET['id'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($_GET['expand']) && preg_match('/^[A-Za-z0-9_,\/()$=;]+$/', $_GET['expand'])) {
|
|
||||||
$params['$expand'] = $_GET['expand'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// PHP_QUERY_RFC3986 codifica gli spazi come %20 (più compatibile con OData)
|
|
||||||
$endpoint = 'Cliente?' . http_build_query($params, '', '&', PHP_QUERY_RFC3986);
|
|
||||||
|
|
||||||
$data = $api->get($endpoint);
|
|
||||||
|
|
||||||
// Copia su file per consultarla con calma
|
|
||||||
file_put_contents(
|
|
||||||
__DIR__ . '/clienti_full_response.json',
|
|
||||||
json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Elenco dei nomi dei campi trovati, utile per capire cosa è disponibile
|
|
||||||
$campi = [];
|
|
||||||
foreach ($data['value'] ?? [] as $row) {
|
|
||||||
$campi = array_unique(array_merge($campi, array_keys($row)));
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
'endpoint' => $endpoint,
|
|
||||||
'campi' => array_values($campi),
|
|
||||||
'dati' => $data,
|
|
||||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
||||||
} catch (Throwable $e) {
|
|
||||||
http_response_code(500);
|
|
||||||
echo json_encode([
|
|
||||||
'error' => $e->getMessage(),
|
|
||||||
'file' => $e->getFile(),
|
|
||||||
'line' => $e->getLine(),
|
|
||||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
|
||||||
}
|
|
||||||
@@ -263,116 +263,6 @@
|
|||||||
await resolveClientNames();
|
await resolveClientNames();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Blocco clienti (CodiceASL contenente "blocco") ─────────────────────
|
|
||||||
|
|
||||||
const clientBlockMap = {}; // IdCliente -> testo completo di CodiceASL
|
|
||||||
|
|
||||||
async function loadClientBlocks() {
|
|
||||||
try {
|
|
||||||
const resp = await fetch("get_clienti.php", { cache: "no-store" });
|
|
||||||
const json = await resp.json();
|
|
||||||
(json.value || []).forEach((c) => {
|
|
||||||
const txt = (c.CodiceASL || "").trim();
|
|
||||||
if (txt && /blocco/i.test(txt)) {
|
|
||||||
clientBlockMap[String(c.IdCliente)] = txt;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log(
|
|
||||||
"[gridRenderer] clienti bloccati:",
|
|
||||||
Object.keys(clientBlockMap).length,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(
|
|
||||||
"[gridRenderer] errore caricamento blocchi clienti",
|
|
||||||
e,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regola: se "Fatturazione avanzata" è compilata si controlla quella, altrimenti il Client
|
|
||||||
function getRowBlockInfo(rowIndex) {
|
|
||||||
const row = data[rowIndex];
|
|
||||||
if (!row) return null;
|
|
||||||
const fatt =
|
|
||||||
(row.fixedFields && row.fixedFields.ClienteFatturazione) || "";
|
|
||||||
if (fatt) {
|
|
||||||
return {
|
|
||||||
colKey: "ClienteFatturazione",
|
|
||||||
reason: clientBlockMap[String(fatt)] || "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
colKey: "idclient",
|
|
||||||
reason: clientBlockMap[String(row.idclient || "")] || "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideBlockTooltip(el) {
|
|
||||||
if (window.bootstrap && bootstrap.Tooltip) {
|
|
||||||
const t = bootstrap.Tooltip.getInstance(el);
|
|
||||||
if (t) t.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyBlockMarker(rowIndex, rowEl) {
|
|
||||||
const row = data[rowIndex];
|
|
||||||
if (!row) return;
|
|
||||||
rowEl =
|
|
||||||
rowEl ||
|
|
||||||
(rowContainer &&
|
|
||||||
rowContainer.querySelector(
|
|
||||||
`.grid-row[data-id="${row.iddatadb}"]`,
|
|
||||||
));
|
|
||||||
if (!rowEl) return;
|
|
||||||
|
|
||||||
// Rimuove marcatori precedenti (sia su Client sia su Fatturazione avanzata)
|
|
||||||
rowEl
|
|
||||||
.querySelectorAll(".grid-cell.client-blocked")
|
|
||||||
.forEach((c) => c.classList.remove("client-blocked"));
|
|
||||||
rowEl.querySelectorAll(".client-block-icon").forEach((i) => {
|
|
||||||
hideBlockTooltip(i);
|
|
||||||
i.remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
const info = getRowBlockInfo(rowIndex);
|
|
||||||
if (!info || !info.reason) return;
|
|
||||||
|
|
||||||
const cell = rowEl.querySelector(
|
|
||||||
`.grid-cell[data-col="${info.colKey}"]`,
|
|
||||||
);
|
|
||||||
if (!cell) return;
|
|
||||||
|
|
||||||
cell.classList.add("client-blocked");
|
|
||||||
const icon = document.createElement("span");
|
|
||||||
icon.className = "client-block-icon";
|
|
||||||
icon.innerHTML = '<i class="fas fa-exclamation-circle"></i>';
|
|
||||||
icon.dataset.reason = info.reason;
|
|
||||||
icon.setAttribute("aria-label", info.reason);
|
|
||||||
cell.appendChild(icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshAllBlockMarkers() {
|
|
||||||
data.forEach((_, i) => applyBlockMarker(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
function initBlockTooltips() {
|
|
||||||
$(document).on("mouseenter", ".client-block-icon", function () {
|
|
||||||
if (!(window.bootstrap && bootstrap.Tooltip)) {
|
|
||||||
this.title = this.dataset.reason;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bootstrap.Tooltip.getOrCreateInstance(this, {
|
|
||||||
title: this.dataset.reason,
|
|
||||||
placement: "top",
|
|
||||||
container: "body",
|
|
||||||
trigger: "manual",
|
|
||||||
}).show();
|
|
||||||
});
|
|
||||||
$(document).on("mouseleave", ".client-block-icon", function () {
|
|
||||||
hideBlockTooltip(this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Fixed field data loading ───────────────────────────────────────────
|
// ── Fixed field data loading ───────────────────────────────────────────
|
||||||
|
|
||||||
const fixedFieldApiConfig = {
|
const fixedFieldApiConfig = {
|
||||||
@@ -573,7 +463,6 @@
|
|||||||
async function preloadAllData() {
|
async function preloadAllData() {
|
||||||
// 1. Clients
|
// 1. Clients
|
||||||
await loadClientData();
|
await loadClientData();
|
||||||
await loadClientBlocks();
|
|
||||||
|
|
||||||
// 2. Non-dependent fixed fields
|
// 2. Non-dependent fixed fields
|
||||||
const nonDependent = Object.keys(fixedFieldApiConfig).filter(
|
const nonDependent = Object.keys(fixedFieldApiConfig).filter(
|
||||||
@@ -866,8 +755,7 @@
|
|||||||
|
|
||||||
function renderActionButtons(rowIndex) {
|
function renderActionButtons(rowIndex) {
|
||||||
const row = data[rowIndex];
|
const row = data[rowIndex];
|
||||||
// _exporting: export in corso per questa riga (la riga può essere ri-renderizzata durante l'attesa)
|
const isExported = row.status === "l";
|
||||||
const isExported = row.status === "l" || row._exporting === true;
|
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.className = "grid-cell button-cell";
|
div.className = "grid-cell button-cell";
|
||||||
div.style.flex = "0 0 auto";
|
div.style.flex = "0 0 auto";
|
||||||
@@ -986,8 +874,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applyBlockMarker(rowIndex, rowDiv);
|
|
||||||
|
|
||||||
return rowDiv;
|
return rowDiv;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1084,8 +970,6 @@
|
|||||||
|
|
||||||
cell.classList.add("cell-changed");
|
cell.classList.add("cell-changed");
|
||||||
});
|
});
|
||||||
|
|
||||||
refreshAllBlockMarkers();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSingleRow(rowIndex) {
|
function renderSingleRow(rowIndex) {
|
||||||
@@ -1194,9 +1078,6 @@
|
|||||||
if (columns[colPos]) {
|
if (columns[colPos]) {
|
||||||
columns[colPos].width = newWidth;
|
columns[colPos].width = newWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Riallinea larghezza/posizione delle tendine Select2 aperte
|
|
||||||
$(window).trigger("resize.select2");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncVisibleRowsToGridData() {
|
function syncVisibleRowsToGridData() {
|
||||||
@@ -1833,8 +1714,6 @@
|
|||||||
}
|
}
|
||||||
cell.classList.add("cell-changed");
|
cell.classList.add("cell-changed");
|
||||||
updateDirtyIndicator();
|
updateDirtyIndicator();
|
||||||
|
|
||||||
if (colType === "idclient") applyBlockMarker(rowIndex);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle SceltaMultipla changes and persist them into gridData.
|
// Handle SceltaMultipla changes and persist them into gridData.
|
||||||
@@ -1886,8 +1765,6 @@
|
|||||||
data[rowIndex]._dirty = true;
|
data[rowIndex]._dirty = true;
|
||||||
cell.classList.add("cell-changed");
|
cell.classList.add("cell-changed");
|
||||||
updateDirtyIndicator();
|
updateDirtyIndicator();
|
||||||
|
|
||||||
if (key === "ClienteFatturazione") applyBlockMarker(rowIndex);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2031,9 +1908,6 @@
|
|||||||
if (columns[colPos]) {
|
if (columns[colPos]) {
|
||||||
columns[colPos].width = newWidth;
|
columns[colPos].width = newWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Riallinea larghezza/posizione delle tendine Select2 aperte
|
|
||||||
$(window).trigger("resize.select2");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopResize() {
|
function stopResize() {
|
||||||
@@ -2097,7 +1971,6 @@
|
|||||||
// Events
|
// Events
|
||||||
attachEvents();
|
attachEvents();
|
||||||
initLazySelect2();
|
initLazySelect2();
|
||||||
initBlockTooltips();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start when DOM ready
|
// Start when DOM ready
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -493,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) {
|
||||||
@@ -551,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 + '%';
|
||||||
@@ -566,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);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1518,38 +1518,6 @@ $gridMeta = [
|
|||||||
}
|
}
|
||||||
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
/* ── Cliente bloccato (CodiceASL contiene "blocco") ── */
|
|
||||||
.grid-container .grid-cell.client-blocked {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid-container .grid-cell.client-blocked>select,
|
|
||||||
.grid-container .grid-cell.client-blocked .select2-container {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
min-width: 0;
|
|
||||||
width: auto !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid-container .grid-cell.client-blocked select,
|
|
||||||
.grid-container .grid-cell.client-blocked .select2-container--default .select2-selection--single {
|
|
||||||
background-color: #f8d7da !important;
|
|
||||||
border-color: #f1aeb5 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid-container .grid-cell.client-blocked .select2-selection__rendered {
|
|
||||||
color: #842029 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.client-block-icon {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
color: #dc3545;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 1;
|
|
||||||
cursor: help;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
<title>Edit Imported Data - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
<title>Edit Imported Data - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -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; ?>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="row parts-row">
|
<div class="row parts-row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-9">
|
||||||
<!-- Prima riga: Elenco Parti, Rinumera, Voce -->
|
<!-- Prima riga: Elenco Parti, Rinumera, Voce -->
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; min-width: 0;">
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; min-width: 0;">
|
||||||
<h6 style="margin: 0; white-space: nowrap;">Elenco Parti</h6>
|
<h6 style="margin: 0; white-space: nowrap;">Elenco Parti</h6>
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
<i class="fas fa-microphone"></i> Voce
|
<i class="fas fa-microphone"></i> Voce
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-outline-primary btn-sm ms-2" id="showHideImageBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
|
<button type="button" class="btn btn-outline-primary btn-sm ms-2" id="showHideImageBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
|
||||||
<i class="fas fa-eye" style="font-size: 0.8rem;"></i>
|
<i class="fas fa-eye-slash" style="font-size: 0.8rem;"></i>
|
||||||
<i class="fas fa-image ms-1" style="font-size: 0.8rem;"></i>
|
<i class="fas fa-image ms-1" style="font-size: 0.8rem;"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 d-none">
|
<div class="col-md-3">
|
||||||
<h6>Foto del Campione</h6>
|
<h6>Foto del Campione</h6>
|
||||||
<div style="display: flex; align-items: center; margin-bottom: 10px;">
|
<div style="display: flex; align-items: center; margin-bottom: 10px;">
|
||||||
<button type="button" class="btn btn-primary btn-sm" id="downloadPhotoBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem; margin-right: 10px;"><i class="fas fa-download"></i></button>
|
<button type="button" class="btn btn-primary btn-sm" id="downloadPhotoBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem; margin-right: 10px;"><i class="fas fa-download"></i></button>
|
||||||
@@ -432,7 +432,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Evita stretching del flex nella riga dei filtri */
|
/* Evita stretching del flex nella riga dei filtri */
|
||||||
#partsModal .parts-row>div:first-child>div[style*="display: flex"]>* {
|
#partsModal .modal-body>.row .col-md-9>div[style*="display: flex"]>* {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -611,7 +611,7 @@
|
|||||||
|
|
||||||
#partsTable {
|
#partsTable {
|
||||||
table-layout: fixed !important;
|
table-layout: fixed !important;
|
||||||
width: 100% !important;
|
width: max-content !important;
|
||||||
min-width: unset !important;
|
min-width: unset !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -733,6 +733,9 @@
|
|||||||
|
|
||||||
/* Extra field propagation header - full column width */
|
/* Extra field propagation header - full column width */
|
||||||
#partsTable th.extra-field-th {
|
#partsTable th.extra-field-th {
|
||||||
|
width: auto !important;
|
||||||
|
min-width: 220px !important;
|
||||||
|
max-width: none !important;
|
||||||
padding: 0.25rem !important;
|
padding: 0.25rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -754,9 +757,9 @@
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.15;
|
line-height: 1.15;
|
||||||
color: #222;
|
color: #222;
|
||||||
white-space: normal !important;
|
white-space: nowrap !important;
|
||||||
word-break: break-word;
|
overflow: visible !important;
|
||||||
overflow: hidden !important;
|
text-overflow: unset !important;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -803,69 +806,5 @@
|
|||||||
z-index: 1065 !important
|
z-index: 1065 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Campi nelle celle: occupano tutta la cella */
|
|
||||||
#partsTable td input.form-control,
|
|
||||||
#partsTable td textarea.form-control {
|
|
||||||
width: 100% !important;
|
|
||||||
max-width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#partsTable td>div[style*="display: flex"] {
|
|
||||||
width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Select2 nelle celle (Matrice ed Extra): occupano la cella e vanno a capo */
|
|
||||||
#partsTable td .select2-container {
|
|
||||||
width: 100% !important;
|
|
||||||
min-width: 0 !important;
|
|
||||||
max-width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#partsTable td .part-matrice+.select2 {
|
|
||||||
flex: 1 1 auto !important;
|
|
||||||
width: auto !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#partsTable td .select2-selection--single {
|
|
||||||
height: auto !important;
|
|
||||||
min-height: 24px !important;
|
|
||||||
display: flex !important;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#partsTable td .select2-selection__rendered {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
white-space: normal !important;
|
|
||||||
word-break: break-word;
|
|
||||||
overflow: visible !important;
|
|
||||||
text-overflow: clip !important;
|
|
||||||
line-height: 1.2 !important;
|
|
||||||
padding: 2px 22px 2px 4px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#partsTable td .select2-selection__arrow {
|
|
||||||
height: 100% !important;
|
|
||||||
top: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Campo extra di tipo testo: textarea che cresce col contenuto */
|
|
||||||
#partsTable textarea.part-extra-field {
|
|
||||||
field-sizing: content;
|
|
||||||
min-height: 24px;
|
|
||||||
height: auto;
|
|
||||||
padding: 0.1rem 0.3rem;
|
|
||||||
line-height: 1.2;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
resize: none;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Voci del dropdown che vanno a capo */
|
|
||||||
.select2-results__option {
|
|
||||||
white-space: normal;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* rosso */
|
/* rosso */
|
||||||
</style>
|
</style>
|
||||||
+75
-148
@@ -147,9 +147,10 @@ $(document).ready(function () {
|
|||||||
return `
|
return `
|
||||||
<td class="extra-field-td" style="width:200px;">
|
<td class="extra-field-td" style="width:200px;">
|
||||||
<input type="hidden" class="part-extra-field-id" value="${partsExtraField.field_id}">
|
<input type="hidden" class="part-extra-field-id" value="${partsExtraField.field_id}">
|
||||||
<textarea rows="1" class="form-control form-control-sm part-extra-field" data-type="${partsExtraField.data_type || ""}">${selectedValueText || ""}</textarea>
|
<input type="text" class="form-control form-control-sm part-extra-field" data-type="${partsExtraField.data_type || ""}" value="${selectedValueText || ""}">
|
||||||
</td>`;
|
</td>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildExtraFieldHeaderHtml() {
|
function buildExtraFieldHeaderHtml() {
|
||||||
if (!partsExtraField) return "";
|
if (!partsExtraField) return "";
|
||||||
|
|
||||||
@@ -887,17 +888,6 @@ $(document).ready(function () {
|
|||||||
$("#partsTableBody").empty();
|
$("#partsTableBody").empty();
|
||||||
$("#global-matrice").empty();
|
$("#global-matrice").empty();
|
||||||
$("#macro-matrice-filter").empty();
|
$("#macro-matrice-filter").empty();
|
||||||
|
|
||||||
// Ripristina lo stato di default: foto nascosta
|
|
||||||
const $partsRow = $("#partsModal .parts-row");
|
|
||||||
$partsRow.find("> .col-md-3").addClass("d-none");
|
|
||||||
$partsRow
|
|
||||||
.find("> div:first-child")
|
|
||||||
.removeClass("col-md-9")
|
|
||||||
.addClass("col-md-12");
|
|
||||||
$("#showHideImageBtn").html(
|
|
||||||
"<i class='fas fa-eye' style='font-size: 0.8rem;'></i><i class='fas fa-image ms-1' style='font-size: 0.8rem;'></i>",
|
|
||||||
);
|
|
||||||
$(".temp-alert").remove();
|
$(".temp-alert").remove();
|
||||||
$(".modal-backdrop").remove();
|
$(".modal-backdrop").remove();
|
||||||
$("body").removeClass("modal-open").css("padding-right", "");
|
$("body").removeClass("modal-open").css("padding-right", "");
|
||||||
@@ -2164,9 +2154,6 @@ $(document).ready(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
window.saveAllParts = saveAllParts;
|
window.saveAllParts = saveAllParts;
|
||||||
window.markUnsaved = function () {
|
|
||||||
markUnsaved();
|
|
||||||
};
|
|
||||||
function renumberParts() {
|
function renumberParts() {
|
||||||
console.log(
|
console.log(
|
||||||
"Inizio rinumera parts, numero righe:",
|
"Inizio rinumera parts, numero righe:",
|
||||||
@@ -2685,195 +2672,135 @@ $(document).on("click", "#showHideImageBtn", function () {
|
|||||||
// RESIZABLE PARTS TABLE COLUMNS - FIXED COLGROUP VERSION
|
// RESIZABLE PARTS TABLE COLUMNS - FIXED COLGROUP VERSION
|
||||||
// ===================
|
// ===================
|
||||||
(function () {
|
(function () {
|
||||||
const NUM_W = 55; // colonna Num fissa
|
// Larghezze default per indice colonna (0-based)
|
||||||
const MIN_W = 70; // larghezza minima di ogni colonna
|
const defaultWidths = [55, 320, 360, 150, 220, 230];
|
||||||
|
const savedWidths = [...defaultWidths];
|
||||||
// Pesi di default delle colonne dopo Num
|
|
||||||
// senza extra: Descrizione, Matrice, Data, Azioni
|
|
||||||
// con extra: Descrizione, Matrice, Data, Extra, Azioni
|
|
||||||
const DEFAULT_NO_EXTRA = [320, 360, 150, 200];
|
|
||||||
const DEFAULT_EXTRA = [280, 320, 140, 240, 180];
|
|
||||||
|
|
||||||
let weights = null;
|
|
||||||
let weightsCount = 0;
|
|
||||||
|
|
||||||
function getColgroup() {
|
function getColgroup() {
|
||||||
return $("#partsTableColgroup");
|
return $("#partsTableColgroup");
|
||||||
}
|
}
|
||||||
|
|
||||||
function getThs() {
|
function syncColgroupToHeaders() {
|
||||||
return $("#partsTable thead tr:first th");
|
const $table = $("#partsTable");
|
||||||
}
|
const $ths = $table.find("thead tr:first th");
|
||||||
|
const $colgroup = getColgroup();
|
||||||
|
const thCount = $ths.length;
|
||||||
|
|
||||||
function getAvail() {
|
// Assicura che ci siano esattamente tante <col> quante <th>
|
||||||
const w = $("#partsModal .parts-table-scroll").innerWidth();
|
while ($colgroup.find("col").length < thCount) {
|
||||||
return w > 0 ? Math.floor(w) : 0;
|
$colgroup.append("<col>");
|
||||||
}
|
}
|
||||||
|
while ($colgroup.find("col").length > thCount) {
|
||||||
function ensureCols(n) {
|
$colgroup.find("col:last").remove();
|
||||||
const $cg = getColgroup();
|
|
||||||
while ($cg.find("col").length < n) $cg.append("<col>");
|
|
||||||
while ($cg.find("col").length > n) $cg.find("col:last").remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureWeights(n) {
|
|
||||||
const k = n - 1;
|
|
||||||
if (weights && weightsCount === k) return;
|
|
||||||
if (k === 4) weights = DEFAULT_NO_EXTRA.slice();
|
|
||||||
else if (k === 5) weights = DEFAULT_EXTRA.slice();
|
|
||||||
else weights = Array(k).fill(150);
|
|
||||||
weightsCount = k;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converte i pesi in pixel: la somma è sempre = larghezza disponibile
|
|
||||||
function computePx(n) {
|
|
||||||
const avail = getAvail();
|
|
||||||
const rest = Math.max(avail - NUM_W, MIN_W * (n - 1));
|
|
||||||
const sum = weights.reduce((a, b) => a + b, 0) || 1;
|
|
||||||
const px = [NUM_W];
|
|
||||||
let used = 0;
|
|
||||||
for (let i = 0; i < weights.length; i++) {
|
|
||||||
const v =
|
|
||||||
i === weights.length - 1
|
|
||||||
? rest - used
|
|
||||||
: Math.floor((rest * weights[i]) / sum);
|
|
||||||
px.push(v);
|
|
||||||
used += v;
|
|
||||||
}
|
}
|
||||||
return px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Riallinea larghezza e posizione delle tendine Select2 aperte nella tabella
|
// Applica le larghezze salvate
|
||||||
function syncOpenSelect2() {
|
$colgroup.find("col").each(function (i) {
|
||||||
$("#partsTable select.select2-hidden-accessible").each(function () {
|
const w = savedWidths[i] !== undefined ? savedWidths[i] : 150;
|
||||||
const inst = $(this).data("select2");
|
$(this).css("width", w + "px");
|
||||||
if (!inst || typeof inst.isOpen !== "function" || !inst.isOpen()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const dd = inst.dropdown;
|
|
||||||
try {
|
|
||||||
if (dd && typeof dd._resizeDropdown === "function") {
|
|
||||||
dd._resizeDropdown();
|
|
||||||
}
|
|
||||||
if (dd && typeof dd._positionDropdown === "function") {
|
|
||||||
dd._positionDropdown();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Se la versione di Select2 non lo supporta, chiude la tendina
|
|
||||||
inst.close();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Imposta larghezza totale della tabella = somma colonne (evita reflow)
|
||||||
|
const total = savedWidths.slice(0, thCount).reduce((a, b) => a + b, 0);
|
||||||
|
$table.css("width", total + "px");
|
||||||
}
|
}
|
||||||
|
|
||||||
function render() {
|
function applyColumnWidth(colIndex, newWidth) {
|
||||||
const n = getThs().length;
|
const w = Math.max(40, Math.round(newWidth));
|
||||||
if (!n || !getAvail()) return;
|
savedWidths[colIndex] = w;
|
||||||
ensureCols(n);
|
|
||||||
ensureWeights(n);
|
|
||||||
const px = computePx(n);
|
|
||||||
getColgroup()
|
|
||||||
.find("col")
|
|
||||||
.each(function (i) {
|
|
||||||
$(this).css("width", px[i] + "px");
|
|
||||||
});
|
|
||||||
|
|
||||||
syncOpenSelect2();
|
const $col = getColgroup().find("col").eq(colIndex);
|
||||||
|
if ($col.length) {
|
||||||
|
$col.css("width", w + "px");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggiorna larghezza totale tabella senza toccare le altre colonne
|
||||||
|
const thCount = $("#partsTable thead tr:first th").length;
|
||||||
|
const total = savedWidths.slice(0, thCount).reduce((a, b) => a + b, 0);
|
||||||
|
$("#partsTable").css("width", total + "px");
|
||||||
}
|
}
|
||||||
|
|
||||||
function addResizers() {
|
function addResizers() {
|
||||||
const $ths = getThs();
|
const $table = $("#partsTable");
|
||||||
const n = $ths.length;
|
if (!$table.length) return;
|
||||||
if (!n) return;
|
|
||||||
|
|
||||||
$ths.find(".parts-resizer").remove();
|
|
||||||
|
|
||||||
// Resizer sul bordo destro delle colonne da 1 a n-2:
|
|
||||||
// sposta spazio tra la colonna e quella alla sua destra
|
|
||||||
$ths.each(function (i) {
|
|
||||||
if (i === 0 || i >= n - 1) return;
|
|
||||||
|
|
||||||
|
$table.find("thead tr:first th").each(function (colIndex) {
|
||||||
const $th = $(this);
|
const $th = $(this);
|
||||||
$th.css("position", "relative");
|
|
||||||
|
// Salta colonna Num (indice 0) — non ridimensionabile
|
||||||
|
if (colIndex === 0) return;
|
||||||
|
|
||||||
|
// Non aggiungere due volte
|
||||||
|
if ($th.find(".parts-resizer").length) return;
|
||||||
|
|
||||||
const $resizer = $("<span class='parts-resizer'></span>");
|
const $resizer = $("<span class='parts-resizer'></span>");
|
||||||
|
$th.css("position", "relative"); // necessario per il posizionamento assoluto
|
||||||
$th.append($resizer);
|
$th.append($resizer);
|
||||||
|
|
||||||
$resizer.on("mousedown", function (e) {
|
$resizer.on("mousedown", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
ensureWeights(n);
|
|
||||||
const startPx = computePx(n);
|
|
||||||
const a = startPx[i];
|
|
||||||
const b = startPx[i + 1];
|
|
||||||
const startX = e.pageX;
|
const startX = e.pageX;
|
||||||
|
// Leggi la larghezza ESATTA dalla <col>, non dal <th>
|
||||||
|
const startWidth =
|
||||||
|
savedWidths[colIndex] !== undefined
|
||||||
|
? savedWidths[colIndex]
|
||||||
|
: parseInt(
|
||||||
|
getColgroup()
|
||||||
|
.find("col")
|
||||||
|
.eq(colIndex)
|
||||||
|
.css("width"),
|
||||||
|
10,
|
||||||
|
) || 150;
|
||||||
|
|
||||||
$("body").css({ "user-select": "none", cursor: "col-resize" });
|
$("body")
|
||||||
|
.css("user-select", "none")
|
||||||
|
.css("cursor", "col-resize");
|
||||||
|
|
||||||
$(document).on("mousemove.partsResize", function (ev) {
|
$(document).on("mousemove.partsResize", function (ev) {
|
||||||
const delta = ev.pageX - startX;
|
const delta = ev.pageX - startX;
|
||||||
const newA = Math.min(
|
applyColumnWidth(colIndex, startWidth + delta);
|
||||||
Math.max(a + delta, MIN_W),
|
|
||||||
a + b - MIN_W,
|
|
||||||
);
|
|
||||||
const newB = a + b - newA;
|
|
||||||
|
|
||||||
const px = startPx.slice();
|
|
||||||
px[i] = newA;
|
|
||||||
px[i + 1] = newB;
|
|
||||||
weights = px.slice(1); // i pixel diventano i nuovi pesi
|
|
||||||
render();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on("mouseup.partsResize", function () {
|
$(document).on("mouseup.partsResize", function () {
|
||||||
$("body").css({ "user-select": "", cursor: "" });
|
$("body").css("user-select", "").css("cursor", "");
|
||||||
$(document).off(".partsResize");
|
$(document).off(".partsResize");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function refresh() {
|
function init() {
|
||||||
render();
|
syncColgroupToHeaders();
|
||||||
addResizers();
|
addResizers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init al primo show del modale
|
||||||
$(document).on("shown.bs.modal", "#partsModal", function () {
|
$(document).on("shown.bs.modal", "#partsModal", function () {
|
||||||
setTimeout(refresh, 120);
|
setTimeout(init, 120);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Re-init dopo ogni AJAX (nuove righe, caricamenti)
|
||||||
$(document).ajaxComplete(function () {
|
$(document).ajaxComplete(function () {
|
||||||
if ($("#partsModal").hasClass("show")) {
|
if ($("#partsModal").hasClass("show")) {
|
||||||
setTimeout(refresh, 200);
|
setTimeout(syncColgroupToHeaders, 200);
|
||||||
|
setTimeout(addResizers, 220);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Re-init dopo aggiunta/rimozione righe
|
||||||
$(document).on(
|
$(document).on(
|
||||||
"click",
|
"click",
|
||||||
".add-row-global, .add-mix-global, .add-mix-row, .remove-row, #renumberPartsBtn, #clonePartsBtn",
|
".add-row-global, .add-mix-global, .add-mix-row, .remove-row, #renumberPartsBtn, #clonePartsBtn",
|
||||||
function () {
|
function () {
|
||||||
setTimeout(refresh, 120);
|
setTimeout(syncColgroupToHeaders, 120);
|
||||||
|
setTimeout(addResizers, 140);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let resizeTimer = null;
|
window.initPartsResizableColumns = init;
|
||||||
$(window).on("resize.partsCols", function () {
|
window.applyPartsColumnWidths = syncColgroupToHeaders;
|
||||||
if (!$("#partsModal").hasClass("show")) return;
|
|
||||||
clearTimeout(resizeTimer);
|
|
||||||
resizeTimer = setTimeout(render, 150);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Textarea campo extra: auto-altezza (per browser senza field-sizing)
|
|
||||||
$(document).on(
|
|
||||||
"input focus",
|
|
||||||
"#partsTable textarea.part-extra-field",
|
|
||||||
function () {
|
|
||||||
this.style.height = "auto";
|
|
||||||
this.style.height = this.scrollHeight + "px";
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
window.initPartsResizableColumns = refresh;
|
|
||||||
window.applyPartsColumnWidths = refresh;
|
|
||||||
})();
|
})();
|
||||||
// ===================
|
// ===================
|
||||||
// NAVIGAZIONE TABELLA PARTI CON FRECCE TASTIERA
|
// NAVIGAZIONE TABELLA PARTI CON FRECCE TASTIERA
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -8,41 +8,12 @@ header('Content-Type: application/json');
|
|||||||
|
|
||||||
$iddatadb = (int)($_POST['iddatadb'] ?? 0);
|
$iddatadb = (int)($_POST['iddatadb'] ?? 0);
|
||||||
$newStatus = $_POST['status'] ?? 'i';
|
$newStatus = $_POST['status'] ?? 'i';
|
||||||
$action = $_POST['action'] ?? 'revert';
|
|
||||||
|
|
||||||
if ($iddatadb <= 0) {
|
if ($iddatadb <= 0) {
|
||||||
echo json_encode(['success' => false, 'message' => 'ID record mancante']);
|
echo json_encode(['success' => false, 'message' => 'ID record mancante']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancellazione record (consentita solo da stato 'i' o 'x')
|
|
||||||
if ($action === 'delete') {
|
|
||||||
try {
|
|
||||||
$db = DBHandlerSelect::getInstance();
|
|
||||||
$pdo = $db->getConnection();
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare("DELETE FROM datadb WHERE iddatadb = ? AND status IN ('i', 'x')");
|
|
||||||
$stmt->execute([$iddatadb]);
|
|
||||||
|
|
||||||
if ($stmt->rowCount() > 0) {
|
|
||||||
echo json_encode(['success' => true]);
|
|
||||||
} else {
|
|
||||||
$chk = $pdo->prepare("SELECT status FROM datadb WHERE iddatadb = ?");
|
|
||||||
$chk->execute([$iddatadb]);
|
|
||||||
$current = $chk->fetchColumn();
|
|
||||||
|
|
||||||
if ($current === false) {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Record non trovato']);
|
|
||||||
} else {
|
|
||||||
echo json_encode(['success' => false, 'message' => "Cancellazione non consentita: stato attuale '$current'"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Throwable $e) {
|
|
||||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Da questa azione consentiamo solo il ritorno a 'i'
|
// Da questa azione consentiamo solo il ritorno a 'i'
|
||||||
if ($newStatus !== 'i') {
|
if ($newStatus !== 'i') {
|
||||||
echo json_encode(['success' => false, 'message' => 'Stato non consentito']);
|
echo json_encode(['success' => false, 'message' => 'Stato non consentito']);
|
||||||
@@ -53,11 +24,8 @@ try {
|
|||||||
$db = DBHandlerSelect::getInstance();
|
$db = DBHandlerSelect::getInstance();
|
||||||
$pdo = $db->getConnection();
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
// Aggiorna solo se attualmente è in stato 'l' (To LIMS).
|
// Aggiorna solo se attualmente è in stato 'l' (To LIMS)
|
||||||
// Azzera idcommessaweb: il revert è una scelta esplicita di riesportare, altrimenti il
|
$stmt = $pdo->prepare("UPDATE datadb SET status = ? WHERE iddatadb = ? AND status = 'l'");
|
||||||
// CHECK PRELIMINARE di export_to_lims.php riallineerebbe la riga alla vecchia commessa.
|
|
||||||
// commessaweb resta come traccia del codice precedente.
|
|
||||||
$stmt = $pdo->prepare("UPDATE datadb SET status = ?, idcommessaweb = NULL WHERE iddatadb = ? AND status IN ('l', 'x')");
|
|
||||||
$stmt->execute([$newStatus, $iddatadb]);
|
$stmt->execute([$newStatus, $iddatadb]);
|
||||||
|
|
||||||
if ($stmt->rowCount() > 0) {
|
if ($stmt->rowCount() > 0) {
|
||||||
|
|||||||
@@ -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()]);
|
||||||
|
}
|
||||||
@@ -57,7 +57,7 @@ try {
|
|||||||
$api = VisualLimsApiClient::getInstance();
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
|
||||||
$params = [
|
$params = [
|
||||||
'$select' => 'IdCliente,Nominativo,CodiceCliente,Inattivo,CodiceASL',
|
'$select' => 'IdCliente,Nominativo,CodiceCliente,Inattivo',
|
||||||
'$orderby' => 'Nominativo asc'
|
'$orderby' => 'Nominativo asc'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -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' => []]);
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
include('include/headscript.php');
|
include('include/headscript.php');
|
||||||
require_once __DIR__ . '/class/Mailer.class.php';
|
require_once __DIR__ . '/class/Mailer.class.php';
|
||||||
require_once __DIR__ . '/class/block_alert.php';
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,10 +91,9 @@ $sql = "
|
|||||||
FROM datadb d
|
FROM datadb d
|
||||||
LEFT JOIN auth_users u ON d.user_id = u.id
|
LEFT JOIN auth_users u ON d.user_id = u.id
|
||||||
WHERE d.iddatadb IN ($ph)
|
WHERE d.iddatadb IN ($ph)
|
||||||
ORDER BY FIELD(d.iddatadb, $ph)
|
|
||||||
";
|
";
|
||||||
$stmt = $pdo->prepare($sql);
|
$stmt = $pdo->prepare($sql);
|
||||||
$stmt->execute(array_merge($ids, $ids));
|
$stmt->execute($ids);
|
||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
if (empty($rows)) {
|
if (empty($rows)) {
|
||||||
echo json_encode(['success' => false, 'message' => 'Righe non trovate']);
|
echo json_encode(['success' => false, 'message' => 'Righe non trovate']);
|
||||||
@@ -159,16 +157,6 @@ foreach ($s->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
|||||||
}
|
}
|
||||||
$noPriceTotal = array_sum(array_map('count', $noPriceByRow));
|
$noPriceTotal = array_sum(array_map('count', $noPriceByRow));
|
||||||
|
|
||||||
// cliente bloccato per ogni iddatadb (CodiceASL con "blocco", letto dalla cache clienti, nessuna chiamata API)
|
|
||||||
$blockByRow = []; // iddatadb => ['idCliente','nominativo','motivo','campo']
|
|
||||||
foreach ($rows as $r) {
|
|
||||||
$bl = getClientBlockAlert($r['idclient'] ?? 0, $r['ClienteFatturazione'] ?? null);
|
|
||||||
if ($bl) {
|
|
||||||
$blockByRow[(int)$r['iddatadb']] = $bl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$blockTotal = count($blockByRow);
|
|
||||||
|
|
||||||
// destinatari: utente/i dell'export che hanno la notifica attiva + admin fisso
|
// destinatari: utente/i dell'export che hanno la notifica attiva + admin fisso
|
||||||
$recipients = [];
|
$recipients = [];
|
||||||
foreach ($rows as $r) {
|
foreach ($rows as $r) {
|
||||||
@@ -186,18 +174,14 @@ if (empty($recipients)) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// conteggi. La mail riflette lo stato in DB al momento dell'invio: una riga ancora 'i'
|
// conteggi
|
||||||
// (export interrotto da timeout/errore di rete, o ancora in corso) NON è un successo.
|
|
||||||
$okCount = 0;
|
$okCount = 0;
|
||||||
$errCount = 0;
|
$errCount = 0;
|
||||||
$pendingCount = 0;
|
|
||||||
foreach ($rows as $r) {
|
foreach ($rows as $r) {
|
||||||
if ($r['status'] === 'l') $okCount++;
|
if ($r['status'] === 'l') $okCount++;
|
||||||
elseif ($r['status'] === 'x') $errCount++;
|
elseif ($r['status'] === 'x') $errCount++;
|
||||||
else $pendingCount++;
|
|
||||||
}
|
}
|
||||||
$hasError = $errCount > 0;
|
$hasError = $errCount > 0;
|
||||||
$hasPending = $pendingCount > 0;
|
|
||||||
|
|
||||||
// --- tabella righe ---
|
// --- tabella righe ---
|
||||||
$rowsHtml = '';
|
$rowsHtml = '';
|
||||||
@@ -212,7 +196,7 @@ foreach ($rows as $r) {
|
|||||||
$bg = '#fdecea';
|
$bg = '#fdecea';
|
||||||
$col = '#c0392b';
|
$col = '#c0392b';
|
||||||
} else {
|
} else {
|
||||||
$label = 'NON CONFERMATA';
|
$label = htmlspecialchars((string)$st);
|
||||||
$bg = '#fff8e1';
|
$bg = '#fff8e1';
|
||||||
$col = '#8a6d3b';
|
$col = '#8a6d3b';
|
||||||
}
|
}
|
||||||
@@ -253,44 +237,12 @@ foreach ($rows as $r) {
|
|||||||
</td>
|
</td>
|
||||||
</tr>";
|
</tr>";
|
||||||
}
|
}
|
||||||
|
|
||||||
// sotto-riga: cliente bloccato
|
|
||||||
$bl = $blockByRow[(int)$r['iddatadb']] ?? null;
|
|
||||||
if ($bl) {
|
|
||||||
$blCampo = htmlspecialchars($bl['campo']);
|
|
||||||
$blNome = htmlspecialchars($bl['nominativo'] !== '' ? $bl['nominativo'] : (string)$bl['idCliente']);
|
|
||||||
$blMotivo = htmlspecialchars($bl['motivo']);
|
|
||||||
|
|
||||||
$rowsHtml .= "<tr>
|
|
||||||
<td colspan='5' style='padding:8px 12px 12px 24px;border-bottom:1px solid #eee;background:#fdecea;'>
|
|
||||||
<div style='font-size:12px;color:#842029;font-weight:600;'>
|
|
||||||
<span style='display:inline-block;padding:2px 8px;border-radius:10px;background:#842029;color:#fff;font-size:11px;'>Cliente bloccato</span>
|
|
||||||
{$blCampo}: {$blNome}
|
|
||||||
</div>
|
|
||||||
<div style='margin-top:4px;font-size:13px;color:#842029;font-weight:700;'>{$blMotivo}</div>
|
|
||||||
</td>
|
|
||||||
</tr>";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($hasError) {
|
$headerColor = $hasError ? '#c0392b' : '#1e7e34';
|
||||||
$headerColor = '#c0392b';
|
$headerText = $hasError ? 'Export completato con errori' : 'Export completato con successo';
|
||||||
$headerText = 'Export completato con errori';
|
|
||||||
} elseif ($hasPending) {
|
|
||||||
$headerColor = '#b7791f';
|
|
||||||
$headerText = 'Export da verificare';
|
|
||||||
} else {
|
|
||||||
$headerColor = '#1e7e34';
|
|
||||||
$headerText = 'Export completato con successo';
|
|
||||||
}
|
|
||||||
$total = count($rows);
|
$total = count($rows);
|
||||||
|
|
||||||
// Committente / Fatturazione in testata solo se uguali per tutte le righe (batch misti → "vari")
|
|
||||||
$headerClient = function ($col) use ($rows, $clientLabel) {
|
|
||||||
$vals = array_unique(array_map(fn($r) => (int)($r[$col] ?? 0), $rows));
|
|
||||||
return count($vals) === 1 ? $clientLabel(reset($vals)) : 'vari';
|
|
||||||
};
|
|
||||||
|
|
||||||
$html = "
|
$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='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;'>
|
<div style='background:{$headerColor};color:#fff;padding:18px 24px;'>
|
||||||
@@ -298,9 +250,9 @@ $html = "
|
|||||||
<p style='margin:6px 0 0;font-size:13px;opacity:.9;'>Riepilogo esportazione verso LIMS" .
|
<p style='margin:6px 0 0;font-size:13px;opacity:.9;'>Riepilogo esportazione verso LIMS" .
|
||||||
($templateName !== '' ? " · Template: <strong>" . htmlspecialchars($templateName) . "</strong>" : "") . "</p>
|
($templateName !== '' ? " · Template: <strong>" . htmlspecialchars($templateName) . "</strong>" : "") . "</p>
|
||||||
<p style='margin:6px 0 0;font-size:13px;opacity:.95;'>
|
<p style='margin:6px 0 0;font-size:13px;opacity:.95;'>
|
||||||
Committente: <strong>" . $headerClient('idclient') . "</strong>
|
Committente: <strong>" . $clientLabel($rows[0]['idclient'] ?? 0) . "</strong>
|
||||||
·
|
·
|
||||||
FatturazioneAvanzata: <strong>" . $headerClient('ClienteFatturazione') . "</strong>
|
FatturazioneAvanzata: <strong>" . $clientLabel($rows[0]['ClienteFatturazione'] ?? 0) . "</strong>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div style='padding:20px 24px;'>
|
<div style='padding:20px 24px;'>
|
||||||
@@ -308,9 +260,7 @@ $html = "
|
|||||||
Totale commesse: <strong>{$total}</strong> •
|
Totale commesse: <strong>{$total}</strong> •
|
||||||
<span style='color:#1e7e34;'>Inviate: {$okCount}</span> •
|
<span style='color:#1e7e34;'>Inviate: {$okCount}</span> •
|
||||||
<span style='color:#c0392b;'>Errori: {$errCount}</span>" .
|
<span style='color:#c0392b;'>Errori: {$errCount}</span>" .
|
||||||
($pendingCount > 0 ? " • <span style='color:#8a6d3b;'>Non confermate: {$pendingCount}</span>" : "") .
|
($noPriceTotal > 0 ? " • <span style='color:#c0392b;'>Analisi No price: {$noPriceTotal}</span>" : "") . "
|
||||||
($noPriceTotal > 0 ? " • <span style='color:#c0392b;'>Analisi No price: {$noPriceTotal}</span>" : "") .
|
|
||||||
($blockTotal > 0 ? " • <span style='color:#842029;'>Clienti bloccati: {$blockTotal}</span>" : "") . "
|
|
||||||
</p>
|
</p>
|
||||||
<table style='width:100%;border-collapse:collapse;font-size:14px;table-layout:fixed;'>
|
<table style='width:100%;border-collapse:collapse;font-size:14px;table-layout:fixed;'>
|
||||||
<thead>
|
<thead>
|
||||||
@@ -324,16 +274,15 @@ $html = "
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>{$rowsHtml}</tbody>
|
<tbody>{$rowsHtml}</tbody>
|
||||||
</table>
|
</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>" : "") .
|
" . ($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>" : "") . "
|
||||||
($hasPending ? "<p style='margin:18px 0 0;padding:12px;background:#fff8e1;border-left:4px solid #b7791f;color:#8a6d3b;font-size:13px;border-radius:4px;'>⚠️ Per alcune commesse l'esito non è ancora confermato (timeout o errore di rete durante l'invio). <strong>Non rilanciarle</strong>: ricarica la pagina tra qualche minuto e verifica lo stato.</p>" : "") . "
|
|
||||||
</div>
|
</div>
|
||||||
<div style='background:#fafafa;padding:14px 24px;font-size:12px;color:#999;border-top:1px solid #eee;'>
|
<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.
|
Messaggio automatico da SmartTRF — non rispondere a questa email.
|
||||||
</div>
|
</div>
|
||||||
</div>";
|
</div>";
|
||||||
|
|
||||||
$subjectPrefix = $hasError ? '⚠️ Errori Export LIMS' : ($hasPending ? '⏳ Export LIMS da verificare' : '✅ Export LIMS OK');
|
$subjectPrefix = $hasError ? '⚠️ Errori Export LIMS' : '✅ Export LIMS OK';
|
||||||
$subject = "{$subjectPrefix} — {$total} commesse ({$okCount} ok, {$errCount} err" . ($pendingCount > 0 ? ", {$pendingCount} non confermate" : "") . ")";
|
$subject = "{$subjectPrefix} — {$total} commesse ({$okCount} ok, {$errCount} err)";
|
||||||
|
|
||||||
$res = Mailer::send($recipients, $subject, $html);
|
$res = Mailer::send($recipients, $subject, $html);
|
||||||
|
|
||||||
@@ -341,5 +290,5 @@ echo json_encode([
|
|||||||
'success' => $res['ok'],
|
'success' => $res['ok'],
|
||||||
'message' => $res['ok'] ? 'Mail inviata' : ('Mail non inviata: ' . $res['error']),
|
'message' => $res['ok'] ? 'Mail inviata' : ('Mail non inviata: ' . $res['error']),
|
||||||
'recipients' => $recipients,
|
'recipients' => $recipients,
|
||||||
'counts' => ['ok' => $okCount, 'err' => $errCount, 'pending' => $pendingCount, 'total' => $total],
|
'counts' => ['ok' => $okCount, 'err' => $errCount, 'total' => $total],
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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] ?? [],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ try {
|
|||||||
|
|
||||||
// 1. Clients
|
// 1. Clients
|
||||||
warmLog('[clients] Fetching...', $isCli);
|
warmLog('[clients] Fetching...', $isCli);
|
||||||
$params = ['$select' => 'IdCliente,Nominativo,CodiceCliente,Inattivo,CodiceASL', '$orderby' => 'Nominativo asc'];
|
$params = ['$select' => 'IdCliente,Nominativo,CodiceCliente,Inattivo', '$orderby' => 'Nominativo asc'];
|
||||||
$clientData = $api->get("Cliente?" . http_build_query($params));
|
$clientData = $api->get("Cliente?" . http_build_query($params));
|
||||||
file_put_contents($cacheDir . '/clienti.json', json_encode($clientData));
|
file_put_contents($cacheDir . '/clienti.json', json_encode($clientData));
|
||||||
$clientCount = count($clientData['value'] ?? []);
|
$clientCount = count($clientData['value'] ?? []);
|
||||||
|
|||||||
Reference in New Issue
Block a user