Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 955b663322 | |||
| 4dc010d80e | |||
| 83c6157b10 |
@@ -61,3 +61,4 @@ public/userarea/schemi_base_response.json
|
|||||||
*.log
|
*.log
|
||||||
|
|
||||||
public/userarea/cache/
|
public/userarea/cache/
|
||||||
|
public/userarea/error_log.txt
|
||||||
|
|||||||
@@ -109,6 +109,8 @@ class LoginController extends Controller
|
|||||||
// Reindirizza in base al ruolo
|
// Reindirizza in base al ruolo
|
||||||
if ($user->hasRole('Admin')) {
|
if ($user->hasRole('Admin')) {
|
||||||
return redirect()->to('userarea/import_dashboard.php');
|
return redirect()->to('userarea/import_dashboard.php');
|
||||||
|
} elseif ($user->hasRole('SuperUser')) {
|
||||||
|
return redirect()->to('userarea/import_dashboard.php');
|
||||||
} elseif ($user->hasRole('User')) {
|
} elseif ($user->hasRole('User')) {
|
||||||
return redirect()->to('userarea/import_dashboard.php');
|
return redirect()->to('userarea/import_dashboard.php');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,449 +0,0 @@
|
|||||||
<?php
|
|
||||||
include('include/headscript.php');
|
|
||||||
require_once(__DIR__ . '/class/binding-functions.php');
|
|
||||||
|
|
||||||
$db = DBHandlerSelect::getInstance();
|
|
||||||
$pdo = $db->getConnection();
|
|
||||||
|
|
||||||
// Filtri comuni.
|
|
||||||
$templateFilter = isset($_GET['template_id']) ? intval($_GET['template_id']) : 0;
|
|
||||||
$search = trim($_GET['q'] ?? '');
|
|
||||||
$perPage = 50;
|
|
||||||
$page = max(1, intval($_GET['page'] ?? 1));
|
|
||||||
$dir = (strtolower($_GET['dir'] ?? 'asc') === 'desc') ? 'DESC' : 'ASC';
|
|
||||||
|
|
||||||
// Modalita': overview (gruppi per campo) oppure detail (valori di un campo).
|
|
||||||
$focusTarget = trim($_GET['target'] ?? '');
|
|
||||||
$mode = $focusTarget !== '' ? 'detail' : 'overview';
|
|
||||||
|
|
||||||
$templates = $pdo->query("SELECT id, name FROM excel_templates ORDER BY name")->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
// Helper: URL che preserva i filtri correnti.
|
|
||||||
$bmUrl = function (array $ov) use ($templateFilter, $search, $dir, $page, $focusTarget) {
|
|
||||||
$base = [
|
|
||||||
'template_id' => $templateFilter ?: null,
|
|
||||||
'q' => $search !== '' ? $search : null,
|
|
||||||
'order' => $_GET['order'] ?? null,
|
|
||||||
'dir' => strtolower($dir),
|
|
||||||
'page' => $page,
|
|
||||||
'target' => $focusTarget !== '' ? $focusTarget : null,
|
|
||||||
];
|
|
||||||
$p = array_filter(array_merge($base, $ov), fn($v) => $v !== null && $v !== '');
|
|
||||||
return 'bindings_manage.php?' . http_build_query($p);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Filtri WHERE comuni (template/kind/search).
|
|
||||||
$conds = [];
|
|
||||||
$params = [];
|
|
||||||
if ($templateFilter > 0) {
|
|
||||||
$conds[] = 'b.template_id = ?';
|
|
||||||
$params[] = $templateFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($mode === 'detail') {
|
|
||||||
// ---- DETAIL: valori (json_value -> lims) di un singolo campo (target_key) ----
|
|
||||||
$conds[] = 'b.target_key = ?';
|
|
||||||
$params[] = $focusTarget;
|
|
||||||
if ($search !== '') {
|
|
||||||
$conds[] = '(b.json_value LIKE ? OR b.lims_value LIKE ?)';
|
|
||||||
$like = '%' . $search . '%';
|
|
||||||
array_push($params, $like, $like);
|
|
||||||
}
|
|
||||||
$where = 'WHERE ' . implode(' AND ', $conds);
|
|
||||||
|
|
||||||
$orderCols = ['json' => 'b.json_value', 'lims' => 'b.lims_value'];
|
|
||||||
$order = array_key_exists($_GET['order'] ?? '', $orderCols) ? $_GET['order'] : 'json';
|
|
||||||
$orderSql = $orderCols[$order] . ' ' . $dir;
|
|
||||||
|
|
||||||
$countStmt = $pdo->prepare("SELECT COUNT(*) FROM json_lims_binding b $where");
|
|
||||||
$countStmt->execute($params);
|
|
||||||
$total = (int) $countStmt->fetchColumn();
|
|
||||||
$totalPages = max(1, (int) ceil($total / $perPage));
|
|
||||||
if ($page > $totalPages) $page = $totalPages;
|
|
||||||
$offset = ($page - 1) * $perPage;
|
|
||||||
|
|
||||||
$sql = "SELECT b.id, b.template_id, b.binding_kind, b.mapping_id, b.fixed_field_key, b.field_id,
|
|
||||||
b.json_value, b.lims_value_id, b.lims_value,
|
|
||||||
t.name AS template_name, m.field_label
|
|
||||||
FROM json_lims_binding b
|
|
||||||
LEFT JOIN excel_templates t ON t.id = b.template_id
|
|
||||||
LEFT JOIN template_mapping m ON m.id = b.mapping_id
|
|
||||||
$where
|
|
||||||
ORDER BY $orderSql, b.json_value ASC
|
|
||||||
LIMIT $perPage OFFSET $offset";
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
$stmt->execute($params);
|
|
||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
// Intestazione del gruppo (tutte le righe condividono campo/template/kind).
|
|
||||||
$head = $rows[0] ?? null;
|
|
||||||
if (!$head) {
|
|
||||||
// Target vuoto (es. dopo aver svuotato il campo): ricavo i meta dal target_key.
|
|
||||||
$head = ['binding_kind' => str_starts_with($focusTarget, 'fx:') ? 'fixed' : 'custom'];
|
|
||||||
}
|
|
||||||
$headKind = $head['binding_kind'] ?? 'custom';
|
|
||||||
if ($headKind === 'fixed') {
|
|
||||||
$headFixedKey = $head['fixed_field_key'] ?? (explode(':', $focusTarget)[2] ?? '');
|
|
||||||
$headLabel = binding_fixed_label((string) $headFixedKey);
|
|
||||||
$headTpl = (int) ($head['template_id'] ?? (explode(':', $focusTarget)[1] ?? 0));
|
|
||||||
} else {
|
|
||||||
$headLabel = $head['field_label'] ?? ('mapping ' . ($head['mapping_id'] ?? ''));
|
|
||||||
$headTpl = (int) ($head['template_id'] ?? 0);
|
|
||||||
}
|
|
||||||
$headTplName = $headTpl ? ($pdo->query("SELECT name FROM excel_templates WHERE id=" . $headTpl)->fetchColumn() ?: ('#' . $headTpl)) : '';
|
|
||||||
} else {
|
|
||||||
// ---- OVERVIEW: un gruppo per campo (target_key) con il conteggio ----
|
|
||||||
if ($search !== '') {
|
|
||||||
$conds[] = '(t.name LIKE ? OR m.field_label LIKE ? OR b.fixed_field_key LIKE ?)';
|
|
||||||
$like = '%' . $search . '%';
|
|
||||||
array_push($params, $like, $like, $like);
|
|
||||||
}
|
|
||||||
$where = $conds ? ('WHERE ' . implode(' AND ', $conds)) : '';
|
|
||||||
|
|
||||||
$orderCols = ['template' => 'template_name', 'field' => 'field_label', 'count' => 'cnt'];
|
|
||||||
$order = array_key_exists($_GET['order'] ?? '', $orderCols) ? $_GET['order'] : 'template';
|
|
||||||
$orderSql = $orderCols[$order] . ' ' . $dir;
|
|
||||||
|
|
||||||
$countStmt = $pdo->prepare("SELECT COUNT(DISTINCT b.target_key)
|
|
||||||
FROM json_lims_binding b
|
|
||||||
LEFT JOIN excel_templates t ON t.id = b.template_id
|
|
||||||
LEFT JOIN template_mapping m ON m.id = b.mapping_id $where");
|
|
||||||
$countStmt->execute($params);
|
|
||||||
$total = (int) $countStmt->fetchColumn();
|
|
||||||
$totalPages = max(1, (int) ceil($total / $perPage));
|
|
||||||
if ($page > $totalPages) $page = $totalPages;
|
|
||||||
$offset = ($page - 1) * $perPage;
|
|
||||||
|
|
||||||
$sql = "SELECT b.target_key, b.template_id, b.binding_kind, b.mapping_id, b.fixed_field_key,
|
|
||||||
MAX(t.name) AS template_name, MAX(m.field_label) AS field_label,
|
|
||||||
COUNT(*) AS cnt, MAX(b.updated_at) AS last_updated
|
|
||||||
FROM json_lims_binding b
|
|
||||||
LEFT JOIN excel_templates t ON t.id = b.template_id
|
|
||||||
LEFT JOIN template_mapping m ON m.id = b.mapping_id
|
|
||||||
$where
|
|
||||||
GROUP BY b.target_key, b.template_id, b.binding_kind, b.mapping_id, b.fixed_field_key
|
|
||||||
ORDER BY $orderSql, template_name ASC
|
|
||||||
LIMIT $perPage OFFSET $offset";
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
$stmt->execute($params);
|
|
||||||
$groups = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
}
|
|
||||||
|
|
||||||
$sortLink = function (string $col, string $label) use ($bmUrl, $order, $dir) {
|
|
||||||
$nextDir = ($order === $col && $dir === 'ASC') ? 'desc' : 'asc';
|
|
||||||
$caret = $order === $col ? ($dir === 'ASC' ? ' ▲' : ' ▼') : '';
|
|
||||||
return '<a href="' . htmlspecialchars($bmUrl(['order' => $col, 'dir' => $nextDir, 'page' => 1]))
|
|
||||||
. '" class="text-decoration-none text-reset">' . $label . $caret . '</a>';
|
|
||||||
};
|
|
||||||
?>
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
|
||||||
<?php include('cssinclude.php'); ?>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
.json-value {
|
|
||||||
font-family: Consolas, Monaco, monospace;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
td .select2-container {
|
|
||||||
min-width: 220px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-status {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-row {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-row:hover {
|
|
||||||
background-color: #f1f5ff;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<title>Gestione Binding JSON → LIMS - <?= htmlspecialchars($titlewebsite ?? '', ENT_QUOTES, 'UTF-8'); ?></title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="wrapper">
|
|
||||||
<?php include('include/navbar.php'); ?>
|
|
||||||
<?php include('include/topbar.php'); ?>
|
|
||||||
<div class="page-wrapper">
|
|
||||||
<div class="page-content">
|
|
||||||
|
|
||||||
<div class="card radius-10">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
|
|
||||||
<h6 class="mb-0">Gestione Binding JSON → LIMS</h6>
|
|
||||||
<form method="GET" class="d-flex align-items-center gap-2 flex-wrap mb-0">
|
|
||||||
<?php if ($mode === 'detail'): ?>
|
|
||||||
<input type="hidden" name="target" value="<?= htmlspecialchars($focusTarget) ?>">
|
|
||||||
<?php endif; ?>
|
|
||||||
<input type="hidden" name="dir" value="<?= htmlspecialchars(strtolower($dir)) ?>">
|
|
||||||
<input type="text" name="q" class="form-control form-control-sm" style="width:220px;"
|
|
||||||
placeholder="<?= $mode === 'detail' ? 'Cerca valore...' : 'Cerca campo/template...' ?>"
|
|
||||||
value="<?= htmlspecialchars($search) ?>">
|
|
||||||
<?php if ($mode === 'overview'): ?>
|
|
||||||
<select name="template_id" class="form-select form-select-sm" style="width:auto;" onchange="this.form.submit()">
|
|
||||||
<option value="0">Tutti i template</option>
|
|
||||||
<?php foreach ($templates as $t): ?>
|
|
||||||
<option value="<?= (int) $t['id'] ?>" <?= $templateFilter === (int) $t['id'] ? 'selected' : '' ?>>
|
|
||||||
<?= htmlspecialchars($t['name']) ?>
|
|
||||||
</option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
<?php endif; ?>
|
|
||||||
<button type="submit" class="btn btn-sm btn-primary">Cerca</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-body">
|
|
||||||
<div id="globalError" class="alert alert-danger" style="display:none;"></div>
|
|
||||||
|
|
||||||
<?php if ($mode === 'detail'): ?>
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2 flex-wrap gap-2">
|
|
||||||
<div>
|
|
||||||
<a href="<?= htmlspecialchars($bmUrl(['target' => null, 'page' => 1, 'order' => null])) ?>" class="btn btn-sm btn-outline-secondary">
|
|
||||||
<i class="fas fa-arrow-left"></i> Tutti i campi
|
|
||||||
</a>
|
|
||||||
<span class="ms-2">
|
|
||||||
<strong><?= htmlspecialchars($headLabel) ?></strong>
|
|
||||||
<?php if ($headKind === 'fixed'): ?><span class="badge bg-light text-dark border">fixed</span><?php endif; ?>
|
|
||||||
<span class="text-muted">· <?= htmlspecialchars((string) $headTplName) ?></span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<span class="small text-muted"><?= $total ?> valore/i · pagina <?= $page ?>/<?= $totalPages ?></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-striped table-bordered align-middle" id="bindingsTable">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th><?= $sortLink('json', 'Valore JSON') ?></th>
|
|
||||||
<th><?= $sortLink('lims', 'Valore LIMS') ?></th>
|
|
||||||
<th style="width:170px;">Azioni</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php if (empty($rows)): ?>
|
|
||||||
<tr class="no-data-row">
|
|
||||||
<td colspan="3" class="text-center text-muted">Nessun valore per questo campo.</td>
|
|
||||||
</tr>
|
|
||||||
<?php else: ?>
|
|
||||||
<?php foreach ($rows as $b): ?>
|
|
||||||
<?php $bDangling = ($b['binding_kind'] === 'custom' && $b['field_label'] === null); ?>
|
|
||||||
<tr data-id="<?= (int) $b['id'] ?>"
|
|
||||||
data-kind="<?= $b['binding_kind'] ?>"
|
|
||||||
data-mapping-id="<?= (int) $b['mapping_id'] ?>"
|
|
||||||
data-field-id="<?= (int) $b['field_id'] ?>"
|
|
||||||
data-fixed-key="<?= htmlspecialchars((string) $b['fixed_field_key'], ENT_QUOTES) ?>"
|
|
||||||
data-template-id="<?= (int) $b['template_id'] ?>"
|
|
||||||
data-json-value="<?= htmlspecialchars($b['json_value'], ENT_QUOTES) ?>">
|
|
||||||
<td class="json-value"><?= htmlspecialchars($b['json_value']) ?></td>
|
|
||||||
<td>
|
|
||||||
<select class="form-select binding-select" <?= $bDangling ? 'disabled' : '' ?>>
|
|
||||||
<?php if ($b['lims_value_id']): ?>
|
|
||||||
<option value="<?= (int) $b['lims_value_id'] ?>" selected><?= htmlspecialchars($b['lims_value']) ?></option>
|
|
||||||
<?php endif; ?>
|
|
||||||
</select>
|
|
||||||
<span class="row-status text-muted"></span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button type="button" class="btn btn-sm btn-success save-binding-btn" disabled>
|
|
||||||
<i class="fas fa-save"></i> Salva
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-danger delete-binding-btn">
|
|
||||||
<i class="fas fa-trash"></i>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="alert alert-secondary small">
|
|
||||||
Scegli un campo per gestirne i valori. Le modifiche valgono per le <strong>importazioni future</strong>.
|
|
||||||
</div>
|
|
||||||
<div class="small text-muted mb-2"><?= $total ?> camp<?= $total === 1 ? 'o' : 'i' ?> con binding · pagina <?= $page ?>/<?= $totalPages ?></div>
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-striped table-bordered align-middle">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th><?= $sortLink('template', 'Template') ?></th>
|
|
||||||
<th><?= $sortLink('field', 'Campo') ?></th>
|
|
||||||
<th style="width:120px;"><?= $sortLink('count', '# Binding') ?></th>
|
|
||||||
<th style="width:90px;"></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php if (empty($groups)): ?>
|
|
||||||
<tr>
|
|
||||||
<td colspan="4" class="text-center text-muted">Nessun binding presente.</td>
|
|
||||||
</tr>
|
|
||||||
<?php else: ?>
|
|
||||||
<?php foreach ($groups as $g): ?>
|
|
||||||
<?php
|
|
||||||
$gKind = $g['binding_kind'];
|
|
||||||
$gDangling = ($gKind === 'custom' && $g['field_label'] === null);
|
|
||||||
$gLabel = $gKind === 'fixed'
|
|
||||||
? binding_fixed_label((string) $g['fixed_field_key'])
|
|
||||||
: ($g['field_label'] ?? ('mapping ' . $g['mapping_id']));
|
|
||||||
$gUrl = $bmUrl(['target' => $g['target_key'], 'page' => 1, 'order' => null, 'q' => null]);
|
|
||||||
?>
|
|
||||||
<tr class="group-row" onclick="window.location.href='<?= htmlspecialchars($gUrl) ?>'">
|
|
||||||
<td><?= htmlspecialchars($g['template_name'] ?? ('#' . $g['template_id'])) ?></td>
|
|
||||||
<td>
|
|
||||||
<?= htmlspecialchars($gLabel) ?>
|
|
||||||
<?php if ($gKind === 'fixed'): ?><span class="badge bg-light text-dark border">fixed</span><?php endif; ?>
|
|
||||||
<?php if ($gDangling): ?><span class="badge bg-warning text-dark" title="Il campo mappato non esiste piu'">campo rimosso</span><?php endif; ?>
|
|
||||||
</td>
|
|
||||||
<td><span class="badge bg-primary"><?= (int) $g['cnt'] ?></span></td>
|
|
||||||
<td><a href="<?= htmlspecialchars($gUrl) ?>" class="btn btn-sm btn-outline-primary">Apri</a></td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php if ($totalPages > 1): ?>
|
|
||||||
<nav class="d-flex justify-content-center mt-2">
|
|
||||||
<ul class="pagination pagination-sm mb-0">
|
|
||||||
<li class="page-item <?= $page <= 1 ? 'disabled' : '' ?>">
|
|
||||||
<a class="page-link" href="<?= htmlspecialchars($bmUrl(['page' => max(1, $page - 1)])) ?>">«</a>
|
|
||||||
</li>
|
|
||||||
<li class="page-item disabled"><span class="page-link"><?= $page ?> / <?= $totalPages ?></span></li>
|
|
||||||
<li class="page-item <?= $page >= $totalPages ? 'disabled' : '' ?>">
|
|
||||||
<a class="page-link" href="<?= htmlspecialchars($bmUrl(['page' => min($totalPages, $page + 1)])) ?>">»</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="overlay toggle-icon"></div>
|
|
||||||
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
|
||||||
<?php include('include/footer.php'); ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php include('jsinclude.php'); ?>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
$(function() {
|
|
||||||
const $globalError = $('#globalError');
|
|
||||||
|
|
||||||
// Select2 solo nelle righe valore della pagina corrente (max 50), saltando le disabilitate.
|
|
||||||
$('.binding-select').not(':disabled').each(function() {
|
|
||||||
const $row = $(this).closest('tr');
|
|
||||||
const isFixed = $row.data('kind') === 'fixed';
|
|
||||||
const initialVal = $(this).val();
|
|
||||||
|
|
||||||
$(this).select2({
|
|
||||||
width: '220px',
|
|
||||||
ajax: {
|
|
||||||
url: isFixed ? 'search_fixed_field_values.php' : 'search_customfield_values.php',
|
|
||||||
dataType: 'json',
|
|
||||||
delay: 200,
|
|
||||||
data: params => isFixed ? {
|
|
||||||
field_key: $row.data('fixed-key'),
|
|
||||||
template_id: $row.data('template-id'),
|
|
||||||
q: params.term || '',
|
|
||||||
limit: 50
|
|
||||||
} : {
|
|
||||||
field_id: $row.data('field-id'),
|
|
||||||
q: params.term || '',
|
|
||||||
limit: 50
|
|
||||||
},
|
|
||||||
processResults: data => ({
|
|
||||||
results: data.results || []
|
|
||||||
})
|
|
||||||
},
|
|
||||||
minimumInputLength: 0
|
|
||||||
});
|
|
||||||
|
|
||||||
$(this).data('original-val', initialVal);
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.binding-select').on('change', function() {
|
|
||||||
const $row = $(this).closest('tr');
|
|
||||||
const changed = String($(this).val()) !== String($(this).data('original-val'));
|
|
||||||
$row.find('.save-binding-btn').prop('disabled', !changed);
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.save-binding-btn').on('click', function() {
|
|
||||||
const $row = $(this).closest('tr');
|
|
||||||
const $select = $row.find('.binding-select');
|
|
||||||
const selectedData = $select.select2('data')[0] || {};
|
|
||||||
const $status = $row.find('.row-status');
|
|
||||||
const $btn = $(this);
|
|
||||||
|
|
||||||
$globalError.hide();
|
|
||||||
$btn.prop('disabled', true);
|
|
||||||
$status.text('Salvataggio...').removeClass('text-success text-danger').addClass('text-muted');
|
|
||||||
|
|
||||||
const isFixed = $row.data('kind') === 'fixed';
|
|
||||||
const targetFields = isFixed
|
|
||||||
? { kind: 'fixed', fixed_field_key: $row.data('fixed-key') }
|
|
||||||
: { kind: 'custom', mapping_id: $row.data('mapping-id'), field_id: $row.data('field-id') };
|
|
||||||
|
|
||||||
$.post('save_binding.php', {
|
|
||||||
...targetFields,
|
|
||||||
template_id: $row.data('template-id'),
|
|
||||||
json_value: String($row.data('json-value')),
|
|
||||||
lims_value_id: $select.val(),
|
|
||||||
lims_value: selectedData.text || ''
|
|
||||||
}).then(resp => {
|
|
||||||
if (resp && resp.success) {
|
|
||||||
$status.text('Salvato').removeClass('text-muted').addClass('text-success');
|
|
||||||
$select.data('original-val', $select.val());
|
|
||||||
} else {
|
|
||||||
throw new Error((resp && resp.error) || 'Errore salvataggio');
|
|
||||||
}
|
|
||||||
}).catch(err => {
|
|
||||||
$status.text('Errore').removeClass('text-muted').addClass('text-danger');
|
|
||||||
$globalError.text(err.message || 'Errore durante il salvataggio.').show();
|
|
||||||
$btn.prop('disabled', false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.delete-binding-btn').on('click', function() {
|
|
||||||
if (!confirm('Eliminare questo binding?')) return;
|
|
||||||
const $row = $(this).closest('tr');
|
|
||||||
const $btn = $(this);
|
|
||||||
$globalError.hide();
|
|
||||||
$btn.prop('disabled', true);
|
|
||||||
|
|
||||||
$.post('delete_binding.php', {
|
|
||||||
id: $row.data('id')
|
|
||||||
}).then(resp => {
|
|
||||||
if (resp && resp.success) {
|
|
||||||
$row.fadeOut(200, () => $row.remove());
|
|
||||||
} else {
|
|
||||||
throw new Error((resp && resp.error) || 'Errore eliminazione');
|
|
||||||
}
|
|
||||||
}).catch(err => {
|
|
||||||
$globalError.text(err.message || 'Errore durante l\'eliminazione.').show();
|
|
||||||
$btn.prop('disabled', false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -257,56 +257,35 @@ class VisualLimsApiClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get raw/binary content from VisualLims API.
|
* Recupera contenuto binario - Adattato per https://bvcpsitaly-elims.com/limsapi
|
||||||
* Used for PDF downloads from MediaFile/DownloadStream.
|
|
||||||
*/
|
*/
|
||||||
public function getRaw($endpoint)
|
public function getRaw($endpoint)
|
||||||
{
|
{
|
||||||
$token = $this->getToken();
|
$token = $this->getToken();
|
||||||
|
|
||||||
/*
|
// IMPORTANTE: usa /odata/ e NON /api/odata/
|
||||||
* Normal JSON OData calls use:
|
$url = "{$this->baseUrl}/odata/{$endpoint}";
|
||||||
* {$this->baseUrl}/api/odata/...
|
|
||||||
*
|
|
||||||
* Media file downloads use:
|
|
||||||
* {$this->baseUrl}/api/MediaFile/DownloadStream...
|
|
||||||
*/
|
|
||||||
$url = rtrim($this->baseUrl, '/') . '/api/' . ltrim($endpoint, '/');
|
|
||||||
|
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
curl_setopt_array($ch, [
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
|
||||||
CURLOPT_HTTPHEADER => [
|
|
||||||
"Authorization: Bearer {$token}",
|
"Authorization: Bearer {$token}",
|
||||||
"Accept: application/pdf,*/*"
|
"Accept: */*"
|
||||||
],
|
|
||||||
CURLOPT_SSL_VERIFYPEER => false,
|
|
||||||
CURLOPT_SSL_VERIFYHOST => false,
|
|
||||||
CURLOPT_FOLLOWLOCATION => true,
|
|
||||||
CURLOPT_TIMEOUT => 60
|
|
||||||
]);
|
]);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
|
||||||
$response = curl_exec($ch);
|
$response = curl_exec($ch);
|
||||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
|
$curl_error = curl_error($ch);
|
||||||
$curlError = curl_error($ch);
|
|
||||||
|
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
|
|
||||||
if ($response === false) {
|
if ($response === false) {
|
||||||
throw new Exception("Errore cURL download raw: " . $curlError);
|
throw new Exception("Errore cURL: " . $curl_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($httpCode < 200 || $httpCode >= 300) {
|
if ($http_code !== 200) {
|
||||||
throw new Exception(
|
throw new Exception("HTTP {$http_code} su endpoint: " . $url);
|
||||||
"Errore HTTP {$httpCode} durante download raw. Content-Type: {$contentType}. Response: " .
|
|
||||||
substr($response, 0, 500)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($response)) {
|
|
||||||
throw new Exception("Risposta vuota dal download raw.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
|
|||||||
@@ -1,448 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
// Helpers for JSON -> LIMS value bindings (table json_lims_binding).
|
|
||||||
// Supports two kinds: 'custom' (template_mapping list fields, value -> import_data_details)
|
|
||||||
// and 'fixed' (template_fixed_mapping list fields, id -> datadb column).
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Fixed-field metadata
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function binding_fixed_alias_map(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'ClienteResponsabile' => 'cliente_responsabile_id',
|
|
||||||
'ClienteFornitore' => 'cliente_fornitore_id',
|
|
||||||
'ClienteAnalisi' => 'clienteAnalisi',
|
|
||||||
'MoltiplicatorePrezzo' => 'moltiplicatore_prezzo_id',
|
|
||||||
'AnagraficaCertestObject' => 'anagrafica_certest_object_id',
|
|
||||||
'AnagraficaCertestService' => 'anagrafica_certest_service_id',
|
|
||||||
'ConsegnaRichiesta' => 'consegna_richiesta',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fixed-field keys that are LIMS list dropdowns (bindable). ConsegnaRichiesta is a date.
|
|
||||||
function binding_fixed_is_list(string $key): bool
|
|
||||||
{
|
|
||||||
return in_array($key, [
|
|
||||||
'ClienteResponsabile',
|
|
||||||
'ClienteFornitore',
|
|
||||||
'ClienteAnalisi',
|
|
||||||
'MoltiplicatorePrezzo',
|
|
||||||
'AnagraficaCertestObject',
|
|
||||||
'AnagraficaCertestService',
|
|
||||||
], true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function binding_fixed_column(string $key): ?string
|
|
||||||
{
|
|
||||||
return binding_fixed_alias_map()[$key] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-match only the small global lists; the client-based ones are huge / client-specific.
|
|
||||||
function binding_fixed_auto_matchable(string $key): bool
|
|
||||||
{
|
|
||||||
return in_array($key, [
|
|
||||||
'MoltiplicatorePrezzo',
|
|
||||||
'AnagraficaCertestObject',
|
|
||||||
'AnagraficaCertestService',
|
|
||||||
], true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function binding_fixed_label(string $key): string
|
|
||||||
{
|
|
||||||
$labels = [
|
|
||||||
'AnagraficaCertestObject' => 'Anagrafica Certest Object',
|
|
||||||
'AnagraficaCertestService' => 'Anagrafica Certest Service',
|
|
||||||
'MoltiplicatorePrezzo' => 'Moltiplicatore Prezzo',
|
|
||||||
'ClienteResponsabile' => 'Cliente Responsabile',
|
|
||||||
'ClienteFornitore' => 'Cliente Fornitore',
|
|
||||||
'ClienteAnalisi' => 'Cliente Analisi',
|
|
||||||
];
|
|
||||||
return $labels[$key] ?? $key;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Bindable check (custom mapping row)
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function binding_is_list_field(array $mapping): bool
|
|
||||||
{
|
|
||||||
$hasList = (int) ($mapping['has_list'] ?? 0) === 1;
|
|
||||||
$isMultiChoice = strcasecmp(trim((string) ($mapping['data_type'] ?? '')), 'SceltaMultipla') === 0;
|
|
||||||
return $hasList || $isMultiChoice;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Target keys + lookup / upsert
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function binding_target_custom(int $mappingId): string
|
|
||||||
{
|
|
||||||
return 'cf:' . $mappingId;
|
|
||||||
}
|
|
||||||
|
|
||||||
function binding_target_fixed(int $templateId, string $fixedKey): string
|
|
||||||
{
|
|
||||||
return 'fx:' . $templateId . ':' . $fixedKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
function binding_lookup_target(PDO $pdo, string $targetKey, string $jsonValue): ?array
|
|
||||||
{
|
|
||||||
$stmt = $pdo->prepare(
|
|
||||||
"SELECT id, lims_value_id, lims_value
|
|
||||||
FROM json_lims_binding
|
|
||||||
WHERE target_key = ? AND json_value = ?
|
|
||||||
LIMIT 1"
|
|
||||||
);
|
|
||||||
$stmt->execute([$targetKey, $jsonValue]);
|
|
||||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
return $row ?: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function binding_lookup(PDO $pdo, int $mappingId, string $jsonValue): ?array
|
|
||||||
{
|
|
||||||
return binding_lookup_target($pdo, binding_target_custom($mappingId), $jsonValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
function binding_lookup_fixed(PDO $pdo, int $templateId, string $fixedKey, string $jsonValue): ?array
|
|
||||||
{
|
|
||||||
return binding_lookup_target($pdo, binding_target_fixed($templateId, $fixedKey), $jsonValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
function binding_upsert_row(
|
|
||||||
PDO $pdo,
|
|
||||||
string $kind,
|
|
||||||
int $templateId,
|
|
||||||
?int $mappingId,
|
|
||||||
?string $fixedFieldKey,
|
|
||||||
string $targetKey,
|
|
||||||
int $fieldId,
|
|
||||||
string $jsonValue,
|
|
||||||
int $limsValueId,
|
|
||||||
string $limsValue,
|
|
||||||
?int $createdBy
|
|
||||||
): void {
|
|
||||||
$stmt = $pdo->prepare(
|
|
||||||
"INSERT INTO json_lims_binding
|
|
||||||
(template_id, binding_kind, mapping_id, fixed_field_key, target_key, field_id, json_value, lims_value_id, lims_value, created_by)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
ON DUPLICATE KEY UPDATE
|
|
||||||
lims_value_id = VALUES(lims_value_id),
|
|
||||||
lims_value = VALUES(lims_value),
|
|
||||||
field_id = VALUES(field_id),
|
|
||||||
template_id = VALUES(template_id),
|
|
||||||
binding_kind = VALUES(binding_kind),
|
|
||||||
mapping_id = VALUES(mapping_id),
|
|
||||||
fixed_field_key = VALUES(fixed_field_key)"
|
|
||||||
);
|
|
||||||
$stmt->execute([
|
|
||||||
$templateId, $kind, $mappingId, $fixedFieldKey, $targetKey,
|
|
||||||
$fieldId, $jsonValue, $limsValueId, $limsValue, $createdBy,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function binding_upsert(
|
|
||||||
PDO $pdo,
|
|
||||||
int $templateId,
|
|
||||||
int $mappingId,
|
|
||||||
int $fieldId,
|
|
||||||
string $jsonValue,
|
|
||||||
int $limsValueId,
|
|
||||||
string $limsValue,
|
|
||||||
?int $createdBy
|
|
||||||
): void {
|
|
||||||
binding_upsert_row(
|
|
||||||
$pdo, 'custom', $templateId, $mappingId, null,
|
|
||||||
binding_target_custom($mappingId), $fieldId, $jsonValue, $limsValueId, $limsValue, $createdBy
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function binding_upsert_fixed(
|
|
||||||
PDO $pdo,
|
|
||||||
int $templateId,
|
|
||||||
string $fixedKey,
|
|
||||||
string $jsonValue,
|
|
||||||
int $limsValueId,
|
|
||||||
string $limsValue,
|
|
||||||
?int $createdBy
|
|
||||||
): void {
|
|
||||||
binding_upsert_row(
|
|
||||||
$pdo, 'fixed', $templateId, null, $fixedKey,
|
|
||||||
binding_target_fixed($templateId, $fixedKey), 0, $jsonValue, $limsValueId, $limsValue, $createdBy
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function binding_delete_target(PDO $pdo, string $targetKey, string $jsonValue): int
|
|
||||||
{
|
|
||||||
$stmt = $pdo->prepare("DELETE FROM json_lims_binding WHERE target_key = ? AND json_value = ?");
|
|
||||||
$stmt->execute([$targetKey, $jsonValue]);
|
|
||||||
return $stmt->rowCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Custom-field LIMS values (cache/customfield_{id}.json) + auto-match
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function binding_get_lims_values(int $fieldId): array
|
|
||||||
{
|
|
||||||
static $memo = [];
|
|
||||||
if ($fieldId <= 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
if (array_key_exists($fieldId, $memo)) {
|
|
||||||
return $memo[$fieldId];
|
|
||||||
}
|
|
||||||
|
|
||||||
$cacheDir = dirname(__DIR__) . '/cache';
|
|
||||||
$cacheFile = $cacheDir . '/customfield_' . $fieldId . '.json';
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
|
||||||
$values = json_decode(file_get_contents($cacheFile), true);
|
|
||||||
} else {
|
|
||||||
require_once __DIR__ . '/VisualLimsApiClient.class.php';
|
|
||||||
$api = VisualLimsApiClient::getInstance();
|
|
||||||
$data = $api->get("CustomField($fieldId)?\$expand=CustomFieldsValues");
|
|
||||||
$values = $data['CustomFieldsValues'] ?? [];
|
|
||||||
if (!is_dir($cacheDir)) {
|
|
||||||
mkdir($cacheDir, 0777, true);
|
|
||||||
}
|
|
||||||
file_put_contents($cacheFile, json_encode($values));
|
|
||||||
}
|
|
||||||
} catch (Throwable $e) {
|
|
||||||
error_log("binding_get_lims_values failed for field $fieldId: " . $e->getMessage());
|
|
||||||
$values = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_array($values)) {
|
|
||||||
$values = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $memo[$fieldId] = $values;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exactly one case-insensitive match by Valore -> that value, otherwise null.
|
|
||||||
function binding_auto_match(array $limsValues, string $jsonValue): ?array
|
|
||||||
{
|
|
||||||
$needle = mb_strtolower(trim($jsonValue));
|
|
||||||
if ($needle === '') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$matches = [];
|
|
||||||
foreach ($limsValues as $v) {
|
|
||||||
$valore = (string) ($v['Valore'] ?? '');
|
|
||||||
if (mb_strtolower(trim($valore)) === $needle) {
|
|
||||||
$matches[] = $v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return count($matches) === 1 ? $matches[0] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Fixed-field LIMS values (per-field source) + auto-match
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function binding_template_idclient(PDO $pdo, int $templateId): int
|
|
||||||
{
|
|
||||||
$stmt = $pdo->prepare("SELECT idclient FROM excel_templates WHERE id = ?");
|
|
||||||
$stmt->execute([$templateId]);
|
|
||||||
return (int) ($stmt->fetchColumn() ?: 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function binding_client_label(array $client): string
|
|
||||||
{
|
|
||||||
$name = trim($client['Nominativo'] ?? '');
|
|
||||||
$id = trim((string) ($client['IdCliente'] ?? ''));
|
|
||||||
$code = trim((string) ($client['CodiceCliente'] ?? ''));
|
|
||||||
|
|
||||||
$parts = explode('_', $code);
|
|
||||||
$suffix = trim($parts[1] ?? '');
|
|
||||||
if ($suffix === '' && $code !== '') {
|
|
||||||
$suffix = substr($code, 0, 1);
|
|
||||||
}
|
|
||||||
if ($suffix === '') {
|
|
||||||
$suffix = '--';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $name . ' - ' . $suffix . ' (ID: ' . $id . ')';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch an OData payload with a 1-hour file cache.
|
|
||||||
function binding_cached_get($api, string $cacheFile, string $endpoint): array
|
|
||||||
{
|
|
||||||
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
|
||||||
$data = json_decode(file_get_contents($cacheFile), true);
|
|
||||||
} else {
|
|
||||||
$data = $api->get($endpoint);
|
|
||||||
$dir = dirname($cacheFile);
|
|
||||||
if (!is_dir($dir)) {
|
|
||||||
mkdir($dir, 0777, true);
|
|
||||||
}
|
|
||||||
file_put_contents($cacheFile, json_encode($data));
|
|
||||||
}
|
|
||||||
return is_array($data) ? $data : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalized LIMS value list for a fixed field: [['id' => int, 'text' => string], ...].
|
|
||||||
function binding_get_fixed_values(PDO $pdo, string $fieldKey, int $templateId): array
|
|
||||||
{
|
|
||||||
static $memo = [];
|
|
||||||
$memoKey = $fieldKey . '|' . $templateId;
|
|
||||||
if (isset($memo[$memoKey])) {
|
|
||||||
return $memo[$memoKey];
|
|
||||||
}
|
|
||||||
|
|
||||||
$cacheDir = dirname(__DIR__) . '/cache';
|
|
||||||
$out = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
require_once __DIR__ . '/VisualLimsApiClient.class.php';
|
|
||||||
$api = VisualLimsApiClient::getInstance();
|
|
||||||
|
|
||||||
switch ($fieldKey) {
|
|
||||||
case 'MoltiplicatorePrezzo':
|
|
||||||
$data = binding_cached_get($api, "$cacheDir/moltiplicatori_prezzo.json", 'MoltiplicatorePrezzi');
|
|
||||||
foreach (($data['value'] ?? []) as $r) {
|
|
||||||
$out[] = ['id' => (int) ($r['IdMoltiplicatorePrezzo'] ?? 0), 'text' => (string) ($r['Descrizione'] ?? '')];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'AnagraficaCertestObject':
|
|
||||||
case 'AnagraficaCertestService':
|
|
||||||
$file = $fieldKey === 'AnagraficaCertestObject' ? 'anagrafica_certest_object.json' : 'anagrafica_certest_service.json';
|
|
||||||
$data = binding_cached_get($api, "$cacheDir/$file", $fieldKey);
|
|
||||||
foreach (($data['value'] ?? []) as $r) {
|
|
||||||
$code = trim((string) ($r['Codice'] ?? ''));
|
|
||||||
$text = ($code !== '' ? $code . ' - ' : '') . (string) ($r['NomeAnagrafica'] ?? '');
|
|
||||||
$out[] = ['id' => (int) ($r['IdAnagrafica'] ?? 0), 'text' => $text];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'ClienteResponsabile':
|
|
||||||
$idCliente = binding_template_idclient($pdo, $templateId);
|
|
||||||
if ($idCliente > 0) {
|
|
||||||
$data = binding_cached_get($api, "$cacheDir/cliente_responsabili_$idCliente.json", "Cliente($idCliente)?\$expand=Responsabili");
|
|
||||||
foreach (($data['Responsabili'] ?? []) as $r) {
|
|
||||||
$out[] = ['id' => (int) ($r['IdClienteResponsabile'] ?? 0), 'text' => (string) ($r['Nominativo'] ?? '')];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'ClienteFornitore':
|
|
||||||
case 'ClienteAnalisi':
|
|
||||||
$endpoint = 'Cliente?' . http_build_query(['$select' => 'IdCliente,Nominativo,CodiceCliente', '$orderby' => 'Nominativo asc']);
|
|
||||||
$data = binding_cached_get($api, "$cacheDir/clienti.json", $endpoint);
|
|
||||||
foreach (($data['value'] ?? []) as $r) {
|
|
||||||
$out[] = ['id' => (int) ($r['IdCliente'] ?? 0), 'text' => binding_client_label($r)];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (Throwable $e) {
|
|
||||||
error_log("binding_get_fixed_values($fieldKey) failed: " . $e->getMessage());
|
|
||||||
$out = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $memo[$memoKey] = $out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exactly one case-insensitive match by text on a [{id,text}] list, otherwise null.
|
|
||||||
function binding_auto_match_fixed(array $values, string $jsonValue): ?array
|
|
||||||
{
|
|
||||||
$needle = mb_strtolower(trim($jsonValue));
|
|
||||||
if ($needle === '') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
$matches = [];
|
|
||||||
foreach ($values as $v) {
|
|
||||||
if (mb_strtolower(trim((string) ($v['text'] ?? ''))) === $needle) {
|
|
||||||
$matches[] = $v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count($matches) === 1 ? $matches[0] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// JSON node -> column matching (shared with import_insert custom logic)
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function binding_find_column_index(string $sourceColumn, array $columns): int
|
|
||||||
{
|
|
||||||
$sourceColumn = trim($sourceColumn);
|
|
||||||
if ($sourceColumn === '') {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
$columnsTrimmed = array_map('trim', $columns);
|
|
||||||
$candidates = [
|
|
||||||
$sourceColumn,
|
|
||||||
preg_replace('/^data\[\]\./', '', $sourceColumn),
|
|
||||||
preg_replace('/^data\.0\./', '', $sourceColumn),
|
|
||||||
str_replace('data[].', 'data.0.', $sourceColumn),
|
|
||||||
str_replace('data.0.', 'data[].', $sourceColumn),
|
|
||||||
];
|
|
||||||
$candidates = array_values(array_unique(array_filter(array_map('trim', $candidates), function ($v) {
|
|
||||||
return $v !== '';
|
|
||||||
})));
|
|
||||||
|
|
||||||
foreach ($candidates as $c) {
|
|
||||||
$i = array_search($c, $columnsTrimmed, true);
|
|
||||||
if ($i !== false) {
|
|
||||||
return (int) $i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Apply resolved values
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Custom: write the resolved LIMS text into import_data_details for the given datadb ids.
|
|
||||||
function binding_apply_to_details(
|
|
||||||
PDO $pdo,
|
|
||||||
int $mappingId,
|
|
||||||
string $limsValue,
|
|
||||||
array $datadbIds
|
|
||||||
): int {
|
|
||||||
$datadbIds = array_values(array_filter(array_map('intval', $datadbIds)));
|
|
||||||
if (empty($datadbIds)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$placeholders = implode(',', array_fill(0, count($datadbIds), '?'));
|
|
||||||
$sql = "UPDATE import_data_details
|
|
||||||
SET field_value = ?
|
|
||||||
WHERE mapping_id = ?
|
|
||||||
AND id IN ($placeholders)";
|
|
||||||
|
|
||||||
$params = array_merge([$limsValue, $mappingId], $datadbIds);
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
$stmt->execute($params);
|
|
||||||
return $stmt->rowCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fixed: write the resolved LIMS id into a whitelisted datadb column for the given ids.
|
|
||||||
// $limsValueId null clears the column.
|
|
||||||
function binding_apply_to_datadb(
|
|
||||||
PDO $pdo,
|
|
||||||
string $column,
|
|
||||||
?int $limsValueId,
|
|
||||||
array $datadbIds
|
|
||||||
): int {
|
|
||||||
if (!in_array($column, array_values(binding_fixed_alias_map()), true)) {
|
|
||||||
throw new InvalidArgumentException("Invalid fixed-field column: $column");
|
|
||||||
}
|
|
||||||
$datadbIds = array_values(array_filter(array_map('intval', $datadbIds)));
|
|
||||||
if (empty($datadbIds)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$placeholders = implode(',', array_fill(0, count($datadbIds), '?'));
|
|
||||||
$sql = "UPDATE datadb SET `$column` = ? WHERE iddatadb IN ($placeholders)";
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
$stmt->execute(array_merge([$limsValueId], $datadbIds));
|
|
||||||
return $stmt->rowCount();
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
// Elimina un binding JSON -> LIMS. Ritorna JSON.
|
|
||||||
|
|
||||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
|
||||||
require_once __DIR__ . '/class/db-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;
|
|
||||||
}
|
|
||||||
|
|
||||||
$id = intval($_POST['id'] ?? 0);
|
|
||||||
if ($id <= 0) {
|
|
||||||
http_response_code(422);
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Missing id']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
|
||||||
$stmt = $pdo->prepare("DELETE FROM json_lims_binding WHERE id = ?");
|
|
||||||
$stmt->execute([$id]);
|
|
||||||
echo json_encode(['success' => true, 'deleted' => $stmt->rowCount()]);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
http_response_code(500);
|
|
||||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
|
||||||
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
|
|
||||||
|
|
||||||
ini_set('display_errors', '0');
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$idRapportoFile = isset($_GET['id_rapporto_file']) ? (int)$_GET['id_rapporto_file'] : 0;
|
|
||||||
|
|
||||||
if ($idRapportoFile <= 0) {
|
|
||||||
throw new Exception("Parametro id_rapporto_file mancante o non valido.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This endpoint returns the PDF binary stream.
|
|
||||||
* Do not call this with the normal get() method because get() expects JSON.
|
|
||||||
*/
|
|
||||||
$endpoint = "MediaFile/DownloadStream?objectType=RapportoFile&propertyName=FileContent&objectKey={$idRapportoFile}";
|
|
||||||
|
|
||||||
$api = VisualLimsApiClient::getInstance();
|
|
||||||
$pdfContent = $api->getRaw($endpoint);
|
|
||||||
|
|
||||||
if (empty($pdfContent)) {
|
|
||||||
throw new Exception("PDF vuoto o non ricevuto dal server.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$fileName = "rapporto_{$idRapportoFile}.pdf";
|
|
||||||
|
|
||||||
header('Content-Type: application/pdf');
|
|
||||||
header('Content-Disposition: inline; filename="' . $fileName . '"');
|
|
||||||
header('Content-Length: ' . strlen($pdfContent));
|
|
||||||
header('Cache-Control: private, max-age=0, must-revalidate');
|
|
||||||
header('Pragma: public');
|
|
||||||
|
|
||||||
echo $pdfContent;
|
|
||||||
exit;
|
|
||||||
} catch (Exception $e) {
|
|
||||||
http_response_code(500);
|
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
'success' => false,
|
|
||||||
'error' => $e->getMessage()
|
|
||||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
|
||||||
}
|
|
||||||
@@ -207,6 +207,21 @@ if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonText
|
|||||||
<input type="text" name="start_column" id="startColumn" class="form-control" value="<?php echo htmlspecialchars($template['start_column'] ?? ''); ?>">
|
<input type="text" name="start_column" id="startColumn" class="form-control" value="<?php echo htmlspecialchars($template['start_column'] ?? ''); ?>">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3" id="xlsEndColumnWrapper">
|
||||||
|
<label class="form-label">Last Column included</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="xls_end_column"
|
||||||
|
id="xlsEndColumn"
|
||||||
|
class="form-control"
|
||||||
|
value="<?php echo htmlspecialchars($template['xls_end_column'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
|
placeholder="Example: AN">
|
||||||
|
<small class="text-danger fw-semibold">
|
||||||
|
Attention: if left empty, the system will read the entire XLS/XLSX sheet.
|
||||||
|
Dirty Excel files may cause memory errors or timeout.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-3" id="xlsSheetNumberWrapper">
|
<div class="mb-3" id="xlsSheetNumberWrapper">
|
||||||
<label class="form-label">XLS Sheet Number</label>
|
<label class="form-label">XLS Sheet Number</label>
|
||||||
<input
|
<input
|
||||||
@@ -404,11 +419,13 @@ if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonText
|
|||||||
|
|
||||||
const headerRowWrapper = document.getElementById("headerRowWrapper");
|
const headerRowWrapper = document.getElementById("headerRowWrapper");
|
||||||
const startColumnWrapper = document.getElementById("startColumnWrapper");
|
const startColumnWrapper = document.getElementById("startColumnWrapper");
|
||||||
|
const xlsEndColumnWrapper = document.getElementById("xlsEndColumnWrapper");
|
||||||
const xlsSheetNumberWrapper = document.getElementById("xlsSheetNumberWrapper");
|
const xlsSheetNumberWrapper = document.getElementById("xlsSheetNumberWrapper");
|
||||||
const apiConfigWrapper = document.getElementById("apiConfigWrapper");
|
const apiConfigWrapper = document.getElementById("apiConfigWrapper");
|
||||||
|
|
||||||
const headerRow = document.getElementById("headerRow");
|
const headerRow = document.getElementById("headerRow");
|
||||||
const startColumn = document.getElementById("startColumn");
|
const startColumn = document.getElementById("startColumn");
|
||||||
|
const xlsEndColumn = document.getElementById("xlsEndColumn");
|
||||||
const xlsSheetIndex = document.getElementById("xlsSheetIndex");
|
const xlsSheetIndex = document.getElementById("xlsSheetIndex");
|
||||||
const apiConfigSelect = document.getElementById("apiConfigSelect");
|
const apiConfigSelect = document.getElementById("apiConfigSelect");
|
||||||
|
|
||||||
@@ -444,13 +461,16 @@ if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonText
|
|||||||
if (isXls) {
|
if (isXls) {
|
||||||
headerRowWrapper.style.display = 'block';
|
headerRowWrapper.style.display = 'block';
|
||||||
startColumnWrapper.style.display = 'block';
|
startColumnWrapper.style.display = 'block';
|
||||||
|
xlsEndColumnWrapper.style.display = 'block';
|
||||||
xlsSheetNumberWrapper.style.display = 'block';
|
xlsSheetNumberWrapper.style.display = 'block';
|
||||||
|
|
||||||
headerRow.required = true;
|
headerRow.required = true;
|
||||||
startColumn.required = true;
|
startColumn.required = true;
|
||||||
|
xlsEndColumn.required = false;
|
||||||
|
|
||||||
headerRow.disabled = false;
|
headerRow.disabled = false;
|
||||||
startColumn.disabled = false;
|
startColumn.disabled = false;
|
||||||
|
xlsEndColumn.disabled = false;
|
||||||
xlsSheetIndex.disabled = false;
|
xlsSheetIndex.disabled = false;
|
||||||
|
|
||||||
apiConfigWrapper.style.display = 'none';
|
apiConfigWrapper.style.display = 'none';
|
||||||
@@ -460,13 +480,16 @@ if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonText
|
|||||||
} else {
|
} else {
|
||||||
headerRowWrapper.style.display = 'none';
|
headerRowWrapper.style.display = 'none';
|
||||||
startColumnWrapper.style.display = 'none';
|
startColumnWrapper.style.display = 'none';
|
||||||
|
xlsEndColumnWrapper.style.display = 'none';
|
||||||
xlsSheetNumberWrapper.style.display = 'none';
|
xlsSheetNumberWrapper.style.display = 'none';
|
||||||
|
|
||||||
headerRow.required = false;
|
headerRow.required = false;
|
||||||
startColumn.required = false;
|
startColumn.required = false;
|
||||||
|
xlsEndColumn.required = false;
|
||||||
|
|
||||||
headerRow.disabled = true;
|
headerRow.disabled = true;
|
||||||
startColumn.disabled = true;
|
startColumn.disabled = true;
|
||||||
|
xlsEndColumn.disabled = true;
|
||||||
xlsSheetIndex.disabled = true;
|
xlsSheetIndex.disabled = true;
|
||||||
|
|
||||||
if (isApiOrJson) {
|
if (isApiOrJson) {
|
||||||
@@ -726,6 +749,22 @@ if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonText
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selectedSource === 'XLS' && xlsEndColumn.value.trim() !== '') {
|
||||||
|
const lastColumnValue = xlsEndColumn.value.trim().toUpperCase();
|
||||||
|
|
||||||
|
if (!/^[A-Z]+$|^[1-9][0-9]*$/.test(lastColumnValue)) {
|
||||||
|
Swal.fire({
|
||||||
|
title: "Error!",
|
||||||
|
text: "Last Column must be an Excel column like AN or a positive number like 40.",
|
||||||
|
icon: "error",
|
||||||
|
confirmButtonText: "OK"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
xlsEndColumn.value = lastColumnValue;
|
||||||
|
}
|
||||||
|
|
||||||
fetch("process_edit_template_xls.php", {
|
fetch("process_edit_template_xls.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData
|
body: formData
|
||||||
|
|||||||
@@ -331,3 +331,35 @@
|
|||||||
2026-04-30 15:01:25 - Errore nel recupero dati: HTTP 404, Risposta: {"title":"Not Found","status":404,"detail":"Not Found","instance":"GET /api/odata/Rapporto(2621521)","errorCode":"d25cbd678"}
|
2026-04-30 15:01:25 - Errore nel recupero dati: HTTP 404, Risposta: {"title":"Not Found","status":404,"detail":"Not Found","instance":"GET /api/odata/Rapporto(2621521)","errorCode":"d25cbd678"}
|
||||||
2026-04-30 15:02:04 - Errore nel recupero dati: HTTP 404, Risposta:
|
2026-04-30 15:02:04 - Errore nel recupero dati: HTTP 404, Risposta:
|
||||||
2026-04-30 15:03:19 - Errore nella richiesta: URL rejected: Malformed input to a URL function
|
2026-04-30 15:03:19 - Errore nella richiesta: URL rejected: Malformed input to a URL function
|
||||||
|
2026-06-16 14:29:15 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21037 ms: Couldn't connect to server, Risposta:
|
||||||
|
|
||||||
|
|
||||||
|
2026-06-16 14:29:36 [ClienteResponsabile] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21099 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:30:03 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21076 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:30:03 [AnagraficaCertestObject] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21083 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:30:03 [AnagraficaCertestService] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21083 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:30:34 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21043 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:30:55 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21053 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:30:55 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21050 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:31:46 [AnagraficaCertestObject] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21048 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:31:46 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21043 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:31:46 [AnagraficaCertestService] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21042 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:32:07 [ClienteResponsabile] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21047 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:33:09 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21058 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:33:09 [AnagraficaCertestObject] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21057 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:33:09 [AnagraficaCertestService] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21057 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:33:30 [ClienteResponsabile] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21047 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:36:11 [AnagraficaCertestService] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21062 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:36:11 [AnagraficaCertestObject] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21064 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:36:11 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21065 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:36:33 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21049 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:36:33 [AnagraficaCertestService] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21057 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:36:33 [AnagraficaCertestObject] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21053 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:37:13 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21032 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:37:14 [AnagraficaCertestService] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21031 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:37:14 [AnagraficaCertestObject] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21027 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:37:35 [ClienteResponsabile] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21054 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 17:13:55 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21067 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 17:14:25 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21065 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 17:14:46 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21071 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 17:14:46 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21078 ms: Couldn't connect to server, Risposta:
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once 'include/headscript.php';
|
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
ini_set('display_errors', '0');
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
||||||
http_response_code(405);
|
|
||||||
echo json_encode(['error' => 'Invalid request method']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$input = json_decode(file_get_contents('php://input'), true);
|
|
||||||
$template_id = isset($input['template_id']) ? (int)$input['template_id'] : 0;
|
|
||||||
$code = isset($input['code']) ? trim($input['code']) : '';
|
|
||||||
|
|
||||||
if ($template_id <= 0) {
|
|
||||||
http_response_code(400);
|
|
||||||
echo json_encode(['error' => 'Missing or invalid template_id']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($code === '') {
|
|
||||||
http_response_code(400);
|
|
||||||
echo json_encode(['error' => 'Missing code']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: Replace this block with your real API call.
|
|
||||||
* Expected response for import_json.php:
|
|
||||||
* {
|
|
||||||
* "success": true,
|
|
||||||
* "reference": "CODE123",
|
|
||||||
* "json": { ... real JSON payload ... }
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
|
|
||||||
$sampleJson = [
|
|
||||||
'data' => [[
|
|
||||||
'type' => 'trf_data_request',
|
|
||||||
'id' => $code,
|
|
||||||
'attributes' => [
|
|
||||||
'trf_type' => 'apparel',
|
|
||||||
'service_required' => 'regular',
|
|
||||||
'submitter_information' => [
|
|
||||||
'submitter_type' => 'supplier'
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]]
|
|
||||||
];
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
'success' => true,
|
|
||||||
'reference' => $code,
|
|
||||||
'json' => $sampleJson
|
|
||||||
]);
|
|
||||||
} catch (Throwable $e) {
|
|
||||||
http_response_code(500);
|
|
||||||
echo json_encode(['error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
<?php
|
|
||||||
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();
|
|
||||||
|
|
||||||
$idCliente = isset($_GET['id_cliente']) ? (int)$_GET['id_cliente'] : 0;
|
|
||||||
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 3;
|
|
||||||
$signedStatus = trim($_GET['signed_status'] ?? 'all');
|
|
||||||
|
|
||||||
if ($idCliente <= 0) {
|
|
||||||
throw new Exception("Parametro id_cliente mancante o non valido.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Allowed limits only.
|
|
||||||
* This prevents risky wide queries on the live LIMS.
|
|
||||||
*/
|
|
||||||
$allowedLimits = [1, 3, 5, 10];
|
|
||||||
|
|
||||||
if (!in_array($limit, $allowedLimits, true)) {
|
|
||||||
$limit = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Allowed signature filters.
|
|
||||||
*/
|
|
||||||
$allowedSignedStatuses = ['all', 'signed', 'not_signed'];
|
|
||||||
|
|
||||||
if (!in_array($signedStatus, $allowedSignedStatuses, true)) {
|
|
||||||
$signedStatus = 'all';
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Base filter by customer.
|
|
||||||
* We already verified that Rapporto can expand Cliente and returns Cliente.IdCliente.
|
|
||||||
*/
|
|
||||||
$filters = [
|
|
||||||
"Cliente/IdCliente eq {$idCliente}"
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($signedStatus === 'signed') {
|
|
||||||
$filters[] = "Firmato eq true";
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($signedStatus === 'not_signed') {
|
|
||||||
$filters[] = "Firmato eq false";
|
|
||||||
}
|
|
||||||
|
|
||||||
$filter = implode(' and ', $filters);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Important:
|
|
||||||
* - $top limits the number of reports.
|
|
||||||
* - $orderby=Data desc gets the latest reports first.
|
|
||||||
* - $expand=RapportiFiles retrieves only the PDF file metadata, not the binary PDF.
|
|
||||||
*/
|
|
||||||
$params = [
|
|
||||||
'$filter' => $filter,
|
|
||||||
'$select' => 'IdRapporto,CodiceRapporto,Data,Versione,Firmato,DataStampa',
|
|
||||||
'$expand' => 'RapportiFiles',
|
|
||||||
'$orderby' => 'Data desc',
|
|
||||||
'$top' => $limit
|
|
||||||
];
|
|
||||||
|
|
||||||
$endpoint = "Rapporto?" . http_build_query($params);
|
|
||||||
|
|
||||||
file_put_contents(
|
|
||||||
__DIR__ . '/last_rapporti_cliente_endpoint.txt',
|
|
||||||
'[' . date('Y-m-d H:i:s') . '] ' . $endpoint . PHP_EOL,
|
|
||||||
FILE_APPEND
|
|
||||||
);
|
|
||||||
|
|
||||||
$data = $api->get($endpoint);
|
|
||||||
|
|
||||||
$items = $data['value'] ?? [];
|
|
||||||
|
|
||||||
if (!is_array($items)) {
|
|
||||||
$items = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$reports = [];
|
|
||||||
|
|
||||||
foreach ($items as $item) {
|
|
||||||
$rapportiFiles = $item['RapportiFiles'] ?? [];
|
|
||||||
$pdfFiles = [];
|
|
||||||
|
|
||||||
if (is_array($rapportiFiles)) {
|
|
||||||
foreach ($rapportiFiles as $file) {
|
|
||||||
$idRapportoFile = intval($file['IdRapportoFile'] ?? 0);
|
|
||||||
|
|
||||||
if ($idRapportoFile > 0) {
|
|
||||||
$pdfFiles[] = [
|
|
||||||
'id_rapporto_file' => $idRapportoFile,
|
|
||||||
'file_name' => $file['FileName'] ?? null,
|
|
||||||
'categoria' => $file['Categoria'] ?? null,
|
|
||||||
'tipo_rapporto' => $file['TipoRapporto'] ?? null,
|
|
||||||
'download_url' => "download_rapporto_pdf.php?id_rapporto_file={$idRapportoFile}"
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$reports[] = [
|
|
||||||
'id_rapporto' => $item['IdRapporto'] ?? null,
|
|
||||||
'codice_rapporto' => $item['CodiceRapporto'] ?? null,
|
|
||||||
'data' => $item['Data'] ?? null,
|
|
||||||
'data_stampa' => $item['DataStampa'] ?? null,
|
|
||||||
'versione' => $item['Versione'] ?? null,
|
|
||||||
'firmato' => $item['Firmato'] ?? null,
|
|
||||||
'pdf_files' => $pdfFiles
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
'success' => true,
|
|
||||||
'id_cliente' => $idCliente,
|
|
||||||
'limit' => $limit,
|
|
||||||
'signed_status' => $signedStatus,
|
|
||||||
'endpoint' => $endpoint,
|
|
||||||
'count' => count($reports),
|
|
||||||
'reports' => $reports
|
|
||||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
file_put_contents(
|
|
||||||
__DIR__ . '/error_log.txt',
|
|
||||||
date('Y-m-d H:i:s') . ' - get_rapporti_cliente.php - ' . $e->getMessage() . PHP_EOL,
|
|
||||||
FILE_APPEND
|
|
||||||
);
|
|
||||||
|
|
||||||
http_response_code(500);
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
'success' => false,
|
|
||||||
'error' => $e->getMessage()
|
|
||||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
|
||||||
}
|
|
||||||
@@ -17,7 +17,6 @@ try {
|
|||||||
// rapporto_by_codice_expand_step.php?codice=2541111&step=files_campioni
|
// rapporto_by_codice_expand_step.php?codice=2541111&step=files_campioni
|
||||||
|
|
||||||
$codiceRapporto = trim($_GET['codice'] ?? '');
|
$codiceRapporto = trim($_GET['codice'] ?? '');
|
||||||
// Safe step mode: default is base, but allows controlled read-only steps
|
|
||||||
$step = trim($_GET['step'] ?? 'base');
|
$step = trim($_GET['step'] ?? 'base');
|
||||||
|
|
||||||
if ($codiceRapporto === '') {
|
if ($codiceRapporto === '') {
|
||||||
@@ -26,9 +25,10 @@ try {
|
|||||||
|
|
||||||
$allowedSteps = [
|
$allowedSteps = [
|
||||||
'base' => '',
|
'base' => '',
|
||||||
|
'files' => 'RapportiFiles',
|
||||||
|
'allegati' => 'RapportiAllegati',
|
||||||
'campioni' => 'CampioniDatiRapporto',
|
'campioni' => 'CampioniDatiRapporto',
|
||||||
'files' => 'RapportiFiles,Cliente',
|
'files_campioni' => 'RapportiFiles,CampioniDatiRapporto'
|
||||||
'cliente' => 'Cliente'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!array_key_exists($step, $allowedSteps)) {
|
if (!array_key_exists($step, $allowedSteps)) {
|
||||||
@@ -37,8 +37,7 @@ try {
|
|||||||
|
|
||||||
// Escape OData per eventuali apostrofi
|
// Escape OData per eventuali apostrofi
|
||||||
$codiceRapportoSafe = str_replace("'", "''", $codiceRapporto);
|
$codiceRapportoSafe = str_replace("'", "''", $codiceRapporto);
|
||||||
// Safe version of codice rapporto for filenames
|
|
||||||
$codiceRapportoFileSafe = preg_replace('/[^a-zA-Z0-9_-]/', '_', $codiceRapporto);
|
|
||||||
/*
|
/*
|
||||||
* STEP 1 - Trova IdRapporto partendo da CodiceRapporto.
|
* STEP 1 - Trova IdRapporto partendo da CodiceRapporto.
|
||||||
* Query leggera, con $select e $top=1.
|
* Query leggera, con $select e $top=1.
|
||||||
@@ -108,43 +107,15 @@ try {
|
|||||||
|
|
||||||
$detailData = $api->get($detailEndpoint);
|
$detailData = $api->get($detailEndpoint);
|
||||||
|
|
||||||
$pdfFiles = [];
|
|
||||||
|
|
||||||
if ($step === 'files') {
|
|
||||||
$rapportiFiles = $detailData['RapportiFiles'] ?? [];
|
|
||||||
|
|
||||||
if (is_array($rapportiFiles)) {
|
|
||||||
foreach ($rapportiFiles as $file) {
|
|
||||||
$idRapportoFile = intval($file['IdRapportoFile'] ?? 0);
|
|
||||||
|
|
||||||
if ($idRapportoFile > 0) {
|
|
||||||
$pdfFiles[] = [
|
|
||||||
'id_rapporto_file' => $idRapportoFile,
|
|
||||||
'file_name' => $file['FileName'] ?? null,
|
|
||||||
'categoria' => $file['Categoria'] ?? null,
|
|
||||||
'tipo_rapporto' => $file['TipoRapporto'] ?? null,
|
|
||||||
'download_endpoint' => "MediaFile/DownloadStream?objectType=RapportoFile&propertyName=FileContent&objectKey={$idRapportoFile}"
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$clienteData = null;
|
|
||||||
|
|
||||||
if ($step === 'cliente' || $step === 'files') {
|
|
||||||
$clienteData = $detailData['Cliente'] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
file_put_contents(
|
file_put_contents(
|
||||||
__DIR__ . "/rapporto_codice_{$codiceRapportoFileSafe}_{$step}.json",
|
__DIR__ . "/rapporto_codice_{$codiceRapportoSafe}_{$step}.json",
|
||||||
json_encode([
|
json_encode([
|
||||||
'search' => $searchData,
|
'search' => $searchData,
|
||||||
'detail' => $detailData
|
'detail' => $detailData
|
||||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
|
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
|
||||||
);
|
);
|
||||||
|
|
||||||
$response = [
|
echo json_encode([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'codice_rapporto' => $codiceRapporto,
|
'codice_rapporto' => $codiceRapporto,
|
||||||
'id_rapporto' => $rapportoId,
|
'id_rapporto' => $rapportoId,
|
||||||
@@ -153,17 +124,7 @@ try {
|
|||||||
'detail_endpoint' => $detailEndpoint,
|
'detail_endpoint' => $detailEndpoint,
|
||||||
'rapporto_base' => $rapportoBase,
|
'rapporto_base' => $rapportoBase,
|
||||||
'data' => $detailData
|
'data' => $detailData
|
||||||
];
|
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
if ($step === 'files') {
|
|
||||||
$response['pdf_files'] = $pdfFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($step === 'cliente' || $step === 'files') {
|
|
||||||
$response['cliente'] = $clienteData;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
file_put_contents(
|
file_put_contents(
|
||||||
__DIR__ . '/error_log.txt',
|
__DIR__ . '/error_log.txt',
|
||||||
|
|||||||
@@ -237,18 +237,7 @@
|
|||||||
const iconClass = getTemplateIcon(sourceType);
|
const iconClass = getTemplateIcon(sourceType);
|
||||||
|
|
||||||
const btn = document.createElement("a");
|
const btn = document.createElement("a");
|
||||||
|
|
||||||
// Redirect based on template source type
|
|
||||||
if (sourceType === 'XLS') {
|
|
||||||
btn.href = `import_xls2.php?id=${template.id}`;
|
btn.href = `import_xls2.php?id=${template.id}`;
|
||||||
} else if (sourceType === 'API' || sourceType === 'JSON') {
|
|
||||||
btn.href = `import_json.php?id=${template.id}`;
|
|
||||||
} else if (sourceType === 'PDF') {
|
|
||||||
btn.href = `import_pdf.php?id=${template.id}`;
|
|
||||||
} else {
|
|
||||||
btn.href = `import_xls2.php?id=${template.id}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
btn.className = `btn ${sizeClass}`;
|
btn.className = `btn ${sizeClass}`;
|
||||||
btn.style.backgroundColor = template.button_bg_color || '#0d6efd';
|
btn.style.backgroundColor = template.button_bg_color || '#0d6efd';
|
||||||
btn.style.color = template.button_text_color || '#ffffff';
|
btn.style.color = template.button_text_color || '#ffffff';
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ 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"));
|
||||||
@@ -26,7 +25,6 @@ $rows = json_decode(urldecode($_POST['rows'] ?? '[]'), true);
|
|||||||
$excelrows = json_decode(urldecode($_POST['excelrows'] ?? '[]'), true);
|
$excelrows = json_decode(urldecode($_POST['excelrows'] ?? '[]'), true);
|
||||||
|
|
||||||
$newFilename = $_POST['filename'];
|
$newFilename = $_POST['filename'];
|
||||||
$source_type = strtolower(trim($_POST['source_type'] ?? 'xls'));
|
|
||||||
|
|
||||||
$_SESSION['template_id'] = $template_id;
|
$_SESSION['template_id'] = $template_id;
|
||||||
$_SESSION['selected_rows'] = $selected_rows;
|
$_SESSION['selected_rows'] = $selected_rows;
|
||||||
@@ -39,7 +37,6 @@ error_log("Received Data - Template ID: $template_id, Selected Rows: " . json_en
|
|||||||
error_log("Columns: " . json_encode($columns));
|
error_log("Columns: " . json_encode($columns));
|
||||||
error_log("Rows: " . json_encode($rows));
|
error_log("Rows: " . json_encode($rows));
|
||||||
error_log("Excelrows: " . json_encode($excelrows));
|
error_log("Excelrows: " . json_encode($excelrows));
|
||||||
error_log("Source type: " . $source_type);
|
|
||||||
|
|
||||||
$user_id = $iduserlogin ?? 1;
|
$user_id = $iduserlogin ?? 1;
|
||||||
|
|
||||||
@@ -50,23 +47,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("
|
$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 = ?");
|
||||||
SELECT
|
|
||||||
id,
|
|
||||||
excel_column,
|
|
||||||
json_node,
|
|
||||||
data_type,
|
|
||||||
is_required,
|
|
||||||
manual_default,
|
|
||||||
is_manual,
|
|
||||||
field_label,
|
|
||||||
field_id,
|
|
||||||
main_field,
|
|
||||||
auto_value,
|
|
||||||
has_list
|
|
||||||
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);
|
||||||
|
|
||||||
@@ -84,41 +65,14 @@ foreach ($allMappings as $mapping) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Campi fixed mappati da JSON (default_source='json') per questo template.
|
|
||||||
$fixedJsonFields = [];
|
|
||||||
if ($source_type === 'json') {
|
|
||||||
$fxStmt = $pdo->prepare("
|
|
||||||
SELECT fixed_field_key, json_node, data_type
|
|
||||||
FROM template_fixed_mapping
|
|
||||||
WHERE template_id = ? AND default_source = 'json' AND json_node IS NOT NULL AND json_node <> ''
|
|
||||||
");
|
|
||||||
$fxStmt->execute([$template_id]);
|
|
||||||
$fixedJsonFields = $fxStmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inserisci le righe selezionate in datadb
|
// Inserisci le righe selezionate in datadb
|
||||||
$insertedIds = [];
|
$insertedIds = [];
|
||||||
|
foreach ($selected_rows as $rowIndex) {
|
||||||
// Binding JSON -> LIMS senza corrispondenza salvata, per "kind:key|json_value".
|
|
||||||
$pendingBindings = [];
|
|
||||||
// Binding risolti in automatico durante questo import (solo per visualizzazione).
|
|
||||||
$autoBindings = [];
|
|
||||||
// Binding gia' salvati in precedenza, usati su questo import (visualizzazione + modifica).
|
|
||||||
$savedBindings = [];
|
|
||||||
foreach ($selected_rows as $loopIndex => $rowIndex) {
|
|
||||||
|
|
||||||
if ($source_type === 'json') {
|
|
||||||
// JSON import sends only selected rows in rows/excelrows
|
|
||||||
$row = $rows[$loopIndex] ?? null;
|
|
||||||
$excelrow = $excelrows[$loopIndex] ?? ('JSON-' . ($loopIndex + 1));
|
|
||||||
} else {
|
|
||||||
// XLS import keeps original row indexes
|
|
||||||
$row = $rows[$rowIndex] ?? null;
|
$row = $rows[$rowIndex] ?? null;
|
||||||
$excelrow = $excelrows[$rowIndex] ?? null;
|
$excelrow = $excelrows[$rowIndex] ?? null;
|
||||||
}
|
|
||||||
|
|
||||||
if ($row === null || $excelrow === null) {
|
if ($row === null || $excelrow === null) {
|
||||||
error_log("Errore: riga o excelrow mancante. Source type: $source_type, loopIndex: $loopIndex, rowIndex: $rowIndex");
|
error_log("Errore: riga o excelrow mancante per rowIndex $rowIndex");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,14 +82,6 @@ foreach ($selected_rows as $loopIndex => $rowIndex) {
|
|||||||
$template = $template_stmt->fetch(PDO::FETCH_ASSOC);
|
$template = $template_stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
$default_idclient = $template['idclient'] ?? null;
|
$default_idclient = $template['idclient'] ?? null;
|
||||||
|
|
||||||
// excelrow e' INT: dal JSON arriva tipo 'JSON-1', tengo solo la parte numerica.
|
|
||||||
if (is_numeric($excelrow)) {
|
|
||||||
$excelrowDb = (int) $excelrow;
|
|
||||||
} else {
|
|
||||||
$digits = preg_replace('/\D+/', '', (string) $excelrow);
|
|
||||||
$excelrowDb = $digits !== '' ? (int) $digits : ($loopIndex + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$values = [
|
$values = [
|
||||||
$template_id,
|
$template_id,
|
||||||
$importReferenceCode,
|
$importReferenceCode,
|
||||||
@@ -144,7 +90,7 @@ foreach ($selected_rows as $loopIndex => $rowIndex) {
|
|||||||
$user_id,
|
$user_id,
|
||||||
null,
|
null,
|
||||||
date('Y-m-d'),
|
date('Y-m-d'),
|
||||||
$excelrowDb,
|
$excelrow,
|
||||||
$default_idclient // Aggiungi idclient
|
$default_idclient // Aggiungi idclient
|
||||||
];
|
];
|
||||||
$sql = "INSERT INTO datadb (templateid, importreferencecode, filename_import, status, user_id, limscode, importdate, excelrow, idclient) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
$sql = "INSERT INTO datadb (templateid, importreferencecode, filename_import, status, user_id, limscode, importdate, excelrow, idclient) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||||
@@ -158,72 +104,14 @@ foreach ($selected_rows as $loopIndex => $rowIndex) {
|
|||||||
foreach ($allMappings as $mapping) {
|
foreach ($allMappings as $mapping) {
|
||||||
$fieldValue = null;
|
$fieldValue = null;
|
||||||
if (!$mapping['is_manual']) {
|
if (!$mapping['is_manual']) {
|
||||||
$sourceColumn = '';
|
$excelColumn = trim($mapping['excel_column']);
|
||||||
|
$excelColumnIndex = array_search($excelColumn, array_map('trim', $columns));
|
||||||
if ($source_type === 'json') {
|
if ($excelColumnIndex !== false && isset($row[$excelColumnIndex]) && $row[$excelColumnIndex] !== '') {
|
||||||
$sourceColumn = trim($mapping['json_node'] ?? '');
|
$fieldValue = $row[$excelColumnIndex];
|
||||||
} else {
|
error_log("Found Excel column '$excelColumn' at index $excelColumnIndex, value: " . var_export($fieldValue, true));
|
||||||
$sourceColumn = trim($mapping['excel_column'] ?? '');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: if JSON node is empty, try excel_column
|
|
||||||
if ($sourceColumn === '') {
|
|
||||||
$sourceColumn = trim($mapping['excel_column'] ?? '');
|
|
||||||
}
|
|
||||||
|
|
||||||
$columnsTrimmed = array_map('trim', $columns);
|
|
||||||
|
|
||||||
$candidateColumns = [];
|
|
||||||
|
|
||||||
if ($sourceColumn !== '') {
|
|
||||||
$candidateColumns[] = $sourceColumn;
|
|
||||||
|
|
||||||
if ($source_type === 'json') {
|
|
||||||
// Common JSON path variants
|
|
||||||
$candidateColumns[] = preg_replace('/^data\[\]\./', '', $sourceColumn);
|
|
||||||
$candidateColumns[] = preg_replace('/^data\.0\./', '', $sourceColumn);
|
|
||||||
$candidateColumns[] = str_replace('data[].', 'data.0.', $sourceColumn);
|
|
||||||
$candidateColumns[] = str_replace('data.0.', 'data[].', $sourceColumn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove empty and duplicate candidates
|
|
||||||
$candidateColumns = array_values(array_unique(array_filter($candidateColumns, function ($value) {
|
|
||||||
return trim((string)$value) !== '';
|
|
||||||
})));
|
|
||||||
|
|
||||||
$sourceColumnIndex = false;
|
|
||||||
$matchedColumn = '';
|
|
||||||
|
|
||||||
foreach ($candidateColumns as $candidateColumn) {
|
|
||||||
$candidateColumn = trim($candidateColumn);
|
|
||||||
$index = array_search($candidateColumn, $columnsTrimmed);
|
|
||||||
|
|
||||||
if ($index !== false) {
|
|
||||||
$sourceColumnIndex = $index;
|
|
||||||
$matchedColumn = $candidateColumn;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($sourceColumnIndex !== false && isset($row[$sourceColumnIndex]) && $row[$sourceColumnIndex] !== '') {
|
|
||||||
$fieldValue = $row[$sourceColumnIndex];
|
|
||||||
|
|
||||||
error_log(
|
|
||||||
"Found source column. Original: '$sourceColumn', Matched: '$matchedColumn', Index: $sourceColumnIndex, Value: " .
|
|
||||||
var_export($fieldValue, true)
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
$fieldValue = $mapping['manual_default'] ?? '';
|
$fieldValue = $mapping['manual_default'] ?? '';
|
||||||
|
error_log("Excel column '$excelColumn' not found or empty, using default: " . var_export($fieldValue, true));
|
||||||
error_log(
|
|
||||||
"Source column not found or empty. Original: '$sourceColumn'. Candidates: " .
|
|
||||||
json_encode($candidateColumns) .
|
|
||||||
". Available columns: " .
|
|
||||||
json_encode($columnsTrimmed) .
|
|
||||||
". Using default: " .
|
|
||||||
var_export($fieldValue, true)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
switch ($mapping['data_type']) {
|
switch ($mapping['data_type']) {
|
||||||
case 'INT':
|
case 'INT':
|
||||||
@@ -256,215 +144,18 @@ foreach ($selected_rows as $loopIndex => $rowIndex) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Binding JSON -> LIMS solo per i campi a lista importati da JSON.
|
|
||||||
if (
|
|
||||||
$source_type === 'json'
|
|
||||||
&& !$mapping['is_manual']
|
|
||||||
&& binding_is_list_field($mapping)
|
|
||||||
&& $fieldValue !== null
|
|
||||||
&& $fieldValue !== ''
|
|
||||||
) {
|
|
||||||
$jsonValue = (string) $fieldValue;
|
|
||||||
$existing = binding_lookup($pdo, (int) $mapping['id'], $jsonValue);
|
|
||||||
|
|
||||||
if ($existing) {
|
|
||||||
$fieldValue = $existing['lims_value'];
|
|
||||||
|
|
||||||
$key = 'cf:' . $mapping['id'] . '|' . $jsonValue;
|
|
||||||
if (!isset($savedBindings[$key])) {
|
|
||||||
$savedBindings[$key] = [
|
|
||||||
'kind' => 'custom',
|
|
||||||
'mapping_id' => (int) $mapping['id'],
|
|
||||||
'field_id' => (int) $mapping['field_id'],
|
|
||||||
'field_label' => $mapping['field_label'],
|
|
||||||
'json_value' => $jsonValue,
|
|
||||||
'lims_value' => (string) $existing['lims_value'],
|
|
||||||
'lims_value_id' => (int) $existing['lims_value_id'],
|
|
||||||
'datadb_ids' => [],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
$savedBindings[$key]['datadb_ids'][] = (int) $iddatadb;
|
|
||||||
} else {
|
|
||||||
// Nessun binding salvato: provo l'auto-match 1-a-1 sui valori LIMS.
|
|
||||||
$limsValues = binding_get_lims_values((int) $mapping['field_id']);
|
|
||||||
$autoMatch = binding_auto_match($limsValues, $jsonValue);
|
|
||||||
|
|
||||||
if ($autoMatch) {
|
|
||||||
binding_upsert(
|
|
||||||
$pdo,
|
|
||||||
(int) $template_id,
|
|
||||||
(int) $mapping['id'],
|
|
||||||
(int) $mapping['field_id'],
|
|
||||||
$jsonValue,
|
|
||||||
(int) $autoMatch['IdCustomFieldsValue'],
|
|
||||||
(string) $autoMatch['Valore'],
|
|
||||||
$user_id
|
|
||||||
);
|
|
||||||
$fieldValue = (string) $autoMatch['Valore'];
|
|
||||||
|
|
||||||
$key = 'cf:' . $mapping['id'] . '|' . $jsonValue;
|
|
||||||
if (!isset($autoBindings[$key])) {
|
|
||||||
$autoBindings[$key] = [
|
|
||||||
'kind' => 'custom',
|
|
||||||
'mapping_id' => (int) $mapping['id'],
|
|
||||||
'field_id' => (int) $mapping['field_id'],
|
|
||||||
'field_label' => $mapping['field_label'],
|
|
||||||
'json_value' => $jsonValue,
|
|
||||||
'lims_value' => (string) $autoMatch['Valore'],
|
|
||||||
'lims_value_id' => (int) $autoMatch['IdCustomFieldsValue'],
|
|
||||||
'datadb_ids' => [],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
$autoBindings[$key]['datadb_ids'][] = (int) $iddatadb;
|
|
||||||
} else {
|
|
||||||
$key = 'cf:' . $mapping['id'] . '|' . $jsonValue;
|
|
||||||
if (!isset($pendingBindings[$key])) {
|
|
||||||
$pendingBindings[$key] = [
|
|
||||||
'kind' => 'custom',
|
|
||||||
'mapping_id' => (int) $mapping['id'],
|
|
||||||
'field_id' => (int) $mapping['field_id'],
|
|
||||||
'field_label' => $mapping['field_label'],
|
|
||||||
'json_value' => $jsonValue,
|
|
||||||
'datadb_ids' => [],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
$pendingBindings[$key]['datadb_ids'][] = (int) $iddatadb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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']);
|
||||||
}
|
}
|
||||||
error_log("Inserting into import_data_details - Mapping ID: " . $mapping['id'] . ", Field Value: " . var_export($fieldValue, true) . ", Is Manual: " . $mapping['is_manual'] . ", Source Column: " . ($sourceColumn ?? 'N/A') . ", Source Type: " . $source_type . ", Manual Default: " . ($mapping['manual_default'] ?? 'N/A'));
|
error_log("Inserting into import_data_details - Mapping ID: " . $mapping['id'] . ", Field Value: " . var_export($fieldValue, true) . ", Is Manual: " . $mapping['is_manual'] . ", Excel Column: " . ($mapping['excel_column'] ?? 'N/A') . ", Manual Default: " . ($mapping['manual_default'] ?? 'N/A'));
|
||||||
$stmt = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, ?)");
|
$stmt = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, ?)");
|
||||||
$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));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Fixed fields mappati da JSON (scrivono colonne datadb) ----
|
|
||||||
if ($source_type === 'json' && !empty($fixedJsonFields)) {
|
|
||||||
$fixedUpdates = []; // colonna datadb => valore (id LIMS o data)
|
|
||||||
|
|
||||||
foreach ($fixedJsonFields as $fx) {
|
|
||||||
$fixedKey = $fx['fixed_field_key'];
|
|
||||||
$column = binding_fixed_column($fixedKey);
|
|
||||||
if (!$column) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$idx = binding_find_column_index((string) $fx['json_node'], $columns);
|
|
||||||
$raw = ($idx >= 0 && isset($row[$idx])) ? trim((string) $row[$idx]) : '';
|
|
||||||
if ($raw === '') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Campo non a lista (es. ConsegnaRichiesta DATE): scrivo il valore direttamente.
|
|
||||||
if (!binding_fixed_is_list($fixedKey)) {
|
|
||||||
if (($fx['data_type'] ?? '') === 'DATE') {
|
|
||||||
$fixedUpdates[$column] = date('Y-m-d', strtotime($raw));
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Binding gia' salvato.
|
|
||||||
$existing = binding_lookup_fixed($pdo, (int) $template_id, $fixedKey, $raw);
|
|
||||||
if ($existing) {
|
|
||||||
$fixedUpdates[$column] = (int) $existing['lims_value_id'];
|
|
||||||
$key = 'fx:' . $fixedKey . '|' . $raw;
|
|
||||||
if (!isset($savedBindings[$key])) {
|
|
||||||
$savedBindings[$key] = [
|
|
||||||
'kind' => 'fixed',
|
|
||||||
'fixed_field_key' => $fixedKey,
|
|
||||||
'field_label' => binding_fixed_label($fixedKey),
|
|
||||||
'json_value' => $raw,
|
|
||||||
'lims_value' => (string) $existing['lims_value'],
|
|
||||||
'lims_value_id' => (int) $existing['lims_value_id'],
|
|
||||||
'datadb_ids' => [],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
$savedBindings[$key]['datadb_ids'][] = (int) $iddatadb;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-match 1-a-1 (solo per le liste globali piccole).
|
|
||||||
$autoMatch = null;
|
|
||||||
if (binding_fixed_auto_matchable($fixedKey)) {
|
|
||||||
$fixedValues = binding_get_fixed_values($pdo, $fixedKey, (int) $template_id);
|
|
||||||
$autoMatch = binding_auto_match_fixed($fixedValues, $raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($autoMatch) {
|
|
||||||
binding_upsert_fixed($pdo, (int) $template_id, $fixedKey, $raw, (int) $autoMatch['id'], (string) $autoMatch['text'], $user_id);
|
|
||||||
$fixedUpdates[$column] = (int) $autoMatch['id'];
|
|
||||||
$key = 'fx:' . $fixedKey . '|' . $raw;
|
|
||||||
if (!isset($autoBindings[$key])) {
|
|
||||||
$autoBindings[$key] = [
|
|
||||||
'kind' => 'fixed',
|
|
||||||
'fixed_field_key' => $fixedKey,
|
|
||||||
'field_label' => binding_fixed_label($fixedKey),
|
|
||||||
'json_value' => $raw,
|
|
||||||
'lims_value' => (string) $autoMatch['text'],
|
|
||||||
'lims_value_id' => (int) $autoMatch['id'],
|
|
||||||
'datadb_ids' => [],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
$autoBindings[$key]['datadb_ids'][] = (int) $iddatadb;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nessuna corrispondenza: colonna lasciata vuota, segnalo come pending.
|
|
||||||
$key = 'fx:' . $fixedKey . '|' . $raw;
|
|
||||||
if (!isset($pendingBindings[$key])) {
|
|
||||||
$pendingBindings[$key] = [
|
|
||||||
'kind' => 'fixed',
|
|
||||||
'fixed_field_key' => $fixedKey,
|
|
||||||
'field_label' => binding_fixed_label($fixedKey),
|
|
||||||
'json_value' => $raw,
|
|
||||||
'datadb_ids' => [],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
$pendingBindings[$key]['datadb_ids'][] = (int) $iddatadb;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($fixedUpdates)) {
|
|
||||||
$setParts = [];
|
|
||||||
$params = [];
|
|
||||||
foreach ($fixedUpdates as $col => $val) {
|
|
||||||
$setParts[] = "`$col` = ?";
|
|
||||||
$params[] = $val;
|
|
||||||
}
|
|
||||||
$params[] = (int) $iddatadb;
|
|
||||||
$upd = $pdo->prepare("UPDATE datadb SET " . implode(', ', $setParts) . " WHERE iddatadb = ?");
|
|
||||||
$upd->execute($params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$_SESSION['inserted_ids'] = $insertedIds;
|
$_SESSION['inserted_ids'] = $insertedIds;
|
||||||
|
|
||||||
$importedUrl = "imported.php?id=" . urlencode($template_id) . "&importref=" . urlencode($importReferenceCode);
|
header("Location: imported.php?id=" . urlencode($template_id) . "&importref=" . urlencode($importReferenceCode));
|
||||||
|
|
||||||
// Solo se restano binding da risolvere mostro la pagina (con anche gli auto, modificabili).
|
|
||||||
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']);
|
|
||||||
|
|
||||||
// Solo auto-collegati: vado diretto alla griglia, segnalando quanti.
|
|
||||||
if (!empty($autoBindings)) {
|
|
||||||
$importedUrl .= "&autobound=" . count($autoBindings);
|
|
||||||
}
|
|
||||||
|
|
||||||
header("Location: " . $importedUrl);
|
|
||||||
exit;
|
exit;
|
||||||
|
?>
|
||||||
|
|||||||
@@ -1,829 +0,0 @@
|
|||||||
<?php
|
|
||||||
include('include/headscript.php');
|
|
||||||
|
|
||||||
// Check if a valid template ID has been provided
|
|
||||||
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
|
|
||||||
header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Invalid ID"));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$id = intval($_GET['id']);
|
|
||||||
|
|
||||||
// Load template
|
|
||||||
$db = DBHandlerSelect::getInstance();
|
|
||||||
$pdo = $db->getConnection();
|
|
||||||
$stmt = $pdo->prepare("SELECT * FROM excel_templates WHERE id = ?");
|
|
||||||
$stmt->execute([$id]);
|
|
||||||
$template = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if (!$template) {
|
|
||||||
header("Location: template_dashboard.php?status=error&message=" . urlencode("Template not found"));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check mappings
|
|
||||||
$stmt = $pdo->prepare("SELECT id FROM template_mapping WHERE template_id = ?");
|
|
||||||
$stmt->execute([$id]);
|
|
||||||
$hasMappings = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
error_log("Loaded JSON import template: " . print_r($template, true));
|
|
||||||
?>
|
|
||||||
|
|
||||||
<!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/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
.top-scrollbar {
|
|
||||||
overflow-x: auto;
|
|
||||||
overflow-y: hidden;
|
|
||||||
width: 100%;
|
|
||||||
height: 18px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
background: #f8f9fa;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-scrollbar-inner {
|
|
||||||
height: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-container {
|
|
||||||
overflow-x: auto;
|
|
||||||
max-width: 100%;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table th,
|
|
||||||
.table td {
|
|
||||||
padding: 10px;
|
|
||||||
text-align: left;
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
min-width: 120px;
|
|
||||||
max-width: 260px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table th:first-child,
|
|
||||||
.table td:first-child {
|
|
||||||
min-width: 50px;
|
|
||||||
max-width: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table th {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
position: relative;
|
|
||||||
cursor: col-resize;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table th .resize-handle {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
width: 5px;
|
|
||||||
height: 100%;
|
|
||||||
cursor: col-resize;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table th .resize-handle:hover {
|
|
||||||
background: #007bff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
display: none;
|
|
||||||
border: 4px solid #f3f3f3;
|
|
||||||
border-top: 4px solid #3498db;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
margin: 10px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.column-filters th {
|
|
||||||
background: #ffffff;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.column-filters input {
|
|
||||||
width: 100%;
|
|
||||||
min-width: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.json-code-input {
|
|
||||||
font-size: 1.15rem;
|
|
||||||
min-height: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.json-paste-area {
|
|
||||||
min-height: 260px;
|
|
||||||
font-family: Consolas, Monaco, monospace;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.json-help-box {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 12px 14px;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.source-badge {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<title><?= htmlspecialchars($template['name']) ?> - JSON Import - <?= 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">
|
|
||||||
<?php include('top_stat_widget.php'); ?>
|
|
||||||
|
|
||||||
<div class="mb-3 text">
|
|
||||||
<a href="imported.php?id=<?= $id ?>" class="btn btn-warning me-2">Imported (i)</a>
|
|
||||||
<a href="tolims.php?id=<?= $id ?>" class="btn btn-success me-2">To LIMS (l)</a>
|
|
||||||
<a href="bindings_manage.php?template_id=<?= $id ?>" class="btn btn-outline-secondary">Binding JSON → LIMS</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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"><?= htmlspecialchars($template['name']) ?> - JSON Import</h6>
|
|
||||||
<small>
|
|
||||||
Template ID: <?= $id ?>
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
<span class="badge bg-info text-dark">JSON mode</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-body">
|
|
||||||
<?php if (!$hasMappings): ?>
|
|
||||||
<div class="alert alert-warning" role="alert">
|
|
||||||
Nessun mapping trovato per questo template. Configura i mapping prima di procedere.
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<div class="json-help-box mb-3">
|
|
||||||
<strong>Flusso:</strong> inserisci/scansiona un codice per recuperare il JSON da API, oppure incolla manualmente un JSON nel secondo tab.
|
|
||||||
Ogni JSON aggiunto diventa una riga della tabella di preview. Quando hai finito, seleziona le righe e clicca <strong>Prosegui</strong>.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="nav nav-tabs" id="jsonImportTabs" role="tablist">
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link active" id="api-code-tab" data-bs-toggle="tab" data-bs-target="#api-code-pane" type="button" role="tab" aria-controls="api-code-pane" aria-selected="true">
|
|
||||||
Code / Barcode
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link" id="paste-json-tab" data-bs-toggle="tab" data-bs-target="#paste-json-pane" type="button" role="tab" aria-controls="paste-json-pane" aria-selected="false">
|
|
||||||
Paste JSON
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="tab-content border border-top-0 p-3 mb-3" id="jsonImportTabsContent">
|
|
||||||
<div class="tab-pane fade show active" id="api-code-pane" role="tabpanel" aria-labelledby="api-code-tab" tabindex="0">
|
|
||||||
<form id="jsonCodeForm" class="row g-3 align-items-end">
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<label for="json_code" class="form-label">Code / Barcode</label>
|
|
||||||
<input type="text" class="form-control json-code-input" id="json_code" name="json_code" placeholder="Write or scan code" autocomplete="off" <?= !$hasMappings ? 'disabled' : '' ?>>
|
|
||||||
<small class="text-muted">Lo scanner barcode normalmente scrive qui il codice e invia Enter.</small>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-4 d-flex gap-2">
|
|
||||||
<button type="submit" class="btn btn-primary flex-fill" <?= !$hasMappings ? 'disabled' : '' ?>>Load JSON</button>
|
|
||||||
<button type="button" class="btn btn-outline-secondary" id="clearCodeBtn">Clear</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tab-pane fade" id="paste-json-pane" role="tabpanel" aria-labelledby="paste-json-tab" tabindex="0">
|
|
||||||
<form id="pasteJsonForm">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="manual_json_reference" class="form-label">Reference / filename</label>
|
|
||||||
<input type="text" class="form-control" id="manual_json_reference" placeholder="Optional reference, e.g. manual-json-001">
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="manual_json" class="form-label">Paste JSON</label>
|
|
||||||
<textarea class="form-control json-paste-area" id="manual_json" placeholder='{"data":[{"id":"MM000620","attributes":{"trf_type":"apparel"}}]}' <?= !$hasMappings ? 'disabled' : '' ?>></textarea>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary" <?= !$hasMappings ? 'disabled' : '' ?>>Add pasted JSON</button>
|
|
||||||
<button type="button" class="btn btn-outline-secondary" id="clearManualJsonBtn">Clear</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="loader" id="loader"></div>
|
|
||||||
<div id="errorContainer" class="alert alert-danger mt-3" style="display:none;"></div>
|
|
||||||
<div id="successContainer" class="alert alert-success mt-3" style="display:none;"></div>
|
|
||||||
|
|
||||||
<div id="tableContainer"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="overlay toggle-icon"></div>
|
|
||||||
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
|
||||||
<?php include('include/footer.php'); ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.min.js"></script>
|
|
||||||
<?php include('jsinclude.php'); ?>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
|
||||||
const TEMPLATE_ID = <?= (int)$id ?>;
|
|
||||||
const API_ENDPOINT = 'get_json_by_code.php';
|
|
||||||
const INCLUDE_SOURCE_CODE_COLUMN = true;
|
|
||||||
const UNWRAP_SINGLE_DATA_ITEM = true;
|
|
||||||
|
|
||||||
const jsonCodeForm = document.getElementById('jsonCodeForm');
|
|
||||||
const pasteJsonForm = document.getElementById('pasteJsonForm');
|
|
||||||
const jsonCodeInput = document.getElementById('json_code');
|
|
||||||
const manualJsonInput = document.getElementById('manual_json');
|
|
||||||
const manualJsonReferenceInput = document.getElementById('manual_json_reference');
|
|
||||||
const clearCodeBtn = document.getElementById('clearCodeBtn');
|
|
||||||
const clearManualJsonBtn = document.getElementById('clearManualJsonBtn');
|
|
||||||
const loader = document.getElementById('loader');
|
|
||||||
const errorContainer = document.getElementById('errorContainer');
|
|
||||||
const successContainer = document.getElementById('successContainer');
|
|
||||||
const tableContainer = document.getElementById('tableContainer');
|
|
||||||
|
|
||||||
let jsonRows = [];
|
|
||||||
let columns = [];
|
|
||||||
|
|
||||||
if (jsonCodeInput && !jsonCodeInput.disabled) {
|
|
||||||
jsonCodeInput.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonCodeForm.addEventListener('submit', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const code = jsonCodeInput.value.trim();
|
|
||||||
|
|
||||||
if (!code) {
|
|
||||||
showError('Inserisci o scansiona un codice.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
loadJsonFromApi(code);
|
|
||||||
});
|
|
||||||
|
|
||||||
pasteJsonForm.addEventListener('submit', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const rawJson = manualJsonInput.value.trim();
|
|
||||||
const reference = manualJsonReferenceInput.value.trim() || 'manual-json-' + (jsonRows.length + 1);
|
|
||||||
|
|
||||||
if (!rawJson) {
|
|
||||||
showError('Incolla un JSON prima di aggiungerlo.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const parsedJson = JSON.parse(rawJson);
|
|
||||||
addJsonRow(parsedJson, reference, 'paste');
|
|
||||||
manualJsonInput.value = '';
|
|
||||||
manualJsonReferenceInput.value = '';
|
|
||||||
showSuccess('JSON incollato aggiunto correttamente.');
|
|
||||||
} catch (err) {
|
|
||||||
showError('JSON non valido: ' + err.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
clearCodeBtn.addEventListener('click', function() {
|
|
||||||
jsonCodeInput.value = '';
|
|
||||||
jsonCodeInput.focus();
|
|
||||||
});
|
|
||||||
|
|
||||||
clearManualJsonBtn.addEventListener('click', function() {
|
|
||||||
manualJsonInput.value = '';
|
|
||||||
manualJsonReferenceInput.value = '';
|
|
||||||
});
|
|
||||||
|
|
||||||
function loadJsonFromApi(code) {
|
|
||||||
hideMessages();
|
|
||||||
loader.style.display = 'block';
|
|
||||||
|
|
||||||
fetch(API_ENDPOINT, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
template_id: TEMPLATE_ID,
|
|
||||||
code: code
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('HTTP status ' + response.status);
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(responseData => {
|
|
||||||
loader.style.display = 'none';
|
|
||||||
|
|
||||||
if (responseData.error) {
|
|
||||||
showError(responseData.error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let jsonPayload = responseData;
|
|
||||||
let reference = code;
|
|
||||||
|
|
||||||
// Supported endpoint response format:
|
|
||||||
// { success: true, json: {...}, reference: "..." }
|
|
||||||
if (responseData.json !== undefined) {
|
|
||||||
jsonPayload = responseData.json;
|
|
||||||
reference = responseData.reference || responseData.filename || responseData.code || code;
|
|
||||||
}
|
|
||||||
|
|
||||||
addJsonRow(jsonPayload, reference, 'api');
|
|
||||||
jsonCodeInput.value = '';
|
|
||||||
jsonCodeInput.focus();
|
|
||||||
showSuccess('JSON recuperato e aggiunto correttamente.');
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
loader.style.display = 'none';
|
|
||||||
showError('Errore durante il recupero del JSON: ' + error.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function addJsonRow(jsonPayload, reference, sourceType) {
|
|
||||||
// Ogni elemento di data[] diventa una riga (non colonne data.0.*, data.1.*).
|
|
||||||
const records = extractRecords(jsonPayload);
|
|
||||||
|
|
||||||
records.forEach((record, recordIndex) => {
|
|
||||||
const flattened = flattenJson(record);
|
|
||||||
|
|
||||||
const rowReference = records.length > 1 ?
|
|
||||||
reference + '#' + (recordIndex + 1) :
|
|
||||||
reference;
|
|
||||||
|
|
||||||
if (INCLUDE_SOURCE_CODE_COLUMN) {
|
|
||||||
flattened.source_code = rowReference;
|
|
||||||
flattened.source_type = sourceType;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newColumns = Object.keys(flattened).filter(col => !columns.includes(col));
|
|
||||||
columns = columns.concat(newColumns);
|
|
||||||
|
|
||||||
jsonRows.push({
|
|
||||||
excelrow: 'JSON-' + (jsonRows.length + 1),
|
|
||||||
reference: rowReference,
|
|
||||||
sourceType: sourceType,
|
|
||||||
flat: flattened
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
renderTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record da trasformare in righe: gli oggetti in data[], altrimenti il payload stesso.
|
|
||||||
function extractRecords(payload) {
|
|
||||||
if (
|
|
||||||
payload &&
|
|
||||||
typeof payload === 'object' &&
|
|
||||||
!Array.isArray(payload) &&
|
|
||||||
Array.isArray(payload.data) &&
|
|
||||||
payload.data.length > 0
|
|
||||||
) {
|
|
||||||
const items = payload.data.filter(item => item && typeof item === 'object');
|
|
||||||
if (items.length > 0) {
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [payload];
|
|
||||||
}
|
|
||||||
|
|
||||||
function flattenJson(value, prefix = '', result = {}) {
|
|
||||||
if (value === null || value === undefined) {
|
|
||||||
result[prefix || 'value'] = '';
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
if (value.length === 0) {
|
|
||||||
result[prefix || 'value'] = '';
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
value.forEach((item, index) => {
|
|
||||||
const newPrefix = prefix ? prefix + '.' + index : String(index);
|
|
||||||
flattenJson(item, newPrefix, result);
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === 'object') {
|
|
||||||
const keys = Object.keys(value);
|
|
||||||
if (keys.length === 0) {
|
|
||||||
result[prefix || 'value'] = '';
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
keys.forEach(key => {
|
|
||||||
const newPrefix = prefix ? prefix + '.' + key : key;
|
|
||||||
flattenJson(value[key], newPrefix, result);
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
result[prefix || 'value'] = String(value);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildImportData() {
|
|
||||||
const rows = jsonRows.map(row => columns.map(col => row.flat[col] ?? ''));
|
|
||||||
const excelData = rows.map((rowData, index) => ({
|
|
||||||
excelrow: jsonRows[index].excelrow,
|
|
||||||
data: rowData
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
|
||||||
template_id: TEMPLATE_ID,
|
|
||||||
columns: columns,
|
|
||||||
rows: rows,
|
|
||||||
excel_data: excelData,
|
|
||||||
filename: 'json_import_' + new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderTable() {
|
|
||||||
if (jsonRows.length === 0) {
|
|
||||||
tableContainer.innerHTML = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = buildImportData();
|
|
||||||
|
|
||||||
let html = `
|
|
||||||
<form id="selectRowsForm" action="import_insert.php" method="POST">
|
|
||||||
<input type="hidden" name="template_id" value="${escapeHtml(data.template_id)}">
|
|
||||||
<input type="hidden" name="columns" value="${encodeURIComponent(JSON.stringify(data.columns))}">
|
|
||||||
<input type="hidden" name="rows" id="selectedRowsData" value="">
|
|
||||||
<input type="hidden" name="excelrows" id="selectedExcelRowsData" value="">
|
|
||||||
<input type="hidden" name="filename" value="${escapeHtml(data.filename)}">
|
|
||||||
<input type="hidden" name="source_type" value="json">
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
|
|
||||||
<div>
|
|
||||||
<strong>JSON rows loaded:</strong> ${jsonRows.length}
|
|
||||||
<span class="badge bg-secondary source-badge ms-2">Columns: ${data.columns.length}</span>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex gap-2">
|
|
||||||
<button type="button" class="btn btn-outline-danger btn-sm" id="clearAllRowsBtn">Clear all rows</button>
|
|
||||||
<button type="submit" class="btn btn-primary" id="proceedButtonTop" disabled>Prosegui</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="top-scrollbar" id="topTableScrollbar">
|
|
||||||
<div class="top-scrollbar-inner" id="topTableScrollbarInner"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="table-container" id="mainTableContainer">
|
|
||||||
<table class="table table-striped table-bordered" id="importPreviewTable">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th><input type="checkbox" id="selectAll"> Select</th>
|
|
||||||
${data.columns.map(col => `<th title="${escapeHtml(col)}">${escapeHtml(readableColumnLabel(col))}<div class="resize-handle"></div></th>`).join('')}
|
|
||||||
<th>Action</th>
|
|
||||||
</tr>
|
|
||||||
<tr class="column-filters">
|
|
||||||
<th></th>
|
|
||||||
${data.columns.map((col, i) => `
|
|
||||||
<th>
|
|
||||||
<input type="text"
|
|
||||||
class="form-control form-control-sm column-filter"
|
|
||||||
data-col-index="${i}"
|
|
||||||
placeholder="Filter...">
|
|
||||||
</th>
|
|
||||||
`).join('')}
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
${data.excel_data.map((row, index) => `
|
|
||||||
<tr data-row-index="${index}">
|
|
||||||
<td><input type="checkbox" class="row-checkbox" name="selected_rows[]" value="${index}" data-excelrow="${escapeHtml(row.excelrow)}"></td>
|
|
||||||
${row.data.map(cell => `<td title="${escapeHtml(cell)}">${escapeHtml(cell)}</td>`).join('')}
|
|
||||||
<td><button type="button" class="btn btn-sm btn-outline-danger remove-row-btn" data-row-index="${index}">Remove</button></td>
|
|
||||||
</tr>
|
|
||||||
`).join('')}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary mt-3" id="proceedButtonBottom" disabled>Prosegui</button>
|
|
||||||
</form>
|
|
||||||
`;
|
|
||||||
|
|
||||||
tableContainer.innerHTML = html;
|
|
||||||
bindTableEvents(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function bindTableEvents(data) {
|
|
||||||
const selectRowsForm = document.getElementById('selectRowsForm');
|
|
||||||
const clearAllRowsBtn = document.getElementById('clearAllRowsBtn');
|
|
||||||
const removeRowButtons = document.querySelectorAll('.remove-row-btn');
|
|
||||||
|
|
||||||
selectRowsForm.addEventListener('submit', function(e) {
|
|
||||||
const checkedBoxes = Array.from(document.querySelectorAll('.row-checkbox:checked'));
|
|
||||||
|
|
||||||
if (checkedBoxes.length === 0) {
|
|
||||||
e.preventDefault();
|
|
||||||
alert('Seleziona almeno una riga.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedRows = [];
|
|
||||||
const selectedExcelRows = [];
|
|
||||||
|
|
||||||
checkedBoxes.forEach((cb, newIndex) => {
|
|
||||||
const originalIndex = parseInt(cb.value, 10);
|
|
||||||
|
|
||||||
if (data.rows && data.rows[originalIndex]) {
|
|
||||||
selectedRows.push(data.rows[originalIndex]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.excel_data && data.excel_data[originalIndex]) {
|
|
||||||
selectedExcelRows.push(data.excel_data[originalIndex].excelrow);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reindex selected_rows so import_insert.php receives only the reduced rows array
|
|
||||||
cb.value = newIndex;
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('selectedRowsData').value =
|
|
||||||
encodeURIComponent(JSON.stringify(selectedRows));
|
|
||||||
|
|
||||||
document.getElementById('selectedExcelRowsData').value =
|
|
||||||
encodeURIComponent(JSON.stringify(selectedExcelRows));
|
|
||||||
});
|
|
||||||
|
|
||||||
clearAllRowsBtn.addEventListener('click', function() {
|
|
||||||
if (!confirm('Vuoi rimuovere tutte le righe JSON caricate?')) return;
|
|
||||||
jsonRows = [];
|
|
||||||
columns = [];
|
|
||||||
renderTable();
|
|
||||||
showSuccess('Righe JSON rimosse.');
|
|
||||||
jsonCodeInput.focus();
|
|
||||||
});
|
|
||||||
|
|
||||||
removeRowButtons.forEach(button => {
|
|
||||||
button.addEventListener('click', function() {
|
|
||||||
const rowIndex = parseInt(this.dataset.rowIndex, 10);
|
|
||||||
jsonRows.splice(rowIndex, 1);
|
|
||||||
rebuildColumnsFromRows();
|
|
||||||
renderTable();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const topTableScrollbar = document.getElementById('topTableScrollbar');
|
|
||||||
const topTableScrollbarInner = document.getElementById('topTableScrollbarInner');
|
|
||||||
const mainTableContainer = document.getElementById('mainTableContainer');
|
|
||||||
const importPreviewTable = document.getElementById('importPreviewTable');
|
|
||||||
|
|
||||||
function updateTopTableScrollbar() {
|
|
||||||
if (!topTableScrollbar || !topTableScrollbarInner || !mainTableContainer || !importPreviewTable) return;
|
|
||||||
|
|
||||||
topTableScrollbarInner.style.width = importPreviewTable.scrollWidth + 'px';
|
|
||||||
|
|
||||||
if (mainTableContainer.scrollWidth > mainTableContainer.clientWidth) {
|
|
||||||
topTableScrollbar.style.display = 'block';
|
|
||||||
} else {
|
|
||||||
topTableScrollbar.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let syncingTop = false;
|
|
||||||
let syncingBottom = false;
|
|
||||||
|
|
||||||
if (topTableScrollbar && mainTableContainer) {
|
|
||||||
topTableScrollbar.addEventListener('scroll', function() {
|
|
||||||
if (syncingBottom) return;
|
|
||||||
syncingTop = true;
|
|
||||||
mainTableContainer.scrollLeft = topTableScrollbar.scrollLeft;
|
|
||||||
syncingTop = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
mainTableContainer.addEventListener('scroll', function() {
|
|
||||||
if (syncingTop) return;
|
|
||||||
syncingBottom = true;
|
|
||||||
topTableScrollbar.scrollLeft = mainTableContainer.scrollLeft;
|
|
||||||
syncingBottom = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTopTableScrollbar();
|
|
||||||
setTimeout(updateTopTableScrollbar, 100);
|
|
||||||
setTimeout(updateTopTableScrollbar, 300);
|
|
||||||
window.addEventListener('resize', updateTopTableScrollbar);
|
|
||||||
|
|
||||||
const proceedButtonTop = document.getElementById('proceedButtonTop');
|
|
||||||
const proceedButtonBottom = document.getElementById('proceedButtonBottom');
|
|
||||||
const selectAllCheckbox = document.getElementById('selectAll');
|
|
||||||
const checkboxes = document.querySelectorAll('.row-checkbox');
|
|
||||||
|
|
||||||
function updateProceedButton() {
|
|
||||||
const enabled = Array.from(document.querySelectorAll('.row-checkbox')).some(cb => cb.checked);
|
|
||||||
|
|
||||||
if (proceedButtonTop) proceedButtonTop.disabled = !enabled;
|
|
||||||
if (proceedButtonBottom) proceedButtonBottom.disabled = !enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
selectAllCheckbox.addEventListener('change', function() {
|
|
||||||
const visibleRows = Array.from(document.querySelectorAll('#importPreviewTable tbody tr'))
|
|
||||||
.filter(row => row.style.display !== 'none');
|
|
||||||
|
|
||||||
visibleRows.forEach(row => {
|
|
||||||
const checkbox = row.querySelector('.row-checkbox');
|
|
||||||
if (checkbox) {
|
|
||||||
checkbox.checked = this.checked;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
updateProceedButton();
|
|
||||||
});
|
|
||||||
|
|
||||||
checkboxes.forEach(checkbox => {
|
|
||||||
checkbox.addEventListener('change', function() {
|
|
||||||
const visibleCheckboxes = Array.from(document.querySelectorAll('#importPreviewTable tbody tr'))
|
|
||||||
.filter(row => row.style.display !== 'none')
|
|
||||||
.map(row => row.querySelector('.row-checkbox'))
|
|
||||||
.filter(cb => cb !== null);
|
|
||||||
|
|
||||||
selectAllCheckbox.checked =
|
|
||||||
visibleCheckboxes.length > 0 &&
|
|
||||||
visibleCheckboxes.every(cb => cb.checked);
|
|
||||||
|
|
||||||
updateProceedButton();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const thElements = document.querySelectorAll('#importPreviewTable th');
|
|
||||||
thElements.forEach((th, index) => {
|
|
||||||
if (index === 0) return;
|
|
||||||
const resizeHandle = th.querySelector('.resize-handle');
|
|
||||||
if (resizeHandle) {
|
|
||||||
resizeHandle.addEventListener('mousedown', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const startX = e.clientX;
|
|
||||||
const startWidth = th.offsetWidth;
|
|
||||||
|
|
||||||
const onMouseMove = (e) => {
|
|
||||||
const newWidth = Math.max(60, startWidth + (e.clientX - startX));
|
|
||||||
th.style.width = `${newWidth}px`;
|
|
||||||
th.style.minWidth = `${newWidth}px`;
|
|
||||||
th.style.maxWidth = `${newWidth}px`;
|
|
||||||
|
|
||||||
const cells = document.querySelectorAll(`#importPreviewTable td:nth-child(${index + 1})`);
|
|
||||||
cells.forEach(cell => {
|
|
||||||
cell.style.width = `${newWidth}px`;
|
|
||||||
cell.style.minWidth = `${newWidth}px`;
|
|
||||||
cell.style.maxWidth = `${newWidth}px`;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onMouseUp = () => {
|
|
||||||
document.removeEventListener('mousemove', onMouseMove);
|
|
||||||
document.removeEventListener('mouseup', onMouseUp);
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('mousemove', onMouseMove);
|
|
||||||
document.addEventListener('mouseup', onMouseUp);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const rows = document.querySelectorAll('#importPreviewTable tbody tr');
|
|
||||||
const filterInputs = document.querySelectorAll('.column-filter');
|
|
||||||
const activeFilters = {};
|
|
||||||
|
|
||||||
function applyColumnFilters() {
|
|
||||||
rows.forEach(row => {
|
|
||||||
let visible = true;
|
|
||||||
|
|
||||||
for (const [colIndexStr, filterValue] of Object.entries(activeFilters)) {
|
|
||||||
const colIndex = parseInt(colIndexStr, 10);
|
|
||||||
const cell = row.cells[colIndex + 1];
|
|
||||||
const cellText = (cell?.textContent || '').toLowerCase();
|
|
||||||
const searchText = (filterValue || '').toLowerCase().trim();
|
|
||||||
|
|
||||||
if (searchText && !cellText.includes(searchText)) {
|
|
||||||
visible = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
row.style.display = visible ? '' : 'none';
|
|
||||||
});
|
|
||||||
|
|
||||||
const visibleCheckboxes = Array.from(document.querySelectorAll('#importPreviewTable tbody tr'))
|
|
||||||
.filter(row => row.style.display !== 'none')
|
|
||||||
.map(row => row.querySelector('.row-checkbox'))
|
|
||||||
.filter(cb => cb !== null);
|
|
||||||
|
|
||||||
selectAllCheckbox.checked =
|
|
||||||
visibleCheckboxes.length > 0 &&
|
|
||||||
visibleCheckboxes.every(cb => cb.checked);
|
|
||||||
|
|
||||||
updateProceedButton();
|
|
||||||
}
|
|
||||||
|
|
||||||
filterInputs.forEach(input => {
|
|
||||||
input.addEventListener('input', function() {
|
|
||||||
const colIndex = this.dataset.colIndex;
|
|
||||||
activeFilters[colIndex] = this.value;
|
|
||||||
applyColumnFilters();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
updateProceedButton();
|
|
||||||
}
|
|
||||||
|
|
||||||
function rebuildColumnsFromRows() {
|
|
||||||
columns = [];
|
|
||||||
jsonRows.forEach(row => {
|
|
||||||
Object.keys(row.flat).forEach(col => {
|
|
||||||
if (!columns.includes(col)) {
|
|
||||||
columns.push(col);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function readableColumnLabel(columnName) {
|
|
||||||
if (!columnName) return 'Column without name';
|
|
||||||
return columnName;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showError(message) {
|
|
||||||
successContainer.style.display = 'none';
|
|
||||||
errorContainer.textContent = message;
|
|
||||||
errorContainer.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
function showSuccess(message) {
|
|
||||||
errorContainer.style.display = 'none';
|
|
||||||
successContainer.textContent = message;
|
|
||||||
successContainer.style.display = 'block';
|
|
||||||
setTimeout(() => {
|
|
||||||
successContainer.style.display = 'none';
|
|
||||||
}, 3500);
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideMessages() {
|
|
||||||
errorContainer.style.display = 'none';
|
|
||||||
successContainer.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeHtml(value) {
|
|
||||||
return String(value ?? '')
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/'/g, ''');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1301,17 +1301,9 @@ $gridMeta = [
|
|||||||
<?php include('include/topbar.php'); ?>
|
<?php include('include/topbar.php'); ?>
|
||||||
<div class="page-wrapper">
|
<div class="page-wrapper">
|
||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
<?php $autoBoundCount = isset($_GET['autobound']) ? (int) $_GET['autobound'] : 0; ?>
|
|
||||||
<?php if ($autoBoundCount > 0): ?>
|
|
||||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
|
||||||
<?= $autoBoundCount ?> valore/i collegato/i automaticamente al LIMS durante l'import.
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<div class="mb-3 text d-flex align-items-center gap-2">
|
<div class="mb-3 text d-flex align-items-center gap-2">
|
||||||
<a href="imported.php?id=<?= $template_id ?>" class="btn btn-warning me-2">Imported (i)</a>
|
<a href="imported.php?id=<?= $template_id ?>" class="btn btn-warning me-2">Imported (i)</a>
|
||||||
<a href="tolims.php?id=<?= $template_id ?>" class="btn btn-success">To LIMS (l)</a>
|
<a href="tolims.php?id=<?= $template_id ?>" class="btn btn-success">To LIMS (l)</a>
|
||||||
<a href="bindings_manage.php?template_id=<?= $template_id ?>" class="btn btn-outline-secondary">Binding JSON → LIMS</a>
|
|
||||||
<?php if ($importref === ''): ?>
|
<?php if ($importref === ''): ?>
|
||||||
<span class="ms-3">
|
<span class="ms-3">
|
||||||
<label class="form-check-label" style="font-size: 13px; cursor: pointer;">
|
<label class="form-check-label" style="font-size: 13px; cursor: pointer;">
|
||||||
|
|||||||
@@ -6,110 +6,147 @@
|
|||||||
<div>
|
<div>
|
||||||
<h4 class="logo-text"><?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></h4>
|
<h4 class="logo-text"><?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="toggle-icon ms-auto"><i class='bx bx-arrow-back'></i>
|
<div class="toggle-icon ms-auto">
|
||||||
|
<i class='bx bx-arrow-back'></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--navigation-->
|
<!--navigation-->
|
||||||
<ul class="metismenu" id="menu">
|
<ul class="metismenu" id="menu">
|
||||||
<!-- user, admin, superuser menù -->
|
|
||||||
<?php if ((Auth::user()->hasRole('Admin')) || (Auth::user()->hasRole('User')) || (Auth::user()->hasRole('Superuser'))) : ?>
|
<?php
|
||||||
|
$currentUser = Auth::user();
|
||||||
|
|
||||||
|
$isAdmin = $currentUser->hasRole('Admin');
|
||||||
|
$isSuperUser = $currentUser->hasRole('SuperUser');
|
||||||
|
$isUser = $currentUser->hasRole('User');
|
||||||
|
|
||||||
|
$canAccessMainMenu = $isAdmin || $isSuperUser || $isUser;
|
||||||
|
$canAccessUserAdmin = $isAdmin || $isSuperUser;
|
||||||
|
$canAccessTemplates = $isAdmin || $isSuperUser;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- user, admin, superuser menu -->
|
||||||
|
<?php if ($canAccessMainMenu) : ?>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="javascript:;" class="has-arrow">
|
<a href="javascript:;" class="has-arrow">
|
||||||
<div class="parent-icon"><i class='bx bx-home-alt'></i>
|
<div class="parent-icon">
|
||||||
|
<i class='bx bx-home-alt'></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-title">Dashboard</div>
|
<div class="menu-title">Dashboard</div>
|
||||||
</a>
|
</a>
|
||||||
<ul>
|
<ul>
|
||||||
<!-- <li> <a href="index.php"><i class='bx bx-radio-circle'></i>Default</a>
|
<li>
|
||||||
</li> -->
|
<a href="import_dashboard.php">
|
||||||
<li> <a href="import_dashboard.php"><i class='bx bx-radio-circle'></i>Import AREA</a>
|
<i class='bx bx-radio-circle'></i>XLS Import
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<?php if ($canAccessTemplates) : ?>
|
||||||
<li>
|
<li>
|
||||||
<a href="javascript:;" class="has-arrow">
|
<a href="javascript:;" class="has-arrow">
|
||||||
<div class="parent-icon"><i class="bx bx-category"></i>
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-category"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-title">Templates</div>
|
<div class="menu-title">Templates</div>
|
||||||
</a>
|
</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li> <a href="templates_dashboard.php"><i class='bx bx-radio-circle'></i><?= htmlspecialchars($dashtemplate, ENT_QUOTES, 'UTF-8'); ?></a>
|
<li>
|
||||||
</li>
|
<a href="templates_dashboard.php">
|
||||||
<li> <a href="insert_template_xls.php"><i class='bx bx-radio-circle'></i><?= htmlspecialchars($insertnewtemplatexls, ENT_QUOTES, 'UTF-8'); ?></a>
|
<i class='bx bx-radio-circle'></i><?= htmlspecialchars($dashtemplate, ENT_QUOTES, 'UTF-8'); ?>
|
||||||
</li>
|
</a>
|
||||||
|
|
||||||
</ul>
|
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="insert_template_xls.php">
|
||||||
|
<i class='bx bx-radio-circle'></i><?= htmlspecialchars($insertnewtemplatexls, ENT_QUOTES, 'UTF-8'); ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="javascript:;" class="has-arrow">
|
<a href="javascript:;" class="has-arrow">
|
||||||
<div class="parent-icon"><i class="bx bx-category"></i>
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-category"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-title">Other Functions</div>
|
<div class="menu-title">Other Functions</div>
|
||||||
</a>
|
</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li> <a href="quotations.php"><i class='bx bx-radio-circle'></i><?php echo $quotationstitle; ?></a>
|
<li>
|
||||||
|
<a href="quotations.php">
|
||||||
|
<i class='bx bx-radio-circle'></i><?= htmlspecialchars($quotationstitle, ENT_QUOTES, 'UTF-8'); ?>
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li> <a href="bindings_manage.php"><i class='bx bx-radio-circle'></i>Binding JSON → LIMS</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<?php if ($canAccessUserAdmin) : ?>
|
||||||
|
<li class="menu-label">Administration</li>
|
||||||
|
|
||||||
<li class="menu-label">Reports</li>
|
|
||||||
<li>
|
<li>
|
||||||
<a href="rapporti_cliente_lookup.php" target="">
|
<a href="user-admin.php">
|
||||||
<div class="parent-icon"><i class="bx bx-file-find"></i>
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-user-plus"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-title">Ricerca Reports</div>
|
<div class="menu-title">User Admin</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<li class="menu-label">Others</li>
|
<li class="menu-label">Others</li>
|
||||||
|
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="https://helpdesk.cesoft.io" target="_blank">
|
<a href="https://helpdesk.cesoft.io" target="_blank">
|
||||||
<div class="parent-icon"><i class="bx bx-support"></i>
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-support"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-title">Support</div>
|
<div class="menu-title">Support</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<?php
|
|
||||||
endif; ?>
|
<?php endif; ?>
|
||||||
<!-- admin, superuser menù -->
|
|
||||||
<?php if ((Auth::user()->hasRole('Admin')) || (Auth::user()->hasRole('Superuser'))) : ?>
|
|
||||||
<?php
|
<!-- admin only menu -->
|
||||||
endif; ?>
|
<?php if ($isAdmin) : ?>
|
||||||
<!-- admin menù -->
|
|
||||||
<?php if (Auth::user()->hasRole('Admin')) : ?>
|
|
||||||
<li class="menu-label">Admin Menù</li>
|
<li class="menu-label">Admin Menù</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="../" target="_blank">
|
<a href="../" target="_blank">
|
||||||
<div class="parent-icon"><i class="bx bx-support"></i>
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-support"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-title">User Management</div>
|
<div class="menu-title">User Management</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<!-- <li>
|
|
||||||
|
<!--
|
||||||
|
<li>
|
||||||
<a href="template/index.html" target="_blank">
|
<a href="template/index.html" target="_blank">
|
||||||
<div class="parent-icon"><i class="bx bx-support"></i>
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-support"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-title">Template</div>
|
<div class="menu-title">Template</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="https://codervent.com/rocker/documentation/index.html" target="_blank">
|
<a href="https://codervent.com/rocker/documentation/index.html" target="_blank">
|
||||||
<div class="parent-icon"><i class="bx bx-folder"></i>
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-folder"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-title">Documentation</div>
|
<div class="menu-title">Documentation</div>
|
||||||
</a>
|
</a>
|
||||||
</li> -->
|
</li>
|
||||||
<?php
|
-->
|
||||||
endif; ?>
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
<!--end navigation-->
|
<!--end navigation-->
|
||||||
</div>
|
</div>
|
||||||
@@ -94,8 +94,14 @@
|
|||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li><a class="dropdown-item d-flex align-items-center" href="user-profile.php"><i class="bx bx-user fs-5"></i><span>Profile</span></a>
|
<li><a class="dropdown-item d-flex align-items-center" href="user-profile.php"><i class="bx bx-user fs-5"></i><span>Profile</span></a>
|
||||||
</li>
|
</li>
|
||||||
<li><a class="dropdown-item d-flex align-items-center" href="settings.php"><i class="bx bx-cog fs-5"></i><span>Settings</span></a>
|
<?php if ($user->hasRole('Admin') || $user->hasRole('SuperUser')): ?>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item d-flex align-items-center" href="user-admin.php">
|
||||||
|
<i class="bx bx-user-plus fs-5"></i>
|
||||||
|
<span>User Admin</span>
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
<li>
|
<li>
|
||||||
<div class="dropdown-divider mb-0"></div>
|
<div class="dropdown-divider mb-0"></div>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -95,6 +95,21 @@ $apiConfigurations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
<input type="text" name="start_column" id="startColumn" class="form-control" value="A" required>
|
<input type="text" name="start_column" id="startColumn" class="form-control" value="A" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3" id="xlsEndColumnWrapper">
|
||||||
|
<label class="form-label">Last Column included</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="xls_end_column"
|
||||||
|
id="xlsEndColumn"
|
||||||
|
class="form-control"
|
||||||
|
value=""
|
||||||
|
placeholder="Example: AN">
|
||||||
|
<small class="text-danger fw-semibold">
|
||||||
|
Attention: if left empty, the system will read the entire XLS/XLSX sheet.
|
||||||
|
Dirty Excel files may cause memory errors or timeout.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-3" id="xlsSheetNumberWrapper">
|
<div class="mb-3" id="xlsSheetNumberWrapper">
|
||||||
<label class="form-label">XLS Sheet Number</label>
|
<label class="form-label">XLS Sheet Number</label>
|
||||||
<input
|
<input
|
||||||
@@ -253,11 +268,13 @@ $apiConfigurations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
|
|
||||||
const headerRowWrapper = document.getElementById("headerRowWrapper");
|
const headerRowWrapper = document.getElementById("headerRowWrapper");
|
||||||
const startColumnWrapper = document.getElementById("startColumnWrapper");
|
const startColumnWrapper = document.getElementById("startColumnWrapper");
|
||||||
|
const xlsEndColumnWrapper = document.getElementById("xlsEndColumnWrapper");
|
||||||
const xlsSheetNumberWrapper = document.getElementById("xlsSheetNumberWrapper");
|
const xlsSheetNumberWrapper = document.getElementById("xlsSheetNumberWrapper");
|
||||||
const apiConfigWrapper = document.getElementById("apiConfigWrapper");
|
const apiConfigWrapper = document.getElementById("apiConfigWrapper");
|
||||||
|
|
||||||
const headerRow = document.getElementById("headerRow");
|
const headerRow = document.getElementById("headerRow");
|
||||||
const startColumn = document.getElementById("startColumn");
|
const startColumn = document.getElementById("startColumn");
|
||||||
|
const xlsEndColumn = document.getElementById("xlsEndColumn");
|
||||||
const xlsSheetIndex = document.getElementById("xlsSheetIndex");
|
const xlsSheetIndex = document.getElementById("xlsSheetIndex");
|
||||||
const apiConfigSelect = document.getElementById("apiConfigSelect");
|
const apiConfigSelect = document.getElementById("apiConfigSelect");
|
||||||
|
|
||||||
@@ -295,14 +312,20 @@ $apiConfigurations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
if (isXls) {
|
if (isXls) {
|
||||||
headerRowWrapper.style.display = 'block';
|
headerRowWrapper.style.display = 'block';
|
||||||
startColumnWrapper.style.display = 'block';
|
startColumnWrapper.style.display = 'block';
|
||||||
|
xlsEndColumnWrapper.style.display = 'block';
|
||||||
xlsSheetNumberWrapper.style.display = 'block';
|
xlsSheetNumberWrapper.style.display = 'block';
|
||||||
|
|
||||||
headerRow.required = true;
|
headerRow.required = true;
|
||||||
startColumn.required = true;
|
startColumn.required = true;
|
||||||
xlsSheetIndex.required = true;
|
xlsSheetIndex.required = true;
|
||||||
|
|
||||||
|
// Last Column is optional.
|
||||||
|
// If empty, the import will read the entire sheet.
|
||||||
|
xlsEndColumn.required = false;
|
||||||
|
|
||||||
headerRow.disabled = false;
|
headerRow.disabled = false;
|
||||||
startColumn.disabled = false;
|
startColumn.disabled = false;
|
||||||
|
xlsEndColumn.disabled = false;
|
||||||
xlsSheetIndex.disabled = false;
|
xlsSheetIndex.disabled = false;
|
||||||
|
|
||||||
apiConfigWrapper.style.display = 'none';
|
apiConfigWrapper.style.display = 'none';
|
||||||
@@ -312,16 +335,21 @@ $apiConfigurations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
} else {
|
} else {
|
||||||
headerRowWrapper.style.display = 'none';
|
headerRowWrapper.style.display = 'none';
|
||||||
startColumnWrapper.style.display = 'none';
|
startColumnWrapper.style.display = 'none';
|
||||||
|
xlsEndColumnWrapper.style.display = 'none';
|
||||||
xlsSheetNumberWrapper.style.display = 'none';
|
xlsSheetNumberWrapper.style.display = 'none';
|
||||||
|
|
||||||
headerRow.required = false;
|
headerRow.required = false;
|
||||||
startColumn.required = false;
|
startColumn.required = false;
|
||||||
|
xlsEndColumn.required = false;
|
||||||
xlsSheetIndex.required = false;
|
xlsSheetIndex.required = false;
|
||||||
|
|
||||||
headerRow.disabled = true;
|
headerRow.disabled = true;
|
||||||
startColumn.disabled = true;
|
startColumn.disabled = true;
|
||||||
|
xlsEndColumn.disabled = true;
|
||||||
xlsSheetIndex.disabled = true;
|
xlsSheetIndex.disabled = true;
|
||||||
|
|
||||||
|
xlsEndColumn.value = '';
|
||||||
|
|
||||||
if (isApiJson) {
|
if (isApiJson) {
|
||||||
apiConfigWrapper.style.display = 'block';
|
apiConfigWrapper.style.display = 'block';
|
||||||
apiConfigSelect.required = true;
|
apiConfigSelect.required = true;
|
||||||
|
|||||||
@@ -87,16 +87,7 @@ $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
|
SELECT id, fixed_field_key, is_manual, data_type, is_required, default_value, is_visible_import
|
||||||
id,
|
|
||||||
fixed_field_key,
|
|
||||||
is_manual,
|
|
||||||
data_type,
|
|
||||||
is_required,
|
|
||||||
default_value,
|
|
||||||
default_source,
|
|
||||||
json_node,
|
|
||||||
is_visible_import
|
|
||||||
FROM template_fixed_mapping
|
FROM template_fixed_mapping
|
||||||
WHERE template_id = ?
|
WHERE template_id = ?
|
||||||
ORDER BY id ASC
|
ORDER BY id ASC
|
||||||
@@ -456,18 +447,19 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
<td>
|
<td>
|
||||||
<?php
|
<?php
|
||||||
$isSceltaMultipla = ($mapping['data_type'] === 'SceltaMultipla');
|
$isSceltaMultipla = ($mapping['data_type'] === 'SceltaMultipla');
|
||||||
$isApiSceltaMultipla = ($sourceType === 'API' && $isSceltaMultipla);
|
|
||||||
|
|
||||||
$autoValue = $mapping['auto_value'] ?? 'none';
|
$autoValue = $mapping['auto_value'] ?? 'none';
|
||||||
$hasAuto = ($autoValue && $autoValue !== 'none');
|
$hasAuto = ($autoValue && $autoValue !== 'none');
|
||||||
|
|
||||||
if ($sourceType === 'API' && !empty($mapping['json_node'])) {
|
if ($isSceltaMultipla) {
|
||||||
$mappingValue = 'json';
|
$mappingValue = 'manual';
|
||||||
} elseif ($sourceType === 'XLS' && !$isSceltaMultipla && !empty($mapping['excel_column'])) {
|
} elseif ($hasAuto) {
|
||||||
$mappingValue = 'xls';
|
|
||||||
} elseif (!$isSceltaMultipla && $hasAuto) {
|
|
||||||
$mappingValue = 'auto';
|
$mappingValue = 'auto';
|
||||||
} elseif ((int)$mapping['is_manual'] === 1 || $isSceltaMultipla) {
|
} elseif ($sourceType === 'XLS' && !empty($mapping['excel_column'])) {
|
||||||
|
$mappingValue = 'xls';
|
||||||
|
} elseif ($sourceType === 'API' && !empty($mapping['json_node'])) {
|
||||||
|
$mappingValue = 'json';
|
||||||
|
} elseif ((int)$mapping['is_manual'] === 1) {
|
||||||
$mappingValue = 'manual';
|
$mappingValue = 'manual';
|
||||||
} else {
|
} else {
|
||||||
$mappingValue = '';
|
$mappingValue = '';
|
||||||
@@ -477,18 +469,17 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
<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']; ?>"
|
||||||
data-is-scelta-multipla="<?php echo $isSceltaMultipla ? '1' : '0'; ?>"
|
<?php echo $isSceltaMultipla ? 'disabled' : ''; ?>>
|
||||||
<?php echo ($isSceltaMultipla && $sourceType !== 'API') ? 'disabled' : ''; ?>>
|
|
||||||
|
|
||||||
|
<?php if (!$isSceltaMultipla): ?>
|
||||||
<option value="">Select Option</option>
|
<option value="">Select Option</option>
|
||||||
|
|
||||||
<?php if ($sourceType === 'XLS' && !$isSceltaMultipla): ?>
|
<?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; ?>
|
||||||
|
|
||||||
@@ -624,34 +615,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
<td><?php echo htmlspecialchars($fm['data_type']); ?></td>
|
<td><?php echo htmlspecialchars($fm['data_type']); ?></td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<?php
|
|
||||||
$fixedDefaultSource = $fm['default_source'] ?? 'manual';
|
|
||||||
$fixedJsonNode = $fm['json_node'] ?? '';
|
|
||||||
$showFixedManual = ($fixedDefaultSource !== 'json');
|
|
||||||
$showFixedJson = ($fixedDefaultSource === 'json');
|
|
||||||
?>
|
|
||||||
|
|
||||||
<?php if ($sourceType === 'API'): ?>
|
|
||||||
<select class="form-select fixed-source-select"
|
|
||||||
data-fixed-id="<?php echo (int)$fm['id']; ?>"
|
|
||||||
style="margin-bottom:6px;">
|
|
||||||
<option value="manual" <?php echo ($fixedDefaultSource === 'manual') ? 'selected' : ''; ?>>
|
|
||||||
Manual value
|
|
||||||
</option>
|
|
||||||
<option value="json" <?php echo ($fixedDefaultSource === 'json') ? 'selected' : ''; ?>>
|
|
||||||
Map to JSON Node
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select class="form-select fixed-json-node-select"
|
|
||||||
data-fixed-id="<?php echo (int)$fm['id']; ?>"
|
|
||||||
data-current-json="<?php echo htmlspecialchars($fixedJsonNode, ENT_QUOTES, 'UTF-8'); ?>"
|
|
||||||
style="display:<?php echo $showFixedJson ? 'block' : 'none'; ?>; margin-bottom:6px;">
|
|
||||||
</select>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<div class="fixed-manual-wrapper"
|
|
||||||
style="display:<?php echo $showFixedManual ? 'block' : 'none'; ?>;">
|
|
||||||
|
|
||||||
<?php if ($fm['data_type'] === 'DATE'): ?>
|
<?php if ($fm['data_type'] === 'DATE'): ?>
|
||||||
|
|
||||||
@@ -685,7 +648,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
@@ -1221,46 +1183,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
initSelect2ForJsonDropdowns();
|
initSelect2ForJsonDropdowns();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFixedJsonDropdowns() {
|
|
||||||
document.querySelectorAll('select.fixed-json-node-select').forEach(select => {
|
|
||||||
let currentValue = select.value || select.dataset.currentJson || '';
|
|
||||||
|
|
||||||
let options = availableJsonNodes
|
|
||||||
.map(node => {
|
|
||||||
const clean = String(node || '').trim();
|
|
||||||
if (!clean) return '';
|
|
||||||
|
|
||||||
const info = jsonNodeLabels[clean] || {};
|
|
||||||
const shortName = info.shortName || getLastJsonNodeName(clean);
|
|
||||||
const sample = info.sample || '';
|
|
||||||
|
|
||||||
let label = shortName;
|
|
||||||
if (sample) {
|
|
||||||
label += ` — ${sample}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isSelected = clean === currentValue ? 'selected' : '';
|
|
||||||
|
|
||||||
return `
|
|
||||||
<option
|
|
||||||
value="${escapeHtmlAttr(clean)}"
|
|
||||||
data-short-name="${escapeHtmlAttr(shortName)}"
|
|
||||||
data-sample="${escapeHtmlAttr(sample)}"
|
|
||||||
data-full-path="${escapeHtmlAttr(clean)}"
|
|
||||||
${isSelected}>
|
|
||||||
${escapeHtmlText(label)}
|
|
||||||
</option>
|
|
||||||
`;
|
|
||||||
})
|
|
||||||
.join('');
|
|
||||||
|
|
||||||
select.innerHTML = '<option value="">Select JSON Node</option>' + options;
|
|
||||||
select.dataset.currentJson = currentValue;
|
|
||||||
});
|
|
||||||
|
|
||||||
initSelect2ForFixedJsonDropdowns();
|
|
||||||
}
|
|
||||||
|
|
||||||
function initSelect2ForJsonDropdowns() {
|
function initSelect2ForJsonDropdowns() {
|
||||||
if (!(window.jQuery && $.fn.select2)) return;
|
if (!(window.jQuery && $.fn.select2)) return;
|
||||||
|
|
||||||
@@ -1344,81 +1266,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initSelect2ForFixedJsonDropdowns() {
|
|
||||||
if (!(window.jQuery && $.fn.select2)) return;
|
|
||||||
|
|
||||||
$('.fixed-json-node-select').each(function() {
|
|
||||||
const $el = $(this);
|
|
||||||
|
|
||||||
if (this.style.display === 'none') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($el.hasClass('select2-hidden-accessible')) {
|
|
||||||
$el.select2('destroy');
|
|
||||||
}
|
|
||||||
|
|
||||||
$el.select2({
|
|
||||||
width: '100%',
|
|
||||||
placeholder: 'Select JSON Node',
|
|
||||||
allowClear: true,
|
|
||||||
|
|
||||||
templateResult: function(data) {
|
|
||||||
if (!data.id) return data.text;
|
|
||||||
|
|
||||||
const option = data.element;
|
|
||||||
const shortName = option.getAttribute('data-short-name') || data.text;
|
|
||||||
const sample = option.getAttribute('data-sample') || '';
|
|
||||||
|
|
||||||
const $row = $('<span class="select2-json-row"></span>');
|
|
||||||
$row.append(`<span class="select2-json-node">${escapeHtmlText(shortName)}</span>`);
|
|
||||||
|
|
||||||
if (sample) {
|
|
||||||
$row.append(`<span class="select2-json-value">${escapeHtmlText(sample)}</span>`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $row;
|
|
||||||
},
|
|
||||||
|
|
||||||
templateSelection: function(data) {
|
|
||||||
if (!data.id) return data.text;
|
|
||||||
|
|
||||||
const option = data.element;
|
|
||||||
const shortName = option.getAttribute('data-short-name') || data.text;
|
|
||||||
const sample = option.getAttribute('data-sample') || '';
|
|
||||||
|
|
||||||
if (sample) {
|
|
||||||
return `${shortName} — ${sample}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return shortName;
|
|
||||||
},
|
|
||||||
|
|
||||||
matcher: function(params, data) {
|
|
||||||
if ($.trim(params.term) === '') {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
const term = params.term.toLowerCase();
|
|
||||||
const option = data.element;
|
|
||||||
|
|
||||||
const fullPath = option?.getAttribute('data-full-path')?.toLowerCase() || '';
|
|
||||||
const shortName = option?.getAttribute('data-short-name')?.toLowerCase() || '';
|
|
||||||
const sample = option?.getAttribute('data-sample')?.toLowerCase() || '';
|
|
||||||
|
|
||||||
if (
|
|
||||||
fullPath.includes(term) ||
|
|
||||||
shortName.includes(term) ||
|
|
||||||
sample.includes(term)
|
|
||||||
) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveJsonNodes(sampleJson, nodes) {
|
function saveJsonNodes(sampleJson, nodes) {
|
||||||
return fetch('update_api_json_nodes.php', {
|
return fetch('update_api_json_nodes.php', {
|
||||||
@@ -1563,7 +1411,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
availableJsonNodes = nodes;
|
availableJsonNodes = nodes;
|
||||||
usedJsonNodesFromDB = [];
|
usedJsonNodesFromDB = [];
|
||||||
updateJsonDropdowns();
|
updateJsonDropdowns();
|
||||||
updateFixedJsonDropdowns();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await saveJsonNodes(rawJson, nodes);
|
const data = await saveJsonNodes(rawJson, nodes);
|
||||||
@@ -1709,12 +1556,9 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mappingSelect = tr?.querySelector('.mapping-select');
|
|
||||||
const currentMappingType = mappingSelect ? mappingSelect.value : 'manual';
|
|
||||||
|
|
||||||
saveMapping(
|
saveMapping(
|
||||||
mappingId,
|
mappingId,
|
||||||
currentMappingType || 'manual',
|
'manual',
|
||||||
el.value,
|
el.value,
|
||||||
xlsSelect ? xlsSelect.value : null,
|
xlsSelect ? xlsSelect.value : null,
|
||||||
null,
|
null,
|
||||||
@@ -1744,12 +1588,9 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mappingSelect = tr?.querySelector('.mapping-select');
|
|
||||||
const currentMappingType = mappingSelect ? mappingSelect.value : 'manual';
|
|
||||||
|
|
||||||
saveMapping(
|
saveMapping(
|
||||||
mappingId,
|
mappingId,
|
||||||
currentMappingType || 'manual',
|
'manual',
|
||||||
el.value,
|
el.value,
|
||||||
xlsSelect ? xlsSelect.value : null,
|
xlsSelect ? xlsSelect.value : null,
|
||||||
null,
|
null,
|
||||||
@@ -1833,7 +1674,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
|
|
||||||
const removeBtn = tr.querySelector('.remove-xls');
|
const removeBtn = tr.querySelector('.remove-xls');
|
||||||
const removeJsonBtn = tr.querySelector('.remove-json');
|
const removeJsonBtn = tr.querySelector('.remove-json');
|
||||||
const isSceltaMultipla = mappingSelect.dataset.isSceltaMultipla === '1';
|
|
||||||
|
|
||||||
function destroyJsonSelect2() {
|
function destroyJsonSelect2() {
|
||||||
if (jsonSelect && window.jQuery && $(jsonSelect).hasClass('select2-hidden-accessible')) {
|
if (jsonSelect && window.jQuery && $(jsonSelect).hasClass('select2-hidden-accessible')) {
|
||||||
@@ -1876,13 +1716,9 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
if (autoSelect) autoSelect.style.display = 'none';
|
if (autoSelect) autoSelect.style.display = 'none';
|
||||||
|
|
||||||
if (manualInput) {
|
if (manualInput) {
|
||||||
if (isSceltaMultipla) {
|
|
||||||
manualInput.style.display = 'block';
|
|
||||||
} else {
|
|
||||||
manualInput.style.display = 'none';
|
manualInput.style.display = 'none';
|
||||||
manualInput.value = '';
|
manualInput.value = '';
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (mappedColumn) mappedColumn.style.display = 'none';
|
if (mappedColumn) mappedColumn.style.display = 'none';
|
||||||
if (mappedJsonNode) mappedJsonNode.style.display = 'none';
|
if (mappedJsonNode) mappedJsonNode.style.display = 'none';
|
||||||
@@ -2122,19 +1958,13 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
let mappingId = event.target.getAttribute('data-id');
|
let mappingId = event.target.getAttribute('data-id');
|
||||||
let xlsSelect = tr.querySelector('.xls-columns');
|
let xlsSelect = tr.querySelector('.xls-columns');
|
||||||
let jsonSelect = tr.querySelector('.json-nodes');
|
let jsonSelect = tr.querySelector('.json-nodes');
|
||||||
let mappingSelect = tr.querySelector('.mapping-select');
|
|
||||||
|
|
||||||
let currentMappingType = mappingSelect ? mappingSelect.value : 'manual';
|
|
||||||
|
|
||||||
console.log("Manual default dropdown changed:", {
|
console.log("Manual default dropdown changed:", {
|
||||||
id: mappingId,
|
id: mappingId,
|
||||||
value: event.target.value,
|
value: event.target.value
|
||||||
mappingType: currentMappingType
|
|
||||||
});
|
});
|
||||||
|
|
||||||
saveMapping(
|
saveMapping(
|
||||||
mappingId,
|
mappingId,
|
||||||
currentMappingType || 'manual',
|
'manual',
|
||||||
event.target.value,
|
event.target.value,
|
||||||
xlsSelect ? xlsSelect.value : null,
|
xlsSelect ? xlsSelect.value : null,
|
||||||
null,
|
null,
|
||||||
@@ -2233,7 +2063,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
mapping_type: mappingType,
|
mapping_type: mappingType,
|
||||||
excel_column: mappingType === 'xls' ? excelColumn : null,
|
excel_column: mappingType === 'xls' ? excelColumn : null,
|
||||||
json_node: mappingType === 'json' ? jsonNode : null,
|
json_node: mappingType === 'json' ? jsonNode : null,
|
||||||
manual_default: defaultValue,
|
manual_default: mappingType === 'manual' ? defaultValue : null,
|
||||||
auto_value: mappingType === 'auto' ? (autoValue || 'none') : 'none',
|
auto_value: mappingType === 'auto' ? (autoValue || 'none') : 'none',
|
||||||
tablename: "<?php echo $template['target_table']; ?>"
|
tablename: "<?php echo $template['target_table']; ?>"
|
||||||
})
|
})
|
||||||
@@ -2262,115 +2092,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
// FIXED FIELDS (DB autosave)
|
// FIXED FIELDS (DB autosave)
|
||||||
// =======================
|
// =======================
|
||||||
|
|
||||||
// =======================
|
|
||||||
// FIXED FIELDS: source manual/json autosave
|
|
||||||
// Only used for API/JSON templates
|
|
||||||
// =======================
|
|
||||||
document.addEventListener('change', function(e) {
|
|
||||||
if (!e.target.classList.contains('fixed-source-select')) return;
|
|
||||||
|
|
||||||
const sourceSelect = e.target;
|
|
||||||
const fixedId = sourceSelect.dataset.fixedId;
|
|
||||||
const tr = sourceSelect.closest('tr');
|
|
||||||
|
|
||||||
const manualWrapper = tr.querySelector('.fixed-manual-wrapper');
|
|
||||||
const jsonSelect = tr.querySelector('.fixed-json-node-select');
|
|
||||||
|
|
||||||
if (sourceSelect.value === 'json') {
|
|
||||||
if (manualWrapper) {
|
|
||||||
manualWrapper.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jsonSelect) {
|
|
||||||
jsonSelect.style.display = 'block';
|
|
||||||
updateFixedJsonDropdowns();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (jsonSelect) {
|
|
||||||
if (window.jQuery && $(jsonSelect).hasClass('select2-hidden-accessible')) {
|
|
||||||
$(jsonSelect).select2('destroy');
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonSelect.value = '';
|
|
||||||
jsonSelect.dataset.currentJson = '';
|
|
||||||
jsonSelect.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (manualWrapper) {
|
|
||||||
manualWrapper.style.display = 'block';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch('update_fixed_field.php', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
id: fixedId,
|
|
||||||
field: 'default_source',
|
|
||||||
value: sourceSelect.value
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (!data.success) {
|
|
||||||
throw new Error(data.message || 'Update failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
fixedStatus('✅ Saved');
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error(error);
|
|
||||||
fixedStatus('❌ Save error', true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// =======================
|
|
||||||
// FIXED FIELDS: JSON node autosave
|
|
||||||
// =======================
|
|
||||||
function saveFixedJsonNode(selectEl) {
|
|
||||||
const fixedId = selectEl.dataset.fixedId;
|
|
||||||
const jsonNode = selectEl.value || '';
|
|
||||||
|
|
||||||
fetch('update_fixed_field.php', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
id: fixedId,
|
|
||||||
field: 'json_node',
|
|
||||||
value: jsonNode
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (!data.success) {
|
|
||||||
throw new Error(data.message || 'Update failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
selectEl.dataset.currentJson = jsonNode;
|
|
||||||
fixedStatus('✅ Saved');
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error(error);
|
|
||||||
fixedStatus('❌ Save error', true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('change', function(e) {
|
|
||||||
if (!e.target.classList.contains('fixed-json-node-select')) return;
|
|
||||||
|
|
||||||
saveFixedJsonNode(e.target);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (window.jQuery) {
|
|
||||||
$(document).on('select2:select select2:clear', '.fixed-json-node-select', function() {
|
|
||||||
saveFixedJsonNode(this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeHtml(str) {
|
function escapeHtml(str) {
|
||||||
return String(str ?? '')
|
return String(str ?? '')
|
||||||
.replaceAll('&', '&')
|
.replaceAll('&', '&')
|
||||||
@@ -2607,7 +2328,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateJsonDropdowns();
|
updateJsonDropdowns();
|
||||||
updateFixedJsonDropdowns();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fillFixedDropdowns().then(() => {
|
fillFixedDropdowns().then(() => {
|
||||||
|
|||||||
@@ -4,6 +4,35 @@ require_once 'class/db-functions.php';
|
|||||||
|
|
||||||
$response = ["success" => false, "message" => ""];
|
$response = ["success" => false, "message" => ""];
|
||||||
|
|
||||||
|
function excelColumnToIndex($column)
|
||||||
|
{
|
||||||
|
$column = strtoupper(trim((string)$column));
|
||||||
|
|
||||||
|
if ($column === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Numeric column index, example: 40
|
||||||
|
if (ctype_digit($column)) {
|
||||||
|
$index = (int)$column;
|
||||||
|
return $index > 0 ? $index : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Excel column letters, example: A, AN, XFC
|
||||||
|
if (!preg_match('/^[A-Z]+$/', $column)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$index = 0;
|
||||||
|
$length = strlen($column);
|
||||||
|
|
||||||
|
for ($i = 0; $i < $length; $i++) {
|
||||||
|
$index = ($index * 26) + (ord($column[$i]) - ord('A') + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $index;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
|
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
|
||||||
throw new Exception("Invalid request method.");
|
throw new Exception("Invalid request method.");
|
||||||
@@ -19,6 +48,8 @@ try {
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
$start_column = trim($_POST['start_column'] ?? '');
|
$start_column = trim($_POST['start_column'] ?? '');
|
||||||
|
$xls_end_column = strtoupper(trim($_POST['xls_end_column'] ?? ''));
|
||||||
|
$xls_end_column = $xls_end_column !== '' ? $xls_end_column : null;
|
||||||
|
|
||||||
$xls_sheet_index = isset($_POST['xls_sheet_index']) && $_POST['xls_sheet_index'] !== ''
|
$xls_sheet_index = isset($_POST['xls_sheet_index']) && $_POST['xls_sheet_index'] !== ''
|
||||||
? intval($_POST['xls_sheet_index'])
|
? intval($_POST['xls_sheet_index'])
|
||||||
@@ -60,6 +91,24 @@ try {
|
|||||||
throw new Exception("XLS Sheet Number cannot be negative.");
|
throw new Exception("XLS Sheet Number cannot be negative.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$startColumnIndex = excelColumnToIndex($start_column);
|
||||||
|
|
||||||
|
if ($startColumnIndex === null) {
|
||||||
|
throw new Exception("Start Column is not valid. Use Excel column letters like A, AN or a positive number.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($xls_end_column !== null) {
|
||||||
|
$endColumnIndex = excelColumnToIndex($xls_end_column);
|
||||||
|
|
||||||
|
if ($endColumnIndex === null) {
|
||||||
|
throw new Exception("Last Column is not valid. Use Excel column letters like AN or a positive number.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($endColumnIndex < $startColumnIndex) {
|
||||||
|
throw new Exception("Last Column cannot be before Start Column.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$api_config_id = null;
|
$api_config_id = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,6 +120,7 @@ try {
|
|||||||
|
|
||||||
$header_row = null;
|
$header_row = null;
|
||||||
$start_column = null;
|
$start_column = null;
|
||||||
|
$xls_end_column = null;
|
||||||
$xls_sheet_index = null;
|
$xls_sheet_index = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +128,7 @@ try {
|
|||||||
if ($source_type === 'PDF') {
|
if ($source_type === 'PDF') {
|
||||||
$header_row = null;
|
$header_row = null;
|
||||||
$start_column = null;
|
$start_column = null;
|
||||||
|
$xls_end_column = null;
|
||||||
$xls_sheet_index = null;
|
$xls_sheet_index = null;
|
||||||
$api_config_id = null;
|
$api_config_id = null;
|
||||||
}
|
}
|
||||||
@@ -109,6 +160,7 @@ try {
|
|||||||
source_type = ?,
|
source_type = ?,
|
||||||
header_row = ?,
|
header_row = ?,
|
||||||
start_column = ?,
|
start_column = ?,
|
||||||
|
xls_end_column = ?,
|
||||||
xls_sheet_index = ?,
|
xls_sheet_index = ?,
|
||||||
api_config_id = ?,
|
api_config_id = ?,
|
||||||
description = ?,
|
description = ?,
|
||||||
@@ -131,6 +183,7 @@ try {
|
|||||||
$source_type,
|
$source_type,
|
||||||
$header_row,
|
$header_row,
|
||||||
$start_column,
|
$start_column,
|
||||||
|
$xls_end_column,
|
||||||
$xls_sheet_index,
|
$xls_sheet_index,
|
||||||
$api_config_id,
|
$api_config_id,
|
||||||
$description,
|
$description,
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ try {
|
|||||||
id,
|
id,
|
||||||
header_row,
|
header_row,
|
||||||
start_column,
|
start_column,
|
||||||
|
xls_end_column,
|
||||||
xls_sheet_index,
|
xls_sheet_index,
|
||||||
idroutine,
|
idroutine,
|
||||||
idclient
|
idclient
|
||||||
@@ -93,6 +94,12 @@ try {
|
|||||||
|
|
||||||
$start_column_raw = $template['start_column'] ?? 'A';
|
$start_column_raw = $template['start_column'] ?? 'A';
|
||||||
$start_column = normalizeColumnIndex($start_column_raw);
|
$start_column = normalizeColumnIndex($start_column_raw);
|
||||||
|
$xls_end_column_raw = trim((string)($template['xls_end_column'] ?? ''));
|
||||||
|
$xls_end_column = $xls_end_column_raw !== '' ? normalizeColumnIndex($xls_end_column_raw) : 0;
|
||||||
|
|
||||||
|
if ($xls_end_column > 0 && $xls_end_column < $start_column) {
|
||||||
|
throw new Exception("Last Column cannot be before Start Column.");
|
||||||
|
}
|
||||||
|
|
||||||
$xlsSheetIndex = isset($template['xls_sheet_index']) && $template['xls_sheet_index'] !== null
|
$xlsSheetIndex = isset($template['xls_sheet_index']) && $template['xls_sheet_index'] !== null
|
||||||
? (int)$template['xls_sheet_index']
|
? (int)$template['xls_sheet_index']
|
||||||
@@ -109,7 +116,15 @@ try {
|
|||||||
// Debug del template_id ricevuto
|
// Debug del template_id ricevuto
|
||||||
error_log("Received template_id from POST: " . print_r($_POST['template_id'], true));
|
error_log("Received template_id from POST: " . print_r($_POST['template_id'], true));
|
||||||
error_log("Converted template_id: $template_id");
|
error_log("Converted template_id: $template_id");
|
||||||
error_log("Template XLS settings - header_row: $header_row, start_column_raw: $start_column_raw, start_column_index: $start_column, xls_sheet_index: $xlsSheetIndex");
|
error_log(
|
||||||
|
"Template XLS settings - " .
|
||||||
|
"header_row: $header_row, " .
|
||||||
|
"start_column_raw: $start_column_raw, " .
|
||||||
|
"start_column_index: $start_column, " .
|
||||||
|
"xls_end_column_raw: " . ($xls_end_column_raw !== '' ? $xls_end_column_raw : '[empty = read all]') . ", " .
|
||||||
|
"xls_end_column_index: " . ($xls_end_column > 0 ? $xls_end_column : '[no limit]') . ", " .
|
||||||
|
"xls_sheet_index: $xlsSheetIndex"
|
||||||
|
);
|
||||||
|
|
||||||
$file = $_FILES['excel_file'];
|
$file = $_FILES['excel_file'];
|
||||||
$fileError = $file['error'];
|
$fileError = $file['error'];
|
||||||
@@ -161,8 +176,33 @@ try {
|
|||||||
if (empty($mappings)) {
|
if (empty($mappings)) {
|
||||||
$response['error'] = "Nessun mapping trovato per il template con ID $template_id";
|
$response['error'] = "Nessun mapping trovato per il template con ID $template_id";
|
||||||
} else {
|
} else {
|
||||||
// Carica il file rinominato con PHPSpreadsheet
|
// Load the XLS/XLSX file.
|
||||||
$spreadsheet = IOFactory::load($destination);
|
// If Last Column is configured in the template, load only the configured column range.
|
||||||
|
// If Last Column is empty, keep the original behavior and read the entire sheet.
|
||||||
|
$reader = IOFactory::createReaderForFile($destination);
|
||||||
|
$reader->setReadDataOnly(true);
|
||||||
|
|
||||||
|
if ($xls_end_column > 0) {
|
||||||
|
$reader->setReadFilter(new class($start_column, $xls_end_column) implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter {
|
||||||
|
private int $startColumn;
|
||||||
|
private int $endColumn;
|
||||||
|
|
||||||
|
public function __construct(int $startColumn, int $endColumn)
|
||||||
|
{
|
||||||
|
$this->startColumn = $startColumn;
|
||||||
|
$this->endColumn = $endColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readCell($columnAddress, $row, $worksheetName = ''): bool
|
||||||
|
{
|
||||||
|
$columnIndex = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($columnAddress);
|
||||||
|
|
||||||
|
return $columnIndex >= $this->startColumn && $columnIndex <= $this->endColumn;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$spreadsheet = $reader->load($destination);
|
||||||
|
|
||||||
$sheetCount = $spreadsheet->getSheetCount();
|
$sheetCount = $spreadsheet->getSheetCount();
|
||||||
$sheetNames = $spreadsheet->getSheetNames();
|
$sheetNames = $spreadsheet->getSheetNames();
|
||||||
@@ -193,6 +233,12 @@ try {
|
|||||||
$highestColumn = $worksheet->getHighestColumn();
|
$highestColumn = $worksheet->getHighestColumn();
|
||||||
$highestColumnIndex = Coordinate::columnIndexFromString($highestColumn);
|
$highestColumnIndex = Coordinate::columnIndexFromString($highestColumn);
|
||||||
|
|
||||||
|
// Force the effective highest column to Last Column, if configured.
|
||||||
|
if ($xls_end_column > 0) {
|
||||||
|
$highestColumnIndex = $xls_end_column;
|
||||||
|
$highestColumn = Coordinate::stringFromColumnIndex($highestColumnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
$startRow = max(1, $header_row);
|
$startRow = max(1, $header_row);
|
||||||
$startColumn = max(1, $start_column);
|
$startColumn = max(1, $start_column);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,35 @@ require_once 'class/db-functions.php';
|
|||||||
|
|
||||||
$response = ["success" => false, "message" => ""];
|
$response = ["success" => false, "message" => ""];
|
||||||
|
|
||||||
|
function excelColumnToIndex($column)
|
||||||
|
{
|
||||||
|
$column = strtoupper(trim((string)$column));
|
||||||
|
|
||||||
|
if ($column === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Numeric column index, example: 40
|
||||||
|
if (ctype_digit($column)) {
|
||||||
|
$index = (int)$column;
|
||||||
|
return $index > 0 ? $index : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Excel column letters, example: A, AN, XFC
|
||||||
|
if (!preg_match('/^[A-Z]+$/', $column)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$index = 0;
|
||||||
|
$length = strlen($column);
|
||||||
|
|
||||||
|
for ($i = 0; $i < $length; $i++) {
|
||||||
|
$index = ($index * 26) + (ord($column[$i]) - ord('A') + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $index;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
|
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
|
||||||
throw new Exception("Invalid request method.");
|
throw new Exception("Invalid request method.");
|
||||||
@@ -18,6 +47,8 @@ try {
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
$start_column = trim($_POST['start_column'] ?? '');
|
$start_column = trim($_POST['start_column'] ?? '');
|
||||||
|
$xls_end_column = strtoupper(trim($_POST['xls_end_column'] ?? ''));
|
||||||
|
$xls_end_column = $xls_end_column !== '' ? $xls_end_column : null;
|
||||||
|
|
||||||
$xls_sheet_index = isset($_POST['xls_sheet_index']) && $_POST['xls_sheet_index'] !== ''
|
$xls_sheet_index = isset($_POST['xls_sheet_index']) && $_POST['xls_sheet_index'] !== ''
|
||||||
? intval($_POST['xls_sheet_index'])
|
? intval($_POST['xls_sheet_index'])
|
||||||
@@ -63,6 +94,24 @@ try {
|
|||||||
throw new Exception("XLS Sheet Number cannot be negative.");
|
throw new Exception("XLS Sheet Number cannot be negative.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$startColumnIndex = excelColumnToIndex($start_column);
|
||||||
|
|
||||||
|
if ($startColumnIndex === null) {
|
||||||
|
throw new Exception("Start Column is not valid. Use Excel column letters like A, AN or a positive number.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($xls_end_column !== null) {
|
||||||
|
$endColumnIndex = excelColumnToIndex($xls_end_column);
|
||||||
|
|
||||||
|
if ($endColumnIndex === null) {
|
||||||
|
throw new Exception("Last Column is not valid. Use Excel column letters like AN or a positive number.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($endColumnIndex < $startColumnIndex) {
|
||||||
|
throw new Exception("Last Column cannot be before Start Column.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$api_config_id = null;
|
$api_config_id = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +124,7 @@ try {
|
|||||||
$header_row = null;
|
$header_row = null;
|
||||||
$start_column = null;
|
$start_column = null;
|
||||||
$xls_sheet_index = null;
|
$xls_sheet_index = null;
|
||||||
|
$xls_end_column = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// PDF currently does not require XLS coordinates or API configuration
|
// PDF currently does not require XLS coordinates or API configuration
|
||||||
@@ -82,6 +132,7 @@ try {
|
|||||||
$header_row = null;
|
$header_row = null;
|
||||||
$start_column = null;
|
$start_column = null;
|
||||||
$xls_sheet_index = null;
|
$xls_sheet_index = null;
|
||||||
|
$xls_end_column = null;
|
||||||
$api_config_id = null;
|
$api_config_id = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +163,7 @@ try {
|
|||||||
source_type,
|
source_type,
|
||||||
header_row,
|
header_row,
|
||||||
start_column,
|
start_column,
|
||||||
|
xls_end_column,
|
||||||
xls_sheet_index,
|
xls_sheet_index,
|
||||||
api_config_id,
|
api_config_id,
|
||||||
description,
|
description,
|
||||||
@@ -130,7 +182,7 @@ try {
|
|||||||
)
|
)
|
||||||
VALUES
|
VALUES
|
||||||
(
|
(
|
||||||
?, ?, ?, ?, ?, ?,
|
?, ?, ?, ?, ?, ?,?,
|
||||||
?, ?, ?, ?, ?, ?,
|
?, ?, ?, ?, ?, ?,
|
||||||
?, ?, ?, ?, ?,
|
?, ?, ?, ?, ?,
|
||||||
NOW(), NOW()
|
NOW(), NOW()
|
||||||
@@ -142,6 +194,7 @@ try {
|
|||||||
$source_type,
|
$source_type,
|
||||||
$header_row,
|
$header_row,
|
||||||
$start_column,
|
$start_column,
|
||||||
|
$xls_end_column,
|
||||||
$xls_sheet_index,
|
$xls_sheet_index,
|
||||||
$api_config_id,
|
$api_config_id,
|
||||||
$description,
|
$description,
|
||||||
|
|||||||
@@ -1,700 +0,0 @@
|
|||||||
<?php include('include/headscript.php'); ?>
|
|
||||||
<!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" />
|
|
||||||
<link href="assets/plugins/datatable/css/dataTables.bootstrap5.min.css" rel="stylesheet" />
|
|
||||||
|
|
||||||
<title>TRF-Project - Customer Reports</title>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.lookup-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
max-width: none;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lookup-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lookup-subtitle {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #6c757d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.compact-card .card-body {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-report th {
|
|
||||||
font-size: 12px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: #6c757d;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-report td {
|
|
||||||
font-size: 13px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-pill {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 5px 10px;
|
|
||||||
border-radius: 999px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-pill-success {
|
|
||||||
background: #e8fff1;
|
|
||||||
color: #198754;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-pill-warning {
|
|
||||||
background: #fff3cd;
|
|
||||||
color: #b58100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-icon-link {
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
border-radius: 10px;
|
|
||||||
background: #dc3545;
|
|
||||||
color: #fff;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 20px;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: all 0.15s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-icon-link:hover {
|
|
||||||
color: #fff;
|
|
||||||
opacity: 0.85;
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-pdf {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #adb5bd;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state {
|
|
||||||
border: 1px dashed #ced4da;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 24px;
|
|
||||||
text-align: center;
|
|
||||||
color: #6c757d;
|
|
||||||
background: #fafafa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner-inline {
|
|
||||||
display: none;
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.customer-box {
|
|
||||||
display: none;
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 12px 14px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.customer-label {
|
|
||||||
font-size: 11px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: #6c757d;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.customer-value {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #212529;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.json-preview {
|
|
||||||
display: none;
|
|
||||||
background: #111827;
|
|
||||||
color: #e5e7eb;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 14px;
|
|
||||||
font-size: 12px;
|
|
||||||
max-height: 420px;
|
|
||||||
overflow: auto;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.table-responsive {
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container {
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--default .select2-selection--single {
|
|
||||||
height: 38px;
|
|
||||||
border: 1px solid #ced4da;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--default .select2-selection--single .select2-selection__rendered {
|
|
||||||
line-height: 36px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #212529;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--default .select2-selection--single .select2-selection__arrow {
|
|
||||||
height: 36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-dropdown {
|
|
||||||
z-index: 9999;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="wrapper">
|
|
||||||
|
|
||||||
<?php include('include/navbar.php'); ?>
|
|
||||||
|
|
||||||
<?php include('include/topbar.php'); ?>
|
|
||||||
|
|
||||||
<div class="page-wrapper">
|
|
||||||
<div class="page-content">
|
|
||||||
|
|
||||||
<?php include('top_stat_widget.php'); ?>
|
|
||||||
|
|
||||||
<div class="lookup-wrapper">
|
|
||||||
|
|
||||||
<div class="card radius-10 compact-card">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
|
|
||||||
<div>
|
|
||||||
<div class="lookup-title">Customer Test Reports</div>
|
|
||||||
<div class="lookup-subtitle">
|
|
||||||
Select a VisualLims customer and retrieve the latest reports with PDF links.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-body">
|
|
||||||
<form id="customerReportsForm" class="row g-3 align-items-end">
|
|
||||||
|
|
||||||
<div class="col-md-5">
|
|
||||||
<label for="idCliente" class="form-label fw-semibold">Customer</label>
|
|
||||||
<select id="idCliente" name="idCliente" class="form-select">
|
|
||||||
<option value="">Loading customers...</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-2">
|
|
||||||
<label for="limitReports" class="form-label fw-semibold">Limit</label>
|
|
||||||
<select id="limitReports" name="limitReports" class="form-select">
|
|
||||||
<option value="1">Last 1</option>
|
|
||||||
<option value="3" selected>Last 3</option>
|
|
||||||
<option value="5">Last 5</option>
|
|
||||||
<option value="10">Last 10</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-2">
|
|
||||||
<label for="signedStatus" class="form-label fw-semibold">Status</label>
|
|
||||||
<select id="signedStatus" name="signedStatus" class="form-select">
|
|
||||||
<option value="all" selected>All</option>
|
|
||||||
<option value="signed">Signed</option>
|
|
||||||
<option value="not_signed">Not signed</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-3">
|
|
||||||
<button type="submit" id="btnSearchReports" class="btn btn-primary w-100">
|
|
||||||
<i class="bx bx-search"></i> Search Reports
|
|
||||||
<span class="spinner-border spinner-border-sm spinner-inline" id="searchSpinner" role="status" aria-hidden="true"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="customerBox" class="customer-box">
|
|
||||||
<div class="row g-3">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="customer-label">Customer Code</div>
|
|
||||||
<div class="customer-value" id="selectedCustomerCode">-</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-8">
|
|
||||||
<div class="customer-label">Customer Name</div>
|
|
||||||
<div class="customer-value" id="selectedCustomerName">-</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="resultContainer" style="display:none;">
|
|
||||||
|
|
||||||
<div class="card radius-10 compact-card">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
|
|
||||||
<h6 class="mb-0">
|
|
||||||
Reports
|
|
||||||
<span class="badge bg-light text-dark ms-1" id="reportCountBadge">0</span>
|
|
||||||
</h6>
|
|
||||||
|
|
||||||
<button type="button" id="toggleJsonBtn" class="btn btn-sm btn-outline-secondary">
|
|
||||||
<i class="bx bx-code-alt"></i> Show JSON
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-body">
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-striped table-hover table-report align-middle mb-0" id="reportsTable">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Report Number</th>
|
|
||||||
<th>Report ID</th>
|
|
||||||
<th>Report Date</th>
|
|
||||||
<th>Print Date</th>
|
|
||||||
<th>Version</th>
|
|
||||||
<th>Status</th>
|
|
||||||
<th class="text-center">PDF</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="reportsTableBody"></tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<pre id="jsonPreview" class="json-preview mt-3"></pre>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="emptyState" class="empty-state">
|
|
||||||
<i class="bx bx-file-find" style="font-size:34px;"></i>
|
|
||||||
<div class="mt-2 fw-semibold">No reports loaded</div>
|
|
||||||
<div class="small">Select a customer, choose the limit and click Search Reports.</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="overlay toggle-icon"></div>
|
|
||||||
|
|
||||||
<a href="javaScript:;" class="back-to-top">
|
|
||||||
<i class='bx bxs-up-arrow-alt'></i>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<?php include('include/footer.php'); ?>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php include('jsinclude.php'); ?>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
|
||||||
|
|
||||||
<script src="assets/plugins/datatable/js/jquery.dataTables.min.js"></script>
|
|
||||||
<script src="assets/plugins/datatable/js/dataTables.bootstrap5.min.js"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
$(document).ready(function() {
|
|
||||||
|
|
||||||
let lastJsonResponse = null;
|
|
||||||
let loadedCustomers = [];
|
|
||||||
let reportsDataTable = null;
|
|
||||||
|
|
||||||
function escapeHtml(value) {
|
|
||||||
if (value === null || value === undefined) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return String(value)
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/'/g, ''');
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatDate(value) {
|
|
||||||
if (!value) {
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
|
|
||||||
const date = new Date(value);
|
|
||||||
|
|
||||||
if (isNaN(date.getTime())) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return date.toLocaleString('it-IT', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: '2-digit',
|
|
||||||
day: '2-digit',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setLoading(isLoading) {
|
|
||||||
$('#btnSearchReports').prop('disabled', isLoading);
|
|
||||||
$('#searchSpinner').toggle(isLoading);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetResults() {
|
|
||||||
lastJsonResponse = null;
|
|
||||||
|
|
||||||
if (reportsDataTable !== null) {
|
|
||||||
reportsDataTable.destroy();
|
|
||||||
reportsDataTable = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#resultContainer').hide();
|
|
||||||
$('#emptyState').show();
|
|
||||||
$('#reportsTableBody').html('');
|
|
||||||
$('#reportCountBadge').text('0');
|
|
||||||
$('#jsonPreview').hide().text('');
|
|
||||||
$('#toggleJsonBtn').html('<i class="bx bx-code-alt"></i> Show JSON');
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadCustomers() {
|
|
||||||
$.ajax({
|
|
||||||
url: 'get_clienti.php',
|
|
||||||
method: 'GET',
|
|
||||||
dataType: 'json',
|
|
||||||
success: function(response) {
|
|
||||||
const select = $('#idCliente');
|
|
||||||
select.empty();
|
|
||||||
|
|
||||||
const customers = response.value || response || [];
|
|
||||||
loadedCustomers = Array.isArray(customers) ? customers : [];
|
|
||||||
|
|
||||||
select.append('<option value="">Select customer...</option>');
|
|
||||||
|
|
||||||
loadedCustomers.forEach(function(customer) {
|
|
||||||
const idCliente = customer.IdCliente || '';
|
|
||||||
const codiceCliente = customer.CodiceCliente || '';
|
|
||||||
const nominativo = customer.Nominativo || '';
|
|
||||||
|
|
||||||
const label = codiceCliente ?
|
|
||||||
codiceCliente + ' - ' + nominativo :
|
|
||||||
nominativo;
|
|
||||||
|
|
||||||
select.append(
|
|
||||||
'<option value="' + escapeHtml(idCliente) + '" ' +
|
|
||||||
'data-code="' + escapeHtml(codiceCliente) + '" ' +
|
|
||||||
'data-name="' + escapeHtml(nominativo) + '">' +
|
|
||||||
escapeHtml(label) +
|
|
||||||
'</option>'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if ($.fn.select2) {
|
|
||||||
select.select2({
|
|
||||||
width: '100%',
|
|
||||||
placeholder: 'Search customer...',
|
|
||||||
allowClear: true,
|
|
||||||
minimumInputLength: 0,
|
|
||||||
matcher: function(params, data) {
|
|
||||||
if ($.trim(params.term) === '') {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof data.text === 'undefined') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const term = params.term.toLowerCase();
|
|
||||||
const text = data.text.toLowerCase();
|
|
||||||
|
|
||||||
if (text.indexOf(term) > -1) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
sorter: function(data) {
|
|
||||||
const term = $('.select2-search__field').val();
|
|
||||||
|
|
||||||
if (!term) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
const search = term.toLowerCase();
|
|
||||||
|
|
||||||
return data.sort(function(a, b) {
|
|
||||||
const aText = (a.text || '').toLowerCase();
|
|
||||||
const bText = (b.text || '').toLowerCase();
|
|
||||||
|
|
||||||
const aStarts = aText.startsWith(search);
|
|
||||||
const bStarts = bText.startsWith(search);
|
|
||||||
|
|
||||||
if (aStarts && !bStarts) return -1;
|
|
||||||
if (!aStarts && bStarts) return 1;
|
|
||||||
|
|
||||||
return aText.localeCompare(bText);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function(xhr) {
|
|
||||||
$('#idCliente').html('<option value="">Error loading customers</option>');
|
|
||||||
|
|
||||||
let message = 'Unable to load customers.';
|
|
||||||
|
|
||||||
if (xhr.responseJSON && xhr.responseJSON.error) {
|
|
||||||
message = xhr.responseJSON.error;
|
|
||||||
}
|
|
||||||
|
|
||||||
Swal.fire({
|
|
||||||
title: 'Customer loading error',
|
|
||||||
text: message,
|
|
||||||
icon: 'error',
|
|
||||||
confirmButtonText: 'OK'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateSelectedCustomerBox() {
|
|
||||||
const selectedOption = $('#idCliente option:selected');
|
|
||||||
|
|
||||||
const customerCode = selectedOption.data('code') || '-';
|
|
||||||
const customerName = selectedOption.data('name') || '-';
|
|
||||||
|
|
||||||
if (!$('#idCliente').val()) {
|
|
||||||
$('#customerBox').hide();
|
|
||||||
$('#selectedCustomerCode').text('-');
|
|
||||||
$('#selectedCustomerName').text('-');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#selectedCustomerCode').text(customerCode);
|
|
||||||
$('#selectedCustomerName').text(customerName);
|
|
||||||
$('#customerBox').show();
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderStatus(isSigned) {
|
|
||||||
if (isSigned === true) {
|
|
||||||
return '<span class="status-pill status-pill-success"><i class="bx bx-check-circle"></i> Signed</span>';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSigned === false) {
|
|
||||||
return '<span class="status-pill status-pill-warning"><i class="bx bx-time"></i> Not signed</span>';
|
|
||||||
}
|
|
||||||
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderPdfCell(pdfFiles) {
|
|
||||||
if (!Array.isArray(pdfFiles) || pdfFiles.length === 0) {
|
|
||||||
return '<span class="no-pdf">No PDF</span>';
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstPdf = pdfFiles[0];
|
|
||||||
|
|
||||||
if (!firstPdf.download_url) {
|
|
||||||
return '<span class="no-pdf">No PDF</span>';
|
|
||||||
}
|
|
||||||
|
|
||||||
return `
|
|
||||||
<a href="${escapeHtml(firstPdf.download_url)}"
|
|
||||||
target="_blank"
|
|
||||||
class="pdf-icon-link"
|
|
||||||
title="${escapeHtml(firstPdf.file_name || 'Download PDF')}">
|
|
||||||
<i class="bx bxs-file-pdf"></i>
|
|
||||||
</a>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function initReportsDataTable() {
|
|
||||||
if (reportsDataTable !== null) {
|
|
||||||
reportsDataTable.destroy();
|
|
||||||
reportsDataTable = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
reportsDataTable = $('#reportsTable').DataTable({
|
|
||||||
paging: false,
|
|
||||||
searching: false,
|
|
||||||
info: false,
|
|
||||||
ordering: true,
|
|
||||||
order: [
|
|
||||||
[2, 'desc']
|
|
||||||
],
|
|
||||||
autoWidth: false,
|
|
||||||
responsive: true,
|
|
||||||
columnDefs: [{
|
|
||||||
targets: 6,
|
|
||||||
orderable: false
|
|
||||||
}],
|
|
||||||
language: {
|
|
||||||
emptyTable: 'No reports found',
|
|
||||||
zeroRecords: 'No matching reports found'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderReports(response) {
|
|
||||||
lastJsonResponse = response;
|
|
||||||
|
|
||||||
const reports = response.reports || [];
|
|
||||||
const tbody = $('#reportsTableBody');
|
|
||||||
|
|
||||||
tbody.empty();
|
|
||||||
|
|
||||||
$('#reportCountBadge').text(reports.length);
|
|
||||||
|
|
||||||
if (reports.length > 0) {
|
|
||||||
reports.forEach(function(report) {
|
|
||||||
const reportDateOrder = report.data || '';
|
|
||||||
const printDateOrder = report.data_stampa || '';
|
|
||||||
const statusOrder = report.firmato === true ? 1 : 0;
|
|
||||||
|
|
||||||
const row = `
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<strong>${escapeHtml(report.codice_rapporto || '-')}</strong>
|
|
||||||
</td>
|
|
||||||
<td>${escapeHtml(report.id_rapporto || '-')}</td>
|
|
||||||
<td data-order="${escapeHtml(reportDateOrder)}">${escapeHtml(formatDate(report.data))}</td>
|
|
||||||
<td data-order="${escapeHtml(printDateOrder)}">${escapeHtml(formatDate(report.data_stampa))}</td>
|
|
||||||
<td>${escapeHtml(report.versione !== null && report.versione !== undefined ? report.versione : '-')}</td>
|
|
||||||
<td data-order="${statusOrder}">${renderStatus(report.firmato)}</td>
|
|
||||||
<td class="text-center">${renderPdfCell(report.pdf_files)}</td>
|
|
||||||
</tr>
|
|
||||||
`;
|
|
||||||
|
|
||||||
tbody.append(row);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#jsonPreview').text(JSON.stringify(response, null, 4));
|
|
||||||
|
|
||||||
$('#emptyState').hide();
|
|
||||||
$('#resultContainer').show();
|
|
||||||
|
|
||||||
setTimeout(function() {
|
|
||||||
initReportsDataTable();
|
|
||||||
}, 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#idCliente').on('change', function() {
|
|
||||||
updateSelectedCustomerBox();
|
|
||||||
resetResults();
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#customerReportsForm').on('submit', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const idCliente = $('#idCliente').val();
|
|
||||||
const limit = $('#limitReports').val();
|
|
||||||
const signedStatus = $('#signedStatus').val();
|
|
||||||
|
|
||||||
if (!idCliente) {
|
|
||||||
Swal.fire({
|
|
||||||
title: 'Missing customer',
|
|
||||||
text: 'Please select a customer.',
|
|
||||||
icon: 'warning',
|
|
||||||
confirmButtonText: 'OK'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
resetResults();
|
|
||||||
updateSelectedCustomerBox();
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: 'get_rapporti_cliente.php',
|
|
||||||
method: 'GET',
|
|
||||||
dataType: 'json',
|
|
||||||
data: {
|
|
||||||
id_cliente: idCliente,
|
|
||||||
limit: limit,
|
|
||||||
signed_status: signedStatus
|
|
||||||
},
|
|
||||||
success: function(response) {
|
|
||||||
if (!response || response.success !== true) {
|
|
||||||
Swal.fire({
|
|
||||||
title: 'No data',
|
|
||||||
text: response && response.error ? response.error : 'No reports were returned.',
|
|
||||||
icon: 'warning',
|
|
||||||
confirmButtonText: 'OK'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderReports(response);
|
|
||||||
},
|
|
||||||
error: function(xhr) {
|
|
||||||
let message = 'Unexpected error while loading reports.';
|
|
||||||
|
|
||||||
if (xhr.responseJSON && xhr.responseJSON.error) {
|
|
||||||
message = xhr.responseJSON.error;
|
|
||||||
} else if (xhr.responseText) {
|
|
||||||
message = xhr.responseText.substring(0, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
Swal.fire({
|
|
||||||
title: 'Error',
|
|
||||||
text: message,
|
|
||||||
icon: 'error',
|
|
||||||
confirmButtonText: 'OK'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
complete: function() {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#toggleJsonBtn').on('click', function() {
|
|
||||||
const jsonPreview = $('#jsonPreview');
|
|
||||||
const isVisible = jsonPreview.is(':visible');
|
|
||||||
|
|
||||||
jsonPreview.toggle(!isVisible);
|
|
||||||
|
|
||||||
if (isVisible) {
|
|
||||||
$(this).html('<i class="bx bx-code-alt"></i> Show JSON');
|
|
||||||
} else {
|
|
||||||
$(this).html('<i class="bx bx-hide"></i> Hide JSON');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
loadCustomers();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1,580 +0,0 @@
|
|||||||
<?php include('include/headscript.php'); ?>
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<!-- Required meta tags -->
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
|
|
||||||
<!--favicon-->
|
|
||||||
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
|
||||||
|
|
||||||
<?php include('cssinclude.php'); ?>
|
|
||||||
|
|
||||||
<title>TRF-Project - Test Report Lookup</title>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.compact-card .card-body {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lookup-wrapper {
|
|
||||||
max-width: 1100px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lookup-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lookup-subtitle {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #6c757d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-section-title {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
color: #344767;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-box {
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 14px;
|
|
||||||
background: #fff;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-label {
|
|
||||||
font-size: 11px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: #6c757d;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-value {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #212529;
|
|
||||||
font-weight: 600;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-pill {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 5px 10px;
|
|
||||||
border-radius: 999px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-pill-success {
|
|
||||||
background: #e8fff1;
|
|
||||||
color: #198754;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-pill-warning {
|
|
||||||
background: #fff3cd;
|
|
||||||
color: #b58100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-card {
|
|
||||||
border: 1px solid #f1d1d1;
|
|
||||||
background: #fffafa;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 14px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-icon {
|
|
||||||
width: 46px;
|
|
||||||
height: 46px;
|
|
||||||
border-radius: 12px;
|
|
||||||
background: #dc3545;
|
|
||||||
color: #fff;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 24px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-file-name {
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf-meta {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #6c757d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.json-preview {
|
|
||||||
display: none;
|
|
||||||
background: #111827;
|
|
||||||
color: #e5e7eb;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 14px;
|
|
||||||
font-size: 12px;
|
|
||||||
max-height: 420px;
|
|
||||||
overflow: auto;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state {
|
|
||||||
border: 1px dashed #ced4da;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 24px;
|
|
||||||
text-align: center;
|
|
||||||
color: #6c757d;
|
|
||||||
background: #fafafa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner-inline {
|
|
||||||
display: none;
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-download-pdf {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.pdf-card {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-download-pdf {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<!--wrapper-->
|
|
||||||
<div class="wrapper">
|
|
||||||
|
|
||||||
<!--sidebar wrapper -->
|
|
||||||
<?php include('include/navbar.php'); ?>
|
|
||||||
<!--end sidebar wrapper -->
|
|
||||||
|
|
||||||
<!--start header -->
|
|
||||||
<?php include('include/topbar.php'); ?>
|
|
||||||
<!--end header -->
|
|
||||||
|
|
||||||
<!--start page wrapper -->
|
|
||||||
<div class="page-wrapper">
|
|
||||||
<div class="page-content">
|
|
||||||
|
|
||||||
<?php include('top_stat_widget.php'); ?>
|
|
||||||
|
|
||||||
<div class="lookup-wrapper">
|
|
||||||
|
|
||||||
<div class="card radius-10 compact-card">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
|
|
||||||
<div>
|
|
||||||
<div class="lookup-title">Test Report Lookup</div>
|
|
||||||
<div class="lookup-subtitle">
|
|
||||||
Search a test report from VisualLims by report number and download the PDF if available.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-body">
|
|
||||||
|
|
||||||
<form id="reportSearchForm" class="row g-3 align-items-end">
|
|
||||||
<div class="col-md-8">
|
|
||||||
<label for="codiceRapporto" class="form-label fw-semibold">
|
|
||||||
Report Number
|
|
||||||
</label>
|
|
||||||
<input type="text"
|
|
||||||
class="form-control"
|
|
||||||
id="codiceRapporto"
|
|
||||||
name="codiceRapporto"
|
|
||||||
placeholder="Example: 2621521"
|
|
||||||
autocomplete="off">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-4">
|
|
||||||
<button type="submit" id="btnSearchReport" class="btn btn-primary w-100">
|
|
||||||
<i class="bx bx-search"></i> Proceed
|
|
||||||
<span class="spinner-border spinner-border-sm spinner-inline" id="searchSpinner" role="status" aria-hidden="true"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="resultContainer" style="display:none;">
|
|
||||||
|
|
||||||
<div class="card radius-10 compact-card">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
|
|
||||||
<h6 class="mb-0">Report Data</h6>
|
|
||||||
|
|
||||||
<button type="button" id="toggleJsonBtn" class="btn btn-sm btn-outline-secondary">
|
|
||||||
<i class="bx bx-code-alt"></i> Show JSON
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-body">
|
|
||||||
|
|
||||||
<div class="result-section-title">General Information</div>
|
|
||||||
|
|
||||||
<div class="row g-3 mb-4">
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="info-box">
|
|
||||||
<div class="info-label">Report Number</div>
|
|
||||||
<div class="info-value" id="resCodiceRapporto">-</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="info-box">
|
|
||||||
<div class="info-label">Report ID</div>
|
|
||||||
<div class="info-value" id="resIdRapporto">-</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="info-box">
|
|
||||||
<div class="info-label">Customer Code</div>
|
|
||||||
<div class="info-value" id="resCodiceCliente">-</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="info-box">
|
|
||||||
<div class="info-label">Customer Name</div>
|
|
||||||
<div class="info-value" id="resNominativoCliente">-</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="info-box">
|
|
||||||
<div class="info-label">Report Date</div>
|
|
||||||
<div class="info-value" id="resDataRapporto">-</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="info-box">
|
|
||||||
<div class="info-label">Print Date</div>
|
|
||||||
<div class="info-value" id="resDataStampa">-</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="info-box">
|
|
||||||
<div class="info-label">Version</div>
|
|
||||||
<div class="info-value" id="resVersione">-</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="info-box">
|
|
||||||
<div class="info-label">Signed</div>
|
|
||||||
<div class="info-value" id="resFirmato">-</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="info-box">
|
|
||||||
<div class="info-label">Detail Endpoint</div>
|
|
||||||
<div class="info-value small" id="resDetailEndpoint">-</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="result-section-title">Available PDF Files</div>
|
|
||||||
|
|
||||||
<div id="pdfFilesContainer" class="mb-4"></div>
|
|
||||||
|
|
||||||
<pre id="jsonPreview" class="json-preview"></pre>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="emptyState" class="empty-state">
|
|
||||||
<i class="bx bx-file-find" style="font-size:34px;"></i>
|
|
||||||
<div class="mt-2 fw-semibold">No report loaded</div>
|
|
||||||
<div class="small">Enter a report number and click Proceed.</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!--end page wrapper -->
|
|
||||||
|
|
||||||
<!--start overlay-->
|
|
||||||
<div class="overlay toggle-icon"></div>
|
|
||||||
<!--end overlay-->
|
|
||||||
|
|
||||||
<!--Start Back To Top Button-->
|
|
||||||
<a href="javaScript:;" class="back-to-top">
|
|
||||||
<i class='bx bxs-up-arrow-alt'></i>
|
|
||||||
</a>
|
|
||||||
<!--End Back To Top Button-->
|
|
||||||
|
|
||||||
<?php include('include/footer.php'); ?>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<!--end wrapper-->
|
|
||||||
|
|
||||||
<?php include('jsinclude.php'); ?>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
$(document).ready(function() {
|
|
||||||
|
|
||||||
let lastJsonResponse = null;
|
|
||||||
|
|
||||||
function escapeHtml(value) {
|
|
||||||
if (value === null || value === undefined) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return String(value)
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/'/g, ''');
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatDate(value) {
|
|
||||||
if (!value) {
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
|
|
||||||
const date = new Date(value);
|
|
||||||
|
|
||||||
if (isNaN(date.getTime())) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return date.toLocaleString('it-IT', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: '2-digit',
|
|
||||||
day: '2-digit',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setLoading(isLoading) {
|
|
||||||
$('#btnSearchReport').prop('disabled', isLoading);
|
|
||||||
$('#searchSpinner').toggle(isLoading);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetResult() {
|
|
||||||
lastJsonResponse = null;
|
|
||||||
|
|
||||||
$('#resultContainer').hide();
|
|
||||||
$('#emptyState').show();
|
|
||||||
$('#jsonPreview').hide().text('');
|
|
||||||
$('#toggleJsonBtn').html('<i class="bx bx-code-alt"></i> Show JSON');
|
|
||||||
|
|
||||||
$('#resCodiceRapporto').text('-');
|
|
||||||
$('#resIdRapporto').text('-');
|
|
||||||
$('#resCodiceCliente').text('-');
|
|
||||||
$('#resNominativoCliente').text('-');
|
|
||||||
$('#resDataRapporto').text('-');
|
|
||||||
$('#resDataStampa').text('-');
|
|
||||||
$('#resVersione').text('-');
|
|
||||||
$('#resFirmato').text('-');
|
|
||||||
$('#resDetailEndpoint').text('-');
|
|
||||||
$('#pdfFilesContainer').html('');
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderPdfFiles(pdfFiles) {
|
|
||||||
const container = $('#pdfFilesContainer');
|
|
||||||
container.empty();
|
|
||||||
|
|
||||||
if (!Array.isArray(pdfFiles) || pdfFiles.length === 0) {
|
|
||||||
container.html(`
|
|
||||||
<div class="empty-state">
|
|
||||||
<i class="bx bx-file-blank" style="font-size:28px;"></i>
|
|
||||||
<div class="mt-2 fw-semibold">No PDF available</div>
|
|
||||||
<div class="small">No report PDF file was returned by VisualLims.</div>
|
|
||||||
</div>
|
|
||||||
`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pdfFiles.forEach(function(file) {
|
|
||||||
const idRapportoFile = file.id_rapporto_file || '';
|
|
||||||
const fileName = file.file_name || 'report.pdf';
|
|
||||||
const categoria = file.categoria || '-';
|
|
||||||
const tipoRapporto = file.tipo_rapporto || '-';
|
|
||||||
|
|
||||||
const downloadUrl = 'download_rapporto_pdf.php?id_rapporto_file=' + encodeURIComponent(idRapportoFile);
|
|
||||||
|
|
||||||
const html = `
|
|
||||||
<div class="pdf-card mb-2">
|
|
||||||
<div class="d-flex align-items-center gap-3">
|
|
||||||
<div class="pdf-icon">
|
|
||||||
<i class="bx bxs-file-pdf"></i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div class="pdf-file-name">${escapeHtml(fileName)}</div>
|
|
||||||
<div class="pdf-meta">
|
|
||||||
ID File: ${escapeHtml(idRapportoFile)}
|
|
||||||
|
|
|
||||||
Category: ${escapeHtml(categoria)}
|
|
||||||
|
|
|
||||||
Type: ${escapeHtml(tipoRapporto)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a href="${downloadUrl}" target="_blank" class="btn btn-danger btn-sm btn-download-pdf">
|
|
||||||
<i class="bx bx-download"></i> Download PDF
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
container.append(html);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderResult(response) {
|
|
||||||
lastJsonResponse = response;
|
|
||||||
|
|
||||||
const base = response.rapporto_base || {};
|
|
||||||
const data = response.data || {};
|
|
||||||
const cliente = response.cliente || data.Cliente || {};
|
|
||||||
|
|
||||||
$('#resCodiceRapporto').text(response.codice_rapporto || base.CodiceRapporto || '-');
|
|
||||||
$('#resIdRapporto').text(response.id_rapporto || base.IdRapporto || '-');
|
|
||||||
$('#resCodiceCliente').text(cliente.CodiceCliente || '-');
|
|
||||||
$('#resNominativoCliente').text(cliente.Nominativo || '-');
|
|
||||||
$('#resDataRapporto').text(formatDate(base.Data || data.Data));
|
|
||||||
$('#resDataStampa').text(formatDate(base.DataStampa || data.DataStampa));
|
|
||||||
$('#resVersione').text(base.Versione !== undefined ? base.Versione : (data.Versione !== undefined ? data.Versione : '-'));
|
|
||||||
$('#resDetailEndpoint').text(response.detail_endpoint || '-');
|
|
||||||
|
|
||||||
const isSigned = base.Firmato === true || data.Firmato === true;
|
|
||||||
|
|
||||||
if (isSigned) {
|
|
||||||
$('#resFirmato').html('<span class="status-pill status-pill-success"><i class="bx bx-check-circle"></i> Signed</span>');
|
|
||||||
} else {
|
|
||||||
$('#resFirmato').html('<span class="status-pill status-pill-warning"><i class="bx bx-time"></i> Not signed</span>');
|
|
||||||
}
|
|
||||||
|
|
||||||
renderPdfFiles(response.pdf_files || []);
|
|
||||||
|
|
||||||
$('#jsonPreview').text(JSON.stringify(response, null, 4));
|
|
||||||
|
|
||||||
$('#emptyState').hide();
|
|
||||||
$('#resultContainer').show();
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#reportSearchForm').on('submit', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const codiceRapporto = $('#codiceRapporto').val().trim();
|
|
||||||
|
|
||||||
if (!codiceRapporto) {
|
|
||||||
Swal.fire({
|
|
||||||
title: 'Missing report number',
|
|
||||||
text: 'Please enter a report number.',
|
|
||||||
icon: 'warning',
|
|
||||||
confirmButtonText: 'OK'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
resetResult();
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: 'get_rapporto_prova.php',
|
|
||||||
method: 'GET',
|
|
||||||
dataType: 'json',
|
|
||||||
data: {
|
|
||||||
codice: codiceRapporto,
|
|
||||||
step: 'files'
|
|
||||||
},
|
|
||||||
success: function(response) {
|
|
||||||
if (!response || response.success !== true) {
|
|
||||||
Swal.fire({
|
|
||||||
title: 'Report not found',
|
|
||||||
text: response && response.message ? response.message : 'No report was found for this number.',
|
|
||||||
icon: 'warning',
|
|
||||||
confirmButtonText: 'OK'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderResult(response);
|
|
||||||
},
|
|
||||||
error: function(xhr) {
|
|
||||||
let message = 'Unexpected error while loading the report.';
|
|
||||||
|
|
||||||
if (xhr.responseJSON && xhr.responseJSON.error) {
|
|
||||||
message = xhr.responseJSON.error;
|
|
||||||
} else if (xhr.responseJSON && xhr.responseJSON.message) {
|
|
||||||
message = xhr.responseJSON.message;
|
|
||||||
} else if (xhr.responseText) {
|
|
||||||
message = xhr.responseText.substring(0, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
Swal.fire({
|
|
||||||
title: 'Error',
|
|
||||||
text: message,
|
|
||||||
icon: 'error',
|
|
||||||
confirmButtonText: 'OK'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
complete: function() {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#toggleJsonBtn').on('click', function() {
|
|
||||||
const jsonPreview = $('#jsonPreview');
|
|
||||||
const isVisible = jsonPreview.is(':visible');
|
|
||||||
|
|
||||||
jsonPreview.toggle(!isVisible);
|
|
||||||
|
|
||||||
if (isVisible) {
|
|
||||||
$(this).html('<i class="bx bx-code-alt"></i> Show JSON');
|
|
||||||
} else {
|
|
||||||
$(this).html('<i class="bx bx-hide"></i> Hide JSON');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
const prefillCodice = urlParams.get('codice');
|
|
||||||
|
|
||||||
if (prefillCodice) {
|
|
||||||
$('#codiceRapporto').val(prefillCodice);
|
|
||||||
$('#reportSearchForm').trigger('submit');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1,385 +0,0 @@
|
|||||||
<?php
|
|
||||||
include('include/headscript.php');
|
|
||||||
|
|
||||||
// Binding preparati da import_insert.php (da risolvere + collegati in automatico).
|
|
||||||
$pending = $_SESSION['pending_bindings'] ?? null;
|
|
||||||
|
|
||||||
if (empty($pending) || (empty($pending['items']) && empty($pending['auto']) && empty($pending['saved']))) {
|
|
||||||
// Niente da mostrare: vado alla griglia.
|
|
||||||
$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 = $pending['items'] ?? [];
|
|
||||||
$autoItems = $pending['auto'] ?? [];
|
|
||||||
$savedItems = $pending['saved'] ?? [];
|
|
||||||
|
|
||||||
// Righe gia' risolte (modificabili): auto-collegate + binding gia' salvati.
|
|
||||||
$resolvedItems = [];
|
|
||||||
foreach ($autoItems as $a) {
|
|
||||||
$a['badge'] = 'auto';
|
|
||||||
$a['badge_class'] = 'bg-success';
|
|
||||||
$resolvedItems[] = $a;
|
|
||||||
}
|
|
||||||
foreach ($savedItems as $s) {
|
|
||||||
$s['badge'] = 'salvato';
|
|
||||||
$s['badge_class'] = 'bg-secondary';
|
|
||||||
$resolvedItems[] = $s;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Raggruppa le righe per campo (custom: mapping_id, fixed: fixed_field_key),
|
|
||||||
// mantenendo l'ordine di prima apparizione. Ogni gruppo elenca prima i pending.
|
|
||||||
$groups = [];
|
|
||||||
$groupOrder = [];
|
|
||||||
$pushToGroup = function (array $row, string $type, $idx = null) use (&$groups, &$groupOrder) {
|
|
||||||
$kind = $row['kind'] ?? 'custom';
|
|
||||||
$gkey = $kind === 'fixed'
|
|
||||||
? 'fx:' . ($row['fixed_field_key'] ?? '')
|
|
||||||
: 'cf:' . (int) ($row['mapping_id'] ?? 0);
|
|
||||||
if (!isset($groups[$gkey])) {
|
|
||||||
$groups[$gkey] = [
|
|
||||||
'label' => $row['field_label'] ?? $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');
|
|
||||||
}
|
|
||||||
|
|
||||||
$db = DBHandlerSelect::getInstance();
|
|
||||||
$pdo = $db->getConnection();
|
|
||||||
$stmt = $pdo->prepare("SELECT name FROM excel_templates WHERE id = ?");
|
|
||||||
$stmt->execute([$templateId]);
|
|
||||||
$templateName = $stmt->fetchColumn() ?: ('Template ' . $templateId);
|
|
||||||
?>
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
|
||||||
<?php include('cssinclude.php'); ?>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
.json-value {
|
|
||||||
font-family: Consolas, Monaco, monospace;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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 JSON → LIMS - <?= htmlspecialchars($titlewebsite ?? '', ENT_QUOTES, 'UTF-8'); ?></title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="wrapper">
|
|
||||||
<?php include('include/navbar.php'); ?>
|
|
||||||
<?php include('include/topbar.php'); ?>
|
|
||||||
<div class="page-wrapper">
|
|
||||||
<div class="page-content">
|
|
||||||
|
|
||||||
<div class="card radius-10">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
|
|
||||||
<div>
|
|
||||||
<h6 class="mb-0">Binding JSON → 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">
|
|
||||||
<?php if (!empty($items)): ?>
|
|
||||||
<div class="alert alert-info">
|
|
||||||
Alcuni valori importati dal JSON non hanno ancora una corrispondenza con i valori del LIMS.
|
|
||||||
Seleziona il valore LIMS corretto per ciascuno e conferma. I binding verranno salvati e
|
|
||||||
riutilizzati nelle importazioni successive.
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?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 JSON</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="4">
|
|
||||||
<strong><?= htmlspecialchars($g['label']) ?></strong>
|
|
||||||
<?php if ($g['kind'] === 'fixed'): ?><span class="badge bg-light text-dark border">fixed</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']; $kind = $item['kind'] ?? 'custom'; ?>
|
|
||||||
<tr class="binding-row"
|
|
||||||
data-index="<?= $idx ?>"
|
|
||||||
data-kind="<?= $kind ?>"
|
|
||||||
<?php if ($kind === 'fixed'): ?>
|
|
||||||
data-fixed-key="<?= htmlspecialchars($item['fixed_field_key'], ENT_QUOTES) ?>"
|
|
||||||
<?php else: ?>
|
|
||||||
data-mapping-id="<?= (int) ($item['mapping_id'] ?? 0) ?>"
|
|
||||||
data-field-id="<?= (int) ($item['field_id'] ?? 0) ?>"
|
|
||||||
<?php endif; ?>
|
|
||||||
data-json-value="<?= htmlspecialchars($item['json_value'], ENT_QUOTES) ?>"
|
|
||||||
data-datadb-ids="<?= htmlspecialchars(json_encode($item['datadb_ids']), ENT_QUOTES) ?>">
|
|
||||||
<td class="text-muted ps-4">›</td>
|
|
||||||
<td class="json-value"><?= htmlspecialchars($item['json_value']) ?></td>
|
|
||||||
<td>
|
|
||||||
<select class="form-select binding-select">
|
|
||||||
<option value="">Seleziona valore LIMS...</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary skip-binding-btn">Nessuna corrispondenza</button>
|
|
||||||
<div class="binding-status text-muted mt-1">In attesa</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
|
|
||||||
<?php foreach ($g['resolved'] as $r): $res = $r['data']; $kind = $res['kind'] ?? 'custom'; ?>
|
|
||||||
<tr class="binding-row"
|
|
||||||
data-kind="<?= $kind ?>"
|
|
||||||
<?php if ($kind === 'fixed'): ?>
|
|
||||||
data-fixed-key="<?= htmlspecialchars($res['fixed_field_key'], ENT_QUOTES) ?>"
|
|
||||||
<?php else: ?>
|
|
||||||
data-mapping-id="<?= (int) ($res['mapping_id'] ?? 0) ?>"
|
|
||||||
data-field-id="<?= (int) ($res['field_id'] ?? 0) ?>"
|
|
||||||
<?php endif; ?>
|
|
||||||
data-json-value="<?= htmlspecialchars($res['json_value'], ENT_QUOTES) ?>"
|
|
||||||
data-datadb-ids="<?= htmlspecialchars(json_encode($res['datadb_ids']), ENT_QUOTES) ?>">
|
|
||||||
<td class="text-muted ps-4">›</td>
|
|
||||||
<td class="json-value"><?= htmlspecialchars($res['json_value']) ?></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="imported.php?id=<?= $templateId ?>&importref=<?= urlencode($importRef) ?>"
|
|
||||||
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 = 'imported.php?id=' + TEMPLATE_ID + '&importref=' + encodeURIComponent(IMPORT_REF);
|
|
||||||
|
|
||||||
const $form = $('#bindingForm');
|
|
||||||
const $btn = $('#confirmBindingsBtn');
|
|
||||||
const $error = $('#bindingError');
|
|
||||||
|
|
||||||
// Dropdown valori LIMS per riga: sorgente custom vs fixed.
|
|
||||||
$('.binding-select').each(function() {
|
|
||||||
const $row = $(this).closest('.binding-row');
|
|
||||||
const kind = $row.data('kind');
|
|
||||||
const isFixed = 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
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Una riga e' pronta se ha un valore scelto oppure e' marcata "nessuna corrispondenza".
|
|
||||||
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();
|
|
||||||
|
|
||||||
// "Nessuna corrispondenza": azzera il valore importato, nessun binding salvato.
|
|
||||||
$('.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.text(skipped ? 'Nessuna corrispondenza' : 'In attesa');
|
|
||||||
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 kind = $row.data('kind');
|
|
||||||
const isFixed = kind === 'fixed';
|
|
||||||
const datadbIds = JSON.stringify($row.data('datadb-ids') || []);
|
|
||||||
const jsonValue = String($row.data('json-value'));
|
|
||||||
|
|
||||||
const targetFields = isFixed
|
|
||||||
? { kind: 'fixed', fixed_field_key: $row.data('fixed-key') }
|
|
||||||
: { kind: 'custom', mapping_id: $row.data('mapping-id'), field_id: $row.data('field-id') };
|
|
||||||
|
|
||||||
// Riga senza corrispondenza: azzera il valore, niente binding.
|
|
||||||
if ($row.hasClass('is-skipped')) {
|
|
||||||
return $.post('skip_binding.php', {
|
|
||||||
...targetFields,
|
|
||||||
template_id: TEMPLATE_ID,
|
|
||||||
json_value: jsonValue,
|
|
||||||
datadb_ids: datadbIds
|
|
||||||
}).then(resp => {
|
|
||||||
if (resp && resp.success) {
|
|
||||||
$status.text('Azzerato').removeClass('text-muted').addClass('text-success');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
$status.text('Errore').addClass('text-danger');
|
|
||||||
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', {
|
|
||||||
...targetFields,
|
|
||||||
template_id: TEMPLATE_ID,
|
|
||||||
json_value: jsonValue,
|
|
||||||
lims_value_id: $select.val(),
|
|
||||||
lims_value: selectedData.text || '',
|
|
||||||
datadb_ids: datadbIds
|
|
||||||
}).then(resp => {
|
|
||||||
if (resp && resp.success) {
|
|
||||||
$status.text('Salvato').removeClass('text-muted').addClass('text-success');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
$status.text('Errore').addClass('text-danger');
|
|
||||||
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>
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
// Salva un binding JSON -> LIMS (custom o fixed) e lo applica ai record appena importati. Ritorna JSON.
|
|
||||||
|
|
||||||
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);
|
|
||||||
$jsonValue = (string) ($_POST['json_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 || $jsonValue === '' || $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') {
|
|
||||||
$fixedKey = trim($_POST['fixed_field_key'] ?? '');
|
|
||||||
$column = binding_fixed_column($fixedKey);
|
|
||||||
if ($fixedKey === '' || !binding_fixed_is_list($fixedKey) || !$column) {
|
|
||||||
http_response_code(422);
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Invalid fixed field']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
binding_upsert_fixed($pdo, $templateId, $fixedKey, $jsonValue, $limsValueId, $limsValue, $createdBy);
|
|
||||||
$applied = !empty($datadbIds) ? binding_apply_to_datadb($pdo, $column, $limsValueId, $datadbIds) : 0;
|
|
||||||
} else {
|
|
||||||
$mappingId = intval($_POST['mapping_id'] ?? 0);
|
|
||||||
$fieldId = intval($_POST['field_id'] ?? 0);
|
|
||||||
if ($mappingId <= 0) {
|
|
||||||
http_response_code(422);
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Missing mapping_id']);
|
|
||||||
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, $jsonValue, $limsValueId, $limsValue, $createdBy);
|
|
||||||
$applied = !empty($datadbIds) ? binding_apply_to_details($pdo, $mappingId, $limsValue, $datadbIds) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
'success' => true,
|
|
||||||
'applied_rows' => $applied,
|
|
||||||
'kind' => $kind,
|
|
||||||
'json_value' => $jsonValue,
|
|
||||||
'lims_value' => $limsValue,
|
|
||||||
'lims_value_id' => $limsValueId,
|
|
||||||
]);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
http_response_code(500);
|
|
||||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
// Select2 source of LIMS values for a fixed field. Returns {results:[{id,text}]}.
|
|
||||||
// Params: field_key (required), template_id (required, drives client-dependent lists),
|
|
||||||
// q (search), id (resolve a single value for preselect), limit.
|
|
||||||
|
|
||||||
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 = intval($_GET['template_id'] ?? 0);
|
|
||||||
$q = mb_strtolower(trim($_GET['q'] ?? ''));
|
|
||||||
$id = isset($_GET['id']) ? intval($_GET['id']) : null;
|
|
||||||
$rawLimit = intval($_GET['limit'] ?? 30);
|
|
||||||
$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 ($id !== null) {
|
|
||||||
foreach ($values as $v) {
|
|
||||||
if ((int) $v['id'] === $id) {
|
|
||||||
echo json_encode(['results' => [['id' => $v['id'], 'text' => $v['text']]]]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
echo json_encode(['results' => []]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$results = [];
|
|
||||||
foreach ($values as $v) {
|
|
||||||
$text = (string) $v['text'];
|
|
||||||
if ($q === '' || mb_strpos(mb_strtolower($text), $q) !== false) {
|
|
||||||
$results[] = ['id' => $v['id'], 'text' => $text];
|
|
||||||
if ($limit > 0 && count($results) >= $limit) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
usort($results, fn($a, $b) => strcasecmp($a['text'], $b['text']));
|
|
||||||
echo json_encode(['results' => $results]);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
http_response_code(500);
|
|
||||||
echo json_encode(['error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
// "Nessuna corrispondenza": azzera il valore importato (custom o fixed) e rimuove il binding. Ritorna JSON.
|
|
||||||
|
|
||||||
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);
|
|
||||||
$jsonValue = (string) ($_POST['json_value'] ?? '');
|
|
||||||
|
|
||||||
$datadbIds = [];
|
|
||||||
if (isset($_POST['datadb_ids'])) {
|
|
||||||
$decoded = json_decode($_POST['datadb_ids'], true);
|
|
||||||
if (is_array($decoded)) {
|
|
||||||
$datadbIds = $decoded;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($jsonValue === '') {
|
|
||||||
http_response_code(422);
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Missing json_value']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
|
||||||
|
|
||||||
if ($kind === 'fixed') {
|
|
||||||
$fixedKey = trim($_POST['fixed_field_key'] ?? '');
|
|
||||||
$column = binding_fixed_column($fixedKey);
|
|
||||||
if ($fixedKey === '' || !binding_fixed_is_list($fixedKey) || !$column || $templateId <= 0) {
|
|
||||||
http_response_code(422);
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Invalid fixed field']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
$cleared = !empty($datadbIds) ? binding_apply_to_datadb($pdo, $column, null, $datadbIds) : 0;
|
|
||||||
binding_delete_target($pdo, binding_target_fixed($templateId, $fixedKey), $jsonValue);
|
|
||||||
} else {
|
|
||||||
$mappingId = intval($_POST['mapping_id'] ?? 0);
|
|
||||||
if ($mappingId <= 0) {
|
|
||||||
http_response_code(422);
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Missing mapping_id']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
$cleared = !empty($datadbIds) ? binding_apply_to_details($pdo, $mappingId, '', $datadbIds) : 0;
|
|
||||||
binding_delete_target($pdo, binding_target_custom($mappingId), $jsonValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'cleared_rows' => $cleared]);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
http_response_code(500);
|
|
||||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
@@ -78,51 +78,6 @@
|
|||||||
color: #198754;
|
color: #198754;
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge-source-pdf {
|
|
||||||
background-color: #fff3cd;
|
|
||||||
color: #b58100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-filter-bar {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-filter-btn {
|
|
||||||
border: 0;
|
|
||||||
border-radius: 999px;
|
|
||||||
padding: 7px 14px;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #fff;
|
|
||||||
opacity: 0.35;
|
|
||||||
transition: all 0.15s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-filter-btn.active {
|
|
||||||
opacity: 1;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-filter-btn[data-type="XLS"] {
|
|
||||||
background-color: #0d6efd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-filter-btn[data-type="API"] {
|
|
||||||
background-color: #198754;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-filter-btn[data-type="PDF"] {
|
|
||||||
background-color: #b58100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-filter-btn:hover {
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
#xlsTemplatesTable {
|
#xlsTemplatesTable {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
@@ -181,41 +136,28 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
<div class="type-filter-bar">
|
|
||||||
<span class="text-muted fw-semibold me-1">Filter by type:</span>
|
|
||||||
|
|
||||||
<button type="button" class="type-filter-btn active" data-type="XLS">
|
|
||||||
XLS
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button type="button" class="type-filter-btn active" data-type="API">
|
|
||||||
JSON/API
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button type="button" class="type-filter-btn active" data-type="PDF">
|
|
||||||
PDF
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table id="xlsTemplatesTable" class="table table-striped table-bordered align-middle w-100">
|
<table id="xlsTemplatesTable" class="table table-striped table-bordered table-sm w-100">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Actions</th>
|
<th><?= htmlspecialchars($action, ENT_QUOTES, 'UTF-8'); ?></th>
|
||||||
<th>Template Name</th>
|
<th><?= htmlspecialchars($nametemplate, ENT_QUOTES, 'UTF-8'); ?></th>
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
<th>Row</th>
|
<th>Row</th>
|
||||||
<th>Col</th>
|
<th>Col</th>
|
||||||
<th>Description</th>
|
<th><?= htmlspecialchars($desctemplate, ENT_QUOTES, 'UTF-8'); ?></th>
|
||||||
<th>Client</th>
|
<th>Client</th>
|
||||||
<th>Button</th>
|
<th>Button</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody></tbody>
|
<tbody>
|
||||||
|
<!-- DataTables will populate this section automatically -->
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -253,7 +195,7 @@
|
|||||||
const cleanUrl = window.location.pathname;
|
const cleanUrl = window.location.pathname;
|
||||||
window.history.replaceState({}, document.title, cleanUrl);
|
window.history.replaceState({}, document.title, cleanUrl);
|
||||||
}
|
}
|
||||||
const templatesTable = $('#xlsTemplatesTable').DataTable({
|
$('#xlsTemplatesTable').DataTable({
|
||||||
processing: true,
|
processing: true,
|
||||||
serverSide: false,
|
serverSide: false,
|
||||||
ajax: 'load_templates.php',
|
ajax: 'load_templates.php',
|
||||||
@@ -297,22 +239,12 @@
|
|||||||
title: "Type",
|
title: "Type",
|
||||||
className: "text-center",
|
className: "text-center",
|
||||||
render: function(data, type, row) {
|
render: function(data, type, row) {
|
||||||
let sourceType = (data || 'XLS').toUpperCase();
|
const sourceType = (data || 'XLS').toUpperCase();
|
||||||
|
|
||||||
// Treat JSON as API group for dashboard filter
|
|
||||||
if (sourceType === 'JSON') {
|
|
||||||
sourceType = 'API';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'display') {
|
if (type === 'display') {
|
||||||
if (sourceType === 'API') {
|
if (sourceType === 'API') {
|
||||||
return '<span class="badge-source badge-source-api">JSON/API</span>';
|
return '<span class="badge-source badge-source-api">API</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sourceType === 'PDF') {
|
|
||||||
return '<span class="badge-source badge-source-pdf">PDF</span>';
|
|
||||||
}
|
|
||||||
|
|
||||||
return '<span class="badge-source badge-source-xls">XLS</span>';
|
return '<span class="badge-source badge-source-xls">XLS</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,44 +319,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const activeSourceTypes = {
|
|
||||||
XLS: true,
|
|
||||||
API: true,
|
|
||||||
PDF: true
|
|
||||||
};
|
|
||||||
|
|
||||||
$.fn.dataTable.ext.search.push(function(settings, data, dataIndex) {
|
|
||||||
if (settings.nTable.id !== 'xlsTemplatesTable') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const api = new $.fn.dataTable.Api(settings);
|
|
||||||
const rowData = api.row(dataIndex).data();
|
|
||||||
|
|
||||||
let sourceType = ((rowData && rowData.source_type) ? rowData.source_type : 'XLS').toUpperCase();
|
|
||||||
|
|
||||||
if (sourceType === 'JSON') {
|
|
||||||
sourceType = 'API';
|
|
||||||
}
|
|
||||||
|
|
||||||
return activeSourceTypes[sourceType] === true;
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.type-filter-btn').on('click', function() {
|
|
||||||
const type = $(this).data('type');
|
|
||||||
|
|
||||||
activeSourceTypes[type] = !activeSourceTypes[type];
|
|
||||||
$(this).toggleClass('active', activeSourceTypes[type]);
|
|
||||||
|
|
||||||
const hasAtLeastOneActive = Object.values(activeSourceTypes).some(Boolean);
|
|
||||||
|
|
||||||
if (!hasAtLeastOneActive) {
|
|
||||||
activeSourceTypes[type] = true;
|
|
||||||
$(this).addClass('active');
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#xlsTemplatesTable').DataTable().draw();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function confirmDelete(id) {
|
function confirmDelete(id) {
|
||||||
|
|||||||
@@ -3,84 +3,33 @@ header('Content-Type: application/json');
|
|||||||
|
|
||||||
require_once(__DIR__ . '/include/headscript.php');
|
require_once(__DIR__ . '/include/headscript.php');
|
||||||
|
|
||||||
try {
|
$db = DBHandlerSelect::getInstance();
|
||||||
$db = DBHandlerSelect::getInstance();
|
$pdo = $db->getConnection();
|
||||||
$pdo = $db->getConnection();
|
|
||||||
|
|
||||||
$input = json_decode(file_get_contents('php://input'), true);
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
$id = (int)($input['id'] ?? 0);
|
$id = (int)($input['id'] ?? 0);
|
||||||
$field = (string)($input['field'] ?? '');
|
$field = (string)($input['field'] ?? '');
|
||||||
$value = $input['value'] ?? null;
|
$value = $input['value'] ?? null;
|
||||||
|
|
||||||
if ($id <= 0) {
|
if ($id <= 0) {
|
||||||
echo json_encode(['success' => false, 'message' => 'Invalid id']);
|
echo json_encode(['success' => false, 'message' => 'Invalid id']);
|
||||||
exit;
|
exit;
|
||||||
}
|
|
||||||
|
|
||||||
$allowed = [
|
|
||||||
'default_value',
|
|
||||||
'default_source',
|
|
||||||
'json_node',
|
|
||||||
'is_visible_import',
|
|
||||||
'is_required'
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!in_array($field, $allowed, true)) {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Invalid field: ' . $field]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($field === 'is_visible_import' || $field === 'is_required') {
|
|
||||||
$value = ((int)$value === 1) ? 1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($field === 'default_source') {
|
|
||||||
$value = (string)$value;
|
|
||||||
|
|
||||||
if (!in_array($value, ['manual', 'json'], true)) {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Invalid default_source']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the user goes back to manual, clear the JSON node
|
|
||||||
if ($value === 'manual') {
|
|
||||||
$sql = "
|
|
||||||
UPDATE template_fixed_mapping
|
|
||||||
SET default_source = :val, json_node = NULL
|
|
||||||
WHERE id = :id
|
|
||||||
";
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
$ok = $stmt->execute([
|
|
||||||
':val' => $value,
|
|
||||||
':id' => $id
|
|
||||||
]);
|
|
||||||
|
|
||||||
echo json_encode(['success' => (bool)$ok]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($field === 'json_node') {
|
|
||||||
$value = trim((string)$value);
|
|
||||||
|
|
||||||
if ($value === '') {
|
|
||||||
$value = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$sql = "UPDATE template_fixed_mapping SET {$field} = :val WHERE id = :id";
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
|
|
||||||
$ok = $stmt->execute([
|
|
||||||
':val' => $value,
|
|
||||||
':id' => $id
|
|
||||||
]);
|
|
||||||
|
|
||||||
echo json_encode(['success' => (bool)$ok]);
|
|
||||||
} catch (Throwable $e) {
|
|
||||||
echo json_encode([
|
|
||||||
'success' => false,
|
|
||||||
'message' => $e->getMessage()
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$allowed = ['default_value', 'is_visible_import', 'is_required'];
|
||||||
|
if (!in_array($field, $allowed, true)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Invalid field']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($field === 'is_visible_import' || $field === 'is_required') {
|
||||||
|
$value = ((int)$value === 1) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$sql = "UPDATE template_fixed_mapping SET {$field} = :val WHERE id = :id";
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$ok = $stmt->execute([':val' => $value, ':id' => $id]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => (bool)$ok]);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,8 +8,71 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<!--favicon-->
|
<!--favicon-->
|
||||||
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||||
|
|
||||||
<?php include('cssinclude.php'); ?>
|
<?php include('cssinclude.php'); ?>
|
||||||
<title>xxx - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?> - User Profile</title>
|
|
||||||
|
<!-- Select2 CSS -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||||
|
|
||||||
|
<title>SmartTRF - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?> - User Profile</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.profile-avatar-preview {
|
||||||
|
max-width: 100px;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
background: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-muted {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lims-loading {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-note {
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
color: #475569;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-error {
|
||||||
|
display: none;
|
||||||
|
background: #fff1f2;
|
||||||
|
border: 1px solid #fecdd3;
|
||||||
|
color: #be123c;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-success {
|
||||||
|
display: none;
|
||||||
|
background: #f0fdf4;
|
||||||
|
border: 1px solid #bbf7d0;
|
||||||
|
color: #166534;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -18,9 +81,11 @@
|
|||||||
<!--sidebar wrapper -->
|
<!--sidebar wrapper -->
|
||||||
<?php include('include/navbar.php'); ?>
|
<?php include('include/navbar.php'); ?>
|
||||||
<!--end sidebar wrapper -->
|
<!--end sidebar wrapper -->
|
||||||
|
|
||||||
<!--start header -->
|
<!--start header -->
|
||||||
<?php include('include/topbar.php'); ?>
|
<?php include('include/topbar.php'); ?>
|
||||||
<!--end header -->
|
<!--end header -->
|
||||||
|
|
||||||
<!--start page wrapper -->
|
<!--start page wrapper -->
|
||||||
<div class="page-wrapper">
|
<div class="page-wrapper">
|
||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
@@ -31,61 +96,195 @@
|
|||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div>
|
<div>
|
||||||
<h6 class="mb-0">Edit Profile</h6>
|
<h6 class="mb-0">Edit Profile</h6>
|
||||||
|
<small class="text-muted">Update your personal information and LIMS references.</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form action="update-profile.php" method="POST" enctype="multipart/form-data">
|
<form action="update-profile.php" method="POST" enctype="multipart/form-data" id="profileForm">
|
||||||
<div class="mb-3">
|
<div class="row g-3">
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
<label for="first_name" class="form-label">First Name</label>
|
<label for="first_name" class="form-label">First Name</label>
|
||||||
<input type="text" class="form-control" id="first_name" name="first_name" value="<?= htmlspecialchars($nameuser); ?>" required>
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="first_name"
|
||||||
|
name="first_name"
|
||||||
|
value="<?= htmlspecialchars($nameuser ?? '', ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
|
required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
|
||||||
|
<div class="col-md-6">
|
||||||
<label for="last_name" class="form-label">Last Name</label>
|
<label for="last_name" class="form-label">Last Name</label>
|
||||||
<input type="text" class="form-control" id="last_name" name="last_name" value="<?= htmlspecialchars($surnameuser); ?>" required>
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="last_name"
|
||||||
|
name="last_name"
|
||||||
|
value="<?= htmlspecialchars($surnameuser ?? '', ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
|
required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
|
||||||
|
<div class="col-md-12">
|
||||||
<label for="email" class="form-label">Email</label>
|
<label for="email" class="form-label">Email</label>
|
||||||
<input type="email" class="form-control" id="email" name="email" value="<?= htmlspecialchars($emailuser); ?>" required>
|
<input
|
||||||
|
type="email"
|
||||||
|
class="form-control"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
value="<?= htmlspecialchars($emailuser ?? '', ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
|
required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="col-md-6">
|
||||||
<label for="lims_user_id" class="form-label">Accettatore</label>
|
<label for="lims_user_id" class="form-label">Accettatore</label>
|
||||||
<input type="number" class="form-control" id="lims_user_id" name="lims_user_id" value="<?= htmlspecialchars($lims_user_id ?? ''); ?>">
|
<select
|
||||||
|
class="form-select"
|
||||||
|
id="lims_user_id"
|
||||||
|
name="lims_user_id"
|
||||||
|
data-current-value="<?= htmlspecialchars($lims_user_id ?? '', ENT_QUOTES, 'UTF-8'); ?>">
|
||||||
|
<option value="">Select acceptor</option>
|
||||||
|
</select>
|
||||||
|
<div class="small-muted mt-1">
|
||||||
|
Values loaded from CustomField ID 244.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="col-md-6">
|
||||||
<label for="lims_global_user_id" class="form-label">LIMS Global</label>
|
<label for="lims_global_user_id" class="form-label">LIMS Global</label>
|
||||||
<input type="number" class="form-control" id="lims_global_user_id" name="lims_global_user_id" value="<?= htmlspecialchars($lims_global_user_id ?? ''); ?>">
|
<select
|
||||||
|
class="form-select"
|
||||||
|
id="lims_global_user_id"
|
||||||
|
name="lims_global_user_id"
|
||||||
|
data-current-value="<?= htmlspecialchars($lims_global_user_id ?? '', ENT_QUOTES, 'UTF-8'); ?>">
|
||||||
|
<option value="">Select LIMS user</option>
|
||||||
|
</select>
|
||||||
|
<div class="small-muted mt-1">
|
||||||
|
Values loaded from LIMS users list.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="col-md-12">
|
||||||
|
<div class="lims-loading" id="limsLoadingMessage">
|
||||||
|
Loading LIMS users and acceptors...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-12">
|
||||||
<label for="avatar" class="form-label">Profile Picture</label>
|
<label for="avatar" class="form-label">Profile Picture</label>
|
||||||
<input type="file" class="form-control" id="avatar" name="avatar">
|
<input
|
||||||
<?php if ($photousername): ?>
|
type="file"
|
||||||
<p>Current avatar: <img src="..//upload/users/<?= htmlspecialchars($photousername); ?>" alt="Current Avatar" style="max-width: 100px;"></p>
|
class="form-control"
|
||||||
|
id="avatar"
|
||||||
|
name="avatar"
|
||||||
|
accept="image/jpeg,image/png,image/webp,image/gif">
|
||||||
|
|
||||||
|
<div class="mt-3 d-flex align-items-center gap-3">
|
||||||
|
<?php if (!empty($photousername)): ?>
|
||||||
|
<img
|
||||||
|
src="../upload/users/<?= htmlspecialchars($photousername, ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
|
alt="Current Avatar"
|
||||||
|
class="profile-avatar-preview"
|
||||||
|
id="avatarPreview">
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<p>Current avatar: <img src="..//upload/users/profile.png" alt="Default Avatar" style="max-width: 100px;"></p>
|
<img
|
||||||
|
src="../upload/users/profile.png"
|
||||||
|
alt="Default Avatar"
|
||||||
|
class="profile-avatar-preview"
|
||||||
|
id="avatarPreview">
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="small-muted">Current avatar</div>
|
||||||
|
<div class="small-muted">Allowed formats: JPG, PNG, WEBP, GIF.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
|
||||||
<label for="password" class="form-label">Password (leave blank to keep current)</label>
|
|
||||||
<input type="password" class="form-control" id="password" name="password">
|
|
||||||
</div>
|
</div>
|
||||||
<input type="hidden" name="iduserlogin" value="<?= htmlspecialchars($iduserlogin); ?>">
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">Update Profile</button>
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="password" class="form-label">New Password</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
class="form-control"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
autocomplete="new-password">
|
||||||
|
<button
|
||||||
|
class="btn btn-outline-secondary toggle-password"
|
||||||
|
type="button"
|
||||||
|
data-target="password"
|
||||||
|
aria-label="Show or hide password">
|
||||||
|
<i class="bx bx-show"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="password_confirm" class="form-label">Confirm New Password</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
class="form-control"
|
||||||
|
id="password_confirm"
|
||||||
|
name="password_confirm"
|
||||||
|
autocomplete="new-password">
|
||||||
|
<button
|
||||||
|
class="btn btn-outline-secondary toggle-password"
|
||||||
|
type="button"
|
||||||
|
data-target="password_confirm"
|
||||||
|
aria-label="Show or hide password confirmation">
|
||||||
|
<i class="bx bx-show"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="password-note" id="passwordNote">
|
||||||
|
<strong>Important:</strong> leave both password fields empty to keep your current password unchanged.
|
||||||
|
Type a new password only if you want to replace it.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="password-error mt-2" id="passwordError">
|
||||||
|
The two password fields do not match. Please check them before saving.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="password-success mt-2" id="passwordSuccess">
|
||||||
|
The two passwords match.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="iduserlogin"
|
||||||
|
value="<?= htmlspecialchars($iduserlogin ?? '', ENT_QUOTES, 'UTF-8'); ?>">
|
||||||
|
|
||||||
|
<div class="col-md-12">
|
||||||
|
<button type="submit" class="btn btn-primary" id="submitProfileButton">
|
||||||
|
Update Profile
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--end page wrapper -->
|
<!--end page wrapper -->
|
||||||
|
|
||||||
<!--start overlay-->
|
<!--start overlay-->
|
||||||
<div class="overlay toggle-icon"></div>
|
<div class="overlay toggle-icon"></div>
|
||||||
<!--end overlay-->
|
<!--end overlay-->
|
||||||
|
|
||||||
<!--Start Back To Top Button-->
|
<!--Start Back To Top Button-->
|
||||||
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
<a href="javaScript:;" class="back-to-top">
|
||||||
|
<i class='bx bxs-up-arrow-alt'></i>
|
||||||
|
</a>
|
||||||
<!--End Back To Top Button-->
|
<!--End Back To Top Button-->
|
||||||
|
|
||||||
<?php include('include/footer.php'); ?>
|
<?php include('include/footer.php'); ?>
|
||||||
</div>
|
</div>
|
||||||
<!--end wrapper-->
|
<!--end wrapper-->
|
||||||
@@ -99,7 +298,389 @@
|
|||||||
<?php //include('include/themeswitcher.php');
|
<?php //include('include/themeswitcher.php');
|
||||||
?>
|
?>
|
||||||
<!--end switcher-->
|
<!--end switcher-->
|
||||||
|
|
||||||
<?php include('jsinclude.php'); ?>
|
<?php include('jsinclude.php'); ?>
|
||||||
|
|
||||||
|
<!-- Select2 JS -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const profileForm = document.getElementById('profileForm');
|
||||||
|
const submitProfileButton = document.getElementById('submitProfileButton');
|
||||||
|
|
||||||
|
const passwordInput = document.getElementById('password');
|
||||||
|
const passwordConfirmInput = document.getElementById('password_confirm');
|
||||||
|
const passwordError = document.getElementById('passwordError');
|
||||||
|
const passwordSuccess = document.getElementById('passwordSuccess');
|
||||||
|
|
||||||
|
const limsUserSelect = document.getElementById('lims_user_id');
|
||||||
|
const limsGlobalUserSelect = document.getElementById('lims_global_user_id');
|
||||||
|
const limsLoadingMessage = document.getElementById('limsLoadingMessage');
|
||||||
|
const avatarInput = document.getElementById('avatar');
|
||||||
|
const avatarPreview = document.getElementById('avatarPreview');
|
||||||
|
|
||||||
|
const currentLimsUserId = String(limsUserSelect.dataset.currentValue || '');
|
||||||
|
const currentLimsGlobalUserId = String(limsGlobalUserSelect.dataset.currentValue || '');
|
||||||
|
|
||||||
|
const limsGlobalUsersEndpoint = 'get_utenti.php';
|
||||||
|
const limsAcceptorsEndpoint = 'get_customfield_values.php?field_ids=244';
|
||||||
|
|
||||||
|
function validatePasswords(showEmptySuccess = false) {
|
||||||
|
const password = passwordInput.value;
|
||||||
|
const confirmPassword = passwordConfirmInput.value;
|
||||||
|
|
||||||
|
passwordError.style.display = 'none';
|
||||||
|
passwordSuccess.style.display = 'none';
|
||||||
|
passwordInput.classList.remove('is-invalid', 'is-valid');
|
||||||
|
passwordConfirmInput.classList.remove('is-invalid', 'is-valid');
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Both empty means: keep current password.
|
||||||
|
*/
|
||||||
|
if (password === '' && confirmPassword === '') {
|
||||||
|
submitProfileButton.disabled = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One field filled and the other empty is not valid.
|
||||||
|
*/
|
||||||
|
if (password === '' || confirmPassword === '') {
|
||||||
|
passwordError.textContent = 'Please fill both password fields, or leave both empty to keep the current password.';
|
||||||
|
passwordError.style.display = 'block';
|
||||||
|
passwordInput.classList.add('is-invalid');
|
||||||
|
passwordConfirmInput.classList.add('is-invalid');
|
||||||
|
submitProfileButton.disabled = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Both filled but different.
|
||||||
|
*/
|
||||||
|
if (password !== confirmPassword) {
|
||||||
|
passwordError.textContent = 'The two password fields do not match. Please check them before saving.';
|
||||||
|
passwordError.style.display = 'block';
|
||||||
|
passwordInput.classList.add('is-invalid');
|
||||||
|
passwordConfirmInput.classList.add('is-invalid');
|
||||||
|
submitProfileButton.disabled = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Both filled and equal.
|
||||||
|
*/
|
||||||
|
passwordInput.classList.add('is-valid');
|
||||||
|
passwordConfirmInput.classList.add('is-valid');
|
||||||
|
passwordSuccess.style.display = showEmptySuccess ? 'block' : 'block';
|
||||||
|
submitProfileButton.disabled = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordInput.addEventListener('input', function() {
|
||||||
|
validatePasswords();
|
||||||
|
});
|
||||||
|
|
||||||
|
passwordConfirmInput.addEventListener('input', function() {
|
||||||
|
validatePasswords();
|
||||||
|
});
|
||||||
|
|
||||||
|
profileForm.addEventListener('submit', function(event) {
|
||||||
|
if (!validatePasswords(true)) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (typeof Swal !== 'undefined') {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Password mismatch',
|
||||||
|
text: 'Please check the password fields before saving.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
function initSelect2() {
|
||||||
|
if (typeof $ === 'undefined') {
|
||||||
|
console.error('jQuery is not loaded. Select2 cannot start.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof $.fn.select2 === 'undefined') {
|
||||||
|
console.error('Select2 is not loaded. Check select2.min.js include.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#lims_user_id').select2({
|
||||||
|
width: '100%',
|
||||||
|
placeholder: 'Select acceptor',
|
||||||
|
allowClear: true,
|
||||||
|
minimumResultsForSearch: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#lims_global_user_id').select2({
|
||||||
|
width: '100%',
|
||||||
|
placeholder: 'Select LIMS user',
|
||||||
|
allowClear: true,
|
||||||
|
minimumResultsForSearch: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroySelect2IfActive(selector) {
|
||||||
|
if (typeof $ !== 'undefined' && $.fn.select2 && $(selector).hasClass('select2-hidden-accessible')) {
|
||||||
|
$(selector).select2('destroy');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshSelect2() {
|
||||||
|
if (typeof $ === 'undefined' || typeof $.fn.select2 === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroySelect2IfActive('#lims_user_id');
|
||||||
|
destroySelect2IfActive('#lims_global_user_id');
|
||||||
|
|
||||||
|
$('#lims_user_id').select2({
|
||||||
|
width: '100%',
|
||||||
|
placeholder: 'Select acceptor',
|
||||||
|
allowClear: true,
|
||||||
|
minimumResultsForSearch: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#lims_global_user_id').select2({
|
||||||
|
width: '100%',
|
||||||
|
placeholder: 'Select LIMS user',
|
||||||
|
allowClear: true,
|
||||||
|
minimumResultsForSearch: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#lims_user_id').trigger('change');
|
||||||
|
$('#lims_global_user_id').trigger('change');
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSelect(selectElement, placeholderText) {
|
||||||
|
selectElement.innerHTML = '';
|
||||||
|
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = '';
|
||||||
|
option.textContent = placeholderText;
|
||||||
|
selectElement.appendChild(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendOption(selectElement, value, text) {
|
||||||
|
if (value === null || value === undefined || value === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = String(value);
|
||||||
|
option.textContent = text || String(value);
|
||||||
|
selectElement.appendChild(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSelectValue(selectElement, value) {
|
||||||
|
selectElement.value = value || '';
|
||||||
|
|
||||||
|
if (typeof $ !== 'undefined' && $.fn.select2) {
|
||||||
|
$(selectElement).trigger('change');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLimsGlobalUsers(data) {
|
||||||
|
const list = Array.isArray(data) ? data : (data.value || []);
|
||||||
|
const normalized = [];
|
||||||
|
|
||||||
|
list.forEach(function(item) {
|
||||||
|
const id = item.IdUtente ?? item.id ?? item.ID ?? null;
|
||||||
|
const text = item.Nome || item.Nominativo || item.UserName || item.Email || id;
|
||||||
|
|
||||||
|
if (id !== null && id !== undefined && id !== '') {
|
||||||
|
normalized.push({
|
||||||
|
id: String(id),
|
||||||
|
text: String(text)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
normalized.sort(function(a, b) {
|
||||||
|
return String(a.text).localeCompare(String(b.text), 'it', {
|
||||||
|
sensitivity: 'base'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAcceptors(data) {
|
||||||
|
const list = data['244'] || data[244] || [];
|
||||||
|
const normalized = [];
|
||||||
|
|
||||||
|
list.forEach(function(item) {
|
||||||
|
const id =
|
||||||
|
item.IdCustomFieldValue ??
|
||||||
|
item.IdCustomFieldsValue ??
|
||||||
|
item.IdCustomFieldValues ??
|
||||||
|
item.Id ??
|
||||||
|
item.ID ??
|
||||||
|
item.id ??
|
||||||
|
item.ValueId ??
|
||||||
|
item.value_id ??
|
||||||
|
item.Codice ??
|
||||||
|
null;
|
||||||
|
|
||||||
|
const text =
|
||||||
|
item.Value ??
|
||||||
|
item.Valore ??
|
||||||
|
item.Nome ??
|
||||||
|
item.Name ??
|
||||||
|
item.Descrizione ??
|
||||||
|
item.Description ??
|
||||||
|
item.Text ??
|
||||||
|
item.text ??
|
||||||
|
item.Label ??
|
||||||
|
item.label ??
|
||||||
|
id;
|
||||||
|
|
||||||
|
if (id !== null && id !== undefined && id !== '') {
|
||||||
|
normalized.push({
|
||||||
|
id: String(id),
|
||||||
|
text: String(text)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
normalized.sort(function(a, b) {
|
||||||
|
return String(a.text).localeCompare(String(b.text), 'it', {
|
||||||
|
sensitivity: 'base'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateLimsGlobalUsers(users) {
|
||||||
|
clearSelect(limsGlobalUserSelect, 'Select LIMS user');
|
||||||
|
|
||||||
|
users.forEach(function(user) {
|
||||||
|
appendOption(limsGlobalUserSelect, user.id, user.text);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (currentLimsGlobalUserId !== '') {
|
||||||
|
setSelectValue(limsGlobalUserSelect, currentLimsGlobalUserId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateAcceptors(acceptors) {
|
||||||
|
clearSelect(limsUserSelect, 'Select acceptor');
|
||||||
|
|
||||||
|
acceptors.forEach(function(item) {
|
||||||
|
appendOption(limsUserSelect, item.id, item.text);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (currentLimsUserId !== '') {
|
||||||
|
setSelectValue(limsUserSelect, currentLimsUserId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadLimsDropdowns() {
|
||||||
|
try {
|
||||||
|
const [globalResponse, acceptorsResponse] = await Promise.all([
|
||||||
|
fetch(limsGlobalUsersEndpoint, {
|
||||||
|
cache: 'no-store'
|
||||||
|
}),
|
||||||
|
fetch(limsAcceptorsEndpoint, {
|
||||||
|
cache: 'no-store'
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!globalResponse.ok) {
|
||||||
|
throw new Error('Unable to load LIMS global users.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!acceptorsResponse.ok) {
|
||||||
|
throw new Error('Unable to load LIMS acceptors.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const globalData = await globalResponse.json();
|
||||||
|
const acceptorsData = await acceptorsResponse.json();
|
||||||
|
|
||||||
|
const globalUsers = normalizeLimsGlobalUsers(globalData);
|
||||||
|
const acceptors = normalizeAcceptors(acceptorsData);
|
||||||
|
|
||||||
|
populateLimsGlobalUsers(globalUsers);
|
||||||
|
populateAcceptors(acceptors);
|
||||||
|
refreshSelect2();
|
||||||
|
|
||||||
|
if (limsLoadingMessage) {
|
||||||
|
limsLoadingMessage.textContent = 'LIMS users and acceptors loaded.';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
if (limsLoadingMessage) {
|
||||||
|
limsLoadingMessage.textContent = 'Warning: unable to load LIMS users or acceptors.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof Swal !== 'undefined') {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'warning',
|
||||||
|
title: 'LIMS lists not loaded',
|
||||||
|
text: error.message || 'Unable to load LIMS dropdown values.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
avatarInput.addEventListener('change', function(event) {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = function(e) {
|
||||||
|
avatarPreview.src = e.target.result;
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
document.querySelectorAll('.toggle-password').forEach(function(button) {
|
||||||
|
button.addEventListener('click', function() {
|
||||||
|
const targetId = button.dataset.target;
|
||||||
|
const input = document.getElementById(targetId);
|
||||||
|
const icon = button.querySelector('i');
|
||||||
|
|
||||||
|
if (!input) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.type === 'password') {
|
||||||
|
input.type = 'text';
|
||||||
|
|
||||||
|
if (icon) {
|
||||||
|
icon.classList.remove('bx-show');
|
||||||
|
icon.classList.add('bx-hide');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
input.type = 'password';
|
||||||
|
|
||||||
|
if (icon) {
|
||||||
|
icon.classList.remove('bx-hide');
|
||||||
|
icon.classList.add('bx-show');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
initSelect2();
|
||||||
|
loadLimsDropdowns();
|
||||||
|
validatePasswords();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate rows before export to LIMS.
|
* Validate rows before export to LIMS.
|
||||||
*
|
*
|
||||||
@@ -185,7 +186,7 @@ try {
|
|||||||
|
|
||||||
// Mandatory-field config per template
|
// Mandatory-field config per template
|
||||||
$templateIds = array_values(array_unique(array_filter(array_map(
|
$templateIds = array_values(array_unique(array_filter(array_map(
|
||||||
fn ($r) => (int)($r['templateid'] ?? 0),
|
fn($r) => (int)($r['templateid'] ?? 0),
|
||||||
$recordsInfo
|
$recordsInfo
|
||||||
))));
|
))));
|
||||||
|
|
||||||
@@ -210,13 +211,14 @@ try {
|
|||||||
$requiredFixedByTemplate[(int)$r['template_id']][$key] = $key;
|
$requiredFixedByTemplate[(int)$r['template_id']][$key] = $key;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Required custom fields that are visible in the import grid
|
// Required custom fields that are visible in the import grid (excluding filed_id = 189 Tested component)
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
SELECT id AS mapping_id, template_id, field_label
|
SELECT id AS mapping_id, template_id, field_label
|
||||||
FROM template_mapping
|
FROM template_mapping
|
||||||
WHERE template_id IN ($tplPlaceholders)
|
WHERE template_id IN ($tplPlaceholders)
|
||||||
AND is_required = 1
|
AND is_required = 1
|
||||||
AND is_visible_import = 1
|
AND is_visible_import = 1
|
||||||
|
AND id <> 189
|
||||||
");
|
");
|
||||||
$stmt->execute($templateIds);
|
$stmt->execute($templateIds);
|
||||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
||||||
@@ -272,7 +274,6 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'results' => $results]);
|
echo json_encode(['success' => true, 'results' => $results]);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
error_log("Validation error: " . $e->getMessage());
|
error_log("Validation error: " . $e->getMessage());
|
||||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
|||||||
Reference in New Issue
Block a user