diff --git a/public/userarea/bindings_manage.php b/public/userarea/bindings_manage.php new file mode 100644 index 0000000..2ec351c --- /dev/null +++ b/public/userarea/bindings_manage.php @@ -0,0 +1,246 @@ +getConnection(); + +// Filtro opzionale per template. +$templateFilter = isset($_GET['template_id']) ? intval($_GET['template_id']) : 0; + +$templates = $pdo->query("SELECT id, name FROM excel_templates ORDER BY name")->fetchAll(PDO::FETCH_ASSOC); + +$where = ''; +$params = []; +if ($templateFilter > 0) { + $where = 'WHERE b.template_id = ?'; + $params[] = $templateFilter; +} + +$sql = "SELECT b.id, b.template_id, b.mapping_id, b.field_id, b.json_value, + b.lims_value_id, b.lims_value, b.updated_at, + 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 t.name, m.field_label, b.json_value"; +$stmt = $pdo->prepare($sql); +$stmt->execute($params); +$bindings = $stmt->fetchAll(PDO::FETCH_ASSOC); +?> + + + + + + + + + + + Gestione Binding JSON -> LIMS - <?= htmlspecialchars($titlewebsite ?? '', ENT_QUOTES, 'UTF-8'); ?> + + + +
+ + +
+
+ +
+
+
+
Gestione Binding JSON -> LIMS
+
+ + +
+
+
+ +
+ + +
+ Le modifiche ai binding si applicano alle importazioni future. + I record gia' importati non vengono ricalcolati. +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
TemplateCampo (template_mapping)Valore JSONValore LIMSAzioni
Nessun binding presente.
+ + + + + +
+
+
+
+ +
+
+
+ + +
+ + + + + + + + diff --git a/public/userarea/class/binding-functions.php b/public/userarea/class/binding-functions.php new file mode 100644 index 0000000..faaa9cf --- /dev/null +++ b/public/userarea/class/binding-functions.php @@ -0,0 +1,128 @@ + LIMS value bindings (table json_lims_binding). + +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; +} + +function binding_lookup(PDO $pdo, int $mappingId, string $jsonValue): ?array +{ + $stmt = $pdo->prepare( + "SELECT id, lims_value_id, lims_value + FROM json_lims_binding + WHERE mapping_id = ? AND json_value = ? + LIMIT 1" + ); + $stmt->execute([$mappingId, $jsonValue]); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + return $row ?: null; +} + +function binding_upsert( + PDO $pdo, + int $templateId, + int $mappingId, + int $fieldId, + string $jsonValue, + int $limsValueId, + string $limsValue, + ?int $createdBy +): void { + $stmt = $pdo->prepare( + "INSERT INTO json_lims_binding + (template_id, mapping_id, 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)" + ); + $stmt->execute([$templateId, $mappingId, $fieldId, $jsonValue, $limsValueId, $limsValue, $createdBy]); +} + +// LIMS list values for a field, reusing the cache/customfield_{id}.json cache (1h). +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; +} + +// Scrive il valore risolto sui record indicati (datadb_ids gia' delimita l'import corrente). +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(); +} diff --git a/public/userarea/delete_binding.php b/public/userarea/delete_binding.php new file mode 100644 index 0000000..122debb --- /dev/null +++ b/public/userarea/delete_binding.php @@ -0,0 +1,40 @@ + 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()]); +} diff --git a/public/userarea/import_insert.php b/public/userarea/import_insert.php index 29d1824..a0fb58a 100644 --- a/public/userarea/import_insert.php +++ b/public/userarea/import_insert.php @@ -12,6 +12,7 @@ if (!file_exists(__DIR__ . '/import_debug.log')) { error_log("Inizio importazione alle " . date('Y-m-d H:i:s')); 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'])) { header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Richiesta non valida")); @@ -61,7 +62,8 @@ $stmt = $pdo->prepare(" field_label, field_id, main_field, - auto_value + auto_value, + has_list FROM template_mapping WHERE template_id = ? "); @@ -84,6 +86,13 @@ foreach ($allMappings as $mapping) { // Inserisci le righe selezionate in datadb $insertedIds = []; + +// Binding JSON -> LIMS senza corrispondenza salvata, per "mapping_id|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') { @@ -107,6 +116,14 @@ foreach ($selected_rows as $loopIndex => $rowIndex) { $template = $template_stmt->fetch(PDO::FETCH_ASSOC); $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 = [ $template_id, $importReferenceCode, @@ -115,7 +132,7 @@ foreach ($selected_rows as $loopIndex => $rowIndex) { $user_id, null, date('Y-m-d'), - $excelrow, + $excelrowDb, $default_idclient // Aggiungi idclient ]; $sql = "INSERT INTO datadb (templateid, importreferencecode, filename_import, status, user_id, limscode, importdate, excelrow, idclient) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; @@ -227,6 +244,80 @@ 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 = $mapping['id'] . '|' . $jsonValue; + if (!isset($savedBindings[$key])) { + $savedBindings[$key] = [ + '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 = $mapping['id'] . '|' . $jsonValue; + if (!isset($autoBindings[$key])) { + $autoBindings[$key] = [ + '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 = $mapping['id'] . '|' . $jsonValue; + if (!isset($pendingBindings[$key])) { + $pendingBindings[$key] = [ + '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 === '')) { error_log("Required field missing for mapping ID: " . $mapping['id'] . ", field: " . $mapping['field_label']); } @@ -239,5 +330,28 @@ foreach ($selected_rows as $loopIndex => $rowIndex) { $_SESSION['inserted_ids'] = $insertedIds; -header("Location: imported.php?id=" . urlencode($template_id) . "&importref=" . urlencode($importReferenceCode)); +$importedUrl = "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; diff --git a/public/userarea/import_json.php b/public/userarea/import_json.php index 4dd51e8..fb18281 100644 --- a/public/userarea/import_json.php +++ b/public/userarea/import_json.php @@ -173,7 +173,8 @@ error_log("Loaded JSON import template: " . print_r($template, true));
Imported (i) - To LIMS (l) + To LIMS (l) + Binding JSON -> LIMS
@@ -384,42 +385,51 @@ error_log("Loaded JSON import template: " . print_r($template, true)); } function addJsonRow(jsonPayload, reference, sourceType) { - const normalizedPayload = normalizeJsonPayload(jsonPayload); - const flattened = flattenJson(normalizedPayload); + // Ogni elemento di data[] diventa una riga (non colonne data.0.*, data.1.*). + const records = extractRecords(jsonPayload); - if (INCLUDE_SOURCE_CODE_COLUMN) { - flattened.source_code = reference; - flattened.source_type = sourceType; - } + records.forEach((record, recordIndex) => { + const flattened = flattenJson(record); - const newColumns = Object.keys(flattened).filter(col => !columns.includes(col)); - columns = columns.concat(newColumns); + const rowReference = records.length > 1 ? + reference + '#' + (recordIndex + 1) : + reference; - jsonRows.push({ - excelrow: 'JSON-' + (jsonRows.length + 1), - reference: reference, - sourceType: sourceType, - flat: flattened + 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(); } - function normalizeJsonPayload(payload) { + // Record da trasformare in righe: gli oggetti in data[], altrimenti il payload stesso. + function extractRecords(payload) { if ( - UNWRAP_SINGLE_DATA_ITEM && payload && typeof payload === 'object' && !Array.isArray(payload) && Array.isArray(payload.data) && - payload.data.length === 1 && - payload.data[0] && - typeof payload.data[0] === 'object' + payload.data.length > 0 ) { - return payload.data[0]; + const items = payload.data.filter(item => item && typeof item === 'object'); + if (items.length > 0) { + return items; + } } - return payload; + return [payload]; } function flattenJson(value, prefix = '', result = {}) { diff --git a/public/userarea/imported.php b/public/userarea/imported.php index 3ba13d9..9a9626f 100644 --- a/public/userarea/imported.php +++ b/public/userarea/imported.php @@ -1301,9 +1301,17 @@ $gridMeta = [
+ + 0): ?> + +
Imported (i) To LIMS (l) + Binding JSON -> LIMS