Compare commits
9 Commits
c3a6dd73b6
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 955b663322 | |||
| 4dc010d80e | |||
| 83c6157b10 | |||
| 4dd7b89c22 | |||
| dec42b4442 | |||
| dab8d9aebf | |||
| 375a10a678 | |||
| 15990be884 | |||
| 67bbd9bbbb |
@@ -47,6 +47,7 @@ yarn-error.log
|
|||||||
/public/userarea/class/curl_auth_debug.log
|
/public/userarea/class/curl_auth_debug.log
|
||||||
/public/userarea/class/curl_request_debug.log
|
/public/userarea/class/curl_request_debug.log
|
||||||
/public/userarea/schema_dettagli_response.json
|
/public/userarea/schema_dettagli_response.json
|
||||||
|
public/userarea/schemi_base_response.json
|
||||||
|
|
||||||
# File XLSX temporanei importati
|
# File XLSX temporanei importati
|
||||||
/public/userarea/imported_trf/*.xlsx
|
/public/userarea/imported_trf/*.xlsx
|
||||||
@@ -60,3 +61,4 @@ yarn-error.log
|
|||||||
*.log
|
*.log
|
||||||
|
|
||||||
public/userarea/cache/
|
public/userarea/cache/
|
||||||
|
public/userarea/error_log.txt
|
||||||
|
|||||||
@@ -109,6 +109,8 @@ class LoginController extends Controller
|
|||||||
// Reindirizza in base al ruolo
|
// Reindirizza in base al ruolo
|
||||||
if ($user->hasRole('Admin')) {
|
if ($user->hasRole('Admin')) {
|
||||||
return redirect()->to('userarea/import_dashboard.php');
|
return redirect()->to('userarea/import_dashboard.php');
|
||||||
|
} elseif ($user->hasRole('SuperUser')) {
|
||||||
|
return redirect()->to('userarea/import_dashboard.php');
|
||||||
} elseif ($user->hasRole('User')) {
|
} elseif ($user->hasRole('User')) {
|
||||||
return redirect()->to('userarea/import_dashboard.php');
|
return redirect()->to('userarea/import_dashboard.php');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -431,7 +431,7 @@
|
|||||||
const emptyEl = modal.querySelector("#analysisEmptyBox");
|
const emptyEl = modal.querySelector("#analysisEmptyBox");
|
||||||
const errorEl = modal.querySelector("#analysisErrorBox");
|
const errorEl = modal.querySelector("#analysisErrorBox");
|
||||||
|
|
||||||
const webOnly = webOnlyEl ? webOnlyEl.checked : false;
|
const webOnly = true;
|
||||||
const searchValue = searchEl ? searchEl.value.trim().toLowerCase() : "";
|
const searchValue = searchEl ? searchEl.value.trim().toLowerCase() : "";
|
||||||
|
|
||||||
let visibleCount = 0;
|
let visibleCount = 0;
|
||||||
@@ -496,8 +496,10 @@
|
|||||||
emptyEl.classList.add("d-none");
|
emptyEl.classList.add("d-none");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (analysisLoadedCache[String(matrixId)]) {
|
const cacheKey = String(matrixId) + "_WEB_ONLY";
|
||||||
renderAnalysesList(analysisLoadedCache[String(matrixId)]);
|
|
||||||
|
if (analysisLoadedCache[cacheKey]) {
|
||||||
|
renderAnalysesList(analysisLoadedCache[cacheKey]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -509,13 +511,21 @@
|
|||||||
dataType: "json",
|
dataType: "json",
|
||||||
data: {
|
data: {
|
||||||
id_matrice: matrixId,
|
id_matrice: matrixId,
|
||||||
|
web_only: 1,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.done(function (response) {
|
.done(function (response) {
|
||||||
const analyses = Array.isArray(response.value)
|
const analyses = Array.isArray(response.value)
|
||||||
? response.value
|
? response.value.filter(function (item) {
|
||||||
|
return (
|
||||||
|
item.SelezionabileSuWeb === true ||
|
||||||
|
item.SelezionabileSuWeb === 1 ||
|
||||||
|
item.SelezionabileSuWeb === "1"
|
||||||
|
);
|
||||||
|
})
|
||||||
: [];
|
: [];
|
||||||
analysisLoadedCache[String(matrixId)] = analyses;
|
|
||||||
|
analysisLoadedCache[cacheKey] = analyses;
|
||||||
renderAnalysesList(analyses);
|
renderAnalysesList(analyses);
|
||||||
})
|
})
|
||||||
.fail(function (xhr) {
|
.fail(function (xhr) {
|
||||||
@@ -674,12 +684,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const webOnlyEl = modal.querySelector("#analysisWebOnly");
|
// WEB only is now fixed by default
|
||||||
if (webOnlyEl) {
|
|
||||||
webOnlyEl.addEventListener("change", function () {
|
|
||||||
filterAnalysisList();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const searchEl = modal.querySelector("#analysisSearchInput");
|
const searchEl = modal.querySelector("#analysisSearchInput");
|
||||||
if (searchEl) {
|
if (searchEl) {
|
||||||
|
|||||||
@@ -207,6 +207,21 @@ if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonText
|
|||||||
<input type="text" name="start_column" id="startColumn" class="form-control" value="<?php echo htmlspecialchars($template['start_column'] ?? ''); ?>">
|
<input type="text" name="start_column" id="startColumn" class="form-control" value="<?php echo htmlspecialchars($template['start_column'] ?? ''); ?>">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3" id="xlsEndColumnWrapper">
|
||||||
|
<label class="form-label">Last Column included</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="xls_end_column"
|
||||||
|
id="xlsEndColumn"
|
||||||
|
class="form-control"
|
||||||
|
value="<?php echo htmlspecialchars($template['xls_end_column'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
|
placeholder="Example: AN">
|
||||||
|
<small class="text-danger fw-semibold">
|
||||||
|
Attention: if left empty, the system will read the entire XLS/XLSX sheet.
|
||||||
|
Dirty Excel files may cause memory errors or timeout.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-3" id="xlsSheetNumberWrapper">
|
<div class="mb-3" id="xlsSheetNumberWrapper">
|
||||||
<label class="form-label">XLS Sheet Number</label>
|
<label class="form-label">XLS Sheet Number</label>
|
||||||
<input
|
<input
|
||||||
@@ -404,11 +419,13 @@ if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonText
|
|||||||
|
|
||||||
const headerRowWrapper = document.getElementById("headerRowWrapper");
|
const headerRowWrapper = document.getElementById("headerRowWrapper");
|
||||||
const startColumnWrapper = document.getElementById("startColumnWrapper");
|
const startColumnWrapper = document.getElementById("startColumnWrapper");
|
||||||
|
const xlsEndColumnWrapper = document.getElementById("xlsEndColumnWrapper");
|
||||||
const xlsSheetNumberWrapper = document.getElementById("xlsSheetNumberWrapper");
|
const xlsSheetNumberWrapper = document.getElementById("xlsSheetNumberWrapper");
|
||||||
const apiConfigWrapper = document.getElementById("apiConfigWrapper");
|
const apiConfigWrapper = document.getElementById("apiConfigWrapper");
|
||||||
|
|
||||||
const headerRow = document.getElementById("headerRow");
|
const headerRow = document.getElementById("headerRow");
|
||||||
const startColumn = document.getElementById("startColumn");
|
const startColumn = document.getElementById("startColumn");
|
||||||
|
const xlsEndColumn = document.getElementById("xlsEndColumn");
|
||||||
const xlsSheetIndex = document.getElementById("xlsSheetIndex");
|
const xlsSheetIndex = document.getElementById("xlsSheetIndex");
|
||||||
const apiConfigSelect = document.getElementById("apiConfigSelect");
|
const apiConfigSelect = document.getElementById("apiConfigSelect");
|
||||||
|
|
||||||
@@ -444,13 +461,16 @@ if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonText
|
|||||||
if (isXls) {
|
if (isXls) {
|
||||||
headerRowWrapper.style.display = 'block';
|
headerRowWrapper.style.display = 'block';
|
||||||
startColumnWrapper.style.display = 'block';
|
startColumnWrapper.style.display = 'block';
|
||||||
|
xlsEndColumnWrapper.style.display = 'block';
|
||||||
xlsSheetNumberWrapper.style.display = 'block';
|
xlsSheetNumberWrapper.style.display = 'block';
|
||||||
|
|
||||||
headerRow.required = true;
|
headerRow.required = true;
|
||||||
startColumn.required = true;
|
startColumn.required = true;
|
||||||
|
xlsEndColumn.required = false;
|
||||||
|
|
||||||
headerRow.disabled = false;
|
headerRow.disabled = false;
|
||||||
startColumn.disabled = false;
|
startColumn.disabled = false;
|
||||||
|
xlsEndColumn.disabled = false;
|
||||||
xlsSheetIndex.disabled = false;
|
xlsSheetIndex.disabled = false;
|
||||||
|
|
||||||
apiConfigWrapper.style.display = 'none';
|
apiConfigWrapper.style.display = 'none';
|
||||||
@@ -460,13 +480,16 @@ if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonText
|
|||||||
} else {
|
} else {
|
||||||
headerRowWrapper.style.display = 'none';
|
headerRowWrapper.style.display = 'none';
|
||||||
startColumnWrapper.style.display = 'none';
|
startColumnWrapper.style.display = 'none';
|
||||||
|
xlsEndColumnWrapper.style.display = 'none';
|
||||||
xlsSheetNumberWrapper.style.display = 'none';
|
xlsSheetNumberWrapper.style.display = 'none';
|
||||||
|
|
||||||
headerRow.required = false;
|
headerRow.required = false;
|
||||||
startColumn.required = false;
|
startColumn.required = false;
|
||||||
|
xlsEndColumn.required = false;
|
||||||
|
|
||||||
headerRow.disabled = true;
|
headerRow.disabled = true;
|
||||||
startColumn.disabled = true;
|
startColumn.disabled = true;
|
||||||
|
xlsEndColumn.disabled = true;
|
||||||
xlsSheetIndex.disabled = true;
|
xlsSheetIndex.disabled = true;
|
||||||
|
|
||||||
if (isApiOrJson) {
|
if (isApiOrJson) {
|
||||||
@@ -726,6 +749,22 @@ if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonText
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selectedSource === 'XLS' && xlsEndColumn.value.trim() !== '') {
|
||||||
|
const lastColumnValue = xlsEndColumn.value.trim().toUpperCase();
|
||||||
|
|
||||||
|
if (!/^[A-Z]+$|^[1-9][0-9]*$/.test(lastColumnValue)) {
|
||||||
|
Swal.fire({
|
||||||
|
title: "Error!",
|
||||||
|
text: "Last Column must be an Excel column like AN or a positive number like 40.",
|
||||||
|
icon: "error",
|
||||||
|
confirmButtonText: "OK"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
xlsEndColumn.value = lastColumnValue;
|
||||||
|
}
|
||||||
|
|
||||||
fetch("process_edit_template_xls.php", {
|
fetch("process_edit_template_xls.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData
|
body: formData
|
||||||
|
|||||||
@@ -331,3 +331,35 @@
|
|||||||
2026-04-30 15:01:25 - Errore nel recupero dati: HTTP 404, Risposta: {"title":"Not Found","status":404,"detail":"Not Found","instance":"GET /api/odata/Rapporto(2621521)","errorCode":"d25cbd678"}
|
2026-04-30 15:01:25 - Errore nel recupero dati: HTTP 404, Risposta: {"title":"Not Found","status":404,"detail":"Not Found","instance":"GET /api/odata/Rapporto(2621521)","errorCode":"d25cbd678"}
|
||||||
2026-04-30 15:02:04 - Errore nel recupero dati: HTTP 404, Risposta:
|
2026-04-30 15:02:04 - Errore nel recupero dati: HTTP 404, Risposta:
|
||||||
2026-04-30 15:03:19 - Errore nella richiesta: URL rejected: Malformed input to a URL function
|
2026-04-30 15:03:19 - Errore nella richiesta: URL rejected: Malformed input to a URL function
|
||||||
|
2026-06-16 14:29:15 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21037 ms: Couldn't connect to server, Risposta:
|
||||||
|
|
||||||
|
|
||||||
|
2026-06-16 14:29:36 [ClienteResponsabile] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21099 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:30:03 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21076 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:30:03 [AnagraficaCertestObject] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21083 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:30:03 [AnagraficaCertestService] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21083 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:30:34 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21043 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:30:55 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21053 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:30:55 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21050 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:31:46 [AnagraficaCertestObject] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21048 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:31:46 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21043 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:31:46 [AnagraficaCertestService] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21042 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:32:07 [ClienteResponsabile] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21047 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:33:09 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21058 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:33:09 [AnagraficaCertestObject] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21057 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:33:09 [AnagraficaCertestService] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21057 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:33:30 [ClienteResponsabile] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21047 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:36:11 [AnagraficaCertestService] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21062 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:36:11 [AnagraficaCertestObject] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21064 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:36:11 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21065 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:36:33 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21049 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:36:33 [AnagraficaCertestService] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21057 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:36:33 [AnagraficaCertestObject] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21053 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:37:13 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21032 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:37:14 [AnagraficaCertestService] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21031 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:37:14 [AnagraficaCertestObject] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21027 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 14:37:35 [ClienteResponsabile] Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21054 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 17:13:55 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21067 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 17:14:25 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21065 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 17:14:46 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21071 ms: Couldn't connect to server, Risposta:
|
||||||
|
2026-06-16 17:14:46 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21078 ms: Couldn't connect to server, Risposta:
|
||||||
|
|||||||
@@ -477,6 +477,71 @@ try {
|
|||||||
$logFilePhotos = $logDir . "commessa_{$commessaId}_photos_step5_2_" . time() . ".txt";
|
$logFilePhotos = $logDir . "commessa_{$commessaId}_photos_step5_2_" . time() . ".txt";
|
||||||
$writeLog($logFilePhotos, $logContentPhotos, "STEP 6.2 - Photos (commessa={$commessaId})");
|
$writeLog($logFilePhotos, $logContentPhotos, "STEP 6.2 - Photos (commessa={$commessaId})");
|
||||||
|
|
||||||
|
// 🔹 STEP 6.3: Add Analyses (AnalisiCampione) via Campione({id})/AddAnalisi bound action
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT part_id, analysis_recordkey, analysis_name, analysis_method
|
||||||
|
FROM identification_parts_analyses
|
||||||
|
WHERE iddatadb = :iddatadb
|
||||||
|
ORDER BY part_id, id
|
||||||
|
");
|
||||||
|
$stmt->execute(['iddatadb' => $iddatadb]);
|
||||||
|
$analysesRows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$partIdToIndex = [];
|
||||||
|
foreach ($parts as $idx => $part) {
|
||||||
|
$partIdToIndex[(int)$part['part_id']] = $idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
$totalAnalyses = count($analysesRows);
|
||||||
|
$addedAnalyses = 0;
|
||||||
|
$failedAnalyses = [];
|
||||||
|
$logContentStep63Analisi = "Analyses for iddatadb={$iddatadb}: total={$totalAnalyses}\n\n";
|
||||||
|
|
||||||
|
foreach ($analysesRows as $a) {
|
||||||
|
$partId = (int)$a['part_id'];
|
||||||
|
$recordKey = trim((string)($a['analysis_recordkey'] ?? ''));
|
||||||
|
$idx = $partIdToIndex[$partId] ?? null;
|
||||||
|
|
||||||
|
if ($idx === null || !isset($campioni[$idx]) || $recordKey === '') {
|
||||||
|
$logContentStep63Analisi .= "SKIP (no campione for part_id={$partId} / empty recordkey): '{$recordKey}'\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$campioneId = (int)($campioni[$idx]['IdCampione'] ?? 0);
|
||||||
|
if ($campioneId <= 0) {
|
||||||
|
$logContentStep63Analisi .= "SKIP (invalid IdCampione for part_id={$partId}): '{$recordKey}'\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = ['RecordKey' => $recordKey];
|
||||||
|
$jsonPayload = json_encode($payload, JSON_UNESCAPED_SLASHES);
|
||||||
|
|
||||||
|
$logContentStep63Analisi .= "curl --location --request POST '{$apiBaseUrl}Campione({$campioneId})/AddAnalisi' \\\n" .
|
||||||
|
"--header 'Content-Type: application/json' \\\n" .
|
||||||
|
"--header 'Authorization: Bearer ••••••' \\\n" .
|
||||||
|
"--data '{$jsonPayload}'\n";
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = $api->post("Campione({$campioneId})/AddAnalisi", $payload);
|
||||||
|
$logContentStep63Analisi .= "OK (part_id={$partId}, campione={$campioneId}): " .
|
||||||
|
($a['analysis_name'] ?? '') . "\n---\n";
|
||||||
|
$addedAnalyses++;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$errMsg = $e->getMessage();
|
||||||
|
$logContentStep63Analisi .= "FAIL: {$errMsg}\n---\n";
|
||||||
|
$failedAnalyses[] = [
|
||||||
|
'part_id' => $partId,
|
||||||
|
'campione_id' => $campioneId,
|
||||||
|
'analysis_recordkey' => $recordKey,
|
||||||
|
'analysis_name' => $a['analysis_name'] ?? '',
|
||||||
|
'error' => $errMsg,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$logFileStep63Analisi = $logDir . "commessa_{$commessaId}_analyses_step63_" . time() . ".txt";
|
||||||
|
$writeLog($logFileStep63Analisi, $logContentStep63Analisi, "STEP 6.3 - AddAnalisi (commessa={$commessaId})");
|
||||||
|
|
||||||
// 🔹 STEP 7: Update Custom Fields for CommessaWeb
|
// 🔹 STEP 7: Update Custom Fields for CommessaWeb
|
||||||
if (!empty($fieldValues)) {
|
if (!empty($fieldValues)) {
|
||||||
// GET con espansione per CustomField
|
// GET con espansione per CustomField
|
||||||
@@ -629,11 +694,15 @@ try {
|
|||||||
"totalCampioni" => count($campioni),
|
"totalCampioni" => count($campioni),
|
||||||
"totalCustomFields" => count($commessaAfterPatch["CommesseCustomFields"] ?? []),
|
"totalCustomFields" => count($commessaAfterPatch["CommesseCustomFields"] ?? []),
|
||||||
"totalPhotos" => count($photos),
|
"totalPhotos" => count($photos),
|
||||||
|
"totalAnalyses" => $totalAnalyses,
|
||||||
|
"addedAnalyses" => $addedAnalyses,
|
||||||
|
"failedAnalyses" => $failedAnalyses,
|
||||||
"message" => "Export successful",
|
"message" => "Export successful",
|
||||||
"logFiles" => [
|
"logFiles" => [
|
||||||
"step5_create" => $logFileStep5,
|
"step5_create" => $logFileStep5,
|
||||||
"step5_2_photos" => $logFilePhotos,
|
"step5_2_photos" => $logFilePhotos,
|
||||||
"step6_campioni" => $logFileStep6,
|
"step6_campioni" => $logFileStep6,
|
||||||
|
"step63_analyses" => $logFileStep63Analisi,
|
||||||
"step7_patch" => $logFileStep7 ?? null,
|
"step7_patch" => $logFileStep7 ?? null,
|
||||||
"step9_1_importa" => $logFileStep91,
|
"step9_1_importa" => $logFileStep91,
|
||||||
"step10_get" => $logFileStep10
|
"step10_get" => $logFileStep10
|
||||||
@@ -649,6 +718,7 @@ try {
|
|||||||
"step5_create" => $logFileStep5 ?? null,
|
"step5_create" => $logFileStep5 ?? null,
|
||||||
"step5_2_photos" => $logFilePhotos ?? null,
|
"step5_2_photos" => $logFilePhotos ?? null,
|
||||||
"step6_campioni" => $logFileStep6 ?? null,
|
"step6_campioni" => $logFileStep6 ?? null,
|
||||||
|
"step63_analyses" => $logFileStep63Analisi ?? null,
|
||||||
"step7_patch" => $logFileStep7 ?? null,
|
"step7_patch" => $logFileStep7 ?? null,
|
||||||
"step9_1_importa" => $logFileStep91 ?? null,
|
"step9_1_importa" => $logFileStep91 ?? null,
|
||||||
"step10_get" => $logFileStep10 ?? null
|
"step10_get" => $logFileStep10 ?? null
|
||||||
|
|||||||
@@ -18,7 +18,15 @@ try {
|
|||||||
|
|
||||||
$api = VisualLimsApiClient::getInstance();
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
|
||||||
$filter = rawurlencode("Matrice/IdMatrice eq $idMatrice");
|
$webOnly = isset($_GET['web_only']) ? (int)$_GET['web_only'] : 1;
|
||||||
|
|
||||||
|
$filterString = "Matrice/IdMatrice eq $idMatrice";
|
||||||
|
|
||||||
|
if ($webOnly === 1) {
|
||||||
|
$filterString .= " and SelezionabileSuWeb eq true";
|
||||||
|
}
|
||||||
|
|
||||||
|
$filter = rawurlencode($filterString);
|
||||||
$endpoint = "Analisi?\$filter={$filter}";
|
$endpoint = "Analisi?\$filter={$filter}";
|
||||||
|
|
||||||
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
|
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
|
||||||
|
|||||||
@@ -20,9 +20,10 @@ $db = DBHandlerSelect::getInstance();
|
|||||||
$pdo = $db->getConnection();
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
// Recupera tutti i mapping dal template, includendo is_visible_import
|
// Recupera tutti i mapping dal template, includendo is_visible_import
|
||||||
$stmt = $pdo->prepare("SELECT id, excel_column, data_type, is_required, manual_default, is_manual, field_label, field_id, main_field, is_visible_import, auto_value
|
$stmt = $pdo->prepare("SELECT id, excel_column, data_type, is_required, manual_default, is_manual, field_label, field_id, field_order, main_field, is_visible_import, auto_value
|
||||||
FROM template_mapping
|
FROM template_mapping
|
||||||
WHERE template_id = ?");
|
WHERE template_id = ?
|
||||||
|
ORDER BY field_order ASC, id ASC");
|
||||||
$stmt->execute([$template_id]);
|
$stmt->execute([$template_id]);
|
||||||
$allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
@@ -252,19 +253,28 @@ foreach ($importedData as $index => $row) {
|
|||||||
// Build columns in display order
|
// Build columns in display order
|
||||||
$gridColumns = [];
|
$gridColumns = [];
|
||||||
|
|
||||||
// 1. Main fields, maximum 2
|
// 1. Main fields first, immediately after buttons
|
||||||
foreach ($mainFieldMappings as $mainMapping) {
|
foreach ($allMappings as $mapping) {
|
||||||
|
if (
|
||||||
|
(int)$mapping['is_visible_import'] === 1
|
||||||
|
&& (string)$mapping['main_field'] === '1'
|
||||||
|
&& trim((string)$mapping['field_label']) !== 'Tested Component:'
|
||||||
|
) {
|
||||||
$gridColumns[] = [
|
$gridColumns[] = [
|
||||||
'type' => 'main_field',
|
'type' => 'main_field',
|
||||||
'key' => (string)$mainMapping['id'],
|
'key' => (string)$mapping['id'],
|
||||||
'label' => $mainMapping['field_label'],
|
'label' => $mapping['field_label'],
|
||||||
'dataType' => $mainMapping['data_type'],
|
'dataType' => $mapping['data_type'],
|
||||||
'isManual' => (bool)$mainMapping['is_manual'],
|
'isManual' => (bool)$mapping['is_manual'],
|
||||||
'isRequired' => (bool)$mainMapping['is_required'],
|
'isRequired' => (bool)$mapping['is_required'],
|
||||||
'fieldId' => $mainMapping['field_id'] ?? null,
|
'fieldId' => $mapping['field_id'] ?? null,
|
||||||
|
'fieldOrder' => (int)($mapping['field_order'] ?? 9999),
|
||||||
|
'manualDefault' => $mapping['manual_default'] ?? '',
|
||||||
|
'autoValue' => $mapping['auto_value'] ?? 'none',
|
||||||
'width' => 150,
|
'width' => 150,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 2. Status
|
// 2. Status
|
||||||
$gridColumns[] = ['type' => 'status', 'key' => 'status', 'label' => 'Status', 'width' => 150, 'editable' => false];
|
$gridColumns[] = ['type' => 'status', 'key' => 'status', 'label' => 'Status', 'width' => 150, 'editable' => false];
|
||||||
@@ -275,50 +285,30 @@ $gridColumns[] = ['type' => 'idclient', 'key' => 'idclient', 'label' => 'Client'
|
|||||||
// 4. Cliente Fornitore
|
// 4. Cliente Fornitore
|
||||||
$gridColumns[] = ['type' => 'cliente_fornitore_id', 'key' => 'cliente_fornitore_id', 'label' => $slugMapping['ClienteFornitore'] ?? 'ClienteFornitore', 'width' => 300];
|
$gridColumns[] = ['type' => 'cliente_fornitore_id', 'key' => 'cliente_fornitore_id', 'label' => $slugMapping['ClienteFornitore'] ?? 'ClienteFornitore', 'width' => 300];
|
||||||
|
|
||||||
// 5. Auto fields
|
// 5. Other custom fields in schema order
|
||||||
foreach ($allMappings as $mapping) {
|
foreach ($allMappings as $mapping) {
|
||||||
if (
|
if (
|
||||||
!$mapping['is_manual']
|
(int)$mapping['is_visible_import'] === 1
|
||||||
&& $mapping['main_field'] != 1
|
&& (string)$mapping['main_field'] !== '1'
|
||||||
&& $mapping['is_visible_import'] == 1
|
|
||||||
&& trim((string)$mapping['field_label']) !== 'Tested Component:'
|
&& trim((string)$mapping['field_label']) !== 'Tested Component:'
|
||||||
) {
|
) {
|
||||||
|
$isMainField = ((string)$mapping['main_field'] === '1');
|
||||||
|
|
||||||
$gridColumns[] = [
|
$gridColumns[] = [
|
||||||
'type' => 'detail',
|
'type' => $isMainField ? 'main_field' : 'detail',
|
||||||
'key' => (string)$mapping['id'],
|
'key' => (string)$mapping['id'],
|
||||||
'label' => $mapping['field_label'],
|
'label' => $mapping['field_label'],
|
||||||
'dataType' => $mapping['data_type'],
|
'dataType' => $mapping['data_type'],
|
||||||
'isManual' => false,
|
'isManual' => (bool)$mapping['is_manual'],
|
||||||
'isRequired' => (bool)$mapping['is_required'],
|
'isRequired' => (bool)$mapping['is_required'],
|
||||||
'fieldId' => $mapping['field_id'] ?? null,
|
'fieldId' => $mapping['field_id'] ?? null,
|
||||||
|
'fieldOrder' => (int)($mapping['field_order'] ?? 9999),
|
||||||
|
'manualDefault' => $mapping['manual_default'] ?? '',
|
||||||
'autoValue' => $mapping['auto_value'] ?? 'none',
|
'autoValue' => $mapping['auto_value'] ?? 'none',
|
||||||
'width' => 150,
|
'width' => 150,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Manual fields
|
|
||||||
foreach ($allMappings as $mapping) {
|
|
||||||
if (
|
|
||||||
$mapping['is_manual']
|
|
||||||
&& $mapping['main_field'] != 1
|
|
||||||
&& $mapping['is_visible_import'] == 1
|
|
||||||
&& trim((string)$mapping['field_label']) !== 'Tested Component:'
|
|
||||||
) {
|
|
||||||
$gridColumns[] = [
|
|
||||||
'type' => 'detail',
|
|
||||||
'key' => (string)$mapping['id'],
|
|
||||||
'label' => $mapping['field_label'],
|
|
||||||
'dataType' => $mapping['data_type'],
|
|
||||||
'isManual' => true,
|
|
||||||
'isRequired' => (bool)$mapping['is_required'],
|
|
||||||
'fieldId' => $mapping['field_id'] ?? null,
|
|
||||||
'manualDefault' => $mapping['manual_default'] ?? '',
|
|
||||||
'width' => 150,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7. Tested Component
|
// 7. Tested Component
|
||||||
$gridColumns[] = ['type' => 'tested_component', 'key' => 'tested_component', 'label' => 'Tested Component', 'width' => 150];
|
$gridColumns[] = ['type' => 'tested_component', 'key' => 'tested_component', 'label' => 'Tested Component', 'width' => 150];
|
||||||
|
|
||||||
|
|||||||
@@ -6,102 +6,147 @@
|
|||||||
<div>
|
<div>
|
||||||
<h4 class="logo-text"><?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></h4>
|
<h4 class="logo-text"><?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="toggle-icon ms-auto"><i class='bx bx-arrow-back'></i>
|
<div class="toggle-icon ms-auto">
|
||||||
|
<i class='bx bx-arrow-back'></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--navigation-->
|
<!--navigation-->
|
||||||
<ul class="metismenu" id="menu">
|
<ul class="metismenu" id="menu">
|
||||||
<!-- user, admin, superuser menù -->
|
|
||||||
<?php if ((Auth::user()->hasRole('Admin')) || (Auth::user()->hasRole('User')) || (Auth::user()->hasRole('Superuser'))) : ?>
|
<?php
|
||||||
|
$currentUser = Auth::user();
|
||||||
|
|
||||||
|
$isAdmin = $currentUser->hasRole('Admin');
|
||||||
|
$isSuperUser = $currentUser->hasRole('SuperUser');
|
||||||
|
$isUser = $currentUser->hasRole('User');
|
||||||
|
|
||||||
|
$canAccessMainMenu = $isAdmin || $isSuperUser || $isUser;
|
||||||
|
$canAccessUserAdmin = $isAdmin || $isSuperUser;
|
||||||
|
$canAccessTemplates = $isAdmin || $isSuperUser;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- user, admin, superuser menu -->
|
||||||
|
<?php if ($canAccessMainMenu) : ?>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="javascript:;" class="has-arrow">
|
<a href="javascript:;" class="has-arrow">
|
||||||
<div class="parent-icon"><i class='bx bx-home-alt'></i>
|
<div class="parent-icon">
|
||||||
|
<i class='bx bx-home-alt'></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-title">Dashboard</div>
|
<div class="menu-title">Dashboard</div>
|
||||||
</a>
|
</a>
|
||||||
<ul>
|
<ul>
|
||||||
<!-- <li> <a href="index.php"><i class='bx bx-radio-circle'></i>Default</a>
|
<li>
|
||||||
</li> -->
|
<a href="import_dashboard.php">
|
||||||
<li> <a href="import_dashboard.php"><i class='bx bx-radio-circle'></i>XLS Import</a>
|
<i class='bx bx-radio-circle'></i>XLS Import
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<?php if ($canAccessTemplates) : ?>
|
||||||
<li>
|
<li>
|
||||||
<a href="javascript:;" class="has-arrow">
|
<a href="javascript:;" class="has-arrow">
|
||||||
<div class="parent-icon"><i class="bx bx-category"></i>
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-category"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-title">Templates</div>
|
<div class="menu-title">Templates</div>
|
||||||
</a>
|
</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li> <a href="templates_dashboard.php"><i class='bx bx-radio-circle'></i><?= htmlspecialchars($dashtemplate, ENT_QUOTES, 'UTF-8'); ?></a>
|
<li>
|
||||||
</li>
|
<a href="templates_dashboard.php">
|
||||||
<li> <a href="insert_template_xls.php"><i class='bx bx-radio-circle'></i><?= htmlspecialchars($insertnewtemplatexls, ENT_QUOTES, 'UTF-8'); ?></a>
|
<i class='bx bx-radio-circle'></i><?= htmlspecialchars($dashtemplate, ENT_QUOTES, 'UTF-8'); ?>
|
||||||
</li>
|
</a>
|
||||||
|
|
||||||
</ul>
|
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="insert_template_xls.php">
|
||||||
|
<i class='bx bx-radio-circle'></i><?= htmlspecialchars($insertnewtemplatexls, ENT_QUOTES, 'UTF-8'); ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="javascript:;" class="has-arrow">
|
<a href="javascript:;" class="has-arrow">
|
||||||
<div class="parent-icon"><i class="bx bx-category"></i>
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-category"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-title">Other Functions</div>
|
<div class="menu-title">Other Functions</div>
|
||||||
</a>
|
</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li> <a href="quotations.php"><i class='bx bx-radio-circle'></i><?php echo $quotationstitle; ?></a>
|
<li>
|
||||||
|
<a href="quotations.php">
|
||||||
|
<i class='bx bx-radio-circle'></i><?= htmlspecialchars($quotationstitle, ENT_QUOTES, 'UTF-8'); ?>
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<?php if ($canAccessUserAdmin) : ?>
|
||||||
|
<li class="menu-label">Administration</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a href="user-admin.php">
|
||||||
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-user-plus"></i>
|
||||||
|
</div>
|
||||||
|
<div class="menu-title">User Admin</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<li class="menu-label">Others</li>
|
<li class="menu-label">Others</li>
|
||||||
|
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="https://helpdesk.cesoft.io" target="_blank">
|
<a href="https://helpdesk.cesoft.io" target="_blank">
|
||||||
<div class="parent-icon"><i class="bx bx-support"></i>
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-support"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-title">Support</div>
|
<div class="menu-title">Support</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<?php
|
|
||||||
endif; ?>
|
<?php endif; ?>
|
||||||
<!-- admin, superuser menù -->
|
|
||||||
<?php if ((Auth::user()->hasRole('Admin')) || (Auth::user()->hasRole('Superuser'))) : ?>
|
|
||||||
<?php
|
<!-- admin only menu -->
|
||||||
endif; ?>
|
<?php if ($isAdmin) : ?>
|
||||||
<!-- admin menù -->
|
|
||||||
<?php if (Auth::user()->hasRole('Admin')) : ?>
|
|
||||||
<li class="menu-label">Admin Menù</li>
|
<li class="menu-label">Admin Menù</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="../" target="_blank">
|
<a href="../" target="_blank">
|
||||||
<div class="parent-icon"><i class="bx bx-support"></i>
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-support"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-title">User Management</div>
|
<div class="menu-title">User Management</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<!-- <li>
|
|
||||||
|
<!--
|
||||||
|
<li>
|
||||||
<a href="template/index.html" target="_blank">
|
<a href="template/index.html" target="_blank">
|
||||||
<div class="parent-icon"><i class="bx bx-support"></i>
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-support"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-title">Template</div>
|
<div class="menu-title">Template</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="https://codervent.com/rocker/documentation/index.html" target="_blank">
|
<a href="https://codervent.com/rocker/documentation/index.html" target="_blank">
|
||||||
<div class="parent-icon"><i class="bx bx-folder"></i>
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-folder"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-title">Documentation</div>
|
<div class="menu-title">Documentation</div>
|
||||||
</a>
|
</a>
|
||||||
</li> -->
|
</li>
|
||||||
<?php
|
-->
|
||||||
endif; ?>
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
<!--end navigation-->
|
<!--end navigation-->
|
||||||
</div>
|
</div>
|
||||||
@@ -94,8 +94,14 @@
|
|||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li><a class="dropdown-item d-flex align-items-center" href="user-profile.php"><i class="bx bx-user fs-5"></i><span>Profile</span></a>
|
<li><a class="dropdown-item d-flex align-items-center" href="user-profile.php"><i class="bx bx-user fs-5"></i><span>Profile</span></a>
|
||||||
</li>
|
</li>
|
||||||
<li><a class="dropdown-item d-flex align-items-center" href="settings.php"><i class="bx bx-cog fs-5"></i><span>Settings</span></a>
|
<?php if ($user->hasRole('Admin') || $user->hasRole('SuperUser')): ?>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item d-flex align-items-center" href="user-admin.php">
|
||||||
|
<i class="bx bx-user-plus fs-5"></i>
|
||||||
|
<span>User Admin</span>
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
<li>
|
<li>
|
||||||
<div class="dropdown-divider mb-0"></div>
|
<div class="dropdown-divider mb-0"></div>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -95,6 +95,21 @@ $apiConfigurations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
<input type="text" name="start_column" id="startColumn" class="form-control" value="A" required>
|
<input type="text" name="start_column" id="startColumn" class="form-control" value="A" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3" id="xlsEndColumnWrapper">
|
||||||
|
<label class="form-label">Last Column included</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="xls_end_column"
|
||||||
|
id="xlsEndColumn"
|
||||||
|
class="form-control"
|
||||||
|
value=""
|
||||||
|
placeholder="Example: AN">
|
||||||
|
<small class="text-danger fw-semibold">
|
||||||
|
Attention: if left empty, the system will read the entire XLS/XLSX sheet.
|
||||||
|
Dirty Excel files may cause memory errors or timeout.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-3" id="xlsSheetNumberWrapper">
|
<div class="mb-3" id="xlsSheetNumberWrapper">
|
||||||
<label class="form-label">XLS Sheet Number</label>
|
<label class="form-label">XLS Sheet Number</label>
|
||||||
<input
|
<input
|
||||||
@@ -253,11 +268,13 @@ $apiConfigurations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
|
|
||||||
const headerRowWrapper = document.getElementById("headerRowWrapper");
|
const headerRowWrapper = document.getElementById("headerRowWrapper");
|
||||||
const startColumnWrapper = document.getElementById("startColumnWrapper");
|
const startColumnWrapper = document.getElementById("startColumnWrapper");
|
||||||
|
const xlsEndColumnWrapper = document.getElementById("xlsEndColumnWrapper");
|
||||||
const xlsSheetNumberWrapper = document.getElementById("xlsSheetNumberWrapper");
|
const xlsSheetNumberWrapper = document.getElementById("xlsSheetNumberWrapper");
|
||||||
const apiConfigWrapper = document.getElementById("apiConfigWrapper");
|
const apiConfigWrapper = document.getElementById("apiConfigWrapper");
|
||||||
|
|
||||||
const headerRow = document.getElementById("headerRow");
|
const headerRow = document.getElementById("headerRow");
|
||||||
const startColumn = document.getElementById("startColumn");
|
const startColumn = document.getElementById("startColumn");
|
||||||
|
const xlsEndColumn = document.getElementById("xlsEndColumn");
|
||||||
const xlsSheetIndex = document.getElementById("xlsSheetIndex");
|
const xlsSheetIndex = document.getElementById("xlsSheetIndex");
|
||||||
const apiConfigSelect = document.getElementById("apiConfigSelect");
|
const apiConfigSelect = document.getElementById("apiConfigSelect");
|
||||||
|
|
||||||
@@ -295,14 +312,20 @@ $apiConfigurations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
if (isXls) {
|
if (isXls) {
|
||||||
headerRowWrapper.style.display = 'block';
|
headerRowWrapper.style.display = 'block';
|
||||||
startColumnWrapper.style.display = 'block';
|
startColumnWrapper.style.display = 'block';
|
||||||
|
xlsEndColumnWrapper.style.display = 'block';
|
||||||
xlsSheetNumberWrapper.style.display = 'block';
|
xlsSheetNumberWrapper.style.display = 'block';
|
||||||
|
|
||||||
headerRow.required = true;
|
headerRow.required = true;
|
||||||
startColumn.required = true;
|
startColumn.required = true;
|
||||||
xlsSheetIndex.required = true;
|
xlsSheetIndex.required = true;
|
||||||
|
|
||||||
|
// Last Column is optional.
|
||||||
|
// If empty, the import will read the entire sheet.
|
||||||
|
xlsEndColumn.required = false;
|
||||||
|
|
||||||
headerRow.disabled = false;
|
headerRow.disabled = false;
|
||||||
startColumn.disabled = false;
|
startColumn.disabled = false;
|
||||||
|
xlsEndColumn.disabled = false;
|
||||||
xlsSheetIndex.disabled = false;
|
xlsSheetIndex.disabled = false;
|
||||||
|
|
||||||
apiConfigWrapper.style.display = 'none';
|
apiConfigWrapper.style.display = 'none';
|
||||||
@@ -312,16 +335,21 @@ $apiConfigurations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
} else {
|
} else {
|
||||||
headerRowWrapper.style.display = 'none';
|
headerRowWrapper.style.display = 'none';
|
||||||
startColumnWrapper.style.display = 'none';
|
startColumnWrapper.style.display = 'none';
|
||||||
|
xlsEndColumnWrapper.style.display = 'none';
|
||||||
xlsSheetNumberWrapper.style.display = 'none';
|
xlsSheetNumberWrapper.style.display = 'none';
|
||||||
|
|
||||||
headerRow.required = false;
|
headerRow.required = false;
|
||||||
startColumn.required = false;
|
startColumn.required = false;
|
||||||
|
xlsEndColumn.required = false;
|
||||||
xlsSheetIndex.required = false;
|
xlsSheetIndex.required = false;
|
||||||
|
|
||||||
headerRow.disabled = true;
|
headerRow.disabled = true;
|
||||||
startColumn.disabled = true;
|
startColumn.disabled = true;
|
||||||
|
xlsEndColumn.disabled = true;
|
||||||
xlsSheetIndex.disabled = true;
|
xlsSheetIndex.disabled = true;
|
||||||
|
|
||||||
|
xlsEndColumn.value = '';
|
||||||
|
|
||||||
if (isApiJson) {
|
if (isApiJson) {
|
||||||
apiConfigWrapper.style.display = 'block';
|
apiConfigWrapper.style.display = 'block';
|
||||||
apiConfigSelect.required = true;
|
apiConfigSelect.required = true;
|
||||||
|
|||||||
@@ -265,6 +265,47 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
font-family: Consolas, Monaco, monospace;
|
font-family: Consolas, Monaco, monospace;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-results__option {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 520px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-json-node {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-json-value {
|
||||||
|
color: #6c757d;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-json-row {
|
||||||
|
display: block;
|
||||||
|
margin: -6px;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-results__option:has(.select2-json-row.used-option) {
|
||||||
|
background-color: #fff3cd !important;
|
||||||
|
color: #856404 !important;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-results__option:has(.select2-json-row.used-option).select2-results__option--highlighted {
|
||||||
|
background-color: #ffe69c !important;
|
||||||
|
color: #856404 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-json-row.used-option .select2-json-node::after {
|
||||||
|
content: " (already used)";
|
||||||
|
color: #856404;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -1018,6 +1059,42 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
return escapeHtmlText(str);
|
return escapeHtmlText(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let jsonNodeLabels = {};
|
||||||
|
|
||||||
|
function getLastJsonNodeName(path) {
|
||||||
|
return String(path || '')
|
||||||
|
.replace(/\[\]/g, '')
|
||||||
|
.split('.')
|
||||||
|
.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatJsonSampleValue(value) {
|
||||||
|
if (value === null || value === undefined) return '';
|
||||||
|
|
||||||
|
let text = '';
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
text = '[Array]';
|
||||||
|
} else if (typeof value === 'object') {
|
||||||
|
text = '{Object}';
|
||||||
|
} else {
|
||||||
|
text = String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
text = text
|
||||||
|
.replace(/[\r\n\t]+/g, ' ')
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
if (text === '/' || text === '\\/') return '';
|
||||||
|
|
||||||
|
if (text.length > 38) {
|
||||||
|
text = text.substring(0, 38) + '...';
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
function flattenJsonNodes(obj, prefix = '') {
|
function flattenJsonNodes(obj, prefix = '') {
|
||||||
let nodes = [];
|
let nodes = [];
|
||||||
|
|
||||||
@@ -1036,6 +1113,14 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
if (value !== null && typeof value === 'object') {
|
if (value !== null && typeof value === 'object') {
|
||||||
nodes = nodes.concat(flattenJsonNodes(value, path));
|
nodes = nodes.concat(flattenJsonNodes(value, path));
|
||||||
} else {
|
} else {
|
||||||
|
const sample = formatJsonSampleValue(value);
|
||||||
|
const shortName = getLastJsonNodeName(path);
|
||||||
|
|
||||||
|
jsonNodeLabels[path] = {
|
||||||
|
shortName: shortName,
|
||||||
|
sample: sample
|
||||||
|
};
|
||||||
|
|
||||||
nodes.push(path);
|
nodes.push(path);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1061,18 +1146,128 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
if (!clean) return '';
|
if (!clean) return '';
|
||||||
|
|
||||||
const isUsed = uniqueUsedNodes.includes(clean) && clean !== currentValue;
|
const isUsed = uniqueUsedNodes.includes(clean) && clean !== currentValue;
|
||||||
const label = isUsed ? `⚠ ${clean} (already used)` : clean;
|
const info = jsonNodeLabels[clean] || {};
|
||||||
|
const shortName = info.shortName || getLastJsonNodeName(clean);
|
||||||
|
const sample = info.sample || '';
|
||||||
|
|
||||||
|
let label = shortName;
|
||||||
|
if (sample) {
|
||||||
|
label += ` — ${sample}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isUsed) {
|
||||||
|
label = `⚠ ${label} (already used)`;
|
||||||
|
}
|
||||||
|
|
||||||
const isSelected = clean === currentValue ? 'selected' : '';
|
const isSelected = clean === currentValue ? 'selected' : '';
|
||||||
|
|
||||||
return `<option value="${escapeHtmlAttr(clean)}" class="${isUsed ? 'used-option' : ''}" ${isSelected}>${escapeHtmlText(label)}</option>`;
|
return `
|
||||||
|
<option
|
||||||
|
value="${escapeHtmlAttr(clean)}"
|
||||||
|
data-short-name="${escapeHtmlAttr(shortName)}"
|
||||||
|
data-sample="${escapeHtmlAttr(sample)}"
|
||||||
|
data-full-path="${escapeHtmlAttr(clean)}"
|
||||||
|
class="${isUsed ? 'used-option' : ''}"
|
||||||
|
data-used="${isUsed ? '1' : '0'}"
|
||||||
|
${isSelected}>
|
||||||
|
${escapeHtmlText(label)}
|
||||||
|
</option>
|
||||||
|
`;
|
||||||
})
|
})
|
||||||
.join('');
|
.join('');
|
||||||
|
|
||||||
select.innerHTML = '<option value="">Select JSON Node</option>' + options;
|
select.innerHTML = '<option value="">Select JSON Node</option>' + options;
|
||||||
select.dataset.currentJson = currentValue;
|
select.dataset.currentJson = currentValue;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
initSelect2ForJsonDropdowns();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initSelect2ForJsonDropdowns() {
|
||||||
|
if (!(window.jQuery && $.fn.select2)) return;
|
||||||
|
|
||||||
|
$('.json-nodes').filter(function() {
|
||||||
|
const tr = this.closest('tr');
|
||||||
|
const mappingSelect = tr ? tr.querySelector('.mapping-select') : null;
|
||||||
|
return mappingSelect && mappingSelect.value === 'json';
|
||||||
|
}).each(function() {
|
||||||
|
const $el = $(this);
|
||||||
|
if (this.style.display === 'none') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($el.hasClass('select2-hidden-accessible')) {
|
||||||
|
$el.select2('destroy');
|
||||||
|
}
|
||||||
|
|
||||||
|
$el.select2({
|
||||||
|
width: '100%',
|
||||||
|
placeholder: 'Select JSON Node',
|
||||||
|
allowClear: true,
|
||||||
|
dropdownAutoWidth: false,
|
||||||
|
|
||||||
|
templateResult: function(data) {
|
||||||
|
if (!data.id) return data.text;
|
||||||
|
|
||||||
|
const option = data.element;
|
||||||
|
const shortName = option.getAttribute('data-short-name') || data.text;
|
||||||
|
const sample = option.getAttribute('data-sample') || '';
|
||||||
|
const isUsed = option.getAttribute('data-used') === '1';
|
||||||
|
|
||||||
|
const $row = $('<span class="select2-json-row"></span>');
|
||||||
|
if (isUsed) {
|
||||||
|
$row.addClass('used-option');
|
||||||
|
}
|
||||||
|
$row.append(`<span class="select2-json-node">${escapeHtmlText(shortName)}</span>`);
|
||||||
|
|
||||||
|
if (sample) {
|
||||||
|
$row.append(`<span class="select2-json-value">${escapeHtmlText(sample)}</span>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $row;
|
||||||
|
},
|
||||||
|
|
||||||
|
templateSelection: function(data) {
|
||||||
|
if (!data.id) return data.text;
|
||||||
|
|
||||||
|
const option = data.element;
|
||||||
|
const shortName = option.getAttribute('data-short-name') || data.text;
|
||||||
|
const sample = option.getAttribute('data-sample') || '';
|
||||||
|
|
||||||
|
if (sample) {
|
||||||
|
return `${shortName} — ${sample}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return shortName;
|
||||||
|
},
|
||||||
|
|
||||||
|
matcher: function(params, data) {
|
||||||
|
if ($.trim(params.term) === '') {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const term = params.term.toLowerCase();
|
||||||
|
const option = data.element;
|
||||||
|
|
||||||
|
const fullPath = option?.getAttribute('data-full-path')?.toLowerCase() || '';
|
||||||
|
const shortName = option?.getAttribute('data-short-name')?.toLowerCase() || '';
|
||||||
|
const sample = option?.getAttribute('data-sample')?.toLowerCase() || '';
|
||||||
|
|
||||||
|
if (
|
||||||
|
fullPath.includes(term) ||
|
||||||
|
shortName.includes(term) ||
|
||||||
|
sample.includes(term)
|
||||||
|
) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function saveJsonNodes(sampleJson, nodes) {
|
function saveJsonNodes(sampleJson, nodes) {
|
||||||
return fetch('update_api_json_nodes.php', {
|
return fetch('update_api_json_nodes.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -1463,25 +1658,38 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
document.getElementById('updateSchemaButton').addEventListener('click', updateSchemaDetails);
|
document.getElementById('updateSchemaButton').addEventListener('click', updateSchemaDetails);
|
||||||
|
|
||||||
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
|
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
|
||||||
|
if (!event.target.classList.contains('mapping-select')) return;
|
||||||
|
|
||||||
if (event.target.classList.contains('mapping-select')) {
|
const mappingSelect = event.target;
|
||||||
let tr = event.target.closest('tr');
|
const tr = mappingSelect.closest('tr');
|
||||||
let mappingId = event.target.getAttribute('data-id');
|
const mappingId = mappingSelect.getAttribute('data-id');
|
||||||
|
|
||||||
let xlsSelect = tr.querySelector('.xls-columns');
|
const xlsSelect = tr.querySelector('.xls-columns');
|
||||||
let jsonSelect = tr.querySelector('.json-nodes');
|
const jsonSelect = tr.querySelector('.json-nodes');
|
||||||
let manualInput = tr.querySelector('.manual-default');
|
const manualInput = tr.querySelector('.manual-default');
|
||||||
let autoSelect = tr.querySelector('.auto-value-select');
|
const autoSelect = tr.querySelector('.auto-value-select');
|
||||||
|
|
||||||
let mappedColumn = tr.querySelector('.mapped-column');
|
const mappedColumn = tr.querySelector('.mapped-column');
|
||||||
let mappedJsonNode = tr.querySelector('.mapped-json-node');
|
const mappedJsonNode = tr.querySelector('.mapped-json-node');
|
||||||
|
|
||||||
let removeBtn = tr.querySelector('.remove-xls');
|
const removeBtn = tr.querySelector('.remove-xls');
|
||||||
let removeJsonBtn = tr.querySelector('.remove-json');
|
const removeJsonBtn = tr.querySelector('.remove-json');
|
||||||
|
|
||||||
if (event.target.value === 'xls') {
|
function destroyJsonSelect2() {
|
||||||
|
if (jsonSelect && window.jQuery && $(jsonSelect).hasClass('select2-hidden-accessible')) {
|
||||||
|
$(jsonSelect).select2('destroy');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mappingSelect.value === 'xls') {
|
||||||
if (xlsSelect) xlsSelect.style.display = 'block';
|
if (xlsSelect) xlsSelect.style.display = 'block';
|
||||||
if (jsonSelect) jsonSelect.style.display = 'none';
|
|
||||||
|
destroyJsonSelect2();
|
||||||
|
if (jsonSelect) {
|
||||||
|
jsonSelect.style.display = 'none';
|
||||||
|
jsonSelect.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
if (autoSelect) autoSelect.style.display = 'none';
|
if (autoSelect) autoSelect.style.display = 'none';
|
||||||
|
|
||||||
if (manualInput) {
|
if (manualInput) {
|
||||||
@@ -1495,9 +1703,16 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
if (removeBtn) removeBtn.style.display = xlsSelect && xlsSelect.value ? 'inline-block' : 'none';
|
if (removeBtn) removeBtn.style.display = xlsSelect && xlsSelect.value ? 'inline-block' : 'none';
|
||||||
if (removeJsonBtn) removeJsonBtn.style.display = 'none';
|
if (removeJsonBtn) removeJsonBtn.style.display = 'none';
|
||||||
|
|
||||||
} else if (event.target.value === 'json') {
|
} else if (mappingSelect.value === 'json') {
|
||||||
if (xlsSelect) xlsSelect.style.display = 'none';
|
if (xlsSelect) {
|
||||||
if (jsonSelect) jsonSelect.style.display = 'block';
|
xlsSelect.style.display = 'none';
|
||||||
|
xlsSelect.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jsonSelect) {
|
||||||
|
jsonSelect.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
if (autoSelect) autoSelect.style.display = 'none';
|
if (autoSelect) autoSelect.style.display = 'none';
|
||||||
|
|
||||||
if (manualInput) {
|
if (manualInput) {
|
||||||
@@ -1511,9 +1726,17 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
if (removeBtn) removeBtn.style.display = 'none';
|
if (removeBtn) removeBtn.style.display = 'none';
|
||||||
if (removeJsonBtn) removeJsonBtn.style.display = jsonSelect && jsonSelect.value ? 'inline-block' : 'none';
|
if (removeJsonBtn) removeJsonBtn.style.display = jsonSelect && jsonSelect.value ? 'inline-block' : 'none';
|
||||||
|
|
||||||
} else if (event.target.value === 'manual') {
|
updateJsonDropdowns();
|
||||||
|
|
||||||
|
} else if (mappingSelect.value === 'manual') {
|
||||||
if (xlsSelect) xlsSelect.style.display = 'none';
|
if (xlsSelect) xlsSelect.style.display = 'none';
|
||||||
if (jsonSelect) jsonSelect.style.display = 'none';
|
|
||||||
|
destroyJsonSelect2();
|
||||||
|
if (jsonSelect) {
|
||||||
|
jsonSelect.style.display = 'none';
|
||||||
|
jsonSelect.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
if (autoSelect) autoSelect.style.display = 'none';
|
if (autoSelect) autoSelect.style.display = 'none';
|
||||||
|
|
||||||
if (manualInput) {
|
if (manualInput) {
|
||||||
@@ -1526,12 +1749,13 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
if (removeBtn) removeBtn.style.display = 'none';
|
if (removeBtn) removeBtn.style.display = 'none';
|
||||||
if (removeJsonBtn) removeJsonBtn.style.display = 'none';
|
if (removeJsonBtn) removeJsonBtn.style.display = 'none';
|
||||||
|
|
||||||
} else if (event.target.value === 'auto') {
|
} else if (mappingSelect.value === 'auto') {
|
||||||
if (xlsSelect) {
|
if (xlsSelect) {
|
||||||
xlsSelect.style.display = 'none';
|
xlsSelect.style.display = 'none';
|
||||||
xlsSelect.value = '';
|
xlsSelect.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroyJsonSelect2();
|
||||||
if (jsonSelect) {
|
if (jsonSelect) {
|
||||||
jsonSelect.style.display = 'none';
|
jsonSelect.style.display = 'none';
|
||||||
jsonSelect.value = '';
|
jsonSelect.value = '';
|
||||||
@@ -1552,7 +1776,13 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (xlsSelect) xlsSelect.style.display = 'none';
|
if (xlsSelect) xlsSelect.style.display = 'none';
|
||||||
if (jsonSelect) jsonSelect.style.display = 'none';
|
|
||||||
|
destroyJsonSelect2();
|
||||||
|
if (jsonSelect) {
|
||||||
|
jsonSelect.style.display = 'none';
|
||||||
|
jsonSelect.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
if (autoSelect) autoSelect.style.display = 'none';
|
if (autoSelect) autoSelect.style.display = 'none';
|
||||||
|
|
||||||
if (manualInput) {
|
if (manualInput) {
|
||||||
@@ -1569,7 +1799,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
|
|
||||||
saveMapping(
|
saveMapping(
|
||||||
mappingId,
|
mappingId,
|
||||||
event.target.value,
|
mappingSelect.value,
|
||||||
manualInput ? manualInput.value : '',
|
manualInput ? manualInput.value : '',
|
||||||
xlsSelect ? xlsSelect.value : null,
|
xlsSelect ? xlsSelect.value : null,
|
||||||
autoSelect ? autoSelect.value : null,
|
autoSelect ? autoSelect.value : null,
|
||||||
@@ -1577,143 +1807,67 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (sourceType === 'XLS') updateXlsDropdowns();
|
if (sourceType === 'XLS') updateXlsDropdowns();
|
||||||
if (sourceType === 'API') updateJsonDropdowns();
|
});
|
||||||
|
|
||||||
|
function saveJsonNodeSelection(jsonSelect) {
|
||||||
|
if (!jsonSelect) return;
|
||||||
|
|
||||||
|
let tr = jsonSelect.closest('tr');
|
||||||
|
let mappingId = jsonSelect.getAttribute('data-id');
|
||||||
|
let manualInput = tr.querySelector('.manual-default');
|
||||||
|
let mappedJsonNode = tr.querySelector('.mapped-json-node');
|
||||||
|
let removeJsonBtn = tr.querySelector('.remove-json');
|
||||||
|
let mappingSelect = tr.querySelector('.mapping-select');
|
||||||
|
|
||||||
|
if (mappingSelect && mappingSelect.value !== 'json') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.target.classList.contains('main-field-checkbox')) {
|
if (!mappedJsonNode) {
|
||||||
const checkbox = event.target;
|
mappedJsonNode = document.createElement('span');
|
||||||
const mappingId = checkbox.dataset.mappingId;
|
mappedJsonNode.className = 'mapped-json-node';
|
||||||
const value = checkbox.checked ? 1 : 0;
|
mappedJsonNode.style.marginLeft = '5px';
|
||||||
|
tr.querySelector('td:nth-child(7)').appendChild(mappedJsonNode);
|
||||||
// Count only the other Main fields already checked in this table
|
|
||||||
const otherCheckedMainFields = Array.from(
|
|
||||||
document.querySelectorAll('#schemaFieldsBody .main-field-checkbox')
|
|
||||||
).filter(cb => cb !== checkbox && cb.checked);
|
|
||||||
|
|
||||||
// If I am checking this one, I can have max 2 total:
|
|
||||||
// this checkbox + max 1 other already checked
|
|
||||||
if (checkbox.checked && otherCheckedMainFields.length >= 2) {
|
|
||||||
checkbox.checked = false;
|
|
||||||
alert('Puoi selezionare al massimo 2 campi Main.');
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch('update_main_field.php', {
|
if (!removeJsonBtn) {
|
||||||
method: 'POST',
|
removeJsonBtn = document.createElement('button');
|
||||||
headers: {
|
removeJsonBtn.className = 'btn btn-danger btn-sm remove-json';
|
||||||
'Content-Type': 'application/json'
|
removeJsonBtn.textContent = 'X';
|
||||||
},
|
removeJsonBtn.style.marginLeft = '5px';
|
||||||
body: JSON.stringify({
|
removeJsonBtn.setAttribute('data-id', mappingId);
|
||||||
template_id: <?php echo $id; ?>,
|
tr.querySelector('td:nth-child(7)').appendChild(removeJsonBtn);
|
||||||
mapping_id: mappingId,
|
|
||||||
value: value
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (!data.success) {
|
|
||||||
console.error("❌ Error updating main_field:", data.message);
|
|
||||||
|
|
||||||
document.querySelectorAll('#schemaFieldsBody .main-field-checkbox').forEach(cb => {
|
|
||||||
cb.checked = cb.dataset.originalChecked === 'true';
|
|
||||||
});
|
|
||||||
|
|
||||||
alert(data.message || 'Errore durante il salvataggio del campo Main.');
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelectorAll('#schemaFieldsBody .main-field-checkbox').forEach(cb => {
|
mappedJsonNode.textContent = jsonSelect.value ? `(${jsonSelect.value})` : '';
|
||||||
cb.dataset.originalChecked = cb.checked ? 'true' : 'false';
|
mappedJsonNode.style.display = jsonSelect.value ? 'inline' : 'none';
|
||||||
});
|
removeJsonBtn.style.display = jsonSelect.value ? 'inline-block' : 'none';
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error("❌ Fetch error:", error);
|
|
||||||
|
|
||||||
document.querySelectorAll('#schemaFieldsBody .main-field-checkbox').forEach(cb => {
|
console.log('[JSON NODE SAVE]', {
|
||||||
cb.checked = cb.dataset.originalChecked === 'true';
|
mappingId: mappingId,
|
||||||
|
value: jsonSelect.value
|
||||||
});
|
});
|
||||||
|
|
||||||
alert('Errore di comunicazione durante il salvataggio del campo Main.');
|
saveMapping(
|
||||||
});
|
mappingId,
|
||||||
|
'json',
|
||||||
return;
|
manualInput ? manualInput.value : '',
|
||||||
}
|
null,
|
||||||
if (event.target.classList.contains('visible-import-checkbox')) {
|
null,
|
||||||
const checkbox = event.target;
|
jsonSelect.value
|
||||||
const mappingId = checkbox.dataset.mappingId;
|
);
|
||||||
const value = checkbox.checked ? 1 : 0;
|
|
||||||
const prevChecked = checkbox.checked;
|
|
||||||
|
|
||||||
fetch('update_visible_import.php', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
template_id: <?php echo $id; ?>,
|
|
||||||
mapping_id: mappingId,
|
|
||||||
value: value
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.then(r => r.json())
|
|
||||||
.then(data => {
|
|
||||||
if (!data.success) {
|
|
||||||
console.error("❌ Error updating is_visible_import:", data.message);
|
|
||||||
checkbox.checked = !prevChecked;
|
|
||||||
alert(data.message || 'Errore durante il salvataggio del campo Import.');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error("❌ Fetch error:", error);
|
|
||||||
checkbox.checked = !prevChecked;
|
|
||||||
alert('Errore di comunicazione durante il salvataggio del campo Import.');
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (event.target.classList.contains('visible-parts-checkbox')) {
|
|
||||||
const checkbox = event.target;
|
|
||||||
const mappingId = checkbox.dataset.mappingId;
|
|
||||||
const value = checkbox.checked ? 1 : 0;
|
|
||||||
const prevChecked = checkbox.checked;
|
|
||||||
|
|
||||||
if (value === 1) {
|
|
||||||
document.querySelectorAll('.visible-parts-checkbox').forEach(cb => {
|
|
||||||
if (cb !== checkbox) cb.checked = false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch('update_visible_parts.php', {
|
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
|
||||||
method: 'POST',
|
if (!event.target.classList.contains('json-nodes')) return;
|
||||||
headers: {
|
saveJsonNodeSelection(event.target);
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
template_id: <?php echo $id; ?>,
|
|
||||||
mapping_id: mappingId,
|
|
||||||
value: value
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.then(r => r.json())
|
|
||||||
.then(data => {
|
|
||||||
if (!data.success) {
|
|
||||||
console.error("❌ Error updating is_visible_parts:", data.message);
|
|
||||||
checkbox.checked = !prevChecked;
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error("❌ Fetch error:", error);
|
|
||||||
checkbox.checked = !prevChecked;
|
|
||||||
location.reload();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
if (window.jQuery) {
|
||||||
}
|
$(document).on('select2:select select2:clear', 'select.json-nodes', function() {
|
||||||
|
saveJsonNodeSelection(this);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Save original Main checkbox state
|
// Save original Main checkbox state
|
||||||
@@ -1797,51 +1951,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
|
|
||||||
if (event.target.classList.contains('json-nodes')) {
|
|
||||||
let tr = event.target.closest('tr');
|
|
||||||
let mappingId = event.target.getAttribute('data-id');
|
|
||||||
let manualInput = tr.querySelector('.manual-default');
|
|
||||||
let mappedJsonNode = tr.querySelector('.mapped-json-node');
|
|
||||||
let removeJsonBtn = tr.querySelector('.remove-json');
|
|
||||||
|
|
||||||
if (!mappedJsonNode) {
|
|
||||||
mappedJsonNode = document.createElement('span');
|
|
||||||
mappedJsonNode.className = 'mapped-json-node';
|
|
||||||
mappedJsonNode.style.marginLeft = '5px';
|
|
||||||
tr.querySelector('td:nth-child(7)').appendChild(mappedJsonNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!removeJsonBtn) {
|
|
||||||
removeJsonBtn = document.createElement('button');
|
|
||||||
removeJsonBtn.className = 'btn btn-danger btn-sm remove-json';
|
|
||||||
removeJsonBtn.textContent = 'X';
|
|
||||||
removeJsonBtn.style.marginLeft = '5px';
|
|
||||||
removeJsonBtn.setAttribute('data-id', mappingId);
|
|
||||||
tr.querySelector('td:nth-child(7)').appendChild(removeJsonBtn);
|
|
||||||
}
|
|
||||||
|
|
||||||
mappedJsonNode.textContent = event.target.value ? `(${event.target.value})` : '';
|
|
||||||
mappedJsonNode.style.display = event.target.value ? 'inline' : 'none';
|
|
||||||
removeJsonBtn.style.display = event.target.value ? 'inline-block' : 'none';
|
|
||||||
|
|
||||||
const mappingSelect = tr.querySelector('.mapping-select');
|
|
||||||
if (mappingSelect && mappingSelect.value !== 'json') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
saveMapping(
|
|
||||||
mappingId,
|
|
||||||
'json',
|
|
||||||
manualInput ? manualInput.value : '',
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
event.target.value
|
|
||||||
);
|
|
||||||
|
|
||||||
updateJsonDropdowns();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
|
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
|
||||||
if (event.target.classList.contains('manual-default') && event.target.tagName === 'SELECT') {
|
if (event.target.classList.contains('manual-default') && event.target.tagName === 'SELECT') {
|
||||||
@@ -1968,9 +2077,11 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
updateXlsDropdowns();
|
updateXlsDropdowns();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.success && mappingType === 'json' && jsonNode) {
|
if (data.success && mappingType === 'json') {
|
||||||
usedJsonNodesFromDB = usedJsonNodesFromDB.filter(node => node !== jsonNode);
|
usedJsonNodesFromDB = Array.from(document.querySelectorAll('select.json-nodes'))
|
||||||
usedJsonNodesFromDB.push(jsonNode);
|
.map(select => select.value || select.dataset.currentJson || '')
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
updateJsonDropdowns();
|
updateJsonDropdowns();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -2205,6 +2316,17 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sourceType === 'API' && availableJsonNodes.length) {
|
if (sourceType === 'API' && availableJsonNodes.length) {
|
||||||
|
const rawJson = document.getElementById('apiJsonExample')?.value || '';
|
||||||
|
|
||||||
|
if (rawJson.trim()) {
|
||||||
|
try {
|
||||||
|
jsonNodeLabels = {};
|
||||||
|
flattenJsonNodes(JSON.parse(rawJson));
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Cannot rebuild JSON labels from sample JSON', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateJsonDropdowns();
|
updateJsonDropdowns();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -259,11 +259,9 @@ $matrixGroups = array_values($matrixGroups);
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex flex-wrap align-items-center gap-2 mb-3">
|
<div class="d-flex flex-wrap align-items-center gap-2 mb-3">
|
||||||
<div class="form-check m-0">
|
<input type="hidden" id="analysisWebOnly" value="1">
|
||||||
<input class="form-check-input" type="checkbox" id="analysisWebOnly">
|
<div class="small text-success fw-semibold">
|
||||||
<label class="form-check-label small" for="analysisWebOnly">
|
Showing WEB analyses only
|
||||||
Web only
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-grow-1" style="min-width: 220px;">
|
<div class="flex-grow-1" style="min-width: 220px;">
|
||||||
|
|||||||
@@ -4,6 +4,35 @@ require_once 'class/db-functions.php';
|
|||||||
|
|
||||||
$response = ["success" => false, "message" => ""];
|
$response = ["success" => false, "message" => ""];
|
||||||
|
|
||||||
|
function excelColumnToIndex($column)
|
||||||
|
{
|
||||||
|
$column = strtoupper(trim((string)$column));
|
||||||
|
|
||||||
|
if ($column === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Numeric column index, example: 40
|
||||||
|
if (ctype_digit($column)) {
|
||||||
|
$index = (int)$column;
|
||||||
|
return $index > 0 ? $index : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Excel column letters, example: A, AN, XFC
|
||||||
|
if (!preg_match('/^[A-Z]+$/', $column)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$index = 0;
|
||||||
|
$length = strlen($column);
|
||||||
|
|
||||||
|
for ($i = 0; $i < $length; $i++) {
|
||||||
|
$index = ($index * 26) + (ord($column[$i]) - ord('A') + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $index;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
|
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
|
||||||
throw new Exception("Invalid request method.");
|
throw new Exception("Invalid request method.");
|
||||||
@@ -19,6 +48,8 @@ try {
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
$start_column = trim($_POST['start_column'] ?? '');
|
$start_column = trim($_POST['start_column'] ?? '');
|
||||||
|
$xls_end_column = strtoupper(trim($_POST['xls_end_column'] ?? ''));
|
||||||
|
$xls_end_column = $xls_end_column !== '' ? $xls_end_column : null;
|
||||||
|
|
||||||
$xls_sheet_index = isset($_POST['xls_sheet_index']) && $_POST['xls_sheet_index'] !== ''
|
$xls_sheet_index = isset($_POST['xls_sheet_index']) && $_POST['xls_sheet_index'] !== ''
|
||||||
? intval($_POST['xls_sheet_index'])
|
? intval($_POST['xls_sheet_index'])
|
||||||
@@ -60,6 +91,24 @@ try {
|
|||||||
throw new Exception("XLS Sheet Number cannot be negative.");
|
throw new Exception("XLS Sheet Number cannot be negative.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$startColumnIndex = excelColumnToIndex($start_column);
|
||||||
|
|
||||||
|
if ($startColumnIndex === null) {
|
||||||
|
throw new Exception("Start Column is not valid. Use Excel column letters like A, AN or a positive number.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($xls_end_column !== null) {
|
||||||
|
$endColumnIndex = excelColumnToIndex($xls_end_column);
|
||||||
|
|
||||||
|
if ($endColumnIndex === null) {
|
||||||
|
throw new Exception("Last Column is not valid. Use Excel column letters like AN or a positive number.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($endColumnIndex < $startColumnIndex) {
|
||||||
|
throw new Exception("Last Column cannot be before Start Column.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$api_config_id = null;
|
$api_config_id = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,6 +120,7 @@ try {
|
|||||||
|
|
||||||
$header_row = null;
|
$header_row = null;
|
||||||
$start_column = null;
|
$start_column = null;
|
||||||
|
$xls_end_column = null;
|
||||||
$xls_sheet_index = null;
|
$xls_sheet_index = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +128,7 @@ try {
|
|||||||
if ($source_type === 'PDF') {
|
if ($source_type === 'PDF') {
|
||||||
$header_row = null;
|
$header_row = null;
|
||||||
$start_column = null;
|
$start_column = null;
|
||||||
|
$xls_end_column = null;
|
||||||
$xls_sheet_index = null;
|
$xls_sheet_index = null;
|
||||||
$api_config_id = null;
|
$api_config_id = null;
|
||||||
}
|
}
|
||||||
@@ -109,6 +160,7 @@ try {
|
|||||||
source_type = ?,
|
source_type = ?,
|
||||||
header_row = ?,
|
header_row = ?,
|
||||||
start_column = ?,
|
start_column = ?,
|
||||||
|
xls_end_column = ?,
|
||||||
xls_sheet_index = ?,
|
xls_sheet_index = ?,
|
||||||
api_config_id = ?,
|
api_config_id = ?,
|
||||||
description = ?,
|
description = ?,
|
||||||
@@ -131,6 +183,7 @@ try {
|
|||||||
$source_type,
|
$source_type,
|
||||||
$header_row,
|
$header_row,
|
||||||
$start_column,
|
$start_column,
|
||||||
|
$xls_end_column,
|
||||||
$xls_sheet_index,
|
$xls_sheet_index,
|
||||||
$api_config_id,
|
$api_config_id,
|
||||||
$description,
|
$description,
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ try {
|
|||||||
id,
|
id,
|
||||||
header_row,
|
header_row,
|
||||||
start_column,
|
start_column,
|
||||||
|
xls_end_column,
|
||||||
xls_sheet_index,
|
xls_sheet_index,
|
||||||
idroutine,
|
idroutine,
|
||||||
idclient
|
idclient
|
||||||
@@ -93,6 +94,12 @@ try {
|
|||||||
|
|
||||||
$start_column_raw = $template['start_column'] ?? 'A';
|
$start_column_raw = $template['start_column'] ?? 'A';
|
||||||
$start_column = normalizeColumnIndex($start_column_raw);
|
$start_column = normalizeColumnIndex($start_column_raw);
|
||||||
|
$xls_end_column_raw = trim((string)($template['xls_end_column'] ?? ''));
|
||||||
|
$xls_end_column = $xls_end_column_raw !== '' ? normalizeColumnIndex($xls_end_column_raw) : 0;
|
||||||
|
|
||||||
|
if ($xls_end_column > 0 && $xls_end_column < $start_column) {
|
||||||
|
throw new Exception("Last Column cannot be before Start Column.");
|
||||||
|
}
|
||||||
|
|
||||||
$xlsSheetIndex = isset($template['xls_sheet_index']) && $template['xls_sheet_index'] !== null
|
$xlsSheetIndex = isset($template['xls_sheet_index']) && $template['xls_sheet_index'] !== null
|
||||||
? (int)$template['xls_sheet_index']
|
? (int)$template['xls_sheet_index']
|
||||||
@@ -109,7 +116,15 @@ try {
|
|||||||
// Debug del template_id ricevuto
|
// Debug del template_id ricevuto
|
||||||
error_log("Received template_id from POST: " . print_r($_POST['template_id'], true));
|
error_log("Received template_id from POST: " . print_r($_POST['template_id'], true));
|
||||||
error_log("Converted template_id: $template_id");
|
error_log("Converted template_id: $template_id");
|
||||||
error_log("Template XLS settings - header_row: $header_row, start_column_raw: $start_column_raw, start_column_index: $start_column, xls_sheet_index: $xlsSheetIndex");
|
error_log(
|
||||||
|
"Template XLS settings - " .
|
||||||
|
"header_row: $header_row, " .
|
||||||
|
"start_column_raw: $start_column_raw, " .
|
||||||
|
"start_column_index: $start_column, " .
|
||||||
|
"xls_end_column_raw: " . ($xls_end_column_raw !== '' ? $xls_end_column_raw : '[empty = read all]') . ", " .
|
||||||
|
"xls_end_column_index: " . ($xls_end_column > 0 ? $xls_end_column : '[no limit]') . ", " .
|
||||||
|
"xls_sheet_index: $xlsSheetIndex"
|
||||||
|
);
|
||||||
|
|
||||||
$file = $_FILES['excel_file'];
|
$file = $_FILES['excel_file'];
|
||||||
$fileError = $file['error'];
|
$fileError = $file['error'];
|
||||||
@@ -161,8 +176,33 @@ try {
|
|||||||
if (empty($mappings)) {
|
if (empty($mappings)) {
|
||||||
$response['error'] = "Nessun mapping trovato per il template con ID $template_id";
|
$response['error'] = "Nessun mapping trovato per il template con ID $template_id";
|
||||||
} else {
|
} else {
|
||||||
// Carica il file rinominato con PHPSpreadsheet
|
// Load the XLS/XLSX file.
|
||||||
$spreadsheet = IOFactory::load($destination);
|
// If Last Column is configured in the template, load only the configured column range.
|
||||||
|
// If Last Column is empty, keep the original behavior and read the entire sheet.
|
||||||
|
$reader = IOFactory::createReaderForFile($destination);
|
||||||
|
$reader->setReadDataOnly(true);
|
||||||
|
|
||||||
|
if ($xls_end_column > 0) {
|
||||||
|
$reader->setReadFilter(new class($start_column, $xls_end_column) implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter {
|
||||||
|
private int $startColumn;
|
||||||
|
private int $endColumn;
|
||||||
|
|
||||||
|
public function __construct(int $startColumn, int $endColumn)
|
||||||
|
{
|
||||||
|
$this->startColumn = $startColumn;
|
||||||
|
$this->endColumn = $endColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readCell($columnAddress, $row, $worksheetName = ''): bool
|
||||||
|
{
|
||||||
|
$columnIndex = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($columnAddress);
|
||||||
|
|
||||||
|
return $columnIndex >= $this->startColumn && $columnIndex <= $this->endColumn;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$spreadsheet = $reader->load($destination);
|
||||||
|
|
||||||
$sheetCount = $spreadsheet->getSheetCount();
|
$sheetCount = $spreadsheet->getSheetCount();
|
||||||
$sheetNames = $spreadsheet->getSheetNames();
|
$sheetNames = $spreadsheet->getSheetNames();
|
||||||
@@ -193,6 +233,12 @@ try {
|
|||||||
$highestColumn = $worksheet->getHighestColumn();
|
$highestColumn = $worksheet->getHighestColumn();
|
||||||
$highestColumnIndex = Coordinate::columnIndexFromString($highestColumn);
|
$highestColumnIndex = Coordinate::columnIndexFromString($highestColumn);
|
||||||
|
|
||||||
|
// Force the effective highest column to Last Column, if configured.
|
||||||
|
if ($xls_end_column > 0) {
|
||||||
|
$highestColumnIndex = $xls_end_column;
|
||||||
|
$highestColumn = Coordinate::stringFromColumnIndex($highestColumnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
$startRow = max(1, $header_row);
|
$startRow = max(1, $header_row);
|
||||||
$startColumn = max(1, $start_column);
|
$startColumn = max(1, $start_column);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,35 @@ require_once 'class/db-functions.php';
|
|||||||
|
|
||||||
$response = ["success" => false, "message" => ""];
|
$response = ["success" => false, "message" => ""];
|
||||||
|
|
||||||
|
function excelColumnToIndex($column)
|
||||||
|
{
|
||||||
|
$column = strtoupper(trim((string)$column));
|
||||||
|
|
||||||
|
if ($column === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Numeric column index, example: 40
|
||||||
|
if (ctype_digit($column)) {
|
||||||
|
$index = (int)$column;
|
||||||
|
return $index > 0 ? $index : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Excel column letters, example: A, AN, XFC
|
||||||
|
if (!preg_match('/^[A-Z]+$/', $column)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$index = 0;
|
||||||
|
$length = strlen($column);
|
||||||
|
|
||||||
|
for ($i = 0; $i < $length; $i++) {
|
||||||
|
$index = ($index * 26) + (ord($column[$i]) - ord('A') + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $index;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
|
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
|
||||||
throw new Exception("Invalid request method.");
|
throw new Exception("Invalid request method.");
|
||||||
@@ -18,6 +47,8 @@ try {
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
$start_column = trim($_POST['start_column'] ?? '');
|
$start_column = trim($_POST['start_column'] ?? '');
|
||||||
|
$xls_end_column = strtoupper(trim($_POST['xls_end_column'] ?? ''));
|
||||||
|
$xls_end_column = $xls_end_column !== '' ? $xls_end_column : null;
|
||||||
|
|
||||||
$xls_sheet_index = isset($_POST['xls_sheet_index']) && $_POST['xls_sheet_index'] !== ''
|
$xls_sheet_index = isset($_POST['xls_sheet_index']) && $_POST['xls_sheet_index'] !== ''
|
||||||
? intval($_POST['xls_sheet_index'])
|
? intval($_POST['xls_sheet_index'])
|
||||||
@@ -63,6 +94,24 @@ try {
|
|||||||
throw new Exception("XLS Sheet Number cannot be negative.");
|
throw new Exception("XLS Sheet Number cannot be negative.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$startColumnIndex = excelColumnToIndex($start_column);
|
||||||
|
|
||||||
|
if ($startColumnIndex === null) {
|
||||||
|
throw new Exception("Start Column is not valid. Use Excel column letters like A, AN or a positive number.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($xls_end_column !== null) {
|
||||||
|
$endColumnIndex = excelColumnToIndex($xls_end_column);
|
||||||
|
|
||||||
|
if ($endColumnIndex === null) {
|
||||||
|
throw new Exception("Last Column is not valid. Use Excel column letters like AN or a positive number.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($endColumnIndex < $startColumnIndex) {
|
||||||
|
throw new Exception("Last Column cannot be before Start Column.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$api_config_id = null;
|
$api_config_id = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +124,7 @@ try {
|
|||||||
$header_row = null;
|
$header_row = null;
|
||||||
$start_column = null;
|
$start_column = null;
|
||||||
$xls_sheet_index = null;
|
$xls_sheet_index = null;
|
||||||
|
$xls_end_column = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// PDF currently does not require XLS coordinates or API configuration
|
// PDF currently does not require XLS coordinates or API configuration
|
||||||
@@ -82,6 +132,7 @@ try {
|
|||||||
$header_row = null;
|
$header_row = null;
|
||||||
$start_column = null;
|
$start_column = null;
|
||||||
$xls_sheet_index = null;
|
$xls_sheet_index = null;
|
||||||
|
$xls_end_column = null;
|
||||||
$api_config_id = null;
|
$api_config_id = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +163,7 @@ try {
|
|||||||
source_type,
|
source_type,
|
||||||
header_row,
|
header_row,
|
||||||
start_column,
|
start_column,
|
||||||
|
xls_end_column,
|
||||||
xls_sheet_index,
|
xls_sheet_index,
|
||||||
api_config_id,
|
api_config_id,
|
||||||
description,
|
description,
|
||||||
@@ -130,7 +182,7 @@ try {
|
|||||||
)
|
)
|
||||||
VALUES
|
VALUES
|
||||||
(
|
(
|
||||||
?, ?, ?, ?, ?, ?,
|
?, ?, ?, ?, ?, ?,?,
|
||||||
?, ?, ?, ?, ?, ?,
|
?, ?, ?, ?, ?, ?,
|
||||||
?, ?, ?, ?, ?,
|
?, ?, ?, ?, ?,
|
||||||
NOW(), NOW()
|
NOW(), NOW()
|
||||||
@@ -142,6 +194,7 @@ try {
|
|||||||
$source_type,
|
$source_type,
|
||||||
$header_row,
|
$header_row,
|
||||||
$start_column,
|
$start_column,
|
||||||
|
$xls_end_column,
|
||||||
$xls_sheet_index,
|
$xls_sheet_index,
|
||||||
$api_config_id,
|
$api_config_id,
|
||||||
$description,
|
$description,
|
||||||
|
|||||||
@@ -46,8 +46,8 @@
|
|||||||
{
|
{
|
||||||
"IdSchemaCustomFields": 48,
|
"IdSchemaCustomFields": 48,
|
||||||
"ConteggioClienti": 0,
|
"ConteggioClienti": 0,
|
||||||
"Nome": "Standard Generico \/ Generic Standard",
|
"Nome": "Standard \/ Generico",
|
||||||
"Descrizione": "Schema per tutti i campioni di qualsiasi matrice escluso cuoio\/pelle\r\n\r\n"
|
"Descrizione": "\r\n"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"IdSchemaCustomFields": 49,
|
"IdSchemaCustomFields": 49,
|
||||||
@@ -730,8 +730,8 @@
|
|||||||
{
|
{
|
||||||
"IdSchemaCustomFields": 177,
|
"IdSchemaCustomFields": 177,
|
||||||
"ConteggioClienti": 0,
|
"ConteggioClienti": 0,
|
||||||
"Nome": "Phoebe philo ACC",
|
"Nome": "Phoebe Philo ",
|
||||||
"Descrizione": "(scarpe, borse, cinture, occhiali, gioielleria)\r\n"
|
"Descrizione": "\r\n\r\n"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"IdSchemaCustomFields": 178,
|
"IdSchemaCustomFields": 178,
|
||||||
@@ -882,6 +882,24 @@
|
|||||||
"ConteggioClienti": 0,
|
"ConteggioClienti": 0,
|
||||||
"Nome": "LIMS-CIM - MAX MARA",
|
"Nome": "LIMS-CIM - MAX MARA",
|
||||||
"Descrizione": "Schema per MAX MARA scambio dati Database"
|
"Descrizione": "Schema per MAX MARA scambio dati Database"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdSchemaCustomFields": 203,
|
||||||
|
"ConteggioClienti": 0,
|
||||||
|
"Nome": "Vince",
|
||||||
|
"Descrizione": "Schema per tutti i campioni di VINCE\r\n\r\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdSchemaCustomFields": 204,
|
||||||
|
"ConteggioClienti": 0,
|
||||||
|
"Nome": "Max Mara",
|
||||||
|
"Descrizione": "Schema da usare per Max Mara\r\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdSchemaCustomFields": 205,
|
||||||
|
"ConteggioClienti": 0,
|
||||||
|
"Nome": "Chanel Flammability",
|
||||||
|
"Descrizione": "Schema per Chanel Flammability\r\n"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -82,6 +82,7 @@ try {
|
|||||||
template_id,
|
template_id,
|
||||||
schema_id,
|
schema_id,
|
||||||
field_id,
|
field_id,
|
||||||
|
field_order,
|
||||||
data_type,
|
data_type,
|
||||||
is_required,
|
is_required,
|
||||||
default_value,
|
default_value,
|
||||||
@@ -97,6 +98,7 @@ try {
|
|||||||
:template_id,
|
:template_id,
|
||||||
:schema_id,
|
:schema_id,
|
||||||
:field_id,
|
:field_id,
|
||||||
|
:field_order,
|
||||||
:data_type,
|
:data_type,
|
||||||
:is_required,
|
:is_required,
|
||||||
:default_value,
|
:default_value,
|
||||||
@@ -116,6 +118,7 @@ try {
|
|||||||
UPDATE template_mapping
|
UPDATE template_mapping
|
||||||
SET
|
SET
|
||||||
schema_id = :schema_id,
|
schema_id = :schema_id,
|
||||||
|
field_order = :field_order,
|
||||||
data_type = :data_type,
|
data_type = :data_type,
|
||||||
is_required = :is_required,
|
is_required = :is_required,
|
||||||
default_value = :default_value,
|
default_value = :default_value,
|
||||||
@@ -172,6 +175,7 @@ try {
|
|||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
':schema_id' => $schema_id,
|
':schema_id' => $schema_id,
|
||||||
|
':field_order' => (int)($field['Ordine'] ?? 9999),
|
||||||
':data_type' => $newDataType,
|
':data_type' => $newDataType,
|
||||||
':is_required' => !empty($custom_field['ObbligatorioWeb']) ? 1 : 0,
|
':is_required' => !empty($custom_field['ObbligatorioWeb']) ? 1 : 0,
|
||||||
':default_value' => $custom_field['ValoreDefault'] ?? null,
|
':default_value' => $custom_field['ValoreDefault'] ?? null,
|
||||||
@@ -234,7 +238,6 @@ try {
|
|||||||
|
|
||||||
$response["success"] = true;
|
$response["success"] = true;
|
||||||
$response["message"] = "Schema JSON updated, mappings synchronized, removed fields deleted, and changed fields updated successfully.";
|
$response["message"] = "Schema JSON updated, mappings synchronized, removed fields deleted, and changed fields updated successfully.";
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
if (isset($pdo) && $pdo->inTransaction()) {
|
if (isset($pdo) && $pdo->inTransaction()) {
|
||||||
$pdo->rollback();
|
$pdo->rollback();
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,8 +8,71 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<!--favicon-->
|
<!--favicon-->
|
||||||
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||||
|
|
||||||
<?php include('cssinclude.php'); ?>
|
<?php include('cssinclude.php'); ?>
|
||||||
<title>xxx - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?> - User Profile</title>
|
|
||||||
|
<!-- Select2 CSS -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||||
|
|
||||||
|
<title>SmartTRF - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?> - User Profile</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.profile-avatar-preview {
|
||||||
|
max-width: 100px;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
background: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-muted {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lims-loading {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-note {
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
color: #475569;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-error {
|
||||||
|
display: none;
|
||||||
|
background: #fff1f2;
|
||||||
|
border: 1px solid #fecdd3;
|
||||||
|
color: #be123c;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-success {
|
||||||
|
display: none;
|
||||||
|
background: #f0fdf4;
|
||||||
|
border: 1px solid #bbf7d0;
|
||||||
|
color: #166534;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -18,9 +81,11 @@
|
|||||||
<!--sidebar wrapper -->
|
<!--sidebar wrapper -->
|
||||||
<?php include('include/navbar.php'); ?>
|
<?php include('include/navbar.php'); ?>
|
||||||
<!--end sidebar wrapper -->
|
<!--end sidebar wrapper -->
|
||||||
|
|
||||||
<!--start header -->
|
<!--start header -->
|
||||||
<?php include('include/topbar.php'); ?>
|
<?php include('include/topbar.php'); ?>
|
||||||
<!--end header -->
|
<!--end header -->
|
||||||
|
|
||||||
<!--start page wrapper -->
|
<!--start page wrapper -->
|
||||||
<div class="page-wrapper">
|
<div class="page-wrapper">
|
||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
@@ -31,61 +96,195 @@
|
|||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div>
|
<div>
|
||||||
<h6 class="mb-0">Edit Profile</h6>
|
<h6 class="mb-0">Edit Profile</h6>
|
||||||
|
<small class="text-muted">Update your personal information and LIMS references.</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form action="update-profile.php" method="POST" enctype="multipart/form-data">
|
<form action="update-profile.php" method="POST" enctype="multipart/form-data" id="profileForm">
|
||||||
<div class="mb-3">
|
<div class="row g-3">
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
<label for="first_name" class="form-label">First Name</label>
|
<label for="first_name" class="form-label">First Name</label>
|
||||||
<input type="text" class="form-control" id="first_name" name="first_name" value="<?= htmlspecialchars($nameuser); ?>" required>
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="first_name"
|
||||||
|
name="first_name"
|
||||||
|
value="<?= htmlspecialchars($nameuser ?? '', ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
|
required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
|
||||||
|
<div class="col-md-6">
|
||||||
<label for="last_name" class="form-label">Last Name</label>
|
<label for="last_name" class="form-label">Last Name</label>
|
||||||
<input type="text" class="form-control" id="last_name" name="last_name" value="<?= htmlspecialchars($surnameuser); ?>" required>
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="last_name"
|
||||||
|
name="last_name"
|
||||||
|
value="<?= htmlspecialchars($surnameuser ?? '', ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
|
required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
|
||||||
|
<div class="col-md-12">
|
||||||
<label for="email" class="form-label">Email</label>
|
<label for="email" class="form-label">Email</label>
|
||||||
<input type="email" class="form-control" id="email" name="email" value="<?= htmlspecialchars($emailuser); ?>" required>
|
<input
|
||||||
|
type="email"
|
||||||
|
class="form-control"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
value="<?= htmlspecialchars($emailuser ?? '', ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
|
required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="col-md-6">
|
||||||
<label for="lims_user_id" class="form-label">Accettatore</label>
|
<label for="lims_user_id" class="form-label">Accettatore</label>
|
||||||
<input type="number" class="form-control" id="lims_user_id" name="lims_user_id" value="<?= htmlspecialchars($lims_user_id ?? ''); ?>">
|
<select
|
||||||
|
class="form-select"
|
||||||
|
id="lims_user_id"
|
||||||
|
name="lims_user_id"
|
||||||
|
data-current-value="<?= htmlspecialchars($lims_user_id ?? '', ENT_QUOTES, 'UTF-8'); ?>">
|
||||||
|
<option value="">Select acceptor</option>
|
||||||
|
</select>
|
||||||
|
<div class="small-muted mt-1">
|
||||||
|
Values loaded from CustomField ID 244.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="col-md-6">
|
||||||
<label for="lims_global_user_id" class="form-label">LIMS Global</label>
|
<label for="lims_global_user_id" class="form-label">LIMS Global</label>
|
||||||
<input type="number" class="form-control" id="lims_global_user_id" name="lims_global_user_id" value="<?= htmlspecialchars($lims_global_user_id ?? ''); ?>">
|
<select
|
||||||
|
class="form-select"
|
||||||
|
id="lims_global_user_id"
|
||||||
|
name="lims_global_user_id"
|
||||||
|
data-current-value="<?= htmlspecialchars($lims_global_user_id ?? '', ENT_QUOTES, 'UTF-8'); ?>">
|
||||||
|
<option value="">Select LIMS user</option>
|
||||||
|
</select>
|
||||||
|
<div class="small-muted mt-1">
|
||||||
|
Values loaded from LIMS users list.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="col-md-12">
|
||||||
|
<div class="lims-loading" id="limsLoadingMessage">
|
||||||
|
Loading LIMS users and acceptors...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-12">
|
||||||
<label for="avatar" class="form-label">Profile Picture</label>
|
<label for="avatar" class="form-label">Profile Picture</label>
|
||||||
<input type="file" class="form-control" id="avatar" name="avatar">
|
<input
|
||||||
<?php if ($photousername): ?>
|
type="file"
|
||||||
<p>Current avatar: <img src="..//upload/users/<?= htmlspecialchars($photousername); ?>" alt="Current Avatar" style="max-width: 100px;"></p>
|
class="form-control"
|
||||||
|
id="avatar"
|
||||||
|
name="avatar"
|
||||||
|
accept="image/jpeg,image/png,image/webp,image/gif">
|
||||||
|
|
||||||
|
<div class="mt-3 d-flex align-items-center gap-3">
|
||||||
|
<?php if (!empty($photousername)): ?>
|
||||||
|
<img
|
||||||
|
src="../upload/users/<?= htmlspecialchars($photousername, ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
|
alt="Current Avatar"
|
||||||
|
class="profile-avatar-preview"
|
||||||
|
id="avatarPreview">
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<p>Current avatar: <img src="..//upload/users/profile.png" alt="Default Avatar" style="max-width: 100px;"></p>
|
<img
|
||||||
|
src="../upload/users/profile.png"
|
||||||
|
alt="Default Avatar"
|
||||||
|
class="profile-avatar-preview"
|
||||||
|
id="avatarPreview">
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="small-muted">Current avatar</div>
|
||||||
|
<div class="small-muted">Allowed formats: JPG, PNG, WEBP, GIF.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
|
||||||
<label for="password" class="form-label">Password (leave blank to keep current)</label>
|
|
||||||
<input type="password" class="form-control" id="password" name="password">
|
|
||||||
</div>
|
</div>
|
||||||
<input type="hidden" name="iduserlogin" value="<?= htmlspecialchars($iduserlogin); ?>">
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">Update Profile</button>
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="password" class="form-label">New Password</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
class="form-control"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
autocomplete="new-password">
|
||||||
|
<button
|
||||||
|
class="btn btn-outline-secondary toggle-password"
|
||||||
|
type="button"
|
||||||
|
data-target="password"
|
||||||
|
aria-label="Show or hide password">
|
||||||
|
<i class="bx bx-show"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="password_confirm" class="form-label">Confirm New Password</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
class="form-control"
|
||||||
|
id="password_confirm"
|
||||||
|
name="password_confirm"
|
||||||
|
autocomplete="new-password">
|
||||||
|
<button
|
||||||
|
class="btn btn-outline-secondary toggle-password"
|
||||||
|
type="button"
|
||||||
|
data-target="password_confirm"
|
||||||
|
aria-label="Show or hide password confirmation">
|
||||||
|
<i class="bx bx-show"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="password-note" id="passwordNote">
|
||||||
|
<strong>Important:</strong> leave both password fields empty to keep your current password unchanged.
|
||||||
|
Type a new password only if you want to replace it.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="password-error mt-2" id="passwordError">
|
||||||
|
The two password fields do not match. Please check them before saving.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="password-success mt-2" id="passwordSuccess">
|
||||||
|
The two passwords match.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="iduserlogin"
|
||||||
|
value="<?= htmlspecialchars($iduserlogin ?? '', ENT_QUOTES, 'UTF-8'); ?>">
|
||||||
|
|
||||||
|
<div class="col-md-12">
|
||||||
|
<button type="submit" class="btn btn-primary" id="submitProfileButton">
|
||||||
|
Update Profile
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--end page wrapper -->
|
<!--end page wrapper -->
|
||||||
|
|
||||||
<!--start overlay-->
|
<!--start overlay-->
|
||||||
<div class="overlay toggle-icon"></div>
|
<div class="overlay toggle-icon"></div>
|
||||||
<!--end overlay-->
|
<!--end overlay-->
|
||||||
|
|
||||||
<!--Start Back To Top Button-->
|
<!--Start Back To Top Button-->
|
||||||
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
<a href="javaScript:;" class="back-to-top">
|
||||||
|
<i class='bx bxs-up-arrow-alt'></i>
|
||||||
|
</a>
|
||||||
<!--End Back To Top Button-->
|
<!--End Back To Top Button-->
|
||||||
|
|
||||||
<?php include('include/footer.php'); ?>
|
<?php include('include/footer.php'); ?>
|
||||||
</div>
|
</div>
|
||||||
<!--end wrapper-->
|
<!--end wrapper-->
|
||||||
@@ -99,7 +298,389 @@
|
|||||||
<?php //include('include/themeswitcher.php');
|
<?php //include('include/themeswitcher.php');
|
||||||
?>
|
?>
|
||||||
<!--end switcher-->
|
<!--end switcher-->
|
||||||
|
|
||||||
<?php include('jsinclude.php'); ?>
|
<?php include('jsinclude.php'); ?>
|
||||||
|
|
||||||
|
<!-- Select2 JS -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const profileForm = document.getElementById('profileForm');
|
||||||
|
const submitProfileButton = document.getElementById('submitProfileButton');
|
||||||
|
|
||||||
|
const passwordInput = document.getElementById('password');
|
||||||
|
const passwordConfirmInput = document.getElementById('password_confirm');
|
||||||
|
const passwordError = document.getElementById('passwordError');
|
||||||
|
const passwordSuccess = document.getElementById('passwordSuccess');
|
||||||
|
|
||||||
|
const limsUserSelect = document.getElementById('lims_user_id');
|
||||||
|
const limsGlobalUserSelect = document.getElementById('lims_global_user_id');
|
||||||
|
const limsLoadingMessage = document.getElementById('limsLoadingMessage');
|
||||||
|
const avatarInput = document.getElementById('avatar');
|
||||||
|
const avatarPreview = document.getElementById('avatarPreview');
|
||||||
|
|
||||||
|
const currentLimsUserId = String(limsUserSelect.dataset.currentValue || '');
|
||||||
|
const currentLimsGlobalUserId = String(limsGlobalUserSelect.dataset.currentValue || '');
|
||||||
|
|
||||||
|
const limsGlobalUsersEndpoint = 'get_utenti.php';
|
||||||
|
const limsAcceptorsEndpoint = 'get_customfield_values.php?field_ids=244';
|
||||||
|
|
||||||
|
function validatePasswords(showEmptySuccess = false) {
|
||||||
|
const password = passwordInput.value;
|
||||||
|
const confirmPassword = passwordConfirmInput.value;
|
||||||
|
|
||||||
|
passwordError.style.display = 'none';
|
||||||
|
passwordSuccess.style.display = 'none';
|
||||||
|
passwordInput.classList.remove('is-invalid', 'is-valid');
|
||||||
|
passwordConfirmInput.classList.remove('is-invalid', 'is-valid');
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Both empty means: keep current password.
|
||||||
|
*/
|
||||||
|
if (password === '' && confirmPassword === '') {
|
||||||
|
submitProfileButton.disabled = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One field filled and the other empty is not valid.
|
||||||
|
*/
|
||||||
|
if (password === '' || confirmPassword === '') {
|
||||||
|
passwordError.textContent = 'Please fill both password fields, or leave both empty to keep the current password.';
|
||||||
|
passwordError.style.display = 'block';
|
||||||
|
passwordInput.classList.add('is-invalid');
|
||||||
|
passwordConfirmInput.classList.add('is-invalid');
|
||||||
|
submitProfileButton.disabled = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Both filled but different.
|
||||||
|
*/
|
||||||
|
if (password !== confirmPassword) {
|
||||||
|
passwordError.textContent = 'The two password fields do not match. Please check them before saving.';
|
||||||
|
passwordError.style.display = 'block';
|
||||||
|
passwordInput.classList.add('is-invalid');
|
||||||
|
passwordConfirmInput.classList.add('is-invalid');
|
||||||
|
submitProfileButton.disabled = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Both filled and equal.
|
||||||
|
*/
|
||||||
|
passwordInput.classList.add('is-valid');
|
||||||
|
passwordConfirmInput.classList.add('is-valid');
|
||||||
|
passwordSuccess.style.display = showEmptySuccess ? 'block' : 'block';
|
||||||
|
submitProfileButton.disabled = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordInput.addEventListener('input', function() {
|
||||||
|
validatePasswords();
|
||||||
|
});
|
||||||
|
|
||||||
|
passwordConfirmInput.addEventListener('input', function() {
|
||||||
|
validatePasswords();
|
||||||
|
});
|
||||||
|
|
||||||
|
profileForm.addEventListener('submit', function(event) {
|
||||||
|
if (!validatePasswords(true)) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (typeof Swal !== 'undefined') {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Password mismatch',
|
||||||
|
text: 'Please check the password fields before saving.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
function initSelect2() {
|
||||||
|
if (typeof $ === 'undefined') {
|
||||||
|
console.error('jQuery is not loaded. Select2 cannot start.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof $.fn.select2 === 'undefined') {
|
||||||
|
console.error('Select2 is not loaded. Check select2.min.js include.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#lims_user_id').select2({
|
||||||
|
width: '100%',
|
||||||
|
placeholder: 'Select acceptor',
|
||||||
|
allowClear: true,
|
||||||
|
minimumResultsForSearch: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#lims_global_user_id').select2({
|
||||||
|
width: '100%',
|
||||||
|
placeholder: 'Select LIMS user',
|
||||||
|
allowClear: true,
|
||||||
|
minimumResultsForSearch: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroySelect2IfActive(selector) {
|
||||||
|
if (typeof $ !== 'undefined' && $.fn.select2 && $(selector).hasClass('select2-hidden-accessible')) {
|
||||||
|
$(selector).select2('destroy');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshSelect2() {
|
||||||
|
if (typeof $ === 'undefined' || typeof $.fn.select2 === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroySelect2IfActive('#lims_user_id');
|
||||||
|
destroySelect2IfActive('#lims_global_user_id');
|
||||||
|
|
||||||
|
$('#lims_user_id').select2({
|
||||||
|
width: '100%',
|
||||||
|
placeholder: 'Select acceptor',
|
||||||
|
allowClear: true,
|
||||||
|
minimumResultsForSearch: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#lims_global_user_id').select2({
|
||||||
|
width: '100%',
|
||||||
|
placeholder: 'Select LIMS user',
|
||||||
|
allowClear: true,
|
||||||
|
minimumResultsForSearch: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#lims_user_id').trigger('change');
|
||||||
|
$('#lims_global_user_id').trigger('change');
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSelect(selectElement, placeholderText) {
|
||||||
|
selectElement.innerHTML = '';
|
||||||
|
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = '';
|
||||||
|
option.textContent = placeholderText;
|
||||||
|
selectElement.appendChild(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendOption(selectElement, value, text) {
|
||||||
|
if (value === null || value === undefined || value === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = String(value);
|
||||||
|
option.textContent = text || String(value);
|
||||||
|
selectElement.appendChild(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSelectValue(selectElement, value) {
|
||||||
|
selectElement.value = value || '';
|
||||||
|
|
||||||
|
if (typeof $ !== 'undefined' && $.fn.select2) {
|
||||||
|
$(selectElement).trigger('change');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLimsGlobalUsers(data) {
|
||||||
|
const list = Array.isArray(data) ? data : (data.value || []);
|
||||||
|
const normalized = [];
|
||||||
|
|
||||||
|
list.forEach(function(item) {
|
||||||
|
const id = item.IdUtente ?? item.id ?? item.ID ?? null;
|
||||||
|
const text = item.Nome || item.Nominativo || item.UserName || item.Email || id;
|
||||||
|
|
||||||
|
if (id !== null && id !== undefined && id !== '') {
|
||||||
|
normalized.push({
|
||||||
|
id: String(id),
|
||||||
|
text: String(text)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
normalized.sort(function(a, b) {
|
||||||
|
return String(a.text).localeCompare(String(b.text), 'it', {
|
||||||
|
sensitivity: 'base'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAcceptors(data) {
|
||||||
|
const list = data['244'] || data[244] || [];
|
||||||
|
const normalized = [];
|
||||||
|
|
||||||
|
list.forEach(function(item) {
|
||||||
|
const id =
|
||||||
|
item.IdCustomFieldValue ??
|
||||||
|
item.IdCustomFieldsValue ??
|
||||||
|
item.IdCustomFieldValues ??
|
||||||
|
item.Id ??
|
||||||
|
item.ID ??
|
||||||
|
item.id ??
|
||||||
|
item.ValueId ??
|
||||||
|
item.value_id ??
|
||||||
|
item.Codice ??
|
||||||
|
null;
|
||||||
|
|
||||||
|
const text =
|
||||||
|
item.Value ??
|
||||||
|
item.Valore ??
|
||||||
|
item.Nome ??
|
||||||
|
item.Name ??
|
||||||
|
item.Descrizione ??
|
||||||
|
item.Description ??
|
||||||
|
item.Text ??
|
||||||
|
item.text ??
|
||||||
|
item.Label ??
|
||||||
|
item.label ??
|
||||||
|
id;
|
||||||
|
|
||||||
|
if (id !== null && id !== undefined && id !== '') {
|
||||||
|
normalized.push({
|
||||||
|
id: String(id),
|
||||||
|
text: String(text)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
normalized.sort(function(a, b) {
|
||||||
|
return String(a.text).localeCompare(String(b.text), 'it', {
|
||||||
|
sensitivity: 'base'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateLimsGlobalUsers(users) {
|
||||||
|
clearSelect(limsGlobalUserSelect, 'Select LIMS user');
|
||||||
|
|
||||||
|
users.forEach(function(user) {
|
||||||
|
appendOption(limsGlobalUserSelect, user.id, user.text);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (currentLimsGlobalUserId !== '') {
|
||||||
|
setSelectValue(limsGlobalUserSelect, currentLimsGlobalUserId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateAcceptors(acceptors) {
|
||||||
|
clearSelect(limsUserSelect, 'Select acceptor');
|
||||||
|
|
||||||
|
acceptors.forEach(function(item) {
|
||||||
|
appendOption(limsUserSelect, item.id, item.text);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (currentLimsUserId !== '') {
|
||||||
|
setSelectValue(limsUserSelect, currentLimsUserId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadLimsDropdowns() {
|
||||||
|
try {
|
||||||
|
const [globalResponse, acceptorsResponse] = await Promise.all([
|
||||||
|
fetch(limsGlobalUsersEndpoint, {
|
||||||
|
cache: 'no-store'
|
||||||
|
}),
|
||||||
|
fetch(limsAcceptorsEndpoint, {
|
||||||
|
cache: 'no-store'
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!globalResponse.ok) {
|
||||||
|
throw new Error('Unable to load LIMS global users.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!acceptorsResponse.ok) {
|
||||||
|
throw new Error('Unable to load LIMS acceptors.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const globalData = await globalResponse.json();
|
||||||
|
const acceptorsData = await acceptorsResponse.json();
|
||||||
|
|
||||||
|
const globalUsers = normalizeLimsGlobalUsers(globalData);
|
||||||
|
const acceptors = normalizeAcceptors(acceptorsData);
|
||||||
|
|
||||||
|
populateLimsGlobalUsers(globalUsers);
|
||||||
|
populateAcceptors(acceptors);
|
||||||
|
refreshSelect2();
|
||||||
|
|
||||||
|
if (limsLoadingMessage) {
|
||||||
|
limsLoadingMessage.textContent = 'LIMS users and acceptors loaded.';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
if (limsLoadingMessage) {
|
||||||
|
limsLoadingMessage.textContent = 'Warning: unable to load LIMS users or acceptors.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof Swal !== 'undefined') {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'warning',
|
||||||
|
title: 'LIMS lists not loaded',
|
||||||
|
text: error.message || 'Unable to load LIMS dropdown values.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
avatarInput.addEventListener('change', function(event) {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = function(e) {
|
||||||
|
avatarPreview.src = e.target.result;
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
document.querySelectorAll('.toggle-password').forEach(function(button) {
|
||||||
|
button.addEventListener('click', function() {
|
||||||
|
const targetId = button.dataset.target;
|
||||||
|
const input = document.getElementById(targetId);
|
||||||
|
const icon = button.querySelector('i');
|
||||||
|
|
||||||
|
if (!input) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.type === 'password') {
|
||||||
|
input.type = 'text';
|
||||||
|
|
||||||
|
if (icon) {
|
||||||
|
icon.classList.remove('bx-show');
|
||||||
|
icon.classList.add('bx-hide');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
input.type = 'password';
|
||||||
|
|
||||||
|
if (icon) {
|
||||||
|
icon.classList.remove('bx-hide');
|
||||||
|
icon.classList.add('bx-show');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
initSelect2();
|
||||||
|
loadLimsDropdowns();
|
||||||
|
validatePasswords();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate rows before export to LIMS.
|
* Validate rows before export to LIMS.
|
||||||
*
|
*
|
||||||
@@ -88,6 +89,58 @@ $validators[] = function (int $iddatadb, array $ctx): array {
|
|||||||
return [];
|
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 ────────────────────────────────────────────────────────────────────
|
// ── Main ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -104,9 +157,12 @@ try {
|
|||||||
$iddatadbList = array_column($rows, 'iddatadb');
|
$iddatadbList = array_column($rows, 'iddatadb');
|
||||||
$placeholders = implode(',', array_fill(0, count($iddatadbList), '?'));
|
$placeholders = implode(',', array_fill(0, count($iddatadbList), '?'));
|
||||||
|
|
||||||
// Records (datadb) for fixed field validation
|
// Records (datadb) — templateid + fixed-field columns for mandatory validation
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
SELECT iddatadb, consegna_richiesta
|
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
|
FROM datadb
|
||||||
WHERE iddatadb IN ($placeholders)
|
WHERE iddatadb IN ($placeholders)
|
||||||
");
|
");
|
||||||
@@ -128,6 +184,63 @@ try {
|
|||||||
$partsInfo[(int)$r['iddatadb']][] = $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 (excluding filed_id = 189 Tested component)
|
||||||
|
$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
|
||||||
|
AND id <> 189
|
||||||
|
");
|
||||||
|
$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 ──────────────────────────────────────────────
|
// ── Run validators per row ──────────────────────────────────────────────
|
||||||
|
|
||||||
$results = [];
|
$results = [];
|
||||||
@@ -137,9 +250,15 @@ try {
|
|||||||
$index = $rowInfo['index'];
|
$index = $rowInfo['index'];
|
||||||
|
|
||||||
// Build context for validators
|
// Build context for validators
|
||||||
|
$record = $recordsInfo[$iddatadb] ?? null;
|
||||||
|
$templateId = (int)($record['templateid'] ?? 0);
|
||||||
$ctx = [
|
$ctx = [
|
||||||
'record' => $recordsInfo[$iddatadb] ?? null,
|
'record' => $record,
|
||||||
'parts' => $partsInfo[$iddatadb] ?? [],
|
'parts' => $partsInfo[$iddatadb] ?? [],
|
||||||
|
'fixedAliasMap' => $fixedAliasMap,
|
||||||
|
'requiredFixed' => $requiredFixedByTemplate[$templateId] ?? [],
|
||||||
|
'requiredCustom' => $requiredCustomByTemplate[$templateId] ?? [],
|
||||||
|
'customValues' => $customValuesByRecord[$iddatadb] ?? [],
|
||||||
];
|
];
|
||||||
|
|
||||||
$errors = [];
|
$errors = [];
|
||||||
@@ -155,7 +274,6 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'results' => $results]);
|
echo json_encode(['success' => true, 'results' => $results]);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
error_log("Validation error: " . $e->getMessage());
|
error_log("Validation error: " . $e->getMessage());
|
||||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
|||||||
Reference in New Issue
Block a user