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); }