": { * valid: bool, * iddatadb: int, * errors: [ { field: "", message: "..." }, ... ] * } * } * } * * Validators are closures registered in $validators[]. * Each receives ($iddatadb, $ctx) and returns an array of errors (or empty []). * $ctx holds all prefetched data (record, parts, field values, mappings, etc.). * To add a new rule — just append another closure to $validators. */ include('include/headscript.php'); $dbHandler = DBHandlerSelect::getInstance(); $pdo = $dbHandler->getConnection(); header("Content-Type: application/json"); // ── Validators ────────────────────────────────────────────────────────────── // Each validator is a closure: fn(int $iddatadb, array $ctx): array<{field,message}> // $ctx keys: record, parts, fieldValues, requiredFixed, requiredMappings, fixedAliasMap $validators = []; // 1. Every part must have a valid Matrice // Without Matrice the LIMS API rejects the Campione creation. $validators[] = function (int $iddatadb, array $ctx): array { $parts = $ctx['parts']; if (empty($parts)) { return []; } $missing = []; foreach ($parts as $p) { $matrice = (int)($p['idmatrice'] ?? 0); if ($matrice <= 0) { $label = $p['part_number'] ?: ($p['part_description'] ?: '(senza nome)'); $missing[] = $label; } } if (!empty($missing)) { return [[ 'field' => 'parts', 'message' => 'Matrice mancante — il LIMS rifiuterà l\'esportazione. ' . 'Part senza matrice: ' . implode(', ', $missing) . '. Aprire la sezione Parts e assegnare la matrice.', ]]; } return []; }; // 2. ConsegnaRichiesta must be filled and >= today $validators[] = function (int $iddatadb, array $ctx): array { $record = $ctx['record'] ?? null; if (!$record) { return []; } $consegna = $record['consegna_richiesta'] ?? null; if (empty($consegna)) { return [[ 'field' => 'ConsegnaRichiesta', 'message' => 'ConsegnaRichiesta è obbligatorio.', ]]; } $consegnaDate = DateTime::createFromFormat('Y-m-d', $consegna) ?: DateTime::createFromFormat('Y-m-d H:i:s', $consegna); $today = new DateTime('today'); if ($consegnaDate && $consegnaDate < $today) { return [[ 'field' => 'ConsegnaRichiesta', 'message' => 'ConsegnaRichiesta deve essere uguale o superiore alla data odierna (' . $today->format('Y-m-d') . ').', ]]; } return []; }; // 3. All LIMS-mandatory fields must be filled. $validators[] = function (int $iddatadb, array $ctx): array { $record = $ctx['record'] ?? null; if (!$record) { return []; } $errors = []; // Fixed fields (stored as columns in datadb) foreach (($ctx['requiredFixed'] ?? []) as $key => $label) { $col = $ctx['fixedAliasMap'][$key] ?? null; $val = $col !== null ? ($record[$col] ?? null) : null; if ($val === null || $val === '' || (int) $val === 0) { $errors[] = [ 'field' => $key, 'message' => $label . ' è obbligatorio.', ]; } } // Custom fields (values stored in import_data_details, keyed by mapping_id) foreach (($ctx['requiredCustom'] ?? []) as $cf) { $val = $ctx['customValues'][(int) $cf['mapping_id']] ?? null; if ($val === null || trim((string) $val) === '') { $errors[] = [ 'field' => 'field_label:' . $cf['field_label'], 'message' => rtrim($cf['field_label'], ': ') . ' è obbligatorio.', ]; } } return $errors; }; // Logical fixed_field_key - real datadb column (mirrors imported.php $fixedAliasMap) $fixedAliasMap = [ '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 keys NOT enforced by the generic mandatory check above: // - ConsegnaRichiesta: handled by its dedicated validator (also checks the date) // - ClienteFornitore / ClienteAnalisi: nullable placeholders, sent as null on // export and accepted by LIMS. $skipRequiredFixed = ['ConsegnaRichiesta', 'ClienteFornitore', 'ClienteAnalisi']; // ── Main ──────────────────────────────────────────────────────────────────── try { $input = json_decode(file_get_contents('php://input'), true); $rows = $input['rows'] ?? []; if (empty($rows)) { echo json_encode(['success' => false, 'message' => 'No rows provided']); exit; } // ── Prefetch all data in bulk ─────────────────────────────────────────── $iddatadbList = array_column($rows, 'iddatadb'); $placeholders = implode(',', array_fill(0, count($iddatadbList), '?')); // Records (datadb) — templateid + fixed-field columns for mandatory validation $stmt = $pdo->prepare(" SELECT iddatadb, templateid, consegna_richiesta, cliente_responsabile_id, moltiplicatore_prezzo_id, anagrafica_certest_object_id, anagrafica_certest_service_id, cliente_fornitore_id, clienteAnalisi FROM datadb WHERE iddatadb IN ($placeholders) "); $stmt->execute($iddatadbList); $recordsInfo = []; foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) { $recordsInfo[(int)$r['iddatadb']] = $r; } // Parts (full rows, so validators can report which parts have issues) $stmt = $pdo->prepare(" SELECT iddatadb, part_number, part_description, idmatrice FROM identification_parts WHERE iddatadb IN ($placeholders) "); $stmt->execute($iddatadbList); $partsInfo = []; foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) { $partsInfo[(int)$r['iddatadb']][] = $r; } // Mandatory-field config per template $templateIds = array_values(array_unique(array_filter(array_map( fn ($r) => (int)($r['templateid'] ?? 0), $recordsInfo )))); $requiredFixedByTemplate = []; // template_id => [ fixed_field_key => label ] $requiredCustomByTemplate = []; // template_id => [ { mapping_id, field_label }, ... ] if (!empty($templateIds)) { $tplPlaceholders = implode(',', array_fill(0, count($templateIds), '?')); // Required fixed fields (is_required synced from LIMS ObbligatorioWeb) $stmt = $pdo->prepare(" SELECT template_id, fixed_field_key FROM template_fixed_mapping WHERE template_id IN ($tplPlaceholders) AND is_required = 1 "); $stmt->execute($templateIds); foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) { $key = $r['fixed_field_key']; if (in_array($key, $skipRequiredFixed, true)) { continue; } $requiredFixedByTemplate[(int)$r['template_id']][$key] = $key; } // Required custom fields that are visible in the import grid $stmt = $pdo->prepare(" SELECT id AS mapping_id, template_id, field_label FROM template_mapping WHERE template_id IN ($tplPlaceholders) AND is_required = 1 AND is_visible_import = 1 "); $stmt->execute($templateIds); foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) { $requiredCustomByTemplate[(int)$r['template_id']][] = [ 'mapping_id' => (int)$r['mapping_id'], 'field_label' => $r['field_label'], ]; } } // Custom field values per record (import_data_details.id is the FK to datadb) $stmt = $pdo->prepare(" SELECT id AS iddatadb, mapping_id, field_value FROM import_data_details WHERE id IN ($placeholders) "); $stmt->execute($iddatadbList); $customValuesByRecord = []; // iddatadb => [ mapping_id => field_value ] foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) { $customValuesByRecord[(int)$r['iddatadb']][(int)$r['mapping_id']] = $r['field_value']; } // ── Run validators per row ────────────────────────────────────────────── $results = []; foreach ($rows as $rowInfo) { $iddatadb = (int)$rowInfo['iddatadb']; $index = $rowInfo['index']; // Build context for validators $record = $recordsInfo[$iddatadb] ?? null; $templateId = (int)($record['templateid'] ?? 0); $ctx = [ 'record' => $record, 'parts' => $partsInfo[$iddatadb] ?? [], 'fixedAliasMap' => $fixedAliasMap, 'requiredFixed' => $requiredFixedByTemplate[$templateId] ?? [], 'requiredCustom' => $requiredCustomByTemplate[$templateId] ?? [], 'customValues' => $customValuesByRecord[$iddatadb] ?? [], ]; $errors = []; foreach ($validators as $validator) { $errors = array_merge($errors, $validator($iddatadb, $ctx)); } $results[$index] = [ 'valid' => empty($errors), 'iddatadb' => $iddatadb, 'errors' => $errors, ]; } echo json_encode(['success' => true, 'results' => $results]); } catch (Exception $e) { error_log("Validation error: " . $e->getMessage()); echo json_encode(['success' => false, 'message' => $e->getMessage()]); }