fixed import XLS big
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
ini_set('display_errors', 0); // in AJAX gli errori devono NON sporcare il JSON
|
||||
error_reporting(E_ALL);
|
||||
ini_set('log_errors', 1);
|
||||
ini_set('error_log', __DIR__ . '/import_debug.log');
|
||||
|
||||
include('include/headscript.php');
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Legge il body JSON
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (
|
||||
!$input ||
|
||||
!isset($input['template_id']) ||
|
||||
!isset($input['importreferencecode']) ||
|
||||
!isset($input['filename']) ||
|
||||
!isset($input['columns']) ||
|
||||
!isset($input['batch_rows']) ||
|
||||
!isset($input['batch_excelrows'])
|
||||
) {
|
||||
echo json_encode(['ok' => 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;
|
||||
}
|
||||
+169
-20
@@ -344,8 +344,9 @@ error_log("Loaded template: " . print_r($template, true));
|
||||
<input type="hidden" name="excelrows" id="selectedExcelRowsData" value="">
|
||||
<input type="hidden" name="filename" value="${data.filename}">
|
||||
|
||||
<!-- TOP BUTTON -->
|
||||
<div class="d-flex justify-content-end mb-3">
|
||||
<!-- TOP BUTTON -->
|
||||
<div class="d-flex justify-content-end align-items-center mb-3 gap-2">
|
||||
<span class="badge bg-secondary" id="selectedCountTop">0 selezionati</span>
|
||||
<button type="submit" class="btn btn-primary" id="proceedButtonTop" disabled>Prosegui</button>
|
||||
</div>
|
||||
|
||||
@@ -386,46 +387,187 @@ error_log("Loaded template: " . print_r($template, true));
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- BOTTOM BUTTON -->
|
||||
<button type="submit" class="btn btn-primary mt-3" id="proceedButtonBottom" disabled>Prosegui</button>
|
||||
<!-- BOTTOM BUTTON -->
|
||||
<div class="d-flex align-items-center mt-3 gap-2">
|
||||
<span class="badge bg-secondary" id="selectedCountBottom">0 selezionati</span>
|
||||
<button type="submit" class="btn btn-primary" id="proceedButtonBottom" disabled>Prosegui</button>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
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 = `
|
||||
<div id="importProgressWrap" style="
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
z-index: 20000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;">
|
||||
<div style="
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
padding: 25px 30px;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.3);">
|
||||
<div class="mb-2"><strong>Importazione in corso...</strong>
|
||||
<span id="importProgressText" class="float-end">0 / ${total}</span>
|
||||
</div>
|
||||
<div class="progress" style="height: 26px;">
|
||||
<div id="importProgressBar" class="progress-bar progress-bar-striped progress-bar-animated"
|
||||
role="progressbar" style="width: 0%;">0%</div>
|
||||
</div>
|
||||
<div id="importProgressError" class="alert alert-danger mt-3 mb-0" style="display:none;"></div>
|
||||
</div>
|
||||
</div>`;
|
||||
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() {
|
||||
|
||||
Reference in New Issue
Block a user