From 1fed113c5cac8ee73e95e2e7354546cc2c0dd755 Mon Sep 17 00:00:00 2001 From: "r.mubarakzyanov" Date: Sat, 28 Feb 2026 00:44:21 +0300 Subject: [PATCH] VisualLimsApiClientMock: fake data for all LIMS endpoints; getInstance() branches on SIMULATE_EXPORT_LIMS get_clienti.php, get_fixed_field_data.php: simulate mode support CustomField dropdown values mock added (get_customfield_values.php) exportUnsavedModal: prompt save before export, MutationObserver waits for save, then proceeds to confirm Removed old jQuery .export-lims-btn handler that bypassed confirm modal Fix false "Unsaved changes" on page load: data-restoring guard in all programmatic trigger/dispatchEvent calls (populateSelect, populateClientDropdowns, populateDropdowns) Fix ConsegnaRichiesta not shown on refresh: add to PHP $fixedAliasMap Add step5_2_photos, step9_1_importa --- .../class/VisualLimsApiClient.class.php | 62 ++- .../class/VisualLimsApiClientMock.class.php | 135 ++++++ public/userarea/export_to_lims.js | 400 +++++++++--------- public/userarea/export_to_lims.php | 124 +++++- public/userarea/get_clienti.php | 19 +- public/userarea/get_fixed_field_data.php | 18 +- public/userarea/import_edit2.php | 95 +++-- 7 files changed, 584 insertions(+), 269 deletions(-) create mode 100644 public/userarea/class/VisualLimsApiClientMock.class.php diff --git a/public/userarea/class/VisualLimsApiClient.class.php b/public/userarea/class/VisualLimsApiClient.class.php index ca7d5ec..f2d992d 100644 --- a/public/userarea/class/VisualLimsApiClient.class.php +++ b/public/userarea/class/VisualLimsApiClient.class.php @@ -24,7 +24,17 @@ class VisualLimsApiClient public static function getInstance() { if (self::$instance === null) { - self::$instance = new VisualLimsApiClient(); + $dotenv = Dotenv::createImmutable(dirname(__DIR__, 3)); + $dotenv->load(); + + $simulate = ($_ENV['SIMULATE_EXPORT_LIMS'] ?? '') === 'true'; + + if ($simulate) { + require_once __DIR__ . '/VisualLimsApiClientMock.class.php'; + self::$instance = new VisualLimsApiClientMock(); + } else { + self::$instance = new VisualLimsApiClient(); + } } return self::$instance; } @@ -193,6 +203,56 @@ class VisualLimsApiClient return json_decode($response, true); } + /** + * POST a file as multipart/form-data (used for photo/attachment uploads). + * + * @param string $endpoint OData endpoint, e.g. "AllegatoCommessaWeb" + * @param string $filePath Absolute path to the file on disk + * @param string $fileName Original file name to send + * @param int $commessaId CommessaWeb ID to link the attachment to + * @return array|null Decoded JSON response + */ + public function postMultipart($endpoint, $filePath, $fileName, $commessaId) + { + $token = $this->getToken(); + $url = "{$this->baseUrl}/api/odata/{$endpoint}"; + + $cfile = new CURLFile($filePath, mime_content_type($filePath) ?: 'application/octet-stream', $fileName); + + $payload = [ + 'IdCommessa' => $commessaId, + 'NomeFile' => $fileName, + 'file' => $cfile, + ]; + + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "Authorization: Bearer {$token}", + "Accept: application/json", + // Content-Type is set automatically to multipart/form-data by cURL + ]); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + + $response = curl_exec($ch); + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $curl_error = curl_error($ch); + curl_close($ch); + + if ($response === false) { + throw new Exception("Errore nella richiesta POST multipart: {$curl_error}"); + } + + if ($http_code < 200 || $http_code >= 300) { + throw new Exception("POST multipart fallito: HTTP {$http_code}, Risposta: " . substr($response, 0, 1000)); + } + + return json_decode($response, true); + } + public function getBaseUrl() { return $this->baseUrl; diff --git a/public/userarea/class/VisualLimsApiClientMock.class.php b/public/userarea/class/VisualLimsApiClientMock.class.php new file mode 100644 index 0000000..2f5be33 --- /dev/null +++ b/public/userarea/class/VisualLimsApiClientMock.class.php @@ -0,0 +1,135 @@ +fakeCommessaId = mt_rand(90001, 99999); + error_log("[SIMULATE] VisualLimsApiClientMock initialised (fakeCommessaId={$this->fakeCommessaId})"); + } + + public function get(string $endpoint): array + { + error_log("[SIMULATE] GET {$endpoint}"); + + // --- Fixed-field dropdown lists --- + + if (str_starts_with($endpoint, 'MoltiplicatorePrezzi')) { + return ['value' => [ + ['IdMoltiplicatorePrezzo' => 1, 'Codice' => 'MP-01', 'Descrizione' => 'Standard (1x)'], + ['IdMoltiplicatorePrezzo' => 2, 'Codice' => 'MP-02', 'Descrizione' => 'Urgente (1.5x)'], + ['IdMoltiplicatorePrezzo' => 3, 'Codice' => 'MP-03', 'Descrizione' => 'Extra Urgente (2x)'], + ]]; + } + + if (str_starts_with($endpoint, 'AnagraficaCertestObject')) { + return ['value' => [ + ['IdAnagrafica' => 1, 'Codice' => 'OBJ-01', 'NomeAnagrafica' => 'Articolo Tessile'], + ['IdAnagrafica' => 2, 'Codice' => 'OBJ-02', 'NomeAnagrafica' => 'Componente Meccanico'], + ['IdAnagrafica' => 3, 'Codice' => 'OBJ-03', 'NomeAnagrafica' => 'Materiale Plastico'], + ]]; + } + + if (str_starts_with($endpoint, 'AnagraficaCertestService')) { + return ['value' => [ + ['IdAnagrafica' => 1, 'Codice' => 'SRV-01', 'NomeAnagrafica' => 'Analisi Chimica'], + ['IdAnagrafica' => 2, 'Codice' => 'SRV-02', 'NomeAnagrafica' => 'Test Meccanico'], + ['IdAnagrafica' => 3, 'Codice' => 'SRV-03', 'NomeAnagrafica' => 'Prova Ambientale'], + ]]; + } + + // Cliente? list — get_clienti.php exits early in simulate mode, but guard here too + if (str_starts_with($endpoint, 'Cliente?')) { + return ['value' => []]; + } + + // Cliente(N)?$expand=Responsabili + if (str_starts_with($endpoint, 'Cliente(')) { + preg_match('/Cliente\((\d+)\)/', $endpoint, $m); + $clienteId = isset($m[1]) ? (int) $m[1] : 0; + return [ + 'IdCliente' => $clienteId, + 'Responsabili' => [ + ['IdClienteResponsabile' => 1, 'Nominativo' => 'Marco Bianchi'], + ['IdClienteResponsabile' => 2, 'Nominativo' => 'Giulia Ferrari'], + ['IdClienteResponsabile' => 3, 'Nominativo' => 'Andrea Russo'], + ], + ]; + } + + // --- CustomField dropdown values (get_customfield_values.php) --- + + if (str_starts_with($endpoint, 'CustomField(')) { + preg_match('/CustomField\((\d+)\)/', $endpoint, $m); + $fieldId = isset($m[1]) ? (int) $m[1] : 0; + return [ + 'CustomFieldsValues' => [ + ['IdCustomFieldsValue' => $fieldId * 10 + 1, 'Valore' => 'Opzione A'], + ['IdCustomFieldsValue' => $fieldId * 10 + 2, 'Valore' => 'Opzione B'], + ['IdCustomFieldsValue' => $fieldId * 10 + 3, 'Valore' => 'Opzione C'], + ], + ]; + } + + // --- CommessaWeb OData calls (STEP 7 GET + STEP 10 verification) --- + + preg_match('/\((\d+)\)/', $endpoint, $m); + $id = isset($m[1]) ? (int) $m[1] : $this->fakeCommessaId; + + return [ + 'IdCommessa' => $id, + 'CodiceCommessa' => "SIM-{$id}", + 'CommesseCustomFields' => [], // Empty → PATCH step is skipped correctly + ]; + } + + public function post(string $endpoint, array $payload): array + { + error_log("[SIMULATE] POST {$endpoint} payload=" . json_encode($payload)); + + // CommessaWeb creation + if ($endpoint === 'CommessaWeb') { + return [ + 'IdCommessa' => $this->fakeCommessaId, + 'CodiceCommessa' => "SIM-{$this->fakeCommessaId}", + 'Richiedente' => $payload['Richiedente'] ?? '', + 'Descrizione' => $payload['Descrizione'] ?? '', + ]; + } + + // Campione creation + if ($endpoint === 'Campione') { + return [ + 'IdCampione' => mt_rand(10001, 19999), + 'Commessa' => $payload['Commessa'] ?? null, + 'Matrice' => $payload['Matrice'] ?? null, + ]; + } + + // InviaCommessa / ImportaCommessa (currently commented out upstream) + return ['simulated' => true, 'endpoint' => $endpoint]; + } + + public function patch(string $endpoint, array $payload): array + { + error_log("[SIMULATE] PATCH {$endpoint} payload=" . json_encode($payload)); + + return []; + } + + public function postMultipart(string $endpoint, string $filePath, string $fileName, int $commessaId): array + { + error_log("[SIMULATE] POST multipart {$endpoint} file={$fileName} commessaId={$commessaId}"); + + return ['simulated' => true, 'file' => $fileName]; + } +} diff --git a/public/userarea/export_to_lims.js b/public/userarea/export_to_lims.js index 74e2be2..c8970da 100644 --- a/public/userarea/export_to_lims.js +++ b/public/userarea/export_to_lims.js @@ -1,7 +1,6 @@ document.addEventListener("DOMContentLoaded", () => { console.log("export_to_lims.js loaded"); - // Debug: verifica che i pulsanti siano trovati const exportButtons = document.querySelectorAll(".export-lims-btn"); console.log(`Found ${exportButtons.length} export-lims-btn buttons`); @@ -10,6 +9,170 @@ document.addEventListener("DOMContentLoaded", () => { return; } + // Tracks the active confirm handler so it can be replaced on re-open + let pendingConfirmHandler = null; + + // ── Helpers ────────────────────────────────────────────────────────────── + + function cleanupBackdrop() { + document.querySelectorAll(".modal-backdrop").forEach((b) => b.remove()); + document.body.classList.remove("modal-open"); + document.body.style.paddingRight = ""; + const overlay = document.querySelector(".overlay.toggle-icon"); + if (overlay) overlay.style.display = "none"; + } + + // ── Step 2: show export-confirm modal, send on "Conferma" ──────────────── + + function startExportConfirmFlow(iddatadb, btn) { + const confirmModalElement = document.getElementById("exportConfirmModal"); + if (!confirmModalElement) { + alert("Errore: Modale di conferma non trovato"); + return; + } + + const confirmModal = new bootstrap.Modal(confirmModalElement, { + keyboard: false, + }); + document.getElementById("exportIddatadb").textContent = iddatadb; + confirmModal.show(); + + const confirmBtn = document.getElementById("exportConfirmBtn"); + if (!confirmBtn) { + confirmModal.hide(); + alert("Errore: Pulsante di conferma non trovato"); + return; + } + + const confirmHandler = async () => { + pendingConfirmHandler = null; + console.log(`Confirmed export for iddatadb: ${iddatadb}`); + confirmModal.hide(); + + const formData = new FormData(); + formData.append("iddatadb", iddatadb); + + try { + const response = await fetch("export_to_lims.php", { + method: "POST", + body: formData, + }); + if (!response.ok) + throw new Error(`HTTP error! status: ${response.status}`); + const data = await response.json(); + + console.log("Export response:", data); + + const responseModalElement = + document.getElementById("exportResponseModal"); + if (!responseModalElement) { + alert("Errore: Modale di risposta non trovato"); + return; + } + + const responseModal = new bootstrap.Modal( + responseModalElement, + { keyboard: false }, + ); + const responseMessage = document.getElementById( + "exportResponseMessage", + ); + + if (data.success) { + responseMessage.innerHTML = + `${data.message.replace(/\n/g, "
")}` + + `
ID CommessaWeb: ${data.idcommessaweb}` + + `
Codice CommessaWeb: ${data.commessaweb}` + + (data.totalPhotos > 0 + ? `
Foto trovate: ${data.totalPhotos}` + : ""); + document.getElementById( + "exportResponseModalLabel", + ).textContent = "Esportazione Completata"; + responseModal.show(); + + // Update status badge + const gridRow = btn.closest(".grid-row"); + const statusBadge = gridRow?.querySelector( + '.grid-cell[data-col="status"] .status-badge', + ); + if (statusBadge) { + statusBadge.classList.remove("status-i", "status-P"); + statusBadge.classList.add("status-l"); + statusBadge.textContent = "To LIMS"; + } + + // Insert/update CommessaWeb code span + const statusCell = gridRow?.querySelector( + '.grid-cell[data-col="status"]', + ); + if (statusCell && data.commessaweb) { + let cwSpan = + statusCell.querySelector(".commessaweb-code"); + if (!cwSpan) { + cwSpan = document.createElement("span"); + cwSpan.className = "commessaweb-code"; + cwSpan.style.cssText = + "display:block; font-size:0.75em; color:#555; margin-top:2px;"; + cwSpan.title = "CommessaWeb"; + const hiddenInput = statusCell.querySelector( + 'input[type="hidden"]', + ); + hiddenInput + ? statusCell.insertBefore(cwSpan, hiddenInput) + : statusCell.appendChild(cwSpan); + } + cwSpan.textContent = data.commessaweb; + } + } else { + responseMessage.textContent = `Errore durante la generazione dei payload: ${data.message}`; + document.getElementById( + "exportResponseModalLabel", + ).textContent = "Errore Esportazione"; + responseModal.show(); + } + + responseModalElement.addEventListener( + "hidden.bs.modal", + cleanupBackdrop, + { once: true }, + ); + } catch (error) { + console.error("Export error:", error); + const responseModalElement = + document.getElementById("exportResponseModal"); + if (!responseModalElement) { + alert("Errore: Modale di risposta non trovato"); + return; + } + const responseModal = new bootstrap.Modal( + responseModalElement, + { keyboard: false }, + ); + document.getElementById( + "exportResponseMessage", + ).textContent = `Errore: ${error.message}`; + document.getElementById( + "exportResponseModalLabel", + ).textContent = "Errore Esportazione"; + responseModal.show(); + responseModalElement.addEventListener( + "hidden.bs.modal", + cleanupBackdrop, + { once: true }, + ); + } + }; + + if (pendingConfirmHandler) { + confirmBtn.removeEventListener("click", pendingConfirmHandler); + } + pendingConfirmHandler = confirmHandler; + confirmBtn.addEventListener("click", confirmHandler, { once: true }); + } + + // ── Step 1: check unsaved changes, save if needed, then export ─────────── + exportButtons.forEach((btn) => { btn.addEventListener("click", (e) => { e.preventDefault(); @@ -19,221 +182,40 @@ document.addEventListener("DOMContentLoaded", () => { `Export to LIMS clicked for row ${rowIndex}, iddatadb: ${iddatadb}`, ); - // Mostra il modale di conferma - const confirmModalElement = - document.getElementById("exportConfirmModal"); - if (!confirmModalElement) { - console.error("exportConfirmModal not found in the DOM"); - alert("Errore: Modale di conferma non trovato"); - return; - } + const gridRow = btn.closest(".grid-row"); - const confirmModal = new bootstrap.Modal(confirmModalElement, { - keyboard: false, - }); - document.getElementById("exportIddatadb").textContent = iddatadb; - confirmModal.show(); + if (gridRow && gridRow.querySelector(".cell-changed")) { + const unsavedModal = new bootstrap.Modal( + document.getElementById("exportUnsavedModal"), + { keyboard: false }, + ); + unsavedModal.show(); - // Gestisci il click su "Conferma" - const confirmBtn = document.getElementById("exportConfirmBtn"); - if (!confirmBtn) { - console.error("exportConfirmBtn not found in the DOM"); - confirmModal.hide(); - alert("Errore: Pulsante di conferma non trovato"); - return; - } + document.getElementById("saveAndExportBtn").addEventListener("click", () => { + unsavedModal.hide(); + const saveBtn = gridRow.querySelector(".save-btn"); + if (!saveBtn) return; - const confirmHandler = async () => { - console.log(`Confirmed export for iddatadb: ${iddatadb}`); - confirmModal.hide(); - - const formData = new FormData(); - formData.append("iddatadb", iddatadb); - - try { - const response = await fetch("export_to_lims.php", { - method: "POST", - body: formData, - }); - if (!response.ok) - throw new Error( - `HTTP error! status: ${response.status}`, - ); - const data = await response.json(); - - console.log("Export response:", data); - - // Mostra il modale di risposta - const responseModalElement = document.getElementById( - "exportResponseModal", - ); - if (!responseModalElement) { - console.error( - "exportResponseModal not found in the DOM", - ); - alert("Errore: Modale di risposta non trovato"); - return; - } - - const responseModal = new bootstrap.Modal( - responseModalElement, - { - keyboard: false, - }, - ); - const responseMessage = document.getElementById( - "exportResponseMessage", - ); - if (data.success) { - responseMessage.innerHTML = `${data.message.replace(/\n/g, "
")}
ID CommessaWeb: ${data.idcommessaweb}`; - document.getElementById( - "exportResponseModalLabel", - ).textContent = "Esportazione Completata"; - responseModal.show(); - - // Aggiorna la UI per riflettere lo stato 'To LIMS' - const statusCell = btn - .closest(".grid-row") - .querySelector( - '.grid-cell[data-col="status"] .status-badge', - ); - if (statusCell) { - statusCell.classList.remove("status-i", "status-P"); - statusCell.classList.add("status-l"); - statusCell.textContent = "To LIMS"; + const observer = new MutationObserver(() => { + if (!gridRow.querySelector(".cell-changed")) { + observer.disconnect(); + startExportConfirmFlow(iddatadb, btn); } + }); + observer.observe(gridRow, { + subtree: true, + attributes: true, + attributeFilter: ["class"], + }); - // Gestisci la chiusura del modale di risposta - responseModalElement.addEventListener( - "hidden.bs.modal", - () => { - console.log( - "exportResponseModal closed, cleaning up", - ); - // Rimuovi tutti i backdrop residui - document - .querySelectorAll(".modal-backdrop") - .forEach((backdrop) => { - console.log( - "Removing backdrop:", - backdrop, - ); - backdrop.remove(); - }); - // Ripristina il body - document.body.classList.remove("modal-open"); - document.body.style.paddingRight = ""; - // Nascondi l'overlay - const overlay = document.querySelector( - ".overlay.toggle-icon", - ); - if (overlay) { - overlay.style.display = "none"; - } - }, - { once: true }, - ); - } else { - responseMessage.textContent = `Errore durante la generazione dei payload: ${data.message}`; - document.getElementById( - "exportResponseModalLabel", - ).textContent = "Errore Esportazione"; - responseModal.show(); + saveBtn.click(); + }, { once: true }); - // Gestisci la chiusura del modale di risposta anche in caso di errore - responseModalElement.addEventListener( - "hidden.bs.modal", - () => { - console.log( - "exportResponseModal closed, cleaning up", - ); - // Rimuovi tutti i backdrop residui - document - .querySelectorAll(".modal-backdrop") - .forEach((backdrop) => { - console.log( - "Removing backdrop:", - backdrop, - ); - backdrop.remove(); - }); - // Ripristina il body - document.body.classList.remove("modal-open"); - document.body.style.paddingRight = ""; - // Nascondi l'overlay - const overlay = document.querySelector( - ".overlay.toggle-icon", - ); - if (overlay) { - overlay.style.display = "none"; - } - }, - { once: true }, - ); - } - } catch (error) { - console.error("Export error:", error); - const responseModalElement = document.getElementById( - "exportResponseModal", - ); - if (!responseModalElement) { - console.error( - "exportResponseModal not found in the DOM", - ); - alert("Errore: Modale di risposta non trovato"); - return; - } - const responseModal = new bootstrap.Modal( - responseModalElement, - { - keyboard: false, - }, - ); - document.getElementById( - "exportResponseMessage", - ).textContent = - `Errore durante la generazione dei payload: ${error.message}`; - document.getElementById( - "exportResponseModalLabel", - ).textContent = "Errore Esportazione"; - responseModal.show(); + return; + } - // Gestisci la chiusura del modale di risposta in caso di errore - responseModalElement.addEventListener( - "hidden.bs.modal", - () => { - console.log( - "exportResponseModal closed, cleaning up", - ); - // Rimuovi tutti i backdrop residui - document - .querySelectorAll(".modal-backdrop") - .forEach((backdrop) => { - console.log("Removing backdrop:", backdrop); - backdrop.remove(); - }); - // Ripristina il body - document.body.classList.remove("modal-open"); - document.body.style.paddingRight = ""; - // Nascondi l'overlay - const overlay = document.querySelector( - ".overlay.toggle-icon", - ); - if (overlay) { - overlay.style.display = "none"; - } - }, - { once: true }, - ); - } - - // Rimuovi il listener dopo l'esecuzione - confirmBtn.removeEventListener("click", confirmHandler); - }; - - // Rimuovi eventuali listener precedenti - confirmBtn.removeEventListener("click", confirmHandler); - confirmBtn.addEventListener("click", confirmHandler); + // No unsaved changes — go straight to export confirm + startExportConfirmFlow(iddatadb, btn); }); }); }); diff --git a/public/userarea/export_to_lims.php b/public/userarea/export_to_lims.php index e7f8dfa..2461a1b 100644 --- a/public/userarea/export_to_lims.php +++ b/public/userarea/export_to_lims.php @@ -13,6 +13,8 @@ if (!is_dir($logDir)) { mkdir($logDir, 0755, true); } +$uploadDir = realpath(__DIR__ . '/../photostrf') . '/'; + // 🔹 Base URL API $apiBaseUrl = 'https://93.43.5.102/limsapi/api/odata/'; @@ -34,8 +36,14 @@ try { } // 🔹 STEP 1+2: Fetch Cliente ID from datadb and Schema ID from excel_templates + // Also fetch fixed fields stored in datadb $stmt = $pdo->prepare(" - SELECT d.idclient AS clienteId, et.idschema AS schemaId + SELECT d.idclient AS clienteId, et.idschema AS schemaId, + d.cliente_responsabile_id, + d.moltiplicatore_prezzo_id, + d.anagrafica_certest_object_id, + d.anagrafica_certest_service_id, + d.cliente_fornitore_id FROM datadb as d INNER JOIN excel_templates as et ON d.templateid = et.id WHERE d.iddatadb = :iddatadb @@ -51,6 +59,13 @@ try { $clienteId = (int) $result['clienteId']; $schemaId = (int) $result['schemaId']; + // Extract fixed fields (nullable INTs) + $clienteResponsabile = !empty($result['cliente_responsabile_id']) ? (int) $result['cliente_responsabile_id'] : null; + $moltiplicatorePrezzo = !empty($result['moltiplicatore_prezzo_id']) ? (int) $result['moltiplicatore_prezzo_id'] : null; + $anagraficaObject = !empty($result['anagrafica_certest_object_id']) ? (int) $result['anagrafica_certest_object_id'] : null; + $anagraficaService = !empty($result['anagrafica_certest_service_id'])? (int) $result['anagrafica_certest_service_id']: null; + $clienteFornitore = !empty($result['cliente_fornitore_id'])? (int) $result['cliente_fornitore_id']: null; + // 🔹 STEP 3: Fetch Parts (including idmatrice) $stmt = $pdo->prepare(" SELECT part_number, part_description, material, color, mix, idmatrice @@ -94,11 +109,18 @@ try { $api = VisualLimsApiClient::getInstance(); // 🔹 STEP 5: Create CommessaWeb (NOT WebOrder) + // Includes fixed fields fetched from datadb in STEP 1+2 $commessaWebPayload = [ - "Cliente" => $clienteId, - "SchemaCustomField" => $schemaId, - "Richiedente" => "Test Web Import", - "Descrizione" => "TEST CommessaWeb", + "Cliente" => $clienteId, + "SchemaCustomField" => $schemaId, + "Richiedente" => "Test Web Import", + "Descrizione" => "TEST CommessaWeb", + // Fixed fields from datadb + "ClienteResponsabile" => $clienteResponsabile, + "MoltiplicatorePrezzo" => $moltiplicatorePrezzo, + "AnagraficaCertestObject" => $anagraficaObject, + "AnagraficaCertestService" => $anagraficaService, + "ClienteFornitore" => $clienteFornitore, // PLACEHOLDER — to be implemented ]; // Costruisci log curl-like per STEP 5 @@ -119,6 +141,49 @@ try { $commessaId = $commessaWeb["IdCommessa"]; $commessaWebCode = substr($commessaWeb["CodiceCommessa"] ?? "TEST CommessaWeb", 0, 30); // Limite a 30 caratteri + // 🔹 STEP 5.1: Fetch photos linked to this iddatadb + $stmtPhotos = $pdo->prepare(" + SELECT id, file_path, file_name + FROM datadb_photos + WHERE iddatadb = :iddatadb + ORDER BY id ASC + "); + $stmtPhotos->execute(['iddatadb' => $iddatadb]); + $photos = $stmtPhotos->fetchAll(PDO::FETCH_ASSOC); + + // 🔹 STEP 5.2: Upload photos to CommessaWeb + // NOTE: The sample number corresponds to the CommessaWeb ID ($commessaId). + // Photos may be multiple or may not exist at all. + + $photosUploaded = 0; + $logContentPhotos = "Photos for CommessaWeb {$commessaId} (iddatadb={$iddatadb}):\n"; + $logContentPhotos .= "Total photos found: " . count($photos) . "\n\n"; + + foreach ($photos as $photo) { + // Build absolute path from the relative path stored in DB + $photoPath = $uploadDir . '/' . ltrim($photo['file_path'], './'); + $fullPath = realpath($photoPath); + + if (!$fullPath || !file_exists($fullPath)) { + $logContentPhotos .= "SKIP (file not found): full - $photoPath " . $photo['file_path'] . "\n"; + continue; + } + + // Construct curl-like log entry + $logContentPhotos .= "curl --location --request POST '{$apiBaseUrl}AllegatoCommessaWeb' \\\n" . + "--header 'Authorization: Bearer ••••••' \\\n" . + "--form 'IdCommessa={$commessaId}' \\\n" . + "--form 'file=@{$fullPath}'\n\n"; + + // ENDPOINT NAME TO BE CONFIRMED + $photoResult = $api->postMultipart("AllegatoCommessaWeb", $fullPath, $photo['file_name'], $commessaId); + $logContentPhotos .= "RESPONSE:\n" . json_encode($photoResult, JSON_PRETTY_PRINT) . "\n\n---\n"; + $photosUploaded++; + } + + $logFilePhotos = $logDir . "commessa_{$commessaId}_photos_step5_2_" . time() . ".txt"; + file_put_contents($logFilePhotos, $logContentPhotos); + // 🔹 STEP 6: Create Campioni (Samples) for each part $campioni = []; $logContentStep6 = ""; @@ -239,6 +304,20 @@ try { file_put_contents($logFileStep9, $logContentStep9); */ + // 🔹 STEP 9.5: Importazione da CommessaWeb a Commessa (commentato come richiesto) + // Supplier call: POST api/odata/CommessaWeb(XXX)/ImportaCommessa + + $importResult = $api->post("CommessaWeb({$commessaId})/ImportaCommessa", []); + + // Logga il POST + $logContentStep91 = "curl --location --request POST '{$apiBaseUrl}CommessaWeb({$commessaId})/ImportaCommessa' \\\n" . + "--header 'Content-Type: application/json' \\\n" . + "--header 'Authorization: Bearer ••••••' \\\n" . + "--data '{}'\n\n" . + "RESPONSE:\n" . json_encode($importResult, JSON_PRETTY_PRINT); + $logFileStep91 = $logDir . "commessa_{$commessaId}_importa_step91_" . time() . ".txt"; + file_put_contents($logFileStep91, $logContentStep91); + // 🔹 STEP 10: GET di controllo post-PATCH $expand = "CommesseCustomFields(\$expand=CustomField)"; $commessaAfterPatch = $api->get("CommessaWeb(" . $commessaId . ")?\$expand=" . $expand); @@ -262,17 +341,22 @@ try { ]; echo json_encode([ - "success" => true, - "commessaWeb" => $finalCommessa, + "success" => true, + "idcommessaweb" => $commessaId, + "commessaweb" => $commessaWebCode, + "commessaWeb" => $finalCommessa, "commessaWebApiResponse" => $commessaWeb, // Incluso per debug - "totalCampioni" => count($campioni), - "totalCustomFields" => count($commessaAfterPatch["CommesseCustomFields"] ?? []), - "message" => "Export successful", - "logFiles" => [ - "step5_create" => $logFileStep5, - "step6_campioni" => $logFileStep6, - "step7_patch" => $logFileStep7, - "step10_get" => $logFileStep10 + "totalCampioni" => count($campioni), + "totalCustomFields" => count($commessaAfterPatch["CommesseCustomFields"] ?? []), + "totalPhotos" => count($photos), + "message" => "Export successful", + "logFiles" => [ + "step5_create" => $logFileStep5, + "step5_2_photos" => $logFilePhotos, + "step6_campioni" => $logFileStep6, + "step7_patch" => $logFileStep7 ?? null, + "step9_1_importa" => $logFileStep91, + "step10_get" => $logFileStep10 ] ]); } catch (Exception $e) { @@ -282,10 +366,12 @@ try { "success" => false, "message" => "Export failed: " . $e->getMessage(), "logFiles" => [ - "step5_create" => $logFileStep5 ?? null, - "step6_campioni" => $logFileStep6 ?? null, - "step7_patch" => $logFileStep7 ?? null, - "step10_get" => $logFileStep10 ?? null + "step5_create" => $logFileStep5 ?? null, + "step5_2_photos" => $logFilePhotos ?? null, + "step6_campioni" => $logFileStep6 ?? null, + "step7_patch" => $logFileStep7 ?? null, + "step9_1_importa" => $logFileStep91 ?? null, + "step10_get" => $logFileStep10 ?? null ] ]); } diff --git a/public/userarea/get_clienti.php b/public/userarea/get_clienti.php index 086c5cd..0e6dd83 100644 --- a/public/userarea/get_clienti.php +++ b/public/userarea/get_clienti.php @@ -9,7 +9,24 @@ ini_set('display_errors', '0'); error_reporting(E_ALL); try { - $api = VisualLimsApiClient::getInstance(); + $api = VisualLimsApiClient::getInstance(); // also loads dotenv + + // In simulate mode: return fake clients built from idclient values already in datadb. + // This ensures client dropdowns auto-select the correct row idclient, which in turn + // triggers the ClienteResponsabile select to populate via the mock. + if (($_ENV['SIMULATE_EXPORT_LIMS'] ?? '') === 'true') { + require_once __DIR__ . '/class/db-functions.php'; + $pdo = DBHandlerSelect::getInstance()->getConnection(); + $stmt = $pdo->query("SELECT DISTINCT idclient FROM datadb WHERE idclient IS NOT NULL AND idclient > 0 ORDER BY idclient ASC"); + $ids = $stmt->fetchAll(PDO::FETCH_COLUMN); + $fakeClients = array_map(fn($id) => [ + 'IdCliente' => (int) $id, + 'Nominativo' => "Cliente Simulato {$id}", + 'CodiceCliente' => "SIM_{$id}", + ], $ids); + echo json_encode(['value' => $fakeClients]); + exit; + } // Parametri OData $params = [ diff --git a/public/userarea/get_fixed_field_data.php b/public/userarea/get_fixed_field_data.php index a9fbd28..e5035b5 100644 --- a/public/userarea/get_fixed_field_data.php +++ b/public/userarea/get_fixed_field_data.php @@ -16,7 +16,8 @@ if (!$field) { exit; } -$api = VisualLimsApiClient::getInstance(); +$api = VisualLimsApiClient::getInstance(); // also loads dotenv as a side-effect +$simulate = ($_ENV['SIMULATE_EXPORT_LIMS'] ?? '') === 'true'; $base_url = 'https://93.43.5.102/limsapi/api/odata/'; $data = null; @@ -44,9 +45,12 @@ try { case 'ClienteResponsabile': $id_cliente = (int)($_GET['id_cliente'] ?? 0); if ($id_cliente <= 0) { - http_response_code(400); - echo json_encode(['error' => 'Manca o invalido id_cliente']); - exit; + if (!$simulate) { + http_response_code(400); + echo json_encode(['error' => 'Manca o invalido id_cliente']); + exit; + } + $id_cliente = 1; // dummy — mock ignores the actual ID } $endpoint = "Cliente($id_cliente)?\$expand=Responsabili"; $cache_file = __DIR__ . '/cache/cliente_responsabili_' . $id_cliente . '.json'; @@ -62,13 +66,13 @@ try { exit; } - // Opzionale: caching semplice (molto utile se i dati cambiano poco) - if ($cache_file && file_exists($cache_file) && (time() - filemtime($cache_file) < 3600)) { // 1 ora + // Caching skipped in simulate mode to avoid polluting real-data cache files + if (!$simulate && $cache_file && file_exists($cache_file) && (time() - filemtime($cache_file) < 3600)) { // 1 ora $data = json_decode(file_get_contents($cache_file), true); } else { $data = $api->get($endpoint, $options); - if ($cache_file) { + if (!$simulate && $cache_file) { file_put_contents($cache_file, json_encode($data, JSON_PRETTY_PRINT)); } } diff --git a/public/userarea/import_edit2.php b/public/userarea/import_edit2.php index 220077b..65585b5 100644 --- a/public/userarea/import_edit2.php +++ b/public/userarea/import_edit2.php @@ -192,6 +192,16 @@ foreach ($tempMap as $f) { $fixedFields[] = $f; } +// Maps logical fixed_field_key → real datadb column name (mirrors JS fixedAliasMap) +$fixedAliasMap = [ + 'ClienteResponsabile' => 'cliente_responsabile_id', + 'MoltiplicatorePrezzo' => 'moltiplicatore_prezzo_id', + 'AnagraficaCertestObject' => 'anagrafica_certest_object_id', + 'AnagraficaCertestService' => 'anagrafica_certest_service_id', + 'ClienteFornitore' => 'cliente_fornitore_id', + 'ConsegnaRichiesta' => 'consegna_richiesta', +]; + // helper default (DATE can use 'today' if you already use it elsewhere) function fixedDefaultValue(array $f): string { @@ -638,12 +648,14 @@ function fixedDefaultValue(array $f): string } #exportConfirmModal, - #exportResponseModal { + #exportResponseModal, + #exportUnsavedModal { z-index: 1300 !important; } #exportConfirmModal .modal-backdrop, - #exportResponseModal .modal-backdrop { + #exportResponseModal .modal-backdrop, + #exportUnsavedModal .modal-backdrop { z-index: 1299 !important; } @@ -1055,6 +1067,9 @@ function fixedDefaultValue(array $f): string + + + { el.addEventListener("change", () => { + if (el.hasAttribute('data-restoring')) return; hasChanges = true; const gridCell = el.closest(".grid-cell"); @@ -1443,7 +1434,8 @@ function fixedDefaultValue(array $f): string hasChanges = changedRows.size > 0; - alert('Salvataggio riga avvenuto con successo!'); + const toastEl = document.getElementById('saveSuccessToast'); + if (toastEl) bootstrap.Toast.getOrCreateInstance(toastEl).show(); } else { alert('Errore durante il salvataggio: ' + data.message); } @@ -1561,7 +1553,7 @@ function fixedDefaultValue(array $f): string }); // Gestisci la chiusura dei modali per rimuovere i backdrop - document.querySelectorAll('#exportConfirmModal, #exportResponseModal').forEach(modal => { + document.querySelectorAll('#exportConfirmModal, #exportResponseModal, #exportUnsavedModal').forEach(modal => { modal.addEventListener('hidden.bs.modal', () => { // Rimuovi tutti i backdrop residui document.querySelectorAll('.modal-backdrop').forEach(backdrop => { @@ -1657,10 +1649,12 @@ function fixedDefaultValue(array $f): string // Ripristina il valore corrente if (currentValue) { dropdown.value = currentValue; + dropdown.setAttribute('data-restoring', ''); const event = new Event('change', { bubbles: true }); dropdown.dispatchEvent(event); + dropdown.removeAttribute('data-restoring'); } }); } @@ -1969,7 +1963,9 @@ function fixedDefaultValue(array $f): string s2container.after(dropdown); } if (valToSelect) { + dropdown.setAttribute('data-restoring', ''); $(dropdown).val(valToSelect).trigger('change'); + dropdown.removeAttribute('data-restoring'); } } else { // Select nativa @@ -1988,9 +1984,11 @@ function fixedDefaultValue(array $f): string }); if (valToSelect) { dropdown.value = valToSelect; + dropdown.setAttribute('data-restoring', ''); dropdown.dispatchEvent(new Event('change', { bubbles: true })); + dropdown.removeAttribute('data-restoring'); } } }); @@ -2083,10 +2081,12 @@ function fixedDefaultValue(array $f): string // Ripristina il valore corrente if (currentValue) { dropdown.value = currentValue; + dropdown.setAttribute('data-restoring', ''); const event = new Event('change', { bubbles: true }); dropdown.dispatchEvent(event); + dropdown.removeAttribute('data-restoring'); } }); } @@ -2528,7 +2528,9 @@ function fixedDefaultValue(array $f): string if (currentVal) { const found = results.find(r => String(r.id) === String(currentVal)); if (found) { + $select[0].setAttribute('data-restoring', ''); $select.val(currentVal).trigger('change'); + $select[0].removeAttribute('data-restoring'); } } } @@ -2715,6 +2717,35 @@ function fixedDefaultValue(array $f): string + + + + +
+ +
+ - \ No newline at end of file +