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="excelrows" id="selectedExcelRowsData" value="">
|
||||||
<input type="hidden" name="filename" value="${data.filename}">
|
<input type="hidden" name="filename" value="${data.filename}">
|
||||||
|
|
||||||
<!-- TOP BUTTON -->
|
<!-- TOP BUTTON -->
|
||||||
<div class="d-flex justify-content-end mb-3">
|
<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>
|
<button type="submit" class="btn btn-primary" id="proceedButtonTop" disabled>Prosegui</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -386,46 +387,187 @@ error_log("Loaded template: " . print_r($template, true));
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- BOTTOM BUTTON -->
|
<!-- BOTTOM BUTTON -->
|
||||||
<button type="submit" class="btn btn-primary mt-3" id="proceedButtonBottom" disabled>Prosegui</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>
|
</form>
|
||||||
`;
|
`;
|
||||||
tableContainer.innerHTML = html;
|
tableContainer.innerHTML = html;
|
||||||
|
|
||||||
const selectRowsForm = document.getElementById('selectRowsForm');
|
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'));
|
const checkedBoxes = Array.from(document.querySelectorAll('.row-checkbox:checked'));
|
||||||
|
|
||||||
if (checkedBoxes.length === 0) {
|
if (checkedBoxes.length === 0) {
|
||||||
e.preventDefault();
|
|
||||||
alert('Seleziona almeno una riga.');
|
alert('Seleziona almeno una riga.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Raccoglie le righe selezionate (riga mappata + excelrow parallelo)
|
||||||
const selectedRows = [];
|
const selectedRows = [];
|
||||||
const selectedExcelRows = [];
|
const selectedExcelRows = [];
|
||||||
|
|
||||||
checkedBoxes.forEach((cb, newIndex) => {
|
checkedBoxes.forEach(cb => {
|
||||||
const originalIndex = parseInt(cb.value, 10);
|
const originalIndex = parseInt(cb.value, 10);
|
||||||
|
|
||||||
if (data.rows && data.rows[originalIndex]) {
|
if (data.rows && data.rows[originalIndex]) {
|
||||||
selectedRows.push(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 =
|
const total = selectedRows.length;
|
||||||
encodeURIComponent(JSON.stringify(selectedRows));
|
if (total === 0) {
|
||||||
|
alert('Nessuna riga valida da importare.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('selectedExcelRowsData').value =
|
// importreferencecode generato UNA volta, condiviso da tutti i batch
|
||||||
encodeURIComponent(JSON.stringify(selectedExcelRows));
|
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');
|
const topTableScrollbar = document.getElementById('topTableScrollbar');
|
||||||
@@ -475,10 +617,17 @@ error_log("Loaded template: " . print_r($template, true));
|
|||||||
const checkboxes = document.querySelectorAll('.row-checkbox');
|
const checkboxes = document.querySelectorAll('.row-checkbox');
|
||||||
|
|
||||||
function updateProceedButton() {
|
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 (proceedButtonTop) proceedButtonTop.disabled = !enabled;
|
||||||
if (proceedButtonBottom) proceedButtonBottom.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() {
|
selectAllCheckbox.addEventListener('change', function() {
|
||||||
|
|||||||
Reference in New Issue
Block a user