From b8b01cd5d52e7540a4bf8332b1f0d78810acfc99 Mon Sep 17 00:00:00 2001 From: "r.mubarakzyanov" Date: Wed, 23 Sep 2026 15:40:37 +0300 Subject: [PATCH] xls binding --- public/languages/en/general.php | 1 + public/userarea/bindings_manage.php | 299 ++++++++ public/userarea/class/binding-functions.php | 716 ++++++++++++++++++ public/userarea/delete_binding.php | 40 + public/userarea/import_insert.php | 90 ++- public/userarea/import_insert_batch.php | 98 ++- public/userarea/import_xls2.php | 6 +- public/userarea/include/navbar.php | 5 + .../userarea/mapping_template_xls_scheme2.php | 66 +- public/userarea/resolve_bindings.php | 417 ++++++++++ public/userarea/save_binding.php | 101 +++ public/userarea/search_fixed_field_values.php | 54 ++ public/userarea/skip_binding.php | 71 ++ public/userarea/sql/fixed_field_binding.sql | 19 + public/userarea/sql/lims_value_binding.sql | 17 + public/userarea/update_fixed_field.php | 17 +- public/userarea/validate_export.php | 41 + 17 files changed, 2046 insertions(+), 12 deletions(-) create mode 100644 public/userarea/bindings_manage.php create mode 100644 public/userarea/class/binding-functions.php create mode 100644 public/userarea/delete_binding.php create mode 100644 public/userarea/resolve_bindings.php create mode 100644 public/userarea/save_binding.php create mode 100644 public/userarea/search_fixed_field_values.php create mode 100644 public/userarea/skip_binding.php create mode 100644 public/userarea/sql/fixed_field_binding.sql create mode 100644 public/userarea/sql/lims_value_binding.sql diff --git a/public/languages/en/general.php b/public/languages/en/general.php index c0ad6c6b..582ba962 100644 --- a/public/languages/en/general.php +++ b/public/languages/en/general.php @@ -25,6 +25,7 @@ $templatedashboard = "Template Dashboard"; $newtemplate = "New Template"; $dashtemplate = "Dashboard Templates"; $insertnewtemplatexls = "Insert New Template XLS"; +$bindingsmanage = "Value Bindings"; // Datatables $langdatatables = [ "paginate_first" => "First", diff --git a/public/userarea/bindings_manage.php b/public/userarea/bindings_manage.php new file mode 100644 index 00000000..692176b2 --- /dev/null +++ b/public/userarea/bindings_manage.php @@ -0,0 +1,299 @@ +getConnection(); + +$templateFilter = isset($_GET['template_id']) ? (int) $_GET['template_id'] : 0; +$search = trim($_GET['q'] ?? ''); + +$templates = $pdo->query("SELECT id, name FROM excel_templates ORDER BY name")->fetchAll(PDO::FETCH_ASSOC); + +$conds = []; +$params = []; + +if ($templateFilter > 0) { + $conds[] = 'b.template_id = ?'; + $params[] = $templateFilter; +} +if ($search !== '') { + $conds[] = '(b.source_value LIKE ? OR b.lims_value LIKE ? OR m.field_label LIKE ? OR f.fixed_field_key LIKE ?)'; + $like = '%' . $search . '%'; + array_push($params, $like, $like, $like, $like); +} +$where = $conds ? ('WHERE ' . implode(' AND ', $conds)) : ''; + +$stmt = $pdo->prepare( + "SELECT b.id, b.template_id, b.binding_kind, b.mapping_id, b.field_id, b.source_value, + b.lims_value_id, b.lims_value, b.updated_at, + t.name AS template_name, + COALESCE(m.field_label, f.fixed_field_key) AS field_label, + COALESCE(m.excel_column, f.excel_column) AS excel_column, + f.fixed_field_key + FROM lims_value_binding b + LEFT JOIN excel_templates t ON t.id = b.template_id + LEFT JOIN template_mapping m ON m.id = b.mapping_id AND b.binding_kind = 'custom' + LEFT JOIN template_fixed_mapping f ON f.id = b.mapping_id AND b.binding_kind = 'fixed' + $where + ORDER BY t.name ASC, field_label ASC, b.source_value ASC" +); +$stmt->execute($params); +$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + +$groups = []; +foreach ($rows as $r) { + $kind = $r['binding_kind'] ?? 'custom'; + $gkey = $kind . ':' . (int) $r['mapping_id']; + if (!isset($groups[$gkey])) { + $groups[$gkey] = [ + 'kind' => $kind, + 'label' => $kind === 'fixed' + ? binding_fixed_label((string) $r['fixed_field_key']) + : ($r['field_label'] ?: ('Mapping ' . $gkey)), + 'excel_column' => $r['excel_column'] ?: '', + 'template_name' => $r['template_name'] ?: ('Template ' . $r['template_id']), + 'template_id' => (int) $r['template_id'], + 'rows' => [], + ]; + } + $groups[$gkey]['rows'][] = $r; +} +?> + + + + + + + + + + + Gestione binding - <?= htmlspecialchars($titlewebsite ?? '', ENT_QUOTES, 'UTF-8'); ?> + + + +
+ + +
+
+ +
+
+
Binding valori importati → LIMS
+ + Le modifiche valgono dalla prossima importazione: i record gia' importati non vengono aggiornati. + +
+ +
+
+
+ + +
+
+ + +
+
+ + Azzera +
+
+ + + + +
Nessun binding salvato per i filtri selezionati.
+ +
+ + + + + + + + + + + + $g): ?> + + + + + + + + + + + + + + +
Valore importatoValore LIMSAggiornatoAzioni
+ + campo fisso + + · + + · colonna «» + + · valore/i + +
› + +
+
+ +
+
+ +
+
+ +
+
+
+ + +
+ + + + + + + + diff --git a/public/userarea/class/binding-functions.php b/public/userarea/class/binding-functions.php new file mode 100644 index 00000000..c6cc41a0 --- /dev/null +++ b/public/userarea/class/binding-functions.php @@ -0,0 +1,716 @@ +get("CustomField($fieldId)?\$expand=CustomFieldsValues"); + $values = $data['CustomFieldsValues'] ?? []; + if (!is_dir($cacheDir)) { + mkdir($cacheDir, 0777, true); + } + file_put_contents($cacheFile, json_encode($values)); + } + } catch (Throwable $e) { + error_log("binding_get_lims_values failed for field $fieldId: " . $e->getMessage()); + $values = []; + } + + if (!is_array($values)) { + $values = []; + } + + return $memo[$fieldId] = $values; +} + +function binding_lims_value_index(int $fieldId): array +{ + static $memo = []; + + if (array_key_exists($fieldId, $memo)) { + return $memo[$fieldId]; + } + + $index = []; + foreach (binding_get_lims_values($fieldId) as $v) { + $id = (int) ($v['IdCustomFieldsValue'] ?? 0); + if ($id > 0) { + $index[$id] = (string) ($v['Valore'] ?? ''); + } + } + + return $memo[$fieldId] = $index; +} + +function binding_has_lims_values(int $fieldId): bool +{ + return binding_get_lims_values($fieldId) !== []; +} + +function binding_is_lims_id(int $fieldId, $value): bool +{ + $raw = trim((string) $value); + if ($raw === '' || !ctype_digit($raw)) { + return false; + } + + $index = binding_lims_value_index($fieldId); + + return $index === [] ? false : isset($index[(int) $raw]); +} + +function binding_auto_match(int $fieldId, string $sourceValue): ?array +{ + $needle = binding_normalize_value($sourceValue); + if ($needle === '') { + return null; + } + + $matches = []; + foreach (binding_get_lims_values($fieldId) as $v) { + if (binding_normalize_value($v['Valore'] ?? '') === $needle) { + $matches[] = [ + 'id' => (int) ($v['IdCustomFieldsValue'] ?? 0), + 'text' => (string) ($v['Valore'] ?? ''), + ]; + } + } + + return count($matches) === 1 && $matches[0]['id'] > 0 ? $matches[0] : null; +} + +function binding_lookup(PDO $pdo, int $mappingId, string $sourceValue, string $kind = 'custom'): ?array +{ + $stmt = $pdo->prepare( + "SELECT id, lims_value_id, lims_value, source_value + FROM lims_value_binding + WHERE binding_kind = ? AND mapping_id = ? AND source_key = ? + LIMIT 1" + ); + $stmt->execute([$kind, $mappingId, binding_normalize_value($sourceValue)]); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + return $row ?: null; +} + +function binding_upsert( + PDO $pdo, + int $templateId, + int $mappingId, + int $fieldId, + string $sourceValue, + int $limsValueId, + string $limsValue, + ?int $createdBy, + string $kind = 'custom' +): void { + $stmt = $pdo->prepare( + "INSERT INTO lims_value_binding + (template_id, binding_kind, mapping_id, field_id, source_value, source_key, lims_value_id, lims_value, created_by) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + lims_value_id = VALUES(lims_value_id), + lims_value = VALUES(lims_value), + field_id = VALUES(field_id), + template_id = VALUES(template_id)" + ); + $stmt->execute([ + $templateId, + $kind, + $mappingId, + $fieldId, + mb_substr(trim($sourceValue), 0, 500), + mb_substr(binding_normalize_value($sourceValue), 0, 500), + $limsValueId, + mb_substr($limsValue, 0, 500), + $createdBy, + ]); +} + +function binding_delete(PDO $pdo, int $mappingId, string $sourceValue, string $kind = 'custom'): int +{ + $stmt = $pdo->prepare("DELETE FROM lims_value_binding WHERE binding_kind = ? AND mapping_id = ? AND source_key = ?"); + $stmt->execute([$kind, $mappingId, binding_normalize_value($sourceValue)]); + + return $stmt->rowCount(); +} + +function binding_delete_by_id(PDO $pdo, int $bindingId): int +{ + $stmt = $pdo->prepare("DELETE FROM lims_value_binding WHERE id = ?"); + $stmt->execute([$bindingId]); + + return $stmt->rowCount(); +} + +function binding_suggest(PDO $pdo, int $fieldId, string $sourceValue, int $excludeMappingId): ?array +{ + if ($fieldId <= 0) { + return null; + } + + $stmt = $pdo->prepare( + "SELECT b.lims_value_id, b.lims_value, b.template_id, t.name AS template_name + FROM lims_value_binding b + LEFT JOIN excel_templates t ON t.id = b.template_id + WHERE b.binding_kind = 'custom' AND b.field_id = ? AND b.source_key = ? AND b.mapping_id <> ? + ORDER BY b.updated_at DESC + LIMIT 1" + ); + $stmt->execute([$fieldId, binding_normalize_value($sourceValue), $excludeMappingId]); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + return $row ?: null; +} + +function binding_apply_to_details(PDO $pdo, int $mappingId, string $value, array $datadbIds): int +{ + $datadbIds = array_values(array_filter(array_map('intval', $datadbIds))); + if (empty($datadbIds)) { + return 0; + } + + $placeholders = implode(',', array_fill(0, count($datadbIds), '?')); + $stmt = $pdo->prepare( + "UPDATE import_data_details + SET field_value = ? + WHERE mapping_id = ? AND id IN ($placeholders)" + ); + $stmt->execute(array_merge([$value, $mappingId], $datadbIds)); + + return $stmt->rowCount(); +} + +function binding_find_column_index(string $column, array $columns) +{ + $needle = binding_normalize_value($column); + if ($needle === '') { + return false; + } + + foreach ($columns as $i => $c) { + if (binding_normalize_value($c) === $needle) { + return $i; + } + } + + return false; +} + +function binding_fixed_alias_map(): array +{ + return [ + 'ClienteResponsabile' => 'cliente_responsabile_id', + 'ClienteFornitore' => 'cliente_fornitore_id', + 'ClienteAnalisi' => 'clienteAnalisi', + 'ClienteFatturazione' => 'ClienteFatturazione', + 'MoltiplicatorePrezzo' => 'moltiplicatore_prezzo_id', + 'AnagraficaCertestObject' => 'anagrafica_certest_object_id', + 'AnagraficaCertestService' => 'anagrafica_certest_service_id', + 'ConsegnaRichiesta' => 'consegna_richiesta', + ]; +} + +function binding_fixed_column(string $key): ?string +{ + return binding_fixed_alias_map()[$key] ?? null; +} + +function binding_fixed_is_list(string $key): bool +{ + return in_array($key, [ + 'ClienteResponsabile', + 'ClienteFornitore', + 'ClienteAnalisi', + 'ClienteFatturazione', + 'MoltiplicatorePrezzo', + 'AnagraficaCertestObject', + 'AnagraficaCertestService', + ], true); +} + +function binding_fixed_is_date(string $key): bool +{ + return $key === 'ConsegnaRichiesta'; +} + +function binding_fixed_auto_matchable(string $key): bool +{ + return in_array($key, [ + 'MoltiplicatorePrezzo', + 'AnagraficaCertestObject', + 'AnagraficaCertestService', + ], true); +} + +function binding_fixed_label(string $key): string +{ + $labels = [ + 'AnagraficaCertestObject' => 'Anagrafica Certest Object', + 'AnagraficaCertestService' => 'Anagrafica Certest Service', + 'MoltiplicatorePrezzo' => 'Moltiplicatore Prezzo', + 'ClienteResponsabile' => 'Cliente Responsabile', + 'ClienteFornitore' => 'Cliente Fornitore', + 'ClienteAnalisi' => 'Buyer', + 'ClienteFatturazione' => 'Cliente Fatturazione', + 'ConsegnaRichiesta' => 'Consegna Richiesta', + ]; + + return $labels[$key] ?? $key; +} + +function binding_parse_date($value): ?string +{ + $raw = trim((string) $value); + if ($raw === '') { + return null; + } + + foreach (['Y-m-d', 'd/m/Y', 'd-m-Y', 'd.m.Y', 'Y/m/d', 'd/m/y', 'Y-m-d H:i:s'] as $format) { + $date = DateTime::createFromFormat($format, $raw); + if ($date && $date->format($format) === $raw) { + return $date->format('Y-m-d'); + } + } + + $ts = strtotime($raw); + + return $ts ? date('Y-m-d', $ts) : null; +} + +function binding_fixed_default(array $fixedMapping): string +{ + $value = trim((string) ($fixedMapping['default_value'] ?? '')); + if ($value === '') { + return ''; + } + + $isDate = strcasecmp((string) ($fixedMapping['data_type'] ?? ''), 'DATE') === 0 + || binding_fixed_is_date((string) ($fixedMapping['fixed_field_key'] ?? '')); + + if ($isDate && preg_match('/^today\s*(?:\+\s*(\d+))?$/i', $value, $m)) { + return date('Y-m-d', strtotime('+' . (int) ($m[1] ?? 0) . ' days')); + } + + return $value; +} + +function binding_cached_get($api, string $cacheFile, string $endpoint, int $ttl = 3600): array +{ + if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < $ttl)) { + $data = json_decode(file_get_contents($cacheFile), true); + if (is_array($data)) { + return $data; + } + } + + $data = $api->get($endpoint); + if (is_array($data)) { + $dir = dirname($cacheFile); + if (!is_dir($dir)) { + mkdir($dir, 0777, true); + } + file_put_contents($cacheFile, json_encode($data)); + } + + return is_array($data) ? $data : []; +} + +function binding_template_idclient(PDO $pdo, int $templateId): int +{ + $stmt = $pdo->prepare("SELECT idclient FROM excel_templates WHERE id = ?"); + $stmt->execute([$templateId]); + + return (int) ($stmt->fetchColumn() ?: 0); +} + +function binding_client_label(array $client): string +{ + $name = trim((string) ($client['Nominativo'] ?? '')); + $code = trim((string) ($client['CodiceCliente'] ?? '')); + + return $code !== '' ? ($name . ' - ' . $code) : $name; +} + +function binding_get_fixed_values(PDO $pdo, string $fieldKey, int $templateId): array +{ + static $memo = []; + $memoKey = $fieldKey . '|' . $templateId; + if (isset($memo[$memoKey])) { + return $memo[$memoKey]; + } + + $cacheDir = dirname(__DIR__) . '/cache'; + $out = []; + + try { + require_once __DIR__ . '/VisualLimsApiClient.class.php'; + $api = VisualLimsApiClient::getInstance(); + + switch ($fieldKey) { + case 'MoltiplicatorePrezzo': + $data = binding_cached_get($api, "$cacheDir/moltiplicatori_prezzo.json", 'MoltiplicatorePrezzi'); + foreach (($data['value'] ?? []) as $r) { + $out[] = ['id' => (int) ($r['IdMoltiplicatorePrezzo'] ?? 0), 'text' => (string) ($r['Descrizione'] ?? '')]; + } + break; + + case 'AnagraficaCertestObject': + case 'AnagraficaCertestService': + $file = $fieldKey === 'AnagraficaCertestObject' + ? 'anagrafica_certest_object.json' + : 'anagrafica_certest_service.json'; + $data = binding_cached_get($api, "$cacheDir/$file", $fieldKey); + foreach (($data['value'] ?? []) as $r) { + $code = trim((string) ($r['Codice'] ?? '')); + $name = (string) ($r['NomeAnagrafica'] ?? ''); + $out[] = [ + 'id' => (int) ($r['IdAnagrafica'] ?? 0), + 'text' => $name, + 'aliases' => array_values(array_filter([$code, $code !== '' ? "$code - $name" : ''])), + ]; + } + break; + + case 'ClienteResponsabile': + $idCliente = binding_template_idclient($pdo, $templateId); + if ($idCliente > 0) { + $data = binding_cached_get( + $api, + "$cacheDir/cliente_responsabili_$idCliente.json", + "Cliente($idCliente)?\$expand=Responsabili" + ); + foreach (($data['Responsabili'] ?? []) as $r) { + $out[] = ['id' => (int) ($r['IdClienteResponsabile'] ?? 0), 'text' => (string) ($r['Nominativo'] ?? '')]; + } + } + break; + + case 'ClienteFornitore': + case 'ClienteAnalisi': + case 'ClienteFatturazione': + $endpoint = 'Cliente?' . http_build_query([ + '$select' => 'IdCliente,Nominativo,CodiceCliente', + '$orderby' => 'Nominativo asc', + ]); + $data = binding_cached_get($api, "$cacheDir/clienti.json", $endpoint); + foreach (($data['value'] ?? []) as $r) { + $out[] = ['id' => (int) ($r['IdCliente'] ?? 0), 'text' => binding_client_label($r)]; + } + break; + } + } catch (Throwable $e) { + error_log("binding_get_fixed_values($fieldKey) failed: " . $e->getMessage()); + $out = []; + } + + return $memo[$memoKey] = array_values(array_filter($out, fn($v) => $v['id'] > 0)); +} + +function binding_fixed_has_values(PDO $pdo, string $fieldKey, int $templateId): bool +{ + return binding_get_fixed_values($pdo, $fieldKey, $templateId) !== []; +} + +function binding_fixed_is_lims_id(PDO $pdo, string $fieldKey, int $templateId, $value): bool +{ + $raw = trim((string) $value); + if ($raw === '' || !ctype_digit($raw)) { + return false; + } + + foreach (binding_get_fixed_values($pdo, $fieldKey, $templateId) as $v) { + if ((int) $v['id'] === (int) $raw) { + return true; + } + } + + return false; +} + +function binding_auto_match_fixed(array $values, string $sourceValue): ?array +{ + $needle = binding_normalize_value($sourceValue); + if ($needle === '') { + return null; + } + + $matches = []; + foreach ($values as $v) { + $candidates = array_merge([$v['text'] ?? ''], $v['aliases'] ?? []); + foreach ($candidates as $candidate) { + if (binding_normalize_value($candidate) === $needle) { + $matches[] = $v; + break; + } + } + } + + return count($matches) === 1 ? $matches[0] : null; +} + +function binding_suggest_fixed(PDO $pdo, string $fieldKey, string $sourceValue, int $excludeMappingId): ?array +{ + $stmt = $pdo->prepare( + "SELECT b.lims_value_id, b.lims_value, b.template_id, t.name AS template_name + FROM lims_value_binding b + JOIN template_fixed_mapping f ON f.id = b.mapping_id + LEFT JOIN excel_templates t ON t.id = b.template_id + WHERE b.binding_kind = 'fixed' AND f.fixed_field_key = ? + AND b.source_key = ? AND b.mapping_id <> ? + ORDER BY b.updated_at DESC + LIMIT 1" + ); + $stmt->execute([$fieldKey, binding_normalize_value($sourceValue), $excludeMappingId]); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + return $row ?: null; +} + +function binding_apply_to_datadb(PDO $pdo, string $fieldKey, $value, array $datadbIds): int +{ + $column = binding_fixed_column($fieldKey); + if ($column === null) { + throw new InvalidArgumentException("Campo fisso sconosciuto: $fieldKey"); + } + + $datadbIds = array_values(array_filter(array_map('intval', $datadbIds))); + if (empty($datadbIds)) { + return 0; + } + + $placeholders = implode(',', array_fill(0, count($datadbIds), '?')); + $stmt = $pdo->prepare("UPDATE datadb SET `$column` = ? WHERE iddatadb IN ($placeholders)"); + $stmt->execute(array_merge([$value === '' ? null : $value], $datadbIds)); + + return $stmt->rowCount(); +} + +function binding_write_fixed_columns(PDO $pdo, int $iddatadb, array $valuesByKey): int +{ + $map = binding_fixed_alias_map(); + $sets = []; + $params = []; + + foreach ($valuesByKey as $key => $value) { + if (!isset($map[$key]) || $value === null || $value === '') { + continue; + } + $sets[] = "`{$map[$key]}` = ?"; + $params[] = $value; + } + + if (empty($sets)) { + return 0; + } + + $params[] = $iddatadb; + $stmt = $pdo->prepare("UPDATE datadb SET " . implode(', ', $sets) . " WHERE iddatadb = ?"); + $stmt->execute($params); + + return $stmt->rowCount(); +} + +function binding_resolve_for_import( + PDO $pdo, + array $mapping, + int $templateId, + string $sourceValue, + int $iddatadb, + ?int $userId, + array &$saved, + array &$auto, + array &$pending +): string { + $mappingId = (int) $mapping['id']; + $fieldId = (int) $mapping['field_id']; + $key = 'cf:' . $mappingId . '|' . binding_normalize_value($sourceValue); + + $collect = function (array &$bucket, array $extra = []) use ($key, $mapping, $mappingId, $fieldId, $sourceValue, $iddatadb) { + if (!isset($bucket[$key])) { + $bucket[$key] = array_merge([ + 'kind' => 'custom', + 'mapping_id' => $mappingId, + 'field_id' => $fieldId, + 'field_label' => $mapping['field_label'] ?? ('Mapping ' . $mappingId), + 'source_value' => $sourceValue, + 'datadb_ids' => [], + ], $extra); + } + $bucket[$key]['datadb_ids'][] = $iddatadb; + }; + + if (!binding_has_lims_values($fieldId)) { + return $sourceValue; + } + + if (binding_is_lims_id($fieldId, $sourceValue)) { + return $sourceValue; + } + + if ($existing = binding_lookup($pdo, $mappingId, $sourceValue)) { + $collect($saved, [ + 'lims_value_id' => (int) $existing['lims_value_id'], + 'lims_value' => (string) $existing['lims_value'], + ]); + + return (string) $existing['lims_value_id']; + } + + if ($match = binding_auto_match($fieldId, $sourceValue)) { + binding_upsert($pdo, $templateId, $mappingId, $fieldId, $sourceValue, $match['id'], $match['text'], $userId); + $collect($auto, [ + 'lims_value_id' => $match['id'], + 'lims_value' => $match['text'], + ]); + + return (string) $match['id']; + } + + $collect($pending); + + return $sourceValue; +} + +function binding_resolve_fixed_for_import( + PDO $pdo, + array $fixedMapping, + int $templateId, + string $sourceValue, + int $iddatadb, + ?int $userId, + array &$saved, + array &$auto, + array &$pending +): ?string { + $fieldKey = (string) $fixedMapping['fixed_field_key']; + $mappingId = (int) $fixedMapping['id']; + $key = 'fx:' . $mappingId . '|' . binding_normalize_value($sourceValue); + + $collect = function (array &$bucket, array $extra = []) use ($key, $fieldKey, $mappingId, $sourceValue, $iddatadb) { + if (!isset($bucket[$key])) { + $bucket[$key] = array_merge([ + 'kind' => 'fixed', + 'mapping_id' => $mappingId, + 'fixed_field_key' => $fieldKey, + 'field_id' => 0, + 'field_label' => binding_fixed_label($fieldKey), + 'source_value' => $sourceValue, + 'datadb_ids' => [], + ], $extra); + } + $bucket[$key]['datadb_ids'][] = $iddatadb; + }; + + if (binding_fixed_is_date($fieldKey)) { + return binding_parse_date($sourceValue); + } + + if (!binding_fixed_is_list($fieldKey)) { + return $sourceValue; + } + + if (!binding_fixed_has_values($pdo, $fieldKey, $templateId)) { + error_log("[BINDING] campo fisso $fieldKey: lista LIMS non disponibile, valore non scritto"); + + return null; + } + + if (binding_fixed_is_lims_id($pdo, $fieldKey, $templateId, $sourceValue)) { + return (string) (int) $sourceValue; + } + + if ($existing = binding_lookup($pdo, $mappingId, $sourceValue, 'fixed')) { + $collect($saved, [ + 'lims_value_id' => (int) $existing['lims_value_id'], + 'lims_value' => (string) $existing['lims_value'], + ]); + + return (string) $existing['lims_value_id']; + } + + if (binding_fixed_auto_matchable($fieldKey)) { + $match = binding_auto_match_fixed(binding_get_fixed_values($pdo, $fieldKey, $templateId), $sourceValue); + if ($match) { + binding_upsert($pdo, $templateId, $mappingId, 0, $sourceValue, (int) $match['id'], (string) $match['text'], $userId, 'fixed'); + $collect($auto, [ + 'lims_value_id' => (int) $match['id'], + 'lims_value' => (string) $match['text'], + ]); + + return (string) $match['id']; + } + } + + $collect($pending); + + return null; +} + +function binding_merge_buckets(array $bucket, array $items): array +{ + foreach ($items as $key => $item) { + if (!isset($bucket[$key])) { + $bucket[$key] = $item; + continue; + } + $bucket[$key]['datadb_ids'] = array_values(array_unique(array_merge( + $bucket[$key]['datadb_ids'] ?? [], + $item['datadb_ids'] ?? [] + ))); + } + + return $bucket; +} + +function binding_list_for_template(PDO $pdo, int $templateId): array +{ + $stmt = $pdo->prepare( + "SELECT b.*, + COALESCE(m.field_label, f.fixed_field_key) AS field_label, + COALESCE(m.excel_column, f.excel_column) AS excel_column, + f.fixed_field_key + FROM lims_value_binding b + LEFT JOIN template_mapping m ON m.id = b.mapping_id AND b.binding_kind = 'custom' + LEFT JOIN template_fixed_mapping f ON f.id = b.mapping_id AND b.binding_kind = 'fixed' + WHERE b.template_id = ? + ORDER BY field_label ASC, b.source_value ASC" + ); + $stmt->execute([$templateId]); + + return $stmt->fetchAll(PDO::FETCH_ASSOC); +} diff --git a/public/userarea/delete_binding.php b/public/userarea/delete_binding.php new file mode 100644 index 00000000..008b2caa --- /dev/null +++ b/public/userarea/delete_binding.php @@ -0,0 +1,40 @@ + false, 'error' => 'Unauthorized']); + exit; +} + +if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + http_response_code(405); + echo json_encode(['success' => false, 'error' => 'Method not allowed']); + exit; +} + +$bindingId = intval($_POST['binding_id'] ?? 0); + +if ($bindingId <= 0) { + http_response_code(422); + echo json_encode(['success' => false, 'error' => 'Missing binding_id']); + exit; +} + +try { + $pdo = DBHandlerSelect::getInstance()->getConnection(); + $deleted = binding_delete_by_id($pdo, $bindingId); + + echo json_encode(['success' => $deleted > 0, 'deleted' => $deleted]); +} catch (Exception $e) { + http_response_code(500); + echo json_encode(['success' => false, 'error' => $e->getMessage()]); +} diff --git a/public/userarea/import_insert.php b/public/userarea/import_insert.php index f861f0f7..2582cb07 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")); @@ -70,7 +71,7 @@ $pdo = $db->getConnection(); $importReferenceCode = date('YmdHis') . '-' . uniqid(); // Recupera tutti i mapping dal template -$stmt = $pdo->prepare("SELECT id, excel_column, data_type, is_required, manual_default, is_manual, field_label, field_id, main_field, auto_value FROM template_mapping WHERE template_id = ?"); +$stmt = $pdo->prepare("SELECT id, excel_column, data_type, has_list, is_required, manual_default, is_manual, field_label, field_id, main_field, auto_value FROM template_mapping WHERE template_id = ?"); $stmt->execute([$template_id]); $allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC); @@ -88,6 +89,23 @@ foreach ($allMappings as $mapping) { } } +$fixedMappings = []; +try { + $fxStmt = $pdo->prepare( + "SELECT id, fixed_field_key, data_type, default_value, default_source, excel_column + FROM template_fixed_mapping + WHERE template_id = ?" + ); + $fxStmt->execute([$template_id]); + $fixedMappings = $fxStmt->fetchAll(PDO::FETCH_ASSOC); +} catch (Exception $e) { + error_log("[FIXED] lettura template_fixed_mapping fallita: " . $e->getMessage()); +} + +$savedBindings = []; // risolti con un binding gia' salvato +$autoBindings = []; // collegati in automatico (corrispondenza esatta) +$pendingBindings = []; // senza corrispondenza: da risolvere a mano + // Inserisci le righe selezionate in datadb $insertedIds = []; foreach ($selected_rows as $rowIndex) { @@ -172,6 +190,24 @@ foreach ($selected_rows as $rowIndex) { } } + if ( + binding_is_list_field($mapping) + && $fieldValue !== null + && trim((string) $fieldValue) !== '' + ) { + $fieldValue = binding_resolve_for_import( + $pdo, + $mapping, + (int) $template_id, + (string) $fieldValue, + (int) $iddatadb, + (int) $user_id, + $savedBindings, + $autoBindings, + $pendingBindings + ); + } + if ($mapping['is_required'] && (is_null($fieldValue) || $fieldValue === '')) { error_log("Required field missing for mapping ID: " . $mapping['id'] . ", field: " . $mapping['field_label']); } @@ -180,6 +216,43 @@ foreach ($selected_rows as $rowIndex) { $stmt->execute([$iddatadb, $mapping['id'], $fieldValue]); error_log("Inserted into import_data_details for ID $iddatadb, Mapping ID: " . $mapping['id'] . ", Field Value: " . var_export($fieldValue, true)); } + $fixedValues = []; + foreach ($fixedMappings as $fm) { + $fixedKey = (string) $fm['fixed_field_key']; + $value = null; + + if (($fm['default_source'] ?? 'manual') === 'xls' && trim((string) ($fm['excel_column'] ?? '')) !== '') { + $idx = binding_find_column_index((string) $fm['excel_column'], $columns); + $raw = ($idx !== false && isset($row[$idx])) ? trim((string) $row[$idx]) : ''; + + if ($raw !== '') { + $value = binding_resolve_fixed_for_import( + $pdo, + $fm, + (int) $template_id, + $raw, + (int) $iddatadb, + (int) $user_id, + $savedBindings, + $autoBindings, + $pendingBindings + ); + } + } + + if ($value === null || $value === '') { + $default = binding_fixed_default($fm); + $value = $default !== '' ? $default : null; + } + + if ($value !== null && $value !== '') { + $fixedValues[$fixedKey] = $value; + } + } + + if (!empty($fixedValues)) { + binding_write_fixed_columns($pdo, (int) $iddatadb, $fixedValues); + } } // ------------------------------------------------------------ @@ -237,5 +310,20 @@ try { $_SESSION['inserted_ids'] = $insertedIds; +if (!empty($pendingBindings)) { + $_SESSION['pending_bindings'] = [ + 'template_id' => $template_id, + 'importref' => $importReferenceCode, + 'items' => array_values($pendingBindings), + 'auto' => array_values($autoBindings), + 'saved' => array_values($savedBindings), + ]; + + header("Location: resolve_bindings.php"); + exit; +} + +unset($_SESSION['pending_bindings']); + header("Location: imported.php?id=" . urlencode($template_id) . "&importref=" . urlencode($importReferenceCode)); exit; diff --git a/public/userarea/import_insert_batch.php b/public/userarea/import_insert_batch.php index 5c6c0873..4557c246 100644 --- a/public/userarea/import_insert_batch.php +++ b/public/userarea/import_insert_batch.php @@ -5,6 +5,7 @@ ini_set('log_errors', 1); ini_set('error_log', __DIR__ . '/import_debug.log'); include('include/headscript.php'); +require_once __DIR__ . '/class/binding-functions.php'; header('Content-Type: application/json'); @@ -49,7 +50,7 @@ $db = DBHandlerSelect::getInstance(); $pdo = $db->getConnection(); // Recupera i mapping -$stmt = $pdo->prepare("SELECT id, excel_column, data_type, is_required, manual_default, is_manual, field_label, field_id, main_field, auto_value FROM template_mapping WHERE template_id = ?"); +$stmt = $pdo->prepare("SELECT id, excel_column, data_type, has_list, is_required, manual_default, is_manual, field_label, field_id, main_field, auto_value FROM template_mapping WHERE template_id = ?"); $stmt->execute([$template_id]); $allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC); @@ -76,6 +77,23 @@ $mappingId244 = (int)$stmtMap->fetchColumn(); $insertedIds = []; +$fixedMappings = []; +try { + $fxStmt = $pdo->prepare( + "SELECT id, fixed_field_key, data_type, default_value, default_source, excel_column + FROM template_fixed_mapping + WHERE template_id = ?" + ); + $fxStmt->execute([$template_id]); + $fixedMappings = $fxStmt->fetchAll(PDO::FETCH_ASSOC); +} catch (Exception $e) { + error_log("[FIXED] lettura template_fixed_mapping fallita: " . $e->getMessage()); +} + +$savedBindings = []; +$autoBindings = []; +$pendingBindings = []; + try { $pdo->beginTransaction(); @@ -146,9 +164,65 @@ try { } } + if ( + binding_is_list_field($mapping) + && $fieldValue !== null + && trim((string) $fieldValue) !== '' + ) { + $fieldValue = binding_resolve_for_import( + $pdo, + $mapping, + $template_id, + (string) $fieldValue, + (int) $iddatadb, + (int) $user_id, + $savedBindings, + $autoBindings, + $pendingBindings + ); + } + $insDetail->execute([$iddatadb, $mapping['id'], $fieldValue]); } + $fixedValues = []; + foreach ($fixedMappings as $fm) { + $fixedKey = (string) $fm['fixed_field_key']; + $value = null; + + if (($fm['default_source'] ?? 'manual') === 'xls' && trim((string) ($fm['excel_column'] ?? '')) !== '') { + $idx = binding_find_column_index((string) $fm['excel_column'], $columns); + $raw = ($idx !== false && isset($row[$idx])) ? trim((string) $row[$idx]) : ''; + + if ($raw !== '') { + $value = binding_resolve_fixed_for_import( + $pdo, + $fm, + (int) $template_id, + $raw, + (int) $iddatadb, + (int) $user_id, + $savedBindings, + $autoBindings, + $pendingBindings + ); + } + } + + if ($value === null || $value === '') { + $default = binding_fixed_default($fm); + $value = $default !== '' ? $default : null; + } + + if ($value !== null && $value !== '') { + $fixedValues[$fixedKey] = $value; + } + } + + if (!empty($fixedValues)) { + binding_write_fixed_columns($pdo, (int) $iddatadb, $fixedValues); + } + // Campo 244 (Accettatore) con lims_user_id if ($limsUserId !== '' && $mappingId244 > 0) { $insDetail->execute([$iddatadb, $mappingId244, $limsUserId]); @@ -157,9 +231,27 @@ try { $pdo->commit(); + $state = $_SESSION['pending_bindings'] ?? null; + if (!is_array($state) || ($state['importref'] ?? null) !== $importReferenceCode) { + $state = [ + 'template_id' => $template_id, + 'importref' => $importReferenceCode, + 'items' => [], + 'auto' => [], + 'saved' => [], + ]; + } + + $state['items'] = binding_merge_buckets($state['items'], $pendingBindings); + $state['auto'] = binding_merge_buckets($state['auto'], $autoBindings); + $state['saved'] = binding_merge_buckets($state['saved'], $savedBindings); + + $_SESSION['pending_bindings'] = $state; + echo json_encode([ - 'ok' => true, - 'inserted' => count($insertedIds) + 'ok' => true, + 'inserted' => count($insertedIds), + 'pending_bindings' => count($state['items']) ]); exit; } catch (Exception $e) { diff --git a/public/userarea/import_xls2.php b/public/userarea/import_xls2.php index b3d94d03..4b795d78 100644 --- a/public/userarea/import_xls2.php +++ b/public/userarea/import_xls2.php @@ -492,6 +492,7 @@ error_log("Loaded template: " . print_r($template, true)); const progressErr = document.getElementById('importProgressError'); let insertedTotal = 0; + let pendingBindings = 0; // valori a lista senza corrispondenza LIMS // Invia un batch e ritorna la risposta JSON async function sendBatch(startIndex) { @@ -550,6 +551,7 @@ error_log("Loaded template: " . print_r($template, true)); } insertedTotal += (result.inserted || 0); + pendingBindings = result.pending_bindings || pendingBindings; const done = Math.min(start + BATCH_SIZE, total); const pct = Math.round((done / total) * 100); progressBar.style.width = pct + '%'; @@ -565,7 +567,9 @@ error_log("Loaded template: " . print_r($template, true)); // PAUSA temporanea di 2 secondi per vedere la barra completa (da rimuovere dopo il test) await new Promise(r => setTimeout(r, 2000)); - window.location.href = 'imported.php?id=' + + window.location.href = pendingBindings > 0 + ? 'resolve_bindings.php' + : 'imported.php?id=' + encodeURIComponent(data.template_id) + '&importref=' + encodeURIComponent(importReferenceCode); }); diff --git a/public/userarea/include/navbar.php b/public/userarea/include/navbar.php index 5f7d63cd..6151e185 100644 --- a/public/userarea/include/navbar.php +++ b/public/userarea/include/navbar.php @@ -64,6 +64,11 @@ +
  • + + + +
  • diff --git a/public/userarea/mapping_template_xls_scheme2.php b/public/userarea/mapping_template_xls_scheme2.php index 07a68ce8..a3b74de6 100644 --- a/public/userarea/mapping_template_xls_scheme2.php +++ b/public/userarea/mapping_template_xls_scheme2.php @@ -88,7 +88,8 @@ $mappings = $stmt->fetchAll(PDO::FETCH_ASSOC); // Recupera i fixed fields dalla tabella template_fixed_mapping $stmt = $pdo->prepare(" - SELECT id, fixed_field_key, is_manual, data_type, is_required, default_value, is_visible_import + SELECT id, fixed_field_key, is_manual, data_type, is_required, default_value, is_visible_import, + default_source, excel_column FROM template_fixed_mapping WHERE template_id = ? ORDER BY id ASC @@ -594,6 +595,7 @@ $apiSampleJson = $template['api_sample_json'] ?? ''; Required Field Type + Source Default Value @@ -626,6 +628,28 @@ $apiSampleJson = $template['api_sample_json'] ?? ''; + + + + + + + + + @@ -1279,8 +1303,6 @@ $apiSampleJson = $template['api_sample_json'] ?? ''; }); } - - function saveJsonNodes(sampleJson, nodes) { return fetch('update_api_json_nodes.php', { method: 'POST', @@ -1363,6 +1385,41 @@ $apiSampleJson = $template['api_sample_json'] ?? ''; }); // fallback (nel caso cambi senza select2) + $(document).on('change', '.fixed-source-select', function() { + const id = this.dataset.fixedId; + const value = this.value; + const $colSelect = $(this).closest('td').find('.fixed-xls-column'); + + $colSelect.toggle(value === 'xls'); + if (value !== 'xls') { + $colSelect.val(''); + } + + saveFixedField(id, 'default_source', value); + }); + + $(document).on('change', '.fixed-xls-column', function() { + saveFixedField(this.dataset.fixedId, 'excel_column', this.value || null); + }); + + function saveFixedField(id, field, value) { + const status = document.getElementById('fixedSaveStatus'); + if (status) status.textContent = 'Salvataggio...'; + + fetch('update_fixed_field.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ id: parseInt(id, 10), field: field, value: value }) + }) + .then(r => r.json()) + .then(d => { + if (status) status.textContent = d.success ? 'Salvato' : ('Errore: ' + (d.message || '')); + }) + .catch(() => { + if (status) status.textContent = 'Errore di rete'; + }); + } + $(document).on('change', '.fixed-default-select', function() { saveFixedDefaultSelect(this); }); @@ -1882,7 +1939,6 @@ $apiSampleJson = $template['api_sample_json'] ?? ''; }); } - // Save original Main checkbox state document.querySelectorAll('#schemaFieldsBody .main-field-checkbox').forEach(cb => { cb.dataset.originalChecked = cb.checked ? 'true' : 'false'; @@ -2031,7 +2087,6 @@ $apiSampleJson = $template['api_sample_json'] ?? ''; } }); - document.getElementById('schemaFieldsBody').addEventListener('change', function(event) { if (event.target.classList.contains('manual-default') && event.target.tagName === 'SELECT') { let tr = event.target.closest('tr'); @@ -2233,7 +2288,6 @@ $apiSampleJson = $template['api_sample_json'] ?? ''; ${escapeHtml(fixedFieldLabels[r.fixed_field_key] || r.fixed_field_key)} - ${escapeHtml(r.data_type || '')} diff --git a/public/userarea/resolve_bindings.php b/public/userarea/resolve_bindings.php new file mode 100644 index 00000000..8c4e2385 --- /dev/null +++ b/public/userarea/resolve_bindings.php @@ -0,0 +1,417 @@ +getConnection(); + +$stmt = $pdo->prepare("SELECT name FROM excel_templates WHERE id = ?"); +$stmt->execute([$templateId]); +$templateName = $stmt->fetchColumn() ?: ('Template ' . $templateId); + +foreach ($items as $i => $item) { + $items[$i]['suggestion'] = ($item['kind'] ?? 'custom') === 'fixed' + ? binding_suggest_fixed( + $pdo, + (string) ($item['fixed_field_key'] ?? ''), + (string) $item['source_value'], + (int) $item['mapping_id'] + ) + : binding_suggest( + $pdo, + (int) $item['field_id'], + (string) $item['source_value'], + (int) $item['mapping_id'] + ); +} + +$resolvedItems = []; +foreach ($autoItems as $a) { + $a['badge'] = 'automatico'; + $a['badge_class'] = 'bg-success'; + $resolvedItems[] = $a; +} +foreach ($savedItems as $s) { + $s['badge'] = 'binding salvato'; + $s['badge_class'] = 'bg-secondary'; + $resolvedItems[] = $s; +} + +$groups = []; +$groupOrder = []; +$pushToGroup = function (array $row, string $type, $idx = null) use (&$groups, &$groupOrder) { + $kind = $row['kind'] ?? 'custom'; + $gkey = $kind . ':' . (int) ($row['mapping_id'] ?? 0); + if (!isset($groups[$gkey])) { + $groups[$gkey] = [ + 'label' => $row['field_label'] ?? ('Mapping ' . $gkey), + 'kind' => $kind, + 'pending' => [], + 'resolved' => [], + ]; + $groupOrder[] = $gkey; + } + if ($type === 'pending') { + $groups[$gkey]['pending'][] = ['idx' => $idx, 'data' => $row]; + } else { + $groups[$gkey]['resolved'][] = ['data' => $row]; + } +}; +foreach ($items as $idx => $item) { + $pushToGroup($item, 'pending', $idx); +} +foreach ($resolvedItems as $res) { + $pushToGroup($res, 'resolved'); +} + +$pendingRecords = 0; +foreach ($items as $item) { + $pendingRecords += count($item['datadb_ids'] ?? []); +} +?> + + + + + + + + + + + Binding valori → LIMS - <?= htmlspecialchars($titlewebsite ?? '', ENT_QUOTES, 'UTF-8'); ?> + + + +
    + + +
    +
    + +
    +
    +
    +
    +
    Binding valori importati → LIMS
    + · Template ID: +
    + + Gestione binding + +
    +
    + +
    +
    + Alcuni valori importati dal file non corrispondono a nessun valore delle liste LIMS. + Finche' non vengono collegati, questi campi non possono essere esportati. + Scegli il valore LIMS corretto per ciascuno: il binding viene salvato e + riutilizzato nelle importazioni successive. +
    + valore/i da risolvere su + record importati. +
    +
    + +
    + valore/i collegato/i automaticamente (corrispondenza esatta con il LIMS). +
    + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Valore importatoRecordValore LIMSAzioni
    + + campo fisso + · valore/i, da risolvere +
    › + + + +
    + + + suggerito · + + + In attesa + +
    +
    › + + + +
    +
    +
    + +
    + Salta per ora + +
    +
    +
    +
    + +
    +
    +
    + + +
    + + + + + + + + diff --git a/public/userarea/save_binding.php b/public/userarea/save_binding.php new file mode 100644 index 00000000..a6fe8e9b --- /dev/null +++ b/public/userarea/save_binding.php @@ -0,0 +1,101 @@ + false, 'error' => 'Unauthorized']); + exit; +} + +if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + http_response_code(405); + echo json_encode(['success' => false, 'error' => 'Method not allowed']); + exit; +} + +$kind = ($_POST['kind'] ?? 'custom') === 'fixed' ? 'fixed' : 'custom'; +$templateId = intval($_POST['template_id'] ?? 0); +$mappingId = intval($_POST['mapping_id'] ?? 0); +$fieldId = intval($_POST['field_id'] ?? 0); +$sourceValue = (string) ($_POST['source_value'] ?? ''); +$limsValueId = intval($_POST['lims_value_id'] ?? 0); +$limsValue = (string) ($_POST['lims_value'] ?? ''); + +$datadbIds = []; +if (isset($_POST['datadb_ids'])) { + $decoded = json_decode($_POST['datadb_ids'], true); + if (is_array($decoded)) { + $datadbIds = $decoded; + } +} + +if ($templateId <= 0 || $mappingId <= 0 || trim($sourceValue) === '' || $limsValueId <= 0) { + http_response_code(422); + echo json_encode(['success' => false, 'error' => 'Missing required parameters']); + exit; +} + +try { + $pdo = DBHandlerSelect::getInstance()->getConnection(); + $createdBy = Auth::user() ? (int) Auth::user()->present()->id : null; + + if ($kind === 'fixed') { + $stmt = $pdo->prepare("SELECT fixed_field_key FROM template_fixed_mapping WHERE id = ?"); + $stmt->execute([$mappingId]); + $fixedKey = (string) ($stmt->fetchColumn() ?: ''); + + if ($fixedKey === '' || !binding_fixed_is_list($fixedKey)) { + http_response_code(422); + echo json_encode(['success' => false, 'error' => 'Invalid fixed field']); + exit; + } + + binding_upsert($pdo, $templateId, $mappingId, 0, $sourceValue, $limsValueId, $limsValue, $createdBy, 'fixed'); + + $applied = !empty($datadbIds) + ? binding_apply_to_datadb($pdo, $fixedKey, (string) $limsValueId, $datadbIds) + : 0; + + echo json_encode([ + 'success' => true, + 'applied_rows' => $applied, + 'kind' => 'fixed', + 'source_value' => $sourceValue, + 'lims_value' => $limsValue, + 'lims_value_id' => $limsValueId, + ]); + exit; + } + + if ($fieldId <= 0) { + $stmt = $pdo->prepare("SELECT field_id FROM template_mapping WHERE id = ?"); + $stmt->execute([$mappingId]); + $fieldId = (int) ($stmt->fetchColumn() ?: 0); + } + + binding_upsert($pdo, $templateId, $mappingId, $fieldId, $sourceValue, $limsValueId, $limsValue, $createdBy); + + $applied = !empty($datadbIds) + ? binding_apply_to_details($pdo, $mappingId, (string) $limsValueId, $datadbIds) + : 0; + + echo json_encode([ + 'success' => true, + 'applied_rows' => $applied, + 'kind' => 'custom', + 'source_value' => $sourceValue, + 'lims_value' => $limsValue, + 'lims_value_id' => $limsValueId, + ]); +} catch (Exception $e) { + http_response_code(500); + echo json_encode(['success' => false, 'error' => $e->getMessage()]); +} diff --git a/public/userarea/search_fixed_field_values.php b/public/userarea/search_fixed_field_values.php new file mode 100644 index 00000000..b06b536d --- /dev/null +++ b/public/userarea/search_fixed_field_values.php @@ -0,0 +1,54 @@ + 'Unauthorized']); + exit; +} + +$fieldKey = trim($_GET['field_key'] ?? ''); +$templateId = (int) ($_GET['template_id'] ?? 0); +$q = binding_normalize_value($_GET['q'] ?? ''); +$rawLimit = (int) ($_GET['limit'] ?? 50); +$limit = $rawLimit <= 0 ? 0 : max(1, min(500, $rawLimit)); + +if ($fieldKey === '' || !binding_fixed_is_list($fieldKey)) { + echo json_encode(['results' => []]); + exit; +} + +try { + $pdo = DBHandlerSelect::getInstance()->getConnection(); + $values = binding_get_fixed_values($pdo, $fieldKey, $templateId); + + if ($q !== '') { + $terms = array_values(array_filter(preg_split('/\s+/u', $q, -1, PREG_SPLIT_NO_EMPTY))); + $values = array_values(array_filter($values, function ($v) use ($terms) { + $hay = binding_normalize_value($v['text'] ?? ''); + foreach ($terms as $t) { + if (mb_strpos($hay, $t) === false) { + return false; + } + } + return true; + })); + } + + if ($limit > 0) { + $values = array_slice($values, 0, $limit); + } + + echo json_encode(['results' => $values]); +} catch (Exception $e) { + http_response_code(500); + echo json_encode(['error' => $e->getMessage(), 'results' => []]); +} diff --git a/public/userarea/skip_binding.php b/public/userarea/skip_binding.php new file mode 100644 index 00000000..509d5173 --- /dev/null +++ b/public/userarea/skip_binding.php @@ -0,0 +1,71 @@ + false, 'error' => 'Unauthorized']); + exit; +} + +if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + http_response_code(405); + echo json_encode(['success' => false, 'error' => 'Method not allowed']); + exit; +} + +$kind = ($_POST['kind'] ?? 'custom') === 'fixed' ? 'fixed' : 'custom'; +$mappingId = intval($_POST['mapping_id'] ?? 0); +$sourceValue = (string) ($_POST['source_value'] ?? ''); + +$datadbIds = []; +if (isset($_POST['datadb_ids'])) { + $decoded = json_decode($_POST['datadb_ids'], true); + if (is_array($decoded)) { + $datadbIds = $decoded; + } +} + +if ($mappingId <= 0 || trim($sourceValue) === '') { + http_response_code(422); + echo json_encode(['success' => false, 'error' => 'Missing required parameters']); + exit; +} + +try { + $pdo = DBHandlerSelect::getInstance()->getConnection(); + + if ($kind === 'fixed') { + $stmt = $pdo->prepare("SELECT fixed_field_key FROM template_fixed_mapping WHERE id = ?"); + $stmt->execute([$mappingId]); + $fixedKey = (string) ($stmt->fetchColumn() ?: ''); + + if ($fixedKey === '') { + http_response_code(422); + echo json_encode(['success' => false, 'error' => 'Invalid fixed field']); + exit; + } + + $cleared = !empty($datadbIds) + ? binding_apply_to_datadb($pdo, $fixedKey, null, $datadbIds) + : 0; + } else { + $cleared = !empty($datadbIds) + ? binding_apply_to_details($pdo, $mappingId, '', $datadbIds) + : 0; + } + + binding_delete($pdo, $mappingId, $sourceValue, $kind); + + echo json_encode(['success' => true, 'cleared_rows' => $cleared]); +} catch (Exception $e) { + http_response_code(500); + echo json_encode(['success' => false, 'error' => $e->getMessage()]); +} diff --git a/public/userarea/sql/fixed_field_binding.sql b/public/userarea/sql/fixed_field_binding.sql new file mode 100644 index 00000000..a56e147c --- /dev/null +++ b/public/userarea/sql/fixed_field_binding.sql @@ -0,0 +1,19 @@ +-- json_node e default_source possono gia' esistere: in quel caso saltare le ADD COLUMN. + +ALTER TABLE `template_fixed_mapping` + ADD COLUMN `json_node` varchar(500) DEFAULT NULL, + ADD COLUMN `default_source` enum('manual','xls','json') NOT NULL DEFAULT 'manual'; + +ALTER TABLE `template_fixed_mapping` + ADD COLUMN `excel_column` varchar(255) DEFAULT NULL AFTER `json_node`; + +ALTER TABLE `template_fixed_mapping` + MODIFY COLUMN `default_source` enum('manual','xls','json') NOT NULL DEFAULT 'manual'; + +ALTER TABLE `lims_value_binding` + ADD COLUMN `binding_kind` enum('custom','fixed') NOT NULL DEFAULT 'custom' AFTER `template_id`, + MODIFY COLUMN `field_id` int(11) NOT NULL DEFAULT 0; + +ALTER TABLE `lims_value_binding` DROP INDEX `uq_mapping_source`; +ALTER TABLE `lims_value_binding` + ADD UNIQUE KEY `uq_kind_mapping_source` (`binding_kind`, `mapping_id`, `source_key`); diff --git a/public/userarea/sql/lims_value_binding.sql b/public/userarea/sql/lims_value_binding.sql new file mode 100644 index 00000000..06cc8a4e --- /dev/null +++ b/public/userarea/sql/lims_value_binding.sql @@ -0,0 +1,17 @@ +CREATE TABLE IF NOT EXISTS `lims_value_binding` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `template_id` int(11) NOT NULL, + `mapping_id` int(11) NOT NULL, + `field_id` int(11) NOT NULL, + `source_value` varchar(500) NOT NULL, + `source_key` varchar(500) NOT NULL, + `lims_value_id` int(11) NOT NULL, + `lims_value` varchar(500) DEFAULT NULL, + `created_by` int(11) DEFAULT NULL, + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uq_mapping_source` (`mapping_id`, `source_key`), + KEY `idx_template` (`template_id`), + KEY `idx_field_source` (`field_id`, `source_key`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; diff --git a/public/userarea/update_fixed_field.php b/public/userarea/update_fixed_field.php index 71b6c97e..3b0a7132 100644 --- a/public/userarea/update_fixed_field.php +++ b/public/userarea/update_fixed_field.php @@ -17,7 +17,7 @@ if ($id <= 0) { exit; } -$allowed = ['default_value', 'is_visible_import', 'is_required']; +$allowed = ['default_value', 'is_visible_import', 'is_required', 'default_source', 'excel_column']; if (!in_array($field, $allowed, true)) { echo json_encode(['success' => false, 'message' => 'Invalid field']); exit; @@ -27,6 +27,21 @@ if ($field === 'is_visible_import' || $field === 'is_required') { $value = ((int)$value === 1) ? 1 : 0; } +if ($field === 'default_source') { + $value = in_array($value, ['manual', 'xls', 'json'], true) ? $value : 'manual'; + + if ($value !== 'xls') { + $pdo->prepare("UPDATE template_fixed_mapping SET excel_column = NULL WHERE id = :id") + ->execute([':id' => $id]); + } +} + +if ($field === 'excel_column') { + $value = trim((string)$value); + if ($value === '') { + $value = null; + } +} $sql = "UPDATE template_fixed_mapping SET {$field} = :val WHERE id = :id"; $stmt = $pdo->prepare($sql); diff --git a/public/userarea/validate_export.php b/public/userarea/validate_export.php index 76b48f92..e039dcac 100644 --- a/public/userarea/validate_export.php +++ b/public/userarea/validate_export.php @@ -22,6 +22,7 @@ * To add a new rule — just append another closure to $validators. */ include('include/headscript.php'); +require_once __DIR__ . '/class/binding-functions.php'; $dbHandler = DBHandlerSelect::getInstance(); $pdo = $dbHandler->getConnection(); @@ -124,6 +125,29 @@ $validators[] = function (int $iddatadb, array $ctx): array { return $errors; }; +$validators[] = function (int $iddatadb, array $ctx): array { + $errors = []; + + foreach (($ctx['listCustom'] ?? []) as $cf) { + $val = trim((string) ($ctx['customValues'][(int) $cf['mapping_id']] ?? '')); + if ($val === '' || ctype_digit($val)) { + continue; + } + if (!binding_has_lims_values((int) $cf['field_id'])) { + continue; + } + + $label = rtrim($cf['field_label'], ': '); + $errors[] = [ + 'field' => 'field_label:' . $cf['field_label'], + 'message' => $label . ': il valore importato "' . $val . '" non e\' collegato a nessun valore ' + . 'della lista LIMS. Aprire Gestione binding o ri-importare per collegarlo.', + ]; + } + + return $errors; +}; + // Logical fixed_field_key - real datadb column (mirrors imported.php $fixedAliasMap) $fixedAliasMap = [ 'ClienteResponsabile' => 'cliente_responsabile_id', @@ -197,6 +221,7 @@ try { $requiredFixedByTemplate = []; // template_id => [ fixed_field_key => label ] $requiredCustomByTemplate = []; // template_id => [ { mapping_id, field_label }, ... ] + $listCustomByTemplate = []; // template_id => [ { mapping_id, field_label }, ... ] if (!empty($templateIds)) { $tplPlaceholders = implode(',', array_fill(0, count($templateIds), '?')); @@ -237,6 +262,21 @@ try { 'field_label' => $r['field_label'], ]; } + + $stmt = $pdo->prepare(" + SELECT id AS mapping_id, template_id, field_id, field_label + FROM template_mapping + WHERE template_id IN ($tplPlaceholders) + AND data_type = 'SceltaMultipla' + "); + $stmt->execute($templateIds); + foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) { + $listCustomByTemplate[(int)$r['template_id']][] = [ + 'mapping_id' => (int)$r['mapping_id'], + 'field_id' => (int)$r['field_id'], + 'field_label' => $r['field_label'], + ]; + } } // Custom field values per record (import_data_details.id is the FK to datadb) @@ -268,6 +308,7 @@ try { 'fixedAliasMap' => $fixedAliasMap, 'requiredFixed' => $requiredFixedByTemplate[$templateId] ?? [], 'requiredCustom' => $requiredCustomByTemplate[$templateId] ?? [], + 'listCustom' => $listCustomByTemplate[$templateId] ?? [], 'customValues' => $customValuesByRecord[$iddatadb] ?? [], ];