Files
script/DOC_NRAP & AB_NRAP portale trasporto v4.5 WORKING..user.js
2026-05-12 21:05:35 +02:00

645 lines
27 KiB
JavaScript

// ==UserScript==
// @name DOC_NRAP & AB_NRAP portale trasporto v4.5 WORKING.
// @namespace http://tampermonkey.net/
// @version 4.5
// @description Automazione DOC_NRAP e AB_NRAP con gestione modale di conferma
// @author Tu
// @match *://www.ilportaledeltrasporto.it/nove/gestioneistanzanove/spa/*
// @grant GM_xmlhttpRequest
// @connect localhost
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
// --- VARIABILI DI STATO ---
let TELAIO = '';
let VALORE2 = '0';
let VALORE3 = '';
let TIPO_ATTUALE = '';
let taskAttivo = false;
let downloadInCorso = false;
let completatoLocalmente = false;
const ENDPOINT_GET = 'http://localhost/hook/dati2.php';
// --- HELPER FUNCTIONS ---
// Funzione per discriminare lo stato delle schede/tessere
function getTessereStatus() {
let stati = { Tess1: "Sconosciuto", Tess2: "Sconosciuto", Tess3: "Sconosciuto" };
const cardContainers = document.querySelectorAll('.CardComponent.d-flex.col');
// Se non ci sono tessere, esci silenziosamente senza fare log di errore
if (cardContainers.length === 0) {
return null; // Restituiamo null per capire che non siamo nella dashboard
}
cardContainers.forEach((container, index) => {
const article = container.querySelector('article.Card');
const h2 = container.querySelector('.cardText h2');
if (article && h2) {
const titolo = h2.innerText.trim().toLowerCase();
let statoRilevato = "Sconosciuto";
// Verifica lo stato dalle classi dell'elemento article
if (article.classList.contains('Active')) statoRilevato = "Attivo";
else if (article.classList.contains('Locked')) statoRilevato = "Bloccato";
else if (article.classList.contains('Completed')) statoRilevato = "Completato";
// Mappatura precisa basata sul testo dell'H2
if (titolo.includes("verifica documentazione")) {
stati.Tess1 = statoRilevato;
} else if (titolo.includes("visita e prova")) {
stati.Tess2 = statoRilevato;
} else if (titolo.includes("abilitazione telaio")) {
stati.Tess3 = statoRilevato;
}
// console.log(`[TM] Rilevata Tessera: "${h2.innerText.trim()}" | Stato: ${statoRilevato}`);
}
});
return stati;
}
function getTessereStatus_old() {
let stati = {
Tess1: "Sconosciuto",
Tess2: "Sconosciuto",
Tess3: "Sconosciuto"
};
// Seleziona i contenitori basandosi sulla classe che hai indicato
const cardContainers = document.querySelectorAll('.CardComponent.d-flex.col');
if (cardContainers.length === 0) {
console.warn("[TM] Nessun contenitore CardComponent trovato.");
return stati;
}
cardContainers.forEach((container, index) => {
const article = container.querySelector('article.Card');
const h2 = container.querySelector('.cardText h2');
if (article && h2) {
const titolo = h2.innerText.trim().toLowerCase();
let statoRilevato = "Sconosciuto";
// Verifica lo stato dalle classi dell'elemento article
if (article.classList.contains('Active')) statoRilevato = "Attivo";
else if (article.classList.contains('Locked')) statoRilevato = "Bloccato";
else if (article.classList.contains('Completed')) statoRilevato = "Completato";
// Mappatura precisa basata sul testo dell'H2
if (titolo.includes("verifica documentazione")) {
stati.Tess1 = statoRilevato;
} else if (titolo.includes("visita e prova")) {
stati.Tess2 = statoRilevato;
} else if (titolo.includes("abilitazione telaio")) {
stati.Tess3 = statoRilevato;
}
// console.log(`[TM] Rilevata Tessera: "${h2.innerText.trim()}" | Stato: ${statoRilevato}`);
}
});
return stati;
}
// Pulizia stringhe: rimuove ogni spazio (inizio, fine, centro) e porta in maiuscolo
function cleanString(str) {
if (!str) return "";
return str.toString().replace(/\s+/g, '').toUpperCase();
}
// Click sicuro per elementi React
function clickReactSafe(element) {
if (!element || typeof element.dispatchEvent !== 'function') return;
const opts = { bubbles: true, cancelable: true, view: window.unsafeWindow || window };
try {
element.dispatchEvent(new MouseEvent('mousedown', opts));
element.dispatchEvent(new MouseEvent('mouseup', opts));
element.click();
} catch (e) {
element.dispatchEvent(new Event('mousedown', { bubbles: true }));
element.dispatchEvent(new Event('mouseup', { bubbles: true }));
element.click();
}
}
/**
* Premere il pulsante "Procedi" della tessera ennesima (1-based).
* @param {number} n - Indice della tessera (1 per la prima, 2 per la seconda, ecc.)
*/
function Procedi(n) {
console.log(`[TM] Test Procedi(${n}) avviato...`);
// CORREZIONE: Le classi devono essere unite dai punti senza spazi
// perché appartengono allo stesso elemento div
const cardContainers = document.querySelectorAll('.CardComponent.d-flex.col');
if (cardContainers.length === 0) {
console.error("[TM] ERRORE: Nessun contenitore trovato. Verificare il selettore .CardComponent.d-flex.col");
return;
}
console.log(`[TM] Tessere trovate in pagina: ${cardContainers.length}`);
// Controllo indice
if (n < 1 || n > cardContainers.length) {
console.error(`[TM] ERRORE: Indice ${n} non valido. Tessere disponibili: ${cardContainers.length}`);
return;
}
// Selezioniamo la tessera corretta (n-1)
const targetCard = cardContainers[n - 1];
console.log(`[TM] Opero sulla tessera #${n}:`, targetCard.querySelector('h2')?.innerText);
// Cerchiamo il pulsante Procedi
// Nota: nell'HTML inviato la classe del footer è "cardFooter" (minuscolo/maiuscolo come nel DOM)
const btnProcedi = Array.from(targetCard.querySelectorAll('button'))
.find(b => b.textContent.trim().toUpperCase() === 'PROCEDI');
if (!btnProcedi) {
console.error(`[TM] Pulsante 'Procedi' non trovato nella tessera #${n}.`);
return;
}
if (btnProcedi.disabled) {
console.warn(`[TM] Il pulsante della tessera #${n} è DISABILITATO.`);
return;
}
console.log(`[TM] Click sulla tessera #${n}...`);
clickReactSafe(btnProcedi);
};
// Input sicuro per elementi React
function setInputReactSafe(element, value) {
if (!element) return;
// Determina se è un input o una textarea per usare il prototipo corretto
const prototype = element.tagName === 'TEXTAREA'
? HTMLTextAreaElement.prototype
: HTMLInputElement.prototype;
const setter = Object.getOwnPropertyDescriptor(prototype, 'value').set;
element.focus();
setter.call(element, value);
// Trigger degli eventi per far capire a React che il testo è cambiato
element.dispatchEvent(new Event('input', { bubbles: true }));
element.dispatchEvent(new Event('change', { bubbles: true }));
element.dispatchEvent(new Event('blur', { bubbles: true }));
}
function setInputReactSafe_old(input, value) {
if (!input) return;
const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set;
input.focus();
setter.call(input, value);
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
}
// Gestione della modale di conferma per AB_NRAP
function gestisciModaleConferma() {
const modale = document.querySelector('.ModalsPortal__container.mx-auto');
if (modale) {
const btnConfermaModale = Array.from(modale.querySelectorAll('button'))
.find(b => {
const spans = b.querySelectorAll('span');
return Array.from(spans).some(s => s.textContent.trim() === "Conferma");
});
if (btnConfermaModale) {
console.log("[TM] Pulsante 'Conferma' trovato nella modale. Clicco...");
clickReactSafe(btnConfermaModale);
// Attendiamo che la modale si chiuda e il sistema elabori prima di segnare fine task
setTimeout(segnaComeCompletato, 3000);
return true;
}
}
return false;
}
// --- LOGICA SERVER ---
function avviaDownloadDocumenti() {
if (downloadInCorso) return;
const tuttiIBottoni = Array.from(document.querySelectorAll('button.DocumentsSection__file-btn'));
if (tuttiIBottoni.length === 0) return;
downloadInCorso = true;
const titoliGiaVisti = new Set();
let delayDoc = 0;
tuttiIBottoni.forEach((btn, index, array) => {
const riga = btn.closest('tr');
const titolo = riga ? riga.innerText.trim().split('\t')[0] : btn.innerText.trim();
if (!titoliGiaVisti.has(titolo)) {
titoliGiaVisti.add(titolo);
setTimeout(() => {
console.log(`[TM] 1. Avvio Download: ${titolo}`);
clickReactSafe(btn);
// --- SEQUENZA MODALE ---
// STEP A: Click Esporta File (dopo 1.2 secondi)
setTimeout(() => {
const btnEsporta = Array.from(document.querySelectorAll('button.btn-primary.btn-xs'));
if (btnEsporta) {
console.log(`[TM] 2. Click Esporta File per: ${titolo}`);
clickReactSafe(btnEsporta);
}
// STEP B: Click Chiudi Modale (dopo altri 1.5 secondi dal click esporta)
setTimeout(() => {
const btnChiudiModale = document.querySelector('button.ModalContent__header--btn');
if (btnChiudiModale) {
console.log(`[TM] 3. Chiusura modale per: ${titolo}`);
clickReactSafe(btnChiudiModale);
}
}, 1500);
}, 1200);
// -----------------------
if (index === array.length - 1) {
setTimeout(segnaComeCompletato, 5000);
}
}, delayDoc);
delayDoc += 3000;
}
});
}
function fetchDati() {
GM_xmlhttpRequest({
method: "GET",
url: ENDPOINT_GET,
onload: function(response) {
try {
const data = JSON.parse(response.responseText);
const targetHome = 'https://www.ilportaledeltrasporto.it/nove/gestioneistanzanove/spa/le-mie-istanze';
// Cerca questo blocco dentro fetchDati() e sostituiscilo:
if (data.tipo === 'RESET_NRAP') {
console.log("[TM] Ricevuto comando RESET. Fermo i task e avviso il server...");
// 1. Impostiamo lo stato su RESET così segnaComeCompletato() sa cosa inviare
TIPO_ATTUALE = 'RESET_NRAP';
TELAIO = '';
VALORE2 = '0';
VALORE3 = '';
// 2. Chiamiamo la funzione che già possiedi per notificare il server
segnaComeCompletato();
// 3. Opzionale: Torna in home solo se non ci sei già
if (window.location.href !== targetHome) {
window.location.href = targetHome;
}
return;
}
if ((data.tipo === 'DOC_NRAP' || data.tipo === 'AB_NRAP') && data.task === 'fare') {
if (TELAIO !== '' && TELAIO !== data.valore1) {
TELAIO = data.valore1;
window.location.href = targetHome;
return;
}
if (TELAIO !== data.valore1 || TIPO_ATTUALE !== data.tipo) {
TELAIO = data.valore1;
VALORE2 = data.valore2;
VALORE3 = data.valore3;
TIPO_ATTUALE = data.tipo;
completatoLocalmente = false;
console.log(`[TM] Ricevuto task ${TIPO_ATTUALE} per: ${TELAIO}`);
}
if (!completatoLocalmente) {
taskAttivo = true;
eseguiAutomazione();
}
// Dentro l'else di fetchDati (quando data.task NON è 'fare')
} else {
if (taskAttivo) {
console.log("[TM] Nessun task attivo dal server, rilascio il controllo manuale.");
}
taskAttivo = false;
completatoLocalmente = false; // Rimuovi o commenta questa riga se vuoi evitare loop continui
}
} catch (e) {
console.error("[TM] Errore parsing dati", e);
}
}
});
}
function segnaComeCompletato() {
if (completatoLocalmente) return;
completatoLocalmente = true;
taskAttivo = false;
const payload = {
tipo: TIPO_ATTUALE,
task: "COMPLETATO",
valore1: TELAIO,
valore2: VALORE2,
valore3: VALORE3
};
GM_xmlhttpRequest({
method: "POST",
url: ENDPOINT_GET,
data: JSON.stringify(payload),
headers: { "Content-Type": "application/json" },
onload: function() {
console.log(`[TM] Task ${TIPO_ATTUALE} completato sul server.`);
downloadInCorso = false;
}
});
}
// --- AUTOMAZIONE PAGINE ---
function eseguiAutomazione() {
// Se il task non è attivo o abbiamo già finito su questa pagina, esci subito
if (!taskAttivo || completatoLocalmente) return;
const url = window.location.href;
// --- 1. Pagina di Ricerca ---
if (url.includes('le-mie-istanze')) {
const input = document.getElementById('numeroTelaio');
const btnCerca = Array.from(document.querySelectorAll('button')).find(b => b.textContent.includes('Cerca'));
const btnTabella = document.querySelector('button.action-button-table');
if (input && TELAIO !== '' && cleanString(input.value) !== cleanString(TELAIO)) {
setInputReactSafe(input, TELAIO);
setTimeout(() => { if(btnCerca) clickReactSafe(btnCerca); }, 800);
}
if (btnTabella) setTimeout(() => clickReactSafe(btnTabella), 1500);
return; // Fine logica ricerca
}
// --- 2. Gestione Dashboard vs Dettaglio ---
// Recuperiamo lo stato delle tessere. getTessereStatus() ora deve restituire null
// se non trova i contenitori (come spiegato prima), evitando log di errore.
const situazione = getTessereStatus();
// CASO A: Siamo nella Dashboard (Tessere visibili)
if (situazione !== null && !url.includes('dettaglio-istanza') && !url.includes('abilitazione-telaio')) {
const btnProcedi = Array.from(document.querySelectorAll('button')).find(b => b.textContent.trim() === 'Procedi');
if (btnProcedi && !btnProcedi.disabled) {
if (TIPO_ATTUALE === 'DOC_NRAP' && situazione.Tess1 === "Attivo") {
console.log("[TM] Dashboard: Apro Tessera 1 (Documentazione)");
setTimeout(() => { Procedi(1); }, 1000);
} else if (TIPO_ATTUALE === 'AB_NRAP' && situazione.Tess3 === "Attivo") {
console.log("[TM] Dashboard: Apro Tessera 3 (Abilitazione)");
setTimeout(() => { Procedi(3); }, 1000);
}
}
return; // Esci: abbiamo gestito la dashboard
}
// CASO B: Siamo nella Pagina di Dettaglio/Compilazione (Abilitazione o Download)
if (url.includes('dettaglio-istanza') || url.includes('abilitazione-telaio')) {
// Sotto-caso: Compilazione Dati (AB_NRAP)
if (TIPO_ATTUALE === 'AB_NRAP') {
const inputCodImmat = document.getElementById('Codice immatricolazione');
const inputNota = document.getElementById('Nota*') || document.getElementById('Nota');
if (inputCodImmat && inputNota) {
console.log("[TM] Pagina compilazione rilevata. Inserisco dati...");
// Inserimento dati con logica React
setInputReactSafe(inputCodImmat, VALORE2);
setInputReactSafe(inputNota, VALORE3);
// Forza il focus out per "fissare" il valore in React
inputCodImmat.dispatchEvent(new Event('blur', { bubbles: true }));
inputNota.dispatchEvent(new Event('blur', { bubbles: true }));
console.log("[TM] Compilazione eseguita. Ora l'Observer si ferma.");
// BLOCCA L'OBSERVER: impostando a true, questa intera funzione
// si fermerà al primo IF nei cicli successivi.
completatoLocalmente = true;
}
}
// Sotto-caso: Download Documenti (DOC_NRAP)
else if (TIPO_ATTUALE === 'DOC_NRAP') {
if (downloadInCorso) return;
if (parseInt(VALORE2) > 0) {
console.log("[TM] Pagina documenti rilevata. Avvio download...");
avviaDownloadDocumenti();
} else {
console.log("[TM] Nessun documento richiesto (Valore2=0). Chiudo.");
segnaComeCompletato();
}
}
}
}
function eseguiAutomazione_old() {
if (!taskAttivo || completatoLocalmente) return;
const url = window.location.href;
// 1. Pagina di Ricerca
if (url.includes('le-mie-istanze')) {
const input = document.getElementById('numeroTelaio');
const btnCerca = Array.from(document.querySelectorAll('button')).find(b => b.textContent.includes('Cerca'));
const btnTabella = document.querySelector('button.action-button-table');
if (input && TELAIO !== '' && cleanString(input.value) !== cleanString(TELAIO)) {
setInputReactSafe(input, TELAIO);
setTimeout(() => { if(btnCerca) clickReactSafe(btnCerca); }, 800);
}
if (btnTabella) setTimeout(() => clickReactSafe(btnTabella), 1500);
//if (btnTabella) console.log("Pulsante Tabella trovato! esegui il comando a mano");
}
// --- 2. Pagina Dashboard (Tessere) ---
if (url.includes('/abilitazione/') && !url.includes('dettaglio-istanza')) {
const situazione = getTessereStatus();
const btnProcedi = Array.from(document.querySelectorAll('button')).find(b => b.textContent.trim() === 'Procedi');
if (btnProcedi && !btnProcedi.disabled) {
if (TIPO_ATTUALE === 'DOC_NRAP' && situazione.Tess1 === "Attivo") {
setTimeout(() => { Procedi(1); }, 1000);
} else if (TIPO_ATTUALE === 'AB_NRAP' && situazione.Tess3 === "Attivo") {
setTimeout(() => { Procedi(3); }, 1000);
}
}
}
// --- 3. Pagina di Abilitazione (Dettaglio) ---
if (url.includes('/abilitazione/') && url.includes('/abilitazione-telaio')) {
if (TIPO_ATTUALE === 'AB_NRAP') {
const inputCodImmat = document.getElementById('Codice immatricolazione');
// Cerca la nota sia con che senza asterisco
const inputNota = document.getElementById('Nota*') || document.getElementById('Nota');
console.log("[TM] Pagina di abilitazione rilevata. Compilo...");
if (inputCodImmat && inputNota) {
console.log("[TM] Pagina di abilitazione rilevata. Compilo...");
//setInputReactSafe(inputCodImmat, VALORE2);
//setInputReactSafe(inputNota, VALORE3);
console.log("[TM] Dati inseriti. Ora mi fermo e ti lascio il controllo.");
// IMPORTANTE: Segniamo come completato localmente solo la "scrittura"
// così l'Observer smette di chiamare eseguiAutomazione() per questa pagina.
completatoLocalmente = true;
}
}
if (TIPO_ATTUALE === 'DOC_NRAP') {
if (downloadInCorso) return;
if (parseInt(VALORE2) > 0) {
avviaDownloadDocumenti();
} else {
segnaComeCompletato();
}
}
}
}
function eseguiAutomazione2() {
if (!taskAttivo || completatoLocalmente) return;
const situazione = getTessereStatus();
// GUARDIA: Se tutte le tessere sono "Bloccato" o "Sconosciuto",
// probabilmente React sta ancora caricando. Fermati e aspetta il prossimo ciclo.
if (situazione.Tess1 === "Bloccato" && situazione.Tess2 === "Bloccato") {
return;
}
const url = window.location.href;
// 1. Pagina di Ricerca
if (url.includes('le-mie-istanze')) {
const input = document.getElementById('numeroTelaio');
const btnCerca = Array.from(document.querySelectorAll('button')).find(b => b.textContent.includes('Cerca'));
const btnTabella = document.querySelector('button.action-button-table');
if (input && TELAIO !== '' && cleanString(input.value) !== cleanString(TELAIO)) {
setInputReactSafe(input, TELAIO);
setTimeout(() => { if(btnCerca) clickReactSafe(btnCerca); }, 800);
}
if (btnTabella) setTimeout(() => clickReactSafe(btnTabella), 1500);
//if (btnTabella) console.log("Pulsante Tabella trovato! esegui il comando a mano");
}
// 2. Pagina Intermedia (Abilitazione / Dashboard)
if (url.includes('/abilitazione/') && !url.includes('dettaglio-istanza')) {
const btnProcedi = Array.from(document.querySelectorAll('button')).find(b => b.textContent.trim() === 'Procedi');
if (btnProcedi && !btnProcedi.disabled) {
// --- LOGICA SELETTIVA ---
if (TIPO_ATTUALE === 'DOC_NRAP') {
console.log("Stato tessere al click:", situazione);
if (situazione.Tess1 === "Attivo" && situazione.Tess3 === "Bloccato") {
console.log("[TM] DOC_NRAP: Tessera 1 pronta. Procedo apertura documenti...");
setTimeout(() => { Procedi(1); }, 1000);
} else if (situazione.Tess1 === "Completato"){
console.warn("[TM] DOC_NRAP: Vista documentazione");
} else if (situazione.Tess1 === "Bloccato"){
console.warn("[TM] DOC_NRAP: Documentazione bloccata, attendere");
}
}
else if (TIPO_ATTUALE === 'AB_NRAP') {
// Logica specifica per AB_NRAP (es. solo se Tess3 è Attiva)
if (situazione.Tess3 === "Attivo") {
console.log("[TM] DOC_NRAP: Tessera 3 Ok si procede a validazione...");
setTimeout(() => { Procedi(3); }, 1000);
}
}
}
}
// 3. Pagina Dettaglio Istanza
if (url.includes('dettaglio-istanza')) {
const situazioneInDettaglio = getTessereStatus();
// CASO A: Operazione AB_NRAP (Solo inserimento dati, NO clic automatico)
if (TIPO_ATTUALE === 'AB_NRAP') {
const inputCodImmat = document.getElementById('Codice immatricolazione');
const inputNota = document.getElementById('Nota*');
let datiInseriti = false;
// 1. Inserimento Codice Immatricolazione (Valore2)
if (inputCodImmat.value !== VALORE2) {
console.log("[TM] Inserisco Codice Immatricolazione:", VALORE2);
setInputReactSafe(inputCodImmat, VALORE2);
datiInseriti = true;
}
// 2. Inserimento Nota (Valore3)
if (inputNota.value !== VALORE3) {
console.log("[TM] Inserisco Nota:", VALORE3);
setInputReactSafe(inputNota, VALORE3);
datiInseriti = true;
}
// 3. Feedback visivo e stop
if (datiInseriti) {
console.log("[TM] Campi compilati. Attendo interazione manuale per il tasto Conferma.");
// Non facciamo nulla: il ciclo MutationObserver continuerà a girare
// ma non troverà discrepanze nei valori e non farà clic.
}
}
// CASO B: Operazione DOC_NRAP (Download)
else if (TIPO_ATTUALE === 'DOC_NRAP') {
if (downloadInCorso) return; // Se sto già scaricando, NON rientrare nella funzione
if (parseInt(VALORE2) > 0) {
avviaDownloadDocumenti();
//console.log("Avvio download documenti. Stato attuale tessere:", situazione);
} else {
console.log("[TM] Valore2 <= 1, chiusura task senza download.");
segnaComeCompletato();
}
}
}
}
// --- INITIALIZATION ---
const observer = new MutationObserver(() => {
if (taskAttivo) eseguiAutomazione();
});
observer.observe(document.body, { childList: true, subtree: true });
setInterval(fetchDati, 2500);
fetchDati();
})();