982 lines
36 KiB
JavaScript
982 lines
36 KiB
JavaScript
// ==UserScript==
|
||
// @name IDEA KFZ Listing
|
||
// @namespace http://tampermonkey.net/
|
||
// @version 3.1
|
||
// @description Ulepszenia dla listy faktur KFZ w systemie IDEA ERP
|
||
// @author Adam
|
||
// @match https://*.ideaerp.pl/*
|
||
// @icon https://www.ideaerp.pl/favicon.ico
|
||
// @downloadURL https://n8n.emma.net.pl/webhook/kfz-listing
|
||
// @updateURL https://n8n.emma.net.pl/webhook/kfz-listing
|
||
// @grant GM_setValue
|
||
// @grant GM_getValue
|
||
// @run-at document-end
|
||
// ==/UserScript==
|
||
|
||
/* ========================OPIS==============================
|
||
WERSJA 3.1 - Uproszczona logika wykrywania strony
|
||
|
||
DZIAŁANIE SKRYPTU:
|
||
- Wykrywa strony z listą faktur KFZ w systemie IDEA ERP
|
||
- Dodaje ulepszenia wizualne tabeli (sticky header, podświetlanie)
|
||
- Implementuje szybkie filtry według dostawców
|
||
- Umożliwia kopiowanie zawartości komórek w kolumnach:
|
||
* Numer faktury (data-name="name")
|
||
* Data faktury (data-name="invoice_date")
|
||
* Odnośnik (data-name="invoice_origin")
|
||
* Należność (data-name="amount_residual_signed" - NIE NETTO!)
|
||
* Na podstawie (data-name="reversed_entry_id")
|
||
- Pokazuje powiadomienia o skopiowaniu
|
||
|
||
ZMIANY W WERSJI 3.1:
|
||
- 🔍 UPROSZCZONA logika wykrywania strony - bardziej liberalne warunki
|
||
- ✅ Działaj jeśli: action=278+menu_id=141 LUB breadcrumb="Korekty" LUB numery KFZ
|
||
- 📊 Dodano szczegółowe logi w initEnhancements()
|
||
- 🚀 Usunięto zbędne skomplikowane warunki
|
||
- 🎯 Focus na działanie, nie na perfekcyjne wykrywanie
|
||
|
||
ZMIANY W WERSJI 3.0:
|
||
- 🔄 POWRÓT do stabilnej wersji bez waitForPageLoad()
|
||
- ❌ USUNIĘTO skomplikowaną funkcję waitForPageLoad() która powodowała błędy
|
||
- ✅ PRZYWRÓCONO prostą logikę z setTimeout(initEnhancements)
|
||
- 🎯 Wymagane action=278 + menu_id=141 dla aktywacji
|
||
- 📊 Zachowano podstawowe logi debugowania
|
||
- 🚀 Punkt wyjścia do dalszych ulepszeń
|
||
|
||
ZMIANY W WERSJI 2.9.1:
|
||
- 🐛 NAPRAWIONO błąd "hasAction278 is not defined"
|
||
- 🔧 Poprawiono wszystkie odwołania do zmiennych w waitForPageLoad()
|
||
- ✅ Usunięto błędy JavaScript które blokowały działanie skryptu
|
||
|
||
ZMIANY W WERSJI 2.9:
|
||
- 🔍 DODANO szczegółowe debugowanie w waitForPageLoad() i isKfzListingPage()
|
||
- 📊 Emoji w logach dla lepszej czytelności
|
||
- 🎯 Wymagane action=278 + menu_id=141 dla aktywacji
|
||
- 📍 Logi URL i elementów na starcie skryptu
|
||
- 🚀 Test czy skrypt w ogóle się ładuje
|
||
- 🔧 Diagnostyka dla problemu "nie działa w ogóle"
|
||
|
||
ZMIANY W WERSJI 2.8:
|
||
- 🔄 DODANO resetowanie stanu przy zmianie URL (hashchange i polling)
|
||
- 🔍 Sprawdzanie czy elementy rzeczywiście istnieją mimo markera
|
||
- 🧹 Automatyczne usuwanie markerów przy zmianie strony
|
||
- 🎯 Dodano hasKfzNumbers jako dodatkowy warunek aktywacji
|
||
- 📊 Szczegółowe logi resetowania stanu
|
||
- ✅ Rozwiązano problem niespójnego działania przy nawigacji
|
||
|
||
ZMIANY W WERSJI 2.7:
|
||
- 🕐 DODANO funkcję waitForPageLoad() - inteligentne oczekiwanie na elementy
|
||
- 🔄 Maksymalnie 10 prób co 1 sekundę na załadowanie tabeli/breadcrumb
|
||
- 🚫 Wykrywanie elementów ładowania (.o_loading, .fa-spin) - czeka aż znikną
|
||
- 🎯 Wszystkie wywołania initEnhancements zastąpione waitForPageLoad
|
||
- 📊 Szczegółowe logi procesu oczekiwania na załadowanie
|
||
- ✅ Rozwiązano problem "Table: false, Breadcrumb: null" po odświeżeniu
|
||
|
||
ZMIANY W WERSJI 2.6:
|
||
- 🎯 DODANO wykrywanie strony przez breadcrumb "Korekty"
|
||
- 🔄 ZMIENIONA logika: aktywacja nawet bez danych KFZ (mogą się załadować później)
|
||
- 📍 Backup timer sprawdza breadcrumb co 3 sekundy
|
||
- 🚀 Rozwiązano problem z URL: action=278&model=account.move&view_type=list&cids=1&menu_id=141
|
||
- 📊 Dodatkowe logi dla debugowania wykrywania strony
|
||
|
||
ZMIANY W WERSJI 2.5:
|
||
- 🔄 DODANO wielopoziomowe wykrywanie zmian URL dla aplikacji SPA
|
||
- 📍 5 metod wykrywania: hashchange, URL polling, title observer, breadcrumb observer, backup timer
|
||
- ⚡ Szybsze reakcje: 200ms, 300ms, 400ms, 500ms, 1000ms, 1500ms
|
||
- 🎯 Rozwiązano problem z brakiem działania przy nawigacji (bez odświeżania)
|
||
- 🔍 Backup sprawdzanie co 3 sekundy czy strona KFZ ma filtr
|
||
- 📊 Szczegółowe logi dla debugowania zmian URL
|
||
|
||
ZMIANY W WERSJI 2.4:
|
||
- 🔧 PRZENIESIONO filtr za przyciski w lewym panelu (.o_cp_left)
|
||
- 🚫 ROZWIĄZANO problem nakładania się na wyszukiwarkę
|
||
- 📍 Filtr teraz w linii: [Zapisz] [Odrzuć] [🔽 Dostawca] | [Wyszukiwarka]
|
||
- 💫 Lepsze wykorzystanie przestrzeni bez konfliktów
|
||
|
||
ZMIANY W WERSJI 2.3:
|
||
- 🔧 NAPRAWIONO pozycjonowanie filtra - teraz jest OBOK wyszukiwarki
|
||
- 📍 Filtr wstawiony do `.o_searchview_input_container` zamiast `.o_cp_searchview`
|
||
- 💫 Filtr i wyszukiwarka w tej samej linii (flex-shrink: 0)
|
||
- 🎯 Lepsze wykorzystanie przestrzeni w panelu kontrolnym
|
||
|
||
ZMIANY W WERSJI 2.2:
|
||
- 🎨 PRZENIESIONO filtr dostawców do panelu kontrolnego (.o_cp_searchview)
|
||
- 💫 Filtr ma teraz profesjonalny wygląd zgodny z interfejsem Odoo
|
||
- 📍 Umieszczony obok wyszukiwarki z ikoną i odpowiednim stylem
|
||
- 🔄 Dodano fallback - jeśli brak panelu, filtr pojawi się przed tabelą
|
||
- 🎯 Zaktualizowano funkcję filtrowania na nowe indeksy pozycyjne
|
||
|
||
ZMIANY W WERSJI 2.1:
|
||
- 🔧 NAPRAWIONO błąd TypeError w isKfzListingPage()
|
||
- 🎯 ZASTĄPIONO wyszukiwanie po data-name na indeksy pozycyjne
|
||
- 📍 Mapowanie kolumn na podstawie rzeczywistej struktury:
|
||
* Komórka 2: Numer faktury (KFZ/...)
|
||
* Komórka 4: Data faktury (DD.MM.YYYY)
|
||
* Komórka 5: Dokument źródłowy (ZZ/...)
|
||
* Komórka 6: Odnośnik (KFAS/...)
|
||
* Komórka 10: Należność (...zł)
|
||
* Komórka 11: Na podstawie (KFZ/... (...))
|
||
- ✅ Wszystkie kolumny powinny teraz działać poprawnie!
|
||
|
||
ZMIANY W WERSJI 2.0:
|
||
- Dodano bardzo szczegółowe debugowanie dla wszystkich kolumn
|
||
- Logowanie struktury pierwszego wiersza z klasami CSS
|
||
- Szczegółowe logi dla każdej poszukiwanej kolumny:
|
||
* invoice_origin (Dokument źródłowy)
|
||
* ref (Odnośnik)
|
||
* amount_residual_signed (Należność)
|
||
* reversed_entry_id (Na podstawie)
|
||
- Informowanie o pustych wartościach i myślnikach
|
||
- Wyraźne oznaczenie ❌ gdy kolumna nie została znaleziona
|
||
|
||
ZMIANY W WERSJI 1.9:
|
||
- Usunięto niepotrzebne kopiowanie z kolumny NETTO (amount_untaxed_signed)
|
||
- Dodano sprawdzanie data-name żeby unikać kopiowania z NETTO
|
||
- Pozostawiono tylko kopiowanie z kolumny NALEŻNOŚĆ (amount_residual_signed)
|
||
- Ograniczono debugowanie do pierwszego wiersza
|
||
- Dodano szczegółowe logowanie dla każdej kolumny
|
||
|
||
ZMIANY W WERSJI 1.8:
|
||
- Dodano szczegółowe debugowanie struktury tabeli
|
||
- Fallback dla różnych formatów data-name (małe/duże litery)
|
||
- Alternatywne wyszukiwanie po zawartości komórek
|
||
- Debugowanie wszystkich komórek w wierszach
|
||
- Logowanie procesu dodawania kopiowania
|
||
- Sprawdzanie czy w ogóle są wiersze danych
|
||
|
||
ZMIANY W WERSJI 1.7:
|
||
- Zmieniono z indeksów kolumn na querySelector z data-name
|
||
- Numer: td[data-name="name"]
|
||
- Data faktury: td[data-name="invoice_date"]
|
||
- Odnośnik: td[data-name="invoice_origin"]
|
||
- Należność: td[data-name="amount_residual_signed"]
|
||
- Na podstawie: td[data-name="reversed_entry_id"]
|
||
- Dodano szczegółowe logowanie dla każdej kolumny
|
||
- Rozwiązano problem z ukrytymi kolumnami
|
||
|
||
ZMIANY W WERSJI 1.6:
|
||
- Zmieniono nazwę skryptu na "IDEA KFZ Listing"
|
||
- Poprawiono mapowanie kolumny "Należność" (amount_residual_signed, indeks 9)
|
||
- Naprawiono kopiowanie kolumny "Odnośnik" (invoice_origin, indeks 5)
|
||
- Dodano debugowanie dla kolumny "Odnośnik"
|
||
- Wykluczenie pustych wartości i myślników "-" z kopiowania
|
||
|
||
ZMIANY W WERSJI 1.5:
|
||
- Rozszerzone kopiowanie na 5 kolumn (Numer, Data faktury, Odnośnik, Należność, Na podstawie)
|
||
- Ulepszone czyszczenie kwot (usuwanie "zł" i znaków niełamliwych spacji)
|
||
- Automatyczne wykrywanie kolumny "Na podstawie" przez data-name="reversed_entry_id"
|
||
- Inteligentne wykrywanie dat w formacie DD.MM.YYYY
|
||
- Kopiowanie odnośników (dokumentów źródłowych)
|
||
|
||
ZMIANY W WERSJI 1.4:
|
||
- Dodano zabezpieczenie przed duplikowaniem elementów
|
||
- Sprawdzanie czy skrypt już został uruchomiony (.kfz-script-initialized)
|
||
- Unikalne ID dla stylów CSS (#kfz-copy-styles, #kfz-table-styles)
|
||
- Sprawdzanie istnienia filtrów (.kfz-filter-container)
|
||
- Eliminacja powielania elementów przy wielokrotnym uruchomieniu
|
||
|
||
ZMIANY W WERSJI 1.3:
|
||
- Obsługa dynamicznego ładowania danych przez AJAX
|
||
- Przechwytywanie żądań do /web/dataset/search_read
|
||
- Wykrywanie różnicy między widokiem grupowanym a szczegółowym
|
||
- Uruchamianie skryptu po kliknięciu na dostawcę
|
||
- Inteligentny MutationObserver dla nowych wierszy danych
|
||
|
||
ZMIANY W WERSJI 1.2:
|
||
- Poprawione wykrywanie strony KFZ (sprawdza URL account.move)
|
||
- Dodano obserwację zmian DOM dla dynamicznych stron
|
||
- Lepsze debugowanie z logami w konsoli
|
||
- Obsługa zmian URL (hashchange)
|
||
- MutationObserver dla automatycznego uruchamiania
|
||
|
||
ZMIANY W WERSJI 1.1:
|
||
- Dodano funkcjonalność kopiowania w stylu idea-orders.user.js
|
||
- Ikony kopiowania ⧉ po prawej stronie numerów KFZ (pomarańczowe)
|
||
- Kliknięcie na komórkę z numerem kopiuje do schowka
|
||
- Efekt wizualny po skopiowaniu (pomarańczowa ramka)
|
||
- Fallback dla starszych przeglądarek
|
||
- Dodano @updateURL i @downloadURL zgodnie z zasadami
|
||
|
||
WERSJA 1.0 - Podstawowe ulepszenia listy KFZ
|
||
- Wykrywanie strony z listą faktur KFZ
|
||
- Ulepszenia wizualne tabeli
|
||
- Szybkie filtry według dostawców
|
||
- System logowania z flagą DEBUG
|
||
============================================================ */
|
||
|
||
(() => {
|
||
'use strict';
|
||
|
||
// [Tampermonkey] Konfiguracja
|
||
const DEBUG = true;
|
||
const SCRIPT_NAME = '[IDEA KFZ Listing]';
|
||
|
||
/**
|
||
* Funkcja logowania z prefiksem
|
||
* @param {string} message - Wiadomość do zalogowania
|
||
*/
|
||
function log(message) {
|
||
if (DEBUG) {
|
||
console.log(`${SCRIPT_NAME} ${message}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Sprawdza czy jesteśmy na stronie z listą faktur KFZ
|
||
* @returns {boolean}
|
||
*/
|
||
function isKfzListingPage() {
|
||
// Sprawdź URL - czy to model account.move
|
||
const url = window.location.href;
|
||
const isAccountMoveList = url.includes('model=account.move') && url.includes('view_type=list');
|
||
const hasAction278 = url.includes('action=278');
|
||
const hasMenu141 = url.includes('menu_id=141');
|
||
|
||
// Sprawdź czy jest tabela
|
||
const table = document.querySelector('.o_list_table');
|
||
|
||
// Sprawdź czy są numery KFZ w różnych formatach
|
||
const hasKfzNumbers = document.querySelector('td[title*="KFZ/"]') ||
|
||
Array.from(document.querySelectorAll('td')).some(td => td.textContent.includes('KFZ/')) ||
|
||
(document.textContent && document.textContent.includes('KFZ/'));
|
||
|
||
// Sprawdź czy są wiersze danych (nie tylko grupowanie)
|
||
const hasDataRows = document.querySelectorAll('.o_data_row').length > 0;
|
||
|
||
// Sprawdź czy to nie jest widok grupowany (ma grupy ale nie ma szczegółów)
|
||
const hasGroups = document.querySelectorAll('.o_group_header').length > 0;
|
||
const isGroupedView = hasGroups && !hasDataRows;
|
||
|
||
// Sprawdź breadcrumb - czy zawiera "Korekty"
|
||
const breadcrumb = document.querySelector('.breadcrumb');
|
||
const hasCorrectionsBreadcrumb = breadcrumb && breadcrumb.textContent.includes('Korekty');
|
||
|
||
log(`URL: ${isAccountMoveList}, Action278: ${hasAction278}, Menu141: ${hasMenu141}, Table: ${!!table}, KFZ: ${!!hasKfzNumbers}, DataRows: ${hasDataRows}, Breadcrumb: ${hasCorrectionsBreadcrumb}`);
|
||
|
||
// Bardziej liberalne warunki - działaj jeśli:
|
||
// 1. Jest action=278 + menu_id=141 (specificzny URL dla KFZ)
|
||
// 2. LUB breadcrumb zawiera "Korekty"
|
||
// 3. LUB znaleziono numery KFZ w treści strony
|
||
const shouldActivate = (hasAction278 && hasMenu141) ||
|
||
hasCorrectionsBreadcrumb ||
|
||
(isAccountMoveList && hasKfzNumbers);
|
||
|
||
log(`Czy aktywować: ${shouldActivate}`);
|
||
|
||
return shouldActivate;
|
||
}
|
||
|
||
/**
|
||
* Czyści duplikaty elementów z poprzednich uruchomień
|
||
*/
|
||
function cleanupDuplicates() {
|
||
// Usuń duplikaty filtrów
|
||
const filterContainers = document.querySelectorAll('.kfz-filter-container');
|
||
if (filterContainers.length > 1) {
|
||
for (let i = 1; i < filterContainers.length; i++) {
|
||
filterContainers[i].remove();
|
||
}
|
||
log(`Usunięto ${filterContainers.length - 1} duplikatów filtrów`);
|
||
}
|
||
|
||
// Usuń duplikaty stylów
|
||
const copyStyles = document.querySelectorAll('#kfz-copy-styles');
|
||
if (copyStyles.length > 1) {
|
||
for (let i = 1; i < copyStyles.length; i++) {
|
||
copyStyles[i].remove();
|
||
}
|
||
log(`Usunięto ${copyStyles.length - 1} duplikatów stylów kopiowania`);
|
||
}
|
||
|
||
const tableStyles = document.querySelectorAll('#kfz-table-styles');
|
||
if (tableStyles.length > 1) {
|
||
for (let i = 1; i < tableStyles.length; i++) {
|
||
tableStyles[i].remove();
|
||
}
|
||
log(`Usunięto ${tableStyles.length - 1} duplikatów stylów tabeli`);
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* Główna funkcja inicjalizująca ulepszenia
|
||
*/
|
||
async function initEnhancements() {
|
||
try {
|
||
log(`🔍 INIT ENHANCEMENTS - sprawdzam stronę: ${window.location.href}`);
|
||
|
||
if (!isKfzListingPage()) {
|
||
log('❌ Nie wykryto strony z listą KFZ');
|
||
return;
|
||
}
|
||
|
||
log('✅ WYKRYTO STRONĘ KFZ - kontynuuję...');
|
||
|
||
// Sprawdź czy skrypt już został uruchomiony na tej stronie
|
||
const scriptMarker = document.querySelector('.kfz-script-initialized');
|
||
if (scriptMarker) {
|
||
log('Skrypt już został uruchomiony - sprawdzam czy elementy istnieją');
|
||
|
||
// Sprawdź czy elementy rzeczywiście istnieją
|
||
const hasFilter = document.querySelector('.kfz-filter-container');
|
||
const hasStyles = document.querySelector('#kfz-copy-styles');
|
||
|
||
if (!hasFilter || !hasStyles) {
|
||
log('Elementy skryptu nie istnieją mimo markera - resetuję i uruchamiam ponownie');
|
||
scriptMarker.remove();
|
||
// Usuń wszystkie elementy skryptu
|
||
document.querySelectorAll('.kfz-filter-container, #kfz-copy-styles, #kfz-table-styles').forEach(el => el.remove());
|
||
} else {
|
||
log('Elementy skryptu istnieją - pomijam');
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Usuń ewentualne duplikaty z poprzednich uruchomień
|
||
cleanupDuplicates();
|
||
|
||
log('Wykryto stronę z listą faktur KFZ');
|
||
|
||
// Oznacz że skrypt został uruchomiony
|
||
const marker = document.createElement('div');
|
||
marker.className = 'kfz-script-initialized';
|
||
marker.style.display = 'none';
|
||
document.body.appendChild(marker);
|
||
|
||
// Tutaj można dodać konkretne ulepszenia
|
||
enhanceTableVisibility();
|
||
addQuickFilters();
|
||
addCopyStyles();
|
||
addCopyFunctionality();
|
||
|
||
log('Wszystkie ulepszenia zostały zastosowane');
|
||
|
||
} catch (error) {
|
||
console.error(`${SCRIPT_NAME} Błąd:`, error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Poprawia widoczność tabeli
|
||
*/
|
||
function enhanceTableVisibility() {
|
||
const table = document.querySelector('.o_list_table');
|
||
if (!table) return;
|
||
|
||
// Sprawdź czy style już zostały dodane
|
||
if (document.querySelector('#kfz-table-styles')) {
|
||
log('Style tabeli już istnieją - pomijam dodawanie');
|
||
return;
|
||
}
|
||
|
||
// Dodaj style CSS dla lepszej czytelności
|
||
const style = document.createElement('style');
|
||
style.id = 'kfz-table-styles';
|
||
style.textContent = `
|
||
.o_list_table .text-danger {
|
||
background-color: #fff5f5 !important;
|
||
border-left: 3px solid #dc3545 !important;
|
||
}
|
||
|
||
.o_list_table .o_data_row:hover {
|
||
background-color: #f8f9fa !important;
|
||
}
|
||
|
||
.o_list_table th {
|
||
position: sticky;
|
||
top: 0;
|
||
background-color: #fff !important;
|
||
z-index: 10;
|
||
}
|
||
`;
|
||
document.head.appendChild(style);
|
||
|
||
log('Zastosowano ulepszenia wizualne tabeli');
|
||
}
|
||
|
||
/**
|
||
* Dodaje szybkie filtry do panelu kontrolnego
|
||
*/
|
||
function addQuickFilters() {
|
||
// Sprawdź czy filtry już istnieją
|
||
if (document.querySelector('.kfz-filter-container')) {
|
||
log('Filtry już istnieją - pomijam dodawanie');
|
||
return;
|
||
}
|
||
|
||
// Znajdź lewą część panelu kontrolnego (za przyciskami)
|
||
const leftPanel = document.querySelector('.o_cp_left .o_cp_buttons');
|
||
|
||
if (!leftPanel) {
|
||
log('Nie znaleziono lewego panelu - dodaję filtr przed tabelą');
|
||
addQuickFiltersBeforeTable();
|
||
return;
|
||
}
|
||
|
||
// Pobierz unikalnych dostawców z wierszy danych
|
||
const suppliers = new Set();
|
||
const rows = document.querySelectorAll('.o_data_row');
|
||
|
||
rows.forEach(row => {
|
||
const supplierCell = row.children[3]; // Dostawca jest w komórce 3
|
||
if (supplierCell) {
|
||
const supplierName = supplierCell.textContent.trim();
|
||
if (supplierName) {
|
||
suppliers.add(supplierName);
|
||
}
|
||
}
|
||
});
|
||
|
||
if (suppliers.size === 0) {
|
||
log('Brak dostawców do filtrowania');
|
||
return;
|
||
}
|
||
|
||
// Utwórz kontener filtra - będzie za przyciskami w lewym panelu
|
||
const filterContainer = document.createElement('div');
|
||
filterContainer.className = 'kfz-filter-container';
|
||
filterContainer.style.cssText = `
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-left: 15px;
|
||
height: 32px;
|
||
`;
|
||
|
||
// Etykieta z ikoną
|
||
const label = document.createElement('span');
|
||
label.innerHTML = '<i class="fa fa-filter"></i> Dostawca:';
|
||
label.style.cssText = `
|
||
color: #6c757d;
|
||
font-size: 13px;
|
||
white-space: nowrap;
|
||
font-weight: normal;
|
||
`;
|
||
|
||
// Select z dostawcami - styl podobny do innych kontrolek Odoo
|
||
const supplierFilter = document.createElement('select');
|
||
supplierFilter.className = 'form-control';
|
||
supplierFilter.style.cssText = `
|
||
height: 32px;
|
||
padding: 4px 8px;
|
||
border: 1px solid #ced4da;
|
||
border-radius: 4px;
|
||
font-size: 13px;
|
||
background-color: white;
|
||
min-width: 200px;
|
||
max-width: 250px;
|
||
line-height: 1.5;
|
||
`;
|
||
|
||
// Opcja "Wszyscy"
|
||
const allOption = document.createElement('option');
|
||
allOption.value = '';
|
||
allOption.textContent = 'Wszyscy dostawcy';
|
||
supplierFilter.appendChild(allOption);
|
||
|
||
// Opcje dla każdego dostawcy
|
||
Array.from(suppliers).sort().forEach(supplier => {
|
||
const option = document.createElement('option');
|
||
option.value = supplier;
|
||
option.textContent = supplier;
|
||
supplierFilter.appendChild(option);
|
||
});
|
||
|
||
// Event listener dla filtra dostawców
|
||
supplierFilter.addEventListener('change', () => {
|
||
filterBySupplier(supplierFilter.value);
|
||
});
|
||
|
||
filterContainer.appendChild(label);
|
||
filterContainer.appendChild(supplierFilter);
|
||
|
||
// Wstaw filtr za przyciskami w lewym panelu
|
||
leftPanel.parentNode.appendChild(filterContainer);
|
||
|
||
log('Dodano filtr dostawców za przyciskami w lewym panelu');
|
||
}
|
||
|
||
/**
|
||
* Fallback - dodaje filtry przed tabelą jeśli nie ma panelu wyszukiwania
|
||
*/
|
||
function addQuickFiltersBeforeTable() {
|
||
const table = document.querySelector('.o_list_table');
|
||
if (!table) return;
|
||
|
||
// Kontener dla filtrów (stary styl)
|
||
const filterContainer = document.createElement('div');
|
||
filterContainer.className = 'kfz-filter-container';
|
||
filterContainer.style.cssText = `
|
||
margin: 10px 0;
|
||
padding: 10px;
|
||
background: #f8f9fa;
|
||
border-radius: 5px;
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: center;
|
||
`;
|
||
|
||
// Filtr po dostawcy
|
||
const supplierFilter = document.createElement('select');
|
||
supplierFilter.style.cssText = 'padding: 5px; border-radius: 3px; border: 1px solid #ccc;';
|
||
supplierFilter.innerHTML = '<option value="">Wszyscy dostawcy</option>';
|
||
|
||
// Pobierz unikalnych dostawców
|
||
const suppliers = new Set();
|
||
const rows = document.querySelectorAll('.o_data_row');
|
||
|
||
rows.forEach(row => {
|
||
const supplierCell = row.children[3];
|
||
if (supplierCell) {
|
||
const supplierName = supplierCell.textContent.trim();
|
||
if (supplierName) {
|
||
suppliers.add(supplierName);
|
||
}
|
||
}
|
||
});
|
||
|
||
suppliers.forEach(supplier => {
|
||
const option = document.createElement('option');
|
||
option.value = supplier;
|
||
option.textContent = supplier;
|
||
supplierFilter.appendChild(option);
|
||
});
|
||
|
||
// Event listener dla filtra dostawców
|
||
supplierFilter.addEventListener('change', () => {
|
||
filterBySupplier(supplierFilter.value);
|
||
});
|
||
|
||
filterContainer.appendChild(document.createTextNode('Filtruj: '));
|
||
filterContainer.appendChild(supplierFilter);
|
||
|
||
// Wstaw filtry przed tabelą
|
||
table.parentNode.insertBefore(filterContainer, table);
|
||
|
||
log('Dodano filtry przed tabelą (fallback)');
|
||
}
|
||
|
||
/**
|
||
* Filtruje wiersze według dostawcy
|
||
* @param {string} supplier - Nazwa dostawcy
|
||
*/
|
||
function filterBySupplier(supplier) {
|
||
const rows = document.querySelectorAll('.o_data_row');
|
||
|
||
rows.forEach(row => {
|
||
const supplierCell = row.children[3]; // Dostawca jest w komórce 3
|
||
if (!supplierCell) return;
|
||
|
||
const supplierName = supplierCell.textContent.trim();
|
||
const shouldShow = !supplier || supplierName === supplier;
|
||
row.style.display = shouldShow ? '' : 'none';
|
||
});
|
||
|
||
log(`Filtrowanie według dostawcy: ${supplier || 'wszyscy'}`);
|
||
}
|
||
|
||
/**
|
||
* Kopiuje tekst do schowka (jak w idea-orders.user.js)
|
||
* @param {string} text - Tekst do skopiowania
|
||
*/
|
||
async function tmCopyToClipboard(text) {
|
||
try {
|
||
await navigator.clipboard.writeText(text);
|
||
} catch (error) {
|
||
// Fallback dla starszych przeglądarek
|
||
const textArea = document.createElement('textarea');
|
||
textArea.value = text;
|
||
document.body.appendChild(textArea);
|
||
textArea.select();
|
||
document.execCommand('copy');
|
||
document.body.removeChild(textArea);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Czyni komórkę kopiowalną (styl z idea-orders.user.js)
|
||
* @param {Element} td - Komórka tabeli
|
||
*/
|
||
function makeCellCopyable(td) {
|
||
if (!td || td.dataset.tmCopyable) return;
|
||
td.dataset.tmCopyable = '1';
|
||
td.classList.add('tm-copyable');
|
||
td.addEventListener('click', async (e) => {
|
||
e.stopPropagation(); // nie wyzwalaj akcji Odoo na wierszu
|
||
let val = (td.textContent || '').trim();
|
||
if (!val) return;
|
||
|
||
// Dla kwot usuń "zł" i zbędne spacje oraz znaki specjalne
|
||
if (val.includes('zł')) {
|
||
val = val.replace(/\s*zł\s*$/, '').trim();
|
||
// Usuń też znaki niełamliwych spacji i inne znaki formatujące
|
||
val = val.replace(/[\u00A0\u202F\u2009]/g, ' ').trim();
|
||
}
|
||
|
||
try {
|
||
await tmCopyToClipboard(val);
|
||
const old = td.title;
|
||
td.title = `Skopiowano: ${val}`;
|
||
td.classList.add('tm-copied');
|
||
setTimeout(() => {
|
||
td.title = old || 'Skopiowano';
|
||
td.classList.remove('tm-copied');
|
||
}, 1200);
|
||
log(`Skopiowano: ${val}`);
|
||
} catch (error) {
|
||
console.error('[Tampermonkey] Błąd podczas kopiowania komórki:', error);
|
||
td.title = 'Błąd podczas kopiowania';
|
||
}
|
||
}, true); // capture=true
|
||
}
|
||
|
||
/**
|
||
* Dodaje funkcjonalność kopiowania do wybranych kolumn
|
||
*/
|
||
function addCopyFunctionality() {
|
||
// Znajdź wszystkie wiersze danych
|
||
const dataRows = document.querySelectorAll('.o_data_row');
|
||
log(`Znaleziono ${dataRows.length} wierszy danych`);
|
||
|
||
if (dataRows.length === 0) {
|
||
log('Brak wierszy danych - sprawdzam strukturę tabeli');
|
||
const allRows = document.querySelectorAll('tr');
|
||
log(`Wszystkich wierszy: ${allRows.length}`);
|
||
allRows.forEach((row, index) => {
|
||
if (index < 5) { // Pokaż tylko pierwsze 5
|
||
log(`Wiersz ${index}: klasy="${row.className}", komórek=${row.children.length}`);
|
||
}
|
||
});
|
||
}
|
||
|
||
dataRows.forEach((row, rowIndex) => {
|
||
log(`Przetwarzam wiersz ${rowIndex}`);
|
||
|
||
// Debuguj wszystkie komórki w wierszu (tylko pierwszy wiersz żeby nie spamować)
|
||
if (rowIndex === 0) {
|
||
log(`=== DEBUGOWANIE STRUKTURY PIERWSZEGO WIERSZA ===`);
|
||
Array.from(row.children).forEach((cell, cellIndex) => {
|
||
const dataName = cell.getAttribute('data-name');
|
||
const text = cell.textContent.trim();
|
||
const classes = cell.className;
|
||
log(` Komórka ${cellIndex}: data-name="${dataName}", klasy="${classes}", tekst="${text.substring(0, 40)}"`);
|
||
});
|
||
log(`=== KONIEC DEBUGOWANIA STRUKTURY ===`);
|
||
}
|
||
|
||
// Numer faktury - komórka 2 (na podstawie debugowania)
|
||
let numberCell = row.children[2];
|
||
|
||
if (numberCell && numberCell.textContent.includes('KFZ/')) {
|
||
makeCellCopyable(numberCell);
|
||
log(`✓ Dodano kopiowanie dla numeru: "${numberCell.textContent.trim()}"`);
|
||
} else if (numberCell) {
|
||
log(`Pominięto kopiowanie numeru: "${numberCell.textContent.trim()}" (brak KFZ/)`);
|
||
} else {
|
||
log(`Nie znaleziono komórki numeru (indeks 2)`);
|
||
}
|
||
|
||
// Data faktury - komórka 4 (na podstawie debugowania)
|
||
let dateCell = row.children[4];
|
||
|
||
if (dateCell && dateCell.textContent.match(/\d{2}\.\d{2}\.\d{4}/)) {
|
||
makeCellCopyable(dateCell);
|
||
log(`✓ Dodano kopiowanie dla daty: "${dateCell.textContent.trim()}"`);
|
||
} else if (dateCell) {
|
||
log(`Pominięto kopiowanie daty: "${dateCell.textContent.trim()}" (nie pasuje format)`);
|
||
} else {
|
||
log(`Nie znaleziono komórki daty (indeks 4)`);
|
||
}
|
||
|
||
// Dokument źródłowy - komórka 5 (na podstawie debugowania: "ZZ/00310")
|
||
let invoiceOriginCell = row.children[5];
|
||
|
||
if (invoiceOriginCell) {
|
||
const invoiceOriginText = invoiceOriginCell.textContent.trim();
|
||
log(`Znaleziono dokument źródłowy: "${invoiceOriginText}"`);
|
||
if (invoiceOriginText && invoiceOriginText !== '' && invoiceOriginText !== '-') {
|
||
makeCellCopyable(invoiceOriginCell);
|
||
log(`✓ Dodano kopiowanie dla dokumentu źródłowego: "${invoiceOriginText}"`);
|
||
} else {
|
||
log(`Dokument źródłowy pusty lub myślnik: "${invoiceOriginText}"`);
|
||
}
|
||
} else {
|
||
log(`❌ Nie znaleziono komórki dokumentu źródłowego (indeks 5)`);
|
||
}
|
||
|
||
// Odnośnik - komórka 6 (na podstawie debugowania: "KFAS/2025/10/269/PL")
|
||
let refCell = row.children[6];
|
||
|
||
if (refCell) {
|
||
const refText = refCell.textContent.trim();
|
||
log(`Znaleziono odnośnik: "${refText}"`);
|
||
if (refText && refText !== '' && refText !== '-') {
|
||
makeCellCopyable(refCell);
|
||
log(`✓ Dodano kopiowanie dla odnośnika: "${refText}"`);
|
||
} else {
|
||
log(`Odnośnik pusty lub myślnik: "${refText}"`);
|
||
}
|
||
} else {
|
||
log(`❌ Nie znaleziono komórki odnośnika (indeks 6)`);
|
||
}
|
||
|
||
// Należność - komórka 10 (na podstawie debugowania: "1 123,80 zł")
|
||
let amountCell = row.children[10];
|
||
|
||
if (amountCell) {
|
||
const amountText = amountCell.textContent.trim();
|
||
log(`Znaleziono należność: "${amountText}"`);
|
||
|
||
if (amountText.includes('zł')) {
|
||
makeCellCopyable(amountCell);
|
||
log(`✓ Dodano kopiowanie dla należności: "${amountText}"`);
|
||
} else {
|
||
log(`Należność bez 'zł': "${amountText}"`);
|
||
}
|
||
} else {
|
||
log(`❌ Nie znaleziono komórki należności (indeks 10)`);
|
||
}
|
||
|
||
// Na podstawie - komórka 11 (na podstawie debugowania: "KFZ/283/09/2025 (KFAS/2025/09/799/PL)")
|
||
let basisCell = row.children[11];
|
||
|
||
if (basisCell) {
|
||
const basisText = basisCell.textContent.trim();
|
||
log(`Znaleziono "Na podstawie": "${basisText}"`);
|
||
if (basisText && basisText !== '' && basisText !== '-') {
|
||
makeCellCopyable(basisCell);
|
||
log(`✓ Dodano kopiowanie dla "Na podstawie": "${basisText}"`);
|
||
} else {
|
||
log(`Na podstawie puste lub myślnik: "${basisText}"`);
|
||
}
|
||
} else {
|
||
log(`❌ Nie znaleziono komórki "Na podstawie" (indeks 11)`);
|
||
}
|
||
});
|
||
|
||
log('Zakończono dodawanie funkcjonalności kopiowania');
|
||
}
|
||
|
||
/**
|
||
* Dodaje style CSS dla funkcjonalności kopiowania (dokładnie jak w idea-orders.user.js)
|
||
*/
|
||
function addCopyStyles() {
|
||
// Sprawdź czy style już zostały dodane
|
||
if (document.querySelector('#kfz-copy-styles')) {
|
||
log('Style kopiowania już istnieją - pomijam dodawanie');
|
||
return;
|
||
}
|
||
|
||
const style = document.createElement('style');
|
||
style.id = 'kfz-copy-styles';
|
||
style.textContent = `
|
||
/* Kopiowanie – ikona tuż za liczbą (inline), mniejsza, pomarańczowa */
|
||
td.tm-copyable { cursor: copy; }
|
||
td.tm-copyable::after {
|
||
content: '⧉'; /* symbol kopiowania w kolorze CSS */
|
||
display: inline-block; /* w tej samej linii co liczba */
|
||
margin-left: 6px; /* mały odstęp od liczby */
|
||
font-size: 12px; /* mniejszy rozmiar */
|
||
line-height: 1;
|
||
color: #ff9800; /* pomarańczowy */
|
||
opacity: .75; /* widoczna także bez hovera */
|
||
vertical-align: baseline;
|
||
pointer-events: none;
|
||
transition: opacity .15s ease;
|
||
}
|
||
td.tm-copyable:hover::after { opacity: 1; }
|
||
td.tm-copyable.tm-copied {
|
||
outline: 2px solid #ff9800;
|
||
background-image: linear-gradient(90deg, rgba(255,152,0,.12), rgba(255,152,0,0));
|
||
}
|
||
`;
|
||
document.head.appendChild(style);
|
||
}
|
||
|
||
/**
|
||
* Czeka na załadowanie elementu
|
||
* @param {string} selector - Selektor CSS
|
||
* @param {number} timeout - Timeout w ms
|
||
* @returns {Promise<Element>}
|
||
*/
|
||
function waitForElement(selector, timeout = 5000) {
|
||
return new Promise((resolve, reject) => {
|
||
const element = document.querySelector(selector);
|
||
if (element) {
|
||
resolve(element);
|
||
return;
|
||
}
|
||
|
||
const observer = new MutationObserver(() => {
|
||
const element = document.querySelector(selector);
|
||
if (element) {
|
||
observer.disconnect();
|
||
resolve(element);
|
||
}
|
||
});
|
||
|
||
observer.observe(document.body, {
|
||
childList: true,
|
||
subtree: true
|
||
});
|
||
|
||
setTimeout(() => {
|
||
observer.disconnect();
|
||
reject(new Error(`Element ${selector} nie został znaleziony w czasie ${timeout}ms`));
|
||
}, timeout);
|
||
});
|
||
}
|
||
|
||
// Przechwytywanie żądań AJAX do search_read
|
||
function interceptAjaxRequests() {
|
||
const originalFetch = window.fetch;
|
||
window.fetch = function(...args) {
|
||
const promise = originalFetch.apply(this, args);
|
||
|
||
// Sprawdź czy to żądanie do search_read
|
||
if (args[0] && args[0].includes('/web/dataset/search_read')) {
|
||
promise.then(response => {
|
||
if (response.ok) {
|
||
log('Wykryto żądanie AJAX search_read - uruchamiam skrypt po opóźnieniu');
|
||
setTimeout(initEnhancements, 1500); // Większe opóźnienie dla AJAX
|
||
}
|
||
});
|
||
}
|
||
|
||
return promise;
|
||
};
|
||
|
||
// Przechwytywanie XMLHttpRequest (backup)
|
||
const originalXHR = window.XMLHttpRequest.prototype.open;
|
||
window.XMLHttpRequest.prototype.open = function(method, url, ...args) {
|
||
if (url && url.includes('/web/dataset/search_read')) {
|
||
this.addEventListener('load', () => {
|
||
log('Wykryto XHR search_read - uruchamiam skrypt po opóźnieniu');
|
||
setTimeout(initEnhancements, 1500);
|
||
});
|
||
}
|
||
return originalXHR.call(this, method, url, ...args);
|
||
};
|
||
}
|
||
|
||
// Inicjalizacja skryptu z obserwacją zmian
|
||
function startScript() {
|
||
log('🚀 SKRYPT ZAŁADOWANY - WERSJA 3.0');
|
||
log(`📍 URL: ${window.location.href}`);
|
||
|
||
// Przechwytuj żądania AJAX
|
||
interceptAjaxRequests();
|
||
|
||
// Pierwsze uruchomienie
|
||
setTimeout(initEnhancements, 1000);
|
||
|
||
// Obserwuj zmiany w DOM (dla dynamicznych stron)
|
||
const observer = new MutationObserver((mutations) => {
|
||
// Sprawdź czy dodano nowe wiersze danych
|
||
const hasNewDataRows = mutations.some(mutation =>
|
||
Array.from(mutation.addedNodes).some(node =>
|
||
node.nodeType === 1 && (
|
||
node.classList?.contains('o_data_row') ||
|
||
node.querySelector?.('.o_data_row')
|
||
)
|
||
)
|
||
);
|
||
|
||
if (hasNewDataRows) {
|
||
log('Wykryto nowe wiersze danych - uruchamiam skrypt');
|
||
setTimeout(initEnhancements, 800);
|
||
}
|
||
});
|
||
|
||
observer.observe(document.body, {
|
||
childList: true,
|
||
subtree: true
|
||
});
|
||
|
||
// Obserwuj zmiany URL (hash) - kilka metod dla pewności
|
||
let lastUrl = window.location.href;
|
||
|
||
// 1. Standardowy hashchange
|
||
window.addEventListener('hashchange', () => {
|
||
log('Wykryto hashchange - resetuję stan i uruchamiam skrypt');
|
||
// Resetuj stan przy zmianie URL
|
||
const scriptMarker = document.querySelector('.kfz-script-initialized');
|
||
if (scriptMarker) {
|
||
scriptMarker.remove();
|
||
log('Usunięto marker inicjalizacji przy hashchange');
|
||
}
|
||
setTimeout(initEnhancements, 500);
|
||
setTimeout(initEnhancements, 1500); // Backup po dłuższym czasie
|
||
});
|
||
|
||
// 2. Obserwuj zmiany URL przez polling
|
||
setInterval(() => {
|
||
const currentUrl = window.location.href;
|
||
if (currentUrl !== lastUrl) {
|
||
log(`URL zmieniony z ${lastUrl} na ${currentUrl}`);
|
||
lastUrl = currentUrl;
|
||
// Resetuj stan przy zmianie URL
|
||
const scriptMarker = document.querySelector('.kfz-script-initialized');
|
||
if (scriptMarker) {
|
||
scriptMarker.remove();
|
||
log('Usunięto marker inicjalizacji przy zmianie URL');
|
||
}
|
||
setTimeout(initEnhancements, 300);
|
||
setTimeout(initEnhancements, 1000);
|
||
}
|
||
}, 500);
|
||
|
||
// 3. Obserwuj zmiany w tytule strony (Odoo często zmienia tytuł)
|
||
let lastTitle = document.title;
|
||
const titleObserver = new MutationObserver(() => {
|
||
if (document.title !== lastTitle) {
|
||
log(`Tytuł zmieniony z "${lastTitle}" na "${document.title}"`);
|
||
lastTitle = document.title;
|
||
setTimeout(initEnhancements, 400);
|
||
}
|
||
});
|
||
|
||
titleObserver.observe(document.querySelector('title') || document.head, {
|
||
childList: true,
|
||
characterData: true,
|
||
subtree: true
|
||
});
|
||
|
||
// 4. Obserwuj zmiany w breadcrumb (ścieżka nawigacji Odoo)
|
||
const breadcrumbObserver = new MutationObserver(() => {
|
||
log('Wykryto zmiany w breadcrumb - sprawdzam czy to strona KFZ');
|
||
setTimeout(initEnhancements, 200);
|
||
});
|
||
|
||
// Obserwuj breadcrumb jeśli istnieje
|
||
const breadcrumbElement = document.querySelector('.breadcrumb, .o_cp_controller');
|
||
if (breadcrumbElement) {
|
||
breadcrumbObserver.observe(breadcrumbElement, {
|
||
childList: true,
|
||
characterData: true,
|
||
subtree: true
|
||
});
|
||
}
|
||
|
||
// 5. Dodatkowe sprawdzenie co 3 sekundy (backup)
|
||
setInterval(() => {
|
||
const url = window.location.href;
|
||
const isAccountMoveList = url.includes('model=account.move') && url.includes('view_type=list');
|
||
const hasFilter = document.querySelector('.kfz-filter-container');
|
||
const breadcrumbCheck = document.querySelector('.breadcrumb');
|
||
const hasCorrectionsBreadcrumb = breadcrumbCheck && breadcrumbCheck.textContent.includes('Korekty');
|
||
|
||
if (isAccountMoveList && !hasFilter && (isKfzListingPage() || hasCorrectionsBreadcrumb)) {
|
||
log('Backup check - wykryto stronę account.move bez filtra, uruchamiam skrypt');
|
||
initEnhancements();
|
||
}
|
||
}, 3000);
|
||
}
|
||
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', startScript);
|
||
} else {
|
||
startScript();
|
||
}
|
||
})();
|