diff --git a/public/userarea/import_insert_batch.php b/public/userarea/import_insert_batch.php new file mode 100644 index 0000000..5c6c087 --- /dev/null +++ b/public/userarea/import_insert_batch.php @@ -0,0 +1,173 @@ + false, 'error' => 'Richiesta non valida']); + exit; +} + +$template_id = intval($input['template_id']); +$importReferenceCode = (string)$input['importreferencecode']; +$newFilename = (string)$input['filename']; +$columns = $input['columns']; // array +$batchRows = $input['batch_rows']; // array di righe (ognuna array di celle) +$batchExcelrows = $input['batch_excelrows']; // array di excelrow paralleli a batchRows + +$user_id = $iduserlogin ?? 1; + +function normalizeColName($s): string +{ + $s = (string)$s; + $s = str_replace( + ["\xC2\xA0", "\xE2\x80\xAF", "\xE2\x80\x87", "\xE2\x80\x89", "\xE2\x80\x8A", "\xEF\xBB\xBF"], + ' ', + $s + ); + $s = preg_replace('/\s+/u', ' ', $s); + return trim((string)$s); +} + +$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->execute([$template_id]); +$allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC); + +if (empty($allMappings)) { + echo json_encode(['ok' => false, 'error' => 'Nessun mapping trovato per il template']); + exit; +} + +// idclient di default (una volta sola) +$template_stmt = $pdo->prepare("SELECT idclient FROM excel_templates WHERE id = ?"); +$template_stmt->execute([$template_id]); +$tpl = $template_stmt->fetch(PDO::FETCH_ASSOC); +$default_idclient = $tpl['idclient'] ?? null; + +// lims_user_id per il campo 244 (una volta sola) +$stmtUser = $pdo->prepare("SELECT lims_user_id FROM auth_users WHERE id = ? LIMIT 1"); +$stmtUser->execute([(int)$user_id]); +$limsUserId = $stmtUser->fetchColumn(); +$limsUserId = ($limsUserId !== false && $limsUserId !== null && $limsUserId !== '') ? (string)$limsUserId : ''; + +$stmtMap = $pdo->prepare("SELECT id FROM template_mapping WHERE template_id = ? AND field_id = 244 LIMIT 1"); +$stmtMap->execute([(int)$template_id]); +$mappingId244 = (int)$stmtMap->fetchColumn(); + +$insertedIds = []; + +try { + $pdo->beginTransaction(); + + $insDatadb = $pdo->prepare("INSERT INTO datadb (templateid, importreferencecode, filename_import, status, user_id, limscode, importdate, excelrow, idclient) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"); + $insDetail = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, ?)"); + + foreach ($batchRows as $i => $row) { + $excelrow = $batchExcelrows[$i] ?? null; + if ($row === null || $excelrow === null) { + continue; + } + + $insDatadb->execute([ + $template_id, + $importReferenceCode, + $newFilename, + 'i', + $user_id, + null, + date('Y-m-d'), + $excelrow, + $default_idclient + ]); + + $iddatadb = $pdo->lastInsertId(); + $insertedIds[] = $iddatadb; + + foreach ($allMappings as $mapping) { + $fieldValue = null; + if (!$mapping['is_manual']) { + $excelColumn = trim($mapping['excel_column']); + $excelColumnIndex = array_search( + normalizeColName($excelColumn), + array_map('normalizeColName', $columns) + ); + if ($excelColumnIndex !== false && isset($row[$excelColumnIndex]) && $row[$excelColumnIndex] !== '') { + $fieldValue = $row[$excelColumnIndex]; + } else { + $fieldValue = $mapping['manual_default'] ?? ''; + } + switch ($mapping['data_type']) { + case 'INT': + $fieldValue = is_numeric($fieldValue) ? (int)$fieldValue : ($mapping['manual_default'] ?? 0); + break; + case 'DATE': + $fieldValue = !empty($fieldValue) ? date('Y-m-d', strtotime($fieldValue)) : ($mapping['manual_default'] === 'today' ? date('Y-m-d') : ($mapping['manual_default'] ?? '')); + break; + case 'CHAR': + $fieldValue = !empty($fieldValue) ? substr((string)$fieldValue, 0, 1) : ($mapping['manual_default'] ?? ''); + break; + case 'Testo': + case 'VARCHAR': + default: + $fieldValue = !empty($fieldValue) ? (string)$fieldValue : ($mapping['manual_default'] ?? ''); + break; + } + } else { + $fieldValue = $mapping['manual_default'] ?? ''; + if ($mapping['data_type'] === 'DATE' && $mapping['manual_default'] === 'today') { + $fieldValue = date('Y-m-d'); + } + } + if (($fieldValue === null || $fieldValue === '') && !empty($mapping['auto_value']) && $mapping['auto_value'] !== 'none') { + if ($mapping['auto_value'] === 'import_date') { + $fieldValue = date('Y-m-d'); + } elseif ($mapping['auto_value'] === 'import_time') { + $fieldValue = date('H:i'); + } + } + + $insDetail->execute([$iddatadb, $mapping['id'], $fieldValue]); + } + + // Campo 244 (Accettatore) con lims_user_id + if ($limsUserId !== '' && $mappingId244 > 0) { + $insDetail->execute([$iddatadb, $mappingId244, $limsUserId]); + } + } + + $pdo->commit(); + + echo json_encode([ + 'ok' => true, + 'inserted' => count($insertedIds) + ]); + exit; +} catch (Exception $e) { + if ($pdo->inTransaction()) $pdo->rollBack(); + error_log("[BATCH IMPORT] " . $e->getMessage()); + echo json_encode([ + 'ok' => false, + 'error' => $e->getMessage() + ]); + exit; +} diff --git a/public/userarea/import_xls2.php b/public/userarea/import_xls2.php index e0b9f87..b3d94d0 100644 --- a/public/userarea/import_xls2.php +++ b/public/userarea/import_xls2.php @@ -344,8 +344,9 @@ error_log("Loaded template: " . print_r($template, true)); - -
+ +
+ 0 selezionati
@@ -386,46 +387,187 @@ error_log("Loaded template: " . print_r($template, true));
- - + +
+ 0 selezionati + +
`; tableContainer.innerHTML = html; const selectRowsForm = document.getElementById('selectRowsForm'); - selectRowsForm.addEventListener('submit', function(e) { + // Clic sulla cella (oltre che sulla checkbox) per selezionare/deselezionare la riga + document.querySelectorAll('#importPreviewTable tbody tr').forEach(tr => { + tr.style.cursor = 'pointer'; + tr.addEventListener('click', function(e) { + // Se ho cliccato proprio sulla checkbox, lascio fare a lei (evita doppio toggle) + if (e.target.classList.contains('row-checkbox')) return; + + const cb = this.querySelector('.row-checkbox'); + if (!cb) return; + cb.checked = !cb.checked; + cb.dispatchEvent(new Event('change', { + bubbles: true + })); + }); + }); + + selectRowsForm.addEventListener('submit', async function(e) { + e.preventDefault(); // gestiamo tutto via AJAX a batch + const checkedBoxes = Array.from(document.querySelectorAll('.row-checkbox:checked')); if (checkedBoxes.length === 0) { - e.preventDefault(); alert('Seleziona almeno una riga.'); return; } + // Raccoglie le righe selezionate (riga mappata + excelrow parallelo) const selectedRows = []; const selectedExcelRows = []; - checkedBoxes.forEach((cb, newIndex) => { + checkedBoxes.forEach(cb => { const originalIndex = parseInt(cb.value, 10); - if (data.rows && data.rows[originalIndex]) { selectedRows.push(data.rows[originalIndex]); + selectedExcelRows.push( + (data.excel_data && data.excel_data[originalIndex]) ? + data.excel_data[originalIndex].excelrow : + null + ); } - - if (data.excel_data && data.excel_data[originalIndex]) { - selectedExcelRows.push(data.excel_data[originalIndex].excelrow); - } - - // Reindex selected_rows so import_insert.php receives only the reduced rows array - cb.value = newIndex; }); - document.getElementById('selectedRowsData').value = - encodeURIComponent(JSON.stringify(selectedRows)); + const total = selectedRows.length; + if (total === 0) { + alert('Nessuna riga valida da importare.'); + return; + } - document.getElementById('selectedExcelRowsData').value = - encodeURIComponent(JSON.stringify(selectedExcelRows)); + // importreferencecode generato UNA volta, condiviso da tutti i batch + const importReferenceCode = + new Date().toISOString().replace(/[-:.TZ]/g, '').slice(0, 14) + + '-' + Math.random().toString(36).slice(2, 10); + + const BATCH_SIZE = 20; + + // Disabilita i pulsanti e mostra la barra di progresso + const btnTop = document.getElementById('proceedButtonTop'); + const btnBottom = document.getElementById('proceedButtonBottom'); + if (btnTop) btnTop.disabled = true; + if (btnBottom) btnBottom.disabled = true; + + const progressHtml = ` +
+
+
Importazione in corso... + 0 / ${total} +
+
+
0%
+
+ +
+
`; + document.body.insertAdjacentHTML('beforeend', progressHtml); + + const progressBar = document.getElementById('importProgressBar'); + const progressText = document.getElementById('importProgressText'); + const progressErr = document.getElementById('importProgressError'); + + let insertedTotal = 0; + + // Invia un batch e ritorna la risposta JSON + async function sendBatch(startIndex) { + const batchRows = selectedRows.slice(startIndex, startIndex + BATCH_SIZE); + const batchExcelrows = selectedExcelRows.slice(startIndex, startIndex + BATCH_SIZE); + + const resp = await fetch('import_insert_batch.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + template_id: data.template_id, + importreferencecode: importReferenceCode, + filename: data.filename, + columns: data.columns, + batch_rows: batchRows, + batch_excelrows: batchExcelrows + }) + }); + return resp.json(); + } + + // Cicla i batch in sequenza + for (let start = 0; start < total; start += BATCH_SIZE) { + let result; + try { + result = await sendBatch(start); + } catch (err) { + result = { + ok: false, + error: err.message + }; + } + + if (!result.ok) { + // Batch fallito: chiediamo all'utente se continuare + progressErr.textContent = + 'Errore su un blocco (righe ' + (start + 1) + '-' + + Math.min(start + BATCH_SIZE, total) + '): ' + (result.error || 'errore sconosciuto'); + progressErr.style.display = 'block'; + + const continua = confirm( + 'Un blocco di righe non รจ stato importato.\n\n' + + 'Righe inserite finora: ' + insertedTotal + ' di ' + total + '.\n\n' + + 'Vuoi continuare con i blocchi successivi? (Annulla = interrompi)' + ); + + if (!continua) { + progressBar.classList.remove('progress-bar-animated'); + progressBar.classList.add('bg-warning'); + return; + } + // se continua, prosegue col prossimo blocco + continue; + } + + insertedTotal += (result.inserted || 0); + const done = Math.min(start + BATCH_SIZE, total); + const pct = Math.round((done / total) * 100); + progressBar.style.width = pct + '%'; + progressBar.textContent = pct + '%'; + progressText.textContent = insertedTotal + ' / ' + total; + } + + // Fine: barra verde e redirect a imported.php + progressBar.classList.remove('progress-bar-animated', 'progress-bar-striped'); + progressBar.classList.add('bg-success'); + progressText.textContent = insertedTotal + ' / ' + total + ' completate'; + + // 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=' + + encodeURIComponent(data.template_id) + + '&importref=' + encodeURIComponent(importReferenceCode); }); const topTableScrollbar = document.getElementById('topTableScrollbar'); @@ -475,10 +617,17 @@ error_log("Loaded template: " . print_r($template, true)); const checkboxes = document.querySelectorAll('.row-checkbox'); function updateProceedButton() { - const enabled = Array.from(checkboxes).some(cb => cb.checked); + const checkedCount = Array.from(checkboxes).filter(cb => cb.checked).length; + const enabled = checkedCount > 0; if (proceedButtonTop) proceedButtonTop.disabled = !enabled; if (proceedButtonBottom) proceedButtonBottom.disabled = !enabled; + + const label = checkedCount + ' selezionati'; + const countTop = document.getElementById('selectedCountTop'); + const countBottom = document.getElementById('selectedCountBottom'); + if (countTop) countTop.textContent = label; + if (countBottom) countBottom.textContent = label; } selectAllCheckbox.addEventListener('change', function() {