Compare commits

..

160 Commits

Author SHA1 Message Date
RMubarakzyanov d623ee797c Merge branch 'main' into features/jsonintegration 2026-06-11 15:33:30 +03:00
RMubarakzyanov 4dd7b89c22 added mandatory field checking 2026-06-11 13:41:31 +03:00
RMubarakzyanov dec42b4442 Merge branch 'main' into feature/analysis 2026-06-11 09:37:01 +03:00
solocla d088364a0d report search 2026-06-10 11:02:43 +02:00
solocla 6e43a178af added fixed field in json 2026-06-09 09:46:21 +02:00
solocla 25bd916221 import insert json 2026-06-08 12:45:37 +02:00
solocla dab8d9aebf fixed mapping for json 2026-06-08 08:47:17 +02:00
solocla 375a10a678 filter analysis web 2026-06-08 07:40:05 +02:00
solocla 15990be884 fxied column order LIMS 2026-06-04 16:48:22 +02:00
RMubarakzyanov c3a6dd73b6 import backoff 2026-05-28 23:59:15 +03:00
solocla 44ed1186e0 added richmento pelletteria routine 2026-05-28 13:50:52 +02:00
solocla 9050cb1006 routine burberry 2026-05-26 12:15:45 +02:00
solocla e6820fdb62 added order column 2026-05-25 10:59:58 +02:00
solocla 5da37a7836 paulshark routine 2026-05-22 12:27:53 +02:00
solocla c5f27cb69a routine fendi 2026-05-21 10:11:53 +02:00
solocla 1d81d6c996 fixed import update 2026-05-20 18:47:41 +02:00
solocla 0c72dbf5ae sort first with exactly phrase 2026-05-20 17:01:07 +02:00
solocla 8455be04e1 remove hidden from xls import to avoid big file dimension 2026-05-20 16:47:18 +02:00
solocla e42d1b9c51 fix limit dropdown 2026-05-19 16:20:55 +02:00
solocla 3e69e3c322 fixed routine valentino 2026-05-19 11:23:26 +02:00
solocla 0eb4f7a2ad valentino routine 2026-05-19 11:12:18 +02:00
solocla 4f2cfc1930 fix render gird for doppi apici 2026-05-18 15:38:43 +02:00
solocla df075dd76a main field show in imported 2026-05-14 10:14:59 +02:00
solocla 6460454201 fixed main field 2026-05-14 10:00:47 +02:00
solocla 574ddbbd32 fixed propagation 2026-05-13 15:12:13 +02:00
solocla 41f414db5c 2 main 2026-05-13 14:58:13 +02:00
solocla 4a863e8c16 fixed double scroll bar parts dropdown 2026-05-13 14:26:11 +02:00
solocla b431f1d4e9 added top scrollbar imported 2026-05-13 14:22:43 +02:00
solocla f97b52f158 skip empty parts during clone and LIMS export 2026-05-13 11:54:28 +02:00
solocla 836fc055ec cliente reponsabile change based on client 2026-05-12 15:48:35 +02:00
solocla e8dd585df4 added arrows to jump parts 2026-05-12 09:37:52 +02:00
solocla 198b8c08ad Revert "update scheme different obbligatorioweb"
This reverts commit a3eb0f0a57.
2026-05-11 14:47:42 +02:00
solocla 28c467d55e fixed order import 2026-05-11 14:24:35 +02:00
solocla 56eee99a67 change template and import o use also sheet number of XLS 2026-05-09 15:39:43 +02:00
solocla f514b3d2c7 added sheet and config API in insert edit template 2026-05-09 09:44:52 +02:00
solocla a3eb0f0a57 update scheme different obbligatorioweb 2026-05-09 09:30:59 +02:00
solocla aa355905d7 update schema 2026-05-08 11:10:21 +02:00
solocla b38f3e1240 Stop tracking generated schema response file 2026-05-06 16:52:53 +02:00
solocla cbd0c5b68a annotation color and clone templates 2026-05-06 16:44:42 +02:00
solocla f3e5cb4ffd fixed capitolato riferimento 2026-05-05 09:37:18 +02:00
solocla 0f0c6a04b7 fixed Mix 2026-05-05 09:07:14 +02:00
solocla 813bd66f96 scroll table parts 2026-05-04 12:10:12 +02:00
solocla ce00247d1c edit templtae lingue e colori 2026-05-04 11:21:30 +02:00
solocla 3a7dd266c8 fixed quotation 2026-04-29 12:16:44 +02:00
solocla ba8dc4c721 added get 2026-04-29 08:38:16 +02:00
RMubarakzyanov cfbbc36116 Fix TZ Issue 2026-04-28 12:53:21 +03:00
solocla 50d578eea1 commesssa on commessaweb 2026-04-27 16:02:50 +02:00
solocla 6b0d2aa9b9 fixed color and import dahboard 2026-04-27 14:34:45 +02:00
RMubarakzyanov 67bbd9bbbb export analyses 2026-04-21 00:09:59 +03:00
solocla fa7997c980 tested component fixed 2026-04-19 17:38:35 +02:00
solocla 66be442eb6 scrollbar import page 2026-04-17 15:00:17 +02:00
solocla 19a2d6e3f7 scrollbar also top 2026-04-17 10:47:41 +02:00
solocla a15ab08576 Merge branch 'main' of http://192.168.1.93:8418/solocla/trf_certest 2026-04-17 08:43:32 +02:00
solocla f71e8a56b5 cloneparts 2026-04-17 08:43:05 +02:00
RMubarakzyanov cb38bfb75a fix resize on tolims page 2026-04-16 11:39:00 +03:00
solocla 28a708dad3 removed limit admin 2026-04-15 16:19:39 +02:00
solocla 6b9cf20ab9 fixed resize and analysis 2026-04-15 14:37:49 +02:00
solocla d40fc7d177 update gitignore 2026-04-15 08:49:06 +02:00
RMubarakzyanov b812563023 Merge branch 'main' into feature/milestone3 2026-04-08 09:24:30 +03:00
solocla 39a821357e analysis complete 2026-04-03 11:15:33 +02:00
RMubarakzyanov 53c223ea5f merge 2026-04-02 17:01:22 +03:00
RMubarakzyanov 9775a12d4a Merge branch 'main' into feature/milestone3
# Conflicts:
#	public/userarea/import_insert.php
#	public/userarea/mapping_template_xls_scheme2.php
#	public/userarea/process_import_xls2.php
2026-04-02 12:29:17 +03:00
solocla 7dfb935e33 new mocnler routine (3 steps) 2026-04-02 09:49:09 +02:00
RMubarakzyanov abb4200215 Fix import 2026-03-31 17:15:43 +03:00
RMubarakzyanov 0be7109df4 Fix header finder 2026-03-31 16:33:08 +03:00
solocla 578671e013 added analysis by client and matrici 2026-03-31 14:08:51 +02:00
RMubarakzyanov d24836e2b1 matrici cache 2026-03-31 14:25:10 +03:00
RMubarakzyanov d983659000 transfer auto-detect header 2026-03-31 13:41:31 +03:00
RMubarakzyanov 7c5aa7734f hide form 2026-03-30 18:19:16 +03:00
RMubarakzyanov 4f0dbc7e91 cache and view improvements 2026-03-30 16:28:20 +03:00
RMubarakzyanov fb09f033ae removed htmlspecialchars on import && savings 2026-03-30 13:24:18 +03:00
solocla 8bae2d7008 analysis 2026-03-30 11:31:25 +02:00
RMubarakzyanov 7463fc6726 change links 2026-03-30 08:53:14 +03:00
RMubarakzyanov 0bc2ff7e9d fix autodetect 2026-03-29 21:47:23 +03:00
RMubarakzyanov aa1c32b7ed autodetect mapping 2026-03-29 21:07:16 +03:00
RMubarakzyanov c573a46318 tolims filter, pagination 2026-03-29 17:13:54 +03:00
solocla bf18a904bd update API template 2026-03-28 10:33:28 +01:00
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
RMubarakzyanov 3e66d67dc5 remove htmlspecialchars from saving 2026-03-27 20:55:54 +03: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
RMubarakzyanov 2a7b1fae17 imported && tolims 2026-03-26 16:05:17 +03: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
165 changed files with 71072 additions and 2306 deletions
+6 -17
View File
@@ -7,9 +7,9 @@ LOG_CHANNEL=stack
DB_CONNECTION=mysql DB_CONNECTION=mysql
DB_HOST="localhost" DB_HOST="localhost"
DB_DATABASE="trfcertest" DB_DATABASE="xxxx"
DB_USERNAME="solocla" DB_USERNAME="xxxx"
DB_PASSWORD="xxxxxxx" DB_PASSWORD="xxxxx"
DB_PREFIX="auth_" DB_PREFIX="auth_"
BROADCAST_DRIVER=log BROADCAST_DRIVER=log
@@ -41,19 +41,8 @@ MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
# Credenziali API VisualLims # Credenziali API VisualLims
SIMULATE_EXPORT_LIMS=true SIMULATE_EXPORT_LIMS=true
API_BASE_URL=https://93.43.5.102/limsapi API_BASE_URL=https://bvcpsitaly-elims.com/limsapi
API_USERNAME=xxxx API_USERNAME=WebApiUser
API_PASSWORD=XXXX API_PASSWORD=webapiuser01
BASE_URL=http://localhost:8000/userarea/ 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"
+38 -41
View File
@@ -1,66 +1,63 @@
.DS_Store .DS_Store
/node_modules /node_modules
/public/hot
/public/storage
/storage/*.key
/vendor /vendor
/.idea /.idea
/.fleet /.fleet
/.vscode /.vscode
/.vagrant /.vagrant
Homestead.json
Homestead.yaml /public/hot
npm-debug.log /public/storage
yarn-error.log
.env
.phpunit.result.cache
.php_cs.cache
/documentation
/.phpunit.cache
/public/build /public/build
/storage/*.key
.env
.env.backup .env.backup
.env.production .env.production
auth.json 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/*.json
/public/userarea/*.log /public/userarea/*.log
/public/userarea/*.txt /public/userarea/*.txt
/public/userarea/*_response.json
# File di log nella sottocartella class /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/logs/
/public/userarea/logs/**
/public/userarea/photostrf/
/public/userarea/class/*.log /public/userarea/class/*.log
/public/userarea/class/curl_auth_debug.log
/public/userarea/class/curl_request_debug.log
/public/userarea/schema_dettagli_response.json
public/userarea/schemi_base_response.json
# File XLSX temporanei importati # File XLSX temporanei importati
/public/userarea/imported_trf/*.xlsx /public/userarea/imported_trf/*.xlsx
/public/userarea/xlstemplates/*.xlsx /public/userarea/xlstemplates/*.xlsx
# Ignora cartelle di foto generate # Cartelle foto generate
/public/photostrf/ /public/photostrf/
/public/photostrf/qrcodes/ /public/photostrf/qrcodes/
public/userarea/import_debug.log
public/userarea/last_url.txt
public/userarea/class/curl_auth_debug.log
public/userarea/class/curl_request_debug.log
public/userarea/last_url.txt
public/userarea/class/curl_auth_debug.log
public/userarea/class/curl_request_debug.log
# Ignora tutti i log # Ignora tutti i log ovunque
*.log *.log
public/userarea/cache/
# 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
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;
+73
View File
@@ -0,0 +1,73 @@
<?php
include('include/headscript.php');
header('Content-Type: application/json');
try {
$input = json_decode(file_get_contents('php://input'), true);
$templateId = intval($input['template_id'] ?? 0);
$importRefFromClient = trim($input['importreferencecode'] ?? '');
if (!$templateId) {
throw new Exception('Template ID missing');
}
$userId = $iduserlogin ?? 1;
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// Get default idclient from template
$stmt = $pdo->prepare("SELECT idclient FROM excel_templates WHERE id = ?");
$stmt->execute([$templateId]);
$template = $stmt->fetch(PDO::FETCH_ASSOC);
$idclient = $template['idclient'] ?? null;
// Use provided importreferencecode (from filtered page) or generate new
$importReferenceCode = $importRefFromClient !== '' ? $importRefFromClient : date('YmdHis') . '-' . uniqid();
// Insert empty record
$stmt = $pdo->prepare("
INSERT INTO datadb (templateid, user_id, status, idclient, importreferencecode, importdate)
VALUES (?, ?, 'i', ?, ?, NOW())
");
$stmt->execute([$templateId, $userId, $idclient, $importReferenceCode]);
$iddatadb = (int)$pdo->lastInsertId();
// Create import_data_details for all mappings (with auto_value support)
$mappingStmt = $pdo->prepare("SELECT id, auto_value, manual_default, data_type FROM template_mapping WHERE template_id = ?");
$mappingStmt->execute([$templateId]);
$mappings = $mappingStmt->fetchAll(PDO::FETCH_ASSOC);
if (!empty($mappings)) {
$insertStmt = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, ?)");
foreach ($mappings as $m) {
$val = '';
$auto = $m['auto_value'] ?? 'none';
if ($auto === 'import_date') {
$val = date('Y-m-d');
} elseif ($auto === 'import_time') {
$val = date('H:i');
} elseif ($m['data_type'] === 'DATE' && ($m['manual_default'] ?? '') === 'today') {
$val = date('Y-m-d');
} elseif (!empty($m['manual_default'])) {
$val = $m['manual_default'];
}
$insertStmt->execute([$iddatadb, $m['id'], $val]);
}
}
// Get user name
$userStmt = $pdo->prepare("SELECT CONCAT(first_name, ' ', last_name) AS user_name FROM auth_users WHERE id = ?");
$userStmt->execute([$userId]);
$userName = $userStmt->fetchColumn() ?: '';
echo json_encode([
'success' => true,
'iddatadb' => $iddatadb,
'importreferencecode' => $importReferenceCode,
'user_name' => $userName,
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
}
+854
View File
@@ -0,0 +1,854 @@
(function () {
"use strict";
let analysisMatriciMap = {};
let analysisLoadedCache = {};
let analysisAssignedState = {};
let analysisSelectedState = {};
function loadAnalysisMatrixNames() {
return $.ajax({
url: "get_matrici_db.php",
method: "GET",
dataType: "json",
})
.done(function (data) {
analysisMatriciMap = {};
(data.value || []).forEach(function (matrice) {
analysisMatriciMap[String(matrice.IdMatrice)] =
matrice.NomeMatrice || "#" + matrice.IdMatrice;
});
applyAnalysisMatrixNames();
})
.fail(function () {
analysisMatriciMap = {};
applyAnalysisMatrixNames();
});
}
function applyAnalysisMatrixNames() {
const modal = document.getElementById("analysisModal");
if (!modal) return;
modal.querySelectorAll(".analysis-matrix-item").forEach((item) => {
const matrixId = item.getAttribute("data-matrix-id");
const nameEl = item.querySelector(".analysis-matrix-name");
let matrixName = "No Matrix";
if (matrixId && matrixId !== "NO_MATRIX") {
matrixName =
analysisMatriciMap[String(matrixId)] || "#" + matrixId;
}
item.setAttribute("data-matrix-name", matrixName);
if (nameEl) {
nameEl.textContent = matrixName;
}
});
modal.querySelectorAll(".analysis-part-matrix-name").forEach((el) => {
const matrixId = el.getAttribute("data-matrix-id");
if (!matrixId || matrixId === "NO_MATRIX") {
el.textContent = "No Matrix";
return;
}
el.textContent =
analysisMatriciMap[String(matrixId)] || "#" + matrixId;
});
}
function readInitialAssignedAnalyses() {
const jsonEl = document.getElementById("analysisAssignedInitialData");
if (!jsonEl) {
analysisAssignedState = {};
return;
}
try {
analysisAssignedState =
JSON.parse(jsonEl.textContent || "{}") || {};
} catch (e) {
analysisAssignedState = {};
}
}
function getSelectedPartIds() {
const modal = document.getElementById("analysisModal");
if (!modal) return [];
return Array.from(
modal.querySelectorAll(".analysis-part-checkbox:checked"),
).map((el) => String(el.value));
}
function getCurrentSelectedAnalysisRecordKeys() {
return Object.keys(analysisSelectedState).filter(function (key) {
return analysisSelectedState[key] === true;
});
}
function renderAssignedAnalysesForPart(partId) {
const container = document.getElementById(
"analysisAssignedListPart" + partId,
);
if (!container) return;
const items = Array.isArray(analysisAssignedState[String(partId)])
? analysisAssignedState[String(partId)]
: [];
if (!items.length) {
container.innerHTML = "";
return;
}
container.innerHTML = items
.map(function (item) {
const recordKey = item.analysis_recordkey || "";
const title = item.analysis_name || "Unnamed analysis";
const method = item.analysis_method || "";
return `
<div class="analysis-assigned-chip" data-recordkey="${escapeHtml(recordKey)}">
<div class="analysis-assigned-chip-text">
<div class="analysis-assigned-chip-title">${escapeHtml(title)}</div>
${method ? `<div class="analysis-assigned-chip-method">${escapeHtml(method)}</div>` : ""}
</div>
<button type="button"
class="analysis-remove-btn"
data-part-id="${escapeHtml(partId)}"
data-recordkey="${escapeHtml(recordKey)}"
title="Remove analysis">×</button>
</div>
`;
})
.join("");
}
function renderAssignedAnalysesForSelectedParts() {
const modal = document.getElementById("analysisModal");
if (!modal) return;
modal.querySelectorAll(".analysis-part-item").forEach(function (item) {
const partId = item.getAttribute("data-part-id");
renderAssignedAnalysesForPart(partId);
});
}
function syncSelectedAnalysisRows() {
const modal = document.getElementById("analysisModal");
if (!modal) return;
modal
.querySelectorAll(".analysis-analysis-item")
.forEach(function (item) {
const recordKey = item.getAttribute("data-recordkey") || "";
if (recordKey && analysisSelectedState[recordKey]) {
item.classList.add("analysis-selected");
} else {
item.classList.remove("analysis-selected");
}
});
}
function buildAnalysisPayloadFromRow(rowEl) {
return {
analysis_recordkey: rowEl.getAttribute("data-recordkey") || "",
analysis_name: rowEl.getAttribute("data-analysis-name") || "",
analysis_method: rowEl.getAttribute("data-analysis-method") || "",
analysis_level: rowEl.getAttribute("data-analysis-level") || "",
is_web_selectable: rowEl.getAttribute("data-web") === "1" ? 1 : 0,
is_accredited:
rowEl.getAttribute("data-accredited") === "1" ? 1 : 0,
};
}
function addAnalysisToLocalState(partId, payload, iddatadb, idmatrice) {
const key = String(partId);
if (!Array.isArray(analysisAssignedState[key])) {
analysisAssignedState[key] = [];
}
const exists = analysisAssignedState[key].some(function (item) {
return item.analysis_recordkey === payload.analysis_recordkey;
});
if (!exists) {
analysisAssignedState[key].push({
id: null,
part_id: parseInt(partId, 10),
iddatadb: iddatadb || null,
idmatrice: idmatrice || null,
analysis_recordkey: payload.analysis_recordkey,
analysis_name: payload.analysis_name,
analysis_method: payload.analysis_method,
analysis_level:
payload.analysis_level !== ""
? parseInt(payload.analysis_level, 10)
: null,
is_web_selectable: payload.is_web_selectable,
is_accredited: payload.is_accredited,
});
}
}
function removeAnalysisFromLocalState(partId, recordKey) {
const key = String(partId);
if (!Array.isArray(analysisAssignedState[key])) {
return;
}
analysisAssignedState[key] = analysisAssignedState[key].filter(
function (item) {
return item.analysis_recordkey !== recordKey;
},
);
}
function saveAnalysisAssociation(partId, payload, callback) {
const modal = document.getElementById("analysisModal");
if (!modal) return;
const iddatadb =
modal.querySelector("#analysisModalIddatadb")?.value || "";
const partItem = modal.querySelector(
'.analysis-part-item[data-part-id="' + partId + '"]',
);
const idmatrice = partItem
? partItem.getAttribute("data-matrix-id") || ""
: "";
$.ajax({
url: "save_part_analysis.php",
method: "POST",
dataType: "json",
data: {
part_id: partId,
iddatadb: iddatadb,
idmatrice: idmatrice !== "NO_MATRIX" ? idmatrice : "",
analysis_recordkey: payload.analysis_recordkey,
analysis_name: payload.analysis_name,
analysis_method: payload.analysis_method,
analysis_level: payload.analysis_level,
is_web_selectable: payload.is_web_selectable,
is_accredited: payload.is_accredited,
},
})
.done(function () {
addAnalysisToLocalState(
partId,
payload,
iddatadb,
idmatrice !== "NO_MATRIX" ? idmatrice : null,
);
renderAssignedAnalysesForPart(partId);
if (typeof callback === "function") callback(true);
})
.fail(function (xhr) {
let message = "Error saving analysis association";
if (xhr && xhr.responseJSON && xhr.responseJSON.message) {
message = xhr.responseJSON.message;
}
alert(message);
if (typeof callback === "function") callback(false);
});
}
function deleteAnalysisAssociation(partId, recordKey, callback) {
$.ajax({
url: "delete_part_analysis.php",
method: "POST",
dataType: "json",
data: {
part_id: partId,
analysis_recordkey: recordKey,
},
})
.done(function () {
removeAnalysisFromLocalState(partId, recordKey);
renderAssignedAnalysesForPart(partId);
if (typeof callback === "function") callback(true);
})
.fail(function (xhr) {
let message = "Error deleting analysis association";
if (xhr && xhr.responseJSON && xhr.responseJSON.message) {
message = xhr.responseJSON.message;
}
alert(message);
if (typeof callback === "function") callback(false);
});
}
function setAnalysisLoadingState(isLoading) {
const modal = document.getElementById("analysisModal");
if (!modal) return;
const loadingEl = modal.querySelector("#analysisLoadingBox");
if (!loadingEl) return;
if (isLoading) {
loadingEl.classList.remove("d-none");
} else {
loadingEl.classList.add("d-none");
}
}
function showAnalysisError(message) {
const modal = document.getElementById("analysisModal");
if (!modal) return;
const errorEl = modal.querySelector("#analysisErrorBox");
const emptyEl = modal.querySelector("#analysisEmptyBox");
const listEl = modal.querySelector("#analysisList");
if (listEl) listEl.innerHTML = "";
if (emptyEl) emptyEl.classList.add("d-none");
if (errorEl) {
errorEl.textContent = message || "Error loading analyses";
errorEl.classList.remove("d-none");
}
}
function showAnalysisEmpty(message) {
const modal = document.getElementById("analysisModal");
if (!modal) return;
const errorEl = modal.querySelector("#analysisErrorBox");
const emptyEl = modal.querySelector("#analysisEmptyBox");
const listEl = modal.querySelector("#analysisList");
if (listEl) listEl.innerHTML = "";
if (errorEl) errorEl.classList.add("d-none");
if (emptyEl) {
emptyEl.textContent =
message || "No analyses found for this matrix";
emptyEl.classList.remove("d-none");
}
}
function escapeHtml(value) {
return String(value ?? "")
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
function renderAnalysesList(analyses) {
const modal = document.getElementById("analysisModal");
if (!modal) return;
const errorEl = modal.querySelector("#analysisErrorBox");
const emptyEl = modal.querySelector("#analysisEmptyBox");
const listEl = modal.querySelector("#analysisList");
if (!listEl) return;
if (errorEl) errorEl.classList.add("d-none");
if (!Array.isArray(analyses) || analyses.length === 0) {
showAnalysisEmpty("No analyses found for this matrix");
return;
}
if (emptyEl) emptyEl.classList.add("d-none");
listEl.innerHTML = analyses
.map(function (item) {
const recordKey = item.RecordKey || "";
const analysisName =
item.NomeAnalisiTraduzione ||
item.NomeAnalisi ||
"Unnamed analysis";
const methodName = item.MetodoNome || "";
const selectable = item.SelezionabileSuWeb === true;
const accredited = item.Accreditato === true;
const level = item.Livello ?? "";
const searchText = (
analysisName +
" " +
methodName
).toLowerCase();
let badges = "";
if (selectable) {
badges += '<span class="badge bg-success">Web</span>';
} else {
badges += '<span class="badge bg-secondary">Not web</span>';
}
if (accredited) {
badges +=
'<span class="badge bg-info text-dark">Accredited</span>';
}
if (level !== "") {
badges +=
'<span class="badge bg-light text-dark border">Level ' +
escapeHtml(level) +
"</span>";
}
return `
<div
class="list-group-item analysis-analysis-item"
data-recordkey="${escapeHtml(recordKey)}"
data-analysis-name="${escapeHtml(analysisName)}"
data-analysis-method="${escapeHtml(methodName)}"
data-analysis-level="${escapeHtml(level)}"
data-web="${selectable ? "1" : "0"}"
data-accredited="${accredited ? "1" : "0"}"
data-search="${escapeHtml(searchText)}"
>
<div class="fw-semibold">${escapeHtml(analysisName)}</div>
${methodName ? `<div class="analysis-analysis-meta mt-1">${escapeHtml(methodName)}</div>` : ""}
<div class="analysis-analysis-badges mt-2">
${badges}
</div>
</div>
`;
})
.join("");
filterAnalysisList();
syncSelectedAnalysisRows();
}
function filterAnalysisList() {
const modal = document.getElementById("analysisModal");
if (!modal) return;
const webOnlyEl = modal.querySelector("#analysisWebOnly");
const searchEl = modal.querySelector("#analysisSearchInput");
const items = modal.querySelectorAll(".analysis-analysis-item");
const emptyEl = modal.querySelector("#analysisEmptyBox");
const errorEl = modal.querySelector("#analysisErrorBox");
const webOnly = true;
const searchValue = searchEl ? searchEl.value.trim().toLowerCase() : "";
let visibleCount = 0;
items.forEach((item) => {
const isWeb = item.getAttribute("data-web") === "1";
const searchText = (
item.getAttribute("data-search") || ""
).toLowerCase();
let visible = true;
if (webOnly && !isWeb) {
visible = false;
}
if (visible && searchValue && !searchText.includes(searchValue)) {
visible = false;
}
item.style.display = visible ? "" : "none";
if (visible) {
visibleCount++;
}
});
if (errorEl) {
errorEl.classList.add("d-none");
}
if (emptyEl) {
if (items.length === 0) {
emptyEl.textContent = "No analyses found for this matrix";
emptyEl.classList.remove("d-none");
} else if (visibleCount === 0) {
emptyEl.textContent = "No analyses match the current filters";
emptyEl.classList.remove("d-none");
} else {
emptyEl.classList.add("d-none");
}
}
}
function loadAnalysesByMatrix(matrixId) {
const modal = document.getElementById("analysisModal");
if (!modal) return;
if (!matrixId || matrixId === "NO_MATRIX") {
showAnalysisEmpty("No matrix selected");
return;
}
const listEl = modal.querySelector("#analysisList");
const errorEl = modal.querySelector("#analysisErrorBox");
const emptyEl = modal.querySelector("#analysisEmptyBox");
if (listEl) listEl.innerHTML = "";
if (errorEl) errorEl.classList.add("d-none");
if (emptyEl) {
emptyEl.textContent = "";
emptyEl.classList.add("d-none");
}
const cacheKey = String(matrixId) + "_WEB_ONLY";
if (analysisLoadedCache[cacheKey]) {
renderAnalysesList(analysisLoadedCache[cacheKey]);
return;
}
setAnalysisLoadingState(true);
$.ajax({
url: "get_analisi_matrice_filter.php",
method: "GET",
dataType: "json",
data: {
id_matrice: matrixId,
web_only: 1,
},
})
.done(function (response) {
const analyses = Array.isArray(response.value)
? response.value.filter(function (item) {
return (
item.SelezionabileSuWeb === true ||
item.SelezionabileSuWeb === 1 ||
item.SelezionabileSuWeb === "1"
);
})
: [];
analysisLoadedCache[cacheKey] = analyses;
renderAnalysesList(analyses);
})
.fail(function (xhr) {
let message = "Error loading analyses";
if (xhr && xhr.responseJSON && xhr.responseJSON.error) {
message = xhr.responseJSON.error;
}
showAnalysisError(message);
})
.always(function () {
setAnalysisLoadingState(false);
});
}
function updateSelectedPartsInfo() {
const modal = document.getElementById("analysisModal");
if (!modal) return;
const checked = modal.querySelectorAll(
".analysis-part-checkbox:checked",
);
const ids = Array.from(checked).map((el) => el.value);
const countEl = modal.querySelector("#analysisSelectedPartsCount");
const idsEl = modal.querySelector("#analysisSelectedPartsIds");
if (countEl) {
countEl.textContent = `${ids.length} selected`;
}
if (idsEl) {
idsEl.textContent = ids.length ? ids.join(", ") : "-";
}
modal.querySelectorAll(".analysis-part-item").forEach((item) => {
const checkbox = item.querySelector(".analysis-part-checkbox");
if (checkbox && checkbox.checked) {
item.classList.add("part-checked");
} else {
item.classList.remove("part-checked");
}
});
}
function selectPartsByMatrix(matrixId, matrixLabel) {
const modal = document.getElementById("analysisModal");
if (!modal) return;
const partItems = modal.querySelectorAll(".analysis-part-item");
const matrixLabelEl = modal.querySelector("#analysisCurrentMatrix");
partItems.forEach((item) => {
const itemMatrixId = item.getAttribute("data-matrix-id");
const checkbox = item.querySelector(".analysis-part-checkbox");
item.classList.remove("matrix-active");
if (itemMatrixId === String(matrixId)) {
item.classList.add("matrix-active");
if (checkbox) checkbox.checked = true;
} else {
if (checkbox) checkbox.checked = false;
}
});
if (matrixLabelEl) {
matrixLabelEl.textContent = matrixLabel || "-";
}
analysisSelectedState = {};
const selectedPartIds = getSelectedPartIds();
selectedPartIds.forEach(function (partId) {
const assigned = Array.isArray(
analysisAssignedState[String(partId)],
)
? analysisAssignedState[String(partId)]
: [];
assigned.forEach(function (item) {
if (item.analysis_recordkey) {
analysisSelectedState[item.analysis_recordkey] = true;
}
});
});
updateSelectedPartsInfo();
loadAnalysesByMatrix(matrixId);
}
function clearAnalysisSelection() {
const modal = document.getElementById("analysisModal");
if (!modal) return;
modal.querySelectorAll(".analysis-matrix-item").forEach((item) => {
item.classList.remove("active");
});
modal.querySelectorAll(".analysis-part-item").forEach((item) => {
item.classList.remove("matrix-active", "part-checked");
});
modal.querySelectorAll(".analysis-part-checkbox").forEach((cb) => {
cb.checked = false;
});
const matrixLabelEl = modal.querySelector("#analysisCurrentMatrix");
if (matrixLabelEl) matrixLabelEl.textContent = "-";
const webOnlyEl = modal.querySelector("#analysisWebOnly");
if (webOnlyEl) webOnlyEl.checked = false;
const searchEl = modal.querySelector("#analysisSearchInput");
if (searchEl) searchEl.value = "";
const listEl = modal.querySelector("#analysisList");
if (listEl) listEl.innerHTML = "";
showAnalysisEmpty("Select a matrix to load analyses");
updateSelectedPartsInfo();
}
function initAnalysisModal() {
const modal = document.getElementById("analysisModal");
if (!modal) return;
analysisLoadedCache = {};
analysisSelectedState = {};
readInitialAssignedAnalyses();
renderAssignedAnalysesForSelectedParts();
modal.querySelectorAll(".analysis-matrix-item").forEach((btn) => {
btn.addEventListener("click", function () {
modal
.querySelectorAll(".analysis-matrix-item")
.forEach((x) => x.classList.remove("active"));
this.classList.add("active");
const matrixId = this.getAttribute("data-matrix-id");
const matrixLabel =
this.getAttribute("data-matrix-name") ||
this.textContent.trim();
selectPartsByMatrix(matrixId, matrixLabel);
});
});
modal.querySelectorAll(".analysis-part-checkbox").forEach((cb) => {
cb.addEventListener("change", function () {
updateSelectedPartsInfo();
});
});
const clearBtn = modal.querySelector("#analysisClearSelectionBtn");
if (clearBtn) {
clearBtn.addEventListener("click", function () {
clearAnalysisSelection();
});
}
// WEB only is now fixed by default
const searchEl = modal.querySelector("#analysisSearchInput");
if (searchEl) {
searchEl.addEventListener("input", function () {
filterAnalysisList();
});
}
modal.addEventListener("click", function (e) {
const removeBtn = e.target.closest(".analysis-remove-btn");
if (removeBtn) {
e.preventDefault();
e.stopPropagation();
const partId = removeBtn.getAttribute("data-part-id");
const recordKey = removeBtn.getAttribute("data-recordkey");
deleteAnalysisAssociation(partId, recordKey, function () {
const stillUsed = Object.keys(analysisAssignedState).some(
function (pid) {
return (
Array.isArray(analysisAssignedState[pid]) &&
analysisAssignedState[pid].some(
function (item) {
return (
item.analysis_recordkey ===
recordKey
);
},
)
);
},
);
if (!stillUsed) {
delete analysisSelectedState[recordKey];
}
syncSelectedAnalysisRows();
});
return;
}
const row = e.target.closest(".analysis-analysis-item");
if (!row) return;
const selectedPartIds = getSelectedPartIds();
if (!selectedPartIds.length) {
alert("Select at least one part first");
return;
}
const payload = buildAnalysisPayloadFromRow(row);
if (!payload.analysis_recordkey) {
alert("Invalid analysis record key");
return;
}
const recordKey = payload.analysis_recordkey;
const alreadySelected = !!analysisSelectedState[recordKey];
if (alreadySelected) {
selectedPartIds.forEach(function (partId) {
deleteAnalysisAssociation(partId, recordKey);
});
delete analysisSelectedState[recordKey];
syncSelectedAnalysisRows();
return;
}
let pending = selectedPartIds.length;
let atLeastOneSaved = false;
selectedPartIds.forEach(function (partId) {
saveAnalysisAssociation(partId, payload, function (ok) {
if (ok) atLeastOneSaved = true;
pending--;
if (pending <= 0 && atLeastOneSaved) {
analysisSelectedState[recordKey] = true;
syncSelectedAnalysisRows();
}
});
});
});
const firstActiveMatrix = modal.querySelector(
".analysis-matrix-item.active",
);
if (firstActiveMatrix) {
const matrixId = firstActiveMatrix.getAttribute("data-matrix-id");
const matrixLabel =
firstActiveMatrix.getAttribute("data-matrix-name") ||
firstActiveMatrix.textContent.trim();
selectPartsByMatrix(matrixId, matrixLabel);
} else {
updateSelectedPartsInfo();
}
}
// OPEN ANALYSIS MODAL FROM PARTS MODAL BUTTON
$(document).on("click", ".open-analysis-modal-btn", function () {
const partsModal = document.getElementById("partsModal");
const iddatadb = $("#partsModal").data("iddatadb");
if (!iddatadb) {
console.error("iddatadb not found on #partsModal");
alert("iddatadb not found");
return;
}
$.ajax({
url: "modal_analysis.php",
method: "GET",
data: { iddatadb: iddatadb },
success: function (response) {
$("#analysisModalContainer").html(response);
const modalElement = document.getElementById("analysisModal");
if (!modalElement) {
console.error("Analysis modal not found: #analysisModal");
return;
}
let modal = bootstrap.Modal.getInstance(modalElement);
if (!modal) {
modal = new bootstrap.Modal(modalElement, {
backdrop: true,
keyboard: true,
focus: true,
});
}
loadAnalysisMatrixNames().always(function () {
initAnalysisModal();
modal.show();
});
},
error: function (xhr, status, error) {
console.error("Error loading analysis modal:", error);
alert("Error loading analysis modal: " + error);
},
});
});
// CLEANUP ON CLOSE
$(document).on("hidden.bs.modal", "#analysisModal", function () {
const modalElement = document.getElementById("analysisModal");
if (modalElement) {
const modal = bootstrap.Modal.getInstance(modalElement);
if (modal) modal.dispose();
}
$("#analysisModalContainer").empty();
// keep parts modal alive, but remove extra backdrop leftovers
$(".modal-backdrop").last().remove();
if ($("#partsModal").hasClass("show")) {
$("body").addClass("modal-open");
} else {
$("body").removeClass("modal-open").css("padding-right", "");
}
});
})();
+269 -132
View File
@@ -12,12 +12,18 @@ $(document).ready(function () {
let photoAnnotations = {}; let photoAnnotations = {};
let partColors = {}; let partColors = {};
let partSizes = {}; // memorizza la dimensione specifica per parte
let selectedPartNumber = null; let selectedPartNumber = null;
let unsavedChanges = false; let unsavedChanges = false;
let fabricCanvas = null; let fabricCanvas = null;
let descriptionTextbox = null; let descriptionTextbox = null;
let markerObjects = {}; let markerObjects = {};
let nextMarkerId = 1;
let partsListData = []; let partsListData = [];
// DIMENSIONE GLOBALE MARKER
let globalMarkerSize = 16;
// COLORE TESTO LISTA DESCRIZIONI
let globalDescriptionColor = "#000000";
// =================== // ===================
// MODAL INITIALIZATION // MODAL INITIALIZATION
@@ -29,6 +35,8 @@ $(document).ready(function () {
trfHeader, trfHeader,
}); });
$("#annotationsModal").attr("data-iddatadb", iddatadb);
if (!iddatadb && !idquotations) { if (!iddatadb && !idquotations) {
const errorMsg = $( const errorMsg = $(
'<div class="alert alert-danger temp-alert" role="alert">Errore: ID TRF mancante. Impossibile inizializzare il modale delle annotazioni.</div>', '<div class="alert alert-danger temp-alert" role="alert">Errore: ID TRF mancante. Impossibile inizializzare il modale delle annotazioni.</div>',
@@ -67,6 +75,9 @@ $(document).ready(function () {
}); });
} }
modal.show(); modal.show();
// Inizializza slider dimensione marker
$("#markerSizeSlider").val(globalMarkerSize);
$("#markerSizeValue").text(globalMarkerSize + "px");
// Debug: Verifica presenza elementi DOM // Debug: Verifica presenza elementi DOM
console.log( console.log(
@@ -128,6 +139,8 @@ $(document).ready(function () {
} }
descriptionTextbox = null; descriptionTextbox = null;
markerObjects = {}; markerObjects = {};
globalMarkerSize = 16;
globalDescriptionColor = "#000000";
$("#photoSelectorContainerAnnotations").empty().hide(); $("#photoSelectorContainerAnnotations").empty().hide();
$("#samplePhotoAnnotations").attr("src", ""); $("#samplePhotoAnnotations").attr("src", "");
$("#partsListAnnotations").empty(); $("#partsListAnnotations").empty();
@@ -144,6 +157,48 @@ $(document).ready(function () {
$(":focus").blur(); $(":focus").blur();
}); });
// SLIDER DIMENSIONE MARKER
$(document)
.off("input", "#markerSizeSlider")
.on("input", "#markerSizeSlider", function () {
globalMarkerSize = parseInt($(this).val());
$("#markerSizeValue").text(globalMarkerSize + "px");
partsListData.forEach(function (part) {
if (part && part.part_number != null) {
partSizes[part.part_number] = globalMarkerSize;
}
});
$("#partsListAnnotations .marker-size-slider").val(
globalMarkerSize,
);
$("#partsListAnnotations .marker-size-value").text(
globalMarkerSize + "px",
);
updateMarkers();
markUnsaved();
});
// ===================
// COLORE LISTA DESCRIZIONI
// ===================
$(document)
.off("input.descriptionColor", "#descriptionColorPickerAnnotations")
.on(
"input.descriptionColor",
"#descriptionColorPickerAnnotations",
function () {
globalDescriptionColor = $(this).val();
if (descriptionTextbox && fabricCanvas) {
descriptionTextbox.set("fill", globalDescriptionColor);
fabricCanvas.renderAll();
}
markUnsaved();
},
);
// =================== // ===================
// PHOTO LOADERS // PHOTO LOADERS
// =================== // ===================
@@ -347,22 +402,13 @@ $(document).ready(function () {
const partColor = const partColor =
partColors[selectedPartNumber] || "#ff0000"; partColors[selectedPartNumber] || "#ff0000";
const existingMarker = photoAnnotations[
currentPhoto
].markers.find((m) => m.partNumber == selectedPartNumber);
if (existingMarker) {
existingMarker.x = x;
existingMarker.y = y;
existingMarker.color = partColor;
} else {
photoAnnotations[currentPhoto].markers.push({ photoAnnotations[currentPhoto].markers.push({
id: nextMarkerId++,
partNumber: selectedPartNumber, partNumber: selectedPartNumber,
x, x,
y, y,
color: partColor, color: partColor,
}); });
}
console.log("Marker aggiunto/spostato:", { console.log("Marker aggiunto/spostato:", {
partNumber: selectedPartNumber, partNumber: selectedPartNumber,
@@ -372,8 +418,6 @@ $(document).ready(function () {
}); });
updateMarkers(); updateMarkers();
markUnsaved(); markUnsaved();
selectedPartNumber = null;
$("#partsListAnnotations li").removeClass("active");
}); });
fabricCanvas.upperCanvasEl.focus(); fabricCanvas.upperCanvasEl.focus();
@@ -465,113 +509,89 @@ $(document).ready(function () {
); );
// =================== // ===================
// BACK TO PARTS MODAL // TORNA AL MODALE PARTI (modal_partsTable.php)
// =================== // ===================
$(document) $(document)
.off("click.backToParts", "#backToPartsBtnAnnotations") .off("click.backToParts", "#backToPartsBtnAnnotations")
.on("click.backToParts", "#backToPartsBtnAnnotations", function (e) { .on("click.backToParts", "#backToPartsBtnAnnotations", function (e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
console.log(
"Evento click su #backToPartsBtnAnnotations, ID elemento:",
$(this).attr("id"),
);
if (!$("#backToPartsBtnAnnotations").length) {
console.error(
"Pulsante #backToPartsBtnAnnotations non trovato nel DOM.",
);
const errorMsg = $(
'<div class="alert alert-danger temp-alert" role="alert">Errore: Pulsante #backToPartsBtnAnnotations non trovato nel DOM.</div>',
);
$("#annotationsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
return;
}
// Controlla modifiche non salvate
if ( if (
unsavedChanges && unsavedChanges &&
!confirm( !confirm(
"Hai modifiche non salvate. Vuoi davvero tornare al modale delle parti?", "Hai modifiche non salvate. Vuoi davvero tornare al modale delle parti?",
) )
) { ) {
console.log(
"Tornare al modale delle parti annullato a causa di modifiche non salvate.",
);
return; return;
} }
// Chiudi annotationsModal
const annotationsModalElement =
document.getElementById("annotationsModal");
const annotationsModal = bootstrap.Modal.getInstance(
annotationsModalElement,
);
if (annotationsModal) {
annotationsModal.hide();
} else {
console.error(
"Impossibile trovare l'istanza del modale #annotationsModal.",
);
}
// Apri partsModal
const iddatadb = $("#annotationsModal").data("iddatadb"); const iddatadb = $("#annotationsModal").data("iddatadb");
const idquotations = $("#annotationsModal").data("idquotations"); const idquotations = $("#annotationsModal").data("idquotations");
const trfHeader = $("#trfHeaderAnnotations").text(); const trfHeader = $("#trfHeaderAnnotations").text();
console.log("Apertura partsModal con:", {
// CHIUDI MODALE ANNOTAZIONI IN MODO SICURO
const annotationsModalElement =
document.getElementById("annotationsModal");
if (annotationsModalElement) {
const modalInstance = bootstrap.Modal.getInstance(
annotationsModalElement,
);
if (modalInstance) {
modalInstance.hide();
} else {
$(annotationsModalElement).modal("hide"); // fallback jQuery
}
}
console.log("Torno a modal_partsTable.php con:", {
iddatadb, iddatadb,
idquotations, idquotations,
trfHeader, trfHeader,
}); });
// CARICA E APRI MODALE PARTI
$.get(
"modal_partsTable.php",
{
iddatadb: iddatadb || "",
idquotations: idquotations || "",
trfHeader: trfHeader,
},
function (data) {
// Rimuovi vecchio modale (con ID corretto)
$("#partsTableModal").remove();
$(".modal-backdrop").remove();
const partsModalElement = document.getElementById("partsModal"); // Aggiungi nuovo modale
if (!partsModalElement) { $("body").append(data);
console.error("Elemento #partsModal non trovato nel DOM.");
const errorMsg = $(
'<div class="alert alert-danger temp-alert" role="alert">Errore: Il modale delle parti non è presente nel DOM.</div>',
);
$("#annotationsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
return;
}
let partsModal = bootstrap.Modal.getInstance(partsModalElement); // Apri con Bootstrap 5
if (!partsModal) { const partsModalElement =
partsModal = new bootstrap.Modal(partsModalElement, { document.getElementById("partsTableModal");
if (partsModalElement) {
const modal = new bootstrap.Modal(partsModalElement, {
backdrop: true, backdrop: true,
keyboard: true, keyboard: true,
focus: true, focus: true,
}); });
} modal.show();
// Inizializza il modale delle parti
if (typeof window.initPartsModal === "function") {
window.initPartsModal(iddatadb, idquotations, trfHeader);
partsModal.show();
console.log("partsModal aperto con successo.");
} else { } else {
console.error("Funzione initPartsModal non definita."); let iddatadb =
const errorMsg = $( $("#annotationsModal").attr("data-iddatadb");
'<div class="alert alert-danger temp-alert" role="alert">Errore: Funzione initPartsModal non trovata.</div>',
);
$("#annotationsModal .modal-body").prepend(errorMsg);
setTimeout(function () {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
}
});
$(
"button.parts-btn[data-iddatadb='" +
iddatadb +
"']",
).trigger("click");
}
},
).fail(function (xhr) {
console.error("Errore caricamento modale parti:", xhr);
alert(
"Errore 404: modal_partsTable.php non trovato o errore server.",
);
});
});
// =================== // ===================
// PARTS LIST // PARTS LIST
// =================== // ===================
@@ -615,17 +635,11 @@ $(document).ready(function () {
partsListData.forEach((part) => { partsListData.forEach((part) => {
const partNumber = part.part_number; const partNumber = part.part_number;
const partDescription = part.part_description; const partDescription = part.part_description;
const isMixPart = String(part.mix || "N").toUpperCase() === "Y";
const partColor = const partColor =
partColors[partNumber] || partColors[partNumber] || (isMixPart ? "#0000ff" : "#ff0000");
(partDescription.toLowerCase().startsWith("mix") if (partNumber && partDescription && (showMixParts || !isMixPart)) {
? "#0000ff"
: "#ff0000");
if (
partNumber &&
partDescription &&
(showMixParts ||
!partDescription.toLowerCase().startsWith("mix"))
) {
const colorOptions = predefinedColors const colorOptions = predefinedColors
.map( .map(
(color) => (color) =>
@@ -634,11 +648,18 @@ $(document).ready(function () {
.join(""); .join("");
const listItem = ` const listItem = `
<li class="list-group-item" data-part-number="${partNumber}"> <li class="list-group-item" data-part-number="${partNumber}">
<span class="part-info" style="cursor: pointer;">${partNumber} - ${partDescription}</span> <span class="part-info" style="cursor: pointer;">${partNumber} - ${partDescription}</span>
<div class="color-picker-container" style="display: inline-block; position: relative; overflow: visible; z-index: 2000;"> <div class="color-picker-container" style="display:inline-block; position: relative; overflow: visible; z-index: 2000; margin-left:5px;">
<div class="color-option selected-color" style="background-color: ${partColor}; width: 20px; height: 20px; display: inline-block; margin-left: 5px; cursor: pointer; pointer-events: auto;"></div> <div class="color-option selected-color" style="background-color: ${partColor}; width: 20px; height: 20px; display: inline-block; cursor: pointer; pointer-events: auto;"></div>
<div class="color-picker" style="display: none; position: absolute; right: 0; z-index: 2000; background: #fff; border: 1px solid #ccc; padding: 5px;">${colorOptions}</div> <div class="color-picker" style="display: none; position: absolute; right: 0; z-index: 2000; background: #fff; border: 1px solid #ccc; padding: 5px;">${colorOptions}</div>
</div> </div>
<div style="display:inline-flex; align-items:center; gap:4px; margin-left:5px;">
<input type="range" class="marker-size-slider"
min="12" max="48" step="2"
value="${partSizes[partNumber] || globalMarkerSize}"
style="width:70px;">
<span class="marker-size-value" style="font-size:0.8rem;">${partSizes[partNumber] || globalMarkerSize}px</span>
</div>
</li>`; </li>`;
partsListElement.append(listItem); partsListElement.append(listItem);
} }
@@ -663,8 +684,21 @@ $(document).ready(function () {
); );
return; return;
} }
const $listItem = $(this); const $listItem = $(this);
selectedPartNumber = $listItem.data("part-number"); const partNumber = $listItem.data("part-number");
if (
selectedPartNumber == partNumber &&
$listItem.hasClass("active")
) {
selectedPartNumber = null;
$listItem.removeClass("active");
return;
}
selectedPartNumber = partNumber;
$listItem.addClass("active").siblings().removeClass("active"); $listItem.addClass("active").siblings().removeClass("active");
console.log( console.log(
"Parte selezionata tramite riga:", "Parte selezionata tramite riga:",
@@ -687,30 +721,88 @@ $(document).ready(function () {
$picker.toggle(); $picker.toggle();
}); });
// === Gestione cambio colore ===
partsListElement partsListElement
.off("click.colorOption") .off("click.colorOption")
.on("click.colorOption", ".color-option", function (e) { .on("click.colorOption", ".color-option", function (e) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
const $this = $(this); const $this = $(this);
const color = $this.data("color"); const color = $this.data("color");
const $listItem = $this.closest("li"); const $listItem = $this.closest("li");
const partNumber = $listItem.data("part-number"); const partNumber = $listItem.data("part-number");
console.log( console.log(
"Cliccato .color-option, colore:", "Cliccato .color-option, colore:",
color, color,
"per parte:", "per parte:",
partNumber, partNumber,
); );
// Salva il nuovo colore
partColors[partNumber] = color; partColors[partNumber] = color;
// Aggiorna il colore visivo nel selettore
$listItem $listItem
.find(".selected-color") .find(".selected-color")
.css("background-color", color); .css("background-color", color);
let currentPhoto = $("#samplePhotoAnnotations").attr("src");
let annotations = photoAnnotations[currentPhoto];
if (annotations) {
annotations.markers.forEach(function (m) {
if (m.partNumber == partNumber) {
m.color = color;
let group = markerObjects[m.id];
if (group) {
let circle = group.item(0);
circle.set("fill", color);
circle.set("stroke", color);
}
}
});
fabricCanvas.renderAll();
}
// Chiudi la palette e aggiorna canvas
$this.closest(".color-picker").hide(); $this.closest(".color-picker").hide();
updateMarkers(); updateMarkers();
markUnsaved(); markUnsaved();
}); });
// === Slider locale per dimensione marker ===
partsListElement
.off("input.localMarkerSize")
.on("input.localMarkerSize", ".marker-size-slider", function (e) {
e.stopPropagation();
e.preventDefault();
// Riferimenti alla parte e valore scelto
const $slider = $(this);
const partNumber = $slider.closest("li").data("part-number");
const newSize = parseInt($slider.val());
// Aggiorna il valore visivo accanto allo slider
$slider.siblings(".marker-size-value").text(newSize + "px");
// Memorizza la nuova dimensione per quella parte
partSizes[partNumber] = newSize;
// Aggiorna i marker sul canvas
updateMarkers();
// Segnala modifiche non salvate
markUnsaved();
console.log(
`Dimensione marker aggiornata per parte ${partNumber}: ${newSize}px`,
);
});
$(document) $(document)
.off("click.colorPicker") .off("click.colorPicker")
.on("click.colorPicker", function (e) { .on("click.colorPicker", function (e) {
@@ -721,6 +813,48 @@ $(document).ready(function () {
}); });
} }
function enableDragDropPartsList() {
const list = $("#partsListAnnotations");
if (!list.length || typeof $.ui === "undefined" || !$.ui.sortable) {
console.warn("jQuery UI o .sortable non disponibile. Ritento...");
setTimeout(enableDragDropPartsList, 100);
return;
}
if (list.hasClass("ui-sortable")) {
list.sortable("destroy"); // evita duplicati
}
list.sortable({
items: "li.list-group-item",
placeholder: "list-group-item placeholder",
axis: "y",
containment: "parent",
tolerance: "pointer",
start: function (e, ui) {
ui.item.addClass("dragging");
},
stop: function (e, ui) {
ui.item.removeClass("dragging");
const newOrder = [];
list.find("li").each(function () {
const partNumber = $(this).data("part-number");
const part = partsListData.find(
(p) => p.part_number == partNumber,
);
if (part) newOrder.push(part);
});
partsListData = newOrder;
console.log(
"Ordine parti aggiornato:",
partsListData.map((p) => p.part_number),
);
markUnsaved();
updateMarkers();
},
});
}
// Delegazione evento per il checkbox // Delegazione evento per il checkbox
$(document) $(document)
.off("change.showMix", "#showMixPartsAnnotations") .off("change.showMix", "#showMixPartsAnnotations")
@@ -753,6 +887,7 @@ $(document).ready(function () {
} }
updatePartsList(); updatePartsList();
updateMarkers(); updateMarkers();
setTimeout(enableDragDropPartsList, 50);
if ( if (
photoAnnotations[$("#samplePhotoAnnotations").attr("src")] photoAnnotations[$("#samplePhotoAnnotations").attr("src")]
?.hasDescriptions ?.hasDescriptions
@@ -802,11 +937,10 @@ $(document).ready(function () {
) { ) {
partsListData = response.parts; partsListData = response.parts;
response.parts.forEach((part) => { response.parts.forEach((part) => {
const defaultColor = part.part_description const isMixPart =
.toLowerCase() String(part.mix || "N").toUpperCase() === "Y";
.startsWith("mix") const defaultColor = isMixPart ? "#0000ff" : "#ff0000";
? "#0000ff"
: "#ff0000";
partColors[part.part_number] = defaultColor; partColors[part.part_number] = defaultColor;
}); });
updatePartsList(); updatePartsList();
@@ -860,9 +994,9 @@ $(document).ready(function () {
"updateMarkers chiamato, markerObjects:", "updateMarkers chiamato, markerObjects:",
Object.keys(markerObjects), Object.keys(markerObjects),
); );
for (let partNumber in markerObjects) { for (let markerId in markerObjects) {
fabricCanvas.remove(markerObjects[partNumber]); fabricCanvas.remove(markerObjects[markerId]);
delete markerObjects[partNumber]; delete markerObjects[markerId];
} }
markerObjects = {}; markerObjects = {};
@@ -883,17 +1017,18 @@ $(document).ready(function () {
(p) => p.part_number == marker.partNumber, (p) => p.part_number == marker.partNumber,
); );
const partDescription = part ? part.part_description : ""; const partDescription = part ? part.part_description : "";
if ( const isMixPart =
!showMixParts && part && String(part.mix || "N").toUpperCase() === "Y";
partDescription &&
partDescription.toLowerCase().startsWith("mix") if (!showMixParts && isMixPart) {
) {
console.log("Ignoro marker per parte Mix:", marker.partNumber); console.log("Ignoro marker per parte Mix:", marker.partNumber);
return; return;
} }
const radius = 12; const size = partSizes[marker.partNumber] || globalMarkerSize;
const fontSize = 16; const radius = size / 2;
const fontSize = Math.max(10, Math.round(size * 0.65));
const markerColor = const markerColor =
marker.color || partColors[marker.partNumber] || "#ff0000"; marker.color || partColors[marker.partNumber] || "#ff0000";
@@ -941,6 +1076,9 @@ $(document).ready(function () {
lockRotation: true, lockRotation: true,
}); });
group.markerId = marker.id;
group.partNumber = marker.partNumber;
group.on("moving", function () { group.on("moving", function () {
marker.x = this.left / photoData.scale; marker.x = this.left / photoData.scale;
marker.y = this.top / photoData.scale; marker.y = this.top / photoData.scale;
@@ -953,7 +1091,7 @@ $(document).ready(function () {
}); });
fabricCanvas.add(group); fabricCanvas.add(group);
markerObjects[marker.partNumber] = group; markerObjects[marker.id] = group;
}); });
fabricCanvas.renderAll(); fabricCanvas.renderAll();
@@ -990,11 +1128,10 @@ $(document).ready(function () {
} }
const partsList = partsListData const partsList = partsListData
.filter( .filter((part) => {
(part) => const isMixPart = String(part.mix || "N").toUpperCase() === "Y";
showMixParts || return showMixParts || !isMixPart;
!part.part_description.toLowerCase().startsWith("mix"), })
)
.map((part) => `${part.part_number} ${part.part_description}`); .map((part) => `${part.part_number} ${part.part_description}`);
const text = partsList.join("\n"); const text = partsList.join("\n");
@@ -1013,8 +1150,8 @@ $(document).ready(function () {
scaleY: 1, scaleY: 1,
backgroundColor: "transparent", backgroundColor: "transparent",
fontFamily: "Arial", fontFamily: "Arial",
fontSize: 24, fontSize: Math.max(16, Math.round(globalMarkerSize * 0.8)),
fill: "#000000", fill: globalDescriptionColor,
padding: 10, padding: 10,
editable: false, editable: false,
selectable: true, selectable: true,
@@ -1107,9 +1244,9 @@ $(document).ready(function () {
} }
} }
for (let partNumber in markerObjects) { for (let markerId in markerObjects) {
fabricCanvas.remove(markerObjects[partNumber]); fabricCanvas.remove(markerObjects[markerId]);
delete markerObjects[partNumber]; delete markerObjects[markerId];
} }
markerObjects = {}; markerObjects = {};
@@ -1144,9 +1281,9 @@ $(document).ready(function () {
photoAnnotations[currentPhoto].markers.length > 0 photoAnnotations[currentPhoto].markers.length > 0
) { ) {
const lastMarker = photoAnnotations[currentPhoto].markers.pop(); const lastMarker = photoAnnotations[currentPhoto].markers.pop();
if (markerObjects[lastMarker.partNumber]) { if (markerObjects[lastMarker.id]) {
fabricCanvas.remove(markerObjects[lastMarker.partNumber]); fabricCanvas.remove(markerObjects[lastMarker.id]);
delete markerObjects[lastMarker.partNumber]; delete markerObjects[lastMarker.id];
fabricCanvas.renderAll(); fabricCanvas.renderAll();
} }
console.log("Ultimo marker rimosso:", lastMarker); console.log("Ultimo marker rimosso:", lastMarker);
+3
View File
@@ -3,6 +3,9 @@ ob_start();
session_start(); session_start();
require_once '../../vendor/autoload.php'; require_once '../../vendor/autoload.php';
Dotenv\Dotenv::createImmutable(dirname(__DIR__, 2))->safeLoad();
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
$response = ['error' => '', 'rows' => [], 'columns' => [], 'template_id' => 0, 'filename' => '', 'excel_data' => []]; $response = ['error' => '', 'rows' => [], 'columns' => [], 'template_id' => 0, 'filename' => '', 'excel_data' => []];
try { try {
@@ -24,8 +24,18 @@ class VisualLimsApiClient
public static function getInstance() public static function getInstance()
{ {
if (self::$instance === null) { if (self::$instance === null) {
$dotenv = Dotenv::createImmutable(dirname(__DIR__, 3));
$dotenv->load();
$simulate = ($_ENV['SIMULATE_EXPORT_LIMS'] ?? '') === 'true';
if ($simulate) {
require_once __DIR__ . '/VisualLimsApiClientMock.class.php';
self::$instance = new VisualLimsApiClientMock();
} else {
self::$instance = new VisualLimsApiClient(); self::$instance = new VisualLimsApiClient();
} }
}
return self::$instance; return self::$instance;
} }
@@ -193,8 +203,112 @@ class VisualLimsApiClient
return json_decode($response, true); return json_decode($response, true);
} }
/**
* POST a file as multipart/form-data (used for photo/attachment uploads).
*
* @param string $endpoint OData endpoint, e.g. "Campione(613388)/UploadCampioneFile"
* @param string $filePath Absolute path to the file on disk
* @param string $fileName Original file name to send
* @param array $extraFields Additional form fields to include
* @return array|null Decoded JSON response
*/
public function postMultipart($endpoint, $filePath, $fileName, array $extraFields = [])
{
$token = $this->getToken();
$url = "{$this->baseUrl}/api/odata/{$endpoint}";
$cfile = new CURLFile($filePath, mime_content_type($filePath) ?: 'application/octet-stream', $fileName);
$payload = array_merge($extraFields, [
'file' => $cfile,
]);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer {$token}",
"Accept: application/json",
// Content-Type is set automatically to multipart/form-data by cURL
]);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curl_error = curl_error($ch);
curl_close($ch);
if ($response === false) {
throw new Exception("Errore nella richiesta POST multipart: {$curl_error}");
}
if ($http_code < 200 || $http_code >= 300) {
throw new Exception("POST multipart fallito: HTTP {$http_code}, Risposta: " . substr($response, 0, 1000));
}
return json_decode($response, true);
}
public function getBaseUrl() public function getBaseUrl()
{ {
return $this->baseUrl; return $this->baseUrl;
} }
/**
* Get raw/binary content from VisualLims API.
* Used for PDF downloads from MediaFile/DownloadStream.
*/
public function getRaw($endpoint)
{
$token = $this->getToken();
/*
* Normal JSON OData calls use:
* {$this->baseUrl}/api/odata/...
*
* Media file downloads use:
* {$this->baseUrl}/api/MediaFile/DownloadStream...
*/
$url = rtrim($this->baseUrl, '/') . '/api/' . ltrim($endpoint, '/');
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer {$token}",
"Accept: application/pdf,*/*"
],
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 60
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
$curlError = curl_error($ch);
curl_close($ch);
if ($response === false) {
throw new Exception("Errore cURL download raw: " . $curlError);
}
if ($httpCode < 200 || $httpCode >= 300) {
throw new Exception(
"Errore HTTP {$httpCode} durante download raw. Content-Type: {$contentType}. Response: " .
substr($response, 0, 500)
);
}
if (empty($response)) {
throw new Exception("Risposta vuota dal download raw.");
}
return $response;
}
} }
@@ -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];
}
}
+3
View File
@@ -3,6 +3,9 @@ require_once dirname(__DIR__, 3) . '/vendor/autoload.php';
use Dotenv\Dotenv; use Dotenv\Dotenv;
Dotenv::createImmutable(dirname(__DIR__, 3))->safeLoad();
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
class DBHandlerSelect class DBHandlerSelect
{ {
private static $instance = null; private static $instance = null;
+133
View File
@@ -0,0 +1,133 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$data = json_decode(file_get_contents('php://input'), true);
$sourceIddatadb = isset($data['source_iddatadb']) ? (int)$data['source_iddatadb'] : 0;
$targetList = $data['target_iddatadb_list'] ?? [];
$targetIds = array_values(array_unique(array_filter(array_map('intval', (array)$targetList), function ($v) use ($sourceIddatadb) {
return $v > 0 && $v !== $sourceIddatadb;
})));
if ($sourceIddatadb <= 0 || empty($targetIds)) {
echo json_encode([
'success' => false,
'message' => 'Missing source or target records'
]);
exit;
}
try {
$pdo->beginTransaction();
// 1. Load source parts
$stmtParts = $pdo->prepare("
SELECT id, part_number, part_description, mix, idmatrice, note, dateexpiry
FROM identification_parts
WHERE iddatadb = ?
AND part_description IS NOT NULL
AND TRIM(part_description) <> ''
ORDER BY part_number ASC, id ASC
");
$stmtParts->execute([$sourceIddatadb]);
$sourceParts = $stmtParts->fetchAll(PDO::FETCH_ASSOC);
if (empty($sourceParts)) {
$pdo->rollBack();
echo json_encode([
'success' => false,
'message' => 'No parts found for source record'
]);
exit;
}
// 2. Prepare statements
$stmtInsertPart = $pdo->prepare("
INSERT INTO identification_parts
(iddatadb, part_number, part_description, mix, idmatrice, note, dateexpiry, created_at, updated_at)
VALUES
(:iddatadb, :part_number, :part_description, :mix, :idmatrice, :note, :dateexpiry, NOW(), NOW())
");
$stmtLoadCF = $pdo->prepare("
SELECT field_id, value_id, value_text
FROM identification_parts_customfields
WHERE part_id = ?
ORDER BY id ASC
");
$stmtInsertCF = $pdo->prepare("
INSERT INTO identification_parts_customfields
(part_id, field_id, value_id, value_text, created_at, updated_at)
VALUES
(:part_id, :field_id, :value_id, :value_text, NOW(), NOW())
");
$details = [];
$totalClonedParts = 0;
// 3. Clone source parts to each target record
foreach ($targetIds as $targetIddatadb) {
$clonedCountForTarget = 0;
foreach ($sourceParts as $part) {
$stmtInsertPart->execute([
':iddatadb' => $targetIddatadb,
':part_number' => $part['part_number'],
':part_description' => $part['part_description'],
':mix' => $part['mix'] ?? 'N',
':idmatrice' => $part['idmatrice'] !== '' ? $part['idmatrice'] : null,
':note' => $part['note'] ?? null,
':dateexpiry' => $part['dateexpiry'] ?? null,
]);
$newPartId = (int)$pdo->lastInsertId();
// Load source custom fields for this part
$stmtLoadCF->execute([(int)$part['id']]);
$customFields = $stmtLoadCF->fetchAll(PDO::FETCH_ASSOC);
foreach ($customFields as $cf) {
$stmtInsertCF->execute([
':part_id' => $newPartId,
':field_id' => (int)$cf['field_id'],
':value_id' => ($cf['value_id'] !== null && $cf['value_id'] !== '') ? (int)$cf['value_id'] : null,
':value_text' => $cf['value_text'] !== '' ? $cf['value_text'] : null,
]);
}
$clonedCountForTarget++;
$totalClonedParts++;
}
$details[] = [
'target_iddatadb' => $targetIddatadb,
'cloned_parts' => $clonedCountForTarget
];
}
$pdo->commit();
echo json_encode([
'success' => true,
'source_iddatadb' => $sourceIddatadb,
'cloned_targets' => count($targetIds),
'total_cloned_parts' => $totalClonedParts,
'details' => $details
]);
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
echo json_encode([
'success' => false,
'message' => 'Clone failed: ' . $e->getMessage()
]);
}
+228
View File
@@ -0,0 +1,228 @@
<?php
require_once 'class/db-functions.php';
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
die("Invalid template ID");
}
$sourceTemplateId = (int)$_GET['id'];
try {
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$pdo->beginTransaction();
// 1. Get source template
$stmt = $pdo->prepare("
SELECT
name,
source_type,
header_row,
start_column,
description,
target_table,
sample_xlsx,
button_size,
button_bg_color,
button_text_color,
button_label,
status,
client_specific_fields,
idclient,
clientname,
schemaname,
idschema,
schemajson,
xls_headers,
api_sample_json,
json_nodes,
idroutine
FROM excel_templates
WHERE id = ?
LIMIT 1
");
$stmt->execute([$sourceTemplateId]);
$template = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$template) {
throw new Exception("Template not found.");
}
// 2. Generate unique clone name
$baseName = 'Copia di ' . $template['name'];
$newName = $baseName;
$checkStmt = $pdo->prepare("SELECT COUNT(*) FROM excel_templates WHERE name = ?");
$checkStmt->execute([$newName]);
if ((int)$checkStmt->fetchColumn() > 0) {
$counter = 2;
do {
$newName = $baseName . ' (' . $counter . ')';
$checkStmt->execute([$newName]);
$exists = (int)$checkStmt->fetchColumn() > 0;
$counter++;
} while ($exists);
}
// 3. Insert cloned template
$insertTemplate = $pdo->prepare("
INSERT INTO excel_templates (
name,
source_type,
created_at,
updated_at,
header_row,
start_column,
description,
target_table,
sample_xlsx,
button_size,
button_bg_color,
button_text_color,
button_label,
status,
client_specific_fields,
idclient,
clientname,
schemaname,
idschema,
schemajson,
xls_headers,
api_sample_json,
json_nodes,
idroutine
) VALUES (
:name,
:source_type,
NOW(),
NOW(),
:header_row,
:start_column,
:description,
:target_table,
:sample_xlsx,
:button_size,
:button_bg_color,
:button_text_color,
:button_label,
:status,
:client_specific_fields,
:idclient,
:clientname,
:schemaname,
:idschema,
:schemajson,
:xls_headers,
:api_sample_json,
:json_nodes,
:idroutine
)
");
$insertTemplate->execute([
':name' => $newName,
':source_type' => $template['source_type'],
':header_row' => $template['header_row'],
':start_column' => $template['start_column'],
':description' => $template['description'],
':target_table' => $template['target_table'],
':sample_xlsx' => $template['sample_xlsx'],
':button_size' => $template['button_size'],
':button_bg_color' => $template['button_bg_color'],
':button_text_color' => $template['button_text_color'],
':button_label' => $template['button_label'],
':status' => $template['status'],
':client_specific_fields' => $template['client_specific_fields'],
':idclient' => $template['idclient'],
':clientname' => $template['clientname'],
':schemaname' => $template['schemaname'],
':idschema' => $template['idschema'],
':schemajson' => $template['schemajson'],
':xls_headers' => $template['xls_headers'],
':api_sample_json' => $template['api_sample_json'],
':json_nodes' => $template['json_nodes'],
':idroutine' => $template['idroutine']
]);
$newTemplateId = (int)$pdo->lastInsertId();
if ($newTemplateId <= 0) {
throw new Exception("Unable to create cloned template.");
}
// 4. Clone template_mapping rows
$cloneMappings = $pdo->prepare("
INSERT INTO template_mapping (
template_id,
schema_id,
field_id,
excel_column,
json_node,
is_manual,
manual_default,
auto_value,
data_type,
is_required,
default_value,
has_list,
length,
decimals,
min_value,
max_value,
default_curr_date,
tablename,
field_label,
main_field,
is_visible_import,
is_visible_parts
)
SELECT
:new_template_id,
schema_id,
field_id,
excel_column,
json_node,
is_manual,
manual_default,
auto_value,
data_type,
is_required,
default_value,
has_list,
length,
decimals,
min_value,
max_value,
default_curr_date,
tablename,
field_label,
main_field,
is_visible_import,
is_visible_parts
FROM template_mapping
WHERE template_id = :source_template_id
");
$cloneMappings->execute([
':new_template_id' => $newTemplateId,
':source_template_id' => $sourceTemplateId
]);
$pdo->commit();
header("Location: templates_dashboard.php?cloned=1&new_id=" . $newTemplateId);
exit;
} catch (Exception $e) {
if (isset($pdo) && $pdo->inTransaction()) {
$pdo->rollBack();
}
die("Clone error: " . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8'));
}
+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()]);
}
File diff suppressed because it is too large Load Diff
@@ -17,6 +17,8 @@ try {
exit(1); exit(1);
} }
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
// Recupera le variabili d'ambiente // Recupera le variabili d'ambiente
$dbHost = $_ENV['DB_HOST']; $dbHost = $_ENV['DB_HOST'];
$dbName = $_ENV['DB_DATABASE']; $dbName = $_ENV['DB_DATABASE'];
+1
View File
@@ -0,0 +1 @@
https://93.43.5.102/limsapi/api/odata/Matrice
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
2026-04-03 10:07:01 - Aggiornamento completato: 3198 record inseriti.
File diff suppressed because one or more lines are too long
+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
-->
+48
View File
@@ -0,0 +1,48 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once __DIR__ . '/class/db-functions.php';
header('Content-Type: application/json');
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['success' => false, 'message' => 'Method not allowed']);
exit;
}
$partId = isset($_POST['part_id']) ? (int)$_POST['part_id'] : 0;
$analysisRecordkey = trim($_POST['analysis_recordkey'] ?? '');
if ($partId <= 0 || $analysisRecordkey === '') {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Missing required data']);
exit;
}
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("
DELETE FROM identification_parts_analyses
WHERE part_id = :part_id
AND analysis_recordkey = :analysis_recordkey
");
$stmt->execute([
':part_id' => $partId,
':analysis_recordkey' => $analysisRecordkey,
]);
echo json_encode([
'success' => true,
'message' => 'Association deleted'
]);
} catch (Throwable $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'message' => $e->getMessage()
]);
}
+112
View File
@@ -0,0 +1,112 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
ini_set('display_errors', '0');
error_reporting(E_ALL);
/**
* Uso:
* download_rapporto_file.php?id=123
*/
try {
$api = VisualLimsApiClient::getInstance();
$id = isset($_GET['id']) ? intval($_GET['id']) : 0;
if (!$id) {
throw new Exception("ID RapportoFile mancante");
}
$debugDir = __DIR__ . '/logs/lims_get_commessa/';
if (!is_dir($debugDir)) {
mkdir($debugDir, 0755, true);
}
/**
* Recupero il record RapportoFile.
*/
$data = $api->get("RapportoFile({$id})", []);
file_put_contents(
$debugDir . "rapporto_file_{$id}.json",
json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
);
/**
* Provo vari nomi campo possibili per il contenuto PDF.
* Dopo il primo test potremo adattarlo al nome esatto.
*/
$possibleContentFields = [
'File',
'file',
'Content',
'content',
'FileContent',
'fileContent',
'Contenuto',
'contenuto',
'Bytes',
'bytes'
];
$base64 = null;
foreach ($possibleContentFields as $field) {
if (!empty($data[$field])) {
$base64 = $data[$field];
break;
}
}
if (!$base64) {
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'success' => false,
'message' => 'Record RapportoFile recuperato, ma non ho trovato un campo base64 del PDF.',
'hint' => 'Apri logs/lims_get_commessa/rapporto_file_' . $id . '.json e controlla il nome reale del campo file.',
'data' => $data
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
}
/**
* Se il contenuto ha prefisso data URI, lo pulisco.
*/
if (strpos($base64, 'base64,') !== false) {
$parts = explode('base64,', $base64);
$base64 = end($parts);
}
$pdfBinary = base64_decode($base64, true);
if ($pdfBinary === false) {
throw new Exception("Il contenuto trovato non è un base64 valido");
}
$filename = $data['NomeFile']
?? $data['nomeFile']
?? $data['FileName']
?? $data['fileName']
?? "rapporto_{$id}.pdf";
if (!str_ends_with(strtolower($filename), '.pdf')) {
$filename .= '.pdf';
}
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="' . basename($filename) . '"');
header('Content-Length: ' . strlen($pdfBinary));
echo $pdfBinary;
} catch (Exception $e) {
http_response_code(500);
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'success' => false,
'error' => $e->getMessage()
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
+46
View File
@@ -0,0 +1,46 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$idRapportoFile = isset($_GET['id_rapporto_file']) ? (int)$_GET['id_rapporto_file'] : 0;
if ($idRapportoFile <= 0) {
throw new Exception("Parametro id_rapporto_file mancante o non valido.");
}
/*
* This endpoint returns the PDF binary stream.
* Do not call this with the normal get() method because get() expects JSON.
*/
$endpoint = "MediaFile/DownloadStream?objectType=RapportoFile&propertyName=FileContent&objectKey={$idRapportoFile}";
$api = VisualLimsApiClient::getInstance();
$pdfContent = $api->getRaw($endpoint);
if (empty($pdfContent)) {
throw new Exception("PDF vuoto o non ricevuto dal server.");
}
$fileName = "rapporto_{$idRapportoFile}.pdf";
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="' . $fileName . '"');
header('Content-Length: ' . strlen($pdfContent));
header('Cache-Control: private, max-age=0, must-revalidate');
header('Pragma: public');
echo $pdfContent;
exit;
} catch (Exception $e) {
http_response_code(500);
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'success' => false,
'error' => $e->getMessage()
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
+406 -74
View File
@@ -1,29 +1,75 @@
<?php include('include/headscript.php'); <?php include('include/headscript.php');
// Controlla se è stato passato un ID valido // Check if a valid ID was provided
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) { if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Invalid ID")); header("Location: templates_dashboard.php?status=error&message=" . urlencode("Invalid ID"));
exit; exit;
} }
$id = intval($_GET['id']); // Sanifica l'ID $id = intval($_GET['id']);
// Recupera il template dal database // Retrieve template from database
$db = DBHandlerSelect::getInstance(); $db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection(); $pdo = $db->getConnection();
$stmt = $pdo->prepare("SELECT * FROM excel_templates WHERE id = ?"); $stmt = $pdo->prepare("SELECT * FROM excel_templates WHERE id = ?");
$stmt->execute([$id]); $stmt->execute([$id]);
$template = $stmt->fetch(PDO::FETCH_ASSOC); $template = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$template) { if (!$template) {
header("Location: template_dashboard.php?status=error&message=" . urlencode("Template not found")); header("Location: templates_dashboard.php?status=error&message=" . urlencode("Template not found"));
exit; exit;
} }
// Recupera tutte le routine dal database // Retrieve all routines
$stmt = $pdo->prepare("SELECT * FROM routine"); $stmt = $pdo->prepare("SELECT * FROM routine");
$stmt->execute(); $stmt->execute();
$routines = $stmt->fetchAll(PDO::FETCH_ASSOC); $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Retrieve active API/JSON configurations
$stmt = $pdo->prepare("
SELECT id, name, provider_code, api_type, php_class_name
FROM api_configurations
WHERE is_active = 1
ORDER BY name ASC
");
$stmt->execute();
$apiConfigurations = $stmt->fetchAll(PDO::FETCH_ASSOC);
$buttonBgPalette = [
'#0d6efd' => 'Blue',
'#6610f2' => 'Indigo',
'#6f42c1' => 'Purple',
'#d63384' => 'Pink',
'#dc3545' => 'Red',
'#fd7e14' => 'Orange',
'#ffc107' => 'Yellow',
'#198754' => 'Green',
'#20c997' => 'Teal',
'#0dcaf0' => 'Cyan',
'#212529' => 'Dark',
'#6c757d' => 'Gray',
'#495057' => 'Slate',
'#795548' => 'Brown',
'#2f5d50' => 'Sage Green',
];
$buttonTextPalette = [
'#ffffff' => 'White',
'#6c757d' => 'Gray',
'#000000' => 'Black',
];
$currentButtonBgColor = strtolower($template['button_bg_color'] ?? '#007bff');
$currentButtonTextColor = strtolower($template['button_text_color'] ?? '#ffffff');
if (!array_key_exists($currentButtonBgColor, array_change_key_case($buttonBgPalette, CASE_LOWER))) {
$currentButtonBgColor = '#0d6efd';
}
if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonTextPalette, CASE_LOWER))) {
$currentButtonTextColor = '#ffffff';
}
?> ?>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
@@ -34,7 +80,64 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" /> <link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" /> <link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<?php include('cssinclude.php'); ?> <?php include('cssinclude.php'); ?>
<!-- Include jQuery prima di Select2 --> <style>
.color-palette {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 8px;
}
.color-option {
position: relative;
cursor: pointer;
}
.color-option input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.color-swatch {
width: 42px;
height: 42px;
border-radius: 10px;
border: 2px solid #d6dbe0;
display: inline-flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
transition: all 0.18s ease;
}
.color-option input:checked+.color-swatch {
border: 3px solid #111827;
transform: scale(1.08);
box-shadow: 0 0 0 4px rgba(13, 110, 253, 0.18);
}
.color-swatch .check-mark {
display: none;
font-size: 18px;
font-weight: 700;
color: #ffffff;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}
.color-option input:checked+.color-swatch .check-mark {
display: inline-block;
}
.color-name {
display: block;
font-size: 11px;
text-align: center;
margin-top: 4px;
color: #555;
max-width: 55px;
}
</style>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script> <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<title>Edit Template <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title> <title>Edit Template <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
@@ -44,19 +147,22 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
<div class="wrapper"> <div class="wrapper">
<?php include('include/navbar.php'); ?> <?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?> <?php include('include/topbar.php'); ?>
<div class="page-wrapper"> <div class="page-wrapper">
<div class="page-content"> <div class="page-content">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header"> <div class="card-header">
<h5 class="mb-0">Update XLS Template</h5> <h5 class="mb-0">Update Template</h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<p class="mb-2">Edit the following form in order to update the selected import XLS template</p> <p class="mb-2">Edit the following form in order to update the selected import template</p>
<p class="mb-2">Mandatory Fields</p> <p class="mb-2">Mandatory Fields</p>
<ul class="mb-0"> <ul class="mb-0">
<li>Template Name</li> <li>Template Name</li>
<li>Row Header and Column Header: where the title of the excel starts</li> <li>Source Type</li>
<li>Schema</li> <li>Schema and Client</li>
<li>Row Header and Column Header only for XLS templates</li>
</ul> </ul>
</div> </div>
</div> </div>
@@ -69,34 +175,100 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
</div> </div>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="col-12"> <div class="col-12">
<form id="editTemplateForm" method="POST"> <form id="editTemplateForm" method="POST">
<input type="hidden" name="id" value="<?php echo $template['id']; ?>"> <input type="hidden" name="id" value="<?php echo (int)$template['id']; ?>">
<div class="mb-3"> <div class="mb-3">
<label class="form-label"><?= htmlspecialchars($templatename, ENT_QUOTES, 'UTF-8'); ?> *</label> <label class="form-label"><?= htmlspecialchars($templatename, ENT_QUOTES, 'UTF-8'); ?> *</label>
<input type="text" name="name" class="form-control" value="<?php echo htmlspecialchars($template['name']); ?>" required> <input type="text" name="name" class="form-control" value="<?php echo htmlspecialchars($template['name'] ?? ''); ?>" required>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Source Type *</label>
<select name="source_type" id="sourceType" class="form-control" required>
<option value="XLS" <?php echo (($template['source_type'] ?? 'XLS') === 'XLS') ? 'selected' : ''; ?>>XLS</option>
<option value="API" <?php echo (($template['source_type'] ?? 'XLS') === 'API') ? 'selected' : ''; ?>>API</option>
<option value="JSON" <?php echo (($template['source_type'] ?? 'XLS') === 'JSON') ? 'selected' : ''; ?>>JSON</option>
<option value="PDF" <?php echo (($template['source_type'] ?? 'XLS') === 'PDF') ? 'selected' : ''; ?>>PDF</option>
</select>
<small class="text-muted">Choose the source used by this template</small>
</div>
<div class="mb-3" id="headerRowWrapper">
<label class="form-label"><?= htmlspecialchars($rowheader, ENT_QUOTES, 'UTF-8'); ?> *</label> <label class="form-label"><?= htmlspecialchars($rowheader, ENT_QUOTES, 'UTF-8'); ?> *</label>
<input type="number" name="header_row" class="form-control" value="<?php echo $template['header_row']; ?>" required> <input type="number" name="header_row" id="headerRow" class="form-control" value="<?php echo htmlspecialchars($template['header_row'] ?? ''); ?>">
</div> </div>
<div class="mb-3"> <div class="mb-3" id="startColumnWrapper">
<label class="form-label"><?= htmlspecialchars($columnheader, ENT_QUOTES, 'UTF-8'); ?>*</label> <label class="form-label"><?= htmlspecialchars($columnheader, ENT_QUOTES, 'UTF-8'); ?> *</label>
<input type="text" name="start_column" class="form-control" value="<?php echo htmlspecialchars($template['start_column']); ?>" required> <input type="text" name="start_column" id="startColumn" class="form-control" value="<?php echo htmlspecialchars($template['start_column'] ?? ''); ?>">
</div>
<div class="mb-3" id="xlsSheetNumberWrapper">
<label class="form-label">XLS Sheet Number</label>
<input
type="number"
name="xls_sheet_index"
id="xlsSheetIndex"
class="form-control"
min="0"
value="<?php echo htmlspecialchars($template['xls_sheet_index'] ?? 0); ?>">
<small class="text-muted">
Use 0 for the first sheet, 1 for the second sheet, 2 for the third sheet, and so on.
</small>
</div>
<div class="mb-3" id="apiConfigWrapper" style="display: none;">
<label class="form-label">API / JSON Configuration *</label>
<select name="api_config_id" id="apiConfigSelect" class="form-control">
<option value="">Select an API configuration...</option>
<?php foreach ($apiConfigurations as $apiConfig): ?>
<?php
$apiLabelParts = [];
if (!empty($apiConfig['name'])) {
$apiLabelParts[] = $apiConfig['name'];
}
if (!empty($apiConfig['provider_code'])) {
$apiLabelParts[] = '[' . $apiConfig['provider_code'] . ']';
}
if (!empty($apiConfig['api_type'])) {
$apiLabelParts[] = '(' . $apiConfig['api_type'] . ')';
}
if (!empty($apiConfig['php_class_name'])) {
$apiLabelParts[] = '- ' . $apiConfig['php_class_name'];
}
$apiLabel = implode(' ', $apiLabelParts);
?>
<option
value="<?php echo (int)$apiConfig['id']; ?>"
<?php echo ((int)($template['api_config_id'] ?? 0) === (int)$apiConfig['id']) ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($apiLabel, ENT_QUOTES, 'UTF-8'); ?>
</option>
<?php endforeach; ?>
</select>
<small class="text-muted">
Select the API/JSON configuration linked to this template.
</small>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label"><?= htmlspecialchars($desctemplate, ENT_QUOTES, 'UTF-8'); ?></label> <label class="form-label"><?= htmlspecialchars($desctemplate, ENT_QUOTES, 'UTF-8'); ?></label>
<textarea name="description" class="form-control"><?php echo htmlspecialchars($template['description']); ?></textarea> <textarea name="description" class="form-control"><?php echo htmlspecialchars($template['description'] ?? ''); ?></textarea>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label"><?= htmlspecialchars($desttable, ENT_QUOTES, 'UTF-8'); ?>*</label> <label class="form-label"><?= htmlspecialchars($desttable, ENT_QUOTES, 'UTF-8'); ?> *</label>
<input type="text" name="target_table" class="form-control" value="<?php echo htmlspecialchars($template['target_table']); ?>" readonly required> <input type="text" name="target_table" class="form-control" value="<?php echo htmlspecialchars($template['target_table'] ?? 'datadb'); ?>" readonly required>
</div> </div>
<div class="mb-3"> <div class="mb-3">
@@ -110,12 +282,46 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Button Background Color</label> <label class="form-label">Button Background Color</label>
<input type="color" name="button_bg_color" class="form-control" value="<?php echo htmlspecialchars($template['button_bg_color'] ?? '#007bff'); ?>">
<div class="color-palette">
<?php foreach ($buttonBgPalette as $colorValue => $colorLabel): ?>
<?php $isChecked = strtolower($colorValue) === $currentButtonBgColor; ?>
<label class="color-option" title="<?= htmlspecialchars($colorLabel, ENT_QUOTES, 'UTF-8'); ?>">
<input
type="radio"
name="button_bg_color"
value="<?= htmlspecialchars($colorValue, ENT_QUOTES, 'UTF-8'); ?>"
<?= $isChecked ? 'checked' : ''; ?>>
<span class="color-swatch" style="background-color: <?= htmlspecialchars($colorValue, ENT_QUOTES, 'UTF-8'); ?>;">
<span class="check-mark"></span>
</span>
<span class="color-name"><?= htmlspecialchars($colorLabel, ENT_QUOTES, 'UTF-8'); ?></span>
</label>
<?php endforeach; ?>
</div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Button Text Color</label> <label class="form-label">Button Text Color</label>
<input type="color" name="button_text_color" class="form-control" value="<?php echo htmlspecialchars($template['button_text_color'] ?? '#ffffff'); ?>">
<div class="color-palette">
<?php foreach ($buttonTextPalette as $colorValue => $colorLabel): ?>
<?php $isChecked = strtolower($colorValue) === $currentButtonTextColor; ?>
<label class="color-option" title="<?= htmlspecialchars($colorLabel, ENT_QUOTES, 'UTF-8'); ?>">
<input
type="radio"
name="button_text_color"
value="<?= htmlspecialchars($colorValue, ENT_QUOTES, 'UTF-8'); ?>"
<?= $isChecked ? 'checked' : ''; ?>>
<span class="color-swatch" style="background-color: <?= htmlspecialchars($colorValue, ENT_QUOTES, 'UTF-8'); ?>;">
<span class="check-mark"></span>
</span>
<span class="color-name"><?= htmlspecialchars($colorLabel, ENT_QUOTES, 'UTF-8'); ?></span>
</label>
<?php endforeach; ?>
</div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
@@ -128,7 +334,7 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
<select name="client_id" id="clientSelect" class="form-control" required> <select name="client_id" id="clientSelect" class="form-control" required>
<option value="">Select a client...</option> <option value="">Select a client...</option>
</select> </select>
<span id="clientLoadingStatus" class="text-muted" style="margin-left: 10px; display: none;">Recupero clienti in corso...</span> <span id="clientLoadingStatus" class="text-muted" style="margin-left: 10px; display: none;">Loading clients...</span>
</div> </div>
<div class="mb-3"> <div class="mb-3">
@@ -136,7 +342,7 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
<select name="schema_id" id="schemaSelect" class="form-control" required> <select name="schema_id" id="schemaSelect" class="form-control" required>
<option value="">Select a schema...</option> <option value="">Select a schema...</option>
</select> </select>
<span id="schemaLoadingStatus" class="text-muted" style="margin-left: 10px; display: none;">Caricamento schemi in corso...</span> <span id="schemaLoadingStatus" class="text-muted" style="margin-left: 10px; display: none;">Loading schemas...</span>
</div> </div>
<div class="mb-3"> <div class="mb-3">
@@ -144,11 +350,12 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
<select name="idroutine" id="routineSelect" class="form-control"> <select name="idroutine" id="routineSelect" class="form-control">
<option value="">Select a routine...</option> <option value="">Select a routine...</option>
<?php foreach ($routines as $routine): ?> <?php foreach ($routines as $routine): ?>
<option value="<?php echo $routine['idroutine']; ?>" <?php echo ($template['idroutine'] ?? '') == $routine['idroutine'] ? 'selected' : ''; ?>> <option value="<?php echo $routine['idroutine']; ?>" <?php echo (($template['idroutine'] ?? '') == $routine['idroutine']) ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($routine['name']); ?> <?php echo htmlspecialchars($routine['name']); ?>
</option> </option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<div id="routineDetails" class="mt-2" style="display: none;"> <div id="routineDetails" class="mt-2" style="display: none;">
<h6>Routine Details</h6> <h6>Routine Details</h6>
<p><strong>Name:</strong> <span id="routineName"></span></p> <p><strong>Name:</strong> <span id="routineName"></span></p>
@@ -166,8 +373,10 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="overlay toggle-icon"></div> <div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a> <a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?> <?php include('include/footer.php'); ?>
@@ -175,9 +384,8 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
<script> <script>
document.addEventListener("DOMContentLoaded", function() { document.addEventListener("DOMContentLoaded", function() {
// Verifica che jQuery sia caricato
if (typeof jQuery === 'undefined') { if (typeof jQuery === 'undefined') {
alert("Errore: jQuery non è caricato. Contatta l'amministratore."); alert("Error: jQuery is not loaded.");
return; return;
} }
@@ -192,12 +400,21 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
const routineAction2 = document.getElementById("routineAction2"); const routineAction2 = document.getElementById("routineAction2");
const routineAction3 = document.getElementById("routineAction3"); const routineAction3 = document.getElementById("routineAction3");
if (!form || !clientLoadingStatus || !schemaLoadingStatus || !routineSelect || !routineDetails) { const sourceType = document.getElementById("sourceType");
alert("Errore: Uno o più elementi della pagina non sono stati trovati. Contatta l'amministratore.");
return; const headerRowWrapper = document.getElementById("headerRowWrapper");
} const startColumnWrapper = document.getElementById("startColumnWrapper");
const xlsSheetNumberWrapper = document.getElementById("xlsSheetNumberWrapper");
const apiConfigWrapper = document.getElementById("apiConfigWrapper");
const headerRow = document.getElementById("headerRow");
const startColumn = document.getElementById("startColumn");
const xlsSheetIndex = document.getElementById("xlsSheetIndex");
const apiConfigSelect = document.getElementById("apiConfigSelect");
const selectedClientId = <?php echo json_encode((int)($template['idclient'] ?? 0)); ?>;
const selectedSchemaId = <?php echo json_encode((int)($template['idschema'] ?? 0)); ?>;
// Inizializza Select2
$('#clientSelect').select2({ $('#clientSelect').select2({
placeholder: "Search for a client...", placeholder: "Search for a client...",
allowClear: true allowClear: true
@@ -213,108 +430,201 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
allowClear: true allowClear: true
}); });
// Carica i clienti $('#apiConfigSelect').select2({
placeholder: "Select an API configuration...",
allowClear: true
});
function updateSourceFields() {
const selectedSource = sourceType.value;
const isXls = selectedSource === 'XLS';
const isApiOrJson = selectedSource === 'API' || selectedSource === 'JSON';
if (isXls) {
headerRowWrapper.style.display = 'block';
startColumnWrapper.style.display = 'block';
xlsSheetNumberWrapper.style.display = 'block';
headerRow.required = true;
startColumn.required = true;
headerRow.disabled = false;
startColumn.disabled = false;
xlsSheetIndex.disabled = false;
apiConfigWrapper.style.display = 'none';
apiConfigSelect.required = false;
apiConfigSelect.disabled = true;
$('#apiConfigSelect').val(null).trigger('change');
} else {
headerRowWrapper.style.display = 'none';
startColumnWrapper.style.display = 'none';
xlsSheetNumberWrapper.style.display = 'none';
headerRow.required = false;
startColumn.required = false;
headerRow.disabled = true;
startColumn.disabled = true;
xlsSheetIndex.disabled = true;
if (isApiOrJson) {
apiConfigWrapper.style.display = 'block';
apiConfigSelect.required = true;
apiConfigSelect.disabled = false;
} else {
apiConfigWrapper.style.display = 'none';
apiConfigSelect.required = false;
apiConfigSelect.disabled = true;
$('#apiConfigSelect').val(null).trigger('change');
}
}
}
sourceType.addEventListener('change', updateSourceFields);
updateSourceFields();
function formatClientLabel(client) {
const nome = client.Nominativo || "Name not available";
const id = client.IdCliente || "";
const codiceCliente = (client.CodiceCliente ?? client.codiceCliente ?? "").toString().trim();
const suffix = (codiceCliente.split("_")[1] || "").trim();
const shortCode = suffix || (codiceCliente ? codiceCliente.charAt(0) : "--");
return `${nome.trim()} - ${shortCode} (ID: ${id})`;
}
async function loadClients() { async function loadClients() {
try { try {
clientLoadingStatus.style.display = 'inline'; clientLoadingStatus.style.display = 'inline';
clientLoadingStatus.textContent = 'Recupero clienti in corso...'; clientLoadingStatus.textContent = 'Loading clients...';
const response = await fetch("get_clienti.php", { const response = await fetch("get_clienti.php", {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json"
} }
}); });
const data = await response.json(); const data = await response.json();
if (!response.ok) throw new Error(data.error || `Errore HTTP: ${response.status}`);
if (!response.ok) {
throw new Error(data.error || `HTTP error: ${response.status}`);
}
const select = document.getElementById("clientSelect"); const select = document.getElementById("clientSelect");
select.innerHTML = '<option value="">Select a client...</option>'; select.innerHTML = '<option value="">Select a client...</option>';
data.value.forEach(client => { data.value.forEach(client => {
const nome = client.Nominativo || "Nome non disponibile"; const id = client.IdCliente || "";
const id = client.IdCliente || "ID non disponibile"; const option = new Option(formatClientLabel(client), id);
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
if (parseInt(id) === parseInt(<?php echo json_encode($template['idclient'] ?? 0); ?>)) { if (parseInt(id) === parseInt(selectedClientId)) {
option.selected = true; option.selected = true;
} }
select.add(option); select.add(option);
}); });
$(select).trigger('change'); $(select).trigger('change');
clientLoadingStatus.textContent = "Clienti caricati."; clientLoadingStatus.textContent = "Clients loaded.";
} catch (error) { } catch (error) {
clientLoadingStatus.textContent = "Errore nel caricamento."; clientLoadingStatus.textContent = "Loading error.";
Swal.fire({ Swal.fire({
title: "Errore!", title: "Error!",
text: "Impossibile caricare i clienti: " + error.message, text: "Unable to load clients: " + error.message,
icon: "error", icon: "error",
confirmButtonText: "OK" confirmButtonText: "OK"
}); });
} finally { } finally {
setTimeout(() => clientLoadingStatus.style.display = 'none', 2000); setTimeout(() => clientLoadingStatus.style.display = 'none', 1500);
} }
} }
// Carica gli schemi
async function loadSchemas() { async function loadSchemas() {
try { try {
schemaLoadingStatus.style.display = 'inline'; schemaLoadingStatus.style.display = 'inline';
schemaLoadingStatus.textContent = 'Caricamento schemi in corso...'; schemaLoadingStatus.textContent = 'Loading schemas...';
const response = await fetch("get_schemi.php", { const response = await fetch("get_schemi.php", {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json"
} }
}); });
const data = await response.json(); const data = await response.json();
if (!response.ok) throw new Error(data.error || `Errore HTTP: ${response.status}`);
if (!response.ok) {
throw new Error(data.error || `HTTP error: ${response.status}`);
}
const select = document.getElementById("schemaSelect"); const select = document.getElementById("schemaSelect");
select.innerHTML = '<option value="">Select a schema...</option>'; select.innerHTML = '<option value="">Select a schema...</option>';
data.value.forEach(schema => {
const nome = schema.Nome || "Nome non disponibile"; const sortedSchemas = [...data.value].sort((a, b) => {
const id = schema.IdSchemaCustomFields || "ID non disponibile"; const nomeA = (a.Nome || "").trim().toLowerCase();
const nomeB = (b.Nome || "").trim().toLowerCase();
return nomeA.localeCompare(nomeB, 'it', {
sensitivity: 'base'
});
});
sortedSchemas.forEach(schema => {
const nome = schema.Nome || "Name not available";
const id = schema.IdSchemaCustomFields || "";
const option = new Option(`${nome.trim()} (ID: ${id})`, id); const option = new Option(`${nome.trim()} (ID: ${id})`, id);
if (parseInt(id) === parseInt(<?php echo json_encode($template['idschema'] ?? 0); ?>)) {
if (parseInt(id) === parseInt(selectedSchemaId)) {
option.selected = true; option.selected = true;
} }
select.add(option); select.add(option);
}); });
$(select).trigger('change'); $(select).trigger('change');
schemaLoadingStatus.textContent = "Schemi caricati."; schemaLoadingStatus.textContent = "Schemas loaded.";
} catch (error) { } catch (error) {
schemaLoadingStatus.textContent = "Errore nel caricamento."; schemaLoadingStatus.textContent = "Loading error.";
Swal.fire({ Swal.fire({
title: "Errore!", title: "Error!",
text: "Impossibile caricare gli schemi: " + error.message, text: "Unable to load schemas: " + error.message,
icon: "error", icon: "error",
confirmButtonText: "OK" confirmButtonText: "OK"
}); });
} finally { } finally {
setTimeout(() => schemaLoadingStatus.style.display = 'none', 2000); setTimeout(() => schemaLoadingStatus.style.display = 'none', 1500);
} }
} }
// Carica i dati
async function loadData() { async function loadData() {
try { try {
await loadClients(); await loadClients();
await loadSchemas(); await loadSchemas();
} catch (error) { } catch (error) {
Swal.fire({ Swal.fire({
title: "Errore!", title: "Error!",
text: "Errore nel caricamento dei dati: " + error.message, text: "Error while loading data: " + error.message,
icon: "error", icon: "error",
confirmButtonText: "OK" confirmButtonText: "OK"
}); });
} }
} }
loadData(); loadData();
// Routine dettagli
const routines = <?php echo json_encode($routines); ?>; const routines = <?php echo json_encode($routines); ?>;
function updateRoutineDetails() { function updateRoutineDetails() {
const selectedId = routineSelect.value; const selectedId = routineSelect.value;
routineDetails.style.display = selectedId ? 'block' : 'none'; routineDetails.style.display = selectedId ? 'block' : 'none';
if (selectedId) { if (selectedId) {
const routine = routines.find(r => r.idroutine == selectedId); const routine = routines.find(r => r.idroutine == selectedId);
if (routine) { if (routine) {
routineName.textContent = routine.name || 'N/A'; routineName.textContent = routine.name || 'N/A';
routineDescription.textContent = routine.description || 'N/A'; routineDescription.textContent = routine.description || 'N/A';
@@ -336,10 +646,10 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
routineAction3.textContent = ''; routineAction3.textContent = '';
} }
} }
routineSelect.addEventListener('change', updateRoutineDetails);
updateRoutineDetails(); // Inizializza dettagli se una routine è preselezionata
// Submit del form routineSelect.addEventListener('change', updateRoutineDetails);
updateRoutineDetails();
form.addEventListener("submit", function(e) { form.addEventListener("submit", function(e) {
e.preventDefault(); e.preventDefault();
@@ -351,8 +661,8 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!clientId) { if (!clientId) {
Swal.fire({ Swal.fire({
title: "Errore!", title: "Error!",
text: "Per favore seleziona un cliente.", text: "Please select a client.",
icon: "error", icon: "error",
confirmButtonText: "OK" confirmButtonText: "OK"
}); });
@@ -373,8 +683,8 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!schemaId) { if (!schemaId) {
Swal.fire({ Swal.fire({
title: "Errore!", title: "Error!",
text: "Per favore seleziona uno schema.", text: "Please select a schema.",
icon: "error", icon: "error",
confirmButtonText: "OK" confirmButtonText: "OK"
}); });
@@ -387,13 +697,35 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
const nameMatch = optionText.match(/^(.+?)(?:\s*\(ID:\s*\d+\))?$/); const nameMatch = optionText.match(/^(.+?)(?:\s*\(ID:\s*\d+\))?$/);
schemaName = nameMatch ? nameMatch[1].trim() : optionText; schemaName = nameMatch ? nameMatch[1].trim() : optionText;
} }
formData.append("idschema", schemaId); formData.append("idschema", schemaId);
formData.append("schemaname", schemaName); formData.append("schemaname", schemaName);
// Aggiungi idroutine
const routineId = routineSelect.value; const routineId = routineSelect.value;
formData.append("idroutine", routineId); formData.append("idroutine", routineId);
const selectedSource = sourceType.value;
if ((selectedSource === 'API' || selectedSource === 'JSON') && !apiConfigSelect.value) {
Swal.fire({
title: "Error!",
text: "Please select an API/JSON configuration.",
icon: "error",
confirmButtonText: "OK"
});
return;
}
if (selectedSource === 'XLS' && xlsSheetIndex.value === '') {
Swal.fire({
title: "Error!",
text: "Please enter the XLS sheet number.",
icon: "error",
confirmButtonText: "OK"
});
return;
}
fetch("process_edit_template_xls.php", { fetch("process_edit_template_xls.php", {
method: "POST", method: "POST",
body: formData body: formData
@@ -402,8 +734,8 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
.then(data => { .then(data => {
if (data.success) { if (data.success) {
Swal.fire({ Swal.fire({
title: "Successo!", title: "Success!",
text: "Template aggiornato con successo!", text: "Template updated successfully!",
icon: "success", icon: "success",
confirmButtonText: "OK" confirmButtonText: "OK"
}).then(() => { }).then(() => {
@@ -411,17 +743,17 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
}); });
} else { } else {
Swal.fire({ Swal.fire({
title: "Errore!", title: "Error!",
text: data.message, text: data.message,
icon: "error", icon: "error",
confirmButtonText: "OK" confirmButtonText: "OK"
}); });
} }
}) })
.catch(error => { .catch(() => {
Swal.fire({ Swal.fire({
title: "Errore!", title: "Error!",
text: "Si è verificato un errore imprevisto.", text: "An unexpected error occurred.",
icon: "error", icon: "error",
confirmButtonText: "OK" confirmButtonText: "OK"
}); });
+319
View File
@@ -12,3 +12,322 @@
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: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-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 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"}
2026-04-30 15:01:25 - Errore nel recupero dati: HTTP 404, Risposta: {"title":"Not Found","status":404,"detail":"Not Found","instance":"GET /api/odata/Rapporto(2621521)","errorCode":"d25cbd678"}
2026-04-30 15:02:04 - Errore nel recupero dati: HTTP 404, Risposta:
2026-04-30 15:03:19 - Errore nella richiesta: URL rejected: Malformed input to a URL function
+555
View File
@@ -0,0 +1,555 @@
/**
* exportLims_gridData.js — Export to LIMS using gridData (for imported.php)
*
* Replaces export_to_lims.js for pages that use gridData instead of DOM-rendered rows.
* Single export + batch export (Export All) with validation.
*/
(function () {
'use strict';
let pendingConfirmHandler = null;
let batchRunning = false;
Object.defineProperty(window, "batchRunning", { get: () => batchRunning });
let batchCancelled = false;
let pendingBatchConfirmHandler = null;
// ── Helpers ──────────────────────────────────────────────────────────
function cleanupBackdrop() {
document.querySelectorAll(".modal-backdrop").forEach(b => b.remove());
document.body.classList.remove("modal-open");
document.body.style.paddingRight = "";
const overlay = document.querySelector(".overlay.toggle-icon");
if (overlay) overlay.style.display = "none";
}
function getGridRow(iddatadb) {
return document.querySelector(`.grid-row[data-id="${iddatadb}"]`);
}
function getRowIndexByIddatadb(iddatadb) {
return (window.gridData || []).findIndex(r => String(r.iddatadb) === String(iddatadb));
}
// ── Validation ──────────────────────────────────────────────────────
async function validateRows(rowsToValidate) {
const response = await fetch("validate_export.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ rows: rowsToValidate }),
});
if (!response.ok) throw new Error(`Validation HTTP error: ${response.status}`);
return response.json();
}
function clearValidationErrors() {
// Clear from gridData
(window.gridData || []).forEach(row => { delete row._validationErrors; delete row._exportError; });
document.querySelectorAll(".grid-cell.validation-error").forEach(cell => {
cell.classList.remove("validation-error");
cell.querySelectorAll(".input-validation-error").forEach(el => el.classList.remove("input-validation-error"));
const tooltip = cell.querySelector(".validation-tooltip");
if (tooltip) tooltip.remove();
});
document.querySelectorAll(".grid-row.validation-row-error").forEach(row => row.classList.remove("validation-row-error"));
clearAllRowErrors();
}
function showValidationErrors(gridRow, iddatadb, errors) {
// Store in gridData for re-render persistence
const idx = getRowIndexByIddatadb(iddatadb);
if (idx >= 0) window.gridData[idx]._validationErrors = errors;
if (!gridRow) return;
gridRow.classList.add("validation-row-error");
const messages = [];
errors.forEach(err => {
messages.push(err.message);
if (!err.field) return;
let cell = null;
if (err.field.startsWith("field_label:")) {
const label = err.field.substring("field_label:".length);
const headers = document.querySelectorAll(".grid-header");
let targetIndex = null;
headers.forEach(h => {
if (h.textContent.trim() === label) targetIndex = h.getAttribute("data-index");
});
if (targetIndex) {
cell = gridRow.querySelector(`.grid-cell[data-index="${targetIndex}"]`);
}
} else {
cell = gridRow.querySelector(`.grid-cell[data-col="${err.field}"]`);
}
if (cell) {
cell.classList.add("validation-error");
cell.querySelectorAll("input, select").forEach(el => el.classList.add("input-validation-error"));
let tooltip = cell.querySelector(".validation-tooltip");
if (!tooltip) {
tooltip = document.createElement("div");
tooltip.className = "validation-tooltip";
cell.appendChild(tooltip);
}
tooltip.textContent = err.message;
}
});
showRowError(gridRow, iddatadb, messages.join("\n"));
}
// ── Send export ─────────────────────────────────────────────────────
async function sendExport(iddatadb, batchUuid) {
const formData = new FormData();
formData.append("iddatadb", iddatadb);
if (batchUuid) formData.append("batch_uuid", batchUuid);
const response = await fetch("export_to_lims.php", { method: "POST", body: formData });
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const data = await response.json();
if (data.success) {
// Update gridData
const idx = getRowIndexByIddatadb(iddatadb);
if (idx >= 0) {
window.gridData[idx].status = 'l';
window.gridData[idx].commessaweb = data.commessaweb;
}
// Update visible DOM row
const gridRow = getGridRow(iddatadb);
if (gridRow) {
const statusBadge = gridRow.querySelector('.grid-cell[data-col="status"] .status-badge');
if (statusBadge) {
statusBadge.classList.remove("status-i", "status-P");
statusBadge.classList.add("status-l");
statusBadge.textContent = "To LIMS";
}
const statusCell = gridRow.querySelector('.grid-cell[data-col="status"]');
if (statusCell && data.commessaweb) {
let cwSpan = statusCell.querySelector(".commessaweb-code");
if (!cwSpan) {
cwSpan = document.createElement("span");
cwSpan.className = "commessaweb-code";
cwSpan.style.cssText = "display:block; font-size:0.75em; color:#555; margin-top:2px;";
statusCell.appendChild(cwSpan);
}
cwSpan.textContent = data.commessaweb;
}
const exportBtn = gridRow.querySelector(".export-lims-btn");
if (exportBtn) {
exportBtn.disabled = true;
exportBtn.style.background = "#ccc";
exportBtn.style.cursor = "not-allowed";
exportBtn.style.opacity = "0.5";
}
}
}
return data;
}
// ── Show result modal ───────────────────────────────────────────────
function showExportResult(data) {
const el = document.getElementById("exportResponseModal");
if (!el) return;
const modal = new bootstrap.Modal(el, { keyboard: false });
const msg = document.getElementById("exportResponseMessage");
const label = document.getElementById("exportResponseModalLabel");
if (data.success) {
msg.innerHTML = `${data.message.replace(/\n/g, "<br>")}` +
`<br>ID CommessaWeb: ${data.idcommessaweb}` +
`<br>Codice CommessaWeb: ${data.commessaweb}` +
(data.totalPhotos > 0 ? `<br>Foto: ${data.totalPhotos}` : "");
label.textContent = "Esportazione Completata";
} else {
msg.textContent = `Errore: ${data.message}`;
label.textContent = "Errore Esportazione";
}
modal.show();
el.addEventListener("hidden.bs.modal", cleanupBackdrop, { once: true });
}
// ── Row UI helpers ──────────────────────────────────────────────────
function setRowExporting(row, active) {
if (!row) return;
const btnCell = row.querySelector(".button-cell");
if (active) {
row.classList.remove("batch-disabled");
row.classList.add("batch-exporting");
if (btnCell) {
btnCell.querySelectorAll(".action-btn").forEach(b => { b.dataset.prevDisplay = b.style.display; b.style.display = "none"; });
const spinner = document.createElement("span");
spinner.className = "batch-row-spinner";
spinner.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Exporting...';
btnCell.appendChild(spinner);
}
} else {
row.classList.remove("batch-exporting");
if (btnCell) {
const spinner = btnCell.querySelector(".batch-row-spinner");
if (spinner) spinner.remove();
btnCell.querySelectorAll(".action-btn").forEach(b => { b.style.display = b.dataset.prevDisplay || ""; delete b.dataset.prevDisplay; });
}
}
}
function showRowError(row, iddatadb, message) {
if (!row) return;
row.classList.add("batch-row-error");
const btnCell = row.querySelector(".button-cell");
if (btnCell) {
const old = btnCell.querySelector(".batch-error-msg");
if (old) old.remove();
const errorEl = document.createElement("div");
errorEl.className = "batch-error-msg";
errorEl.textContent = "Warning — click for details";
errorEl.addEventListener("click", () => {
document.getElementById("exportResponseMessage").innerHTML = message.replace(/\n/g, "<br>");
document.getElementById("exportResponseModalLabel").textContent = "Error (id: " + iddatadb + ")";
new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show();
});
btnCell.appendChild(errorEl);
}
}
function clearAllRowErrors() {
document.querySelectorAll(".grid-row.batch-row-error").forEach(row => {
row.classList.remove("batch-row-error");
const msg = row.querySelector(".batch-error-msg");
if (msg) msg.remove();
});
}
function disableAllRowButtons() {
document.querySelectorAll(".grid-row[data-id]").forEach(row => row.classList.add("batch-disabled"));
const toggle = document.querySelector(".actions-dropdown .dropdown-toggle");
if (toggle) { toggle.disabled = true; toggle.style.opacity = "0.5"; toggle.style.pointerEvents = "none"; }
}
function enableAllRowButtons() {
document.querySelectorAll(".grid-row[data-id]").forEach(row => row.classList.remove("batch-disabled"));
const toggle = document.querySelector(".actions-dropdown .dropdown-toggle");
if (toggle) { toggle.disabled = false; toggle.style.opacity = ""; toggle.style.pointerEvents = ""; }
}
// ── Single row export: validate → confirm → send ────────────────────
function startExportConfirmFlow(iddatadb, rowIndex) {
const gridRow = getGridRow(iddatadb);
clearValidationErrors();
if (gridRow) {
setRowExporting(gridRow, true);
const spinner = gridRow.querySelector(".batch-row-spinner");
if (spinner) spinner.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Validating...';
}
validateRows([{ iddatadb: parseInt(iddatadb), index: rowIndex }])
.then(validationData => {
if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); }
if (!validationData.success) {
showExportResult({ success: false, message: validationData.message || "Validation error" });
return;
}
const result = validationData.results[rowIndex];
if (result && !result.valid) {
if (gridRow) showValidationErrors(gridRow, iddatadb, result.errors);
return;
}
showConfirmAndExport(iddatadb, rowIndex);
})
.catch(error => {
if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); }
showExportResult({ success: false, message: "Validation error: " + error.message });
});
}
function showConfirmAndExport(iddatadb, rowIndex) {
const confirmModalElement = document.getElementById("exportConfirmModal");
if (!confirmModalElement) return;
const confirmModal = new bootstrap.Modal(confirmModalElement, { keyboard: false });
document.getElementById("exportIddatadb").textContent = iddatadb;
confirmModal.show();
const confirmBtn = document.getElementById("exportConfirmBtn");
if (!confirmBtn) { confirmModal.hide(); return; }
const confirmHandler = async () => {
pendingConfirmHandler = null;
confirmModal.hide();
const gridRow = getGridRow(iddatadb);
if (gridRow) setRowExporting(gridRow, true);
try {
const data = await sendExport(iddatadb);
if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); }
if (!data.success) showRowError(gridRow, iddatadb, data.message || "Unknown error");
showExportResult(data);
} catch (error) {
if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); }
showRowError(gridRow, iddatadb, error.message);
showExportResult({ success: false, message: error.message });
}
};
if (pendingConfirmHandler) confirmBtn.removeEventListener("click", pendingConfirmHandler);
pendingConfirmHandler = confirmHandler;
confirmBtn.addEventListener("click", confirmHandler, { once: true });
}
// ── Single row click (event delegation) ─────────────────────────────
$(document).on('click', '.export-lims-btn', function (e) {
e.preventDefault();
if (batchRunning) return;
const iddatadb = this.dataset.iddatadb;
const rowIndex = parseInt(this.dataset.row);
const gridRow = getGridRow(iddatadb);
// Check unsaved changes for this row
const dataRow = window.gridData?.[rowIndex];
if (dataRow && dataRow._dirty) {
const unsavedModal = new bootstrap.Modal(document.getElementById("exportUnsavedModal"), { keyboard: false });
unsavedModal.show();
document.getElementById("saveAndExportBtn")?.addEventListener("click", () => {
unsavedModal.hide();
// Save first, then export
const formData = window.buildSavePayload(rowIndex);
fetch('save_edited_row.php', { method: 'POST', body: formData })
.then(r => r.json())
.then(result => {
if (result.success) {
dataRow._dirty = false;
startExportConfirmFlow(iddatadb, rowIndex);
} else {
alert('Save failed: ' + result.message);
}
});
}, { once: true });
return;
}
startExportConfirmFlow(iddatadb, rowIndex);
});
// ── Batch export (Export All) ───────────────────────────────────────
function collectEligibleRows() {
// Read from gridData, not DOM
const eligible = [];
(window.gridData || []).forEach((row, index) => {
if (row.status !== 'l') {
eligible.push({ iddatadb: row.iddatadb, index, row: getGridRow(row.iddatadb) });
}
});
return eligible;
}
function hasUnsavedChanges() {
return (window.gridData || []).some(r => r._dirty);
}
async function validateAndFilter(eligibleRows) {
const rowsToValidate = eligibleRows.map(({ iddatadb, index }) => ({
iddatadb: parseInt(iddatadb),
index,
}));
const validationData = await validateRows(rowsToValidate);
if (!validationData.success) throw new Error(validationData.message || "Validation error");
const validRows = [];
let invalidCount = 0;
for (const item of eligibleRows) {
const result = validationData.results[item.index];
if (result && !result.valid) {
if (item.row) showValidationErrors(item.row, item.iddatadb, result.errors);
invalidCount++;
} else {
validRows.push(item);
}
}
return { validRows, invalidCount };
}
function showValidationSpinner(show) {
const bar = document.getElementById("batchExportBar");
const statusEl = document.getElementById("batchExportStatus");
const cancelBtn = document.getElementById("exportBatchCancelBtn");
if (show) {
if (bar) bar.style.display = "";
if (statusEl) statusEl.textContent = "Validating...";
if (cancelBtn) cancelBtn.style.display = "none";
} else {
if (bar) bar.style.display = "none";
if (cancelBtn) cancelBtn.style.display = "";
}
}
function showBatchConfirm(eligibleRows) {
clearValidationErrors();
showValidationSpinner(true);
validateAndFilter(eligibleRows)
.then(({ validRows, invalidCount }) => {
showValidationSpinner(false);
if (validRows.length === 0) {
document.getElementById("exportResponseMessage").innerHTML =
`No valid rows for export.<br><strong>${invalidCount}</strong> rows with validation errors.`;
document.getElementById("exportResponseModalLabel").textContent = "Validation Failed";
new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show();
return;
}
const confirmModal = new bootstrap.Modal(document.getElementById("exportBatchConfirmModal"), { keyboard: false });
let countText = String(validRows.length);
if (invalidCount > 0) countText += ` (${invalidCount} excluded due to errors)`;
document.getElementById("exportBatchCount").textContent = countText;
confirmModal.show();
const confirmBtn = document.getElementById("exportBatchConfirmBtn");
if (pendingBatchConfirmHandler) confirmBtn.removeEventListener("click", pendingBatchConfirmHandler);
pendingBatchConfirmHandler = () => {
pendingBatchConfirmHandler = null;
confirmModal.hide();
startBatchExport(validRows);
};
confirmBtn.addEventListener("click", pendingBatchConfirmHandler, { once: true });
})
.catch(error => {
showValidationSpinner(false);
document.getElementById("exportResponseMessage").textContent = "Validation error: " + error.message;
document.getElementById("exportResponseModalLabel").textContent = "Validation Error";
new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show();
});
}
$(document).on('click', '.export-all-lims-btn', function (e) {
e.preventDefault();
if (batchRunning) return;
if (hasUnsavedChanges()) {
const unsavedModal = new bootstrap.Modal(document.getElementById("exportBatchUnsavedModal"), { keyboard: false });
unsavedModal.show();
document.getElementById("batchSaveAndExportBtn")?.addEventListener("click", () => {
unsavedModal.hide();
// Trigger save all first — listen for completion
alert("Please Save All first, then Export All.");
}, { once: true });
return;
}
const eligibleRows = collectEligibleRows();
if (eligibleRows.length === 0) {
document.getElementById("exportResponseMessage").textContent = "All rows already exported to LIMS.";
document.getElementById("exportResponseModalLabel").textContent = "Export All";
new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show();
return;
}
showBatchConfirm(eligibleRows);
});
function startBatchExport(eligibleRows) {
batchCancelled = false;
batchRunning = true;
const batchUuid = crypto.randomUUID();
const total = eligibleRows.length;
let processed = 0, succeeded = 0, failed = 0;
disableAllRowButtons();
const bar = document.getElementById("batchExportBar");
const statusEl = document.getElementById("batchExportStatus");
const cancelBtn = document.getElementById("exportBatchCancelBtn");
if (bar) bar.style.display = "";
if (cancelBtn) cancelBtn.disabled = false;
if (statusEl) statusEl.textContent = `Exporting 0 / ${total}...`;
cancelBtn?.addEventListener("click", () => {
batchCancelled = true;
if (statusEl) statusEl.textContent = "Cancelling... (waiting for current row)";
if (cancelBtn) cancelBtn.disabled = true;
}, { once: true });
(async () => {
for (let i = 0; i < eligibleRows.length; i++) {
if (batchCancelled) break;
const { iddatadb, row } = eligibleRows[i];
if (statusEl) statusEl.textContent = `Exporting ${processed + 1} / ${total} (id: ${iddatadb})...`;
const gridRow = row || getGridRow(iddatadb);
if (gridRow) setRowExporting(gridRow, true);
try {
const data = await sendExport(iddatadb, batchUuid);
processed++;
if (data.success) {
succeeded++;
} else {
failed++;
const errIdx = getRowIndexByIddatadb(iddatadb);
if (errIdx >= 0) window.gridData[errIdx]._exportError = data.message || "Unknown error";
if (gridRow) showRowError(gridRow, iddatadb, data.message || "Unknown error");
}
} catch (error) {
processed++;
failed++;
const errIdx = getRowIndexByIddatadb(iddatadb);
if (errIdx >= 0) window.gridData[errIdx]._exportError = error.message;
if (gridRow) showRowError(gridRow, iddatadb, error.message);
}
if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); }
}
batchRunning = false;
enableAllRowButtons();
if (bar) bar.style.display = "none";
// Re-render to reflect status changes, then restore error indicators
const gr = window.gridRenderer;
if (gr) gr.renderVisibleRows();
// Restore error state from gridData._exportError
(window.gridData || []).forEach((row, idx) => {
if (row._exportError) {
const gridRow = getGridRow(row.iddatadb);
if (gridRow) showRowError(gridRow, row.iddatadb, row._exportError);
}
});
const msgEl = document.getElementById("exportResponseMessage");
const labelEl = document.getElementById("exportResponseModalLabel");
if (batchCancelled) {
labelEl.textContent = "Export All — Cancelled";
msgEl.innerHTML = `Exported: <strong>${succeeded}</strong><br>Errors: <strong>${failed}</strong><br>Not processed: <strong>${total - processed}</strong>`;
} else if (failed === 0) {
labelEl.textContent = "Export All — Complete";
msgEl.innerHTML = `All <strong>${succeeded}</strong> rows exported successfully.`;
} else {
labelEl.textContent = "Export All — Completed with errors";
msgEl.innerHTML = `Exported: <strong>${succeeded}</strong><br>Errors: <strong>${failed}</strong>`;
}
const modalEl = document.getElementById("exportResponseModal");
new bootstrap.Modal(modalEl, { keyboard: false }).show();
modalEl.addEventListener("hidden.bs.modal", cleanupBackdrop, { once: true });
})();
}
})();
+738 -177
View File
@@ -1,29 +1,373 @@
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
console.log("export_to_lims.js loaded"); console.log("export_to_lims.js loaded");
// Debug: verifica che i pulsanti siano trovati
const exportButtons = document.querySelectorAll(".export-lims-btn"); const exportButtons = document.querySelectorAll(".export-lims-btn");
console.log(`Found ${exportButtons.length} export-lims-btn buttons`); console.log(`Found ${exportButtons.length} export-lims-btn buttons`);
if (exportButtons.length === 0) { // Tracks the active confirm handler so it can be replaced on re-open
console.warn("No .export-lims-btn buttons found in the DOM"); let pendingConfirmHandler = null;
let batchRunning = false;
// Expose for Save All to check
Object.defineProperty(window, "batchRunning", {
get: () => batchRunning,
});
// ── Helpers ──────────────────────────────────────────────────────────────
function cleanupBackdrop() {
document.querySelectorAll(".modal-backdrop").forEach((b) => b.remove());
document.body.classList.remove("modal-open");
document.body.style.paddingRight = "";
const overlay = document.querySelector(".overlay.toggle-icon");
if (overlay) overlay.style.display = "none";
}
// ── Validation ───────────────────────────────────────────────────────────
/**
* Call the validation endpoint for an array of { iddatadb, index } objects.
* Returns the parsed JSON response.
*/
async function validateRows(rowsToValidate) {
const response = await fetch("validate_export.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ rows: rowsToValidate }),
});
if (!response.ok)
throw new Error(`Validation HTTP error: ${response.status}`);
return response.json();
}
/**
* Clear all validation-error highlights from the grid.
*/
function clearValidationErrors() {
document.querySelectorAll(".grid-cell.validation-error").forEach((cell) => {
cell.classList.remove("validation-error");
cell.querySelectorAll(".input-validation-error").forEach((el) => {
el.classList.remove("input-validation-error");
});
const tooltip = cell.querySelector(".validation-tooltip");
if (tooltip) tooltip.remove();
});
document.querySelectorAll(".grid-row.validation-row-error").forEach((row) => {
row.classList.remove("validation-row-error");
});
// Also clear batch-row-error that came from validation
clearAllRowErrors();
}
/**
* Highlight specific cells on a row based on validation errors.
* Each error has { field, message }.
* field can be: a data-col value, "parts", "field_label:SomeLabel", or null (row-level).
*/
function showValidationErrors(row, iddatadb, errors) {
row.classList.add("validation-row-error");
const messages = [];
errors.forEach((err) => {
messages.push(err.message);
if (!err.field) return; // row-level error, no specific cell
let cell = null;
if (err.field === "parts") {
// No specific cell to highlight, but we highlight the parts button area
// just add to messages
return;
} else if (err.field.startsWith("field_label:")) {
// Match by field_label text — find the header with this label, get its index
const label = err.field.substring("field_label:".length);
const headers = document.querySelectorAll(".grid-header");
let targetIndex = null;
headers.forEach((h) => {
if (h.textContent.trim() === label) {
targetIndex = h.getAttribute("data-index");
}
});
if (targetIndex) {
const rowIndex = row.querySelector(".grid-cell[data-row]")?.getAttribute("data-row");
if (rowIndex !== null) {
cell = row.querySelector(`.grid-cell[data-row="${rowIndex}"][data-index="${targetIndex}"]`);
}
}
} else {
// Direct data-col match
const rowIndex = row.querySelector(".grid-cell[data-row]")?.getAttribute("data-row");
if (rowIndex !== null) {
cell = row.querySelector(`.grid-cell[data-col="${err.field}"][data-row="${rowIndex}"]`);
}
}
if (cell) {
cell.classList.add("validation-error");
// Mark the input/select inside the cell
cell.querySelectorAll("input, select").forEach((el) => {
el.classList.add("input-validation-error");
});
// Add tooltip with error message
let tooltip = cell.querySelector(".validation-tooltip");
if (!tooltip) {
tooltip = document.createElement("div");
tooltip.className = "validation-tooltip";
cell.appendChild(tooltip);
}
tooltip.textContent = err.message;
}
});
// Show aggregated error on the row using existing mechanism
showRowError(row, iddatadb, messages.join("\n"));
}
/**
* Send a single export request and update the row UI on success.
* Returns the parsed JSON response.
*/
async function sendExport(iddatadb, gridRow, batchUuid = null) {
const formData = new FormData();
formData.append("iddatadb", iddatadb);
if (batchUuid) {
formData.append("batch_uuid", batchUuid);
}
const response = await fetch("export_to_lims.php", {
method: "POST",
body: formData,
});
if (!response.ok)
throw new Error(`HTTP error! status: ${response.status}`);
const data = await response.json();
if (data.success && gridRow) {
// Update status badge
const statusBadge = gridRow.querySelector(
'.grid-cell[data-col="status"] .status-badge',
);
if (statusBadge) {
statusBadge.classList.remove("status-i", "status-P");
statusBadge.classList.add("status-l");
statusBadge.textContent = "To LIMS";
}
// Insert/update CommessaWeb code span
const statusCell = gridRow.querySelector(
'.grid-cell[data-col="status"]',
);
if (statusCell && data.commessaweb) {
let cwSpan = statusCell.querySelector(".commessaweb-code");
if (!cwSpan) {
cwSpan = document.createElement("span");
cwSpan.className = "commessaweb-code";
cwSpan.style.cssText =
"display:block; font-size:0.75em; color:#555; margin-top:2px;";
cwSpan.title = "CommessaWeb";
const hiddenInput = statusCell.querySelector(
'input[type="hidden"]',
);
hiddenInput
? statusCell.insertBefore(cwSpan, hiddenInput)
: statusCell.appendChild(cwSpan);
}
cwSpan.textContent = data.commessaweb;
}
// Disable export button for this row
const exportBtn = gridRow.querySelector(".export-lims-btn");
if (exportBtn) {
exportBtn.disabled = true;
exportBtn.style.background = "#ccc";
exportBtn.style.cursor = "not-allowed";
exportBtn.style.opacity = "0.5";
exportBtn.title = "Già esportato";
}
}
return data;
}
/**
* Show the result of a single export in the response modal.
*/
function showExportResult(data) {
const responseModalElement =
document.getElementById("exportResponseModal");
if (!responseModalElement) {
alert("Errore: Modale di risposta non trovato");
return; return;
} }
exportButtons.forEach((btn) => { const responseModal = new bootstrap.Modal(responseModalElement, {
btn.addEventListener("click", (e) => { keyboard: false,
e.preventDefault(); });
const rowIndex = btn.dataset.row; const responseMessage = document.getElementById(
const iddatadb = btn.dataset.iddatadb; "exportResponseMessage",
console.log(
`Export to LIMS clicked for row ${rowIndex}, iddatadb: ${iddatadb}`,
); );
// Mostra il modale di conferma if (data.success) {
responseMessage.innerHTML =
`${data.message.replace(/\n/g, "<br>")}` +
`<br>ID CommessaWeb: ${data.idcommessaweb}` +
`<br>Codice CommessaWeb: ${data.commessaweb}` +
(data.totalPhotos > 0
? `<br>Foto trovate: ${data.totalPhotos}`
: "");
document.getElementById("exportResponseModalLabel").textContent =
"Esportazione Completata";
} else {
responseMessage.textContent = `Errore durante la generazione dei payload: ${data.message}`;
document.getElementById("exportResponseModalLabel").textContent =
"Errore Esportazione";
}
responseModal.show();
responseModalElement.addEventListener(
"hidden.bs.modal",
cleanupBackdrop,
{ once: true },
);
}
// ── Row button helpers (disable/enable during batch) ────────────────────
function setRowExporting(row, active) {
const btnCell = row.querySelector(".button-cell");
if (active) {
row.classList.remove("batch-disabled");
row.classList.add("batch-exporting");
if (btnCell) {
btnCell.querySelectorAll(".action-btn").forEach((b) => {
b.dataset.prevDisplay = b.style.display;
b.style.display = "none";
});
const spinner = document.createElement("span");
spinner.className = "batch-row-spinner";
spinner.innerHTML =
'<i class="fas fa-spinner fa-spin"></i> Exporting...';
btnCell.appendChild(spinner);
}
} else {
row.classList.remove("batch-exporting");
row.classList.add("batch-disabled");
if (btnCell) {
const spinner = btnCell.querySelector(".batch-row-spinner");
if (spinner) spinner.remove();
btnCell.querySelectorAll(".action-btn").forEach((b) => {
b.style.display = b.dataset.prevDisplay || "";
delete b.dataset.prevDisplay;
});
}
}
}
function showRowError(row, iddatadb, message) {
row.classList.add("batch-row-error");
const btnCell = row.querySelector(".button-cell");
if (btnCell) {
// Remove existing error msg
const old = btnCell.querySelector(".batch-error-msg");
if (old) old.remove();
const errorEl = document.createElement("div");
errorEl.className = "batch-error-msg";
errorEl.textContent = "⚠ Errore — clicca per dettagli";
errorEl.addEventListener("click", () => {
document.getElementById("exportResponseMessage").innerHTML = message.replace(/\n/g, "<br>");
document.getElementById("exportResponseModalLabel").textContent = "Errore Validazione (id: " + iddatadb + ")";
new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show();
});
btnCell.appendChild(errorEl);
}
}
function clearAllRowErrors() {
document.querySelectorAll(".grid-row.batch-row-error").forEach((row) => {
row.classList.remove("batch-row-error");
const msg = row.querySelector(".batch-error-msg");
if (msg) msg.remove();
});
}
function disableAllRowButtons() {
document.querySelectorAll(".grid-row[data-id]").forEach((row) => {
row.classList.add("batch-disabled");
});
// Disable Actions dropdown
const toggle = document.querySelector(
".actions-dropdown .dropdown-toggle",
);
if (toggle) {
toggle.disabled = true;
toggle.style.opacity = "0.5";
toggle.style.pointerEvents = "none";
}
}
function enableAllRowButtons() {
document.querySelectorAll(".grid-row[data-id]").forEach((row) => {
row.classList.remove("batch-disabled");
});
const toggle = document.querySelector(
".actions-dropdown .dropdown-toggle",
);
if (toggle) {
toggle.disabled = false;
toggle.style.opacity = "";
toggle.style.pointerEvents = "";
}
}
// ── Single row export: validate, confirm modal, then send ───────────────
function startExportConfirmFlow(iddatadb, btn) {
const gridRow = btn.closest(".grid-row");
const rowIndex = btn.dataset.row;
// Validate first
clearValidationErrors();
// Show validating state on the row
setRowExporting(gridRow, true);
const spinner = gridRow.querySelector(".batch-row-spinner");
if (spinner) spinner.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Validating...';
validateRows([{ iddatadb: parseInt(iddatadb), index: parseInt(rowIndex) }])
.then((validationData) => {
setRowExporting(gridRow, false);
gridRow.classList.remove("batch-disabled");
if (!validationData.success) {
showExportResult({ success: false, message: validationData.message || "Errore di validazione" });
return;
}
const result = validationData.results[rowIndex];
if (result && !result.valid) {
// Show validation errors on the row
showValidationErrors(gridRow, iddatadb, result.errors);
return;
}
// Validation passed — show confirm modal
showConfirmAndExport(iddatadb, btn);
})
.catch((error) => {
setRowExporting(gridRow, false);
gridRow.classList.remove("batch-disabled");
console.error("Validation error:", error);
showExportResult({ success: false, message: "Errore di validazione: " + error.message });
});
}
function showConfirmAndExport(iddatadb, btn) {
const confirmModalElement = const confirmModalElement =
document.getElementById("exportConfirmModal"); document.getElementById("exportConfirmModal");
if (!confirmModalElement) { if (!confirmModalElement) {
console.error("exportConfirmModal not found in the DOM");
alert("Errore: Modale di conferma non trovato"); alert("Errore: Modale di conferma non trovato");
return; return;
} }
@@ -34,206 +378,423 @@ document.addEventListener("DOMContentLoaded", () => {
document.getElementById("exportIddatadb").textContent = iddatadb; document.getElementById("exportIddatadb").textContent = iddatadb;
confirmModal.show(); confirmModal.show();
// Gestisci il click su "Conferma"
const confirmBtn = document.getElementById("exportConfirmBtn"); const confirmBtn = document.getElementById("exportConfirmBtn");
if (!confirmBtn) { if (!confirmBtn) {
console.error("exportConfirmBtn not found in the DOM");
confirmModal.hide(); confirmModal.hide();
alert("Errore: Pulsante di conferma non trovato"); alert("Errore: Pulsante di conferma non trovato");
return; return;
} }
const confirmHandler = async () => { const confirmHandler = async () => {
pendingConfirmHandler = null;
console.log(`Confirmed export for iddatadb: ${iddatadb}`); console.log(`Confirmed export for iddatadb: ${iddatadb}`);
confirmModal.hide(); confirmModal.hide();
const formData = new FormData(); const gridRow = btn.closest(".grid-row");
formData.append("iddatadb", iddatadb); setRowExporting(gridRow, true);
try { try {
const response = await fetch("export_to_lims.php", { const data = await sendExport(iddatadb, gridRow);
method: "POST",
body: formData,
});
if (!response.ok)
throw new Error(
`HTTP error! status: ${response.status}`,
);
const data = await response.json();
console.log("Export response:", data); console.log("Export response:", data);
// Mostra il modale di risposta // Stop spinner, fully restore row
const responseModalElement = document.getElementById( setRowExporting(gridRow, false);
"exportResponseModal", gridRow.classList.remove("batch-disabled");
);
if (!responseModalElement) {
console.error(
"exportResponseModal not found in the DOM",
);
alert("Errore: Modale di risposta non trovato");
return;
}
const responseModal = new bootstrap.Modal( if (!data.success) {
responseModalElement, showRowError(gridRow, iddatadb, data.message || "Errore sconosciuto");
{
keyboard: false,
},
);
const responseMessage = document.getElementById(
"exportResponseMessage",
);
if (data.success) {
responseMessage.innerHTML = `${data.message.replace(/\n/g, "<br>")}<br>ID CommessaWeb: ${data.idcommessaweb}`;
document.getElementById(
"exportResponseModalLabel",
).textContent = "Esportazione Completata";
responseModal.show();
// Aggiorna la UI per riflettere lo stato 'To LIMS'
const statusCell = btn
.closest(".grid-row")
.querySelector(
'.grid-cell[data-col="status"] .status-badge',
);
if (statusCell) {
statusCell.classList.remove("status-i", "status-P");
statusCell.classList.add("status-l");
statusCell.textContent = "To LIMS";
}
// Gestisci la chiusura del modale di risposta
responseModalElement.addEventListener(
"hidden.bs.modal",
() => {
console.log(
"exportResponseModal closed, cleaning up",
);
// Rimuovi tutti i backdrop residui
document
.querySelectorAll(".modal-backdrop")
.forEach((backdrop) => {
console.log(
"Removing backdrop:",
backdrop,
);
backdrop.remove();
});
// Ripristina il body
document.body.classList.remove("modal-open");
document.body.style.paddingRight = "";
// Nascondi l'overlay
const overlay = document.querySelector(
".overlay.toggle-icon",
);
if (overlay) {
overlay.style.display = "none";
}
},
{ once: true },
);
} else {
responseMessage.textContent = `Errore durante la generazione dei payload: ${data.message}`;
document.getElementById(
"exportResponseModalLabel",
).textContent = "Errore Esportazione";
responseModal.show();
// Gestisci la chiusura del modale di risposta anche in caso di errore
responseModalElement.addEventListener(
"hidden.bs.modal",
() => {
console.log(
"exportResponseModal closed, cleaning up",
);
// Rimuovi tutti i backdrop residui
document
.querySelectorAll(".modal-backdrop")
.forEach((backdrop) => {
console.log(
"Removing backdrop:",
backdrop,
);
backdrop.remove();
});
// Ripristina il body
document.body.classList.remove("modal-open");
document.body.style.paddingRight = "";
// Nascondi l'overlay
const overlay = document.querySelector(
".overlay.toggle-icon",
);
if (overlay) {
overlay.style.display = "none";
}
},
{ once: true },
);
} }
showExportResult(data);
} catch (error) { } catch (error) {
console.error("Export error:", error); console.error("Export error:", error);
const responseModalElement = document.getElementById(
"exportResponseModal", // Stop spinner, fully restore row so user can retry
setRowExporting(gridRow, false);
gridRow.classList.remove("batch-disabled");
showRowError(gridRow, iddatadb, error.message);
showExportResult({
success: false,
message: error.message,
});
}
};
if (pendingConfirmHandler) {
confirmBtn.removeEventListener("click", pendingConfirmHandler);
}
pendingConfirmHandler = confirmHandler;
confirmBtn.addEventListener("click", confirmHandler, { once: true });
}
// ── Single row click handler ────────────────────────────────────────────
exportButtons.forEach((btn) => {
btn.addEventListener("click", (e) => {
e.preventDefault();
if (batchRunning) return;
const rowIndex = btn.dataset.row;
const iddatadb = btn.dataset.iddatadb;
console.log(
`Export to LIMS clicked for row ${rowIndex}, iddatadb: ${iddatadb}`,
); );
if (!responseModalElement) {
console.error( const gridRow = btn.closest(".grid-row");
"exportResponseModal not found in the DOM",
if (gridRow && gridRow.querySelector(".cell-changed")) {
const unsavedModal = new bootstrap.Modal(
document.getElementById("exportUnsavedModal"),
{ keyboard: false },
); );
alert("Errore: Modale di risposta non trovato"); unsavedModal.show();
document.getElementById("saveAndExportBtn").addEventListener(
"click",
() => {
unsavedModal.hide();
const saveBtn = gridRow.querySelector(".save-btn");
if (!saveBtn) return;
const observer = new MutationObserver(() => {
if (!gridRow.querySelector(".cell-changed")) {
observer.disconnect();
startExportConfirmFlow(iddatadb, btn);
}
});
observer.observe(gridRow, {
subtree: true,
attributes: true,
attributeFilter: ["class"],
});
saveBtn.click();
},
{ once: true },
);
return; return;
} }
const responseModal = new bootstrap.Modal(
responseModalElement, // No unsaved changes — go straight to validate + export confirm
{ startExportConfirmFlow(iddatadb, btn);
keyboard: false, });
}, });
// ── Batch export (Export All) ───────────────────────────────────────────
const exportAllBtn = document.querySelector(".export-all-lims-btn");
if (!exportAllBtn) return;
let batchCancelled = false;
let pendingBatchConfirmHandler = null;
function collectEligibleRows() {
const allRows = document.querySelectorAll(".grid-row[data-id]");
const eligible = [];
allRows.forEach((row) => {
const statusBadge = row.querySelector(
'.grid-cell[data-col="status"] .status-badge',
); );
if (statusBadge && !statusBadge.classList.contains("status-l")) {
const iddatadb = row.dataset.id;
if (iddatadb) {
eligible.push({ iddatadb, row });
}
}
});
return eligible;
}
/**
* Get the data-row index for a grid row element.
*/
function getRowIndex(row) {
const cell = row.querySelector(".grid-cell[data-row]");
return cell ? parseInt(cell.getAttribute("data-row")) : null;
}
function hasUnsavedChanges() {
return !!document.querySelector(".grid-row[data-id] .cell-changed");
}
/**
* Validate all eligible rows, show errors, and return only the valid ones.
*/
async function validateAndFilter(eligibleRows) {
const rowsToValidate = eligibleRows.map(({ iddatadb, row }) => ({
iddatadb: parseInt(iddatadb),
index: getRowIndex(row),
}));
const validationData = await validateRows(rowsToValidate);
if (!validationData.success) {
throw new Error(validationData.message || "Errore di validazione");
}
const validRows = [];
let invalidCount = 0;
for (const { iddatadb, row } of eligibleRows) {
const rowIdx = getRowIndex(row);
const result = validationData.results[rowIdx];
if (result && !result.valid) {
showValidationErrors(row, iddatadb, result.errors);
invalidCount++;
} else {
validRows.push({ iddatadb, row });
}
}
return { validRows, invalidCount };
}
function showValidationSpinner(show) {
const bar = document.getElementById("batchExportBar");
const statusEl = document.getElementById("batchExportStatus");
const cancelBtn = document.getElementById("exportBatchCancelBtn");
if (show) {
bar.style.display = "";
statusEl.textContent = "Validazione in corso...";
cancelBtn.style.display = "none";
} else {
bar.style.display = "none";
cancelBtn.style.display = "";
}
}
function showBatchConfirm(eligibleRows) {
clearValidationErrors();
showValidationSpinner(true);
// Validate before showing confirm
validateAndFilter(eligibleRows)
.then(({ validRows, invalidCount }) => {
showValidationSpinner(false);
if (validRows.length === 0) {
document.getElementById("exportResponseMessage").innerHTML =
`Nessuna riga valida per l'esportazione.<br>` +
`<strong>${invalidCount}</strong> righe con errori di validazione.`;
document.getElementById("exportResponseModalLabel").textContent =
"Validazione Fallita";
new bootstrap.Modal(
document.getElementById("exportResponseModal"),
{ keyboard: false },
).show();
return;
}
const confirmModal = new bootstrap.Modal(
document.getElementById("exportBatchConfirmModal"),
{ keyboard: false },
);
let countText = String(validRows.length);
if (invalidCount > 0) {
countText += ` (${invalidCount} escluse per errori di validazione)`;
}
document.getElementById("exportBatchCount").textContent = countText;
confirmModal.show();
const confirmBtn = document.getElementById("exportBatchConfirmBtn");
if (pendingBatchConfirmHandler) {
confirmBtn.removeEventListener("click", pendingBatchConfirmHandler);
}
pendingBatchConfirmHandler = () => {
pendingBatchConfirmHandler = null;
confirmModal.hide();
startBatchExport(validRows);
};
confirmBtn.addEventListener("click", pendingBatchConfirmHandler, { once: true });
})
.catch((error) => {
showValidationSpinner(false);
console.error("Batch validation error:", error);
document.getElementById("exportResponseMessage").textContent =
"Errore di validazione: " + error.message;
document.getElementById("exportResponseModalLabel").textContent =
"Errore Validazione";
new bootstrap.Modal(
document.getElementById("exportResponseModal"),
{ keyboard: false },
).show();
});
}
exportAllBtn.addEventListener("click", (e) => {
e.preventDefault();
if (batchRunning) return;
// Check unsaved changes first
if (hasUnsavedChanges()) {
const unsavedModal = new bootstrap.Modal(
document.getElementById("exportBatchUnsavedModal"),
{ keyboard: false },
);
unsavedModal.show();
document
.getElementById("batchSaveAndExportBtn")
.addEventListener(
"click",
() => {
unsavedModal.hide();
// Trigger Save All, then proceed
const saveAllEl =
document.querySelector(".save-all-btn");
if (!saveAllEl) return;
// Watch for all .cell-changed to disappear
const observer = new MutationObserver(() => {
if (!hasUnsavedChanges()) {
observer.disconnect();
const eligibleRows = collectEligibleRows();
if (eligibleRows.length === 0) {
document.getElementById( document.getElementById(
"exportResponseMessage", "exportResponseMessage",
).textContent = ).textContent =
`Errore durante la generazione dei payload: ${error.message}`; "Tutte le righe sono già state esportate al LIMS.";
document.getElementById( document.getElementById(
"exportResponseModalLabel", "exportResponseModalLabel",
).textContent = "Errore Esportazione"; ).textContent = "Export All";
responseModal.show(); new bootstrap.Modal(
document.getElementById(
// Gestisci la chiusura del modale di risposta in caso di errore "exportResponseModal",
responseModalElement.addEventListener( ),
"hidden.bs.modal", { keyboard: false },
() => { ).show();
console.log( return;
"exportResponseModal closed, cleaning up",
);
// Rimuovi tutti i backdrop residui
document
.querySelectorAll(".modal-backdrop")
.forEach((backdrop) => {
console.log("Removing backdrop:", backdrop);
backdrop.remove();
});
// Ripristina il body
document.body.classList.remove("modal-open");
document.body.style.paddingRight = "";
// Nascondi l'overlay
const overlay = document.querySelector(
".overlay.toggle-icon",
);
if (overlay) {
overlay.style.display = "none";
} }
showBatchConfirm(eligibleRows);
}
});
observer.observe(
document.querySelector(".grid-container"),
{
subtree: true,
attributes: true,
attributeFilter: ["class"],
},
);
saveAllEl.click();
}, },
{ once: true }, { once: true },
); );
return;
} }
// Rimuovi il listener dopo l'esecuzione const eligibleRows = collectEligibleRows();
confirmBtn.removeEventListener("click", confirmHandler);
};
// Rimuovi eventuali listener precedenti if (eligibleRows.length === 0) {
confirmBtn.removeEventListener("click", confirmHandler); document.getElementById("exportResponseMessage").textContent =
confirmBtn.addEventListener("click", confirmHandler); "Tutte le righe sono già state esportate al LIMS.";
}); document.getElementById("exportResponseModalLabel").textContent =
"Export All";
const modal = new bootstrap.Modal(
document.getElementById("exportResponseModal"),
{ keyboard: false },
);
modal.show();
return;
}
showBatchConfirm(eligibleRows);
}); });
function startBatchExport(eligibleRows) {
batchCancelled = false;
batchRunning = true;
// Don't clear validation errors — they should stay visible for invalid rows
const batchUuid = crypto.randomUUID();
const total = eligibleRows.length;
let processed = 0;
let succeeded = 0;
let failed = 0;
// Disable all row buttons
disableAllRowButtons();
// Show inline status bar
const bar = document.getElementById("batchExportBar");
const statusEl = document.getElementById("batchExportStatus");
const cancelBtn = document.getElementById("exportBatchCancelBtn");
bar.style.display = "";
cancelBtn.disabled = false;
statusEl.textContent = `Esportazione 0 / ${total}...`;
// Cancel handler
cancelBtn.addEventListener(
"click",
() => {
batchCancelled = true;
statusEl.textContent =
"Annullamento... (attendi riga corrente)";
cancelBtn.disabled = true;
},
{ once: true },
);
(async () => {
for (let i = 0; i < eligibleRows.length; i++) {
if (batchCancelled) break;
const { iddatadb, row } = eligibleRows[i];
statusEl.textContent = `Esportazione ${processed + 1} / ${total} (id: ${iddatadb})...`;
// Highlight current row
setRowExporting(row, true);
try {
const data = await sendExport(iddatadb, row, batchUuid);
processed++;
if (data.success) {
succeeded++;
} else {
failed++;
showRowError(row, iddatadb, data.message || "Errore sconosciuto");
}
} catch (error) {
processed++;
failed++;
showRowError(row, iddatadb, error.message);
}
// Remove highlight from current row
setRowExporting(row, false);
}
// Finished
batchRunning = false;
enableAllRowButtons();
bar.style.display = "none";
// Show result modal
const msgEl = document.getElementById("exportResponseMessage");
const labelEl = document.getElementById("exportResponseModalLabel");
if (batchCancelled) {
labelEl.textContent = "Export All — Annullato";
msgEl.innerHTML =
`Esportate: <strong>${succeeded}</strong><br>` +
`Errori: <strong>${failed}</strong><br>` +
`Non processate: <strong>${total - processed}</strong>`;
} else if (failed === 0) {
labelEl.textContent = "Export All — Completato";
msgEl.innerHTML = `Tutte le <strong>${succeeded}</strong> righe esportate con successo.`;
} else {
labelEl.textContent = "Export All — Completato con errori";
msgEl.innerHTML =
`Esportate: <strong>${succeeded}</strong><br>` +
`Errori: <strong>${failed}</strong>`;
}
const modalEl = document.getElementById("exportResponseModal");
const modal = new bootstrap.Modal(modalEl, { keyboard: false });
modal.show();
modalEl.addEventListener("hidden.bs.modal", cleanupBackdrop, { once: true });
})();
}
}); });
+454 -18
View File
@@ -13,9 +13,31 @@ if (!is_dir($logDir)) {
mkdir($logDir, 0755, true); mkdir($logDir, 0755, true);
} }
$uploadDir = realpath(__DIR__ . '/../photostrf') . '/';
// 🔹 Base URL API // 🔹 Base URL API
$apiBaseUrl = 'https://93.43.5.102/limsapi/api/odata/'; $apiBaseUrl = 'https://93.43.5.102/limsapi/api/odata/';
// 🔹 Batch UUID — if present, all logs go to a single file
$batchUuid = $_POST['batch_uuid'] ?? null;
$writeLog = (function () use ($batchUuid, $logDir) {
$batchLogFile = $batchUuid ? $logDir . "batch_export_{$batchUuid}.log" : null;
return function ($individualPath, $content, $stepLabel = null) use ($batchLogFile) {
if ($batchLogFile) {
$header = "\n" . str_repeat("=", 60) . "\n";
if ($stepLabel) {
$header .= "[{$stepLabel}] " . date('Y-m-d H:i:s') . "\n";
}
$header .= str_repeat("=", 60) . "\n";
file_put_contents($batchLogFile, $header . $content . "\n", FILE_APPEND);
} else {
file_put_contents($individualPath, $content);
}
};
})();
// 🔹 Funzione per validare e convertire date // 🔹 Funzione per validare e convertire date
function validateDate($value) function validateDate($value)
{ {
@@ -27,15 +49,81 @@ function validateDate($value)
return null; // Imposta null se non è una data valida return null; // Imposta null se non è una data valida
} }
// 🔹 Funzione per validare e convertire date
function formatDateToExport($value)
{
$date = DateTime::createFromFormat('Y-m-d', $value) ?: DateTime::createFromFormat('Y-m-d H:i:s', $value);
if ($date) {
return $date->format('d/m/Y');
}
return null; // Imposta null se non è una data valida
}
// ImportaCommessa con retry: la chiamata è asincrona lato LIMS e a volte
// risponde 200 senza importare (StatoCommessaWeb resta "Inviata"/"Nuova").
// Riprova con backoff esponenziale finché non passa a "Elaborata".
function importaCommessaWithRetry($api, $commessaId, array $payload, $maxRetries = 3, $initialBackoff = 1)
{
$result = null;
$stato = null;
$succeeded = false;
$log = "";
$backoff = $initialBackoff;
set_time_limit(120); // i backoff non devono far scadere il timeout della richiesta
for ($attempt = 1; $attempt <= $maxRetries + 1; $attempt++) {
try {
$result = $api->post("CommessaWeb({$commessaId})/ImportaCommessa", $payload);
$stato = $result['StatoCommessaWeb'] ?? null;
$log .= "Attempt {$attempt}: HTTP 200, StatoCommessaWeb=" . ($stato ?? 'null') . "\n";
} catch (Exception $e) {
$stato = null;
$log .= "Attempt {$attempt}: EXCEPTION " . $e->getMessage() . "\n";
}
if ($stato === 'Elaborata') {
$succeeded = true;
break;
}
if ($attempt <= $maxRetries) {
$log .= " -> not Elaborata, waiting {$backoff}s before retry\n";
sleep($backoff);
$backoff *= 2;
}
}
return [
'succeeded' => $succeeded,
'stato' => $stato,
'result' => $result,
'log' => $log,
];
}
try { try {
$iddatadb = $_POST['iddatadb'] ?? null; $iddatadb = $_POST['iddatadb'] ?? null;
if (!$iddatadb) { if (!$iddatadb) {
throw new Exception("Missing iddatadb"); throw new Exception("Missing iddatadb");
} }
// TEMP: simulate error on every other row for testing
if (env('SIMULATE_EXPORT_LIMS') && $iddatadb % 2 === 0) {
throw new Exception("Simulated error for iddatadb $iddatadb");
}
// 🔹 STEP 1+2: Fetch Cliente ID from datadb and Schema ID from excel_templates // 🔹 STEP 1+2: Fetch Cliente ID from datadb and Schema ID from excel_templates
// Also fetch fixed fields stored in datadb
$stmt = $pdo->prepare(" $stmt = $pdo->prepare("
SELECT d.idclient AS clienteId, et.idschema AS schemaId SELECT d.idclient AS clienteId, et.idschema AS schemaId,
d.cliente_responsabile_id,
d.moltiplicatore_prezzo_id,
d.anagrafica_certest_object_id,
d.anagrafica_certest_service_id,
d.cliente_fornitore_id,
d.clienteAnalisi,
d.consegna_richiesta
FROM datadb as d FROM datadb as d
INNER JOIN excel_templates as et ON d.templateid = et.id INNER JOIN excel_templates as et ON d.templateid = et.id
WHERE d.iddatadb = :iddatadb WHERE d.iddatadb = :iddatadb
@@ -51,15 +139,60 @@ try {
$clienteId = (int) $result['clienteId']; $clienteId = (int) $result['clienteId'];
$schemaId = (int) $result['schemaId']; $schemaId = (int) $result['schemaId'];
// 🔹 STEP 3: Fetch Parts (including idmatrice) // Extract fixed fields (nullable INTs)
$clienteResponsabile = !empty($result['cliente_responsabile_id']) ? (int) $result['cliente_responsabile_id'] : null;
$moltiplicatorePrezzo = !empty($result['moltiplicatore_prezzo_id']) ? (int) $result['moltiplicatore_prezzo_id'] : null;
$anagraficaObject = !empty($result['anagrafica_certest_object_id']) ? (int) $result['anagrafica_certest_object_id'] : null;
$anagraficaService = !empty($result['anagrafica_certest_service_id']) ? (int) $result['anagrafica_certest_service_id'] : null;
$clienteFornitore = !empty($result['cliente_fornitore_id']) ? (int) $result['cliente_fornitore_id'] : null;
$clienteAnalisi = !empty($result['clienteAnalisi']) ? (int) $result['clienteAnalisi'] : null;
$consegnaRichiesta = !empty($result['consegna_richiesta']) ? $result['consegna_richiesta'] : null;
// 🔹 STEP 3: Fetch Parts (including idmatrice and part id for custom fields)
$stmt = $pdo->prepare(" $stmt = $pdo->prepare("
SELECT part_number, part_description, material, color, mix, idmatrice SELECT id AS part_id, part_number, part_description, material, color, mix, idmatrice, dateexpiry
FROM identification_parts FROM identification_parts
WHERE iddatadb = :iddatadb WHERE iddatadb = :iddatadb
AND part_description IS NOT NULL
AND TRIM(part_description) <> ''
ORDER BY CAST(part_number AS UNSIGNED) ASC, part_number ASC
"); ");
$stmt->execute(['iddatadb' => $iddatadb]); $stmt->execute(['iddatadb' => $iddatadb]);
$parts = $stmt->fetchAll(PDO::FETCH_ASSOC); $parts = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 🔹 STEP 3.1: Fetch custom field values per part from identification_parts_customfields
$partIds = array_column($parts, 'part_id');
$partsCustomFields = []; // part_id => [ { field_id, value_id, value_text }, ... ]
if (!empty($partIds)) {
$placeholders = implode(',', array_fill(0, count($partIds), '?'));
$cfStmt = $pdo->prepare("
SELECT part_id, field_id, value_id, value_text
FROM identification_parts_customfields
WHERE part_id IN ({$placeholders})
");
$cfStmt->execute($partIds);
foreach ($cfStmt->fetchAll(PDO::FETCH_ASSOC) as $cfRow) {
$partsCustomFields[(int)$cfRow['part_id']][] = $cfRow;
}
}
// 🔹 STEP 4a: Auto-populate export_date / export_time fields
$stmt = $pdo->prepare("
UPDATE import_data_details idd
JOIN template_mapping m ON idd.mapping_id = m.id
SET idd.field_value = CASE m.auto_value
WHEN 'export_date' THEN :export_date
WHEN 'export_time' THEN :export_time
END
WHERE idd.id = :iddatadb
AND m.auto_value IN ('export_date', 'export_time')
");
$stmt->execute([
'iddatadb' => $iddatadb,
'export_date' => date('Y-m-d'),
'export_time' => date('H:i'),
]);
// 🔹 STEP 4: Fetch Field Values with Labels // 🔹 STEP 4: Fetch Field Values with Labels
$stmt = $pdo->prepare(" $stmt = $pdo->prepare("
SELECT SELECT
@@ -93,12 +226,21 @@ try {
// 🔹 Initialize API client // 🔹 Initialize API client
$api = VisualLimsApiClient::getInstance(); $api = VisualLimsApiClient::getInstance();
// 🔹 STEP 5: Create CommessaWeb (NOT WebOrder) // 🔹 STEP 5: Create CommessaWeb
// Fixed fields are sent as direct properties — they are navigation properties on Commessa
// accepted by the XAF API even though not explicitly listed in OData $metadata
$commessaWebPayload = [ $commessaWebPayload = [
"Cliente" => $clienteId, "Cliente" => $clienteId,
"SchemaCustomField" => $schemaId, "SchemaCustomField" => $schemaId,
"Richiedente" => "Test Web Import", "Richiedente" => "From TRFSmart Application", // TODO: replace with real value
"Descrizione" => "TEST CommessaWeb", "Descrizione" => "From TRFSmart Application", // TODO: replace with real value
"ClienteResponsabile" => $clienteResponsabile,
"MoltiplicatorePrezzo" => $moltiplicatorePrezzo,
"AnagraficaCertestObject" => $anagraficaObject,
"AnagraficaCertestService" => $anagraficaService,
"ClienteFornitore" => $clienteFornitore, // PLACEHOLDER — to be implemented
"ClienteAnalisi" => $clienteAnalisi, // PLACEHOLDER — to be implemented
// DeliveryRequest goes to Campione, not CommessaWeb
]; ];
// Costruisci log curl-like per STEP 5 // Costruisci log curl-like per STEP 5
@@ -114,7 +256,7 @@ try {
// Salva log // Salva log
$logFileStep5 = $logDir . "commessa_create_step5_" . $iddatadb . "_" . time() . ".txt"; $logFileStep5 = $logDir . "commessa_create_step5_" . $iddatadb . "_" . time() . ".txt";
file_put_contents($logFileStep5, $logContentStep5); $writeLog($logFileStep5, $logContentStep5, "STEP 5 - Create CommessaWeb (iddatadb={$iddatadb})");
$commessaId = $commessaWeb["IdCommessa"]; $commessaId = $commessaWeb["IdCommessa"];
$commessaWebCode = substr($commessaWeb["CodiceCommessa"] ?? "TEST CommessaWeb", 0, 30); // Limite a 30 caratteri $commessaWebCode = substr($commessaWeb["CodiceCommessa"] ?? "TEST CommessaWeb", 0, 30); // Limite a 30 caratteri
@@ -135,7 +277,9 @@ try {
"Matrice" => $matriceId, "Matrice" => $matriceId,
"SottoMatrice" => null, "SottoMatrice" => null,
"SchemaCustomField" => $schemaId, "SchemaCustomField" => $schemaId,
"NoteWeb" => $part["part_description"] ?? "" // "Riferimento" => $part["part_description"] ?? "",
"NoteWeb" => $part["part_description"] ?? "",
"ConsegnaRichiesta" => !empty($part["dateexpiry"]) ? $part["dateexpiry"] : $consegnaRichiesta,
]; ];
// Costruisci curl-like per questo campione // Costruisci curl-like per questo campione
@@ -160,20 +304,256 @@ try {
// Salva log per STEP 6 // Salva log per STEP 6
$logFileStep6 = $logDir . "commessa_{$commessaId}_campioni_step6_" . time() . ".txt"; $logFileStep6 = $logDir . "commessa_{$commessaId}_campioni_step6_" . time() . ".txt";
file_put_contents($logFileStep6, $logContentStep6); $writeLog($logFileStep6, $logContentStep6, "STEP 6 - Campioni (commessa={$commessaId})");
// 🔹 STEP 6.0: PATCH each Campione custom fields:
// - field 189 (Tested Component) = part_description
// - additional fields from identification_parts_customfields (field_id → value_id/value_text)
$logContentStep63 = "";
foreach ($campioni as $index => $campione) {
$campioneId = (int)($campione['IdCampione'] ?? 0);
$partDescription = $parts[$index]['part_description'] ?? '';
$partId = (int)($parts[$index]['part_id'] ?? 0);
if ($campioneId <= 0) {
continue;
}
// Build a map of overrides: IdCustomField => value
$overrides = [];
// Override 1: Tested Component (field 189) = part_description
if ($partDescription !== '') {
$overrides[189] = $partDescription;
}
// Override 2: values from identification_parts_customfields
$partCFs = $partsCustomFields[$partId] ?? [];
foreach ($partCFs as $pcf) {
$cfFieldId = (int)$pcf['field_id'];
$cfValue = $pcf['value_text'] ?? $pcf['value_id'] ?? null;
if ($cfFieldId > 0 && $cfValue !== null && $cfValue !== '') {
$overrides[$cfFieldId] = (string)$cfValue;
}
}
// Skip if nothing to override
if (empty($overrides)) {
continue;
}
// GET campione custom fields
$campioneWithFields = $api->get("Campione({$campioneId})?\$expand=CampioniCustomFields(\$expand=CustomField)");
$logContentStep63 .= "GET Campione({$campioneId}) CustomFields:\n" .
json_encode($campioneWithFields['CampioniCustomFields'] ?? [], JSON_PRETTY_PRINT) . "\n\n";
$logContentStep63 .= "Overrides for part {$partId}: " . json_encode($overrides) . "\n\n";
// Build PATCH payload — apply overrides where IdCustomField matches
$campioniCustomFields = [];
foreach ($campioneWithFields["CampioniCustomFields"] ?? [] as $cf) {
$definitionId = (int)($cf["CustomField"]["IdCustomField"] ?? 0);
$fieldInstanceId = (int)$cf["IdCampioniCustomFields"];
$currentValue = $cf["Valore"] ?? '';
$newValue = $overrides[$definitionId] ?? $currentValue;
$campioniCustomFields[] = [
"IdCampioniCustomFields" => $fieldInstanceId,
"Valore" => $newValue
];
}
if (!empty($campioniCustomFields)) {
$patchPayload = ["CampioniCustomFields" => $campioniCustomFields];
$patchJson = json_encode($patchPayload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$logContentStep63 .= "PATCH Campione({$campioneId}):\n" .
"curl --location --request PATCH '{$apiBaseUrl}Campione({$campioneId})' \\\n" .
"--header 'Content-Type: application/json' \\\n" .
"--header 'Authorization: Bearer ••••••' \\\n" .
"--data '{$patchJson}'\n\n";
$patchResult = $api->patch("Campione({$campioneId})", $patchPayload);
$logContentStep63 .= "RESPONSE:\n" . json_encode($patchResult, JSON_PRETTY_PRINT) . "\n\n---\n";
}
}
$logFileStep63 = $logDir . "commessa_{$commessaId}_campioni_customfields_step60_" . time() . ".txt";
$writeLog($logFileStep63, $logContentStep63, "STEP 6.0 - Campioni CustomFields (commessa={$commessaId})");
// 🔹 STEP 6.1: Fetch photos linked to this iddatadb
$stmtPhotos = $pdo->prepare("
SELECT id, file_path, file_name, StampaNelRapporto, PrimaPagina
FROM datadb_photos
WHERE iddatadb = :iddatadb
ORDER BY id ASC
");
$stmtPhotos->execute(['iddatadb' => $iddatadb]);
$photos = $stmtPhotos->fetchAll(PDO::FETCH_ASSOC);
// 🔹 STEP 6.2: Upload photos to Campione .01 (fetched from API)
$photosUploaded = 0;
$logContentPhotos = "Photos for CommessaWeb {$commessaId} (iddatadb={$iddatadb}):\n";
$logContentPhotos .= "Total photos found: " . count($photos) . ", campioni: " . count($campioni) . "\n\n";
if (!empty($campioni) && !empty($photos)) {
// Fetch campioni list from API to find the .01 campione
$commessaCampioni = $api->get("CommessaWeb({$commessaId})?\$expand=Campioni");
$apiCampioni = $commessaCampioni['Campioni'] ?? [];
// Sort by CodiceCampione to find .01
usort($apiCampioni, fn($a, $b) => strcmp($a['CodiceCampione'] ?? '', $b['CodiceCampione'] ?? ''));
$mainCampione = $apiCampioni[0] ?? null;
$campioneId = (int)($mainCampione['IdCampione'] ?? 0);
$logContentPhotos .= "API Campioni order:\n";
foreach ($apiCampioni as $ac) {
$logContentPhotos .= " - {$ac['CodiceCampione']} (IdCampione: {$ac['IdCampione']})\n";
}
$logContentPhotos .= "Selected .01 campione: IdCampione={$campioneId}\n\n";
if ($campioneId > 0) {
$logContentPhotos .= "=== Campione {$campioneId} (main) ===\n";
foreach ($photos as $photo) {
$photoPath = $uploadDir . '/' . ltrim($photo['file_path'], './');
$fullPath = realpath($photoPath);
if (!$fullPath || !file_exists($fullPath)) {
$logContentPhotos .= "SKIP (file not found): {$photoPath}\n";
continue;
}
$photoEndpoint = "Campione({$campioneId})/UploadCampioneFile";
$stampaNelRapporto = !empty($photo['StampaNelRapporto']);
$primaPagina = !empty($photo['PrimaPagina']);
$logContentPhotos .= "curl --location --request POST '{$apiBaseUrl}{$photoEndpoint}' \\\n" .
"--header 'Authorization: Bearer ••••••' \\\n" .
"--form 'file=@{$fullPath}'\n\n";
// Step 1: Upload file (flags are ignored by API during upload)
$photoResult = $api->postMultipart($photoEndpoint, $fullPath, $photo['file_name']);
$logContentPhotos .= "UPLOAD RESPONSE:\n" . json_encode($photoResult, JSON_PRETTY_PRINT) . "\n\n";
// Step 2: PATCH CampioneFile to set flags (StampaNelRapporto, PrimaPagina)
$campioneFileId = (int)($photoResult['IdCampioneFile'] ?? 0);
if ($campioneFileId > 0 && ($stampaNelRapporto || $primaPagina)) {
$patchPayload = [];
if ($stampaNelRapporto) {
$patchPayload['StampaNelRapporto'] = true;
}
if ($primaPagina) {
$patchPayload['PrimaPagina'] = true;
}
$patchEndpoint = "CampioneFile({$campioneFileId})";
$patchJsonLog = json_encode($patchPayload, JSON_PRETTY_PRINT);
$logContentPhotos .= "curl --location --request PATCH '{$apiBaseUrl}{$patchEndpoint}' \\\n" .
"--header 'Content-Type: application/json' \\\n" .
"--header 'Authorization: Bearer ••••••' \\\n" .
"--data '{$patchJsonLog}'\n\n";
$patchResult = $api->patch($patchEndpoint, $patchPayload);
$logContentPhotos .= "PATCH RESPONSE:\n" . json_encode($patchResult, JSON_PRETTY_PRINT) . "\n\n";
}
$logContentPhotos .= "---\n";
$photosUploaded++;
}
} else {
$logContentPhotos .= "SKIP: main campione has invalid IdCampione\n";
}
} elseif (empty($campioni)) {
$logContentPhotos .= "SKIP: no campioni created, cannot upload photos\n";
}
$logFilePhotos = $logDir . "commessa_{$commessaId}_photos_step5_2_" . time() . ".txt";
$writeLog($logFilePhotos, $logContentPhotos, "STEP 6.2 - Photos (commessa={$commessaId})");
// 🔹 STEP 6.3: Add Analyses (AnalisiCampione) via Campione({id})/AddAnalisi bound action
$stmt = $pdo->prepare("
SELECT part_id, analysis_recordkey, analysis_name, analysis_method
FROM identification_parts_analyses
WHERE iddatadb = :iddatadb
ORDER BY part_id, id
");
$stmt->execute(['iddatadb' => $iddatadb]);
$analysesRows = $stmt->fetchAll(PDO::FETCH_ASSOC);
$partIdToIndex = [];
foreach ($parts as $idx => $part) {
$partIdToIndex[(int)$part['part_id']] = $idx;
}
$totalAnalyses = count($analysesRows);
$addedAnalyses = 0;
$failedAnalyses = [];
$logContentStep63Analisi = "Analyses for iddatadb={$iddatadb}: total={$totalAnalyses}\n\n";
foreach ($analysesRows as $a) {
$partId = (int)$a['part_id'];
$recordKey = trim((string)($a['analysis_recordkey'] ?? ''));
$idx = $partIdToIndex[$partId] ?? null;
if ($idx === null || !isset($campioni[$idx]) || $recordKey === '') {
$logContentStep63Analisi .= "SKIP (no campione for part_id={$partId} / empty recordkey): '{$recordKey}'\n";
continue;
}
$campioneId = (int)($campioni[$idx]['IdCampione'] ?? 0);
if ($campioneId <= 0) {
$logContentStep63Analisi .= "SKIP (invalid IdCampione for part_id={$partId}): '{$recordKey}'\n";
continue;
}
$payload = ['RecordKey' => $recordKey];
$jsonPayload = json_encode($payload, JSON_UNESCAPED_SLASHES);
$logContentStep63Analisi .= "curl --location --request POST '{$apiBaseUrl}Campione({$campioneId})/AddAnalisi' \\\n" .
"--header 'Content-Type: application/json' \\\n" .
"--header 'Authorization: Bearer ••••••' \\\n" .
"--data '{$jsonPayload}'\n";
try {
$result = $api->post("Campione({$campioneId})/AddAnalisi", $payload);
$logContentStep63Analisi .= "OK (part_id={$partId}, campione={$campioneId}): " .
($a['analysis_name'] ?? '') . "\n---\n";
$addedAnalyses++;
} catch (Exception $e) {
$errMsg = $e->getMessage();
$logContentStep63Analisi .= "FAIL: {$errMsg}\n---\n";
$failedAnalyses[] = [
'part_id' => $partId,
'campione_id' => $campioneId,
'analysis_recordkey' => $recordKey,
'analysis_name' => $a['analysis_name'] ?? '',
'error' => $errMsg,
];
}
}
$logFileStep63Analisi = $logDir . "commessa_{$commessaId}_analyses_step63_" . time() . ".txt";
$writeLog($logFileStep63Analisi, $logContentStep63Analisi, "STEP 6.3 - AddAnalisi (commessa={$commessaId})");
// 🔹 STEP 7: Update Custom Fields for CommessaWeb // 🔹 STEP 7: Update Custom Fields for CommessaWeb
if (!empty($fieldValues)) { if (!empty($fieldValues)) {
// GET con espansione per CustomField // GET con espansione per CustomField
$expand = "CommesseCustomFields(\$expand=CustomField)"; $expand = "CommesseCustomFields(\$expand=CustomField)";
$commessaWithFields = $api->get("CommessaWeb(" . $commessaId . ")?\$expand=" . $expand); $commessaWithFields = $api->get("CommessaWeb({$commessaId})?\$expand={$expand}");
// Logga il GET // Logga il GET
$logContentGet = "curl --location --request GET '{$apiBaseUrl}CommessaWeb({$commessaId})?\$expand=CommesseCustomFields(\$expand=CustomField)' \\\n" . $logContentGet = "curl --location --request GET '{$apiBaseUrl}CommessaWeb({$commessaId})?\$expand=CommesseCustomFields(\$expand=CustomField)' \\\n" .
"--header 'Authorization: Bearer ••••••'\n\n" . "--header 'Authorization: Bearer ••••••'\n\n" .
"RESPONSE:\n" . json_encode($commessaWithFields, JSON_PRETTY_PRINT); "RESPONSE:\n" . json_encode($commessaWithFields, JSON_PRETTY_PRINT);
$logFileGet = $logDir . "commessa_{$commessaId}_get_step7_" . time() . ".txt"; $logFileGet = $logDir . "commessa_{$commessaId}_get_step7_" . time() . ".txt";
file_put_contents($logFileGet, $logContentGet); $writeLog($logFileGet, $logContentGet, "STEP 7 - GET CustomFields (commessa={$commessaId})");
// Prepara payload PATCH // Prepara payload PATCH
$commessaCustomFields = []; $commessaCustomFields = [];
@@ -186,7 +566,7 @@ try {
// Valida se il campo è di tipo Data // Valida se il campo è di tipo Data
if ($fieldType === 'Data' && $newValue !== $currentValue) { if ($fieldType === 'Data' && $newValue !== $currentValue) {
$newValue = validateDate($newValue); $newValue = formatDateToExport($newValue);
} }
$commessaCustomFields[] = [ $commessaCustomFields[] = [
@@ -195,6 +575,7 @@ try {
]; ];
} }
$logFileStep7 = null;
if (!empty($commessaCustomFields)) { if (!empty($commessaCustomFields)) {
$updatePayload = ["CommesseCustomFields" => $commessaCustomFields]; $updatePayload = ["CommesseCustomFields" => $commessaCustomFields];
@@ -209,7 +590,7 @@ try {
$logContentStep7 .= "\n\nRESPONSE:\n" . json_encode($patchResponse, JSON_PRETTY_PRINT); $logContentStep7 .= "\n\nRESPONSE:\n" . json_encode($patchResponse, JSON_PRETTY_PRINT);
$logFileStep7 = $logDir . "commessa_{$commessaId}_update_step7_" . time() . ".txt"; $logFileStep7 = $logDir . "commessa_{$commessaId}_update_step7_" . time() . ".txt";
file_put_contents($logFileStep7, $logContentStep7); $writeLog($logFileStep7, $logContentStep7, "STEP 7 - PATCH CustomFields (commessa={$commessaId})");
} }
} }
@@ -226,7 +607,7 @@ try {
]); ]);
// 🔹 STEP 9: Send CommessaWeb to laboratory (commentato come richiesto) // 🔹 STEP 9: Send CommessaWeb to laboratory (commentato come richiesto)
/*
$sendResult = $api->post("CommessaWeb({$commessaId})/InviaCommessa", []); $sendResult = $api->post("CommessaWeb({$commessaId})/InviaCommessa", []);
// Logga il POST // Logga il POST
@@ -236,8 +617,35 @@ try {
"--data '{}'\n\n" . "--data '{}'\n\n" .
"RESPONSE:\n" . json_encode($sendResult, JSON_PRETTY_PRINT); "RESPONSE:\n" . json_encode($sendResult, JSON_PRETTY_PRINT);
$logFileStep9 = $logDir . "commessa_{$commessaId}_send_step9_" . time() . ".txt"; $logFileStep9 = $logDir . "commessa_{$commessaId}_send_step9_" . time() . ".txt";
file_put_contents($logFileStep9, $logContentStep9); $writeLog($logFileStep9, $logContentStep9, "STEP 9 - InviaCommessa (commessa={$commessaId})");
*/
// 🔹 STEP 9.5: Importazione da CommessaWeb a Commessa (con retry)
// 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
];
$importPayloadLog = json_encode($importPayload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$importOutcome = importaCommessaWithRetry($api, $commessaId, $importPayload);
$importResult = $importOutcome['result'];
$importStato = $importOutcome['stato'];
$importSucceeded = $importOutcome['succeeded'];
// Logga il POST (tutti i tentativi)
$logContentStep91 = "curl --location --request POST '{$apiBaseUrl}CommessaWeb({$commessaId})/ImportaCommessa' \\\n" .
"--header 'Content-Type: application/json' \\\n" .
"--header 'Authorization: Bearer ••••••' \\\n" .
"--data '{$importPayloadLog}'\n\n" .
"ATTEMPTS:\n" . $importOutcome['log'] . "\n" .
"SUCCEEDED: " . ($importSucceeded ? 'yes' : 'NO') . "\n\n" .
"LAST 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}, succeeded=" . ($importSucceeded ? '1' : '0') . ")");
// 🔹 STEP 10: GET di controllo post-PATCH // 🔹 STEP 10: GET di controllo post-PATCH
$expand = "CommesseCustomFields(\$expand=CustomField)"; $expand = "CommesseCustomFields(\$expand=CustomField)";
@@ -248,8 +656,24 @@ try {
"--header 'Authorization: Bearer ••••••'\n\n" . "--header 'Authorization: Bearer ••••••'\n\n" .
"RESPONSE:\n" . json_encode($commessaAfterPatch, JSON_PRETTY_PRINT); "RESPONSE:\n" . json_encode($commessaAfterPatch, JSON_PRETTY_PRINT);
$logFileStep10 = $logDir . "commessa_{$commessaId}_get_step10_" . time() . ".txt"; $logFileStep10 = $logDir . "commessa_{$commessaId}_get_step10_" . time() . ".txt";
file_put_contents($logFileStep10, $logContentStep10); $writeLog($logFileStep10, $logContentStep10, "STEP 10 - GET verify (commessa={$commessaId})");
// 🔹 STEP 10.1: Save final CodiceCommessa into datadb.commessaweb
// After ImportaCommessa, the API returns the final LIMS job code in CodiceCommessa.
// Example: CodiceCommessa = 2614795, CodiceCommessaWeb = 26C0103.
$finalCodiceCommessa = trim((string)($commessaAfterPatch['CodiceCommessa'] ?? ''));
if ($finalCodiceCommessa !== '') {
$stmt = $pdo->prepare("
UPDATE datadb
SET commessaweb = :commessaweb,
status = 'l'
WHERE iddatadb = :iddatadb
");
$stmt->execute([
'commessaweb' => substr($finalCodiceCommessa, 0, 30),
'iddatadb' => $iddatadb
]);
}
// 🔹 STEP 11: Prepare final response // 🔹 STEP 11: Prepare final response
$finalCommessa = [ $finalCommessa = [
"Cliente" => $clienteId, "Cliente" => $clienteId,
@@ -263,15 +687,24 @@ try {
echo json_encode([ echo json_encode([
"success" => true, "success" => true,
"idcommessaweb" => $commessaId,
"commessaweb" => $finalCodiceCommessa ?: $commessaWebCode,
"commessaWeb" => $finalCommessa, "commessaWeb" => $finalCommessa,
"commessaWebApiResponse" => $commessaWeb, // Incluso per debug "commessaWebApiResponse" => $commessaWeb, // Incluso per debug
"totalCampioni" => count($campioni), "totalCampioni" => count($campioni),
"totalCustomFields" => count($commessaAfterPatch["CommesseCustomFields"] ?? []), "totalCustomFields" => count($commessaAfterPatch["CommesseCustomFields"] ?? []),
"totalPhotos" => count($photos),
"totalAnalyses" => $totalAnalyses,
"addedAnalyses" => $addedAnalyses,
"failedAnalyses" => $failedAnalyses,
"message" => "Export successful", "message" => "Export successful",
"logFiles" => [ "logFiles" => [
"step5_create" => $logFileStep5, "step5_create" => $logFileStep5,
"step5_2_photos" => $logFilePhotos,
"step6_campioni" => $logFileStep6, "step6_campioni" => $logFileStep6,
"step7_patch" => $logFileStep7, "step63_analyses" => $logFileStep63Analisi,
"step7_patch" => $logFileStep7 ?? null,
"step9_1_importa" => $logFileStep91,
"step10_get" => $logFileStep10 "step10_get" => $logFileStep10
] ]
]); ]);
@@ -283,8 +716,11 @@ try {
"message" => "Export failed: " . $e->getMessage(), "message" => "Export failed: " . $e->getMessage(),
"logFiles" => [ "logFiles" => [
"step5_create" => $logFileStep5 ?? null, "step5_create" => $logFileStep5 ?? null,
"step5_2_photos" => $logFilePhotos ?? null,
"step6_campioni" => $logFileStep6 ?? null, "step6_campioni" => $logFileStep6 ?? null,
"step63_analyses" => $logFileStep63Analisi ?? null,
"step7_patch" => $logFileStep7 ?? null, "step7_patch" => $logFileStep7 ?? null,
"step9_1_importa" => $logFileStep91 ?? null,
"step10_get" => $logFileStep10 ?? null "step10_get" => $logFileStep10 ?? null
] ]
]); ]);
@@ -0,0 +1,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,68 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json');
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$idCliente = isset($_GET['id_cliente']) ? (int)$_GET['id_cliente'] : 0;
$idMatrice = isset($_GET['id_matrice']) ? (int)$_GET['id_matrice'] : 0;
$debug = isset($_GET['debug']) ? (int)$_GET['debug'] : 0;
if ($idCliente <= 0) {
http_response_code(400);
echo json_encode(['error' => 'Missing or invalid id_cliente']);
exit;
}
$api = VisualLimsApiClient::getInstance();
if ($idMatrice > 0) {
$expandExpr = "AnalisiAbilitate(\$filter=Matrice/IdMatrice eq $idMatrice)";
} else {
$expandExpr = "AnalisiAbilitate";
}
// Encode only the $expand expression because it contains spaces, slash and parentheses
$expandEncoded = rawurlencode($expandExpr);
$endpoint = "Cliente($idCliente)?\$expand={$expandEncoded}";
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
$full_url = $base_url . $endpoint;
file_put_contents(__DIR__ . '/last_url_analisi.txt', $full_url . PHP_EOL, FILE_APPEND);
$data = $api->get($endpoint, []);
file_put_contents(
__DIR__ . '/analisi_by_cliente_matrice_response.json',
json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
);
if ($debug === 1) {
echo json_encode([
'endpoint' => $endpoint,
'full_url' => $full_url,
'raw_response' => $data
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
}
$analisi = $data['AnalisiAbilitate'] ?? [];
echo json_encode([
'value' => $analisi,
'count' => is_array($analisi) ? count($analisi) : 0
], JSON_UNESCAPED_UNICODE);
} catch (Exception $e) {
file_put_contents(
__DIR__ . '/error_log_analisi.txt',
date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL,
FILE_APPEND
);
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
@@ -0,0 +1,56 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json');
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$idMatrice = isset($_GET['id_matrice']) ? (int)$_GET['id_matrice'] : 0;
if ($idMatrice <= 0) {
http_response_code(400);
echo json_encode(['error' => 'Missing or invalid id_matrice']);
exit;
}
$api = VisualLimsApiClient::getInstance();
/**
* OData hypothesis:
* - Expand enabled matrices
* - Return only selectable analyses
* - Include generic analyses OR analyses enabled for the given matrix
*
* This endpoint must be verified against the real VisualLims API metadata.
*/
$filter = rawurlencode("SelezionabileSuWeb eq true and (IsGenerico eq true or MatriciAbilitate/any(m:m/IdMatrice eq $idMatrice))");
$expand = rawurlencode("MatriciAbilitate");
$endpoint = "Analisi?\$top=10";
// Debug URL
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
$full_url = $base_url . $endpoint;
file_put_contents(__DIR__ . '/last_url_analisi.txt', $full_url . PHP_EOL, FILE_APPEND);
$data = $api->get($endpoint, []);
file_put_contents(
__DIR__ . '/analisi_by_matrice_response.json',
json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
);
$analisi = $data['value'] ?? [];
echo json_encode(['value' => $analisi], JSON_UNESCAPED_UNICODE);
} catch (Exception $e) {
file_put_contents(
__DIR__ . '/error_log_analisi.txt',
date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL,
FILE_APPEND
);
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
@@ -0,0 +1,68 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json');
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$idMatrice = isset($_GET['id_matrice']) ? (int)$_GET['id_matrice'] : 0;
$debug = isset($_GET['debug']) ? (int)$_GET['debug'] : 0;
if ($idMatrice <= 0) {
http_response_code(400);
echo json_encode(['error' => 'Missing or invalid id_matrice']);
exit;
}
$api = VisualLimsApiClient::getInstance();
$webOnly = isset($_GET['web_only']) ? (int)$_GET['web_only'] : 1;
$filterString = "Matrice/IdMatrice eq $idMatrice";
if ($webOnly === 1) {
$filterString .= " and SelezionabileSuWeb eq true";
}
$filter = rawurlencode($filterString);
$endpoint = "Analisi?\$filter={$filter}";
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
$full_url = $base_url . $endpoint;
file_put_contents(__DIR__ . '/last_url_analisi.txt', $full_url . PHP_EOL, FILE_APPEND);
$data = $api->get($endpoint, []);
file_put_contents(
__DIR__ . '/analisi_by_matrice_response.json',
json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
);
if ($debug === 1) {
echo json_encode([
'endpoint' => $endpoint,
'full_url' => $full_url,
'raw_response' => $data
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
}
$analisi = $data['value'] ?? [];
echo json_encode([
'value' => $analisi,
'count' => is_array($analisi) ? count($analisi) : 0
], JSON_UNESCAPED_UNICODE);
} catch (Exception $e) {
file_put_contents(
__DIR__ . '/error_log_analisi.txt',
date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL,
FILE_APPEND
);
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
+95
View File
@@ -0,0 +1,95 @@
<?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();
// Campione hardcoded del tuo esempio
$idCampione = 749027;
// Analisi che stai provando ad assegnare
$targetRecordKey = '11;1218320';
$targetIdAnalisi = '1218320';
// Endpoint: recupera il campione espandendo le analisi abilitate
$endpoint = "Campione($idCampione)";
// ATTENZIONE:
// nella tua classe normalmente le opzioni vengono trasformate in query string.
// Qui serve: ?$expand=AnalisiAbilitate
$options = [
'$expand' => 'AnalisiAbilitate'
];
// Debug: salva URL usato
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
$query = http_build_query($options);
$query = urldecode($query); // rende leggibile $expand invece di %24expand
$full_url = $base_url . $endpoint . ($query ? '?' . $query : '');
file_put_contents(__DIR__ . '/last_url_analisi_abilitate.txt', $full_url . PHP_EOL, FILE_APPEND);
// Chiamata API
$data = $api->get($endpoint, $options);
// Recupera AnalisiAbilitate dalla risposta
$analisiAbilitate = $data['AnalisiAbilitate'] ?? [];
// Alcune API OData possono restituire collection dentro "value"
if (isset($analisiAbilitate['value']) && is_array($analisiAbilitate['value'])) {
$analisiAbilitate = $analisiAbilitate['value'];
}
// Cerca se il RecordKey / IdAnalisi che stai usando è effettivamente assegnabile
$matches = [];
foreach ($analisiAbilitate as $analisi) {
$recordKey = isset($analisi['RecordKey']) ? (string)$analisi['RecordKey'] : '';
$idAnalisi = isset($analisi['IdAnalisi']) ? (string)$analisi['IdAnalisi'] : '';
if ($recordKey === $targetRecordKey || $idAnalisi === $targetIdAnalisi) {
$matches[] = $analisi;
}
}
// Output diagnostico
$output = [
'success' => true,
'idCampione' => $idCampione,
'request_url' => $full_url,
'targetRecordKey' => $targetRecordKey,
'targetIdAnalisi' => $targetIdAnalisi,
'enabled_analyses_count' => is_array($analisiAbilitate) ? count($analisiAbilitate) : 0,
'target_found' => count($matches) > 0,
'target_matches' => $matches,
'analisi_abilitate' => $analisiAbilitate,
'raw_response' => $data
];
// Salva il JSON in locale
file_put_contents(
__DIR__ . '/analisi_abilitate_campione_749027_response.json',
json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
);
echo json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
} catch (Exception $e) {
file_put_contents(
__DIR__ . '/error_log_analisi_abilitate.txt',
date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL,
FILE_APPEND
);
http_response_code(500);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
+88
View File
@@ -0,0 +1,88 @@
<?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();
// Campione hardcoded del tuo esempio
$idCampione = 749027;
// Matrice attesa dal log STEP 6
$expectedMatriceId = 6430;
// Endpoint con expand della Matrice
$endpoint = "Campione($idCampione)";
$options = [
'$expand' => 'Matrice'
];
// Debug URL
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
$query = http_build_query($options);
$queryReadable = urldecode($query);
$full_url = $base_url . $endpoint . ($queryReadable ? '?' . $queryReadable : '');
file_put_contents(
__DIR__ . '/last_url_check_matrice.txt',
$full_url . PHP_EOL,
FILE_APPEND
);
// Chiamata API
$data = $api->get($endpoint, $options);
// Recupero Matrice dalla response
$matrice = $data['Matrice'] ?? null;
$actualMatriceId = null;
if (is_array($matrice)) {
// Provo i nomi più probabili
$actualMatriceId = $matrice['IdMatrice']
?? $matrice['idMatrice']
?? $matrice['Id']
?? $matrice['ID']
?? null;
}
$matrice_ok = ((int)$actualMatriceId === (int)$expectedMatriceId);
$output = [
'success' => true,
'idCampione' => $idCampione,
'expectedMatriceId' => $expectedMatriceId,
'actualMatriceId' => $actualMatriceId,
'matrice_ok' => $matrice_ok,
'request_url' => $full_url,
'matrice' => $matrice,
'raw_response' => $data
];
// Salva JSON completo
file_put_contents(
__DIR__ . '/check_matrice_campione_749027_response.json',
json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
);
echo json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
} catch (Exception $e) {
file_put_contents(
__DIR__ . '/error_log_check_matrice.txt',
date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL,
FILE_APPEND
);
http_response_code(500);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
], JSON_PRETTY_PRINT | 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()]);
}
+31 -3
View File
@@ -9,11 +9,28 @@ ini_set('display_errors', '0');
error_reporting(E_ALL); error_reporting(E_ALL);
try { try {
$api = VisualLimsApiClient::getInstance(); $api = VisualLimsApiClient::getInstance(); // also loads dotenv
// In simulate mode: return fake clients built from idclient values already in datadb.
// This ensures client dropdowns auto-select the correct row idclient, which in turn
// triggers the ClienteResponsabile select to populate via the mock.
if (($_ENV['SIMULATE_EXPORT_LIMS'] ?? '') === 'true') {
require_once __DIR__ . '/class/db-functions.php';
$pdo = DBHandlerSelect::getInstance()->getConnection();
$stmt = $pdo->query("SELECT DISTINCT idclient FROM datadb WHERE idclient IS NOT NULL AND idclient > 0 ORDER BY idclient ASC");
$ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
$fakeClients = array_map(fn($id) => [
'IdCliente' => (int) $id,
'Nominativo' => "Cliente Simulato {$id}",
'CodiceCliente' => "SIM_{$id}",
], $ids);
echo json_encode(['value' => $fakeClients]);
exit;
}
// Parametri OData // Parametri OData
$params = [ $params = [
'$select' => 'IdCliente,Nominativo,CodiceNazioneFatturazione', '$select' => 'IdCliente,Nominativo,CodiceCliente',
'$orderby' => 'Nominativo asc' '$orderby' => 'Nominativo asc'
]; ];
@@ -57,10 +74,21 @@ try {
throw new Exception("Massimo numero di tentativi raggiunto per $endpoint"); throw new Exception("Massimo numero di tentativi raggiunto per $endpoint");
} }
// Cache file (1 hour TTL)
$cacheFile = __DIR__ . '/cache/clienti.json';
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
readfile($cacheFile);
exit;
}
// Esegui la chiamata con retry // Esegui la chiamata con retry
$data = makeApiRequest($api, $endpoint); $data = makeApiRequest($api, $endpoint);
echo json_encode($data); $json = json_encode($data);
if (!is_dir(__DIR__ . '/cache')) mkdir(__DIR__ . '/cache', 0777, true);
file_put_contents($cacheFile, $json);
echo $json;
} catch (Exception $e) { } catch (Exception $e) {
http_response_code(500); http_response_code(500);
$errorResponse = [ $errorResponse = [
+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);
}
+14 -5
View File
@@ -21,16 +21,25 @@ try {
} }
$results = []; $results = [];
$cacheDir = __DIR__ . '/cache';
if (!is_dir($cacheDir)) mkdir($cacheDir, 0777, true);
foreach ($fieldIds as $customFieldId) { foreach ($fieldIds as $customFieldId) {
$endpoint = "CustomField($customFieldId)?\$expand=CustomFieldsValues"; $cacheFile = $cacheDir . '/customfield_' . $customFieldId . '.json';
$data = $api->get($endpoint);
$results[$customFieldId] = $data['CustomFieldsValues'] ?? []; // Use cache if fresh (1 hour)
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
$results[$customFieldId] = json_decode(file_get_contents($cacheFile), true);
continue;
} }
// Debug ფაილი $endpoint = "CustomField($customFieldId)?\$expand=CustomFieldsValues";
file_put_contents(__DIR__ . '/customfield_values_response.json', json_encode($results)); $data = $api->get($endpoint);
$values = $data['CustomFieldsValues'] ?? [];
$results[$customFieldId] = $values;
file_put_contents($cacheFile, json_encode($values));
}
echo json_encode($results); echo json_encode($results);
} catch (Exception $e) { } catch (Exception $e) {
+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()]);
}
@@ -0,0 +1,63 @@
<?php
require_once 'include/headscript.php';
header('Content-Type: application/json');
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => 'Invalid request method']);
exit;
}
$input = json_decode(file_get_contents('php://input'), true);
$template_id = isset($input['template_id']) ? (int)$input['template_id'] : 0;
$code = isset($input['code']) ? trim($input['code']) : '';
if ($template_id <= 0) {
http_response_code(400);
echo json_encode(['error' => 'Missing or invalid template_id']);
exit;
}
if ($code === '') {
http_response_code(400);
echo json_encode(['error' => 'Missing code']);
exit;
}
/*
* TODO: Replace this block with your real API call.
* Expected response for import_json.php:
* {
* "success": true,
* "reference": "CODE123",
* "json": { ... real JSON payload ... }
* }
*/
$sampleJson = [
'data' => [[
'type' => 'trf_data_request',
'id' => $code,
'attributes' => [
'trf_type' => 'apparel',
'service_required' => 'regular',
'submitter_information' => [
'submitter_type' => 'supplier'
]
]
]]
];
echo json_encode([
'success' => true,
'reference' => $code,
'json' => $sampleJson
]);
} catch (Throwable $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
+37 -50
View File
@@ -1,59 +1,46 @@
<?php <?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php'; // Risale a root/vendor/ require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
use Dotenv\Dotenv; require_once __DIR__ . '/class/db-functions.php';
include dirname(__DIR__) . '/../extra/auth.php';
if (!Auth::check()) { http_response_code(401); echo json_encode(['error' => 'Unauthorized']); exit; }
// Set JSON header
header('Content-Type: application/json'); header('Content-Type: application/json');
// Debug: Log the path where we expect the .env file
$envPath = dirname(__DIR__, 2);
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . ' - Expected .env path: ' . $envPath . PHP_EOL, FILE_APPEND);
// Carica il file .env dalla root del progetto
try { try {
$dotenv = Dotenv::createImmutable($envPath); // Read from matrici cache (populated by get_matrici.php / warm_cache.php)
$dotenv->load(); $cacheFile = __DIR__ . '/cache/matrici.json';
} catch (Exception $e) { if (file_exists($cacheFile)) {
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - Errore caricamento .env: ' . $e->getMessage() . PHP_EOL, FILE_APPEND); $data = json_decode(file_get_contents($cacheFile), true);
echo json_encode(['success' => false, 'message' => 'Errore caricamento configurazione: ' . $e->getMessage()]); $matrici = $data['value'] ?? [];
exit(1); } else {
} // Fallback: trigger cache creation
require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
$api = VisualLimsApiClient::getInstance();
$apiData = $api->get('Matrice');
$matrici = [];
foreach (($apiData['value'] ?? []) as $item) {
$nome = $item['NomeMatrice'] ?? '';
if ($nome !== '' && substr($nome, 0, 1) !== '*') {
$matrici[] = ['IdMatrice' => $item['IdMatrice'], 'NomeMatrice' => $nome, 'MacroMatrice' => $item['MacroMatrice'] ?? null];
}
}
if (!is_dir(__DIR__ . '/cache')) mkdir(__DIR__ . '/cache', 0777, true);
file_put_contents($cacheFile, json_encode(['success' => true, 'value' => $matrici]));
}
// Recupera le variabili d'ambiente // Extract unique MacroMatrice values
$dbHost = $_ENV['DB_HOST']; $macroValues = [];
$dbName = $_ENV['DB_DATABASE']; foreach ($matrici as $m) {
$dbUser = $_ENV['DB_USERNAME']; $macro = $m['MacroMatrice'] ?? null;
$dbPass = $_ENV['DB_PASSWORD']; if ($macro !== null && $macro !== '' && substr($macro, 0, 1) !== '*') {
$dbPrefix = $_ENV['DB_PREFIX']; $macroValues[$macro] = true;
}
}
$macroMatrici = array_keys($macroValues);
sort($macroMatrici);
// Debug: Log database connection details (excluding password)
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . " - DB Connection: host=$dbHost, dbname=$dbName, user=$dbUser, prefix=$dbPrefix" . PHP_EOL, FILE_APPEND);
// Connessione al database MySQL
try {
$pdo = new PDO("mysql:host=$dbHost;dbname=$dbName;charset=utf8mb4", $dbUser, $dbPass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - Errore connessione DB: ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
echo json_encode(['success' => false, 'message' => 'Errore connessione al database: ' . $e->getMessage()]);
exit(1);
}
try {
// Query per recuperare i valori distinti di MacroMatrice, escludendo quelli che iniziano con '*' e ordinandoli
$query = "SELECT DISTINCT MacroMatrice FROM {$dbPrefix}matrici WHERE MacroMatrice IS NOT NULL AND MacroMatrice NOT LIKE '*%' ORDER BY MacroMatrice ASC";
$stmt = $pdo->prepare($query);
$stmt->execute();
$macroMatrici = $stmt->fetchAll(PDO::FETCH_COLUMN);
// Debug: Log del numero di MacroMatrice recuperate
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . ' - Retrieved ' . count($macroMatrici) . ' MacroMatrice from database' . PHP_EOL, FILE_APPEND);
// Restituisci risposta JSON
echo json_encode(['success' => true, 'value' => $macroMatrici]); echo json_encode(['success' => true, 'value' => $macroMatrici]);
} catch (PDOException $e) { } catch (Exception $e) {
// Log errore e restituisci risposta di errore http_response_code(500);
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - Errore nel recupero delle MacroMatrice: ' . $e->getMessage() . PHP_EOL, FILE_APPEND); echo json_encode(['success' => false, 'message' => $e->getMessage()]);
echo json_encode(['success' => false, 'message' => 'Errore nel recupero delle MacroMatrice: ' . $e->getMessage()]);
exit(1);
} }
+47
View File
@@ -0,0 +1,47 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once __DIR__ . '/class/db-functions.php';
include dirname(__DIR__) . '/../extra/auth.php';
if (!Auth::check()) { http_response_code(401); echo json_encode(['error' => 'Unauthorized']); exit; }
require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json');
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
// Cache file (1 hour TTL)
$cacheFile = __DIR__ . '/cache/matrici.json';
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
readfile($cacheFile);
exit;
}
$api = VisualLimsApiClient::getInstance();
$data = $api->get('Matrice');
$matrici = [];
if (isset($data['value']) && is_array($data['value'])) {
foreach ($data['value'] as $item) {
$nome = $item['NomeMatrice'] ?? '';
if ($nome !== '' && substr($nome, 0, 1) !== '*') {
$matrici[] = [
'IdMatrice' => $item['IdMatrice'],
'NomeMatrice' => $nome,
'MacroMatrice' => $item['MacroMatrice'] ?? null,
];
}
}
usort($matrici, fn($a, $b) => strcasecmp($a['NomeMatrice'], $b['NomeMatrice']));
}
$json = json_encode(['success' => true, 'value' => $matrici]);
if (!is_dir(__DIR__ . '/cache')) mkdir(__DIR__ . '/cache', 0777, true);
file_put_contents($cacheFile, $json);
echo $json;
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
}
@@ -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()]);
}
+143
View File
@@ -0,0 +1,143 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json; charset=utf-8');
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$api = VisualLimsApiClient::getInstance();
$idCliente = isset($_GET['id_cliente']) ? (int)$_GET['id_cliente'] : 0;
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 3;
$signedStatus = trim($_GET['signed_status'] ?? 'all');
if ($idCliente <= 0) {
throw new Exception("Parametro id_cliente mancante o non valido.");
}
/*
* Allowed limits only.
* This prevents risky wide queries on the live LIMS.
*/
$allowedLimits = [1, 3, 5, 10];
if (!in_array($limit, $allowedLimits, true)) {
$limit = 3;
}
/*
* Allowed signature filters.
*/
$allowedSignedStatuses = ['all', 'signed', 'not_signed'];
if (!in_array($signedStatus, $allowedSignedStatuses, true)) {
$signedStatus = 'all';
}
/*
* Base filter by customer.
* We already verified that Rapporto can expand Cliente and returns Cliente.IdCliente.
*/
$filters = [
"Cliente/IdCliente eq {$idCliente}"
];
if ($signedStatus === 'signed') {
$filters[] = "Firmato eq true";
}
if ($signedStatus === 'not_signed') {
$filters[] = "Firmato eq false";
}
$filter = implode(' and ', $filters);
/*
* Important:
* - $top limits the number of reports.
* - $orderby=Data desc gets the latest reports first.
* - $expand=RapportiFiles retrieves only the PDF file metadata, not the binary PDF.
*/
$params = [
'$filter' => $filter,
'$select' => 'IdRapporto,CodiceRapporto,Data,Versione,Firmato,DataStampa',
'$expand' => 'RapportiFiles',
'$orderby' => 'Data desc',
'$top' => $limit
];
$endpoint = "Rapporto?" . http_build_query($params);
file_put_contents(
__DIR__ . '/last_rapporti_cliente_endpoint.txt',
'[' . date('Y-m-d H:i:s') . '] ' . $endpoint . PHP_EOL,
FILE_APPEND
);
$data = $api->get($endpoint);
$items = $data['value'] ?? [];
if (!is_array($items)) {
$items = [];
}
$reports = [];
foreach ($items as $item) {
$rapportiFiles = $item['RapportiFiles'] ?? [];
$pdfFiles = [];
if (is_array($rapportiFiles)) {
foreach ($rapportiFiles as $file) {
$idRapportoFile = intval($file['IdRapportoFile'] ?? 0);
if ($idRapportoFile > 0) {
$pdfFiles[] = [
'id_rapporto_file' => $idRapportoFile,
'file_name' => $file['FileName'] ?? null,
'categoria' => $file['Categoria'] ?? null,
'tipo_rapporto' => $file['TipoRapporto'] ?? null,
'download_url' => "download_rapporto_pdf.php?id_rapporto_file={$idRapportoFile}"
];
}
}
}
$reports[] = [
'id_rapporto' => $item['IdRapporto'] ?? null,
'codice_rapporto' => $item['CodiceRapporto'] ?? null,
'data' => $item['Data'] ?? null,
'data_stampa' => $item['DataStampa'] ?? null,
'versione' => $item['Versione'] ?? null,
'firmato' => $item['Firmato'] ?? null,
'pdf_files' => $pdfFiles
];
}
echo json_encode([
'success' => true,
'id_cliente' => $idCliente,
'limit' => $limit,
'signed_status' => $signedStatus,
'endpoint' => $endpoint,
'count' => count($reports),
'reports' => $reports
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
} catch (Exception $e) {
file_put_contents(
__DIR__ . '/error_log.txt',
date('Y-m-d H:i:s') . ' - get_rapporti_cliente.php - ' . $e->getMessage() . PHP_EOL,
FILE_APPEND
);
http_response_code(500);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
@@ -0,0 +1,256 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json; charset=utf-8');
ini_set('display_errors', '0');
error_reporting(E_ALL);
/**
* Uso:
* rapporto_full_by_codice.php?codice=2621521
* rapporto_full_by_codice.php?codice=2621521&download=1
*/
try {
$api = VisualLimsApiClient::getInstance();
$codiceRapporto = trim($_GET['codice'] ?? '');
$downloadPdf = isset($_GET['download']) && $_GET['download'] == '1';
if ($codiceRapporto === '') {
throw new Exception("Parametro codice mancante. Usa ?codice=2621521");
}
$debugDir = __DIR__ . '/logs/lims_rapporti/';
$pdfDir = __DIR__ . '/pdf/rapporti/';
if (!is_dir($debugDir)) mkdir($debugDir, 0755, true);
if (!is_dir($pdfDir)) mkdir($pdfDir, 0755, true);
$codiceRapportoSafe = str_replace("'", "''", $codiceRapporto);
/**
* STEP 1 - Search IdRapporto
*/
$searchParams = [
'$filter' => "CodiceRapporto eq '{$codiceRapportoSafe}'",
'$select' => 'IdRapporto,CodiceRapporto,Data,Versione,Firmato,DataStampa',
'$top' => 1
];
$searchEndpoint = "Rapporto?" . http_build_query($searchParams);
$searchData = $api->get($searchEndpoint);
$rapporti = $searchData['value'] ?? [];
if (empty($rapporti)) {
echo json_encode([
'success' => false,
'message' => 'Nessun rapporto trovato per questo CodiceRapporto.',
'codice_rapporto' => $codiceRapporto
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
exit;
}
$rapportoBase = $rapporti[0];
$idRapporto = intval($rapportoBase['IdRapporto'] ?? 0);
if (!$idRapporto) {
throw new Exception("IdRapporto non trovato nella risposta.");
}
/**
* STEP 2 - Dettaglio Rapporto
*/
$clienteExpandError = null;
$detailEndpoint = "Rapporto({$idRapporto})?\$expand=Cliente,RapportiFiles,CampioniDatiRapporto";
try {
$detailData = $api->get($detailEndpoint);
} catch (Exception $e) {
$clienteExpandError = $e->getMessage();
$detailEndpoint = "Rapporto({$idRapporto})?\$expand=RapportiFiles,CampioniDatiRapporto";
$detailData = $api->get($detailEndpoint);
}
file_put_contents(
$debugDir . "rapporto_{$codiceRapportoSafe}_light.json",
json_encode($detailData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
);
$cliente = $detailData['Cliente'] ?? null;
$rapportiFiles = $detailData['RapportiFiles'] ?? [];
$campioniDatiRapporto = $detailData['CampioniDatiRapporto'] ?? [];
// ====================== CLIENTE ======================
$clienteInfo = [
'found' => is_array($cliente),
'id_cliente' => $cliente['IdCliente'] ?? $cliente['Id'] ?? null,
'codice_cliente' => $cliente['CodiceCliente'] ?? $cliente['Codice'] ?? null,
'nome_cliente' => $cliente['Nominativo'] ?? $cliente['RagioneSociale'] ?? $cliente['Nome'] ?? $cliente['Descrizione'] ?? null,
'partita_iva' => $cliente['PartitaIva'] ?? null,
'codice_fiscale' => $cliente['CodiceFiscale'] ?? null
];
// ====================== FILE PDF ======================
$selectedRapportoFile = null;
$idRapportoFile = null;
$pdfFileName = $codiceRapporto . '.pdf';
$pdfFullPath = $pdfDir . $pdfFileName;
if (is_array($rapportiFiles) && count($rapportiFiles) > 0) {
foreach ($rapportiFiles as $file) {
$fileName = $file['FileName'] ?? '';
$tipoRapporto = $file['TipoRapporto'] ?? '';
$categoria = $file['Categoria'] ?? '';
if (
stripos($fileName, '.pdf') !== false ||
stripos($tipoRapporto, 'Rapporto') !== false ||
stripos($categoria, 'Rapporti') !== false
) {
$selectedRapportoFile = $file;
$idRapportoFile = $file['IdRapportoFile'] ?? null;
break;
}
}
if (!$selectedRapportoFile && count($rapportiFiles) > 0) {
$selectedRapportoFile = $rapportiFiles[0];
$idRapportoFile = $rapportiFiles[0]['IdRapportoFile'] ?? null;
}
}
// ====================== DOWNLOAD PDF - DEBUG AVANZATO ======================
$pdfDownloaded = false;
$pdfError = null;
$endpointUsato = null;
if ($downloadPdf && $idRapportoFile) {
$tentativi = [
"RapportoFile({$idRapportoFile})/\$value",
"RapportoFile({$idRapportoFile})/Contenuto/\$value",
"RapportoFile({$idRapportoFile})/File/\$value",
"Rapporto({$idRapporto})/RapportiFiles({$idRapportoFile})/\$value",
"Rapporto({$idRapporto})/RapportoFile/\$value",
"GetRapportoFile?IdRapportoFile={$idRapportoFile}",
"RapportoFile/Download?Id={$idRapportoFile}",
"Rapporto/DownloadFile?IdRapporto={$idRapporto}"
];
foreach ($tentativi as $ep) {
try {
$pdfContent = $api->getRaw($ep);
if (strlen($pdfContent) > 2000) { // PDF decente deve essere più grande
file_put_contents($pdfFullPath, $pdfContent);
$pdfDownloaded = true;
$endpointUsato = $ep;
$pdfError = "SUCCESSO con: " . $ep;
break;
} else {
$pdfError = "Contenuto troppo piccolo con: " . $ep;
}
} catch (Exception $e) {
$pdfError = "Fallito con " . $ep . "" . $e->getMessage();
}
}
}
// ====================== CAMPIONI + RATING (ripristinato dal tuo originale) ======================
$campioniSintesi = [];
$ratingFinale = null;
$ratingSource = null;
$hasIrregolare = false;
$hasPositiveResults = false;
if (is_array($campioniDatiRapporto)) {
foreach ($campioniDatiRapporto as $campione) {
$giudizioRapporto = $campione['GiudizioRapporto'] ?? null;
$giudizioCertificato = $campione['GiudizioCertificato'] ?? null;
$esitoGiudizioLMR = $campione['EsitoGiudizioLMR'] ?? null;
$esitoGiudizioImpiego = $campione['EsitoGiudizioImpiego'] ?? null;
$risultatiIrregolari = ($campione['RisultatiIrregolari'] ?? false) === true;
$risultatiPositivi = ($campione['RisultatiPositivi'] ?? false) === true;
$campioniSintesi[] = [
'id_campione_dati_rapporto' => $campione['IdCampioneDatiRapporto'] ?? null,
'codice_campione' => $campione['CodiceCampione'] ?? null,
'stato_campione' => $campione['StatoCampione'] ?? null,
'stato_campione_web' => $campione['StatoCampioneWeb'] ?? null,
'matrice' => $campione['Matrice'] ?? null,
'macro_matrice' => $campione['MacroMatrice'] ?? null,
'giudizio_rapporto' => $giudizioRapporto,
'giudizio_certificato' => $giudizioCertificato,
'esito_giudizio_lmr' => $esitoGiudizioLMR,
'esito_giudizio_impiego' => $esitoGiudizioImpiego,
'risultati_positivi' => $risultatiPositivi,
'risultati_irregolari' => $risultatiIrregolari
];
if (!$ratingFinale && !empty($giudizioRapporto)) {
$ratingFinale = $giudizioRapporto;
$ratingSource = 'CampioniDatiRapporto.GiudizioRapporto';
}
if (!$ratingFinale && !empty($giudizioCertificato)) {
$ratingFinale = $giudizioCertificato;
$ratingSource = 'CampioniDatiRapporto.GiudizioCertificato';
}
if ($risultatiIrregolari) $hasIrregolare = true;
if ($risultatiPositivi) $hasPositiveResults = true;
}
}
// Fallback rating
if (!$ratingFinale) {
if ($hasIrregolare) {
$ratingFinale = 'Irregolare';
$ratingSource = 'RisultatiIrregolari';
} elseif ($hasPositiveResults || count($campioniSintesi) > 0) {
$ratingFinale = 'Regolare';
$ratingSource = 'fallback';
}
}
// ====================== RISPOSTA FINALE ======================
echo json_encode([
'success' => true,
'codice_rapporto' => $codiceRapporto,
'id_rapporto' => $idRapporto,
'search_endpoint' => $searchEndpoint,
'detail_endpoint' => $detailEndpoint,
'cliente_expand_error' => $clienteExpandError,
'rapporto_base' => $rapportoBase,
'cliente' => $clienteInfo,
'rating_finale' => $ratingFinale,
'rating_source' => $ratingSource,
'rapporti_files_count' => count($rapportiFiles),
'selected_rapporto_file' => $selectedRapportoFile,
'pdf_file_name' => $pdfFileName,
'pdf_path' => $pdfDownloaded ? $pdfFullPath : null,
'pdf_downloaded' => $pdfDownloaded,
'pdf_error' => $pdfError,
'campioni_count' => count($campioniSintesi),
'campioni_sintesi' => $campioniSintesi,
'download_requested' => $downloadPdf
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
} catch (Exception $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
+118
View File
@@ -0,0 +1,118 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json; charset=utf-8');
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$api = VisualLimsApiClient::getInstance();
$commessa = trim($_GET['commessa'] ?? '');
if ($commessa === '') {
throw new Exception("Parametro commessa mancante");
}
$debugDir = __DIR__ . '/logs/lims_rapporto/';
if (!is_dir($debugDir)) {
mkdir($debugDir, 0755, true);
}
/**
* STEP 1
* Provo a cercare il rapporto con filtri leggeri.
* NIENTE expand pesanti.
*/
$attempts = [];
$filters = [
'CodiceCommessa' => "CodiceCommessa eq '{$commessa}'",
'Commessa_CodiceCommessa' => "Commessa/CodiceCommessa eq '{$commessa}'",
'Commessa_IdCommessa' => is_numeric($commessa) ? "Commessa/IdCommessa eq {$commessa}" : null,
'Codice' => "Codice eq '{$commessa}'"
];
foreach ($filters as $label => $filter) {
if (!$filter) {
continue;
}
try {
$options = [
'$filter' => $filter,
'$top' => 10
];
$data = $api->get('Rapporto', $options);
$attempts[$label] = [
'success' => true,
'filter' => $filter,
'records' => isset($data['value']) && is_array($data['value']) ? count($data['value']) : null,
'data' => $data
];
file_put_contents(
$debugDir . "rapporto_{$commessa}_{$label}.json",
json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
);
} catch (Exception $e) {
$attempts[$label] = [
'success' => false,
'filter' => $filter,
'error' => $e->getMessage()
];
}
}
/**
* STEP 2
* Prendo solo eventuali rapporti trovati.
*/
$rapportiFound = [];
foreach ($attempts as $label => $attempt) {
if (!$attempt['success']) {
continue;
}
$items = $attempt['data']['value'] ?? [];
if (!is_array($items)) {
continue;
}
foreach ($items as $item) {
$rapportiFound[] = [
'matched_by' => $label,
'rapporto' => $item
];
}
}
echo json_encode([
'success' => true,
'input_commessa' => $commessa,
'message' => 'Ricerca leggera su Rapporto completata. Se trovi un rapporto, poi recuperiamo RapportiFiles solo per quello.',
'rapporti_found_count' => count($rapportiFound),
'rapporti_found' => $rapportiFound,
'attempts_summary' => array_map(function ($a) {
return [
'success' => $a['success'],
'filter' => $a['filter'],
'records' => $a['records'] ?? null,
'error' => $a['error'] ?? null
];
}, $attempts)
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
} catch (Exception $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
+164 -10
View File
@@ -2,25 +2,179 @@
require_once dirname(__DIR__, 2) . '/vendor/autoload.php'; require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php'; require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json'); header('Content-Type: application/json; charset=utf-8');
ini_set('display_errors', '0'); ini_set('display_errors', '0');
error_reporting(E_ALL); error_reporting(E_ALL);
try { try {
$api = VisualLimsApiClient::getInstance(); $api = VisualLimsApiClient::getInstance();
$rapporto_id = 533329;
// Costruzione manuale dell'endpoint con espansione annidata // Esempi:
$endpoint = "Rapporto($rapporto_id)?\$expand=CampioniDatiRapporto(\$expand=AnalisiDatiRapporto,CustomFieldsDatiRapporto)"; // rapporto_by_codice_expand_step.php?codice=2541111&step=base
// rapporto_by_codice_expand_step.php?codice=2541111&step=files
// rapporto_by_codice_expand_step.php?codice=2541111&step=campioni
// rapporto_by_codice_expand_step.php?codice=2541111&step=files_campioni
// Non passiamo options, già incluso nell'endpoint $codiceRapporto = trim($_GET['codice'] ?? '');
$data = $api->get($endpoint); // Safe step mode: default is base, but allows controlled read-only steps
$step = trim($_GET['step'] ?? 'base');
file_put_contents(__DIR__ . '/rapporto_expanded.json', json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); if ($codiceRapporto === '') {
echo json_encode($data); throw new Exception("Parametro codice mancante. Usa ?codice=2541111");
}
$allowedSteps = [
'base' => '',
'campioni' => 'CampioniDatiRapporto',
'files' => 'RapportiFiles,Cliente',
'cliente' => 'Cliente'
];
if (!array_key_exists($step, $allowedSteps)) {
throw new Exception("Step non valido. Usa: " . implode(', ', array_keys($allowedSteps)));
}
// Escape OData per eventuali apostrofi
$codiceRapportoSafe = str_replace("'", "''", $codiceRapporto);
// Safe version of codice rapporto for filenames
$codiceRapportoFileSafe = preg_replace('/[^a-zA-Z0-9_-]/', '_', $codiceRapporto);
/*
* STEP 1 - Trova IdRapporto partendo da CodiceRapporto.
* Query leggera, con $select e $top=1.
*/
$searchParams = [
'$filter' => "CodiceRapporto eq '{$codiceRapportoSafe}'",
'$select' => 'IdRapporto,CodiceRapporto,Data,Versione,Firmato,DataStampa',
'$top' => 1
];
$searchEndpoint = "Rapporto?" . http_build_query($searchParams);
$searchData = $api->get($searchEndpoint);
$items = $searchData['value'] ?? [];
if (!is_array($items) || count($items) === 0) {
echo json_encode([
'success' => false,
'message' => 'Nessun rapporto trovato per questo CodiceRapporto.',
'codice_rapporto' => $codiceRapporto,
'search_endpoint' => $searchEndpoint,
'search_data' => $searchData
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
exit;
}
$rapportoBase = $items[0];
$rapportoId = intval($rapportoBase['IdRapporto'] ?? 0);
if (!$rapportoId) {
throw new Exception("IdRapporto non trovato nella risposta.");
}
/*
* STEP 2 - Se step=base, restituisco solo la ricerca base.
*/
if ($step === 'base') {
echo json_encode([
'success' => true,
'codice_rapporto' => $codiceRapporto,
'id_rapporto' => $rapportoId,
'step' => $step,
'search_endpoint' => $searchEndpoint,
'rapporto_base' => $rapportoBase
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
exit;
}
/*
* STEP 3 - Espande SOLO il rapporto trovato.
*/
$expandValue = $allowedSteps[$step];
$detailParams = [
'$expand' => $expandValue
];
$detailEndpoint = "Rapporto({$rapportoId})?" . http_build_query($detailParams);
file_put_contents(
__DIR__ . '/last_rapporto_by_codice_expand_endpoint.txt',
'[' . date('Y-m-d H:i:s') . '] SEARCH: ' . $searchEndpoint . PHP_EOL .
'[' . date('Y-m-d H:i:s') . '] DETAIL: ' . $detailEndpoint . PHP_EOL,
FILE_APPEND
);
$detailData = $api->get($detailEndpoint);
$pdfFiles = [];
if ($step === 'files') {
$rapportiFiles = $detailData['RapportiFiles'] ?? [];
if (is_array($rapportiFiles)) {
foreach ($rapportiFiles as $file) {
$idRapportoFile = intval($file['IdRapportoFile'] ?? 0);
if ($idRapportoFile > 0) {
$pdfFiles[] = [
'id_rapporto_file' => $idRapportoFile,
'file_name' => $file['FileName'] ?? null,
'categoria' => $file['Categoria'] ?? null,
'tipo_rapporto' => $file['TipoRapporto'] ?? null,
'download_endpoint' => "MediaFile/DownloadStream?objectType=RapportoFile&propertyName=FileContent&objectKey={$idRapportoFile}"
];
}
}
}
}
$clienteData = null;
if ($step === 'cliente' || $step === 'files') {
$clienteData = $detailData['Cliente'] ?? null;
}
file_put_contents(
__DIR__ . "/rapporto_codice_{$codiceRapportoFileSafe}_{$step}.json",
json_encode([
'search' => $searchData,
'detail' => $detailData
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
);
$response = [
'success' => true,
'codice_rapporto' => $codiceRapporto,
'id_rapporto' => $rapportoId,
'step' => $step,
'search_endpoint' => $searchEndpoint,
'detail_endpoint' => $detailEndpoint,
'rapporto_base' => $rapportoBase,
'data' => $detailData
];
if ($step === 'files') {
$response['pdf_files'] = $pdfFiles;
}
if ($step === 'cliente' || $step === 'files') {
$response['cliente'] = $clienteData;
}
echo json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
} catch (Exception $e) { } catch (Exception $e) {
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL, FILE_APPEND); file_put_contents(
__DIR__ . '/error_log.txt',
date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL,
FILE_APPEND
);
http_response_code(500); http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
} }
+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);
}
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -59,7 +59,8 @@ foreach ($allMappings as $mapping) {
} }
} }
if (!$mainFieldMapping) { 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 // Retrieve data from datadb
+262 -60
View File
@@ -10,65 +10,125 @@
<title>Template Buttons - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title> <title>Template Buttons - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
<style> <style>
/* Layout flessibile per gestire dimensioni diverse */ /* Main buttons container */
#templateButtons { .template-buttons {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 10px; gap: 12px;
justify-content: flex-start; justify-content: flex-start;
/* Allinea a sinistra */ padding: 10px 0;
padding: 20px;
} }
/* Definizione delle dimensioni */ /* Button sizes */
/* Definizione delle dimensioni */
.btn-small { .btn-small {
font-size: 12px; font-size: 12px;
padding: 6px 12px; padding: 6px 12px;
min-width: 100px; min-width: 100px;
min-height: 30px; min-height: 36px;
display: flex; display: inline-flex;
/* Aggiunto */
justify-content: center; justify-content: center;
/* Aggiunto */
align-items: center; align-items: center;
/* Aggiunto */ gap: 8px;
border-radius: 8px;
text-align: center;
} }
.btn-medium { .btn-medium {
font-size: 16px; font-size: 16px;
padding: 10px 20px; padding: 10px 20px;
min-width: 130px; min-width: 140px;
min-height: 45px; min-height: 48px;
display: flex; display: inline-flex;
/* Aggiunto */
justify-content: center; justify-content: center;
/* Aggiunto */
align-items: center; align-items: center;
/* Aggiunto */ gap: 8px;
border-radius: 10px;
text-align: center;
} }
.btn-large { .btn-large {
font-size: 20px; font-size: 20px;
padding: 14px 28px; padding: 14px 28px;
min-width: 180px; min-width: 190px;
min-height: 60px; min-height: 64px;
display: flex; display: inline-flex;
/* Aggiunto */
justify-content: center; justify-content: center;
/* Aggiunto */
align-items: center; align-items: center;
/* Aggiunto */ gap: 10px;
border-radius: 12px;
text-align: center;
} }
/* Stile della barra di ricerca */ .template-icon {
font-size: 18px;
line-height: 1;
}
.btn-large .template-icon {
font-size: 22px;
}
.btn-small .template-icon {
font-size: 15px;
}
/* Search box */
#searchInput { #searchInput {
width: 100%; width: 100%;
padding: 10px; padding: 10px 14px;
font-size: 16px; font-size: 16px;
margin-bottom: 15px; margin-bottom: 18px;
border: 1px solid #ccc; border: 1px solid #d9d9d9;
border-radius: 5px; border-radius: 8px;
outline: none;
}
#searchInput:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.15rem rgba(13, 110, 253, 0.15);
}
/* Tabs */
.custom-tabs {
border-bottom: 1px solid #e5e5e5;
margin-bottom: 20px;
gap: 6px;
}
.custom-tabs .nav-link {
border: none;
border-radius: 10px 10px 0 0;
color: #555;
font-weight: 500;
display: flex;
align-items: center;
gap: 8px;
padding: 10px 18px;
}
.custom-tabs .nav-link:hover {
background: #f8f9fa;
color: #0d6efd;
}
.custom-tabs .nav-link.active {
background: #0d6efd;
color: #fff;
}
.tab-pane {
min-height: 140px;
}
.empty-message {
color: #6c757d;
font-style: italic;
padding: 10px 0;
}
.loading-message {
color: #6c757d;
padding: 10px 0;
} }
</style> </style>
</head> </head>
@@ -87,14 +147,54 @@
<h6 class="mb-0">Active Templates</h6> <h6 class="mb-0">Active Templates</h6>
</div> </div>
<div class="card-body"> <div class="card-body">
<!-- Barra di ricerca -->
<input type="text" id="searchInput" placeholder="Search template..."> <input type="text" id="searchInput" placeholder="Search template...">
<div id="templateButtons"></div>
<ul class="nav nav-tabs custom-tabs" id="templateTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="xls-tab" data-bs-toggle="tab" data-bs-target="#xls-pane" type="button" role="tab" aria-controls="xls-pane" aria-selected="true">
<i class="bx bx-spreadsheet template-icon"></i>
XLS
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="api-tab" data-bs-toggle="tab" data-bs-target="#api-pane" type="button" role="tab" aria-controls="api-pane" aria-selected="false">
<i class="bx bx-transfer-alt template-icon"></i>
JSON/API
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="pdf-tab" data-bs-toggle="tab" data-bs-target="#pdf-pane" type="button" role="tab" aria-controls="pdf-pane" aria-selected="false">
<i class="bx bx-file-pdf template-icon"></i>
PDF
</button>
</li>
</ul>
<div class="tab-content" id="templateTabsContent">
<div class="tab-pane fade show active" id="xls-pane" role="tabpanel" aria-labelledby="xls-tab">
<div id="templateButtonsXLS" class="template-buttons">
<div class="loading-message">Loading XLS templates...</div>
</div>
</div>
<div class="tab-pane fade" id="api-pane" role="tabpanel" aria-labelledby="api-tab">
<div id="templateButtonsAPI" class="template-buttons">
<div class="loading-message">Loading API templates...</div>
</div>
</div>
<div class="tab-pane fade" id="pdf-pane" role="tabpanel" aria-labelledby="pdf-tab">
<div id="templateButtonsPDF" class="template-buttons">
<div class="loading-message">Loading PDF templates...</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="overlay toggle-icon"></div> <div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a> <a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?> <?php include('include/footer.php'); ?>
@@ -104,47 +204,149 @@
<script> <script>
document.addEventListener("DOMContentLoaded", function() { document.addEventListener("DOMContentLoaded", function() {
const allTemplates = [];
const containers = {
XLS: document.getElementById("templateButtonsXLS"),
API: document.getElementById("templateButtonsAPI"),
PDF: document.getElementById("templateButtonsPDF")
};
function getSizeClass(buttonSize) {
if (buttonSize === "small") return "btn-small";
if (buttonSize === "large") return "btn-large";
return "btn-medium";
}
function getTemplateIcon(sourceType) {
switch ((sourceType || '').toUpperCase()) {
case 'XLS':
return 'bx bx-spreadsheet';
case 'API':
return 'bx bx-transfer-alt';
case 'PDF':
return 'bx bx-file-pdf';
default:
return 'bx bx-file';
}
}
function createButton(template) {
const sizeClass = getSizeClass(template.button_size);
const sourceType = (template.source_type || '').toUpperCase();
const iconClass = getTemplateIcon(sourceType);
const btn = document.createElement("a");
// Redirect based on template source type
if (sourceType === 'XLS') {
btn.href = `import_xls2.php?id=${template.id}`;
} else if (sourceType === 'API' || sourceType === 'JSON') {
btn.href = `import_json.php?id=${template.id}`;
} else if (sourceType === 'PDF') {
btn.href = `import_pdf.php?id=${template.id}`;
} else {
btn.href = `import_xls2.php?id=${template.id}`;
}
btn.className = `btn ${sizeClass}`;
btn.style.backgroundColor = template.button_bg_color || '#0d6efd';
btn.style.color = template.button_text_color || '#ffffff';
btn.setAttribute("data-label", (template.button_label || '').toLowerCase());
btn.setAttribute("data-source-type", sourceType);
btn.innerHTML = `
<i class="${iconClass} template-icon"></i>
<span>${escapeHtml(template.button_label || 'Unnamed')}</span>
`;
return btn;
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function clearContainers() {
Object.values(containers).forEach(container => {
container.innerHTML = '';
});
}
function renderTemplates(searchValue = '') {
clearContainers();
const grouped = {
XLS: [],
API: [],
PDF: []
};
allTemplates.forEach(template => {
const sourceType = (template.source_type || '').toUpperCase();
const label = (template.button_label || '').toLowerCase();
if (searchValue && !label.includes(searchValue)) {
return;
}
if (grouped[sourceType]) {
grouped[sourceType].push(template);
}
});
Object.keys(grouped).forEach(type => {
const container = containers[type];
if (!container) return;
if (grouped[type].length === 0) {
container.innerHTML = `<div class="empty-message">No templates found in this section.</div>`;
return;
}
grouped[type].forEach(template => {
container.appendChild(createButton(template));
});
});
}
fetch("load_active_templates.php") fetch("load_active_templates.php")
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (!data.success) { if (!data.success) {
console.error("Error loading templates:", data.message); console.error("Error loading templates:", data.message);
clearContainers();
Object.values(containers).forEach(container => {
container.innerHTML = `<div class="empty-message">Error loading templates.</div>`;
});
return; return;
} }
let templateButtons = document.getElementById("templateButtons"); if (!Array.isArray(data.data)) {
templateButtons.innerHTML = ''; clearContainers();
Object.values(containers).forEach(container => {
data.data.forEach(template => { container.innerHTML = `<div class="empty-message">Invalid response format.</div>`;
let sizeClass = "btn-medium"; // Default
if (template.button_size === "small") sizeClass = "btn-small";
if (template.button_size === "large") sizeClass = "btn-large";
let btn = document.createElement("a");
btn.href = `import_xls2.php?id=${template.id}`;
btn.className = `btn ${sizeClass}`;
btn.style.backgroundColor = template.button_bg_color;
btn.style.color = template.button_text_color;
btn.textContent = template.button_label;
btn.setAttribute("data-label", template.button_label.toLowerCase()); // Attributo per ricerca
templateButtons.appendChild(btn);
}); });
}) return;
.catch(error => console.error("Fetch error:", error));
// Funzione per la ricerca live
document.getElementById("searchInput").addEventListener("input", function() {
let searchValue = this.value.toLowerCase();
document.querySelectorAll("#templateButtons a").forEach(btn => {
let label = btn.getAttribute("data-label");
if (label.includes(searchValue)) {
btn.style.display = "inline-block";
} else {
btn.style.display = "none";
} }
allTemplates.push(...data.data);
renderTemplates();
})
.catch(error => {
console.error("Fetch error:", error);
clearContainers();
Object.values(containers).forEach(container => {
container.innerHTML = `<div class="empty-message">Fetch error while loading templates.</div>`;
}); });
}); });
document.getElementById("searchInput").addEventListener("input", function() {
const searchValue = this.value.toLowerCase().trim();
renderTemplates(searchValue);
});
}); });
</script> </script>
File diff suppressed because it is too large Load Diff
+109 -39
View File
@@ -20,10 +20,12 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['template_id']) || !i
$template_id = intval($_POST['template_id']); $template_id = intval($_POST['template_id']);
$selected_rows = array_map('intval', $_POST['selected_rows']); $selected_rows = array_map('intval', $_POST['selected_rows']);
$columns = json_decode($_POST['columns'], true); $columns = json_decode(urldecode($_POST['columns'] ?? '[]'), true);
$rows = json_decode($_POST['rows'], true); $rows = json_decode(urldecode($_POST['rows'] ?? '[]'), true);
$excelrows = json_decode($_POST['excelrows'], true); $excelrows = json_decode(urldecode($_POST['excelrows'] ?? '[]'), true);
$newFilename = htmlspecialchars($_POST['filename']);
$newFilename = $_POST['filename'];
$source_type = strtolower(trim($_POST['source_type'] ?? 'xls'));
$_SESSION['template_id'] = $template_id; $_SESSION['template_id'] = $template_id;
$_SESSION['selected_rows'] = $selected_rows; $_SESSION['selected_rows'] = $selected_rows;
@@ -36,6 +38,7 @@ error_log("Received Data - Template ID: $template_id, Selected Rows: " . json_en
error_log("Columns: " . json_encode($columns)); error_log("Columns: " . json_encode($columns));
error_log("Rows: " . json_encode($rows)); error_log("Rows: " . json_encode($rows));
error_log("Excelrows: " . json_encode($excelrows)); error_log("Excelrows: " . json_encode($excelrows));
error_log("Source type: " . $source_type);
$user_id = $iduserlogin ?? 1; $user_id = $iduserlogin ?? 1;
@@ -46,7 +49,22 @@ $pdo = $db->getConnection();
$importReferenceCode = date('YmdHis') . '-' . uniqid(); $importReferenceCode = date('YmdHis') . '-' . uniqid();
// Recupera tutti i mapping dal template // Recupera tutti i mapping dal template
$stmt = $pdo->prepare("SELECT id, excel_column, data_type, is_required, manual_default, is_manual, field_label, field_id, main_field FROM template_mapping WHERE template_id = ?"); $stmt = $pdo->prepare("
SELECT
id,
excel_column,
json_node,
data_type,
is_required,
manual_default,
is_manual,
field_label,
field_id,
main_field,
auto_value
FROM template_mapping
WHERE template_id = ?
");
$stmt->execute([$template_id]); $stmt->execute([$template_id]);
$allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC); $allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
@@ -66,12 +84,20 @@ foreach ($allMappings as $mapping) {
// Inserisci le righe selezionate in datadb // Inserisci le righe selezionate in datadb
$insertedIds = []; $insertedIds = [];
foreach ($selected_rows as $rowIndex) { foreach ($selected_rows as $loopIndex => $rowIndex) {
if ($source_type === 'json') {
// JSON import sends only selected rows in rows/excelrows
$row = $rows[$loopIndex] ?? null;
$excelrow = $excelrows[$loopIndex] ?? ('JSON-' . ($loopIndex + 1));
} else {
// XLS import keeps original row indexes
$row = $rows[$rowIndex] ?? null; $row = $rows[$rowIndex] ?? null;
$excelrow = $excelrows[$rowIndex] ?? null; $excelrow = $excelrows[$rowIndex] ?? null;
}
if ($row === null || $excelrow === null) { if ($row === null || $excelrow === null) {
error_log("Errore: riga o excelrow mancante per rowIndex $rowIndex"); error_log("Errore: riga o excelrow mancante. Source type: $source_type, loopIndex: $loopIndex, rowIndex: $rowIndex");
continue; continue;
} }
@@ -103,14 +129,72 @@ foreach ($selected_rows as $rowIndex) {
foreach ($allMappings as $mapping) { foreach ($allMappings as $mapping) {
$fieldValue = null; $fieldValue = null;
if (!$mapping['is_manual']) { if (!$mapping['is_manual']) {
$excelColumn = trim($mapping['excel_column']); $sourceColumn = '';
$excelColumnIndex = array_search($excelColumn, array_map('trim', $columns));
if ($excelColumnIndex !== false && isset($row[$excelColumnIndex]) && $row[$excelColumnIndex] !== '') { if ($source_type === 'json') {
$fieldValue = $row[$excelColumnIndex]; $sourceColumn = trim($mapping['json_node'] ?? '');
error_log("Found Excel column '$excelColumn' at index $excelColumnIndex, value: " . var_export($fieldValue, true)); } else {
$sourceColumn = trim($mapping['excel_column'] ?? '');
}
// Fallback: if JSON node is empty, try excel_column
if ($sourceColumn === '') {
$sourceColumn = trim($mapping['excel_column'] ?? '');
}
$columnsTrimmed = array_map('trim', $columns);
$candidateColumns = [];
if ($sourceColumn !== '') {
$candidateColumns[] = $sourceColumn;
if ($source_type === 'json') {
// Common JSON path variants
$candidateColumns[] = preg_replace('/^data\[\]\./', '', $sourceColumn);
$candidateColumns[] = preg_replace('/^data\.0\./', '', $sourceColumn);
$candidateColumns[] = str_replace('data[].', 'data.0.', $sourceColumn);
$candidateColumns[] = str_replace('data.0.', 'data[].', $sourceColumn);
}
}
// Remove empty and duplicate candidates
$candidateColumns = array_values(array_unique(array_filter($candidateColumns, function ($value) {
return trim((string)$value) !== '';
})));
$sourceColumnIndex = false;
$matchedColumn = '';
foreach ($candidateColumns as $candidateColumn) {
$candidateColumn = trim($candidateColumn);
$index = array_search($candidateColumn, $columnsTrimmed);
if ($index !== false) {
$sourceColumnIndex = $index;
$matchedColumn = $candidateColumn;
break;
}
}
if ($sourceColumnIndex !== false && isset($row[$sourceColumnIndex]) && $row[$sourceColumnIndex] !== '') {
$fieldValue = $row[$sourceColumnIndex];
error_log(
"Found source column. Original: '$sourceColumn', Matched: '$matchedColumn', Index: $sourceColumnIndex, Value: " .
var_export($fieldValue, true)
);
} else { } else {
$fieldValue = $mapping['manual_default'] ?? ''; $fieldValue = $mapping['manual_default'] ?? '';
error_log("Excel column '$excelColumn' not found or empty, using default: " . var_export($fieldValue, true));
error_log(
"Source column not found or empty. Original: '$sourceColumn'. Candidates: " .
json_encode($candidateColumns) .
". Available columns: " .
json_encode($columnsTrimmed) .
". Using default: " .
var_export($fieldValue, true)
);
} }
switch ($mapping['data_type']) { switch ($mapping['data_type']) {
case 'INT': case 'INT':
@@ -125,7 +209,7 @@ foreach ($selected_rows as $rowIndex) {
case 'Testo': case 'Testo':
case 'VARCHAR': case 'VARCHAR':
default: default:
$fieldValue = !empty($fieldValue) ? htmlspecialchars((string)$fieldValue) : ($mapping['manual_default'] ?? ''); $fieldValue = !empty($fieldValue) ? (string)$fieldValue : ($mapping['manual_default'] ?? '');
break; break;
} }
} else { } else {
@@ -134,10 +218,19 @@ foreach ($selected_rows as $rowIndex) {
$fieldValue = date('Y-m-d'); $fieldValue = date('Y-m-d');
} }
} }
// Apply auto_value if field is still empty
if (($fieldValue === null || $fieldValue === '') && !empty($mapping['auto_value']) && $mapping['auto_value'] !== 'none') {
if ($mapping['auto_value'] === 'import_date') {
$fieldValue = date('Y-m-d');
} elseif ($mapping['auto_value'] === 'import_time') {
$fieldValue = date('H:i');
}
}
if ($mapping['is_required'] && (is_null($fieldValue) || $fieldValue === '')) { if ($mapping['is_required'] && (is_null($fieldValue) || $fieldValue === '')) {
error_log("Required field missing for mapping ID: " . $mapping['id'] . ", field: " . $mapping['field_label']); error_log("Required field missing for mapping ID: " . $mapping['id'] . ", field: " . $mapping['field_label']);
} }
error_log("Inserting into import_data_details - Mapping ID: " . $mapping['id'] . ", Field Value: " . var_export($fieldValue, true) . ", Is Manual: " . $mapping['is_manual'] . ", Excel Column: " . ($mapping['excel_column'] ?? 'N/A') . ", Manual Default: " . ($mapping['manual_default'] ?? 'N/A')); error_log("Inserting into import_data_details - Mapping ID: " . $mapping['id'] . ", Field Value: " . var_export($fieldValue, true) . ", Is Manual: " . $mapping['is_manual'] . ", Source Column: " . ($sourceColumn ?? 'N/A') . ", Source Type: " . $source_type . ", Manual Default: " . ($mapping['manual_default'] ?? 'N/A'));
$stmt = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, ?)"); $stmt = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, ?)");
$stmt->execute([$iddatadb, $mapping['id'], $fieldValue]); $stmt->execute([$iddatadb, $mapping['id'], $fieldValue]);
error_log("Inserted into import_data_details for ID $iddatadb, Mapping ID: " . $mapping['id'] . ", Field Value: " . var_export($fieldValue, true)); error_log("Inserted into import_data_details for ID $iddatadb, Mapping ID: " . $mapping['id'] . ", Field Value: " . var_export($fieldValue, true));
@@ -146,28 +239,5 @@ foreach ($selected_rows as $rowIndex) {
$_SESSION['inserted_ids'] = $insertedIds; $_SESSION['inserted_ids'] = $insertedIds;
$params = [ header("Location: imported.php?id=" . urlencode($template_id) . "&importref=" . urlencode($importReferenceCode));
'template_id' => $template_id,
'filename' => $newFilename,
];
?>
<form id="redirectForm" action="import_edit2.php" method="post">
<input type="hidden" name="template_id" value="<?= htmlspecialchars($template_id) ?>">
<input type="hidden" name="filename" value="<?= htmlspecialchars($newFilename) ?>">
<?php foreach ($selected_rows as $row): ?>
<input type="hidden" name="selected_rows[]" value="<?= htmlspecialchars($row) ?>">
<?php endforeach; ?>
<?php foreach ($insertedIds as $id): ?>
<input type="hidden" name="inserted_ids[]" value="<?= htmlspecialchars($id) ?>">
<?php endforeach; ?>
<input type="hidden" name="columns" value='<?= json_encode($columns) ?>'>
<input type="hidden" name="rows" value='<?= json_encode($rows) ?>'>
<input type="hidden" name="excelrows" value='<?= json_encode($excelrows) ?>'>
</form>
<script>
document.getElementById('redirectForm').submit();
</script>
<?php
exit; exit;
?>
+819
View File
@@ -0,0 +1,819 @@
<?php
include('include/headscript.php');
// Check if a valid template ID has been provided
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Invalid ID"));
exit;
}
$id = intval($_GET['id']);
// Load template
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("SELECT * FROM excel_templates WHERE id = ?");
$stmt->execute([$id]);
$template = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$template) {
header("Location: template_dashboard.php?status=error&message=" . urlencode("Template not found"));
exit;
}
// Check mappings
$stmt = $pdo->prepare("SELECT id FROM template_mapping WHERE template_id = ?");
$stmt->execute([$id]);
$hasMappings = $stmt->fetch(PDO::FETCH_ASSOC);
error_log("Loaded JSON import template: " . print_r($template, true));
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.top-scrollbar {
overflow-x: auto;
overflow-y: hidden;
width: 100%;
height: 18px;
margin-bottom: 8px;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
background: #f8f9fa;
display: none;
}
.top-scrollbar-inner {
height: 1px;
}
.table-container {
overflow-x: auto;
max-width: 100%;
margin-bottom: 20px;
}
.table {
width: 100%;
border-collapse: collapse;
}
.table th,
.table td {
padding: 10px;
text-align: left;
border: 1px solid #dee2e6;
min-width: 120px;
max-width: 260px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
}
.table th:first-child,
.table td:first-child {
min-width: 50px;
max-width: 50px;
}
.table th {
background-color: #f8f9fa;
position: relative;
cursor: col-resize;
user-select: none;
}
.table th .resize-handle {
position: absolute;
top: 0;
right: 0;
width: 5px;
height: 100%;
cursor: col-resize;
background: transparent;
}
.table th .resize-handle:hover {
background: #007bff;
}
.loader {
display: none;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
margin: 10px auto;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.column-filters th {
background: #ffffff;
cursor: default;
}
.column-filters input {
width: 100%;
min-width: 80px;
}
.json-code-input {
font-size: 1.15rem;
min-height: 48px;
}
.json-paste-area {
min-height: 260px;
font-family: Consolas, Monaco, monospace;
font-size: 13px;
}
.json-help-box {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 12px 14px;
font-size: 13px;
}
.source-badge {
font-size: 12px;
}
</style>
<title><?= htmlspecialchars($template['name']) ?> - JSON Import - <?= 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">
<?php include('top_stat_widget.php'); ?>
<div class="mb-3 text">
<a href="imported.php?id=<?= $id ?>" class="btn btn-warning me-2">Imported (i)</a>
<a href="tolims.php?id=<?= $id ?>" class="btn btn-success">To LIMS (l)</a>
</div>
<div class="card radius-10">
<div class="card-header">
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
<div>
<h6 class="mb-0"><?= htmlspecialchars($template['name']) ?> - JSON Import</h6>
<small>
Template ID: <?= $id ?>
</small>
</div>
<span class="badge bg-info text-dark">JSON mode</span>
</div>
</div>
<div class="card-body">
<?php if (!$hasMappings): ?>
<div class="alert alert-warning" role="alert">
Nessun mapping trovato per questo template. Configura i mapping prima di procedere.
</div>
<?php endif; ?>
<div class="json-help-box mb-3">
<strong>Flusso:</strong> inserisci/scansiona un codice per recuperare il JSON da API, oppure incolla manualmente un JSON nel secondo tab.
Ogni JSON aggiunto diventa una riga della tabella di preview. Quando hai finito, seleziona le righe e clicca <strong>Prosegui</strong>.
</div>
<ul class="nav nav-tabs" id="jsonImportTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="api-code-tab" data-bs-toggle="tab" data-bs-target="#api-code-pane" type="button" role="tab" aria-controls="api-code-pane" aria-selected="true">
Code / Barcode
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="paste-json-tab" data-bs-toggle="tab" data-bs-target="#paste-json-pane" type="button" role="tab" aria-controls="paste-json-pane" aria-selected="false">
Paste JSON
</button>
</li>
</ul>
<div class="tab-content border border-top-0 p-3 mb-3" id="jsonImportTabsContent">
<div class="tab-pane fade show active" id="api-code-pane" role="tabpanel" aria-labelledby="api-code-tab" tabindex="0">
<form id="jsonCodeForm" class="row g-3 align-items-end">
<div class="col-lg-8">
<label for="json_code" class="form-label">Code / Barcode</label>
<input type="text" class="form-control json-code-input" id="json_code" name="json_code" placeholder="Write or scan code" autocomplete="off" <?= !$hasMappings ? 'disabled' : '' ?>>
<small class="text-muted">Lo scanner barcode normalmente scrive qui il codice e invia Enter.</small>
</div>
<div class="col-lg-4 d-flex gap-2">
<button type="submit" class="btn btn-primary flex-fill" <?= !$hasMappings ? 'disabled' : '' ?>>Load JSON</button>
<button type="button" class="btn btn-outline-secondary" id="clearCodeBtn">Clear</button>
</div>
</form>
</div>
<div class="tab-pane fade" id="paste-json-pane" role="tabpanel" aria-labelledby="paste-json-tab" tabindex="0">
<form id="pasteJsonForm">
<div class="mb-3">
<label for="manual_json_reference" class="form-label">Reference / filename</label>
<input type="text" class="form-control" id="manual_json_reference" placeholder="Optional reference, e.g. manual-json-001">
</div>
<div class="mb-3">
<label for="manual_json" class="form-label">Paste JSON</label>
<textarea class="form-control json-paste-area" id="manual_json" placeholder='{"data":[{"id":"MM000620","attributes":{"trf_type":"apparel"}}]}' <?= !$hasMappings ? 'disabled' : '' ?>></textarea>
</div>
<button type="submit" class="btn btn-primary" <?= !$hasMappings ? 'disabled' : '' ?>>Add pasted JSON</button>
<button type="button" class="btn btn-outline-secondary" id="clearManualJsonBtn">Clear</button>
</form>
</div>
</div>
<div class="loader" id="loader"></div>
<div id="errorContainer" class="alert alert-danger mt-3" style="display:none;"></div>
<div id="successContainer" class="alert alert-success mt-3" style="display:none;"></div>
<div id="tableContainer"></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'); ?>
</div>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.min.js"></script>
<?php include('jsinclude.php'); ?>
<script>
document.addEventListener("DOMContentLoaded", function() {
const TEMPLATE_ID = <?= (int)$id ?>;
const API_ENDPOINT = 'get_json_by_code.php';
const INCLUDE_SOURCE_CODE_COLUMN = true;
const UNWRAP_SINGLE_DATA_ITEM = true;
const jsonCodeForm = document.getElementById('jsonCodeForm');
const pasteJsonForm = document.getElementById('pasteJsonForm');
const jsonCodeInput = document.getElementById('json_code');
const manualJsonInput = document.getElementById('manual_json');
const manualJsonReferenceInput = document.getElementById('manual_json_reference');
const clearCodeBtn = document.getElementById('clearCodeBtn');
const clearManualJsonBtn = document.getElementById('clearManualJsonBtn');
const loader = document.getElementById('loader');
const errorContainer = document.getElementById('errorContainer');
const successContainer = document.getElementById('successContainer');
const tableContainer = document.getElementById('tableContainer');
let jsonRows = [];
let columns = [];
if (jsonCodeInput && !jsonCodeInput.disabled) {
jsonCodeInput.focus();
}
jsonCodeForm.addEventListener('submit', function(e) {
e.preventDefault();
const code = jsonCodeInput.value.trim();
if (!code) {
showError('Inserisci o scansiona un codice.');
return;
}
loadJsonFromApi(code);
});
pasteJsonForm.addEventListener('submit', function(e) {
e.preventDefault();
const rawJson = manualJsonInput.value.trim();
const reference = manualJsonReferenceInput.value.trim() || 'manual-json-' + (jsonRows.length + 1);
if (!rawJson) {
showError('Incolla un JSON prima di aggiungerlo.');
return;
}
try {
const parsedJson = JSON.parse(rawJson);
addJsonRow(parsedJson, reference, 'paste');
manualJsonInput.value = '';
manualJsonReferenceInput.value = '';
showSuccess('JSON incollato aggiunto correttamente.');
} catch (err) {
showError('JSON non valido: ' + err.message);
}
});
clearCodeBtn.addEventListener('click', function() {
jsonCodeInput.value = '';
jsonCodeInput.focus();
});
clearManualJsonBtn.addEventListener('click', function() {
manualJsonInput.value = '';
manualJsonReferenceInput.value = '';
});
function loadJsonFromApi(code) {
hideMessages();
loader.style.display = 'block';
fetch(API_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
template_id: TEMPLATE_ID,
code: code
})
})
.then(response => {
if (!response.ok) {
throw new Error('HTTP status ' + response.status);
}
return response.json();
})
.then(responseData => {
loader.style.display = 'none';
if (responseData.error) {
showError(responseData.error);
return;
}
let jsonPayload = responseData;
let reference = code;
// Supported endpoint response format:
// { success: true, json: {...}, reference: "..." }
if (responseData.json !== undefined) {
jsonPayload = responseData.json;
reference = responseData.reference || responseData.filename || responseData.code || code;
}
addJsonRow(jsonPayload, reference, 'api');
jsonCodeInput.value = '';
jsonCodeInput.focus();
showSuccess('JSON recuperato e aggiunto correttamente.');
})
.catch(error => {
loader.style.display = 'none';
showError('Errore durante il recupero del JSON: ' + error.message);
});
}
function addJsonRow(jsonPayload, reference, sourceType) {
const normalizedPayload = normalizeJsonPayload(jsonPayload);
const flattened = flattenJson(normalizedPayload);
if (INCLUDE_SOURCE_CODE_COLUMN) {
flattened.source_code = reference;
flattened.source_type = sourceType;
}
const newColumns = Object.keys(flattened).filter(col => !columns.includes(col));
columns = columns.concat(newColumns);
jsonRows.push({
excelrow: 'JSON-' + (jsonRows.length + 1),
reference: reference,
sourceType: sourceType,
flat: flattened
});
renderTable();
}
function normalizeJsonPayload(payload) {
if (
UNWRAP_SINGLE_DATA_ITEM &&
payload &&
typeof payload === 'object' &&
!Array.isArray(payload) &&
Array.isArray(payload.data) &&
payload.data.length === 1 &&
payload.data[0] &&
typeof payload.data[0] === 'object'
) {
return payload.data[0];
}
return payload;
}
function flattenJson(value, prefix = '', result = {}) {
if (value === null || value === undefined) {
result[prefix || 'value'] = '';
return result;
}
if (Array.isArray(value)) {
if (value.length === 0) {
result[prefix || 'value'] = '';
return result;
}
value.forEach((item, index) => {
const newPrefix = prefix ? prefix + '.' + index : String(index);
flattenJson(item, newPrefix, result);
});
return result;
}
if (typeof value === 'object') {
const keys = Object.keys(value);
if (keys.length === 0) {
result[prefix || 'value'] = '';
return result;
}
keys.forEach(key => {
const newPrefix = prefix ? prefix + '.' + key : key;
flattenJson(value[key], newPrefix, result);
});
return result;
}
result[prefix || 'value'] = String(value);
return result;
}
function buildImportData() {
const rows = jsonRows.map(row => columns.map(col => row.flat[col] ?? ''));
const excelData = rows.map((rowData, index) => ({
excelrow: jsonRows[index].excelrow,
data: rowData
}));
return {
template_id: TEMPLATE_ID,
columns: columns,
rows: rows,
excel_data: excelData,
filename: 'json_import_' + new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)
};
}
function renderTable() {
if (jsonRows.length === 0) {
tableContainer.innerHTML = '';
return;
}
const data = buildImportData();
let html = `
<form id="selectRowsForm" action="import_insert.php" method="POST">
<input type="hidden" name="template_id" value="${escapeHtml(data.template_id)}">
<input type="hidden" name="columns" value="${encodeURIComponent(JSON.stringify(data.columns))}">
<input type="hidden" name="rows" id="selectedRowsData" value="">
<input type="hidden" name="excelrows" id="selectedExcelRowsData" value="">
<input type="hidden" name="filename" value="${escapeHtml(data.filename)}">
<input type="hidden" name="source_type" value="json">
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
<div>
<strong>JSON rows loaded:</strong> ${jsonRows.length}
<span class="badge bg-secondary source-badge ms-2">Columns: ${data.columns.length}</span>
</div>
<div class="d-flex gap-2">
<button type="button" class="btn btn-outline-danger btn-sm" id="clearAllRowsBtn">Clear all rows</button>
<button type="submit" class="btn btn-primary" id="proceedButtonTop" disabled>Prosegui</button>
</div>
</div>
<div class="top-scrollbar" id="topTableScrollbar">
<div class="top-scrollbar-inner" id="topTableScrollbarInner"></div>
</div>
<div class="table-container" id="mainTableContainer">
<table class="table table-striped table-bordered" id="importPreviewTable">
<thead>
<tr>
<th><input type="checkbox" id="selectAll"> Select</th>
${data.columns.map(col => `<th title="${escapeHtml(col)}">${escapeHtml(readableColumnLabel(col))}<div class="resize-handle"></div></th>`).join('')}
<th>Action</th>
</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('')}
<th></th>
</tr>
</thead>
<tbody>
${data.excel_data.map((row, index) => `
<tr data-row-index="${index}">
<td><input type="checkbox" class="row-checkbox" name="selected_rows[]" value="${index}" data-excelrow="${escapeHtml(row.excelrow)}"></td>
${row.data.map(cell => `<td title="${escapeHtml(cell)}">${escapeHtml(cell)}</td>`).join('')}
<td><button type="button" class="btn btn-sm btn-outline-danger remove-row-btn" data-row-index="${index}">Remove</button></td>
</tr>
`).join('')}
</tbody>
</table>
</div>
<button type="submit" class="btn btn-primary mt-3" id="proceedButtonBottom" disabled>Prosegui</button>
</form>
`;
tableContainer.innerHTML = html;
bindTableEvents(data);
}
function bindTableEvents(data) {
const selectRowsForm = document.getElementById('selectRowsForm');
const clearAllRowsBtn = document.getElementById('clearAllRowsBtn');
const removeRowButtons = document.querySelectorAll('.remove-row-btn');
selectRowsForm.addEventListener('submit', function(e) {
const checkedBoxes = Array.from(document.querySelectorAll('.row-checkbox:checked'));
if (checkedBoxes.length === 0) {
e.preventDefault();
alert('Seleziona almeno una riga.');
return;
}
const selectedRows = [];
const selectedExcelRows = [];
checkedBoxes.forEach((cb, newIndex) => {
const originalIndex = parseInt(cb.value, 10);
if (data.rows && data.rows[originalIndex]) {
selectedRows.push(data.rows[originalIndex]);
}
if (data.excel_data && data.excel_data[originalIndex]) {
selectedExcelRows.push(data.excel_data[originalIndex].excelrow);
}
// Reindex selected_rows so import_insert.php receives only the reduced rows array
cb.value = newIndex;
});
document.getElementById('selectedRowsData').value =
encodeURIComponent(JSON.stringify(selectedRows));
document.getElementById('selectedExcelRowsData').value =
encodeURIComponent(JSON.stringify(selectedExcelRows));
});
clearAllRowsBtn.addEventListener('click', function() {
if (!confirm('Vuoi rimuovere tutte le righe JSON caricate?')) return;
jsonRows = [];
columns = [];
renderTable();
showSuccess('Righe JSON rimosse.');
jsonCodeInput.focus();
});
removeRowButtons.forEach(button => {
button.addEventListener('click', function() {
const rowIndex = parseInt(this.dataset.rowIndex, 10);
jsonRows.splice(rowIndex, 1);
rebuildColumnsFromRows();
renderTable();
});
});
const topTableScrollbar = document.getElementById('topTableScrollbar');
const topTableScrollbarInner = document.getElementById('topTableScrollbarInner');
const mainTableContainer = document.getElementById('mainTableContainer');
const importPreviewTable = document.getElementById('importPreviewTable');
function updateTopTableScrollbar() {
if (!topTableScrollbar || !topTableScrollbarInner || !mainTableContainer || !importPreviewTable) return;
topTableScrollbarInner.style.width = importPreviewTable.scrollWidth + 'px';
if (mainTableContainer.scrollWidth > mainTableContainer.clientWidth) {
topTableScrollbar.style.display = 'block';
} else {
topTableScrollbar.style.display = 'none';
}
}
let syncingTop = false;
let syncingBottom = false;
if (topTableScrollbar && mainTableContainer) {
topTableScrollbar.addEventListener('scroll', function() {
if (syncingBottom) return;
syncingTop = true;
mainTableContainer.scrollLeft = topTableScrollbar.scrollLeft;
syncingTop = false;
});
mainTableContainer.addEventListener('scroll', function() {
if (syncingTop) return;
syncingBottom = true;
topTableScrollbar.scrollLeft = mainTableContainer.scrollLeft;
syncingBottom = false;
});
}
updateTopTableScrollbar();
setTimeout(updateTopTableScrollbar, 100);
setTimeout(updateTopTableScrollbar, 300);
window.addEventListener('resize', updateTopTableScrollbar);
const proceedButtonTop = document.getElementById('proceedButtonTop');
const proceedButtonBottom = document.getElementById('proceedButtonBottom');
const selectAllCheckbox = document.getElementById('selectAll');
const checkboxes = document.querySelectorAll('.row-checkbox');
function updateProceedButton() {
const enabled = Array.from(document.querySelectorAll('.row-checkbox')).some(cb => cb.checked);
if (proceedButtonTop) proceedButtonTop.disabled = !enabled;
if (proceedButtonBottom) proceedButtonBottom.disabled = !enabled;
}
selectAllCheckbox.addEventListener('change', function() {
const visibleRows = Array.from(document.querySelectorAll('#importPreviewTable 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() {
const visibleCheckboxes = Array.from(document.querySelectorAll('#importPreviewTable 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();
});
});
const thElements = document.querySelectorAll('#importPreviewTable th');
thElements.forEach((th, index) => {
if (index === 0) return;
const resizeHandle = th.querySelector('.resize-handle');
if (resizeHandle) {
resizeHandle.addEventListener('mousedown', (e) => {
e.preventDefault();
const startX = e.clientX;
const startWidth = th.offsetWidth;
const onMouseMove = (e) => {
const newWidth = Math.max(60, startWidth + (e.clientX - startX));
th.style.width = `${newWidth}px`;
th.style.minWidth = `${newWidth}px`;
th.style.maxWidth = `${newWidth}px`;
const cells = document.querySelectorAll(`#importPreviewTable td:nth-child(${index + 1})`);
cells.forEach(cell => {
cell.style.width = `${newWidth}px`;
cell.style.minWidth = `${newWidth}px`;
cell.style.maxWidth = `${newWidth}px`;
});
};
const onMouseUp = () => {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
};
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
});
}
});
const rows = document.querySelectorAll('#importPreviewTable tbody tr');
const filterInputs = document.querySelectorAll('.column-filter');
const activeFilters = {};
function applyColumnFilters() {
rows.forEach(row => {
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('#importPreviewTable 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;
activeFilters[colIndex] = this.value;
applyColumnFilters();
});
});
updateProceedButton();
}
function rebuildColumnsFromRows() {
columns = [];
jsonRows.forEach(row => {
Object.keys(row.flat).forEach(col => {
if (!columns.includes(col)) {
columns.push(col);
}
});
});
}
function readableColumnLabel(columnName) {
if (!columnName) return 'Column without name';
return columnName;
}
function showError(message) {
successContainer.style.display = 'none';
errorContainer.textContent = message;
errorContainer.style.display = 'block';
}
function showSuccess(message) {
errorContainer.style.display = 'none';
successContainer.textContent = message;
successContainer.style.display = 'block';
setTimeout(() => {
successContainer.style.display = 'none';
}, 3500);
}
function hideMessages() {
errorContainer.style.display = 'none';
successContainer.style.display = 'none';
}
function escapeHtml(value) {
return String(value ?? '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
});
</script>
</body>
</html>
+213 -27
View File
@@ -40,6 +40,22 @@ error_log("Loaded template: " . print_r($template, true));
<?php include('cssinclude.php'); ?> <?php include('cssinclude.php'); ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style> <style>
.top-scrollbar {
overflow-x: auto;
overflow-y: hidden;
width: 100%;
height: 18px;
margin-bottom: 8px;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
background: #f8f9fa;
display: none;
}
.top-scrollbar-inner {
height: 1px;
}
.table-container { .table-container {
overflow-x: auto; overflow-x: auto;
max-width: 100%; max-width: 100%;
@@ -121,6 +137,16 @@ error_log("Loaded template: " . print_r($template, true));
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
.column-filters th {
background: #ffffff;
cursor: default;
}
.column-filters input {
width: 100%;
min-width: 80px;
}
</style> </style>
<title><?= htmlspecialchars($template['name']) ?> - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title> <title><?= htmlspecialchars($template['name']) ?> - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
</head> </head>
@@ -133,16 +159,20 @@ error_log("Loaded template: " . print_r($template, true));
<div class="page-content"> <div class="page-content">
<?php include('top_stat_widget.php'); ?> <?php include('top_stat_widget.php'); ?>
<div class="mb-3 text"> <div class="mb-3 text">
<a href="historical_trf.php?id=<?= $id ?>&status=i" class="btn btn-warning me-2">Imported (i)</a> <a href="imported.php?id=<?= $id ?>" class="btn btn-warning me-2">Imported (i)</a>
<a href="historical_trf.php?id=<?= $id ?>&status=P" class="btn btn-primary me-2">In Progress (P)</a> <a href="tolims.php?id=<?= $id ?>" class="btn btn-success">To LIMS (l)</a>
<a href="historical_trf.php?id=<?= $id ?>&status=l" class="btn btn-success">To LIMS (l)</a>
</div> </div>
<div class="card radius-10"> <div class="card radius-10">
<div class="card-header"> <div class="card-header">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<div> <div>
<h6 class="mb-0"><?= htmlspecialchars($template['name']) ?></h6> <h6 class="mb-0"><?= htmlspecialchars($template['name']) ?></h6>
<small>Template ID: <?= $id ?>, Start Row: <?= $template['header_row'] ?>, Start Column: <?= $template['start_column'] ?></small> <small>
Template ID: <?= $id ?>,
Sheet Number: <?= (int)($template['xls_sheet_index'] ?? 0) ?>,
Start Row: <?= $template['header_row'] ?>,
Start Column: <?= $template['start_column'] ?>
</small>
</div> </div>
</div> </div>
</div> </div>
@@ -219,8 +249,9 @@ error_log("Loaded template: " . print_r($template, true));
const templateId = <?= $id ?>; const templateId = <?= $id ?>;
console.log('Template ID passed to formData:', templateId); console.log('Template ID passed to formData:', templateId);
formData.append('template_id', templateId); formData.append('template_id', templateId);
formData.append('header_row', <?= $template['header_row'] ?>); formData.append('header_row', <?= (int)$template['header_row'] ?>);
formData.append('start_column', <?= $template['start_column'] ?>); formData.append('start_column', <?= json_encode($template['start_column']) ?>);
formData.append('xls_sheet_index', <?= (int)($template['xls_sheet_index'] ?? 0) ?>);
fetch('process_import_xls2.php', { fetch('process_import_xls2.php', {
method: 'POST', method: 'POST',
@@ -303,21 +334,42 @@ error_log("Loaded template: " . print_r($template, true));
function showTable(data) { function showTable(data) {
console.log('Mostro tabella con dati:', data); console.log('Mostro tabella con dati:', data);
let html = ` let html = `
<form id="selectRowsForm" action="import_insert.php" method="POST"> <form id="selectRowsForm" action="import_insert.php" method="POST">
<input type="hidden" name="template_id" value="${data.template_id}"> <input type="hidden" name="template_id" value="${data.template_id}">
<input type="hidden" name="columns" value='${JSON.stringify(data.columns)}'> <input type="hidden" name="columns" value="${encodeURIComponent(JSON.stringify(data.columns))}">
<input type="hidden" name="rows" value='${JSON.stringify(data.rows)}'> <input type="hidden" name="rows" id="selectedRowsData" value="">
<input type="hidden" name="excelrows" value='${JSON.stringify(data.excel_data.map(row => row.excelrow))}'> <input type="hidden" name="excelrows" id="selectedExcelRowsData" value="">
<input type="hidden" name="filename" value="${data.filename}"> <input type="hidden" name="filename" value="${data.filename}">
<div class="search-container">
<input type="text" id="searchInput" class="form-control" placeholder="Cerca nelle righe..."> <!-- 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>
<div class="table-container">
<table class="table table-striped table-bordered"> <div class="top-scrollbar" id="topTableScrollbar">
<div class="top-scrollbar-inner" id="topTableScrollbarInner"></div>
</div>
<div class="table-container" id="mainTableContainer">
<table class="table table-striped table-bordered" id="importPreviewTable">
<thead> <thead>
<tr> <tr>
<th><input type="checkbox" id="selectAll"> Seleziona</th> <th><input type="checkbox" id="selectAll"> Seleziona</th>
${data.columns.map(col => `<th>${col || 'Colonna senza nome'}<div class="resize-handle"></div></th>`).join('')} ${data.columns.map(col => {
const label = !col ? 'Colonna senza nome' : (col.match(/^__empty_\d+__$/) ? 'Colonna senza nome' : col);
return `<th>${label}<div class="resize-handle"></div></th>`;
}).join('')}
</tr>
<tr 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> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -330,30 +382,129 @@ error_log("Loaded template: " . print_r($template, true));
</tbody> </tbody>
</table> </table>
</div> </div>
<button type="submit" class="btn btn-primary mt-3" id="proceedButton" disabled>Prosegui</button>
</form> <!-- BOTTOM BUTTON -->
`; <button type="submit" class="btn btn-primary mt-3" id="proceedButtonBottom" disabled>Prosegui</button>
</form>
`;
tableContainer.innerHTML = html; tableContainer.innerHTML = html;
const proceedButton = document.getElementById('proceedButton'); const selectRowsForm = document.getElementById('selectRowsForm');
selectRowsForm.addEventListener('submit', function(e) {
const checkedBoxes = Array.from(document.querySelectorAll('.row-checkbox:checked'));
if (checkedBoxes.length === 0) {
e.preventDefault();
alert('Seleziona almeno una riga.');
return;
}
const selectedRows = [];
const selectedExcelRows = [];
checkedBoxes.forEach((cb, newIndex) => {
const originalIndex = parseInt(cb.value, 10);
if (data.rows && data.rows[originalIndex]) {
selectedRows.push(data.rows[originalIndex]);
}
if (data.excel_data && data.excel_data[originalIndex]) {
selectedExcelRows.push(data.excel_data[originalIndex].excelrow);
}
// Reindex selected_rows so import_insert.php receives only the reduced rows array
cb.value = newIndex;
});
document.getElementById('selectedRowsData').value =
encodeURIComponent(JSON.stringify(selectedRows));
document.getElementById('selectedExcelRowsData').value =
encodeURIComponent(JSON.stringify(selectedExcelRows));
});
const topTableScrollbar = document.getElementById('topTableScrollbar');
const topTableScrollbarInner = document.getElementById('topTableScrollbarInner');
const mainTableContainer = document.getElementById('mainTableContainer');
const importPreviewTable = document.getElementById('importPreviewTable');
function updateTopTableScrollbar() {
if (!topTableScrollbar || !topTableScrollbarInner || !mainTableContainer || !importPreviewTable) return;
topTableScrollbarInner.style.width = importPreviewTable.scrollWidth + 'px';
if (mainTableContainer.scrollWidth > mainTableContainer.clientWidth) {
topTableScrollbar.style.display = 'block';
} else {
topTableScrollbar.style.display = 'none';
}
}
let syncingTop = false;
let syncingBottom = false;
if (topTableScrollbar && mainTableContainer) {
topTableScrollbar.addEventListener('scroll', function() {
if (syncingBottom) return;
syncingTop = true;
mainTableContainer.scrollLeft = topTableScrollbar.scrollLeft;
syncingTop = false;
});
mainTableContainer.addEventListener('scroll', function() {
if (syncingTop) return;
syncingBottom = true;
topTableScrollbar.scrollLeft = mainTableContainer.scrollLeft;
syncingBottom = false;
});
}
updateTopTableScrollbar();
setTimeout(updateTopTableScrollbar, 100);
setTimeout(updateTopTableScrollbar, 300);
window.addEventListener('resize', updateTopTableScrollbar);
const proceedButtonTop = document.getElementById('proceedButtonTop');
const proceedButtonBottom = document.getElementById('proceedButtonBottom');
const selectAllCheckbox = document.getElementById('selectAll'); const selectAllCheckbox = document.getElementById('selectAll');
const checkboxes = document.querySelectorAll('.row-checkbox'); const checkboxes = document.querySelectorAll('.row-checkbox');
function updateProceedButton() { function updateProceedButton() {
proceedButton.disabled = !Array.from(checkboxes).some(cb => cb.checked); const enabled = Array.from(checkboxes).some(cb => cb.checked);
if (proceedButtonTop) proceedButtonTop.disabled = !enabled;
if (proceedButtonBottom) proceedButtonBottom.disabled = !enabled;
} }
selectAllCheckbox.addEventListener('change', function() { selectAllCheckbox.addEventListener('change', function() {
checkboxes.forEach(checkbox => { const visibleRows = Array.from(document.querySelectorAll('.table tbody tr'))
.filter(row => row.style.display !== 'none');
visibleRows.forEach(row => {
const checkbox = row.querySelector('.row-checkbox');
if (checkbox) {
checkbox.checked = this.checked; checkbox.checked = this.checked;
}
}); });
updateProceedButton(); updateProceedButton();
}); });
checkboxes.forEach(checkbox => { checkboxes.forEach(checkbox => {
checkbox.addEventListener('change', function() { checkbox.addEventListener('change', function() {
console.log('Checkbox changed, checked:', this.checked, 'excelrow:', this.dataset.excelrow); console.log('Checkbox changed, checked:', this.checked, 'excelrow:', this.dataset.excelrow);
selectAllCheckbox.checked = Array.from(checkboxes).every(cb => cb.checked);
const visibleCheckboxes = Array.from(document.querySelectorAll('.table tbody tr'))
.filter(row => row.style.display !== 'none')
.map(row => row.querySelector('.row-checkbox'))
.filter(cb => cb !== null);
selectAllCheckbox.checked =
visibleCheckboxes.length > 0 &&
visibleCheckboxes.every(cb => cb.checked);
updateProceedButton(); updateProceedButton();
}); });
}); });
@@ -393,14 +544,49 @@ error_log("Loaded template: " . print_r($template, true));
} }
}); });
const searchInput = document.getElementById('searchInput');
const rows = document.querySelectorAll('.table tbody tr'); const rows = document.querySelectorAll('.table tbody tr');
const filterInputs = document.querySelectorAll('.column-filter');
searchInput.addEventListener('input', function() { // Stato filtri: key = colIndex, value = testo
const searchTerm = this.value.toLowerCase(); const activeFilters = {};
function applyColumnFilters() {
rows.forEach(row => { rows.forEach(row => {
const text = Array.from(row.cells).slice(1).map(cell => cell.textContent.toLowerCase()).join(' '); let visible = true;
row.style.display = text.includes(searchTerm) ? '' : 'none';
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();
}); });
}); });
File diff suppressed because it is too large Load Diff
+6 -8
View File
@@ -10,6 +10,9 @@ error_reporting(E_ALL | E_STRICT);
include('../../extra/auth.php'); include('../../extra/auth.php');
//require_once __DIR__ . '/extra/auth.php'; //require_once __DIR__ . '/extra/auth.php';
// Laravel bootstrap (loaded by auth.php) forces UTC via config/app.php — re-apply our TZ
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
// Here we just check if user is not // Here we just check if user is not
// logged in, and in that case we redirect // logged in, and in that case we redirect
// the user to vanguard login page. // the user to vanguard login page.
@@ -26,6 +29,8 @@ $nameuser = $user->present()->first_name;
$surnameuser = $user->present()->last_name; $surnameuser = $user->present()->last_name;
$emailuser = $user->present()->email; $emailuser = $user->present()->email;
$avatar = $user->present()->avatar; $avatar = $user->present()->avatar;
$lims_user_id = $user->lims_user_id ?? '';
$lims_global_user_id = $user->lims_global_user_id ?? '';
$kindofrole = $user->present()->role_id; $kindofrole = $user->present()->role_id;
@@ -35,8 +40,6 @@ $kindofrole = $user->present()->role_id;
//$iduserlogin="1"; //$iduserlogin="1";
//$nameuser="Claudio"; //$nameuser="Claudio";
//$emailuser="info@claudiosironi.com"; //$emailuser="info@claudiosironi.com";
?>
<?php
if (session_status() == PHP_SESSION_NONE) { if (session_status() == PHP_SESSION_NONE) {
session_start(); session_start();
} }
@@ -49,13 +52,8 @@ $_SESSION["emailuser"] = $emailuser;
$_SESSION["photouser"] = $avatar; $_SESSION["photouser"] = $avatar;
$photouser = $_SESSION["photouser"]; $photouser = $_SESSION["photouser"];
$photousername = basename($avatar); $photousername = basename($avatar);
?>
<?php //include files
//include files
require_once(__DIR__ . '/../../languages/en/general.php'); require_once(__DIR__ . '/../../languages/en/general.php');
//include("generalsettings.php"); //include("generalsettings.php");
?>
+9 -3
View File
@@ -22,7 +22,7 @@
<ul> <ul>
<!-- <li> <a href="index.php"><i class='bx bx-radio-circle'></i>Default</a> <!-- <li> <a href="index.php"><i class='bx bx-radio-circle'></i>Default</a>
</li> --> </li> -->
<li> <a href="import_dashboard.php"><i class='bx bx-radio-circle'></i>XLS Import</a> <li> <a href="import_dashboard.php"><i class='bx bx-radio-circle'></i>Import AREA</a>
</li> </li>
@@ -57,8 +57,14 @@
</li> </li>
<li class="menu-label">Reports</li>
<li>
<a href="rapporti_cliente_lookup.php" target="">
<div class="parent-icon"><i class="bx bx-file-find"></i>
</div>
<div class="menu-title">Ricerca Reports</div>
</a>
</li>
<li class="menu-label">Others</li> <li class="menu-label">Others</li>
+225 -16
View File
@@ -1,11 +1,22 @@
<?php include('include/headscript.php'); <?php include('include/headscript.php');
// Recupera tutte le routine dal database // Retrieve all routines from database
$db = DBHandlerSelect::getInstance(); $db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection(); $pdo = $db->getConnection();
$stmt = $pdo->prepare("SELECT * FROM routine"); $stmt = $pdo->prepare("SELECT * FROM routine");
$stmt->execute(); $stmt->execute();
$routines = $stmt->fetchAll(PDO::FETCH_ASSOC); $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Retrieve active API/JSON configurations
$stmt = $pdo->prepare("
SELECT id, name, provider_code, api_type, php_class_name
FROM api_configurations
WHERE is_active = 1
ORDER BY name ASC
");
$stmt->execute();
$apiConfigurations = $stmt->fetchAll(PDO::FETCH_ASSOC);
?> ?>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
@@ -18,26 +29,30 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
<?php include('cssinclude.php'); ?> <?php include('cssinclude.php'); ?>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script> <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<title>Insert XLS Template <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title> <title>Insert Template <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
</head> </head>
<body> <body>
<div class="wrapper"> <div class="wrapper">
<?php include('include/navbar.php'); ?> <?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?> <?php include('include/topbar.php'); ?>
<div class="page-wrapper"> <div class="page-wrapper">
<div class="page-content"> <div class="page-content">
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header"> <div class="card-header">
<h5 class="mb-0">Insert new XLS Template</h5> <h5 class="mb-0">Insert New Template</h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<p class="mb-2">Fill the following form in order to create a new import XLS template</p> <p class="mb-2">Fill the following form in order to create a new import template</p>
<p class="mb-2">Mandatory Fields</p> <p class="mb-2">Mandatory Fields</p>
<ul class="mb-0"> <ul class="mb-0">
<li>Template Name</li> <li>Template Name</li>
<li>Row Header and Column Header: where the title of the excel starts</li> <li>Source Type</li>
<li>Schema and client</li> <li>Schema and Client</li>
<li>Row Header, Column Header and Sheet Number only for XLS templates</li>
<li>API / JSON Configuration only for API / JSON templates</li>
</ul> </ul>
</div> </div>
</div> </div>
@@ -50,22 +65,86 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
</div> </div>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="col-12"> <div class="col-12">
<form id="insertTemplateForm" method="POST"> <form id="insertTemplateForm" method="POST">
<div class="mb-3"> <div class="mb-3">
<label class="form-label"><?= htmlspecialchars($templatename, ENT_QUOTES, 'UTF-8'); ?> *</label> <label class="form-label"><?= htmlspecialchars($templatename, ENT_QUOTES, 'UTF-8'); ?> *</label>
<input type="text" name="name" class="form-control" required> <input type="text" name="name" class="form-control" required>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label"><?= htmlspecialchars($rowheader, ENT_QUOTES, 'UTF-8'); ?> *</label> <label class="form-label">Source Type *</label>
<input type="number" name="header_row" class="form-control" required> <select name="source_type" id="sourceType" class="form-control" required>
<option value="XLS" selected>XLS</option>
<option value="API">API / JSON</option>
<option value="PDF">PDF</option>
</select>
<small class="text-muted">Choose the source used by this template</small>
</div> </div>
<div class="mb-3"> <div class="mb-3" id="headerRowWrapper">
<label class="form-label"><?= htmlspecialchars($rowheader, ENT_QUOTES, 'UTF-8'); ?> *</label>
<input type="number" name="header_row" id="headerRow" class="form-control" value="1" required>
</div>
<div class="mb-3" id="startColumnWrapper">
<label class="form-label"><?= htmlspecialchars($columnheader, ENT_QUOTES, 'UTF-8'); ?> *</label> <label class="form-label"><?= htmlspecialchars($columnheader, ENT_QUOTES, 'UTF-8'); ?> *</label>
<input type="text" name="start_column" class="form-control" required> <input type="text" name="start_column" id="startColumn" class="form-control" value="A" required>
</div>
<div class="mb-3" id="xlsSheetNumberWrapper">
<label class="form-label">XLS Sheet Number</label>
<input
type="number"
name="xls_sheet_index"
id="xlsSheetIndex"
class="form-control"
min="0"
value="0">
<small class="text-muted">
Use 0 for the first sheet, 1 for the second sheet, 2 for the third sheet, and so on.
</small>
</div>
<div class="mb-3" id="apiConfigWrapper" style="display: none;">
<label class="form-label">API / JSON Configuration *</label>
<select name="api_config_id" id="apiConfigSelect" class="form-control">
<option value="">Select an API configuration...</option>
<?php foreach ($apiConfigurations as $apiConfig): ?>
<?php
$apiLabelParts = [];
if (!empty($apiConfig['name'])) {
$apiLabelParts[] = $apiConfig['name'];
}
if (!empty($apiConfig['provider_code'])) {
$apiLabelParts[] = '[' . $apiConfig['provider_code'] . ']';
}
if (!empty($apiConfig['api_type'])) {
$apiLabelParts[] = '(' . $apiConfig['api_type'] . ')';
}
if (!empty($apiConfig['php_class_name'])) {
$apiLabelParts[] = '- ' . $apiConfig['php_class_name'];
}
$apiLabel = implode(' ', $apiLabelParts);
?>
<option value="<?php echo (int)$apiConfig['id']; ?>">
<?php echo htmlspecialchars($apiLabel, ENT_QUOTES, 'UTF-8'); ?>
</option>
<?php endforeach; ?>
</select>
<small class="text-muted">
Select the API/JSON configuration linked to this template.
</small>
</div> </div>
<div class="mb-3"> <div class="mb-3">
@@ -86,12 +165,12 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Button Background Color</label> <label class="form-label">Button Background Color</label>
<input type="color" name="button_bg_color" class="form-control" value="#007bff"> <input type="color" name="button_bg_color" class="form-control form-control-color" value="#007bff">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Button Text Color</label> <label class="form-label">Button Text Color</label>
<input type="color" name="button_text_color" class="form-control" value="#ffffff"> <input type="color" name="button_text_color" class="form-control form-control-color" value="#ffffff">
</div> </div>
<div class="mb-3"> <div class="mb-3">
@@ -125,6 +204,7 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
</option> </option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<div id="routineDetails" class="mt-2" style="display: none;"> <div id="routineDetails" class="mt-2" style="display: none;">
<h6>Routine Details</h6> <h6>Routine Details</h6>
<p><strong>Name:</strong> <span id="routineName"></span></p> <p><strong>Name:</strong> <span id="routineName"></span></p>
@@ -142,8 +222,10 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="overlay toggle-icon"></div> <div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a> <a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?> <?php include('include/footer.php'); ?>
@@ -167,6 +249,18 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
const routineAction2 = document.getElementById("routineAction2"); const routineAction2 = document.getElementById("routineAction2");
const routineAction3 = document.getElementById("routineAction3"); const routineAction3 = document.getElementById("routineAction3");
const sourceType = document.getElementById("sourceType");
const headerRowWrapper = document.getElementById("headerRowWrapper");
const startColumnWrapper = document.getElementById("startColumnWrapper");
const xlsSheetNumberWrapper = document.getElementById("xlsSheetNumberWrapper");
const apiConfigWrapper = document.getElementById("apiConfigWrapper");
const headerRow = document.getElementById("headerRow");
const startColumn = document.getElementById("startColumn");
const xlsSheetIndex = document.getElementById("xlsSheetIndex");
const apiConfigSelect = document.getElementById("apiConfigSelect");
if (!form || !clientLoadingStatus || !schemaLoadingStatus || !routineSelect || !routineDetails) { if (!form || !clientLoadingStatus || !schemaLoadingStatus || !routineSelect || !routineDetails) {
alert("Errore: Uno o più elementi della pagina non sono stati trovati. Contatta l'amministratore."); alert("Errore: Uno o più elementi della pagina non sono stati trovati. Contatta l'amministratore.");
return; return;
@@ -187,30 +281,101 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
allowClear: true allowClear: true
}); });
$('#apiConfigSelect').select2({
placeholder: "Select an API configuration...",
allowClear: true
});
function updateSourceFields() {
const selectedSource = sourceType.value;
const isXls = selectedSource === 'XLS';
const isApiJson = selectedSource === 'API';
if (isXls) {
headerRowWrapper.style.display = 'block';
startColumnWrapper.style.display = 'block';
xlsSheetNumberWrapper.style.display = 'block';
headerRow.required = true;
startColumn.required = true;
xlsSheetIndex.required = true;
headerRow.disabled = false;
startColumn.disabled = false;
xlsSheetIndex.disabled = false;
apiConfigWrapper.style.display = 'none';
apiConfigSelect.required = false;
apiConfigSelect.disabled = true;
$('#apiConfigSelect').val(null).trigger('change');
} else {
headerRowWrapper.style.display = 'none';
startColumnWrapper.style.display = 'none';
xlsSheetNumberWrapper.style.display = 'none';
headerRow.required = false;
startColumn.required = false;
xlsSheetIndex.required = false;
headerRow.disabled = true;
startColumn.disabled = true;
xlsSheetIndex.disabled = true;
if (isApiJson) {
apiConfigWrapper.style.display = 'block';
apiConfigSelect.required = true;
apiConfigSelect.disabled = false;
} else {
apiConfigWrapper.style.display = 'none';
apiConfigSelect.required = false;
apiConfigSelect.disabled = true;
$('#apiConfigSelect').val(null).trigger('change');
}
}
}
sourceType.addEventListener('change', updateSourceFields);
updateSourceFields();
async function loadClients() { async function loadClients() {
try { try {
clientLoadingStatus.style.display = 'inline'; clientLoadingStatus.style.display = 'inline';
clientLoadingStatus.textContent = 'Recupero clienti in corso...'; clientLoadingStatus.textContent = 'Recupero clienti in corso...';
const response = await fetch("get_clienti.php", { const response = await fetch("get_clienti.php", {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json"
} }
}); });
const data = await response.json(); const data = await response.json();
if (!response.ok) throw new Error(data.error || `Errore HTTP: ${response.status}`);
if (!response.ok) {
throw new Error(data.error || `Errore HTTP: ${response.status}`);
}
const select = document.getElementById("clientSelect"); const select = document.getElementById("clientSelect");
select.innerHTML = '<option value="">Select a client...</option>'; select.innerHTML = '<option value="">Select a client...</option>';
data.value.forEach(client => { data.value.forEach(client => {
const nome = client.Nominativo || "Nome non disponibile"; const nome = client.Nominativo || "Nome non disponibile";
const id = client.IdCliente || "ID non disponibile"; const id = client.IdCliente || "ID non disponibile";
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
const codiceCliente = (client.CodiceCliente ?? client.codiceCliente ?? "").toString().trim();
const suffix = (codiceCliente.split("_")[1] || "").trim();
const shortCode = suffix || (codiceCliente ? codiceCliente.charAt(0) : "--");
const option = new Option(`${nome.trim()} - ${shortCode} (ID: ${id})`, id);
select.add(option); select.add(option);
}); });
$(select).trigger('change'); $(select).trigger('change');
clientLoadingStatus.textContent = "Clienti caricati."; clientLoadingStatus.textContent = "Clienti caricati.";
} catch (error) { } catch (error) {
clientLoadingStatus.textContent = "Errore nel caricamento."; clientLoadingStatus.textContent = "Errore nel caricamento.";
Swal.fire({ Swal.fire({
title: "Errore!", title: "Errore!",
text: "Impossibile caricare i clienti: " + error.message, text: "Impossibile caricare i clienti: " + error.message,
@@ -226,26 +391,43 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
try { try {
schemaLoadingStatus.style.display = 'inline'; schemaLoadingStatus.style.display = 'inline';
schemaLoadingStatus.textContent = 'Caricamento schemi in corso...'; schemaLoadingStatus.textContent = 'Caricamento schemi in corso...';
const response = await fetch("get_schemi.php", { const response = await fetch("get_schemi.php", {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json"
} }
}); });
const data = await response.json(); const data = await response.json();
if (!response.ok) throw new Error(data.error || `Errore HTTP: ${response.status}`);
if (!response.ok) {
throw new Error(data.error || `Errore HTTP: ${response.status}`);
}
const select = document.getElementById("schemaSelect"); const select = document.getElementById("schemaSelect");
select.innerHTML = '<option value="">Select a schema...</option>'; select.innerHTML = '<option value="">Select a schema...</option>';
data.value.forEach(schema => {
const sortedSchemas = [...data.value].sort((a, b) => {
const nomeA = (a.Nome || "").trim().toLowerCase();
const nomeB = (b.Nome || "").trim().toLowerCase();
return nomeA.localeCompare(nomeB, 'it', {
sensitivity: 'base'
});
});
sortedSchemas.forEach(schema => {
const nome = schema.Nome || "Nome non disponibile"; const nome = schema.Nome || "Nome non disponibile";
const id = schema.IdSchemaCustomFields || "ID non disponibile"; const id = schema.IdSchemaCustomFields || "ID non disponibile";
const option = new Option(`${nome.trim()} (ID: ${id})`, id); const option = new Option(`${nome.trim()} (ID: ${id})`, id);
select.add(option); select.add(option);
}); });
$(select).trigger('change'); $(select).trigger('change');
schemaLoadingStatus.textContent = "Schemi caricati."; schemaLoadingStatus.textContent = "Schemi caricati.";
} catch (error) { } catch (error) {
schemaLoadingStatus.textContent = "Errore nel caricamento."; schemaLoadingStatus.textContent = "Errore nel caricamento.";
Swal.fire({ Swal.fire({
title: "Errore!", title: "Errore!",
text: "Impossibile caricare gli schemi: " + error.message, text: "Impossibile caricare gli schemi: " + error.message,
@@ -270,6 +452,7 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
}); });
} }
} }
loadData(); loadData();
const routines = <?php echo json_encode($routines); ?>; const routines = <?php echo json_encode($routines); ?>;
@@ -277,8 +460,10 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
function updateRoutineDetails() { function updateRoutineDetails() {
const selectedId = routineSelect.value; const selectedId = routineSelect.value;
routineDetails.style.display = selectedId ? 'block' : 'none'; routineDetails.style.display = selectedId ? 'block' : 'none';
if (selectedId) { if (selectedId) {
const routine = routines.find(r => r.idroutine == selectedId); const routine = routines.find(r => r.idroutine == selectedId);
if (routine) { if (routine) {
routineName.textContent = routine.name || 'N/A'; routineName.textContent = routine.name || 'N/A';
routineDescription.textContent = routine.description || 'N/A'; routineDescription.textContent = routine.description || 'N/A';
@@ -300,6 +485,7 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
routineAction3.textContent = ''; routineAction3.textContent = '';
} }
} }
routineSelect.addEventListener('change', updateRoutineDetails); routineSelect.addEventListener('change', updateRoutineDetails);
updateRoutineDetails(); updateRoutineDetails();
@@ -308,6 +494,28 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
let formData = new FormData(this); let formData = new FormData(this);
const selectedSource = sourceType.value;
if (selectedSource === 'XLS' && xlsSheetIndex.value === '') {
Swal.fire({
title: "Errore!",
text: "Inserisci il numero del foglio XLS.",
icon: "error",
confirmButtonText: "OK"
});
return;
}
if (selectedSource === 'API' && !apiConfigSelect.value) {
Swal.fire({
title: "Errore!",
text: "Seleziona una configurazione API / JSON.",
icon: "error",
confirmButtonText: "OK"
});
return;
}
const clientSelect = document.getElementById("clientSelect"); const clientSelect = document.getElementById("clientSelect");
const clientId = clientSelect.value; const clientId = clientSelect.value;
const selectedClientOption = clientSelect.options[clientSelect.selectedIndex]; const selectedClientOption = clientSelect.options[clientSelect.selectedIndex];
@@ -350,6 +558,7 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
const nameMatch = optionText.match(/^(.+?)(?:\s*\(ID:\s*\d+\))?$/); const nameMatch = optionText.match(/^(.+?)(?:\s*\(ID:\s*\d+\))?$/);
schemaName = nameMatch ? nameMatch[1].trim() : optionText; schemaName = nameMatch ? nameMatch[1].trim() : optionText;
} }
formData.append("idschema", schemaId); formData.append("idschema", schemaId);
formData.append("schemaname", schemaName); formData.append("schemaname", schemaName);
+4 -1
View File
@@ -13,7 +13,10 @@ try {
} }
// Recupera solo i template attivi // Recupera solo i template attivi
$stmt = $pdo->query("SELECT id, button_label, button_bg_color, button_text_color, button_size FROM excel_templates WHERE status = 'active'"); $stmt = $pdo->query("SELECT id, button_label, button_size, button_bg_color, button_text_color, source_type
FROM excel_templates
WHERE status = 'active'
ORDER BY button_label ASC");
$templates = $stmt->fetchAll(PDO::FETCH_ASSOC); $templates = $stmt->fetchAll(PDO::FETCH_ASSOC);
$response["success"] = true; $response["success"] = true;
+48 -2
View File
@@ -14,11 +14,57 @@ if (!$iddatadb) {
} }
try { try {
$stmt = $pdo->prepare("SELECT id, iddatadb, part_number, part_description, idmatrice, note, dateexpiry FROM identification_parts WHERE iddatadb = :iddatadb ORDER BY part_number ASC"); // 1) prendo templateid da datadb
$stmtTpl = $pdo->prepare("SELECT templateid FROM datadb WHERE iddatadb = :iddatadb LIMIT 1");
$stmtTpl->execute([':iddatadb' => $iddatadb]);
$templateid = $stmtTpl->fetchColumn();
// 2) prendo (max 1) field_id visibile in parts
$extraFieldId = null;
if ($templateid) {
$stmtEF = $pdo->prepare("SELECT field_id FROM template_mapping WHERE template_id = :templateid AND is_visible_parts = 1 ORDER BY id ASC LIMIT 1");
$stmtEF->execute([':templateid' => $templateid]);
$extraFieldId = $stmtEF->fetchColumn();
if ($extraFieldId !== false) $extraFieldId = (int)$extraFieldId;
else $extraFieldId = null;
}
// 3) carico parts + join su tabella figlia
if ($extraFieldId) {
$stmt = $pdo->prepare("
SELECT
p.id, p.iddatadb, p.part_number, p.part_description, p.mix, 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, mix, idmatrice, note, dateexpiry,
NULL AS extra_value_id, NULL AS extra_value_text
FROM identification_parts
WHERE iddatadb = :iddatadb
ORDER BY part_number ASC
");
$stmt->execute([':iddatadb' => $iddatadb]); $stmt->execute([':iddatadb' => $iddatadb]);
}
$parts = $stmt->fetchAll(PDO::FETCH_ASSOC); $parts = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['success' => true, 'parts' => $parts]); echo json_encode([
'success' => true,
'parts' => $parts,
'extra_field_id' => $extraFieldId,
'debug_sql' => ($extraFieldId ? 'WITH_CF_JOIN' : 'NO_CF')
]);
} catch (PDOException $e) { } catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Errore nel caricamento: ' . $e->getMessage()]); echo json_encode(['success' => false, 'message' => 'Errore nel caricamento: ' . $e->getMessage()]);
} }
+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"
}
---

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