Compare commits

..

103 Commits

Author SHA1 Message Date
solocla b3ce489348 api change dahsboard 2026-03-28 10:28:25 +01:00
solocla 08b89e01cc Stop tracking customfield_values_response.json 2026-03-28 10:28:08 +01:00
solocla 490786731a moncler supplier fabric routine 2026-03-27 15:54:22 +01:00
solocla e96f538a47 added multiple column option template with color 2026-03-27 15:36:57 +01:00
solocla cc96ecb67f order parts by part_number 2026-03-27 15:18:44 +01:00
solocla 4cf03ae742 fixed routine 2026-03-27 09:36:06 +01:00
solocla 7a486e9dcf update moncler routine 2026-03-27 09:24:46 +01:00
solocla a05d9fed2b scheme alphabetical order 2026-03-26 11:47:02 +01:00
solocla 65170a0a7c fixed 285 export to LIMS and header import 2026-03-26 11:42:52 +01:00
solocla 223688c372 added select 2 on capitolato 2026-03-25 14:23:44 +01:00
solocla b562eb4033 fixed ordine alfabetico capitolato 2026-03-25 14:20:45 +01:00
solocla 0645a0c675 update profile 2026-03-25 11:02:27 +01:00
solocla a02a6b2c4c added clienteAnalisi on query export 2026-03-22 15:11:00 +01:00
RMubarakzyanov 45dd8d6907 Link photos to .01 Campioni 2026-03-21 16:18:27 +03:00
RMubarakzyanov 755f6812d4 Campione custom fields (Tested Component + parts), per-part ConsegnaRichiesta with validation 2026-03-20 23:31:31 +03:00
RMubarakzyanov 5e59ae2162 Tested component 2026-03-20 23:15:50 +03:00
solocla 71a19144c8 gitignore fixed 2026-03-19 13:48:51 +01:00
RMubarakzyanov 381a05341b PrimaPagina, StampaNelRapporto flags handling 2026-03-19 13:02:13 +03:00
solocla 5a58decd40 added clienti in template, export 2026-03-19 10:08:29 +01:00
RMubarakzyanov eb21910ef3 per-row buttons locking, progress 2026-03-18 19:58:57 +03:00
RMubarakzyanov 5dedc779df format date to d/m/Y 2026-03-18 19:24:21 +03:00
solocla f4e0074a73 fixed tested components 2026-03-18 16:11:03 +01:00
solocla 9c850e4ea6 fixed filter import 2026-03-18 16:07:25 +01:00
solocla f300811341 photos fixed background 2026-03-18 15:53:46 +01:00
RMubarakzyanov 48387a9945 cliente fornitore 2026-03-17 15:43:12 +03:00
RMubarakzyanov 0e90db8219 import date format 2026-03-17 12:44:37 +03:00
RMubarakzyanov eaf70d5a46 export date format 2026-03-16 20:57:05 +03:00
RMubarakzyanov cdd6551e9c remove validation simulate 2026-03-14 19:09:17 +03:00
RMubarakzyanov 1b97bf4362 1,2,3,4,5,6 points of milestone 2026-03-13 00:33:55 +03:00
solocla c516589483 changed notes TRF 2026-03-12 10:28:58 +01:00
solocla 817bbadf22 added flag to photos 2026-03-12 10:20:22 +01:00
solocla 1f27bc48d4 added get utenti 2026-03-11 14:53:38 +01:00
RMubarakzyanov c9fba48d88 fix post data 2026-03-11 16:04:37 +03:00
RMubarakzyanov 70d4c0759e importpayload logging 2026-03-09 17:34:32 +03:00
RMubarakzyanov 5d7880160a send userid to import 2026-03-09 17:12:38 +03:00
RMubarakzyanov bbe74d1529 fetch changes from branch export_to_lims 2026-03-09 17:07:20 +03:00
RMubarakzyanov 93930227a2 fetch changes from branch export_to_lims 2026-03-09 17:04:03 +03:00
solocla 2598a4c91b Step 9 remove comment on export to lims 2026-03-06 09:15:30 +01:00
solocla 5e677a8b9c Merge branch 'feature/export-to-lims' 2026-03-06 08:48:57 +01:00
solocla 540c44d89a fixed column mappings 2026-03-05 14:19:49 +01:00
solocla 2ee9f2ecb1 added proceed on the top 2026-03-04 10:13:33 +01:00
solocla c9122774b1 added additional field in parts template e layout 2026-03-02 14:37:20 +01:00
RMubarakzyanov 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
RMubarakzyanov e3994d6f9f fix notices running on php-8.2 2026-02-26 22:59:41 +03:00
solocla 9af0df3cca env example update 2026-02-26 17:14:16 +01:00
solocla 48d2a3ff42 time and date setting 2026-02-25 22:07:44 +01:00
solocla cf44e67922 added auto user accettatore id 2026-02-25 15:24:41 +01:00
solocla 35021e9d9b dropdown in alphabetical order 2026-02-25 10:02:45 +01:00
solocla 497ebda65a update gitignore 2026-02-24 09:09:32 +01:00
solocla b0024edb70 mapping fixed 2026-02-24 09:06:45 +01:00
solocla b9852ba226 saved rows 2026-02-19 12:07:32 +01:00
solocla 407d6884a1 fixed note save and import special character 2026-02-19 11:55:24 +01:00
solocla 1a4beadbb2 fixed propagate dropdown 2026-02-18 11:52:26 +01:00
solocla 4bb0445cff added select 2 on all fields (with limit of 12) 2026-02-18 10:48:04 +01:00
solocla 73dd8f4ce8 fixed column size final 2026-02-18 10:04:27 +01:00
solocla bdc4e0e60c fixed size column 2026-02-18 09:58:42 +01:00
solocla ef8f2d8000 fixed labels 2026-02-18 09:54:21 +01:00
solocla 74c9a1d02a added time field 2026-02-18 09:34:26 +01:00
solocla cd3bccd183 added multiple tested components 2026-02-17 09:04:30 +01:00
solocla 78154e43a9 fixed tested components 2026-02-05 10:32:28 +01:00
solocla 9fe9243e60 fixed mapping template 2026-02-05 09:05:51 +01:00
solocla a8330d4aba fixed layout 2026-02-04 15:31:53 +01:00
MGrigoryan 82d6a2ee18 feat(import_edit2): expand entire column including header and fix sticky columns 2026-02-04 17:00:01 +04:00
MGrigoryan 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
solocla f60dc64b2d fixed importedit 2026-02-02 08:46:47 +01:00
solocla 4e4cae1df8 added fixed fields 2026-01-30 12:07:43 +01:00
solocla 8838edf3a1 fixed fields 2026-01-28 11:49:11 +01:00
solocla e75be99e43 added resort manually 2025-11-08 17:55:10 +01:00
solocla a482d975da Merge branch 'feature/parts-photo-load-from-quotation' 2025-10-31 10:38:28 +01:00
solocla 598a2cc84c change marker dimension 2025-10-31 10:16:57 +01:00
MGrigoryan 6ec0c2062e fix: Include single sample photo in save operation when no photos selected 2025-10-31 11:45:04 +04:00
MGrigoryan 5eb5bd1613 fix: deselect part when clicking selected row 2025-10-31 11:30:18 +04:00
MGrigoryan 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
MGrigoryan 03642fdfab feat(annotations): support multiple pins per part 2025-10-30 17:08:57 +04:00
MGrigoryan 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
solocla 1c2b4ab7a6 Merge branch 'bugfix/part-creation-matrice-dropdown-fix' 2025-10-29 18:25:26 +01:00
MGrigoryan 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
MGrigoryan 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
solocla 82af925ac1 added size to line markers 2025-10-29 13:45:38 +01:00
solocla 5d8360dd87 marker with size 2025-10-29 12:32:09 +01:00
MGrigoryan 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
solocla 8d6fe92481 fixed multiple parts mix and single line 2025-10-28 08:58:39 +01:00
solocla dbc66723a6 fixed color save 2025-10-27 15:53:12 +01:00
solocla 218fc14462 fixed country client and parts column matrice 2025-10-27 14:38:20 +01:00
solocla 29e4b41874 update export to lims 2025-10-11 20:19:43 +02:00
solocla eef9ae8d36 added note to export 2025-10-10 11:31:49 +02:00
solocla 68c867a3f4 change clienti to datadb and fixed column pages 2025-10-09 15:30:44 +02:00
solocla a9827e4e81 added note and date to identification parts 2025-10-08 17:34:21 +02:00
solocla b51936f784 various fixing modal 2025-10-07 20:56:57 +02:00
solocla 15b6f38e8b fixed matrici with db cron 2025-10-07 09:41:29 +02:00
solocla 12c6cc5f95 lazy load modal parts and matrici cron 2025-10-07 09:12:54 +02:00
solocla a0b12463c0 fixed for matrici 2025-10-03 08:52:37 +02:00
solocla 07ddcafd3f fixed nologin 2025-09-27 13:38:26 +02:00
solocla 7843d4b1fc added nologin 2025-09-27 13:37:37 +02:00
solocla 4eae855e23 added headscriptnologin 2025-09-27 13:36:26 +02:00
solocla c709f64a17 remove login smartphone 2025-09-27 13:33:23 +02:00
solocla d5f0690f59 background trasparent annotation 2025-09-27 13:28:21 +02:00
solocla 6bbd3fcae9 fixed get clienti with id, nominativo and country 2025-09-27 09:53:21 +02:00
solocla 9e19e9e1d4 fixed export to lims, fixed multiple upload, added calendar to Data 2025-09-27 09:44:00 +02:00
solocla 7caee9c994 prova 2025-09-26 11:27:33 +02:00
solocla f8320315f7 historical added commessaweb., edit added tested components 2025-09-26 09:28:49 +02:00
solocla 7397d86bc2 fixed template dashboard 2025-09-25 14:26:22 +02:00
solocla 2deb1f101a hide column and fixed edit template 2025-09-25 14:18:09 +02:00
112 changed files with 29160 additions and 1038 deletions
+7 -18
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"
BASE_URL=http://localhost:8000/userarea/
+34 -43
View File
@@ -1,66 +1,57 @@
.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
*.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
# Ignora tutti i log ovunque
*.log
File diff suppressed because it is too large Load Diff
+6
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
+91
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;
File diff suppressed because it is too large Load Diff
+131
View File
@@ -0,0 +1,131 @@
<?php
require_once "class/VisualLimsApiClient.class.php";
include('include/headscript.php');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
header("Content-Type: application/json");
// 🔹 Configura directory log (creala se non esiste)
$logDir = __DIR__ . '/logs/api/';
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
// 🔹 Base URL API
$apiBaseUrl = 'https://93.43.5.102/limsapi/api/odata/';
// 🔹 Hardcoded values
$iddatadb = 846;
$commessaId = 533357;
try {
// 🔹 STEP 4: Fetch Field Values with Labels (usa dati reali per iddatadb=845)
$stmt = $pdo->prepare("
SELECT
idd.field_value,
m.field_label,
m.schema_id,
m.field_id
FROM
import_data_details as idd
JOIN template_mapping m ON idd.mapping_id = m.id
WHERE idd.id = :iddatadb
");
$stmt->execute(['iddatadb' => $iddatadb]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
$fieldValues = [];
$valueMap = []; // Mappa per field_id -> valore
foreach ($rows as $row) {
$fieldValues[] = [
"IdCommesseCustomFields" => (int) $row['field_id'],
"Valore" => $row['field_value'],
"FieldLabel" => $row['field_label']
];
$valueMap[(int) $row['field_id']] = $row['field_value']; // Mappa per ID definizione
}
// Logga i fieldValues in error_log
$logFieldValues = "FieldValues dal DB (iddatadb={$iddatadb}):\n" . json_encode($fieldValues, JSON_PRETTY_PRINT);
error_log($logFieldValues);
// 🔹 Initialize API client
$api = VisualLimsApiClient::getInstance();
// 🔹 STEP A: GET iniziale per CommesseCustomFields con espansione CustomField
$expand = "CommesseCustomFields(\$expand=CustomField)"; // Espansione come da istruzioni fornitore
$commessaWithFields = $api->get("CommessaWeb(" . $commessaId . ")?\$expand=" . $expand);
// 🔹 STEP B: Prepara payload PATCH (tutti i campi, sovrascrivi se match su CustomField.IdCustomField == field_id)
$commessaCustomFields = [];
foreach ($commessaWithFields["CommesseCustomFields"] as $customField) {
$definitionId = (int) ($customField["CustomField"]["IdCustomField"] ?? 0); // Usa IdCustomField dal CustomField
$currentValue = $customField["Valore"] ?? '';
$newValue = isset($valueMap[$definitionId]) ? $valueMap[$definitionId] : $currentValue;
$commessaCustomFields[] = [
"IdCommesseCustomFields" => (int) $customField["IdCommesseCustomFields"],
"Valore" => $newValue
];
}
// 🔹 Unico file di log per tutto
$logFile = $logDir . "commessa_{$commessaId}_patch_and_get_" . time() . ".txt";
$logContent = "FieldValues dal DB (iddatadb={$iddatadb}):\n" . json_encode($fieldValues, JSON_PRETTY_PRINT) . "\n\n";
// Log curl-like per GET iniziale
$logContent .= "GET iniziale:\n" .
"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) . "\n\n---\n";
if (!empty($commessaCustomFields)) {
$updatePayload = ["CommesseCustomFields" => $commessaCustomFields];
// Log curl-like per PATCH
$jsonPayload = json_encode($updatePayload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$logContent .= "PATCH:\n" .
"curl --location --request PATCH '{$apiBaseUrl}CommessaWeb({$commessaId})' \\\n" .
"--header 'Content-Type: application/json' \\\n" .
"--header 'Authorization: Bearer ••••••' \\\n" .
"--data '{$jsonPayload}'\n\n";
$patchResponse = $api->patch("CommessaWeb({$commessaId})", $updatePayload);
$logContent .= "PATCH RESPONSE:\n" . json_encode($patchResponse, JSON_PRETTY_PRINT) . "\n\n---\n";
} else {
$logContent .= "PATCH: Nessun campo custom da aggiornare\n\n---\n";
}
// 🔹 STEP C: GET di controllo post-PATCH
$commessaAfterPatch = $api->get("CommessaWeb(" . $commessaId . ")?\$expand=" . $expand);
// Log curl-like per GET di controllo
$logContent .= "GET di controllo:\n" .
"curl --location --request GET '{$apiBaseUrl}CommessaWeb({$commessaId})?\$expand=CommesseCustomFields(\$expand=CustomField)' \\\n" .
"--header 'Authorization: Bearer ••••••'\n\n" .
"RESPONSE:\n" . json_encode($commessaAfterPatch, JSON_PRETTY_PRINT);
// Salva log unico
file_put_contents($logFile, $logContent);
// 🔹 Output a schermo
echo json_encode([
"success" => true,
"message" => "PATCH eseguito su commessa {$commessaId} con dati da iddatadb {$iddatadb}",
"commessaAfterPatch" => $commessaAfterPatch,
"totalCustomFieldsUpdated" => count($commessaCustomFields),
"fieldValues" => $fieldValues,
"logFile" => $logFile
]);
} catch (Exception $e) {
error_log("Patch Error: " . $e->getMessage() . "\nTrace: " . $e->getTraceAsString());
echo json_encode([
"success" => false,
"message" => "Patch failed: " . $e->getMessage(),
"logFile" => $logFile ?? 'Nessun log generato'
]);
}
@@ -24,12 +24,22 @@ 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;
}
private function authenticate()
private function authenticate($retryCount = 0, $maxRetries = 3)
{
$ch = curl_init("{$this->baseUrl}/api/authentication/authenticate");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
@@ -45,16 +55,22 @@ class VisualLimsApiClient
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_VERBOSE, true);
$log = fopen(__DIR__ . '/curl_auth_debug.log', 'w') ?: fopen('php://stderr', 'w');
$log = fopen(__DIR__ . '/curl_auth_debug.log', 'a') ?: fopen('php://stderr', 'w');
curl_setopt($ch, CURLOPT_STDERR, $log);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curl_error = curl_error($ch);
$log_message = date('Y-m-d H:i:s') . " - Auth attempt {$retryCount}: HTTP {$http_code}, Error: {$curl_error}, Response: " . substr($response, 0, 1000) . "\n";
fwrite($log, $log_message);
fclose($log);
curl_close($ch);
if ($response === false || $http_code != 200) {
if ($http_code === 400 && strpos($response, 'Cannot persist the object') !== false && $retryCount < $maxRetries) {
usleep(500000); // Ritardo di 500ms
return $this->authenticate($retryCount + 1, $maxRetries); // Riprova
}
throw new Exception("Autenticazione fallita: HTTP {$http_code}, Errore cURL: {$curl_error}, Risposta: " . substr($response, 0, 1000));
}
@@ -187,9 +203,56 @@ class VisualLimsApiClient
return json_decode($response, true);
}
/**
* POST a file as multipart/form-data (used for photo/attachment uploads).
*
* @param string $endpoint OData endpoint, e.g. "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;
}
}
@@ -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];
}
}
+79
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()]);
}
@@ -0,0 +1,103 @@
<?php
require_once dirname(__DIR__, 3) . '/vendor/autoload.php'; // Risale a root/vendor/
require_once dirname(__FILE__) . '/../class/VisualLimsApiClient.class.php'; // In root/public/userarea/class/
use Dotenv\Dotenv;
// Debug: Log the path where we expect the .env file
$envPath = dirname(__DIR__, 3);
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);
exit(1);
}
// 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'];
// 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);
exit(1);
}
try {
$api = VisualLimsApiClient::getInstance();
// Endpoint per recuperare le Matrici
$endpoint = 'Matrice';
// (Opzionale) aggiungi parametri
$options = []; // es. ['$top' => 100]
// 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_url.txt', $full_url . PHP_EOL, FILE_APPEND);
// Chiamata API
$data = $api->get($endpoint, $options);
// Debug: Log the API response size
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . ' - API response received, value count: ' . (isset($data['value']) ? count($data['value']) : 0) . PHP_EOL, FILE_APPEND);
// Salva il JSON in locale (opzionale, per debug)
file_put_contents(__DIR__ . '/matrici_response.json', json_encode($data, JSON_PRETTY_PRINT));
// Svuota la tabella (dump rapido)
$pdo->exec("TRUNCATE TABLE {$dbPrefix}matrici");
// Debug: Log after truncate
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . ' - Table truncated: ' . $dbPrefix . 'matrici' . PHP_EOL, FILE_APPEND);
// Prepara l'insert
$stmt = $pdo->prepare("
INSERT INTO {$dbPrefix}matrici (
IdMatrice, NomeMatriceTraduzione, DescrizioneTraduzione, MacroMatrice, NomeMatrice, Descrizione
) VALUES (
:IdMatrice, :NomeMatriceTraduzione, :DescrizioneTraduzione, :MacroMatrice, :NomeMatrice, :Descrizione
)
");
// Inserisci i dati
$insertedRows = 0;
if (isset($data['value']) && is_array($data['value'])) {
foreach ($data['value'] as $item) {
$stmt->execute([
':IdMatrice' => $item['IdMatrice'],
':NomeMatriceTraduzione' => $item['NomeMatriceTraduzione'],
':DescrizioneTraduzione' => $item['DescrizioneTraduzione'] ?? null,
':MacroMatrice' => $item['MacroMatrice'] ?? null,
':NomeMatrice' => $item['NomeMatrice'],
':Descrizione' => $item['Descrizione'] ?? null,
]);
$insertedRows++;
// Debug: Log each inserted row
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . ' - Inserted row with IdMatrice: ' . $item['IdMatrice'] . PHP_EOL, FILE_APPEND);
}
}
// Log successo
file_put_contents(__DIR__ . '/success_log.txt', date('Y-m-d H:i:s') . ' - Aggiornamento completato: ' . $insertedRows . ' record inseriti.' . PHP_EOL, FILE_APPEND);
} catch (Exception $e) {
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
exit(1);
}
exit(0); // Esci con successo per cron
File diff suppressed because one or more lines are too long
+290
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
-->
+25 -1
View File
@@ -48,7 +48,7 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
<div class="page-content">
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">UpdateXLS Template</h5>
<h5 class="mb-0">Update XLS 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>
@@ -99,6 +99,30 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
<input type="text" name="target_table" class="form-control" value="<?php echo htmlspecialchars($template['target_table']); ?>" readonly required>
</div>
<div class="mb-3">
<label class="form-label">Button Size</label>
<select name="button_size" class="form-control">
<option value="small" <?php echo ($template['button_size'] ?? 'medium') === 'small' ? 'selected' : ''; ?>>Small</option>
<option value="medium" <?php echo ($template['button_size'] ?? 'medium') === 'medium' ? 'selected' : ''; ?>>Medium</option>
<option value="large" <?php echo ($template['button_size'] ?? 'medium') === 'large' ? 'selected' : ''; ?>>Large</option>
</select>
</div>
<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'); ?>">
</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'); ?>">
</div>
<div class="mb-3">
<label class="form-label">Button Label</label>
<input type="text" name="button_label" class="form-control" value="<?php echo htmlspecialchars($template['button_label'] ?? 'Click Me'); ?>">
</div>
<div class="mb-3">
<label class="form-label">Select Client *</label>
<select name="client_id" id="clientSelect" class="form-control" required>
+316
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"}
File diff suppressed because it is too large Load Diff
+465 -46
View File
@@ -7,20 +7,85 @@ $pdo = $dbHandler->getConnection();
header("Content-Type: application/json");
// 🔹 Configura directory log (creala se non esiste)
$logDir = __DIR__ . '/logs/api/';
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)
{
// Prova a validare come data (accetta formati comuni)
$date = DateTime::createFromFormat('Y-m-d', $value) ?: DateTime::createFromFormat('Y-m-d H:i:s', $value);
if ($date) {
return $date->format('Y-m-d\TH:i:sP'); // Formato ISO 8601
}
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");
}
// 🔹 STEP 1+2: Fetch Cliente ID + Schema ID
// 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 et.idclient AS clienteId, et.idschema AS schemaId
FROM datadb as d
INNER JOIN excel_templates as et ON d.templateid = et.id
WHERE d.iddatadb = :iddatadb
LIMIT 1
");
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
LIMIT 1
");
$stmt->execute(['iddatadb' => $iddatadb]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
@@ -31,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
@@ -56,31 +164,62 @@ try {
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
$fieldValues = [];
$valueMap = [];
foreach ($rows as $row) {
$fieldValues[] = [
"IdCommesseCustomFields" => (int) $row['field_id'],
"Valore" => $row['field_value']
"Valore" => $row['field_value'],
"FieldLabel" => $row['field_label']
];
$valueMap[(int) $row['field_id']] = $row['field_value'];
}
// Logga i fieldValues in error_log
$logFieldValues = "FieldValues dal DB (iddatadb={$iddatadb}):\n" . json_encode($fieldValues, JSON_PRETTY_PRINT);
error_log($logFieldValues);
// 🔹 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
$jsonPayload = json_encode($commessaWebPayload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$logContentStep5 = "curl --location --request POST '{$apiBaseUrl}CommessaWeb' \\\n" .
"--header 'Content-Type: application/json' \\\n" .
"--header 'Authorization: Bearer ••••••' \\\n" .
"--data '{$jsonPayload}'";
$commessaWeb = $api->post("CommessaWeb", $commessaWebPayload);
$logContentStep5 .= "\n\nRESPONSE:\n" . json_encode($commessaWeb, JSON_PRETTY_PRINT);
// Salva log
$logFileStep5 = $logDir . "commessa_create_step5_" . $iddatadb . "_" . time() . ".txt";
$writeLog($logFileStep5, $logContentStep5, "STEP 5 - Create CommessaWeb (iddatadb={$iddatadb})");
$commessaId = $commessaWeb["IdCommessa"];
// Estraiamo il numero della commessa usando CodiceCommessa
$commessaWebCode = substr($commessaWeb["CodiceCommessa"] ?? "TEST CommessaWeb", 0, 30); // Limite a 30 caratteri
// 🔹 STEP 6: Create Campioni (Samples) for each part
$campioni = [];
$logContentStep6 = "";
foreach ($parts as $index => $part) {
$matriceId = (int) ($part["idmatrice"] ?? 0);
@@ -89,15 +228,27 @@ 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
$jsonPayload = json_encode($campionePayload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$logContentStep6 .= "CAMPIONE #{$index}\n" .
"curl --location --request POST '{$apiBaseUrl}Campione' \\\n" .
"--header 'Content-Type: application/json' \\\n" .
"--header 'Authorization: Bearer ••••••' \\\n" .
"--data '{$jsonPayload}'\n\n";
$campione = $api->post("Campione", $campionePayload);
$logContentStep6 .= "RESPONSE:\n" . json_encode($campione, JSON_PRETTY_PRINT) . "\n\n---\n";
$campione["PartNumber"] = $part["part_number"] ?? "";
$campione["Material"] = $part["material"] ?? "";
$campione["Color"] = $part["color"] ?? "";
@@ -106,25 +257,230 @@ try {
$campioni[] = $campione;
}
// 🔹 STEP 7: Update Custom Fields for CommessaWeb
if (!empty($fieldValues)) {
$commessaWithFields = $api->get("CommessaWeb(" . $commessaId . ")?\$expand=CommesseCustomFields");
$commessaCustomFields = [];
foreach ($commessaWithFields["CommesseCustomFields"] as $customField) {
foreach ($fieldValues as $fieldValue) {
if ($customField["IdCommesseCustomFields"] == $fieldValue["IdCommesseCustomFields"]) {
$commessaCustomFields[] = [
"IdCommesseCustomFields" => $customField["IdCommesseCustomFields"],
"Valore" => $fieldValue["Valore"]
];
break;
}
// Salva log per STEP 6
$logFileStep6 = $logDir . "commessa_{$commessaId}_campioni_step6_" . time() . ".txt";
$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}");
// 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";
$writeLog($logFileGet, $logContentGet, "STEP 7 - GET CustomFields (commessa={$commessaId})");
// Prepara payload PATCH
$commessaCustomFields = [];
foreach ($commessaWithFields["CommesseCustomFields"] as $customField) {
$definitionId = (int) ($customField["CustomField"]["IdCustomField"] ?? 0);
$fieldId = (int) $customField["IdCommesseCustomFields"];
$currentValue = $customField["Valore"] ?? '';
$fieldType = $customField["CustomField"]["Tipo"] ?? '';
$newValue = isset($valueMap[$definitionId]) ? $valueMap[$definitionId] : $currentValue;
// Valida se il campo è di tipo Data
if ($fieldType === 'Data' && $newValue !== $currentValue) {
$newValue = formatDateToExport($newValue);
}
$commessaCustomFields[] = [
"IdCommesseCustomFields" => $fieldId,
"Valore" => $newValue
];
}
$logFileStep7 = null;
if (!empty($commessaCustomFields)) {
$updatePayload = ["CommesseCustomFields" => $commessaCustomFields];
$api->patch("CommessaWeb({$commessaId})", $updatePayload);
// Logga payload e response
$jsonPayload = json_encode($updatePayload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$logContentStep7 = "curl --location --request PATCH '{$apiBaseUrl}CommessaWeb({$commessaId})' \\\n" .
"--header 'Content-Type: application/json' \\\n" .
"--header 'Authorization: Bearer ••••••' \\\n" .
"--data '{$jsonPayload}'";
$patchResponse = $api->patch("CommessaWeb({$commessaId})", $updatePayload);
$logContentStep7 .= "\n\nRESPONSE:\n" . json_encode($patchResponse, JSON_PRETTY_PRINT);
$logFileStep7 = $logDir . "commessa_{$commessaId}_update_step7_" . time() . ".txt";
$writeLog($logFileStep7, $logContentStep7, "STEP 7 - PATCH CustomFields (commessa={$commessaId})");
}
}
@@ -140,33 +496,96 @@ try {
'iddatadb' => $iddatadb
]);
// 🔹 STEP 9: Send CommessaWeb to laboratory
// 🔹 STEP 9: Send CommessaWeb to laboratory (commentato come richiesto)
$sendResult = $api->post("CommessaWeb({$commessaId})/InviaCommessa", []);
// 🔹 STEP 10: Prepare final response
// 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);
$logFileStep9 = $logDir . "commessa_{$commessaId}_send_step9_" . time() . ".txt";
$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)";
$commessaAfterPatch = $api->get("CommessaWeb(" . $commessaId . ")?\$expand=" . $expand);
// Logga il GET di controllo
$logContentStep10 = "curl --location --request GET '{$apiBaseUrl}CommessaWeb({$commessaId})?\$expand=CommesseCustomFields(\$expand=CustomField)' \\\n" .
"--header 'Authorization: Bearer ••••••'\n\n" .
"RESPONSE:\n" . json_encode($commessaAfterPatch, JSON_PRETTY_PRINT);
$logFileStep10 = $logDir . "commessa_{$commessaId}_get_step10_" . time() . ".txt";
$writeLog($logFileStep10, $logContentStep10, "STEP 10 - GET verify (commessa={$commessaId})");
// 🔹 STEP 11: Prepare final response
$finalCommessa = [
"Cliente" => $clienteId,
"SchemaCustomField" => $schemaId,
"Richiedente" => $commessaWeb["Richiedente"] ?? "Web Import",
"Descrizione" => $commessaWeb["Descrizione"] ?? "",
"CommesseCustomFields" => $fieldValues,
"CommesseCustomFields" => $commessaAfterPatch["CommesseCustomFields"] ?? [],
"Campioni" => $campioni,
"Inviata" => 1
"Inviata" => 0 // Non inviato, come richiesto
];
echo json_encode([
"success" => true,
"commessaWeb" => $finalCommessa,
"success" => true,
"idcommessaweb" => $commessaId,
"commessaweb" => $commessaWebCode,
"commessaWeb" => $finalCommessa,
"commessaWebApiResponse" => $commessaWeb, // Incluso per debug
"totalCampioni" => count($campioni),
"totalCustomFields" => count($fieldValues),
"message" => "Export successful"
"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) {
error_log("LIMS Export Error: " . $e->getMessage() . "\nTrace: " . $e->getTraceAsString());
echo json_encode([
"success" => false,
"message" => "Export failed: " . $e->getMessage()
"message" => "Export failed: " . $e->getMessage(),
"logFiles" => [
"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
]
]);
}
@@ -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()]);
}
@@ -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()]);
}
+50
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);
}
@@ -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()]);
}
+77 -10
View File
@@ -1,24 +1,91 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php'; // Torna al livello di public per trovare vendor/
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json');
// Disabilita la visualizzazione degli errori PHP per evitare output HTML
// Disable PHP error display
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$api = VisualLimsApiClient::getInstance();
$data = $api->get("Cliente"); // Recupera i clienti
$api = VisualLimsApiClient::getInstance(); // also loads dotenv
// Salva la risposta in un file per debug
file_put_contents(__DIR__ . '/clienti_response.json', json_encode($data));
// 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,CodiceCliente',
'$orderby' => 'Nominativo asc'
];
// Costruisce query string con encoding corretto
$queryString = http_build_query($params);
// Componi endpoint finale
$endpoint = "Cliente?$queryString";
// Funzione per eseguire la chiamata con retry
function makeApiRequest($api, $endpoint, $maxRetries = 3)
{
for ($retry = 0; $retry < $maxRetries; $retry++) {
try {
// Tenta la chiamata API
$data = $api->get($endpoint);
// Salva risposta per debug
file_put_contents(__DIR__ . '/clienti_response.json', json_encode($data, JSON_PRETTY_PRINT));
return $data;
} catch (Exception $e) {
$errorMessage = $e->getMessage();
// Controlla se l'errore è legato all'autenticazione (HTTP 400 con messaggio specifico)
if (strpos($errorMessage, 'HTTP 400') !== false && strpos($errorMessage, 'Cannot persist the object') !== false) {
// Forza il refresh del token
try {
// Assumi che VisualLimsApiClient abbia un metodo per il refresh del token
$api->refreshToken(); // Da implementare in VisualLimsApiClient se non esiste
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());
}
// Ritarda leggermente prima del retry
usleep(500000); // 500ms
continue;
}
// Altri errori non gestiti dal retry
throw $e;
}
}
throw new Exception("Massimo numero di tentativi raggiunto per $endpoint");
}
// Esegui la chiamata con retry
$data = makeApiRequest($api, $endpoint);
echo json_encode($data);
} catch (Exception $e) {
http_response_code(500);
echo json_encode([
'error' => $e->getMessage()
]);
$errorResponse = [
'error' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString()
];
error_log("Errore in get_clienti.php: " . json_encode($errorResponse));
echo json_encode($errorResponse);
}
+103
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);
}
+93
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()]);
}
+59
View File
@@ -0,0 +1,59 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php'; // Risale a root/vendor/
use Dotenv\Dotenv;
// 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);
}
// 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'];
// 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);
}
+59
View File
@@ -0,0 +1,59 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php'; // Risale a root/vendor/
use Dotenv\Dotenv;
// 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);
}
// 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'];
// 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 le matrici, includendo MacroMatrice, escludendo quelle che iniziano con '*' e ordinandole
$query = "SELECT IdMatrice, NomeMatrice, MacroMatrice FROM {$dbPrefix}matrici WHERE NomeMatrice NOT LIKE '*%' ORDER BY NomeMatrice ASC";
$stmt = $pdo->prepare($query);
$stmt->execute();
$matrici = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Debug: Log del numero di matrici recuperate
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . ' - Retrieved ' . count($matrici) . ' matrices from database' . PHP_EOL, FILE_APPEND);
// Restituisci risposta JSON
echo json_encode(['success' => true, 'value' => $matrici]);
} 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 matrici: ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
echo json_encode(['success' => false, 'message' => 'Errore nel recupero delle matrici: ' . $e->getMessage()]);
exit(1);
}
@@ -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()]);
}
+38
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()]);
}
+1 -1
View File
@@ -9,7 +9,7 @@ error_reporting(E_ALL);
try {
$api = VisualLimsApiClient::getInstance();
$rapporto_id = 515081;
$rapporto_id = 533329;
// Costruzione manuale dell'endpoint con espansione annidata
$endpoint = "Rapporto($rapporto_id)?\$expand=CampioniDatiRapporto(\$expand=AnalisiDatiRapporto,CustomFieldsDatiRapporto)";
+94
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);
}
+36 -30
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
@@ -662,6 +663,11 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
} else {
echo "<div class='grid-cell' style='flex: 0 0 150px;'></div>";
}
// Status (subito dopo main_field)
$fixedColumnsReduced = ['status'];
foreach ($fixedColumnsReduced as $col) {
echo "<div class='grid-cell' style='flex: 0 0 150px;'></div>";
}
// Campi automatici (escluso main_field)
$autoIndex = ($mainFieldMapping && !$mainFieldMapping['is_manual']) ? 1 : 0;
foreach ($allMappings as $mapping) {
@@ -712,11 +718,7 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$manualIndex++;
}
}
// Colonne status, Import Reference Code, filename_import
$fixedColumnsReduced = ['status'];
foreach ($fixedColumnsReduced as $col) {
echo "<div class='grid-cell' style='flex: 0 0 150px;'></div>";
}
// Colonne Import Reference Code, filename_import
echo "<div class='grid-cell' style='flex: 0 0 150px;'></div>"; // Import Reference Code
echo "<div class='grid-cell' style='flex: 0 0 150px;'></div>"; // filename_import
// AWB Number e Tracking Info
@@ -734,6 +736,12 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
echo "<div class='grid-header' data-index='$headerIndex' style='flex: 0 0 150px; position: relative;'>" . htmlspecialchars($mainFieldMapping['field_label']) . "<div class='resizer'></div></div>";
$headerIndex++;
}
// Header per status (subito dopo main_field)
foreach ($fixedColumnsReduced as $col) {
$displayName = $slugMapping[$col] ?? $col;
echo "<div class='grid-header' data-index='$headerIndex' style='flex: 0 0 150px; position: relative;'>$displayName<div class='resizer'></div></div>";
$headerIndex++;
}
// Header per campi automatici (escluso main_field)
foreach ($allMappings as $mapping) {
if (!$mapping['is_manual'] && $mapping['main_field'] != 1) {
@@ -748,12 +756,7 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$headerIndex++;
}
}
// Header per status, Import Reference Code, filename_import
foreach ($fixedColumnsReduced as $col) {
$displayName = $slugMapping[$col] ?? $col;
echo "<div class='grid-header' data-index='$headerIndex' style='flex: 0 0 150px; position: relative;'>$displayName<div class='resizer'></div></div>";
$headerIndex++;
}
// Header per Import Reference Code, filename_import
echo "<div class='grid-header' data-index='$headerIndex' style='flex: 0 0 150px; position: relative;'>Import Reference Code<div class='resizer'></div></div>";
$headerIndex++;
echo "<div class='grid-header' data-index='$headerIndex' style='flex: 0 0 150px; position: relative;'>File<div class='resizer'></div></div>";
@@ -770,13 +773,11 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
<div style="display: flex; gap: 5px; justify-content: center;">
<?php if (!$is_readonly): ?>
<button type="button" class="save-btn action-btn" data-row="<?= $index ?>" style="background: #28a745; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: pointer; flex: 1;"><i class="fas fa-save"></i></button>
<button type="button" class="photos-btn action-btn" data-row="<?= $index ?>" data-iddatadb="<?= $row['iddatadb'] ?>" style="background: #007bff; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: pointer; flex: 1;"><i class="fas fa-camera"></i></button>
<button type="button" class="parts-btn action-btn" data-row="<?= $index ?>" data-iddatadb="<?= $row['iddatadb'] ?>" style="background: #ffc107; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: pointer; flex: 1;"><i class="fas fa-puzzle-piece"></i></button>
<?php else: ?>
<button type="button" class="save-btn action-btn" data-row="<?= $index ?>" style="background: #ccc; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: not-allowed; flex: 1;" disabled><i class="fas fa-save"></i></button>
<button type="button" class="photos-btn action-btn" data-row="<?= $index ?>" data-iddatadb="<?= $row['iddatadb'] ?>" style="background: #ccc; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: not-allowed; flex: 1;" disabled><i class="fas fa-camera"></i></button>
<button type="button" class="parts-btn action-btn" data-row="<?= $index ?>" data-iddatadb="<?= $row['iddatadb'] ?>" style="background: #ccc; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: not-allowed; flex: 1;" disabled><i class="fas fa-puzzle-piece"></i></button>
<?php endif; ?>
<button type="button" class="photos-btn action-btn" data-row="<?= $index ?>" data-iddatadb="<?= $row['iddatadb'] ?>" style="background: #007bff; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: pointer; flex: 1;"><i class="fas fa-camera"></i></button>
<button type="button" class="parts-btn action-btn" data-row="<?= $index ?>" data-iddatadb="<?= $row['iddatadb'] ?>" style="background: #ffc107; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: pointer; flex: 1;"><i class="fas fa-puzzle-piece"></i></button>
</div>
</div>
<?php
@@ -806,6 +807,25 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
echo "</div>";
$cellIndex++;
}
// Status (subito dopo main_field)
$fixedColumnsReduced = ['status'];
foreach ($fixedColumnsReduced as $col) {
$value = $row[$col] ?? '';
echo "<div class='grid-cell editable-cell' data-col='$col' data-row='$index' data-index='$cellIndex' style='flex: 0 0 150px;'>";
if ($col === 'status') {
$badgeClass = $value === 'i' ? 'status-i' : ($value === 'P' ? 'status-P' : 'status-l');
$badgeText = $value === 'i' ? 'Imported' : ($value === 'P' ? 'Progress' : 'LIMS');
// Aggiungi il numero di commessaweb se lo status è 'l'
if ($value === 'l') {
$commessaWeb = isset($row['commessaweb']) ? htmlspecialchars($row['commessaweb']) : '';
$badgeText .= " ($commessaWeb)";
}
echo "<span class='status-badge $badgeClass'>" . htmlspecialchars($badgeText) . "</span>";
echo "<input type='hidden' name='rows[$index][$col]' value='" . htmlspecialchars($value ?? 'i') . "'>";
}
echo "</div>";
$cellIndex++;
}
// Campi automatici (escluso main_field)
$autoIndex = ($mainFieldMapping && !$mainFieldMapping['is_manual']) ? 1 : 0;
foreach ($allMappings as $mapping) {
@@ -863,20 +883,6 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$manualIndex++;
}
}
// Colonna status
$fixedColumnsReduced = ['status'];
foreach ($fixedColumnsReduced as $col) {
$value = $row[$col] ?? '';
echo "<div class='grid-cell editable-cell' data-col='$col' data-row='$index' data-index='$cellIndex' style='flex: 0 0 150px;'>";
if ($col === 'status') {
$badgeClass = $value === 'i' ? 'status-i' : ($value === 'P' ? 'status-P' : 'status-l');
$badgeText = $value === 'i' ? 'Imported' : ($value === 'P' ? 'Progress' : 'LIMS');
echo "<span class='status-badge $badgeClass'>" . htmlspecialchars($badgeText) . "</span>";
echo "<input type='hidden' name='rows[$index][$col]' value='" . htmlspecialchars($value ?? 'i') . "'>";
}
echo "</div>";
$cellIndex++;
}
// Colonne Import Reference Code e filename_import
echo "<div class='grid-cell' data-col='importreferencecode' data-row='$index' data-index='$cellIndex' style='flex: 0 0 150px;'>";
echo "<span>" . htmlspecialchars($row['importreferencecode']) . "</span>";
File diff suppressed because it is too large Load Diff
+13 -5
View File
@@ -20,9 +20,10 @@ 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);
$columns = json_decode(urldecode($_POST['columns'] ?? '[]'), true);
$rows = json_decode(urldecode($_POST['rows'] ?? '[]'), true);
$excelrows = json_decode(urldecode($_POST['excelrows'] ?? '[]'), true);
$newFilename = htmlspecialchars($_POST['filename']);
$_SESSION['template_id'] = $template_id;
@@ -75,6 +76,12 @@ foreach ($selected_rows as $rowIndex) {
continue;
}
// Recupera l'idclient di default dal template
$template_stmt = $pdo->prepare("SELECT idclient FROM excel_templates WHERE id = ?");
$template_stmt->execute([$template_id]);
$template = $template_stmt->fetch(PDO::FETCH_ASSOC);
$default_idclient = $template['idclient'] ?? null;
$values = [
$template_id,
$importReferenceCode,
@@ -83,9 +90,10 @@ foreach ($selected_rows as $rowIndex) {
$user_id,
null,
date('Y-m-d'),
$excelrow // Aggiunto excelrow per la colonna excelrow
$excelrow,
$default_idclient // Aggiungi idclient
];
$sql = "INSERT INTO datadb (templateid, importreferencecode, filename_import, status, user_id, limscode, importdate, excelrow) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
$sql = "INSERT INTO datadb (templateid, importreferencecode, filename_import, status, user_id, limscode, importdate, excelrow, idclient) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute($values);
+121 -40
View File
@@ -121,6 +121,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>
@@ -303,57 +313,93 @@ 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="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>
<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 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 +439,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();
});
});
+4 -9
View File
@@ -26,6 +26,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 +37,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 +49,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");
?>
//include("generalsettings.php");
@@ -0,0 +1,25 @@
<?php
require_once(__DIR__ . '/../class/db-functions.php');
$db = DBHandlerSelect::getInstance()->getConnection();
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL | E_STRICT);
// Inizializza la sessione
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// Imposta variabili di sessione di default per evitare errori
$_SESSION['iduserlogin'] = '1'; // Nessun utente loggato
$_SESSION['nameuser'] = 'Ospite';
$_SESSION['surnameuser'] = '';
$_SESSION['emailuser'] = '';
$_SESSION['photouser'] = '';
$photouser = $_SESSION['photouser'];
$photousername = '';
$iduserlogin = $_SESSION['iduserlogin'];
// Include file di lingua, se necessario
require_once(__DIR__ . '/../../languages/en/general.php');
+96 -15
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);
+50 -4
View File
@@ -14,11 +14,57 @@ if (!$iddatadb) {
}
try {
$stmt = $pdo->prepare("SELECT id, iddatadb, part_number, part_description FROM identification_parts WHERE iddatadb = :iddatadb ORDER BY part_number ASC");
$stmt->execute([':iddatadb' => $iddatadb]);
$parts = $stmt->fetchAll();
// 1) prendo templateid da datadb
$stmtTpl = $pdo->prepare("SELECT templateid FROM datadb WHERE iddatadb = :iddatadb LIMIT 1");
$stmtTpl->execute([':iddatadb' => $iddatadb]);
$templateid = $stmtTpl->fetchColumn();
echo json_encode(['success' => true, 'parts' => $parts]);
// 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,
'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()]);
}
+32
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()]);
}
@@ -0,0 +1,60 @@
CAMPIONE #0
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Commessa": 563528,
"Matrice": 3879,
"SottoMatrice": null,
"SchemaCustomField": 82,
"NoteWeb": "Parte AAA",
"ConsegnaRichiesta": "2026-01-30"
}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#Campione\/$entity",
"IdCampione": 734582,
"CodiceCampione": "10978",
"CodiceCampioneWeb": "10978",
"StatoCampione": "Nuovo",
"DataCreazioneWeb": "2026-03-11T14:10:02.1595894+01:00",
"RiferimentoWeb": "",
"ConsegnaRichiesta": "2026-01-30T00:00:00+01:00",
"DataAccettazioneLims": null,
"Riferimento": null,
"NoteWeb": "Parte AAA",
"GruppiRicercati": null
}
---
CAMPIONE #1
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Commessa": 563528,
"Matrice": 3879,
"SottoMatrice": null,
"SchemaCustomField": 82,
"NoteWeb": "PARE BBB",
"ConsegnaRichiesta": "2026-01-30"
}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#Campione\/$entity",
"IdCampione": 734583,
"CodiceCampione": "10979",
"CodiceCampioneWeb": "10979",
"StatoCampione": "Nuovo",
"DataCreazioneWeb": "2026-03-11T14:10:03.9972635+01:00",
"RiferimentoWeb": "",
"ConsegnaRichiesta": "2026-01-30T00:00:00+01:00",
"DataAccettazioneLims": null,
"Riferimento": null,
"NoteWeb": "PARE BBB",
"GruppiRicercati": null
}
---
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,20 @@
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(563528)/ImportaCommessa' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"IdUtente": 285
}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
"IdCommessa": 563528,
"CodiceCommessa": "26C0029",
"RiferimentoCertificato": null,
"StatoCommessaWeb": "Elaborata",
"DataCreazioneWeb": "2026-03-11T14:10:00.897+01:00",
"CodiceCommessaWeb": "26C0029",
"DataInviatoWeb": "2026-03-11T14:10:11.76+01:00",
"Richiedente": "Test Web Import",
"Descrizione": "TEST CommessaWeb"
}
@@ -0,0 +1,44 @@
Photos for CommessaWeb 563528 (iddatadb=1259):
Total photos found: 2, campioni: 2
=== Campione 734582 (main) ===
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione(734582)/UploadCampioneFile' \
--header 'Authorization: Bearer ••••••' \
--form 'file=@C:\xampp\htdocs\trf_certest\public\photostrf\1259-20260311130930-104a2ca5-8a4c-4df8-b2ca-a28f518b7b25.jpg'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CampioneFiles\/$entity",
"IdCampioneFile": 1779561,
"FileName": "1259-20260311130930-104a2ca5-8a4c-4df8-b2ca-a28f518b7b25.jpg",
"Web": false,
"AllegaAlRapporto": false,
"StampaNelRapporto": false,
"ReportGermania": false,
"PrimaPagina": false,
"Duplica": false,
"LastUpdate": "2026-03-11T14:10:06.7051549+01:00",
"Titolo": null
}
---
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione(734582)/UploadCampioneFile' \
--header 'Authorization: Bearer ••••••' \
--form 'file=@C:\xampp\htdocs\trf_certest\public\photostrf\1259-20260311130930-1e672dd9-5420-4432-b422-02d8d271c178.jpg'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CampioneFiles\/$entity",
"IdCampioneFile": 1779562,
"FileName": "1259-20260311130930-1e672dd9-5420-4432-b422-02d8d271c178.jpg",
"Web": false,
"AllegaAlRapporto": false,
"StampaNelRapporto": false,
"ReportGermania": false,
"PrimaPagina": false,
"Duplica": false,
"LastUpdate": "2026-03-11T14:10:08.5442313+01:00",
"Titolo": null
}
---
@@ -0,0 +1,18 @@
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(563528)/InviaCommessa' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
"IdCommessa": 563528,
"CodiceCommessa": "26C0029",
"RiferimentoCertificato": null,
"StatoCommessaWeb": "Nuova",
"DataCreazioneWeb": "2026-03-11T14:10:00.897+01:00",
"CodiceCommessaWeb": "26C0029",
"DataInviatoWeb": "2026-03-11T14:10:11.7602299+01:00",
"Richiedente": "Test Web Import",
"Descrizione": "TEST CommessaWeb"
}
@@ -0,0 +1,226 @@
curl --location --request PATCH 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(563528)' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"CommesseCustomFields": [
{
"IdCommesseCustomFields": 23254249,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254250,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254251,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254252,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254253,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254254,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254260,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254261,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254262,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254264,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254265,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254266,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254267,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254268,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254228,
"Valore": "13526"
},
{
"IdCommesseCustomFields": 23254229,
"Valore": "20262"
},
{
"IdCommesseCustomFields": 23254230,
"Valore": "ART. PEACH"
},
{
"IdCommesseCustomFields": 23254269,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254270,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254271,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254272,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254273,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254274,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254275,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254276,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254277,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254278,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254279,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254280,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254281,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254231,
"Valore": "L209A4M00130M7280"
},
{
"IdCommesseCustomFields": 23254232,
"Valore": "BLACK"
},
{
"IdCommesseCustomFields": 23254233,
"Valore": "PE007J"
},
{
"IdCommesseCustomFields": 23254234,
"Valore": "262"
},
{
"IdCommesseCustomFields": 23254235,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254236,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254237,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254238,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254239,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254240,
"Valore": "Oggi"
},
{
"IdCommesseCustomFields": 23254241,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254242,
"Valore": "MONCLER"
},
{
"IdCommesseCustomFields": 23254243,
"Valore": "236"
},
{
"IdCommesseCustomFields": 23254244,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254245,
"Valore": "BBB"
},
{
"IdCommesseCustomFields": 23254246,
"Valore": "solocla"
},
{
"IdCommesseCustomFields": 23254247,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254248,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254255,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254256,
"Valore": "MONCLER"
},
{
"IdCommesseCustomFields": 23254257,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254258,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254259,
"Valore": ""
},
{
"IdCommesseCustomFields": 23254263,
"Valore": ""
}
]
}'
RESPONSE:
null
@@ -0,0 +1,60 @@
CAMPIONE #0
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Commessa": 564663,
"Matrice": 3028,
"SottoMatrice": null,
"SchemaCustomField": 82,
"NoteWeb": "Parte 1",
"ConsegnaRichiesta": "2026-03-31"
}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#Campione\/$entity",
"IdCampione": 736244,
"CodiceCampione": "11004",
"CodiceCampioneWeb": "11004",
"StatoCampione": "Nuovo",
"DataCreazioneWeb": "2026-03-17T14:52:30.3143366+01:00",
"RiferimentoWeb": "",
"ConsegnaRichiesta": "2026-03-31T00:00:00+02:00",
"DataAccettazioneLims": null,
"Riferimento": null,
"NoteWeb": "Parte 1",
"GruppiRicercati": null
}
---
CAMPIONE #1
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Commessa": 564663,
"Matrice": 3028,
"SottoMatrice": null,
"SchemaCustomField": 82,
"NoteWeb": "Parte 2",
"ConsegnaRichiesta": "2026-03-31"
}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#Campione\/$entity",
"IdCampione": 736245,
"CodiceCampione": "11005",
"CodiceCampioneWeb": "11005",
"StatoCampione": "Nuovo",
"DataCreazioneWeb": "2026-03-17T14:52:31.9631757+01:00",
"RiferimentoWeb": "",
"ConsegnaRichiesta": "2026-03-31T00:00:00+02:00",
"DataAccettazioneLims": null,
"Riferimento": null,
"NoteWeb": "Parte 2",
"GruppiRicercati": null
}
---
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,20 @@
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(564663)/ImportaCommessa' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"IdUtente": 285
}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
"IdCommessa": 564663,
"CodiceCommessa": "26C0059",
"RiferimentoCertificato": null,
"StatoCommessaWeb": "Elaborata",
"DataCreazioneWeb": "2026-03-17T14:52:29.347+01:00",
"CodiceCommessaWeb": "26C0059",
"DataInviatoWeb": "2026-03-17T14:52:38.99+01:00",
"Richiedente": "From TRFSmart Application",
"Descrizione": "From TRFSmart Application"
}
@@ -0,0 +1,48 @@
Photos for CommessaWeb 564663 (iddatadb=1268):
Total photos found: 2, campioni: 2
=== Campione 736244 (main) ===
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione(736244)/UploadCampioneFile' \
--header 'Authorization: Bearer ••••••' \
--form 'file=@C:\xampp\htdocs\trf_certest\public\photostrf\1268-20260317134828-1e672dd9-5420-4432-b422-02d8d271c178.jpg' \
--form 'StampaNelRapporto=true' \
--form 'PrimaPagina=true'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CampioneFiles\/$entity",
"IdCampioneFile": 1784768,
"FileName": "1268-20260317134828-1e672dd9-5420-4432-b422-02d8d271c178.jpg",
"Web": false,
"AllegaAlRapporto": false,
"StampaNelRapporto": false,
"ReportGermania": false,
"PrimaPagina": false,
"Duplica": false,
"LastUpdate": "2026-03-17T14:52:33.9816658+01:00",
"Titolo": null
}
---
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione(736244)/UploadCampioneFile' \
--header 'Authorization: Bearer ••••••' \
--form 'file=@C:\xampp\htdocs\trf_certest\public\photostrf\1268-20260317134828-104a2ca5-8a4c-4df8-b2ca-a28f518b7b25.jpg' \
--form 'StampaNelRapporto=false' \
--form 'PrimaPagina=false'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CampioneFiles\/$entity",
"IdCampioneFile": 1784769,
"FileName": "1268-20260317134828-104a2ca5-8a4c-4df8-b2ca-a28f518b7b25.jpg",
"Web": false,
"AllegaAlRapporto": false,
"StampaNelRapporto": false,
"ReportGermania": false,
"PrimaPagina": false,
"Duplica": false,
"LastUpdate": "2026-03-17T14:52:35.2759882+01:00",
"Titolo": null
}
---
@@ -0,0 +1,18 @@
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(564663)/InviaCommessa' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
"IdCommessa": 564663,
"CodiceCommessa": "26C0059",
"RiferimentoCertificato": null,
"StatoCommessaWeb": "Nuova",
"DataCreazioneWeb": "2026-03-17T14:52:29.347+01:00",
"CodiceCommessaWeb": "26C0059",
"DataInviatoWeb": "2026-03-17T14:52:38.9894528+01:00",
"Richiedente": "From TRFSmart Application",
"Descrizione": "From TRFSmart Application"
}
@@ -0,0 +1,226 @@
curl --location --request PATCH 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(564663)' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"CommesseCustomFields": [
{
"IdCommesseCustomFields": 23308665,
"Valore": "2026-03-17T13:52:36+00:00"
},
{
"IdCommesseCustomFields": 23308666,
"Valore": "13:52"
},
{
"IdCommesseCustomFields": 23308667,
"Valore": "9299"
},
{
"IdCommesseCustomFields": 23308668,
"Valore": "2026-03-17T13:52:36+00:00"
},
{
"IdCommesseCustomFields": 23308669,
"Valore": "aaa"
},
{
"IdCommesseCustomFields": 23308670,
"Valore": "1"
},
{
"IdCommesseCustomFields": 23308676,
"Valore": "673"
},
{
"IdCommesseCustomFields": 23308677,
"Valore": "674"
},
{
"IdCommesseCustomFields": 23308678,
"Valore": "4410"
},
{
"IdCommesseCustomFields": 23308680,
"Valore": "1468"
},
{
"IdCommesseCustomFields": 23308681,
"Valore": "1604"
},
{
"IdCommesseCustomFields": 23308682,
"Valore": "12875"
},
{
"IdCommesseCustomFields": 23308683,
"Valore": "8755"
},
{
"IdCommesseCustomFields": 23308684,
"Valore": ""
},
{
"IdCommesseCustomFields": 23308644,
"Valore": "13497"
},
{
"IdCommesseCustomFields": 23308645,
"Valore": "AAA"
},
{
"IdCommesseCustomFields": 23308646,
"Valore": "Example Description"
},
{
"IdCommesseCustomFields": 23308685,
"Valore": "1892"
},
{
"IdCommesseCustomFields": 23308686,
"Valore": "1111"
},
{
"IdCommesseCustomFields": 23308687,
"Valore": "2026-03-02T13:52:36+00:00"
},
{
"IdCommesseCustomFields": 23308688,
"Valore": "2026-03-17T13:52:36+00:00"
},
{
"IdCommesseCustomFields": 23308689,
"Valore": "2026-03-17T13:52:36+00:00"
},
{
"IdCommesseCustomFields": 23308690,
"Valore": "1978"
},
{
"IdCommesseCustomFields": 23308691,
"Valore": "13:52"
},
{
"IdCommesseCustomFields": 23308692,
"Valore": "14:44"
},
{
"IdCommesseCustomFields": 23308693,
"Valore": "14:00"
},
{
"IdCommesseCustomFields": 23308694,
"Valore": "2026-03-17T13:52:36+00:00"
},
{
"IdCommesseCustomFields": 23308695,
"Valore": "8596"
},
{
"IdCommesseCustomFields": 23308696,
"Valore": "8600"
},
{
"IdCommesseCustomFields": 23308697,
"Valore": "8605"
},
{
"IdCommesseCustomFields": 23308647,
"Valore": "L209A4M00130M7280"
},
{
"IdCommesseCustomFields": 23308648,
"Valore": "1"
},
{
"IdCommesseCustomFields": 23308649,
"Valore": "1"
},
{
"IdCommesseCustomFields": 23308650,
"Valore": "264"
},
{
"IdCommesseCustomFields": 23308651,
"Valore": "1589"
},
{
"IdCommesseCustomFields": 23308652,
"Valore": "AAA"
},
{
"IdCommesseCustomFields": 23308653,
"Valore": "3055"
},
{
"IdCommesseCustomFields": 23308654,
"Valore": "aaa"
},
{
"IdCommesseCustomFields": 23308655,
"Valore": "qqq"
},
{
"IdCommesseCustomFields": 23308656,
"Valore": "1"
},
{
"IdCommesseCustomFields": 23308657,
"Valore": "1"
},
{
"IdCommesseCustomFields": 23308658,
"Valore": "qqq"
},
{
"IdCommesseCustomFields": 23308659,
"Valore": "236"
},
{
"IdCommesseCustomFields": 23308660,
"Valore": "1208"
},
{
"IdCommesseCustomFields": 23308661,
"Valore": "ciao"
},
{
"IdCommesseCustomFields": 23308662,
"Valore": "bbb"
},
{
"IdCommesseCustomFields": 23308663,
"Valore": "12234"
},
{
"IdCommesseCustomFields": 23308664,
"Valore": "MONCLER"
},
{
"IdCommesseCustomFields": 23308671,
"Valore": "L209A4M00130M7280 - PACEY2"
},
{
"IdCommesseCustomFields": 23308672,
"Valore": "4678"
},
{
"IdCommesseCustomFields": 23308673,
"Valore": "1"
},
{
"IdCommesseCustomFields": 23308674,
"Valore": ""
},
{
"IdCommesseCustomFields": 23308675,
"Valore": "2450"
},
{
"IdCommesseCustomFields": 23308679,
"Valore": "744"
}
]
}'
RESPONSE:
null
@@ -0,0 +1,120 @@
CAMPIONE #0
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Commessa": 564817,
"Matrice": 3028,
"SottoMatrice": null,
"SchemaCustomField": 82,
"NoteWeb": "AAA",
"ConsegnaRichiesta": "2026-01-29"
}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#Campione\/$entity",
"IdCampione": 736487,
"CodiceCampione": "11016",
"CodiceCampioneWeb": "11016",
"StatoCampione": "Nuovo",
"DataCreazioneWeb": "2026-03-18T10:54:03.4537462+01:00",
"RiferimentoWeb": "",
"ConsegnaRichiesta": "2026-01-29T00:00:00+01:00",
"DataAccettazioneLims": null,
"Riferimento": null,
"NoteWeb": "AAA",
"GruppiRicercati": null
}
---
CAMPIONE #1
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Commessa": 564817,
"Matrice": 3028,
"SottoMatrice": null,
"SchemaCustomField": 82,
"NoteWeb": "CCC",
"ConsegnaRichiesta": "2026-01-29"
}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#Campione\/$entity",
"IdCampione": 736488,
"CodiceCampione": "11017",
"CodiceCampioneWeb": "11017",
"StatoCampione": "Nuovo",
"DataCreazioneWeb": "2026-03-18T10:54:04.8623753+01:00",
"RiferimentoWeb": "",
"ConsegnaRichiesta": "2026-01-29T00:00:00+01:00",
"DataAccettazioneLims": null,
"Riferimento": null,
"NoteWeb": "CCC",
"GruppiRicercati": null
}
---
CAMPIONE #2
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Commessa": 564817,
"Matrice": 3028,
"SottoMatrice": null,
"SchemaCustomField": 82,
"NoteWeb": "DDD",
"ConsegnaRichiesta": "2026-01-29"
}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#Campione\/$entity",
"IdCampione": 736489,
"CodiceCampione": "11018",
"CodiceCampioneWeb": "11018",
"StatoCampione": "Nuovo",
"DataCreazioneWeb": "2026-03-18T10:54:06.2840533+01:00",
"RiferimentoWeb": "",
"ConsegnaRichiesta": "2026-01-29T00:00:00+01:00",
"DataAccettazioneLims": null,
"Riferimento": null,
"NoteWeb": "DDD",
"GruppiRicercati": null
}
---
CAMPIONE #3
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Commessa": 564817,
"Matrice": 3028,
"SottoMatrice": null,
"SchemaCustomField": 82,
"NoteWeb": "EEE",
"ConsegnaRichiesta": "2026-01-29"
}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#Campione\/$entity",
"IdCampione": 736490,
"CodiceCampione": "11019",
"CodiceCampioneWeb": "11019",
"StatoCampione": "Nuovo",
"DataCreazioneWeb": "2026-03-18T10:54:07.8853452+01:00",
"RiferimentoWeb": "",
"ConsegnaRichiesta": "2026-01-29T00:00:00+01:00",
"DataAccettazioneLims": null,
"Riferimento": null,
"NoteWeb": "EEE",
"GruppiRicercati": null
}
---
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,20 @@
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(564817)/ImportaCommessa' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"IdUtente": 285
}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
"IdCommessa": 564817,
"CodiceCommessa": "26C0071",
"RiferimentoCertificato": null,
"StatoCommessaWeb": "Elaborata",
"DataCreazioneWeb": "2026-03-18T10:54:02.187+01:00",
"CodiceCommessaWeb": "26C0071",
"DataInviatoWeb": "2026-03-18T10:54:14.727+01:00",
"Richiedente": "From TRFSmart Application",
"Descrizione": "From TRFSmart Application"
}
@@ -0,0 +1,48 @@
Photos for CommessaWeb 564817 (iddatadb=1270):
Total photos found: 2, campioni: 4
=== Campione 736487 (main) ===
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione(736487)/UploadCampioneFile' \
--header 'Authorization: Bearer ••••••' \
--form 'file=@C:\xampp\htdocs\trf_certest\public\photostrf\1270-20260318095305-1e672dd9-5420-4432-b422-02d8d271c178.jpg' \
--form 'StampaNelRapporto=true' \
--form 'PrimaPagina=true'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CampioneFiles\/$entity",
"IdCampioneFile": 1785492,
"FileName": "1270-20260318095305-1e672dd9-5420-4432-b422-02d8d271c178.jpg",
"Web": false,
"AllegaAlRapporto": false,
"StampaNelRapporto": false,
"ReportGermania": false,
"PrimaPagina": false,
"Duplica": false,
"LastUpdate": "2026-03-18T10:54:09.5438326+01:00",
"Titolo": null
}
---
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione(736487)/UploadCampioneFile' \
--header 'Authorization: Bearer ••••••' \
--form 'file=@C:\xampp\htdocs\trf_certest\public\photostrf\1270-20260318095305-104a2ca5-8a4c-4df8-b2ca-a28f518b7b25.jpg' \
--form 'StampaNelRapporto=false' \
--form 'PrimaPagina=false'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CampioneFiles\/$entity",
"IdCampioneFile": 1785493,
"FileName": "1270-20260318095305-104a2ca5-8a4c-4df8-b2ca-a28f518b7b25.jpg",
"Web": false,
"AllegaAlRapporto": false,
"StampaNelRapporto": false,
"ReportGermania": false,
"PrimaPagina": false,
"Duplica": false,
"LastUpdate": "2026-03-18T10:54:10.8237002+01:00",
"Titolo": null
}
---
@@ -0,0 +1,18 @@
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(564817)/InviaCommessa' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
"IdCommessa": 564817,
"CodiceCommessa": "26C0071",
"RiferimentoCertificato": null,
"StatoCommessaWeb": "Nuova",
"DataCreazioneWeb": "2026-03-18T10:54:02.187+01:00",
"CodiceCommessaWeb": "26C0071",
"DataInviatoWeb": "2026-03-18T10:54:14.7252132+01:00",
"Richiedente": "From TRFSmart Application",
"Descrizione": "From TRFSmart Application"
}
@@ -0,0 +1,226 @@
curl --location --request PATCH 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(564817)' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"CommesseCustomFields": [
{
"IdCommesseCustomFields": 23315543,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315544,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315545,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315546,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315547,
"Valore": "/"
},
{
"IdCommesseCustomFields": 23315548,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315554,
"Valore": "673"
},
{
"IdCommesseCustomFields": 23315555,
"Valore": "674"
},
{
"IdCommesseCustomFields": 23315556,
"Valore": "669"
},
{
"IdCommesseCustomFields": 23315558,
"Valore": "1469"
},
{
"IdCommesseCustomFields": 23315559,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315560,
"Valore": "12875"
},
{
"IdCommesseCustomFields": 23315561,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315562,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315522,
"Valore": "13497"
},
{
"IdCommesseCustomFields": 23315523,
"Valore": "20262"
},
{
"IdCommesseCustomFields": 23315524,
"Valore": "ART. PEACH"
},
{
"IdCommesseCustomFields": 23315563,
"Valore": "1892"
},
{
"IdCommesseCustomFields": 23315564,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315565,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315566,
"Valore": "2026-03-18T09:54:11+00:00"
},
{
"IdCommesseCustomFields": 23315567,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315568,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315569,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315570,
"Valore": "09:53"
},
{
"IdCommesseCustomFields": 23315571,
"Valore": "10:37"
},
{
"IdCommesseCustomFields": 23315572,
"Valore": "2026-03-18T09:54:11+00:00"
},
{
"IdCommesseCustomFields": 23315573,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315574,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315575,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315525,
"Valore": "L209A4M00130M7280"
},
{
"IdCommesseCustomFields": 23315526,
"Valore": "BLACK"
},
{
"IdCommesseCustomFields": 23315527,
"Valore": "PE007J"
},
{
"IdCommesseCustomFields": 23315528,
"Valore": "264"
},
{
"IdCommesseCustomFields": 23315529,
"Valore": "420"
},
{
"IdCommesseCustomFields": 23315530,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315531,
"Valore": "15357"
},
{
"IdCommesseCustomFields": 23315532,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315533,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315534,
"Valore": "P"
},
{
"IdCommesseCustomFields": 23315535,
"Valore": "RM-ACC"
},
{
"IdCommesseCustomFields": 23315536,
"Valore": " CONCERIA M2 SRL"
},
{
"IdCommesseCustomFields": 23315537,
"Valore": "344"
},
{
"IdCommesseCustomFields": 23315538,
"Valore": "520"
},
{
"IdCommesseCustomFields": 23315539,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315540,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315541,
"Valore": "Not provided"
},
{
"IdCommesseCustomFields": 23315542,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315549,
"Valore": ""
},
{
"IdCommesseCustomFields": 23315550,
"Valore": "4678"
},
{
"IdCommesseCustomFields": 23315551,
"Valore": "Leather&amp;amp;Fur"
},
{
"IdCommesseCustomFields": 23315552,
"Valore": "278"
},
{
"IdCommesseCustomFields": 23315553,
"Valore": "2089"
},
{
"IdCommesseCustomFields": 23315557,
"Valore": "745"
}
]
}'
RESPONSE:
null
@@ -0,0 +1,90 @@
CAMPIONE #0
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Commessa": 565085,
"Matrice": 7714,
"SottoMatrice": null,
"SchemaCustomField": 82,
"NoteWeb": "Parte AAA",
"ConsegnaRichiesta": "2026-04-08"
}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#Campione\/$entity",
"IdCampione": 736899,
"CodiceCampione": "11038",
"CodiceCampioneWeb": "11038",
"StatoCampione": "Nuovo",
"DataCreazioneWeb": "2026-03-19T10:03:19.8547279+01:00",
"RiferimentoWeb": "",
"ConsegnaRichiesta": "2026-04-08T00:00:00+02:00",
"DataAccettazioneLims": null,
"Riferimento": null,
"NoteWeb": "Parte AAA",
"GruppiRicercati": null
}
---
CAMPIONE #1
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Commessa": 565085,
"Matrice": 7714,
"SottoMatrice": null,
"SchemaCustomField": 82,
"NoteWeb": "Parte BBB",
"ConsegnaRichiesta": "2026-04-08"
}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#Campione\/$entity",
"IdCampione": 736900,
"CodiceCampione": "11039",
"CodiceCampioneWeb": "11039",
"StatoCampione": "Nuovo",
"DataCreazioneWeb": "2026-03-19T10:03:22.0431135+01:00",
"RiferimentoWeb": "",
"ConsegnaRichiesta": "2026-04-08T00:00:00+02:00",
"DataAccettazioneLims": null,
"Riferimento": null,
"NoteWeb": "Parte BBB",
"GruppiRicercati": null
}
---
CAMPIONE #2
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Commessa": 565085,
"Matrice": 7714,
"SottoMatrice": null,
"SchemaCustomField": 82,
"NoteWeb": "Parte CCC",
"ConsegnaRichiesta": "2026-04-08"
}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#Campione\/$entity",
"IdCampione": 736901,
"CodiceCampione": "11040",
"CodiceCampioneWeb": "11040",
"StatoCampione": "Nuovo",
"DataCreazioneWeb": "2026-03-19T10:03:23.6170597+01:00",
"RiferimentoWeb": "",
"ConsegnaRichiesta": "2026-04-08T00:00:00+02:00",
"DataAccettazioneLims": null,
"Riferimento": null,
"NoteWeb": "Parte CCC",
"GruppiRicercati": null
}
---
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,20 @@
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(565085)/ImportaCommessa' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"IdUtente": 285
}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
"IdCommessa": 565085,
"CodiceCommessa": "26C0089",
"RiferimentoCertificato": null,
"StatoCommessaWeb": "Elaborata",
"DataCreazioneWeb": "2026-03-19T10:03:18.653+01:00",
"CodiceCommessaWeb": "26C0089",
"DataInviatoWeb": "2026-03-19T10:03:30.603+01:00",
"Richiedente": "From TRFSmart Application",
"Descrizione": "From TRFSmart Application"
}
@@ -0,0 +1,48 @@
Photos for CommessaWeb 565085 (iddatadb=1279):
Total photos found: 2, campioni: 3
=== Campione 736899 (main) ===
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione(736899)/UploadCampioneFile' \
--header 'Authorization: Bearer ••••••' \
--form 'file=@C:\xampp\htdocs\trf_certest\public\photostrf\1279-20260319090037-1e672dd9-5420-4432-b422-02d8d271c178.jpg' \
--form 'StampaNelRapporto=true' \
--form 'PrimaPagina=true'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CampioneFiles\/$entity",
"IdCampioneFile": 1786602,
"FileName": "1279-20260319090037-1e672dd9-5420-4432-b422-02d8d271c178.jpg",
"Web": false,
"AllegaAlRapporto": false,
"StampaNelRapporto": false,
"ReportGermania": false,
"PrimaPagina": false,
"Duplica": false,
"LastUpdate": "2026-03-19T10:03:25.2757049+01:00",
"Titolo": null
}
---
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione(736899)/UploadCampioneFile' \
--header 'Authorization: Bearer ••••••' \
--form 'file=@C:\xampp\htdocs\trf_certest\public\photostrf\1279-20260319090037-104a2ca5-8a4c-4df8-b2ca-a28f518b7b25.jpg' \
--form 'StampaNelRapporto=false' \
--form 'PrimaPagina=false'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CampioneFiles\/$entity",
"IdCampioneFile": 1786603,
"FileName": "1279-20260319090037-104a2ca5-8a4c-4df8-b2ca-a28f518b7b25.jpg",
"Web": false,
"AllegaAlRapporto": false,
"StampaNelRapporto": false,
"ReportGermania": false,
"PrimaPagina": false,
"Duplica": false,
"LastUpdate": "2026-03-19T10:03:26.7566613+01:00",
"Titolo": null
}
---
@@ -0,0 +1,18 @@
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(565085)/InviaCommessa' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
"IdCommessa": 565085,
"CodiceCommessa": "26C0089",
"RiferimentoCertificato": null,
"StatoCommessaWeb": "Nuova",
"DataCreazioneWeb": "2026-03-19T10:03:18.653+01:00",
"CodiceCommessaWeb": "26C0089",
"DataInviatoWeb": "2026-03-19T10:03:30.6022436+01:00",
"Richiedente": "From TRFSmart Application",
"Descrizione": "From TRFSmart Application"
}
@@ -0,0 +1,226 @@
curl --location --request PATCH 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(565085)' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"CommesseCustomFields": [
{
"IdCommesseCustomFields": 23328248,
"Valore": "18/03/2026"
},
{
"IdCommesseCustomFields": 23328249,
"Valore": "09:00"
},
{
"IdCommesseCustomFields": 23328250,
"Valore": "9299"
},
{
"IdCommesseCustomFields": 23328251,
"Valore": "17/03/2026"
},
{
"IdCommesseCustomFields": 23328252,
"Valore": "/"
},
{
"IdCommesseCustomFields": 23328253,
"Valore": "5"
},
{
"IdCommesseCustomFields": 23328259,
"Valore": "673"
},
{
"IdCommesseCustomFields": 23328260,
"Valore": "674"
},
{
"IdCommesseCustomFields": 23328261,
"Valore": "669"
},
{
"IdCommesseCustomFields": 23328263,
"Valore": "1469"
},
{
"IdCommesseCustomFields": 23328264,
"Valore": "1604"
},
{
"IdCommesseCustomFields": 23328265,
"Valore": "12875"
},
{
"IdCommesseCustomFields": 23328266,
"Valore": "8755"
},
{
"IdCommesseCustomFields": 23328267,
"Valore": ""
},
{
"IdCommesseCustomFields": 23328227,
"Valore": "13497"
},
{
"IdCommesseCustomFields": 23328228,
"Valore": "20262"
},
{
"IdCommesseCustomFields": 23328229,
"Valore": "ART. PEACH"
},
{
"IdCommesseCustomFields": 23328268,
"Valore": "1892"
},
{
"IdCommesseCustomFields": 23328269,
"Valore": ""
},
{
"IdCommesseCustomFields": 23328270,
"Valore": ""
},
{
"IdCommesseCustomFields": 23328271,
"Valore": "19/03/2026"
},
{
"IdCommesseCustomFields": 23328272,
"Valore": "18/03/2026"
},
{
"IdCommesseCustomFields": 23328273,
"Valore": ""
},
{
"IdCommesseCustomFields": 23328274,
"Valore": "09:00"
},
{
"IdCommesseCustomFields": 23328275,
"Valore": "09:03"
},
{
"IdCommesseCustomFields": 23328276,
"Valore": "09:58"
},
{
"IdCommesseCustomFields": 23328277,
"Valore": "19/03/2026"
},
{
"IdCommesseCustomFields": 23328278,
"Valore": ""
},
{
"IdCommesseCustomFields": 23328279,
"Valore": ""
},
{
"IdCommesseCustomFields": 23328280,
"Valore": ""
},
{
"IdCommesseCustomFields": 23328230,
"Valore": "L209B4M00110M7280"
},
{
"IdCommesseCustomFields": 23328231,
"Valore": "BLACK"
},
{
"IdCommesseCustomFields": 23328232,
"Valore": "PE007J"
},
{
"IdCommesseCustomFields": 23328233,
"Valore": "264"
},
{
"IdCommesseCustomFields": 23328234,
"Valore": "420"
},
{
"IdCommesseCustomFields": 23328235,
"Valore": "AAA"
},
{
"IdCommesseCustomFields": 23328236,
"Valore": "3055"
},
{
"IdCommesseCustomFields": 23328237,
"Valore": "AAA"
},
{
"IdCommesseCustomFields": 23328238,
"Valore": "AAA"
},
{
"IdCommesseCustomFields": 23328239,
"Valore": "P"
},
{
"IdCommesseCustomFields": 23328240,
"Valore": "RM-ACC"
},
{
"IdCommesseCustomFields": 23328241,
"Valore": "\u00a0CONCERIA M2 SRL"
},
{
"IdCommesseCustomFields": 23328242,
"Valore": "344"
},
{
"IdCommesseCustomFields": 23328243,
"Valore": "1208"
},
{
"IdCommesseCustomFields": 23328244,
"Valore": "AAA"
},
{
"IdCommesseCustomFields": 23328245,
"Valore": "AAA"
},
{
"IdCommesseCustomFields": 23328246,
"Valore": "Not provided"
},
{
"IdCommesseCustomFields": 23328247,
"Valore": "AAA"
},
{
"IdCommesseCustomFields": 23328254,
"Valore": "aaa"
},
{
"IdCommesseCustomFields": 23328255,
"Valore": "4678"
},
{
"IdCommesseCustomFields": 23328256,
"Valore": "Leather&amp;amp;Fur"
},
{
"IdCommesseCustomFields": 23328257,
"Valore": "278"
},
{
"IdCommesseCustomFields": 23328258,
"Valore": "2089"
},
{
"IdCommesseCustomFields": 23328262,
"Valore": "745"
}
]
}'
RESPONSE:
null
@@ -0,0 +1,40 @@
CAMPIONE #0
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Commessa": 95833,
"Matrice": 8413,
"SottoMatrice": null,
"SchemaCustomField": 82,
"NoteWeb": "aaa"
}'
RESPONSE:
{
"IdCampione": 14445,
"Commessa": 95833,
"Matrice": 8413
}
---
CAMPIONE #1
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/Campione' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Commessa": 95833,
"Matrice": 3879,
"SottoMatrice": null,
"SchemaCustomField": 82,
"NoteWeb": "bbb"
}'
RESPONSE:
{
"IdCampione": 15750,
"Commessa": 95833,
"Matrice": 3879
}
---
@@ -0,0 +1,9 @@
curl --location --request GET 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(95833)?$expand=CommesseCustomFields($expand=CustomField)' \
--header 'Authorization: Bearer ••••••'
RESPONSE:
{
"IdCommessa": 95833,
"CodiceCommessa": "SIM-95833",
"CommesseCustomFields": []
}
@@ -0,0 +1,9 @@
curl --location --request GET 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(95833)?$expand=CommesseCustomFields($expand=CustomField)' \
--header 'Authorization: Bearer ••••••'
RESPONSE:
{
"IdCommessa": 95833,
"CodiceCommessa": "SIM-95833",
"CommesseCustomFields": []
}
@@ -0,0 +1,10 @@
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb(95833)/ImportaCommessa' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{}'
RESPONSE:
{
"simulated": true,
"endpoint": "CommessaWeb(95833)\/ImportaCommessa"
}
@@ -0,0 +1,15 @@
Photos for CommessaWeb 95833 (iddatadb=1237):
Total photos found: 1
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/AllegatoCommessaWeb' \
--header 'Authorization: Bearer ••••••' \
--form 'IdCommessa=95833' \
--form 'file=@C:\xampp\htdocs\trf_certest\public\photostrf\1237-20260228192737-Blue and White Simple Daily Vlogger YouTube Banner (15) (1) (1).png'
RESPONSE:
{
"simulated": true,
"file": "1237-20260228192737-Blue and White Simple Daily Vlogger YouTube Banner (15) (1) (1).png"
}
---
@@ -0,0 +1,22 @@
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Cliente": 3378,
"SchemaCustomField": 82,
"Richiedente": "Test Web Import",
"Descrizione": "TEST CommessaWeb",
"ClienteResponsabile": 3,
"MoltiplicatorePrezzo": 1,
"AnagraficaCertestObject": 2,
"AnagraficaCertestService": 1,
"ClienteFornitore": null
}'
RESPONSE:
{
"IdCommessa": 95833,
"CodiceCommessa": "SIM-95833",
"Richiedente": "Test Web Import",
"Descrizione": "TEST CommessaWeb"
}
@@ -0,0 +1,28 @@
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Cliente": 3378,
"SchemaCustomField": 82,
"Richiedente": "Test Web Import",
"Descrizione": "TEST CommessaWeb",
"ClienteResponsabile": 7586,
"MoltiplicatorePrezzo": 3,
"AnagraficaCertestObject": 8963,
"AnagraficaCertestService": 9007,
"ClienteFornitore": null
}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
"IdCommessa": 562479,
"CodiceCommessa": "26C0013",
"RiferimentoCertificato": null,
"StatoCommessaWeb": "Nuova",
"DataCreazioneWeb": "2026-03-06T09:09:16.9338074+01:00",
"CodiceCommessaWeb": "26C0013",
"DataInviatoWeb": null,
"Richiedente": "Test Web Import",
"Descrizione": "TEST CommessaWeb"
}
@@ -0,0 +1,28 @@
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Cliente": 3378,
"SchemaCustomField": 82,
"Richiedente": "Test Web Import",
"Descrizione": "TEST CommessaWeb",
"ClienteResponsabile": 10653,
"MoltiplicatorePrezzo": 1,
"AnagraficaCertestObject": 8960,
"AnagraficaCertestService": 9010,
"ClienteFornitore": null
}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
"IdCommessa": 563528,
"CodiceCommessa": "26C0029",
"RiferimentoCertificato": null,
"StatoCommessaWeb": "Nuova",
"DataCreazioneWeb": "2026-03-11T14:10:00.8960358+01:00",
"CodiceCommessaWeb": "26C0029",
"DataInviatoWeb": null,
"Richiedente": "Test Web Import",
"Descrizione": "TEST CommessaWeb"
}
@@ -0,0 +1,28 @@
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Cliente": 3378,
"SchemaCustomField": 82,
"Richiedente": "From TRFSmart Application",
"Descrizione": "From TRFSmart Application",
"ClienteResponsabile": 7598,
"MoltiplicatorePrezzo": 2,
"AnagraficaCertestObject": 8964,
"AnagraficaCertestService": 9012,
"ClienteFornitore": 11208
}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
"IdCommessa": 564662,
"CodiceCommessa": "26C0058",
"RiferimentoCertificato": null,
"StatoCommessaWeb": "Nuova",
"DataCreazioneWeb": "2026-03-17T14:49:39.4933462+01:00",
"CodiceCommessaWeb": "26C0058",
"DataInviatoWeb": null,
"Richiedente": "From TRFSmart Application",
"Descrizione": "From TRFSmart Application"
}
@@ -0,0 +1,28 @@
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Cliente": 3378,
"SchemaCustomField": 82,
"Richiedente": "From TRFSmart Application",
"Descrizione": "From TRFSmart Application",
"ClienteResponsabile": 7598,
"MoltiplicatorePrezzo": 2,
"AnagraficaCertestObject": 8964,
"AnagraficaCertestService": 9012,
"ClienteFornitore": 11208
}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
"IdCommessa": 564663,
"CodiceCommessa": "26C0059",
"RiferimentoCertificato": null,
"StatoCommessaWeb": "Nuova",
"DataCreazioneWeb": "2026-03-17T14:52:29.3463415+01:00",
"CodiceCommessaWeb": "26C0059",
"DataInviatoWeb": null,
"Richiedente": "From TRFSmart Application",
"Descrizione": "From TRFSmart Application"
}
@@ -0,0 +1,28 @@
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Cliente": 3378,
"SchemaCustomField": 82,
"Richiedente": "From TRFSmart Application",
"Descrizione": "From TRFSmart Application",
"ClienteResponsabile": 7586,
"MoltiplicatorePrezzo": null,
"AnagraficaCertestObject": 8973,
"AnagraficaCertestService": 9007,
"ClienteFornitore": 11208
}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
"IdCommessa": 564817,
"CodiceCommessa": "26C0071",
"RiferimentoCertificato": null,
"StatoCommessaWeb": "Nuova",
"DataCreazioneWeb": "2026-03-18T10:54:02.1867227+01:00",
"CodiceCommessaWeb": "26C0071",
"DataInviatoWeb": null,
"Richiedente": "From TRFSmart Application",
"Descrizione": "From TRFSmart Application"
}
@@ -0,0 +1,29 @@
curl --location --request POST 'https://93.43.5.102/limsapi/api/odata/CommessaWeb' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ••••••' \
--data '{
"Cliente": 3378,
"SchemaCustomField": 82,
"Richiedente": "From TRFSmart Application",
"Descrizione": "From TRFSmart Application",
"ClienteResponsabile": 7586,
"MoltiplicatorePrezzo": 2,
"AnagraficaCertestObject": 8973,
"AnagraficaCertestService": 9007,
"ClienteFornitore": 4505,
"ClienteAnalisi": null
}'
RESPONSE:
{
"@odata.context": "https:\/\/bvcpsitaly-elims.com\/limsapi\/api\/odata\/$metadata#CommessaWeb\/$entity",
"IdCommessa": 565085,
"CodiceCommessa": "26C0089",
"RiferimentoCertificato": null,
"StatoCommessaWeb": "Nuova",
"DataCreazioneWeb": "2026-03-19T10:03:18.6527414+01:00",
"CodiceCommessaWeb": "26C0089",
"DataInviatoWeb": null,
"Richiedente": "From TRFSmart Application",
"Descrizione": "From TRFSmart Application"
}
+117 -1
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
+274
View File
@@ -0,0 +1,274 @@
<!-- 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: 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>
<div style="display: flex; align-items: center;">
<input type="checkbox" id="showMixPartsAnnotations" name="showMixPartsAnnotations" style="margin-right: 5px;">
<label for="showMixPartsAnnotations" style="font-size: 0.9rem;">Mix</label>
</div>
</div>
<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: 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>
<div id="descriptionListAnnotations" class="draggable-description" style="display: none;"></div>
<div id="markerContainerAnnotations"></div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary btn-sm" id="addDescriptionsBtnAnnotations" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Aggiungi Lista Descrizioni</button>
<button type="button" class="btn btn-danger btn-sm" id="removeAnnotationsBtnAnnotations" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Rimuovi Descrizioni</button>
<button type="button" class="btn btn-warning btn-sm" id="undoMarkerBtnAnnotations" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Undo Marker</button>
<button type="button" class="btn btn-success btn-sm" id="savePhotoBtnAnnotations" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Salva Foto con Nome</button>
<button type="button" class="btn btn-primary btn-sm" id="backToPartsBtnAnnotations" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Torna alle Parti</button>
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Chiudi</button>
</div>
</div>
</div>
</div>
<style>
#annotationsModal {
z-index: 1070 !important;
}
#annotationsModal .modal-backdrop {
z-index: 1065 !important;
}
#annotationsModal .modal-content {
width: 100% !important;
max-width: 100% !important;
}
#partsListAnnotations .list-group-item {
cursor: pointer;
transition: background-color 0.2s;
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px 10px;
}
#partsListAnnotations .list-group-item:hover {
background-color: #f5f5f5;
}
#partsListAnnotations .list-group-item.active {
background-color: #e9ecef;
border-color: #dee2e6;
}
.draggable-description {
position: absolute;
background: rgba(255, 255, 255, 0.8);
padding: 5px;
font-family: Arial, sans-serif;
color: #000000;
cursor: move;
user-select: none;
z-index: 1000;
min-width: 100px;
min-height: 50px;
overflow: visible;
border: 1px solid #ccc;
}
.draggable-description.active-interaction {
border: 2px dashed #000;
}
.draggable-description div {
white-space: nowrap;
}
.resize-handle {
position: absolute;
bottom: 0;
right: 0;
width: 10px;
height: 10px;
background: #888;
cursor: se-resize;
}
.draggable-marker {
position: absolute;
width: 24px;
height: 24px;
border-radius: 50%;
color: #ffffff;
text-align: center;
line-height: 24px;
font-size: 12px;
cursor: move;
user-select: none;
z-index: 1000;
}
#markerContainerAnnotations {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
#savePhotoBtnAnnotations {
transition: all 0.3s ease-in-out;
}
#savePhotoBtnAnnotations.unsaved {
background-color: #dc3545 !important;
border-color: #dc3545 !important;
color: white !important;
animation: pulse 1.2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(220, 53, 69, 0.7);
}
70% {
box-shadow: 0 0 10px 15px rgba(220, 53, 69, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(220, 53, 69, 0);
}
}
.color-picker-container {
position: relative;
display: inline-block;
overflow: visible;
z-index: 2000;
}
.color-picker {
display: none;
position: absolute;
right: 0;
top: 25px;
background: #fff;
border: 1px solid #ccc;
padding: 5px;
z-index: 2000;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
flex-wrap: wrap;
width: 120px;
}
.color-option {
width: 24px;
height: 24px;
margin: 3px;
border: 1px solid #000;
cursor: pointer;
display: inline-block;
pointer-events: auto;
}
.color-option:hover {
border: 2px solid #000;
margin: 2px;
}
.selected-color {
width: 24px;
height: 24px;
margin-left: 5px;
border: 1px solid #000;
cursor: pointer;
display: inline-block;
pointer-events: auto;
}
.selected-color:hover {
border: 2px solid #000;
margin-left: 4px;
}
.temp-alert {
position: absolute;
top: 10px;
left: 50%;
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>
+83 -6
View File
@@ -1,4 +1,4 @@
<!-- Modal modificato con pulsante per riconoscimento vocale e download -->
<!-- Modal modificato con pulsante per riconoscimento vocale, download e selezione matrici -->
<div class="modal fade" id="partsModal" tabindex="-1" aria-labelledby="partsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl" style="max-width: 80% !important; width: 80% !important;">
<div class="modal-content">
@@ -9,10 +9,12 @@
<div class="modal-body">
<div class="row">
<div class="col-md-6">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; min-width: 0;">
<h6 style="margin: 0; white-space: nowrap;">Elenco Parti</h6>
<div style="display: flex; align-items: center;">
<input type="checkbox" id="showMixParts" name="showMixParts" style="margin-right: 5px;">
<div style="display: flex; align-items: center; min-width: 0;">
<select id="global-matrice" class="ms-2" style="width: 250px !important; min-width: 250px !important;">
</select>
<input type="checkbox" id="showMixParts" name="showMixParts" style="margin-right: 5px; margin-left: 10px;">
<label for="showMixParts" style="font-size: 0.9rem; margin-right: 10px;">Mix</label>
<button type="button" class="btn btn-info btn-sm" id="renumberPartsBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Rinumera Parti</button>
<button type="button" class="btn btn-secondary btn-sm ms-2" id="toggleVoiceBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;"><i class="fas fa-microphone"></i> Voce</button>
@@ -45,13 +47,14 @@
<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="downloadPhotoBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem; margin-right: 10px;"><i class="fas fa-download"></i> </button>
<button type="button" class="btn btn-primary btn-sm" id="downloadPhotoBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem; margin-right: 10px;"><i class="fas fa-download"></i></button>
<div id="photoSelectorContainer" style="display: none;"></div>
</div>
<div style="position: relative; width: 100%; min-height: 400px;">
<img id="samplePhoto" src="" alt="Foto del campione" style="max-width: 100%; max-height: 100%; object-fit: contain; position: absolute; top: 0; left: 0;">
<canvas id="photoCanvas" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></canvas>
<canvas id="overlayCanvas" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1000;"></canvas> <!-- Nuovo canvas per Fabric.js -->
<canvas id="overlayCanvas" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1000;"></canvas>
<div id="descriptionList" class="draggable-description" style="display: none;"></div>
<div id="markerContainer"></div>
</div>
</div>
@@ -249,4 +252,78 @@
border: 2px solid #000;
margin: 2px;
}
/* Stili per Select2 in #partsList e #global-matrice */
#partsList .select2-container,
.select2-container--default #global-matrice {
width: 250px !important;
min-width: 250px !important;
margin-left: 10px;
}
#partsList .select2-selection--single,
.select2-container--default #global-matrice .select2-selection--single {
height: 26px !important;
padding: 0.2rem 0.5rem !important;
font-size: 0.9rem !important;
border: 1px solid #ced4da !important;
background-color: #fff !important;
}
#partsList .select2-selection__rendered,
.select2-container--default #global-matrice .select2-selection__rendered {
line-height: 24px !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
white-space: nowrap !important;
}
#partsList .select2-selection__arrow,
.select2-container--default #global-matrice .select2-selection__arrow {
height: 26px !important;
}
#partsList .save-status,
#partsList .save-loading {
margin-left: 5px;
}
.select2-container--open .select2-dropdown {
z-index: 1051 !important;
border: 1px solid #aaa !important;
border-radius: 4px !important;
background: white !important;
overflow-y: auto !important;
max-height: 200px !important;
}
.select2-container--default .select2-search--dropdown .select2-search__field {
width: 100% !important;
padding: 0.2rem !important;
}
.select2-container--default .part-matrice,
.select2-container--default #global-matrice {
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>
+500
View File
@@ -0,0 +1,500 @@
<div class="modal fade" id="partsModal" tabindex="-1" aria-labelledby="partsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl" style="max-width: 95vw !important;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="partsModalLabel">Parti per TRF: <span id="trfHeader"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row parts-row">
<div class="col-md-9">
<!-- Prima riga: Elenco Parti, Rinumera, Voce -->
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h6 style="margin: 0;">Elenco Parti</h6>
<div style="display: flex; align-items: center;">
<button type="button" class="btn btn-info btn-sm" id="renumberPartsBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Rinumera Parti</button>
<button type="button" class="btn btn-secondary btn-sm ms-2" id="toggleVoiceBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;"><i class="fas fa-microphone"></i> Voce</button>
<button type="button" class="btn btn-info btn-sm ms-2 d-none" id="quotationeBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Add Quotation</button>
<button type="button" class="btn btn-primary btn-sm ms-2" id="showHideImageBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
<i class="fas fa-eye-slash" style="font-size: 0.8rem;"></i>
<i class="fas fa-image ms-1" style="font-size: 0.8rem;"></i>
</button>
</div>
</div>
<!-- Seconda riga: +, M, MacroMatrice, Matrice globale, Propaga -->
<div style="display: flex; align-items: center; margin-bottom: 10px;">
<button type="button" class="btn btn-success btn-sm add-row-global" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; margin-right: 5px;"><i class="fas fa-plus fa-xs"></i></button>
<button type="button" class="btn btn-primary btn-sm add-mix-global" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; margin-right: 10px;">M</button>
<select id="macro-matrice-filter" class="form-control form-control-sm ms-2" style="width: 200px !important; min-width: 200px !important; margin-right: 10px;">
<option value="">Tutte le MacroMatrici</option>
</select>
<select id="global-matrice" class="form-control form-control-sm" style="width: 350px !important; margin-right: 10px;"></select>
<button type="button" class="btn btn-primary btn-sm propagate-all-btn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;"><i class="fas fa-arrow-right fa-xs"></i> Propaga a tutte</button>
</div>
<table class="table table-striped table-sm" id="partsTable">
<thead>
<tr>
<th style="width: 55px;">Num</th>
<th>Descrizione</th>
<th style="width: 200px;">Matrice</th>
<th style="width: 150px;">
<input type="date" class="form-control form-control-sm propagate-date-input" style="width: 130px; margin-left: 5px; display: inline-block;" title="Propaga data a tutte le parti">
</th>
<th style="width: 200px;">
<button type="button" class="btn btn-light btn-sm propagate-note-btn" style="padding: 0.2rem 0.4rem; font-size: 0.9rem; margin-left: 5px;" title="Propaga nota a tutte le parti">
<i class="fas fa-sticky-note"></i>
</button>
Azioni
</th>
</tr>
</thead>
<tbody id="partsTableBody">
<tr data-part-id="new">
<td><input type="number" class="form-control form-control-sm part-number" value="1" style="width: 55px;"></td>
<td><input type="text" class="form-control form-control-sm part-description" placeholder="Inserisci descrizione"></td>
<td>
<div style="display: flex; align-items: center;">
<button type="button" class="btn btn-primary btn-sm propagate-matrice-btn" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; margin-right: 3px;"><i class="fas fa-arrow-right fa-xs"></i></button>
<select class="part-matrice form-control form-control-sm" style="width: 150px;"></select>
</div>
</td>
<td><input type="date" class="form-control form-control-sm part-dateexpiry" style="width: 130px;"></td>
<td>
<button type="button" class="btn btn-light btn-sm note-btn" style="padding: 0.2rem 0.4rem; font-size: 0.9rem;" title="Aggiungi/Modifica nota"><i class="fas fa-sticky-note"></i></button>
<button type="button" class="btn btn-warning btn-sm add-mix-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;">M+</button>
<button type="button" class="btn btn-danger btn-sm remove-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; display: none;"><i class="fas fa-trash fa-xs"></i></button>
<span class="save-status text-success" style="display: none; margin-left: 5px;"><i class="fas fa-check fa-xs"></i></span>
<span class="save-loading text-warning" style="display: none; margin-left: 5px;"><i class="fas fa-spinner fa-spin fa-xs"></i></span>
</td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-3">
<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="downloadPhotoBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem; margin-right: 10px;"><i class="fas fa-download"></i></button>
<div id="photoSelectorContainer" style="display: none;"></div>
</div>
<div style="position: relative; width: 100%; min-height: 400px;">
<img id="samplePhoto" src="" alt="Foto del campione" style="max-width: 100%; max-height: 400px; object-fit: contain;">
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary btn-sm" id="openAnnotationsBtn">Apri Annotazioni</button>
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Chiudi</button>
</div>
</div>
</div>
</div>
<!-- Modale per la nota -->
<div class="modal fade" id="noteModal" tabindex="-1" aria-labelledby="noteModalLabel" aria-hidden="true">
<div class="modal-dialog modal-md">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="noteModalLabel">Nota per Parte</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<textarea class="form-control part-note" rows="5" placeholder="Inserisci una nota"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Annulla</button>
<button type="button" class="btn btn-primary btn-sm save-note-btn">Salva</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="commonNoteModal" tabindex="-1" aria-labelledby="commonNoteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="commonNoteModalLabel">Nota comune per tutte le parti</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<textarea class="form-control part-note" rows="4" placeholder="Inserisci nota comune"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Chiudi</button>
<button type="button" class="btn btn-primary save-common-note-btn">Salva</button>
</div>
</div>
</div>
</div>
<!-- Modale di conferma per l'eliminazione -->
<div class="modal fade" id="confirmDeleteModal" tabindex="-1" aria-labelledby="confirmDeleteModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="confirmDeleteModalLabel">Conferma Eliminazione</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Sei sicuro di voler eliminare questa parte?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Annulla</button>
<button type="button" class="btn btn-danger btn-sm" id="confirmDeleteBtn">Elimina</button>
</div>
</div>
</div>
</div>
<!-- Finestra modale "Aggiungi preventivo" -->
<div class="modal fade" id="addQuotationModal" tabindex="-1" aria-labelledby="addQuotationModalLabel" aria-hidden="true">
<div class="modal-dialog modal-md">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addQuotationModalLabel">Choose a quotation</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<select id="addQuotationSelect" class="form-control form-control-sm" style="width: 100% !important; min-width: 100% !important"></select>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Annulla</button>
<button type="button" class="btn btn-primary btn-sm" id="addQuotationBtn">Confirm</button>
</div>
</div>
</div>
</div>
<style>
/* --- Base --- */
#partsModal {
z-index: 1060 !important
}
#partsModal .modal-backdrop {
z-index: 1055 !important
}
#partsModal .modal-content {
width: 100% !important;
max-width: 100% !important
}
#addQuotationModal {
z-index: 1060 !important
}
#addQuotationModal .modal-backdrop {
z-index: 1055 !important
}
/* Tabelle */
#partsTable tr {
display: table-row !important
}
#partsTable tr:hover {
background: #f5f5f5
}
#partsTable td,
#partsTable th {
padding: .2rem;
vertical-align: middle
}
#partsTable input,
#partsTable select {
height: 24px;
padding: .1rem .3rem
}
#partsTable button {
padding: .1rem .3rem;
margin: 0 2px
}
#partsTable i {
font-size: .6rem !important
}
/* --- Larghezze fisse header --- */
/* MacroMatrici = 250px */
#macro-matrice-filter {
width: 250px !important;
min-width: 250px !important;
max-width: 250px !important;
flex: 0 0 250px !important;
box-sizing: border-box;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#macro-matrice-filter.select2-hidden-accessible+.select2 {
width: 250px !important;
min-width: 250px !important;
max-width: 250px !important;
flex: 0 0 250px !important;
}
#macro-matrice-filter.select2-hidden-accessible+.select2 .select2-selection__rendered {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Matrice globale = 450px */
#global-matrice {
width: 450px !important;
min-width: 450px !important;
max-width: 450px !important;
flex: 0 0 450px !important;
box-sizing: border-box;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#global-matrice.select2-hidden-accessible+.select2 {
width: 450px !important;
min-width: 450px !important;
max-width: 450px !important;
flex: 0 0 450px !important;
}
#global-matrice.select2-hidden-accessible+.select2 .select2-selection__rendered {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Select delle righe (colonna Matrice) = 150px */
.part-matrice {
width: 300px !important;
min-width: 300px !important;
max-width: 300px !important;
flex: 0 0 300px !important;
}
.part-matrice.select2-hidden-accessible+.select2 {
width: 300px !important;
min-width: 300px !important;
max-width: 300px !important;
flex: 0 0 300px !important;
}
/* Colonna Descrizione (2ª colonna) = 420px */
#partsTable th:nth-child(2),
#partsTable td:nth-child(2) {
width: 300px !important;
min-width: 300px !important;
max-width: 300px !important;
}
#partsTable .part-number {
text-align: center;
}
#partsTable td:nth-child(2) .part-description {
width: 100% !important;
max-width: 100% !important;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Aspetto Select2 */
.select2-container--default .select2-selection--single {
height: 24px !important;
padding: .1rem .3rem !important;
font-size: .8rem !important;
border: 1px solid #ced4da !important;
}
.select2-container--default .select2-selection__arrow {
height: 24px !important
}
.select2-container--open .select2-dropdown {
z-index: 1061 !important;
border: 1px solid #aaa !important;
border-radius: 4px !important;
background: #fff !important;
max-height: 200px !important;
overflow-y: auto !important;
}
/* Evita stretching del flex nella riga dei filtri */
#partsModal .modal-body>.row .col-md-9>div[style*="display: flex"]>* {
flex: 0 0 auto;
}
/* Altri modali e pulsanti */
.propagate-matrice-btn,
.propagate-all-btn {
padding: .1rem .3rem !important;
font-size: .8rem !important
}
.save-status,
.save-loading {
margin-left: 5px
}
#confirmDeleteModal {
z-index: 1070 !important
}
#confirmDeleteModal .modal-backdrop {
z-index: 1065 !important
}
.note-btn {
padding: .2rem .4rem !important;
font-size: .9rem !important
}
.note-btn.has-note {
color: #dc3545 !important
}
#noteModal {
z-index: 1090 !important
}
#noteModal .modal-backdrop {
z-index: 1085 !important
}
#noteModal .modal-dialog {
position: relative;
z-index: 1090 !important
}
#noteModal textarea,
#commonNoteModal textarea {
resize: vertical
}
#commonNoteModal {
z-index: 1095 !important
}
#commonNoteModal .modal-backdrop {
z-index: 1090 !important
}
#commonNoteModal .modal-dialog {
position: relative;
z-index: 1095 !important
}
/* Evidenza salvataggio riga nel parts table */
/* Aumenta la specificità per le classi di flash */
table#partsTable tr.row-saving {
background-color: #f0ad4e !important;
/* Arancione per salvataggio in corso */
transition: background-color 0.3s ease;
}
table#partsTable tr.row-success {
background-color: #5cb85c !important;
/* Verde per successo */
transition: background-color 0.3s ease;
}
table#partsTable tr.row-error {
background-color: #d9534f !important;
/* Rosso per errore */
transition: background-color 0.3s ease;
}
/* Stato base: nascosti (verrà sovrascritto dallo style inline di jQuery) */
#partsModal .save-loading,
#partsModal .save-status {
display: none;
align-items: center;
gap: 6px;
padding: 2px 8px;
border-radius: 999px;
font-weight: 700;
font-size: 12px;
line-height: 1.2;
margin-left: 5px;
}
/* Quando NON sono nascosti via style inline (jQuery .show()), forzali a inline-flex */
#partsModal .save-loading:not([style*="display: none"]),
#partsModal .save-status:not([style*="display: none"]) {
display: inline-flex;
}
/* Loading (giallo) */
/* Loading (giallo) */
#partsModal .save-loading {
background: #ffd753ff;
border: 1px solid #ffd042ff;
color: #111;
/* testo nero */
}
#partsModal .save-loading i {
color: #111;
}
/* icona nera */
#partsModal .save-loading::after {
content: " Salvataggio…";
color: #111;
}
/* Salvato (verde) */
#partsModal .save-status {
background: #5dff83ff;
border: 1px solid #4effafff;
color: #111;
/* testo nero */
}
#partsModal .save-status i {
color: #111;
}
/* icona nera */
#partsModal .save-status::after {
content: " Salvato";
color: #111;
}
/* Animazioni */
@keyframes pulse {
0%,
100% {
transform: scale(1);
opacity: .9
}
50% {
transform: scale(1.05);
opacity: 1
}
}
@keyframes pop {
0% {
transform: scale(.85);
opacity: 0
}
100% {
transform: scale(1);
opacity: 1
}
}
/* rosso */
</style>
+294 -18
View File
@@ -12,11 +12,13 @@ $(document).ready(function () {
let photoAnnotations = {};
let partColors = {};
let partMatrice = {};
let selectedPartNumber = null;
let unsavedChanges = false;
let fabricCanvas = null;
let descriptionTextbox = null;
let markerObjects = {};
let matrici = [];
// ===================
// VOICE RECOGNITION SETUP
@@ -137,21 +139,82 @@ $(document).ready(function () {
.data("iddatadb", iddatadb)
.data("idquotations", idquotations);
loadPhoto(iddatadb, idquotations);
loadExistingParts(iddatadb, idquotations);
// Precarica le matrici se iddatadb (assumendo matrici solo per iddatadb)
if (iddatadb) {
if (matrici.length === 0) {
$.ajax({
url: "get_matrici_db.php",
method: "GET",
dataType: "json",
success: function (data) {
matrici = data.value || [];
initializeGlobalSelect2();
loadPhoto(iddatadb, idquotations);
loadExistingParts(iddatadb, idquotations);
},
error: function (xhr, status, error) {
matrici = [];
initializeGlobalSelect2();
loadPhoto(iddatadb, idquotations);
loadExistingParts(iddatadb, idquotations);
const errorMsg = $(
'<div class="alert alert-danger temp-alert" role="alert">Errore nel caricamento delle matrici: ' +
error +
" (" +
xhr.status +
")</div>",
);
$("#partsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
},
});
} else {
initializeGlobalSelect2();
loadPhoto(iddatadb, idquotations);
loadExistingParts(iddatadb, idquotations);
}
} else {
loadPhoto(iddatadb, idquotations);
loadExistingParts(iddatadb, idquotations);
}
const modal = new bootstrap.Modal(
document.getElementById("partsModal"),
);
modal.show();
const modalElement = document.getElementById("partsModal");
if (modalElement) {
// Verifica se il modale è già stato inizializzato
let modal = bootstrap.Modal.getInstance(modalElement);
if (!modal) {
modal = new bootstrap.Modal(modalElement, {
backdrop: true,
keyboard: true,
focus: true,
});
}
modal.show();
} else {
console.error("Elemento modale non trovato: #partsModal");
const errorMsg = $(
'<div class="alert alert-danger temp-alert" role="alert">Errore: Modale non trovato.</div>',
);
$("body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
}
});
$("#partsModal .close-btn, #partsModal").on("click", function (event) {
if (event.target === this) {
const modal = bootstrap.Modal.getInstance(
document.getElementById("partsModal"),
);
modal.hide();
if (event.target === this || $(event.target).hasClass("close-btn")) {
const modalElement = document.getElementById("partsModal");
const modal = bootstrap.Modal.getInstance(modalElement);
if (modal) {
modal.hide();
}
}
});
@@ -165,7 +228,7 @@ $(document).ready(function () {
});
$("#partsModal").on("hidden.bs.modal", function () {
// Reset global state to initial values
// Resetta lo stato
photoData = {
naturalWidth: 0,
naturalHeight: 0,
@@ -175,21 +238,33 @@ $(document).ready(function () {
};
photoAnnotations = {};
partColors = {};
partMatrice = {};
selectedPartNumber = null;
unsavedChanges = false;
if (fabricCanvas) {
fabricCanvas.off(); // Rimuove tutti gli eventi
fabricCanvas.off();
fabricCanvas.dispose();
fabricCanvas = null;
}
descriptionTextbox = null;
markerObjects = {};
// Clear UI elements
matrici = [];
$("#photoSelectorContainer").empty().hide();
$("#samplePhoto").attr("src", "");
$("#partsTableBody").empty();
// Remove any temporary messages
$("#global-matrice").empty();
$(".temp-alert").remove();
// Rimuovi manualmente il backdrop e ripristina il body
const modalElement = document.getElementById("partsModal");
const modal = bootstrap.Modal.getInstance(modalElement);
if (modal) {
modal.dispose(); // Distrugge l'istanza del modale
}
$(".modal-backdrop").remove();
$("body").removeClass("modal-open");
$("body").css("padding-right", "");
$(":focus").blur();
});
// ===================
@@ -535,6 +610,7 @@ $(document).ready(function () {
if (response.success) {
$row.remove();
delete partColors[partNumber];
delete partMatrice[partNumber];
if (markerObjects[partNumber]) {
fabricCanvas.remove(markerObjects[partNumber]);
delete markerObjects[partNumber];
@@ -576,6 +652,7 @@ $(document).ready(function () {
} else {
$row.remove();
delete partColors[partNumber];
delete partMatrice[partNumber];
if (markerObjects[partNumber]) {
fabricCanvas.remove(markerObjects[partNumber]);
delete markerObjects[partNumber];
@@ -717,6 +794,9 @@ $(document).ready(function () {
</tr>`;
$("#partsTableBody").append(newRow);
partColors[part.part_number] = defaultColor;
if (part.idmatrice) {
partMatrice[part.part_number] = part.idmatrice;
}
});
} else {
addNewRow(1);
@@ -743,6 +823,163 @@ $(document).ready(function () {
});
}
// Funzione per inizializzare Select2 sulle tendine delle matrici
function initializeSelect2($select, partNumber, partId, idmatrice) {
if (typeof $.fn.select2 === "undefined") {
$select.replaceWith(
'<input type="text" class="form-control form-control-sm" placeholder="Select2 non disponibile" disabled>',
);
return;
}
const options = matrici.map(function (matrice) {
return {
id: matrice.IdMatrice,
text: matrice.NomeMatrice, // Updated to use NomeMatrice
};
});
$select.select2({
placeholder: "Seleziona matrice",
allowClear: true,
data: options,
dropdownParent: $("#partsModal"),
matcher: function (params, data) {
if (!params.term || params.term.length < 3) {
return data;
}
const term = params.term.toUpperCase();
if (data.text.toUpperCase().indexOf(term) >= 0) {
return data;
}
return null;
},
});
if (partId && partId !== "new" && idmatrice) {
const matrice = matrici.find((m) => m.IdMatrice == idmatrice);
if (matrice) {
const option = new Option(
matrice.NomeMatrice, // Updated to use NomeMatrice
matrice.IdMatrice,
true,
true,
);
$select.append(option).trigger("change");
partMatrice[partNumber] = matrice.IdMatrice;
}
}
$select.on("change", function () {
const idmatrice = $(this).val();
const $listItem = $(this).closest("li");
const $saveStatus = $listItem.find(".save-status");
const $saveLoading = $listItem.find(".save-loading");
partMatrice[partNumber] = idmatrice || null;
if (partId && partId !== "new") {
$saveLoading.show();
$saveStatus.hide();
const iddatadb = $("#partsModal").data("iddatadb");
const idquotations = $("#partsModal").data("idquotations");
const endpoint = idquotations
? "save_matrice_quotation.php"
: "save_matrice.php";
const data = idquotations
? { idquotations: idquotations }
: { iddatadb: iddatadb };
$.ajax({
url: endpoint,
method: "POST",
data: JSON.stringify({
...data,
parts: [
{
id: partId,
idmatrice: idmatrice || null,
},
],
}),
contentType: "application/json",
success: function (response) {
if (response.success) {
$saveLoading.hide();
$saveStatus.show();
setTimeout(() => $saveStatus.hide(), 2000);
} else {
const errorMsg = $(
'<div class="alert alert-danger temp-alert" role="alert">Errore nel salvataggio della matrice: ' +
response.message +
"</div>",
);
$("#partsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
$saveLoading.hide();
}
},
error: function (xhr, status, error) {
const errorMsg = $(
'<div class="alert alert-danger temp-alert" role="alert">Errore nel salvataggio della matrice: ' +
error +
" (" +
xhr.status +
")</div>",
);
$("#partsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
$saveLoading.hide();
},
});
}
});
}
// Funzione per inizializzare Select2 sul dropdown globale
function initializeGlobalSelect2() {
const $select = $("#global-matrice");
if (typeof $.fn.select2 === "undefined") {
$select.replaceWith(
'<input type="text" class="form-control form-control-sm" placeholder="Select2 non disponibile" disabled>',
);
return;
}
const options = matrici.map(function (matrice) {
return {
id: matrice.IdMatrice,
text: matrice.NomeMatrice, // Updated to use NomeMatrice
};
});
$select.select2({
placeholder: "Seleziona matrice globale",
allowClear: true,
data: options,
dropdownParent: $("#partsModal"),
matcher: function (params, data) {
if (!params.term || params.term.length < 3) {
return data;
}
const term = params.term.toUpperCase();
if (data.text.toUpperCase().indexOf(term) >= 0) {
return data;
}
return null;
},
});
}
// ===================
// PARTS LIST
// ===================
@@ -763,6 +1000,7 @@ $(document).ready(function () {
$("#partsTableBody tr").each(function () {
const partNumber = $(this).find(".part-number").val();
const partDescription = $(this).find(".part-description").val();
const partId = $(this).data("part-id");
const partColor =
partColors[partNumber] ||
(partDescription.startsWith("Mix") ? "#0000ff" : "#ff0000");
@@ -778,17 +1016,30 @@ $(document).ready(function () {
)
.join("");
const listItem = `
<li class="list-group-item" data-part-number="${partNumber}">
<li class="list-group-item" data-part-number="${partNumber}" data-part-id="${partId}">
${partNumber} - ${partDescription}
<div style="display: flex; align-items: center;">
<button type="button" class="btn btn-primary btn-sm propagate-matrice-btn" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; margin-right: 3px;"><i class="fas fa-arrow-right fa-xs"></i></button>
<select class="part-matrice" style="width: 250px !important; margin-right: 10px;"></select>
<button type="button" class="btn btn-success btn-sm add-to-mix-btn" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;"><i class="fas fa-plus fa-xs"></i></button>
<div class="color-picker-container">
<div class="color-option selected-color" style="background-color: ${partColor}; margin-left: 5px;"></div>
<div class="color-picker">${colorOptions}</div>
</div>
<span class="save-status text-success" style="display: none; margin-left: 5px;"><i class="fas fa-check fa-xs"></i></span>
<span class="save-loading text-warning" style="display: none; margin-left: 5px;"><i class="fas fa-spinner fa-spin fa-xs"></i></span>
</div>
</li>`;
$("#partsList").append(listItem);
const $select = $("#partsList").find(
`li[data-part-number="${partNumber}"] .part-matrice`,
);
initializeSelect2(
$select,
partNumber,
partId,
partMatrice[partNumber],
);
}
});
@@ -862,11 +1113,32 @@ $(document).ready(function () {
}
});
$(document).on("click", ".propagate-matrice-btn", function () {
const $listItem = $(this).closest("li");
const globalVal = $("#global-matrice").val();
if (globalVal) {
$listItem.find(".part-matrice").val(globalVal).trigger("change");
} else {
const errorMsg = $(
'<div class="alert alert-danger temp-alert" role="alert">Seleziona una matrice globale prima di propagare.</div>',
);
$("#partsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
}
});
$("#partsList").on("click", "li", function (e) {
if (
$(e.target).hasClass("add-to-mix-btn") ||
$(e.target).hasClass("color-option") ||
$(e.target).closest(".color-picker-container").length
$(e.target).closest(".color-picker-container").length ||
$(e.target).hasClass("part-matrice") ||
$(e.target).closest(".select2-container").length ||
$(e.target).hasClass("propagate-matrice-btn")
)
return;
selectedPartNumber = $(this).data("part-number");
@@ -892,6 +1164,7 @@ $(document).ready(function () {
? { idquotations: idquotations }
: { iddatadb: iddatadb };
let newPartColors = {};
let newPartMatrice = {};
let newMarkerObjects = {};
let partsData = $rows
@@ -908,6 +1181,7 @@ $(document).ready(function () {
partsData.forEach((part, index) => {
const newNumber = index + 1;
newPartColors[newNumber] = partColors[part.partNumber] || "#ff0000";
newPartMatrice[newNumber] = partMatrice[part.partNumber] || null;
if (markerObjects[part.partNumber]) {
newMarkerObjects[newNumber] = markerObjects[part.partNumber];
}
@@ -934,6 +1208,7 @@ $(document).ready(function () {
}
partColors = newPartColors;
partMatrice = newPartMatrice;
markerObjects = newMarkerObjects;
const partsToSave = partsData.map((part) => ({
@@ -941,6 +1216,7 @@ $(document).ready(function () {
part_number: part.partNumber,
part_description: part.partDescription,
mix: part.partDescription.startsWith("Mix") ? "Y" : "N",
idmatrice: partMatrice[part.partNumber] || null,
}));
$.ajax({
@@ -1157,7 +1433,7 @@ $(document).ready(function () {
width: annotations.descriptionSize.width,
scaleX: 1,
scaleY: 1,
backgroundColor: "rgba(255, 255, 255, 0.8)",
backgroundColor: "rgba(255, 255, 255, 0)", // Changed to fully transparent
fontFamily: "Arial",
fontSize: 24,
fill: "#000000",
File diff suppressed because it is too large Load Diff
+207 -21
View File
@@ -185,13 +185,21 @@ document.addEventListener("DOMContentLoaded", function () {
return;
}
for (const file of files) {
loader.style.display = "flex";
console.log(`Inizio upload di ${files.length} file`);
let successCount = 0;
let errorMessages = [];
let uploadPromises = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
if (!file.type.startsWith("image/")) {
alert("Per favore, carica solo immagini!");
alert(`File ${file.name} non è un'immagine, saltato!`);
continue;
}
loader.style.display = "flex";
console.log(`Preparazione upload file ${i + 1}: ${file.name}`);
const formData = new FormData();
formData.append("photo", file);
@@ -201,23 +209,43 @@ document.addEventListener("DOMContentLoaded", function () {
formData.append("iddatadb", iddatadb);
}
try {
const response = await fetch("upload_photo.php", {
method: "POST",
body: formData,
const uploadPromise = fetch("upload_photo.php", {
method: "POST",
body: formData,
})
.then((response) => response.json())
.then((result) => {
if (result.success) {
successCount++;
console.log(`Successo per ${file.name}`);
} else {
errorMessages.push(
`Errore per ${file.name}: ${result.message}`,
);
}
})
.catch((error) => {
errorMessages.push(
`Errore per ${file.name}: ${error.message}`,
);
});
const result = await response.json();
if (result.success) {
loadPopupContent(iddatadb, idquotations);
} else {
alert("Errore durante il caricamento: " + result.message);
}
} catch (error) {
alert("Errore durante il caricamento: " + error.message);
} finally {
loader.style.display = "none";
}
uploadPromises.push(uploadPromise);
}
await Promise.all(uploadPromises);
loader.style.display = "none";
console.log(
`Fine upload: ${successCount} riusciti, ${errorMessages.length} errori`,
);
if (errorMessages.length > 0) {
alert("Errori durante l'upload:\n" + errorMessages.join("\n"));
}
// Ricarica sempre alla fine per aggiornare la lista, anche se parziale successo
loadPopupContent(iddatadb, idquotations);
}
function attachPhotoEventListeners(iddatadb, idquotations) {
@@ -321,6 +349,69 @@ document.addEventListener("DOMContentLoaded", function () {
});
});
document
.querySelectorAll(".photo-flag-checkbox")
.forEach((checkbox) => {
checkbox.addEventListener("change", async function () {
const photoId = this.getAttribute("data-photo-id");
const field = this.getAttribute("data-field");
const value = this.checked ? 1 : 0;
const currentCheckbox = this;
// Only one PrimaPagina visually in the current popup
if (field === "PrimaPagina" && value === 1) {
document
.querySelectorAll(
".photo-flag-checkbox[data-field='PrimaPagina']",
)
.forEach((cb) => {
if (cb !== currentCheckbox) {
cb.checked = false;
}
});
}
try {
const formData = new URLSearchParams();
formData.append("photo_id", photoId);
formData.append("field", field);
formData.append("value", value);
const response = await fetch("update_photo_flags.php", {
method: "POST",
headers: {
"Content-Type":
"application/x-www-form-urlencoded",
},
body: formData.toString(),
});
const result = await response.json();
if (!result.success) {
throw new Error(
result.message || "Errore salvataggio flag",
);
}
// Safety refresh for PrimaPagina to ensure UI matches DB
if (field === "PrimaPagina") {
loadPopupContent(iddatadb, idquotations);
}
} catch (error) {
alert(
"Errore durante il salvataggio del flag: " +
error.message,
);
// rollback current checkbox
currentCheckbox.checked = !currentCheckbox.checked;
// reload popup to restore coherent state
loadPopupContent(iddatadb, idquotations);
}
});
});
document.querySelectorAll(".thumbnail").forEach((img) => {
img.addEventListener("click", function () {
const enlargedImage = document.getElementById("enlargedImage");
@@ -384,6 +475,11 @@ document.addEventListener("DOMContentLoaded", function () {
);
return;
}
if (canvas) {
canvas.dispose();
}
canvas = new fabric.Canvas("collageCanvas", {
backgroundColor: "#fff",
selection: true,
@@ -492,6 +588,7 @@ document.addEventListener("DOMContentLoaded", function () {
if (isCropping || isRemovingBackground) {
return;
}
const state = JSON.stringify(
canvas.toJSON([
"cornerColor",
@@ -499,12 +596,16 @@ document.addEventListener("DOMContentLoaded", function () {
"cornerSize",
"borderColor",
"transparentCorners",
"backgroundImage",
]),
);
history.push(state);
if (history.length > maxHistory) {
history.shift();
}
updateButtons();
}
@@ -512,10 +613,11 @@ document.addEventListener("DOMContentLoaded", function () {
if (history.length <= 1) {
return;
}
history.pop();
const previousState = history[history.length - 1];
if (previousState) {
canvas.clear();
canvas.loadFromJSON(previousState, () => {
canvas.renderAll();
updateLayersPanel();
@@ -524,8 +626,11 @@ document.addEventListener("DOMContentLoaded", function () {
} else {
console.warn("Nessuno stato precedente disponibile");
canvas.clear();
canvas.setBackgroundColor("#fff");
canvas.renderAll();
canvas.backgroundImage = null;
canvas.setBackgroundColor(
"#fff",
canvas.renderAll.bind(canvas),
);
updateLayersPanel();
updateButtons();
}
@@ -818,6 +923,61 @@ document.addEventListener("DOMContentLoaded", function () {
canvas.renderAll();
}
function setCanvasBackground(imgPath) {
console.log("setCanvasBackground chiamata con:", imgPath);
if (!canvas) {
console.error("Canvas non inizializzato");
return;
}
fabric.Image.fromURL(
imgPath,
(img) => {
const canvasWidth = canvas.getWidth();
const canvasHeight = canvas.getHeight();
const imgWidth = img.width;
const imgHeight = img.height;
if (!imgWidth || !imgHeight) {
alert(
"Impossibile leggere le dimensioni dell'immagine.",
);
return;
}
// Scale to cover: l'immagine copre tutto il canvas mantenendo il ratio
const scale = Math.max(
canvasWidth / imgWidth,
canvasHeight / imgHeight,
);
img.set({
originX: "left",
originY: "top",
scaleX: scale,
scaleY: scale,
left: (canvasWidth - imgWidth * scale) / 2,
top: (canvasHeight - imgHeight * scale) / 2,
selectable: false,
evented: false,
hasControls: false,
hasBorders: false,
excludeFromExport: false,
});
canvas.setBackgroundImage(img, () => {
canvas.requestRenderAll();
saveCanvasState();
updateLayersPanel();
updateButtons();
});
},
{ crossOrigin: "anonymous" },
);
}
const addToCanvasBtn = document.getElementById("addToCanvasBtn");
if (addToCanvasBtn) {
addToCanvasBtn.addEventListener("click", () => {
@@ -857,6 +1017,32 @@ document.addEventListener("DOMContentLoaded", function () {
});
}
const setBackgroundBtn = document.getElementById("setBackgroundBtn");
if (setBackgroundBtn) {
setBackgroundBtn.addEventListener("click", () => {
const checkboxes = document.querySelectorAll(
".photo-checkbox:checked",
);
if (checkboxes.length === 0) {
alert("Seleziona una foto da usare come sfondo!");
return;
}
if (checkboxes.length > 1) {
alert("Seleziona una sola foto per lo sfondo!");
return;
}
const imgPath = checkboxes[0].getAttribute("data-path");
setCanvasBackground(imgPath);
checkboxes.forEach((cb) => {
cb.checked = false;
});
});
}
const saveCollageBtn = document.getElementById("saveCollageBtn");
if (saveCollageBtn) {
saveCollageBtn.addEventListener("click", async () => {
+136 -17
View File
@@ -117,12 +117,24 @@ $code = $row[$field] ?? 'Non disponibile';
// Recupera le foto associate alla riga
try {
$stmt = $pdo->prepare("SELECT id, file_path, file_name, description, uploaded_at FROM {$photoTable} WHERE {$photoParamName} = ? ORDER BY uploaded_at DESC");
$stmt = $pdo->prepare("
SELECT
id,
file_path,
file_name,
uploaded_at,
description,
StampaNelRapporto,
PrimaPagina
FROM {$photoTable}
WHERE {$photoParamName} = ?
ORDER BY uploaded_at DESC
");
$stmt->execute([$paramValue]);
$photos = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (Exception $e) {
error_log("Errore query foto: " . $e->getMessage());
$photos = []; // Imposta array vuoto in caso di errore
$photos = [];
}
// Definisci il percorso base per le foto
@@ -134,7 +146,7 @@ $uploadUrl = $iddatadb
? $baseUrl . "/upload_photos_mobile.php?iddatadb=" . $iddatadb
: $baseUrl . "/upload_photos_mobile.php?idquotations=" . $idquotations;
// Genera il QR code con endroid/qr-code 6.0.6
// Genera il QR code con endroid/qr-code
$qrCodeDir = '../photostrf/qrcodes/';
if (!is_dir($qrCodeDir)) {
mkdir($qrCodeDir, 0755, true);
@@ -164,6 +176,7 @@ $result->saveToFile($qrCodeFile);
<p>Caricamento in corso...</p>
</div>
</div>
<h3>Manage Photos</h3>
<p><strong>ID:</strong> <?= htmlspecialchars($id) ?></p>
<p><strong>Code:</strong> <?= htmlspecialchars($code) ?></p>
@@ -182,6 +195,7 @@ $result->saveToFile($qrCodeFile);
<p>Drag the photo here or click to select</p>
<input type="file" id="photoInput" multiple accept="image/*" style="display: none;">
</div>
<!-- Area per la webcam -->
<div id="webcamArea" style="display: none; text-align: center; margin-bottom: 20px;">
<p>Webcam Preview</p>
@@ -194,7 +208,9 @@ $result->saveToFile($qrCodeFile);
<button id="closeWebcamBtn" style="padding: 10px 20px; background: #dc3545; color: white; border: none; cursor: pointer;">Close Webcam</button>
</div>
</div>
<button id="openWebcamBtn" style="padding: 10px 20px; background: #007bff; color: white; border: none; cursor: pointer; margin-bottom: 20px;">Take Photo with Webcam</button>
<!-- Elenco delle foto -->
<div id="photosList">
<?php if (empty($photos)): ?>
@@ -204,25 +220,57 @@ $result->saveToFile($qrCodeFile);
<?php
$filePath = $photoBasePath . $photo['file_path'];
$fileExists = file_exists($filePath);
$stampaNelRapporto = !empty($photo['StampaNelRapporto']) ? 1 : 0;
$primaPagina = !empty($photo['PrimaPagina']) ? 1 : 0;
?>
<div class="photo-item" data-photo-id="<?= $photo['id'] ?>" style="display: flex; align-items: center; margin-bottom: 10px; border-bottom: 1px solid #eee; padding-bottom: 10px;">
<div style="flex: 1;">
<div class="photo-item" data-photo-id="<?= (int)$photo['id'] ?>">
<div class="photo-thumb-area">
<?php if ($fileExists): ?>
<img src="../photostrf/<?= htmlspecialchars($photo['file_path']) ?>" alt="<?= htmlspecialchars($photo['file_name']) ?>" class="thumbnail" style="max-width: 100px; max-height: 100px; margin-right: 10px; cursor: pointer;">
<img src="../photostrf/<?= htmlspecialchars($photo['file_path']) ?>"
alt="<?= htmlspecialchars($photo['file_name']) ?>"
class="thumbnail"
style="max-width: 100px; max-height: 100px; cursor: pointer;">
<?php else: ?>
<p style="color: red;">[File non trovato]</p>
<div class="missing-file-box">[File non trovato]</div>
<?php endif; ?>
<p style="margin: 0;">
<strong>Nome:</strong> <?= htmlspecialchars($photo['file_name']) ?><br>
<strong>Caricata il:</strong> <?= htmlspecialchars($photo['uploaded_at']) ?><br>
<strong>Descrizione:</strong> <?= htmlspecialchars($photo['description'] ?? 'Nessuna descrizione') ?>
</p>
</div>
<button class="delete-photo-btn" data-photo-id="<?= $photo['id'] ?>" style="background: none; border: none; color: #dc3545; cursor: pointer;">
<i class="fas fa-trash"></i>
</button>
<div class="photo-info-area">
<p class="photo-name">
<strong>Name:</strong> <?= htmlspecialchars($photo['file_name']) ?>
</p>
<div class="photo-flags">
<label class="flag-label">
<input
type="checkbox"
class="photo-flag-checkbox"
data-photo-id="<?= (int)$photo['id'] ?>"
data-field="StampaNelRapporto"
<?= $stampaNelRapporto ? 'checked' : '' ?>>
Print in Report
</label>
<label class="flag-label">
<input
type="checkbox"
class="photo-flag-checkbox prima-pagina-checkbox"
data-photo-id="<?= (int)$photo['id'] ?>"
data-field="PrimaPagina"
<?= $primaPagina ? 'checked' : '' ?>>
First Page
</label>
</div>
</div>
<div class="photo-actions-area">
<button class="delete-photo-btn" data-photo-id="<?= (int)$photo['id'] ?>" type="button">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
<?php endforeach; ?>
<!-- Bottone Crea Collage -->
<button id="createCollageBtn" style="padding: 10px 20px; background: #ffc107; color: white; border: none; cursor: pointer; margin-top: 20px;">Crea Collage</button>
<?php endif; ?>
@@ -250,8 +298,11 @@ $result->saveToFile($qrCodeFile);
<?php endforeach; ?>
</div>
<!-- Bottone per aggiungere selezionate al canvas -->
<button id="addToCanvasBtn">Aggiungi Selezionate al Canvas</button>
<!-- Bottoni azioni collage -->
<div style="display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 10px;">
<button id="addToCanvasBtn">Aggiungi Selezionate al Canvas</button>
<button id="setBackgroundBtn" type="button">Imposta Selezionata come Sfondo</button>
</div>
<!-- Canvas per editing -->
<canvas id="collageCanvas" width="960" height="720" style="border: 1px solid #ccc; margin-top: 20px;"></canvas>
@@ -283,6 +334,12 @@ $result->saveToFile($qrCodeFile);
<style>
.photo-item {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 12px;
border-bottom: 1px solid #eee;
padding: 12px 0;
transition: background-color 0.3s;
}
@@ -290,6 +347,68 @@ $result->saveToFile($qrCodeFile);
background-color: #f8f9fa;
}
.photo-thumb-area {
flex: 0 0 110px;
text-align: center;
}
.photo-info-area {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.photo-actions-area {
flex: 0 0 40px;
display: flex;
justify-content: center;
align-items: center;
}
.photo-name {
margin: 0;
word-break: break-word;
}
.photo-flags {
display: flex;
flex-wrap: wrap;
gap: 18px;
margin-top: 4px;
}
.flag-label {
display: flex;
align-items: center;
gap: 6px;
margin: 0;
font-size: 14px;
cursor: pointer;
}
.flag-label input[type="checkbox"] {
transform: scale(1.1);
cursor: pointer;
}
.delete-photo-btn {
background: none;
border: none;
color: #dc3545;
cursor: pointer;
font-size: 18px;
}
.delete-photo-btn:hover {
color: #b02a37;
}
.missing-file-box {
color: red;
font-size: 13px;
}
#dropArea.highlight {
border-color: #28a745;
background-color: #e9ecef;
+12 -2
View File
@@ -21,6 +21,10 @@ try {
$idschema = intval($_POST['idschema'] ?? 0); // Nuovo campo
$schemaname = trim($_POST['schemaname'] ?? ''); // Corretto da schemamaname
$idroutine = isset($_POST['idroutine']) && $_POST['idroutine'] !== '' ? intval($_POST['idroutine']) : null; // Aggiunto idroutine
$button_size = trim($_POST['button_size'] ?? 'medium'); // Nuovo campo
$button_bg_color = trim($_POST['button_bg_color'] ?? '#007bff'); // Nuovo campo
$button_text_color = trim($_POST['button_text_color'] ?? '#ffffff'); // Nuovo campo
$button_label = trim($_POST['button_label'] ?? 'Click Me'); // Nuovo campo
// Controllo sui campi obbligatori
if (empty($id) || empty($name) || empty($header_row) || empty($start_column) || empty($target_table) || $idschema <= 0) {
@@ -36,10 +40,12 @@ try {
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// Aggiorna il database, includendo idschema, schemaname e idroutine
// Aggiorna il database, includendo i nuovi campi
$stmt = $pdo->prepare("UPDATE excel_templates
SET name = ?, header_row = ?, start_column = ?, description = ?, target_table = ?,
idclient = ?, clientname = ?, schemaname = ?, idschema = ?, idroutine = ?, updated_at = NOW()
idclient = ?, clientname = ?, schemaname = ?, idschema = ?, idroutine = ?,
button_size = ?, button_bg_color = ?, button_text_color = ?, button_label = ?,
updated_at = NOW()
WHERE id = ?");
$stmt->execute([
$name,
@@ -52,6 +58,10 @@ try {
$schemaname,
$idschema,
$idroutine,
$button_size,
$button_bg_color,
$button_text_color,
$button_label,
$id
]);
+62 -6
View File
@@ -11,6 +11,36 @@ session_start();
require_once '../../vendor/autoload.php';
require_once __DIR__ . '/class/db-functions.php';
function findHeaderRow(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet, array $expectedHeaders, int $startCol, int $highestColIndex, int $maxRowsToScan = 20): ?int
{
$normalizedExpected = array_filter(array_map(function ($h) {
return strtolower(trim(str_replace(['\\r\\n', '\r\n', "\r\n", "\n", "\r"], ' ', $h)));
}, $expectedHeaders));
$normalizedExpected = array_values($normalizedExpected);
sort($normalizedExpected);
for ($row = 1; $row <= $maxRowsToScan; $row++) {
$rowHeaders = [];
for ($col = $startCol; $col <= $highestColIndex; $col++) {
$colLetter = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($col);
$cell = $worksheet->getCell($colLetter . $row);
$val = $cell ? trim((string)$cell->getCalculatedValue()) : '';
if ($val !== '') {
$rowHeaders[] = strtolower(trim(str_replace(["\r\n", "\n", "\r"], ' ', $val)));
}
}
$normalizedRow = $rowHeaders;
sort($normalizedRow);
$matches = count(array_intersect($normalizedExpected, $normalizedRow));
$threshold = (int) ceil(count($normalizedExpected) * 0.6);
if ($matches >= $threshold) {
return $row;
}
}
return null;
}
$response = ['error' => '', 'rows' => [], 'columns' => [], 'template_id' => 0, 'filename' => '', 'apply_routine' => false];
try {
@@ -71,9 +101,35 @@ try {
$highestColumn = $worksheet->getHighestColumn();
$highestColumnIndex = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($highestColumn);
$startRow = max(1, $header_row);
$startColumn = max(1, $start_column);
// Recupera routine e headers dal template — DEVE essere prima dell'auto-detect
$stmt = $pdo->prepare("SELECT idroutine, idclient, xls_headers FROM excel_templates WHERE id = ?");
$stmt->execute([$template_id]);
$template = $stmt->fetch(PDO::FETCH_ASSOC);
error_log("=== DEBUG TEMPLATE ===");
error_log("template raw: " . print_r($template, true));
error_log("xls_headers value: " . var_export($template['xls_headers'] ?? 'KEY NON ESISTE', true));
error_log("xls_headers empty check: " . var_export(empty($template['xls_headers']), true));
// Auto-detect della riga header se xls_headers è disponibile
$detectedHeaderRow = $header_row;
if (!empty($template['xls_headers'])) {
$expectedHeaders = json_decode($template['xls_headers'], true);
if (is_array($expectedHeaders) && !empty($expectedHeaders)) {
error_log("Expected headers from DB: " . print_r($expectedHeaders, true));
$found = findHeaderRow($worksheet, $expectedHeaders, $startColumn, $highestColumnIndex);
error_log("findHeaderRow result: " . var_export($found, true));
if ($found !== null) {
$detectedHeaderRow = $found;
error_log("Header row auto-detected at row: $detectedHeaderRow (was: $header_row)");
} else {
error_log("Header row auto-detection failed, using provided header_row: $header_row");
}
}
}
$startRow = max(1, $detectedHeaderRow);
$header_row = $detectedHeaderRow;
// Debug dei parametri
error_log("Processing - template_id: $template_id, startRow: $startRow, startColumn: $startColumn, highestRow: $highestRow, highestColumn: $highestColumn, highestColumnIndex: $highestColumnIndex");
@@ -94,7 +150,7 @@ try {
}
// Estrai i dati a partire dalla riga successiva, includendo excelrow
for ($row = $startRow + 1; $row <= $highestRow; $row++) {
for ($row = $header_row + 1; $row <= $highestRow; $row++) {
$rowData = [];
for ($col = $startColumn; $col <= $highestColumnIndex; $col++) {
$columnLetter = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($col);
@@ -107,10 +163,7 @@ try {
}
}
// Recupera routine dal template
$stmt = $pdo->prepare("SELECT idroutine FROM excel_templates WHERE id = ?");
$stmt->execute([$template_id]);
$template = $stmt->fetch(PDO::FETCH_ASSOC);
if ($template && $template['idroutine']) {
$stmtRoutine = $pdo->prepare("SELECT idroutine, name, filename, headerrow, instruction FROM routine WHERE idroutine = ?");
@@ -133,6 +186,9 @@ try {
error_log("Nessuna routine associata al template {$template_id}");
}
// Aggiungi idclient alla risposta
$response['idclient'] = $template['idclient'] ?? null;
// Salva i dati in sessione
$_SESSION['excel_data'] = $excelData;
$_SESSION['template_id'] = $template_id;
+50 -12
View File
@@ -9,12 +9,13 @@ try {
throw new Exception("Invalid request method.");
}
// Recupera e sanifica i dati
$name = trim($_POST['name']);
$header_row = intval($_POST['header_row']);
$start_column = trim($_POST['start_column']);
// Retrieve and sanitize form data
$name = trim($_POST['name'] ?? '');
$source_type = strtoupper(trim($_POST['source_type'] ?? 'XLS'));
$header_row = isset($_POST['header_row']) && $_POST['header_row'] !== '' ? intval($_POST['header_row']) : null;
$start_column = trim($_POST['start_column'] ?? '');
$description = trim($_POST['description'] ?? '');
$target_table = trim($_POST['target_table']);
$target_table = trim($_POST['target_table'] ?? 'datadb');
$idclient = intval($_POST['client_id'] ?? 0);
$clientname = trim($_POST['client_name'] ?? '');
$idschema = intval($_POST['idschema'] ?? 0);
@@ -25,24 +26,61 @@ try {
$button_text_color = trim($_POST['button_text_color'] ?? '#ffffff');
$button_label = trim($_POST['button_label'] ?? 'Click Me');
// Controllo sui campi obbligatori
if (empty($name) || empty($header_row) || empty($start_column) || empty($target_table) || $idclient <= 0 || $idschema <= 0) {
// Normalize source type
if (!in_array($source_type, ['XLS', 'API'], true)) {
$source_type = 'XLS';
}
// Required fields validation
if ($name === '' || $target_table === '' || $idclient <= 0 || $idschema <= 0) {
throw new Exception("All fields marked with * are required, including client and schema.");
}
// Connessione al database
// XLS-only validation
if ($source_type === 'XLS') {
if ($header_row === null || $header_row <= 0 || $start_column === '') {
throw new Exception("Header Row and Start Column are required for XLS templates.");
}
}
// API templates do not require XLS coordinates
if ($source_type === 'API') {
$header_row = null;
$start_column = null;
}
// Database connection
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// Inserisci il nuovo template
// Insert the new template
$stmt = $pdo->prepare("
INSERT INTO excel_templates
(name, header_row, start_column, description, target_table, idclient, clientname, idschema, schemaname, idroutine,
button_size, button_bg_color, button_text_color, button_label, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
(
name,
source_type,
header_row,
start_column,
description,
target_table,
idclient,
clientname,
idschema,
schemaname,
idroutine,
button_size,
button_bg_color,
button_text_color,
button_label,
created_at,
updated_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
");
$stmt->execute([
$name,
$source_type,
$header_row,
$start_column,
$description,
+47 -9
View File
@@ -399,14 +399,18 @@ if (isset($_GET['edit_id'])) {
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
</div>
<?php include('modal_parts.php'); ?>
<?php include('photos_functions.php'); ?>
<div id="partsModalContainer"></div>
<div id="annotationsModalContainer"></div>
<?php include 'photos_functions.php'; ?>
<?php include('jsinclude.php'); ?>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<script src="photos.js"></script>
<script src="parts.js"></script>
<script src="annotationsModal.js"></script>
<script src="partsTable.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
// Mostra messaggi di stato se presenti
@@ -423,6 +427,45 @@ if (isset($_GET['edit_id'])) {
}, 5000);
}
$(document).on('click', '.parts-btn', function() {
const idquotations = $(this).data('idquotations');
$.ajax({
url: 'modal_partsTable.php',
method: 'GET',
data: {
idquotations: idquotations
},
success: function(response) {
$('#partsModalContainer').html(response);
const modalElement = document.getElementById('partsModal');
if (!modalElement) return;
$("#trfHeader").text(`Quotation #${idquotations}`);
$("#partsModal").data("idquotations", idquotations);
let modal = bootstrap.Modal.getInstance(modalElement) || new bootstrap.Modal(modalElement, {
backdrop: true
});
modal.show();
if (typeof window.loadParts === 'function') window.loadParts(null, idquotations);
},
error: function(xhr, status, error) {
alert('Errore nel caricamento del modale: ' + error);
}
});
});
$(document).on('hidden.bs.modal', '#partsModal', function() {
$('#partsModalContainer').empty();
$('.modal-backdrop').remove();
$('body').removeClass('modal-open').css('padding-right', '');
});
$(document).on('hidden.bs.modal', '#annotationsModal', function() {
$('#annotationsModalContainer').empty();
$('.modal-backdrop').remove();
$('body').removeClass('modal-open').css('padding-right', '');
});
// Inizializza DataTables se non siamo in modalità modifica
if (!document.querySelector('#editForm')) {
$('#quotationsTable').DataTable({
@@ -524,12 +567,7 @@ if (isset($_GET['edit_id'])) {
</script>
<!-- Modale per le foto in quotations.php -->
<div class="modal" id="photosModal">
<div class="modal-content">
<span class="close-btn">&times;</span>
<div class="popup-content"></div>
</div>
</div>
</body>
+170 -61
View File
@@ -5,102 +5,211 @@ ini_set('error_log', __DIR__ . '/routine_debug.log');
function applyRoutine(&$excelData, $routineData)
{
try {
// Log iniziale
// Initial log
error_log("Inizio esecuzione routine Moncler: " . date('Y-m-d H:i:s'));
error_log("Dati routine: " . print_r($routineData, true));
error_log("Dati excel_data: " . print_r($excelData, true));
// Verifica se excelData è vuoto
// Check if excelData is empty
if (empty($excelData)) {
throw new Exception("excelData è vuoto o non valido.");
}
// Estrai informazioni dalla routine con valori predefiniti
// Extract routine settings with default values
$action1 = trim($routineData['action1'] ?? 'K');
$action2 = trim($routineData['action2'] ?? 'STYLE CODE + STYLE DESCRIPTION');
$action3 = trim($routineData['action3'] ?? 'STYLE CODE');
$action4 = trim($routineData['action4'] ?? 'STYLE DESCRIPTION');
$headers = $routineData['xls_headers'] ?? [];
// Package-related headers
$package_header = 'PACKAGE';
$other_test_header = 'Other test from Control Matrix';
$only_colorfastness_header = 'Only Colorfastness package';
$only_chemical_header = 'Only Chemical package';
if (empty($headers)) {
throw new Exception("Nessun header trovato per la routine Moncler.");
}
// If headers arrive as JSON string, decode them
if (!is_array($headers) && is_string($headers)) {
$decoded_headers = json_decode($headers, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded_headers)) {
$headers = $decoded_headers;
}
}
if (!is_array($headers) || empty($headers)) {
throw new Exception("Gli header della routine non sono validi.");
}
error_log("Header ricevuti: " . print_r($headers, true));
// Normalizza gli header (solo trim)
$normalized_headers = array_map('trim', $headers);
// Normalize headers
$normalized_headers = array_map(function ($header) {
return trim((string)$header);
}, $headers);
error_log("Header normalizzati: " . print_r($normalized_headers, true));
error_log("Action values - action1: '$action1', action2: '$action2', action3: '$action3', action4: '$action4'");
// Trova gli indici delle colonne
$action1_index = array_search($action1, $normalized_headers);
$action2_index = array_search($action2, $normalized_headers);
$action3_index = array_search($action3, $normalized_headers);
$action4_index = array_search($action4, $normalized_headers);
// Find package-related column indexes
$package_index = array_search($package_header, $normalized_headers, true);
$other_test_index = array_search($other_test_header, $normalized_headers, true);
$only_colorfastness_index = array_search($only_colorfastness_header, $normalized_headers, true);
$only_chemical_index = array_search($only_chemical_header, $normalized_headers, true);
if ($package_index === false) {
throw new Exception("Colonna PACKAGE non trovata.");
}
error_log(
"Indici package - PACKAGE: " . var_export($package_index, true) .
", Other test from Control Matrix: " . var_export($other_test_index, true) .
", Only Colorfastness package: " . var_export($only_colorfastness_index, true) .
", Only Chemical package: " . var_export($only_chemical_index, true)
);
/**
* PART 1
* Aggregate rows by action1 and split action2 into action3 + action4
* If it fails, continue with PART 2 only
*/
$part1_applied = false;
$action1_index = array_search($action1, $normalized_headers, true);
$action2_index = array_search($action2, $normalized_headers, true);
$action3_index = array_search($action3, $normalized_headers, true);
$action4_index = array_search($action4, $normalized_headers, true);
if ($action1_index === false || $action2_index === false || $action3_index === false || $action4_index === false) {
throw new Exception("Colonne non trovate - action1: '$action1' (index: " . var_export($action1_index, true) . "), action2: '$action2' (index: " . var_export($action2_index, true) . "), action3: '$action3' (index: " . var_export($action3_index, true) . "), action4: '$action4' (index: " . var_export($action4_index, true) . ")");
error_log(
"Unable to apply routine part 1. Package merge logic has been applied only. " .
"Missing columns - action1: '$action1' (index: " . var_export($action1_index, true) . ")" .
", action2: '$action2' (index: " . var_export($action2_index, true) . ")" .
", action3: '$action3' (index: " . var_export($action3_index, true) . ")" .
", action4: '$action4' (index: " . var_export($action4_index, true) . ")"
);
} else {
error_log("Indici colonne part 1 - action1: $action1_index, action2: $action2_index, action3: $action3_index, action4: $action4_index");
$grouped_data = [];
foreach ($excelData as $row) {
if (!isset($row['data']) || !is_array($row['data'])) {
error_log("Riga non valida, manca 'data' per excelrow " . ($row['excelrow'] ?? 'N/A'));
continue;
}
$key = $row['data'][$action1_index] ?? '';
$key = trim((string)$key);
$key = $key === '' ? '_empty_' : $key;
if (!isset($grouped_data[$key])) {
$grouped_data[$key] = [
'data' => $row['data'],
'excelrow' => [($row['excelrow'] ?? '')],
'style_codes' => [],
'style_descriptions' => []
];
} else {
$grouped_data[$key]['excelrow'][] = ($row['excelrow'] ?? '');
}
// Split STYLE CODE + STYLE DESCRIPTION
$action2_value = trim((string)($row['data'][$action2_index] ?? ''));
if ($action2_value !== '') {
$parts = explode(' - ', $action2_value, 2);
$style_code = trim((string)($parts[0] ?? ''));
$style_description = trim((string)($parts[1] ?? ''));
if ($style_code !== '') {
$grouped_data[$key]['style_codes'][] = $style_code;
}
if ($style_description !== '') {
$grouped_data[$key]['style_descriptions'][] = $style_description;
}
} else {
error_log("Valore vuoto in action2 per excelrow " . ($row['excelrow'] ?? 'N/A'));
}
}
$new_excel_data = [];
foreach ($grouped_data as $key => $group) {
$row_data = $group['data'];
// Update STYLE CODE and STYLE DESCRIPTION with aggregated unique values
$row_data[$action3_index] = implode(' - ', array_unique($group['style_codes']));
$row_data[$action4_index] = implode(' - ', array_unique($group['style_descriptions']));
// Concatenate excelrow values with "+"
$excelrow_clean = array_filter($group['excelrow'], function ($value) {
return $value !== null && $value !== '';
});
$excelrow_value = count($excelrow_clean) > 1
? implode('+', $excelrow_clean)
: (reset($excelrow_clean) ?: '');
$new_excel_data[] = [
'data' => $row_data,
'excelrow' => $excelrow_value
];
}
$excelData = $new_excel_data;
$part1_applied = true;
error_log("Routine part 1 completata - Righe aggregate: " . count($new_excel_data));
error_log("Excelrow aggregati part 1: " . print_r(array_column($new_excel_data, 'excelrow'), true));
}
error_log("Indici colonne - action1: $action1_index, action2: $action2_index, action3: $action3_index, action4: $action4_index");
// Raggruppa le righe per il valore in action1 (colonna K)
$grouped_data = [];
foreach ($excelData as $row) {
/**
* PART 2
* Merge package-related columns into PACKAGE
* Always applied if PACKAGE exists
*/
foreach ($excelData as $index => $row) {
if (!isset($row['data']) || !is_array($row['data'])) {
error_log("Riga non valida, manca 'data' per excelrow {$row['excelrow']}");
error_log("Riga non valida nella part 2, manca 'data' all'indice $index");
continue;
}
$key = $row['data'][$action1_index] ?? '';
$key = empty($key) ? '_empty_' : $key;
if (!isset($grouped_data[$key])) {
$grouped_data[$key] = [
'data' => $row['data'],
'excelrow' => [$row['excelrow']],
'style_codes' => [],
'style_descriptions' => []
];
} else {
$grouped_data[$key]['excelrow'][] = $row['excelrow'];
$package_values = [];
$value_package = trim((string)($row['data'][$package_index] ?? ''));
$value_other_test = $other_test_index !== false ? trim((string)($row['data'][$other_test_index] ?? '')) : '';
$value_only_colorfastness = $only_colorfastness_index !== false ? trim((string)($row['data'][$only_colorfastness_index] ?? '')) : '';
$value_only_chemical = $only_chemical_index !== false ? trim((string)($row['data'][$only_chemical_index] ?? '')) : '';
if ($value_package !== '') {
$package_values[] = $value_package;
}
if ($value_other_test !== '') {
$package_values[] = $value_other_test;
}
if ($value_only_colorfastness !== '') {
$package_values[] = $value_only_colorfastness;
}
if ($value_only_chemical !== '') {
$package_values[] = $value_only_chemical;
}
// Separa il valore in action2 (STYLE CODE + STYLE DESCRIPTION)
$action2_value = $row['data'][$action2_index] ?? '';
if (!empty($action2_value)) {
$parts = explode(' - ', trim($action2_value));
$style_code = $parts[0] ?? '';
$style_description = $parts[1] ?? '';
if (!empty($style_code)) {
$grouped_data[$key]['style_codes'][] = $style_code;
}
if (!empty($style_description)) {
$grouped_data[$key]['style_descriptions'][] = $style_description;
}
} else {
error_log("Valore vuoto in action2 per excelrow {$row['excelrow']}");
}
$package_values = array_unique($package_values);
$excelData[$index]['data'][$package_index] = implode(' - ', $package_values);
}
// Crea il nuovo array excel_data aggregato
$new_excel_data = [];
foreach ($grouped_data as $key => $group) {
$row_data = $group['data'];
// Aggiorna action3 (STYLE CODE) e action4 (STYLE DESCRIPTION) con valori aggregati
$row_data[$action3_index] = implode(' - ', array_unique($group['style_codes']));
$row_data[$action4_index] = implode(' - ', array_unique($group['style_descriptions']));
// Concatena gli excelrow con '+' per le righe aggregate
$excelrow_value = count($group['excelrow']) > 1 ? implode('+', $group['excelrow']) : $group['excelrow'][0];
$new_excel_data[] = [
'data' => $row_data,
'excelrow' => $excelrow_value
];
error_log("Routine part 2 completata - Merge package applicato su " . count($excelData) . " righe");
if (!$part1_applied) {
error_log("Warning finale: routine part 1 non applicata, routine part 2 applicata correttamente");
}
// Modifica excelData in-place
$excelData = $new_excel_data;
error_log("Routine Moncler completata - Righe aggregate: " . count($new_excel_data));
error_log("Excelrow aggregati: " . print_r(array_column($new_excel_data, 'excelrow'), true));
error_log("Routine Moncler completata con successo");
} catch (Exception $e) {
error_log("Eccezione nella routine Moncler: " . $e->getMessage());
throw $e;
@@ -0,0 +1,212 @@
<?php
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/routine_debug.log');
function applyRoutine(&$excelData, $routineData)
{
try {
// Initial log
error_log("Inizio esecuzione nuova routine: " . date('Y-m-d H:i:s'));
error_log("Dati routine: " . print_r($routineData, true));
error_log("Dati excel_data: " . print_r($excelData, true));
// Check if excelData is empty
if (empty($excelData)) {
throw new Exception("excelData è vuoto o non valido.");
}
// Get headers from routine data
$headers = $routineData['xls_headers'] ?? [];
if (empty($headers)) {
throw new Exception("Nessun header trovato per la routine.");
}
// If headers arrive as JSON string, decode them
if (!is_array($headers) && is_string($headers)) {
$decoded_headers = json_decode($headers, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded_headers)) {
$headers = $decoded_headers;
}
}
if (!is_array($headers) || empty($headers)) {
throw new Exception("Gli header della routine non sono validi.");
}
error_log("Header ricevuti: " . print_r($headers, true));
// Normalize headers
$normalized_headers = array_map(function ($header) {
return trim((string)$header);
}, $headers);
error_log("Header normalizzati: " . print_r($normalized_headers, true));
/**
* Define source and target headers
*/
$parte_moncler_header = 'PARTE MONCLER';
$parte_fornitore_header = 'PARTE FORNITORE';
$descr_parte_moncler_header = 'DESCRIZIONE PARTE MONCLER';
$descr_parte_fornitore_header = 'DESCRIZIONE PARTE FORNITORE';
$colore_moncler_header = 'COLORE MONCLER';
$colore_fornitore_header = 'COLORE FORNITORE';
$composizione_tessile_header = 'COMPOSIZIONE TESSILE';
$composizione_totale_header = 'COMPOSIZIONE TOTALE';
$pacchetti_test_header = 'PACCHETTI TEST';
$test_addizionali_header = 'TEST ADDIZIONALI (A5 solo se richiesti dal cliente)';
/**
* Find indexes
*/
$parte_moncler_index = array_search($parte_moncler_header, $normalized_headers, true);
$parte_fornitore_index = array_search($parte_fornitore_header, $normalized_headers, true);
$descr_parte_moncler_index = array_search($descr_parte_moncler_header, $normalized_headers, true);
$descr_parte_fornitore_index = array_search($descr_parte_fornitore_header, $normalized_headers, true);
$colore_moncler_index = array_search($colore_moncler_header, $normalized_headers, true);
$colore_fornitore_index = array_search($colore_fornitore_header, $normalized_headers, true);
$composizione_tessile_index = array_search($composizione_tessile_header, $normalized_headers, true);
$composizione_totale_index = array_search($composizione_totale_header, $normalized_headers, true);
$pacchetti_test_index = array_search($pacchetti_test_header, $normalized_headers, true);
$test_addizionali_index = array_search($test_addizionali_header, $normalized_headers, true);
error_log(
"Indici trovati - " .
"PARTE MONCLER: " . var_export($parte_moncler_index, true) .
", PARTE FORNITORE: " . var_export($parte_fornitore_index, true) .
", DESCRIZIONE PARTE MONCLER: " . var_export($descr_parte_moncler_index, true) .
", DESCRIZIONE PARTE FORNITORE: " . var_export($descr_parte_fornitore_index, true) .
", COLORE MONCLER: " . var_export($colore_moncler_index, true) .
", COLORE FORNITORE: " . var_export($colore_fornitore_index, true) .
", COMPOSIZIONE TESSILE: " . var_export($composizione_tessile_index, true) .
", COMPOSIZIONE TOTALE: " . var_export($composizione_totale_index, true) .
", PACCHETTI TEST: " . var_export($pacchetti_test_index, true) .
", TEST ADDIZIONALI: " . var_export($test_addizionali_index, true)
);
// Required target columns must exist
if ($parte_moncler_index === false) {
throw new Exception("Colonna PARTE MONCLER non trovata.");
}
if ($descr_parte_moncler_index === false) {
throw new Exception("Colonna DESCRIZIONE PARTE MONCLER non trovata.");
}
if ($colore_moncler_index === false) {
throw new Exception("Colonna COLORE MONCLER non trovata.");
}
if ($composizione_tessile_index === false) {
throw new Exception("Colonna COMPOSIZIONE TESSILE non trovata.");
}
if ($pacchetti_test_index === false) {
throw new Exception("Colonna PACCHETTI TEST non trovata.");
}
/**
* Apply merge row by row
*/
foreach ($excelData as $index => $row) {
if (!isset($row['data']) || !is_array($row['data'])) {
error_log("Riga non valida, manca 'data' all'indice $index");
continue;
}
// 1. PARTE MONCLER + PARTE FORNITORE -> PARTE MONCLER
$parte_values = [];
$value_parte_moncler = trim((string)($row['data'][$parte_moncler_index] ?? ''));
$value_parte_fornitore = $parte_fornitore_index !== false ? trim((string)($row['data'][$parte_fornitore_index] ?? '')) : '';
if ($value_parte_moncler !== '') {
$parte_values[] = $value_parte_moncler;
}
if ($value_parte_fornitore !== '') {
$parte_values[] = $value_parte_fornitore;
}
$parte_values = array_unique($parte_values);
$excelData[$index]['data'][$parte_moncler_index] = implode(' - ', $parte_values);
// 2. DESCRIZIONE PARTE MONCLER + DESCRIZIONE PARTE FORNITORE -> DESCRIZIONE PARTE MONCLER
$descr_values = [];
$value_descr_moncler = trim((string)($row['data'][$descr_parte_moncler_index] ?? ''));
$value_descr_fornitore = $descr_parte_fornitore_index !== false ? trim((string)($row['data'][$descr_parte_fornitore_index] ?? '')) : '';
if ($value_descr_moncler !== '') {
$descr_values[] = $value_descr_moncler;
}
if ($value_descr_fornitore !== '') {
$descr_values[] = $value_descr_fornitore;
}
$descr_values = array_unique($descr_values);
$excelData[$index]['data'][$descr_parte_moncler_index] = implode(' - ', $descr_values);
// 3. COLORE MONCLER + COLORE FORNITORE -> COLORE MONCLER
$colore_values = [];
$value_colore_moncler = trim((string)($row['data'][$colore_moncler_index] ?? ''));
$value_colore_fornitore = $colore_fornitore_index !== false ? trim((string)($row['data'][$colore_fornitore_index] ?? '')) : '';
if ($value_colore_moncler !== '') {
$colore_values[] = $value_colore_moncler;
}
if ($value_colore_fornitore !== '') {
$colore_values[] = $value_colore_fornitore;
}
$colore_values = array_unique($colore_values);
$excelData[$index]['data'][$colore_moncler_index] = implode(' - ', $colore_values);
// 4. COMPOSIZIONE TESSILE + COMPOSIZIONE TOTALE -> COMPOSIZIONE TESSILE
$composizione_values = [];
$value_composizione_tessile = trim((string)($row['data'][$composizione_tessile_index] ?? ''));
$value_composizione_totale = $composizione_totale_index !== false ? trim((string)($row['data'][$composizione_totale_index] ?? '')) : '';
if ($value_composizione_tessile !== '') {
$composizione_values[] = $value_composizione_tessile;
}
if ($value_composizione_totale !== '') {
$composizione_values[] = $value_composizione_totale;
}
$composizione_values = array_unique($composizione_values);
$excelData[$index]['data'][$composizione_tessile_index] = implode(' - ', $composizione_values);
// 5. PACCHETTI TEST + TEST ADDIZIONALI -> PACCHETTI TEST
$pacchetti_values = [];
$value_pacchetti_test = trim((string)($row['data'][$pacchetti_test_index] ?? ''));
$value_test_addizionali = $test_addizionali_index !== false ? trim((string)($row['data'][$test_addizionali_index] ?? '')) : '';
if ($value_pacchetti_test !== '') {
$pacchetti_values[] = $value_pacchetti_test;
}
if ($value_test_addizionali !== '') {
$pacchetti_values[] = $value_test_addizionali;
}
$pacchetti_values = array_unique($pacchetti_values);
$excelData[$index]['data'][$pacchetti_test_index] = implode(' - ', $pacchetti_values);
}
error_log("Nuova routine completata con successo. Righe elaborate: " . count($excelData));
} catch (Exception $e) {
error_log("Eccezione nella nuova routine: " . $e->getMessage());
throw $e;
}
}
+152 -13
View File
@@ -11,13 +11,80 @@ try {
}
$iddatadb = intval($_POST['iddatadb']);
$db = DBHandlerSelect::getInstance();
$idclient = isset($_POST['idclient']) ? (is_numeric($_POST['idclient']) ? intval($_POST['idclient']) : null) : null;
$clienteFornitoreId = isset($_POST['cliente_fornitore_id']) ? (is_numeric($_POST['cliente_fornitore_id']) ? intval($_POST['cliente_fornitore_id']) : null) : null;
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// ---------------- FIXED FIELDS (template_fixed_mapping) ----------------
// ALIAS: fixed_field_key "logico" -> colonna reale su datadb
// (NON tocchiamo MySQL, gestiamo solo qui la traduzione)
$fixedAliasMap = [
'ClienteResponsabile' => 'cliente_responsabile_id',
'ClienteFornitore' => 'cliente_fornitore_id',
'ClienteAnalisi' => 'clienteAnalisi',
'MoltiplicatorePrezzo' => 'moltiplicatore_prezzo_id',
'AnagraficaCertestObject' => 'anagrafica_certest_object_id',
'AnagraficaCertestService' => 'anagrafica_certest_service_id',
'ConsegnaRichiesta' => 'consegna_richiesta',
];
// 1) Recupera templateid dalla riga datadb (serve per sapere quali fixed_field_key sono permessi)
$stmtTpl = $pdo->prepare("SELECT templateid FROM datadb WHERE iddatadb = ?");
$stmtTpl->execute([$iddatadb]);
$tplRow = $stmtTpl->fetch(PDO::FETCH_ASSOC);
$templateId = isset($tplRow['templateid']) ? (int)$tplRow['templateid'] : 0;
if ($templateId <= 0) {
throw new Exception("Template non trovato per iddatadb=$iddatadb");
}
// 2) Recupera elenco fixed fields visibili per quel template
$fxStmt = $pdo->prepare("
SELECT fixed_field_key, data_type, is_required, default_value
FROM template_fixed_mapping
WHERE template_id = ? AND is_visible_import = 1
");
$fxStmt->execute([$templateId]);
$fixedList = $fxStmt->fetchAll(PDO::FETCH_ASSOC);
// 3) Crea whitelist LOGICA: fixed_field_key => metadata
$fixedWhitelist = [];
foreach ($fixedList as $fx) {
$k = (string)$fx['fixed_field_key'];
// sicurezza: key ammessa solo se "safe"
if (!preg_match('/^[a-zA-Z0-9_]+$/', $k)) {
continue;
}
$fixedWhitelist[$k] = [
'data_type' => (string)$fx['data_type'], // INT | DATE
'is_required' => (int)$fx['is_required'],
'default_value' => $fx['default_value'] ?? null
];
}
// 3b) Crea whitelist REALE: colonna datadb reale => metadata
$realWhitelist = [];
foreach ($fixedWhitelist as $logicalKey => $meta) {
$realCol = $fixedAliasMap[$logicalKey] ?? $logicalKey;
// sicurezza: anche la colonna reale deve essere "safe"
if (!preg_match('/^[a-zA-Z0-9_]+$/', $realCol)) {
continue;
}
$realWhitelist[$realCol] = $meta;
}
// ---------------- DETAILS (import_data_details) ----------------
$data = $_POST;
$details = [];
// 1. POST-დან ამოვიღოთ მხოლოდ details
// 1. Estrarre i dettagli da POST
foreach ($data as $key => $value) {
if (preg_match('/^details(\d+)field_value$/', $key, $matches)) {
$id = $matches[1];
@@ -25,16 +92,15 @@ try {
}
}
// 2. DB-დან წამოვიღოთ არსებული მნიშვნელობები
// 2. Recupera i valori esistenti da import_data_details
$stmt = $pdo->prepare("SELECT mapping_id, field_value FROM import_data_details WHERE id = ?");
$stmt->execute([$iddatadb]);
$currentValues = [];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$currentValues[$row['mapping_id']] = $row['field_value'];
}
// 3. შევადაროთ POST-ს და DB-ს
// 3. Confronta i valori nuovi con quelli esistenti
$changed = [];
foreach ($details as $id => $newValue) {
$oldValue = $currentValues[$id] ?? null;
@@ -46,7 +112,7 @@ try {
}
}
// 4. თუ არის ცვლილებები → UPDATE
// 4. Aggiorna i dettagli se ci sono modifiche
if (!empty($changed)) {
$updateStmt = $pdo->prepare("
UPDATE import_data_details
@@ -56,20 +122,93 @@ try {
foreach ($changed as $mappingId => $values) {
$updateStmt->execute([
':newValue' => $values['new'],
':iddatadb' => $iddatadb,
':mappingId' => $mappingId
':newValue' => $values['new'],
':iddatadb' => $iddatadb,
':mappingId' => $mappingId
]);
}
}
$response['success'] = true;
$response['message'] = "Updated successfully";
$response['changed'] = $changed; // Debug / optional
// ---------------- UPDATE datadb: idclient + FIXED FIELDS ----------------
$setParts = [];
$params = [];
// 5a) idclient (se presente)
if (isset($idclient)) {
$setParts[] = "idclient = ?";
$params[] = $idclient;
}
// 5a2) cliente_fornitore_id (se presente)
if (isset($clienteFornitoreId)) {
$setParts[] = "cliente_fornitore_id = ?";
$params[] = $clienteFornitoreId;
}
// 5b) fixed fields dal POST
// QUI è il punto chiave: accettiamo SOLO colonne reali (realWhitelist),
// ma se dal JS arrivassero ancora key logiche, funzionerebbe uguale
// perché sotto controlliamo anche l'alias.
foreach ($realWhitelist as $realCol => $meta) {
$postKeyToRead = null;
// Caso 1: POST contiene già la colonna reale
if (array_key_exists($realCol, $_POST)) {
$postKeyToRead = $realCol;
} else {
// Caso 2: POST contiene la key logica -> troviamo quale logica mappa su questa colonna reale
// (fallback, utile se non hai ancora aggiornato il JS)
foreach ($fixedAliasMap as $logical => $mappedReal) {
if ($mappedReal === $realCol && array_key_exists($logical, $_POST)) {
$postKeyToRead = $logical;
break;
}
}
}
if ($postKeyToRead === null) {
continue; // non inviato
}
$val = $_POST[$postKeyToRead];
// Normalizzazione per tipo
if ($meta['data_type'] === 'DATE') {
$val = trim((string)$val);
$val = ($val === '') ? null : $val; // atteso formato Y-m-d
} else { // INT
$val = trim((string)$val);
$val = ($val === '') ? null : (int)$val;
}
$setParts[] = "`$realCol` = ?";
$params[] = $val;
}
// esegui update solo se c'è qualcosa da aggiornare
if (!empty($setParts)) {
$params[] = $iddatadb;
$sqlUpd = "UPDATE datadb SET " . implode(", ", $setParts) . " WHERE iddatadb = ?";
$updStmt = $pdo->prepare($sqlUpd);
$updStmt->execute($params);
}
// Messaggio risposta (mantengo la tua logica ma includo fixed)
if (!empty($setParts) && !empty($changed)) {
$response['message'] = "Updated details and datadb fields successfully";
} elseif (!empty($setParts)) {
$response['message'] = "Updated datadb fields successfully";
} elseif (!empty($changed)) {
$response['message'] = "Updated details successfully";
} else {
$response['success'] = true;
$response['message'] = "No changes found";
}
$response['success'] = true;
$response['changed'] = $changed; // Debug / optional
} catch (Exception $e) {
$response['success'] = false;
$response['message'] = $e->getMessage();
+67 -4
View File
@@ -20,19 +20,82 @@ $mappingId = $data['id'];
$mappingType = $data['mapping_type'] ?? '';
$excelColumn = $data['excel_column'] ?? null;
$manualDefault = $data['manual_default'] ?? null;
$autoValue = $data['auto_value'] ?? 'none';
$tablename = $data['tablename'] ?? '';
try {
$stmt = $pdo->prepare("UPDATE template_mapping SET is_manual = ?, excel_column = ?, manual_default = ? WHERE id = ?");
$isManual = ($mappingType === 'manual') ? 1 : 0;
$result = $stmt->execute([$isManual, $excelColumn, $manualDefault, $mappingId]);
// Normalize mapping type
$allowedTypes = ['', 'xls', 'manual', 'auto'];
if (!in_array($mappingType, $allowedTypes, true)) {
echo json_encode(["success" => false, "message" => "Invalid mapping_type"]);
exit;
}
// Normalize auto_value
$allowedAuto = ['none', 'import_date', 'import_time', 'export_date', 'export_time'];
if (!in_array($autoValue, $allowedAuto, true)) {
$autoValue = 'none';
}
// Decide what to persist based on mapping_type
$isManual = 0;
$excelToSave = null;
$manualToSave = null;
$autoToSave = 'none';
if ($mappingType === 'xls') {
$isManual = 0;
$excelToSave = $excelColumn ?: null;
$manualToSave = null;
$autoToSave = 'none';
} elseif ($mappingType === 'manual') {
$isManual = 1;
$excelToSave = null;
$manualToSave = $manualDefault;
$autoToSave = 'none';
} elseif ($mappingType === 'auto') {
$isManual = 0;
$excelToSave = null;
$manualToSave = null;
$autoToSave = $autoValue ?: 'none';
} else {
// reset
$isManual = 0;
$excelToSave = null;
$manualToSave = null;
$autoToSave = 'none';
}
$stmt = $pdo->prepare("
UPDATE template_mapping
SET
is_manual = ?,
excel_column = ?,
manual_default = ?,
auto_value = ?
WHERE id = ?
");
$result = $stmt->execute([$isManual, $excelToSave, $manualToSave, $autoToSave, $mappingId]);
if (!$result) {
echo json_encode(["success" => false, "message" => "Database update failed"]);
exit;
}
echo json_encode(["success" => true, "message" => "Mapping updated successfully", "data" => $data]); // Aggiunto debug
echo json_encode([
"success" => true,
"message" => "Mapping updated successfully",
"saved" => [
"id" => (int)$mappingId,
"mapping_type" => $mappingType,
"is_manual" => $isManual,
"excel_column" => $excelToSave,
"manual_default" => $manualToSave,
"auto_value" => $autoToSave
]
]);
} catch (Exception $e) {
echo json_encode(["success" => false, "message" => "Error: " . $e->getMessage()]);
}
+39
View File
@@ -0,0 +1,39 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
$data = json_decode(file_get_contents('php://input'), true);
$iddatadb = $data['iddatadb'] ?? null;
$parts = $data['parts'] ?? [];
if (!$iddatadb || empty($parts)) {
echo json_encode(['success' => false, 'message' => 'Dati mancanti']);
exit;
}
$part = $parts[0];
$partId = $part['id'] ?? null;
$idmatrice = $part['idmatrice'] ?? null;
if (!$partId) {
echo json_encode(['success' => false, 'message' => 'ID parte mancante']);
exit;
}
try {
$stmt = $pdo->prepare("UPDATE identification_parts
SET idmatrice = :idmatrice,
updated_at = NOW()
WHERE id = :id");
$stmt->execute([
':id' => $partId,
':idmatrice' => $idmatrice // Può essere NULL
]);
echo json_encode(['success' => true, 'message' => 'Matrice aggiornata con successo']);
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Errore nel salvataggio della matrice: ' . $e->getMessage()]);
}
+133 -26
View File
@@ -4,6 +4,7 @@ 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);
@@ -15,46 +16,152 @@ if (!$iddatadb || empty($parts)) {
exit;
}
$part = $parts[0];
$partId = $part['id'] ?? null; // part_id თუ არსებობს
$partNumber = $part['part_number'] ?? null;
$partDescription = $part['part_description'] ?? '';
$mix = $part['mix'] ?? 'N';
try {
$pdo->beginTransaction();
$results = [];
// Custom fields statements (child table)
$stmtUpsertCF = $pdo->prepare("
INSERT INTO identification_parts_customfields (part_id, field_id, value_id, value_text)
VALUES (:part_id, :field_id, :value_id, :value_text)
ON DUPLICATE KEY UPDATE
value_id = VALUES(value_id),
value_text = VALUES(value_text),
updated_at = NOW()
");
$stmtDeleteCF = $pdo->prepare("
DELETE FROM identification_parts_customfields
WHERE part_id = :part_id AND field_id = :field_id
");
foreach ($parts as $part) {
$partId = $part['id'] ?? null;
$partNumber = $part['part_number'] ?? null;
$partDescription = $part['part_description'] ?? '';
$mix = $part['mix'] ?? 'N';
$idmatrice = $part['idmatrice'] ?? null;
$note = $part['note'] ?? null;
$dateexpiry = $part['dateexpiry'] ?? null;
// Extra field (0/1)
$extraFieldId = $part['extra_field_id'] ?? null;
$extraValueId = $part['extra_value_id'] ?? null;
$extraValueText = $part['extra_value_text'] ?? null;
// Normalizza vuoti
if ($extraFieldId !== null && $extraFieldId !== '') $extraFieldId = (int)$extraFieldId;
else $extraFieldId = null;
if ($extraValueId !== null && $extraValueId !== '') $extraValueId = (int)$extraValueId;
else $extraValueId = null;
if ($extraValueText !== null) {
$extraValueText = trim((string)$extraValueText);
if ($extraValueText === '') $extraValueText = null;
}
if ($partDescription) {
try {
if ($partId) {
// UPDATE თუ უკვე არსებობს part
// UPDATE se la parte esiste (sempre)
$stmt = $pdo->prepare("UPDATE identification_parts
SET part_number = :part_number,
part_description = :part_description,
mix = :mix,
updated_at = NOW()
WHERE id = :id");
SET part_number = :part_number,
part_description = :part_description,
mix = :mix,
idmatrice = :idmatrice,
note = :note,
dateexpiry = :dateexpiry,
updated_at = NOW()
WHERE id = :id");
$stmt->execute([
':id' => $partId,
':part_number' => $partNumber,
':part_description' => $partDescription,
':mix' => $mix
':mix' => $mix,
':idmatrice' => $idmatrice,
':note' => $note,
':dateexpiry' => $dateexpiry,
]);
echo json_encode(['success' => true, 'part_id' => $partId, 'part_number'=>$partNumber, 'message' => 'Parte aggiornata con successo']);
} else {
// INSERT თუ ახალია
// Save extra custom field (if provided)
if ($extraFieldId !== null) {
if ($extraValueId === null && $extraValueText === null) {
$stmtDeleteCF->execute([
':part_id' => $partId,
':field_id' => $extraFieldId,
]);
} else {
$stmtUpsertCF->execute([
':part_id' => $partId,
':field_id' => $extraFieldId,
':value_id' => $extraValueId,
':value_text' => $extraValueText,
]);
}
}
$cf_row = null;
if ($extraFieldId !== null) {
$chk = $pdo->prepare("SELECT id, value_id, value_text FROM identification_parts_customfields WHERE part_id = ? AND field_id = ?");
$chk->execute([$partId, $extraFieldId]);
$cf_row = $chk->fetch(PDO::FETCH_ASSOC);
}
$results[] = [
'part_id' => $partId,
'part_number' => $partNumber,
'message' => 'Parte aggiornata con successo',
'cf_row' => $cf_row
];
} else if ($partDescription || $note || $dateexpiry) {
// INSERT per nuova parte (solo se ha contenuto)
$stmt = $pdo->prepare("INSERT INTO identification_parts
(iddatadb, part_number, part_description, mix, created_at, updated_at)
VALUES (:iddatadb, :part_number, :part_description, :mix, NOW(), NOW())");
(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())");
$stmt->execute([
':iddatadb' => $iddatadb,
':part_number' => $partNumber,
':part_description' => $partDescription,
':mix' => $mix
':mix' => $mix,
':idmatrice' => $idmatrice,
':note' => $note,
':dateexpiry' => $dateexpiry,
]);
$newId = $pdo->lastInsertId();
echo json_encode(['success' => true, 'part_id' => $newId, 'part_number'=>$partNumber, 'message' => 'Parte salvata con successo']);
$newId = (int)$pdo->lastInsertId();
if ($extraFieldId !== null) {
if ($extraValueId === null && $extraValueText === null) {
$stmtDeleteCF->execute([
':part_id' => $newId,
':field_id' => $extraFieldId,
]);
} else {
$stmtUpsertCF->execute([
':part_id' => $newId,
':field_id' => $extraFieldId,
':value_id' => $extraValueId,
':value_text' => $extraValueText,
]);
}
}
$results[] = [
'part_id' => $newId,
'part_number' => $partNumber,
'message' => 'Parte salvata con successo'
];
}
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Errore nel salvataggio: ' . $e->getMessage()]);
}
} else {
echo json_encode(['success' => false, 'message' => 'Descrizione mancante']);
$pdo->commit();
echo json_encode([
'success' => true,
'results' => $results,
'debug_last' => [
'extra_field_id' => $extraFieldId ?? null,
'extra_value_id' => $extraValueId ?? null,
'extra_value_text' => $extraValueText ?? null,
'part_id' => $partId ?? ($newId ?? null),
]
]);
} catch (PDOException $e) {
$pdo->rollBack();
echo json_encode(['success' => false, 'message' => 'Errore nel salvataggio: ' . $e->getMessage()]);
}
@@ -0,0 +1,48 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
$data = json_decode(file_get_contents('php://input'), true);
$iddatadb = $data['iddatadb'] ?? null;
$partIds = $data['partIds'] ?? [];
$photoList = $data['photoList'] ?? null;
if ($iddatadb) {
if (count($partIds) != 0) {
$idList = array_values(array_map('intval', $partIds));
$placeholders = [];
$parameters = [':iddatadb' => $iddatadb];
foreach ($idList as $index => $id) {
$paramName = ":id{$index}";
$placeholders[] = $paramName;
$parameters[$paramName] = $id;
}
$sql = 'UPDATE identification_parts SET iddatadb = :iddatadb WHERE id IN (' . implode(',', $placeholders) . ')';
$stmt = $pdo->prepare($sql);
$stmt->execute($parameters);
}
if (count($photoList) != 0) {
$placeholders = [];
$parameters = [':iddatadb' => $iddatadb];
foreach ($photoList as $index => $photo) {
$paramName = ":photo{$index}";
$placeholders[] = $paramName;
$parameters[$paramName] = $photo;
}
$stmt = $pdo->prepare('UPDATE datadb_photos SET iddatadb = :iddatadb WHERE file_path IN (' . implode(',', $placeholders) . ')');
$stmt->execute($parameters);
}
echo json_encode(['success' => true, 'message' => '']);
} else {
echo json_encode(['success' => false, 'message' => 'Dati mancanti']);
}

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