// ==UserScript== // @name IDEA ERP – KFZ // @namespace http://tampermonkey.net/ // @version 0.16 // @description Przełączanie widoczności produktów z korektą w module KFZ IDEA ERP // @author Adam // @match https://emma.ideaerp.pl/* // @icon https://emma.ideaerp.pl/web/static/src/img/favicon.ico // @downloadURL https://n8n.emma.net.pl/webhook/kfz-zd // @updateURL https://n8n.emma.net.pl/webhook/kfz-zd // @grant none // @run-at document-end // ==/UserScript== try { console.log('[Tampermonkey KFZ] Skrypt załadowany - wersja 0.16'); } catch(e) { alert('Błąd w Tampermonkey KFZ: ' + e); } /* ========================OPIS============================== WERSJA 0.16 - Znajdowanie kolumny po indeksie (data-name tylko w th, nie w td) WYMAGANE PARAMETRY URL: - action=276 (ID akcji KFZ - Korekty zakupu) - menu_id=141 (ID menu KFZ) - view_type=form (tylko widok formularza) - Skrypt działa TYLKO na stronie faktury korygującej (KFZ) WYMAGANE ELEMENTY: - Element .o_notebook ul.nav.nav-tabs (zakładki na stronie) - Tabela .o_field_x2many[name="invoice_line_ids"] table.o_list_table - Kolumna data-name="price_subtotal" (wartość korekty) - Element .o_pager .o_pager_value (zakres paginacji) - Element .o_pager .o_pager_limit (całkowita liczba produktów) DZIAŁANIE SKRYPTU: - Dodaje przycisk w zakładkach strony - "Pokaż korygowane produkty" → ładuje wszystkie produkty i ukrywa wiersze z wartością = 0 - "Pokaż wszystko" → pokazuje wszystkie wiersze i resetuje do domyślnego widoku - Filtrowanie po kolumnie price_subtotal (nie używa indeksów kolumn) - Bezpieczne obsługiwanie dla < 41 produktów (brak paginacji) FUNKCJE: - runScriptCheck() - sprawdza czy hash się zmienił i czy to właściwa strona, dodaje przycisk tylko raz - isCorrectPage() - sprawdza czy to właściwa strona KFZ (action=276, menu_id=141, view_type=form) - createTestButton() - tworzy przycisk w interfejsie - parsePolishNumber() - parsuje polskie wartości numeryczne (przecinek, zł, etc.) - toggleValueRows() - filtruje wiersze według wartości (ukrywa/pokazuje wartość = 0) - changePaginationRange() - pomocnicza funkcja do zmiany zakresu paginacji - loadAllProducts() - ładuje wszystkie produkty - resetToDefault() - resetuje do domyślnego widoku (1-40) - init() - inicjalizuje skrypt i czeka na poprawne parametry URL w SPA (do 5 sek) - startElementCheck() - sprawdza elementy DOM po potwierdzeniu poprawności URL NASTĘPNE KROKI: - Zapamiętywanie stanu filtra między sesjami - Dodanie wskaźnika liczby widocznych/ukrytych wierszy AUTOMATYCZNE AKTUALIZACJE: - Serwer aktualizacji: https://n8n.emma.net.pl/webhook/kfz-zd - Tampermonkey automatycznie sprawdza dostępność nowych wersji CHANGELOG: v0.16 (2025) - ZNAJDOWANIE KOLUMNY PO INDEKSIE Z NAGŁÓWKA - Naprawiono problem z brakiem atrybutu data-name w komórkach - Teraz znajdujemy kolumnę po indeksie z nagłówka - Pobieramy indeks kolumny z nagłówka i używamy go do znalezienia komórki w wierszu - Dodano obsługę wielu wierszy w thead (pomijamy .i7_list_search_wrap) - Dodano szczegółowe logi pokazujące liczbę znalezionych komórek w wierszu - Podwyższenie numeru wersji do 0.16 v0.15 (2025) - POPRAWIONO PARSOWANIE WARTOŚCI Z &NBSP; I PRZECINKAMI - Naprawiono funkcję parsePolishNumber() - zamiana WSZYSTKICH przecinków na kropki - Dodano obsługę &nbsp; (zakodowany entity) w atrybucie title - Dodano szczegółowe logi parsowania każdej wartości - Naprawiono problem z filtrowaniem wierszy z wartościami ujemnymi (np. -10,15) - Filtrowanie teraz poprawnie rozpoznaje wartości z przecinkami i spacjami - Podwyższenie numeru wersji do 0.16 v0.14 (2025) - DODANO NASŁUCHIWANIE NA ZMIANY HASH - Dodano window.addEventListener('hashchange') do nasłuchiwania zmian URL - Dodano funkcję runScriptCheck() która sprawdza czy dodano przycisk - Dodano zmienną buttonAdded żeby nie dodawać przycisku wielokrotnie - Gdy hash się zmienia, resetujemy stan i ponownie sprawdzamy - Naprawiono problem z brakiem działania przy nawigacji z listingu - Podwyższenie numeru wersji do 0.16 v0.13 (2025) - ZMIANA @run-at NA document-end - Zmieniono @run-at z document-start na document-end - Dodano try-catch blok dla wychwycenia błędów - document-end jest bardziej kompatybilny z SPA aplikacjami - Naprawiono problem z brakiem uruchomienia skryptu - Podwyższenie numeru wersji do 0.16 v0.12 (2025) - ELASTYCZNE SPRAWDZANIE PARAMETRÓW URL - Funkcja isCorrectPage() teraz rozróżnia brak view_type od błędnego view_type - Gdy action=276 i menu_id=141 są OK, ale view_type jest null - skrypt czeka - Dodano specjalny log "⏳ Czekam na view_type=form..." gdy parametry są częściowo poprawne - To pozwala na dłuższe czekanie aż view_type zostanie ustawiony asynchronicznie - Podwyższenie numeru wersji do 0.12 v0.11 (2025) - ZWIĘKSZONO CZAS OCZEKIWANIA NA URL - Zwiększono czas oczekiwania z 5 do 10 sekund (100 prób) - Dodano logowanie co 10 prób (mniej spam w konsoli) - Naprawiono problem z brakiem działania przy nawigacji z listingu - Skrypt teraz czeka dłużej na asynchroniczne ustawienie parametrów URL - Dodano więcej szczegółowych logów przy timeout - Podwyższenie numeru wersji do 0.11 v0.10 (2025) - @run-at document-start + natychmiastowy console.log - Zmieniono @run-at z document-end na document-start dla wcześniejszego uruchomienia - Dodano natychmiastowy console.log przed IIFE dla debugowania - Naprawiono problem z brakiem logów w konsoli - Skrypt uruchamia się teraz na samym początku ładowania strony - Podwyższenie numeru wersji do 0.10 v0.9 (2025) - OBSŁUGA ASYNCHRONICZNEGO URL W SPA - Skrypt czeka do 5 sekund na ustawienie prawidłowych parametrów URL - Naprawiono problem z brakiem przycisku gdy przechodzi się z listingu - Dodano rozszerzone logi sprawdzające parametry URL - Funkcja startElementCheck() - osobna funkcja do sprawdzania elementów DOM - Skrypt działa zarówno przy bezpośrednim linku jak i przy nawigacji z listingu - URLSearchParams parsuje parametry niezależnie od kolejności - Podwyższenie numeru wersji do 0.9 v0.8 (2025) - DODANO KONTROLĘ URL - Dodano funkcję isCorrectPage() sprawdzającą parametry URL - Skrypt działa tylko na stronie KFZ (action=276, menu_id=141, view_type=form) - Poprawiono parametry z action=278 na action=276 - Zmieniono menu_id=167 lub 141 na tylko menu_id=141 - Dodano sprawdzanie view_type=form (tylko widok formularza) - Skrypt nie uruchamia się na niewłaściwych stronach - Podwyższenie numeru wersji do 0.8 v0.7 (2025) - DODANO FILTROWANIE WIERSZY WEDŁUG WARTOŚCI - Dodano funkcję toggleValueRows() do ukrywania wierszy z wartością = 0 - Dodano funkcję parsePolishNumber() do parsowania polskich wartości - Filtrowanie po kolumnie data-name="price_subtotal" (nie używa indeksów) - "Pokaż korygowane produkty" → ukrywa wiersze z wartością = 0 - "Pokaż wszystko" → pokazuje wszystkie wiersze - Dodano logi pokazujące wartość każdego wiersza - Podwyższenie numeru wersji do 0.7 v0.6 (2025) - POPRAWKA SPRAWDZANIA PUSTYCH WARTOŚCI PAGINACJI - Dodano sprawdzenie czy totalProducts i currentRange nie są puste - Eliminacja problemu z ustawianiem zakresu "1-" (pusta wartość) - Teraz poprawnie wykrywa gdy wartości są puste i przerywa działanie - Podwyższenie numeru wersji do 0.6 v0.5 (2025) - POPRAWKA OBSŁUGI < 41 PRODUKTÓW - Dodano lepsze zabezpieczenia w loadAllProducts() dla < 41 produktów - Dodano sprawdzenie w resetToDefault() - nie resetuje gdy brak paginacji - Eliminacja problemu z pojawianiem się paginacji po pierwszym kliknięciu - Dodano szczegółowe logi do debugowania - Podwyższenie numeru wersji do 0.5 v0.4 (2025) - POPRAWKA LOGIKI PAGINACJI I BEZPIECZEŃSTWA - Dodano funkcję changePaginationRange() - wspólna logika zmiany zakresu - Dodano funkcję resetToDefault() - reset do domyślnego widoku - Poprawiono logikę przycisku: "Pokaż korygowane" → wszystkie produkty, "Pokaż wszystko" → 1-40 - Bezpieczne obsługiwanie < 41 produktów (brak paginacji nie powoduje błędów) - Eliminacja problemu z pokazywaniem tylko ostatniego produktu - Podwyższenie numeru wersji do 0.4 v0.3 (2025) - POPRAWKA ZAKRESU PAGINACJI - Poprawiono zakres paginacji z "195" na "1-195" (pełny zakres) - Teraz pokazuje wszystkie produkty zamiast tylko ostatniego - Zaktualizowano logi konsoli dla lepszej czytelności - Podwyższenie numeru wersji do 0.3 v0.2 (2025) - ŁADOWANIE WSZYSTKICH PRODUKTÓW - Dodano funkcję loadAllProducts() do automatycznego ładowania wszystkich produktów - Po kliknięciu przycisku zmienia zakres paginacji z "1-40" na "1-{całkowita_liczba}" - Implementacja poprzez kliknięcie w .o_pager_value, wpisanie wartości i naciśnięcie Enter - Podwyższenie numeru wersji do 0.2 v0.1 (2025) - TESTOWA WERSJA - Dodano podstawowy przycisk w zakładkach - Przycisk zmienia swoją nazwę po kliknięciu - Brak funkcjonalności filtrowania – - Podwyższenie numeru wersji do 0.1 (testowa wersja) ============================================================ */ (() => { 'use strict'; console.log('[Tampermonkey KFZ] Wersja 0.16 - znajdowanie kolumny po indeksie z nagłówka'); // Zmienne do śledzenia stanu let lastHash = ''; let buttonAdded = false; // Funkcja do uruchomienia skryptu function runScriptCheck() { console.log('[Tampermonkey KFZ] runScriptCheck wywołane, hash:', window.location.hash); // Jeśli już dodaliśmy przycisk, nie dodajemy ponownie if (buttonAdded) { console.log('[Tampermonkey KFZ] Przycisk już dodany, pomijam'); return; } // Jeśli hash się nie zmienił, nie sprawdzamy ponownie if (lastHash === window.location.hash) { console.log('[Tampermonkey KFZ] Hash bez zmian, pomijam'); return; } lastHash = window.location.hash; // Sprawdzamy czy to właściwa strona if (isCorrectPage()) { console.log('[Tampermonkey KFZ] ✅ Poprawna strona, uruchamiam startElementCheck()'); buttonAdded = true; // Oznaczamy że dodaliśmy przycisk startElementCheck(); } } // Funkcja sprawdzająca czy to właściwa strona (KFZ - korekty zakupu) function isCorrectPage() { const hash = window.location.hash; console.log('[Tampermonkey KFZ] Sprawdzam URL hash:', hash); console.log('[Tampermonkey KFZ] Pełny URL:', window.location.href); // Parametry są w hash, np. #action=276&menu_id=141&view_type=form if (!hash || hash.length <= 1) { console.log('[Tampermonkey KFZ] Brak parametrów w hash lub hash jest pusty'); return false; } if (!hash.includes('action=')) { console.log('[Tampermonkey KFZ] Hash nie zawiera parametru action'); return false; } // Parsujemy parametry z hash const params = new URLSearchParams(hash.substring(1)); // usuwamy # const action = params.get('action'); const menuId = params.get('menu_id'); const viewType = params.get('view_type'); console.log('[Tampermonkey KFZ] Parsowanie: action =', action, ', menu_id =', menuId, ', view_type =', viewType); // Sprawdzamy czy to strona korekty zakupu // Wymagamy action=276 i menu_id=141 // view_type może być jeszcze nie ustawiony (będzie ustawiony później) const hasCorrectAction = action === '276'; const hasCorrectMenu = menuId === '141'; const hasFormView = viewType === 'form'; const isWaitingForViewType = hasCorrectAction && hasCorrectMenu && !viewType; const isCorrect = hasCorrectAction && hasCorrectMenu && hasFormView; if (isWaitingForViewType) { console.log('[Tampermonkey KFZ] ⏳ Czekam na view_type=form...'); console.log('[Tampermonkey KFZ] Otrzymano: action=' + action + ', menu_id=' + menuId + ', view_type=' + viewType); return false; // Jeszcze czekamy } if (!isCorrect) { console.log('[Tampermonkey KFZ] ❌ To nie jest strona KFZ (korekty zakupu na formularzu)'); console.log('[Tampermonkey KFZ] Wymagane: action=276, menu_id=141, view_type=form'); console.log('[Tampermonkey KFZ] Otrzymano: action=' + action + ', menu_id=' + menuId + ', view_type=' + viewType); } else { console.log('[Tampermonkey KFZ] ✅ Poprawna strona KFZ wykryta'); } return isCorrect; } // Funkcja pomocnicza do zmiany zakresu paginacji function changePaginationRange(newRange) { console.log('[Tampermonkey KFZ] Zmieniam zakres paginacji na:', newRange); const pager = document.querySelector('.o_field_x2many[name="invoice_line_ids"] .o_pager'); if (!pager) { console.log('[Tampermonkey KFZ] Nie znaleziono paginacji (mniej niż 41 produktów)'); return false; } const pagerValue = pager.querySelector('.o_pager_value'); if (!pagerValue) { console.log('[Tampermonkey KFZ] Nie znaleziono elementu .o_pager_value'); return false; } pagerValue.click(); setTimeout(() => { const input = pager.querySelector('.o_pager_value input'); if (input) { input.value = newRange; const enterEvent = new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }); input.dispatchEvent(enterEvent); console.log('[Tampermonkey KFZ] Ustawiono zakres na:', newRange); } }, 500); return true; } // Funkcja do parsowania polskich wartości numerycznych function parsePolishNumber(value) { if (!value) return 0; console.log('[Tampermonkey KFZ] Parsuję wartość:', value); // Usuwamy "zł", wszystkie spacje (w tym   i &nbsp;), zastępujemy przecinki kropkami let cleanValue = value .replace(/zł/gi, '') // Usuń zł .replace(/\s+/g, '') // Usuń wszystkie białe znaki (w tym zwykłe spacje) .replace(/ /g, '') // Usuń   .replace(/&nbsp;/g, '') // Usuń zakodowane &nbsp; .replace(/,/g, '.'); // Zamień WSZYSTKIE przecinki na kropki console.log('[Tampermonkey KFZ] Oczyszczona wartość:', cleanValue); const result = parseFloat(cleanValue) || 0; console.log('[Tampermonkey KFZ] Wynik:', result); return result; } // Funkcja do filtrowania wierszy - ukrywanie/pokazywanie wierszy z wartością = 0 function toggleValueRows(showOnlyWithValue) { console.log('[Tampermonkey KFZ] Filtruję wiersze - pokazuję tylko z wartością ≠ 0:', showOnlyWithValue); // Szukamy tabeli pozycji faktury const table = document.querySelector('.o_field_x2many[name="invoice_line_ids"] table.o_list_table'); if (!table) { console.log('[Tampermonkey KFZ] Nie znaleziono tabeli'); return; } // Szukamy nagłówka kolumny "Wartość" po data-name // W thead może być kilka wierszy, bierzemy pierwszy (nie .i7_list_search_wrap) const headerRow = table.querySelector('thead tr:not(.i7_list_search_wrap)'); const headers = headerRow.querySelectorAll('th'); let priceSubtotalIndex = -1; headers.forEach((th, idx) => { if (th.getAttribute('data-name') === 'price_subtotal') { priceSubtotalIndex = idx; console.log('[Tampermonkey KFZ] Znaleziono kolumnę price_subtotal na indeksie', priceSubtotalIndex); } }); if (priceSubtotalIndex === -1) { console.log('[Tampermonkey KFZ] Nie znaleziono kolumny "price_subtotal"'); return; } // Pobieramy wszystkie wiersze danych const rows = table.querySelectorAll('tbody tr.o_data_row'); console.log('[Tampermonkey KFZ] Znaleziono', rows.length, 'wierszy'); let hiddenCount = 0; let visibleCount = 0; // Przetwarzamy każdy wiersz rows.forEach((row, index) => { // Szukamy wszystkich komórek w wierszu (włączając th i td) const cells = row.querySelectorAll('th, td'); console.log('[Tampermonkey KFZ] Wiersz', index + 1, '- znaleziono', cells.length, 'komórek'); // Pobieramy komórkę po indeksie kolumny // UWAGA: W wierszu pierwsza komórka to , więc indeks się zgadza const valueCell = cells[priceSubtotalIndex]; if (!valueCell) { console.log('[Tampermonkey KFZ] Wiersz', index + 1, '- brak komórki na indeksie', priceSubtotalIndex); return; } console.log('[Tampermonkey KFZ] Wiersz', index + 1, '- komórka:', valueCell); // Pobieramy wartość z atrybutu title lub textContent const valueText = valueCell.getAttribute('title') || valueCell.textContent; const numericValue = parsePolishNumber(valueText); console.log('[Tampermonkey KFZ] Wiersz', index + 1, ':', valueText, '->', numericValue); if (showOnlyWithValue) { // Pokaż tylko wiersze z wartością ≠ 0 if (numericValue !== 0) { row.style.display = ''; visibleCount++; } else { row.style.display = 'none'; hiddenCount++; } } else { // Pokaż wszystkie wiersze row.style.display = ''; visibleCount++; } }); console.log('[Tampermonkey KFZ] Filtrowanie zakończone - widoczne:', visibleCount, 'ukryte:', hiddenCount); } // Funkcja do ładowania wszystkich produktów function loadAllProducts() { console.log('[Tampermonkey KFZ] Ładuję wszystkie produkty...'); // Szukamy elementów paginacji - trzeba znaleźć je w kontekście tabeli pozycji faktury const pager = document.querySelector('.o_field_x2many[name="invoice_line_ids"] .o_pager'); // Jeśli nie ma paginacji, to znaczy że jest mniej niż 41 produktów - wszystkie są już widoczne if (!pager) { console.log('[Tampermonkey KFZ] Nie ma paginacji - wszystkie produkty (< 41) są już widoczne'); return; } const pagerLimit = pager.querySelector('.o_pager_limit'); const pagerValue = pager.querySelector('.o_pager_value'); if (!pagerLimit || !pagerValue) { console.log('[Tampermonkey KFZ] Nie znaleziono elementów paginacji'); return; } const totalProducts = pagerLimit.textContent.trim(); const currentRange = pagerValue.textContent.trim(); console.log('[Tampermonkey KFZ] Aktualny zakres:', currentRange, 'Całkowita liczba:', totalProducts); // Jeśli wartości są puste, to znaczy że nie ma prawidłowej paginacji if (!totalProducts || !currentRange) { console.log('[Tampermonkey KFZ] Wartości paginacji są puste - wszystkie produkty są już widoczne'); return; } // Sprawdzamy, czy już są pokazane wszystkie produkty if (currentRange.includes(`1-${totalProducts}`)) { console.log('[Tampermonkey KFZ] Wszystkie produkty są już załadowane'); return; } console.log('[Tampermonkey KFZ] Zmieniam zakres z', currentRange, 'na 1-', totalProducts); changePaginationRange(`1-${totalProducts}`); } // Funkcja do resetowania do domyślnego widoku function resetToDefault() { console.log('[Tampermonkey KFZ] Resetuję do domyślnego widoku...'); // Sprawdzamy czy w ogóle jest paginacja const pager = document.querySelector('.o_field_x2many[name="invoice_line_ids"] .o_pager'); if (!pager) { console.log('[Tampermonkey KFZ] Nie ma paginacji (< 41 produktów) - reset nie jest potrzebny'); return; } changePaginationRange('1-40'); } function createTestButton() { // Szukamy przycisku w zakładkach const nav = document.querySelector('.o_notebook ul.nav.nav-tabs'); if (!nav) { console.log('[Tampermonkey KFZ] Nie znaleziono nav-tabs'); return; } console.log('[Tampermonkey KFZ] Znaleziono nav-tabs, tworzę przycisk'); // Tworzymy element li const li = document.createElement('li'); li.className = 'nav-item'; // Tworzymy link const a = document.createElement('a'); a.href = '#'; a.className = 'nav-link'; a.textContent = 'Pokaż korygowane produkty'; // Dodajemy event listener a.addEventListener('click', async (e) => { e.preventDefault(); // Sprawdzamy aktualną nazwę if (a.textContent === 'Pokaż korygowane produkty') { a.textContent = 'Pokaż wszystko'; console.log('[Tampermonkey KFZ] Zmieniono nazwę na: Pokaż wszystko'); // Ładujemy wszystkie produkty loadAllProducts(); // Po załadowaniu filtrujemy wiersze (ukrywamy wartość = 0) setTimeout(() => { toggleValueRows(true); }, 1000); } else { a.textContent = 'Pokaż korygowane produkty'; console.log('[Tampermonkey KFZ] Zmieniono nazwę na: Pokaż korygowane produkty'); // Pokaż wszystkie wiersze toggleValueRows(false); // Resetujemy do domyślnego widoku resetToDefault(); } }); li.appendChild(a); nav.appendChild(li); console.log('[Tampermonkey KFZ] Przycisk dodany pomyślnie'); } // Funkcja inicjalizująca function init() { console.log('[Tampermonkey KFZ] Inicjalizacja...'); // W aplikacji SPA parametry URL mogą być ustawiane asynchronicznie // Czekamy do 10 sekund na poprawne parametry URL let attempts = 0; const maxAttempts = 100; // 100 * 100ms = 10 sekund console.log('[Tampermonkey KFZ] Rozpoczynam sprawdzanie URL (max 100 prób co 100ms)'); // Nasłuchujemy na zmiany URL (hashchange) window.addEventListener('hashchange', () => { console.log('[Tampermonkey KFZ] 🎯 Hash changed!'); buttonAdded = false; // Resetujemy stan - możliwa nowa strona lastHash = ''; // Resetujemy hash żeby wykryć zmianę setTimeout(runScriptCheck, 200); }); const urlCheckInterval = setInterval(() => { attempts++; // Log co 10 prób if (attempts % 10 === 0 || attempts === 1) { console.log('[Tampermonkey KFZ] Próba', attempts, '/', maxAttempts, 'sprawdzenia URL...'); } runScriptCheck(); if (attempts >= maxAttempts) { console.log('[Tampermonkey KFZ] ❌ Timeout po', attempts, 'próbach'); clearInterval(urlCheckInterval); } }, 100); // Sprawdzamy co 100ms } // Funkcja sprawdzająca elementy strony po potwierdzeniu URL function startElementCheck() { // Szukamy elementów strony const checkInterval = setInterval(() => { const nav = document.querySelector('.o_notebook ul.nav.nav-tabs'); console.log('[Tampermonkey KFZ] Sprawdzam nav-tabs:', !!nav); if (nav) { clearInterval(checkInterval); createTestButton(); } }, 500); // Timeout po 10 sekundach setTimeout(() => { clearInterval(checkInterval); console.log('[Tampermonkey KFZ] Timeout - nie znaleziono nav-tabs'); }, 10000); } // Start if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();