Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 53c223ea5f | |||
| 9775a12d4a | |||
| 7dfb935e33 | |||
| abb4200215 | |||
| 0be7109df4 | |||
| 578671e013 | |||
| d24836e2b1 | |||
| d983659000 | |||
| 7c5aa7734f | |||
| 4f0dbc7e91 | |||
| fb09f033ae | |||
| 8bae2d7008 | |||
| 7463fc6726 | |||
| 0bc2ff7e9d | |||
| aa1c32b7ed | |||
| c573a46318 | |||
| bf18a904bd | |||
| b3ce489348 | |||
| 08b89e01cc | |||
| 3e66d67dc5 | |||
| 490786731a | |||
| e96f538a47 | |||
| cc96ecb67f | |||
| 4cf03ae742 | |||
| 7a486e9dcf | |||
| 2a7b1fae17 | |||
| a05d9fed2b | |||
| 65170a0a7c | |||
| 223688c372 | |||
| b562eb4033 | |||
| 0645a0c675 | |||
| a02a6b2c4c | |||
| 45dd8d6907 | |||
| 755f6812d4 | |||
| 5e59ae2162 | |||
| 71a19144c8 | |||
| 381a05341b | |||
| 5a58decd40 | |||
| eb21910ef3 | |||
| 5dedc779df | |||
| f4e0074a73 | |||
| 9c850e4ea6 | |||
| f300811341 | |||
| 48387a9945 | |||
| 0e90db8219 | |||
| eaf70d5a46 | |||
| cdd6551e9c | |||
| 1b97bf4362 | |||
| c516589483 | |||
| 817bbadf22 | |||
| 1f27bc48d4 | |||
| c9fba48d88 | |||
| 70d4c0759e | |||
| 5d7880160a | |||
| bbe74d1529 | |||
| 93930227a2 | |||
| 2598a4c91b | |||
| 5e677a8b9c | |||
| 540c44d89a | |||
| 2ee9f2ecb1 | |||
| c9122774b1 | |||
| 1fed113c5c |
+3
-1
@@ -33,11 +33,13 @@ yarn-error.log
|
|||||||
/public/userarea/*.log
|
/public/userarea/*.log
|
||||||
/public/userarea/*.txt
|
/public/userarea/*.txt
|
||||||
/public/userarea/*_response.json
|
/public/userarea/*_response.json
|
||||||
|
/public/userarea/customfield_values_response.json
|
||||||
/public/userarea/error_log.txt
|
/public/userarea/error_log.txt
|
||||||
/public/userarea/import_debug.log
|
/public/userarea/import_debug.log
|
||||||
/public/userarea/last_url.txt
|
/public/userarea/last_url.txt
|
||||||
/public/userarea/logaspi/
|
/public/userarea/logaspi/
|
||||||
/public/userarea/logsapi/
|
/public/userarea/logs/api/
|
||||||
|
/public/userarea/logs/api/**
|
||||||
/public/userarea/photostrf/
|
/public/userarea/photostrf/
|
||||||
/public/userarea/class/*.log
|
/public/userarea/class/*.log
|
||||||
/public/userarea/class/curl_auth_debug.log
|
/public/userarea/class/curl_auth_debug.log
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
|||||||
|
# DB LOCALE (Windows 11)
|
||||||
|
url=jdbc:mysql://localhost:3306/trfcertest
|
||||||
|
username=solocla
|
||||||
|
password=!Massarosa2
|
||||||
|
driver=com.mysql.cj.jdbc.Driver
|
||||||
|
changeLogFile=liquibase/changelog/db.changelog-master.yaml
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
include('include/headscript.php');
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$templateId = intval($input['template_id'] ?? 0);
|
||||||
|
$importRefFromClient = trim($input['importreferencecode'] ?? '');
|
||||||
|
|
||||||
|
if (!$templateId) {
|
||||||
|
throw new Exception('Template ID missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
$userId = $iduserlogin ?? 1;
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
// Get default idclient from template
|
||||||
|
$stmt = $pdo->prepare("SELECT idclient FROM excel_templates WHERE id = ?");
|
||||||
|
$stmt->execute([$templateId]);
|
||||||
|
$template = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
$idclient = $template['idclient'] ?? null;
|
||||||
|
|
||||||
|
// Use provided importreferencecode (from filtered page) or generate new
|
||||||
|
$importReferenceCode = $importRefFromClient !== '' ? $importRefFromClient : date('YmdHis') . '-' . uniqid();
|
||||||
|
|
||||||
|
// Insert empty record
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO datadb (templateid, user_id, status, idclient, importreferencecode, importdate)
|
||||||
|
VALUES (?, ?, 'i', ?, ?, NOW())
|
||||||
|
");
|
||||||
|
$stmt->execute([$templateId, $userId, $idclient, $importReferenceCode]);
|
||||||
|
$iddatadb = (int)$pdo->lastInsertId();
|
||||||
|
|
||||||
|
// Create import_data_details for all mappings (with auto_value support)
|
||||||
|
$mappingStmt = $pdo->prepare("SELECT id, auto_value, manual_default, data_type FROM template_mapping WHERE template_id = ?");
|
||||||
|
$mappingStmt->execute([$templateId]);
|
||||||
|
$mappings = $mappingStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!empty($mappings)) {
|
||||||
|
$insertStmt = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, ?)");
|
||||||
|
foreach ($mappings as $m) {
|
||||||
|
$val = '';
|
||||||
|
$auto = $m['auto_value'] ?? 'none';
|
||||||
|
if ($auto === 'import_date') {
|
||||||
|
$val = date('Y-m-d');
|
||||||
|
} elseif ($auto === 'import_time') {
|
||||||
|
$val = date('H:i');
|
||||||
|
} elseif ($m['data_type'] === 'DATE' && ($m['manual_default'] ?? '') === 'today') {
|
||||||
|
$val = date('Y-m-d');
|
||||||
|
} elseif (!empty($m['manual_default'])) {
|
||||||
|
$val = $m['manual_default'];
|
||||||
|
}
|
||||||
|
$insertStmt->execute([$iddatadb, $m['id'], $val]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user name
|
||||||
|
$userStmt = $pdo->prepare("SELECT CONCAT(first_name, ' ', last_name) AS user_name FROM auth_users WHERE id = ?");
|
||||||
|
$userStmt->execute([$userId]);
|
||||||
|
$userName = $userStmt->fetchColumn() ?: '';
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'iddatadb' => $iddatadb,
|
||||||
|
'importreferencecode' => $importReferenceCode,
|
||||||
|
'user_name' => $userName,
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
let analysisMatriciMap = {};
|
||||||
|
|
||||||
|
function loadAnalysisMatrixNames() {
|
||||||
|
return $.ajax({
|
||||||
|
url: "get_matrici_db.php",
|
||||||
|
method: "GET",
|
||||||
|
dataType: "json",
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
analysisMatriciMap = {};
|
||||||
|
|
||||||
|
(data.value || []).forEach(function (matrice) {
|
||||||
|
analysisMatriciMap[String(matrice.IdMatrice)] =
|
||||||
|
matrice.NomeMatrice || "#" + matrice.IdMatrice;
|
||||||
|
});
|
||||||
|
|
||||||
|
applyAnalysisMatrixNames();
|
||||||
|
})
|
||||||
|
.fail(function () {
|
||||||
|
analysisMatriciMap = {};
|
||||||
|
applyAnalysisMatrixNames();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyAnalysisMatrixNames() {
|
||||||
|
const modal = document.getElementById("analysisModal");
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
modal.querySelectorAll(".analysis-matrix-item").forEach((item) => {
|
||||||
|
const matrixId = item.getAttribute("data-matrix-id");
|
||||||
|
const nameEl = item.querySelector(".analysis-matrix-name");
|
||||||
|
|
||||||
|
let matrixName = "No Matrix";
|
||||||
|
if (matrixId && matrixId !== "NO_MATRIX") {
|
||||||
|
matrixName =
|
||||||
|
analysisMatriciMap[String(matrixId)] || "#" + matrixId;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.setAttribute("data-matrix-name", matrixName);
|
||||||
|
|
||||||
|
if (nameEl) {
|
||||||
|
nameEl.textContent = matrixName;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
modal.querySelectorAll(".analysis-part-matrix-name").forEach((el) => {
|
||||||
|
const matrixId = el.getAttribute("data-matrix-id");
|
||||||
|
|
||||||
|
if (!matrixId || matrixId === "NO_MATRIX") {
|
||||||
|
el.textContent = "No Matrix";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
el.textContent =
|
||||||
|
analysisMatriciMap[String(matrixId)] || "#" + matrixId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSelectedPartsInfo() {
|
||||||
|
const modal = document.getElementById("analysisModal");
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
const checked = modal.querySelectorAll(
|
||||||
|
".analysis-part-checkbox:checked",
|
||||||
|
);
|
||||||
|
const ids = Array.from(checked).map((el) => el.value);
|
||||||
|
|
||||||
|
const countEl = modal.querySelector("#analysisSelectedPartsCount");
|
||||||
|
const idsEl = modal.querySelector("#analysisSelectedPartsIds");
|
||||||
|
|
||||||
|
if (countEl) {
|
||||||
|
countEl.textContent = `${ids.length} selected`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idsEl) {
|
||||||
|
idsEl.textContent = ids.length ? ids.join(", ") : "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
modal.querySelectorAll(".analysis-part-item").forEach((item) => {
|
||||||
|
const checkbox = item.querySelector(".analysis-part-checkbox");
|
||||||
|
if (checkbox && checkbox.checked) {
|
||||||
|
item.classList.add("part-checked");
|
||||||
|
} else {
|
||||||
|
item.classList.remove("part-checked");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectPartsByMatrix(matrixId, matrixLabel) {
|
||||||
|
const modal = document.getElementById("analysisModal");
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
const partItems = modal.querySelectorAll(".analysis-part-item");
|
||||||
|
const matrixLabelEl = modal.querySelector("#analysisCurrentMatrix");
|
||||||
|
|
||||||
|
partItems.forEach((item) => {
|
||||||
|
const itemMatrixId = item.getAttribute("data-matrix-id");
|
||||||
|
const checkbox = item.querySelector(".analysis-part-checkbox");
|
||||||
|
|
||||||
|
item.classList.remove("matrix-active");
|
||||||
|
|
||||||
|
if (itemMatrixId === String(matrixId)) {
|
||||||
|
item.classList.add("matrix-active");
|
||||||
|
if (checkbox) checkbox.checked = true;
|
||||||
|
} else {
|
||||||
|
if (checkbox) checkbox.checked = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (matrixLabelEl) {
|
||||||
|
matrixLabelEl.textContent = matrixLabel || "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSelectedPartsInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAnalysisSelection() {
|
||||||
|
const modal = document.getElementById("analysisModal");
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
modal.querySelectorAll(".analysis-matrix-item").forEach((item) => {
|
||||||
|
item.classList.remove("active");
|
||||||
|
});
|
||||||
|
|
||||||
|
modal.querySelectorAll(".analysis-part-item").forEach((item) => {
|
||||||
|
item.classList.remove("matrix-active", "part-checked");
|
||||||
|
});
|
||||||
|
|
||||||
|
modal.querySelectorAll(".analysis-part-checkbox").forEach((cb) => {
|
||||||
|
cb.checked = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const matrixLabelEl = modal.querySelector("#analysisCurrentMatrix");
|
||||||
|
if (matrixLabelEl) matrixLabelEl.textContent = "-";
|
||||||
|
|
||||||
|
updateSelectedPartsInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
function initAnalysisModal() {
|
||||||
|
const modal = document.getElementById("analysisModal");
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
modal.querySelectorAll(".analysis-matrix-item").forEach((btn) => {
|
||||||
|
btn.addEventListener("click", function () {
|
||||||
|
modal
|
||||||
|
.querySelectorAll(".analysis-matrix-item")
|
||||||
|
.forEach((x) => x.classList.remove("active"));
|
||||||
|
this.classList.add("active");
|
||||||
|
|
||||||
|
const matrixId = this.getAttribute("data-matrix-id");
|
||||||
|
const matrixLabel =
|
||||||
|
this.getAttribute("data-matrix-name") ||
|
||||||
|
this.textContent.trim();
|
||||||
|
|
||||||
|
selectPartsByMatrix(matrixId, matrixLabel);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modal.querySelectorAll(".analysis-part-checkbox").forEach((cb) => {
|
||||||
|
cb.addEventListener("change", function () {
|
||||||
|
updateSelectedPartsInfo();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const clearBtn = modal.querySelector("#analysisClearSelectionBtn");
|
||||||
|
if (clearBtn) {
|
||||||
|
clearBtn.addEventListener("click", function () {
|
||||||
|
clearAnalysisSelection();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstActiveMatrix = modal.querySelector(
|
||||||
|
".analysis-matrix-item.active",
|
||||||
|
);
|
||||||
|
if (firstActiveMatrix) {
|
||||||
|
const matrixId = firstActiveMatrix.getAttribute("data-matrix-id");
|
||||||
|
const matrixLabel =
|
||||||
|
firstActiveMatrix.getAttribute("data-matrix-name") ||
|
||||||
|
firstActiveMatrix.textContent.trim();
|
||||||
|
|
||||||
|
selectPartsByMatrix(matrixId, matrixLabel);
|
||||||
|
} else {
|
||||||
|
updateSelectedPartsInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OPEN ANALYSIS MODAL FROM PARTS MODAL BUTTON
|
||||||
|
$(document).on("click", ".open-analysis-modal-btn", function () {
|
||||||
|
const partsModal = document.getElementById("partsModal");
|
||||||
|
const iddatadb = $("#partsModal").data("iddatadb");
|
||||||
|
|
||||||
|
if (!iddatadb) {
|
||||||
|
console.error("iddatadb not found on #partsModal");
|
||||||
|
alert("iddatadb not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "modal_analysis.php",
|
||||||
|
method: "GET",
|
||||||
|
data: { iddatadb: iddatadb },
|
||||||
|
success: function (response) {
|
||||||
|
$("#analysisModalContainer").html(response);
|
||||||
|
|
||||||
|
const modalElement = document.getElementById("analysisModal");
|
||||||
|
if (!modalElement) {
|
||||||
|
console.error("Analysis modal not found: #analysisModal");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let modal = bootstrap.Modal.getInstance(modalElement);
|
||||||
|
if (!modal) {
|
||||||
|
modal = new bootstrap.Modal(modalElement, {
|
||||||
|
backdrop: true,
|
||||||
|
keyboard: true,
|
||||||
|
focus: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAnalysisMatrixNames().always(function () {
|
||||||
|
initAnalysisModal();
|
||||||
|
modal.show();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function (xhr, status, error) {
|
||||||
|
console.error("Error loading analysis modal:", error);
|
||||||
|
alert("Error loading analysis modal: " + error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// CLEANUP ON CLOSE
|
||||||
|
$(document).on("hidden.bs.modal", "#analysisModal", function () {
|
||||||
|
const modalElement = document.getElementById("analysisModal");
|
||||||
|
if (modalElement) {
|
||||||
|
const modal = bootstrap.Modal.getInstance(modalElement);
|
||||||
|
if (modal) modal.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#analysisModalContainer").empty();
|
||||||
|
|
||||||
|
// keep parts modal alive, but remove extra backdrop leftovers
|
||||||
|
$(".modal-backdrop").last().remove();
|
||||||
|
|
||||||
|
if ($("#partsModal").hasClass("show")) {
|
||||||
|
$("body").addClass("modal-open");
|
||||||
|
} else {
|
||||||
|
$("body").removeClass("modal-open").css("padding-right", "");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
@@ -24,8 +24,18 @@ class VisualLimsApiClient
|
|||||||
public static function getInstance()
|
public static function getInstance()
|
||||||
{
|
{
|
||||||
if (self::$instance === null) {
|
if (self::$instance === null) {
|
||||||
|
$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();
|
self::$instance = new VisualLimsApiClient();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return self::$instance;
|
return self::$instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,6 +203,54 @@ class VisualLimsApiClient
|
|||||||
return json_decode($response, true);
|
return json_decode($response, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST a file as multipart/form-data (used for photo/attachment uploads).
|
||||||
|
*
|
||||||
|
* @param string $endpoint OData endpoint, e.g. "Campione(613388)/UploadCampioneFile"
|
||||||
|
* @param string $filePath Absolute path to the file on disk
|
||||||
|
* @param string $fileName Original file name to send
|
||||||
|
* @param array $extraFields Additional form fields to include
|
||||||
|
* @return array|null Decoded JSON response
|
||||||
|
*/
|
||||||
|
public function postMultipart($endpoint, $filePath, $fileName, array $extraFields = [])
|
||||||
|
{
|
||||||
|
$token = $this->getToken();
|
||||||
|
$url = "{$this->baseUrl}/api/odata/{$endpoint}";
|
||||||
|
|
||||||
|
$cfile = new CURLFile($filePath, mime_content_type($filePath) ?: 'application/octet-stream', $fileName);
|
||||||
|
|
||||||
|
$payload = array_merge($extraFields, [
|
||||||
|
'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()
|
public function getBaseUrl()
|
||||||
{
|
{
|
||||||
return $this->baseUrl;
|
return $this->baseUrl;
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock implementation of VisualLimsApiClient.
|
||||||
|
* Activated when SIMULATE_EXPORT_LIMS=true in .env.
|
||||||
|
* All HTTP calls are skipped; fake but structurally valid data is returned.
|
||||||
|
* Every simulated call is logged via error_log() with a [SIMULATE] prefix.
|
||||||
|
*/
|
||||||
|
class VisualLimsApiClientMock
|
||||||
|
{
|
||||||
|
private int $fakeCommessaId;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
// Stable fake ID for the lifetime of a single request
|
||||||
|
$this->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, array $extraFields = []): array
|
||||||
|
{
|
||||||
|
error_log("[SIMULATE] POST multipart {$endpoint} file={$fileName}");
|
||||||
|
|
||||||
|
return ['simulated' => true, 'file' => $fileName];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,8 @@ if ((int)$stmt->fetchColumn() > 0) {
|
|||||||
$fixedFields = [
|
$fixedFields = [
|
||||||
// fixed_field_key, data_type
|
// fixed_field_key, data_type
|
||||||
['ClienteResponsabile', 'INT'],
|
['ClienteResponsabile', 'INT'],
|
||||||
|
['ClienteFornitore', 'INT'],
|
||||||
|
['ClienteAnalisi', 'INT'],
|
||||||
['MoltiplicatorePrezzo', 'INT'],
|
['MoltiplicatorePrezzo', 'INT'],
|
||||||
['AnagraficaCertestObject', 'INT'],
|
['AnagraficaCertestObject', 'INT'],
|
||||||
['AnagraficaCertestService', 'INT'],
|
['AnagraficaCertestService', 'INT'],
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,290 @@
|
|||||||
|
<?php
|
||||||
|
require_once "class/VisualLimsApiClient.class.php";
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
// Force HTML response
|
||||||
|
header('Content-Type: text/html; charset=UTF-8');
|
||||||
|
|
||||||
|
// Initialize variables
|
||||||
|
$error = null;
|
||||||
|
$result = null;
|
||||||
|
$entityType = $_GET['entity_type'] ?? 'CommessaWeb';
|
||||||
|
$entityId = isset($_GET['entity_id']) ? (int)$_GET['entity_id'] : 0;
|
||||||
|
$customExpand = trim($_GET['expand'] ?? '');
|
||||||
|
$rawEndpoint = null;
|
||||||
|
|
||||||
|
function h($value)
|
||||||
|
{
|
||||||
|
return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderArrayAsTable(array $items)
|
||||||
|
{
|
||||||
|
if (empty($items)) {
|
||||||
|
echo '<div class="alert alert-secondary">No records found</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$firstRow = reset($items);
|
||||||
|
if (!is_array($firstRow)) {
|
||||||
|
echo '<pre class="bg-light p-3 border rounded">' . h(json_encode($items, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)) . '</pre>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$headers = [];
|
||||||
|
foreach ($items as $row) {
|
||||||
|
if (is_array($row)) {
|
||||||
|
$headers = array_unique(array_merge($headers, array_keys($row)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '<div class="table-responsive">';
|
||||||
|
echo '<table class="table table-striped table-bordered table-sm align-middle">';
|
||||||
|
echo '<thead class="table-dark"><tr>';
|
||||||
|
foreach ($headers as $header) {
|
||||||
|
echo '<th>' . h($header) . '</th>';
|
||||||
|
}
|
||||||
|
echo '</tr></thead><tbody>';
|
||||||
|
|
||||||
|
foreach ($items as $row) {
|
||||||
|
echo '<tr>';
|
||||||
|
foreach ($headers as $header) {
|
||||||
|
$value = $row[$header] ?? '';
|
||||||
|
if (is_array($value) || is_object($value)) {
|
||||||
|
$value = json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
|
echo '<td><pre style="white-space:pre-wrap; margin:0;">' . h($value) . '</pre></td>';
|
||||||
|
} else {
|
||||||
|
echo '<td>' . h($value) . '</td>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo '</tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '</tbody></table>';
|
||||||
|
echo '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($entityId > 0) {
|
||||||
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
|
||||||
|
// Default expands for better debugging
|
||||||
|
if ($customExpand !== '') {
|
||||||
|
$expand = $customExpand;
|
||||||
|
} else {
|
||||||
|
if ($entityType === 'CommessaWeb') {
|
||||||
|
$expand = implode(',', [
|
||||||
|
'CommesseCustomFields($expand=CustomField)',
|
||||||
|
'Campioni'
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$expand = implode(',', [
|
||||||
|
'CommesseCustomFields($expand=CustomField)',
|
||||||
|
'Campioni'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$rawEndpoint = $entityType . '(' . $entityId . ')?$expand=' . $expand;
|
||||||
|
$result = $api->get($rawEndpoint);
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$error = $e->getMessage();
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Debug Commessa API</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: #f5f7fb;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.08);
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-box {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #eef3ff;
|
||||||
|
color: #2446a8;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-right: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-box {
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid #e4e7eb;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-box strong {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container py-4">
|
||||||
|
<div class="debug-card">
|
||||||
|
<h2 class="mb-3">Commessa / CommessaWeb API Inspector</h2>
|
||||||
|
|
||||||
|
<form method="GET" class="row g-3">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label">Entity Type</label>
|
||||||
|
<select name="entity_type" class="form-select">
|
||||||
|
<option value="CommessaWeb" <?= $entityType === 'CommessaWeb' ? 'selected' : '' ?>>CommessaWeb</option>
|
||||||
|
<option value="Commessa" <?= $entityType === 'Commessa' ? 'selected' : '' ?>>Commessa</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-label">Entity ID</label>
|
||||||
|
<input type="number" name="entity_id" class="form-control" value="<?= h($entityId) ?>" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-5">
|
||||||
|
<label class="form-label">Expand</label>
|
||||||
|
<input type="text" name="expand" class="form-control" value="<?= h($customExpand) ?>" placeholder="CommesseCustomFields($expand=CustomField),Campioni">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-2 d-flex align-items-end">
|
||||||
|
<button type="submit" class="btn btn-primary w-100">Load Data</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($rawEndpoint): ?>
|
||||||
|
<div class="debug-card">
|
||||||
|
<div class="section-title">Requested Endpoint</div>
|
||||||
|
<pre><?= h($rawEndpoint) ?></pre>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div class="debug-card">
|
||||||
|
<div class="alert alert-danger mb-0">
|
||||||
|
<strong>API Error:</strong><br>
|
||||||
|
<?= h($error) ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($result && is_array($result)): ?>
|
||||||
|
<div class="debug-card">
|
||||||
|
<div class="section-title">Main Information</div>
|
||||||
|
<div class="mini-grid">
|
||||||
|
<div class="mini-box">
|
||||||
|
<strong>ID</strong>
|
||||||
|
<?= h($result['IdCommessa'] ?? $result['IdCommessaWeb'] ?? '') ?>
|
||||||
|
</div>
|
||||||
|
<div class="mini-box">
|
||||||
|
<strong>Code</strong>
|
||||||
|
<?= h($result['CodiceCommessa'] ?? '') ?>
|
||||||
|
</div>
|
||||||
|
<div class="mini-box">
|
||||||
|
<strong>Cliente</strong>
|
||||||
|
<?= h($result['Cliente'] ?? '') ?>
|
||||||
|
</div>
|
||||||
|
<div class="mini-box">
|
||||||
|
<strong>SchemaCustomField</strong>
|
||||||
|
<?= h($result['SchemaCustomField'] ?? '') ?>
|
||||||
|
</div>
|
||||||
|
<div class="mini-box">
|
||||||
|
<strong>Richiedente</strong>
|
||||||
|
<?= h($result['Richiedente'] ?? '') ?>
|
||||||
|
</div>
|
||||||
|
<div class="mini-box">
|
||||||
|
<strong>Descrizione</strong>
|
||||||
|
<?= h($result['Descrizione'] ?? '') ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="debug-card">
|
||||||
|
<div class="section-title">Direct Properties</div>
|
||||||
|
<pre><?= h(json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)) ?></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!empty($result['CommesseCustomFields']) && is_array($result['CommesseCustomFields'])): ?>
|
||||||
|
<div class="debug-card">
|
||||||
|
<div class="section-title">CommesseCustomFields</div>
|
||||||
|
<?php
|
||||||
|
$fieldsRows = [];
|
||||||
|
foreach ($result['CommesseCustomFields'] as $field) {
|
||||||
|
$fieldsRows[] = [
|
||||||
|
'IdCommesseCustomFields' => $field['IdCommesseCustomFields'] ?? '',
|
||||||
|
'Valore' => $field['Valore'] ?? '',
|
||||||
|
'CustomFieldId' => $field['CustomField']['IdCustomField'] ?? '',
|
||||||
|
'Label' => $field['CustomField']['Descrizione'] ?? ($field['CustomField']['Name'] ?? ''),
|
||||||
|
'Tipo' => $field['CustomField']['Tipo'] ?? '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
renderArrayAsTable($fieldsRows);
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($result['Campioni']) && is_array($result['Campioni'])): ?>
|
||||||
|
<div class="debug-card">
|
||||||
|
<div class="section-title">Campioni</div>
|
||||||
|
<?php renderArrayAsTable($result['Campioni']); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="debug-card">
|
||||||
|
<div class="section-title">Raw JSON</div>
|
||||||
|
<pre><?= h(json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)) ?></pre>
|
||||||
|
</div>
|
||||||
|
<?php elseif ($entityId > 0 && !$error): ?>
|
||||||
|
<div class="debug-card">
|
||||||
|
<div class="alert alert-warning mb-0">No data returned from API</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
||||||
|
<!-- Example of use
|
||||||
|
debug_commessa_api.php?entity_type=CommessaWeb&entity_id=564779
|
||||||
|
debug_commessa_api.php?entity_type=Commessa&entity_id=12345
|
||||||
|
debug_commessa_api.php?entity_type=CommessaWeb&entity_id=564779&expand=CommesseCustomFields($expand=CustomField),Campioni
|
||||||
|
-->
|
||||||
@@ -1,26 +1,27 @@
|
|||||||
<?php include('include/headscript.php');
|
<?php include('include/headscript.php');
|
||||||
|
|
||||||
// Controlla se è stato passato un ID valido
|
// Check if a valid ID was provided
|
||||||
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
|
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
|
||||||
header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Invalid ID"));
|
header("Location: templates_dashboard.php?status=error&message=" . urlencode("Invalid ID"));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$id = intval($_GET['id']); // Sanifica l'ID
|
$id = intval($_GET['id']);
|
||||||
|
|
||||||
// Recupera il template dal database
|
// Retrieve template from database
|
||||||
$db = DBHandlerSelect::getInstance();
|
$db = DBHandlerSelect::getInstance();
|
||||||
$pdo = $db->getConnection();
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
$stmt = $pdo->prepare("SELECT * FROM excel_templates WHERE id = ?");
|
$stmt = $pdo->prepare("SELECT * FROM excel_templates WHERE id = ?");
|
||||||
$stmt->execute([$id]);
|
$stmt->execute([$id]);
|
||||||
$template = $stmt->fetch(PDO::FETCH_ASSOC);
|
$template = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if (!$template) {
|
if (!$template) {
|
||||||
header("Location: template_dashboard.php?status=error&message=" . urlencode("Template not found"));
|
header("Location: templates_dashboard.php?status=error&message=" . urlencode("Template not found"));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recupera tutte le routine dal database
|
// Retrieve all routines
|
||||||
$stmt = $pdo->prepare("SELECT * FROM routine");
|
$stmt = $pdo->prepare("SELECT * FROM routine");
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
@@ -34,7 +35,6 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||||
<?php include('cssinclude.php'); ?>
|
<?php include('cssinclude.php'); ?>
|
||||||
<!-- Include jQuery prima di Select2 -->
|
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||||
<title>Edit Template <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
<title>Edit Template <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||||
@@ -44,19 +44,22 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<?php include('include/navbar.php'); ?>
|
<?php include('include/navbar.php'); ?>
|
||||||
<?php include('include/topbar.php'); ?>
|
<?php include('include/topbar.php'); ?>
|
||||||
|
|
||||||
<div class="page-wrapper">
|
<div class="page-wrapper">
|
||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
|
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="mb-0">Update XLS Template</h5>
|
<h5 class="mb-0">Update Template</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="mb-2">Edit the following form in order to update the selected import XLS template</p>
|
<p class="mb-2">Edit the following form in order to update the selected import template</p>
|
||||||
<p class="mb-2">Mandatory Fields</p>
|
<p class="mb-2">Mandatory Fields</p>
|
||||||
<ul class="mb-0">
|
<ul class="mb-0">
|
||||||
<li>Template Name</li>
|
<li>Template Name</li>
|
||||||
<li>Row Header and Column Header: where the title of the excel starts</li>
|
<li>Source Type</li>
|
||||||
<li>Schema</li>
|
<li>Schema and Client</li>
|
||||||
|
<li>Row Header and Column Header only for XLS templates</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -69,34 +72,44 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<form id="editTemplateForm" method="POST">
|
<form id="editTemplateForm" method="POST">
|
||||||
<input type="hidden" name="id" value="<?php echo $template['id']; ?>">
|
<input type="hidden" name="id" value="<?php echo (int)$template['id']; ?>">
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label"><?= htmlspecialchars($templatename, ENT_QUOTES, 'UTF-8'); ?> *</label>
|
<label class="form-label"><?= htmlspecialchars($templatename, ENT_QUOTES, 'UTF-8'); ?> *</label>
|
||||||
<input type="text" name="name" class="form-control" value="<?php echo htmlspecialchars($template['name']); ?>" required>
|
<input type="text" name="name" class="form-control" value="<?php echo htmlspecialchars($template['name'] ?? ''); ?>" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Source Type *</label>
|
||||||
|
<select name="source_type" id="sourceType" class="form-control" required>
|
||||||
|
<option value="XLS" <?php echo (($template['source_type'] ?? 'XLS') === 'XLS') ? 'selected' : ''; ?>>XLS</option>
|
||||||
|
<option value="API" <?php echo (($template['source_type'] ?? 'XLS') === 'API') ? 'selected' : ''; ?>>API</option>
|
||||||
|
</select>
|
||||||
|
<small class="text-muted">Choose the source used by this template</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3" id="headerRowWrapper">
|
||||||
<label class="form-label"><?= htmlspecialchars($rowheader, ENT_QUOTES, 'UTF-8'); ?> *</label>
|
<label class="form-label"><?= htmlspecialchars($rowheader, ENT_QUOTES, 'UTF-8'); ?> *</label>
|
||||||
<input type="number" name="header_row" class="form-control" value="<?php echo $template['header_row']; ?>" required>
|
<input type="number" name="header_row" id="headerRow" class="form-control" value="<?php echo htmlspecialchars($template['header_row'] ?? ''); ?>">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3" id="startColumnWrapper">
|
||||||
<label class="form-label"><?= htmlspecialchars($columnheader, ENT_QUOTES, 'UTF-8'); ?>*</label>
|
<label class="form-label"><?= htmlspecialchars($columnheader, ENT_QUOTES, 'UTF-8'); ?> *</label>
|
||||||
<input type="text" name="start_column" class="form-control" value="<?php echo htmlspecialchars($template['start_column']); ?>" required>
|
<input type="text" name="start_column" id="startColumn" class="form-control" value="<?php echo htmlspecialchars($template['start_column'] ?? ''); ?>">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label"><?= htmlspecialchars($desctemplate, ENT_QUOTES, 'UTF-8'); ?></label>
|
<label class="form-label"><?= htmlspecialchars($desctemplate, ENT_QUOTES, 'UTF-8'); ?></label>
|
||||||
<textarea name="description" class="form-control"><?php echo htmlspecialchars($template['description']); ?></textarea>
|
<textarea name="description" class="form-control"><?php echo htmlspecialchars($template['description'] ?? ''); ?></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label"><?= htmlspecialchars($desttable, ENT_QUOTES, 'UTF-8'); ?>*</label>
|
<label class="form-label"><?= htmlspecialchars($desttable, ENT_QUOTES, 'UTF-8'); ?> *</label>
|
||||||
<input type="text" name="target_table" class="form-control" value="<?php echo htmlspecialchars($template['target_table']); ?>" readonly required>
|
<input type="text" name="target_table" class="form-control" value="<?php echo htmlspecialchars($template['target_table'] ?? 'datadb'); ?>" readonly required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -110,12 +123,12 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Button Background Color</label>
|
<label class="form-label">Button Background Color</label>
|
||||||
<input type="color" name="button_bg_color" class="form-control" value="<?php echo htmlspecialchars($template['button_bg_color'] ?? '#007bff'); ?>">
|
<input type="color" name="button_bg_color" class="form-control form-control-color" value="<?php echo htmlspecialchars($template['button_bg_color'] ?? '#007bff'); ?>">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Button Text Color</label>
|
<label class="form-label">Button Text Color</label>
|
||||||
<input type="color" name="button_text_color" class="form-control" value="<?php echo htmlspecialchars($template['button_text_color'] ?? '#ffffff'); ?>">
|
<input type="color" name="button_text_color" class="form-control form-control-color" value="<?php echo htmlspecialchars($template['button_text_color'] ?? '#ffffff'); ?>">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -128,7 +141,7 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
<select name="client_id" id="clientSelect" class="form-control" required>
|
<select name="client_id" id="clientSelect" class="form-control" required>
|
||||||
<option value="">Select a client...</option>
|
<option value="">Select a client...</option>
|
||||||
</select>
|
</select>
|
||||||
<span id="clientLoadingStatus" class="text-muted" style="margin-left: 10px; display: none;">Recupero clienti in corso...</span>
|
<span id="clientLoadingStatus" class="text-muted" style="margin-left: 10px; display: none;">Loading clients...</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -136,7 +149,7 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
<select name="schema_id" id="schemaSelect" class="form-control" required>
|
<select name="schema_id" id="schemaSelect" class="form-control" required>
|
||||||
<option value="">Select a schema...</option>
|
<option value="">Select a schema...</option>
|
||||||
</select>
|
</select>
|
||||||
<span id="schemaLoadingStatus" class="text-muted" style="margin-left: 10px; display: none;">Caricamento schemi in corso...</span>
|
<span id="schemaLoadingStatus" class="text-muted" style="margin-left: 10px; display: none;">Loading schemas...</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -144,11 +157,12 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
<select name="idroutine" id="routineSelect" class="form-control">
|
<select name="idroutine" id="routineSelect" class="form-control">
|
||||||
<option value="">Select a routine...</option>
|
<option value="">Select a routine...</option>
|
||||||
<?php foreach ($routines as $routine): ?>
|
<?php foreach ($routines as $routine): ?>
|
||||||
<option value="<?php echo $routine['idroutine']; ?>" <?php echo ($template['idroutine'] ?? '') == $routine['idroutine'] ? 'selected' : ''; ?>>
|
<option value="<?php echo $routine['idroutine']; ?>" <?php echo (($template['idroutine'] ?? '') == $routine['idroutine']) ? 'selected' : ''; ?>>
|
||||||
<?php echo htmlspecialchars($routine['name']); ?>
|
<?php echo htmlspecialchars($routine['name']); ?>
|
||||||
</option>
|
</option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<div id="routineDetails" class="mt-2" style="display: none;">
|
<div id="routineDetails" class="mt-2" style="display: none;">
|
||||||
<h6>Routine Details</h6>
|
<h6>Routine Details</h6>
|
||||||
<p><strong>Name:</strong> <span id="routineName"></span></p>
|
<p><strong>Name:</strong> <span id="routineName"></span></p>
|
||||||
@@ -166,8 +180,10 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="overlay toggle-icon"></div>
|
<div class="overlay toggle-icon"></div>
|
||||||
<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>
|
||||||
<?php include('include/footer.php'); ?>
|
<?php include('include/footer.php'); ?>
|
||||||
@@ -175,9 +191,8 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
// Verifica che jQuery sia caricato
|
|
||||||
if (typeof jQuery === 'undefined') {
|
if (typeof jQuery === 'undefined') {
|
||||||
alert("Errore: jQuery non è caricato. Contatta l'amministratore.");
|
alert("Error: jQuery is not loaded.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,12 +207,15 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
const routineAction2 = document.getElementById("routineAction2");
|
const routineAction2 = document.getElementById("routineAction2");
|
||||||
const routineAction3 = document.getElementById("routineAction3");
|
const routineAction3 = document.getElementById("routineAction3");
|
||||||
|
|
||||||
if (!form || !clientLoadingStatus || !schemaLoadingStatus || !routineSelect || !routineDetails) {
|
const sourceType = document.getElementById("sourceType");
|
||||||
alert("Errore: Uno o più elementi della pagina non sono stati trovati. Contatta l'amministratore.");
|
const headerRowWrapper = document.getElementById("headerRowWrapper");
|
||||||
return;
|
const startColumnWrapper = document.getElementById("startColumnWrapper");
|
||||||
}
|
const headerRow = document.getElementById("headerRow");
|
||||||
|
const startColumn = document.getElementById("startColumn");
|
||||||
|
|
||||||
|
const selectedClientId = <?php echo json_encode((int)($template['idclient'] ?? 0)); ?>;
|
||||||
|
const selectedSchemaId = <?php echo json_encode((int)($template['idschema'] ?? 0)); ?>;
|
||||||
|
|
||||||
// Inizializza Select2
|
|
||||||
$('#clientSelect').select2({
|
$('#clientSelect').select2({
|
||||||
placeholder: "Search for a client...",
|
placeholder: "Search for a client...",
|
||||||
allowClear: true
|
allowClear: true
|
||||||
@@ -213,108 +231,164 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
allowClear: true
|
allowClear: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// Carica i clienti
|
function updateSourceFields() {
|
||||||
|
const selectedSource = sourceType.value;
|
||||||
|
|
||||||
|
if (selectedSource === 'API') {
|
||||||
|
headerRowWrapper.style.opacity = '0.6';
|
||||||
|
startColumnWrapper.style.opacity = '0.6';
|
||||||
|
|
||||||
|
headerRow.required = false;
|
||||||
|
startColumn.required = false;
|
||||||
|
|
||||||
|
headerRow.disabled = true;
|
||||||
|
startColumn.disabled = true;
|
||||||
|
} else {
|
||||||
|
headerRowWrapper.style.opacity = '1';
|
||||||
|
startColumnWrapper.style.opacity = '1';
|
||||||
|
|
||||||
|
headerRow.required = true;
|
||||||
|
startColumn.required = true;
|
||||||
|
|
||||||
|
headerRow.disabled = false;
|
||||||
|
startColumn.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceType.addEventListener('change', updateSourceFields);
|
||||||
|
updateSourceFields();
|
||||||
|
|
||||||
async function loadClients() {
|
async function loadClients() {
|
||||||
try {
|
try {
|
||||||
clientLoadingStatus.style.display = 'inline';
|
clientLoadingStatus.style.display = 'inline';
|
||||||
clientLoadingStatus.textContent = 'Recupero clienti in corso...';
|
clientLoadingStatus.textContent = 'Loading clients...';
|
||||||
|
|
||||||
const response = await fetch("get_clienti.php", {
|
const response = await fetch("get_clienti.php", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (!response.ok) throw new Error(data.error || `Errore HTTP: ${response.status}`);
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(data.error || `HTTP error: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
const select = document.getElementById("clientSelect");
|
const select = document.getElementById("clientSelect");
|
||||||
select.innerHTML = '<option value="">Select a client...</option>';
|
select.innerHTML = '<option value="">Select a client...</option>';
|
||||||
|
|
||||||
data.value.forEach(client => {
|
data.value.forEach(client => {
|
||||||
const nome = client.Nominativo || "Nome non disponibile";
|
const nome = client.Nominativo || "Name not available";
|
||||||
const id = client.IdCliente || "ID non disponibile";
|
const id = client.IdCliente || "";
|
||||||
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
|
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
|
||||||
if (parseInt(id) === parseInt(<?php echo json_encode($template['idclient'] ?? 0); ?>)) {
|
|
||||||
|
if (parseInt(id) === parseInt(selectedClientId)) {
|
||||||
option.selected = true;
|
option.selected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
select.add(option);
|
select.add(option);
|
||||||
});
|
});
|
||||||
|
|
||||||
$(select).trigger('change');
|
$(select).trigger('change');
|
||||||
clientLoadingStatus.textContent = "Clienti caricati.";
|
clientLoadingStatus.textContent = "Clients loaded.";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
clientLoadingStatus.textContent = "Errore nel caricamento.";
|
clientLoadingStatus.textContent = "Loading error.";
|
||||||
|
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Errore!",
|
title: "Error!",
|
||||||
text: "Impossibile caricare i clienti: " + error.message,
|
text: "Unable to load clients: " + error.message,
|
||||||
icon: "error",
|
icon: "error",
|
||||||
confirmButtonText: "OK"
|
confirmButtonText: "OK"
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setTimeout(() => clientLoadingStatus.style.display = 'none', 2000);
|
setTimeout(() => clientLoadingStatus.style.display = 'none', 1500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carica gli schemi
|
|
||||||
async function loadSchemas() {
|
async function loadSchemas() {
|
||||||
try {
|
try {
|
||||||
schemaLoadingStatus.style.display = 'inline';
|
schemaLoadingStatus.style.display = 'inline';
|
||||||
schemaLoadingStatus.textContent = 'Caricamento schemi in corso...';
|
schemaLoadingStatus.textContent = 'Loading schemas...';
|
||||||
|
|
||||||
const response = await fetch("get_schemi.php", {
|
const response = await fetch("get_schemi.php", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (!response.ok) throw new Error(data.error || `Errore HTTP: ${response.status}`);
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(data.error || `HTTP error: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
const select = document.getElementById("schemaSelect");
|
const select = document.getElementById("schemaSelect");
|
||||||
select.innerHTML = '<option value="">Select a schema...</option>';
|
select.innerHTML = '<option value="">Select a schema...</option>';
|
||||||
data.value.forEach(schema => {
|
|
||||||
const nome = schema.Nome || "Nome non disponibile";
|
const sortedSchemas = [...data.value].sort((a, b) => {
|
||||||
const id = schema.IdSchemaCustomFields || "ID non disponibile";
|
const nomeA = (a.Nome || "").trim().toLowerCase();
|
||||||
|
const nomeB = (b.Nome || "").trim().toLowerCase();
|
||||||
|
return nomeA.localeCompare(nomeB, 'it', {
|
||||||
|
sensitivity: 'base'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
sortedSchemas.forEach(schema => {
|
||||||
|
const nome = schema.Nome || "Name not available";
|
||||||
|
const id = schema.IdSchemaCustomFields || "";
|
||||||
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
|
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
|
||||||
if (parseInt(id) === parseInt(<?php echo json_encode($template['idschema'] ?? 0); ?>)) {
|
|
||||||
|
if (parseInt(id) === parseInt(selectedSchemaId)) {
|
||||||
option.selected = true;
|
option.selected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
select.add(option);
|
select.add(option);
|
||||||
});
|
});
|
||||||
|
|
||||||
$(select).trigger('change');
|
$(select).trigger('change');
|
||||||
schemaLoadingStatus.textContent = "Schemi caricati.";
|
schemaLoadingStatus.textContent = "Schemas loaded.";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
schemaLoadingStatus.textContent = "Errore nel caricamento.";
|
schemaLoadingStatus.textContent = "Loading error.";
|
||||||
|
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Errore!",
|
title: "Error!",
|
||||||
text: "Impossibile caricare gli schemi: " + error.message,
|
text: "Unable to load schemas: " + error.message,
|
||||||
icon: "error",
|
icon: "error",
|
||||||
confirmButtonText: "OK"
|
confirmButtonText: "OK"
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setTimeout(() => schemaLoadingStatus.style.display = 'none', 2000);
|
setTimeout(() => schemaLoadingStatus.style.display = 'none', 1500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carica i dati
|
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
try {
|
try {
|
||||||
await loadClients();
|
await loadClients();
|
||||||
await loadSchemas();
|
await loadSchemas();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Errore!",
|
title: "Error!",
|
||||||
text: "Errore nel caricamento dei dati: " + error.message,
|
text: "Error while loading data: " + error.message,
|
||||||
icon: "error",
|
icon: "error",
|
||||||
confirmButtonText: "OK"
|
confirmButtonText: "OK"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadData();
|
loadData();
|
||||||
|
|
||||||
// Routine dettagli
|
|
||||||
const routines = <?php echo json_encode($routines); ?>;
|
const routines = <?php echo json_encode($routines); ?>;
|
||||||
|
|
||||||
function updateRoutineDetails() {
|
function updateRoutineDetails() {
|
||||||
const selectedId = routineSelect.value;
|
const selectedId = routineSelect.value;
|
||||||
routineDetails.style.display = selectedId ? 'block' : 'none';
|
routineDetails.style.display = selectedId ? 'block' : 'none';
|
||||||
|
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
const routine = routines.find(r => r.idroutine == selectedId);
|
const routine = routines.find(r => r.idroutine == selectedId);
|
||||||
|
|
||||||
if (routine) {
|
if (routine) {
|
||||||
routineName.textContent = routine.name || 'N/A';
|
routineName.textContent = routine.name || 'N/A';
|
||||||
routineDescription.textContent = routine.description || 'N/A';
|
routineDescription.textContent = routine.description || 'N/A';
|
||||||
@@ -336,10 +410,10 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
routineAction3.textContent = '';
|
routineAction3.textContent = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
routineSelect.addEventListener('change', updateRoutineDetails);
|
|
||||||
updateRoutineDetails(); // Inizializza dettagli se una routine è preselezionata
|
|
||||||
|
|
||||||
// Submit del form
|
routineSelect.addEventListener('change', updateRoutineDetails);
|
||||||
|
updateRoutineDetails();
|
||||||
|
|
||||||
form.addEventListener("submit", function(e) {
|
form.addEventListener("submit", function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
@@ -351,8 +425,8 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
|
|
||||||
if (!clientId) {
|
if (!clientId) {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Errore!",
|
title: "Error!",
|
||||||
text: "Per favore seleziona un cliente.",
|
text: "Please select a client.",
|
||||||
icon: "error",
|
icon: "error",
|
||||||
confirmButtonText: "OK"
|
confirmButtonText: "OK"
|
||||||
});
|
});
|
||||||
@@ -373,8 +447,8 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
|
|
||||||
if (!schemaId) {
|
if (!schemaId) {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Errore!",
|
title: "Error!",
|
||||||
text: "Per favore seleziona uno schema.",
|
text: "Please select a schema.",
|
||||||
icon: "error",
|
icon: "error",
|
||||||
confirmButtonText: "OK"
|
confirmButtonText: "OK"
|
||||||
});
|
});
|
||||||
@@ -387,10 +461,10 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
const nameMatch = optionText.match(/^(.+?)(?:\s*\(ID:\s*\d+\))?$/);
|
const nameMatch = optionText.match(/^(.+?)(?:\s*\(ID:\s*\d+\))?$/);
|
||||||
schemaName = nameMatch ? nameMatch[1].trim() : optionText;
|
schemaName = nameMatch ? nameMatch[1].trim() : optionText;
|
||||||
}
|
}
|
||||||
|
|
||||||
formData.append("idschema", schemaId);
|
formData.append("idschema", schemaId);
|
||||||
formData.append("schemaname", schemaName);
|
formData.append("schemaname", schemaName);
|
||||||
|
|
||||||
// Aggiungi idroutine
|
|
||||||
const routineId = routineSelect.value;
|
const routineId = routineSelect.value;
|
||||||
formData.append("idroutine", routineId);
|
formData.append("idroutine", routineId);
|
||||||
|
|
||||||
@@ -402,8 +476,8 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Successo!",
|
title: "Success!",
|
||||||
text: "Template aggiornato con successo!",
|
text: "Template updated successfully!",
|
||||||
icon: "success",
|
icon: "success",
|
||||||
confirmButtonText: "OK"
|
confirmButtonText: "OK"
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
@@ -411,17 +485,17 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Errore!",
|
title: "Error!",
|
||||||
text: data.message,
|
text: data.message,
|
||||||
icon: "error",
|
icon: "error",
|
||||||
confirmButtonText: "OK"
|
confirmButtonText: "OK"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(() => {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Errore!",
|
title: "Error!",
|
||||||
text: "Si è verificato un errore imprevisto.",
|
text: "An unexpected error occurred.",
|
||||||
icon: "error",
|
icon: "error",
|
||||||
confirmButtonText: "OK"
|
confirmButtonText: "OK"
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -312,3 +312,19 @@
|
|||||||
|
|
||||||
2026-02-25 15:23:12 [AnagraficaCertestObject] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
2026-02-25 15:23:12 [AnagraficaCertestObject] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
||||||
2026-02-26 15:01:32 [AnagraficaCertestObject] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
2026-02-26 15:01:32 [AnagraficaCertestObject] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
||||||
|
2026-02-28 21:01:27 [AnagraficaCertestService] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
||||||
|
2026-03-01 19:11:58 [AnagraficaCertestObject] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
||||||
|
2026-03-18 15:51:45 [AnagraficaCertestService] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
||||||
|
2026-03-19 09:50:34 [AnagraficaCertestService] Autenticazione fallita: HTTP 400, Errore cURL: , Risposta: {"title":"Bad Request","status":400,"detail":"Cannot persist the object. It was modified or deleted (purged) by another application.","instance":"POST /api/authentication/authenticate","errorCode":"96bfc1252b"}
|
||||||
|
2026-03-25 14:13:34 - Autenticazione fallita: HTTP 503, Errore cURL: , Risposta: The service is unavailable.
|
||||||
|
2026-03-25 14:13:34 - Autenticazione fallita: HTTP 503, Errore cURL: , Risposta: The service is unavailable.
|
||||||
|
2026-03-26 10:57:18 [AnagraficaCertestObject] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
||||||
|
2026-03-26 10:57:21 [AnagraficaCertestObject] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
||||||
|
2026-03-26 10:57:21 [AnagraficaCertestService] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
||||||
|
2026-03-26 10:57:21 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
||||||
|
2026-03-26 10:57:36 [AnagraficaCertestObject] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
||||||
|
2026-03-26 10:57:36 [AnagraficaCertestService] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
||||||
|
2026-03-26 10:57:36 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
||||||
|
2026-03-26 10:57:41 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
||||||
|
2026-03-26 10:57:56 [AnagraficaCertestObject] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
||||||
|
2026-03-26 10:58:11 [AnagraficaCertestService] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
||||||
|
|||||||
@@ -0,0 +1,555 @@
|
|||||||
|
/**
|
||||||
|
* exportLims_gridData.js — Export to LIMS using gridData (for imported.php)
|
||||||
|
*
|
||||||
|
* Replaces export_to_lims.js for pages that use gridData instead of DOM-rendered rows.
|
||||||
|
* Single export + batch export (Export All) with validation.
|
||||||
|
*/
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
let pendingConfirmHandler = null;
|
||||||
|
let batchRunning = false;
|
||||||
|
Object.defineProperty(window, "batchRunning", { get: () => batchRunning });
|
||||||
|
|
||||||
|
let batchCancelled = false;
|
||||||
|
let pendingBatchConfirmHandler = 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";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGridRow(iddatadb) {
|
||||||
|
return document.querySelector(`.grid-row[data-id="${iddatadb}"]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRowIndexByIddatadb(iddatadb) {
|
||||||
|
return (window.gridData || []).findIndex(r => String(r.iddatadb) === String(iddatadb));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Validation ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async function validateRows(rowsToValidate) {
|
||||||
|
const response = await fetch("validate_export.php", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ rows: rowsToValidate }),
|
||||||
|
});
|
||||||
|
if (!response.ok) throw new Error(`Validation HTTP error: ${response.status}`);
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearValidationErrors() {
|
||||||
|
// Clear from gridData
|
||||||
|
(window.gridData || []).forEach(row => { delete row._validationErrors; delete row._exportError; });
|
||||||
|
|
||||||
|
document.querySelectorAll(".grid-cell.validation-error").forEach(cell => {
|
||||||
|
cell.classList.remove("validation-error");
|
||||||
|
cell.querySelectorAll(".input-validation-error").forEach(el => el.classList.remove("input-validation-error"));
|
||||||
|
const tooltip = cell.querySelector(".validation-tooltip");
|
||||||
|
if (tooltip) tooltip.remove();
|
||||||
|
});
|
||||||
|
document.querySelectorAll(".grid-row.validation-row-error").forEach(row => row.classList.remove("validation-row-error"));
|
||||||
|
clearAllRowErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showValidationErrors(gridRow, iddatadb, errors) {
|
||||||
|
// Store in gridData for re-render persistence
|
||||||
|
const idx = getRowIndexByIddatadb(iddatadb);
|
||||||
|
if (idx >= 0) window.gridData[idx]._validationErrors = errors;
|
||||||
|
|
||||||
|
if (!gridRow) return;
|
||||||
|
gridRow.classList.add("validation-row-error");
|
||||||
|
const messages = [];
|
||||||
|
|
||||||
|
errors.forEach(err => {
|
||||||
|
messages.push(err.message);
|
||||||
|
if (!err.field) return;
|
||||||
|
|
||||||
|
let cell = null;
|
||||||
|
if (err.field.startsWith("field_label:")) {
|
||||||
|
const label = err.field.substring("field_label:".length);
|
||||||
|
const headers = document.querySelectorAll(".grid-header");
|
||||||
|
let targetIndex = null;
|
||||||
|
headers.forEach(h => {
|
||||||
|
if (h.textContent.trim() === label) targetIndex = h.getAttribute("data-index");
|
||||||
|
});
|
||||||
|
if (targetIndex) {
|
||||||
|
cell = gridRow.querySelector(`.grid-cell[data-index="${targetIndex}"]`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cell = gridRow.querySelector(`.grid-cell[data-col="${err.field}"]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cell) {
|
||||||
|
cell.classList.add("validation-error");
|
||||||
|
cell.querySelectorAll("input, select").forEach(el => el.classList.add("input-validation-error"));
|
||||||
|
let tooltip = cell.querySelector(".validation-tooltip");
|
||||||
|
if (!tooltip) {
|
||||||
|
tooltip = document.createElement("div");
|
||||||
|
tooltip.className = "validation-tooltip";
|
||||||
|
cell.appendChild(tooltip);
|
||||||
|
}
|
||||||
|
tooltip.textContent = err.message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
showRowError(gridRow, iddatadb, messages.join("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Send export ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async function sendExport(iddatadb, batchUuid) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("iddatadb", iddatadb);
|
||||||
|
if (batchUuid) formData.append("batch_uuid", batchUuid);
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
// Update gridData
|
||||||
|
const idx = getRowIndexByIddatadb(iddatadb);
|
||||||
|
if (idx >= 0) {
|
||||||
|
window.gridData[idx].status = 'l';
|
||||||
|
window.gridData[idx].commessaweb = data.commessaweb;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update visible DOM row
|
||||||
|
const gridRow = getGridRow(iddatadb);
|
||||||
|
if (gridRow) {
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
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;";
|
||||||
|
statusCell.appendChild(cwSpan);
|
||||||
|
}
|
||||||
|
cwSpan.textContent = data.commessaweb;
|
||||||
|
}
|
||||||
|
const exportBtn = gridRow.querySelector(".export-lims-btn");
|
||||||
|
if (exportBtn) {
|
||||||
|
exportBtn.disabled = true;
|
||||||
|
exportBtn.style.background = "#ccc";
|
||||||
|
exportBtn.style.cursor = "not-allowed";
|
||||||
|
exportBtn.style.opacity = "0.5";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Show result modal ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
function showExportResult(data) {
|
||||||
|
const el = document.getElementById("exportResponseModal");
|
||||||
|
if (!el) return;
|
||||||
|
const modal = new bootstrap.Modal(el, { keyboard: false });
|
||||||
|
const msg = document.getElementById("exportResponseMessage");
|
||||||
|
const label = document.getElementById("exportResponseModalLabel");
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
msg.innerHTML = `${data.message.replace(/\n/g, "<br>")}` +
|
||||||
|
`<br>ID CommessaWeb: ${data.idcommessaweb}` +
|
||||||
|
`<br>Codice CommessaWeb: ${data.commessaweb}` +
|
||||||
|
(data.totalPhotos > 0 ? `<br>Foto: ${data.totalPhotos}` : "");
|
||||||
|
label.textContent = "Esportazione Completata";
|
||||||
|
} else {
|
||||||
|
msg.textContent = `Errore: ${data.message}`;
|
||||||
|
label.textContent = "Errore Esportazione";
|
||||||
|
}
|
||||||
|
modal.show();
|
||||||
|
el.addEventListener("hidden.bs.modal", cleanupBackdrop, { once: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Row UI helpers ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function setRowExporting(row, active) {
|
||||||
|
if (!row) return;
|
||||||
|
const btnCell = row.querySelector(".button-cell");
|
||||||
|
if (active) {
|
||||||
|
row.classList.remove("batch-disabled");
|
||||||
|
row.classList.add("batch-exporting");
|
||||||
|
if (btnCell) {
|
||||||
|
btnCell.querySelectorAll(".action-btn").forEach(b => { b.dataset.prevDisplay = b.style.display; b.style.display = "none"; });
|
||||||
|
const spinner = document.createElement("span");
|
||||||
|
spinner.className = "batch-row-spinner";
|
||||||
|
spinner.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Exporting...';
|
||||||
|
btnCell.appendChild(spinner);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
row.classList.remove("batch-exporting");
|
||||||
|
if (btnCell) {
|
||||||
|
const spinner = btnCell.querySelector(".batch-row-spinner");
|
||||||
|
if (spinner) spinner.remove();
|
||||||
|
btnCell.querySelectorAll(".action-btn").forEach(b => { b.style.display = b.dataset.prevDisplay || ""; delete b.dataset.prevDisplay; });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showRowError(row, iddatadb, message) {
|
||||||
|
if (!row) return;
|
||||||
|
row.classList.add("batch-row-error");
|
||||||
|
const btnCell = row.querySelector(".button-cell");
|
||||||
|
if (btnCell) {
|
||||||
|
const old = btnCell.querySelector(".batch-error-msg");
|
||||||
|
if (old) old.remove();
|
||||||
|
const errorEl = document.createElement("div");
|
||||||
|
errorEl.className = "batch-error-msg";
|
||||||
|
errorEl.textContent = "Warning — click for details";
|
||||||
|
errorEl.addEventListener("click", () => {
|
||||||
|
document.getElementById("exportResponseMessage").innerHTML = message.replace(/\n/g, "<br>");
|
||||||
|
document.getElementById("exportResponseModalLabel").textContent = "Error (id: " + iddatadb + ")";
|
||||||
|
new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show();
|
||||||
|
});
|
||||||
|
btnCell.appendChild(errorEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAllRowErrors() {
|
||||||
|
document.querySelectorAll(".grid-row.batch-row-error").forEach(row => {
|
||||||
|
row.classList.remove("batch-row-error");
|
||||||
|
const msg = row.querySelector(".batch-error-msg");
|
||||||
|
if (msg) msg.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function disableAllRowButtons() {
|
||||||
|
document.querySelectorAll(".grid-row[data-id]").forEach(row => row.classList.add("batch-disabled"));
|
||||||
|
const toggle = document.querySelector(".actions-dropdown .dropdown-toggle");
|
||||||
|
if (toggle) { toggle.disabled = true; toggle.style.opacity = "0.5"; toggle.style.pointerEvents = "none"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableAllRowButtons() {
|
||||||
|
document.querySelectorAll(".grid-row[data-id]").forEach(row => row.classList.remove("batch-disabled"));
|
||||||
|
const toggle = document.querySelector(".actions-dropdown .dropdown-toggle");
|
||||||
|
if (toggle) { toggle.disabled = false; toggle.style.opacity = ""; toggle.style.pointerEvents = ""; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Single row export: validate → confirm → send ────────────────────
|
||||||
|
|
||||||
|
function startExportConfirmFlow(iddatadb, rowIndex) {
|
||||||
|
const gridRow = getGridRow(iddatadb);
|
||||||
|
clearValidationErrors();
|
||||||
|
|
||||||
|
if (gridRow) {
|
||||||
|
setRowExporting(gridRow, true);
|
||||||
|
const spinner = gridRow.querySelector(".batch-row-spinner");
|
||||||
|
if (spinner) spinner.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Validating...';
|
||||||
|
}
|
||||||
|
|
||||||
|
validateRows([{ iddatadb: parseInt(iddatadb), index: rowIndex }])
|
||||||
|
.then(validationData => {
|
||||||
|
if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); }
|
||||||
|
|
||||||
|
if (!validationData.success) {
|
||||||
|
showExportResult({ success: false, message: validationData.message || "Validation error" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = validationData.results[rowIndex];
|
||||||
|
if (result && !result.valid) {
|
||||||
|
if (gridRow) showValidationErrors(gridRow, iddatadb, result.errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showConfirmAndExport(iddatadb, rowIndex);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); }
|
||||||
|
showExportResult({ success: false, message: "Validation error: " + error.message });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showConfirmAndExport(iddatadb, rowIndex) {
|
||||||
|
const confirmModalElement = document.getElementById("exportConfirmModal");
|
||||||
|
if (!confirmModalElement) 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(); return; }
|
||||||
|
|
||||||
|
const confirmHandler = async () => {
|
||||||
|
pendingConfirmHandler = null;
|
||||||
|
confirmModal.hide();
|
||||||
|
|
||||||
|
const gridRow = getGridRow(iddatadb);
|
||||||
|
if (gridRow) setRowExporting(gridRow, true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await sendExport(iddatadb);
|
||||||
|
if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); }
|
||||||
|
if (!data.success) showRowError(gridRow, iddatadb, data.message || "Unknown error");
|
||||||
|
showExportResult(data);
|
||||||
|
} catch (error) {
|
||||||
|
if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); }
|
||||||
|
showRowError(gridRow, iddatadb, error.message);
|
||||||
|
showExportResult({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pendingConfirmHandler) confirmBtn.removeEventListener("click", pendingConfirmHandler);
|
||||||
|
pendingConfirmHandler = confirmHandler;
|
||||||
|
confirmBtn.addEventListener("click", confirmHandler, { once: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Single row click (event delegation) ─────────────────────────────
|
||||||
|
|
||||||
|
$(document).on('click', '.export-lims-btn', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (batchRunning) return;
|
||||||
|
|
||||||
|
const iddatadb = this.dataset.iddatadb;
|
||||||
|
const rowIndex = parseInt(this.dataset.row);
|
||||||
|
const gridRow = getGridRow(iddatadb);
|
||||||
|
|
||||||
|
// Check unsaved changes for this row
|
||||||
|
const dataRow = window.gridData?.[rowIndex];
|
||||||
|
if (dataRow && dataRow._dirty) {
|
||||||
|
const unsavedModal = new bootstrap.Modal(document.getElementById("exportUnsavedModal"), { keyboard: false });
|
||||||
|
unsavedModal.show();
|
||||||
|
|
||||||
|
document.getElementById("saveAndExportBtn")?.addEventListener("click", () => {
|
||||||
|
unsavedModal.hide();
|
||||||
|
// Save first, then export
|
||||||
|
const formData = window.buildSavePayload(rowIndex);
|
||||||
|
fetch('save_edited_row.php', { method: 'POST', body: formData })
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(result => {
|
||||||
|
if (result.success) {
|
||||||
|
dataRow._dirty = false;
|
||||||
|
startExportConfirmFlow(iddatadb, rowIndex);
|
||||||
|
} else {
|
||||||
|
alert('Save failed: ' + result.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, { once: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
startExportConfirmFlow(iddatadb, rowIndex);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Batch export (Export All) ───────────────────────────────────────
|
||||||
|
|
||||||
|
function collectEligibleRows() {
|
||||||
|
// Read from gridData, not DOM
|
||||||
|
const eligible = [];
|
||||||
|
(window.gridData || []).forEach((row, index) => {
|
||||||
|
if (row.status !== 'l') {
|
||||||
|
eligible.push({ iddatadb: row.iddatadb, index, row: getGridRow(row.iddatadb) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return eligible;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasUnsavedChanges() {
|
||||||
|
return (window.gridData || []).some(r => r._dirty);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function validateAndFilter(eligibleRows) {
|
||||||
|
const rowsToValidate = eligibleRows.map(({ iddatadb, index }) => ({
|
||||||
|
iddatadb: parseInt(iddatadb),
|
||||||
|
index,
|
||||||
|
}));
|
||||||
|
const validationData = await validateRows(rowsToValidate);
|
||||||
|
if (!validationData.success) throw new Error(validationData.message || "Validation error");
|
||||||
|
|
||||||
|
const validRows = [];
|
||||||
|
let invalidCount = 0;
|
||||||
|
|
||||||
|
for (const item of eligibleRows) {
|
||||||
|
const result = validationData.results[item.index];
|
||||||
|
if (result && !result.valid) {
|
||||||
|
if (item.row) showValidationErrors(item.row, item.iddatadb, result.errors);
|
||||||
|
invalidCount++;
|
||||||
|
} else {
|
||||||
|
validRows.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { validRows, invalidCount };
|
||||||
|
}
|
||||||
|
|
||||||
|
function showValidationSpinner(show) {
|
||||||
|
const bar = document.getElementById("batchExportBar");
|
||||||
|
const statusEl = document.getElementById("batchExportStatus");
|
||||||
|
const cancelBtn = document.getElementById("exportBatchCancelBtn");
|
||||||
|
if (show) {
|
||||||
|
if (bar) bar.style.display = "";
|
||||||
|
if (statusEl) statusEl.textContent = "Validating...";
|
||||||
|
if (cancelBtn) cancelBtn.style.display = "none";
|
||||||
|
} else {
|
||||||
|
if (bar) bar.style.display = "none";
|
||||||
|
if (cancelBtn) cancelBtn.style.display = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showBatchConfirm(eligibleRows) {
|
||||||
|
clearValidationErrors();
|
||||||
|
showValidationSpinner(true);
|
||||||
|
|
||||||
|
validateAndFilter(eligibleRows)
|
||||||
|
.then(({ validRows, invalidCount }) => {
|
||||||
|
showValidationSpinner(false);
|
||||||
|
if (validRows.length === 0) {
|
||||||
|
document.getElementById("exportResponseMessage").innerHTML =
|
||||||
|
`No valid rows for export.<br><strong>${invalidCount}</strong> rows with validation errors.`;
|
||||||
|
document.getElementById("exportResponseModalLabel").textContent = "Validation Failed";
|
||||||
|
new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmModal = new bootstrap.Modal(document.getElementById("exportBatchConfirmModal"), { keyboard: false });
|
||||||
|
let countText = String(validRows.length);
|
||||||
|
if (invalidCount > 0) countText += ` (${invalidCount} excluded due to errors)`;
|
||||||
|
document.getElementById("exportBatchCount").textContent = countText;
|
||||||
|
confirmModal.show();
|
||||||
|
|
||||||
|
const confirmBtn = document.getElementById("exportBatchConfirmBtn");
|
||||||
|
if (pendingBatchConfirmHandler) confirmBtn.removeEventListener("click", pendingBatchConfirmHandler);
|
||||||
|
pendingBatchConfirmHandler = () => {
|
||||||
|
pendingBatchConfirmHandler = null;
|
||||||
|
confirmModal.hide();
|
||||||
|
startBatchExport(validRows);
|
||||||
|
};
|
||||||
|
confirmBtn.addEventListener("click", pendingBatchConfirmHandler, { once: true });
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
showValidationSpinner(false);
|
||||||
|
document.getElementById("exportResponseMessage").textContent = "Validation error: " + error.message;
|
||||||
|
document.getElementById("exportResponseModalLabel").textContent = "Validation Error";
|
||||||
|
new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).on('click', '.export-all-lims-btn', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (batchRunning) return;
|
||||||
|
|
||||||
|
if (hasUnsavedChanges()) {
|
||||||
|
const unsavedModal = new bootstrap.Modal(document.getElementById("exportBatchUnsavedModal"), { keyboard: false });
|
||||||
|
unsavedModal.show();
|
||||||
|
document.getElementById("batchSaveAndExportBtn")?.addEventListener("click", () => {
|
||||||
|
unsavedModal.hide();
|
||||||
|
// Trigger save all first — listen for completion
|
||||||
|
alert("Please Save All first, then Export All.");
|
||||||
|
}, { once: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const eligibleRows = collectEligibleRows();
|
||||||
|
if (eligibleRows.length === 0) {
|
||||||
|
document.getElementById("exportResponseMessage").textContent = "All rows already exported to LIMS.";
|
||||||
|
document.getElementById("exportResponseModalLabel").textContent = "Export All";
|
||||||
|
new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showBatchConfirm(eligibleRows);
|
||||||
|
});
|
||||||
|
|
||||||
|
function startBatchExport(eligibleRows) {
|
||||||
|
batchCancelled = false;
|
||||||
|
batchRunning = true;
|
||||||
|
const batchUuid = crypto.randomUUID();
|
||||||
|
const total = eligibleRows.length;
|
||||||
|
let processed = 0, succeeded = 0, failed = 0;
|
||||||
|
|
||||||
|
disableAllRowButtons();
|
||||||
|
|
||||||
|
const bar = document.getElementById("batchExportBar");
|
||||||
|
const statusEl = document.getElementById("batchExportStatus");
|
||||||
|
const cancelBtn = document.getElementById("exportBatchCancelBtn");
|
||||||
|
if (bar) bar.style.display = "";
|
||||||
|
if (cancelBtn) cancelBtn.disabled = false;
|
||||||
|
if (statusEl) statusEl.textContent = `Exporting 0 / ${total}...`;
|
||||||
|
|
||||||
|
cancelBtn?.addEventListener("click", () => {
|
||||||
|
batchCancelled = true;
|
||||||
|
if (statusEl) statusEl.textContent = "Cancelling... (waiting for current row)";
|
||||||
|
if (cancelBtn) cancelBtn.disabled = true;
|
||||||
|
}, { once: true });
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
for (let i = 0; i < eligibleRows.length; i++) {
|
||||||
|
if (batchCancelled) break;
|
||||||
|
|
||||||
|
const { iddatadb, row } = eligibleRows[i];
|
||||||
|
if (statusEl) statusEl.textContent = `Exporting ${processed + 1} / ${total} (id: ${iddatadb})...`;
|
||||||
|
|
||||||
|
const gridRow = row || getGridRow(iddatadb);
|
||||||
|
if (gridRow) setRowExporting(gridRow, true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await sendExport(iddatadb, batchUuid);
|
||||||
|
processed++;
|
||||||
|
if (data.success) {
|
||||||
|
succeeded++;
|
||||||
|
} else {
|
||||||
|
failed++;
|
||||||
|
const errIdx = getRowIndexByIddatadb(iddatadb);
|
||||||
|
if (errIdx >= 0) window.gridData[errIdx]._exportError = data.message || "Unknown error";
|
||||||
|
if (gridRow) showRowError(gridRow, iddatadb, data.message || "Unknown error");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
processed++;
|
||||||
|
failed++;
|
||||||
|
const errIdx = getRowIndexByIddatadb(iddatadb);
|
||||||
|
if (errIdx >= 0) window.gridData[errIdx]._exportError = error.message;
|
||||||
|
if (gridRow) showRowError(gridRow, iddatadb, error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
batchRunning = false;
|
||||||
|
enableAllRowButtons();
|
||||||
|
if (bar) bar.style.display = "none";
|
||||||
|
|
||||||
|
// Re-render to reflect status changes, then restore error indicators
|
||||||
|
const gr = window.gridRenderer;
|
||||||
|
if (gr) gr.renderVisibleRows();
|
||||||
|
|
||||||
|
// Restore error state from gridData._exportError
|
||||||
|
(window.gridData || []).forEach((row, idx) => {
|
||||||
|
if (row._exportError) {
|
||||||
|
const gridRow = getGridRow(row.iddatadb);
|
||||||
|
if (gridRow) showRowError(gridRow, row.iddatadb, row._exportError);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const msgEl = document.getElementById("exportResponseMessage");
|
||||||
|
const labelEl = document.getElementById("exportResponseModalLabel");
|
||||||
|
|
||||||
|
if (batchCancelled) {
|
||||||
|
labelEl.textContent = "Export All — Cancelled";
|
||||||
|
msgEl.innerHTML = `Exported: <strong>${succeeded}</strong><br>Errors: <strong>${failed}</strong><br>Not processed: <strong>${total - processed}</strong>`;
|
||||||
|
} else if (failed === 0) {
|
||||||
|
labelEl.textContent = "Export All — Complete";
|
||||||
|
msgEl.innerHTML = `All <strong>${succeeded}</strong> rows exported successfully.`;
|
||||||
|
} else {
|
||||||
|
labelEl.textContent = "Export All — Completed with errors";
|
||||||
|
msgEl.innerHTML = `Exported: <strong>${succeeded}</strong><br>Errors: <strong>${failed}</strong>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modalEl = document.getElementById("exportResponseModal");
|
||||||
|
new bootstrap.Modal(modalEl, { keyboard: false }).show();
|
||||||
|
modalEl.addEventListener("hidden.bs.modal", cleanupBackdrop, { once: true });
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
})();
|
||||||
+738
-177
@@ -1,29 +1,373 @@
|
|||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
console.log("export_to_lims.js loaded");
|
console.log("export_to_lims.js loaded");
|
||||||
|
|
||||||
// Debug: verifica che i pulsanti siano trovati
|
|
||||||
const exportButtons = document.querySelectorAll(".export-lims-btn");
|
const exportButtons = document.querySelectorAll(".export-lims-btn");
|
||||||
console.log(`Found ${exportButtons.length} export-lims-btn buttons`);
|
console.log(`Found ${exportButtons.length} export-lims-btn buttons`);
|
||||||
|
|
||||||
if (exportButtons.length === 0) {
|
// Tracks the active confirm handler so it can be replaced on re-open
|
||||||
console.warn("No .export-lims-btn buttons found in the DOM");
|
let pendingConfirmHandler = null;
|
||||||
|
let batchRunning = false;
|
||||||
|
// Expose for Save All to check
|
||||||
|
Object.defineProperty(window, "batchRunning", {
|
||||||
|
get: () => batchRunning,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── 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";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Validation ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call the validation endpoint for an array of { iddatadb, index } objects.
|
||||||
|
* Returns the parsed JSON response.
|
||||||
|
*/
|
||||||
|
async function validateRows(rowsToValidate) {
|
||||||
|
const response = await fetch("validate_export.php", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ rows: rowsToValidate }),
|
||||||
|
});
|
||||||
|
if (!response.ok)
|
||||||
|
throw new Error(`Validation HTTP error: ${response.status}`);
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all validation-error highlights from the grid.
|
||||||
|
*/
|
||||||
|
function clearValidationErrors() {
|
||||||
|
document.querySelectorAll(".grid-cell.validation-error").forEach((cell) => {
|
||||||
|
cell.classList.remove("validation-error");
|
||||||
|
cell.querySelectorAll(".input-validation-error").forEach((el) => {
|
||||||
|
el.classList.remove("input-validation-error");
|
||||||
|
});
|
||||||
|
const tooltip = cell.querySelector(".validation-tooltip");
|
||||||
|
if (tooltip) tooltip.remove();
|
||||||
|
});
|
||||||
|
document.querySelectorAll(".grid-row.validation-row-error").forEach((row) => {
|
||||||
|
row.classList.remove("validation-row-error");
|
||||||
|
});
|
||||||
|
// Also clear batch-row-error that came from validation
|
||||||
|
clearAllRowErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlight specific cells on a row based on validation errors.
|
||||||
|
* Each error has { field, message }.
|
||||||
|
* field can be: a data-col value, "parts", "field_label:SomeLabel", or null (row-level).
|
||||||
|
*/
|
||||||
|
function showValidationErrors(row, iddatadb, errors) {
|
||||||
|
row.classList.add("validation-row-error");
|
||||||
|
|
||||||
|
const messages = [];
|
||||||
|
|
||||||
|
errors.forEach((err) => {
|
||||||
|
messages.push(err.message);
|
||||||
|
|
||||||
|
if (!err.field) return; // row-level error, no specific cell
|
||||||
|
|
||||||
|
let cell = null;
|
||||||
|
|
||||||
|
if (err.field === "parts") {
|
||||||
|
// No specific cell to highlight, but we highlight the parts button area
|
||||||
|
// just add to messages
|
||||||
|
return;
|
||||||
|
} else if (err.field.startsWith("field_label:")) {
|
||||||
|
// Match by field_label text — find the header with this label, get its index
|
||||||
|
const label = err.field.substring("field_label:".length);
|
||||||
|
const headers = document.querySelectorAll(".grid-header");
|
||||||
|
let targetIndex = null;
|
||||||
|
headers.forEach((h) => {
|
||||||
|
if (h.textContent.trim() === label) {
|
||||||
|
targetIndex = h.getAttribute("data-index");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (targetIndex) {
|
||||||
|
const rowIndex = row.querySelector(".grid-cell[data-row]")?.getAttribute("data-row");
|
||||||
|
if (rowIndex !== null) {
|
||||||
|
cell = row.querySelector(`.grid-cell[data-row="${rowIndex}"][data-index="${targetIndex}"]`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Direct data-col match
|
||||||
|
const rowIndex = row.querySelector(".grid-cell[data-row]")?.getAttribute("data-row");
|
||||||
|
if (rowIndex !== null) {
|
||||||
|
cell = row.querySelector(`.grid-cell[data-col="${err.field}"][data-row="${rowIndex}"]`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cell) {
|
||||||
|
cell.classList.add("validation-error");
|
||||||
|
// Mark the input/select inside the cell
|
||||||
|
cell.querySelectorAll("input, select").forEach((el) => {
|
||||||
|
el.classList.add("input-validation-error");
|
||||||
|
});
|
||||||
|
// Add tooltip with error message
|
||||||
|
let tooltip = cell.querySelector(".validation-tooltip");
|
||||||
|
if (!tooltip) {
|
||||||
|
tooltip = document.createElement("div");
|
||||||
|
tooltip.className = "validation-tooltip";
|
||||||
|
cell.appendChild(tooltip);
|
||||||
|
}
|
||||||
|
tooltip.textContent = err.message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show aggregated error on the row using existing mechanism
|
||||||
|
showRowError(row, iddatadb, messages.join("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a single export request and update the row UI on success.
|
||||||
|
* Returns the parsed JSON response.
|
||||||
|
*/
|
||||||
|
async function sendExport(iddatadb, gridRow, batchUuid = null) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("iddatadb", iddatadb);
|
||||||
|
if (batchUuid) {
|
||||||
|
formData.append("batch_uuid", batchUuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
if (data.success && gridRow) {
|
||||||
|
// Update status badge
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable export button for this row
|
||||||
|
const exportBtn = gridRow.querySelector(".export-lims-btn");
|
||||||
|
if (exportBtn) {
|
||||||
|
exportBtn.disabled = true;
|
||||||
|
exportBtn.style.background = "#ccc";
|
||||||
|
exportBtn.style.cursor = "not-allowed";
|
||||||
|
exportBtn.style.opacity = "0.5";
|
||||||
|
exportBtn.title = "Già esportato";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the result of a single export in the response modal.
|
||||||
|
*/
|
||||||
|
function showExportResult(data) {
|
||||||
|
const responseModalElement =
|
||||||
|
document.getElementById("exportResponseModal");
|
||||||
|
if (!responseModalElement) {
|
||||||
|
alert("Errore: Modale di risposta non trovato");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
exportButtons.forEach((btn) => {
|
const responseModal = new bootstrap.Modal(responseModalElement, {
|
||||||
btn.addEventListener("click", (e) => {
|
keyboard: false,
|
||||||
e.preventDefault();
|
});
|
||||||
const rowIndex = btn.dataset.row;
|
const responseMessage = document.getElementById(
|
||||||
const iddatadb = btn.dataset.iddatadb;
|
"exportResponseMessage",
|
||||||
console.log(
|
|
||||||
`Export to LIMS clicked for row ${rowIndex}, iddatadb: ${iddatadb}`,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Mostra il modale di conferma
|
if (data.success) {
|
||||||
|
responseMessage.innerHTML =
|
||||||
|
`${data.message.replace(/\n/g, "<br>")}` +
|
||||||
|
`<br>ID CommessaWeb: ${data.idcommessaweb}` +
|
||||||
|
`<br>Codice CommessaWeb: ${data.commessaweb}` +
|
||||||
|
(data.totalPhotos > 0
|
||||||
|
? `<br>Foto trovate: ${data.totalPhotos}`
|
||||||
|
: "");
|
||||||
|
document.getElementById("exportResponseModalLabel").textContent =
|
||||||
|
"Esportazione Completata";
|
||||||
|
} 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 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Row button helpers (disable/enable during batch) ────────────────────
|
||||||
|
|
||||||
|
function setRowExporting(row, active) {
|
||||||
|
const btnCell = row.querySelector(".button-cell");
|
||||||
|
if (active) {
|
||||||
|
row.classList.remove("batch-disabled");
|
||||||
|
row.classList.add("batch-exporting");
|
||||||
|
if (btnCell) {
|
||||||
|
btnCell.querySelectorAll(".action-btn").forEach((b) => {
|
||||||
|
b.dataset.prevDisplay = b.style.display;
|
||||||
|
b.style.display = "none";
|
||||||
|
});
|
||||||
|
const spinner = document.createElement("span");
|
||||||
|
spinner.className = "batch-row-spinner";
|
||||||
|
spinner.innerHTML =
|
||||||
|
'<i class="fas fa-spinner fa-spin"></i> Exporting...';
|
||||||
|
btnCell.appendChild(spinner);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
row.classList.remove("batch-exporting");
|
||||||
|
row.classList.add("batch-disabled");
|
||||||
|
if (btnCell) {
|
||||||
|
const spinner = btnCell.querySelector(".batch-row-spinner");
|
||||||
|
if (spinner) spinner.remove();
|
||||||
|
btnCell.querySelectorAll(".action-btn").forEach((b) => {
|
||||||
|
b.style.display = b.dataset.prevDisplay || "";
|
||||||
|
delete b.dataset.prevDisplay;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showRowError(row, iddatadb, message) {
|
||||||
|
row.classList.add("batch-row-error");
|
||||||
|
const btnCell = row.querySelector(".button-cell");
|
||||||
|
if (btnCell) {
|
||||||
|
// Remove existing error msg
|
||||||
|
const old = btnCell.querySelector(".batch-error-msg");
|
||||||
|
if (old) old.remove();
|
||||||
|
|
||||||
|
const errorEl = document.createElement("div");
|
||||||
|
errorEl.className = "batch-error-msg";
|
||||||
|
errorEl.textContent = "⚠ Errore — clicca per dettagli";
|
||||||
|
errorEl.addEventListener("click", () => {
|
||||||
|
document.getElementById("exportResponseMessage").innerHTML = message.replace(/\n/g, "<br>");
|
||||||
|
document.getElementById("exportResponseModalLabel").textContent = "Errore Validazione (id: " + iddatadb + ")";
|
||||||
|
new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show();
|
||||||
|
});
|
||||||
|
btnCell.appendChild(errorEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAllRowErrors() {
|
||||||
|
document.querySelectorAll(".grid-row.batch-row-error").forEach((row) => {
|
||||||
|
row.classList.remove("batch-row-error");
|
||||||
|
const msg = row.querySelector(".batch-error-msg");
|
||||||
|
if (msg) msg.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function disableAllRowButtons() {
|
||||||
|
document.querySelectorAll(".grid-row[data-id]").forEach((row) => {
|
||||||
|
row.classList.add("batch-disabled");
|
||||||
|
});
|
||||||
|
// Disable Actions dropdown
|
||||||
|
const toggle = document.querySelector(
|
||||||
|
".actions-dropdown .dropdown-toggle",
|
||||||
|
);
|
||||||
|
if (toggle) {
|
||||||
|
toggle.disabled = true;
|
||||||
|
toggle.style.opacity = "0.5";
|
||||||
|
toggle.style.pointerEvents = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableAllRowButtons() {
|
||||||
|
document.querySelectorAll(".grid-row[data-id]").forEach((row) => {
|
||||||
|
row.classList.remove("batch-disabled");
|
||||||
|
});
|
||||||
|
const toggle = document.querySelector(
|
||||||
|
".actions-dropdown .dropdown-toggle",
|
||||||
|
);
|
||||||
|
if (toggle) {
|
||||||
|
toggle.disabled = false;
|
||||||
|
toggle.style.opacity = "";
|
||||||
|
toggle.style.pointerEvents = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Single row export: validate, confirm modal, then send ───────────────
|
||||||
|
|
||||||
|
function startExportConfirmFlow(iddatadb, btn) {
|
||||||
|
const gridRow = btn.closest(".grid-row");
|
||||||
|
const rowIndex = btn.dataset.row;
|
||||||
|
|
||||||
|
// Validate first
|
||||||
|
clearValidationErrors();
|
||||||
|
|
||||||
|
// Show validating state on the row
|
||||||
|
setRowExporting(gridRow, true);
|
||||||
|
const spinner = gridRow.querySelector(".batch-row-spinner");
|
||||||
|
if (spinner) spinner.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Validating...';
|
||||||
|
|
||||||
|
validateRows([{ iddatadb: parseInt(iddatadb), index: parseInt(rowIndex) }])
|
||||||
|
.then((validationData) => {
|
||||||
|
setRowExporting(gridRow, false);
|
||||||
|
gridRow.classList.remove("batch-disabled");
|
||||||
|
|
||||||
|
if (!validationData.success) {
|
||||||
|
showExportResult({ success: false, message: validationData.message || "Errore di validazione" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = validationData.results[rowIndex];
|
||||||
|
if (result && !result.valid) {
|
||||||
|
// Show validation errors on the row
|
||||||
|
showValidationErrors(gridRow, iddatadb, result.errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation passed — show confirm modal
|
||||||
|
showConfirmAndExport(iddatadb, btn);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setRowExporting(gridRow, false);
|
||||||
|
gridRow.classList.remove("batch-disabled");
|
||||||
|
|
||||||
|
console.error("Validation error:", error);
|
||||||
|
showExportResult({ success: false, message: "Errore di validazione: " + error.message });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showConfirmAndExport(iddatadb, btn) {
|
||||||
const confirmModalElement =
|
const confirmModalElement =
|
||||||
document.getElementById("exportConfirmModal");
|
document.getElementById("exportConfirmModal");
|
||||||
if (!confirmModalElement) {
|
if (!confirmModalElement) {
|
||||||
console.error("exportConfirmModal not found in the DOM");
|
|
||||||
alert("Errore: Modale di conferma non trovato");
|
alert("Errore: Modale di conferma non trovato");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -34,206 +378,423 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
document.getElementById("exportIddatadb").textContent = iddatadb;
|
document.getElementById("exportIddatadb").textContent = iddatadb;
|
||||||
confirmModal.show();
|
confirmModal.show();
|
||||||
|
|
||||||
// Gestisci il click su "Conferma"
|
|
||||||
const confirmBtn = document.getElementById("exportConfirmBtn");
|
const confirmBtn = document.getElementById("exportConfirmBtn");
|
||||||
if (!confirmBtn) {
|
if (!confirmBtn) {
|
||||||
console.error("exportConfirmBtn not found in the DOM");
|
|
||||||
confirmModal.hide();
|
confirmModal.hide();
|
||||||
alert("Errore: Pulsante di conferma non trovato");
|
alert("Errore: Pulsante di conferma non trovato");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirmHandler = async () => {
|
const confirmHandler = async () => {
|
||||||
|
pendingConfirmHandler = null;
|
||||||
console.log(`Confirmed export for iddatadb: ${iddatadb}`);
|
console.log(`Confirmed export for iddatadb: ${iddatadb}`);
|
||||||
confirmModal.hide();
|
confirmModal.hide();
|
||||||
|
|
||||||
const formData = new FormData();
|
const gridRow = btn.closest(".grid-row");
|
||||||
formData.append("iddatadb", iddatadb);
|
setRowExporting(gridRow, true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch("export_to_lims.php", {
|
const data = await sendExport(iddatadb, gridRow);
|
||||||
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);
|
console.log("Export response:", data);
|
||||||
|
|
||||||
// Mostra il modale di risposta
|
// Stop spinner, fully restore row
|
||||||
const responseModalElement = document.getElementById(
|
setRowExporting(gridRow, false);
|
||||||
"exportResponseModal",
|
gridRow.classList.remove("batch-disabled");
|
||||||
);
|
|
||||||
if (!responseModalElement) {
|
|
||||||
console.error(
|
|
||||||
"exportResponseModal not found in the DOM",
|
|
||||||
);
|
|
||||||
alert("Errore: Modale di risposta non trovato");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const responseModal = new bootstrap.Modal(
|
if (!data.success) {
|
||||||
responseModalElement,
|
showRowError(gridRow, iddatadb, data.message || "Errore sconosciuto");
|
||||||
{
|
|
||||||
keyboard: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const responseMessage = document.getElementById(
|
|
||||||
"exportResponseMessage",
|
|
||||||
);
|
|
||||||
if (data.success) {
|
|
||||||
responseMessage.innerHTML = `${data.message.replace(/\n/g, "<br>")}<br>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";
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
|
|
||||||
// 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 },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
showExportResult(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Export error:", error);
|
console.error("Export error:", error);
|
||||||
const responseModalElement = document.getElementById(
|
|
||||||
"exportResponseModal",
|
// Stop spinner, fully restore row so user can retry
|
||||||
|
setRowExporting(gridRow, false);
|
||||||
|
gridRow.classList.remove("batch-disabled");
|
||||||
|
|
||||||
|
showRowError(gridRow, iddatadb, error.message);
|
||||||
|
showExportResult({
|
||||||
|
success: false,
|
||||||
|
message: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pendingConfirmHandler) {
|
||||||
|
confirmBtn.removeEventListener("click", pendingConfirmHandler);
|
||||||
|
}
|
||||||
|
pendingConfirmHandler = confirmHandler;
|
||||||
|
confirmBtn.addEventListener("click", confirmHandler, { once: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Single row click handler ────────────────────────────────────────────
|
||||||
|
|
||||||
|
exportButtons.forEach((btn) => {
|
||||||
|
btn.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (batchRunning) return;
|
||||||
|
|
||||||
|
const rowIndex = btn.dataset.row;
|
||||||
|
const iddatadb = btn.dataset.iddatadb;
|
||||||
|
console.log(
|
||||||
|
`Export to LIMS clicked for row ${rowIndex}, iddatadb: ${iddatadb}`,
|
||||||
);
|
);
|
||||||
if (!responseModalElement) {
|
|
||||||
console.error(
|
const gridRow = btn.closest(".grid-row");
|
||||||
"exportResponseModal not found in the DOM",
|
|
||||||
|
if (gridRow && gridRow.querySelector(".cell-changed")) {
|
||||||
|
const unsavedModal = new bootstrap.Modal(
|
||||||
|
document.getElementById("exportUnsavedModal"),
|
||||||
|
{ keyboard: false },
|
||||||
);
|
);
|
||||||
alert("Errore: Modale di risposta non trovato");
|
unsavedModal.show();
|
||||||
|
|
||||||
|
document.getElementById("saveAndExportBtn").addEventListener(
|
||||||
|
"click",
|
||||||
|
() => {
|
||||||
|
unsavedModal.hide();
|
||||||
|
const saveBtn = gridRow.querySelector(".save-btn");
|
||||||
|
if (!saveBtn) return;
|
||||||
|
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
if (!gridRow.querySelector(".cell-changed")) {
|
||||||
|
observer.disconnect();
|
||||||
|
startExportConfirmFlow(iddatadb, btn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
observer.observe(gridRow, {
|
||||||
|
subtree: true,
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ["class"],
|
||||||
|
});
|
||||||
|
|
||||||
|
saveBtn.click();
|
||||||
|
},
|
||||||
|
{ once: true },
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const responseModal = new bootstrap.Modal(
|
|
||||||
responseModalElement,
|
// No unsaved changes — go straight to validate + export confirm
|
||||||
{
|
startExportConfirmFlow(iddatadb, btn);
|
||||||
keyboard: false,
|
});
|
||||||
},
|
});
|
||||||
|
|
||||||
|
// ── Batch export (Export All) ───────────────────────────────────────────
|
||||||
|
|
||||||
|
const exportAllBtn = document.querySelector(".export-all-lims-btn");
|
||||||
|
if (!exportAllBtn) return;
|
||||||
|
|
||||||
|
let batchCancelled = false;
|
||||||
|
let pendingBatchConfirmHandler = null;
|
||||||
|
|
||||||
|
function collectEligibleRows() {
|
||||||
|
const allRows = document.querySelectorAll(".grid-row[data-id]");
|
||||||
|
const eligible = [];
|
||||||
|
allRows.forEach((row) => {
|
||||||
|
const statusBadge = row.querySelector(
|
||||||
|
'.grid-cell[data-col="status"] .status-badge',
|
||||||
);
|
);
|
||||||
|
if (statusBadge && !statusBadge.classList.contains("status-l")) {
|
||||||
|
const iddatadb = row.dataset.id;
|
||||||
|
if (iddatadb) {
|
||||||
|
eligible.push({ iddatadb, row });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return eligible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the data-row index for a grid row element.
|
||||||
|
*/
|
||||||
|
function getRowIndex(row) {
|
||||||
|
const cell = row.querySelector(".grid-cell[data-row]");
|
||||||
|
return cell ? parseInt(cell.getAttribute("data-row")) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasUnsavedChanges() {
|
||||||
|
return !!document.querySelector(".grid-row[data-id] .cell-changed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate all eligible rows, show errors, and return only the valid ones.
|
||||||
|
*/
|
||||||
|
async function validateAndFilter(eligibleRows) {
|
||||||
|
const rowsToValidate = eligibleRows.map(({ iddatadb, row }) => ({
|
||||||
|
iddatadb: parseInt(iddatadb),
|
||||||
|
index: getRowIndex(row),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const validationData = await validateRows(rowsToValidate);
|
||||||
|
|
||||||
|
if (!validationData.success) {
|
||||||
|
throw new Error(validationData.message || "Errore di validazione");
|
||||||
|
}
|
||||||
|
|
||||||
|
const validRows = [];
|
||||||
|
let invalidCount = 0;
|
||||||
|
|
||||||
|
for (const { iddatadb, row } of eligibleRows) {
|
||||||
|
const rowIdx = getRowIndex(row);
|
||||||
|
const result = validationData.results[rowIdx];
|
||||||
|
|
||||||
|
if (result && !result.valid) {
|
||||||
|
showValidationErrors(row, iddatadb, result.errors);
|
||||||
|
invalidCount++;
|
||||||
|
} else {
|
||||||
|
validRows.push({ iddatadb, row });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { validRows, invalidCount };
|
||||||
|
}
|
||||||
|
|
||||||
|
function showValidationSpinner(show) {
|
||||||
|
const bar = document.getElementById("batchExportBar");
|
||||||
|
const statusEl = document.getElementById("batchExportStatus");
|
||||||
|
const cancelBtn = document.getElementById("exportBatchCancelBtn");
|
||||||
|
if (show) {
|
||||||
|
bar.style.display = "";
|
||||||
|
statusEl.textContent = "Validazione in corso...";
|
||||||
|
cancelBtn.style.display = "none";
|
||||||
|
} else {
|
||||||
|
bar.style.display = "none";
|
||||||
|
cancelBtn.style.display = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showBatchConfirm(eligibleRows) {
|
||||||
|
clearValidationErrors();
|
||||||
|
showValidationSpinner(true);
|
||||||
|
|
||||||
|
// Validate before showing confirm
|
||||||
|
validateAndFilter(eligibleRows)
|
||||||
|
.then(({ validRows, invalidCount }) => {
|
||||||
|
showValidationSpinner(false);
|
||||||
|
if (validRows.length === 0) {
|
||||||
|
document.getElementById("exportResponseMessage").innerHTML =
|
||||||
|
`Nessuna riga valida per l'esportazione.<br>` +
|
||||||
|
`<strong>${invalidCount}</strong> righe con errori di validazione.`;
|
||||||
|
document.getElementById("exportResponseModalLabel").textContent =
|
||||||
|
"Validazione Fallita";
|
||||||
|
new bootstrap.Modal(
|
||||||
|
document.getElementById("exportResponseModal"),
|
||||||
|
{ keyboard: false },
|
||||||
|
).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmModal = new bootstrap.Modal(
|
||||||
|
document.getElementById("exportBatchConfirmModal"),
|
||||||
|
{ keyboard: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
let countText = String(validRows.length);
|
||||||
|
if (invalidCount > 0) {
|
||||||
|
countText += ` (${invalidCount} escluse per errori di validazione)`;
|
||||||
|
}
|
||||||
|
document.getElementById("exportBatchCount").textContent = countText;
|
||||||
|
confirmModal.show();
|
||||||
|
|
||||||
|
const confirmBtn = document.getElementById("exportBatchConfirmBtn");
|
||||||
|
if (pendingBatchConfirmHandler) {
|
||||||
|
confirmBtn.removeEventListener("click", pendingBatchConfirmHandler);
|
||||||
|
}
|
||||||
|
pendingBatchConfirmHandler = () => {
|
||||||
|
pendingBatchConfirmHandler = null;
|
||||||
|
confirmModal.hide();
|
||||||
|
startBatchExport(validRows);
|
||||||
|
};
|
||||||
|
confirmBtn.addEventListener("click", pendingBatchConfirmHandler, { once: true });
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
showValidationSpinner(false);
|
||||||
|
console.error("Batch validation error:", error);
|
||||||
|
document.getElementById("exportResponseMessage").textContent =
|
||||||
|
"Errore di validazione: " + error.message;
|
||||||
|
document.getElementById("exportResponseModalLabel").textContent =
|
||||||
|
"Errore Validazione";
|
||||||
|
new bootstrap.Modal(
|
||||||
|
document.getElementById("exportResponseModal"),
|
||||||
|
{ keyboard: false },
|
||||||
|
).show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
exportAllBtn.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (batchRunning) return;
|
||||||
|
|
||||||
|
// Check unsaved changes first
|
||||||
|
if (hasUnsavedChanges()) {
|
||||||
|
const unsavedModal = new bootstrap.Modal(
|
||||||
|
document.getElementById("exportBatchUnsavedModal"),
|
||||||
|
{ keyboard: false },
|
||||||
|
);
|
||||||
|
unsavedModal.show();
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("batchSaveAndExportBtn")
|
||||||
|
.addEventListener(
|
||||||
|
"click",
|
||||||
|
() => {
|
||||||
|
unsavedModal.hide();
|
||||||
|
// Trigger Save All, then proceed
|
||||||
|
const saveAllEl =
|
||||||
|
document.querySelector(".save-all-btn");
|
||||||
|
if (!saveAllEl) return;
|
||||||
|
|
||||||
|
// Watch for all .cell-changed to disappear
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
if (!hasUnsavedChanges()) {
|
||||||
|
observer.disconnect();
|
||||||
|
const eligibleRows = collectEligibleRows();
|
||||||
|
if (eligibleRows.length === 0) {
|
||||||
document.getElementById(
|
document.getElementById(
|
||||||
"exportResponseMessage",
|
"exportResponseMessage",
|
||||||
).textContent =
|
).textContent =
|
||||||
`Errore durante la generazione dei payload: ${error.message}`;
|
"Tutte le righe sono già state esportate al LIMS.";
|
||||||
document.getElementById(
|
document.getElementById(
|
||||||
"exportResponseModalLabel",
|
"exportResponseModalLabel",
|
||||||
).textContent = "Errore Esportazione";
|
).textContent = "Export All";
|
||||||
responseModal.show();
|
new bootstrap.Modal(
|
||||||
|
document.getElementById(
|
||||||
// Gestisci la chiusura del modale di risposta in caso di errore
|
"exportResponseModal",
|
||||||
responseModalElement.addEventListener(
|
),
|
||||||
"hidden.bs.modal",
|
{ keyboard: false },
|
||||||
() => {
|
).show();
|
||||||
console.log(
|
return;
|
||||||
"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";
|
|
||||||
}
|
}
|
||||||
|
showBatchConfirm(eligibleRows);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
observer.observe(
|
||||||
|
document.querySelector(".grid-container"),
|
||||||
|
{
|
||||||
|
subtree: true,
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ["class"],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
saveAllEl.click();
|
||||||
},
|
},
|
||||||
{ once: true },
|
{ once: true },
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rimuovi il listener dopo l'esecuzione
|
const eligibleRows = collectEligibleRows();
|
||||||
confirmBtn.removeEventListener("click", confirmHandler);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Rimuovi eventuali listener precedenti
|
if (eligibleRows.length === 0) {
|
||||||
confirmBtn.removeEventListener("click", confirmHandler);
|
document.getElementById("exportResponseMessage").textContent =
|
||||||
confirmBtn.addEventListener("click", confirmHandler);
|
"Tutte le righe sono già state esportate al LIMS.";
|
||||||
});
|
document.getElementById("exportResponseModalLabel").textContent =
|
||||||
|
"Export All";
|
||||||
|
const modal = new bootstrap.Modal(
|
||||||
|
document.getElementById("exportResponseModal"),
|
||||||
|
{ keyboard: false },
|
||||||
|
);
|
||||||
|
modal.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showBatchConfirm(eligibleRows);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function startBatchExport(eligibleRows) {
|
||||||
|
batchCancelled = false;
|
||||||
|
batchRunning = true;
|
||||||
|
// Don't clear validation errors — they should stay visible for invalid rows
|
||||||
|
const batchUuid = crypto.randomUUID();
|
||||||
|
const total = eligibleRows.length;
|
||||||
|
let processed = 0;
|
||||||
|
let succeeded = 0;
|
||||||
|
let failed = 0;
|
||||||
|
|
||||||
|
// Disable all row buttons
|
||||||
|
disableAllRowButtons();
|
||||||
|
|
||||||
|
// Show inline status bar
|
||||||
|
const bar = document.getElementById("batchExportBar");
|
||||||
|
const statusEl = document.getElementById("batchExportStatus");
|
||||||
|
const cancelBtn = document.getElementById("exportBatchCancelBtn");
|
||||||
|
bar.style.display = "";
|
||||||
|
cancelBtn.disabled = false;
|
||||||
|
statusEl.textContent = `Esportazione 0 / ${total}...`;
|
||||||
|
|
||||||
|
// Cancel handler
|
||||||
|
cancelBtn.addEventListener(
|
||||||
|
"click",
|
||||||
|
() => {
|
||||||
|
batchCancelled = true;
|
||||||
|
statusEl.textContent =
|
||||||
|
"Annullamento... (attendi riga corrente)";
|
||||||
|
cancelBtn.disabled = true;
|
||||||
|
},
|
||||||
|
{ once: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
for (let i = 0; i < eligibleRows.length; i++) {
|
||||||
|
if (batchCancelled) break;
|
||||||
|
|
||||||
|
const { iddatadb, row } = eligibleRows[i];
|
||||||
|
statusEl.textContent = `Esportazione ${processed + 1} / ${total} (id: ${iddatadb})...`;
|
||||||
|
|
||||||
|
// Highlight current row
|
||||||
|
setRowExporting(row, true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await sendExport(iddatadb, row, batchUuid);
|
||||||
|
processed++;
|
||||||
|
if (data.success) {
|
||||||
|
succeeded++;
|
||||||
|
} else {
|
||||||
|
failed++;
|
||||||
|
showRowError(row, iddatadb, data.message || "Errore sconosciuto");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
processed++;
|
||||||
|
failed++;
|
||||||
|
showRowError(row, iddatadb, error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove highlight from current row
|
||||||
|
setRowExporting(row, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finished
|
||||||
|
batchRunning = false;
|
||||||
|
enableAllRowButtons();
|
||||||
|
bar.style.display = "none";
|
||||||
|
|
||||||
|
// Show result modal
|
||||||
|
const msgEl = document.getElementById("exportResponseMessage");
|
||||||
|
const labelEl = document.getElementById("exportResponseModalLabel");
|
||||||
|
|
||||||
|
if (batchCancelled) {
|
||||||
|
labelEl.textContent = "Export All — Annullato";
|
||||||
|
msgEl.innerHTML =
|
||||||
|
`Esportate: <strong>${succeeded}</strong><br>` +
|
||||||
|
`Errori: <strong>${failed}</strong><br>` +
|
||||||
|
`Non processate: <strong>${total - processed}</strong>`;
|
||||||
|
} else if (failed === 0) {
|
||||||
|
labelEl.textContent = "Export All — Completato";
|
||||||
|
msgEl.innerHTML = `Tutte le <strong>${succeeded}</strong> righe esportate con successo.`;
|
||||||
|
} else {
|
||||||
|
labelEl.textContent = "Export All — Completato con errori";
|
||||||
|
msgEl.innerHTML =
|
||||||
|
`Esportate: <strong>${succeeded}</strong><br>` +
|
||||||
|
`Errori: <strong>${failed}</strong>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modalEl = document.getElementById("exportResponseModal");
|
||||||
|
const modal = new bootstrap.Modal(modalEl, { keyboard: false });
|
||||||
|
modal.show();
|
||||||
|
modalEl.addEventListener("hidden.bs.modal", cleanupBackdrop, { once: true });
|
||||||
|
})();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,9 +13,31 @@ if (!is_dir($logDir)) {
|
|||||||
mkdir($logDir, 0755, true);
|
mkdir($logDir, 0755, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$uploadDir = realpath(__DIR__ . '/../photostrf') . '/';
|
||||||
|
|
||||||
// 🔹 Base URL API
|
// 🔹 Base URL API
|
||||||
$apiBaseUrl = 'https://93.43.5.102/limsapi/api/odata/';
|
$apiBaseUrl = 'https://93.43.5.102/limsapi/api/odata/';
|
||||||
|
|
||||||
|
// 🔹 Batch UUID — if present, all logs go to a single file
|
||||||
|
$batchUuid = $_POST['batch_uuid'] ?? null;
|
||||||
|
|
||||||
|
$writeLog = (function () use ($batchUuid, $logDir) {
|
||||||
|
$batchLogFile = $batchUuid ? $logDir . "batch_export_{$batchUuid}.log" : null;
|
||||||
|
|
||||||
|
return function ($individualPath, $content, $stepLabel = null) use ($batchLogFile) {
|
||||||
|
if ($batchLogFile) {
|
||||||
|
$header = "\n" . str_repeat("=", 60) . "\n";
|
||||||
|
if ($stepLabel) {
|
||||||
|
$header .= "[{$stepLabel}] " . date('Y-m-d H:i:s') . "\n";
|
||||||
|
}
|
||||||
|
$header .= str_repeat("=", 60) . "\n";
|
||||||
|
file_put_contents($batchLogFile, $header . $content . "\n", FILE_APPEND);
|
||||||
|
} else {
|
||||||
|
file_put_contents($individualPath, $content);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
// 🔹 Funzione per validare e convertire date
|
// 🔹 Funzione per validare e convertire date
|
||||||
function validateDate($value)
|
function validateDate($value)
|
||||||
{
|
{
|
||||||
@@ -27,15 +49,38 @@ function validateDate($value)
|
|||||||
return null; // Imposta null se non è una data valida
|
return null; // Imposta null se non è una data valida
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔹 Funzione per validare e convertire date
|
||||||
|
function formatDateToExport($value)
|
||||||
|
{
|
||||||
|
$date = DateTime::createFromFormat('Y-m-d', $value) ?: DateTime::createFromFormat('Y-m-d H:i:s', $value);
|
||||||
|
if ($date) {
|
||||||
|
return $date->format('d/m/Y');
|
||||||
|
}
|
||||||
|
return null; // Imposta null se non è una data valida
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$iddatadb = $_POST['iddatadb'] ?? null;
|
$iddatadb = $_POST['iddatadb'] ?? null;
|
||||||
if (!$iddatadb) {
|
if (!$iddatadb) {
|
||||||
throw new Exception("Missing iddatadb");
|
throw new Exception("Missing iddatadb");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TEMP: simulate error on every other row for testing
|
||||||
|
if (env('SIMULATE_EXPORT_LIMS') && $iddatadb % 2 === 0) {
|
||||||
|
throw new Exception("Simulated error for iddatadb $iddatadb");
|
||||||
|
}
|
||||||
|
|
||||||
// 🔹 STEP 1+2: Fetch Cliente ID from datadb and Schema ID from excel_templates
|
// 🔹 STEP 1+2: Fetch Cliente ID from datadb and Schema ID from excel_templates
|
||||||
|
// Also fetch fixed fields stored in datadb
|
||||||
$stmt = $pdo->prepare("
|
$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,
|
||||||
|
d.clienteAnalisi,
|
||||||
|
d.consegna_richiesta
|
||||||
FROM datadb as d
|
FROM datadb as d
|
||||||
INNER JOIN excel_templates as et ON d.templateid = et.id
|
INNER JOIN excel_templates as et ON d.templateid = et.id
|
||||||
WHERE d.iddatadb = :iddatadb
|
WHERE d.iddatadb = :iddatadb
|
||||||
@@ -51,15 +96,58 @@ try {
|
|||||||
$clienteId = (int) $result['clienteId'];
|
$clienteId = (int) $result['clienteId'];
|
||||||
$schemaId = (int) $result['schemaId'];
|
$schemaId = (int) $result['schemaId'];
|
||||||
|
|
||||||
// 🔹 STEP 3: Fetch Parts (including idmatrice)
|
// 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;
|
||||||
|
$clienteAnalisi = !empty($result['clienteAnalisi']) ? (int) $result['clienteAnalisi'] : null;
|
||||||
|
$consegnaRichiesta = !empty($result['consegna_richiesta']) ? $result['consegna_richiesta'] : null;
|
||||||
|
|
||||||
|
// 🔹 STEP 3: Fetch Parts (including idmatrice and part id for custom fields)
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
SELECT part_number, part_description, material, color, mix, idmatrice
|
SELECT id AS part_id, part_number, part_description, material, color, mix, idmatrice, dateexpiry
|
||||||
FROM identification_parts
|
FROM identification_parts
|
||||||
WHERE iddatadb = :iddatadb
|
WHERE iddatadb = :iddatadb
|
||||||
|
ORDER BY CAST(part_number AS UNSIGNED) ASC, part_number ASC
|
||||||
");
|
");
|
||||||
$stmt->execute(['iddatadb' => $iddatadb]);
|
$stmt->execute(['iddatadb' => $iddatadb]);
|
||||||
$parts = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$parts = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// 🔹 STEP 3.1: Fetch custom field values per part from identification_parts_customfields
|
||||||
|
$partIds = array_column($parts, 'part_id');
|
||||||
|
$partsCustomFields = []; // part_id => [ { field_id, value_id, value_text }, ... ]
|
||||||
|
if (!empty($partIds)) {
|
||||||
|
$placeholders = implode(',', array_fill(0, count($partIds), '?'));
|
||||||
|
$cfStmt = $pdo->prepare("
|
||||||
|
SELECT part_id, field_id, value_id, value_text
|
||||||
|
FROM identification_parts_customfields
|
||||||
|
WHERE part_id IN ({$placeholders})
|
||||||
|
");
|
||||||
|
$cfStmt->execute($partIds);
|
||||||
|
foreach ($cfStmt->fetchAll(PDO::FETCH_ASSOC) as $cfRow) {
|
||||||
|
$partsCustomFields[(int)$cfRow['part_id']][] = $cfRow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔹 STEP 4a: Auto-populate export_date / export_time fields
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE import_data_details idd
|
||||||
|
JOIN template_mapping m ON idd.mapping_id = m.id
|
||||||
|
SET idd.field_value = CASE m.auto_value
|
||||||
|
WHEN 'export_date' THEN :export_date
|
||||||
|
WHEN 'export_time' THEN :export_time
|
||||||
|
END
|
||||||
|
WHERE idd.id = :iddatadb
|
||||||
|
AND m.auto_value IN ('export_date', 'export_time')
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
'iddatadb' => $iddatadb,
|
||||||
|
'export_date' => date('Y-m-d'),
|
||||||
|
'export_time' => date('H:i'),
|
||||||
|
]);
|
||||||
|
|
||||||
// 🔹 STEP 4: Fetch Field Values with Labels
|
// 🔹 STEP 4: Fetch Field Values with Labels
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
SELECT
|
SELECT
|
||||||
@@ -93,12 +181,21 @@ try {
|
|||||||
// 🔹 Initialize API client
|
// 🔹 Initialize API client
|
||||||
$api = VisualLimsApiClient::getInstance();
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
|
||||||
// 🔹 STEP 5: Create CommessaWeb (NOT WebOrder)
|
// 🔹 STEP 5: Create CommessaWeb
|
||||||
|
// Fixed fields are sent as direct properties — they are navigation properties on Commessa
|
||||||
|
// accepted by the XAF API even though not explicitly listed in OData $metadata
|
||||||
$commessaWebPayload = [
|
$commessaWebPayload = [
|
||||||
"Cliente" => $clienteId,
|
"Cliente" => $clienteId,
|
||||||
"SchemaCustomField" => $schemaId,
|
"SchemaCustomField" => $schemaId,
|
||||||
"Richiedente" => "Test Web Import",
|
"Richiedente" => "From TRFSmart Application", // TODO: replace with real value
|
||||||
"Descrizione" => "TEST CommessaWeb",
|
"Descrizione" => "From TRFSmart Application", // TODO: replace with real value
|
||||||
|
"ClienteResponsabile" => $clienteResponsabile,
|
||||||
|
"MoltiplicatorePrezzo" => $moltiplicatorePrezzo,
|
||||||
|
"AnagraficaCertestObject" => $anagraficaObject,
|
||||||
|
"AnagraficaCertestService" => $anagraficaService,
|
||||||
|
"ClienteFornitore" => $clienteFornitore, // PLACEHOLDER — to be implemented
|
||||||
|
"ClienteAnalisi" => $clienteAnalisi, // PLACEHOLDER — to be implemented
|
||||||
|
// DeliveryRequest goes to Campione, not CommessaWeb
|
||||||
];
|
];
|
||||||
|
|
||||||
// Costruisci log curl-like per STEP 5
|
// Costruisci log curl-like per STEP 5
|
||||||
@@ -114,7 +211,7 @@ try {
|
|||||||
|
|
||||||
// Salva log
|
// Salva log
|
||||||
$logFileStep5 = $logDir . "commessa_create_step5_" . $iddatadb . "_" . time() . ".txt";
|
$logFileStep5 = $logDir . "commessa_create_step5_" . $iddatadb . "_" . time() . ".txt";
|
||||||
file_put_contents($logFileStep5, $logContentStep5);
|
$writeLog($logFileStep5, $logContentStep5, "STEP 5 - Create CommessaWeb (iddatadb={$iddatadb})");
|
||||||
|
|
||||||
$commessaId = $commessaWeb["IdCommessa"];
|
$commessaId = $commessaWeb["IdCommessa"];
|
||||||
$commessaWebCode = substr($commessaWeb["CodiceCommessa"] ?? "TEST CommessaWeb", 0, 30); // Limite a 30 caratteri
|
$commessaWebCode = substr($commessaWeb["CodiceCommessa"] ?? "TEST CommessaWeb", 0, 30); // Limite a 30 caratteri
|
||||||
@@ -135,7 +232,9 @@ try {
|
|||||||
"Matrice" => $matriceId,
|
"Matrice" => $matriceId,
|
||||||
"SottoMatrice" => null,
|
"SottoMatrice" => null,
|
||||||
"SchemaCustomField" => $schemaId,
|
"SchemaCustomField" => $schemaId,
|
||||||
"NoteWeb" => $part["part_description"] ?? ""
|
// "Riferimento" => $part["part_description"] ?? "",
|
||||||
|
"NoteWeb" => $part["part_description"] ?? "",
|
||||||
|
"ConsegnaRichiesta" => !empty($part["dateexpiry"]) ? $part["dateexpiry"] : $consegnaRichiesta,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Costruisci curl-like per questo campione
|
// Costruisci curl-like per questo campione
|
||||||
@@ -160,20 +259,191 @@ try {
|
|||||||
|
|
||||||
// Salva log per STEP 6
|
// Salva log per STEP 6
|
||||||
$logFileStep6 = $logDir . "commessa_{$commessaId}_campioni_step6_" . time() . ".txt";
|
$logFileStep6 = $logDir . "commessa_{$commessaId}_campioni_step6_" . time() . ".txt";
|
||||||
file_put_contents($logFileStep6, $logContentStep6);
|
$writeLog($logFileStep6, $logContentStep6, "STEP 6 - Campioni (commessa={$commessaId})");
|
||||||
|
|
||||||
|
// 🔹 STEP 6.0: PATCH each Campione custom fields:
|
||||||
|
// - field 189 (Tested Component) = part_description
|
||||||
|
// - additional fields from identification_parts_customfields (field_id → value_id/value_text)
|
||||||
|
$logContentStep63 = "";
|
||||||
|
foreach ($campioni as $index => $campione) {
|
||||||
|
$campioneId = (int)($campione['IdCampione'] ?? 0);
|
||||||
|
$partDescription = $parts[$index]['part_description'] ?? '';
|
||||||
|
$partId = (int)($parts[$index]['part_id'] ?? 0);
|
||||||
|
|
||||||
|
if ($campioneId <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a map of overrides: IdCustomField => value
|
||||||
|
$overrides = [];
|
||||||
|
|
||||||
|
// Override 1: Tested Component (field 189) = part_description
|
||||||
|
if ($partDescription !== '') {
|
||||||
|
$overrides[189] = $partDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override 2: values from identification_parts_customfields
|
||||||
|
$partCFs = $partsCustomFields[$partId] ?? [];
|
||||||
|
foreach ($partCFs as $pcf) {
|
||||||
|
$cfFieldId = (int)$pcf['field_id'];
|
||||||
|
$cfValue = $pcf['value_text'] ?? $pcf['value_id'] ?? null;
|
||||||
|
if ($cfFieldId > 0 && $cfValue !== null && $cfValue !== '') {
|
||||||
|
$overrides[$cfFieldId] = (string)$cfValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if nothing to override
|
||||||
|
if (empty($overrides)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET campione custom fields
|
||||||
|
$campioneWithFields = $api->get("Campione({$campioneId})?\$expand=CampioniCustomFields(\$expand=CustomField)");
|
||||||
|
|
||||||
|
$logContentStep63 .= "GET Campione({$campioneId}) CustomFields:\n" .
|
||||||
|
json_encode($campioneWithFields['CampioniCustomFields'] ?? [], JSON_PRETTY_PRINT) . "\n\n";
|
||||||
|
$logContentStep63 .= "Overrides for part {$partId}: " . json_encode($overrides) . "\n\n";
|
||||||
|
|
||||||
|
// Build PATCH payload — apply overrides where IdCustomField matches
|
||||||
|
$campioniCustomFields = [];
|
||||||
|
foreach ($campioneWithFields["CampioniCustomFields"] ?? [] as $cf) {
|
||||||
|
$definitionId = (int)($cf["CustomField"]["IdCustomField"] ?? 0);
|
||||||
|
$fieldInstanceId = (int)$cf["IdCampioniCustomFields"];
|
||||||
|
$currentValue = $cf["Valore"] ?? '';
|
||||||
|
|
||||||
|
$newValue = $overrides[$definitionId] ?? $currentValue;
|
||||||
|
|
||||||
|
$campioniCustomFields[] = [
|
||||||
|
"IdCampioniCustomFields" => $fieldInstanceId,
|
||||||
|
"Valore" => $newValue
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($campioniCustomFields)) {
|
||||||
|
$patchPayload = ["CampioniCustomFields" => $campioniCustomFields];
|
||||||
|
$patchJson = json_encode($patchPayload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||||
|
|
||||||
|
$logContentStep63 .= "PATCH Campione({$campioneId}):\n" .
|
||||||
|
"curl --location --request PATCH '{$apiBaseUrl}Campione({$campioneId})' \\\n" .
|
||||||
|
"--header 'Content-Type: application/json' \\\n" .
|
||||||
|
"--header 'Authorization: Bearer ••••••' \\\n" .
|
||||||
|
"--data '{$patchJson}'\n\n";
|
||||||
|
|
||||||
|
$patchResult = $api->patch("Campione({$campioneId})", $patchPayload);
|
||||||
|
|
||||||
|
$logContentStep63 .= "RESPONSE:\n" . json_encode($patchResult, JSON_PRETTY_PRINT) . "\n\n---\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$logFileStep63 = $logDir . "commessa_{$commessaId}_campioni_customfields_step60_" . time() . ".txt";
|
||||||
|
$writeLog($logFileStep63, $logContentStep63, "STEP 6.0 - Campioni CustomFields (commessa={$commessaId})");
|
||||||
|
|
||||||
|
// 🔹 STEP 6.1: Fetch photos linked to this iddatadb
|
||||||
|
$stmtPhotos = $pdo->prepare("
|
||||||
|
SELECT id, file_path, file_name, StampaNelRapporto, PrimaPagina
|
||||||
|
FROM datadb_photos
|
||||||
|
WHERE iddatadb = :iddatadb
|
||||||
|
ORDER BY id ASC
|
||||||
|
");
|
||||||
|
$stmtPhotos->execute(['iddatadb' => $iddatadb]);
|
||||||
|
$photos = $stmtPhotos->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
|
||||||
|
// 🔹 STEP 6.2: Upload photos to Campione .01 (fetched from API)
|
||||||
|
$photosUploaded = 0;
|
||||||
|
$logContentPhotos = "Photos for CommessaWeb {$commessaId} (iddatadb={$iddatadb}):\n";
|
||||||
|
$logContentPhotos .= "Total photos found: " . count($photos) . ", campioni: " . count($campioni) . "\n\n";
|
||||||
|
|
||||||
|
if (!empty($campioni) && !empty($photos)) {
|
||||||
|
// Fetch campioni list from API to find the .01 campione
|
||||||
|
$commessaCampioni = $api->get("CommessaWeb({$commessaId})?\$expand=Campioni");
|
||||||
|
$apiCampioni = $commessaCampioni['Campioni'] ?? [];
|
||||||
|
|
||||||
|
// Sort by CodiceCampione to find .01
|
||||||
|
usort($apiCampioni, fn($a, $b) => strcmp($a['CodiceCampione'] ?? '', $b['CodiceCampione'] ?? ''));
|
||||||
|
|
||||||
|
$mainCampione = $apiCampioni[0] ?? null;
|
||||||
|
$campioneId = (int)($mainCampione['IdCampione'] ?? 0);
|
||||||
|
|
||||||
|
$logContentPhotos .= "API Campioni order:\n";
|
||||||
|
foreach ($apiCampioni as $ac) {
|
||||||
|
$logContentPhotos .= " - {$ac['CodiceCampione']} (IdCampione: {$ac['IdCampione']})\n";
|
||||||
|
}
|
||||||
|
$logContentPhotos .= "Selected .01 campione: IdCampione={$campioneId}\n\n";
|
||||||
|
|
||||||
|
if ($campioneId > 0) {
|
||||||
|
$logContentPhotos .= "=== Campione {$campioneId} (main) ===\n";
|
||||||
|
|
||||||
|
foreach ($photos as $photo) {
|
||||||
|
$photoPath = $uploadDir . '/' . ltrim($photo['file_path'], './');
|
||||||
|
$fullPath = realpath($photoPath);
|
||||||
|
|
||||||
|
if (!$fullPath || !file_exists($fullPath)) {
|
||||||
|
$logContentPhotos .= "SKIP (file not found): {$photoPath}\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$photoEndpoint = "Campione({$campioneId})/UploadCampioneFile";
|
||||||
|
$stampaNelRapporto = !empty($photo['StampaNelRapporto']);
|
||||||
|
$primaPagina = !empty($photo['PrimaPagina']);
|
||||||
|
|
||||||
|
$logContentPhotos .= "curl --location --request POST '{$apiBaseUrl}{$photoEndpoint}' \\\n" .
|
||||||
|
"--header 'Authorization: Bearer ••••••' \\\n" .
|
||||||
|
"--form 'file=@{$fullPath}'\n\n";
|
||||||
|
|
||||||
|
// Step 1: Upload file (flags are ignored by API during upload)
|
||||||
|
$photoResult = $api->postMultipart($photoEndpoint, $fullPath, $photo['file_name']);
|
||||||
|
$logContentPhotos .= "UPLOAD RESPONSE:\n" . json_encode($photoResult, JSON_PRETTY_PRINT) . "\n\n";
|
||||||
|
|
||||||
|
// Step 2: PATCH CampioneFile to set flags (StampaNelRapporto, PrimaPagina)
|
||||||
|
$campioneFileId = (int)($photoResult['IdCampioneFile'] ?? 0);
|
||||||
|
if ($campioneFileId > 0 && ($stampaNelRapporto || $primaPagina)) {
|
||||||
|
$patchPayload = [];
|
||||||
|
|
||||||
|
if ($stampaNelRapporto) {
|
||||||
|
$patchPayload['StampaNelRapporto'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($primaPagina) {
|
||||||
|
$patchPayload['PrimaPagina'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$patchEndpoint = "CampioneFile({$campioneFileId})";
|
||||||
|
$patchJsonLog = json_encode($patchPayload, JSON_PRETTY_PRINT);
|
||||||
|
$logContentPhotos .= "curl --location --request PATCH '{$apiBaseUrl}{$patchEndpoint}' \\\n" .
|
||||||
|
"--header 'Content-Type: application/json' \\\n" .
|
||||||
|
"--header 'Authorization: Bearer ••••••' \\\n" .
|
||||||
|
"--data '{$patchJsonLog}'\n\n";
|
||||||
|
|
||||||
|
$patchResult = $api->patch($patchEndpoint, $patchPayload);
|
||||||
|
$logContentPhotos .= "PATCH RESPONSE:\n" . json_encode($patchResult, JSON_PRETTY_PRINT) . "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$logContentPhotos .= "---\n";
|
||||||
|
$photosUploaded++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$logContentPhotos .= "SKIP: main campione has invalid IdCampione\n";
|
||||||
|
}
|
||||||
|
} elseif (empty($campioni)) {
|
||||||
|
$logContentPhotos .= "SKIP: no campioni created, cannot upload photos\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$logFilePhotos = $logDir . "commessa_{$commessaId}_photos_step5_2_" . time() . ".txt";
|
||||||
|
$writeLog($logFilePhotos, $logContentPhotos, "STEP 6.2 - Photos (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
|
||||||
$expand = "CommesseCustomFields(\$expand=CustomField)";
|
$expand = "CommesseCustomFields(\$expand=CustomField)";
|
||||||
$commessaWithFields = $api->get("CommessaWeb(" . $commessaId . ")?\$expand=" . $expand);
|
$commessaWithFields = $api->get("CommessaWeb({$commessaId})?\$expand={$expand}");
|
||||||
|
|
||||||
// Logga il GET
|
// Logga il GET
|
||||||
$logContentGet = "curl --location --request GET '{$apiBaseUrl}CommessaWeb({$commessaId})?\$expand=CommesseCustomFields(\$expand=CustomField)' \\\n" .
|
$logContentGet = "curl --location --request GET '{$apiBaseUrl}CommessaWeb({$commessaId})?\$expand=CommesseCustomFields(\$expand=CustomField)' \\\n" .
|
||||||
"--header 'Authorization: Bearer ••••••'\n\n" .
|
"--header 'Authorization: Bearer ••••••'\n\n" .
|
||||||
"RESPONSE:\n" . json_encode($commessaWithFields, JSON_PRETTY_PRINT);
|
"RESPONSE:\n" . json_encode($commessaWithFields, JSON_PRETTY_PRINT);
|
||||||
$logFileGet = $logDir . "commessa_{$commessaId}_get_step7_" . time() . ".txt";
|
$logFileGet = $logDir . "commessa_{$commessaId}_get_step7_" . time() . ".txt";
|
||||||
file_put_contents($logFileGet, $logContentGet);
|
$writeLog($logFileGet, $logContentGet, "STEP 7 - GET CustomFields (commessa={$commessaId})");
|
||||||
|
|
||||||
// Prepara payload PATCH
|
// Prepara payload PATCH
|
||||||
$commessaCustomFields = [];
|
$commessaCustomFields = [];
|
||||||
@@ -186,7 +456,7 @@ try {
|
|||||||
|
|
||||||
// Valida se il campo è di tipo Data
|
// Valida se il campo è di tipo Data
|
||||||
if ($fieldType === 'Data' && $newValue !== $currentValue) {
|
if ($fieldType === 'Data' && $newValue !== $currentValue) {
|
||||||
$newValue = validateDate($newValue);
|
$newValue = formatDateToExport($newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
$commessaCustomFields[] = [
|
$commessaCustomFields[] = [
|
||||||
@@ -195,6 +465,7 @@ try {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$logFileStep7 = null;
|
||||||
if (!empty($commessaCustomFields)) {
|
if (!empty($commessaCustomFields)) {
|
||||||
$updatePayload = ["CommesseCustomFields" => $commessaCustomFields];
|
$updatePayload = ["CommesseCustomFields" => $commessaCustomFields];
|
||||||
|
|
||||||
@@ -209,7 +480,7 @@ try {
|
|||||||
|
|
||||||
$logContentStep7 .= "\n\nRESPONSE:\n" . json_encode($patchResponse, JSON_PRETTY_PRINT);
|
$logContentStep7 .= "\n\nRESPONSE:\n" . json_encode($patchResponse, JSON_PRETTY_PRINT);
|
||||||
$logFileStep7 = $logDir . "commessa_{$commessaId}_update_step7_" . time() . ".txt";
|
$logFileStep7 = $logDir . "commessa_{$commessaId}_update_step7_" . time() . ".txt";
|
||||||
file_put_contents($logFileStep7, $logContentStep7);
|
$writeLog($logFileStep7, $logContentStep7, "STEP 7 - PATCH CustomFields (commessa={$commessaId})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,7 +497,7 @@ try {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// 🔹 STEP 9: Send CommessaWeb to laboratory (commentato come richiesto)
|
// 🔹 STEP 9: Send CommessaWeb to laboratory (commentato come richiesto)
|
||||||
/*
|
|
||||||
$sendResult = $api->post("CommessaWeb({$commessaId})/InviaCommessa", []);
|
$sendResult = $api->post("CommessaWeb({$commessaId})/InviaCommessa", []);
|
||||||
|
|
||||||
// Logga il POST
|
// Logga il POST
|
||||||
@@ -236,8 +507,30 @@ try {
|
|||||||
"--data '{}'\n\n" .
|
"--data '{}'\n\n" .
|
||||||
"RESPONSE:\n" . json_encode($sendResult, JSON_PRETTY_PRINT);
|
"RESPONSE:\n" . json_encode($sendResult, JSON_PRETTY_PRINT);
|
||||||
$logFileStep9 = $logDir . "commessa_{$commessaId}_send_step9_" . time() . ".txt";
|
$logFileStep9 = $logDir . "commessa_{$commessaId}_send_step9_" . time() . ".txt";
|
||||||
file_put_contents($logFileStep9, $logContentStep9);
|
$writeLog($logFileStep9, $logContentStep9, "STEP 9 - InviaCommessa (commessa={$commessaId})");
|
||||||
*/
|
|
||||||
|
|
||||||
|
// 🔹 STEP 9.5: Importazione da CommessaWeb a Commessa (commentato come richiesto)
|
||||||
|
// Supplier call: POST api/odata/CommessaWeb(XXX)/ImportaCommessa
|
||||||
|
|
||||||
|
$importUserId = (!empty($lims_global_user_id) && is_numeric($lims_global_user_id))
|
||||||
|
? (int) $lims_global_user_id
|
||||||
|
: 285;
|
||||||
|
|
||||||
|
$importPayload = [
|
||||||
|
"IdUtente" => $importUserId
|
||||||
|
];
|
||||||
|
$importResult = $api->post("CommessaWeb({$commessaId})/ImportaCommessa", $importPayload);
|
||||||
|
|
||||||
|
$importPayloadLog = json_encode($importPayload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||||
|
// Logga il POST
|
||||||
|
$logContentStep91 = "curl --location --request POST '{$apiBaseUrl}CommessaWeb({$commessaId})/ImportaCommessa' \\\n" .
|
||||||
|
"--header 'Content-Type: application/json' \\\n" .
|
||||||
|
"--header 'Authorization: Bearer ••••••' \\\n" .
|
||||||
|
"--data '{$importPayloadLog}'\n\n" .
|
||||||
|
"RESPONSE:\n" . json_encode($importResult, JSON_PRETTY_PRINT);
|
||||||
|
$logFileStep91 = $logDir . "commessa_{$commessaId}_importa_step91_" . time() . ".txt";
|
||||||
|
$writeLog($logFileStep91, $logContentStep91, "STEP 9.5 - ImportaCommessa (commessa={$commessaId})");
|
||||||
|
|
||||||
// 🔹 STEP 10: GET di controllo post-PATCH
|
// 🔹 STEP 10: GET di controllo post-PATCH
|
||||||
$expand = "CommesseCustomFields(\$expand=CustomField)";
|
$expand = "CommesseCustomFields(\$expand=CustomField)";
|
||||||
@@ -248,7 +541,7 @@ try {
|
|||||||
"--header 'Authorization: Bearer ••••••'\n\n" .
|
"--header 'Authorization: Bearer ••••••'\n\n" .
|
||||||
"RESPONSE:\n" . json_encode($commessaAfterPatch, JSON_PRETTY_PRINT);
|
"RESPONSE:\n" . json_encode($commessaAfterPatch, JSON_PRETTY_PRINT);
|
||||||
$logFileStep10 = $logDir . "commessa_{$commessaId}_get_step10_" . time() . ".txt";
|
$logFileStep10 = $logDir . "commessa_{$commessaId}_get_step10_" . time() . ".txt";
|
||||||
file_put_contents($logFileStep10, $logContentStep10);
|
$writeLog($logFileStep10, $logContentStep10, "STEP 10 - GET verify (commessa={$commessaId})");
|
||||||
|
|
||||||
// 🔹 STEP 11: Prepare final response
|
// 🔹 STEP 11: Prepare final response
|
||||||
$finalCommessa = [
|
$finalCommessa = [
|
||||||
@@ -263,15 +556,20 @@ try {
|
|||||||
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
"success" => true,
|
"success" => true,
|
||||||
|
"idcommessaweb" => $commessaId,
|
||||||
|
"commessaweb" => $commessaWebCode,
|
||||||
"commessaWeb" => $finalCommessa,
|
"commessaWeb" => $finalCommessa,
|
||||||
"commessaWebApiResponse" => $commessaWeb, // Incluso per debug
|
"commessaWebApiResponse" => $commessaWeb, // Incluso per debug
|
||||||
"totalCampioni" => count($campioni),
|
"totalCampioni" => count($campioni),
|
||||||
"totalCustomFields" => count($commessaAfterPatch["CommesseCustomFields"] ?? []),
|
"totalCustomFields" => count($commessaAfterPatch["CommesseCustomFields"] ?? []),
|
||||||
|
"totalPhotos" => count($photos),
|
||||||
"message" => "Export successful",
|
"message" => "Export successful",
|
||||||
"logFiles" => [
|
"logFiles" => [
|
||||||
"step5_create" => $logFileStep5,
|
"step5_create" => $logFileStep5,
|
||||||
|
"step5_2_photos" => $logFilePhotos,
|
||||||
"step6_campioni" => $logFileStep6,
|
"step6_campioni" => $logFileStep6,
|
||||||
"step7_patch" => $logFileStep7,
|
"step7_patch" => $logFileStep7 ?? null,
|
||||||
|
"step9_1_importa" => $logFileStep91,
|
||||||
"step10_get" => $logFileStep10
|
"step10_get" => $logFileStep10
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
@@ -283,8 +581,10 @@ try {
|
|||||||
"message" => "Export failed: " . $e->getMessage(),
|
"message" => "Export failed: " . $e->getMessage(),
|
||||||
"logFiles" => [
|
"logFiles" => [
|
||||||
"step5_create" => $logFileStep5 ?? null,
|
"step5_create" => $logFileStep5 ?? null,
|
||||||
|
"step5_2_photos" => $logFilePhotos ?? null,
|
||||||
"step6_campioni" => $logFileStep6 ?? null,
|
"step6_campioni" => $logFileStep6 ?? null,
|
||||||
"step7_patch" => $logFileStep7 ?? null,
|
"step7_patch" => $logFileStep7 ?? null,
|
||||||
|
"step9_1_importa" => $logFileStep91 ?? null,
|
||||||
"step10_get" => $logFileStep10 ?? null
|
"step10_get" => $logFileStep10 ?? null
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||||
|
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
|
||||||
|
// Endpoint per recuperare le Analisi
|
||||||
|
$endpoint = 'Analisi';
|
||||||
|
|
||||||
|
// Opzioni OData
|
||||||
|
$options = [
|
||||||
|
// Restituisce solo i campi principali utili
|
||||||
|
'$select' => 'IdAnalisi,Codice,NomeAnalisi,ClientiAbilitati,MatriciAbilitate,IsGenerico,Tipo,ParentKey,SelezionabileSuWeb',
|
||||||
|
|
||||||
|
// Solo analisi effettivamente selezionabili sul web
|
||||||
|
'$filter' => 'SelezionabileSuWeb eq true',
|
||||||
|
|
||||||
|
// Ordinamento alfabetico per nome analisi
|
||||||
|
'$orderby' => 'NomeAnalisi asc'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Debug: salva URL usato
|
||||||
|
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
|
||||||
|
$query = http_build_query($options);
|
||||||
|
$full_url = $base_url . $endpoint . ($query ? '?' . $query : '');
|
||||||
|
file_put_contents(__DIR__ . '/last_analisi_url.txt', $full_url . PHP_EOL, FILE_APPEND);
|
||||||
|
|
||||||
|
// Chiamata API
|
||||||
|
$data = $api->get($endpoint, $options);
|
||||||
|
|
||||||
|
// Salva il JSON in locale
|
||||||
|
file_put_contents(__DIR__ . '/analisi_response.json', json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||||
|
|
||||||
|
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
file_put_contents(
|
||||||
|
__DIR__ . '/analisi_error_log.txt',
|
||||||
|
date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL,
|
||||||
|
FILE_APPEND
|
||||||
|
);
|
||||||
|
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||||
|
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$idCliente = isset($_GET['id_cliente']) ? (int)$_GET['id_cliente'] : 0;
|
||||||
|
$idMatrice = isset($_GET['id_matrice']) ? (int)$_GET['id_matrice'] : 0;
|
||||||
|
$debug = isset($_GET['debug']) ? (int)$_GET['debug'] : 0;
|
||||||
|
|
||||||
|
if ($idCliente <= 0) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Missing or invalid id_cliente']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
|
||||||
|
if ($idMatrice > 0) {
|
||||||
|
$expandExpr = "AnalisiAbilitate(\$filter=Matrice/IdMatrice eq $idMatrice)";
|
||||||
|
} else {
|
||||||
|
$expandExpr = "AnalisiAbilitate";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode only the $expand expression because it contains spaces, slash and parentheses
|
||||||
|
$expandEncoded = rawurlencode($expandExpr);
|
||||||
|
$endpoint = "Cliente($idCliente)?\$expand={$expandEncoded}";
|
||||||
|
|
||||||
|
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
|
||||||
|
$full_url = $base_url . $endpoint;
|
||||||
|
|
||||||
|
file_put_contents(__DIR__ . '/last_url_analisi.txt', $full_url . PHP_EOL, FILE_APPEND);
|
||||||
|
|
||||||
|
$data = $api->get($endpoint, []);
|
||||||
|
|
||||||
|
file_put_contents(
|
||||||
|
__DIR__ . '/analisi_by_cliente_matrice_response.json',
|
||||||
|
json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($debug === 1) {
|
||||||
|
echo json_encode([
|
||||||
|
'endpoint' => $endpoint,
|
||||||
|
'full_url' => $full_url,
|
||||||
|
'raw_response' => $data
|
||||||
|
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$analisi = $data['AnalisiAbilitate'] ?? [];
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'value' => $analisi,
|
||||||
|
'count' => is_array($analisi) ? count($analisi) : 0
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
file_put_contents(
|
||||||
|
__DIR__ . '/error_log_analisi.txt',
|
||||||
|
date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL,
|
||||||
|
FILE_APPEND
|
||||||
|
);
|
||||||
|
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||||
|
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$idMatrice = isset($_GET['id_matrice']) ? (int)$_GET['id_matrice'] : 0;
|
||||||
|
|
||||||
|
if ($idMatrice <= 0) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Missing or invalid id_matrice']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OData hypothesis:
|
||||||
|
* - Expand enabled matrices
|
||||||
|
* - Return only selectable analyses
|
||||||
|
* - Include generic analyses OR analyses enabled for the given matrix
|
||||||
|
*
|
||||||
|
* This endpoint must be verified against the real VisualLims API metadata.
|
||||||
|
*/
|
||||||
|
$filter = rawurlencode("SelezionabileSuWeb eq true and (IsGenerico eq true or MatriciAbilitate/any(m:m/IdMatrice eq $idMatrice))");
|
||||||
|
$expand = rawurlencode("MatriciAbilitate");
|
||||||
|
$endpoint = "Analisi?\$top=10";
|
||||||
|
|
||||||
|
// Debug URL
|
||||||
|
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
|
||||||
|
$full_url = $base_url . $endpoint;
|
||||||
|
file_put_contents(__DIR__ . '/last_url_analisi.txt', $full_url . PHP_EOL, FILE_APPEND);
|
||||||
|
|
||||||
|
$data = $api->get($endpoint, []);
|
||||||
|
|
||||||
|
file_put_contents(
|
||||||
|
__DIR__ . '/analisi_by_matrice_response.json',
|
||||||
|
json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
|
||||||
|
);
|
||||||
|
|
||||||
|
$analisi = $data['value'] ?? [];
|
||||||
|
|
||||||
|
echo json_encode(['value' => $analisi], JSON_UNESCAPED_UNICODE);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
file_put_contents(
|
||||||
|
__DIR__ . '/error_log_analisi.txt',
|
||||||
|
date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL,
|
||||||
|
FILE_APPEND
|
||||||
|
);
|
||||||
|
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -9,7 +9,24 @@ ini_set('display_errors', '0');
|
|||||||
error_reporting(E_ALL);
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
try {
|
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
|
// Parametri OData
|
||||||
$params = [
|
$params = [
|
||||||
@@ -57,10 +74,21 @@ try {
|
|||||||
throw new Exception("Massimo numero di tentativi raggiunto per $endpoint");
|
throw new Exception("Massimo numero di tentativi raggiunto per $endpoint");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache file (1 hour TTL)
|
||||||
|
$cacheFile = __DIR__ . '/cache/clienti.json';
|
||||||
|
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
||||||
|
readfile($cacheFile);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
// Esegui la chiamata con retry
|
// Esegui la chiamata con retry
|
||||||
$data = makeApiRequest($api, $endpoint);
|
$data = makeApiRequest($api, $endpoint);
|
||||||
|
|
||||||
echo json_encode($data);
|
$json = json_encode($data);
|
||||||
|
if (!is_dir(__DIR__ . '/cache')) mkdir(__DIR__ . '/cache', 0777, true);
|
||||||
|
file_put_contents($cacheFile, $json);
|
||||||
|
|
||||||
|
echo $json;
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
$errorResponse = [
|
$errorResponse = [
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||||
|
require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// Disable PHP error display
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$api = VisualLimsApiClient::getInstance(); // also loads dotenv
|
||||||
|
|
||||||
|
// In simulate mode: return fake clients built from idclient values already in datadb.
|
||||||
|
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], JSON_UNESCAPED_UNICODE);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endpoint senza filtri: recupera tutto
|
||||||
|
$endpoint = 'Cliente?$top=50';
|
||||||
|
|
||||||
|
// Funzione per eseguire la chiamata con retry
|
||||||
|
function makeApiRequest($api, $endpoint, $maxRetries = 3)
|
||||||
|
{
|
||||||
|
for ($retry = 0; $retry < $maxRetries; $retry++) {
|
||||||
|
try {
|
||||||
|
$data = $api->get($endpoint);
|
||||||
|
|
||||||
|
// Save response for debug
|
||||||
|
file_put_contents(
|
||||||
|
__DIR__ . '/clienti_response.json',
|
||||||
|
json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
|
||||||
|
);
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$errorMessage = $e->getMessage();
|
||||||
|
|
||||||
|
// Retry only for specific auth/token related issue
|
||||||
|
if (
|
||||||
|
strpos($errorMessage, 'HTTP 400') !== false &&
|
||||||
|
strpos($errorMessage, 'Cannot persist the object') !== false
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (method_exists($api, 'refreshToken')) {
|
||||||
|
$api->refreshToken();
|
||||||
|
error_log("Tentativo {$retry}: refresh token eseguito per endpoint {$endpoint}");
|
||||||
|
} else {
|
||||||
|
throw new Exception('Il metodo refreshToken() non esiste in VisualLimsApiClient');
|
||||||
|
}
|
||||||
|
} catch (Exception $refreshEx) {
|
||||||
|
error_log("Errore durante il refresh del token: " . $refreshEx->getMessage());
|
||||||
|
throw new Exception("Impossibile eseguire il refresh del token: " . $refreshEx->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
usleep(500000); // 500ms
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("Massimo numero di tentativi raggiunto per {$endpoint}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Esegui la chiamata
|
||||||
|
$data = makeApiRequest($api, $endpoint);
|
||||||
|
|
||||||
|
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
|
||||||
|
$errorResponse = [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'trace' => $e->getTraceAsString()
|
||||||
|
];
|
||||||
|
|
||||||
|
error_log("Errore in get_clienti.php: " . json_encode($errorResponse, JSON_UNESCAPED_UNICODE));
|
||||||
|
echo json_encode($errorResponse, JSON_UNESCAPED_UNICODE);
|
||||||
|
}
|
||||||
@@ -21,16 +21,25 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$results = [];
|
$results = [];
|
||||||
|
$cacheDir = __DIR__ . '/cache';
|
||||||
|
if (!is_dir($cacheDir)) mkdir($cacheDir, 0777, true);
|
||||||
|
|
||||||
foreach ($fieldIds as $customFieldId) {
|
foreach ($fieldIds as $customFieldId) {
|
||||||
$endpoint = "CustomField($customFieldId)?\$expand=CustomFieldsValues";
|
$cacheFile = $cacheDir . '/customfield_' . $customFieldId . '.json';
|
||||||
$data = $api->get($endpoint);
|
|
||||||
|
|
||||||
$results[$customFieldId] = $data['CustomFieldsValues'] ?? [];
|
// Use cache if fresh (1 hour)
|
||||||
|
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
||||||
|
$results[$customFieldId] = json_decode(file_get_contents($cacheFile), true);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug ფაილი
|
$endpoint = "CustomField($customFieldId)?\$expand=CustomFieldsValues";
|
||||||
file_put_contents(__DIR__ . '/customfield_values_response.json', json_encode($results));
|
$data = $api->get($endpoint);
|
||||||
|
$values = $data['CustomFieldsValues'] ?? [];
|
||||||
|
$results[$customFieldId] = $values;
|
||||||
|
|
||||||
|
file_put_contents($cacheFile, json_encode($values));
|
||||||
|
}
|
||||||
|
|
||||||
echo json_encode($results);
|
echo json_encode($results);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ if (!$field) {
|
|||||||
exit;
|
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/';
|
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
|
||||||
|
|
||||||
$data = null;
|
$data = null;
|
||||||
@@ -44,10 +45,13 @@ try {
|
|||||||
case 'ClienteResponsabile':
|
case 'ClienteResponsabile':
|
||||||
$id_cliente = (int)($_GET['id_cliente'] ?? 0);
|
$id_cliente = (int)($_GET['id_cliente'] ?? 0);
|
||||||
if ($id_cliente <= 0) {
|
if ($id_cliente <= 0) {
|
||||||
|
if (!$simulate) {
|
||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
echo json_encode(['error' => 'Manca o invalido id_cliente']);
|
echo json_encode(['error' => 'Manca o invalido id_cliente']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
$id_cliente = 1; // dummy — mock ignores the actual ID
|
||||||
|
}
|
||||||
$endpoint = "Cliente($id_cliente)?\$expand=Responsabili";
|
$endpoint = "Cliente($id_cliente)?\$expand=Responsabili";
|
||||||
$cache_file = __DIR__ . '/cache/cliente_responsabili_' . $id_cliente . '.json';
|
$cache_file = __DIR__ . '/cache/cliente_responsabili_' . $id_cliente . '.json';
|
||||||
break;
|
break;
|
||||||
@@ -62,13 +66,13 @@ try {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Opzionale: caching semplice (molto utile se i dati cambiano poco)
|
// Caching skipped in simulate mode to avoid polluting real-data cache files
|
||||||
if ($cache_file && file_exists($cache_file) && (time() - filemtime($cache_file) < 3600)) { // 1 ora
|
if (!$simulate && $cache_file && file_exists($cache_file) && (time() - filemtime($cache_file) < 3600)) { // 1 ora
|
||||||
$data = json_decode(file_get_contents($cache_file), true);
|
$data = json_decode(file_get_contents($cache_file), true);
|
||||||
} else {
|
} else {
|
||||||
$data = $api->get($endpoint, $options);
|
$data = $api->get($endpoint, $options);
|
||||||
|
|
||||||
if ($cache_file) {
|
if (!$simulate && $cache_file) {
|
||||||
file_put_contents($cache_file, json_encode($data, JSON_PRETTY_PRINT));
|
file_put_contents($cache_file, json_encode($data, JSON_PRETTY_PRINT));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,59 +1,46 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php'; // Risale a root/vendor/
|
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||||
use Dotenv\Dotenv;
|
require_once __DIR__ . '/class/db-functions.php';
|
||||||
|
include dirname(__DIR__) . '/../extra/auth.php';
|
||||||
|
if (!Auth::check()) { http_response_code(401); echo json_encode(['error' => 'Unauthorized']); exit; }
|
||||||
|
|
||||||
// Set JSON header
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
// Debug: Log the path where we expect the .env file
|
|
||||||
$envPath = dirname(__DIR__, 2);
|
|
||||||
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . ' - Expected .env path: ' . $envPath . PHP_EOL, FILE_APPEND);
|
|
||||||
|
|
||||||
// Carica il file .env dalla root del progetto
|
|
||||||
try {
|
try {
|
||||||
$dotenv = Dotenv::createImmutable($envPath);
|
// Read from matrici cache (populated by get_matrici.php / warm_cache.php)
|
||||||
$dotenv->load();
|
$cacheFile = __DIR__ . '/cache/matrici.json';
|
||||||
} catch (Exception $e) {
|
if (file_exists($cacheFile)) {
|
||||||
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - Errore caricamento .env: ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
|
$data = json_decode(file_get_contents($cacheFile), true);
|
||||||
echo json_encode(['success' => false, 'message' => 'Errore caricamento configurazione: ' . $e->getMessage()]);
|
$matrici = $data['value'] ?? [];
|
||||||
exit(1);
|
} else {
|
||||||
}
|
// Fallback: trigger cache creation
|
||||||
|
require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
|
||||||
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
$apiData = $api->get('Matrice');
|
||||||
|
$matrici = [];
|
||||||
|
foreach (($apiData['value'] ?? []) as $item) {
|
||||||
|
$nome = $item['NomeMatrice'] ?? '';
|
||||||
|
if ($nome !== '' && substr($nome, 0, 1) !== '*') {
|
||||||
|
$matrici[] = ['IdMatrice' => $item['IdMatrice'], 'NomeMatrice' => $nome, 'MacroMatrice' => $item['MacroMatrice'] ?? null];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!is_dir(__DIR__ . '/cache')) mkdir(__DIR__ . '/cache', 0777, true);
|
||||||
|
file_put_contents($cacheFile, json_encode(['success' => true, 'value' => $matrici]));
|
||||||
|
}
|
||||||
|
|
||||||
// Recupera le variabili d'ambiente
|
// Extract unique MacroMatrice values
|
||||||
$dbHost = $_ENV['DB_HOST'];
|
$macroValues = [];
|
||||||
$dbName = $_ENV['DB_DATABASE'];
|
foreach ($matrici as $m) {
|
||||||
$dbUser = $_ENV['DB_USERNAME'];
|
$macro = $m['MacroMatrice'] ?? null;
|
||||||
$dbPass = $_ENV['DB_PASSWORD'];
|
if ($macro !== null && $macro !== '' && substr($macro, 0, 1) !== '*') {
|
||||||
$dbPrefix = $_ENV['DB_PREFIX'];
|
$macroValues[$macro] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$macroMatrici = array_keys($macroValues);
|
||||||
|
sort($macroMatrici);
|
||||||
|
|
||||||
// Debug: Log database connection details (excluding password)
|
|
||||||
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . " - DB Connection: host=$dbHost, dbname=$dbName, user=$dbUser, prefix=$dbPrefix" . PHP_EOL, FILE_APPEND);
|
|
||||||
|
|
||||||
// Connessione al database MySQL
|
|
||||||
try {
|
|
||||||
$pdo = new PDO("mysql:host=$dbHost;dbname=$dbName;charset=utf8mb4", $dbUser, $dbPass);
|
|
||||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - Errore connessione DB: ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Errore connessione al database: ' . $e->getMessage()]);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Query per recuperare i valori distinti di MacroMatrice, escludendo quelli che iniziano con '*' e ordinandoli
|
|
||||||
$query = "SELECT DISTINCT MacroMatrice FROM {$dbPrefix}matrici WHERE MacroMatrice IS NOT NULL AND MacroMatrice NOT LIKE '*%' ORDER BY MacroMatrice ASC";
|
|
||||||
$stmt = $pdo->prepare($query);
|
|
||||||
$stmt->execute();
|
|
||||||
$macroMatrici = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
|
||||||
|
|
||||||
// Debug: Log del numero di MacroMatrice recuperate
|
|
||||||
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . ' - Retrieved ' . count($macroMatrici) . ' MacroMatrice from database' . PHP_EOL, FILE_APPEND);
|
|
||||||
|
|
||||||
// Restituisci risposta JSON
|
|
||||||
echo json_encode(['success' => true, 'value' => $macroMatrici]);
|
echo json_encode(['success' => true, 'value' => $macroMatrici]);
|
||||||
} catch (PDOException $e) {
|
} catch (Exception $e) {
|
||||||
// Log errore e restituisci risposta di errore
|
http_response_code(500);
|
||||||
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - Errore nel recupero delle MacroMatrice: ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
echo json_encode(['success' => false, 'message' => 'Errore nel recupero delle MacroMatrice: ' . $e->getMessage()]);
|
|
||||||
exit(1);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||||
|
require_once __DIR__ . '/class/db-functions.php';
|
||||||
|
include dirname(__DIR__) . '/../extra/auth.php';
|
||||||
|
if (!Auth::check()) { http_response_code(401); echo json_encode(['error' => 'Unauthorized']); exit; }
|
||||||
|
|
||||||
|
require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Cache file (1 hour TTL)
|
||||||
|
$cacheFile = __DIR__ . '/cache/matrici.json';
|
||||||
|
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
||||||
|
readfile($cacheFile);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
$data = $api->get('Matrice');
|
||||||
|
|
||||||
|
$matrici = [];
|
||||||
|
if (isset($data['value']) && is_array($data['value'])) {
|
||||||
|
foreach ($data['value'] as $item) {
|
||||||
|
$nome = $item['NomeMatrice'] ?? '';
|
||||||
|
if ($nome !== '' && substr($nome, 0, 1) !== '*') {
|
||||||
|
$matrici[] = [
|
||||||
|
'IdMatrice' => $item['IdMatrice'],
|
||||||
|
'NomeMatrice' => $nome,
|
||||||
|
'MacroMatrice' => $item['MacroMatrice'] ?? null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
usort($matrici, fn($a, $b) => strcasecmp($a['NomeMatrice'], $b['NomeMatrice']));
|
||||||
|
}
|
||||||
|
|
||||||
|
$json = json_encode(['success' => true, 'value' => $matrici]);
|
||||||
|
if (!is_dir(__DIR__ . '/cache')) mkdir(__DIR__ . '/cache', 0777, true);
|
||||||
|
file_put_contents($cacheFile, $json);
|
||||||
|
|
||||||
|
echo $json;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/include/headscript.php'; // o il tuo bootstrap standard
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!isset($_GET['iddatadb']) || !is_numeric($_GET['iddatadb'])) {
|
||||||
|
echo json_encode(['success' => true, 'field' => null]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$iddatadb = (int)$_GET['iddatadb'];
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
// 1) prendo templateid da datadb
|
||||||
|
// 2) cerco max 1 record in template_mapping con is_visible_parts=1
|
||||||
|
$sql = "
|
||||||
|
SELECT tm.field_id, tm.field_label, tm.data_type, tm.has_list
|
||||||
|
FROM datadb d
|
||||||
|
JOIN template_mapping tm ON tm.template_id = d.templateid
|
||||||
|
WHERE d.iddatadb = ?
|
||||||
|
AND tm.is_visible_parts = 1
|
||||||
|
ORDER BY tm.id ASC
|
||||||
|
LIMIT 1
|
||||||
|
";
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute([$iddatadb]);
|
||||||
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'field' => $row ? $row : null
|
||||||
|
]);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
<?php
|
||||||
|
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||||
|
require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// Disable PHP error display
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$api = VisualLimsApiClient::getInstance(); // also loads dotenv
|
||||||
|
|
||||||
|
// In simulate mode: return fake users
|
||||||
|
if (($_ENV['SIMULATE_EXPORT_LIMS'] ?? '') === 'true') {
|
||||||
|
$fakeUsers = [
|
||||||
|
[
|
||||||
|
'IdUtente' => 1001,
|
||||||
|
'Nominativo' => 'Utente Simulato 1001'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'IdUtente' => 1002,
|
||||||
|
'Nominativo' => 'Utente Simulato 1002'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
echo json_encode(['value' => $fakeUsers]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OData parameters
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
// Build query string
|
||||||
|
$queryString = http_build_query($params);
|
||||||
|
|
||||||
|
// Final endpoint
|
||||||
|
$endpoint = "Utente?$queryString";
|
||||||
|
|
||||||
|
// Function to execute request with retry
|
||||||
|
function makeApiRequest($api, $endpoint, $maxRetries = 3)
|
||||||
|
{
|
||||||
|
for ($retry = 0; $retry < $maxRetries; $retry++) {
|
||||||
|
try {
|
||||||
|
$data = $api->get($endpoint);
|
||||||
|
|
||||||
|
// Save response for debug
|
||||||
|
file_put_contents(__DIR__ . '/utenti_response.json', json_encode($data, JSON_PRETTY_PRINT));
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$errorMessage = $e->getMessage();
|
||||||
|
|
||||||
|
// Retry only on token/auth-related issue
|
||||||
|
if (
|
||||||
|
strpos($errorMessage, 'HTTP 400') !== false &&
|
||||||
|
strpos($errorMessage, 'Cannot persist the object') !== false
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
$api->refreshToken(); // must exist in VisualLimsApiClient
|
||||||
|
error_log("Tentativo $retry: Refresh token eseguito per endpoint $endpoint");
|
||||||
|
} catch (Exception $refreshEx) {
|
||||||
|
error_log("Errore durante il refresh del token: " . $refreshEx->getMessage());
|
||||||
|
throw new Exception("Impossibile eseguire il refresh del token: " . $refreshEx->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
usleep(500000); // 500 ms
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("Massimo numero di tentativi raggiunto per $endpoint");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute request
|
||||||
|
$data = makeApiRequest($api, $endpoint);
|
||||||
|
|
||||||
|
echo json_encode($data);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
|
||||||
|
$errorResponse = [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'trace' => $e->getTraceAsString()
|
||||||
|
];
|
||||||
|
|
||||||
|
error_log("Errore in get_utenti.php: " . json_encode($errorResponse));
|
||||||
|
echo json_encode($errorResponse);
|
||||||
|
}
|
||||||
@@ -0,0 +1,979 @@
|
|||||||
|
/**
|
||||||
|
* gridRenderer.js — Data-driven grid renderer for imported.php
|
||||||
|
*
|
||||||
|
* Reads window.gridData (array of row objects) and window.gridMeta (column defs, config).
|
||||||
|
* Renders only visible rows into DOM. Propagate/edit operates on gridData array,
|
||||||
|
* then re-renders visible rows. Save reads from gridData, not DOM.
|
||||||
|
*/
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const PAGE_SIZE = 20;
|
||||||
|
let revealedCount = PAGE_SIZE;
|
||||||
|
let dropdownOptionsCache = {}; // fieldId -> [{id, text}] — used only for small lists
|
||||||
|
const dropdownNameCache = {}; // "fieldId_valueId" -> label
|
||||||
|
let clientData = []; // loaded from get_clienti.php
|
||||||
|
let fixedFieldCache = window.fixedFieldDataCache || {};
|
||||||
|
window.fixedFieldDataCache = fixedFieldCache;
|
||||||
|
|
||||||
|
const data = window.gridData || [];
|
||||||
|
const meta = window.gridMeta || {};
|
||||||
|
const columns = meta.columns || [];
|
||||||
|
let totalRows = data.length;
|
||||||
|
|
||||||
|
// ── DOM refs ────────────────────────────────────────────────────────────
|
||||||
|
let rowContainer = null;
|
||||||
|
let headerContainer = null;
|
||||||
|
let topContainer = null;
|
||||||
|
let statusEl = null;
|
||||||
|
|
||||||
|
// ── Helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function esc(str) {
|
||||||
|
if (str === null || str === undefined) return '';
|
||||||
|
const d = document.createElement('div');
|
||||||
|
d.textContent = String(str);
|
||||||
|
return d.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDetailValue(rowIndex, mappingId) {
|
||||||
|
return data[rowIndex].details[String(mappingId)] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDetailValue(rowIndex, mappingId, value) {
|
||||||
|
data[rowIndex].details[String(mappingId)] = value;
|
||||||
|
data[rowIndex]._dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFixedValue(rowIndex, key) {
|
||||||
|
return data[rowIndex].fixedFields[key] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFixedValue(rowIndex, key, value) {
|
||||||
|
data[rowIndex].fixedFields[key] = value;
|
||||||
|
data[rowIndex]._dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Client data (AJAX Select2, no bulk loading) ──────────────────────
|
||||||
|
|
||||||
|
function formatClientLabel(client) {
|
||||||
|
return (client.Nominativo || '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache of resolved client names: id → name
|
||||||
|
const clientNameCache = {};
|
||||||
|
|
||||||
|
// Build select with only the selected option
|
||||||
|
function buildClientOptionsHTML(selectedId) {
|
||||||
|
let html = '<option value="">Select a client...</option>';
|
||||||
|
if (selectedId) {
|
||||||
|
const label = clientNameCache[selectedId] || selectedId;
|
||||||
|
html += `<option value="${esc(String(selectedId))}" selected>${esc(String(label))}</option>`;
|
||||||
|
}
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-resolve all unique client IDs used in data rows, then re-render
|
||||||
|
async function resolveClientNames() {
|
||||||
|
const ids = new Set();
|
||||||
|
data.forEach(row => {
|
||||||
|
if (row.idclient) ids.add(String(row.idclient));
|
||||||
|
if (row.cliente_fornitore_id) ids.add(String(row.cliente_fornitore_id));
|
||||||
|
// Fixed fields that are client-sourced
|
||||||
|
if (row.fixedFields) {
|
||||||
|
for (const [key, val] of Object.entries(row.fixedFields)) {
|
||||||
|
const cfg = fixedFieldApiConfig[key];
|
||||||
|
if (cfg && cfg.source === 'clients' && val) ids.add(String(val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Batch resolve via single request per ID
|
||||||
|
const unresolvedIds = [...ids].filter(id => !clientNameCache[id]);
|
||||||
|
if (unresolvedIds.length === 0) return;
|
||||||
|
|
||||||
|
await Promise.all(unresolvedIds.map(async (id) => {
|
||||||
|
try {
|
||||||
|
const resp = await fetch('search_clienti.php?id=' + encodeURIComponent(id));
|
||||||
|
const json = await resp.json();
|
||||||
|
const item = (json.results || [])[0];
|
||||||
|
if (item) clientNameCache[id] = item.text;
|
||||||
|
} catch (e) { /* ignore */ }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select2 AJAX config for client selects
|
||||||
|
const clientSelect2Config = {
|
||||||
|
placeholder: 'Search client...',
|
||||||
|
allowClear: true,
|
||||||
|
width: '100%',
|
||||||
|
minimumInputLength: 0,
|
||||||
|
dropdownCssClass: 'select2-dropdown-smaller',
|
||||||
|
ajax: {
|
||||||
|
url: 'search_clienti.php',
|
||||||
|
dataType: 'json',
|
||||||
|
delay: 150,
|
||||||
|
data: function(params) { return { q: params.term || '', limit: 20 }; },
|
||||||
|
processResults: function(data) { return { results: data.results || [] }; },
|
||||||
|
cache: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function loadClientData() {
|
||||||
|
// No longer loads all clients — AJAX Select2 handles search
|
||||||
|
// Just resolve names for pre-selected values
|
||||||
|
await resolveClientNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Fixed field data loading ───────────────────────────────────────────
|
||||||
|
|
||||||
|
const fixedFieldApiConfig = {
|
||||||
|
MoltiplicatorePrezzo: { endpoint: 'MoltiplicatorePrezzo', idKey: 'IdMoltiplicatorePrezzo', textKey: 'Descrizione' },
|
||||||
|
AnagraficaCertestObject: { endpoint: 'AnagraficaCertestObject', idKey: 'IdAnagrafica', textKey: 'NomeAnagrafica' },
|
||||||
|
AnagraficaCertestService: { endpoint: 'AnagraficaCertestService', idKey: 'IdAnagrafica', textKey: 'NomeAnagrafica' },
|
||||||
|
ClienteResponsabile: { endpoint: 'ClienteResponsabile', idKey: 'IdClienteResponsabile', textKey: 'Nominativo', dependsOn: 'idclient', getParams: (cid) => ({ id_cliente: cid }) },
|
||||||
|
ClienteFornitore: { source: 'clients' },
|
||||||
|
ClienteAnalisi: { source: 'clients' },
|
||||||
|
};
|
||||||
|
|
||||||
|
let _pendingFixed = {};
|
||||||
|
|
||||||
|
async function loadFixedFieldOptions(fieldKey, clientId) {
|
||||||
|
const config = fixedFieldApiConfig[fieldKey];
|
||||||
|
if (!config) return [];
|
||||||
|
|
||||||
|
// Client-sourced fields — handled by AJAX Select2, skip preloading
|
||||||
|
if (config.source === 'clients') {
|
||||||
|
fixedFieldCache[fieldKey] = [];
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheKey = fieldKey + (clientId ? '_' + clientId : '');
|
||||||
|
if (fixedFieldCache[cacheKey]) return fixedFieldCache[cacheKey];
|
||||||
|
if (_pendingFixed[cacheKey]) return _pendingFixed[cacheKey];
|
||||||
|
|
||||||
|
const params = { field: config.endpoint };
|
||||||
|
if (config.dependsOn && clientId) {
|
||||||
|
Object.assign(params, config.getParams(clientId));
|
||||||
|
}
|
||||||
|
|
||||||
|
_pendingFixed[cacheKey] = (async () => {
|
||||||
|
try {
|
||||||
|
const resp = await fetch('get_fixed_field_data.php?' + new URLSearchParams(params));
|
||||||
|
const json = await resp.json();
|
||||||
|
let items = (fieldKey === 'ClienteResponsabile') ? (json.Responsabili || []) : (json.value || json.d?.results || json || []);
|
||||||
|
const results = items.map(item => ({
|
||||||
|
id: item[config.idKey],
|
||||||
|
text: (item.Codice ? item.Codice + ' - ' : '') + (item[config.textKey] || '')
|
||||||
|
})).sort((a, b) => String(a.text).localeCompare(String(b.text), 'it', { sensitivity: 'base' }));
|
||||||
|
fixedFieldCache[cacheKey] = results;
|
||||||
|
delete _pendingFixed[cacheKey];
|
||||||
|
return results;
|
||||||
|
} catch (e) {
|
||||||
|
delete _pendingFixed[cacheKey];
|
||||||
|
console.error('Failed to load fixed field ' + fieldKey, e);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return _pendingFixed[cacheKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Custom field dropdown data loading ─────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
// Select2 AJAX config factory for SceltaMultipla
|
||||||
|
function sceltaSelect2Config(fieldId) {
|
||||||
|
return {
|
||||||
|
placeholder: 'Search...',
|
||||||
|
allowClear: true,
|
||||||
|
width: '100%',
|
||||||
|
minimumInputLength: 0,
|
||||||
|
ajax: {
|
||||||
|
url: 'search_customfield_values.php',
|
||||||
|
dataType: 'json',
|
||||||
|
delay: 150,
|
||||||
|
data: function(params) { return { field_id: fieldId, q: params.term || '', limit: 10 }; },
|
||||||
|
processResults: function(data) { return { results: data.results || [] }; },
|
||||||
|
cache: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Preload all data ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async function preloadAllData() {
|
||||||
|
// 1. Clients
|
||||||
|
await loadClientData();
|
||||||
|
|
||||||
|
// 2. Non-dependent fixed fields
|
||||||
|
const nonDependent = Object.keys(fixedFieldApiConfig).filter(k => !fixedFieldApiConfig[k].dependsOn && !fixedFieldApiConfig[k].source);
|
||||||
|
await Promise.all(nonDependent.map(k => loadFixedFieldOptions(k)));
|
||||||
|
|
||||||
|
// Client-sourced fixed fields
|
||||||
|
const clientSourced = Object.keys(fixedFieldApiConfig).filter(k => fixedFieldApiConfig[k].source === 'clients');
|
||||||
|
await Promise.all(clientSourced.map(k => loadFixedFieldOptions(k)));
|
||||||
|
|
||||||
|
// 3. Dependent fixed fields — collect unique clientIds
|
||||||
|
const clientIds = new Set();
|
||||||
|
data.forEach(row => {
|
||||||
|
if (row.idclient) clientIds.add(String(row.idclient));
|
||||||
|
});
|
||||||
|
const dependent = Object.keys(fixedFieldApiConfig).filter(k => fixedFieldApiConfig[k].dependsOn);
|
||||||
|
for (const fieldKey of dependent) {
|
||||||
|
for (const cid of clientIds) {
|
||||||
|
await loadFixedFieldOptions(fieldKey, cid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Warm server cache + resolve SceltaMultipla names in one request
|
||||||
|
const allFieldIds = [...new Set(
|
||||||
|
columns.filter(c => (c.type === 'detail' || c.type === 'main_field') && c.dataType === 'SceltaMultipla' && c.fieldId)
|
||||||
|
.map(c => String(c.fieldId))
|
||||||
|
)];
|
||||||
|
if (allFieldIds.length > 0) {
|
||||||
|
try {
|
||||||
|
const resp = await fetch('get_customfield_values.php?field_ids=' + allFieldIds.join(','));
|
||||||
|
const json = await resp.json();
|
||||||
|
const entries = json.data ? json.data : json;
|
||||||
|
for (const [fid, values] of Object.entries(entries)) {
|
||||||
|
if (Array.isArray(values)) {
|
||||||
|
values.forEach(v => {
|
||||||
|
dropdownNameCache[fid + '_' + v.IdCustomFieldsValue] = v.Valore || '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to preload dropdown data:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[gridRenderer] preload done:', {
|
||||||
|
clients: clientData.length,
|
||||||
|
fixedFieldCache: Object.keys(fixedFieldCache),
|
||||||
|
dropdownOptionsCache: Object.keys(dropdownOptionsCache),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Cell rendering ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function createCell(col, rowIndex, cellIndex) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'grid-cell editable-cell';
|
||||||
|
div.dataset.col = col.key;
|
||||||
|
div.dataset.colType = col.type;
|
||||||
|
div.dataset.row = rowIndex;
|
||||||
|
div.dataset.index = cellIndex;
|
||||||
|
div.style.flex = `0 0 ${col.width}px`;
|
||||||
|
|
||||||
|
const row = data[rowIndex];
|
||||||
|
|
||||||
|
switch (col.type) {
|
||||||
|
case 'main_field':
|
||||||
|
div.innerHTML = createInputHTML(col, row.mainFieldValue || '', rowIndex);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'status': {
|
||||||
|
const st = row.status || 'i';
|
||||||
|
const label = st === 'i' ? 'Imported' : (st === 'P' ? 'In Progress' : 'To LIMS');
|
||||||
|
let html = `<span class="status-badge status-${esc(st)}">${label}</span>`;
|
||||||
|
if (row.commessaweb) {
|
||||||
|
html += `<span class="commessaweb-code" style="display:block; font-size:0.75em; color:#555; margin-top:2px;">${esc(row.commessaweb)}</span>`;
|
||||||
|
}
|
||||||
|
div.innerHTML = html;
|
||||||
|
div.classList.remove('editable-cell');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'idclient': {
|
||||||
|
const sel = document.createElement('select');
|
||||||
|
sel.className = 'cell-input dropdown-select client-select searchable-client';
|
||||||
|
sel.dataset.currentValue = row.idclient || '';
|
||||||
|
sel.innerHTML = buildClientOptionsHTML(row.idclient);
|
||||||
|
div.appendChild(sel);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'cliente_fornitore_id': {
|
||||||
|
const sel = document.createElement('select');
|
||||||
|
sel.className = 'cell-input dropdown-select client-select searchable-client fornitore-select';
|
||||||
|
sel.dataset.currentValue = row.cliente_fornitore_id || '';
|
||||||
|
sel.innerHTML = buildClientOptionsHTML(row.cliente_fornitore_id);
|
||||||
|
div.appendChild(sel);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'detail': {
|
||||||
|
const val = getDetailValue(rowIndex, col.key);
|
||||||
|
div.innerHTML = createInputHTML(col, val, rowIndex);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'tested_component':
|
||||||
|
div.style.overflow = 'visible';
|
||||||
|
div.innerHTML = `<div style="display:flex; align-items:center; gap:4px; width:100%; height:100%;"><input type="text" class="cell-input manual-input" style="flex:1; min-width:0; height:28px;"><button type="button" class="add-part-btn btn btn-sm btn-primary" data-row="${rowIndex}" data-iddatadb="${row.iddatadb}" style="display:inline-flex; align-items:center; justify-content:center; min-width:28px; width:28px; height:28px; padding:0; font-size:12px; flex-shrink:0; text-align:center;"><i class="fas fa-plus" style="margin:0; padding:0;"></i></button></div>`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'awb':
|
||||||
|
div.innerHTML = `<select class="carrier-select"><option value="tnt-it">TNT Italy</option><option value="dhl">DHL</option><option value="gls">GLS</option><option value="sda">SDA</option><option value="ups">UPS</option></select>` +
|
||||||
|
`<input type="text" class="cell-input awb-input" placeholder="AWB Number">` +
|
||||||
|
`<button type="button" class="go-btn" data-row="${rowIndex}"><i class="fas fa-play"></i></button>`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'tracking':
|
||||||
|
div.className = 'grid-cell tracking-info';
|
||||||
|
div.dataset.row = rowIndex;
|
||||||
|
div.innerHTML = '<span class="tracking-result">Shipment Info</span>';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'fixed': {
|
||||||
|
const val = getFixedValue(rowIndex, col.key);
|
||||||
|
div.innerHTML = createFixedFieldHTML(col, val, rowIndex);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'static': {
|
||||||
|
const val = row[col.key] || '';
|
||||||
|
div.classList.remove('editable-cell');
|
||||||
|
if (col.key === 'filename_import' && val) {
|
||||||
|
div.innerHTML = `<a href="imported_trf/${esc(val)}" target="_blank">File</a>`;
|
||||||
|
} else {
|
||||||
|
div.innerHTML = `<span>${esc(val)}</span>`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createInputHTML(col, value, rowIndex) {
|
||||||
|
const cls = col.isManual ? 'manual-input' : 'auto-input';
|
||||||
|
const reqCls = col.isRequired ? ' required-input' : '';
|
||||||
|
const req = col.isRequired ? ' required' : '';
|
||||||
|
const v = esc(value);
|
||||||
|
|
||||||
|
if (col.dataType === 'SceltaMultipla') {
|
||||||
|
const options = buildDropdownOptionsHTML(col.fieldId, value);
|
||||||
|
return `<select class="cell-input dropdown-select searchable-dropdown ${cls}${reqCls}" data-field-id="${col.fieldId}"${req}>${options}</select>`;
|
||||||
|
}
|
||||||
|
if (col.dataType === 'Data') {
|
||||||
|
return `<input type="text" class="cell-input date-picker ${cls}${reqCls}" value="${v}"${req}>`;
|
||||||
|
}
|
||||||
|
if (col.dataType === 'INT') {
|
||||||
|
return `<input type="number" class="cell-input ${cls}${reqCls}" value="${v}"${req}>`;
|
||||||
|
}
|
||||||
|
if (col.autoValue === 'import_time' || (meta.timeLabels || []).includes(col.label)) {
|
||||||
|
return `<input type="time" class="cell-input ${cls}${reqCls}" value="${v}"${req}>`;
|
||||||
|
}
|
||||||
|
return `<input type="text" class="cell-input ${cls}${reqCls}" value="${v}"${req}>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFixedFieldHTML(col, value, rowIndex) {
|
||||||
|
if (col.dataType === 'DATE') {
|
||||||
|
const reqCls = col.isRequired ? ' required-input' : '';
|
||||||
|
const req = col.isRequired ? ' required' : '';
|
||||||
|
return `<input type="text" class="cell-input date-picker manual-input${reqCls} fixed-input" data-fixed-key="${col.key}" value="${esc(value)}"${req}>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client-sourced fields → AJAX Select2 (like idclient)
|
||||||
|
const config = fixedFieldApiConfig[col.key];
|
||||||
|
if (config && config.source === 'clients') {
|
||||||
|
const reqCls = col.isRequired ? ' required-input' : '';
|
||||||
|
const req = col.isRequired ? ' required' : '';
|
||||||
|
let opts = '<option value="">Select...</option>';
|
||||||
|
if (value) {
|
||||||
|
const label = clientNameCache[value] || value;
|
||||||
|
opts += `<option value="${esc(String(value))}" selected>${esc(String(label))}</option>`;
|
||||||
|
}
|
||||||
|
return `<select class="cell-input manual-input fixed-input searchable-client api-fixed-select${reqCls}" data-fixed-key="${col.key}" data-current-value="${esc(value)}"${req}>${opts}</select>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select — build from cache
|
||||||
|
const isApiField = !!config;
|
||||||
|
const selectClass = isApiField ? 'api-fixed-select' : '';
|
||||||
|
let options = '<option value="">Seleziona...</option>';
|
||||||
|
const cacheKey = config?.dependsOn
|
||||||
|
? col.key + '_' + (data[rowIndex].idclient || '')
|
||||||
|
: col.key;
|
||||||
|
const items = fixedFieldCache[cacheKey] || [];
|
||||||
|
items.forEach(item => {
|
||||||
|
const sel = String(item.id) === String(value) ? ' selected' : '';
|
||||||
|
options += `<option value="${item.id}"${sel}>${esc(item.text)}</option>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const reqCls = col.isRequired ? ' required-input' : '';
|
||||||
|
const req = col.isRequired ? ' required' : '';
|
||||||
|
return `<select class="cell-input manual-input fixed-input ${selectClass}${reqCls}" data-fixed-key="${col.key}" data-current-value="${esc(value)}"${req}>${options}</select>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDropdownOptionsHTML(fieldId, selectedValue) {
|
||||||
|
let html = '<option value="">Seleziona...</option>';
|
||||||
|
if (selectedValue) {
|
||||||
|
const label = dropdownNameCache[fieldId + '_' + selectedValue] || selectedValue;
|
||||||
|
html += `<option value="${esc(String(selectedValue))}" selected>${esc(String(label))}</option>`;
|
||||||
|
}
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Row rendering ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function renderActionButtons(rowIndex) {
|
||||||
|
const row = data[rowIndex];
|
||||||
|
const isExported = row.status === 'l';
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'grid-cell button-cell';
|
||||||
|
div.style.flex = '0 0 auto';
|
||||||
|
div.style.position = 'relative';
|
||||||
|
|
||||||
|
let html = '';
|
||||||
|
if (meta.isAdmin) {
|
||||||
|
html += `<button type="button" class="export-lims-btn action-btn" data-row="${rowIndex}" data-iddatadb="${row.iddatadb}" title="${isExported ? 'Already exported' : 'Export to LIMS'}" style="background:${isExported ? '#ccc' : '#eb0b0b'}; color:white; border:none; border-radius:5px; cursor:${isExported ? 'not-allowed' : 'pointer'}; ${isExported ? 'opacity:0.5;' : ''}" ${isExported ? 'disabled' : ''}><i class="fas fa-upload"></i></button>`;
|
||||||
|
}
|
||||||
|
html += `<button type="button" class="save-btn action-btn" data-row="${rowIndex}" title="Save" style="background:#28a745; color:white; border:none; border-radius:5px; cursor:pointer;"><i class="fas fa-save"></i></button>`;
|
||||||
|
html += `<button type="button" class="photos-btn action-btn" data-row="${rowIndex}" data-iddatadb="${row.iddatadb}" title="Photos" style="background:#007bff; color:white; border:none; border-radius:5px; cursor:pointer;"><i class="fas fa-camera"></i></button>`;
|
||||||
|
html += `<button type="button" class="parts-btn action-btn" data-row="${rowIndex}" data-iddatadb="${row.iddatadb}" title="Parts" style="background:#ffc107; color:white; border:none; border-radius:5px; cursor:pointer;"><i class="fas fa-puzzle-piece"></i></button>`;
|
||||||
|
html += `<button type="button" class="delete-btn action-btn" data-row="${rowIndex}" data-iddatadb="${row.iddatadb}" title="Delete" style="background:#dc3545; color:white; border:none; border-radius:5px; cursor:pointer;"><i class="fas fa-trash"></i></button>`;
|
||||||
|
|
||||||
|
div.innerHTML = html;
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderRow(rowIndex) {
|
||||||
|
const row = data[rowIndex];
|
||||||
|
const rowDiv = document.createElement('div');
|
||||||
|
rowDiv.className = 'grid-row';
|
||||||
|
rowDiv.dataset.id = row.iddatadb;
|
||||||
|
|
||||||
|
if (row._dirty) rowDiv.classList.add('row-dirty');
|
||||||
|
|
||||||
|
// Action buttons
|
||||||
|
rowDiv.appendChild(renderActionButtons(rowIndex));
|
||||||
|
|
||||||
|
// All columns
|
||||||
|
let cellIndex = 1;
|
||||||
|
columns.forEach(col => {
|
||||||
|
rowDiv.appendChild(createCell(col, rowIndex, cellIndex));
|
||||||
|
cellIndex++;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Restore validation errors if present
|
||||||
|
if (row._validationErrors && row._validationErrors.length > 0) {
|
||||||
|
rowDiv.classList.add('validation-row-error');
|
||||||
|
const messages = [];
|
||||||
|
row._validationErrors.forEach(err => {
|
||||||
|
messages.push(err.message);
|
||||||
|
if (!err.field) return;
|
||||||
|
let cell = null;
|
||||||
|
if (err.field.startsWith('field_label:')) {
|
||||||
|
const label = err.field.substring('field_label:'.length);
|
||||||
|
const headers = document.querySelectorAll('.grid-header');
|
||||||
|
let targetIndex = null;
|
||||||
|
headers.forEach(h => { if (h.textContent.trim() === label) targetIndex = h.getAttribute('data-index'); });
|
||||||
|
if (targetIndex) cell = rowDiv.querySelector(`.grid-cell[data-index="${targetIndex}"]`);
|
||||||
|
} else {
|
||||||
|
cell = rowDiv.querySelector(`.grid-cell[data-col="${err.field}"]`);
|
||||||
|
}
|
||||||
|
if (cell) {
|
||||||
|
cell.classList.add('validation-error');
|
||||||
|
cell.querySelectorAll('input, select').forEach(el => el.classList.add('input-validation-error'));
|
||||||
|
let tooltip = cell.querySelector('.validation-tooltip');
|
||||||
|
if (!tooltip) { tooltip = document.createElement('div'); tooltip.className = 'validation-tooltip'; cell.appendChild(tooltip); }
|
||||||
|
tooltip.textContent = err.message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Show error msg on button cell
|
||||||
|
const btnCell2 = rowDiv.querySelector('.button-cell');
|
||||||
|
if (btnCell2) {
|
||||||
|
const errorEl = document.createElement('div');
|
||||||
|
errorEl.className = 'batch-error-msg';
|
||||||
|
errorEl.textContent = 'Warning — click for details';
|
||||||
|
errorEl.addEventListener('click', () => {
|
||||||
|
document.getElementById('exportResponseMessage').innerHTML = messages.join('<br>');
|
||||||
|
document.getElementById('exportResponseModalLabel').textContent = 'Validation Error (id: ' + row.iddatadb + ')';
|
||||||
|
new bootstrap.Modal(document.getElementById('exportResponseModal'), { keyboard: false }).show();
|
||||||
|
});
|
||||||
|
btnCell2.appendChild(errorEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore export error indicator if present
|
||||||
|
if (row._exportError) {
|
||||||
|
rowDiv.classList.add('batch-row-error');
|
||||||
|
const btnCell = rowDiv.querySelector('.button-cell');
|
||||||
|
if (btnCell) {
|
||||||
|
const errorEl = document.createElement('div');
|
||||||
|
errorEl.className = 'batch-error-msg';
|
||||||
|
errorEl.textContent = 'Warning — click for details';
|
||||||
|
errorEl.addEventListener('click', () => {
|
||||||
|
document.getElementById('exportResponseMessage').innerHTML = row._exportError.replace(/\n/g, '<br>');
|
||||||
|
document.getElementById('exportResponseModalLabel').textContent = 'Error (id: ' + row.iddatadb + ')';
|
||||||
|
new bootstrap.Modal(document.getElementById('exportResponseModal'), { keyboard: false }).show();
|
||||||
|
});
|
||||||
|
btnCell.appendChild(errorEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rowDiv;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Render visible rows ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function renderVisibleRows() {
|
||||||
|
if (!rowContainer) return;
|
||||||
|
|
||||||
|
// Refresh totalRows in case data was modified externally
|
||||||
|
totalRows = data.length;
|
||||||
|
if (revealedCount < totalRows) revealedCount = totalRows;
|
||||||
|
|
||||||
|
// Destroy Select2 on existing rows
|
||||||
|
$(rowContainer).find('.select2-hidden-accessible').select2('destroy');
|
||||||
|
|
||||||
|
rowContainer.innerHTML = '';
|
||||||
|
const end = Math.min(revealedCount, totalRows);
|
||||||
|
for (let i = 0; i < end; i++) {
|
||||||
|
rowContainer.appendChild(renderRow(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init flatpickr on visible date pickers
|
||||||
|
initFlatpickr();
|
||||||
|
|
||||||
|
updateStatus();
|
||||||
|
updateDirtyIndicator();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSingleRow(rowIndex) {
|
||||||
|
const existing = rowContainer.querySelector(`.grid-row[data-id="${data[rowIndex].iddatadb}"]`);
|
||||||
|
if (!existing) return;
|
||||||
|
|
||||||
|
// Destroy Select2 before removing
|
||||||
|
$(existing).find('.select2-hidden-accessible').select2('destroy');
|
||||||
|
|
||||||
|
const newRow = renderRow(rowIndex);
|
||||||
|
existing.replaceWith(newRow);
|
||||||
|
|
||||||
|
// Init flatpickr
|
||||||
|
$(newRow).find('.date-picker').each(function () {
|
||||||
|
flatpickr(this, { dateFormat: 'Y-m-d', allowInput: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function revealNextBatch() {
|
||||||
|
if (revealedCount >= totalRows) return;
|
||||||
|
const start = revealedCount;
|
||||||
|
revealedCount = Math.min(revealedCount + PAGE_SIZE, totalRows);
|
||||||
|
for (let i = start; i < revealedCount; i++) {
|
||||||
|
rowContainer.appendChild(renderRow(i));
|
||||||
|
}
|
||||||
|
initFlatpickr();
|
||||||
|
updateStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function initFlatpickr() {
|
||||||
|
$(rowContainer).find('.date-picker:not(.flatpickr-input)').each(function () {
|
||||||
|
flatpickr(this, { dateFormat: 'Y-m-d', allowInput: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Headers & Propagate row ────────────────────────────────────────────
|
||||||
|
|
||||||
|
function renderHeaders() {
|
||||||
|
if (!headerContainer) return;
|
||||||
|
headerContainer.innerHTML = '';
|
||||||
|
|
||||||
|
// Actions header
|
||||||
|
const actH = document.createElement('div');
|
||||||
|
actH.className = 'grid-header button-header';
|
||||||
|
actH.style.flex = '0 0 380px';
|
||||||
|
actH.textContent = 'Actions';
|
||||||
|
headerContainer.appendChild(actH);
|
||||||
|
|
||||||
|
let idx = 1;
|
||||||
|
columns.forEach(col => {
|
||||||
|
const h = document.createElement('div');
|
||||||
|
h.className = 'grid-header';
|
||||||
|
h.dataset.index = idx;
|
||||||
|
h.style.flex = `0 0 ${col.width}px`;
|
||||||
|
h.style.position = 'relative';
|
||||||
|
h.innerHTML = esc(col.label) + '<div class="resizer"></div>';
|
||||||
|
headerContainer.appendChild(h);
|
||||||
|
idx++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTopRow() {
|
||||||
|
if (!topContainer) return;
|
||||||
|
topContainer.innerHTML = '';
|
||||||
|
|
||||||
|
// Empty cell for actions column
|
||||||
|
const empty = document.createElement('div');
|
||||||
|
empty.className = 'grid-cell save-all-cell';
|
||||||
|
topContainer.appendChild(empty);
|
||||||
|
|
||||||
|
columns.forEach((col, colIdx) => {
|
||||||
|
const cell = document.createElement('div');
|
||||||
|
cell.className = 'grid-cell grid-top-cell';
|
||||||
|
cell.style.flex = `0 0 ${col.width}px`;
|
||||||
|
|
||||||
|
if (col.editable === false || col.type === 'static' || col.type === 'tracking') {
|
||||||
|
// Empty top cell
|
||||||
|
} else if (col.type === 'idclient') {
|
||||||
|
cell.innerHTML = `<select class="custom-field dropdown-select client-select searchable-client" data-column="idclient" id="clientSelect"><option value="">Select a client...</option></select>` +
|
||||||
|
`<button type="button" class="propagate-btn" data-column="idclient" data-col-index="${colIdx}"><i class="fas fa-arrow-down"></i></button>`;
|
||||||
|
} else if (col.type === 'cliente_fornitore_id') {
|
||||||
|
cell.innerHTML = `<select class="custom-field dropdown-select client-select searchable-client" data-column="cliente_fornitore_id" id="clienteFornitoreSelect"><option value="">Select a supplier...</option></select>` +
|
||||||
|
`<button type="button" class="propagate-btn" data-column="cliente_fornitore_id" data-col-index="${colIdx}"><i class="fas fa-arrow-down"></i></button>`;
|
||||||
|
} else if (col.type === 'fixed' && col.dataType === 'DATE') {
|
||||||
|
cell.innerHTML = `<input type="text" class="custom-field date-picker manual-input fixed-top" data-column="fixed_${col.key}" data-fixed-key="${col.key}" value="">` +
|
||||||
|
`<button type="button" class="propagate-btn" data-column="fixed_${col.key}" data-col-index="${colIdx}"><i class="fas fa-arrow-down"></i></button>`;
|
||||||
|
} else if (col.type === 'fixed') {
|
||||||
|
const isApiField = !!fixedFieldApiConfig[col.key];
|
||||||
|
if (isApiField) {
|
||||||
|
cell.innerHTML = `<select class="custom-field dropdown-select api-fixed-select fixed-top" data-column="fixed_${col.key}" data-fixed-key="${col.key}"><option value="">Seleziona...</option></select>` +
|
||||||
|
`<button type="button" class="propagate-btn" data-column="fixed_${col.key}" data-col-index="${colIdx}"><i class="fas fa-arrow-down"></i></button>`;
|
||||||
|
} else {
|
||||||
|
cell.innerHTML = `<input type="number" class="custom-field manual-input fixed-top" data-column="fixed_${col.key}" data-fixed-key="${col.key}" value="">` +
|
||||||
|
`<button type="button" class="propagate-btn" data-column="fixed_${col.key}" data-col-index="${colIdx}"><i class="fas fa-arrow-down"></i></button>`;
|
||||||
|
}
|
||||||
|
} else if (col.type === 'detail' || col.type === 'main_field') {
|
||||||
|
if (col.dataType === 'SceltaMultipla') {
|
||||||
|
cell.innerHTML = `<select class="custom-field dropdown-select" data-column="col_${colIdx}" data-field-id="${col.fieldId}"><option value="">Seleziona...</option></select>` +
|
||||||
|
`<button type="button" class="propagate-btn" data-column="col_${colIdx}" data-col-index="${colIdx}"><i class="fas fa-arrow-down"></i></button>`;
|
||||||
|
} else if (col.dataType === 'Data') {
|
||||||
|
cell.innerHTML = `<input type="text" class="custom-field date-picker" data-column="col_${colIdx}" value="">` +
|
||||||
|
`<button type="button" class="propagate-btn" data-column="col_${colIdx}" data-col-index="${colIdx}"><i class="fas fa-arrow-down"></i></button>`;
|
||||||
|
} else {
|
||||||
|
cell.innerHTML = `<input type="text" class="custom-field" data-column="col_${colIdx}" value="">` +
|
||||||
|
`<button type="button" class="propagate-btn" data-column="col_${colIdx}" data-col-index="${colIdx}"><i class="fas fa-arrow-down"></i></button>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
topContainer.appendChild(cell);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Populate top row selects
|
||||||
|
populateTopRowSelects();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function populateTopRowSelects() {
|
||||||
|
// Client selects in top row — AJAX mode
|
||||||
|
const clientSel = document.getElementById('clientSelect');
|
||||||
|
if (clientSel) {
|
||||||
|
clientSel.innerHTML = buildClientOptionsHTML(meta.defaultIdclient);
|
||||||
|
$(clientSel).select2(clientSelect2Config);
|
||||||
|
}
|
||||||
|
const fornitSel = document.getElementById('clienteFornitoreSelect');
|
||||||
|
if (fornitSel) {
|
||||||
|
fornitSel.innerHTML = buildClientOptionsHTML('');
|
||||||
|
$(fornitSel).select2(clientSelect2Config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixed field selects in top row
|
||||||
|
topContainer.querySelectorAll('.api-fixed-select').forEach(sel => {
|
||||||
|
const fieldKey = sel.dataset.fixedKey;
|
||||||
|
const config = fixedFieldApiConfig[fieldKey];
|
||||||
|
|
||||||
|
// Client-sourced → init as AJAX Select2
|
||||||
|
if (config && config.source === 'clients') {
|
||||||
|
$(sel).select2(clientSelect2Config);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config && config.dependsOn) {
|
||||||
|
// For dependent fields: merge all cached values across all clientIds
|
||||||
|
const allItems = new Map();
|
||||||
|
for (const [key, items] of Object.entries(fixedFieldCache)) {
|
||||||
|
if (key.startsWith(fieldKey + '_')) {
|
||||||
|
items.forEach(item => allItems.set(String(item.id), item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sel.innerHTML = '<option value="">Seleziona...</option>';
|
||||||
|
[...allItems.values()]
|
||||||
|
.sort((a, b) => String(a.text).localeCompare(String(b.text), 'it', { sensitivity: 'base' }))
|
||||||
|
.forEach(item => sel.add(new Option(item.text, item.id)));
|
||||||
|
} else {
|
||||||
|
const items = fixedFieldCache[fieldKey] || [];
|
||||||
|
sel.innerHTML = '<option value="">Seleziona...</option>';
|
||||||
|
items.forEach(item => sel.add(new Option(item.text, item.id)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Custom field dropdowns in top row — AJAX Select2
|
||||||
|
topContainer.querySelectorAll('.dropdown-select[data-field-id]').forEach(sel => {
|
||||||
|
const fieldId = sel.dataset.fieldId;
|
||||||
|
if (fieldId) $(sel).select2(sceltaSelect2Config(fieldId));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Flatpickr in top row
|
||||||
|
topContainer.querySelectorAll('.date-picker').forEach(el => {
|
||||||
|
flatpickr(el, { dateFormat: 'Y-m-d', allowInput: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Event delegation ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function attachEvents() {
|
||||||
|
if (!rowContainer) return;
|
||||||
|
|
||||||
|
// Cell value changes → write to gridData
|
||||||
|
rowContainer.addEventListener('change', function (e) {
|
||||||
|
const cell = e.target.closest('.grid-cell');
|
||||||
|
if (!cell || !cell.dataset.row) return;
|
||||||
|
const rowIndex = parseInt(cell.dataset.row);
|
||||||
|
const colType = cell.dataset.colType;
|
||||||
|
const colKey = cell.dataset.col;
|
||||||
|
const value = e.target.value;
|
||||||
|
|
||||||
|
if (colType === 'detail' || colType === 'main_field') {
|
||||||
|
if (colType === 'main_field') {
|
||||||
|
data[rowIndex].mainFieldValue = value;
|
||||||
|
}
|
||||||
|
setDetailValue(rowIndex, colKey, value);
|
||||||
|
} else if (colType === 'fixed') {
|
||||||
|
setFixedValue(rowIndex, colKey, value);
|
||||||
|
} else if (colType === 'idclient') {
|
||||||
|
data[rowIndex].idclient = value;
|
||||||
|
data[rowIndex]._dirty = true;
|
||||||
|
} else if (colType === 'cliente_fornitore_id') {
|
||||||
|
data[rowIndex].cliente_fornitore_id = value;
|
||||||
|
data[rowIndex]._dirty = true;
|
||||||
|
console.log('[gridRenderer] cliente_fornitore_id changed:', rowIndex, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visual feedback
|
||||||
|
cell.classList.add('cell-changed');
|
||||||
|
updateDirtyIndicator();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Propagate buttons
|
||||||
|
document.addEventListener('click', function (e) {
|
||||||
|
const btn = e.target.closest('.propagate-btn');
|
||||||
|
if (!btn) return;
|
||||||
|
|
||||||
|
const colIndex = parseInt(btn.dataset.colIndex);
|
||||||
|
const column = btn.dataset.column;
|
||||||
|
if (isNaN(colIndex) && !column) return;
|
||||||
|
|
||||||
|
// Get value from the input/select in the same cell
|
||||||
|
const cell = btn.closest('.grid-cell') || btn.closest('.grid-top-cell');
|
||||||
|
if (!cell) return;
|
||||||
|
const input = cell.querySelector('select, input');
|
||||||
|
if (!input) return;
|
||||||
|
const value = $(input).hasClass('select2-hidden-accessible') ? $(input).val() : input.value;
|
||||||
|
|
||||||
|
// Cache Select2 label so re-render shows name not ID
|
||||||
|
if (value && $(input).hasClass('select2-hidden-accessible')) {
|
||||||
|
const label = $(input).find('option:selected').text();
|
||||||
|
if (label && label !== value) {
|
||||||
|
clientNameCache[value] = label;
|
||||||
|
// Also cache for SceltaMultipla
|
||||||
|
const fieldId = input.dataset?.fieldId;
|
||||||
|
if (fieldId) dropdownNameCache[fieldId + '_' + value] = label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const col = columns[colIndex] || null;
|
||||||
|
|
||||||
|
if (column === 'idclient') {
|
||||||
|
data.forEach(row => { row.idclient = value; row._dirty = true; });
|
||||||
|
} else if (column === 'cliente_fornitore_id') {
|
||||||
|
data.forEach(row => { row.cliente_fornitore_id = value; row._dirty = true; });
|
||||||
|
} else if (column && column.startsWith('fixed_')) {
|
||||||
|
const fixedKey = column.replace('fixed_', '');
|
||||||
|
data.forEach(row => { row.fixedFields[fixedKey] = value; row._dirty = true; });
|
||||||
|
} else if (col) {
|
||||||
|
if (col.type === 'detail' || col.type === 'main_field') {
|
||||||
|
data.forEach(row => {
|
||||||
|
row.details[col.key] = value;
|
||||||
|
if (col.type === 'main_field') row.mainFieldValue = value;
|
||||||
|
row._dirty = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderVisibleRows();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Select2 change events (don't bubble via native addEventListener)
|
||||||
|
$(rowContainer).on('change', '.searchable-client', function () {
|
||||||
|
const cell = this.closest('.grid-cell');
|
||||||
|
if (!cell || !cell.dataset.row) return;
|
||||||
|
const rowIndex = parseInt(cell.dataset.row);
|
||||||
|
const colType = cell.dataset.colType;
|
||||||
|
const value = $(this).val() || '';
|
||||||
|
|
||||||
|
// Cache selected label
|
||||||
|
if (value) {
|
||||||
|
const label = $(this).find('option:selected').text();
|
||||||
|
if (label && label !== value) clientNameCache[value] = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (colType === 'idclient') {
|
||||||
|
data[rowIndex].idclient = value;
|
||||||
|
data[rowIndex]._dirty = true;
|
||||||
|
} else if (colType === 'cliente_fornitore_id') {
|
||||||
|
data[rowIndex].cliente_fornitore_id = value;
|
||||||
|
data[rowIndex]._dirty = true;
|
||||||
|
}
|
||||||
|
cell.classList.add('cell-changed');
|
||||||
|
updateDirtyIndicator();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cache labels on SceltaMultipla change
|
||||||
|
$(rowContainer).on('change', '.searchable-dropdown', function () {
|
||||||
|
const val = $(this).val();
|
||||||
|
const fieldId = this.dataset.fieldId;
|
||||||
|
if (val && fieldId) {
|
||||||
|
const label = $(this).find('option:selected').text();
|
||||||
|
if (label && label !== val) dropdownNameCache[fieldId + '_' + val] = label;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Select2 change on fixed field selects
|
||||||
|
$(rowContainer).on('change', '.api-fixed-select', function () {
|
||||||
|
const cell = this.closest('.grid-cell');
|
||||||
|
if (!cell || !cell.dataset.row) return;
|
||||||
|
const rowIndex = parseInt(cell.dataset.row);
|
||||||
|
const key = this.dataset.fixedKey || cell.dataset.col;
|
||||||
|
const value = $(this).val() || '';
|
||||||
|
|
||||||
|
if (key) {
|
||||||
|
data[rowIndex].fixedFields[key] = value;
|
||||||
|
data[rowIndex]._dirty = true;
|
||||||
|
cell.classList.add('cell-changed');
|
||||||
|
updateDirtyIndicator();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Scroll
|
||||||
|
function onScroll() {
|
||||||
|
if (revealedCount >= totalRows) return;
|
||||||
|
const docEl = document.documentElement;
|
||||||
|
const scrollBottom = Math.max(docEl.scrollTop + docEl.clientHeight, document.body.scrollTop + window.innerHeight);
|
||||||
|
if (scrollBottom >= docEl.scrollHeight - 300) {
|
||||||
|
revealNextBatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.addEventListener('scroll', onScroll, { passive: true });
|
||||||
|
document.querySelector('.page-wrapper')?.addEventListener('scroll', onScroll, { passive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Save ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
window.buildSavePayload = function (rowIndex) {
|
||||||
|
const row = data[rowIndex];
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('iddatadb', row.iddatadb);
|
||||||
|
|
||||||
|
// Details
|
||||||
|
for (const [mappingId, value] of Object.entries(row.details)) {
|
||||||
|
formData.append(`details${mappingId}field_value`, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client
|
||||||
|
if (row.idclient) formData.append('idclient', row.idclient);
|
||||||
|
formData.append('cliente_fornitore_id', row.cliente_fornitore_id || '');
|
||||||
|
|
||||||
|
// Fixed fields → real column names
|
||||||
|
const aliasMap = meta.fixedAliasMap || {};
|
||||||
|
for (const [logicalKey, value] of Object.entries(row.fixedFields)) {
|
||||||
|
const realKey = aliasMap[logicalKey] || logicalKey;
|
||||||
|
formData.append(realKey, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return formData;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── Dirty indicator ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function updateDirtyIndicator() {
|
||||||
|
const dirtyCount = data.filter(r => r._dirty).length;
|
||||||
|
const indicator = document.getElementById('unsavedChanges');
|
||||||
|
const changedEl = document.getElementById('changedRows');
|
||||||
|
if (indicator) {
|
||||||
|
indicator.style.display = dirtyCount > 0 ? '' : 'none';
|
||||||
|
}
|
||||||
|
if (changedEl) {
|
||||||
|
changedEl.textContent = dirtyCount > 0 ? `(${dirtyCount} rows)` : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStatus() {
|
||||||
|
if (!statusEl) return;
|
||||||
|
const shown = Math.min(revealedCount, totalRows);
|
||||||
|
if (shown >= totalRows) {
|
||||||
|
statusEl.textContent = `All ${totalRows} rows loaded`;
|
||||||
|
setTimeout(() => statusEl.style.display = 'none', 2000);
|
||||||
|
} else {
|
||||||
|
statusEl.textContent = `Showing ${shown} of ${totalRows} rows — scroll down for more`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Lazy Select2 for row selects ───────────────────────────────────────
|
||||||
|
|
||||||
|
function initLazySelect2() {
|
||||||
|
$(document).on('mouseenter', '.grid-row .grid-cell', function () {
|
||||||
|
$(this).find('.searchable-client:not(.select2-hidden-accessible)').each(function () {
|
||||||
|
$(this).select2(clientSelect2Config);
|
||||||
|
});
|
||||||
|
$(this).find('.searchable-dropdown:not(.select2-hidden-accessible)').each(function () {
|
||||||
|
const fieldId = this.dataset.fieldId;
|
||||||
|
if (fieldId) $(this).select2(sceltaSelect2Config(fieldId));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Init ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
rowContainer = document.getElementById('gridRowContainer');
|
||||||
|
headerContainer = document.getElementById('gridHeaderContainer');
|
||||||
|
topContainer = document.getElementById('gridTopContainer');
|
||||||
|
|
||||||
|
if (!rowContainer) {
|
||||||
|
console.error('gridRenderer: #gridRowContainer not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status bar
|
||||||
|
statusEl = document.createElement('div');
|
||||||
|
statusEl.style.cssText = 'text-align:center; padding:8px; color:#666; font-size:12px;';
|
||||||
|
if (totalRows > PAGE_SIZE) {
|
||||||
|
statusEl.textContent = `Loading data...`;
|
||||||
|
rowContainer.parentElement.appendChild(statusEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preload all dropdown/field data
|
||||||
|
await preloadAllData();
|
||||||
|
|
||||||
|
// Render
|
||||||
|
renderHeaders();
|
||||||
|
renderTopRow();
|
||||||
|
renderVisibleRows();
|
||||||
|
|
||||||
|
// Events
|
||||||
|
attachEvents();
|
||||||
|
initLazySelect2();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start when DOM ready
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Public API ─────────────────────────────────────────────────────────
|
||||||
|
window.gridRenderer = {
|
||||||
|
renderVisibleRows,
|
||||||
|
renderSingleRow,
|
||||||
|
revealNextBatch,
|
||||||
|
buildSavePayload: window.buildSavePayload,
|
||||||
|
getData: () => data,
|
||||||
|
getMeta: () => meta,
|
||||||
|
getClientData: () => clientData,
|
||||||
|
getDirtyRows: () => data.filter(r => r._dirty).map((r, i) => i),
|
||||||
|
clearDirty: (rowIndex) => { data[rowIndex]._dirty = false; updateDirtyIndicator(); },
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
||||||
+597
-226
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,7 @@ $columns = json_decode(urldecode($_POST['columns'] ?? '[]'), true);
|
|||||||
$rows = json_decode(urldecode($_POST['rows'] ?? '[]'), true);
|
$rows = json_decode(urldecode($_POST['rows'] ?? '[]'), true);
|
||||||
$excelrows = json_decode(urldecode($_POST['excelrows'] ?? '[]'), true);
|
$excelrows = json_decode(urldecode($_POST['excelrows'] ?? '[]'), true);
|
||||||
|
|
||||||
$newFilename = htmlspecialchars($_POST['filename']);
|
$newFilename = $_POST['filename'];
|
||||||
|
|
||||||
$_SESSION['template_id'] = $template_id;
|
$_SESSION['template_id'] = $template_id;
|
||||||
$_SESSION['selected_rows'] = $selected_rows;
|
$_SESSION['selected_rows'] = $selected_rows;
|
||||||
@@ -47,7 +47,7 @@ $pdo = $db->getConnection();
|
|||||||
$importReferenceCode = date('YmdHis') . '-' . uniqid();
|
$importReferenceCode = date('YmdHis') . '-' . uniqid();
|
||||||
|
|
||||||
// Recupera tutti i mapping dal template
|
// Recupera tutti i mapping dal template
|
||||||
$stmt = $pdo->prepare("SELECT id, excel_column, data_type, is_required, manual_default, is_manual, field_label, field_id, main_field FROM template_mapping WHERE template_id = ?");
|
$stmt = $pdo->prepare("SELECT id, excel_column, data_type, is_required, manual_default, is_manual, field_label, field_id, main_field, auto_value FROM template_mapping WHERE template_id = ?");
|
||||||
$stmt->execute([$template_id]);
|
$stmt->execute([$template_id]);
|
||||||
$allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ foreach ($selected_rows as $rowIndex) {
|
|||||||
case 'Testo':
|
case 'Testo':
|
||||||
case 'VARCHAR':
|
case 'VARCHAR':
|
||||||
default:
|
default:
|
||||||
$fieldValue = !empty($fieldValue) ? htmlspecialchars((string)$fieldValue) : ($mapping['manual_default'] ?? '');
|
$fieldValue = !empty($fieldValue) ? (string)$fieldValue : ($mapping['manual_default'] ?? '');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -135,6 +135,15 @@ foreach ($selected_rows as $rowIndex) {
|
|||||||
$fieldValue = date('Y-m-d');
|
$fieldValue = date('Y-m-d');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Apply auto_value if field is still empty
|
||||||
|
if (($fieldValue === null || $fieldValue === '') && !empty($mapping['auto_value']) && $mapping['auto_value'] !== 'none') {
|
||||||
|
if ($mapping['auto_value'] === 'import_date') {
|
||||||
|
$fieldValue = date('Y-m-d');
|
||||||
|
} elseif ($mapping['auto_value'] === 'import_time') {
|
||||||
|
$fieldValue = date('H:i');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($mapping['is_required'] && (is_null($fieldValue) || $fieldValue === '')) {
|
if ($mapping['is_required'] && (is_null($fieldValue) || $fieldValue === '')) {
|
||||||
error_log("Required field missing for mapping ID: " . $mapping['id'] . ", field: " . $mapping['field_label']);
|
error_log("Required field missing for mapping ID: " . $mapping['id'] . ", field: " . $mapping['field_label']);
|
||||||
}
|
}
|
||||||
@@ -147,28 +156,6 @@ foreach ($selected_rows as $rowIndex) {
|
|||||||
|
|
||||||
$_SESSION['inserted_ids'] = $insertedIds;
|
$_SESSION['inserted_ids'] = $insertedIds;
|
||||||
|
|
||||||
$params = [
|
header("Location: imported.php?id=" . urlencode($template_id) . "&importref=" . urlencode($importReferenceCode));
|
||||||
'template_id' => $template_id,
|
|
||||||
'filename' => $newFilename,
|
|
||||||
];
|
|
||||||
|
|
||||||
?>
|
|
||||||
<form id="redirectForm" action="import_edit2.php" method="post">
|
|
||||||
<input type="hidden" name="template_id" value="<?= htmlspecialchars($template_id) ?>">
|
|
||||||
<input type="hidden" name="filename" value="<?= htmlspecialchars($newFilename) ?>">
|
|
||||||
<?php foreach ($selected_rows as $row): ?>
|
|
||||||
<input type="hidden" name="selected_rows[]" value="<?= htmlspecialchars($row) ?>">
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php foreach ($insertedIds as $id): ?>
|
|
||||||
<input type="hidden" name="inserted_ids[]" value="<?= htmlspecialchars($id) ?>">
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<input type="hidden" name="columns" value='<?= json_encode($columns) ?>'>
|
|
||||||
<input type="hidden" name="rows" value='<?= json_encode($rows) ?>'>
|
|
||||||
<input type="hidden" name="excelrows" value='<?= json_encode($excelrows) ?>'>
|
|
||||||
</form>
|
|
||||||
<script>
|
|
||||||
document.getElementById('redirectForm').submit();
|
|
||||||
</script>
|
|
||||||
<?php
|
|
||||||
exit;
|
exit;
|
||||||
?>
|
?>
|
||||||
@@ -143,9 +143,8 @@ error_log("Loaded template: " . print_r($template, true));
|
|||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
<?php include('top_stat_widget.php'); ?>
|
<?php include('top_stat_widget.php'); ?>
|
||||||
<div class="mb-3 text">
|
<div class="mb-3 text">
|
||||||
<a href="historical_trf.php?id=<?= $id ?>&status=i" class="btn btn-warning me-2">Imported (i)</a>
|
<a href="imported.php?id=<?= $id ?>" class="btn btn-warning me-2">Imported (i)</a>
|
||||||
<a href="historical_trf.php?id=<?= $id ?>&status=P" class="btn btn-primary me-2">In Progress (P)</a>
|
<a href="tolims.php?id=<?= $id ?>" class="btn btn-success">To LIMS (l)</a>
|
||||||
<a href="historical_trf.php?id=<?= $id ?>&status=l" class="btn btn-success">To LIMS (l)</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card radius-10">
|
<div class="card radius-10">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@@ -313,20 +312,27 @@ error_log("Loaded template: " . print_r($template, true));
|
|||||||
function showTable(data) {
|
function showTable(data) {
|
||||||
console.log('Mostro tabella con dati:', data);
|
console.log('Mostro tabella con dati:', data);
|
||||||
let html = `
|
let html = `
|
||||||
<form id="selectRowsForm" action="import_insert.php" method="POST">
|
<form id="selectRowsForm" action="import_insert.php" method="POST">
|
||||||
<input type="hidden" name="template_id" value="${data.template_id}">
|
<input type="hidden" name="template_id" value="${data.template_id}">
|
||||||
<input type="hidden" name="columns" value="${encodeURIComponent(JSON.stringify(data.columns))}">
|
<input type="hidden" name="columns" value="${encodeURIComponent(JSON.stringify(data.columns))}">
|
||||||
<input type="hidden" name="rows" value="${encodeURIComponent(JSON.stringify(data.rows))}">
|
<input type="hidden" name="rows" value="${encodeURIComponent(JSON.stringify(data.rows))}">
|
||||||
<input type="hidden" name="excelrows" value="${encodeURIComponent(JSON.stringify(data.excel_data.map(r => r.excelrow)))}">
|
<input type="hidden" name="excelrows" value="${encodeURIComponent(JSON.stringify(data.excel_data.map(r => r.excelrow)))}">
|
||||||
|
|
||||||
<input type="hidden" name="filename" value="${data.filename}">
|
<input type="hidden" name="filename" value="${data.filename}">
|
||||||
|
|
||||||
|
<!-- TOP BUTTON -->
|
||||||
|
<div class="d-flex justify-content-end mb-3">
|
||||||
|
<button type="submit" class="btn btn-primary" id="proceedButtonTop" disabled>Prosegui</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<table class="table table-striped table-bordered">
|
<table class="table table-striped table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th><input type="checkbox" id="selectAll"> Seleziona</th>
|
<th><input type="checkbox" id="selectAll"> Seleziona</th>
|
||||||
${data.columns.map(col => `<th>${col || 'Colonna senza nome'}<div class="resize-handle"></div></th>`).join('')}
|
${data.columns.map(col => {
|
||||||
|
const label = !col ? 'Colonna senza nome' : (col.match(/^__empty_\d+__$/) ? 'Colonna senza nome' : col);
|
||||||
|
return `<th>${label}<div class="resize-handle"></div></th>`;
|
||||||
|
}).join('')}
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="column-filters">
|
<tr class="column-filters">
|
||||||
<th></th>
|
<th></th>
|
||||||
@@ -350,30 +356,52 @@ error_log("Loaded template: " . print_r($template, true));
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary mt-3" id="proceedButton" disabled>Prosegui</button>
|
|
||||||
</form>
|
<!-- BOTTOM BUTTON -->
|
||||||
`;
|
<button type="submit" class="btn btn-primary mt-3" id="proceedButtonBottom" disabled>Prosegui</button>
|
||||||
|
</form>
|
||||||
|
`;
|
||||||
tableContainer.innerHTML = html;
|
tableContainer.innerHTML = html;
|
||||||
|
|
||||||
const proceedButton = document.getElementById('proceedButton');
|
const proceedButtonTop = document.getElementById('proceedButtonTop');
|
||||||
|
const proceedButtonBottom = document.getElementById('proceedButtonBottom');
|
||||||
const selectAllCheckbox = document.getElementById('selectAll');
|
const selectAllCheckbox = document.getElementById('selectAll');
|
||||||
const checkboxes = document.querySelectorAll('.row-checkbox');
|
const checkboxes = document.querySelectorAll('.row-checkbox');
|
||||||
|
|
||||||
function updateProceedButton() {
|
function updateProceedButton() {
|
||||||
proceedButton.disabled = !Array.from(checkboxes).some(cb => cb.checked);
|
const enabled = Array.from(checkboxes).some(cb => cb.checked);
|
||||||
|
|
||||||
|
if (proceedButtonTop) proceedButtonTop.disabled = !enabled;
|
||||||
|
if (proceedButtonBottom) proceedButtonBottom.disabled = !enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
selectAllCheckbox.addEventListener('change', function() {
|
selectAllCheckbox.addEventListener('change', function() {
|
||||||
checkboxes.forEach(checkbox => {
|
const visibleRows = Array.from(document.querySelectorAll('.table tbody tr'))
|
||||||
|
.filter(row => row.style.display !== 'none');
|
||||||
|
|
||||||
|
visibleRows.forEach(row => {
|
||||||
|
const checkbox = row.querySelector('.row-checkbox');
|
||||||
|
if (checkbox) {
|
||||||
checkbox.checked = this.checked;
|
checkbox.checked = this.checked;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
updateProceedButton();
|
updateProceedButton();
|
||||||
});
|
});
|
||||||
|
|
||||||
checkboxes.forEach(checkbox => {
|
checkboxes.forEach(checkbox => {
|
||||||
checkbox.addEventListener('change', function() {
|
checkbox.addEventListener('change', function() {
|
||||||
console.log('Checkbox changed, checked:', this.checked, 'excelrow:', this.dataset.excelrow);
|
console.log('Checkbox changed, checked:', this.checked, 'excelrow:', this.dataset.excelrow);
|
||||||
selectAllCheckbox.checked = Array.from(checkboxes).every(cb => cb.checked);
|
|
||||||
|
const visibleCheckboxes = Array.from(document.querySelectorAll('.table tbody tr'))
|
||||||
|
.filter(row => row.style.display !== 'none')
|
||||||
|
.map(row => row.querySelector('.row-checkbox'))
|
||||||
|
.filter(cb => cb !== null);
|
||||||
|
|
||||||
|
selectAllCheckbox.checked =
|
||||||
|
visibleCheckboxes.length > 0 &&
|
||||||
|
visibleCheckboxes.every(cb => cb.checked);
|
||||||
|
|
||||||
updateProceedButton();
|
updateProceedButton();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -421,12 +449,11 @@ error_log("Loaded template: " . print_r($template, true));
|
|||||||
|
|
||||||
function applyColumnFilters() {
|
function applyColumnFilters() {
|
||||||
rows.forEach(row => {
|
rows.forEach(row => {
|
||||||
// Le celle di data partono da index 1 (perché index 0 è checkbox)
|
|
||||||
let visible = true;
|
let visible = true;
|
||||||
|
|
||||||
for (const [colIndexStr, filterValue] of Object.entries(activeFilters)) {
|
for (const [colIndexStr, filterValue] of Object.entries(activeFilters)) {
|
||||||
const colIndex = parseInt(colIndexStr, 10); // 0..N-1 sulle colonne dati
|
const colIndex = parseInt(colIndexStr, 10);
|
||||||
const cell = row.cells[colIndex + 1]; // +1 per saltare la colonna checkbox
|
const cell = row.cells[colIndex + 1];
|
||||||
|
|
||||||
const cellText = (cell?.textContent || '').toLowerCase();
|
const cellText = (cell?.textContent || '').toLowerCase();
|
||||||
const searchText = (filterValue || '').toLowerCase().trim();
|
const searchText = (filterValue || '').toLowerCase().trim();
|
||||||
@@ -439,6 +466,17 @@ error_log("Loaded template: " . print_r($template, true));
|
|||||||
|
|
||||||
row.style.display = visible ? '' : 'none';
|
row.style.display = visible ? '' : 'none';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const visibleCheckboxes = Array.from(document.querySelectorAll('.table tbody tr'))
|
||||||
|
.filter(row => row.style.display !== 'none')
|
||||||
|
.map(row => row.querySelector('.row-checkbox'))
|
||||||
|
.filter(cb => cb !== null);
|
||||||
|
|
||||||
|
selectAllCheckbox.checked =
|
||||||
|
visibleCheckboxes.length > 0 &&
|
||||||
|
visibleCheckboxes.every(cb => cb.checked);
|
||||||
|
|
||||||
|
updateProceedButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
filterInputs.forEach(input => {
|
filterInputs.forEach(input => {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,8 @@ $nameuser = $user->present()->first_name;
|
|||||||
$surnameuser = $user->present()->last_name;
|
$surnameuser = $user->present()->last_name;
|
||||||
$emailuser = $user->present()->email;
|
$emailuser = $user->present()->email;
|
||||||
$avatar = $user->present()->avatar;
|
$avatar = $user->present()->avatar;
|
||||||
|
$lims_user_id = $user->lims_user_id ?? '';
|
||||||
|
$lims_global_user_id = $user->lims_global_user_id ?? '';
|
||||||
|
|
||||||
$kindofrole = $user->present()->role_id;
|
$kindofrole = $user->present()->role_id;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php include('include/headscript.php');
|
<?php include('include/headscript.php');
|
||||||
|
|
||||||
// Recupera tutte le routine dal database
|
// Retrieve all routines from database
|
||||||
$db = DBHandlerSelect::getInstance();
|
$db = DBHandlerSelect::getInstance();
|
||||||
$pdo = $db->getConnection();
|
$pdo = $db->getConnection();
|
||||||
$stmt = $pdo->prepare("SELECT * FROM routine");
|
$stmt = $pdo->prepare("SELECT * FROM routine");
|
||||||
@@ -18,26 +18,29 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
<?php include('cssinclude.php'); ?>
|
<?php include('cssinclude.php'); ?>
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||||
<title>Insert XLS Template <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
<title>Insert Template <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<?php include('include/navbar.php'); ?>
|
<?php include('include/navbar.php'); ?>
|
||||||
<?php include('include/topbar.php'); ?>
|
<?php include('include/topbar.php'); ?>
|
||||||
|
|
||||||
<div class="page-wrapper">
|
<div class="page-wrapper">
|
||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
|
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="mb-0">Insert new XLS Template</h5>
|
<h5 class="mb-0">Insert New Template</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="mb-2">Fill the following form in order to create a new import XLS template</p>
|
<p class="mb-2">Fill the following form in order to create a new import template</p>
|
||||||
<p class="mb-2">Mandatory Fields</p>
|
<p class="mb-2">Mandatory Fields</p>
|
||||||
<ul class="mb-0">
|
<ul class="mb-0">
|
||||||
<li>Template Name</li>
|
<li>Template Name</li>
|
||||||
<li>Row Header and Column Header: where the title of the excel starts</li>
|
<li>Source Type</li>
|
||||||
<li>Schema and client</li>
|
<li>Schema and Client</li>
|
||||||
|
<li>Row Header and Column Header only for XLS templates</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,22 +53,33 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<form id="insertTemplateForm" method="POST">
|
<form id="insertTemplateForm" method="POST">
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label"><?= htmlspecialchars($templatename, ENT_QUOTES, 'UTF-8'); ?> *</label>
|
<label class="form-label"><?= htmlspecialchars($templatename, ENT_QUOTES, 'UTF-8'); ?> *</label>
|
||||||
<input type="text" name="name" class="form-control" required>
|
<input type="text" name="name" class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label"><?= htmlspecialchars($rowheader, ENT_QUOTES, 'UTF-8'); ?> *</label>
|
<label class="form-label">Source Type *</label>
|
||||||
<input type="number" name="header_row" class="form-control" required>
|
<select name="source_type" id="sourceType" class="form-control" required>
|
||||||
|
<option value="XLS" selected>XLS</option>
|
||||||
|
<option value="API">API</option>
|
||||||
|
</select>
|
||||||
|
<small class="text-muted">Choose the source used by this template</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3" id="headerRowWrapper">
|
||||||
|
<label class="form-label"><?= htmlspecialchars($rowheader, ENT_QUOTES, 'UTF-8'); ?> *</label>
|
||||||
|
<input type="number" name="header_row" id="headerRow" class="form-control" value="1" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3" id="startColumnWrapper">
|
||||||
<label class="form-label"><?= htmlspecialchars($columnheader, ENT_QUOTES, 'UTF-8'); ?> *</label>
|
<label class="form-label"><?= htmlspecialchars($columnheader, ENT_QUOTES, 'UTF-8'); ?> *</label>
|
||||||
<input type="text" name="start_column" class="form-control" required>
|
<input type="text" name="start_column" id="startColumn" class="form-control" value="A" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -86,12 +100,12 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Button Background Color</label>
|
<label class="form-label">Button Background Color</label>
|
||||||
<input type="color" name="button_bg_color" class="form-control" value="#007bff">
|
<input type="color" name="button_bg_color" class="form-control form-control-color" value="#007bff">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Button Text Color</label>
|
<label class="form-label">Button Text Color</label>
|
||||||
<input type="color" name="button_text_color" class="form-control" value="#ffffff">
|
<input type="color" name="button_text_color" class="form-control form-control-color" value="#ffffff">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -125,6 +139,7 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
</option>
|
</option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<div id="routineDetails" class="mt-2" style="display: none;">
|
<div id="routineDetails" class="mt-2" style="display: none;">
|
||||||
<h6>Routine Details</h6>
|
<h6>Routine Details</h6>
|
||||||
<p><strong>Name:</strong> <span id="routineName"></span></p>
|
<p><strong>Name:</strong> <span id="routineName"></span></p>
|
||||||
@@ -142,8 +157,10 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="overlay toggle-icon"></div>
|
<div class="overlay toggle-icon"></div>
|
||||||
<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>
|
||||||
<?php include('include/footer.php'); ?>
|
<?php include('include/footer.php'); ?>
|
||||||
@@ -167,6 +184,12 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
const routineAction2 = document.getElementById("routineAction2");
|
const routineAction2 = document.getElementById("routineAction2");
|
||||||
const routineAction3 = document.getElementById("routineAction3");
|
const routineAction3 = document.getElementById("routineAction3");
|
||||||
|
|
||||||
|
const sourceType = document.getElementById("sourceType");
|
||||||
|
const headerRowWrapper = document.getElementById("headerRowWrapper");
|
||||||
|
const startColumnWrapper = document.getElementById("startColumnWrapper");
|
||||||
|
const headerRow = document.getElementById("headerRow");
|
||||||
|
const startColumn = document.getElementById("startColumn");
|
||||||
|
|
||||||
if (!form || !clientLoadingStatus || !schemaLoadingStatus || !routineSelect || !routineDetails) {
|
if (!form || !clientLoadingStatus || !schemaLoadingStatus || !routineSelect || !routineDetails) {
|
||||||
alert("Errore: Uno o più elementi della pagina non sono stati trovati. Contatta l'amministratore.");
|
alert("Errore: Uno o più elementi della pagina non sono stati trovati. Contatta l'amministratore.");
|
||||||
return;
|
return;
|
||||||
@@ -187,30 +210,66 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
allowClear: true
|
allowClear: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function updateSourceFields() {
|
||||||
|
const selectedSource = sourceType.value;
|
||||||
|
|
||||||
|
if (selectedSource === 'API') {
|
||||||
|
headerRowWrapper.style.opacity = '0.6';
|
||||||
|
startColumnWrapper.style.opacity = '0.6';
|
||||||
|
|
||||||
|
headerRow.required = false;
|
||||||
|
startColumn.required = false;
|
||||||
|
|
||||||
|
headerRow.disabled = true;
|
||||||
|
startColumn.disabled = true;
|
||||||
|
} else {
|
||||||
|
headerRowWrapper.style.opacity = '1';
|
||||||
|
startColumnWrapper.style.opacity = '1';
|
||||||
|
|
||||||
|
headerRow.required = true;
|
||||||
|
startColumn.required = true;
|
||||||
|
|
||||||
|
headerRow.disabled = false;
|
||||||
|
startColumn.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceType.addEventListener('change', updateSourceFields);
|
||||||
|
updateSourceFields();
|
||||||
|
|
||||||
async function loadClients() {
|
async function loadClients() {
|
||||||
try {
|
try {
|
||||||
clientLoadingStatus.style.display = 'inline';
|
clientLoadingStatus.style.display = 'inline';
|
||||||
clientLoadingStatus.textContent = 'Recupero clienti in corso...';
|
clientLoadingStatus.textContent = 'Recupero clienti in corso...';
|
||||||
|
|
||||||
const response = await fetch("get_clienti.php", {
|
const response = await fetch("get_clienti.php", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (!response.ok) throw new Error(data.error || `Errore HTTP: ${response.status}`);
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(data.error || `Errore HTTP: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
const select = document.getElementById("clientSelect");
|
const select = document.getElementById("clientSelect");
|
||||||
select.innerHTML = '<option value="">Select a client...</option>';
|
select.innerHTML = '<option value="">Select a client...</option>';
|
||||||
|
|
||||||
data.value.forEach(client => {
|
data.value.forEach(client => {
|
||||||
const nome = client.Nominativo || "Nome non disponibile";
|
const nome = client.Nominativo || "Nome non disponibile";
|
||||||
const id = client.IdCliente || "ID non disponibile";
|
const id = client.IdCliente || "ID non disponibile";
|
||||||
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
|
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
|
||||||
select.add(option);
|
select.add(option);
|
||||||
});
|
});
|
||||||
|
|
||||||
$(select).trigger('change');
|
$(select).trigger('change');
|
||||||
clientLoadingStatus.textContent = "Clienti caricati.";
|
clientLoadingStatus.textContent = "Clienti caricati.";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
clientLoadingStatus.textContent = "Errore nel caricamento.";
|
clientLoadingStatus.textContent = "Errore nel caricamento.";
|
||||||
|
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Errore!",
|
title: "Errore!",
|
||||||
text: "Impossibile caricare i clienti: " + error.message,
|
text: "Impossibile caricare i clienti: " + error.message,
|
||||||
@@ -226,26 +285,43 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
try {
|
try {
|
||||||
schemaLoadingStatus.style.display = 'inline';
|
schemaLoadingStatus.style.display = 'inline';
|
||||||
schemaLoadingStatus.textContent = 'Caricamento schemi in corso...';
|
schemaLoadingStatus.textContent = 'Caricamento schemi in corso...';
|
||||||
|
|
||||||
const response = await fetch("get_schemi.php", {
|
const response = await fetch("get_schemi.php", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (!response.ok) throw new Error(data.error || `Errore HTTP: ${response.status}`);
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(data.error || `Errore HTTP: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
const select = document.getElementById("schemaSelect");
|
const select = document.getElementById("schemaSelect");
|
||||||
select.innerHTML = '<option value="">Select a schema...</option>';
|
select.innerHTML = '<option value="">Select a schema...</option>';
|
||||||
data.value.forEach(schema => {
|
|
||||||
|
const sortedSchemas = [...data.value].sort((a, b) => {
|
||||||
|
const nomeA = (a.Nome || "").trim().toLowerCase();
|
||||||
|
const nomeB = (b.Nome || "").trim().toLowerCase();
|
||||||
|
return nomeA.localeCompare(nomeB, 'it', {
|
||||||
|
sensitivity: 'base'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
sortedSchemas.forEach(schema => {
|
||||||
const nome = schema.Nome || "Nome non disponibile";
|
const nome = schema.Nome || "Nome non disponibile";
|
||||||
const id = schema.IdSchemaCustomFields || "ID non disponibile";
|
const id = schema.IdSchemaCustomFields || "ID non disponibile";
|
||||||
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
|
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
|
||||||
select.add(option);
|
select.add(option);
|
||||||
});
|
});
|
||||||
|
|
||||||
$(select).trigger('change');
|
$(select).trigger('change');
|
||||||
schemaLoadingStatus.textContent = "Schemi caricati.";
|
schemaLoadingStatus.textContent = "Schemi caricati.";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
schemaLoadingStatus.textContent = "Errore nel caricamento.";
|
schemaLoadingStatus.textContent = "Errore nel caricamento.";
|
||||||
|
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Errore!",
|
title: "Errore!",
|
||||||
text: "Impossibile caricare gli schemi: " + error.message,
|
text: "Impossibile caricare gli schemi: " + error.message,
|
||||||
@@ -270,6 +346,7 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadData();
|
loadData();
|
||||||
|
|
||||||
const routines = <?php echo json_encode($routines); ?>;
|
const routines = <?php echo json_encode($routines); ?>;
|
||||||
@@ -277,8 +354,10 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
function updateRoutineDetails() {
|
function updateRoutineDetails() {
|
||||||
const selectedId = routineSelect.value;
|
const selectedId = routineSelect.value;
|
||||||
routineDetails.style.display = selectedId ? 'block' : 'none';
|
routineDetails.style.display = selectedId ? 'block' : 'none';
|
||||||
|
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
const routine = routines.find(r => r.idroutine == selectedId);
|
const routine = routines.find(r => r.idroutine == selectedId);
|
||||||
|
|
||||||
if (routine) {
|
if (routine) {
|
||||||
routineName.textContent = routine.name || 'N/A';
|
routineName.textContent = routine.name || 'N/A';
|
||||||
routineDescription.textContent = routine.description || 'N/A';
|
routineDescription.textContent = routine.description || 'N/A';
|
||||||
@@ -300,6 +379,7 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
routineAction3.textContent = '';
|
routineAction3.textContent = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
routineSelect.addEventListener('change', updateRoutineDetails);
|
routineSelect.addEventListener('change', updateRoutineDetails);
|
||||||
updateRoutineDetails();
|
updateRoutineDetails();
|
||||||
|
|
||||||
@@ -350,6 +430,7 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
const nameMatch = optionText.match(/^(.+?)(?:\s*\(ID:\s*\d+\))?$/);
|
const nameMatch = optionText.match(/^(.+?)(?:\s*\(ID:\s*\d+\))?$/);
|
||||||
schemaName = nameMatch ? nameMatch[1].trim() : optionText;
|
schemaName = nameMatch ? nameMatch[1].trim() : optionText;
|
||||||
}
|
}
|
||||||
|
|
||||||
formData.append("idschema", schemaId);
|
formData.append("idschema", schemaId);
|
||||||
formData.append("schemaname", schemaName);
|
formData.append("schemaname", schemaName);
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,57 @@ if (!$iddatadb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$stmt = $pdo->prepare("SELECT id, iddatadb, part_number, part_description, idmatrice, note, dateexpiry FROM identification_parts WHERE iddatadb = :iddatadb ORDER BY part_number ASC");
|
// 1) prendo templateid da datadb
|
||||||
|
$stmtTpl = $pdo->prepare("SELECT templateid FROM datadb WHERE iddatadb = :iddatadb LIMIT 1");
|
||||||
|
$stmtTpl->execute([':iddatadb' => $iddatadb]);
|
||||||
|
$templateid = $stmtTpl->fetchColumn();
|
||||||
|
|
||||||
|
// 2) prendo (max 1) field_id visibile in parts
|
||||||
|
$extraFieldId = null;
|
||||||
|
if ($templateid) {
|
||||||
|
$stmtEF = $pdo->prepare("SELECT field_id FROM template_mapping WHERE template_id = :templateid AND is_visible_parts = 1 ORDER BY id ASC LIMIT 1");
|
||||||
|
$stmtEF->execute([':templateid' => $templateid]);
|
||||||
|
$extraFieldId = $stmtEF->fetchColumn();
|
||||||
|
if ($extraFieldId !== false) $extraFieldId = (int)$extraFieldId;
|
||||||
|
else $extraFieldId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) carico parts + join su tabella figlia
|
||||||
|
if ($extraFieldId) {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
p.id, p.iddatadb, p.part_number, p.part_description, p.idmatrice, p.note, p.dateexpiry,
|
||||||
|
cf.value_id AS extra_value_id,
|
||||||
|
cf.value_text AS extra_value_text
|
||||||
|
FROM identification_parts p
|
||||||
|
LEFT JOIN identification_parts_customfields cf
|
||||||
|
ON cf.part_id = p.id AND cf.field_id = :extraFieldId
|
||||||
|
WHERE p.iddatadb = :iddatadb
|
||||||
|
ORDER BY p.part_number ASC
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':iddatadb' => $iddatadb,
|
||||||
|
':extraFieldId' => $extraFieldId
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT id, iddatadb, part_number, part_description, idmatrice, note, dateexpiry,
|
||||||
|
NULL AS extra_value_id, NULL AS extra_value_text
|
||||||
|
FROM identification_parts
|
||||||
|
WHERE iddatadb = :iddatadb
|
||||||
|
ORDER BY part_number ASC
|
||||||
|
");
|
||||||
$stmt->execute([':iddatadb' => $iddatadb]);
|
$stmt->execute([':iddatadb' => $iddatadb]);
|
||||||
|
}
|
||||||
|
|
||||||
$parts = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$parts = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'parts' => $parts]);
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'parts' => $parts,
|
||||||
|
'extra_field_id' => $extraFieldId,
|
||||||
|
'debug_sql' => ($extraFieldId ? 'WITH_CF_JOIN' : 'NO_CF')
|
||||||
|
]);
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
echo json_encode(['success' => false, 'message' => 'Errore nel caricamento: ' . $e->getMessage()]);
|
echo json_encode(['success' => false, 'message' => 'Errore nel caricamento: ' . $e->getMessage()]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
CAMPIONE #0
|
||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"Commessa": 563528,
|
||||||
|
"Matrice": 3879,
|
||||||
|
"SottoMatrice": null,
|
||||||
|
"SchemaCustomField": 82,
|
||||||
|
"NoteWeb": "Parte AAA",
|
||||||
|
"ConsegnaRichiesta": "2026-01-30"
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#Campione\/$entity",
|
||||||
|
"IdCampione": 734582,
|
||||||
|
"CodiceCampione": "10978",
|
||||||
|
"CodiceCampioneWeb": "10978",
|
||||||
|
"StatoCampione": "Nuovo",
|
||||||
|
"DataCreazioneWeb": "2026-03-11T14:10:02.1595894+01:00",
|
||||||
|
"RiferimentoWeb": "",
|
||||||
|
"ConsegnaRichiesta": "2026-01-30T00:00:00+01:00",
|
||||||
|
"DataAccettazioneLims": null,
|
||||||
|
"Riferimento": null,
|
||||||
|
"NoteWeb": "Parte AAA",
|
||||||
|
"GruppiRicercati": null
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
CAMPIONE #1
|
||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"Commessa": 563528,
|
||||||
|
"Matrice": 3879,
|
||||||
|
"SottoMatrice": null,
|
||||||
|
"SchemaCustomField": 82,
|
||||||
|
"NoteWeb": "PARE BBB",
|
||||||
|
"ConsegnaRichiesta": "2026-01-30"
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#Campione\/$entity",
|
||||||
|
"IdCampione": 734583,
|
||||||
|
"CodiceCampione": "10979",
|
||||||
|
"CodiceCampioneWeb": "10979",
|
||||||
|
"StatoCampione": "Nuovo",
|
||||||
|
"DataCreazioneWeb": "2026-03-11T14:10:03.9972635+01:00",
|
||||||
|
"RiferimentoWeb": "",
|
||||||
|
"ConsegnaRichiesta": "2026-01-30T00:00:00+01:00",
|
||||||
|
"DataAccettazioneLims": null,
|
||||||
|
"Riferimento": null,
|
||||||
|
"NoteWeb": "PARE BBB",
|
||||||
|
"GruppiRicercati": null
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
|||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(563528)/ImportaCommessa' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"IdUtente": 285
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
|
||||||
|
"IdCommessa": 563528,
|
||||||
|
"CodiceCommessa": "26C0029",
|
||||||
|
"RiferimentoCertificato": null,
|
||||||
|
"StatoCommessaWeb": "Elaborata",
|
||||||
|
"DataCreazioneWeb": "2026-03-11T14:10:00.897+01:00",
|
||||||
|
"CodiceCommessaWeb": "26C0029",
|
||||||
|
"DataInviatoWeb": "2026-03-11T14:10:11.76+01:00",
|
||||||
|
"Richiedente": "Test Web Import",
|
||||||
|
"Descrizione": "TEST CommessaWeb"
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
Photos for CommessaWeb 563528 (iddatadb=1259):
|
||||||
|
Total photos found: 2, campioni: 2
|
||||||
|
|
||||||
|
=== Campione 734582 (main) ===
|
||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione(734582)/UploadCampioneFile' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--form 'file=@C:\xampp\htdocs\trf_certest\public\photostrf\1259-20260311130930-104a2ca5-8a4c-4df8-b2ca-a28f518b7b25.jpg'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CampioneFiles\/$entity",
|
||||||
|
"IdCampioneFile": 1779561,
|
||||||
|
"FileName": "1259-20260311130930-104a2ca5-8a4c-4df8-b2ca-a28f518b7b25.jpg",
|
||||||
|
"Web": false,
|
||||||
|
"AllegaAlRapporto": false,
|
||||||
|
"StampaNelRapporto": false,
|
||||||
|
"ReportGermania": false,
|
||||||
|
"PrimaPagina": false,
|
||||||
|
"Duplica": false,
|
||||||
|
"LastUpdate": "2026-03-11T14:10:06.7051549+01:00",
|
||||||
|
"Titolo": null
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione(734582)/UploadCampioneFile' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--form 'file=@C:\xampp\htdocs\trf_certest\public\photostrf\1259-20260311130930-1e672dd9-5420-4432-b422-02d8d271c178.jpg'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CampioneFiles\/$entity",
|
||||||
|
"IdCampioneFile": 1779562,
|
||||||
|
"FileName": "1259-20260311130930-1e672dd9-5420-4432-b422-02d8d271c178.jpg",
|
||||||
|
"Web": false,
|
||||||
|
"AllegaAlRapporto": false,
|
||||||
|
"StampaNelRapporto": false,
|
||||||
|
"ReportGermania": false,
|
||||||
|
"PrimaPagina": false,
|
||||||
|
"Duplica": false,
|
||||||
|
"LastUpdate": "2026-03-11T14:10:08.5442313+01:00",
|
||||||
|
"Titolo": null
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(563528)/InviaCommessa' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
|
||||||
|
"IdCommessa": 563528,
|
||||||
|
"CodiceCommessa": "26C0029",
|
||||||
|
"RiferimentoCertificato": null,
|
||||||
|
"StatoCommessaWeb": "Nuova",
|
||||||
|
"DataCreazioneWeb": "2026-03-11T14:10:00.897+01:00",
|
||||||
|
"CodiceCommessaWeb": "26C0029",
|
||||||
|
"DataInviatoWeb": "2026-03-11T14:10:11.7602299+01:00",
|
||||||
|
"Richiedente": "Test Web Import",
|
||||||
|
"Descrizione": "TEST CommessaWeb"
|
||||||
|
}
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
curl --location --request PATCH 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(563528)' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"CommesseCustomFields": [
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254249,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254250,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254251,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254252,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254253,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254254,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254260,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254261,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254262,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254264,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254265,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254266,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254267,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254268,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254228,
|
||||||
|
"Valore": "13526"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254229,
|
||||||
|
"Valore": "20262"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254230,
|
||||||
|
"Valore": "ART. PEACH"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254269,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254270,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254271,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254272,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254273,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254274,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254275,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254276,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254277,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254278,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254279,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254280,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254281,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254231,
|
||||||
|
"Valore": "L209A4M00130M7280"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254232,
|
||||||
|
"Valore": "BLACK"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254233,
|
||||||
|
"Valore": "PE007J"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254234,
|
||||||
|
"Valore": "262"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254235,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254236,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254237,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254238,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254239,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254240,
|
||||||
|
"Valore": "Oggi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254241,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254242,
|
||||||
|
"Valore": "MONCLER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254243,
|
||||||
|
"Valore": "236"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254244,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254245,
|
||||||
|
"Valore": "BBB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254246,
|
||||||
|
"Valore": "solocla"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254247,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254248,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254255,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254256,
|
||||||
|
"Valore": "MONCLER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254257,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254258,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254259,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23254263,
|
||||||
|
"Valore": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
null
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
CAMPIONE #0
|
||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"Commessa": 564663,
|
||||||
|
"Matrice": 3028,
|
||||||
|
"SottoMatrice": null,
|
||||||
|
"SchemaCustomField": 82,
|
||||||
|
"NoteWeb": "Parte 1",
|
||||||
|
"ConsegnaRichiesta": "2026-03-31"
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#Campione\/$entity",
|
||||||
|
"IdCampione": 736244,
|
||||||
|
"CodiceCampione": "11004",
|
||||||
|
"CodiceCampioneWeb": "11004",
|
||||||
|
"StatoCampione": "Nuovo",
|
||||||
|
"DataCreazioneWeb": "2026-03-17T14:52:30.3143366+01:00",
|
||||||
|
"RiferimentoWeb": "",
|
||||||
|
"ConsegnaRichiesta": "2026-03-31T00:00:00+02:00",
|
||||||
|
"DataAccettazioneLims": null,
|
||||||
|
"Riferimento": null,
|
||||||
|
"NoteWeb": "Parte 1",
|
||||||
|
"GruppiRicercati": null
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
CAMPIONE #1
|
||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"Commessa": 564663,
|
||||||
|
"Matrice": 3028,
|
||||||
|
"SottoMatrice": null,
|
||||||
|
"SchemaCustomField": 82,
|
||||||
|
"NoteWeb": "Parte 2",
|
||||||
|
"ConsegnaRichiesta": "2026-03-31"
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#Campione\/$entity",
|
||||||
|
"IdCampione": 736245,
|
||||||
|
"CodiceCampione": "11005",
|
||||||
|
"CodiceCampioneWeb": "11005",
|
||||||
|
"StatoCampione": "Nuovo",
|
||||||
|
"DataCreazioneWeb": "2026-03-17T14:52:31.9631757+01:00",
|
||||||
|
"RiferimentoWeb": "",
|
||||||
|
"ConsegnaRichiesta": "2026-03-31T00:00:00+02:00",
|
||||||
|
"DataAccettazioneLims": null,
|
||||||
|
"Riferimento": null,
|
||||||
|
"NoteWeb": "Parte 2",
|
||||||
|
"GruppiRicercati": null
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
|||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(564663)/ImportaCommessa' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"IdUtente": 285
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
|
||||||
|
"IdCommessa": 564663,
|
||||||
|
"CodiceCommessa": "26C0059",
|
||||||
|
"RiferimentoCertificato": null,
|
||||||
|
"StatoCommessaWeb": "Elaborata",
|
||||||
|
"DataCreazioneWeb": "2026-03-17T14:52:29.347+01:00",
|
||||||
|
"CodiceCommessaWeb": "26C0059",
|
||||||
|
"DataInviatoWeb": "2026-03-17T14:52:38.99+01:00",
|
||||||
|
"Richiedente": "From TRFSmart Application",
|
||||||
|
"Descrizione": "From TRFSmart Application"
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
Photos for CommessaWeb 564663 (iddatadb=1268):
|
||||||
|
Total photos found: 2, campioni: 2
|
||||||
|
|
||||||
|
=== Campione 736244 (main) ===
|
||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione(736244)/UploadCampioneFile' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--form 'file=@C:\xampp\htdocs\trf_certest\public\photostrf\1268-20260317134828-1e672dd9-5420-4432-b422-02d8d271c178.jpg' \
|
||||||
|
--form 'StampaNelRapporto=true' \
|
||||||
|
--form 'PrimaPagina=true'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CampioneFiles\/$entity",
|
||||||
|
"IdCampioneFile": 1784768,
|
||||||
|
"FileName": "1268-20260317134828-1e672dd9-5420-4432-b422-02d8d271c178.jpg",
|
||||||
|
"Web": false,
|
||||||
|
"AllegaAlRapporto": false,
|
||||||
|
"StampaNelRapporto": false,
|
||||||
|
"ReportGermania": false,
|
||||||
|
"PrimaPagina": false,
|
||||||
|
"Duplica": false,
|
||||||
|
"LastUpdate": "2026-03-17T14:52:33.9816658+01:00",
|
||||||
|
"Titolo": null
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione(736244)/UploadCampioneFile' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--form 'file=@C:\xampp\htdocs\trf_certest\public\photostrf\1268-20260317134828-104a2ca5-8a4c-4df8-b2ca-a28f518b7b25.jpg' \
|
||||||
|
--form 'StampaNelRapporto=false' \
|
||||||
|
--form 'PrimaPagina=false'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CampioneFiles\/$entity",
|
||||||
|
"IdCampioneFile": 1784769,
|
||||||
|
"FileName": "1268-20260317134828-104a2ca5-8a4c-4df8-b2ca-a28f518b7b25.jpg",
|
||||||
|
"Web": false,
|
||||||
|
"AllegaAlRapporto": false,
|
||||||
|
"StampaNelRapporto": false,
|
||||||
|
"ReportGermania": false,
|
||||||
|
"PrimaPagina": false,
|
||||||
|
"Duplica": false,
|
||||||
|
"LastUpdate": "2026-03-17T14:52:35.2759882+01:00",
|
||||||
|
"Titolo": null
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(564663)/InviaCommessa' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
|
||||||
|
"IdCommessa": 564663,
|
||||||
|
"CodiceCommessa": "26C0059",
|
||||||
|
"RiferimentoCertificato": null,
|
||||||
|
"StatoCommessaWeb": "Nuova",
|
||||||
|
"DataCreazioneWeb": "2026-03-17T14:52:29.347+01:00",
|
||||||
|
"CodiceCommessaWeb": "26C0059",
|
||||||
|
"DataInviatoWeb": "2026-03-17T14:52:38.9894528+01:00",
|
||||||
|
"Richiedente": "From TRFSmart Application",
|
||||||
|
"Descrizione": "From TRFSmart Application"
|
||||||
|
}
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
curl --location --request PATCH 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(564663)' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"CommesseCustomFields": [
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308665,
|
||||||
|
"Valore": "2026-03-17T13:52:36+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308666,
|
||||||
|
"Valore": "13:52"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308667,
|
||||||
|
"Valore": "9299"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308668,
|
||||||
|
"Valore": "2026-03-17T13:52:36+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308669,
|
||||||
|
"Valore": "aaa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308670,
|
||||||
|
"Valore": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308676,
|
||||||
|
"Valore": "673"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308677,
|
||||||
|
"Valore": "674"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308678,
|
||||||
|
"Valore": "4410"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308680,
|
||||||
|
"Valore": "1468"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308681,
|
||||||
|
"Valore": "1604"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308682,
|
||||||
|
"Valore": "12875"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308683,
|
||||||
|
"Valore": "8755"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308684,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308644,
|
||||||
|
"Valore": "13497"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308645,
|
||||||
|
"Valore": "AAA"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308646,
|
||||||
|
"Valore": "Example Description"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308685,
|
||||||
|
"Valore": "1892"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308686,
|
||||||
|
"Valore": "1111"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308687,
|
||||||
|
"Valore": "2026-03-02T13:52:36+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308688,
|
||||||
|
"Valore": "2026-03-17T13:52:36+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308689,
|
||||||
|
"Valore": "2026-03-17T13:52:36+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308690,
|
||||||
|
"Valore": "1978"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308691,
|
||||||
|
"Valore": "13:52"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308692,
|
||||||
|
"Valore": "14:44"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308693,
|
||||||
|
"Valore": "14:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308694,
|
||||||
|
"Valore": "2026-03-17T13:52:36+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308695,
|
||||||
|
"Valore": "8596"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308696,
|
||||||
|
"Valore": "8600"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308697,
|
||||||
|
"Valore": "8605"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308647,
|
||||||
|
"Valore": "L209A4M00130M7280"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308648,
|
||||||
|
"Valore": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308649,
|
||||||
|
"Valore": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308650,
|
||||||
|
"Valore": "264"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308651,
|
||||||
|
"Valore": "1589"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308652,
|
||||||
|
"Valore": "AAA"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308653,
|
||||||
|
"Valore": "3055"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308654,
|
||||||
|
"Valore": "aaa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308655,
|
||||||
|
"Valore": "qqq"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308656,
|
||||||
|
"Valore": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308657,
|
||||||
|
"Valore": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308658,
|
||||||
|
"Valore": "qqq"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308659,
|
||||||
|
"Valore": "236"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308660,
|
||||||
|
"Valore": "1208"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308661,
|
||||||
|
"Valore": "ciao"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308662,
|
||||||
|
"Valore": "bbb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308663,
|
||||||
|
"Valore": "12234"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308664,
|
||||||
|
"Valore": "MONCLER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308671,
|
||||||
|
"Valore": "L209A4M00130M7280 - PACEY2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308672,
|
||||||
|
"Valore": "4678"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308673,
|
||||||
|
"Valore": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308674,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308675,
|
||||||
|
"Valore": "2450"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23308679,
|
||||||
|
"Valore": "744"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
null
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
CAMPIONE #0
|
||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"Commessa": 564817,
|
||||||
|
"Matrice": 3028,
|
||||||
|
"SottoMatrice": null,
|
||||||
|
"SchemaCustomField": 82,
|
||||||
|
"NoteWeb": "AAA",
|
||||||
|
"ConsegnaRichiesta": "2026-01-29"
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#Campione\/$entity",
|
||||||
|
"IdCampione": 736487,
|
||||||
|
"CodiceCampione": "11016",
|
||||||
|
"CodiceCampioneWeb": "11016",
|
||||||
|
"StatoCampione": "Nuovo",
|
||||||
|
"DataCreazioneWeb": "2026-03-18T10:54:03.4537462+01:00",
|
||||||
|
"RiferimentoWeb": "",
|
||||||
|
"ConsegnaRichiesta": "2026-01-29T00:00:00+01:00",
|
||||||
|
"DataAccettazioneLims": null,
|
||||||
|
"Riferimento": null,
|
||||||
|
"NoteWeb": "AAA",
|
||||||
|
"GruppiRicercati": null
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
CAMPIONE #1
|
||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"Commessa": 564817,
|
||||||
|
"Matrice": 3028,
|
||||||
|
"SottoMatrice": null,
|
||||||
|
"SchemaCustomField": 82,
|
||||||
|
"NoteWeb": "CCC",
|
||||||
|
"ConsegnaRichiesta": "2026-01-29"
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#Campione\/$entity",
|
||||||
|
"IdCampione": 736488,
|
||||||
|
"CodiceCampione": "11017",
|
||||||
|
"CodiceCampioneWeb": "11017",
|
||||||
|
"StatoCampione": "Nuovo",
|
||||||
|
"DataCreazioneWeb": "2026-03-18T10:54:04.8623753+01:00",
|
||||||
|
"RiferimentoWeb": "",
|
||||||
|
"ConsegnaRichiesta": "2026-01-29T00:00:00+01:00",
|
||||||
|
"DataAccettazioneLims": null,
|
||||||
|
"Riferimento": null,
|
||||||
|
"NoteWeb": "CCC",
|
||||||
|
"GruppiRicercati": null
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
CAMPIONE #2
|
||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"Commessa": 564817,
|
||||||
|
"Matrice": 3028,
|
||||||
|
"SottoMatrice": null,
|
||||||
|
"SchemaCustomField": 82,
|
||||||
|
"NoteWeb": "DDD",
|
||||||
|
"ConsegnaRichiesta": "2026-01-29"
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#Campione\/$entity",
|
||||||
|
"IdCampione": 736489,
|
||||||
|
"CodiceCampione": "11018",
|
||||||
|
"CodiceCampioneWeb": "11018",
|
||||||
|
"StatoCampione": "Nuovo",
|
||||||
|
"DataCreazioneWeb": "2026-03-18T10:54:06.2840533+01:00",
|
||||||
|
"RiferimentoWeb": "",
|
||||||
|
"ConsegnaRichiesta": "2026-01-29T00:00:00+01:00",
|
||||||
|
"DataAccettazioneLims": null,
|
||||||
|
"Riferimento": null,
|
||||||
|
"NoteWeb": "DDD",
|
||||||
|
"GruppiRicercati": null
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
CAMPIONE #3
|
||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"Commessa": 564817,
|
||||||
|
"Matrice": 3028,
|
||||||
|
"SottoMatrice": null,
|
||||||
|
"SchemaCustomField": 82,
|
||||||
|
"NoteWeb": "EEE",
|
||||||
|
"ConsegnaRichiesta": "2026-01-29"
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#Campione\/$entity",
|
||||||
|
"IdCampione": 736490,
|
||||||
|
"CodiceCampione": "11019",
|
||||||
|
"CodiceCampioneWeb": "11019",
|
||||||
|
"StatoCampione": "Nuovo",
|
||||||
|
"DataCreazioneWeb": "2026-03-18T10:54:07.8853452+01:00",
|
||||||
|
"RiferimentoWeb": "",
|
||||||
|
"ConsegnaRichiesta": "2026-01-29T00:00:00+01:00",
|
||||||
|
"DataAccettazioneLims": null,
|
||||||
|
"Riferimento": null,
|
||||||
|
"NoteWeb": "EEE",
|
||||||
|
"GruppiRicercati": null
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
|||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(564817)/ImportaCommessa' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"IdUtente": 285
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
|
||||||
|
"IdCommessa": 564817,
|
||||||
|
"CodiceCommessa": "26C0071",
|
||||||
|
"RiferimentoCertificato": null,
|
||||||
|
"StatoCommessaWeb": "Elaborata",
|
||||||
|
"DataCreazioneWeb": "2026-03-18T10:54:02.187+01:00",
|
||||||
|
"CodiceCommessaWeb": "26C0071",
|
||||||
|
"DataInviatoWeb": "2026-03-18T10:54:14.727+01:00",
|
||||||
|
"Richiedente": "From TRFSmart Application",
|
||||||
|
"Descrizione": "From TRFSmart Application"
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
Photos for CommessaWeb 564817 (iddatadb=1270):
|
||||||
|
Total photos found: 2, campioni: 4
|
||||||
|
|
||||||
|
=== Campione 736487 (main) ===
|
||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione(736487)/UploadCampioneFile' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--form 'file=@C:\xampp\htdocs\trf_certest\public\photostrf\1270-20260318095305-1e672dd9-5420-4432-b422-02d8d271c178.jpg' \
|
||||||
|
--form 'StampaNelRapporto=true' \
|
||||||
|
--form 'PrimaPagina=true'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CampioneFiles\/$entity",
|
||||||
|
"IdCampioneFile": 1785492,
|
||||||
|
"FileName": "1270-20260318095305-1e672dd9-5420-4432-b422-02d8d271c178.jpg",
|
||||||
|
"Web": false,
|
||||||
|
"AllegaAlRapporto": false,
|
||||||
|
"StampaNelRapporto": false,
|
||||||
|
"ReportGermania": false,
|
||||||
|
"PrimaPagina": false,
|
||||||
|
"Duplica": false,
|
||||||
|
"LastUpdate": "2026-03-18T10:54:09.5438326+01:00",
|
||||||
|
"Titolo": null
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione(736487)/UploadCampioneFile' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--form 'file=@C:\xampp\htdocs\trf_certest\public\photostrf\1270-20260318095305-104a2ca5-8a4c-4df8-b2ca-a28f518b7b25.jpg' \
|
||||||
|
--form 'StampaNelRapporto=false' \
|
||||||
|
--form 'PrimaPagina=false'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CampioneFiles\/$entity",
|
||||||
|
"IdCampioneFile": 1785493,
|
||||||
|
"FileName": "1270-20260318095305-104a2ca5-8a4c-4df8-b2ca-a28f518b7b25.jpg",
|
||||||
|
"Web": false,
|
||||||
|
"AllegaAlRapporto": false,
|
||||||
|
"StampaNelRapporto": false,
|
||||||
|
"ReportGermania": false,
|
||||||
|
"PrimaPagina": false,
|
||||||
|
"Duplica": false,
|
||||||
|
"LastUpdate": "2026-03-18T10:54:10.8237002+01:00",
|
||||||
|
"Titolo": null
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(564817)/InviaCommessa' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
|
||||||
|
"IdCommessa": 564817,
|
||||||
|
"CodiceCommessa": "26C0071",
|
||||||
|
"RiferimentoCertificato": null,
|
||||||
|
"StatoCommessaWeb": "Nuova",
|
||||||
|
"DataCreazioneWeb": "2026-03-18T10:54:02.187+01:00",
|
||||||
|
"CodiceCommessaWeb": "26C0071",
|
||||||
|
"DataInviatoWeb": "2026-03-18T10:54:14.7252132+01:00",
|
||||||
|
"Richiedente": "From TRFSmart Application",
|
||||||
|
"Descrizione": "From TRFSmart Application"
|
||||||
|
}
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
curl --location --request PATCH 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(564817)' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"CommesseCustomFields": [
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315543,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315544,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315545,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315546,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315547,
|
||||||
|
"Valore": "/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315548,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315554,
|
||||||
|
"Valore": "673"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315555,
|
||||||
|
"Valore": "674"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315556,
|
||||||
|
"Valore": "669"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315558,
|
||||||
|
"Valore": "1469"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315559,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315560,
|
||||||
|
"Valore": "12875"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315561,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315562,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315522,
|
||||||
|
"Valore": "13497"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315523,
|
||||||
|
"Valore": "20262"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315524,
|
||||||
|
"Valore": "ART. PEACH"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315563,
|
||||||
|
"Valore": "1892"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315564,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315565,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315566,
|
||||||
|
"Valore": "2026-03-18T09:54:11+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315567,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315568,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315569,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315570,
|
||||||
|
"Valore": "09:53"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315571,
|
||||||
|
"Valore": "10:37"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315572,
|
||||||
|
"Valore": "2026-03-18T09:54:11+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315573,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315574,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315575,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315525,
|
||||||
|
"Valore": "L209A4M00130M7280"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315526,
|
||||||
|
"Valore": "BLACK"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315527,
|
||||||
|
"Valore": "PE007J"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315528,
|
||||||
|
"Valore": "264"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315529,
|
||||||
|
"Valore": "420"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315530,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315531,
|
||||||
|
"Valore": "15357"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315532,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315533,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315534,
|
||||||
|
"Valore": "P"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315535,
|
||||||
|
"Valore": "RM-ACC"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315536,
|
||||||
|
"Valore": " CONCERIA M2 SRL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315537,
|
||||||
|
"Valore": "344"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315538,
|
||||||
|
"Valore": "520"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315539,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315540,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315541,
|
||||||
|
"Valore": "Not provided"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315542,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315549,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315550,
|
||||||
|
"Valore": "4678"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315551,
|
||||||
|
"Valore": "Leather&amp;Fur"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315552,
|
||||||
|
"Valore": "278"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315553,
|
||||||
|
"Valore": "2089"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23315557,
|
||||||
|
"Valore": "745"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
null
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
CAMPIONE #0
|
||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"Commessa": 565085,
|
||||||
|
"Matrice": 7714,
|
||||||
|
"SottoMatrice": null,
|
||||||
|
"SchemaCustomField": 82,
|
||||||
|
"NoteWeb": "Parte AAA",
|
||||||
|
"ConsegnaRichiesta": "2026-04-08"
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#Campione\/$entity",
|
||||||
|
"IdCampione": 736899,
|
||||||
|
"CodiceCampione": "11038",
|
||||||
|
"CodiceCampioneWeb": "11038",
|
||||||
|
"StatoCampione": "Nuovo",
|
||||||
|
"DataCreazioneWeb": "2026-03-19T10:03:19.8547279+01:00",
|
||||||
|
"RiferimentoWeb": "",
|
||||||
|
"ConsegnaRichiesta": "2026-04-08T00:00:00+02:00",
|
||||||
|
"DataAccettazioneLims": null,
|
||||||
|
"Riferimento": null,
|
||||||
|
"NoteWeb": "Parte AAA",
|
||||||
|
"GruppiRicercati": null
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
CAMPIONE #1
|
||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"Commessa": 565085,
|
||||||
|
"Matrice": 7714,
|
||||||
|
"SottoMatrice": null,
|
||||||
|
"SchemaCustomField": 82,
|
||||||
|
"NoteWeb": "Parte BBB",
|
||||||
|
"ConsegnaRichiesta": "2026-04-08"
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#Campione\/$entity",
|
||||||
|
"IdCampione": 736900,
|
||||||
|
"CodiceCampione": "11039",
|
||||||
|
"CodiceCampioneWeb": "11039",
|
||||||
|
"StatoCampione": "Nuovo",
|
||||||
|
"DataCreazioneWeb": "2026-03-19T10:03:22.0431135+01:00",
|
||||||
|
"RiferimentoWeb": "",
|
||||||
|
"ConsegnaRichiesta": "2026-04-08T00:00:00+02:00",
|
||||||
|
"DataAccettazioneLims": null,
|
||||||
|
"Riferimento": null,
|
||||||
|
"NoteWeb": "Parte BBB",
|
||||||
|
"GruppiRicercati": null
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
CAMPIONE #2
|
||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"Commessa": 565085,
|
||||||
|
"Matrice": 7714,
|
||||||
|
"SottoMatrice": null,
|
||||||
|
"SchemaCustomField": 82,
|
||||||
|
"NoteWeb": "Parte CCC",
|
||||||
|
"ConsegnaRichiesta": "2026-04-08"
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#Campione\/$entity",
|
||||||
|
"IdCampione": 736901,
|
||||||
|
"CodiceCampione": "11040",
|
||||||
|
"CodiceCampioneWeb": "11040",
|
||||||
|
"StatoCampione": "Nuovo",
|
||||||
|
"DataCreazioneWeb": "2026-03-19T10:03:23.6170597+01:00",
|
||||||
|
"RiferimentoWeb": "",
|
||||||
|
"ConsegnaRichiesta": "2026-04-08T00:00:00+02:00",
|
||||||
|
"DataAccettazioneLims": null,
|
||||||
|
"Riferimento": null,
|
||||||
|
"NoteWeb": "Parte CCC",
|
||||||
|
"GruppiRicercati": null
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
|||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(565085)/ImportaCommessa' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"IdUtente": 285
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
|
||||||
|
"IdCommessa": 565085,
|
||||||
|
"CodiceCommessa": "26C0089",
|
||||||
|
"RiferimentoCertificato": null,
|
||||||
|
"StatoCommessaWeb": "Elaborata",
|
||||||
|
"DataCreazioneWeb": "2026-03-19T10:03:18.653+01:00",
|
||||||
|
"CodiceCommessaWeb": "26C0089",
|
||||||
|
"DataInviatoWeb": "2026-03-19T10:03:30.603+01:00",
|
||||||
|
"Richiedente": "From TRFSmart Application",
|
||||||
|
"Descrizione": "From TRFSmart Application"
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
Photos for CommessaWeb 565085 (iddatadb=1279):
|
||||||
|
Total photos found: 2, campioni: 3
|
||||||
|
|
||||||
|
=== Campione 736899 (main) ===
|
||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione(736899)/UploadCampioneFile' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--form 'file=@C:\xampp\htdocs\trf_certest\public\photostrf\1279-20260319090037-1e672dd9-5420-4432-b422-02d8d271c178.jpg' \
|
||||||
|
--form 'StampaNelRapporto=true' \
|
||||||
|
--form 'PrimaPagina=true'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CampioneFiles\/$entity",
|
||||||
|
"IdCampioneFile": 1786602,
|
||||||
|
"FileName": "1279-20260319090037-1e672dd9-5420-4432-b422-02d8d271c178.jpg",
|
||||||
|
"Web": false,
|
||||||
|
"AllegaAlRapporto": false,
|
||||||
|
"StampaNelRapporto": false,
|
||||||
|
"ReportGermania": false,
|
||||||
|
"PrimaPagina": false,
|
||||||
|
"Duplica": false,
|
||||||
|
"LastUpdate": "2026-03-19T10:03:25.2757049+01:00",
|
||||||
|
"Titolo": null
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione(736899)/UploadCampioneFile' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--form 'file=@C:\xampp\htdocs\trf_certest\public\photostrf\1279-20260319090037-104a2ca5-8a4c-4df8-b2ca-a28f518b7b25.jpg' \
|
||||||
|
--form 'StampaNelRapporto=false' \
|
||||||
|
--form 'PrimaPagina=false'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CampioneFiles\/$entity",
|
||||||
|
"IdCampioneFile": 1786603,
|
||||||
|
"FileName": "1279-20260319090037-104a2ca5-8a4c-4df8-b2ca-a28f518b7b25.jpg",
|
||||||
|
"Web": false,
|
||||||
|
"AllegaAlRapporto": false,
|
||||||
|
"StampaNelRapporto": false,
|
||||||
|
"ReportGermania": false,
|
||||||
|
"PrimaPagina": false,
|
||||||
|
"Duplica": false,
|
||||||
|
"LastUpdate": "2026-03-19T10:03:26.7566613+01:00",
|
||||||
|
"Titolo": null
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(565085)/InviaCommessa' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
|
||||||
|
"IdCommessa": 565085,
|
||||||
|
"CodiceCommessa": "26C0089",
|
||||||
|
"RiferimentoCertificato": null,
|
||||||
|
"StatoCommessaWeb": "Nuova",
|
||||||
|
"DataCreazioneWeb": "2026-03-19T10:03:18.653+01:00",
|
||||||
|
"CodiceCommessaWeb": "26C0089",
|
||||||
|
"DataInviatoWeb": "2026-03-19T10:03:30.6022436+01:00",
|
||||||
|
"Richiedente": "From TRFSmart Application",
|
||||||
|
"Descrizione": "From TRFSmart Application"
|
||||||
|
}
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
curl --location --request PATCH 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(565085)' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"CommesseCustomFields": [
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328248,
|
||||||
|
"Valore": "18/03/2026"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328249,
|
||||||
|
"Valore": "09:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328250,
|
||||||
|
"Valore": "9299"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328251,
|
||||||
|
"Valore": "17/03/2026"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328252,
|
||||||
|
"Valore": "/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328253,
|
||||||
|
"Valore": "5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328259,
|
||||||
|
"Valore": "673"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328260,
|
||||||
|
"Valore": "674"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328261,
|
||||||
|
"Valore": "669"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328263,
|
||||||
|
"Valore": "1469"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328264,
|
||||||
|
"Valore": "1604"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328265,
|
||||||
|
"Valore": "12875"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328266,
|
||||||
|
"Valore": "8755"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328267,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328227,
|
||||||
|
"Valore": "13497"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328228,
|
||||||
|
"Valore": "20262"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328229,
|
||||||
|
"Valore": "ART. PEACH"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328268,
|
||||||
|
"Valore": "1892"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328269,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328270,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328271,
|
||||||
|
"Valore": "19/03/2026"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328272,
|
||||||
|
"Valore": "18/03/2026"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328273,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328274,
|
||||||
|
"Valore": "09:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328275,
|
||||||
|
"Valore": "09:03"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328276,
|
||||||
|
"Valore": "09:58"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328277,
|
||||||
|
"Valore": "19/03/2026"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328278,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328279,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328280,
|
||||||
|
"Valore": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328230,
|
||||||
|
"Valore": "L209B4M00110M7280"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328231,
|
||||||
|
"Valore": "BLACK"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328232,
|
||||||
|
"Valore": "PE007J"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328233,
|
||||||
|
"Valore": "264"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328234,
|
||||||
|
"Valore": "420"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328235,
|
||||||
|
"Valore": "AAA"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328236,
|
||||||
|
"Valore": "3055"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328237,
|
||||||
|
"Valore": "AAA"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328238,
|
||||||
|
"Valore": "AAA"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328239,
|
||||||
|
"Valore": "P"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328240,
|
||||||
|
"Valore": "RM-ACC"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328241,
|
||||||
|
"Valore": "\u00a0CONCERIA M2 SRL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328242,
|
||||||
|
"Valore": "344"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328243,
|
||||||
|
"Valore": "1208"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328244,
|
||||||
|
"Valore": "AAA"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328245,
|
||||||
|
"Valore": "AAA"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328246,
|
||||||
|
"Valore": "Not provided"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328247,
|
||||||
|
"Valore": "AAA"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328254,
|
||||||
|
"Valore": "aaa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328255,
|
||||||
|
"Valore": "4678"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328256,
|
||||||
|
"Valore": "Leather&amp;Fur"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328257,
|
||||||
|
"Valore": "278"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328258,
|
||||||
|
"Valore": "2089"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"IdCommesseCustomFields": 23328262,
|
||||||
|
"Valore": "745"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
null
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
CAMPIONE #0
|
||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"Commessa": 95833,
|
||||||
|
"Matrice": 8413,
|
||||||
|
"SottoMatrice": null,
|
||||||
|
"SchemaCustomField": 82,
|
||||||
|
"NoteWeb": "aaa"
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"IdCampione": 14445,
|
||||||
|
"Commessa": 95833,
|
||||||
|
"Matrice": 8413
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
CAMPIONE #1
|
||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"Commessa": 95833,
|
||||||
|
"Matrice": 3879,
|
||||||
|
"SottoMatrice": null,
|
||||||
|
"SchemaCustomField": 82,
|
||||||
|
"NoteWeb": "bbb"
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"IdCampione": 15750,
|
||||||
|
"Commessa": 95833,
|
||||||
|
"Matrice": 3879
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
curl --location --request GET 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(95833)?$expand=CommesseCustomFields($expand=CustomField)' \
|
||||||
|
--header 'Authorization: Bearer ••••••'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"IdCommessa": 95833,
|
||||||
|
"CodiceCommessa": "SIM-95833",
|
||||||
|
"CommesseCustomFields": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
curl --location --request GET 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(95833)?$expand=CommesseCustomFields($expand=CustomField)' \
|
||||||
|
--header 'Authorization: Bearer ••••••'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"IdCommessa": 95833,
|
||||||
|
"CodiceCommessa": "SIM-95833",
|
||||||
|
"CommesseCustomFields": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(95833)/ImportaCommessa' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"simulated": true,
|
||||||
|
"endpoint": "CommessaWeb(95833)\/ImportaCommessa"
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
Photos for CommessaWeb 95833 (iddatadb=1237):
|
||||||
|
Total photos found: 1
|
||||||
|
|
||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/AllegatoCommessaWeb' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--form 'IdCommessa=95833' \
|
||||||
|
--form 'file=@C:\xampp\htdocs\trf_certest\public\photostrf\1237-20260228192737-Blue and White Simple Daily Vlogger YouTube Banner (15) (1) (1).png'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"simulated": true,
|
||||||
|
"file": "1237-20260228192737-Blue and White Simple Daily Vlogger YouTube Banner (15) (1) (1).png"
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"Cliente": 3378,
|
||||||
|
"SchemaCustomField": 82,
|
||||||
|
"Richiedente": "Test Web Import",
|
||||||
|
"Descrizione": "TEST CommessaWeb",
|
||||||
|
"ClienteResponsabile": 3,
|
||||||
|
"MoltiplicatorePrezzo": 1,
|
||||||
|
"AnagraficaCertestObject": 2,
|
||||||
|
"AnagraficaCertestService": 1,
|
||||||
|
"ClienteFornitore": null
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"IdCommessa": 95833,
|
||||||
|
"CodiceCommessa": "SIM-95833",
|
||||||
|
"Richiedente": "Test Web Import",
|
||||||
|
"Descrizione": "TEST CommessaWeb"
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"Cliente": 3378,
|
||||||
|
"SchemaCustomField": 82,
|
||||||
|
"Richiedente": "Test Web Import",
|
||||||
|
"Descrizione": "TEST CommessaWeb",
|
||||||
|
"ClienteResponsabile": 7586,
|
||||||
|
"MoltiplicatorePrezzo": 3,
|
||||||
|
"AnagraficaCertestObject": 8963,
|
||||||
|
"AnagraficaCertestService": 9007,
|
||||||
|
"ClienteFornitore": null
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
|
||||||
|
"IdCommessa": 562479,
|
||||||
|
"CodiceCommessa": "26C0013",
|
||||||
|
"RiferimentoCertificato": null,
|
||||||
|
"StatoCommessaWeb": "Nuova",
|
||||||
|
"DataCreazioneWeb": "2026-03-06T09:09:16.9338074+01:00",
|
||||||
|
"CodiceCommessaWeb": "26C0013",
|
||||||
|
"DataInviatoWeb": null,
|
||||||
|
"Richiedente": "Test Web Import",
|
||||||
|
"Descrizione": "TEST CommessaWeb"
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"Cliente": 3378,
|
||||||
|
"SchemaCustomField": 82,
|
||||||
|
"Richiedente": "Test Web Import",
|
||||||
|
"Descrizione": "TEST CommessaWeb",
|
||||||
|
"ClienteResponsabile": 10653,
|
||||||
|
"MoltiplicatorePrezzo": 1,
|
||||||
|
"AnagraficaCertestObject": 8960,
|
||||||
|
"AnagraficaCertestService": 9010,
|
||||||
|
"ClienteFornitore": null
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
|
||||||
|
"IdCommessa": 563528,
|
||||||
|
"CodiceCommessa": "26C0029",
|
||||||
|
"RiferimentoCertificato": null,
|
||||||
|
"StatoCommessaWeb": "Nuova",
|
||||||
|
"DataCreazioneWeb": "2026-03-11T14:10:00.8960358+01:00",
|
||||||
|
"CodiceCommessaWeb": "26C0029",
|
||||||
|
"DataInviatoWeb": null,
|
||||||
|
"Richiedente": "Test Web Import",
|
||||||
|
"Descrizione": "TEST CommessaWeb"
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"Cliente": 3378,
|
||||||
|
"SchemaCustomField": 82,
|
||||||
|
"Richiedente": "From TRFSmart Application",
|
||||||
|
"Descrizione": "From TRFSmart Application",
|
||||||
|
"ClienteResponsabile": 7598,
|
||||||
|
"MoltiplicatorePrezzo": 2,
|
||||||
|
"AnagraficaCertestObject": 8964,
|
||||||
|
"AnagraficaCertestService": 9012,
|
||||||
|
"ClienteFornitore": 11208
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
|
||||||
|
"IdCommessa": 564662,
|
||||||
|
"CodiceCommessa": "26C0058",
|
||||||
|
"RiferimentoCertificato": null,
|
||||||
|
"StatoCommessaWeb": "Nuova",
|
||||||
|
"DataCreazioneWeb": "2026-03-17T14:49:39.4933462+01:00",
|
||||||
|
"CodiceCommessaWeb": "26C0058",
|
||||||
|
"DataInviatoWeb": null,
|
||||||
|
"Richiedente": "From TRFSmart Application",
|
||||||
|
"Descrizione": "From TRFSmart Application"
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"Cliente": 3378,
|
||||||
|
"SchemaCustomField": 82,
|
||||||
|
"Richiedente": "From TRFSmart Application",
|
||||||
|
"Descrizione": "From TRFSmart Application",
|
||||||
|
"ClienteResponsabile": 7598,
|
||||||
|
"MoltiplicatorePrezzo": 2,
|
||||||
|
"AnagraficaCertestObject": 8964,
|
||||||
|
"AnagraficaCertestService": 9012,
|
||||||
|
"ClienteFornitore": 11208
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
|
||||||
|
"IdCommessa": 564663,
|
||||||
|
"CodiceCommessa": "26C0059",
|
||||||
|
"RiferimentoCertificato": null,
|
||||||
|
"StatoCommessaWeb": "Nuova",
|
||||||
|
"DataCreazioneWeb": "2026-03-17T14:52:29.3463415+01:00",
|
||||||
|
"CodiceCommessaWeb": "26C0059",
|
||||||
|
"DataInviatoWeb": null,
|
||||||
|
"Richiedente": "From TRFSmart Application",
|
||||||
|
"Descrizione": "From TRFSmart Application"
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"Cliente": 3378,
|
||||||
|
"SchemaCustomField": 82,
|
||||||
|
"Richiedente": "From TRFSmart Application",
|
||||||
|
"Descrizione": "From TRFSmart Application",
|
||||||
|
"ClienteResponsabile": 7586,
|
||||||
|
"MoltiplicatorePrezzo": null,
|
||||||
|
"AnagraficaCertestObject": 8973,
|
||||||
|
"AnagraficaCertestService": 9007,
|
||||||
|
"ClienteFornitore": 11208
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
|
||||||
|
"IdCommessa": 564817,
|
||||||
|
"CodiceCommessa": "26C0071",
|
||||||
|
"RiferimentoCertificato": null,
|
||||||
|
"StatoCommessaWeb": "Nuova",
|
||||||
|
"DataCreazioneWeb": "2026-03-18T10:54:02.1867227+01:00",
|
||||||
|
"CodiceCommessaWeb": "26C0071",
|
||||||
|
"DataInviatoWeb": null,
|
||||||
|
"Richiedente": "From TRFSmart Application",
|
||||||
|
"Descrizione": "From TRFSmart Application"
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer ••••••' \
|
||||||
|
--data '{
|
||||||
|
"Cliente": 3378,
|
||||||
|
"SchemaCustomField": 82,
|
||||||
|
"Richiedente": "From TRFSmart Application",
|
||||||
|
"Descrizione": "From TRFSmart Application",
|
||||||
|
"ClienteResponsabile": 7586,
|
||||||
|
"MoltiplicatorePrezzo": 2,
|
||||||
|
"AnagraficaCertestObject": 8973,
|
||||||
|
"AnagraficaCertestService": 9007,
|
||||||
|
"ClienteFornitore": 4505,
|
||||||
|
"ClienteAnalisi": null
|
||||||
|
}'
|
||||||
|
|
||||||
|
RESPONSE:
|
||||||
|
{
|
||||||
|
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
|
||||||
|
"IdCommessa": 565085,
|
||||||
|
"CodiceCommessa": "26C0089",
|
||||||
|
"RiferimentoCertificato": null,
|
||||||
|
"StatoCommessaWeb": "Nuova",
|
||||||
|
"DataCreazioneWeb": "2026-03-19T10:03:18.6527414+01:00",
|
||||||
|
"CodiceCommessaWeb": "26C0089",
|
||||||
|
"DataInviatoWeb": null,
|
||||||
|
"Richiedente": "From TRFSmart Application",
|
||||||
|
"Descrizione": "From TRFSmart Application"
|
||||||
|
}
|
||||||
@@ -96,12 +96,66 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
#schemaFieldsTable th:first-child,
|
/* Force predictable column sizing */
|
||||||
#schemaFieldsTable td:first-child,
|
/* Stable sizing */
|
||||||
|
#schemaFieldsTable {
|
||||||
|
width: 100%;
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkbox columns = minimal */
|
||||||
|
#schemaFieldsTable th:nth-child(1),
|
||||||
|
#schemaFieldsTable td:nth-child(1),
|
||||||
#schemaFieldsTable th:nth-child(2),
|
#schemaFieldsTable th:nth-child(2),
|
||||||
#schemaFieldsTable td:nth-child(2) {
|
#schemaFieldsTable td:nth-child(2),
|
||||||
width: 100px;
|
#schemaFieldsTable th:nth-child(3),
|
||||||
|
#schemaFieldsTable td:nth-child(3) {
|
||||||
|
width: 48px;
|
||||||
|
/* 🔥 più stretto */
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
padding: 2px 4px;
|
||||||
|
/* 🔥 meno padding */
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make checkbox itself not oversized */
|
||||||
|
#schemaFieldsTable td:nth-child(1) input[type="checkbox"],
|
||||||
|
#schemaFieldsTable td:nth-child(2) input[type="checkbox"],
|
||||||
|
#schemaFieldsTable td:nth-child(3) input[type="checkbox"] {
|
||||||
|
transform: scale(0.95);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Title */
|
||||||
|
#schemaFieldsTable th:nth-child(4),
|
||||||
|
#schemaFieldsTable td:nth-child(4) {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Type */
|
||||||
|
#schemaFieldsTable th:nth-child(5),
|
||||||
|
#schemaFieldsTable td:nth-child(5) {
|
||||||
|
width: 120px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mapping = wide but NOT insane */
|
||||||
|
#schemaFieldsTable th:nth-child(6),
|
||||||
|
#schemaFieldsTable td:nth-child(6) {
|
||||||
|
width: 380px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Default Value = wider */
|
||||||
|
#schemaFieldsTable th:nth-child(7),
|
||||||
|
#schemaFieldsTable td:nth-child(7) {
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* selects fill the cell */
|
||||||
|
#schemaFieldsTable td:nth-child(6) .form-select,
|
||||||
|
#schemaFieldsTable td:nth-child(7) .form-control,
|
||||||
|
#schemaFieldsTable td:nth-child(7) .form-select {
|
||||||
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* fix bootstrap/select2 width */
|
/* fix bootstrap/select2 width */
|
||||||
@@ -109,6 +163,12 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.xls-columns option.used-option {
|
||||||
|
background-color: #fff3cd;
|
||||||
|
color: #856404;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
/* Make Title column narrower + ellipsis */
|
/* Make Title column narrower + ellipsis */
|
||||||
#schemaFieldsTable td.title-col {
|
#schemaFieldsTable td.title-col {
|
||||||
max-width: 320px;
|
max-width: 320px;
|
||||||
@@ -144,7 +204,13 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="form-label">Upload XLS Example:</label>
|
<label class="form-label">Upload XLS Example:</label>
|
||||||
<input type="file" id="xlsUpload" class="form-control">
|
<div class="d-flex align-items-center gap-3">
|
||||||
|
<input type="file" id="xlsUpload" class="form-control" style="flex:1;">
|
||||||
|
<label class="form-check-label" style="white-space:nowrap; cursor:pointer;">
|
||||||
|
<input type="checkbox" class="form-check-input" id="autoDetectHeader" checked>
|
||||||
|
Auto-detect header row
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<small id="uploadStatus">
|
<small id="uploadStatus">
|
||||||
<?php if (!empty($template['sample_xlsx'])): ?>
|
<?php if (!empty($template['sample_xlsx'])): ?>
|
||||||
✅ Current file: <a href="xlstemplates/<?php echo htmlspecialchars($template['sample_xlsx']); ?>" target="_blank"><?php echo htmlspecialchars($template['sample_xlsx']); ?></a>
|
✅ Current file: <a href="xlstemplates/<?php echo htmlspecialchars($template['sample_xlsx']); ?>" target="_blank"><?php echo htmlspecialchars($template['sample_xlsx']); ?></a>
|
||||||
@@ -165,11 +231,11 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
<table id="schemaFieldsTable" class="table table-striped">
|
<table id="schemaFieldsTable" class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:90px; text-align:center;">Main</th>
|
<th style="width:45px; text-align:center;">Main</th>
|
||||||
<th style="width:110px; text-align:center;">Import</th>
|
<th style="width:45px; text-align:center;">Import</th>
|
||||||
<th style="width:110px; text-align:center;">Parts</th>
|
<th style="width:45px; text-align:center;">Parts</th>
|
||||||
<th style="width:320px;">Title</th>
|
<th style="width:320px;">Title</th>
|
||||||
<th style="width:140px;">Type</th>
|
<th style="width:120px;">Type</th>
|
||||||
<th>Mapping</th>
|
<th>Mapping</th>
|
||||||
<th>Default Value</th>
|
<th>Default Value</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -360,6 +426,8 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
|
|
||||||
<?php elseif (in_array($fm['fixed_field_key'], [
|
<?php elseif (in_array($fm['fixed_field_key'], [
|
||||||
'ClienteResponsabile',
|
'ClienteResponsabile',
|
||||||
|
'ClienteFornitore',
|
||||||
|
'ClienteAnalisi',
|
||||||
'MoltiplicatorePrezzo',
|
'MoltiplicatorePrezzo',
|
||||||
'AnagraficaCertestObject',
|
'AnagraficaCertestObject',
|
||||||
'AnagraficaCertestService'
|
'AnagraficaCertestService'
|
||||||
@@ -452,8 +520,10 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
type: 'array'
|
type: 'array'
|
||||||
});
|
});
|
||||||
let sheet = workbook.Sheets[workbook.SheetNames[0]];
|
let sheet = workbook.Sheets[workbook.SheetNames[0]];
|
||||||
let rowIndex = parseInt(document.getElementById('headerRow').textContent) || 1;
|
|
||||||
let startColumn = parseInt(document.getElementById('startColumn').textContent) || 1;
|
// Read sheet range to determine column offset
|
||||||
|
const sheetRange = XLSX.utils.decode_range(sheet['!ref'] || 'A1');
|
||||||
|
const colOffset = sheetRange.s.c; // first column index in sheet (0-based)
|
||||||
|
|
||||||
let sheetData = XLSX.utils.sheet_to_json(sheet, {
|
let sheetData = XLSX.utils.sheet_to_json(sheet, {
|
||||||
header: 1,
|
header: 1,
|
||||||
@@ -461,7 +531,22 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
raw: false,
|
raw: false,
|
||||||
range: 0
|
range: 0
|
||||||
});
|
});
|
||||||
console.log("Dati della riga " + rowIndex + ":", sheetData[rowIndex - 1]);
|
|
||||||
|
// Track merged cell ranges — adjust for column offset
|
||||||
|
const merges = (sheet['!merges'] || []).map(m => ({
|
||||||
|
s: { r: m.s.r, c: m.s.c - colOffset },
|
||||||
|
e: { r: m.e.r, c: m.e.c - colOffset }
|
||||||
|
}));
|
||||||
|
console.log('Sheet column offset:', colOffset, '(first col:', String.fromCharCode(65 + colOffset) + ')');
|
||||||
|
|
||||||
|
const useAutoDetect = document.getElementById('autoDetectHeader').checked;
|
||||||
|
|
||||||
|
if (!useAutoDetect) {
|
||||||
|
// Use values from template settings
|
||||||
|
let rowIndex = parseInt(document.getElementById('headerRow').textContent) || 1;
|
||||||
|
let startColumn = parseInt(document.getElementById('startColumn').textContent) || 1;
|
||||||
|
console.log(`Manual mode: row ${rowIndex}, startCol ${startColumn}`);
|
||||||
|
|
||||||
if (!sheetData[rowIndex - 1]) {
|
if (!sheetData[rowIndex - 1]) {
|
||||||
document.getElementById('schemaFieldsBody').querySelectorAll('select.xls-columns').forEach(select => {
|
document.getElementById('schemaFieldsBody').querySelectorAll('select.xls-columns').forEach(select => {
|
||||||
select.innerHTML = '<option value="">Nessuna intestazione trovata</option>';
|
select.innerHTML = '<option value="">Nessuna intestazione trovata</option>';
|
||||||
@@ -469,50 +554,234 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let headers = sheetData[rowIndex - 1].slice(startColumn - 1).map(header => header === undefined ? "" : header);
|
// Build logical headers, collapsing merged cells
|
||||||
console.log("Intestazioni estratte:", headers);
|
const mergeStartMapManual = {};
|
||||||
|
merges.forEach(m => {
|
||||||
|
if ((rowIndex - 1) >= m.s.r && (rowIndex - 1) <= m.e.r) {
|
||||||
|
for (let c = m.s.c; c <= m.e.c; c++) {
|
||||||
|
mergeStartMapManual[c] = m.s.c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let headers = [];
|
||||||
|
const rawRowManual = sheetData[rowIndex - 1] || [];
|
||||||
|
const seenManual = new Set();
|
||||||
|
for (let c = startColumn - 1; c < rawRowManual.length; c++) {
|
||||||
|
const ms = mergeStartMapManual[c];
|
||||||
|
if (ms !== undefined) {
|
||||||
|
if (seenManual.has(ms)) continue;
|
||||||
|
seenManual.add(ms);
|
||||||
|
const v = rawRowManual[ms];
|
||||||
|
headers.push(v === undefined ? "" : String(v).replace(/[\r\n\t]+/g, ' ').trim());
|
||||||
|
} else {
|
||||||
|
const v = rawRowManual[c];
|
||||||
|
headers.push(v === undefined ? "" : String(v).replace(/[\r\n\t]+/g, ' ').trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (headers.length > 0 && headers[headers.length - 1] === '') {
|
||||||
|
headers.pop();
|
||||||
|
}
|
||||||
|
headers = headers.map((h, i) => {
|
||||||
|
h = h.replace(/[\r\n\t]+/g, ' ').trim();
|
||||||
|
return h === '' ? `__empty_${i + 1}__` : h;
|
||||||
|
});
|
||||||
|
console.log("Intestazioni estratte (manual):", headers);
|
||||||
availableXlsColumns = [...headers];
|
availableXlsColumns = [...headers];
|
||||||
usedColumnsFromDB = [];
|
usedColumnsFromDB = [];
|
||||||
saveXlsHeaders(headers);
|
saveXlsHeaders(headers, rowIndex, startColumn);
|
||||||
|
updateXlsDropdowns();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect field_label titles from the schema table
|
||||||
|
const knownLabels = [];
|
||||||
|
document.querySelectorAll('.title-col').forEach(td => {
|
||||||
|
const v = (td.textContent || '').trim().toLowerCase().replace(/\s*required\s*$/i, '').trim();
|
||||||
|
if (v && v !== 'n/a') knownLabels.push(v);
|
||||||
|
});
|
||||||
|
const uniqueLabels = [...new Set(knownLabels)];
|
||||||
|
|
||||||
|
console.group('🔍 Auto-detect header row');
|
||||||
|
console.log('Sheet name:', workbook.SheetNames[0]);
|
||||||
|
console.log('Total rows in sheet:', sheetData.length);
|
||||||
|
console.log('Labels from schema field titles:', knownLabels);
|
||||||
|
console.log('Unique labels to match against:', uniqueLabels);
|
||||||
|
|
||||||
|
// Auto-detect: find the row with most matches, fallback to most non-empty unique text cells
|
||||||
|
let bestRow = 0;
|
||||||
|
let bestScore = 0;
|
||||||
|
let bestStartCol = 0;
|
||||||
|
let fallbackRow = 0;
|
||||||
|
let fallbackScore = 0;
|
||||||
|
let fallbackStartCol = 0;
|
||||||
|
const scanLimit = Math.min(sheetData.length, 50);
|
||||||
|
const rowScores = [];
|
||||||
|
|
||||||
|
for (let r = 0; r < scanLimit; r++) {
|
||||||
|
const row = sheetData[r] || [];
|
||||||
|
let score = 0;
|
||||||
|
let firstNonEmpty = -1;
|
||||||
|
let nonEmptyCount = 0;
|
||||||
|
const matched = [];
|
||||||
|
const cellValues = [];
|
||||||
|
const seen = new Set();
|
||||||
|
for (let c = 0; c < row.length; c++) {
|
||||||
|
const cellVal = String(row[c] || '').trim();
|
||||||
|
const cellLower = cellVal.toLowerCase();
|
||||||
|
if (cellVal) {
|
||||||
|
cellValues.push(`[${c}]="${cellVal}"`);
|
||||||
|
if (firstNonEmpty < 0) firstNonEmpty = c;
|
||||||
|
// Count unique short text cells (likely headers, not data/descriptions)
|
||||||
|
if (cellVal.length < 80 && !seen.has(cellLower)) {
|
||||||
|
nonEmptyCount++;
|
||||||
|
seen.add(cellLower);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cellLower && uniqueLabels.includes(cellLower)) {
|
||||||
|
score++;
|
||||||
|
matched.push(cellVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rowScores.push({ row: r + 1, score, nonEmpty: nonEmptyCount, matched, firstNonEmpty: firstNonEmpty + 1, cells: cellValues });
|
||||||
|
if (score > bestScore) {
|
||||||
|
bestScore = score;
|
||||||
|
bestRow = r;
|
||||||
|
bestStartCol = firstNonEmpty >= 0 ? firstNonEmpty : 0;
|
||||||
|
}
|
||||||
|
// Fallback: row with most unique short text cells (likely header row)
|
||||||
|
if (nonEmptyCount > fallbackScore) {
|
||||||
|
fallbackScore = nonEmptyCount;
|
||||||
|
fallbackRow = r;
|
||||||
|
fallbackStartCol = firstNonEmpty >= 0 ? firstNonEmpty : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no label matches, use fallback (most populated row)
|
||||||
|
if (bestScore === 0 && fallbackScore > 0) {
|
||||||
|
bestRow = fallbackRow;
|
||||||
|
bestStartCol = fallbackStartCol;
|
||||||
|
console.log(`⚠️ No label matches found. Using fallback: row with most headers (${fallbackScore} unique cells)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('All rows scanned:');
|
||||||
|
console.table(rowScores.filter(r => r.nonEmpty > 0).map(r => ({
|
||||||
|
row: r.row,
|
||||||
|
labelMatches: r.score,
|
||||||
|
uniqueCells: r.nonEmpty,
|
||||||
|
matched: r.matched.join(', '),
|
||||||
|
firstCol: r.firstNonEmpty
|
||||||
|
})));
|
||||||
|
console.log(`✅ Result: row ${bestRow + 1}, startCol ${bestStartCol + 1}, labelScore ${bestScore}/${uniqueLabels.length}, fallbackScore ${fallbackScore}`);
|
||||||
|
console.log('Selected row raw data:', sheetData[bestRow]);
|
||||||
|
console.groupEnd();
|
||||||
|
|
||||||
|
// Update display
|
||||||
|
document.getElementById('headerRow').textContent = bestRow + 1;
|
||||||
|
document.getElementById('startColumn').textContent = bestStartCol + 1;
|
||||||
|
|
||||||
|
if (!sheetData[bestRow]) {
|
||||||
|
document.getElementById('schemaFieldsBody').querySelectorAll('select.xls-columns').forEach(select => {
|
||||||
|
select.innerHTML = '<option value="">Nessuna intestazione trovata</option>';
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build logical columns: each merge = one column, each non-merged cell = one column
|
||||||
|
let headers = [];
|
||||||
|
const rawRow = sheetData[bestRow] || [];
|
||||||
|
|
||||||
|
// Map each physical column to its merge start (or itself if not merged)
|
||||||
|
const mergeStartMap = {}; // physCol -> startCol of its merge
|
||||||
|
merges.forEach(m => {
|
||||||
|
if (bestRow >= m.s.r && bestRow <= m.e.r) {
|
||||||
|
for (let c = m.s.c; c <= m.e.c; c++) {
|
||||||
|
mergeStartMap[c] = m.s.c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const seen = new Set();
|
||||||
|
for (let c = bestStartCol; c < rawRow.length; c++) {
|
||||||
|
const mergeStart = mergeStartMap[c];
|
||||||
|
const cleanVal = (v) => (v === undefined ? "" : String(v).replace(/[\r\n\t]+/g, ' ').trim());
|
||||||
|
if (mergeStart !== undefined) {
|
||||||
|
// Part of a merge — only take the first occurrence
|
||||||
|
if (seen.has(mergeStart)) continue;
|
||||||
|
seen.add(mergeStart);
|
||||||
|
headers.push(cleanVal(rawRow[mergeStart]));
|
||||||
|
} else {
|
||||||
|
headers.push(cleanVal(rawRow[c]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Trim trailing empty columns
|
||||||
|
while (headers.length > 0 && headers[headers.length - 1] === '') {
|
||||||
|
headers.pop();
|
||||||
|
}
|
||||||
|
// Final clean + name empty columns to match __empty_N__ convention
|
||||||
|
headers = headers.map((h, i) => {
|
||||||
|
h = h.replace(/[\r\n\t]+/g, ' ').trim();
|
||||||
|
return h === '' ? `__empty_${i + 1}__` : h;
|
||||||
|
});
|
||||||
|
console.log("Logical headers:", headers, `(${headers.length} columns from ${rawRow.length} physical)`);
|
||||||
|
availableXlsColumns = [...headers];
|
||||||
|
usedColumnsFromDB = [];
|
||||||
|
saveXlsHeaders(headers, bestRow + 1, bestStartCol + 1);
|
||||||
updateXlsDropdowns();
|
updateXlsDropdowns();
|
||||||
};
|
};
|
||||||
reader.readAsArrayBuffer(file);
|
reader.readAsArrayBuffer(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveXlsHeaders(headers) {
|
function saveXlsHeaders(headers, headerRow, startColumn) {
|
||||||
|
const payload = {
|
||||||
|
template_id: <?php echo $id; ?>,
|
||||||
|
xls_headers: JSON.stringify(headers)
|
||||||
|
};
|
||||||
|
if (headerRow !== undefined) payload.header_row = headerRow;
|
||||||
|
if (startColumn !== undefined) payload.start_column = startColumn;
|
||||||
|
|
||||||
fetch('update_xls_headers.php', {
|
fetch('update_xls_headers.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify(payload)
|
||||||
template_id: <?php echo $id; ?>,
|
|
||||||
xls_headers: JSON.stringify(headers)
|
|
||||||
})
|
|
||||||
}).then(response => response.json())
|
}).then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (!data.success) console.error("❌ Error saving XLS headers:", data.message);
|
if (!data.success) console.error("❌ Error saving XLS headers:", data.message);
|
||||||
|
else console.log("✅ Saved headers, header_row:", headerRow, "start_column:", startColumn);
|
||||||
})
|
})
|
||||||
.catch(error => console.error("❌ Fetch error:", error));
|
.catch(error => console.error("❌ Fetch error:", error));
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateXlsDropdowns() {
|
function updateXlsDropdowns() {
|
||||||
let usedColumns = Array.from(document.querySelectorAll('select.xls-columns'))
|
let usedColumns = Array.from(document.querySelectorAll('select.xls-columns'))
|
||||||
.filter(select => select.style.display === 'block' && select.value)
|
.map(select => select.value || select.dataset.currentXls || '')
|
||||||
.map(select => select.value)
|
.filter(Boolean)
|
||||||
.concat(usedColumnsFromDB);
|
.concat(usedColumnsFromDB);
|
||||||
|
|
||||||
|
let uniqueUsedColumns = [...new Set(usedColumns)];
|
||||||
|
|
||||||
document.querySelectorAll('select.xls-columns').forEach(select => {
|
document.querySelectorAll('select.xls-columns').forEach(select => {
|
||||||
let currentValue = select.value || select.dataset.currentXls || '';
|
let currentValue = select.value || select.dataset.currentXls || '';
|
||||||
|
|
||||||
let options = availableXlsColumns
|
let options = availableXlsColumns
|
||||||
.filter(col => !usedColumns.includes(col) || col === currentValue)
|
.map((col, origIdx) => ({ col, origIdx }))
|
||||||
.map(col => `<option value="${col}" ${col === currentValue ? 'selected' : ''}>${col}</option>`)
|
.map(({ col, origIdx }) => {
|
||||||
|
const clean = col.replace(/[\r\n\t]+/g, ' ').trim();
|
||||||
|
const isEmpty = clean === '';
|
||||||
|
const colNum = origIdx + 1;
|
||||||
|
const val = isEmpty ? `__empty_${colNum}__` : clean;
|
||||||
|
const isUsed = uniqueUsedColumns.includes(col) && col !== currentValue;
|
||||||
|
const label = isEmpty
|
||||||
|
? `(empty column ${colNum})`
|
||||||
|
: (isUsed ? `⚠ ${clean} (already used)` : clean);
|
||||||
|
const isSelected = (isEmpty ? val === currentValue : col === currentValue) ? 'selected' : '';
|
||||||
|
return `<option value="${val}" class="${isUsed ? 'used-option' : ''}" ${isSelected}>${label}</option>`;
|
||||||
|
})
|
||||||
.join('');
|
.join('');
|
||||||
|
|
||||||
select.innerHTML = '<option value="">Select XLS Column</option>' + options;
|
select.innerHTML = '<option value="">Select XLS Column</option>' + options;
|
||||||
select.dataset.currentXls = currentValue;
|
select.dataset.currentXls = currentValue;
|
||||||
if (currentValue && !options.includes(currentValue)) {
|
|
||||||
select.value = '';
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -913,38 +1182,21 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
cb.checked = cb.dataset.originalChecked === 'true';
|
cb.checked = cb.dataset.originalChecked === 'true';
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else if (event.target.classList.contains('visible-import-checkbox')) {
|
|
||||||
const checkbox = event.target;
|
|
||||||
const mappingId = checkbox.dataset.mappingId;
|
|
||||||
const value = checkbox.checked ? 1 : 0;
|
|
||||||
|
|
||||||
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(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (!data.success) {
|
|
||||||
console.error("❌ Error updating is_visible_import:", data.message);
|
|
||||||
checkbox.checked = !checkbox.checked;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error("❌ Fetch error:", error);
|
|
||||||
checkbox.checked = !checkbox.checked;
|
|
||||||
});
|
|
||||||
} else if (event.target.classList.contains('visible-parts-checkbox')) {
|
} else if (event.target.classList.contains('visible-parts-checkbox')) {
|
||||||
const checkbox = event.target;
|
const checkbox = event.target;
|
||||||
const mappingId = checkbox.dataset.mappingId;
|
const mappingId = checkbox.dataset.mappingId;
|
||||||
const value = checkbox.checked ? 1 : 0;
|
const value = checkbox.checked ? 1 : 0;
|
||||||
|
|
||||||
|
// salva stato per rollback
|
||||||
|
const prevChecked = checkbox.checked;
|
||||||
|
|
||||||
|
// ✅ UI: se sto mettendo a 1, tolgo la spunta a tutti gli altri SUBITO
|
||||||
|
if (value === 1) {
|
||||||
|
document.querySelectorAll('.visible-parts-checkbox').forEach(cb => {
|
||||||
|
if (cb !== checkbox) cb.checked = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fetch('update_visible_parts.php', {
|
fetch('update_visible_parts.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -956,16 +1208,25 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
value: value
|
value: value
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(r => r.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (!data.success) {
|
if (!data.success) {
|
||||||
console.error("❌ Error updating is_visible_parts:", data.message);
|
console.error("❌ Error updating is_visible_parts:", data.message);
|
||||||
checkbox.checked = !checkbox.checked;
|
|
||||||
|
// rollback UI
|
||||||
|
checkbox.checked = !prevChecked;
|
||||||
|
|
||||||
|
// se avevo tolto le spunte agli altri, ricarico per riallineare la UI al DB
|
||||||
|
// (semplice e safe)
|
||||||
|
location.reload();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error("❌ Fetch error:", error);
|
console.error("❌ Fetch error:", error);
|
||||||
checkbox.checked = !checkbox.checked;
|
|
||||||
|
// rollback UI
|
||||||
|
checkbox.checked = !prevChecked;
|
||||||
|
location.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1006,7 +1267,7 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
mappedColumn = document.createElement('span');
|
mappedColumn = document.createElement('span');
|
||||||
mappedColumn.className = 'mapped-column';
|
mappedColumn.className = 'mapped-column';
|
||||||
mappedColumn.style.marginLeft = '5px';
|
mappedColumn.style.marginLeft = '5px';
|
||||||
tr.querySelector('td:nth-child(5)').appendChild(mappedColumn);
|
tr.querySelector('td:nth-child(6)').appendChild(mappedColumn);
|
||||||
}
|
}
|
||||||
if (!removeBtn) {
|
if (!removeBtn) {
|
||||||
removeBtn = document.createElement('button');
|
removeBtn = document.createElement('button');
|
||||||
@@ -1014,7 +1275,7 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
removeBtn.textContent = 'X';
|
removeBtn.textContent = 'X';
|
||||||
removeBtn.style.marginLeft = '5px';
|
removeBtn.style.marginLeft = '5px';
|
||||||
removeBtn.setAttribute('data-id', mappingId);
|
removeBtn.setAttribute('data-id', mappingId);
|
||||||
tr.querySelector('td:nth-child(5)').appendChild(removeBtn);
|
tr.querySelector('td:nth-child(6)').appendChild(removeBtn);
|
||||||
|
|
||||||
removeBtn.addEventListener('click', function(e) {
|
removeBtn.addEventListener('click', function(e) {
|
||||||
let tr = e.target.closest('tr');
|
let tr = e.target.closest('tr');
|
||||||
@@ -1157,6 +1418,8 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
|
|
||||||
const keysWithDropdown = [
|
const keysWithDropdown = [
|
||||||
'ClienteResponsabile',
|
'ClienteResponsabile',
|
||||||
|
'ClienteFornitore',
|
||||||
|
'ClienteAnalisi',
|
||||||
'MoltiplicatorePrezzo',
|
'MoltiplicatorePrezzo',
|
||||||
'AnagraficaCertestObject',
|
'AnagraficaCertestObject',
|
||||||
'AnagraficaCertestService'
|
'AnagraficaCertestService'
|
||||||
@@ -1370,7 +1633,14 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
|
|
||||||
const clientId = <?php echo (int)($template['idclient'] ?? 0); ?>;
|
const clientId = <?php echo (int)($template['idclient'] ?? 0); ?>;
|
||||||
|
|
||||||
|
const requestCache = {};
|
||||||
|
|
||||||
async function fetchJson(url) {
|
async function fetchJson(url) {
|
||||||
|
if (requestCache[url]) {
|
||||||
|
return requestCache[url];
|
||||||
|
}
|
||||||
|
|
||||||
|
requestCache[url] = (async () => {
|
||||||
const r = await fetch(url);
|
const r = await fetch(url);
|
||||||
const text = await r.text();
|
const text = await r.text();
|
||||||
if (!r.ok) throw new Error(`HTTP ${r.status} on ${url}: ${text.slice(0, 200)}`);
|
if (!r.ok) throw new Error(`HTTP ${r.status} on ${url}: ${text.slice(0, 200)}`);
|
||||||
@@ -1379,6 +1649,9 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`Invalid JSON from ${url}: ${text.slice(0, 200)}`);
|
throw new Error(`Invalid JSON from ${url}: ${text.slice(0, 200)}`);
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return requestCache[url];
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeList(j, key) {
|
function normalizeList(j, key) {
|
||||||
@@ -1392,6 +1665,16 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatClientLabel(x) {
|
||||||
|
const nome = x.Nominativo ?? x.Nome ?? x.name ?? 'Nome non disponibile';
|
||||||
|
const id = x.IdCliente ?? x.Id ?? x.id ?? '';
|
||||||
|
const codiceCliente = (x.CodiceCliente ?? x.codiceCliente ?? '').toString().trim();
|
||||||
|
const suffix = (codiceCliente.split('_')[1] || '').trim();
|
||||||
|
const shortCode = suffix || (codiceCliente ? codiceCliente.charAt(0) : '--');
|
||||||
|
|
||||||
|
return `${nome.trim()} - ${shortCode} (ID: ${id})`;
|
||||||
|
}
|
||||||
|
|
||||||
async function loadData(key) {
|
async function loadData(key) {
|
||||||
if (key === 'MoltiplicatorePrezzo') {
|
if (key === 'MoltiplicatorePrezzo') {
|
||||||
const j = await fetchJson('get_moltiplicatoreprezzo.php');
|
const j = await fetchJson('get_moltiplicatoreprezzo.php');
|
||||||
@@ -1434,6 +1717,16 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
})).filter(x => x.id != null && x.label);
|
})).filter(x => x.id != null && x.label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (key === 'ClienteFornitore' || key === 'ClienteAnalisi') {
|
||||||
|
const j = await fetchJson('get_clienti.php');
|
||||||
|
const list = normalizeList(j, key);
|
||||||
|
|
||||||
|
return list.map(x => ({
|
||||||
|
id: x.IdCliente ?? x.Id ?? x.id,
|
||||||
|
label: formatClientLabel(x)
|
||||||
|
})).filter(x => x.id != null && x.label);
|
||||||
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,304 @@
|
|||||||
|
<?php
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
$iddatadb = isset($_GET['iddatadb']) ? (int)$_GET['iddatadb'] : 0;
|
||||||
|
|
||||||
|
if ($iddatadb <= 0) {
|
||||||
|
?>
|
||||||
|
<div class="modal fade" id="analysisModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-fullscreen">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Analysis</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="alert alert-danger mb-0">Invalid iddatadb</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
p.id,
|
||||||
|
p.iddatadb,
|
||||||
|
p.part_number,
|
||||||
|
p.part_description,
|
||||||
|
p.material,
|
||||||
|
p.color,
|
||||||
|
p.mix,
|
||||||
|
p.idmatrice,
|
||||||
|
p.note,
|
||||||
|
p.dateexpiry,
|
||||||
|
a.NomeMatriceTraduzione
|
||||||
|
FROM identification_parts p
|
||||||
|
LEFT JOIN auth_matrici a ON a.IdMatrice = p.idmatrice
|
||||||
|
WHERE p.iddatadb = ?
|
||||||
|
ORDER BY
|
||||||
|
CASE WHEN p.part_number IS NULL THEN 999999 ELSE p.part_number END ASC,
|
||||||
|
p.id ASC
|
||||||
|
");
|
||||||
|
$stmt->execute([$iddatadb]);
|
||||||
|
$parts = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build matrix groups from parts.
|
||||||
|
* No join for now: we use idmatrice only.
|
||||||
|
*/
|
||||||
|
$matrixGroups = [];
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
$matrixKey = (!empty($part['idmatrice'])) ? (string)$part['idmatrice'] : 'NO_MATRIX';
|
||||||
|
|
||||||
|
if (!isset($matrixGroups[$matrixKey])) {
|
||||||
|
$matrixGroups[$matrixKey] = [
|
||||||
|
'idmatrice' => $part['idmatrice'],
|
||||||
|
'NomeMatriceTraduzione' => $part['NomeMatriceTraduzione'] ?? '',
|
||||||
|
'parts_count' => 0,
|
||||||
|
'parts' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$matrixGroups[$matrixKey]['parts_count']++;
|
||||||
|
$matrixGroups[$matrixKey]['parts'][] = $part['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$matrixGroups = array_values($matrixGroups);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="modal fade" id="analysisModal" tabindex="-1" aria-labelledby="analysisModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog" style="max-width: 96vw; width: 96vw; margin: 1.5vh auto;">
|
||||||
|
<div class="modal-content analysis-modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<div>
|
||||||
|
<h5 class="modal-title mb-0" id="analysisModalLabel">Analysis - TRF <?= htmlspecialchars((string)$iddatadb, ENT_QUOTES, 'UTF-8') ?></h5>
|
||||||
|
<small class="text-muted">
|
||||||
|
Parts: <?= count($parts) ?> |
|
||||||
|
Matrices: <?= count($matrixGroups) ?>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body analysis-modal-body">
|
||||||
|
<div class="row g-3 h-100">
|
||||||
|
<!-- MATRICES -->
|
||||||
|
<div class="col-lg-3 col-md-4">
|
||||||
|
<div class="card h-100 shadow-sm">
|
||||||
|
<div class="card-header py-2 d-flex justify-content-between align-items-center">
|
||||||
|
<strong>Matrices</strong>
|
||||||
|
<span class="badge bg-secondary"><?= count($matrixGroups) ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-2 analysis-scroll-area">
|
||||||
|
<div class="list-group analysis-matrix-list" id="analysisMatrixList">
|
||||||
|
<?php if (empty($matrixGroups)): ?>
|
||||||
|
<div class="text-muted small p-2">No matrices found</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($matrixGroups as $index => $group): ?>
|
||||||
|
<button type="button"
|
||||||
|
class="list-group-item list-group-item-action analysis-matrix-item <?= $index === 0 ? 'active' : '' ?>"
|
||||||
|
data-matrix-id="<?= htmlspecialchars((string)($group['idmatrice'] ?? 'NO_MATRIX'), ENT_QUOTES, 'UTF-8') ?>">
|
||||||
|
<div class="fw-semibold">
|
||||||
|
<?= htmlspecialchars(
|
||||||
|
!empty($group['NomeMatriceTraduzione'])
|
||||||
|
? $group['NomeMatriceTraduzione']
|
||||||
|
: (!empty($group['idmatrice']) ? ('Matrix without translation') : 'No Matrix'),
|
||||||
|
ENT_QUOTES,
|
||||||
|
'UTF-8'
|
||||||
|
) ?>
|
||||||
|
</div>
|
||||||
|
<div class="small text-muted">
|
||||||
|
ID: <?= !empty($group['idmatrice']) ? (int)$group['idmatrice'] : '-' ?>
|
||||||
|
</div>
|
||||||
|
<div class="small text-muted">
|
||||||
|
Parts linked: <?= (int)$group['parts_count'] ?>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PARTS -->
|
||||||
|
<div class="col-lg-4 col-md-8">
|
||||||
|
<div class="card h-100 shadow-sm">
|
||||||
|
<div class="card-header py-2 d-flex justify-content-between align-items-center">
|
||||||
|
<strong>Parts</strong>
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<button type="button" class="btn btn-outline-secondary btn-sm" id="analysisClearSelectionBtn">Clear</button>
|
||||||
|
<span class="badge bg-primary" id="analysisSelectedPartsCount">0 selected</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-2 analysis-scroll-area">
|
||||||
|
<div class="list-group analysis-parts-list" id="analysisPartsList">
|
||||||
|
<?php if (empty($parts)): ?>
|
||||||
|
<div class="text-muted small p-2">No parts found</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($parts as $part): ?>
|
||||||
|
<?php
|
||||||
|
$matrixId = !empty($part['idmatrice']) ? (string)$part['idmatrice'] : 'NO_MATRIX';
|
||||||
|
$partLabel = trim(($part['part_number'] !== null ? ('Part ' . $part['part_number']) : 'Part') . ' - ' . ($part['part_description'] ?? ''));
|
||||||
|
?>
|
||||||
|
<div class="list-group-item analysis-part-item"
|
||||||
|
data-part-id="<?= (int)$part['id'] ?>"
|
||||||
|
data-matrix-id="<?= htmlspecialchars($matrixId, ENT_QUOTES, 'UTF-8') ?>">
|
||||||
|
<div class="d-flex align-items-start gap-2">
|
||||||
|
<input type="checkbox"
|
||||||
|
class="form-check-input mt-1 analysis-part-checkbox"
|
||||||
|
value="<?= (int)$part['id'] ?>">
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<div class="fw-semibold">
|
||||||
|
<?= htmlspecialchars($partLabel, ENT_QUOTES, 'UTF-8') ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="small text-muted mt-1">
|
||||||
|
Matrix:
|
||||||
|
<strong>
|
||||||
|
<?= htmlspecialchars(
|
||||||
|
!empty($part['NomeMatriceTraduzione'])
|
||||||
|
? $part['NomeMatriceTraduzione']
|
||||||
|
: (!empty($part['idmatrice']) ? 'Matrix without translation' : 'No Matrix'),
|
||||||
|
ENT_QUOTES,
|
||||||
|
'UTF-8'
|
||||||
|
) ?>
|
||||||
|
</strong>
|
||||||
|
<?php if (!empty($part['idmatrice'])): ?>
|
||||||
|
<span class="small text-muted">(ID: <?= (int)$part['idmatrice'] ?>)</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (!empty($part['mix']) && strtoupper($part['mix']) === 'Y'): ?>
|
||||||
|
| <span class="badge bg-warning text-dark">Mix</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!empty($part['material']) || !empty($part['color'])): ?>
|
||||||
|
<div class="small text-muted">
|
||||||
|
<?php if (!empty($part['material'])): ?>
|
||||||
|
Material: <?= htmlspecialchars($part['material'], ENT_QUOTES, 'UTF-8') ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (!empty($part['material']) && !empty($part['color'])): ?>
|
||||||
|
|
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (!empty($part['color'])): ?>
|
||||||
|
Color: <?= htmlspecialchars($part['color'], ENT_QUOTES, 'UTF-8') ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($part['note'])): ?>
|
||||||
|
<div class="small text-muted mt-1">
|
||||||
|
Note: <?= htmlspecialchars($part['note'], ENT_QUOTES, 'UTF-8') ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ANALYSES PLACEHOLDER -->
|
||||||
|
<div class="col-lg-5">
|
||||||
|
<div class="card h-100 shadow-sm">
|
||||||
|
<div class="card-header py-2">
|
||||||
|
<strong>Analyses</strong>
|
||||||
|
</div>
|
||||||
|
<div class="card-body analysis-scroll-area" id="analysisRightPanel">
|
||||||
|
<div class="alert alert-info mb-3">
|
||||||
|
This area will contain analyses in the next step.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border rounded p-3 bg-light">
|
||||||
|
<div class="mb-2"><strong>Current behavior</strong></div>
|
||||||
|
<ul class="mb-0 ps-3">
|
||||||
|
<li>Click a matrix on the left</li>
|
||||||
|
<li>All parts linked to that matrix become selected</li>
|
||||||
|
<li>Analyses panel will be connected later</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3">
|
||||||
|
<div class="small text-muted">Selected matrix:</div>
|
||||||
|
<div id="analysisCurrentMatrix" class="fw-semibold">-</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3">
|
||||||
|
<div class="small text-muted">Selected parts IDs:</div>
|
||||||
|
<div id="analysisSelectedPartsIds" class="fw-semibold">-</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" id="analysisModalIddatadb" value="<?= (int)$iddatadb ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#analysisModal {
|
||||||
|
z-index: 1080 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#analysisModal .modal-content {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
min-height: 95vh;
|
||||||
|
border-radius: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysis-modal-content {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysis-modal-body {
|
||||||
|
height: calc(95vh - 120px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysis-scroll-area {
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: calc(95vh - 180px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysis-matrix-item {
|
||||||
|
text-align: left;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysis-part-item {
|
||||||
|
border-radius: 8px !important;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysis-part-item.matrix-active {
|
||||||
|
background-color: #e8f4ff !important;
|
||||||
|
border-color: #86b7fe !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analysis-part-item.part-checked {
|
||||||
|
background-color: #eaf7ea !important;
|
||||||
|
border-color: #7ac77a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#analysisSelectedPartsIds {
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -307,4 +307,23 @@
|
|||||||
width: 250px !important;
|
width: 250px !important;
|
||||||
min-width: 250px !important;
|
min-width: 250px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#partsTableBody .extra-field-td .select2-container {
|
||||||
|
width: 100% !important;
|
||||||
|
min-width: 180px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#partsTableBody .extra-field-td .select2-selection--single {
|
||||||
|
height: 31px !important;
|
||||||
|
padding: 0.15rem 0.35rem !important;
|
||||||
|
border: 1px solid #ced4da !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#partsTableBody .extra-field-td .select2-selection__rendered {
|
||||||
|
line-height: 28px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#partsTableBody .extra-field-td .select2-selection__arrow {
|
||||||
|
height: 29px !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="modal fade" id="partsModal" tabindex="-1" aria-labelledby="partsModalLabel" aria-hidden="true">
|
<div class="modal fade" id="partsModal" tabindex="-1" aria-labelledby="partsModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-xl" style="max-width: 80% !important;">
|
<div class="modal-dialog modal-xl" style="max-width: 95vw !important;">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title" id="partsModalLabel">Parti per TRF: <span id="trfHeader"></span></h5>
|
<h5 class="modal-title" id="partsModalLabel">Parti per TRF: <span id="trfHeader"></span></h5>
|
||||||
@@ -9,32 +9,43 @@
|
|||||||
<div class="row parts-row">
|
<div class="row parts-row">
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<!-- Prima riga: Elenco Parti, Rinumera, Voce -->
|
<!-- Prima riga: Elenco Parti, Rinumera, Voce -->
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; min-width: 0;">
|
||||||
<h6 style="margin: 0;">Elenco Parti</h6>
|
<h6 style="margin: 0; white-space: nowrap;">Elenco Parti</h6>
|
||||||
<div style="display: flex; align-items: center;">
|
<div style="display: flex; align-items: center; min-width: 0; gap: 8px;">
|
||||||
<button type="button" class="btn btn-info btn-sm" id="renumberPartsBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Rinumera Parti</button>
|
<button type="button"
|
||||||
<button type="button" class="btn btn-secondary btn-sm ms-2" id="toggleVoiceBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;"><i class="fas fa-microphone"></i> Voce</button>
|
class="btn btn-dark btn-sm open-analysis-modal-btn"
|
||||||
<button type="button" class="btn btn-info btn-sm ms-2 d-none" id="quotationeBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Add Quotation</button>
|
id="openAnalysisModalBtn"
|
||||||
<button type="button" class="btn btn-primary btn-sm ms-2" id="showHideImageBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
|
style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
|
||||||
<i class="fas fa-eye-slash" style="font-size: 0.8rem;"></i>
|
<i class="fas fa-flask"></i> Analysis
|
||||||
<i class="fas fa-image ms-1" style="font-size: 0.8rem;"></i>
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
<input type="checkbox" id="showMixParts" name="showMixParts" style="margin-right: 5px; margin-left: 10px;">
|
||||||
|
<label for="showMixParts" style="font-size: 0.9rem; margin-right: 10px;">Mix</label>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-info btn-sm" id="renumberPartsBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
|
||||||
|
Rinumera Parti
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-secondary btn-sm ms-2" id="toggleVoiceBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
|
||||||
|
<i class="fas fa-microphone"></i> Voce
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Seconda riga: +, M, MacroMatrice, Matrice globale, Propaga -->
|
<!-- Seconda riga: +, M, MacroMatrice, Matrice globale, Propaga -->
|
||||||
<div style="display: flex; align-items: center; margin-bottom: 10px;">
|
<div style="display: flex; align-items: center; margin-bottom: 10px;">
|
||||||
<button type="button" class="btn btn-success btn-sm add-row-global" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; margin-right: 5px;"><i class="fas fa-plus fa-xs"></i></button>
|
<button type="button" class="btn btn-success btn-sm add-row-global" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; margin-right: 5px;"><i class="fas fa-plus"></i></button>
|
||||||
<button type="button" class="btn btn-primary btn-sm add-mix-global" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; margin-right: 10px;">M</button>
|
<button type="button" class="btn btn-primary btn-sm add-mix-global" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; margin-right: 10px;">M</button>
|
||||||
<select id="macro-matrice-filter" class="form-control form-control-sm ms-2" style="width: 200px !important; min-width: 200px !important; margin-right: 10px;">
|
<select id="macro-matrice-filter" class="form-control form-control-sm ms-2" style="width: 200px !important; min-width: 200px !important; margin-right: 10px;">
|
||||||
<option value="">Tutte le MacroMatrici</option>
|
<option value="">Tutte le MacroMatrici</option>
|
||||||
</select>
|
</select>
|
||||||
<select id="global-matrice" class="form-control form-control-sm" style="width: 350px !important; margin-right: 10px;"></select>
|
<select id="global-matrice" class="form-control form-control-sm" style="width: 350px !important; margin-right: 10px;"></select>
|
||||||
<button type="button" class="btn btn-primary btn-sm propagate-all-btn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;"><i class="fas fa-arrow-right fa-xs"></i> Propaga a tutte</button>
|
<button type="button" class="btn btn-primary btn-sm propagate-all-btn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;"><i class="fas fa-arrow-right"></i> Propaga a tutte</button>
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-striped table-sm" id="partsTable">
|
<table class="table table-striped table-sm" id="partsTable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 80px;">Numero</th>
|
<th style="width: 55px;">Num</th>
|
||||||
<th>Descrizione</th>
|
<th>Descrizione</th>
|
||||||
<th style="width: 200px;">Matrice</th>
|
<th style="width: 200px;">Matrice</th>
|
||||||
<th style="width: 150px;">
|
<th style="width: 150px;">
|
||||||
@@ -51,11 +62,11 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody id="partsTableBody">
|
<tbody id="partsTableBody">
|
||||||
<tr data-part-id="new">
|
<tr data-part-id="new">
|
||||||
<td><input type="number" class="form-control form-control-sm part-number" value="1" style="width: 80px;"></td>
|
<td><input type="number" class="form-control form-control-sm part-number" value="1" style="width: 55px;"></td>
|
||||||
<td><input type="text" class="form-control form-control-sm part-description" placeholder="Inserisci descrizione"></td>
|
<td><input type="text" class="form-control form-control-sm part-description" placeholder="Inserisci descrizione"></td>
|
||||||
<td>
|
<td>
|
||||||
<div style="display: flex; align-items: center;">
|
<div style="display: flex; align-items: center;">
|
||||||
<button type="button" class="btn btn-primary btn-sm propagate-matrice-btn" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; margin-right: 3px;"><i class="fas fa-arrow-right fa-xs"></i></button>
|
<button type="button" class="btn btn-primary btn-sm propagate-matrice-btn" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; margin-right: 3px;"><i class="fas fa-arrow-right"></i></button>
|
||||||
<select class="part-matrice form-control form-control-sm" style="width: 150px;"></select>
|
<select class="part-matrice form-control form-control-sm" style="width: 150px;"></select>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -63,9 +74,9 @@
|
|||||||
<td>
|
<td>
|
||||||
<button type="button" class="btn btn-light btn-sm note-btn" style="padding: 0.2rem 0.4rem; font-size: 0.9rem;" title="Aggiungi/Modifica nota"><i class="fas fa-sticky-note"></i></button>
|
<button type="button" class="btn btn-light btn-sm note-btn" style="padding: 0.2rem 0.4rem; font-size: 0.9rem;" title="Aggiungi/Modifica nota"><i class="fas fa-sticky-note"></i></button>
|
||||||
<button type="button" class="btn btn-warning btn-sm add-mix-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;">M+</button>
|
<button type="button" class="btn btn-warning btn-sm add-mix-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;">M+</button>
|
||||||
<button type="button" class="btn btn-danger btn-sm remove-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; display: none;"><i class="fas fa-trash fa-xs"></i></button>
|
<button type="button" class="btn btn-danger btn-sm remove-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; display: none;"><i class="fas fa-trash"></i></button>
|
||||||
<span class="save-status text-success" style="display: none; margin-left: 5px;"><i class="fas fa-check fa-xs"></i></span>
|
<span class="save-status text-success" style="display: none; margin-left: 5px;"><i class="fas fa-check"></i></span>
|
||||||
<span class="save-loading text-warning" style="display: none; margin-left: 5px;"><i class="fas fa-spinner fa-spin fa-xs"></i></span>
|
<span class="save-loading text-warning" style="display: none; margin-left: 5px;"><i class="fas fa-spinner fa-spin"></i></span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -188,6 +199,14 @@
|
|||||||
z-index: 1055 !important
|
z-index: 1055 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Fix: app.css sets .btn i { margin-top:-1em; margin-bottom:-1em; font-size:1.3rem } which collapses small icon buttons */
|
||||||
|
#partsModal .btn i {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
font-size: 0.8rem !important;
|
||||||
|
margin-right: 2px !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Tabelle */
|
/* Tabelle */
|
||||||
#partsTable tr {
|
#partsTable tr {
|
||||||
display: table-row !important
|
display: table-row !important
|
||||||
@@ -287,9 +306,13 @@
|
|||||||
/* Colonna Descrizione (2ª colonna) = 420px */
|
/* Colonna Descrizione (2ª colonna) = 420px */
|
||||||
#partsTable th:nth-child(2),
|
#partsTable th:nth-child(2),
|
||||||
#partsTable td:nth-child(2) {
|
#partsTable td:nth-child(2) {
|
||||||
width: 350 !important;
|
width: 300px !important;
|
||||||
min-width: 350px !important;
|
min-width: 300px !important;
|
||||||
max-width: 350px !important;
|
max-width: 300px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#partsTable .part-number {
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#partsTable td:nth-child(2) .part-description {
|
#partsTable td:nth-child(2) .part-description {
|
||||||
|
|||||||
@@ -0,0 +1,218 @@
|
|||||||
|
/**
|
||||||
|
* modals_gridData.js — Photos, Parts, Tested Component handlers for gridData pages
|
||||||
|
*/
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// ── Photos — use photos.js loadPopupContent (exported to window) ──
|
||||||
|
$(document).on('click', '.photos-btn', function () {
|
||||||
|
const iddatadb = $(this).data('iddatadb') || null;
|
||||||
|
const idquotations = $(this).data('idquotations') || null;
|
||||||
|
const modal = document.getElementById('photosModal');
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
modal.style.display = 'block';
|
||||||
|
|
||||||
|
if (typeof window.loadPopupContent === 'function') {
|
||||||
|
window.loadPopupContent(iddatadb, idquotations);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close photos modal
|
||||||
|
$(document).on('click', '.close-btn', function () {
|
||||||
|
const modal = document.getElementById('photosModal');
|
||||||
|
if (modal) modal.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close on backdrop click
|
||||||
|
$(document).on('click', '#photosModal', function (e) {
|
||||||
|
if (e.target === this) {
|
||||||
|
this.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Parts (matching import_edit2.php behavior) ─────────────────────
|
||||||
|
$(document).on('click', '.parts-btn', function () {
|
||||||
|
const iddatadb = $(this).data('iddatadb') || null;
|
||||||
|
const idquotations = $(this).data('idquotations') || null;
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: 'modal_partsTable.php',
|
||||||
|
method: 'GET',
|
||||||
|
data: { iddatadb: iddatadb },
|
||||||
|
success: function (response) {
|
||||||
|
$('#partsModalContainer').html(response);
|
||||||
|
const modalElement = document.getElementById('partsModal');
|
||||||
|
if (!modalElement) return;
|
||||||
|
|
||||||
|
$("#trfHeader").text(iddatadb || idquotations || '');
|
||||||
|
$("#partsModal").data("iddatadb", iddatadb).data("idquotations", idquotations);
|
||||||
|
|
||||||
|
let modal = bootstrap.Modal.getInstance(modalElement);
|
||||||
|
if (!modal) modal = new bootstrap.Modal(modalElement, { backdrop: true, keyboard: true, focus: true });
|
||||||
|
modal.show();
|
||||||
|
|
||||||
|
if (typeof window.loadParts === 'function') {
|
||||||
|
window.loadParts(iddatadb, idquotations);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (xhr, status, error) {
|
||||||
|
console.error('Error loading parts:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('hidden.bs.modal', '#partsModal', function () {
|
||||||
|
const modalElement = document.getElementById('partsModal');
|
||||||
|
if (modalElement) {
|
||||||
|
const modal = bootstrap.Modal.getInstance(modalElement);
|
||||||
|
if (modal) modal.dispose();
|
||||||
|
}
|
||||||
|
$('#partsModalContainer').empty();
|
||||||
|
$('.modal-backdrop').remove();
|
||||||
|
$('body').removeClass('modal-open').css('padding-right', '');
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Tested Component quick add ───────────────────────────────────────
|
||||||
|
$(document).on('click', '.add-part-btn', async function () {
|
||||||
|
const iddatadb = $(this).data('iddatadb') || null;
|
||||||
|
const rowIndex = parseInt($(this).data('row'));
|
||||||
|
const row = window.gridData?.[rowIndex];
|
||||||
|
const id = iddatadb || (row ? row.iddatadb : null);
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
const $cell = $(this).closest('.grid-cell, div');
|
||||||
|
const $input = $cell.find('input');
|
||||||
|
const raw = ($input.val() || '').trim();
|
||||||
|
const parts = raw.split('|').map(s => s.trim()).filter(s => s.length > 0);
|
||||||
|
const uniqueParts = [...new Set(parts)];
|
||||||
|
|
||||||
|
if (!uniqueParts.length) {
|
||||||
|
alert('Insert a description first.');
|
||||||
|
$input.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const p of uniqueParts) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('iddatadb', id);
|
||||||
|
formData.append('part_description', p);
|
||||||
|
await fetch('add_part_quick.php', { method: 'POST', body: formData });
|
||||||
|
}
|
||||||
|
alert(`Added ${uniqueParts.length} part(s).`);
|
||||||
|
$input.val('');
|
||||||
|
} catch (e) {
|
||||||
|
alert('Error: ' + e.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Delete row ───────────────────────────────────────────────────────
|
||||||
|
let deleteIddatadb = null;
|
||||||
|
let deleteRowIndex = null;
|
||||||
|
|
||||||
|
$(document).on('click', '.delete-btn', function () {
|
||||||
|
deleteIddatadb = $(this).data('iddatadb');
|
||||||
|
deleteRowIndex = parseInt($(this).data('row'));
|
||||||
|
const modalEl = document.getElementById('deleteConfirmModal');
|
||||||
|
if (modalEl) {
|
||||||
|
document.getElementById('deleteIddatadbText').textContent = deleteIddatadb;
|
||||||
|
new bootstrap.Modal(modalEl).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '#deleteConfirmBtn', async function () {
|
||||||
|
const modalEl = document.getElementById('deleteConfirmModal');
|
||||||
|
const modal = bootstrap.Modal.getInstance(modalEl);
|
||||||
|
if (modal) modal.hide();
|
||||||
|
|
||||||
|
if (!deleteIddatadb) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await fetch('delete_record.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ id: deleteIddatadb })
|
||||||
|
});
|
||||||
|
const result = await resp.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
// Remove from gridData
|
||||||
|
const idx = window.gridData.findIndex(r => r.iddatadb === deleteIddatadb);
|
||||||
|
if (idx >= 0) window.gridData.splice(idx, 1);
|
||||||
|
|
||||||
|
// Re-render
|
||||||
|
const gr = window.gridRenderer;
|
||||||
|
if (gr) gr.renderVisibleRows();
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + result.message);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
alert('Error: ' + e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteIddatadb = null;
|
||||||
|
deleteRowIndex = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Add new row ──────────────────────────────────────────────────────
|
||||||
|
$(document).on('click', '#addRowBtn', async function () {
|
||||||
|
const btn = this;
|
||||||
|
btn.disabled = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const templateId = window.gridMeta?.templateId;
|
||||||
|
if (!templateId) { alert('Template ID missing'); return; }
|
||||||
|
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const importref = urlParams.get('importref') || '';
|
||||||
|
const resp = await fetch('add_record.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ template_id: templateId, importreferencecode: importref })
|
||||||
|
});
|
||||||
|
const result = await resp.json();
|
||||||
|
|
||||||
|
if (result.success && result.iddatadb) {
|
||||||
|
// Build new row object
|
||||||
|
const newRow = {
|
||||||
|
iddatadb: result.iddatadb,
|
||||||
|
status: 'i',
|
||||||
|
idclient: window.gridMeta?.defaultIdclient || '',
|
||||||
|
cliente_fornitore_id: null,
|
||||||
|
commessaweb: null,
|
||||||
|
user_name: result.user_name || '',
|
||||||
|
importreferencecode: result.importreferencecode || '',
|
||||||
|
filename_import: '',
|
||||||
|
importdate: new Date().toISOString().slice(0, 19).replace('T', ' '),
|
||||||
|
fixedFields: {},
|
||||||
|
details: {},
|
||||||
|
mainFieldValue: '',
|
||||||
|
_dirty: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add to beginning of gridData
|
||||||
|
window.gridData.unshift(newRow);
|
||||||
|
|
||||||
|
// Re-render
|
||||||
|
const gr = window.gridRenderer;
|
||||||
|
if (gr) gr.renderVisibleRows();
|
||||||
|
|
||||||
|
// Highlight new row briefly
|
||||||
|
const newGridRow = document.querySelector(`.grid-row[data-id="${result.iddatadb}"]`);
|
||||||
|
if (newGridRow) {
|
||||||
|
newGridRow.classList.add('row-just-created');
|
||||||
|
newGridRow.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
|
setTimeout(() => newGridRow.classList.remove('row-just-created'), 4000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + (result.message || 'Unknown error'));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
alert('Error: ' + e.message);
|
||||||
|
} finally {
|
||||||
|
btn.disabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
||||||
@@ -143,7 +143,7 @@ $(document).ready(function () {
|
|||||||
if (iddatadb) {
|
if (iddatadb) {
|
||||||
if (matrici.length === 0) {
|
if (matrici.length === 0) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "get_matrici_db.php",
|
url: "get_matrici.php",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
|
|||||||
+372
-168
@@ -7,6 +7,8 @@ $(document).ready(function () {
|
|||||||
let matrici = [];
|
let matrici = [];
|
||||||
let macroMatrici = [];
|
let macroMatrici = [];
|
||||||
let quotations = [];
|
let quotations = [];
|
||||||
|
let partsExtraField = null; // {field_id, field_label} oppure null
|
||||||
|
let extraFieldOptions = []; // [{id,label}]
|
||||||
|
|
||||||
// --- ROW ID helpers: niente più cache impazzita di jQuery .data() ---
|
// --- ROW ID helpers: niente più cache impazzita di jQuery .data() ---
|
||||||
function getPartId($row) {
|
function getPartId($row) {
|
||||||
@@ -29,6 +31,202 @@ $(document).ready(function () {
|
|||||||
return id === "new" || id === "" || id === null ? null : id;
|
return id === "new" || id === "" || id === null ? null : id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadPartsExtraField(iddatadb, done) {
|
||||||
|
partsExtraField = null;
|
||||||
|
|
||||||
|
if (!iddatadb) return done();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "get_parts_extra_field.php",
|
||||||
|
method: "GET",
|
||||||
|
dataType: "json",
|
||||||
|
data: { iddatadb },
|
||||||
|
success: function (res) {
|
||||||
|
partsExtraField =
|
||||||
|
res && res.success && res.field ? res.field : null;
|
||||||
|
|
||||||
|
if (
|
||||||
|
partsExtraField &&
|
||||||
|
(partsExtraField.data_type || "").toLowerCase() ===
|
||||||
|
"sceltamultipla"
|
||||||
|
) {
|
||||||
|
loadExtraFieldOptions(
|
||||||
|
String(partsExtraField.field_id),
|
||||||
|
function () {
|
||||||
|
applyExtraFieldColumn();
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
applyExtraFieldColumn();
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
partsExtraField = null;
|
||||||
|
applyExtraFieldColumn();
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadExtraFieldOptions(fieldId, done) {
|
||||||
|
extraFieldOptions = [];
|
||||||
|
if (!fieldId) return done();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "get_customfield_values.php",
|
||||||
|
method: "GET",
|
||||||
|
dataType: "json",
|
||||||
|
data: { field_ids: fieldId },
|
||||||
|
success: function (res) {
|
||||||
|
const arr = res && res[fieldId] ? res[fieldId] : [];
|
||||||
|
|
||||||
|
extraFieldOptions = (arr || [])
|
||||||
|
.map((x) => ({
|
||||||
|
id: x.IdCustomFieldsValue,
|
||||||
|
label: x.Valore || "",
|
||||||
|
}))
|
||||||
|
.sort((a, b) =>
|
||||||
|
a.label.localeCompare(b.label, "it", {
|
||||||
|
sensitivity: "base",
|
||||||
|
numeric: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
extraFieldOptions = [];
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildExtraFieldCellHtml($row = null) {
|
||||||
|
if (!partsExtraField) return "";
|
||||||
|
|
||||||
|
const selectedValueId = $row ? $row.data("extra-value-id") || "" : "";
|
||||||
|
|
||||||
|
// SceltaMultipla -> select + hidden value id
|
||||||
|
if (
|
||||||
|
(partsExtraField.data_type || "").toLowerCase() === "sceltamultipla"
|
||||||
|
) {
|
||||||
|
const opts = [`<option value="">Select…</option>`]
|
||||||
|
.concat(
|
||||||
|
extraFieldOptions.map(
|
||||||
|
(o) => `<option value="${o.id}">${o.label}</option>`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
return `
|
||||||
|
<td class="extra-field-td" style="width:200px;">
|
||||||
|
<input type="hidden" class="part-extra-field-id" value="${partsExtraField.field_id}">
|
||||||
|
<input type="hidden" class="part-extra-value-id" value="">
|
||||||
|
<select class="form-control form-control-sm part-extra-select" data-selected="${selectedValueId || ""}">${opts}</select>
|
||||||
|
</td>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Testo -> input + hidden field_id
|
||||||
|
return `
|
||||||
|
<td class="extra-field-td" style="width:200px;">
|
||||||
|
<input type="hidden" class="part-extra-field-id" value="${partsExtraField.field_id}">
|
||||||
|
<input type="text" class="form-control form-control-sm part-extra-field" data-type="${partsExtraField.data_type || ""}">
|
||||||
|
</td>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializeExtraFieldSelect2($context) {
|
||||||
|
if (typeof $.fn.select2 === "undefined") return;
|
||||||
|
|
||||||
|
$context.find(".part-extra-select").each(function () {
|
||||||
|
const $select = $(this);
|
||||||
|
|
||||||
|
if ($select.hasClass("select2-hidden-accessible")) {
|
||||||
|
$select.select2("destroy");
|
||||||
|
}
|
||||||
|
|
||||||
|
$select.select2({
|
||||||
|
placeholder: "Seleziona...",
|
||||||
|
allowClear: true,
|
||||||
|
width: "100%",
|
||||||
|
dropdownParent: $("#partsModal"),
|
||||||
|
minimumResultsForSearch: 0,
|
||||||
|
sorter: function (data) {
|
||||||
|
return data.sort(function (a, b) {
|
||||||
|
const textA = (a.text || "").toLowerCase();
|
||||||
|
const textB = (b.text || "").toLowerCase();
|
||||||
|
return textA.localeCompare(textB, "it", {
|
||||||
|
sensitivity: "base",
|
||||||
|
numeric: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
matcher: function (params, data) {
|
||||||
|
if ($.trim(params.term) === "") return data;
|
||||||
|
if (typeof data.text === "undefined") return null;
|
||||||
|
|
||||||
|
const term = params.term.toLowerCase();
|
||||||
|
const text = data.text.toLowerCase();
|
||||||
|
|
||||||
|
return text.indexOf(term) > -1 ? data : null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const selected = ($select.attr("data-selected") || "").toString();
|
||||||
|
if (selected) {
|
||||||
|
$select.val(selected).trigger("change.select2");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).on("change", ".part-extra-select", function () {
|
||||||
|
const valId = $(this).val() || "";
|
||||||
|
const $row = $(this).closest("tr");
|
||||||
|
|
||||||
|
// salva nello stato della riga (così sopravvive ai re-render)
|
||||||
|
$row.data("extra-value-id", valId);
|
||||||
|
|
||||||
|
$(this).closest("td").find(".part-extra-value-id").val(valId);
|
||||||
|
saveRow($row);
|
||||||
|
});
|
||||||
|
|
||||||
|
function applyExtraFieldColumn() {
|
||||||
|
const $theadRow = $("#partsTable thead tr");
|
||||||
|
|
||||||
|
// 1) rimuovi header extra
|
||||||
|
$theadRow.find("th.extra-field-th").remove();
|
||||||
|
|
||||||
|
// 2) rimuovi celle extra esistenti
|
||||||
|
$("#partsTableBody tr").each(function () {
|
||||||
|
$(this).find("td.extra-field-td").remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3) se non c'è campo extra -> fine
|
||||||
|
if (!partsExtraField) return;
|
||||||
|
|
||||||
|
// 4) inserisci header prima di "Azioni" (ultima colonna)
|
||||||
|
$theadRow
|
||||||
|
.find("th:last")
|
||||||
|
.before(
|
||||||
|
`<th class="extra-field-th" style="width:200px;">${partsExtraField.field_label}</th>`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 5) aggiungi cella a ogni riga già presente (una sola volta)
|
||||||
|
$("#partsTableBody tr").each(function () {
|
||||||
|
const $row = $(this);
|
||||||
|
$row.find("td:last").before(buildExtraFieldCellHtml($row));
|
||||||
|
|
||||||
|
const selected = ($row.data("extra-value-id") || "").toString();
|
||||||
|
if (selected) {
|
||||||
|
$row.find(".part-extra-value-id").val(selected);
|
||||||
|
$row.find(".part-extra-select").attr("data-selected", selected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
initializeExtraFieldSelect2($("#partsTableBody"));
|
||||||
|
}
|
||||||
|
|
||||||
function setPartId($row, id) {
|
function setPartId($row, id) {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
// Sincronizza TUTTO: attributo + cache jQuery + nostra cache
|
// Sincronizza TUTTO: attributo + cache jQuery + nostra cache
|
||||||
@@ -144,48 +342,17 @@ $(document).ready(function () {
|
|||||||
// ===================
|
// ===================
|
||||||
function loadParts(iddatadb, idquotations, callback = null) {
|
function loadParts(iddatadb, idquotations, callback = null) {
|
||||||
if (iddatadb) {
|
if (iddatadb) {
|
||||||
if (matrici.length === 0) {
|
|
||||||
$.ajax({
|
|
||||||
url: "get_matrici_db.php",
|
|
||||||
method: "GET",
|
|
||||||
dataType: "json",
|
|
||||||
success: function (data) {
|
|
||||||
matrici = data.value || [];
|
|
||||||
loadMacroMatrici();
|
loadMacroMatrici();
|
||||||
initializeGlobalSelect2();
|
initializeGlobalSelect2();
|
||||||
|
loadPartsExtraField(iddatadb, function () {
|
||||||
loadPhoto(iddatadb, idquotations);
|
loadPhoto(iddatadb, idquotations);
|
||||||
loadExistingParts(iddatadb, idquotations, callback);
|
loadExistingParts(iddatadb, idquotations, callback);
|
||||||
},
|
|
||||||
error: function (xhr, status, error) {
|
|
||||||
matrici = [];
|
|
||||||
loadMacroMatrici();
|
|
||||||
initializeGlobalSelect2();
|
|
||||||
loadPhoto(iddatadb, idquotations);
|
|
||||||
loadExistingParts(iddatadb, idquotations, callback);
|
|
||||||
const errorMsg = $(
|
|
||||||
'<div class="alert alert-danger temp-alert" role="alert">Errore nel caricamento delle matrici: ' +
|
|
||||||
error +
|
|
||||||
" (" +
|
|
||||||
xhr.status +
|
|
||||||
")</div>",
|
|
||||||
);
|
|
||||||
$("#partsModal .modal-body").prepend(errorMsg);
|
|
||||||
setTimeout(function () {
|
|
||||||
errorMsg.fadeOut(500, function () {
|
|
||||||
$(this).remove();
|
|
||||||
});
|
|
||||||
}, 5000);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
loadMacroMatrici();
|
loadPartsExtraField(iddatadb, function () {
|
||||||
initializeGlobalSelect2();
|
|
||||||
loadPhoto(iddatadb, idquotations);
|
|
||||||
loadExistingParts(iddatadb, idquotations, callback);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
loadPhoto(iddatadb, idquotations);
|
loadPhoto(iddatadb, idquotations);
|
||||||
loadExistingParts(iddatadb, idquotations, callback);
|
loadExistingParts(iddatadb, idquotations, callback);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,6 +681,11 @@ $(document).ready(function () {
|
|||||||
if (response.new_id) return response.new_id;
|
if (response.new_id) return response.new_id;
|
||||||
if (response.partId) return response.partId;
|
if (response.partId) return response.partId;
|
||||||
|
|
||||||
|
// ✅ RISPOSTA DI save_parts.php: { success:true, results:[{part_id:...}] }
|
||||||
|
if (Array.isArray(response.results) && response.results[0]) {
|
||||||
|
if (response.results[0].part_id) return response.results[0].part_id;
|
||||||
|
if (response.results[0].id) return response.results[0].id;
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,6 +699,13 @@ $(document).ready(function () {
|
|||||||
const iddatadb = $("#partsModal").data("iddatadb");
|
const iddatadb = $("#partsModal").data("iddatadb");
|
||||||
const idquotations = $("#partsModal").data("idquotations");
|
const idquotations = $("#partsModal").data("idquotations");
|
||||||
const isMix = partDescription.startsWith("Mix") ? "Y" : "N";
|
const isMix = partDescription.startsWith("Mix") ? "Y" : "N";
|
||||||
|
|
||||||
|
// EXTRA FIELD (0/1)
|
||||||
|
const extra_field_id = $row.find(".part-extra-field-id").val() || null;
|
||||||
|
const extra_value_id = $row.find(".part-extra-value-id").val() || null;
|
||||||
|
const extra_value_text = $row.find(".part-extra-field").length
|
||||||
|
? ($row.find(".part-extra-field").val() || "").trim()
|
||||||
|
: null;
|
||||||
let partId = getPartId($row);
|
let partId = getPartId($row);
|
||||||
if (partId === "new") partId = null; // difesa extra (non dovrebbe più servire, ma sicura)
|
if (partId === "new") partId = null; // difesa extra (non dovrebbe più servire, ma sicura)
|
||||||
const endpoint = idquotations
|
const endpoint = idquotations
|
||||||
@@ -559,8 +738,14 @@ $(document).ready(function () {
|
|||||||
part_number: partNumber,
|
part_number: partNumber,
|
||||||
part_description: partDescription,
|
part_description: partDescription,
|
||||||
mix: isMix,
|
mix: isMix,
|
||||||
|
idmatrice: $row.find(".part-matrice").val() || null,
|
||||||
dateexpiry: dateexpiry || null,
|
dateexpiry: dateexpiry || null,
|
||||||
note: note,
|
note: note,
|
||||||
|
|
||||||
|
// extra custom field
|
||||||
|
extra_field_id: extra_field_id,
|
||||||
|
extra_value_id: extra_value_id,
|
||||||
|
extra_value_text: extra_value_text,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@@ -591,7 +776,8 @@ $(document).ready(function () {
|
|||||||
$saveStatus.show();
|
$saveStatus.show();
|
||||||
setTimeout(() => $saveStatus.hide(), 2000);
|
setTimeout(() => $saveStatus.hide(), 2000);
|
||||||
|
|
||||||
if (!$("#quotationeBtn").hasClass('d-none')) $("#quotationeBtn").addClass("d-none");
|
if (!$("#quotationeBtn").hasClass("d-none"))
|
||||||
|
$("#quotationeBtn").addClass("d-none");
|
||||||
} else {
|
} else {
|
||||||
const errorMsg = $(
|
const errorMsg = $(
|
||||||
'<div class="alert alert-danger temp-alert" role="alert">Errore nel salvataggio: ' +
|
'<div class="alert alert-danger temp-alert" role="alert">Errore nel salvataggio: ' +
|
||||||
@@ -716,23 +902,28 @@ $(document).ready(function () {
|
|||||||
<td><input type="text" class="form-control form-control-sm part-description" value="${description}" placeholder="Inserisci descrizione"></td>
|
<td><input type="text" class="form-control form-control-sm part-description" value="${description}" placeholder="Inserisci descrizione"></td>
|
||||||
<td>
|
<td>
|
||||||
<div style="display: flex; align-items: center;">
|
<div style="display: flex; align-items: center;">
|
||||||
<button type="button" class="btn btn-primary btn-sm propagate-matrice-btn" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; margin-right: 3px;"><i class="fas fa-arrow-right fa-xs"></i></button>
|
<button type="button" class="btn btn-primary btn-sm propagate-matrice-btn" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; margin-right: 3px;"><i class="fas fa-arrow-right"></i></button>
|
||||||
<select class="part-matrice form-control form-control-sm" style="width: 150px;"></select>
|
<select class="part-matrice form-control form-control-sm" style="width: 150px;"></select>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td><input type="date" class="form-control form-control-sm part-dateexpiry" style="width: 130px;"></td>
|
<td><input type="date" class="form-control form-control-sm part-dateexpiry" style="width: 130px;"></td>
|
||||||
|
${partsExtraField ? buildExtraFieldCellHtml(null) : ``}
|
||||||
<td>
|
<td>
|
||||||
<button type="button" class="btn btn-light btn-sm note-btn" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;" title="Aggiungi/Modifica nota"><i class="fas fa-file-alt"></i></button>
|
<button type="button" class="btn btn-light btn-sm note-btn" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;" title="Aggiungi/Modifica nota"><i class="fas fa-file-alt"></i></button>
|
||||||
<button type="button" class="btn btn-warning btn-sm add-mix-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;">M+</button>
|
<button type="button" class="btn btn-warning btn-sm add-mix-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;">M+</button>
|
||||||
<button type="button" class="btn btn-danger btn-sm remove-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; display: none;"><i class="fas fa-trash fa-xs"></i></button>
|
<button type="button" class="btn btn-danger btn-sm remove-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; display: none;"><i class="fas fa-trash"></i></button>
|
||||||
<span class="save-status text-success" style="display: none; margin-left: 5px;"><i class="fas fa-check fa-xs"></i></span>
|
<span class="save-status text-success" style="display: none; margin-left: 5px;"><i class="fas fa-check"></i></span>
|
||||||
<span class="save-loading text-warning" style="display: none; margin-left: 5px;"><i class="fas fa-spinner fa-spin fa-xs"></i></span>
|
<span class="save-loading text-warning" style="display: none; margin-left: 5px;"><i class="fas fa-spinner fa-spin"></i></span>
|
||||||
</td>
|
</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
$("#partsTableBody").append(newRow);
|
$("#partsTableBody").append(newRow);
|
||||||
const $select = $("#partsTableBody tr:last .part-matrice");
|
const $newRow = $("#partsTableBody tr:last");
|
||||||
|
const $select = $newRow.find(".part-matrice");
|
||||||
const selectedMacro = $("#macro-matrice-filter").val() || "";
|
const selectedMacro = $("#macro-matrice-filter").val() || "";
|
||||||
|
|
||||||
initializeSelect2($select, nextPartNumber, "", null, selectedMacro);
|
initializeSelect2($select, nextPartNumber, "", null, selectedMacro);
|
||||||
|
initializeExtraFieldSelect2($newRow);
|
||||||
|
|
||||||
updateRowButtons();
|
updateRowButtons();
|
||||||
markUnsaved();
|
markUnsaved();
|
||||||
}
|
}
|
||||||
@@ -1154,9 +1345,13 @@ $(document).ready(function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on("blur", ".part-description, .part-number", function () {
|
$(document).on(
|
||||||
|
"blur",
|
||||||
|
".part-description, .part-number, .part-extra-field",
|
||||||
|
function () {
|
||||||
saveRow($(this).closest("tr"));
|
saveRow($(this).closest("tr"));
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
function loadExistingParts(iddatadb, idquotations, callback = null) {
|
function loadExistingParts(iddatadb, idquotations, callback = null) {
|
||||||
const endpoint = idquotations
|
const endpoint = idquotations
|
||||||
@@ -1204,20 +1399,41 @@ $(document).ready(function () {
|
|||||||
<td><input type="text" class="form-control form-control-sm part-description" value="${escapedDescription}" placeholder="Inserisci descrizione"></td>
|
<td><input type="text" class="form-control form-control-sm part-description" value="${escapedDescription}" placeholder="Inserisci descrizione"></td>
|
||||||
<td>
|
<td>
|
||||||
<div style="display: flex; align-items: center;">
|
<div style="display: flex; align-items: center;">
|
||||||
<button type="button" class="btn btn-primary btn-sm propagate-matrice-btn" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; margin-right: 3px;"><i class="fas fa-arrow-right fa-xs"></i></button>
|
<button type="button" class="btn btn-primary btn-sm propagate-matrice-btn" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; margin-right: 3px;"><i class="fas fa-arrow-right"></i></button>
|
||||||
<select class="part-matrice form-control form-control-sm" style="width: 150px;" ${idquotations && !part.idmatrice ? "disabled" : ""}></select>
|
<select class="part-matrice form-control form-control-sm" style="width: 150px;" ${idquotations && !part.idmatrice ? "disabled" : ""}></select>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td><input type="date" class="form-control form-control-sm part-dateexpiry" value="${part.dateexpiry || ""}" style="width: 130px;"></td>
|
<td><input type="date" class="form-control form-control-sm part-dateexpiry" value="${part.dateexpiry || ""}" style="width: 130px;"></td>
|
||||||
|
${partsExtraField ? buildExtraFieldCellHtml() : ``}
|
||||||
<td>
|
<td>
|
||||||
<button type="button" class="btn btn-light btn-sm note-btn ${part.note ? "has-note" : ""}" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;" title="Aggiungi/Modifica nota"><i class="fas fa-file-alt"></i></button>
|
<button type="button" class="btn btn-light btn-sm note-btn ${part.note ? "has-note" : ""}" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;" title="Aggiungi/Modifica nota"><i class="fas fa-file-alt"></i></button>
|
||||||
<button type="button" class="btn btn-warning btn-sm add-mix-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;">M+</button>
|
<button type="button" class="btn btn-warning btn-sm add-mix-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;">M+</button>
|
||||||
<button type="button" class="btn btn-danger btn-sm remove-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;"><i class="fas fa-trash fa-xs"></i></button>
|
<button type="button" class="btn btn-danger btn-sm remove-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;"><i class="fas fa-trash"></i></button>
|
||||||
<span class="save-status text-success" style="display: none; margin-left: 5px;"><i class="fas fa-check fa-xs"></i></span>
|
<span class="save-status text-success" style="display: none; margin-left: 5px;"><i class="fas fa-check"></i></span>
|
||||||
<span class="save-loading text-warning" style="display: none; margin-left: 5px;"><i class="fas fa-spinner fa-spin fa-xs"></i></span>
|
<span class="save-loading text-warning" style="display: none; margin-left: 5px;"><i class="fas fa-spinner fa-spin"></i></span>
|
||||||
</td>
|
</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
$("#partsTableBody").append(newRow);
|
$("#partsTableBody").append(newRow);
|
||||||
|
|
||||||
|
const $row = $(
|
||||||
|
`#partsTableBody tr[data-part-id="${part.id}"]`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
part.extra_value_id !== undefined &&
|
||||||
|
part.extra_value_id !== null
|
||||||
|
) {
|
||||||
|
const vid = String(part.extra_value_id);
|
||||||
|
$row.data("extra-value-id", vid);
|
||||||
|
$row.find(".part-extra-value-id").val(vid);
|
||||||
|
$row.find(".part-extra-select").attr(
|
||||||
|
"data-selected",
|
||||||
|
vid,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeExtraFieldSelect2($row);
|
||||||
|
|
||||||
const $select = $("#partsTableBody").find(
|
const $select = $("#partsTableBody").find(
|
||||||
`tr[data-part-id="${part.id}"] .part-matrice`,
|
`tr[data-part-id="${part.id}"] .part-matrice`,
|
||||||
);
|
);
|
||||||
@@ -1263,7 +1479,7 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
function loadMacroMatrici() {
|
function loadMacroMatrici() {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "get_macro_matrici.php",
|
url: "search_matrici.php?macro_list=1",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
@@ -1345,7 +1561,7 @@ $(document).ready(function () {
|
|||||||
partId,
|
partId,
|
||||||
currentValue,
|
currentValue,
|
||||||
selectedMacro,
|
selectedMacro,
|
||||||
true
|
true,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1360,18 +1576,6 @@ $(document).ready(function () {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtra le matrici in base alla MacroMatrice selezionata
|
|
||||||
const filteredMatrici = selectedMacro
|
|
||||||
? matrici.filter(
|
|
||||||
(matrice) => matrice.MacroMatrice === selectedMacro,
|
|
||||||
)
|
|
||||||
: matrici;
|
|
||||||
|
|
||||||
// Crea opzioni per Select2
|
|
||||||
const options = filteredMatrici.map(function (matrice) {
|
|
||||||
return { id: matrice.IdMatrice, text: matrice.NomeMatrice };
|
|
||||||
});
|
|
||||||
|
|
||||||
// Memorizza il valore corrente
|
// Memorizza il valore corrente
|
||||||
const currentValue = $select.val();
|
const currentValue = $select.val();
|
||||||
|
|
||||||
@@ -1380,39 +1584,39 @@ $(document).ready(function () {
|
|||||||
$select.select2("destroy");
|
$select.select2("destroy");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inizializza Select2
|
// Inizializza Select2 con AJAX
|
||||||
$select.empty().select2({
|
$select.empty().select2({
|
||||||
placeholder: filteredMatrici.length
|
placeholder: "Seleziona matrice globale",
|
||||||
? "Seleziona matrice globale"
|
|
||||||
: "Nessuna matrice disponibile",
|
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
data: options,
|
|
||||||
dropdownParent: $("#partsModal"),
|
dropdownParent: $("#partsModal"),
|
||||||
minimumResultsForSearch: 0, // Abilita ricerca senza limite minimo di caratteri
|
minimumInputLength: 0,
|
||||||
matcher: function (params, data) {
|
ajax: {
|
||||||
if (!params.term) return data; // Mostra tutte le opzioni se non c'è termine di ricerca
|
url: "search_matrici.php",
|
||||||
const term = params.term.toUpperCase();
|
dataType: "json",
|
||||||
if (data.text.toUpperCase().indexOf(term) >= 0) return data;
|
delay: 150,
|
||||||
return null;
|
data: function (params) {
|
||||||
|
return { q: params.term || "", limit: 20, macro: selectedMacro || "" };
|
||||||
|
},
|
||||||
|
processResults: function (data) {
|
||||||
|
return { results: data.results || [] };
|
||||||
|
},
|
||||||
|
cache: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ripristina il valore corrente se valido
|
// Ripristina il valore corrente
|
||||||
if (
|
if (currentValue) {
|
||||||
currentValue &&
|
$.ajax({
|
||||||
filteredMatrici.some((m) => m.IdMatrice == currentValue)
|
url: "search_matrici.php",
|
||||||
) {
|
data: { id: currentValue },
|
||||||
$select.val(currentValue).trigger("change");
|
dataType: "json",
|
||||||
} else if (filteredMatrici.length === 0) {
|
}).then(function (data) {
|
||||||
const errorMsg = $(
|
const item = (data.results || [])[0];
|
||||||
'<div class="alert alert-warning temp-alert" role="alert">Nessuna matrice disponibile per la MacroMatrice selezionata.</div>',
|
if (item) {
|
||||||
);
|
const option = new Option(item.text, item.id, true, true);
|
||||||
$("#partsModal .modal-body").prepend(errorMsg);
|
$select.append(option).trigger("change");
|
||||||
setTimeout(function () {
|
}
|
||||||
errorMsg.fadeOut(500, function () {
|
|
||||||
$(this).remove();
|
|
||||||
});
|
});
|
||||||
}, 5000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1422,7 +1626,7 @@ $(document).ready(function () {
|
|||||||
partId,
|
partId,
|
||||||
idmatrice,
|
idmatrice,
|
||||||
selectedMacro = null,
|
selectedMacro = null,
|
||||||
fromFilter = false
|
fromFilter = false,
|
||||||
) {
|
) {
|
||||||
if (typeof $.fn.select2 === "undefined") {
|
if (typeof $.fn.select2 === "undefined") {
|
||||||
$select.replaceWith(
|
$select.replaceWith(
|
||||||
@@ -1431,32 +1635,6 @@ $(document).ready(function () {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtra le matrici in base alla MacroMatrice selezionata
|
|
||||||
const filteredMatrici = selectedMacro
|
|
||||||
? matrici.filter((m) => m.MacroMatrice === selectedMacro)
|
|
||||||
: matrici;
|
|
||||||
|
|
||||||
// Crea opzioni per Select2, includendo il valore pre-selezionato se non è nel filtro
|
|
||||||
let options = filteredMatrici.map(function (matrice) {
|
|
||||||
return { id: matrice.IdMatrice, text: matrice.NomeMatrice };
|
|
||||||
});
|
|
||||||
|
|
||||||
// Se idmatrice esiste ed è fuori dal filtro, aggiungilo come opzione
|
|
||||||
if (
|
|
||||||
idmatrice &&
|
|
||||||
!filteredMatrici.some((m) => m.IdMatrice == idmatrice)
|
|
||||||
) {
|
|
||||||
const selectedMatrice = matrici.find(
|
|
||||||
(m) => m.IdMatrice == idmatrice,
|
|
||||||
);
|
|
||||||
if (selectedMatrice) {
|
|
||||||
options.push({
|
|
||||||
id: selectedMatrice.IdMatrice,
|
|
||||||
text: selectedMatrice.NomeMatrice,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Memorizza il valore corrente
|
// Memorizza il valore corrente
|
||||||
const currentValue = idmatrice || $select.val();
|
const currentValue = idmatrice || $select.val();
|
||||||
|
|
||||||
@@ -1465,44 +1643,44 @@ $(document).ready(function () {
|
|||||||
$select.select2("destroy");
|
$select.select2("destroy");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inizializza Select2
|
// Inizializza Select2 con AJAX
|
||||||
$select.empty().select2({
|
$select.empty().select2({
|
||||||
placeholder: filteredMatrici.length
|
placeholder: "Seleziona matrice",
|
||||||
? "Seleziona matrice"
|
|
||||||
: "Nessuna matrice disponibile",
|
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
data: options,
|
|
||||||
dropdownParent: $("#partsModal"),
|
dropdownParent: $("#partsModal"),
|
||||||
minimumResultsForSearch: 0, // Abilita ricerca senza limite minimo di caratteri
|
minimumInputLength: 0,
|
||||||
matcher: function (params, data) {
|
ajax: {
|
||||||
if (!params.term) return data; // Mostra tutte le opzioni se non c'è termine di ricerca
|
url: "search_matrici.php",
|
||||||
const term = params.term.toUpperCase();
|
dataType: "json",
|
||||||
if (data.text.toUpperCase().indexOf(term) >= 0) return data;
|
delay: 150,
|
||||||
return null;
|
data: function (params) {
|
||||||
|
return { q: params.term || "", limit: 20, macro: selectedMacro || "" };
|
||||||
|
},
|
||||||
|
processResults: function (data) {
|
||||||
|
return { results: data.results || [] };
|
||||||
|
},
|
||||||
|
cache: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ripristina il valore se valido
|
// Ripristina il valore se valido
|
||||||
if (partId && partId !== "new" && currentValue) {
|
if (partId && partId !== "new" && currentValue) {
|
||||||
const matrice = matrici.find((m) => m.IdMatrice == currentValue);
|
$.ajax({
|
||||||
if (matrice) {
|
url: "search_matrici.php",
|
||||||
const option = new Option(
|
data: { id: currentValue },
|
||||||
matrice.NomeMatrice,
|
dataType: "json",
|
||||||
matrice.IdMatrice,
|
}).then(function (data) {
|
||||||
true,
|
const item = (data.results || [])[0];
|
||||||
true,
|
if (item) {
|
||||||
);
|
const option = new Option(item.text, item.id, true, true);
|
||||||
|
|
||||||
if (!fromFilter) $select.append(option).trigger("change");
|
if (!fromFilter) $select.append(option).trigger("change");
|
||||||
else $select.append(option);
|
else $select.append(option);
|
||||||
|
partMatrice[partNumber] = item.id;
|
||||||
partMatrice[partNumber] = matrice.IdMatrice;
|
|
||||||
} else {
|
} else {
|
||||||
// Aggiusta valore non valido
|
|
||||||
if (!fromFilter) $select.val(null).trigger("change");
|
if (!fromFilter) $select.val(null).trigger("change");
|
||||||
|
|
||||||
partMatrice[partNumber] = null;
|
partMatrice[partNumber] = null;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
$select.val(null).trigger("change", [{ skipHandler: true }]);
|
$select.val(null).trigger("change", [{ skipHandler: true }]);
|
||||||
}
|
}
|
||||||
@@ -1579,18 +1757,7 @@ $(document).ready(function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mostra messaggio se non ci sono matrici
|
// Messaggio se macro selezionata ma nessun risultato sarà gestito dal placeholder Select2
|
||||||
if (filteredMatrici.length === 0 && selectedMacro) {
|
|
||||||
const errorMsg = $(
|
|
||||||
'<div class="alert alert-warning temp-alert" role="alert">Nessuna matrice disponibile per la MacroMatrice selezionata.</div>',
|
|
||||||
);
|
|
||||||
$("#partsModal .modal-body").prepend(errorMsg);
|
|
||||||
setTimeout(function () {
|
|
||||||
errorMsg.fadeOut(500, function () {
|
|
||||||
$(this).remove();
|
|
||||||
});
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug per verificare inizializzazione
|
// Debug per verificare inizializzazione
|
||||||
console.log(
|
console.log(
|
||||||
@@ -1606,8 +1773,13 @@ $(document).ready(function () {
|
|||||||
$(document).on("click", ".propagate-matrice-btn", function () {
|
$(document).on("click", ".propagate-matrice-btn", function () {
|
||||||
const $row = $(this).closest("tr");
|
const $row = $(this).closest("tr");
|
||||||
const globalVal = $("#global-matrice").val();
|
const globalVal = $("#global-matrice").val();
|
||||||
|
const globalText = $("#global-matrice").find("option:selected").text();
|
||||||
if (globalVal) {
|
if (globalVal) {
|
||||||
$row.find(".part-matrice").val(globalVal).trigger("change");
|
const $target = $row.find(".part-matrice");
|
||||||
|
if (!$target.find(`option[value="${globalVal}"]`).length) {
|
||||||
|
$target.append(new Option(globalText, globalVal, true, true));
|
||||||
|
}
|
||||||
|
$target.val(globalVal).trigger("change");
|
||||||
} else {
|
} else {
|
||||||
const errorMsg = $(
|
const errorMsg = $(
|
||||||
'<div class="alert alert-danger temp-alert" role="alert">Seleziona una matrice globale prima di propagare.</div>',
|
'<div class="alert alert-danger temp-alert" role="alert">Seleziona una matrice globale prima di propagare.</div>',
|
||||||
@@ -1623,8 +1795,15 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
$(document).on("click", ".propagate-all-btn", function () {
|
$(document).on("click", ".propagate-all-btn", function () {
|
||||||
const globalVal = $("#global-matrice").val();
|
const globalVal = $("#global-matrice").val();
|
||||||
|
const globalText = $("#global-matrice").find("option:selected").text();
|
||||||
if (globalVal) {
|
if (globalVal) {
|
||||||
$("#partsTableBody .part-matrice").val(globalVal).trigger("change");
|
$("#partsTableBody .part-matrice").each(function () {
|
||||||
|
const $target = $(this);
|
||||||
|
if (!$target.find(`option[value="${globalVal}"]`).length) {
|
||||||
|
$target.append(new Option(globalText, globalVal, true, true));
|
||||||
|
}
|
||||||
|
$target.val(globalVal).trigger("change");
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const errorMsg = $(
|
const errorMsg = $(
|
||||||
'<div class="alert alert-danger temp-alert" role="alert">Seleziona una matrice globale prima di propagare.</div>',
|
'<div class="alert alert-danger temp-alert" role="alert">Seleziona una matrice globale prima di propagare.</div>',
|
||||||
@@ -1809,7 +1988,11 @@ $(document).ready(function () {
|
|||||||
reloadQuotations();
|
reloadQuotations();
|
||||||
},
|
},
|
||||||
error: function (xhr, status, error) {
|
error: function (xhr, status, error) {
|
||||||
console.error("Errore AJAX caricamento quotations:", error, xhr.responseText);
|
console.error(
|
||||||
|
"Errore AJAX caricamento quotations:",
|
||||||
|
error,
|
||||||
|
xhr.responseText,
|
||||||
|
);
|
||||||
let message = `
|
let message = `
|
||||||
<div class="alert alert-danger temp-alert" role="alert">
|
<div class="alert alert-danger temp-alert" role="alert">
|
||||||
Errore nel caricamento delle quotations: ${error} (${xhr.status})
|
Errore nel caricamento delle quotations: ${error} (${xhr.status})
|
||||||
@@ -1823,7 +2006,7 @@ $(document).ready(function () {
|
|||||||
$(this).remove();
|
$(this).remove();
|
||||||
});
|
});
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1831,7 +2014,10 @@ $(document).ready(function () {
|
|||||||
$(document).on("click", "#addQuotationBtn", function () {
|
$(document).on("click", "#addQuotationBtn", function () {
|
||||||
const quotationId = $("#addQuotationSelect").val();
|
const quotationId = $("#addQuotationSelect").val();
|
||||||
|
|
||||||
if (quotationId && confirm("Confermo di collegare la quotazione al campione?")) {
|
if (
|
||||||
|
quotationId &&
|
||||||
|
confirm("Confermo di collegare la quotazione al campione?")
|
||||||
|
) {
|
||||||
$("#addQuotationModal").modal("hide");
|
$("#addQuotationModal").modal("hide");
|
||||||
|
|
||||||
loadParts(null, quotationId, () => {
|
loadParts(null, quotationId, () => {
|
||||||
@@ -1842,12 +2028,17 @@ $(document).ready(function () {
|
|||||||
$(this).find(".save-loading").show();
|
$(this).find(".save-loading").show();
|
||||||
});
|
});
|
||||||
|
|
||||||
let photoList = $('#photoSelector option').map(function () {
|
let photoList = $("#photoSelector option")
|
||||||
return this.value?.split('/')?.pop();
|
.map(function () {
|
||||||
}).get();
|
return this.value?.split("/")?.pop();
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
if (!photoList.length) {
|
if (!photoList.length) {
|
||||||
let path = $('#samplePhoto').attr("src")?.split('/')?.pop();
|
let path = $("#samplePhoto")
|
||||||
|
.attr("src")
|
||||||
|
?.split("/")
|
||||||
|
?.pop();
|
||||||
|
|
||||||
if (path) photoList = [path];
|
if (path) photoList = [path];
|
||||||
}
|
}
|
||||||
@@ -1858,9 +2049,11 @@ $(document).ready(function () {
|
|||||||
data: JSON.stringify({
|
data: JSON.stringify({
|
||||||
iddatadb: $("#partsModal").data("iddatadb"),
|
iddatadb: $("#partsModal").data("iddatadb"),
|
||||||
photoList,
|
photoList,
|
||||||
partIds: $('#partsTableBody tr').map(function () {
|
partIds: $("#partsTableBody tr")
|
||||||
|
.map(function () {
|
||||||
return $(this).data("part-id");
|
return $(this).data("part-id");
|
||||||
}).get(),
|
})
|
||||||
|
.get(),
|
||||||
}),
|
}),
|
||||||
success: function () {
|
success: function () {
|
||||||
$("#partsTableBody tr").each(function () {
|
$("#partsTableBody tr").each(function () {
|
||||||
@@ -1873,7 +2066,8 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
setTimeout(() => $saveStatus.hide(), 2000);
|
setTimeout(() => $saveStatus.hide(), 2000);
|
||||||
});
|
});
|
||||||
}, error: function (xhr, status, error) {
|
},
|
||||||
|
error: function (xhr, status, error) {
|
||||||
let message = `
|
let message = `
|
||||||
<div class="alert alert-danger temp-alert" role="alert">
|
<div class="alert alert-danger temp-alert" role="alert">
|
||||||
Errore di salvataggio: ${error} (${xhr.status})
|
Errore di salvataggio: ${error} (${xhr.status})
|
||||||
@@ -1887,7 +2081,7 @@ $(document).ready(function () {
|
|||||||
$(this).remove();
|
$(this).remove();
|
||||||
});
|
});
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
@@ -1898,11 +2092,15 @@ $(document).ready(function () {
|
|||||||
if (quotations.length > 0) {
|
if (quotations.length > 0) {
|
||||||
$("#addQuotationSelect").empty();
|
$("#addQuotationSelect").empty();
|
||||||
|
|
||||||
$("#addQuotationSelect").append("<option value=''>Seleziona Quotation</option>");
|
$("#addQuotationSelect").append(
|
||||||
|
"<option value=''>Seleziona Quotation</option>",
|
||||||
|
);
|
||||||
|
|
||||||
quotations.forEach(quotation => {
|
quotations.forEach((quotation) => {
|
||||||
if (quotation?.description) {
|
if (quotation?.description) {
|
||||||
$("#addQuotationSelect").append(`<option value='${quotation?.id}'>${quotation?.description}</option>`);
|
$("#addQuotationSelect").append(
|
||||||
|
`<option value='${quotation?.id}'>${quotation?.description}</option>`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2136,15 +2334,21 @@ $(document).on("click", ".save-common-note-btn", function () {
|
|||||||
$(document).on("click", "#showHideImageBtn", function () {
|
$(document).on("click", "#showHideImageBtn", function () {
|
||||||
let mainRow = $(this).closest(".parts-row");
|
let mainRow = $(this).closest(".parts-row");
|
||||||
let photoContainer = mainRow.find(".col-md-3");
|
let photoContainer = mainRow.find(".col-md-3");
|
||||||
let tableContainer = mainRow.find("#partsTable").closest("div[class*='col-md']");
|
let tableContainer = mainRow
|
||||||
|
.find("#partsTable")
|
||||||
|
.closest("div[class*='col-md']");
|
||||||
|
|
||||||
if (photoContainer.hasClass("d-none")) {
|
if (photoContainer.hasClass("d-none")) {
|
||||||
photoContainer.removeClass("d-none");
|
photoContainer.removeClass("d-none");
|
||||||
tableContainer.removeClass("col-md-12").addClass("col-md-9");
|
tableContainer.removeClass("col-md-12").addClass("col-md-9");
|
||||||
$(this).html("<i class='fas fa-eye-slash' style='font-size: 0.8rem;'></i><i class='fas fa-image ms-1' style='font-size: 0.8rem;'></i>");
|
$(this).html(
|
||||||
|
"<i class='fas fa-eye-slash' style='font-size: 0.8rem;'></i><i class='fas fa-image ms-1' style='font-size: 0.8rem;'></i>",
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
photoContainer.addClass("d-none");
|
photoContainer.addClass("d-none");
|
||||||
tableContainer.removeClass("col-md-9").addClass("col-md-12");
|
tableContainer.removeClass("col-md-9").addClass("col-md-12");
|
||||||
$(this).html("<i class='fas fa-eye' style='font-size: 0.8rem;'></i><i class='fas fa-image ms-1' style='font-size: 0.8rem;'></i>");
|
$(this).html(
|
||||||
|
"<i class='fas fa-eye' style='font-size: 0.8rem;'></i><i class='fas fa-image ms-1' style='font-size: 0.8rem;'></i>",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
+165
-4
@@ -1,5 +1,5 @@
|
|||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
// Funzione per caricare il contenuto del popup
|
// Funzione per caricare il contenuto del popup (exported for external use)
|
||||||
async function loadPopupContent(iddatadb, idquotations) {
|
async function loadPopupContent(iddatadb, idquotations) {
|
||||||
const popupContent = document.getElementById("popupContent");
|
const popupContent = document.getElementById("popupContent");
|
||||||
if (!popupContent) {
|
if (!popupContent) {
|
||||||
@@ -349,6 +349,69 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.querySelectorAll(".photo-flag-checkbox")
|
||||||
|
.forEach((checkbox) => {
|
||||||
|
checkbox.addEventListener("change", async function () {
|
||||||
|
const photoId = this.getAttribute("data-photo-id");
|
||||||
|
const field = this.getAttribute("data-field");
|
||||||
|
const value = this.checked ? 1 : 0;
|
||||||
|
const currentCheckbox = this;
|
||||||
|
|
||||||
|
// Only one PrimaPagina visually in the current popup
|
||||||
|
if (field === "PrimaPagina" && value === 1) {
|
||||||
|
document
|
||||||
|
.querySelectorAll(
|
||||||
|
".photo-flag-checkbox[data-field='PrimaPagina']",
|
||||||
|
)
|
||||||
|
.forEach((cb) => {
|
||||||
|
if (cb !== currentCheckbox) {
|
||||||
|
cb.checked = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const formData = new URLSearchParams();
|
||||||
|
formData.append("photo_id", photoId);
|
||||||
|
formData.append("field", field);
|
||||||
|
formData.append("value", value);
|
||||||
|
|
||||||
|
const response = await fetch("update_photo_flags.php", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type":
|
||||||
|
"application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
body: formData.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(
|
||||||
|
result.message || "Errore salvataggio flag",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safety refresh for PrimaPagina to ensure UI matches DB
|
||||||
|
if (field === "PrimaPagina") {
|
||||||
|
loadPopupContent(iddatadb, idquotations);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert(
|
||||||
|
"Errore durante il salvataggio del flag: " +
|
||||||
|
error.message,
|
||||||
|
);
|
||||||
|
|
||||||
|
// rollback current checkbox
|
||||||
|
currentCheckbox.checked = !currentCheckbox.checked;
|
||||||
|
|
||||||
|
// reload popup to restore coherent state
|
||||||
|
loadPopupContent(iddatadb, idquotations);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
document.querySelectorAll(".thumbnail").forEach((img) => {
|
document.querySelectorAll(".thumbnail").forEach((img) => {
|
||||||
img.addEventListener("click", function () {
|
img.addEventListener("click", function () {
|
||||||
const enlargedImage = document.getElementById("enlargedImage");
|
const enlargedImage = document.getElementById("enlargedImage");
|
||||||
@@ -412,6 +475,11 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (canvas) {
|
||||||
|
canvas.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
canvas = new fabric.Canvas("collageCanvas", {
|
canvas = new fabric.Canvas("collageCanvas", {
|
||||||
backgroundColor: "#fff",
|
backgroundColor: "#fff",
|
||||||
selection: true,
|
selection: true,
|
||||||
@@ -520,6 +588,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
if (isCropping || isRemovingBackground) {
|
if (isCropping || isRemovingBackground) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = JSON.stringify(
|
const state = JSON.stringify(
|
||||||
canvas.toJSON([
|
canvas.toJSON([
|
||||||
"cornerColor",
|
"cornerColor",
|
||||||
@@ -527,12 +596,16 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
"cornerSize",
|
"cornerSize",
|
||||||
"borderColor",
|
"borderColor",
|
||||||
"transparentCorners",
|
"transparentCorners",
|
||||||
|
"backgroundImage",
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
history.push(state);
|
history.push(state);
|
||||||
|
|
||||||
if (history.length > maxHistory) {
|
if (history.length > maxHistory) {
|
||||||
history.shift();
|
history.shift();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateButtons();
|
updateButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -540,10 +613,11 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
if (history.length <= 1) {
|
if (history.length <= 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
history.pop();
|
history.pop();
|
||||||
const previousState = history[history.length - 1];
|
const previousState = history[history.length - 1];
|
||||||
|
|
||||||
if (previousState) {
|
if (previousState) {
|
||||||
canvas.clear();
|
|
||||||
canvas.loadFromJSON(previousState, () => {
|
canvas.loadFromJSON(previousState, () => {
|
||||||
canvas.renderAll();
|
canvas.renderAll();
|
||||||
updateLayersPanel();
|
updateLayersPanel();
|
||||||
@@ -552,8 +626,11 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
} else {
|
} else {
|
||||||
console.warn("Nessuno stato precedente disponibile");
|
console.warn("Nessuno stato precedente disponibile");
|
||||||
canvas.clear();
|
canvas.clear();
|
||||||
canvas.setBackgroundColor("#fff");
|
canvas.backgroundImage = null;
|
||||||
canvas.renderAll();
|
canvas.setBackgroundColor(
|
||||||
|
"#fff",
|
||||||
|
canvas.renderAll.bind(canvas),
|
||||||
|
);
|
||||||
updateLayersPanel();
|
updateLayersPanel();
|
||||||
updateButtons();
|
updateButtons();
|
||||||
}
|
}
|
||||||
@@ -846,6 +923,61 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
canvas.renderAll();
|
canvas.renderAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setCanvasBackground(imgPath) {
|
||||||
|
console.log("setCanvasBackground chiamata con:", imgPath);
|
||||||
|
|
||||||
|
if (!canvas) {
|
||||||
|
console.error("Canvas non inizializzato");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fabric.Image.fromURL(
|
||||||
|
imgPath,
|
||||||
|
(img) => {
|
||||||
|
const canvasWidth = canvas.getWidth();
|
||||||
|
const canvasHeight = canvas.getHeight();
|
||||||
|
|
||||||
|
const imgWidth = img.width;
|
||||||
|
const imgHeight = img.height;
|
||||||
|
|
||||||
|
if (!imgWidth || !imgHeight) {
|
||||||
|
alert(
|
||||||
|
"Impossibile leggere le dimensioni dell'immagine.",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale to cover: l'immagine copre tutto il canvas mantenendo il ratio
|
||||||
|
const scale = Math.max(
|
||||||
|
canvasWidth / imgWidth,
|
||||||
|
canvasHeight / imgHeight,
|
||||||
|
);
|
||||||
|
|
||||||
|
img.set({
|
||||||
|
originX: "left",
|
||||||
|
originY: "top",
|
||||||
|
scaleX: scale,
|
||||||
|
scaleY: scale,
|
||||||
|
left: (canvasWidth - imgWidth * scale) / 2,
|
||||||
|
top: (canvasHeight - imgHeight * scale) / 2,
|
||||||
|
selectable: false,
|
||||||
|
evented: false,
|
||||||
|
hasControls: false,
|
||||||
|
hasBorders: false,
|
||||||
|
excludeFromExport: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.setBackgroundImage(img, () => {
|
||||||
|
canvas.requestRenderAll();
|
||||||
|
saveCanvasState();
|
||||||
|
updateLayersPanel();
|
||||||
|
updateButtons();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ crossOrigin: "anonymous" },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const addToCanvasBtn = document.getElementById("addToCanvasBtn");
|
const addToCanvasBtn = document.getElementById("addToCanvasBtn");
|
||||||
if (addToCanvasBtn) {
|
if (addToCanvasBtn) {
|
||||||
addToCanvasBtn.addEventListener("click", () => {
|
addToCanvasBtn.addEventListener("click", () => {
|
||||||
@@ -885,6 +1017,32 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setBackgroundBtn = document.getElementById("setBackgroundBtn");
|
||||||
|
if (setBackgroundBtn) {
|
||||||
|
setBackgroundBtn.addEventListener("click", () => {
|
||||||
|
const checkboxes = document.querySelectorAll(
|
||||||
|
".photo-checkbox:checked",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (checkboxes.length === 0) {
|
||||||
|
alert("Seleziona una foto da usare come sfondo!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkboxes.length > 1) {
|
||||||
|
alert("Seleziona una sola foto per lo sfondo!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imgPath = checkboxes[0].getAttribute("data-path");
|
||||||
|
setCanvasBackground(imgPath);
|
||||||
|
|
||||||
|
checkboxes.forEach((cb) => {
|
||||||
|
cb.checked = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const saveCollageBtn = document.getElementById("saveCollageBtn");
|
const saveCollageBtn = document.getElementById("saveCollageBtn");
|
||||||
if (saveCollageBtn) {
|
if (saveCollageBtn) {
|
||||||
saveCollageBtn.addEventListener("click", async () => {
|
saveCollageBtn.addEventListener("click", async () => {
|
||||||
@@ -1060,4 +1218,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
closeBtn,
|
closeBtn,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Export for external use (gridData pages)
|
||||||
|
window.loadPopupContent = loadPopupContent;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -117,12 +117,24 @@ $code = $row[$field] ?? 'Non disponibile';
|
|||||||
|
|
||||||
// Recupera le foto associate alla riga
|
// Recupera le foto associate alla riga
|
||||||
try {
|
try {
|
||||||
$stmt = $pdo->prepare("SELECT id, file_path, file_name, description, uploaded_at FROM {$photoTable} WHERE {$photoParamName} = ? ORDER BY uploaded_at DESC");
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
file_path,
|
||||||
|
file_name,
|
||||||
|
uploaded_at,
|
||||||
|
description,
|
||||||
|
StampaNelRapporto,
|
||||||
|
PrimaPagina
|
||||||
|
FROM {$photoTable}
|
||||||
|
WHERE {$photoParamName} = ?
|
||||||
|
ORDER BY uploaded_at DESC
|
||||||
|
");
|
||||||
$stmt->execute([$paramValue]);
|
$stmt->execute([$paramValue]);
|
||||||
$photos = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$photos = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
error_log("Errore query foto: " . $e->getMessage());
|
error_log("Errore query foto: " . $e->getMessage());
|
||||||
$photos = []; // Imposta array vuoto in caso di errore
|
$photos = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Definisci il percorso base per le foto
|
// Definisci il percorso base per le foto
|
||||||
@@ -134,7 +146,7 @@ $uploadUrl = $iddatadb
|
|||||||
? $baseUrl . "/upload_photos_mobile.php?iddatadb=" . $iddatadb
|
? $baseUrl . "/upload_photos_mobile.php?iddatadb=" . $iddatadb
|
||||||
: $baseUrl . "/upload_photos_mobile.php?idquotations=" . $idquotations;
|
: $baseUrl . "/upload_photos_mobile.php?idquotations=" . $idquotations;
|
||||||
|
|
||||||
// Genera il QR code con endroid/qr-code 6.0.6
|
// Genera il QR code con endroid/qr-code
|
||||||
$qrCodeDir = '../photostrf/qrcodes/';
|
$qrCodeDir = '../photostrf/qrcodes/';
|
||||||
if (!is_dir($qrCodeDir)) {
|
if (!is_dir($qrCodeDir)) {
|
||||||
mkdir($qrCodeDir, 0755, true);
|
mkdir($qrCodeDir, 0755, true);
|
||||||
@@ -164,6 +176,7 @@ $result->saveToFile($qrCodeFile);
|
|||||||
<p>Caricamento in corso...</p>
|
<p>Caricamento in corso...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>Manage Photos</h3>
|
<h3>Manage Photos</h3>
|
||||||
<p><strong>ID:</strong> <?= htmlspecialchars($id) ?></p>
|
<p><strong>ID:</strong> <?= htmlspecialchars($id) ?></p>
|
||||||
<p><strong>Code:</strong> <?= htmlspecialchars($code) ?></p>
|
<p><strong>Code:</strong> <?= htmlspecialchars($code) ?></p>
|
||||||
@@ -182,6 +195,7 @@ $result->saveToFile($qrCodeFile);
|
|||||||
<p>Drag the photo here or click to select</p>
|
<p>Drag the photo here or click to select</p>
|
||||||
<input type="file" id="photoInput" multiple accept="image/*" style="display: none;">
|
<input type="file" id="photoInput" multiple accept="image/*" style="display: none;">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Area per la webcam -->
|
<!-- Area per la webcam -->
|
||||||
<div id="webcamArea" style="display: none; text-align: center; margin-bottom: 20px;">
|
<div id="webcamArea" style="display: none; text-align: center; margin-bottom: 20px;">
|
||||||
<p>Webcam Preview</p>
|
<p>Webcam Preview</p>
|
||||||
@@ -194,7 +208,9 @@ $result->saveToFile($qrCodeFile);
|
|||||||
<button id="closeWebcamBtn" style="padding: 10px 20px; background: #dc3545; color: white; border: none; cursor: pointer;">Close Webcam</button>
|
<button id="closeWebcamBtn" style="padding: 10px 20px; background: #dc3545; color: white; border: none; cursor: pointer;">Close Webcam</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="openWebcamBtn" style="padding: 10px 20px; background: #007bff; color: white; border: none; cursor: pointer; margin-bottom: 20px;">Take Photo with Webcam</button>
|
<button id="openWebcamBtn" style="padding: 10px 20px; background: #007bff; color: white; border: none; cursor: pointer; margin-bottom: 20px;">Take Photo with Webcam</button>
|
||||||
|
|
||||||
<!-- Elenco delle foto -->
|
<!-- Elenco delle foto -->
|
||||||
<div id="photosList">
|
<div id="photosList">
|
||||||
<?php if (empty($photos)): ?>
|
<?php if (empty($photos)): ?>
|
||||||
@@ -204,25 +220,57 @@ $result->saveToFile($qrCodeFile);
|
|||||||
<?php
|
<?php
|
||||||
$filePath = $photoBasePath . $photo['file_path'];
|
$filePath = $photoBasePath . $photo['file_path'];
|
||||||
$fileExists = file_exists($filePath);
|
$fileExists = file_exists($filePath);
|
||||||
|
$stampaNelRapporto = !empty($photo['StampaNelRapporto']) ? 1 : 0;
|
||||||
|
$primaPagina = !empty($photo['PrimaPagina']) ? 1 : 0;
|
||||||
?>
|
?>
|
||||||
<div class="photo-item" data-photo-id="<?= $photo['id'] ?>" style="display: flex; align-items: center; margin-bottom: 10px; border-bottom: 1px solid #eee; padding-bottom: 10px;">
|
<div class="photo-item" data-photo-id="<?= (int)$photo['id'] ?>">
|
||||||
<div style="flex: 1;">
|
<div class="photo-thumb-area">
|
||||||
<?php if ($fileExists): ?>
|
<?php if ($fileExists): ?>
|
||||||
<img src="../photostrf/<?= htmlspecialchars($photo['file_path']) ?>" alt="<?= htmlspecialchars($photo['file_name']) ?>" class="thumbnail" style="max-width: 100px; max-height: 100px; margin-right: 10px; cursor: pointer;">
|
<img src="../photostrf/<?= htmlspecialchars($photo['file_path']) ?>"
|
||||||
|
alt="<?= htmlspecialchars($photo['file_name']) ?>"
|
||||||
|
class="thumbnail"
|
||||||
|
style="max-width: 100px; max-height: 100px; cursor: pointer;">
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<p style="color: red;">[File non trovato]</p>
|
<div class="missing-file-box">[File non trovato]</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<p style="margin: 0;">
|
|
||||||
<strong>Nome:</strong> <?= htmlspecialchars($photo['file_name']) ?><br>
|
|
||||||
<strong>Caricata il:</strong> <?= htmlspecialchars($photo['uploaded_at']) ?><br>
|
|
||||||
<strong>Descrizione:</strong> <?= htmlspecialchars($photo['description'] ?? 'Nessuna descrizione') ?>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<button class="delete-photo-btn" data-photo-id="<?= $photo['id'] ?>" style="background: none; border: none; color: #dc3545; cursor: pointer;">
|
|
||||||
|
<div class="photo-info-area">
|
||||||
|
<p class="photo-name">
|
||||||
|
<strong>Name:</strong> <?= htmlspecialchars($photo['file_name']) ?>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="photo-flags">
|
||||||
|
<label class="flag-label">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="photo-flag-checkbox"
|
||||||
|
data-photo-id="<?= (int)$photo['id'] ?>"
|
||||||
|
data-field="StampaNelRapporto"
|
||||||
|
<?= $stampaNelRapporto ? 'checked' : '' ?>>
|
||||||
|
Print in Report
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="flag-label">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="photo-flag-checkbox prima-pagina-checkbox"
|
||||||
|
data-photo-id="<?= (int)$photo['id'] ?>"
|
||||||
|
data-field="PrimaPagina"
|
||||||
|
<?= $primaPagina ? 'checked' : '' ?>>
|
||||||
|
First Page
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="photo-actions-area">
|
||||||
|
<button class="delete-photo-btn" data-photo-id="<?= (int)$photo['id'] ?>" type="button">
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
<!-- Bottone Crea Collage -->
|
<!-- Bottone Crea Collage -->
|
||||||
<button id="createCollageBtn" style="padding: 10px 20px; background: #ffc107; color: white; border: none; cursor: pointer; margin-top: 20px;">Crea Collage</button>
|
<button id="createCollageBtn" style="padding: 10px 20px; background: #ffc107; color: white; border: none; cursor: pointer; margin-top: 20px;">Crea Collage</button>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -250,8 +298,11 @@ $result->saveToFile($qrCodeFile);
|
|||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bottone per aggiungere selezionate al canvas -->
|
<!-- Bottoni azioni collage -->
|
||||||
|
<div style="display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 10px;">
|
||||||
<button id="addToCanvasBtn">Aggiungi Selezionate al Canvas</button>
|
<button id="addToCanvasBtn">Aggiungi Selezionate al Canvas</button>
|
||||||
|
<button id="setBackgroundBtn" type="button">Imposta Selezionata come Sfondo</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Canvas per editing -->
|
<!-- Canvas per editing -->
|
||||||
<canvas id="collageCanvas" width="960" height="720" style="border: 1px solid #ccc; margin-top: 20px;"></canvas>
|
<canvas id="collageCanvas" width="960" height="720" style="border: 1px solid #ccc; margin-top: 20px;"></canvas>
|
||||||
@@ -283,6 +334,12 @@ $result->saveToFile($qrCodeFile);
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
.photo-item {
|
.photo-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
padding: 12px 0;
|
||||||
transition: background-color 0.3s;
|
transition: background-color 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,6 +347,68 @@ $result->saveToFile($qrCodeFile);
|
|||||||
background-color: #f8f9fa;
|
background-color: #f8f9fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.photo-thumb-area {
|
||||||
|
flex: 0 0 110px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-info-area {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-actions-area {
|
||||||
|
flex: 0 0 40px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-name {
|
||||||
|
margin: 0;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-flags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 18px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flag-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flag-label input[type="checkbox"] {
|
||||||
|
transform: scale(1.1);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-photo-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #dc3545;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-photo-btn:hover {
|
||||||
|
color: #b02a37;
|
||||||
|
}
|
||||||
|
|
||||||
|
.missing-file-box {
|
||||||
|
color: red;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
#dropArea.highlight {
|
#dropArea.highlight {
|
||||||
border-color: #28a745;
|
border-color: #28a745;
|
||||||
background-color: #e9ecef;
|
background-color: #e9ecef;
|
||||||
|
|||||||
@@ -9,46 +9,76 @@ try {
|
|||||||
throw new Exception("Invalid request method.");
|
throw new Exception("Invalid request method.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recupera e sanifica i dati
|
// Retrieve and sanitize form data
|
||||||
$id = intval($_POST['id']);
|
$id = intval($_POST['id'] ?? 0);
|
||||||
$name = trim($_POST['name']);
|
$name = trim($_POST['name'] ?? '');
|
||||||
$header_row = intval($_POST['header_row']);
|
$source_type = strtoupper(trim($_POST['source_type'] ?? 'XLS'));
|
||||||
$start_column = trim($_POST['start_column']);
|
$header_row = isset($_POST['header_row']) && $_POST['header_row'] !== '' ? intval($_POST['header_row']) : null;
|
||||||
|
$start_column = trim($_POST['start_column'] ?? '');
|
||||||
$description = trim($_POST['description'] ?? '');
|
$description = trim($_POST['description'] ?? '');
|
||||||
$target_table = trim($_POST['target_table']);
|
$target_table = trim($_POST['target_table'] ?? 'datadb');
|
||||||
$idclient = intval($_POST['client_id'] ?? 0); // Usa client_id dal form
|
$idclient = intval($_POST['client_id'] ?? 0);
|
||||||
$clientname = trim($_POST['client_name'] ?? ''); // Usa client_name dal form
|
$clientname = trim($_POST['client_name'] ?? '');
|
||||||
$idschema = intval($_POST['idschema'] ?? 0); // Nuovo campo
|
$idschema = intval($_POST['idschema'] ?? 0);
|
||||||
$schemaname = trim($_POST['schemaname'] ?? ''); // Corretto da schemamaname
|
$schemaname = trim($_POST['schemaname'] ?? '');
|
||||||
$idroutine = isset($_POST['idroutine']) && $_POST['idroutine'] !== '' ? intval($_POST['idroutine']) : null; // Aggiunto idroutine
|
$idroutine = isset($_POST['idroutine']) && $_POST['idroutine'] !== '' ? intval($_POST['idroutine']) : null;
|
||||||
$button_size = trim($_POST['button_size'] ?? 'medium'); // Nuovo campo
|
$button_size = trim($_POST['button_size'] ?? 'medium');
|
||||||
$button_bg_color = trim($_POST['button_bg_color'] ?? '#007bff'); // Nuovo campo
|
$button_bg_color = trim($_POST['button_bg_color'] ?? '#007bff');
|
||||||
$button_text_color = trim($_POST['button_text_color'] ?? '#ffffff'); // Nuovo campo
|
$button_text_color = trim($_POST['button_text_color'] ?? '#ffffff');
|
||||||
$button_label = trim($_POST['button_label'] ?? 'Click Me'); // Nuovo campo
|
$button_label = trim($_POST['button_label'] ?? 'Click Me');
|
||||||
|
|
||||||
// Controllo sui campi obbligatori
|
if (!in_array($source_type, ['XLS', 'API'], true)) {
|
||||||
if (empty($id) || empty($name) || empty($header_row) || empty($start_column) || empty($target_table) || $idschema <= 0) {
|
$source_type = 'XLS';
|
||||||
throw new Exception("All fields marked with * are required, including schema.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validazione del idclient
|
// Required fields validation
|
||||||
if ($idclient <= 0) {
|
if ($id <= 0 || $name === '' || $target_table === '' || $idclient <= 0 || $idschema <= 0) {
|
||||||
throw new Exception("Please select a valid client.");
|
throw new Exception("All fields marked with * are required, including client and schema.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connessione al database
|
// XLS-only validation
|
||||||
|
if ($source_type === 'XLS') {
|
||||||
|
if ($header_row === null || $header_row <= 0 || $start_column === '') {
|
||||||
|
throw new Exception("Header Row and Start Column are required for XLS templates.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API templates do not require XLS coordinates
|
||||||
|
if ($source_type === 'API') {
|
||||||
|
$header_row = null;
|
||||||
|
$start_column = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Database connection
|
||||||
$db = DBHandlerSelect::getInstance();
|
$db = DBHandlerSelect::getInstance();
|
||||||
$pdo = $db->getConnection();
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
// Aggiorna il database, includendo i nuovi campi
|
// Update template
|
||||||
$stmt = $pdo->prepare("UPDATE excel_templates
|
$stmt = $pdo->prepare("
|
||||||
SET name = ?, header_row = ?, start_column = ?, description = ?, target_table = ?,
|
UPDATE excel_templates
|
||||||
idclient = ?, clientname = ?, schemaname = ?, idschema = ?, idroutine = ?,
|
SET
|
||||||
button_size = ?, button_bg_color = ?, button_text_color = ?, button_label = ?,
|
name = ?,
|
||||||
|
source_type = ?,
|
||||||
|
header_row = ?,
|
||||||
|
start_column = ?,
|
||||||
|
description = ?,
|
||||||
|
target_table = ?,
|
||||||
|
idclient = ?,
|
||||||
|
clientname = ?,
|
||||||
|
schemaname = ?,
|
||||||
|
idschema = ?,
|
||||||
|
idroutine = ?,
|
||||||
|
button_size = ?,
|
||||||
|
button_bg_color = ?,
|
||||||
|
button_text_color = ?,
|
||||||
|
button_label = ?,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE id = ?");
|
WHERE id = ?
|
||||||
|
");
|
||||||
|
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
$name,
|
$name,
|
||||||
|
$source_type,
|
||||||
$header_row,
|
$header_row,
|
||||||
$start_column,
|
$start_column,
|
||||||
$description,
|
$description,
|
||||||
@@ -65,12 +95,10 @@ try {
|
|||||||
$id
|
$id
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// rowCount potrebbe essere 0 se non ci sono modifiche, quindi consideriamo comunque un successo
|
|
||||||
$response["success"] = true;
|
$response["success"] = true;
|
||||||
$response["message"] = "Template updated successfully!";
|
$response["message"] = "Template updated successfully!";
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$response["message"] = $e->getMessage();
|
$response["message"] = $e->getMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restituisce un JSON per il fetch
|
|
||||||
echo json_encode($response);
|
echo json_encode($response);
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ try {
|
|||||||
$columnLetter = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($col);
|
$columnLetter = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($col);
|
||||||
$cell = $worksheet->getCell($columnLetter . $header_row);
|
$cell = $worksheet->getCell($columnLetter . $header_row);
|
||||||
$cellValue = $cell ? $cell->getCalculatedValue() : ''; // Usa getCalculatedValue per le formule
|
$cellValue = $cell ? $cell->getCalculatedValue() : ''; // Usa getCalculatedValue per le formule
|
||||||
$headerRowData[] = htmlspecialchars($cellValue ?: '');
|
$headerRowData[] = $cellValue ?: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Estrai i dati a partire dalla riga successiva
|
// Estrai i dati a partire dalla riga successiva
|
||||||
@@ -80,7 +80,7 @@ try {
|
|||||||
$columnLetter = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($col);
|
$columnLetter = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($col);
|
||||||
$cell = $worksheet->getCell($columnLetter . $row);
|
$cell = $worksheet->getCell($columnLetter . $row);
|
||||||
$cellValue = $cell ? $cell->getCalculatedValue() : ''; // Usa getCalculatedValue per le formule
|
$cellValue = $cell ? $cell->getCalculatedValue() : ''; // Usa getCalculatedValue per le formule
|
||||||
$rowData[] = htmlspecialchars($cellValue ?: '');
|
$rowData[] = $cellValue ?: '';
|
||||||
}
|
}
|
||||||
if (!empty(array_filter($rowData))) {
|
if (!empty(array_filter($rowData))) {
|
||||||
$excelData[] = $rowData;
|
$excelData[] = $rowData;
|
||||||
|
|||||||
@@ -74,6 +74,16 @@ try {
|
|||||||
$startRow = max(1, $header_row);
|
$startRow = max(1, $header_row);
|
||||||
$startColumn = max(1, $start_column);
|
$startColumn = max(1, $start_column);
|
||||||
|
|
||||||
|
// Advance startColumn to first non-empty cell in header row (match JS behavior)
|
||||||
|
for ($sc = $startColumn; $sc <= $highestColumnIndex; $sc++) {
|
||||||
|
$cl = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($sc);
|
||||||
|
$cv = trim((string)($worksheet->getCell($cl . $header_row)->getCalculatedValue() ?? ''));
|
||||||
|
if ($cv !== '') {
|
||||||
|
$startColumn = $sc;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Debug dei parametri
|
// Debug dei parametri
|
||||||
error_log("Processing - template_id: $template_id, startRow: $startRow, startColumn: $startColumn, highestRow: $highestRow, highestColumn: $highestColumn, highestColumnIndex: $highestColumnIndex");
|
error_log("Processing - template_id: $template_id, startRow: $startRow, startColumn: $startColumn, highestRow: $highestRow, highestColumn: $highestColumn, highestColumnIndex: $highestColumnIndex");
|
||||||
|
|
||||||
@@ -84,25 +94,78 @@ try {
|
|||||||
$response['error'] = "La colonna di partenza ($startColumn) supera il numero totale di colonne ($highestColumnIndex).";
|
$response['error'] = "La colonna di partenza ($startColumn) supera il numero totale di colonne ($highestColumnIndex).";
|
||||||
} else {
|
} else {
|
||||||
$excelData = [];
|
$excelData = [];
|
||||||
// Estrai la riga degli header
|
|
||||||
$headerRowData = [];
|
// Build merge map for header row: physCol -> mergeStartCol
|
||||||
for ($col = $startColumn; $col <= $highestColumnIndex; $col++) {
|
$mergeStartMap = [];
|
||||||
$columnLetter = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($col);
|
foreach ($worksheet->getMergeCells() as $range) {
|
||||||
$cell = $worksheet->getCell($columnLetter . $header_row);
|
[$startCell, $endCell] = explode(':', $range);
|
||||||
$cellValue = $cell ? $cell->getCalculatedValue() : '';
|
$mStartCol = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString(preg_replace('/\d+/', '', $startCell));
|
||||||
$headerRowData[] = htmlspecialchars($cellValue ?: '');
|
$mEndCol = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString(preg_replace('/\d+/', '', $endCell));
|
||||||
|
$mStartRow = (int)preg_replace('/[A-Z]+/i', '', $startCell);
|
||||||
|
$mEndRow = (int)preg_replace('/[A-Z]+/i', '', $endCell);
|
||||||
|
if ($header_row >= $mStartRow && $header_row <= $mEndRow) {
|
||||||
|
for ($c = $mStartCol; $c <= $mEndCol; $c++) {
|
||||||
|
$mergeStartMap[$c] = $mStartCol;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Estrai i dati a partire dalla riga successiva, includendo excelrow
|
// Build logical columns: each merge = one column
|
||||||
|
$logicalCols = []; // array of physical column indices (one per logical column)
|
||||||
|
$seen = [];
|
||||||
|
for ($col = $startColumn; $col <= $highestColumnIndex; $col++) {
|
||||||
|
if (isset($mergeStartMap[$col])) {
|
||||||
|
$ms = $mergeStartMap[$col];
|
||||||
|
if (in_array($ms, $seen, true)) continue;
|
||||||
|
$seen[] = $ms;
|
||||||
|
$logicalCols[] = $ms;
|
||||||
|
} else {
|
||||||
|
$logicalCols[] = $col;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build header row using logical columns
|
||||||
|
$headerRowData = [];
|
||||||
|
$logicalNum = 0;
|
||||||
|
foreach ($logicalCols as $physCol) {
|
||||||
|
$logicalNum++;
|
||||||
|
$columnLetter = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($physCol);
|
||||||
|
$cell = $worksheet->getCell($columnLetter . $header_row);
|
||||||
|
$cellValue = trim((string)($cell ? $cell->getCalculatedValue() : ''));
|
||||||
|
$cellValue = preg_replace('/[\r\n\t]+/', ' ', $cellValue);
|
||||||
|
// Empty headers get __empty_N__ to match mapping page
|
||||||
|
$headerRowData[] = ($cellValue !== '') ? $cellValue : '__empty_' . $logicalNum . '__';
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("Logical headers: " . json_encode($headerRowData));
|
||||||
|
error_log("Logical cols (physical indices): " . json_encode($logicalCols));
|
||||||
|
|
||||||
|
// Find which logical columns have real headers
|
||||||
|
$headerFilledIndices = [];
|
||||||
|
foreach ($headerRowData as $idx => $hVal) {
|
||||||
|
if (!str_starts_with($hVal, '__empty_')) $headerFilledIndices[] = $idx;
|
||||||
|
}
|
||||||
|
$minFilled = max(1, min(2, count($headerFilledIndices)));
|
||||||
|
|
||||||
|
// Extract data rows using logical columns
|
||||||
for ($row = $startRow + 1; $row <= $highestRow; $row++) {
|
for ($row = $startRow + 1; $row <= $highestRow; $row++) {
|
||||||
$rowData = [];
|
$rowData = [];
|
||||||
for ($col = $startColumn; $col <= $highestColumnIndex; $col++) {
|
foreach ($logicalCols as $physCol) {
|
||||||
$columnLetter = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($col);
|
$columnLetter = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($physCol);
|
||||||
$cell = $worksheet->getCell($columnLetter . $row);
|
$cell = $worksheet->getCell($columnLetter . $row);
|
||||||
$cellValue = $cell ? $cell->getCalculatedValue() : '';
|
$cellValue = $cell ? $cell->getCalculatedValue() : '';
|
||||||
$rowData[] = htmlspecialchars($cellValue ?: '');
|
$rowData[] = $cellValue ?: '';
|
||||||
}
|
}
|
||||||
if (!empty(array_filter($rowData))) {
|
|
||||||
|
// Count how many header columns have data in this row
|
||||||
|
$filledCount = 0;
|
||||||
|
foreach ($headerFilledIndices as $idx) {
|
||||||
|
if (isset($rowData[$idx]) && trim((string)$rowData[$idx]) !== '') {
|
||||||
|
$filledCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($filledCount >= $minFilled) {
|
||||||
$excelData[] = ['data' => $rowData, 'excelrow' => $row];
|
$excelData[] = ['data' => $rowData, 'excelrow' => $row];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,13 @@ try {
|
|||||||
throw new Exception("Invalid request method.");
|
throw new Exception("Invalid request method.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recupera e sanifica i dati
|
// Retrieve and sanitize form data
|
||||||
$name = trim($_POST['name']);
|
$name = trim($_POST['name'] ?? '');
|
||||||
$header_row = intval($_POST['header_row']);
|
$source_type = strtoupper(trim($_POST['source_type'] ?? 'XLS'));
|
||||||
$start_column = trim($_POST['start_column']);
|
$header_row = isset($_POST['header_row']) && $_POST['header_row'] !== '' ? intval($_POST['header_row']) : null;
|
||||||
|
$start_column = trim($_POST['start_column'] ?? '');
|
||||||
$description = trim($_POST['description'] ?? '');
|
$description = trim($_POST['description'] ?? '');
|
||||||
$target_table = trim($_POST['target_table']);
|
$target_table = trim($_POST['target_table'] ?? 'datadb');
|
||||||
$idclient = intval($_POST['client_id'] ?? 0);
|
$idclient = intval($_POST['client_id'] ?? 0);
|
||||||
$clientname = trim($_POST['client_name'] ?? '');
|
$clientname = trim($_POST['client_name'] ?? '');
|
||||||
$idschema = intval($_POST['idschema'] ?? 0);
|
$idschema = intval($_POST['idschema'] ?? 0);
|
||||||
@@ -25,24 +26,61 @@ try {
|
|||||||
$button_text_color = trim($_POST['button_text_color'] ?? '#ffffff');
|
$button_text_color = trim($_POST['button_text_color'] ?? '#ffffff');
|
||||||
$button_label = trim($_POST['button_label'] ?? 'Click Me');
|
$button_label = trim($_POST['button_label'] ?? 'Click Me');
|
||||||
|
|
||||||
// Controllo sui campi obbligatori
|
// Normalize source type
|
||||||
if (empty($name) || empty($header_row) || empty($start_column) || empty($target_table) || $idclient <= 0 || $idschema <= 0) {
|
if (!in_array($source_type, ['XLS', 'API'], true)) {
|
||||||
|
$source_type = 'XLS';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required fields validation
|
||||||
|
if ($name === '' || $target_table === '' || $idclient <= 0 || $idschema <= 0) {
|
||||||
throw new Exception("All fields marked with * are required, including client and schema.");
|
throw new Exception("All fields marked with * are required, including client and schema.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connessione al database
|
// XLS-only validation
|
||||||
|
if ($source_type === 'XLS') {
|
||||||
|
if ($header_row === null || $header_row <= 0 || $start_column === '') {
|
||||||
|
throw new Exception("Header Row and Start Column are required for XLS templates.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API templates do not require XLS coordinates
|
||||||
|
if ($source_type === 'API') {
|
||||||
|
$header_row = null;
|
||||||
|
$start_column = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Database connection
|
||||||
$db = DBHandlerSelect::getInstance();
|
$db = DBHandlerSelect::getInstance();
|
||||||
$pdo = $db->getConnection();
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
// Inserisci il nuovo template
|
// Insert the new template
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
INSERT INTO excel_templates
|
INSERT INTO excel_templates
|
||||||
(name, header_row, start_column, description, target_table, idclient, clientname, idschema, schemaname, idroutine,
|
(
|
||||||
button_size, button_bg_color, button_text_color, button_label, created_at, updated_at)
|
name,
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
source_type,
|
||||||
|
header_row,
|
||||||
|
start_column,
|
||||||
|
description,
|
||||||
|
target_table,
|
||||||
|
idclient,
|
||||||
|
clientname,
|
||||||
|
idschema,
|
||||||
|
schemaname,
|
||||||
|
idroutine,
|
||||||
|
button_size,
|
||||||
|
button_bg_color,
|
||||||
|
button_text_color,
|
||||||
|
button_label,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
||||||
");
|
");
|
||||||
|
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
$name,
|
$name,
|
||||||
|
$source_type,
|
||||||
$header_row,
|
$header_row,
|
||||||
$start_column,
|
$start_column,
|
||||||
$description,
|
$description,
|
||||||
|
|||||||
@@ -5,102 +5,211 @@ ini_set('error_log', __DIR__ . '/routine_debug.log');
|
|||||||
function applyRoutine(&$excelData, $routineData)
|
function applyRoutine(&$excelData, $routineData)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
// Log iniziale
|
// Initial log
|
||||||
error_log("Inizio esecuzione routine Moncler: " . date('Y-m-d H:i:s'));
|
error_log("Inizio esecuzione routine Moncler: " . date('Y-m-d H:i:s'));
|
||||||
error_log("Dati routine: " . print_r($routineData, true));
|
error_log("Dati routine: " . print_r($routineData, true));
|
||||||
error_log("Dati excel_data: " . print_r($excelData, true));
|
error_log("Dati excel_data: " . print_r($excelData, true));
|
||||||
|
|
||||||
// Verifica se excelData è vuoto
|
// Check if excelData is empty
|
||||||
if (empty($excelData)) {
|
if (empty($excelData)) {
|
||||||
throw new Exception("excelData è vuoto o non valido.");
|
throw new Exception("excelData è vuoto o non valido.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Estrai informazioni dalla routine con valori predefiniti
|
// Extract routine settings with default values
|
||||||
$action1 = trim($routineData['action1'] ?? 'K');
|
$action1 = trim($routineData['action1'] ?? 'K');
|
||||||
$action2 = trim($routineData['action2'] ?? 'STYLE CODE + STYLE DESCRIPTION');
|
$action2 = trim($routineData['action2'] ?? 'STYLE CODE + STYLE DESCRIPTION');
|
||||||
$action3 = trim($routineData['action3'] ?? 'STYLE CODE');
|
$action3 = trim($routineData['action3'] ?? 'STYLE CODE');
|
||||||
$action4 = trim($routineData['action4'] ?? 'STYLE DESCRIPTION');
|
$action4 = trim($routineData['action4'] ?? 'STYLE DESCRIPTION');
|
||||||
$headers = $routineData['xls_headers'] ?? [];
|
$headers = $routineData['xls_headers'] ?? [];
|
||||||
|
|
||||||
|
// Package-related headers
|
||||||
|
$package_header = 'PACKAGE';
|
||||||
|
$other_test_header = 'Other test from Control Matrix';
|
||||||
|
$only_colorfastness_header = 'Only Colorfastness package';
|
||||||
|
$only_chemical_header = 'Only Chemical package';
|
||||||
|
|
||||||
if (empty($headers)) {
|
if (empty($headers)) {
|
||||||
throw new Exception("Nessun header trovato per la routine Moncler.");
|
throw new Exception("Nessun header trovato per la routine Moncler.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If headers arrive as JSON string, decode them
|
||||||
|
if (!is_array($headers) && is_string($headers)) {
|
||||||
|
$decoded_headers = json_decode($headers, true);
|
||||||
|
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded_headers)) {
|
||||||
|
$headers = $decoded_headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_array($headers) || empty($headers)) {
|
||||||
|
throw new Exception("Gli header della routine non sono validi.");
|
||||||
|
}
|
||||||
|
|
||||||
error_log("Header ricevuti: " . print_r($headers, true));
|
error_log("Header ricevuti: " . print_r($headers, true));
|
||||||
|
|
||||||
// Normalizza gli header (solo trim)
|
// Normalize headers
|
||||||
$normalized_headers = array_map('trim', $headers);
|
$normalized_headers = array_map(function ($header) {
|
||||||
|
return trim((string)$header);
|
||||||
|
}, $headers);
|
||||||
|
|
||||||
error_log("Header normalizzati: " . print_r($normalized_headers, true));
|
error_log("Header normalizzati: " . print_r($normalized_headers, true));
|
||||||
error_log("Action values - action1: '$action1', action2: '$action2', action3: '$action3', action4: '$action4'");
|
error_log("Action values - action1: '$action1', action2: '$action2', action3: '$action3', action4: '$action4'");
|
||||||
|
|
||||||
// Trova gli indici delle colonne
|
// Find package-related column indexes
|
||||||
$action1_index = array_search($action1, $normalized_headers);
|
$package_index = array_search($package_header, $normalized_headers, true);
|
||||||
$action2_index = array_search($action2, $normalized_headers);
|
$other_test_index = array_search($other_test_header, $normalized_headers, true);
|
||||||
$action3_index = array_search($action3, $normalized_headers);
|
$only_colorfastness_index = array_search($only_colorfastness_header, $normalized_headers, true);
|
||||||
$action4_index = array_search($action4, $normalized_headers);
|
$only_chemical_index = array_search($only_chemical_header, $normalized_headers, true);
|
||||||
|
|
||||||
|
if ($package_index === false) {
|
||||||
|
throw new Exception("Colonna PACKAGE non trovata.");
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log(
|
||||||
|
"Indici package - PACKAGE: " . var_export($package_index, true) .
|
||||||
|
", Other test from Control Matrix: " . var_export($other_test_index, true) .
|
||||||
|
", Only Colorfastness package: " . var_export($only_colorfastness_index, true) .
|
||||||
|
", Only Chemical package: " . var_export($only_chemical_index, true)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PART 1
|
||||||
|
* Aggregate rows by action1 and split action2 into action3 + action4
|
||||||
|
* If it fails, continue with PART 2 only
|
||||||
|
*/
|
||||||
|
$part1_applied = false;
|
||||||
|
|
||||||
|
$action1_index = array_search($action1, $normalized_headers, true);
|
||||||
|
$action2_index = array_search($action2, $normalized_headers, true);
|
||||||
|
$action3_index = array_search($action3, $normalized_headers, true);
|
||||||
|
$action4_index = array_search($action4, $normalized_headers, true);
|
||||||
|
|
||||||
if ($action1_index === false || $action2_index === false || $action3_index === false || $action4_index === false) {
|
if ($action1_index === false || $action2_index === false || $action3_index === false || $action4_index === false) {
|
||||||
throw new Exception("Colonne non trovate - action1: '$action1' (index: " . var_export($action1_index, true) . "), action2: '$action2' (index: " . var_export($action2_index, true) . "), action3: '$action3' (index: " . var_export($action3_index, true) . "), action4: '$action4' (index: " . var_export($action4_index, true) . ")");
|
error_log(
|
||||||
}
|
"Unable to apply routine part 1. Package merge logic has been applied only. " .
|
||||||
|
"Missing columns - action1: '$action1' (index: " . var_export($action1_index, true) . ")" .
|
||||||
|
", action2: '$action2' (index: " . var_export($action2_index, true) . ")" .
|
||||||
|
", action3: '$action3' (index: " . var_export($action3_index, true) . ")" .
|
||||||
|
", action4: '$action4' (index: " . var_export($action4_index, true) . ")"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
error_log("Indici colonne part 1 - action1: $action1_index, action2: $action2_index, action3: $action3_index, action4: $action4_index");
|
||||||
|
|
||||||
error_log("Indici colonne - action1: $action1_index, action2: $action2_index, action3: $action3_index, action4: $action4_index");
|
|
||||||
|
|
||||||
// Raggruppa le righe per il valore in action1 (colonna K)
|
|
||||||
$grouped_data = [];
|
$grouped_data = [];
|
||||||
|
|
||||||
foreach ($excelData as $row) {
|
foreach ($excelData as $row) {
|
||||||
if (!isset($row['data']) || !is_array($row['data'])) {
|
if (!isset($row['data']) || !is_array($row['data'])) {
|
||||||
error_log("Riga non valida, manca 'data' per excelrow {$row['excelrow']}");
|
error_log("Riga non valida, manca 'data' per excelrow " . ($row['excelrow'] ?? 'N/A'));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$key = $row['data'][$action1_index] ?? '';
|
$key = $row['data'][$action1_index] ?? '';
|
||||||
$key = empty($key) ? '_empty_' : $key;
|
$key = trim((string)$key);
|
||||||
|
$key = $key === '' ? '_empty_' : $key;
|
||||||
|
|
||||||
if (!isset($grouped_data[$key])) {
|
if (!isset($grouped_data[$key])) {
|
||||||
$grouped_data[$key] = [
|
$grouped_data[$key] = [
|
||||||
'data' => $row['data'],
|
'data' => $row['data'],
|
||||||
'excelrow' => [$row['excelrow']],
|
'excelrow' => [($row['excelrow'] ?? '')],
|
||||||
'style_codes' => [],
|
'style_codes' => [],
|
||||||
'style_descriptions' => []
|
'style_descriptions' => []
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
$grouped_data[$key]['excelrow'][] = $row['excelrow'];
|
$grouped_data[$key]['excelrow'][] = ($row['excelrow'] ?? '');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Separa il valore in action2 (STYLE CODE + STYLE DESCRIPTION)
|
// Split STYLE CODE + STYLE DESCRIPTION
|
||||||
$action2_value = $row['data'][$action2_index] ?? '';
|
$action2_value = trim((string)($row['data'][$action2_index] ?? ''));
|
||||||
if (!empty($action2_value)) {
|
|
||||||
$parts = explode(' - ', trim($action2_value));
|
if ($action2_value !== '') {
|
||||||
$style_code = $parts[0] ?? '';
|
$parts = explode(' - ', $action2_value, 2);
|
||||||
$style_description = $parts[1] ?? '';
|
$style_code = trim((string)($parts[0] ?? ''));
|
||||||
if (!empty($style_code)) {
|
$style_description = trim((string)($parts[1] ?? ''));
|
||||||
|
|
||||||
|
if ($style_code !== '') {
|
||||||
$grouped_data[$key]['style_codes'][] = $style_code;
|
$grouped_data[$key]['style_codes'][] = $style_code;
|
||||||
}
|
}
|
||||||
if (!empty($style_description)) {
|
if ($style_description !== '') {
|
||||||
$grouped_data[$key]['style_descriptions'][] = $style_description;
|
$grouped_data[$key]['style_descriptions'][] = $style_description;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
error_log("Valore vuoto in action2 per excelrow {$row['excelrow']}");
|
error_log("Valore vuoto in action2 per excelrow " . ($row['excelrow'] ?? 'N/A'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crea il nuovo array excel_data aggregato
|
|
||||||
$new_excel_data = [];
|
$new_excel_data = [];
|
||||||
|
|
||||||
foreach ($grouped_data as $key => $group) {
|
foreach ($grouped_data as $key => $group) {
|
||||||
$row_data = $group['data'];
|
$row_data = $group['data'];
|
||||||
// Aggiorna action3 (STYLE CODE) e action4 (STYLE DESCRIPTION) con valori aggregati
|
|
||||||
|
// Update STYLE CODE and STYLE DESCRIPTION with aggregated unique values
|
||||||
$row_data[$action3_index] = implode(' - ', array_unique($group['style_codes']));
|
$row_data[$action3_index] = implode(' - ', array_unique($group['style_codes']));
|
||||||
$row_data[$action4_index] = implode(' - ', array_unique($group['style_descriptions']));
|
$row_data[$action4_index] = implode(' - ', array_unique($group['style_descriptions']));
|
||||||
// Concatena gli excelrow con '+' per le righe aggregate
|
|
||||||
$excelrow_value = count($group['excelrow']) > 1 ? implode('+', $group['excelrow']) : $group['excelrow'][0];
|
// Concatenate excelrow values with "+"
|
||||||
|
$excelrow_clean = array_filter($group['excelrow'], function ($value) {
|
||||||
|
return $value !== null && $value !== '';
|
||||||
|
});
|
||||||
|
|
||||||
|
$excelrow_value = count($excelrow_clean) > 1
|
||||||
|
? implode('+', $excelrow_clean)
|
||||||
|
: (reset($excelrow_clean) ?: '');
|
||||||
|
|
||||||
$new_excel_data[] = [
|
$new_excel_data[] = [
|
||||||
'data' => $row_data,
|
'data' => $row_data,
|
||||||
'excelrow' => $excelrow_value
|
'excelrow' => $excelrow_value
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modifica excelData in-place
|
|
||||||
$excelData = $new_excel_data;
|
$excelData = $new_excel_data;
|
||||||
|
$part1_applied = true;
|
||||||
|
|
||||||
error_log("Routine Moncler completata - Righe aggregate: " . count($new_excel_data));
|
error_log("Routine part 1 completata - Righe aggregate: " . count($new_excel_data));
|
||||||
error_log("Excelrow aggregati: " . print_r(array_column($new_excel_data, 'excelrow'), true));
|
error_log("Excelrow aggregati part 1: " . print_r(array_column($new_excel_data, 'excelrow'), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PART 2
|
||||||
|
* Merge package-related columns into PACKAGE
|
||||||
|
* Always applied if PACKAGE exists
|
||||||
|
*/
|
||||||
|
foreach ($excelData as $index => $row) {
|
||||||
|
if (!isset($row['data']) || !is_array($row['data'])) {
|
||||||
|
error_log("Riga non valida nella part 2, manca 'data' all'indice $index");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$package_values = [];
|
||||||
|
|
||||||
|
$value_package = trim((string)($row['data'][$package_index] ?? ''));
|
||||||
|
$value_other_test = $other_test_index !== false ? trim((string)($row['data'][$other_test_index] ?? '')) : '';
|
||||||
|
$value_only_colorfastness = $only_colorfastness_index !== false ? trim((string)($row['data'][$only_colorfastness_index] ?? '')) : '';
|
||||||
|
$value_only_chemical = $only_chemical_index !== false ? trim((string)($row['data'][$only_chemical_index] ?? '')) : '';
|
||||||
|
|
||||||
|
if ($value_package !== '') {
|
||||||
|
$package_values[] = $value_package;
|
||||||
|
}
|
||||||
|
if ($value_other_test !== '') {
|
||||||
|
$package_values[] = $value_other_test;
|
||||||
|
}
|
||||||
|
if ($value_only_colorfastness !== '') {
|
||||||
|
$package_values[] = $value_only_colorfastness;
|
||||||
|
}
|
||||||
|
if ($value_only_chemical !== '') {
|
||||||
|
$package_values[] = $value_only_chemical;
|
||||||
|
}
|
||||||
|
|
||||||
|
$package_values = array_unique($package_values);
|
||||||
|
|
||||||
|
$excelData[$index]['data'][$package_index] = implode(' - ', $package_values);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("Routine part 2 completata - Merge package applicato su " . count($excelData) . " righe");
|
||||||
|
|
||||||
|
if (!$part1_applied) {
|
||||||
|
error_log("Warning finale: routine part 1 non applicata, routine part 2 applicata correttamente");
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("Routine Moncler completata con successo");
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
error_log("Eccezione nella routine Moncler: " . $e->getMessage());
|
error_log("Eccezione nella routine Moncler: " . $e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
|
|||||||
@@ -0,0 +1,327 @@
|
|||||||
|
<?php
|
||||||
|
ini_set('log_errors', 1);
|
||||||
|
ini_set('error_log', __DIR__ . '/routine_debug.log');
|
||||||
|
|
||||||
|
function applyRoutine(&$excelData, $routineData)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Initial log
|
||||||
|
error_log("Inizio esecuzione routine Moncler: " . date('Y-m-d H:i:s'));
|
||||||
|
error_log("Dati routine: " . print_r($routineData, true));
|
||||||
|
error_log("Dati excel_data: " . print_r($excelData, true));
|
||||||
|
|
||||||
|
// Check if excelData is empty
|
||||||
|
if (empty($excelData)) {
|
||||||
|
throw new Exception("excelData è vuoto o non valido.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract routine settings with default values
|
||||||
|
$action1 = trim($routineData['action1'] ?? 'K');
|
||||||
|
$action2 = trim($routineData['action2'] ?? 'STYLE CODE + STYLE DESCRIPTION');
|
||||||
|
$action3 = trim($routineData['action3'] ?? 'STYLE CODE');
|
||||||
|
$action4 = trim($routineData['action4'] ?? 'STYLE DESCRIPTION');
|
||||||
|
$headers = $routineData['xls_headers'] ?? [];
|
||||||
|
|
||||||
|
// Package-related headers
|
||||||
|
$package_header = 'PACKAGE';
|
||||||
|
$other_test_header = 'Other test from Control Matrix';
|
||||||
|
$only_colorfastness_header = 'Only Colorfastness package';
|
||||||
|
$only_chemical_header = 'Only Chemical package';
|
||||||
|
|
||||||
|
if (empty($headers)) {
|
||||||
|
throw new Exception("Nessun header trovato per la routine Moncler.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If headers arrive as JSON string, decode them
|
||||||
|
if (!is_array($headers) && is_string($headers)) {
|
||||||
|
$decoded_headers = json_decode($headers, true);
|
||||||
|
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded_headers)) {
|
||||||
|
$headers = $decoded_headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_array($headers) || empty($headers)) {
|
||||||
|
throw new Exception("Gli header della routine non sono validi.");
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("Header ricevuti: " . print_r($headers, true));
|
||||||
|
|
||||||
|
// Normalize headers
|
||||||
|
$normalized_headers = array_map(function ($header) {
|
||||||
|
return trim((string)$header);
|
||||||
|
}, $headers);
|
||||||
|
|
||||||
|
error_log("Header normalizzati: " . print_r($normalized_headers, true));
|
||||||
|
error_log("Action values - action1: '$action1', action2: '$action2', action3: '$action3', action4: '$action4'");
|
||||||
|
|
||||||
|
// Find main indexes
|
||||||
|
$action1_index = array_search($action1, $normalized_headers, true);
|
||||||
|
$action2_index = array_search($action2, $normalized_headers, true);
|
||||||
|
$action3_index = array_search($action3, $normalized_headers, true);
|
||||||
|
$action4_index = array_search($action4, $normalized_headers, true);
|
||||||
|
|
||||||
|
// Find package-related column indexes
|
||||||
|
$package_index = array_search($package_header, $normalized_headers, true);
|
||||||
|
$other_test_index = array_search($other_test_header, $normalized_headers, true);
|
||||||
|
$only_colorfastness_index = array_search($only_colorfastness_header, $normalized_headers, true);
|
||||||
|
$only_chemical_index = array_search($only_chemical_header, $normalized_headers, true);
|
||||||
|
|
||||||
|
if ($package_index === false) {
|
||||||
|
throw new Exception("Colonna PACKAGE non trovata.");
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log(
|
||||||
|
"Indici colonne - action1: " . var_export($action1_index, true) .
|
||||||
|
", action2: " . var_export($action2_index, true) .
|
||||||
|
", action3: " . var_export($action3_index, true) .
|
||||||
|
", action4: " . var_export($action4_index, true) .
|
||||||
|
", PACKAGE: " . var_export($package_index, true) .
|
||||||
|
", Other test from Control Matrix: " . var_export($other_test_index, true) .
|
||||||
|
", Only Colorfastness package: " . var_export($only_colorfastness_index, true) .
|
||||||
|
", Only Chemical package: " . var_export($only_chemical_index, true)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Helper: split a value already joined by " - "
|
||||||
|
$splitJoinedValues = function ($value) {
|
||||||
|
$value = trim((string)$value);
|
||||||
|
if ($value === '') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = preg_split('/\s*-\s*/', $value);
|
||||||
|
$parts = array_map('trim', $parts);
|
||||||
|
$parts = array_filter($parts, function ($item) {
|
||||||
|
return $item !== '';
|
||||||
|
});
|
||||||
|
|
||||||
|
return array_values(array_unique($parts));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper: join unique values with " - "
|
||||||
|
$joinUniqueValues = function (array $values) {
|
||||||
|
$clean = [];
|
||||||
|
foreach ($values as $value) {
|
||||||
|
$value = trim((string)$value);
|
||||||
|
if ($value !== '' && !in_array($value, $clean, true)) {
|
||||||
|
$clean[] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return implode(' - ', $clean);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper: split STYLE CODE + STYLE DESCRIPTION into code + description
|
||||||
|
$splitStyleCombined = function ($value) {
|
||||||
|
$value = trim((string)$value);
|
||||||
|
|
||||||
|
if ($value === '') {
|
||||||
|
return ['', ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept both "CODE - DESC" and "CODE-DESC"
|
||||||
|
$parts = preg_split('/\s*-\s*/', $value, 2);
|
||||||
|
|
||||||
|
$styleCode = trim((string)($parts[0] ?? ''));
|
||||||
|
$styleDescription = trim((string)($parts[1] ?? ''));
|
||||||
|
|
||||||
|
return [$styleCode, $styleDescription];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* STEP 0
|
||||||
|
* Normalize STYLE CODE and STYLE DESCRIPTION before grouping by K
|
||||||
|
*
|
||||||
|
* Rules:
|
||||||
|
* - Read STYLE CODE + STYLE DESCRIPTION
|
||||||
|
* - If it contains code/description, fill missing values
|
||||||
|
* - If target field already contains a different value, add it instead of replacing it
|
||||||
|
* - Avoid duplicates
|
||||||
|
*/
|
||||||
|
$step0_applied = false;
|
||||||
|
|
||||||
|
if ($action2_index === false || $action3_index === false || $action4_index === false) {
|
||||||
|
error_log(
|
||||||
|
"Unable to apply routine step 0. Missing columns - action2: '$action2' (index: " . var_export($action2_index, true) . ")" .
|
||||||
|
", action3: '$action3' (index: " . var_export($action3_index, true) . ")" .
|
||||||
|
", action4: '$action4' (index: " . var_export($action4_index, true) . ")"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
foreach ($excelData as $index => $row) {
|
||||||
|
if (!isset($row['data']) || !is_array($row['data'])) {
|
||||||
|
error_log("Riga non valida nello step 0, manca 'data' all'indice $index");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$combinedValue = trim((string)($row['data'][$action2_index] ?? ''));
|
||||||
|
$currentStyleCode = trim((string)($row['data'][$action3_index] ?? ''));
|
||||||
|
$currentStyleDescription = trim((string)($row['data'][$action4_index] ?? ''));
|
||||||
|
|
||||||
|
if ($combinedValue === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$parsedStyleCode, $parsedStyleDescription] = $splitStyleCombined($combinedValue);
|
||||||
|
|
||||||
|
$styleCodeValues = $splitJoinedValues($currentStyleCode);
|
||||||
|
$styleDescriptionValues = $splitJoinedValues($currentStyleDescription);
|
||||||
|
|
||||||
|
if ($parsedStyleCode !== '' && !in_array($parsedStyleCode, $styleCodeValues, true)) {
|
||||||
|
$styleCodeValues[] = $parsedStyleCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($parsedStyleDescription !== '' && !in_array($parsedStyleDescription, $styleDescriptionValues, true)) {
|
||||||
|
$styleDescriptionValues[] = $parsedStyleDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
$excelData[$index]['data'][$action3_index] = $joinUniqueValues($styleCodeValues);
|
||||||
|
$excelData[$index]['data'][$action4_index] = $joinUniqueValues($styleDescriptionValues);
|
||||||
|
|
||||||
|
error_log(
|
||||||
|
"Step 0 - excelrow " . ($row['excelrow'] ?? 'N/A') .
|
||||||
|
" | combined: '" . $combinedValue . "'" .
|
||||||
|
" | style_code_result: '" . $excelData[$index]['data'][$action3_index] . "'" .
|
||||||
|
" | style_description_result: '" . $excelData[$index]['data'][$action4_index] . "'"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$step0_applied = true;
|
||||||
|
error_log("Routine step 0 completato - Normalizzazione style applicata su " . count($excelData) . " righe");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* STEP 1
|
||||||
|
* Aggregate rows by action1 using normalized STYLE CODE and STYLE DESCRIPTION
|
||||||
|
* If it fails, continue with STEP 2 only
|
||||||
|
*/
|
||||||
|
$step1_applied = false;
|
||||||
|
|
||||||
|
if ($action1_index === false || $action3_index === false || $action4_index === false) {
|
||||||
|
error_log(
|
||||||
|
"Unable to apply routine step 1. Package merge logic has been applied only. " .
|
||||||
|
"Missing columns - action1: '$action1' (index: " . var_export($action1_index, true) . ")" .
|
||||||
|
", action3: '$action3' (index: " . var_export($action3_index, true) . ")" .
|
||||||
|
", action4: '$action4' (index: " . var_export($action4_index, true) . ")"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$grouped_data = [];
|
||||||
|
|
||||||
|
foreach ($excelData as $row) {
|
||||||
|
if (!isset($row['data']) || !is_array($row['data'])) {
|
||||||
|
error_log("Riga non valida, manca 'data' per excelrow " . ($row['excelrow'] ?? 'N/A'));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = $row['data'][$action1_index] ?? '';
|
||||||
|
$key = trim((string)$key);
|
||||||
|
$key = $key === '' ? '_empty_' : $key;
|
||||||
|
|
||||||
|
if (!isset($grouped_data[$key])) {
|
||||||
|
$grouped_data[$key] = [
|
||||||
|
'data' => $row['data'],
|
||||||
|
'excelrow' => [($row['excelrow'] ?? '')],
|
||||||
|
'style_codes' => [],
|
||||||
|
'style_descriptions' => []
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$grouped_data[$key]['excelrow'][] = ($row['excelrow'] ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect normalized STYLE CODE values
|
||||||
|
$styleCodeValues = $splitJoinedValues($row['data'][$action3_index] ?? '');
|
||||||
|
foreach ($styleCodeValues as $styleCodeValue) {
|
||||||
|
if (!in_array($styleCodeValue, $grouped_data[$key]['style_codes'], true)) {
|
||||||
|
$grouped_data[$key]['style_codes'][] = $styleCodeValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect normalized STYLE DESCRIPTION values
|
||||||
|
$styleDescriptionValues = $splitJoinedValues($row['data'][$action4_index] ?? '');
|
||||||
|
foreach ($styleDescriptionValues as $styleDescriptionValue) {
|
||||||
|
if (!in_array($styleDescriptionValue, $grouped_data[$key]['style_descriptions'], true)) {
|
||||||
|
$grouped_data[$key]['style_descriptions'][] = $styleDescriptionValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_excel_data = [];
|
||||||
|
|
||||||
|
foreach ($grouped_data as $key => $group) {
|
||||||
|
$row_data = $group['data'];
|
||||||
|
|
||||||
|
// Update STYLE CODE and STYLE DESCRIPTION with aggregated unique values
|
||||||
|
$row_data[$action3_index] = $joinUniqueValues($group['style_codes']);
|
||||||
|
$row_data[$action4_index] = $joinUniqueValues($group['style_descriptions']);
|
||||||
|
|
||||||
|
// Concatenate excelrow values with "+"
|
||||||
|
$excelrow_clean = array_filter($group['excelrow'], function ($value) {
|
||||||
|
return $value !== null && $value !== '';
|
||||||
|
});
|
||||||
|
|
||||||
|
$excelrow_value = count($excelrow_clean) > 1
|
||||||
|
? implode('+', $excelrow_clean)
|
||||||
|
: (reset($excelrow_clean) ?: '');
|
||||||
|
|
||||||
|
$new_excel_data[] = [
|
||||||
|
'data' => $row_data,
|
||||||
|
'excelrow' => $excelrow_value
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$excelData = $new_excel_data;
|
||||||
|
$step1_applied = true;
|
||||||
|
|
||||||
|
error_log("Routine step 1 completato - Righe aggregate: " . count($new_excel_data));
|
||||||
|
error_log("Excelrow aggregati step 1: " . print_r(array_column($new_excel_data, 'excelrow'), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* STEP 2
|
||||||
|
* Merge package-related columns into PACKAGE
|
||||||
|
* Always applied if PACKAGE exists
|
||||||
|
*/
|
||||||
|
foreach ($excelData as $index => $row) {
|
||||||
|
if (!isset($row['data']) || !is_array($row['data'])) {
|
||||||
|
error_log("Riga non valida nello step 2, manca 'data' all'indice $index");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$package_values = [];
|
||||||
|
|
||||||
|
$value_package = trim((string)($row['data'][$package_index] ?? ''));
|
||||||
|
$value_other_test = $other_test_index !== false ? trim((string)($row['data'][$other_test_index] ?? '')) : '';
|
||||||
|
$value_only_colorfastness = $only_colorfastness_index !== false ? trim((string)($row['data'][$only_colorfastness_index] ?? '')) : '';
|
||||||
|
$value_only_chemical = $only_chemical_index !== false ? trim((string)($row['data'][$only_chemical_index] ?? '')) : '';
|
||||||
|
|
||||||
|
if ($value_package !== '') {
|
||||||
|
$package_values[] = $value_package;
|
||||||
|
}
|
||||||
|
if ($value_other_test !== '') {
|
||||||
|
$package_values[] = $value_other_test;
|
||||||
|
}
|
||||||
|
if ($value_only_colorfastness !== '') {
|
||||||
|
$package_values[] = $value_only_colorfastness;
|
||||||
|
}
|
||||||
|
if ($value_only_chemical !== '') {
|
||||||
|
$package_values[] = $value_only_chemical;
|
||||||
|
}
|
||||||
|
|
||||||
|
$package_values = array_unique($package_values);
|
||||||
|
|
||||||
|
$excelData[$index]['data'][$package_index] = implode(' - ', $package_values);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("Routine step 2 completato - Merge package applicato su " . count($excelData) . " righe");
|
||||||
|
|
||||||
|
if (!$step0_applied) {
|
||||||
|
error_log("Warning finale: routine step 0 non applicata");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$step1_applied) {
|
||||||
|
error_log("Warning finale: routine step 1 non applicata, routine step 2 applicata correttamente");
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("Routine Moncler completata con successo");
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Eccezione nella routine Moncler: " . $e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
<?php
|
||||||
|
ini_set('log_errors', 1);
|
||||||
|
ini_set('error_log', __DIR__ . '/routine_debug.log');
|
||||||
|
|
||||||
|
function applyRoutine(&$excelData, $routineData)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Initial log
|
||||||
|
error_log("Inizio esecuzione nuova routine: " . date('Y-m-d H:i:s'));
|
||||||
|
error_log("Dati routine: " . print_r($routineData, true));
|
||||||
|
error_log("Dati excel_data: " . print_r($excelData, true));
|
||||||
|
|
||||||
|
// Check if excelData is empty
|
||||||
|
if (empty($excelData)) {
|
||||||
|
throw new Exception("excelData è vuoto o non valido.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get headers from routine data
|
||||||
|
$headers = $routineData['xls_headers'] ?? [];
|
||||||
|
|
||||||
|
if (empty($headers)) {
|
||||||
|
throw new Exception("Nessun header trovato per la routine.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If headers arrive as JSON string, decode them
|
||||||
|
if (!is_array($headers) && is_string($headers)) {
|
||||||
|
$decoded_headers = json_decode($headers, true);
|
||||||
|
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded_headers)) {
|
||||||
|
$headers = $decoded_headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_array($headers) || empty($headers)) {
|
||||||
|
throw new Exception("Gli header della routine non sono validi.");
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("Header ricevuti: " . print_r($headers, true));
|
||||||
|
|
||||||
|
// Normalize headers
|
||||||
|
$normalized_headers = array_map(function ($header) {
|
||||||
|
return trim((string)$header);
|
||||||
|
}, $headers);
|
||||||
|
|
||||||
|
error_log("Header normalizzati: " . print_r($normalized_headers, true));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define source and target headers
|
||||||
|
*/
|
||||||
|
$parte_moncler_header = 'PARTE MONCLER';
|
||||||
|
$parte_fornitore_header = 'PARTE FORNITORE';
|
||||||
|
|
||||||
|
$descr_parte_moncler_header = 'DESCRIZIONE PARTE MONCLER';
|
||||||
|
$descr_parte_fornitore_header = 'DESCRIZIONE PARTE FORNITORE';
|
||||||
|
|
||||||
|
$colore_moncler_header = 'COLORE MONCLER';
|
||||||
|
$colore_fornitore_header = 'COLORE FORNITORE';
|
||||||
|
|
||||||
|
$composizione_tessile_header = 'COMPOSIZIONE TESSILE';
|
||||||
|
$composizione_totale_header = 'COMPOSIZIONE TOTALE';
|
||||||
|
|
||||||
|
$pacchetti_test_header = 'PACCHETTI TEST';
|
||||||
|
$test_addizionali_header = 'TEST ADDIZIONALI (A5 solo se richiesti dal cliente)';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find indexes
|
||||||
|
*/
|
||||||
|
$parte_moncler_index = array_search($parte_moncler_header, $normalized_headers, true);
|
||||||
|
$parte_fornitore_index = array_search($parte_fornitore_header, $normalized_headers, true);
|
||||||
|
|
||||||
|
$descr_parte_moncler_index = array_search($descr_parte_moncler_header, $normalized_headers, true);
|
||||||
|
$descr_parte_fornitore_index = array_search($descr_parte_fornitore_header, $normalized_headers, true);
|
||||||
|
|
||||||
|
$colore_moncler_index = array_search($colore_moncler_header, $normalized_headers, true);
|
||||||
|
$colore_fornitore_index = array_search($colore_fornitore_header, $normalized_headers, true);
|
||||||
|
|
||||||
|
$composizione_tessile_index = array_search($composizione_tessile_header, $normalized_headers, true);
|
||||||
|
$composizione_totale_index = array_search($composizione_totale_header, $normalized_headers, true);
|
||||||
|
|
||||||
|
$pacchetti_test_index = array_search($pacchetti_test_header, $normalized_headers, true);
|
||||||
|
$test_addizionali_index = array_search($test_addizionali_header, $normalized_headers, true);
|
||||||
|
|
||||||
|
error_log(
|
||||||
|
"Indici trovati - " .
|
||||||
|
"PARTE MONCLER: " . var_export($parte_moncler_index, true) .
|
||||||
|
", PARTE FORNITORE: " . var_export($parte_fornitore_index, true) .
|
||||||
|
", DESCRIZIONE PARTE MONCLER: " . var_export($descr_parte_moncler_index, true) .
|
||||||
|
", DESCRIZIONE PARTE FORNITORE: " . var_export($descr_parte_fornitore_index, true) .
|
||||||
|
", COLORE MONCLER: " . var_export($colore_moncler_index, true) .
|
||||||
|
", COLORE FORNITORE: " . var_export($colore_fornitore_index, true) .
|
||||||
|
", COMPOSIZIONE TESSILE: " . var_export($composizione_tessile_index, true) .
|
||||||
|
", COMPOSIZIONE TOTALE: " . var_export($composizione_totale_index, true) .
|
||||||
|
", PACCHETTI TEST: " . var_export($pacchetti_test_index, true) .
|
||||||
|
", TEST ADDIZIONALI: " . var_export($test_addizionali_index, true)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Required target columns must exist
|
||||||
|
if ($parte_moncler_index === false) {
|
||||||
|
throw new Exception("Colonna PARTE MONCLER non trovata.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($descr_parte_moncler_index === false) {
|
||||||
|
throw new Exception("Colonna DESCRIZIONE PARTE MONCLER non trovata.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($colore_moncler_index === false) {
|
||||||
|
throw new Exception("Colonna COLORE MONCLER non trovata.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($composizione_tessile_index === false) {
|
||||||
|
throw new Exception("Colonna COMPOSIZIONE TESSILE non trovata.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($pacchetti_test_index === false) {
|
||||||
|
throw new Exception("Colonna PACCHETTI TEST non trovata.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply merge row by row
|
||||||
|
*/
|
||||||
|
foreach ($excelData as $index => $row) {
|
||||||
|
if (!isset($row['data']) || !is_array($row['data'])) {
|
||||||
|
error_log("Riga non valida, manca 'data' all'indice $index");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. PARTE MONCLER + PARTE FORNITORE -> PARTE MONCLER
|
||||||
|
$parte_values = [];
|
||||||
|
|
||||||
|
$value_parte_moncler = trim((string)($row['data'][$parte_moncler_index] ?? ''));
|
||||||
|
$value_parte_fornitore = $parte_fornitore_index !== false ? trim((string)($row['data'][$parte_fornitore_index] ?? '')) : '';
|
||||||
|
|
||||||
|
if ($value_parte_moncler !== '') {
|
||||||
|
$parte_values[] = $value_parte_moncler;
|
||||||
|
}
|
||||||
|
if ($value_parte_fornitore !== '') {
|
||||||
|
$parte_values[] = $value_parte_fornitore;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parte_values = array_unique($parte_values);
|
||||||
|
$excelData[$index]['data'][$parte_moncler_index] = implode(' - ', $parte_values);
|
||||||
|
|
||||||
|
// 2. DESCRIZIONE PARTE MONCLER + DESCRIZIONE PARTE FORNITORE -> DESCRIZIONE PARTE MONCLER
|
||||||
|
$descr_values = [];
|
||||||
|
|
||||||
|
$value_descr_moncler = trim((string)($row['data'][$descr_parte_moncler_index] ?? ''));
|
||||||
|
$value_descr_fornitore = $descr_parte_fornitore_index !== false ? trim((string)($row['data'][$descr_parte_fornitore_index] ?? '')) : '';
|
||||||
|
|
||||||
|
if ($value_descr_moncler !== '') {
|
||||||
|
$descr_values[] = $value_descr_moncler;
|
||||||
|
}
|
||||||
|
if ($value_descr_fornitore !== '') {
|
||||||
|
$descr_values[] = $value_descr_fornitore;
|
||||||
|
}
|
||||||
|
|
||||||
|
$descr_values = array_unique($descr_values);
|
||||||
|
$excelData[$index]['data'][$descr_parte_moncler_index] = implode(' - ', $descr_values);
|
||||||
|
|
||||||
|
// 3. COLORE MONCLER + COLORE FORNITORE -> COLORE MONCLER
|
||||||
|
$colore_values = [];
|
||||||
|
|
||||||
|
$value_colore_moncler = trim((string)($row['data'][$colore_moncler_index] ?? ''));
|
||||||
|
$value_colore_fornitore = $colore_fornitore_index !== false ? trim((string)($row['data'][$colore_fornitore_index] ?? '')) : '';
|
||||||
|
|
||||||
|
if ($value_colore_moncler !== '') {
|
||||||
|
$colore_values[] = $value_colore_moncler;
|
||||||
|
}
|
||||||
|
if ($value_colore_fornitore !== '') {
|
||||||
|
$colore_values[] = $value_colore_fornitore;
|
||||||
|
}
|
||||||
|
|
||||||
|
$colore_values = array_unique($colore_values);
|
||||||
|
$excelData[$index]['data'][$colore_moncler_index] = implode(' - ', $colore_values);
|
||||||
|
|
||||||
|
// 4. COMPOSIZIONE TESSILE + COMPOSIZIONE TOTALE -> COMPOSIZIONE TESSILE
|
||||||
|
$composizione_values = [];
|
||||||
|
|
||||||
|
$value_composizione_tessile = trim((string)($row['data'][$composizione_tessile_index] ?? ''));
|
||||||
|
$value_composizione_totale = $composizione_totale_index !== false ? trim((string)($row['data'][$composizione_totale_index] ?? '')) : '';
|
||||||
|
|
||||||
|
if ($value_composizione_tessile !== '') {
|
||||||
|
$composizione_values[] = $value_composizione_tessile;
|
||||||
|
}
|
||||||
|
if ($value_composizione_totale !== '') {
|
||||||
|
$composizione_values[] = $value_composizione_totale;
|
||||||
|
}
|
||||||
|
|
||||||
|
$composizione_values = array_unique($composizione_values);
|
||||||
|
$excelData[$index]['data'][$composizione_tessile_index] = implode(' - ', $composizione_values);
|
||||||
|
|
||||||
|
// 5. PACCHETTI TEST + TEST ADDIZIONALI -> PACCHETTI TEST
|
||||||
|
$pacchetti_values = [];
|
||||||
|
|
||||||
|
$value_pacchetti_test = trim((string)($row['data'][$pacchetti_test_index] ?? ''));
|
||||||
|
$value_test_addizionali = $test_addizionali_index !== false ? trim((string)($row['data'][$test_addizionali_index] ?? '')) : '';
|
||||||
|
|
||||||
|
if ($value_pacchetti_test !== '') {
|
||||||
|
$pacchetti_values[] = $value_pacchetti_test;
|
||||||
|
}
|
||||||
|
if ($value_test_addizionali !== '') {
|
||||||
|
$pacchetti_values[] = $value_test_addizionali;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pacchetti_values = array_unique($pacchetti_values);
|
||||||
|
$excelData[$index]['data'][$pacchetti_test_index] = implode(' - ', $pacchetti_values);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("Nuova routine completata con successo. Righe elaborate: " . count($excelData));
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Eccezione nella nuova routine: " . $e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
/**
|
||||||
|
* saveAll.js — Save All functionality using gridData
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
let saveAllRunning = false;
|
||||||
|
|
||||||
|
function isBusy() {
|
||||||
|
return saveAllRunning || window.batchRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Save single row ──────────────────────────────────────────────────
|
||||||
|
$(document).on('click', '.save-btn', async function() {
|
||||||
|
const btn = this;
|
||||||
|
const rowIndex = parseInt(btn.dataset.row);
|
||||||
|
const row = window.gridData?.[rowIndex];
|
||||||
|
if (!row) return;
|
||||||
|
|
||||||
|
const origHtml = btn.innerHTML;
|
||||||
|
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
||||||
|
btn.disabled = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const formData = window.buildSavePayload(rowIndex);
|
||||||
|
const resp = await fetch('save_edited_row.php', { method: 'POST', body: formData });
|
||||||
|
const result = await resp.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
row._dirty = false;
|
||||||
|
if (window.gridRenderer?.clearDirty) window.gridRenderer.clearDirty(rowIndex);
|
||||||
|
// Flash success on row without re-rendering (preserves Select2 state)
|
||||||
|
const gridRow = document.querySelector(`.grid-row[data-id="${row.iddatadb}"]`);
|
||||||
|
if (gridRow) {
|
||||||
|
gridRow.classList.remove('row-dirty');
|
||||||
|
gridRow.querySelectorAll('.grid-cell').forEach(cell => {
|
||||||
|
cell.classList.remove('cell-changed');
|
||||||
|
cell.classList.add('flash-success');
|
||||||
|
});
|
||||||
|
setTimeout(() => gridRow.querySelectorAll('.flash-success').forEach(c => c.classList.remove('flash-success')), 500);
|
||||||
|
}
|
||||||
|
const toastEl = document.getElementById('saveSuccessToast');
|
||||||
|
if (toastEl) bootstrap.Toast.getOrCreateInstance(toastEl).show();
|
||||||
|
} else {
|
||||||
|
alert('Errore: ' + result.message);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
alert('Errore: ' + e.message);
|
||||||
|
} finally {
|
||||||
|
btn.innerHTML = origHtml;
|
||||||
|
btn.disabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Save All ─────────────────────────────────────────────────────────
|
||||||
|
$(document).on('click', '.save-all-btn', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (isBusy()) return;
|
||||||
|
const modalEl = document.getElementById('saveAllConfirmModal');
|
||||||
|
if (!modalEl) return;
|
||||||
|
new bootstrap.Modal(modalEl, { keyboard: false }).show();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '#saveAllConfirmBtn', async function() {
|
||||||
|
const confirmModal = bootstrap.Modal.getInstance(document.getElementById('saveAllConfirmModal'));
|
||||||
|
if (confirmModal) confirmModal.hide();
|
||||||
|
saveAllRunning = true;
|
||||||
|
|
||||||
|
const bar = document.getElementById('batchExportBar');
|
||||||
|
const statusEl = document.getElementById('batchExportStatus');
|
||||||
|
const cancelBtn = document.getElementById('exportBatchCancelBtn');
|
||||||
|
if (bar) bar.style.display = '';
|
||||||
|
if (cancelBtn) cancelBtn.style.display = 'none';
|
||||||
|
if (statusEl) statusEl.textContent = 'Saving...';
|
||||||
|
|
||||||
|
const data = window.gridData || [];
|
||||||
|
const dirtyRows = data.map((r, i) => r._dirty ? i : -1).filter(i => i >= 0);
|
||||||
|
|
||||||
|
if (dirtyRows.length === 0) {
|
||||||
|
saveAllRunning = false;
|
||||||
|
if (bar) bar.style.display = 'none';
|
||||||
|
const msgEl = document.getElementById('saveAllResultMessage');
|
||||||
|
if (msgEl) msgEl.textContent = 'No changes to save.';
|
||||||
|
new bootstrap.Modal(document.getElementById('saveAllResultModal')).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let success = 0, fail = 0;
|
||||||
|
|
||||||
|
for (const idx of dirtyRows) {
|
||||||
|
if (statusEl) statusEl.textContent = `Saving ${success + fail + 1} / ${dirtyRows.length}...`;
|
||||||
|
try {
|
||||||
|
const formData = window.buildSavePayload(idx);
|
||||||
|
const resp = await fetch('save_edited_row.php', { method: 'POST', body: formData });
|
||||||
|
const result = await resp.json();
|
||||||
|
if (result.success) {
|
||||||
|
data[idx]._dirty = false;
|
||||||
|
if (window.gridRenderer?.clearDirty) window.gridRenderer.clearDirty(idx);
|
||||||
|
success++;
|
||||||
|
} else {
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveAllRunning = false;
|
||||||
|
if (bar) bar.style.display = 'none';
|
||||||
|
|
||||||
|
const gr = window.gridRenderer;
|
||||||
|
if (gr) gr.renderVisibleRows();
|
||||||
|
|
||||||
|
const msg = `Saved: ${success}` + (fail > 0 ? `, Errors: ${fail}` : '');
|
||||||
|
const msgEl = document.getElementById('saveAllResultMessage');
|
||||||
|
if (msgEl) msgEl.textContent = msg;
|
||||||
|
new bootstrap.Modal(document.getElementById('saveAllResultModal')).show();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── beforeunload ─────────────────────────────────────────────────────
|
||||||
|
window.addEventListener('beforeunload', function(e) {
|
||||||
|
if (window.gridData && window.gridData.some(r => r._dirty)) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.returnValue = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Public
|
||||||
|
window.saveAllRunning = () => saveAllRunning;
|
||||||
|
})();
|
||||||
@@ -23,7 +23,7 @@ try {
|
|||||||
foreach ($_POST as $key => $value) {
|
foreach ($_POST as $key => $value) {
|
||||||
if ($key !== 'iddatadb' && !in_array($key, $excludeFields)) {
|
if ($key !== 'iddatadb' && !in_array($key, $excludeFields)) {
|
||||||
$updates[] = "$key = ?";
|
$updates[] = "$key = ?";
|
||||||
$values[] = htmlspecialchars($value);
|
$values[] = $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$values[] = $iddatadb;
|
$values[] = $iddatadb;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ try {
|
|||||||
|
|
||||||
$iddatadb = intval($_POST['iddatadb']);
|
$iddatadb = intval($_POST['iddatadb']);
|
||||||
$idclient = isset($_POST['idclient']) ? (is_numeric($_POST['idclient']) ? intval($_POST['idclient']) : null) : null;
|
$idclient = isset($_POST['idclient']) ? (is_numeric($_POST['idclient']) ? intval($_POST['idclient']) : null) : null;
|
||||||
|
$clienteFornitoreId = isset($_POST['cliente_fornitore_id']) ? (is_numeric($_POST['cliente_fornitore_id']) ? intval($_POST['cliente_fornitore_id']) : null) : null;
|
||||||
|
|
||||||
$db = DBHandlerSelect::getInstance();
|
$db = DBHandlerSelect::getInstance();
|
||||||
$pdo = $db->getConnection();
|
$pdo = $db->getConnection();
|
||||||
@@ -22,10 +23,11 @@ try {
|
|||||||
// (NON tocchiamo MySQL, gestiamo solo qui la traduzione)
|
// (NON tocchiamo MySQL, gestiamo solo qui la traduzione)
|
||||||
$fixedAliasMap = [
|
$fixedAliasMap = [
|
||||||
'ClienteResponsabile' => 'cliente_responsabile_id',
|
'ClienteResponsabile' => 'cliente_responsabile_id',
|
||||||
|
'ClienteFornitore' => 'cliente_fornitore_id',
|
||||||
|
'ClienteAnalisi' => 'clienteAnalisi',
|
||||||
'MoltiplicatorePrezzo' => 'moltiplicatore_prezzo_id',
|
'MoltiplicatorePrezzo' => 'moltiplicatore_prezzo_id',
|
||||||
'AnagraficaCertestObject' => 'anagrafica_certest_object_id',
|
'AnagraficaCertestObject' => 'anagrafica_certest_object_id',
|
||||||
'AnagraficaCertestService' => 'anagrafica_certest_service_id',
|
'AnagraficaCertestService' => 'anagrafica_certest_service_id',
|
||||||
'ClienteFornitore' => 'cliente_fornitore_id',
|
|
||||||
'ConsegnaRichiesta' => 'consegna_richiesta',
|
'ConsegnaRichiesta' => 'consegna_richiesta',
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -137,6 +139,12 @@ try {
|
|||||||
$params[] = $idclient;
|
$params[] = $idclient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5a2) cliente_fornitore_id (se presente)
|
||||||
|
if (isset($clienteFornitoreId)) {
|
||||||
|
$setParts[] = "cliente_fornitore_id = ?";
|
||||||
|
$params[] = $clienteFornitoreId;
|
||||||
|
}
|
||||||
|
|
||||||
// 5b) fixed fields dal POST
|
// 5b) fixed fields dal POST
|
||||||
// QUI è il punto chiave: accettiamo SOLO colonne reali (realWhitelist),
|
// QUI è il punto chiave: accettiamo SOLO colonne reali (realWhitelist),
|
||||||
// ma se dal JS arrivassero ancora key logiche, funzionerebbe uguale
|
// ma se dal JS arrivassero ancora key logiche, funzionerebbe uguale
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ include('include/headscript.php');
|
|||||||
|
|
||||||
$dbHandler = DBHandlerSelect::getInstance();
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
$pdo = $dbHandler->getConnection();
|
$pdo = $dbHandler->getConnection();
|
||||||
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
|
||||||
$data = json_decode(file_get_contents('php://input'), true);
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
@@ -19,6 +20,21 @@ try {
|
|||||||
$pdo->beginTransaction();
|
$pdo->beginTransaction();
|
||||||
$results = [];
|
$results = [];
|
||||||
|
|
||||||
|
// Custom fields statements (child table)
|
||||||
|
$stmtUpsertCF = $pdo->prepare("
|
||||||
|
INSERT INTO identification_parts_customfields (part_id, field_id, value_id, value_text)
|
||||||
|
VALUES (:part_id, :field_id, :value_id, :value_text)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
value_id = VALUES(value_id),
|
||||||
|
value_text = VALUES(value_text),
|
||||||
|
updated_at = NOW()
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmtDeleteCF = $pdo->prepare("
|
||||||
|
DELETE FROM identification_parts_customfields
|
||||||
|
WHERE part_id = :part_id AND field_id = :field_id
|
||||||
|
");
|
||||||
|
|
||||||
foreach ($parts as $part) {
|
foreach ($parts as $part) {
|
||||||
$partId = $part['id'] ?? null;
|
$partId = $part['id'] ?? null;
|
||||||
$partNumber = $part['part_number'] ?? null;
|
$partNumber = $part['part_number'] ?? null;
|
||||||
@@ -28,9 +44,23 @@ try {
|
|||||||
$note = $part['note'] ?? null;
|
$note = $part['note'] ?? null;
|
||||||
$dateexpiry = $part['dateexpiry'] ?? null;
|
$dateexpiry = $part['dateexpiry'] ?? null;
|
||||||
|
|
||||||
if ($partDescription || $note || $dateexpiry) {
|
// Extra field (0/1)
|
||||||
|
$extraFieldId = $part['extra_field_id'] ?? null;
|
||||||
|
$extraValueId = $part['extra_value_id'] ?? null;
|
||||||
|
$extraValueText = $part['extra_value_text'] ?? null;
|
||||||
|
|
||||||
|
// Normalizza vuoti
|
||||||
|
if ($extraFieldId !== null && $extraFieldId !== '') $extraFieldId = (int)$extraFieldId;
|
||||||
|
else $extraFieldId = null;
|
||||||
|
if ($extraValueId !== null && $extraValueId !== '') $extraValueId = (int)$extraValueId;
|
||||||
|
else $extraValueId = null;
|
||||||
|
if ($extraValueText !== null) {
|
||||||
|
$extraValueText = trim((string)$extraValueText);
|
||||||
|
if ($extraValueText === '') $extraValueText = null;
|
||||||
|
}
|
||||||
|
|
||||||
if ($partId) {
|
if ($partId) {
|
||||||
// UPDATE se la parte esiste
|
// UPDATE se la parte esiste (sempre)
|
||||||
$stmt = $pdo->prepare("UPDATE identification_parts
|
$stmt = $pdo->prepare("UPDATE identification_parts
|
||||||
SET part_number = :part_number,
|
SET part_number = :part_number,
|
||||||
part_description = :part_description,
|
part_description = :part_description,
|
||||||
@@ -49,9 +79,39 @@ try {
|
|||||||
':note' => $note,
|
':note' => $note,
|
||||||
':dateexpiry' => $dateexpiry,
|
':dateexpiry' => $dateexpiry,
|
||||||
]);
|
]);
|
||||||
$results[] = ['part_id' => $partId, 'part_number' => $partNumber, 'message' => 'Parte aggiornata con successo'];
|
|
||||||
|
// Save extra custom field (if provided)
|
||||||
|
if ($extraFieldId !== null) {
|
||||||
|
if ($extraValueId === null && $extraValueText === null) {
|
||||||
|
$stmtDeleteCF->execute([
|
||||||
|
':part_id' => $partId,
|
||||||
|
':field_id' => $extraFieldId,
|
||||||
|
]);
|
||||||
} else {
|
} else {
|
||||||
// INSERT per nuova parte
|
$stmtUpsertCF->execute([
|
||||||
|
':part_id' => $partId,
|
||||||
|
':field_id' => $extraFieldId,
|
||||||
|
':value_id' => $extraValueId,
|
||||||
|
':value_text' => $extraValueText,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$cf_row = null;
|
||||||
|
if ($extraFieldId !== null) {
|
||||||
|
$chk = $pdo->prepare("SELECT id, value_id, value_text FROM identification_parts_customfields WHERE part_id = ? AND field_id = ?");
|
||||||
|
$chk->execute([$partId, $extraFieldId]);
|
||||||
|
$cf_row = $chk->fetch(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
$results[] = [
|
||||||
|
'part_id' => $partId,
|
||||||
|
'part_number' => $partNumber,
|
||||||
|
'message' => 'Parte aggiornata con successo',
|
||||||
|
'cf_row' => $cf_row
|
||||||
|
];
|
||||||
|
} else if ($partDescription || $note || $dateexpiry) {
|
||||||
|
// INSERT per nuova parte (solo se ha contenuto)
|
||||||
$stmt = $pdo->prepare("INSERT INTO identification_parts
|
$stmt = $pdo->prepare("INSERT INTO identification_parts
|
||||||
(iddatadb, part_number, part_description, mix, idmatrice, note, dateexpiry, created_at, updated_at)
|
(iddatadb, part_number, part_description, mix, idmatrice, note, dateexpiry, created_at, updated_at)
|
||||||
VALUES (:iddatadb, :part_number, :part_description, :mix, :idmatrice, :note, :dateexpiry, NOW(), NOW())");
|
VALUES (:iddatadb, :part_number, :part_description, :mix, :idmatrice, :note, :dateexpiry, NOW(), NOW())");
|
||||||
@@ -64,14 +124,43 @@ try {
|
|||||||
':note' => $note,
|
':note' => $note,
|
||||||
':dateexpiry' => $dateexpiry,
|
':dateexpiry' => $dateexpiry,
|
||||||
]);
|
]);
|
||||||
$newId = $pdo->lastInsertId();
|
$newId = (int)$pdo->lastInsertId();
|
||||||
$results[] = ['part_id' => $newId, 'part_number' => $partNumber, 'message' => 'Parte salvata con successo'];
|
|
||||||
|
if ($extraFieldId !== null) {
|
||||||
|
if ($extraValueId === null && $extraValueText === null) {
|
||||||
|
$stmtDeleteCF->execute([
|
||||||
|
':part_id' => $newId,
|
||||||
|
':field_id' => $extraFieldId,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$stmtUpsertCF->execute([
|
||||||
|
':part_id' => $newId,
|
||||||
|
':field_id' => $extraFieldId,
|
||||||
|
':value_id' => $extraValueId,
|
||||||
|
':value_text' => $extraValueText,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$results[] = [
|
||||||
|
'part_id' => $newId,
|
||||||
|
'part_number' => $partNumber,
|
||||||
|
'message' => 'Parte salvata con successo'
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$pdo->commit();
|
$pdo->commit();
|
||||||
echo json_encode(['success' => true, 'results' => $results]);
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'results' => $results,
|
||||||
|
'debug_last' => [
|
||||||
|
'extra_field_id' => $extraFieldId ?? null,
|
||||||
|
'extra_value_id' => $extraValueId ?? null,
|
||||||
|
'extra_value_text' => $extraValueText ?? null,
|
||||||
|
'part_id' => $partId ?? ($newId ?? null),
|
||||||
|
]
|
||||||
|
]);
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
$pdo->rollBack();
|
$pdo->rollBack();
|
||||||
echo json_encode(['success' => false, 'message' => 'Errore nel salvataggio: ' . $e->getMessage()]);
|
echo json_encode(['success' => false, 'message' => 'Errore nel salvataggio: ' . $e->getMessage()]);
|
||||||
|
|||||||
@@ -761,7 +761,7 @@
|
|||||||
"IdSchemaCustomFields": 182,
|
"IdSchemaCustomFields": 182,
|
||||||
"ConteggioClienti": 0,
|
"ConteggioClienti": 0,
|
||||||
"Nome": "Ralph Lauren - All testing V.6",
|
"Nome": "Ralph Lauren - All testing V.6",
|
||||||
"Descrizione": null
|
"Descrizione": "AGGIORNAMENTO AL 16\/03\/2026 per estrazione fatturazione"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"IdSchemaCustomFields": 183,
|
"IdSchemaCustomFields": 183,
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||||
|
require_once __DIR__ . '/class/db-functions.php';
|
||||||
|
include dirname(__DIR__) . '/../extra/auth.php';
|
||||||
|
if (!Auth::check()) { http_response_code(401); echo json_encode(['error' => 'Unauthorized']); exit; }
|
||||||
|
|
||||||
|
require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
$q = mb_strtolower(trim($_GET['q'] ?? ''));
|
||||||
|
$limit = max(1, min(50, intval($_GET['limit'] ?? 20)));
|
||||||
|
$id = isset($_GET['id']) ? intval($_GET['id']) : null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Load from cache or API
|
||||||
|
$cacheFile = __DIR__ . '/cache/clienti.json';
|
||||||
|
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
||||||
|
$data = json_decode(file_get_contents($cacheFile), true);
|
||||||
|
} else {
|
||||||
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
$params = [
|
||||||
|
'$select' => 'IdCliente,Nominativo,CodiceCliente',
|
||||||
|
'$orderby' => 'Nominativo asc'
|
||||||
|
];
|
||||||
|
$data = $api->get("Cliente?" . http_build_query($params));
|
||||||
|
if (!is_dir(__DIR__ . '/cache')) mkdir(__DIR__ . '/cache', 0777, true);
|
||||||
|
file_put_contents($cacheFile, json_encode($data));
|
||||||
|
}
|
||||||
|
|
||||||
|
$clients = $data['value'] ?? [];
|
||||||
|
|
||||||
|
// If requesting by specific ID (for loading selected value)
|
||||||
|
if ($id !== null) {
|
||||||
|
foreach ($clients as $c) {
|
||||||
|
if ((int)$c['IdCliente'] === $id) {
|
||||||
|
echo json_encode(['results' => [['id' => $c['IdCliente'], 'text' => trim($c['Nominativo'] ?? '')]]]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo json_encode(['results' => []]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search by query
|
||||||
|
$results = [];
|
||||||
|
foreach ($clients as $c) {
|
||||||
|
$name = trim($c['Nominativo'] ?? '');
|
||||||
|
$code = trim($c['CodiceCliente'] ?? '');
|
||||||
|
if ($q === '' || mb_strpos(mb_strtolower($name), $q) !== false || mb_strpos(mb_strtolower($code), $q) !== false) {
|
||||||
|
$results[] = ['id' => $c['IdCliente'], 'text' => $name];
|
||||||
|
if (count($results) >= $limit) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['results' => $results]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||||
|
require_once __DIR__ . '/class/db-functions.php';
|
||||||
|
include dirname(__DIR__) . '/../extra/auth.php';
|
||||||
|
if (!Auth::check()) { http_response_code(401); echo json_encode(['error' => 'Unauthorized']); exit; }
|
||||||
|
|
||||||
|
require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
$fieldId = intval($_GET['field_id'] ?? 0);
|
||||||
|
$q = mb_strtolower(trim($_GET['q'] ?? ''));
|
||||||
|
$id = isset($_GET['id']) ? intval($_GET['id']) : null;
|
||||||
|
$limit = max(1, min(50, intval($_GET['limit'] ?? 20)));
|
||||||
|
|
||||||
|
if (!$fieldId) {
|
||||||
|
echo json_encode(['results' => []]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$cacheDir = __DIR__ . '/cache';
|
||||||
|
$cacheFile = $cacheDir . '/customfield_' . $fieldId . '.json';
|
||||||
|
|
||||||
|
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
||||||
|
$values = json_decode(file_get_contents($cacheFile), true);
|
||||||
|
} else {
|
||||||
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
$data = $api->get("CustomField($fieldId)?\$expand=CustomFieldsValues");
|
||||||
|
$values = $data['CustomFieldsValues'] ?? [];
|
||||||
|
if (!is_dir($cacheDir)) mkdir($cacheDir, 0777, true);
|
||||||
|
file_put_contents($cacheFile, json_encode($values));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup by ID
|
||||||
|
if ($id !== null) {
|
||||||
|
foreach ($values as $v) {
|
||||||
|
if ((int)($v['IdCustomFieldsValue'] ?? 0) === $id) {
|
||||||
|
echo json_encode(['results' => [['id' => $v['IdCustomFieldsValue'], 'text' => $v['Valore'] ?? '']]]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo json_encode(['results' => []]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search by query
|
||||||
|
$results = [];
|
||||||
|
foreach ($values as $v) {
|
||||||
|
$text = $v['Valore'] ?? '';
|
||||||
|
if ($q === '' || mb_strpos(mb_strtolower($text), $q) !== false) {
|
||||||
|
$results[] = ['id' => $v['IdCustomFieldsValue'], 'text' => $text];
|
||||||
|
if (count($results) >= $limit) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort alphabetically
|
||||||
|
usort($results, fn($a, $b) => strcasecmp($a['text'], $b['text']));
|
||||||
|
|
||||||
|
echo json_encode(['results' => $results]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||||
|
require_once __DIR__ . '/class/db-functions.php';
|
||||||
|
include dirname(__DIR__) . '/../extra/auth.php';
|
||||||
|
if (!Auth::check()) { http_response_code(401); echo json_encode(['error' => 'Unauthorized']); exit; }
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
|
||||||
|
$q = mb_strtolower(trim($_GET['q'] ?? ''));
|
||||||
|
$id = isset($_GET['id']) ? intval($_GET['id']) : null;
|
||||||
|
$limit = max(1, min(50, intval($_GET['limit'] ?? 20)));
|
||||||
|
$macro = trim($_GET['macro'] ?? '');
|
||||||
|
|
||||||
|
$cacheFile = __DIR__ . '/cache/matrici.json';
|
||||||
|
|
||||||
|
if (!file_exists($cacheFile)) {
|
||||||
|
// Trigger cache creation
|
||||||
|
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||||
|
require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
|
||||||
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
$data = $api->get('Matrice');
|
||||||
|
$matrici = [];
|
||||||
|
foreach (($data['value'] ?? []) as $item) {
|
||||||
|
$nome = $item['NomeMatrice'] ?? '';
|
||||||
|
if ($nome !== '' && substr($nome, 0, 1) !== '*') {
|
||||||
|
$matrici[] = ['IdMatrice' => $item['IdMatrice'], 'NomeMatrice' => $nome, 'MacroMatrice' => $item['MacroMatrice'] ?? null];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
usort($matrici, fn($a, $b) => strcasecmp($a['NomeMatrice'], $b['NomeMatrice']));
|
||||||
|
if (!is_dir(__DIR__ . '/cache')) mkdir(__DIR__ . '/cache', 0777, true);
|
||||||
|
file_put_contents($cacheFile, json_encode(['success' => true, 'value' => $matrici]));
|
||||||
|
} else {
|
||||||
|
$cached = json_decode(file_get_contents($cacheFile), true);
|
||||||
|
$matrici = $cached['value'] ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup by ID
|
||||||
|
if ($id !== null) {
|
||||||
|
foreach ($matrici as $m) {
|
||||||
|
if ((int)$m['IdMatrice'] === $id) {
|
||||||
|
echo json_encode(['results' => [['id' => $m['IdMatrice'], 'text' => $m['NomeMatrice']]]]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo json_encode(['results' => []]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return unique MacroMatrice list
|
||||||
|
if (isset($_GET['macro_list'])) {
|
||||||
|
$macros = [];
|
||||||
|
foreach ($matrici as $m) {
|
||||||
|
$mv = $m['MacroMatrice'] ?? '';
|
||||||
|
if ($mv !== '' && !in_array($mv, $macros, true)) $macros[] = $mv;
|
||||||
|
}
|
||||||
|
sort($macros);
|
||||||
|
echo json_encode(['success' => true, 'value' => $macros]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search (with optional MacroMatrice filter)
|
||||||
|
$results = [];
|
||||||
|
foreach ($matrici as $m) {
|
||||||
|
$nome = $m['NomeMatrice'] ?? '';
|
||||||
|
if ($macro !== '' && ($m['MacroMatrice'] ?? '') !== $macro) continue;
|
||||||
|
if ($q === '' || mb_strpos(mb_strtolower($nome), $q) !== false) {
|
||||||
|
$results[] = ['id' => $m['IdMatrice'], 'text' => $nome];
|
||||||
|
if (count($results) >= $limit) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['results' => $results]);
|
||||||
@@ -56,6 +56,56 @@
|
|||||||
input:checked+.slider:before {
|
input:checked+.slider:before {
|
||||||
transform: translateX(14px);
|
transform: translateX(14px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge-source {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 0.30rem 0.55rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-weight: 600;
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 50px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-source-xls {
|
||||||
|
background-color: #e7f1ff;
|
||||||
|
color: #0d6efd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-source-api {
|
||||||
|
background-color: #e8fff1;
|
||||||
|
color: #198754;
|
||||||
|
}
|
||||||
|
|
||||||
|
#xlsTemplatesTable {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#xlsTemplatesTable th,
|
||||||
|
#xlsTemplatesTable td {
|
||||||
|
vertical-align: middle;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#xlsTemplatesTable td.description-cell,
|
||||||
|
#xlsTemplatesTable td.client-cell,
|
||||||
|
#xlsTemplatesTable td.name-cell,
|
||||||
|
#xlsTemplatesTable td.button-cell {
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-actions {
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-actions .btn {
|
||||||
|
padding: 0.25rem 0.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compact-card .card-body {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -65,18 +115,20 @@
|
|||||||
<!--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">
|
||||||
<?php include('top_stat_widget.php'); ?>
|
<?php include('top_stat_widget.php'); ?>
|
||||||
|
|
||||||
<div class="card radius-10">
|
<div class="card radius-10 compact-card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="d-flex align-items-center justify-content-between">
|
<div class="d-flex align-items-center justify-content-between">
|
||||||
<h6 class="mb-0">XLS Templates Dashboard</h6>
|
<h6 class="mb-0">Templates Dashboard</h6>
|
||||||
<a href="insert_template_xls.php" class="btn btn-success ms-auto">
|
<a href="insert_template_xls.php" class="btn btn-success ms-auto">
|
||||||
<i class="fas fa-plus"></i> New Template
|
<i class="fas fa-plus"></i> New Template
|
||||||
</a>
|
</a>
|
||||||
@@ -85,22 +137,22 @@
|
|||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table id="xlsTemplatesTable" class="table table-striped table-bordered">
|
<table id="xlsTemplatesTable" class="table table-striped table-bordered table-sm w-100">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
|
||||||
<th><?= htmlspecialchars($nametemplate, ENT_QUOTES, 'UTF-8'); ?></th>
|
|
||||||
<th><?= htmlspecialchars($rowheader, ENT_QUOTES, 'UTF-8'); ?></th>
|
|
||||||
<th><?= htmlspecialchars($columnheader, ENT_QUOTES, 'UTF-8'); ?></th>
|
|
||||||
<th><?= htmlspecialchars($desctemplate, ENT_QUOTES, 'UTF-8'); ?></th>
|
|
||||||
<th>Client Name</th>
|
|
||||||
<th>Button Label</th>
|
|
||||||
<th>Status</th> <!-- Aggiunta colonna Status -->
|
|
||||||
<th><?= htmlspecialchars($action, ENT_QUOTES, 'UTF-8'); ?></th>
|
<th><?= htmlspecialchars($action, ENT_QUOTES, 'UTF-8'); ?></th>
|
||||||
|
<th><?= htmlspecialchars($nametemplate, ENT_QUOTES, 'UTF-8'); ?></th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Row</th>
|
||||||
|
<th>Col</th>
|
||||||
|
<th><?= htmlspecialchars($desctemplate, ENT_QUOTES, 'UTF-8'); ?></th>
|
||||||
|
<th>Client</th>
|
||||||
|
<th>Button</th>
|
||||||
|
<th>Status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<!-- DataTables riempirà questa sezione automaticamente -->
|
<!-- DataTables will populate this section automatically -->
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,25 +162,19 @@
|
|||||||
</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-->
|
||||||
|
|
||||||
<!-- search modal -->
|
|
||||||
<?php //include('include/searchmodal.php');
|
|
||||||
?>
|
|
||||||
<!-- end search modal -->
|
|
||||||
|
|
||||||
<!--start switcher-->
|
|
||||||
<?php //include('include/themeswitcher.php');
|
|
||||||
?>
|
|
||||||
<!--end switcher-->
|
|
||||||
<?php include('jsinclude.php'); ?>
|
<?php include('jsinclude.php'); ?>
|
||||||
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
|
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
|
||||||
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
|
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
|
||||||
@@ -139,47 +185,92 @@
|
|||||||
processing: true,
|
processing: true,
|
||||||
serverSide: false,
|
serverSide: false,
|
||||||
ajax: 'load_templates.php',
|
ajax: 'load_templates.php',
|
||||||
|
pageLength: 50,
|
||||||
|
autoWidth: false,
|
||||||
columns: [{
|
columns: [{
|
||||||
data: 'id', // ID del template
|
data: 'id',
|
||||||
title: "ID"
|
orderable: false,
|
||||||
},
|
searchable: false,
|
||||||
{
|
title: "Actions",
|
||||||
data: 'name', // Nome del template
|
className: "table-actions text-center",
|
||||||
title: "Template Name"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
data: 'header_row', // Riga degli header
|
|
||||||
title: "Header Row"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: 'start_column', // Colonna di partenza
|
|
||||||
title: "Start Column"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: 'description', // Descrizione del template
|
|
||||||
title: "Description",
|
|
||||||
defaultContent: 'No description'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: null, // Nuova colonna per Client Name e ID
|
|
||||||
title: "Client Name",
|
|
||||||
render: function(data, type, row) {
|
render: function(data, type, row) {
|
||||||
const clientName = row.clientname || "No client";
|
return `
|
||||||
const clientId = row.idclient || "N/A";
|
<div class="d-flex justify-content-center gap-1">
|
||||||
return `${clientName} (ID: ${clientId})`;
|
<a href="edit_template_xls.php?id=${data}" class="btn btn-sm btn-primary" title="Edit">
|
||||||
|
<i class="bx bx-edit-alt"></i>
|
||||||
|
</a>
|
||||||
|
<a href="mapping_template_xls_scheme2.php?id=${data}" class="btn btn-sm btn-success" title="Mapping">
|
||||||
|
<i class="bx bx-link-alt"></i>
|
||||||
|
</a>
|
||||||
|
<button class="btn btn-sm btn-danger" onclick="confirmDelete(${data})" title="Delete">
|
||||||
|
<i class="bx bx-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
data: 'button_label', // Nuova colonna per Button Label
|
data: 'name',
|
||||||
title: "Button Label",
|
title: "Template Name",
|
||||||
|
className: "name-cell"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: 'source_type',
|
||||||
|
title: "Type",
|
||||||
|
className: "text-center",
|
||||||
|
render: function(data, type, row) {
|
||||||
|
const sourceType = (data || 'XLS').toUpperCase();
|
||||||
|
|
||||||
|
if (type === 'display') {
|
||||||
|
if (sourceType === 'API') {
|
||||||
|
return '<span class="badge-source badge-source-api">API</span>';
|
||||||
|
}
|
||||||
|
return '<span class="badge-source badge-source-xls">XLS</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return sourceType;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: 'header_row',
|
||||||
|
title: "Row",
|
||||||
|
className: "text-center",
|
||||||
|
defaultContent: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: 'start_column',
|
||||||
|
title: "Col",
|
||||||
|
className: "text-center",
|
||||||
|
defaultContent: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: 'description',
|
||||||
|
title: "Description",
|
||||||
|
className: "description-cell",
|
||||||
|
defaultContent: 'No description'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: null,
|
||||||
|
title: "Client",
|
||||||
|
className: "client-cell",
|
||||||
|
render: function(data, type, row) {
|
||||||
|
const clientName = row.clientname || "No client";
|
||||||
|
const clientId = row.idclient || "N/A";
|
||||||
|
return `${clientName} <small class="text-muted">(ID: ${clientId})</small>`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: 'button_label',
|
||||||
|
title: "Button",
|
||||||
|
className: "button-cell",
|
||||||
defaultContent: 'Click Me'
|
defaultContent: 'Click Me'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
data: 'status', // Stato con Toggle Switch
|
data: 'status',
|
||||||
title: "Status",
|
title: "Status",
|
||||||
orderable: false,
|
orderable: false,
|
||||||
searchable: false,
|
searchable: false,
|
||||||
|
className: "text-center",
|
||||||
render: function(status, type, row) {
|
render: function(status, type, row) {
|
||||||
let checked = (status === "active") ? "checked" : "";
|
let checked = (status === "active") ? "checked" : "";
|
||||||
return `
|
return `
|
||||||
@@ -189,31 +280,13 @@
|
|||||||
</label>
|
</label>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
data: 'id', // Azioni: Modifica, Mappatura e Eliminazione
|
|
||||||
orderable: false,
|
|
||||||
searchable: false,
|
|
||||||
title: "Actions",
|
|
||||||
render: function(data) {
|
|
||||||
return `
|
|
||||||
<div class="d-flex">
|
|
||||||
<a href="edit_template_xls.php?id=${data}" class="btn btn-sm btn-primary me-1">
|
|
||||||
<i class="bx bx-edit-alt"></i>
|
|
||||||
</a>
|
|
||||||
<a href="mapping_template_xls_scheme2.php?id=${data}" class="btn btn-sm btn-success me-1">
|
|
||||||
<i class="bx bx-link-alt"></i>
|
|
||||||
</a>
|
|
||||||
<button class="btn btn-sm btn-danger" onclick="confirmDelete(${data})">
|
|
||||||
<i class="bx bx-trash"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
dom: '<"card-header border-bottom p-3"<"d-flex align-items-center"<"card-title mb-0 flex-grow-1"f>>>rt<"card-footer border-top p-3"<"d-flex align-items-center"<"me-auto"l><"d-flex gap-2"ip>>>',
|
dom: '<"card-header border-bottom p-3"<"d-flex align-items-center"<"card-title mb-0 flex-grow-1"f>>>rt<"card-footer border-top p-3"<"d-flex align-items-center"<"me-auto"l><"d-flex gap-2"ip>>>',
|
||||||
lengthMenu: [10, 25, 50, 100],
|
lengthMenu: [10, 25, 50, 100],
|
||||||
|
order: [
|
||||||
|
[1, 'asc']
|
||||||
|
],
|
||||||
language: {
|
language: {
|
||||||
search: "Cerca:",
|
search: "Cerca:",
|
||||||
lengthMenu: "Mostra _MENU_ elementi",
|
lengthMenu: "Mostra _MENU_ elementi",
|
||||||
@@ -252,20 +325,21 @@
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
url: "update_template_status.php",
|
url: "update_template_status.php",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
|
dataType: "json",
|
||||||
data: {
|
data: {
|
||||||
id: templateId,
|
id: templateId,
|
||||||
status: newStatus
|
status: newStatus
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
console.log("✅ Status updated successfully.");
|
console.log("Status updated successfully.");
|
||||||
} else {
|
} else {
|
||||||
console.error("❌ Error updating status:", response.message);
|
console.error("Error updating status:", response.message);
|
||||||
alert("Error updating status: " + response.message);
|
alert("Error updating status: " + response.message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function(xhr) {
|
||||||
console.error("❌ AJAX error.");
|
console.error("AJAX error:", xhr.responseText);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user