Compare commits

...

114 Commits

Author SHA1 Message Date
cfbbc36116 Fix TZ Issue 2026-04-28 12:53:21 +03:00
50d578eea1 commesssa on commessaweb 2026-04-27 16:02:50 +02:00
6b0d2aa9b9 fixed color and import dahboard 2026-04-27 14:34:45 +02:00
fa7997c980 tested component fixed 2026-04-19 17:38:35 +02:00
66be442eb6 scrollbar import page 2026-04-17 15:00:17 +02:00
19a2d6e3f7 scrollbar also top 2026-04-17 10:47:41 +02:00
a15ab08576 Merge branch 'main' of http://192.168.1.93:8418/solocla/trf_certest 2026-04-17 08:43:32 +02:00
f71e8a56b5 cloneparts 2026-04-17 08:43:05 +02:00
cb38bfb75a fix resize on tolims page 2026-04-16 11:39:00 +03:00
28a708dad3 removed limit admin 2026-04-15 16:19:39 +02:00
6b9cf20ab9 fixed resize and analysis 2026-04-15 14:37:49 +02:00
d40fc7d177 update gitignore 2026-04-15 08:49:06 +02:00
b812563023 Merge branch 'main' into feature/milestone3 2026-04-08 09:24:30 +03:00
39a821357e analysis complete 2026-04-03 11:15:33 +02:00
53c223ea5f merge 2026-04-02 17:01:22 +03:00
9775a12d4a Merge branch 'main' into feature/milestone3
# Conflicts:
#	public/userarea/import_insert.php
#	public/userarea/mapping_template_xls_scheme2.php
#	public/userarea/process_import_xls2.php
2026-04-02 12:29:17 +03:00
7dfb935e33 new mocnler routine (3 steps) 2026-04-02 09:49:09 +02:00
abb4200215 Fix import 2026-03-31 17:15:43 +03:00
0be7109df4 Fix header finder 2026-03-31 16:33:08 +03:00
578671e013 added analysis by client and matrici 2026-03-31 14:08:51 +02:00
d24836e2b1 matrici cache 2026-03-31 14:25:10 +03:00
d983659000 transfer auto-detect header 2026-03-31 13:41:31 +03:00
7c5aa7734f hide form 2026-03-30 18:19:16 +03:00
4f0dbc7e91 cache and view improvements 2026-03-30 16:28:20 +03:00
fb09f033ae removed htmlspecialchars on import && savings 2026-03-30 13:24:18 +03:00
8bae2d7008 analysis 2026-03-30 11:31:25 +02:00
7463fc6726 change links 2026-03-30 08:53:14 +03:00
0bc2ff7e9d fix autodetect 2026-03-29 21:47:23 +03:00
aa1c32b7ed autodetect mapping 2026-03-29 21:07:16 +03:00
c573a46318 tolims filter, pagination 2026-03-29 17:13:54 +03:00
bf18a904bd update API template 2026-03-28 10:33:28 +01:00
b3ce489348 api change dahsboard 2026-03-28 10:28:25 +01:00
08b89e01cc Stop tracking customfield_values_response.json 2026-03-28 10:28:08 +01:00
3e66d67dc5 remove htmlspecialchars from saving 2026-03-27 20:55:54 +03:00
490786731a moncler supplier fabric routine 2026-03-27 15:54:22 +01:00
e96f538a47 added multiple column option template with color 2026-03-27 15:36:57 +01:00
cc96ecb67f order parts by part_number 2026-03-27 15:18:44 +01:00
4cf03ae742 fixed routine 2026-03-27 09:36:06 +01:00
7a486e9dcf update moncler routine 2026-03-27 09:24:46 +01:00
2a7b1fae17 imported && tolims 2026-03-26 16:05:17 +03:00
a05d9fed2b scheme alphabetical order 2026-03-26 11:47:02 +01:00
65170a0a7c fixed 285 export to LIMS and header import 2026-03-26 11:42:52 +01:00
223688c372 added select 2 on capitolato 2026-03-25 14:23:44 +01:00
b562eb4033 fixed ordine alfabetico capitolato 2026-03-25 14:20:45 +01:00
0645a0c675 update profile 2026-03-25 11:02:27 +01:00
a02a6b2c4c added clienteAnalisi on query export 2026-03-22 15:11:00 +01:00
45dd8d6907 Link photos to .01 Campioni 2026-03-21 16:18:27 +03:00
755f6812d4 Campione custom fields (Tested Component + parts), per-part ConsegnaRichiesta with validation 2026-03-20 23:31:31 +03:00
5e59ae2162 Tested component 2026-03-20 23:15:50 +03:00
71a19144c8 gitignore fixed 2026-03-19 13:48:51 +01:00
381a05341b PrimaPagina, StampaNelRapporto flags handling 2026-03-19 13:02:13 +03:00
5a58decd40 added clienti in template, export 2026-03-19 10:08:29 +01:00
eb21910ef3 per-row buttons locking, progress 2026-03-18 19:58:57 +03:00
5dedc779df format date to d/m/Y 2026-03-18 19:24:21 +03:00
f4e0074a73 fixed tested components 2026-03-18 16:11:03 +01:00
9c850e4ea6 fixed filter import 2026-03-18 16:07:25 +01:00
f300811341 photos fixed background 2026-03-18 15:53:46 +01:00
48387a9945 cliente fornitore 2026-03-17 15:43:12 +03:00
0e90db8219 import date format 2026-03-17 12:44:37 +03:00
eaf70d5a46 export date format 2026-03-16 20:57:05 +03:00
cdd6551e9c remove validation simulate 2026-03-14 19:09:17 +03:00
1b97bf4362 1,2,3,4,5,6 points of milestone 2026-03-13 00:33:55 +03:00
c516589483 changed notes TRF 2026-03-12 10:28:58 +01:00
817bbadf22 added flag to photos 2026-03-12 10:20:22 +01:00
1f27bc48d4 added get utenti 2026-03-11 14:53:38 +01:00
c9fba48d88 fix post data 2026-03-11 16:04:37 +03:00
70d4c0759e importpayload logging 2026-03-09 17:34:32 +03:00
5d7880160a send userid to import 2026-03-09 17:12:38 +03:00
bbe74d1529 fetch changes from branch export_to_lims 2026-03-09 17:07:20 +03:00
93930227a2 fetch changes from branch export_to_lims 2026-03-09 17:04:03 +03:00
2598a4c91b Step 9 remove comment on export to lims 2026-03-06 09:15:30 +01:00
5e677a8b9c Merge branch 'feature/export-to-lims' 2026-03-06 08:48:57 +01:00
540c44d89a fixed column mappings 2026-03-05 14:19:49 +01:00
2ee9f2ecb1 added proceed on the top 2026-03-04 10:13:33 +01:00
c9122774b1 added additional field in parts template e layout 2026-03-02 14:37:20 +01:00
1fed113c5c VisualLimsApiClientMock: fake data for all LIMS endpoints; getInstance() branches on SIMULATE_EXPORT_LIMS
get_clienti.php, get_fixed_field_data.php: simulate mode support
CustomField dropdown values mock added (get_customfield_values.php)

exportUnsavedModal: prompt save before export, MutationObserver waits for save, then proceeds to confirm
Removed old jQuery .export-lims-btn handler that bypassed confirm modal
Fix false "Unsaved changes" on page load: data-restoring guard in all programmatic trigger/dispatchEvent calls (populateSelect, populateClientDropdowns, populateDropdowns)

Fix ConsegnaRichiesta not shown on refresh: add to PHP $fixedAliasMap
Add step5_2_photos, step9_1_importa
2026-02-28 00:44:21 +03:00
e3994d6f9f fix notices running on php-8.2 2026-02-26 22:59:41 +03:00
9af0df3cca env example update 2026-02-26 17:14:16 +01:00
48d2a3ff42 time and date setting 2026-02-25 22:07:44 +01:00
cf44e67922 added auto user accettatore id 2026-02-25 15:24:41 +01:00
35021e9d9b dropdown in alphabetical order 2026-02-25 10:02:45 +01:00
497ebda65a update gitignore 2026-02-24 09:09:32 +01:00
b0024edb70 mapping fixed 2026-02-24 09:06:45 +01:00
b9852ba226 saved rows 2026-02-19 12:07:32 +01:00
407d6884a1 fixed note save and import special character 2026-02-19 11:55:24 +01:00
1a4beadbb2 fixed propagate dropdown 2026-02-18 11:52:26 +01:00
4bb0445cff added select 2 on all fields (with limit of 12) 2026-02-18 10:48:04 +01:00
73dd8f4ce8 fixed column size final 2026-02-18 10:04:27 +01:00
bdc4e0e60c fixed size column 2026-02-18 09:58:42 +01:00
ef8f2d8000 fixed labels 2026-02-18 09:54:21 +01:00
74c9a1d02a added time field 2026-02-18 09:34:26 +01:00
cd3bccd183 added multiple tested components 2026-02-17 09:04:30 +01:00
78154e43a9 fixed tested components 2026-02-05 10:32:28 +01:00
9fe9243e60 fixed mapping template 2026-02-05 09:05:51 +01:00
a8330d4aba fixed layout 2026-02-04 15:31:53 +01:00
82d6a2ee18 feat(import_edit2): expand entire column including header and fix sticky columns 2026-02-04 17:00:01 +04:00
d8eddb3aa5 feat(import_edit2): enhance grid UI with sticky columns and textarea conversion
- Fix grid-top alignment and datepicker positioning
- Extend column resizer to grid-top cells
- Add sticky columns for Actions and Sample Description
- Implement input-to-textarea conversion on focus
- Increase Select2 dropdown height
- Improve grid-container CSS for sticky support
2026-02-04 11:59:59 +04:00
f60dc64b2d fixed importedit 2026-02-02 08:46:47 +01:00
4e4cae1df8 added fixed fields 2026-01-30 12:07:43 +01:00
8838edf3a1 fixed fields 2026-01-28 11:49:11 +01:00
e75be99e43 added resort manually 2025-11-08 17:55:10 +01:00
a482d975da Merge branch 'feature/parts-photo-load-from-quotation' 2025-10-31 10:38:28 +01:00
598a2cc84c change marker dimension 2025-10-31 10:16:57 +01:00
6ec0c2062e fix: Include single sample photo in save operation when no photos selected 2025-10-31 11:45:04 +04:00
5eb5bd1613 fix: deselect part when clicking selected row 2025-10-31 11:30:18 +04:00
03771e3ca8 feat: Add quotation modal and iddatadb update for quotation parts/images
- Add quotation modal for selecting quotation
- Load quotation parts and photos
- Update iddatadb with quotation parts and photo references
2025-10-31 11:26:11 +04:00
03642fdfab feat(annotations): support multiple pins per part 2025-10-30 17:08:57 +04:00
f6ea17388c fix(partsTable, annotationsModal): correct table height and restore "Torna alle Parti"
- partsTable: reduce excessive height to prevent oversized rendering
- annotationsModal: fix "Torna alle Parti" button behavior to return to parts view
2025-10-30 10:26:46 +04:00
1c2b4ab7a6 Merge branch 'bugfix/part-creation-matrice-dropdown-fix' 2025-10-29 18:25:26 +01:00
31cb23b00e fix(partsTable): skip change handler when setting Select2 value programmatically
Pass skipHandler flag in change event data to prevent handler execution
during programmatic value updates.
2025-10-29 19:18:33 +04:00
d29563d20d feat(partsTable): add image icon to show/hide photo button
- Add image icon alongside eye icon in show/hide photo button
- Improve visual indication of photo toggle functionality
2025-10-29 18:27:33 +04:00
82af925ac1 added size to line markers 2025-10-29 13:45:38 +01:00
5d8360dd87 marker with size 2025-10-29 12:32:09 +01:00
683073c244 fix(partsTable): initialize new item matrice as empty and refresh on global filter change
- Default new items' matrice to empty to prevent stale values leaking from prior state
- Force matrice update when the global filter changes to keep items in sync
2025-10-29 14:17:00 +04:00
137 changed files with 61983 additions and 1628 deletions

View File

@ -7,9 +7,9 @@ LOG_CHANNEL=stack
DB_CONNECTION=mysql
DB_HOST="localhost"
DB_DATABASE="trfcertest"
DB_USERNAME="solocla"
DB_PASSWORD="xxxxxxx"
DB_DATABASE="xxxx"
DB_USERNAME="xxxx"
DB_PASSWORD="xxxxx"
DB_PREFIX="auth_"
BROADCAST_DRIVER=log
@ -41,19 +41,8 @@ MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
# Credenziali API VisualLims
SIMULATE_EXPORT_LIMS=true
API_BASE_URL=https://93.43.5.102/limsapi
API_USERNAME=xxxx
API_PASSWORD=XXXX
API_BASE_URL=https://bvcpsitaly-elims.com/limsapi
API_USERNAME=WebApiUser
API_PASSWORD=webapiuser01
BASE_URL=http://localhost:8000/userarea/
# Credenziali ENTRAID
AZURE_CLIENT_ID=your-client-id
AZURE_CLIENT_SECRET=your-client-secret
AZURE_REDIRECT_URI=https://your-app.com/auth/azure/callback
AZURE_TENANT_ID=
MICROSOFT_CLIENT_ID=your_client_id_here
MICROSOFT_CLIENT_SECRET=your_client_secret_here
MICROSOFT_REDIRECT_URI="${APP_URL}/auth/microsoft/callback"

75
.gitignore vendored
View File

@ -1,66 +1,59 @@
.DS_Store
/node_modules
/public/hot
/public/storage
/storage/*.key
/vendor
/.idea
/.fleet
/.vscode
/.vagrant
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
.env
.phpunit.result.cache
.php_cs.cache
/documentation
/.phpunit.cache
/public/hot
/public/storage
/public/build
/storage/*.key
.env
.env.backup
.env.production
auth.json
# File di debug e temporanei JSON e log
.phpunit.result.cache
.php_cs.cache
/.phpunit.cache
npm-debug.log
yarn-error.log
/documentation
# --- Runtime / Debug (userarea) ---
/public/userarea/*.json
/public/userarea/*.log
/public/userarea/*.txt
# File di log nella sottocartella class
/public/userarea/*_response.json
/public/userarea/customfield_values_response.json
/public/userarea/error_log.txt
/public/userarea/import_debug.log
/public/userarea/last_url.txt
/public/userarea/logaspi/
/public/userarea/logs/api/
/public/userarea/logs/api/**
/public/userarea/photostrf/
/public/userarea/class/*.log
/public/userarea/class/curl_auth_debug.log
/public/userarea/class/curl_request_debug.log
# File XLSX temporanei importati
/public/userarea/imported_trf/*.xlsx
/public/userarea/xlstemplates/*.xlsx
# Ignora cartelle di foto generate
# Cartelle foto generate
/public/photostrf/
/public/photostrf/qrcodes/
public/userarea/import_debug.log
public/userarea/last_url.txt
public/userarea/class/curl_auth_debug.log
public/userarea/class/curl_request_debug.log
public/userarea/last_url.txt
public/userarea/class/curl_auth_debug.log
public/userarea/class/curl_request_debug.log
# Ignora tutti i log
# Ignora tutti i log ovunque
*.log
# Ignora cartella photostrf in public/userarea
/public/userarea/photostrf/
public/userarea/customfield_values_response.json
/public/userarea/logaspi/
public/userarea/logsapi/campione_762_1.json
public/userarea/logsapi/campione_763_1.json
public/userarea/logsapi/campione_762_2.json
public/userarea/logsapi/campione_763_2.json
public/userarea/logsapi/commessaweb_create_762.json
public/userarea/logsapi/commessaweb_create_763.json
public/userarea/logsapi/commessaweb_customfields_762.json
public/userarea/logsapi/commessaweb_customfields_763.json
public/userarea/logsapi/commessaweb_invia_762.json
public/userarea/logsapi/commessaweb_invia_763.json
public/userarea/logsapi/last_auth_url.txt
public/userarea/cache/

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -0,0 +1,91 @@
<?php
header('Content-Type: application/json');
ini_set('display_errors', 1);
error_reporting(E_ALL);
require_once(__DIR__ . '/include/headscript.php'); // Auth + $iduserlogin + DBHandlerSelect
try {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
throw new Exception('Invalid request method');
}
$iddatadb = isset($_POST['iddatadb']) ? (int)$_POST['iddatadb'] : 0;
if ($iddatadb <= 0) {
throw new Exception('Missing iddatadb');
}
// ✅ Supporta sia singola stringa (con |) che array part_descriptions[]
$parts = [];
if (isset($_POST['part_descriptions']) && is_array($_POST['part_descriptions'])) {
$parts = $_POST['part_descriptions'];
} else {
$raw = isset($_POST['part_description']) ? (string)$_POST['part_description'] : '';
// split su "|" per multi-part
$parts = preg_split('/\s*\|\s*/', $raw);
}
// normalizza: trim + rimuovi vuoti + dedup
$cleanParts = [];
foreach ($parts as $p) {
$p = trim((string)$p);
if ($p === '') continue;
// limita lunghezza come da DB varchar(255)
if (mb_strlen($p) > 255) {
$p = mb_substr($p, 0, 255);
}
$cleanParts[] = $p;
}
$cleanParts = array_values(array_unique($cleanParts));
if (count($cleanParts) === 0) {
throw new Exception('Part description is empty');
}
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$pdo->beginTransaction();
try {
// prende il prossimo part_number per iddatadb
$stmtMax = $pdo->prepare("SELECT COALESCE(MAX(part_number), 0) AS maxnum FROM identification_parts WHERE iddatadb = ?");
$stmtMax->execute([$iddatadb]);
$nextNumber = (int)$stmtMax->fetchColumn() + 1;
$stmtIns = $pdo->prepare("
INSERT INTO identification_parts (iddatadb, part_number, part_description)
VALUES (?, ?, ?)
");
$insertedIds = [];
foreach ($cleanParts as $p) {
$ok = $stmtIns->execute([$iddatadb, $nextNumber, $p]);
if (!$ok) {
throw new Exception('Insert failed');
}
$insertedIds[] = $pdo->lastInsertId();
$nextNumber++;
}
$pdo->commit();
echo json_encode([
'success' => true,
'message' => 'Parts added',
'count' => count($insertedIds),
'ids' => $insertedIds
]);
} catch (Exception $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
throw $e;
}
} catch (Exception $e) {
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
}
exit;

View File

@ -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()]);
}

View File

@ -0,0 +1,849 @@
(function () {
"use strict";
let analysisMatriciMap = {};
let analysisLoadedCache = {};
let analysisAssignedState = {};
let analysisSelectedState = {};
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 readInitialAssignedAnalyses() {
const jsonEl = document.getElementById("analysisAssignedInitialData");
if (!jsonEl) {
analysisAssignedState = {};
return;
}
try {
analysisAssignedState =
JSON.parse(jsonEl.textContent || "{}") || {};
} catch (e) {
analysisAssignedState = {};
}
}
function getSelectedPartIds() {
const modal = document.getElementById("analysisModal");
if (!modal) return [];
return Array.from(
modal.querySelectorAll(".analysis-part-checkbox:checked"),
).map((el) => String(el.value));
}
function getCurrentSelectedAnalysisRecordKeys() {
return Object.keys(analysisSelectedState).filter(function (key) {
return analysisSelectedState[key] === true;
});
}
function renderAssignedAnalysesForPart(partId) {
const container = document.getElementById(
"analysisAssignedListPart" + partId,
);
if (!container) return;
const items = Array.isArray(analysisAssignedState[String(partId)])
? analysisAssignedState[String(partId)]
: [];
if (!items.length) {
container.innerHTML = "";
return;
}
container.innerHTML = items
.map(function (item) {
const recordKey = item.analysis_recordkey || "";
const title = item.analysis_name || "Unnamed analysis";
const method = item.analysis_method || "";
return `
<div class="analysis-assigned-chip" data-recordkey="${escapeHtml(recordKey)}">
<div class="analysis-assigned-chip-text">
<div class="analysis-assigned-chip-title">${escapeHtml(title)}</div>
${method ? `<div class="analysis-assigned-chip-method">${escapeHtml(method)}</div>` : ""}
</div>
<button type="button"
class="analysis-remove-btn"
data-part-id="${escapeHtml(partId)}"
data-recordkey="${escapeHtml(recordKey)}"
title="Remove analysis">×</button>
</div>
`;
})
.join("");
}
function renderAssignedAnalysesForSelectedParts() {
const modal = document.getElementById("analysisModal");
if (!modal) return;
modal.querySelectorAll(".analysis-part-item").forEach(function (item) {
const partId = item.getAttribute("data-part-id");
renderAssignedAnalysesForPart(partId);
});
}
function syncSelectedAnalysisRows() {
const modal = document.getElementById("analysisModal");
if (!modal) return;
modal
.querySelectorAll(".analysis-analysis-item")
.forEach(function (item) {
const recordKey = item.getAttribute("data-recordkey") || "";
if (recordKey && analysisSelectedState[recordKey]) {
item.classList.add("analysis-selected");
} else {
item.classList.remove("analysis-selected");
}
});
}
function buildAnalysisPayloadFromRow(rowEl) {
return {
analysis_recordkey: rowEl.getAttribute("data-recordkey") || "",
analysis_name: rowEl.getAttribute("data-analysis-name") || "",
analysis_method: rowEl.getAttribute("data-analysis-method") || "",
analysis_level: rowEl.getAttribute("data-analysis-level") || "",
is_web_selectable: rowEl.getAttribute("data-web") === "1" ? 1 : 0,
is_accredited:
rowEl.getAttribute("data-accredited") === "1" ? 1 : 0,
};
}
function addAnalysisToLocalState(partId, payload, iddatadb, idmatrice) {
const key = String(partId);
if (!Array.isArray(analysisAssignedState[key])) {
analysisAssignedState[key] = [];
}
const exists = analysisAssignedState[key].some(function (item) {
return item.analysis_recordkey === payload.analysis_recordkey;
});
if (!exists) {
analysisAssignedState[key].push({
id: null,
part_id: parseInt(partId, 10),
iddatadb: iddatadb || null,
idmatrice: idmatrice || null,
analysis_recordkey: payload.analysis_recordkey,
analysis_name: payload.analysis_name,
analysis_method: payload.analysis_method,
analysis_level:
payload.analysis_level !== ""
? parseInt(payload.analysis_level, 10)
: null,
is_web_selectable: payload.is_web_selectable,
is_accredited: payload.is_accredited,
});
}
}
function removeAnalysisFromLocalState(partId, recordKey) {
const key = String(partId);
if (!Array.isArray(analysisAssignedState[key])) {
return;
}
analysisAssignedState[key] = analysisAssignedState[key].filter(
function (item) {
return item.analysis_recordkey !== recordKey;
},
);
}
function saveAnalysisAssociation(partId, payload, callback) {
const modal = document.getElementById("analysisModal");
if (!modal) return;
const iddatadb =
modal.querySelector("#analysisModalIddatadb")?.value || "";
const partItem = modal.querySelector(
'.analysis-part-item[data-part-id="' + partId + '"]',
);
const idmatrice = partItem
? partItem.getAttribute("data-matrix-id") || ""
: "";
$.ajax({
url: "save_part_analysis.php",
method: "POST",
dataType: "json",
data: {
part_id: partId,
iddatadb: iddatadb,
idmatrice: idmatrice !== "NO_MATRIX" ? idmatrice : "",
analysis_recordkey: payload.analysis_recordkey,
analysis_name: payload.analysis_name,
analysis_method: payload.analysis_method,
analysis_level: payload.analysis_level,
is_web_selectable: payload.is_web_selectable,
is_accredited: payload.is_accredited,
},
})
.done(function () {
addAnalysisToLocalState(
partId,
payload,
iddatadb,
idmatrice !== "NO_MATRIX" ? idmatrice : null,
);
renderAssignedAnalysesForPart(partId);
if (typeof callback === "function") callback(true);
})
.fail(function (xhr) {
let message = "Error saving analysis association";
if (xhr && xhr.responseJSON && xhr.responseJSON.message) {
message = xhr.responseJSON.message;
}
alert(message);
if (typeof callback === "function") callback(false);
});
}
function deleteAnalysisAssociation(partId, recordKey, callback) {
$.ajax({
url: "delete_part_analysis.php",
method: "POST",
dataType: "json",
data: {
part_id: partId,
analysis_recordkey: recordKey,
},
})
.done(function () {
removeAnalysisFromLocalState(partId, recordKey);
renderAssignedAnalysesForPart(partId);
if (typeof callback === "function") callback(true);
})
.fail(function (xhr) {
let message = "Error deleting analysis association";
if (xhr && xhr.responseJSON && xhr.responseJSON.message) {
message = xhr.responseJSON.message;
}
alert(message);
if (typeof callback === "function") callback(false);
});
}
function setAnalysisLoadingState(isLoading) {
const modal = document.getElementById("analysisModal");
if (!modal) return;
const loadingEl = modal.querySelector("#analysisLoadingBox");
if (!loadingEl) return;
if (isLoading) {
loadingEl.classList.remove("d-none");
} else {
loadingEl.classList.add("d-none");
}
}
function showAnalysisError(message) {
const modal = document.getElementById("analysisModal");
if (!modal) return;
const errorEl = modal.querySelector("#analysisErrorBox");
const emptyEl = modal.querySelector("#analysisEmptyBox");
const listEl = modal.querySelector("#analysisList");
if (listEl) listEl.innerHTML = "";
if (emptyEl) emptyEl.classList.add("d-none");
if (errorEl) {
errorEl.textContent = message || "Error loading analyses";
errorEl.classList.remove("d-none");
}
}
function showAnalysisEmpty(message) {
const modal = document.getElementById("analysisModal");
if (!modal) return;
const errorEl = modal.querySelector("#analysisErrorBox");
const emptyEl = modal.querySelector("#analysisEmptyBox");
const listEl = modal.querySelector("#analysisList");
if (listEl) listEl.innerHTML = "";
if (errorEl) errorEl.classList.add("d-none");
if (emptyEl) {
emptyEl.textContent =
message || "No analyses found for this matrix";
emptyEl.classList.remove("d-none");
}
}
function escapeHtml(value) {
return String(value ?? "")
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
function renderAnalysesList(analyses) {
const modal = document.getElementById("analysisModal");
if (!modal) return;
const errorEl = modal.querySelector("#analysisErrorBox");
const emptyEl = modal.querySelector("#analysisEmptyBox");
const listEl = modal.querySelector("#analysisList");
if (!listEl) return;
if (errorEl) errorEl.classList.add("d-none");
if (!Array.isArray(analyses) || analyses.length === 0) {
showAnalysisEmpty("No analyses found for this matrix");
return;
}
if (emptyEl) emptyEl.classList.add("d-none");
listEl.innerHTML = analyses
.map(function (item) {
const recordKey = item.RecordKey || "";
const analysisName =
item.NomeAnalisiTraduzione ||
item.NomeAnalisi ||
"Unnamed analysis";
const methodName = item.MetodoNome || "";
const selectable = item.SelezionabileSuWeb === true;
const accredited = item.Accreditato === true;
const level = item.Livello ?? "";
const searchText = (
analysisName +
" " +
methodName
).toLowerCase();
let badges = "";
if (selectable) {
badges += '<span class="badge bg-success">Web</span>';
} else {
badges += '<span class="badge bg-secondary">Not web</span>';
}
if (accredited) {
badges +=
'<span class="badge bg-info text-dark">Accredited</span>';
}
if (level !== "") {
badges +=
'<span class="badge bg-light text-dark border">Level ' +
escapeHtml(level) +
"</span>";
}
return `
<div
class="list-group-item analysis-analysis-item"
data-recordkey="${escapeHtml(recordKey)}"
data-analysis-name="${escapeHtml(analysisName)}"
data-analysis-method="${escapeHtml(methodName)}"
data-analysis-level="${escapeHtml(level)}"
data-web="${selectable ? "1" : "0"}"
data-accredited="${accredited ? "1" : "0"}"
data-search="${escapeHtml(searchText)}"
>
<div class="fw-semibold">${escapeHtml(analysisName)}</div>
${methodName ? `<div class="analysis-analysis-meta mt-1">${escapeHtml(methodName)}</div>` : ""}
<div class="analysis-analysis-badges mt-2">
${badges}
</div>
</div>
`;
})
.join("");
filterAnalysisList();
syncSelectedAnalysisRows();
}
function filterAnalysisList() {
const modal = document.getElementById("analysisModal");
if (!modal) return;
const webOnlyEl = modal.querySelector("#analysisWebOnly");
const searchEl = modal.querySelector("#analysisSearchInput");
const items = modal.querySelectorAll(".analysis-analysis-item");
const emptyEl = modal.querySelector("#analysisEmptyBox");
const errorEl = modal.querySelector("#analysisErrorBox");
const webOnly = webOnlyEl ? webOnlyEl.checked : false;
const searchValue = searchEl ? searchEl.value.trim().toLowerCase() : "";
let visibleCount = 0;
items.forEach((item) => {
const isWeb = item.getAttribute("data-web") === "1";
const searchText = (
item.getAttribute("data-search") || ""
).toLowerCase();
let visible = true;
if (webOnly && !isWeb) {
visible = false;
}
if (visible && searchValue && !searchText.includes(searchValue)) {
visible = false;
}
item.style.display = visible ? "" : "none";
if (visible) {
visibleCount++;
}
});
if (errorEl) {
errorEl.classList.add("d-none");
}
if (emptyEl) {
if (items.length === 0) {
emptyEl.textContent = "No analyses found for this matrix";
emptyEl.classList.remove("d-none");
} else if (visibleCount === 0) {
emptyEl.textContent = "No analyses match the current filters";
emptyEl.classList.remove("d-none");
} else {
emptyEl.classList.add("d-none");
}
}
}
function loadAnalysesByMatrix(matrixId) {
const modal = document.getElementById("analysisModal");
if (!modal) return;
if (!matrixId || matrixId === "NO_MATRIX") {
showAnalysisEmpty("No matrix selected");
return;
}
const listEl = modal.querySelector("#analysisList");
const errorEl = modal.querySelector("#analysisErrorBox");
const emptyEl = modal.querySelector("#analysisEmptyBox");
if (listEl) listEl.innerHTML = "";
if (errorEl) errorEl.classList.add("d-none");
if (emptyEl) {
emptyEl.textContent = "";
emptyEl.classList.add("d-none");
}
if (analysisLoadedCache[String(matrixId)]) {
renderAnalysesList(analysisLoadedCache[String(matrixId)]);
return;
}
setAnalysisLoadingState(true);
$.ajax({
url: "get_analisi_matrice_filter.php",
method: "GET",
dataType: "json",
data: {
id_matrice: matrixId,
},
})
.done(function (response) {
const analyses = Array.isArray(response.value)
? response.value
: [];
analysisLoadedCache[String(matrixId)] = analyses;
renderAnalysesList(analyses);
})
.fail(function (xhr) {
let message = "Error loading analyses";
if (xhr && xhr.responseJSON && xhr.responseJSON.error) {
message = xhr.responseJSON.error;
}
showAnalysisError(message);
})
.always(function () {
setAnalysisLoadingState(false);
});
}
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 || "-";
}
analysisSelectedState = {};
const selectedPartIds = getSelectedPartIds();
selectedPartIds.forEach(function (partId) {
const assigned = Array.isArray(
analysisAssignedState[String(partId)],
)
? analysisAssignedState[String(partId)]
: [];
assigned.forEach(function (item) {
if (item.analysis_recordkey) {
analysisSelectedState[item.analysis_recordkey] = true;
}
});
});
updateSelectedPartsInfo();
loadAnalysesByMatrix(matrixId);
}
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 = "-";
const webOnlyEl = modal.querySelector("#analysisWebOnly");
if (webOnlyEl) webOnlyEl.checked = false;
const searchEl = modal.querySelector("#analysisSearchInput");
if (searchEl) searchEl.value = "";
const listEl = modal.querySelector("#analysisList");
if (listEl) listEl.innerHTML = "";
showAnalysisEmpty("Select a matrix to load analyses");
updateSelectedPartsInfo();
}
function initAnalysisModal() {
const modal = document.getElementById("analysisModal");
if (!modal) return;
analysisLoadedCache = {};
analysisSelectedState = {};
readInitialAssignedAnalyses();
renderAssignedAnalysesForSelectedParts();
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 webOnlyEl = modal.querySelector("#analysisWebOnly");
if (webOnlyEl) {
webOnlyEl.addEventListener("change", function () {
filterAnalysisList();
});
}
const searchEl = modal.querySelector("#analysisSearchInput");
if (searchEl) {
searchEl.addEventListener("input", function () {
filterAnalysisList();
});
}
modal.addEventListener("click", function (e) {
const removeBtn = e.target.closest(".analysis-remove-btn");
if (removeBtn) {
e.preventDefault();
e.stopPropagation();
const partId = removeBtn.getAttribute("data-part-id");
const recordKey = removeBtn.getAttribute("data-recordkey");
deleteAnalysisAssociation(partId, recordKey, function () {
const stillUsed = Object.keys(analysisAssignedState).some(
function (pid) {
return (
Array.isArray(analysisAssignedState[pid]) &&
analysisAssignedState[pid].some(
function (item) {
return (
item.analysis_recordkey ===
recordKey
);
},
)
);
},
);
if (!stillUsed) {
delete analysisSelectedState[recordKey];
}
syncSelectedAnalysisRows();
});
return;
}
const row = e.target.closest(".analysis-analysis-item");
if (!row) return;
const selectedPartIds = getSelectedPartIds();
if (!selectedPartIds.length) {
alert("Select at least one part first");
return;
}
const payload = buildAnalysisPayloadFromRow(row);
if (!payload.analysis_recordkey) {
alert("Invalid analysis record key");
return;
}
const recordKey = payload.analysis_recordkey;
const alreadySelected = !!analysisSelectedState[recordKey];
if (alreadySelected) {
selectedPartIds.forEach(function (partId) {
deleteAnalysisAssociation(partId, recordKey);
});
delete analysisSelectedState[recordKey];
syncSelectedAnalysisRows();
return;
}
let pending = selectedPartIds.length;
let atLeastOneSaved = false;
selectedPartIds.forEach(function (partId) {
saveAnalysisAssociation(partId, payload, function (ok) {
if (ok) atLeastOneSaved = true;
pending--;
if (pending <= 0 && atLeastOneSaved) {
analysisSelectedState[recordKey] = true;
syncSelectedAnalysisRows();
}
});
});
});
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", "");
}
});
})();

View File

@ -12,12 +12,16 @@ $(document).ready(function () {
let photoAnnotations = {};
let partColors = {};
let partSizes = {}; // memorizza la dimensione specifica per parte
let selectedPartNumber = null;
let unsavedChanges = false;
let fabricCanvas = null;
let descriptionTextbox = null;
let markerObjects = {};
let nextMarkerId = 1;
let partsListData = [];
// DIMENSIONE GLOBALE MARKER
let globalMarkerSize = 16;
// ===================
// MODAL INITIALIZATION
@ -29,6 +33,8 @@ $(document).ready(function () {
trfHeader,
});
$("#annotationsModal").attr("data-iddatadb", iddatadb);
if (!iddatadb && !idquotations) {
const errorMsg = $(
'<div class="alert alert-danger temp-alert" role="alert">Errore: ID TRF mancante. Impossibile inizializzare il modale delle annotazioni.</div>',
@ -67,6 +73,9 @@ $(document).ready(function () {
});
}
modal.show();
// Inizializza slider dimensione marker
$("#markerSizeSlider").val(globalMarkerSize);
$("#markerSizeValue").text(globalMarkerSize + "px");
// Debug: Verifica presenza elementi DOM
console.log(
@ -128,6 +137,7 @@ $(document).ready(function () {
}
descriptionTextbox = null;
markerObjects = {};
globalMarkerSize = 16;
$("#photoSelectorContainerAnnotations").empty().hide();
$("#samplePhotoAnnotations").attr("src", "");
$("#partsListAnnotations").empty();
@ -144,6 +154,30 @@ $(document).ready(function () {
$(":focus").blur();
});
// SLIDER DIMENSIONE MARKER
$(document)
.off("input", "#markerSizeSlider")
.on("input", "#markerSizeSlider", function () {
globalMarkerSize = parseInt($(this).val());
$("#markerSizeValue").text(globalMarkerSize + "px");
partsListData.forEach(function (part) {
if (part && part.part_number != null) {
partSizes[part.part_number] = globalMarkerSize;
}
});
$("#partsListAnnotations .marker-size-slider").val(
globalMarkerSize,
);
$("#partsListAnnotations .marker-size-value").text(
globalMarkerSize + "px",
);
updateMarkers();
markUnsaved();
});
// ===================
// PHOTO LOADERS
// ===================
@ -347,22 +381,13 @@ $(document).ready(function () {
const partColor =
partColors[selectedPartNumber] || "#ff0000";
const existingMarker = photoAnnotations[
currentPhoto
].markers.find((m) => m.partNumber == selectedPartNumber);
if (existingMarker) {
existingMarker.x = x;
existingMarker.y = y;
existingMarker.color = partColor;
} else {
photoAnnotations[currentPhoto].markers.push({
partNumber: selectedPartNumber,
x,
y,
color: partColor,
});
}
photoAnnotations[currentPhoto].markers.push({
id: nextMarkerId++,
partNumber: selectedPartNumber,
x,
y,
color: partColor,
});
console.log("Marker aggiunto/spostato:", {
partNumber: selectedPartNumber,
@ -372,8 +397,6 @@ $(document).ready(function () {
});
updateMarkers();
markUnsaved();
selectedPartNumber = null;
$("#partsListAnnotations li").removeClass("active");
});
fabricCanvas.upperCanvasEl.focus();
@ -465,113 +488,89 @@ $(document).ready(function () {
);
// ===================
// BACK TO PARTS MODAL
// TORNA AL MODALE PARTI (modal_partsTable.php)
// ===================
$(document)
.off("click.backToParts", "#backToPartsBtnAnnotations")
.on("click.backToParts", "#backToPartsBtnAnnotations", function (e) {
e.preventDefault();
e.stopPropagation();
console.log(
"Evento click su #backToPartsBtnAnnotations, ID elemento:",
$(this).attr("id"),
);
if (!$("#backToPartsBtnAnnotations").length) {
console.error(
"Pulsante #backToPartsBtnAnnotations non trovato nel DOM.",
);
const errorMsg = $(
'<div class="alert alert-danger temp-alert" role="alert">Errore: Pulsante #backToPartsBtnAnnotations non trovato nel DOM.</div>',
);
$("#annotationsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
return;
}
// Controlla modifiche non salvate
if (
unsavedChanges &&
!confirm(
"Hai modifiche non salvate. Vuoi davvero tornare al modale delle parti?",
)
) {
console.log(
"Tornare al modale delle parti annullato a causa di modifiche non salvate.",
);
return;
}
// Chiudi annotationsModal
const annotationsModalElement =
document.getElementById("annotationsModal");
const annotationsModal = bootstrap.Modal.getInstance(
annotationsModalElement,
);
if (annotationsModal) {
annotationsModal.hide();
} else {
console.error(
"Impossibile trovare l'istanza del modale #annotationsModal.",
);
}
// Apri partsModal
const iddatadb = $("#annotationsModal").data("iddatadb");
const idquotations = $("#annotationsModal").data("idquotations");
const trfHeader = $("#trfHeaderAnnotations").text();
console.log("Apertura partsModal con:", {
// CHIUDI MODALE ANNOTAZIONI IN MODO SICURO
const annotationsModalElement =
document.getElementById("annotationsModal");
if (annotationsModalElement) {
const modalInstance = bootstrap.Modal.getInstance(
annotationsModalElement,
);
if (modalInstance) {
modalInstance.hide();
} else {
$(annotationsModalElement).modal("hide"); // fallback jQuery
}
}
console.log("Torno a modal_partsTable.php con:", {
iddatadb,
idquotations,
trfHeader,
});
// CARICA E APRI MODALE PARTI
$.get(
"modal_partsTable.php",
{
iddatadb: iddatadb || "",
idquotations: idquotations || "",
trfHeader: trfHeader,
},
function (data) {
// Rimuovi vecchio modale (con ID corretto)
$("#partsTableModal").remove();
$(".modal-backdrop").remove();
const partsModalElement = document.getElementById("partsModal");
if (!partsModalElement) {
console.error("Elemento #partsModal non trovato nel DOM.");
const errorMsg = $(
'<div class="alert alert-danger temp-alert" role="alert">Errore: Il modale delle parti non è presente nel DOM.</div>',
// Aggiungi nuovo modale
$("body").append(data);
// Apri con Bootstrap 5
const partsModalElement =
document.getElementById("partsTableModal");
if (partsModalElement) {
const modal = new bootstrap.Modal(partsModalElement, {
backdrop: true,
keyboard: true,
focus: true,
});
modal.show();
} else {
let iddatadb =
$("#annotationsModal").attr("data-iddatadb");
$(
"button.parts-btn[data-iddatadb='" +
iddatadb +
"']",
).trigger("click");
}
},
).fail(function (xhr) {
console.error("Errore caricamento modale parti:", xhr);
alert(
"Errore 404: modal_partsTable.php non trovato o errore server.",
);
$("#annotationsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
return;
}
let partsModal = bootstrap.Modal.getInstance(partsModalElement);
if (!partsModal) {
partsModal = new bootstrap.Modal(partsModalElement, {
backdrop: true,
keyboard: true,
focus: true,
});
}
// Inizializza il modale delle parti
if (typeof window.initPartsModal === "function") {
window.initPartsModal(iddatadb, idquotations, trfHeader);
partsModal.show();
console.log("partsModal aperto con successo.");
} else {
console.error("Funzione initPartsModal non definita.");
const errorMsg = $(
'<div class="alert alert-danger temp-alert" role="alert">Errore: Funzione initPartsModal non trovata.</div>',
);
$("#annotationsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
}
});
});
// ===================
// PARTS LIST
// ===================
@ -633,12 +632,19 @@ $(document).ready(function () {
)
.join("");
const listItem = `
<li class="list-group-item" data-part-number="${partNumber}">
<span class="part-info" style="cursor: pointer;">${partNumber} - ${partDescription}</span>
<div class="color-picker-container" style="display: inline-block; position: relative; overflow: visible; z-index: 2000;">
<div class="color-option selected-color" style="background-color: ${partColor}; width: 20px; height: 20px; display: inline-block; margin-left: 5px; cursor: pointer; pointer-events: auto;"></div>
<div class="color-picker" style="display: none; position: absolute; right: 0; z-index: 2000; background: #fff; border: 1px solid #ccc; padding: 5px;">${colorOptions}</div>
</div>
<li class="list-group-item" data-part-number="${partNumber}">
<span class="part-info" style="cursor: pointer;">${partNumber} - ${partDescription}</span>
<div class="color-picker-container" style="display:inline-block; position: relative; overflow: visible; z-index: 2000; margin-left:5px;">
<div class="color-option selected-color" style="background-color: ${partColor}; width: 20px; height: 20px; display: inline-block; cursor: pointer; pointer-events: auto;"></div>
<div class="color-picker" style="display: none; position: absolute; right: 0; z-index: 2000; background: #fff; border: 1px solid #ccc; padding: 5px;">${colorOptions}</div>
</div>
<div style="display:inline-flex; align-items:center; gap:4px; margin-left:5px;">
<input type="range" class="marker-size-slider"
min="12" max="48" step="2"
value="${partSizes[partNumber] || globalMarkerSize}"
style="width:70px;">
<span class="marker-size-value" style="font-size:0.8rem;">${partSizes[partNumber] || globalMarkerSize}px</span>
</div>
</li>`;
partsListElement.append(listItem);
}
@ -663,8 +669,21 @@ $(document).ready(function () {
);
return;
}
const $listItem = $(this);
selectedPartNumber = $listItem.data("part-number");
const partNumber = $listItem.data("part-number");
if (
selectedPartNumber == partNumber &&
$listItem.hasClass("active")
) {
selectedPartNumber = null;
$listItem.removeClass("active");
return;
}
selectedPartNumber = partNumber;
$listItem.addClass("active").siblings().removeClass("active");
console.log(
"Parte selezionata tramite riga:",
@ -687,30 +706,88 @@ $(document).ready(function () {
$picker.toggle();
});
// === Gestione cambio colore ===
partsListElement
.off("click.colorOption")
.on("click.colorOption", ".color-option", function (e) {
e.stopPropagation();
e.preventDefault();
const $this = $(this);
const color = $this.data("color");
const $listItem = $this.closest("li");
const partNumber = $listItem.data("part-number");
console.log(
"Cliccato .color-option, colore:",
color,
"per parte:",
partNumber,
);
// Salva il nuovo colore
partColors[partNumber] = color;
// Aggiorna il colore visivo nel selettore
$listItem
.find(".selected-color")
.css("background-color", color);
let currentPhoto = $("#samplePhotoAnnotations").attr("src");
let annotations = photoAnnotations[currentPhoto];
if (annotations) {
annotations.markers.forEach(function (m) {
if (m.partNumber == partNumber) {
m.color = color;
let group = markerObjects[m.id];
if (group) {
let circle = group.item(0);
circle.set("fill", color);
circle.set("stroke", color);
}
}
});
fabricCanvas.renderAll();
}
// Chiudi la palette e aggiorna canvas
$this.closest(".color-picker").hide();
updateMarkers();
markUnsaved();
});
// === Slider locale per dimensione marker ===
partsListElement
.off("input.localMarkerSize")
.on("input.localMarkerSize", ".marker-size-slider", function (e) {
e.stopPropagation();
e.preventDefault();
// Riferimenti alla parte e valore scelto
const $slider = $(this);
const partNumber = $slider.closest("li").data("part-number");
const newSize = parseInt($slider.val());
// Aggiorna il valore visivo accanto allo slider
$slider.siblings(".marker-size-value").text(newSize + "px");
// Memorizza la nuova dimensione per quella parte
partSizes[partNumber] = newSize;
// Aggiorna i marker sul canvas
updateMarkers();
// Segnala modifiche non salvate
markUnsaved();
console.log(
`Dimensione marker aggiornata per parte ${partNumber}: ${newSize}px`,
);
});
$(document)
.off("click.colorPicker")
.on("click.colorPicker", function (e) {
@ -721,6 +798,48 @@ $(document).ready(function () {
});
}
function enableDragDropPartsList() {
const list = $("#partsListAnnotations");
if (!list.length || typeof $.ui === "undefined" || !$.ui.sortable) {
console.warn("jQuery UI o .sortable non disponibile. Ritento...");
setTimeout(enableDragDropPartsList, 100);
return;
}
if (list.hasClass("ui-sortable")) {
list.sortable("destroy"); // evita duplicati
}
list.sortable({
items: "li.list-group-item",
placeholder: "list-group-item placeholder",
axis: "y",
containment: "parent",
tolerance: "pointer",
start: function (e, ui) {
ui.item.addClass("dragging");
},
stop: function (e, ui) {
ui.item.removeClass("dragging");
const newOrder = [];
list.find("li").each(function () {
const partNumber = $(this).data("part-number");
const part = partsListData.find(
(p) => p.part_number == partNumber,
);
if (part) newOrder.push(part);
});
partsListData = newOrder;
console.log(
"Ordine parti aggiornato:",
partsListData.map((p) => p.part_number),
);
markUnsaved();
updateMarkers();
},
});
}
// Delegazione evento per il checkbox
$(document)
.off("change.showMix", "#showMixPartsAnnotations")
@ -753,6 +872,7 @@ $(document).ready(function () {
}
updatePartsList();
updateMarkers();
setTimeout(enableDragDropPartsList, 50);
if (
photoAnnotations[$("#samplePhotoAnnotations").attr("src")]
?.hasDescriptions
@ -860,9 +980,9 @@ $(document).ready(function () {
"updateMarkers chiamato, markerObjects:",
Object.keys(markerObjects),
);
for (let partNumber in markerObjects) {
fabricCanvas.remove(markerObjects[partNumber]);
delete markerObjects[partNumber];
for (let markerId in markerObjects) {
fabricCanvas.remove(markerObjects[markerId]);
delete markerObjects[markerId];
}
markerObjects = {};
@ -892,8 +1012,10 @@ $(document).ready(function () {
return;
}
const radius = 12;
const fontSize = 16;
const size = partSizes[marker.partNumber] || globalMarkerSize;
const radius = size / 2;
const fontSize = Math.max(10, Math.round(size * 0.65));
const markerColor =
marker.color || partColors[marker.partNumber] || "#ff0000";
@ -941,6 +1063,9 @@ $(document).ready(function () {
lockRotation: true,
});
group.markerId = marker.id;
group.partNumber = marker.partNumber;
group.on("moving", function () {
marker.x = this.left / photoData.scale;
marker.y = this.top / photoData.scale;
@ -953,7 +1078,7 @@ $(document).ready(function () {
});
fabricCanvas.add(group);
markerObjects[marker.partNumber] = group;
markerObjects[marker.id] = group;
});
fabricCanvas.renderAll();
@ -1013,7 +1138,7 @@ $(document).ready(function () {
scaleY: 1,
backgroundColor: "transparent",
fontFamily: "Arial",
fontSize: 24,
fontSize: Math.max(16, Math.round(globalMarkerSize * 0.8)),
fill: "#000000",
padding: 10,
editable: false,
@ -1107,9 +1232,9 @@ $(document).ready(function () {
}
}
for (let partNumber in markerObjects) {
fabricCanvas.remove(markerObjects[partNumber]);
delete markerObjects[partNumber];
for (let markerId in markerObjects) {
fabricCanvas.remove(markerObjects[markerId]);
delete markerObjects[markerId];
}
markerObjects = {};
@ -1144,9 +1269,9 @@ $(document).ready(function () {
photoAnnotations[currentPhoto].markers.length > 0
) {
const lastMarker = photoAnnotations[currentPhoto].markers.pop();
if (markerObjects[lastMarker.partNumber]) {
fabricCanvas.remove(markerObjects[lastMarker.partNumber]);
delete markerObjects[lastMarker.partNumber];
if (markerObjects[lastMarker.id]) {
fabricCanvas.remove(markerObjects[lastMarker.id]);
delete markerObjects[lastMarker.id];
fabricCanvas.renderAll();
}
console.log("Ultimo marker rimosso:", lastMarker);

View File

@ -3,6 +3,9 @@ ob_start();
session_start();
require_once '../../vendor/autoload.php';
Dotenv\Dotenv::createImmutable(dirname(__DIR__, 2))->safeLoad();
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
$response = ['error' => '', 'rows' => [], 'columns' => [], 'template_id' => 0, 'filename' => '', 'excel_data' => []];
try {

View File

@ -24,7 +24,17 @@ class VisualLimsApiClient
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new VisualLimsApiClient();
$dotenv = Dotenv::createImmutable(dirname(__DIR__, 3));
$dotenv->load();
$simulate = ($_ENV['SIMULATE_EXPORT_LIMS'] ?? '') === 'true';
if ($simulate) {
require_once __DIR__ . '/VisualLimsApiClientMock.class.php';
self::$instance = new VisualLimsApiClientMock();
} else {
self::$instance = new VisualLimsApiClient();
}
}
return self::$instance;
}
@ -193,6 +203,54 @@ class VisualLimsApiClient
return json_decode($response, true);
}
/**
* POST a file as multipart/form-data (used for photo/attachment uploads).
*
* @param string $endpoint OData endpoint, e.g. "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()
{
return $this->baseUrl;

View File

@ -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];
}
}

View File

@ -3,6 +3,9 @@ require_once dirname(__DIR__, 3) . '/vendor/autoload.php';
use Dotenv\Dotenv;
Dotenv::createImmutable(dirname(__DIR__, 3))->safeLoad();
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
class DBHandlerSelect
{
private static $instance = null;

View File

@ -0,0 +1,131 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$data = json_decode(file_get_contents('php://input'), true);
$sourceIddatadb = isset($data['source_iddatadb']) ? (int)$data['source_iddatadb'] : 0;
$targetList = $data['target_iddatadb_list'] ?? [];
$targetIds = array_values(array_unique(array_filter(array_map('intval', (array)$targetList), function ($v) use ($sourceIddatadb) {
return $v > 0 && $v !== $sourceIddatadb;
})));
if ($sourceIddatadb <= 0 || empty($targetIds)) {
echo json_encode([
'success' => false,
'message' => 'Missing source or target records'
]);
exit;
}
try {
$pdo->beginTransaction();
// 1. Load source parts
$stmtParts = $pdo->prepare("
SELECT id, part_number, part_description, mix, idmatrice, note, dateexpiry
FROM identification_parts
WHERE iddatadb = ?
ORDER BY part_number ASC, id ASC
");
$stmtParts->execute([$sourceIddatadb]);
$sourceParts = $stmtParts->fetchAll(PDO::FETCH_ASSOC);
if (empty($sourceParts)) {
$pdo->rollBack();
echo json_encode([
'success' => false,
'message' => 'No parts found for source record'
]);
exit;
}
// 2. Prepare statements
$stmtInsertPart = $pdo->prepare("
INSERT INTO identification_parts
(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())
");
$stmtLoadCF = $pdo->prepare("
SELECT field_id, value_id, value_text
FROM identification_parts_customfields
WHERE part_id = ?
ORDER BY id ASC
");
$stmtInsertCF = $pdo->prepare("
INSERT INTO identification_parts_customfields
(part_id, field_id, value_id, value_text, created_at, updated_at)
VALUES
(:part_id, :field_id, :value_id, :value_text, NOW(), NOW())
");
$details = [];
$totalClonedParts = 0;
// 3. Clone source parts to each target record
foreach ($targetIds as $targetIddatadb) {
$clonedCountForTarget = 0;
foreach ($sourceParts as $part) {
$stmtInsertPart->execute([
':iddatadb' => $targetIddatadb,
':part_number' => $part['part_number'],
':part_description' => $part['part_description'],
':mix' => $part['mix'] ?? 'N',
':idmatrice' => $part['idmatrice'] !== '' ? $part['idmatrice'] : null,
':note' => $part['note'] ?? null,
':dateexpiry' => $part['dateexpiry'] ?? null,
]);
$newPartId = (int)$pdo->lastInsertId();
// Load source custom fields for this part
$stmtLoadCF->execute([(int)$part['id']]);
$customFields = $stmtLoadCF->fetchAll(PDO::FETCH_ASSOC);
foreach ($customFields as $cf) {
$stmtInsertCF->execute([
':part_id' => $newPartId,
':field_id' => (int)$cf['field_id'],
':value_id' => ($cf['value_id'] !== null && $cf['value_id'] !== '') ? (int)$cf['value_id'] : null,
':value_text' => $cf['value_text'] !== '' ? $cf['value_text'] : null,
]);
}
$clonedCountForTarget++;
$totalClonedParts++;
}
$details[] = [
'target_iddatadb' => $targetIddatadb,
'cloned_parts' => $clonedCountForTarget
];
}
$pdo->commit();
echo json_encode([
'success' => true,
'source_iddatadb' => $sourceIddatadb,
'cloned_targets' => count($targetIds),
'total_cloned_parts' => $totalClonedParts,
'details' => $details
]);
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
echo json_encode([
'success' => false,
'message' => 'Clone failed: ' . $e->getMessage()
]);
}

View File

@ -0,0 +1,79 @@
<?php
header('Content-Type: application/json');
require_once(__DIR__ . '/include/headscript.php');
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$input = json_decode(file_get_contents('php://input'), true);
$templateId = (int)($input['template_id'] ?? 0);
if ($templateId <= 0) {
echo json_encode(['success' => false, 'message' => 'Invalid template_id']);
exit;
}
// If already exists, do nothing
$stmt = $pdo->prepare("SELECT COUNT(*) FROM template_fixed_mapping WHERE template_id = ?");
$stmt->execute([$templateId]);
if ((int)$stmt->fetchColumn() > 0) {
echo json_encode(['success' => true, 'created' => 0, 'message' => 'Fixed fields already exist']);
exit;
}
/**
* FIXED FIELDS STANDARD (no UI selection)
* is_manual always 1
* is_visible_import default 1
*/
$fixedFields = [
// fixed_field_key, data_type
['ClienteResponsabile', 'INT'],
['ClienteFornitore', 'INT'],
['ClienteAnalisi', 'INT'],
['MoltiplicatorePrezzo', 'INT'],
['AnagraficaCertestObject', 'INT'],
['AnagraficaCertestService', 'INT'],
['ConsegnaRichiesta', 'DATE'],
];
try {
$pdo->beginTransaction();
$ins = $pdo->prepare("
INSERT INTO template_fixed_mapping
(template_id, fixed_field_key, is_manual, data_type, is_required, default_value, is_visible_import)
VALUES
(:template_id, :fixed_field_key, 1, :data_type, 1, NULL, 1)
");
foreach ($fixedFields as $f) {
$ins->execute([
':template_id' => $templateId,
':fixed_field_key' => $f[0],
':data_type' => $f[1],
]);
}
$pdo->commit();
// Return rows (for client render)
$stmt = $pdo->prepare("
SELECT id, template_id, fixed_field_key, is_manual, data_type, is_required, default_value, is_visible_import
FROM template_fixed_mapping
WHERE template_id = ?
ORDER BY id ASC
");
$stmt->execute([$templateId]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['success' => true, 'created' => count($rows), 'rows' => $rows]);
} catch (Exception $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
}

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,8 @@ try {
exit(1);
}
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
// Recupera le variabili d'ambiente
$dbHost = $_ENV['DB_HOST'];
$dbName = $_ENV['DB_DATABASE'];

View File

@ -0,0 +1 @@
https://93.43.5.102/limsapi/api/odata/Matrice

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
2026-04-03 10:07:01 - Aggiornamento completato: 3198 record inseriti.

File diff suppressed because one or more lines are too long

View File

@ -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
-->

View File

@ -0,0 +1,48 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once __DIR__ . '/class/db-functions.php';
header('Content-Type: application/json');
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['success' => false, 'message' => 'Method not allowed']);
exit;
}
$partId = isset($_POST['part_id']) ? (int)$_POST['part_id'] : 0;
$analysisRecordkey = trim($_POST['analysis_recordkey'] ?? '');
if ($partId <= 0 || $analysisRecordkey === '') {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Missing required data']);
exit;
}
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("
DELETE FROM identification_parts_analyses
WHERE part_id = :part_id
AND analysis_recordkey = :analysis_recordkey
");
$stmt->execute([
':part_id' => $partId,
':analysis_recordkey' => $analysisRecordkey,
]);
echo json_encode([
'success' => true,
'message' => 'Association deleted'
]);
} catch (Throwable $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'message' => $e->getMessage()
]);
}

View File

@ -1,26 +1,27 @@
<?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'])) {
header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Invalid ID"));
header("Location: templates_dashboard.php?status=error&message=" . urlencode("Invalid ID"));
exit;
}
$id = intval($_GET['id']); // Sanifica l'ID
$id = intval($_GET['id']);
// Recupera il template dal database
// Retrieve template from database
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("SELECT * FROM excel_templates WHERE id = ?");
$stmt->execute([$id]);
$template = $stmt->fetch(PDO::FETCH_ASSOC);
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;
}
// Recupera tutte le routine dal database
// Retrieve all routines
$stmt = $pdo->prepare("SELECT * FROM routine");
$stmt->execute();
$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 href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<?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://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>
@ -44,19 +44,22 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Update XLS Template</h5>
<h5 class="mb-0">Update Template</h5>
</div>
<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>
<ul class="mb-0">
<li>Template Name</li>
<li>Row Header and Column Header: where the title of the excel starts</li>
<li>Schema</li>
<li>Source Type</li>
<li>Schema and Client</li>
<li>Row Header and Column Header only for XLS templates</li>
</ul>
</div>
</div>
@ -69,34 +72,44 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
</div>
</div>
</div>
<div class="card-body">
<div class="col-12">
<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">
<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 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>
<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 class="mb-3">
<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>
<div class="mb-3" id="startColumnWrapper">
<label class="form-label"><?= htmlspecialchars($columnheader, ENT_QUOTES, 'UTF-8'); ?> *</label>
<input type="text" name="start_column" id="startColumn" class="form-control" value="<?php echo htmlspecialchars($template['start_column'] ?? ''); ?>">
</div>
<div class="mb-3">
<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 class="mb-3">
<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>
<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'] ?? 'datadb'); ?>" readonly required>
</div>
<div class="mb-3">
@ -110,12 +123,12 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
<div class="mb-3">
<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 class="mb-3">
<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 class="mb-3">
@ -128,7 +141,7 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
<select name="client_id" id="clientSelect" class="form-control" required>
<option value="">Select a client...</option>
</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 class="mb-3">
@ -136,7 +149,7 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
<select name="schema_id" id="schemaSelect" class="form-control" required>
<option value="">Select a schema...</option>
</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 class="mb-3">
@ -144,11 +157,12 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
<select name="idroutine" id="routineSelect" class="form-control">
<option value="">Select a routine...</option>
<?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']); ?>
</option>
<?php endforeach; ?>
</select>
<div id="routineDetails" class="mt-2" style="display: none;">
<h6>Routine Details</h6>
<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 class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
@ -175,9 +191,8 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
<script>
document.addEventListener("DOMContentLoaded", function() {
// Verifica che jQuery sia caricato
if (typeof jQuery === 'undefined') {
alert("Errore: jQuery non è caricato. Contatta l'amministratore.");
alert("Error: jQuery is not loaded.");
return;
}
@ -192,12 +207,15 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
const routineAction2 = document.getElementById("routineAction2");
const routineAction3 = document.getElementById("routineAction3");
if (!form || !clientLoadingStatus || !schemaLoadingStatus || !routineSelect || !routineDetails) {
alert("Errore: Uno o più elementi della pagina non sono stati trovati. Contatta l'amministratore.");
return;
}
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");
const selectedClientId = <?php echo json_encode((int)($template['idclient'] ?? 0)); ?>;
const selectedSchemaId = <?php echo json_encode((int)($template['idschema'] ?? 0)); ?>;
// Inizializza Select2
$('#clientSelect').select2({
placeholder: "Search for a client...",
allowClear: true
@ -213,108 +231,164 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
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() {
try {
clientLoadingStatus.style.display = 'inline';
clientLoadingStatus.textContent = 'Recupero clienti in corso...';
clientLoadingStatus.textContent = 'Loading clients...';
const response = await fetch("get_clienti.php", {
method: "GET",
headers: {
"Content-Type": "application/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");
select.innerHTML = '<option value="">Select a client...</option>';
data.value.forEach(client => {
const nome = client.Nominativo || "Nome non disponibile";
const id = client.IdCliente || "ID non disponibile";
const nome = client.Nominativo || "Name not available";
const id = client.IdCliente || "";
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;
}
select.add(option);
});
$(select).trigger('change');
clientLoadingStatus.textContent = "Clienti caricati.";
clientLoadingStatus.textContent = "Clients loaded.";
} catch (error) {
clientLoadingStatus.textContent = "Errore nel caricamento.";
clientLoadingStatus.textContent = "Loading error.";
Swal.fire({
title: "Errore!",
text: "Impossibile caricare i clienti: " + error.message,
title: "Error!",
text: "Unable to load clients: " + error.message,
icon: "error",
confirmButtonText: "OK"
});
} finally {
setTimeout(() => clientLoadingStatus.style.display = 'none', 2000);
setTimeout(() => clientLoadingStatus.style.display = 'none', 1500);
}
}
// Carica gli schemi
async function loadSchemas() {
try {
schemaLoadingStatus.style.display = 'inline';
schemaLoadingStatus.textContent = 'Caricamento schemi in corso...';
schemaLoadingStatus.textContent = 'Loading schemas...';
const response = await fetch("get_schemi.php", {
method: "GET",
headers: {
"Content-Type": "application/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");
select.innerHTML = '<option value="">Select a schema...</option>';
data.value.forEach(schema => {
const nome = schema.Nome || "Nome non disponibile";
const id = schema.IdSchemaCustomFields || "ID non disponibile";
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 || "Name not available";
const id = schema.IdSchemaCustomFields || "";
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;
}
select.add(option);
});
$(select).trigger('change');
schemaLoadingStatus.textContent = "Schemi caricati.";
schemaLoadingStatus.textContent = "Schemas loaded.";
} catch (error) {
schemaLoadingStatus.textContent = "Errore nel caricamento.";
schemaLoadingStatus.textContent = "Loading error.";
Swal.fire({
title: "Errore!",
text: "Impossibile caricare gli schemi: " + error.message,
title: "Error!",
text: "Unable to load schemas: " + error.message,
icon: "error",
confirmButtonText: "OK"
});
} finally {
setTimeout(() => schemaLoadingStatus.style.display = 'none', 2000);
setTimeout(() => schemaLoadingStatus.style.display = 'none', 1500);
}
}
// Carica i dati
async function loadData() {
try {
await loadClients();
await loadSchemas();
} catch (error) {
Swal.fire({
title: "Errore!",
text: "Errore nel caricamento dei dati: " + error.message,
title: "Error!",
text: "Error while loading data: " + error.message,
icon: "error",
confirmButtonText: "OK"
});
}
}
loadData();
// Routine dettagli
const routines = <?php echo json_encode($routines); ?>;
function updateRoutineDetails() {
const selectedId = routineSelect.value;
routineDetails.style.display = selectedId ? 'block' : 'none';
if (selectedId) {
const routine = routines.find(r => r.idroutine == selectedId);
if (routine) {
routineName.textContent = routine.name || 'N/A';
routineDescription.textContent = routine.description || 'N/A';
@ -336,10 +410,10 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
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) {
e.preventDefault();
@ -351,8 +425,8 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!clientId) {
Swal.fire({
title: "Errore!",
text: "Per favore seleziona un cliente.",
title: "Error!",
text: "Please select a client.",
icon: "error",
confirmButtonText: "OK"
});
@ -373,8 +447,8 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!schemaId) {
Swal.fire({
title: "Errore!",
text: "Per favore seleziona uno schema.",
title: "Error!",
text: "Please select a schema.",
icon: "error",
confirmButtonText: "OK"
});
@ -387,10 +461,10 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
const nameMatch = optionText.match(/^(.+?)(?:\s*\(ID:\s*\d+\))?$/);
schemaName = nameMatch ? nameMatch[1].trim() : optionText;
}
formData.append("idschema", schemaId);
formData.append("schemaname", schemaName);
// Aggiungi idroutine
const routineId = routineSelect.value;
formData.append("idroutine", routineId);
@ -402,8 +476,8 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
.then(data => {
if (data.success) {
Swal.fire({
title: "Successo!",
text: "Template aggiornato con successo!",
title: "Success!",
text: "Template updated successfully!",
icon: "success",
confirmButtonText: "OK"
}).then(() => {
@ -411,17 +485,17 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
});
} else {
Swal.fire({
title: "Errore!",
title: "Error!",
text: data.message,
icon: "error",
confirmButtonText: "OK"
});
}
})
.catch(error => {
.catch(() => {
Swal.fire({
title: "Errore!",
text: "Si è verificato un errore imprevisto.",
title: "Error!",
text: "An unexpected error occurred.",
icon: "error",
confirmButtonText: "OK"
});

View File

@ -12,3 +12,319 @@
2025-08-26 16:49:24 - Risposta non JSON valida: <?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="DevExpress.ExpressApp.SystemModule" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="DashboardViewItemDescriptor"><Key><PropertyRef Name="ViewId" /></Key><Property Name="ViewId" Type="Edm.String" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem" BaseType="DevExpress.ExpressApp.NonPersistentLiteObject" Abstract="true"><Property Name="Visibility" Type="DevExpress.ExpressApp.Editors.ViewItemVisibility" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem_1OfIModelDashboardViewItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem" Abstract="true" /><EntityType Name="ViewDashboardOrganizationItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem_1OfIModelDashboardViewItem"><Property Name="ObjectType" Type="System.Type" /><Proper
2025-08-26 16:50:23 - Risposta non JSON valida: <?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="DevExpress.ExpressApp.SystemModule" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="DashboardViewItemDescriptor"><Key><PropertyRef Name="ViewId" /></Key><Property Name="ViewId" Type="Edm.String" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem" BaseType="DevExpress.ExpressApp.NonPersistentLiteObject" Abstract="true"><Property Name="Visibility" Type="DevExpress.ExpressApp.Editors.ViewItemVisibility" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem_1OfIModelDashboardViewItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem" Abstract="true" /><EntityType Name="ViewDashboardOrganizationItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem_1OfIModelDashboardViewItem"><Property Name="ObjectType" Type="System.Type" /><Proper
2025-09-08 08:39:17 - Risposta non JSON valida: <?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="DevExpress.ExpressApp.SystemModule" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="DashboardViewItemDescriptor"><Key><PropertyRef Name="ViewId" /></Key><Property Name="ViewId" Type="Edm.String" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem" BaseType="DevExpress.ExpressApp.NonPersistentLiteObject" Abstract="true"><Property Name="Visibility" Type="DevExpress.ExpressApp.Editors.ViewItemVisibility" Nullable="false" /></EntityType><EntityType Name="DashboardOrganizationItem_1OfIModelDashboardViewItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem" Abstract="true" /><EntityType Name="ViewDashboardOrganizationItem" BaseType="DevExpress.ExpressApp.SystemModule.DashboardOrganizationItem_1OfIModelDashboardViewItem"><Property Name="ObjectType" Type="System.Type" /><Proper
2026-01-27 15:33:53 - Errore nel recupero dati: HTTP 404, Risposta:
2026-01-27 15:34:06 - Errore nel recupero dati: HTTP 404, Risposta:
2026-01-27 15:34:10 - Errore nel recupero dati: HTTP 404, Risposta:
2026-01-27 15:35:13 - Errore nel recupero dati: HTTP 404, Risposta:
2026-01-29 14:33:38 [ClienteResponsabile] Errore nel recupero dati: HTTP 404, Risposta:
2026-01-29 14:33:39 [MoltiplicatorePrezzo] 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-01-29 14:34:04 [ClienteResponsabile] Errore nel recupero dati: HTTP 404, Risposta:
2026-01-29 14:37:29 [ClienteResponsabile] Errore nel recupero dati: HTTP 404, Risposta:
2026-01-29 14:41:55 [ClienteResponsabile] Errore nel recupero dati: HTTP 404, Risposta:
2026-01-29 14:42:03 [ClienteResponsabile] Errore nel recupero dati: HTTP 404, Risposta:
2026-01-29 14:42:52 [ClienteResponsabile] Errore nel recupero dati: HTTP 404, Risposta:
2026-01-29 14:43:00 [ClienteResponsabile] Errore nel recupero dati: HTTP 404, Risposta:
2026-01-30 10:50:43 [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-01-30 10:50:43 [MoltiplicatorePrezzo] 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-01-30 11:09:22 [MoltiplicatorePrezzo] 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-19 10:00:13 [MoltiplicatorePrezzo] 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-19 10:00:42 [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-19 10:00:44 [MoltiplicatorePrezzo] 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-23 10:44:04 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:42 [AnagraficaCertestService] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:42 [AnagraficaCertestObject] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:42 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:42 [AnagraficaCertestService] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:42 [AnagraficaCertestObject] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:42 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:42 [AnagraficaCertestService] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:42 [AnagraficaCertestObject] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:42 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:43 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:43 [AnagraficaCertestObject] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:43 [AnagraficaCertestService] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:52 [AnagraficaCertestObject] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:52 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:52 [AnagraficaCertestService] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:52 [AnagraficaCertestObject] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:52 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:52 [AnagraficaCertestService] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:52 [AnagraficaCertestObject] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:52 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:52 [AnagraficaCertestService] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:52 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:52 [AnagraficaCertestObject] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:44:53 [AnagraficaCertestService] Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:48:26 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:48:26 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:48:27 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:48:27 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:48:27 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:49:04 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:49:04 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:49:04 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:49:05 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 10:49:05 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 14:26:52 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 14:26:59 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 15:54:16 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 16:22:45 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
2026-02-23 16:32:17 - Autenticazione fallita: HTTP 404, Errore cURL: , Risposta: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.</p>
</BODY></HTML>
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-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"}

View File

@ -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 });
})();
}
})();

File diff suppressed because it is too large Load Diff

View File

@ -13,9 +13,31 @@ if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
$uploadDir = realpath(__DIR__ . '/../photostrf') . '/';
// 🔹 Base URL API
$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
function validateDate($value)
{
@ -27,15 +49,38 @@ function validateDate($value)
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 {
$iddatadb = $_POST['iddatadb'] ?? null;
if (!$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
// Also fetch fixed fields stored in datadb
$stmt = $pdo->prepare("
SELECT d.idclient AS clienteId, et.idschema AS schemaId
SELECT d.idclient AS clienteId, et.idschema AS schemaId,
d.cliente_responsabile_id,
d.moltiplicatore_prezzo_id,
d.anagrafica_certest_object_id,
d.anagrafica_certest_service_id,
d.cliente_fornitore_id,
d.clienteAnalisi,
d.consegna_richiesta
FROM datadb as d
INNER JOIN excel_templates as et ON d.templateid = et.id
WHERE d.iddatadb = :iddatadb
@ -51,15 +96,58 @@ try {
$clienteId = (int) $result['clienteId'];
$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("
SELECT part_number, part_description, material, color, mix, idmatrice
FROM identification_parts
WHERE iddatadb = :iddatadb
SELECT id AS part_id, part_number, part_description, material, color, mix, idmatrice, dateexpiry
FROM identification_parts
WHERE iddatadb = :iddatadb
ORDER BY CAST(part_number AS UNSIGNED) ASC, part_number ASC
");
$stmt->execute(['iddatadb' => $iddatadb]);
$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
$stmt = $pdo->prepare("
SELECT
@ -93,12 +181,21 @@ try {
// 🔹 Initialize API client
$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 = [
"Cliente" => $clienteId,
"SchemaCustomField" => $schemaId,
"Richiedente" => "Test Web Import",
"Descrizione" => "TEST CommessaWeb",
"Cliente" => $clienteId,
"SchemaCustomField" => $schemaId,
"Richiedente" => "From TRFSmart Application", // TODO: replace with real value
"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
@ -114,7 +211,7 @@ try {
// Salva log
$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"];
$commessaWebCode = substr($commessaWeb["CodiceCommessa"] ?? "TEST CommessaWeb", 0, 30); // Limite a 30 caratteri
@ -131,11 +228,13 @@ try {
}
$campionePayload = [
"Commessa" => $commessaId,
"Matrice" => $matriceId,
"SottoMatrice" => null,
"Commessa" => $commessaId,
"Matrice" => $matriceId,
"SottoMatrice" => null,
"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
@ -160,20 +259,191 @@ try {
// Salva log per STEP 6
$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
if (!empty($fieldValues)) {
// GET con espansione per CustomField
$expand = "CommesseCustomFields(\$expand=CustomField)";
$commessaWithFields = $api->get("CommessaWeb(" . $commessaId . ")?\$expand=" . $expand);
$commessaWithFields = $api->get("CommessaWeb({$commessaId})?\$expand={$expand}");
// Logga il GET
$logContentGet = "curl --location --request GET '{$apiBaseUrl}CommessaWeb({$commessaId})?\$expand=CommesseCustomFields(\$expand=CustomField)' \\\n" .
"--header 'Authorization: Bearer ••••••'\n\n" .
"RESPONSE:\n" . json_encode($commessaWithFields, JSON_PRETTY_PRINT);
$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
$commessaCustomFields = [];
@ -186,7 +456,7 @@ try {
// Valida se il campo è di tipo Data
if ($fieldType === 'Data' && $newValue !== $currentValue) {
$newValue = validateDate($newValue);
$newValue = formatDateToExport($newValue);
}
$commessaCustomFields[] = [
@ -195,6 +465,7 @@ try {
];
}
$logFileStep7 = null;
if (!empty($commessaCustomFields)) {
$updatePayload = ["CommesseCustomFields" => $commessaCustomFields];
@ -209,7 +480,7 @@ try {
$logContentStep7 .= "\n\nRESPONSE:\n" . json_encode($patchResponse, JSON_PRETTY_PRINT);
$logFileStep7 = $logDir . "commessa_{$commessaId}_update_step7_" . time() . ".txt";
file_put_contents($logFileStep7, $logContentStep7);
$writeLog($logFileStep7, $logContentStep7, "STEP 7 - PATCH CustomFields (commessa={$commessaId})");
}
}
@ -226,18 +497,40 @@ try {
]);
// 🔹 STEP 9: Send CommessaWeb to laboratory (commentato come richiesto)
/*
$sendResult = $api->post("CommessaWeb({$commessaId})/InviaCommessa", []);
// Logga il POST
$logContentStep9 = "curl --location --request POST '{$apiBaseUrl}CommessaWeb({$commessaId})/InviaCommessa' \\\n" .
"--header 'Content-Type: application/json' \\\n" .
"--header 'Authorization: Bearer ••••••' \\\n" .
"--data '{}'\n\n" .
"RESPONSE:\n" . json_encode($sendResult, JSON_PRETTY_PRINT);
"--header 'Content-Type: application/json' \\\n" .
"--header 'Authorization: Bearer ••••••' \\\n" .
"--data '{}'\n\n" .
"RESPONSE:\n" . json_encode($sendResult, JSON_PRETTY_PRINT);
$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
$expand = "CommesseCustomFields(\$expand=CustomField)";
@ -248,8 +541,24 @@ try {
"--header 'Authorization: Bearer ••••••'\n\n" .
"RESPONSE:\n" . json_encode($commessaAfterPatch, JSON_PRETTY_PRINT);
$logFileStep10 = $logDir . "commessa_{$commessaId}_get_step10_" . time() . ".txt";
file_put_contents($logFileStep10, $logContentStep10);
$writeLog($logFileStep10, $logContentStep10, "STEP 10 - GET verify (commessa={$commessaId})");
// 🔹 STEP 10.1: Save final CodiceCommessa into datadb.commessaweb
// After ImportaCommessa, the API returns the final LIMS job code in CodiceCommessa.
// Example: CodiceCommessa = 2614795, CodiceCommessaWeb = 26C0103.
$finalCodiceCommessa = trim((string)($commessaAfterPatch['CodiceCommessa'] ?? ''));
if ($finalCodiceCommessa !== '') {
$stmt = $pdo->prepare("
UPDATE datadb
SET commessaweb = :commessaweb,
status = 'l'
WHERE iddatadb = :iddatadb
");
$stmt->execute([
'commessaweb' => substr($finalCodiceCommessa, 0, 30),
'iddatadb' => $iddatadb
]);
}
// 🔹 STEP 11: Prepare final response
$finalCommessa = [
"Cliente" => $clienteId,
@ -262,17 +571,22 @@ try {
];
echo json_encode([
"success" => true,
"commessaWeb" => $finalCommessa,
"success" => true,
"idcommessaweb" => $commessaId,
"commessaweb" => $finalCodiceCommessa ?: $commessaWebCode,
"commessaWeb" => $finalCommessa,
"commessaWebApiResponse" => $commessaWeb, // Incluso per debug
"totalCampioni" => count($campioni),
"totalCustomFields" => count($commessaAfterPatch["CommesseCustomFields"] ?? []),
"message" => "Export successful",
"logFiles" => [
"step5_create" => $logFileStep5,
"step6_campioni" => $logFileStep6,
"step7_patch" => $logFileStep7,
"step10_get" => $logFileStep10
"totalCampioni" => count($campioni),
"totalCustomFields" => count($commessaAfterPatch["CommesseCustomFields"] ?? []),
"totalPhotos" => count($photos),
"message" => "Export successful",
"logFiles" => [
"step5_create" => $logFileStep5,
"step5_2_photos" => $logFilePhotos,
"step6_campioni" => $logFileStep6,
"step7_patch" => $logFileStep7 ?? null,
"step9_1_importa" => $logFileStep91,
"step10_get" => $logFileStep10
]
]);
} catch (Exception $e) {
@ -282,10 +596,12 @@ try {
"success" => false,
"message" => "Export failed: " . $e->getMessage(),
"logFiles" => [
"step5_create" => $logFileStep5 ?? null,
"step6_campioni" => $logFileStep6 ?? null,
"step7_patch" => $logFileStep7 ?? null,
"step10_get" => $logFileStep10 ?? null
"step5_create" => $logFileStep5 ?? null,
"step5_2_photos" => $logFilePhotos ?? null,
"step6_campioni" => $logFileStep6 ?? null,
"step7_patch" => $logFileStep7 ?? null,
"step9_1_importa" => $logFileStep91 ?? null,
"step10_get" => $logFileStep10 ?? null
]
]);
}

View File

@ -0,0 +1,40 @@
<?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: GET api/odata/AnagraficaCertestObject
$endpoint = 'AnagraficaCertestObject';
// Opzionale: parametri OData ($top, $filter, $orderby, ecc.)
$options = []; // es: ['$top' => 100]
// Debug: salva URL usata
$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_url.txt', $full_url . PHP_EOL, FILE_APPEND);
// Chiamata API
$data = $api->get($endpoint, $options);
// Salva il JSON in locale
file_put_contents(__DIR__ . '/anagrafica_certest_object_response.json', json_encode($data, JSON_PRETTY_PRINT));
echo json_encode($data);
} catch (Exception $e) {
file_put_contents(
__DIR__ . '/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()]);
}

View File

@ -0,0 +1,40 @@
<?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: GET api/odata/AnagraficaCertestService
$endpoint = 'AnagraficaCertestService';
// Opzionale: parametri OData ($top, $filter, $orderby, ecc.)
$options = []; // es: ['$top' => 100]
// Debug: salva URL usata
$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_url.txt', $full_url . PHP_EOL, FILE_APPEND);
// Chiamata API
$data = $api->get($endpoint, $options);
// Salva il JSON in locale
file_put_contents(__DIR__ . '/anagrafica_certest_service_response.json', json_encode($data, JSON_PRETTY_PRINT));
echo json_encode($data);
} catch (Exception $e) {
file_put_contents(
__DIR__ . '/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()]);
}

View File

@ -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);
}

View File

@ -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()]);
}

View File

@ -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()]);
}

View File

@ -0,0 +1,60 @@
<?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;
$debug = isset($_GET['debug']) ? (int)$_GET['debug'] : 0;
if ($idMatrice <= 0) {
http_response_code(400);
echo json_encode(['error' => 'Missing or invalid id_matrice']);
exit;
}
$api = VisualLimsApiClient::getInstance();
$filter = rawurlencode("Matrice/IdMatrice eq $idMatrice");
$endpoint = "Analisi?\$filter={$filter}";
$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)
);
if ($debug === 1) {
echo json_encode([
'endpoint' => $endpoint,
'full_url' => $full_url,
'raw_response' => $data
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
}
$analisi = $data['value'] ?? [];
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()]);
}

View File

@ -0,0 +1,40 @@
<?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;
if ($idCliente <= 0) {
http_response_code(400);
echo json_encode(['error' => 'Missing or invalid id_cliente']);
exit;
}
$api = VisualLimsApiClient::getInstance();
// Build endpoint with OData $expand WITHOUT encoding '$' as %24
$endpoint = "Cliente($idCliente)?\$expand=Responsabili";
// Debug URL (real final URL)
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
$full_url = $base_url . $endpoint;
file_put_contents(__DIR__ . '/last_url.txt', $full_url . PHP_EOL, FILE_APPEND);
// Call API: options must be empty because expand is already in endpoint
$data = $api->get($endpoint, []);
file_put_contents(__DIR__ . '/cliente_responsabili_response.json', json_encode($data, JSON_PRETTY_PRINT));
// Return only the list (standard shape used by the frontend)
$responsabili = $data['Responsabili'] ?? [];
echo json_encode(['value' => $responsabili]);
} catch (Exception $e) {
file_put_contents(__DIR__ . '/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()]);
}

View File

@ -9,11 +9,28 @@ ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$api = VisualLimsApiClient::getInstance();
$api = VisualLimsApiClient::getInstance(); // also loads dotenv
// In simulate mode: return fake clients built from idclient values already in datadb.
// This ensures client dropdowns auto-select the correct row idclient, which in turn
// triggers the ClienteResponsabile select to populate via the mock.
if (($_ENV['SIMULATE_EXPORT_LIMS'] ?? '') === 'true') {
require_once __DIR__ . '/class/db-functions.php';
$pdo = DBHandlerSelect::getInstance()->getConnection();
$stmt = $pdo->query("SELECT DISTINCT idclient FROM datadb WHERE idclient IS NOT NULL AND idclient > 0 ORDER BY idclient ASC");
$ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
$fakeClients = array_map(fn($id) => [
'IdCliente' => (int) $id,
'Nominativo' => "Cliente Simulato {$id}",
'CodiceCliente' => "SIM_{$id}",
], $ids);
echo json_encode(['value' => $fakeClients]);
exit;
}
// Parametri OData
$params = [
'$select' => 'IdCliente,Nominativo,CodiceNazioneFatturazione',
'$select' => 'IdCliente,Nominativo,CodiceCliente',
'$orderby' => 'Nominativo asc'
];
@ -57,10 +74,21 @@ try {
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
$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) {
http_response_code(500);
$errorResponse = [

View File

@ -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);
}

View File

@ -21,17 +21,26 @@ try {
}
$results = [];
$cacheDir = __DIR__ . '/cache';
if (!is_dir($cacheDir)) mkdir($cacheDir, 0777, true);
foreach ($fieldIds as $customFieldId) {
$cacheFile = $cacheDir . '/customfield_' . $customFieldId . '.json';
// Use cache if fresh (1 hour)
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
$results[$customFieldId] = json_decode(file_get_contents($cacheFile), true);
continue;
}
$endpoint = "CustomField($customFieldId)?\$expand=CustomFieldsValues";
$data = $api->get($endpoint);
$values = $data['CustomFieldsValues'] ?? [];
$results[$customFieldId] = $values;
$results[$customFieldId] = $data['CustomFieldsValues'] ?? [];
file_put_contents($cacheFile, json_encode($values));
}
// Debug ფაილი
file_put_contents(__DIR__ . '/customfield_values_response.json', json_encode($results));
echo json_encode($results);
} catch (Exception $e) {
http_response_code(500);

View File

@ -0,0 +1,93 @@
<?php
// get_fixed_field_data.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);
$field = $_GET['field'] ?? ''; // es: MoltiplicatorePrezzo, AnagraficaCertestObject, ...
if (!$field) {
http_response_code(400);
echo json_encode(['error' => 'Parametro "field" mancante']);
exit;
}
$api = VisualLimsApiClient::getInstance(); // also loads dotenv as a side-effect
$simulate = ($_ENV['SIMULATE_EXPORT_LIMS'] ?? '') === 'true';
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
$data = null;
$endpoint = null;
$cache_file = null;
$options = []; // qui puoi aggiungere filtri/ordering per campo se serve
try {
switch ($field) {
case 'MoltiplicatorePrezzo':
$endpoint = 'MoltiplicatorePrezzi';
$cache_file = __DIR__ . '/cache/moltiplicatori_prezzo.json';
break;
case 'AnagraficaCertestObject':
$endpoint = 'AnagraficaCertestObject';
$cache_file = __DIR__ . '/cache/anagrafica_certest_object.json';
break;
case 'AnagraficaCertestService':
$endpoint = 'AnagraficaCertestService';
$cache_file = __DIR__ . '/cache/anagrafica_certest_service.json';
break;
case 'ClienteResponsabile':
$id_cliente = (int)($_GET['id_cliente'] ?? 0);
if ($id_cliente <= 0) {
if (!$simulate) {
http_response_code(400);
echo json_encode(['error' => 'Manca o invalido id_cliente']);
exit;
}
$id_cliente = 1; // dummy — mock ignores the actual ID
}
$endpoint = "Cliente($id_cliente)?\$expand=Responsabili";
$cache_file = __DIR__ . '/cache/cliente_responsabili_' . $id_cliente . '.json';
break;
// case 'FuturoCampo5':
// $endpoint = 'QualcosaElse';
// break;
default:
http_response_code(400);
echo json_encode(['error' => "Campo non supportato: $field"]);
exit;
}
// Caching skipped in simulate mode to avoid polluting real-data cache files
if (!$simulate && $cache_file && file_exists($cache_file) && (time() - filemtime($cache_file) < 3600)) { // 1 ora
$data = json_decode(file_get_contents($cache_file), true);
} else {
$data = $api->get($endpoint, $options);
if (!$simulate && $cache_file) {
file_put_contents($cache_file, json_encode($data, JSON_PRETTY_PRINT));
}
}
// Log ultimo URL (opzionale, per debug)
$full_url = $base_url . $endpoint . ($options ? '?' . http_build_query($options) : '');
file_put_contents(__DIR__ . '/last_urls.log', date('c') . " - $field - $full_url\n", FILE_APPEND);
echo json_encode($data);
} catch (Exception $e) {
file_put_contents(
__DIR__ . '/error_log.txt',
date('Y-m-d H:i:s') . " [$field] " . $e->getMessage() . PHP_EOL,
FILE_APPEND
);
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}

View File

@ -1,59 +1,46 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php'; // Risale a root/vendor/
use Dotenv\Dotenv;
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; }
// Set JSON header
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 {
$dotenv = Dotenv::createImmutable($envPath);
$dotenv->load();
} catch (Exception $e) {
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - Errore caricamento .env: ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
echo json_encode(['success' => false, 'message' => 'Errore caricamento configurazione: ' . $e->getMessage()]);
exit(1);
}
// Read from matrici cache (populated by get_matrici.php / warm_cache.php)
$cacheFile = __DIR__ . '/cache/matrici.json';
if (file_exists($cacheFile)) {
$data = json_decode(file_get_contents($cacheFile), true);
$matrici = $data['value'] ?? [];
} 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
$dbHost = $_ENV['DB_HOST'];
$dbName = $_ENV['DB_DATABASE'];
$dbUser = $_ENV['DB_USERNAME'];
$dbPass = $_ENV['DB_PASSWORD'];
$dbPrefix = $_ENV['DB_PREFIX'];
// Extract unique MacroMatrice values
$macroValues = [];
foreach ($matrici as $m) {
$macro = $m['MacroMatrice'] ?? null;
if ($macro !== null && $macro !== '' && substr($macro, 0, 1) !== '*') {
$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]);
} catch (PDOException $e) {
// Log errore e restituisci risposta di errore
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' => 'Errore nel recupero delle MacroMatrice: ' . $e->getMessage()]);
exit(1);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
}

View File

@ -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()]);
}

View File

@ -0,0 +1,55 @@
<?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 i Moltiplicatori Prezzo
// (dal documento: GET api/odata/MoltiplicatorePrezzi)
$endpoint = 'MoltiplicatorePrezzi';
// Opzionale: parametri OData ($top, $filter, $orderby, ecc.)
$options = [
'$orderby' => 'Descrizione asc'
];
// Debug: salva URL usata
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
$queryParts = [];
foreach ($options as $k => $v) {
// mantieni il $ nella chiave, encoda solo il valore
$queryParts[] = $k . '=' . rawurlencode($v);
}
$query = implode('&', $queryParts);
$full_url = $base_url . $endpoint . ($query ? '?' . $query : '');
file_put_contents(__DIR__ . '/last_url.txt', $full_url . PHP_EOL, FILE_APPEND);
// Chiamata API
$data = $api->get($endpoint, $options);
// ✅ Force sort locally by "Descrizione" (A→Z)
if (isset($data['value']) && is_array($data['value'])) {
usort($data['value'], function ($a, $b) {
$da = isset($a['Descrizione']) ? trim((string)$a['Descrizione']) : '';
$db = isset($b['Descrizione']) ? trim((string)$b['Descrizione']) : '';
return strcasecmp($da, $db); // case-insensitive alphabetical
});
}
// Salva il JSON in locale
file_put_contents(__DIR__ . '/moltiplicatori_prezzo_response.json', json_encode($data, JSON_PRETTY_PRINT));
echo json_encode($data);
} catch (Exception $e) {
file_put_contents(
__DIR__ . '/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()]);
}

View File

@ -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()]);
}

View File

@ -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);
}

File diff suppressed because it is too large Load Diff

View File

@ -59,7 +59,8 @@ foreach ($allMappings as $mapping) {
}
}
if (!$mainFieldMapping) {
$mainFieldMapping = reset(array_filter($allMappings, fn($m) => !$m['is_manual']));
$filtered = array_filter($allMappings, fn($m) => !$m['is_manual']);
$mainFieldMapping = reset($filtered);
}
// Retrieve data from datadb

View File

@ -10,65 +10,125 @@
<title>Template Buttons - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
<style>
/* Layout flessibile per gestire dimensioni diverse */
#templateButtons {
/* Main buttons container */
.template-buttons {
display: flex;
flex-wrap: wrap;
gap: 10px;
gap: 12px;
justify-content: flex-start;
/* Allinea a sinistra */
padding: 20px;
padding: 10px 0;
}
/* Definizione delle dimensioni */
/* Definizione delle dimensioni */
/* Button sizes */
.btn-small {
font-size: 12px;
padding: 6px 12px;
min-width: 100px;
min-height: 30px;
display: flex;
/* Aggiunto */
min-height: 36px;
display: inline-flex;
justify-content: center;
/* Aggiunto */
align-items: center;
/* Aggiunto */
gap: 8px;
border-radius: 8px;
text-align: center;
}
.btn-medium {
font-size: 16px;
padding: 10px 20px;
min-width: 130px;
min-height: 45px;
display: flex;
/* Aggiunto */
min-width: 140px;
min-height: 48px;
display: inline-flex;
justify-content: center;
/* Aggiunto */
align-items: center;
/* Aggiunto */
gap: 8px;
border-radius: 10px;
text-align: center;
}
.btn-large {
font-size: 20px;
padding: 14px 28px;
min-width: 180px;
min-height: 60px;
display: flex;
/* Aggiunto */
min-width: 190px;
min-height: 64px;
display: inline-flex;
justify-content: center;
/* Aggiunto */
align-items: center;
/* Aggiunto */
gap: 10px;
border-radius: 12px;
text-align: center;
}
/* Stile della barra di ricerca */
.template-icon {
font-size: 18px;
line-height: 1;
}
.btn-large .template-icon {
font-size: 22px;
}
.btn-small .template-icon {
font-size: 15px;
}
/* Search box */
#searchInput {
width: 100%;
padding: 10px;
padding: 10px 14px;
font-size: 16px;
margin-bottom: 15px;
border: 1px solid #ccc;
border-radius: 5px;
margin-bottom: 18px;
border: 1px solid #d9d9d9;
border-radius: 8px;
outline: none;
}
#searchInput:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.15rem rgba(13, 110, 253, 0.15);
}
/* Tabs */
.custom-tabs {
border-bottom: 1px solid #e5e5e5;
margin-bottom: 20px;
gap: 6px;
}
.custom-tabs .nav-link {
border: none;
border-radius: 10px 10px 0 0;
color: #555;
font-weight: 500;
display: flex;
align-items: center;
gap: 8px;
padding: 10px 18px;
}
.custom-tabs .nav-link:hover {
background: #f8f9fa;
color: #0d6efd;
}
.custom-tabs .nav-link.active {
background: #0d6efd;
color: #fff;
}
.tab-pane {
min-height: 140px;
}
.empty-message {
color: #6c757d;
font-style: italic;
padding: 10px 0;
}
.loading-message {
color: #6c757d;
padding: 10px 0;
}
</style>
</head>
@ -87,14 +147,54 @@
<h6 class="mb-0">Active Templates</h6>
</div>
<div class="card-body">
<!-- Barra di ricerca -->
<input type="text" id="searchInput" placeholder="Search template...">
<div id="templateButtons"></div>
<ul class="nav nav-tabs custom-tabs" id="templateTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="xls-tab" data-bs-toggle="tab" data-bs-target="#xls-pane" type="button" role="tab" aria-controls="xls-pane" aria-selected="true">
<i class="bx bx-spreadsheet template-icon"></i>
XLS
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="api-tab" data-bs-toggle="tab" data-bs-target="#api-pane" type="button" role="tab" aria-controls="api-pane" aria-selected="false">
<i class="bx bx-transfer-alt template-icon"></i>
JSON/API
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="pdf-tab" data-bs-toggle="tab" data-bs-target="#pdf-pane" type="button" role="tab" aria-controls="pdf-pane" aria-selected="false">
<i class="bx bx-file-pdf template-icon"></i>
PDF
</button>
</li>
</ul>
<div class="tab-content" id="templateTabsContent">
<div class="tab-pane fade show active" id="xls-pane" role="tabpanel" aria-labelledby="xls-tab">
<div id="templateButtonsXLS" class="template-buttons">
<div class="loading-message">Loading XLS templates...</div>
</div>
</div>
<div class="tab-pane fade" id="api-pane" role="tabpanel" aria-labelledby="api-tab">
<div id="templateButtonsAPI" class="template-buttons">
<div class="loading-message">Loading API templates...</div>
</div>
</div>
<div class="tab-pane fade" id="pdf-pane" role="tabpanel" aria-labelledby="pdf-tab">
<div id="templateButtonsPDF" class="template-buttons">
<div class="loading-message">Loading PDF templates...</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
@ -104,46 +204,137 @@
<script>
document.addEventListener("DOMContentLoaded", function() {
const allTemplates = [];
const containers = {
XLS: document.getElementById("templateButtonsXLS"),
API: document.getElementById("templateButtonsAPI"),
PDF: document.getElementById("templateButtonsPDF")
};
function getSizeClass(buttonSize) {
if (buttonSize === "small") return "btn-small";
if (buttonSize === "large") return "btn-large";
return "btn-medium";
}
function getTemplateIcon(sourceType) {
switch ((sourceType || '').toUpperCase()) {
case 'XLS':
return 'bx bx-spreadsheet';
case 'API':
return 'bx bx-transfer-alt';
case 'PDF':
return 'bx bx-file-pdf';
default:
return 'bx bx-file';
}
}
function createButton(template) {
const sizeClass = getSizeClass(template.button_size);
const sourceType = (template.source_type || '').toUpperCase();
const iconClass = getTemplateIcon(sourceType);
const btn = document.createElement("a");
btn.href = `import_xls2.php?id=${template.id}`;
btn.className = `btn ${sizeClass}`;
btn.style.backgroundColor = template.button_bg_color || '#0d6efd';
btn.style.color = template.button_text_color || '#ffffff';
btn.setAttribute("data-label", (template.button_label || '').toLowerCase());
btn.setAttribute("data-source-type", sourceType);
btn.innerHTML = `
<i class="${iconClass} template-icon"></i>
<span>${escapeHtml(template.button_label || 'Unnamed')}</span>
`;
return btn;
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function clearContainers() {
Object.values(containers).forEach(container => {
container.innerHTML = '';
});
}
function renderTemplates(searchValue = '') {
clearContainers();
const grouped = {
XLS: [],
API: [],
PDF: []
};
allTemplates.forEach(template => {
const sourceType = (template.source_type || '').toUpperCase();
const label = (template.button_label || '').toLowerCase();
if (searchValue && !label.includes(searchValue)) {
return;
}
if (grouped[sourceType]) {
grouped[sourceType].push(template);
}
});
Object.keys(grouped).forEach(type => {
const container = containers[type];
if (!container) return;
if (grouped[type].length === 0) {
container.innerHTML = `<div class="empty-message">No templates found in this section.</div>`;
return;
}
grouped[type].forEach(template => {
container.appendChild(createButton(template));
});
});
}
fetch("load_active_templates.php")
.then(response => response.json())
.then(data => {
if (!data.success) {
console.error("Error loading templates:", data.message);
clearContainers();
Object.values(containers).forEach(container => {
container.innerHTML = `<div class="empty-message">Error loading templates.</div>`;
});
return;
}
let templateButtons = document.getElementById("templateButtons");
templateButtons.innerHTML = '';
data.data.forEach(template => {
let sizeClass = "btn-medium"; // Default
if (template.button_size === "small") sizeClass = "btn-small";
if (template.button_size === "large") sizeClass = "btn-large";
let btn = document.createElement("a");
btn.href = `import_xls2.php?id=${template.id}`;
btn.className = `btn ${sizeClass}`;
btn.style.backgroundColor = template.button_bg_color;
btn.style.color = template.button_text_color;
btn.textContent = template.button_label;
btn.setAttribute("data-label", template.button_label.toLowerCase()); // Attributo per ricerca
templateButtons.appendChild(btn);
});
})
.catch(error => console.error("Fetch error:", error));
// Funzione per la ricerca live
document.getElementById("searchInput").addEventListener("input", function() {
let searchValue = this.value.toLowerCase();
document.querySelectorAll("#templateButtons a").forEach(btn => {
let label = btn.getAttribute("data-label");
if (label.includes(searchValue)) {
btn.style.display = "inline-block";
} else {
btn.style.display = "none";
if (!Array.isArray(data.data)) {
clearContainers();
Object.values(containers).forEach(container => {
container.innerHTML = `<div class="empty-message">Invalid response format.</div>`;
});
return;
}
allTemplates.push(...data.data);
renderTemplates();
})
.catch(error => {
console.error("Fetch error:", error);
clearContainers();
Object.values(containers).forEach(container => {
container.innerHTML = `<div class="empty-message">Fetch error while loading templates.</div>`;
});
});
document.getElementById("searchInput").addEventListener("input", function() {
const searchValue = this.value.toLowerCase().trim();
renderTemplates(searchValue);
});
});
</script>

File diff suppressed because it is too large Load Diff

View File

@ -20,10 +20,11 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['template_id']) || !i
$template_id = intval($_POST['template_id']);
$selected_rows = array_map('intval', $_POST['selected_rows']);
$columns = json_decode($_POST['columns'], true);
$rows = json_decode($_POST['rows'], true);
$excelrows = json_decode($_POST['excelrows'], true);
$newFilename = htmlspecialchars($_POST['filename']);
$columns = json_decode(urldecode($_POST['columns'] ?? '[]'), true);
$rows = json_decode(urldecode($_POST['rows'] ?? '[]'), true);
$excelrows = json_decode(urldecode($_POST['excelrows'] ?? '[]'), true);
$newFilename = $_POST['filename'];
$_SESSION['template_id'] = $template_id;
$_SESSION['selected_rows'] = $selected_rows;
@ -46,7 +47,7 @@ $pdo = $db->getConnection();
$importReferenceCode = date('YmdHis') . '-' . uniqid();
// 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]);
$allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
@ -125,7 +126,7 @@ foreach ($selected_rows as $rowIndex) {
case 'Testo':
case 'VARCHAR':
default:
$fieldValue = !empty($fieldValue) ? htmlspecialchars((string)$fieldValue) : ($mapping['manual_default'] ?? '');
$fieldValue = !empty($fieldValue) ? (string)$fieldValue : ($mapping['manual_default'] ?? '');
break;
}
} else {
@ -134,6 +135,15 @@ foreach ($selected_rows as $rowIndex) {
$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 === '')) {
error_log("Required field missing for mapping ID: " . $mapping['id'] . ", field: " . $mapping['field_label']);
}
@ -146,28 +156,6 @@ foreach ($selected_rows as $rowIndex) {
$_SESSION['inserted_ids'] = $insertedIds;
$params = [
'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
header("Location: imported.php?id=" . urlencode($template_id) . "&importref=" . urlencode($importReferenceCode));
exit;
?>

View File

@ -40,6 +40,22 @@ error_log("Loaded template: " . print_r($template, true));
<?php include('cssinclude.php'); ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.top-scrollbar {
overflow-x: auto;
overflow-y: hidden;
width: 100%;
height: 18px;
margin-bottom: 8px;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
background: #f8f9fa;
display: none;
}
.top-scrollbar-inner {
height: 1px;
}
.table-container {
overflow-x: auto;
max-width: 100%;
@ -121,6 +137,16 @@ error_log("Loaded template: " . print_r($template, true));
transform: rotate(360deg);
}
}
.column-filters th {
background: #ffffff;
cursor: default;
}
.column-filters input {
width: 100%;
min-width: 80px;
}
</style>
<title><?= htmlspecialchars($template['name']) ?> - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
</head>
@ -133,9 +159,8 @@ error_log("Loaded template: " . print_r($template, true));
<div class="page-content">
<?php include('top_stat_widget.php'); ?>
<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="historical_trf.php?id=<?= $id ?>&status=P" class="btn btn-primary me-2">In Progress (P)</a>
<a href="historical_trf.php?id=<?= $id ?>&status=l" class="btn btn-success">To LIMS (l)</a>
<a href="imported.php?id=<?= $id ?>" class="btn btn-warning me-2">Imported (i)</a>
<a href="tolims.php?id=<?= $id ?>" class="btn btn-success">To LIMS (l)</a>
</div>
<div class="card radius-10">
<div class="card-header">
@ -303,57 +328,141 @@ error_log("Loaded template: " . print_r($template, true));
function showTable(data) {
console.log('Mostro tabella con dati:', data);
let html = `
<form id="selectRowsForm" action="import_insert.php" method="POST">
<input type="hidden" name="template_id" value="${data.template_id}">
<input type="hidden" name="columns" value='${JSON.stringify(data.columns)}'>
<input type="hidden" name="rows" value='${JSON.stringify(data.rows)}'>
<input type="hidden" name="excelrows" value='${JSON.stringify(data.excel_data.map(row => row.excelrow))}'>
<input type="hidden" name="filename" value="${data.filename}">
<div class="search-container">
<input type="text" id="searchInput" class="form-control" placeholder="Cerca nelle righe...">
</div>
<div class="table-container">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th><input type="checkbox" id="selectAll"> Seleziona</th>
${data.columns.map(col => `<th>${col || 'Colonna senza nome'}<div class="resize-handle"></div></th>`).join('')}
</tr>
</thead>
<tbody>
${data.excel_data.map((row, index) => `
<tr>
<td><input type="checkbox" class="row-checkbox" name="selected_rows[]" value="${index}" data-excelrow="${row.excelrow}"></td>
${row.data.map(cell => `<td>${cell}</td>`).join('')}
</tr>
`).join('')}
</tbody>
</table>
</div>
<button type="submit" class="btn btn-primary mt-3" id="proceedButton" disabled>Prosegui</button>
</form>
`;
<form id="selectRowsForm" action="import_insert.php" method="POST">
<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="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="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="top-scrollbar" id="topTableScrollbar">
<div class="top-scrollbar-inner" id="topTableScrollbarInner"></div>
</div>
<div class="table-container" id="mainTableContainer">
<table class="table table-striped table-bordered" id="importPreviewTable">
<thead>
<tr>
<th><input type="checkbox" id="selectAll"> Seleziona</th>
${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 class="column-filters">
<th></th>
${data.columns.map((col, i) => `
<th>
<input type="text"
class="form-control form-control-sm column-filter"
data-col-index="${i}"
placeholder="Filter...">
</th>
`).join('')}
</tr>
</thead>
<tbody>
${data.excel_data.map((row, index) => `
<tr>
<td><input type="checkbox" class="row-checkbox" name="selected_rows[]" value="${index}" data-excelrow="${row.excelrow}"></td>
${row.data.map(cell => `<td>${cell}</td>`).join('')}
</tr>
`).join('')}
</tbody>
</table>
</div>
<!-- BOTTOM BUTTON -->
<button type="submit" class="btn btn-primary mt-3" id="proceedButtonBottom" disabled>Prosegui</button>
</form>
`;
tableContainer.innerHTML = html;
const proceedButton = document.getElementById('proceedButton');
const topTableScrollbar = document.getElementById('topTableScrollbar');
const topTableScrollbarInner = document.getElementById('topTableScrollbarInner');
const mainTableContainer = document.getElementById('mainTableContainer');
const importPreviewTable = document.getElementById('importPreviewTable');
function updateTopTableScrollbar() {
if (!topTableScrollbar || !topTableScrollbarInner || !mainTableContainer || !importPreviewTable) return;
topTableScrollbarInner.style.width = importPreviewTable.scrollWidth + 'px';
if (mainTableContainer.scrollWidth > mainTableContainer.clientWidth) {
topTableScrollbar.style.display = 'block';
} else {
topTableScrollbar.style.display = 'none';
}
}
let syncingTop = false;
let syncingBottom = false;
if (topTableScrollbar && mainTableContainer) {
topTableScrollbar.addEventListener('scroll', function() {
if (syncingBottom) return;
syncingTop = true;
mainTableContainer.scrollLeft = topTableScrollbar.scrollLeft;
syncingTop = false;
});
mainTableContainer.addEventListener('scroll', function() {
if (syncingTop) return;
syncingBottom = true;
topTableScrollbar.scrollLeft = mainTableContainer.scrollLeft;
syncingBottom = false;
});
}
updateTopTableScrollbar();
setTimeout(updateTopTableScrollbar, 100);
setTimeout(updateTopTableScrollbar, 300);
window.addEventListener('resize', updateTopTableScrollbar);
const proceedButtonTop = document.getElementById('proceedButtonTop');
const proceedButtonBottom = document.getElementById('proceedButtonBottom');
const selectAllCheckbox = document.getElementById('selectAll');
const checkboxes = document.querySelectorAll('.row-checkbox');
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() {
checkboxes.forEach(checkbox => {
checkbox.checked = this.checked;
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;
}
});
updateProceedButton();
});
checkboxes.forEach(checkbox => {
checkbox.addEventListener('change', function() {
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();
});
});
@ -393,14 +502,49 @@ error_log("Loaded template: " . print_r($template, true));
}
});
const searchInput = document.getElementById('searchInput');
const rows = document.querySelectorAll('.table tbody tr');
const filterInputs = document.querySelectorAll('.column-filter');
searchInput.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
// Stato filtri: key = colIndex, value = testo
const activeFilters = {};
function applyColumnFilters() {
rows.forEach(row => {
const text = Array.from(row.cells).slice(1).map(cell => cell.textContent.toLowerCase()).join(' ');
row.style.display = text.includes(searchTerm) ? '' : 'none';
let visible = true;
for (const [colIndexStr, filterValue] of Object.entries(activeFilters)) {
const colIndex = parseInt(colIndexStr, 10);
const cell = row.cells[colIndex + 1];
const cellText = (cell?.textContent || '').toLowerCase();
const searchText = (filterValue || '').toLowerCase().trim();
if (searchText && !cellText.includes(searchText)) {
visible = false;
break;
}
}
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 => {
input.addEventListener('input', function() {
const colIndex = this.dataset.colIndex; // string
activeFilters[colIndex] = this.value;
applyColumnFilters();
});
});

1614
public/userarea/imported.php Normal file

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,9 @@ error_reporting(E_ALL | E_STRICT);
include('../../extra/auth.php');
//require_once __DIR__ . '/extra/auth.php';
// Laravel bootstrap (loaded by auth.php) forces UTC via config/app.php — re-apply our TZ
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
// Here we just check if user is not
// logged in, and in that case we redirect
// the user to vanguard login page.
@ -26,6 +29,8 @@ $nameuser = $user->present()->first_name;
$surnameuser = $user->present()->last_name;
$emailuser = $user->present()->email;
$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;
@ -35,8 +40,6 @@ $kindofrole = $user->present()->role_id;
//$iduserlogin="1";
//$nameuser="Claudio";
//$emailuser="info@claudiosironi.com";
?>
<?php
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
@ -49,13 +52,8 @@ $_SESSION["emailuser"] = $emailuser;
$_SESSION["photouser"] = $avatar;
$photouser = $_SESSION["photouser"];
$photousername = basename($avatar);
?>
<?php //include files
//include files
require_once(__DIR__ . '/../../languages/en/general.php');
//include("generalsettings.php");
?>

View File

@ -1,6 +1,6 @@
<?php include('include/headscript.php');
// Recupera tutte le routine dal database
// Retrieve all routines from database
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("SELECT * FROM routine");
@ -18,26 +18,29 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
<?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://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>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Insert new XLS Template</h5>
<h5 class="mb-0">Insert New Template</h5>
</div>
<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>
<ul class="mb-0">
<li>Template Name</li>
<li>Row Header and Column Header: where the title of the excel starts</li>
<li>Schema and client</li>
<li>Source Type</li>
<li>Schema and Client</li>
<li>Row Header and Column Header only for XLS templates</li>
</ul>
</div>
</div>
@ -50,22 +53,33 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
</div>
</div>
</div>
<div class="card-body">
<div class="col-12">
<form id="insertTemplateForm" method="POST">
<div class="mb-3">
<label class="form-label"><?= htmlspecialchars($templatename, ENT_QUOTES, 'UTF-8'); ?> *</label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label"><?= htmlspecialchars($rowheader, ENT_QUOTES, 'UTF-8'); ?> *</label>
<input type="number" name="header_row" class="form-control" required>
<label class="form-label">Source Type *</label>
<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 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>
<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 class="mb-3">
@ -86,12 +100,12 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
<div class="mb-3">
<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 class="mb-3">
<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 class="mb-3">
@ -125,6 +139,7 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
</option>
<?php endforeach; ?>
</select>
<div id="routineDetails" class="mt-2" style="display: none;">
<h6>Routine Details</h6>
<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 class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
@ -167,6 +184,12 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
const routineAction2 = document.getElementById("routineAction2");
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) {
alert("Errore: Uno o più elementi della pagina non sono stati trovati. Contatta l'amministratore.");
return;
@ -187,30 +210,66 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
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() {
try {
clientLoadingStatus.style.display = 'inline';
clientLoadingStatus.textContent = 'Recupero clienti in corso...';
const response = await fetch("get_clienti.php", {
method: "GET",
headers: {
"Content-Type": "application/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");
select.innerHTML = '<option value="">Select a client...</option>';
data.value.forEach(client => {
const nome = client.Nominativo || "Nome non disponibile";
const id = client.IdCliente || "ID non disponibile";
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
select.add(option);
});
$(select).trigger('change');
clientLoadingStatus.textContent = "Clienti caricati.";
} catch (error) {
clientLoadingStatus.textContent = "Errore nel caricamento.";
Swal.fire({
title: "Errore!",
text: "Impossibile caricare i clienti: " + error.message,
@ -226,26 +285,43 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
try {
schemaLoadingStatus.style.display = 'inline';
schemaLoadingStatus.textContent = 'Caricamento schemi in corso...';
const response = await fetch("get_schemi.php", {
method: "GET",
headers: {
"Content-Type": "application/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");
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 id = schema.IdSchemaCustomFields || "ID non disponibile";
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
select.add(option);
});
$(select).trigger('change');
schemaLoadingStatus.textContent = "Schemi caricati.";
} catch (error) {
schemaLoadingStatus.textContent = "Errore nel caricamento.";
Swal.fire({
title: "Errore!",
text: "Impossibile caricare gli schemi: " + error.message,
@ -270,6 +346,7 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
});
}
}
loadData();
const routines = <?php echo json_encode($routines); ?>;
@ -277,8 +354,10 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
function updateRoutineDetails() {
const selectedId = routineSelect.value;
routineDetails.style.display = selectedId ? 'block' : 'none';
if (selectedId) {
const routine = routines.find(r => r.idroutine == selectedId);
if (routine) {
routineName.textContent = routine.name || 'N/A';
routineDescription.textContent = routine.description || 'N/A';
@ -300,6 +379,7 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
routineAction3.textContent = '';
}
}
routineSelect.addEventListener('change', updateRoutineDetails);
updateRoutineDetails();
@ -350,6 +430,7 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
const nameMatch = optionText.match(/^(.+?)(?:\s*\(ID:\s*\d+\))?$/);
schemaName = nameMatch ? nameMatch[1].trim() : optionText;
}
formData.append("idschema", schemaId);
formData.append("schemaname", schemaName);

View File

@ -13,7 +13,10 @@ try {
}
// Recupera solo i template attivi
$stmt = $pdo->query("SELECT id, button_label, button_bg_color, button_text_color, button_size FROM excel_templates WHERE status = 'active'");
$stmt = $pdo->query("SELECT id, button_label, button_size, button_bg_color, button_text_color, source_type
FROM excel_templates
WHERE status = 'active'
ORDER BY button_label ASC");
$templates = $stmt->fetchAll(PDO::FETCH_ASSOC);
$response["success"] = true;

View File

@ -14,11 +14,57 @@ if (!$iddatadb) {
}
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");
$stmt->execute([':iddatadb' => $iddatadb]);
// 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]);
}
$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) {
echo json_encode(['success' => false, 'message' => 'Errore nel caricamento: ' . $e->getMessage()]);
}

View File

@ -0,0 +1,32 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
// Recupera l'ID dell'utente loggato
$user_id = $iduserlogin ?? 1;
if (!$user_id) {
echo json_encode(['success' => false, 'message' => "ID dell'utente autenticato mancante"]);
exit;
}
try {
$stmt = $pdo->prepare(
"SELECT DISTINCT q.*
FROM quotations q
INNER JOIN identification_parts ip
ON ip.idquotations = q.id
AND ip.iddatadb IS NULL
WHERE q.iduser = :iduser"
);
$stmt->execute([':iduser' => $user_id]);
$quotations = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['success' => true, 'quotations' => $quotations]);
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Errore nel caricamento delle quotations: ' . $e->getMessage()]);
}

View File

@ -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

View File

@ -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"
}

View File

@ -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
}
---

View File

@ -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"
}

View File

@ -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

View File

@ -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

View File

@ -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"
}

View File

@ -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
}
---

View File

@ -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"
}

View File

@ -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

View File

@ -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

View File

@ -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"
}

View File

@ -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
}
---

View File

@ -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"
}

View File

@ -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;amp;Fur"
},
{
"IdCommesseCustomFields": 23315552,
"Valore": "278"
},
{
"IdCommesseCustomFields": 23315553,
"Valore": "2089"
},
{
"IdCommesseCustomFields": 23315557,
"Valore": "745"
}
]
}'
RESPONSE:
null

View File

@ -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

View File

@ -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"
}

View File

@ -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
}
---

View File

@ -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"
}

View File

@ -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;amp;Fur"
},
{
"IdCommesseCustomFields": 23328257,
"Valore": "278"
},
{
"IdCommesseCustomFields": 23328258,
"Valore": "2089"
},
{
"IdCommesseCustomFields": 23328262,
"Valore": "745"
}
]
}'
RESPONSE:
null

View File

@ -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
}
---

View File

@ -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": []
}

View File

@ -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": []
}

View File

@ -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"
}

View File

@ -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"
}
---

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -97,6 +97,31 @@ if (!$template) {
<ul id="associationsList" class="list-group border p-3"></ul>
</div>
<!-- Default Values (Fixed fields) -->
<div class="mt-4">
<h5>Default Values (Fixed fields)</h5>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">MoltiplicatorePrezzo (default)</label>
<select id="defaultMoltiplicatorePrezzo" class="form-select">
<option value="">Loading...</option>
</select>
<small class="text-muted">Loaded from API: get_moltiplicatoreprezzo.php</small>
</div>
<div class="col-md-6">
<label class="form-label">ConsegnaRichiesta (default)</label>
<input type="date" id="defaultConsegnaRichiesta" class="form-control">
</div>
</div>
<div class="mt-3">
<button class="btn btn-success" id="saveDefaultsBtn">💾 Save defaults</button>
<span id="defaultsStatus" class="ms-2 text-muted"></span>
</div>
</div>
<!-- Save Button -->
<div class="mt-4 text-end">
<a href="templates_dashboard.php" class="btn btn-primary"> Back to Template Dashboard</a>
@ -273,7 +298,7 @@ if (!$template) {
}
})
.catch(error => console.error('Error loading table columns:', error));
*/
*/
/** =======================
* CARICAMENTO MAPPATURE GIÀ ESISTENTI
* ======================= */
@ -441,6 +466,97 @@ if (!$template) {
})
.catch(error => console.error("❌ Fetch error:", error));
}
/** =======================
* DEFAULTS (Fixed fields)
* - stored in localStorage for now
* ======================= */
const defaultsKey = "tpl_defaults_" + templateId;
function loadDefaultsFromLocal() {
try {
const raw = localStorage.getItem(defaultsKey);
if (!raw) return;
const saved = JSON.parse(raw);
if (saved && typeof saved === "object") {
if (saved.moltiplicatore_prezzo_id !== undefined) {
document.getElementById('defaultMoltiplicatorePrezzo').value = saved.moltiplicatore_prezzo_id || "";
}
if (saved.consegna_richiesta !== undefined) {
document.getElementById('defaultConsegnaRichiesta').value = saved.consegna_richiesta || "";
}
}
} catch (e) {
console.warn("⚠️ Cannot parse saved defaults:", e);
}
}
function saveDefaultsToLocal() {
const moltiplicatoreId = document.getElementById('defaultMoltiplicatorePrezzo').value || "";
const consegna = document.getElementById('defaultConsegnaRichiesta').value || "";
const payload = {
moltiplicatore_prezzo_id: moltiplicatoreId,
consegna_richiesta: consegna
};
localStorage.setItem(defaultsKey, JSON.stringify(payload));
const status = document.getElementById('defaultsStatus');
status.textContent = "✅ Defaults saved for this template (local)";
setTimeout(() => status.textContent = "", 2500);
}
function loadMoltiplicatoriPrezzo() {
const select = document.getElementById('defaultMoltiplicatorePrezzo');
select.innerHTML = `<option value="">-- Select --</option>`;
fetch('get_moltiplicatoreprezzo.php')
.then(r => r.json())
.then(data => {
// OData tipico: { value: [...] }
const rows = Array.isArray(data?.value) ? data.value : (Array.isArray(data) ? data : []);
if (!rows.length) {
select.innerHTML = `<option value="">(No data)</option>`;
return;
}
rows.forEach(item => {
// ⚠️ Qui i nomi campi dipendono dal JSON reale:
// - id: item.Id / item.ID / item.idMoltiplicatorePrezzo
// - label: item.Descrizione / item.Nome / item.Codice
const id = item.IdMoltiplicatorePrezzo;
const label = `${item.Descrizione} (x${item.Fattore})`;
if (id === undefined || id === null) return;
const opt = document.createElement('option');
opt.value = String(id);
opt.textContent = String(label);
select.appendChild(opt);
});
// dopo aver popolato, prova a rimettere il valore salvato
loadDefaultsFromLocal();
})
.catch(err => {
console.error("❌ Error loading MoltiplicatorePrezzo:", err);
select.innerHTML = `<option value="">(Load error)</option>`;
});
}
// Events
document.getElementById('saveDefaultsBtn').addEventListener('click', function() {
saveDefaultsToLocal();
});
// Init
loadMoltiplicatoriPrezzo();
loadDefaultsFromLocal();
});
</script>
</body>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,425 @@
<?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);
$partIds = array_column($parts, 'id');
$assignedAnalysesByPart = [];
if (!empty($partIds)) {
$placeholders = implode(',', array_fill(0, count($partIds), '?'));
$stmtAssigned = $pdo->prepare("
SELECT
ipa.id,
ipa.part_id,
ipa.analysis_recordkey,
ipa.analysis_name,
ipa.analysis_method,
ipa.analysis_level,
ipa.is_web_selectable,
ipa.is_accredited
FROM identification_parts_analyses ipa
WHERE ipa.part_id IN ($placeholders)
ORDER BY ipa.analysis_name ASC, ipa.id ASC
");
$stmtAssigned->execute($partIds);
$assignedRows = $stmtAssigned->fetchAll(PDO::FETCH_ASSOC);
foreach ($assignedRows as $row) {
$partId = (int)$row['part_id'];
if (!isset($assignedAnalysesByPart[$partId])) {
$assignedAnalysesByPart[$partId] = [];
}
$assignedAnalysesByPart[$partId][] = $row;
}
}
/**
* 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 class="analysis-part-assigned-list mt-2" id="analysisAssignedListPart<?= (int)$part['id'] ?>"></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="mt-1 mb-3">
<div class="small text-muted">Selected matrix:</div>
<div id="analysisCurrentMatrix" class="fw-semibold">-</div>
</div>
<div class="mb-3">
<div class="small text-muted">Selected parts IDs:</div>
<div id="analysisSelectedPartsIds" class="fw-semibold">-</div>
</div>
<div class="d-flex flex-wrap align-items-center gap-2 mb-3">
<div class="form-check m-0">
<input class="form-check-input" type="checkbox" id="analysisWebOnly">
<label class="form-check-label small" for="analysisWebOnly">
Web only
</label>
</div>
<div class="flex-grow-1" style="min-width: 220px;">
<input type="text" class="form-control form-control-sm" id="analysisSearchInput" placeholder="Search analyses or method">
</div>
</div>
<div id="analysisLoadingBox" class="alert alert-info mb-3 d-none">
Loading analyses...
</div>
<div id="analysisErrorBox" class="alert alert-danger mb-3 d-none"></div>
<div id="analysisEmptyBox" class="alert alert-secondary mb-3">
Select a matrix to load analyses
</div>
<div class="list-group" id="analysisList"></div>
</div>
</div>
</div>
</div>
<input type="hidden" id="analysisModalIddatadb" value="<?= (int)$iddatadb ?>">
<script type="application/json" id="analysisAssignedInitialData">
<?= json_encode($assignedAnalysesByPart, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>
</script>
</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;
}
.analysis-analysis-item {
border-radius: 8px !important;
margin-bottom: 6px;
background-color: #ffffff;
}
#analysisList .analysis-analysis-item:nth-child(even) {
background-color: #eef4ff;
}
.analysis-analysis-meta {
font-size: 0.84rem;
color: #6c757d;
}
.analysis-analysis-badges .badge {
margin-right: 4px;
margin-bottom: 4px;
}
.analysis-assigned-chip {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 4px 8px;
border: 1px solid #dbe7f3;
background: #f7fbff;
border-radius: 8px;
margin-bottom: 6px;
font-size: 0.84rem;
}
.analysis-assigned-chip-text {
min-width: 0;
flex: 1;
}
.analysis-assigned-chip-title {
font-weight: 600;
line-height: 1.2;
}
.analysis-assigned-chip-method {
color: #6c757d;
font-size: 0.78rem;
line-height: 1.2;
margin-top: 2px;
}
.analysis-remove-btn {
border: 0;
background: transparent;
color: #dc3545;
font-weight: 700;
font-size: 1rem;
line-height: 1;
padding: 0 2px;
cursor: pointer;
}
.analysis-analysis-item.analysis-selected {
border-color: #198754 !important;
background-color: #eaf7ea !important;
}
.analysis-part-assigned-list {
border-top: 1px dashed #dee2e6;
padding-top: 8px;
}
</style>

View File

@ -1,13 +1,22 @@
<!-- Modal per la gestione delle annotazioni -->
<div class="modal fade" id="annotationsModal" tabindex="-1" aria-labelledby="annotationsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl" style="max-width: 80% !important; width: 80% !important;">
<div class="modal-dialog modal-xl" style="max-width: 90% !important; width: 90% !important;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="annotationsModalLabel">Annotazioni per TRF: <span id="trfHeaderAnnotations"></span></h5>
<!-- SLIDER PER DIMENSIONE MARKER -->
<div style="display: flex; align-items: center; gap: 10px; margin-left: 20px;">
<label for="markerSizeSlider" style="margin: 0; font-size: 0.9rem; white-space: nowrap;">Dimensione marker:</label>
<input type="range" id="markerSizeSlider" min="16" max="48" value="16" step="2" style="width: 120px;">
<span id="markerSizeValue" style="font-weight: bold; min-width: 30px;">24px</span>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<!-- COLONNA SINISTRA RIDOTTA -->
<div class="col-md-6">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h6 style="margin: 0;">Elenco Parti</h6>
@ -16,15 +25,17 @@
<label for="showMixPartsAnnotations" style="font-size: 0.9rem;">Mix</label>
</div>
</div>
<ul id="partsListAnnotations" class="list-group"></ul>
<ul id="partsListAnnotations" class="list-group" style="max-height: 500px; overflow-y: auto;"></ul>
</div>
<!-- COLONNA DESTRA PIÙ GRANDE -->
<div class="col-md-6">
<h6>Foto del Campione</h6>
<div style="display: flex; align-items: center; margin-bottom: 10px;">
<button type="button" class="btn btn-primary btn-sm" id="downloadPhotoBtnAnnotations" style="padding: 0.1rem 0.5rem; font-size: 0.8rem; margin-right: 10px;"><i class="fas fa-download"></i></button>
<div id="photoSelectorContainerAnnotations" style="display: none;"></div>
</div>
<div style="position: relative; width: 100%; min-height: 400px;">
<div style="position: relative; width: 100%; min-height: 500px; border: 1px solid #ddd; border-radius: 4px; overflow: hidden;">
<img id="samplePhotoAnnotations" src="" alt="Foto del campione" style="max-width: 100%; max-height: 100%; object-fit: contain; position: absolute; top: 0; left: 0;">
<canvas id="photoCanvasAnnotations" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></canvas>
<canvas id="overlayCanvasAnnotations" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1000;"></canvas>
@ -216,4 +227,48 @@
transform: translateX(-50%);
z-index: 3000;
}
/* Stile per lo slider */
#markerSizeSlider {
-webkit-appearance: none;
height: 6px;
border-radius: 3px;
background: #ddd;
outline: none;
}
#markerSizeSlider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: #007bff;
cursor: pointer;
}
#markerSizeSlider::-moz-range-thumb {
width: 16px;
height: 16px;
border-radius: 50%;
background: #007bff;
cursor: pointer;
border: none;
}
#partsListAnnotations .list-group-item.dragging {
opacity: 0.6;
background-color: #d1e7dd !important;
transform: rotate(3deg);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
#partsListAnnotations .placeholder {
background-color: #f8d7da !important;
border: 2px dashed #dc3545;
height: 40px;
margin: 2px 0;
border-radius: 4px;
}
</style>
<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.min.js"></script>

View File

@ -307,4 +307,23 @@
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>

Some files were not shown because too many files have changed in this diff Show More