// ==UserScript== // @name IDEAERP - KFZ Korekty // @namespace http://tampermonkey.net/ // @version 1.8 // @description Kopiowanie danych z tabeli KFZ, pól formularza i podsumowania - kliknij aby skopiować // @match https://emma.ideaerp.pl/web* // @icon https://emma.ideaerp.pl/web/image/res.company/1/favicon/ // @downloadURL https://n8n.emma.net.pl/webhook/kfz // @updateURL https://n8n.emma.net.pl/webhook/kfz // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_registerMenuCommand // @run-at document-end // ==/UserScript== /* ========================OPIS============================== WERSJA 1.1 - Funkcjonalność kopiowania danych z tabeli KFZ, pól formularza i podsumowania WYMAGANE PARAMETRY URL: - action=278 → ID akcji KFZ - menu_id=141 → ID menu KFZ - model=account.move → Model faktury/dokumentu - view_type=form → Widok formularza WYMAGANE KOLUMNY w tabeli KFZ: - product_id → Produkt - quantity_after → Ilość po korekcie - price_unit_after → Cena jednostkowa po korekcie - price_subtotal → Wartość - invoice_line_net → Netto - invoice_line_tax Podatek - invoice_line_gross → Brutto DZIAŁANIE SKRYPTU: - Kopiowanie produktów z trybem SKU/pełna nazwa (jak w idea-orders) - Kopiowanie wartości liczbowych z kolumn KFZ - Ikona kopiowania ⧉ przy każdej kopiowalnej komórce - Przycisk przełączania trybu kopiowania produktów - Obsługa błędów zgodnie z zasadami .rules - Automatyczne wykrywanie kolumn po atrybucie title FUNKCJE KOPIOWANIA: - Produkt: SKU (z nawiasów kwadratowych) lub pełna nazwa - Ilość po korekcie: wartość liczbowa - Cena jednostkowa po korekcie: wartość liczbowa - Wartość: wartość liczbowa - Netto: wartość liczbowa - Podatek: wartość liczbowa - Brutto: wartość liczbowa ZAPAMIĘTYWANIE USTAWIEŃ: - Tryb kopiowania produktów (SKU/pełna nazwa) zapisywany w pamięci - Ustawienia przetrwają restart przeglądarki i odświeżenie strony AUTOMATYCZNE AKTUALIZACJE: - Serwer aktualizacji: https://n8n.emma.net.pl/webhook/kfz - Tampermonkey automatycznie sprawdza dostępność nowych wersji CHANGELOG: v1.8 (2024) - PRZYWRÓCONO DWUKROPEK W TEKŚCIE PRZYCISKU - Przywrócono dwukropek po słowie "Kopiuję" - Tekst przycisku: "Kopiuję: SKU" i "Kopiuję: Pełna nazwa" - Podwyższenie numeru wersji do 1.8 v1.7 (2024) - USUNIĘTO DWUKROPEK Z TEKSTU PRZYCISKU - Zmieniono tekst przycisku z "Kopiuję: SKU" na "Kopiuję SKU" - Zmieniono tekst z "Kopiuję: Pełna nazwa" na "Kopiuję Pełna nazwa" - Usunięto dwukropek dla lepszej czytelności - Podwyższenie numeru wersji do 1.7 v1.6 (2024) - POPRAWKA LOKALIZACJI PRZYCISKU W TAB-CONTENT - Poprawiono findActionsContainer() - szuka w .tab-content > .tab-pane - Przycisk umieszczany w zakładce obok "Załaduj z cennika" i "Masowa zmiana" - Dodano wyszukiwanie zakładek z przyciskami (load_from_pricelist, button[name="523"]) - Przycisk w właściwym miejscu:
zamiast o_control_panel - Podwyższenie numeru wersji do 1.6 v1.5 (2024) - POPRAWKA UMIESZCZANIA PRZYCISKU - Poprawiono funkcję findActionsContainer() - priorytet dla .o_form_buttons_view - Przycisk "Kopiuję: SKU" teraz umieszczany w tym samym miejscu co w idea-orders - Usunięto skomplikowaną logikę umieszczania w zakładkach - Dokładnie ta sama strategia umieszczania co w idea-orders.user.js - Podwyższenie numeru wersji do 1.5 v1.4 (2024) - DODANO BRAKUJĄCE POLA - Dodano kopiowanie nazwy dokumentu (name) - np. "KFZ/302/10/2025" - Dodano kopiowanie NIP (invoice_name) - np. "CSG SPÓŁKA AKCYJNA" - Poprawiono mapowanie pól formularza na rzeczywiste selektory HTML - Rozszerzone selektory dla pola name (również h1 span[name="name"]) - Podwyższenie numeru wersji do 1.4 v1.3 (2024) - NAPRAWKA BŁĘDU SELEKTORÓW CSS - Usunięto nieprawidłowe selektory :contains() powodujące błąd SyntaxError - Dodano bezpieczne try/catch dla wszystkich selektorów - Alternatywne szukanie pól po etykietach bez :contains() - Naprawiono blokowanie strony przez nieprawidłowe selektory - Podwyższenie numeru wersji do 1.3 v1.2 (2024) - POPRAWKI KOPIOWANIA VAT I PÓL FORMULARZA - Poprawiono kopiowanie VAT - znaczek kopiowania przy wartościach, nie nazwach - Dodano commercial_partner_id jako "Nazwa na fakturę" - Rozszerzone selektory dla NIP (partner_vat, vat) - Dodano kopiowanie uwag (note, narration) - Lepsze wykrywanie elementów po etykietach - Podwyższenie numeru wersji do 1.2 v1.1 (2024) - KOPIOWANIE PÓL FORMULARZA I PODSUMOWANIA - Dodano kopiowanie pól formularza: Nazwa na fakturę, NIP, Odnosnik, Data faktury - Dodano kopiowanie pól podsumowania: Należność, Suma, Kwota bez podatku - Dodano kopiowanie VAT (różne stawki) - Automatyczna konwersja wartości ujemnych na dodatnie w podsumowaniu - Funkcja makePositive() dla zapewnienia dodatnich wartości - Rozszerzone selektory dla różnych typów elementów (span, a, input) - Podwyższenie numeru wersji do 1.1 v1.0 (2024) - PIERWSZA WERSJA - Dodano kopiowanie produktów z trybem SKU/pełna nazwa - Dodano kopiowanie wartości liczbowych z 6 kolumn KFZ - Dodano style CSS dla elementów kopiowalnych (pomarańczowy kolor #ff9800) - Dodano przycisk przełączania trybu kopiowania - Dodano menu Tampermonkey z opcjami konfiguracji - Obsługa błędów w funkcjach kopiowania zgodnie z zasadami .rules - Automatyczne wykrywanie kolumn po title zamiast indeksów - Spójny styl wizualny z idea-orders.user.js - Precyzyjne wykrywanie strony KFZ po parametrach action=278 i menu_id=141 ============================================================ */ (function () { 'use strict'; // Debug flag zgodnie z zasadami .rules const DEBUG = false; const log = (...args) => DEBUG && console.log('[Tampermonkey KFZ]', ...args); // ——— Klucze stanu ——— const COPY_MODE_KEY = 'tm_kfz_product_copy_mode_v10'; // true = SKU, false = pełna nazwa (domyślnie true) const TABLE_SEL = 'table.o_list_table.o_section_and_note_list_view'; // Mapowanie title na data-name dla kolumn do kopiowania const COPYABLE_COLUMNS = { 'Produkt': 'product_id', 'Ilość po korekcie': 'quantity_after', 'Cena jednostkowa po korekcie': 'price_unit_after', 'Wartość': 'price_subtotal', 'Netto': 'invoice_line_net', 'Podatek': 'invoice_line_tax', 'Brutto': 'invoice_line_gross' }; // Pola formularza do kopiowania const COPYABLE_FORM_FIELDS = { 'name': 'Nazwa dokumentu', 'commercial_partner_id': 'Nazwa na fakturę', 'partner_id': 'Kontrahent', 'invoice_name': 'NIP', 'partner_vat': 'NIP (alternatywny)', 'ref': 'Odnosnik', 'invoice_date': 'Data faktury', 'note': 'Uwagi', 'narration': 'Uwagi wewnętrzne' }; // Pola podsumowania do kopiowania const COPYABLE_SUMMARY_FIELDS = { 'amount_residual': 'Należność', 'amount_total': 'Suma', 'amount_untaxed': 'Kwota bez podatku' }; GM_addStyle(` /* 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)); } `); // ——— Pomocnicze funkcje ——— const textOf = el => el ? (el.getAttribute('title') || el.textContent || '').trim() : ''; // Funkcja do konwersji wartości na dodatnią const makePositive = (value) => { if (typeof value === 'string') { // Usuń znaki waluty i spacje const cleanValue = value.replace(/[^\d,.-]/g, '').replace(',', '.'); const numValue = parseFloat(cleanValue); return isNaN(numValue) ? value : Math.abs(numValue).toString(); } return Math.abs(parseFloat(value) || 0).toString(); }; function isKFZForm() { const params = new URLSearchParams((location.hash || '').replace(/^#/, '')); const action = params.get('action'); const menuId = params.get('menu_id'); const model = params.get('model'); const viewType = params.get('view_type'); log('Sprawdzanie parametrów URL:', { action, menuId, model, viewType, hash: location.hash, url: location.href }); // Sprawdź czy to właściwa strona KFZ z konkretnymi parametrami const isKFZ = action === '278' && menuId === '141' && model === 'account.move' && viewType === 'form'; log('Czy to strona KFZ?', isKFZ); return isKFZ; } // ——— Funkcja wyodrębniania SKU z nawiasów kwadratowych ——— function extractSKU(productText) { const match = productText.match(/\[([^\]]+)\]/); return match ? match[1] : productText; } // ——— Schowek: kopiowanie wartości z komórek ——— async function tmCopyToClipboard(text) { try { await navigator.clipboard.writeText(text); } catch (error) { log('Fallback do document.execCommand dla kopiowania'); const ta = document.createElement('textarea'); ta.value = text; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); } } // ——— Kopiowanie zwykłych komórek (wartości liczbowe) ——— function makeCellCopyable(td, makePositiveValue = false) { 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; // Konwertuj na wartość dodatnią jeśli wymagane if (makePositiveValue) { val = makePositive(val); } 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 komórkę:', val); } catch (error) { console.error('[Tampermonkey KFZ] Błąd podczas kopiowania komórki:', error); td.title = 'Błąd podczas kopiowania'; } }, true); // capture=true } // ——— Kopiowanie produktów z trybem SKU/pełna nazwa ——— function makeProductCellCopyable(td) { if (!td || td.dataset.tmProductCopyable) return; td.dataset.tmProductCopyable = '1'; td.classList.add('tm-copyable'); td.addEventListener('click', async (e) => { e.stopPropagation(); // nie wyzwalaj akcji Odoo na wierszu const fullText = (td.textContent || '').trim(); if (!fullText) return; try { const copyOnlySKU = GM_getValue(COPY_MODE_KEY, true); // domyślnie SKU const textToCopy = copyOnlySKU ? extractSKU(fullText) : fullText; await tmCopyToClipboard(textToCopy); const old = td.title; const mode = copyOnlySKU ? 'SKU' : 'pełną nazwę'; td.title = `Skopiowano ${mode}: ${textToCopy}`; td.classList.add('tm-copied'); setTimeout(() => { td.title = old || 'Skopiowano'; td.classList.remove('tm-copied'); }, 1200); log('Skopiowano produkt:', textToCopy, 'tryb:', mode); } catch (error) { console.error('[Tampermonkey KFZ] Błąd podczas kopiowania produktu:', error); td.title = 'Błąd podczas kopiowania'; } }, true); // capture=true } // ——— Kopiowanie pól formularza ——— function makeFormFieldCopyable(element, fieldName) { if (!element || element.dataset.tmFormCopyable) return; element.dataset.tmFormCopyable = '1'; element.classList.add('tm-copyable'); element.style.cursor = 'copy'; element.title = `Kliknij aby skopiować ${fieldName}`; element.addEventListener('click', async (e) => { e.stopPropagation(); let text = ''; // Różne sposoby wyciągania tekstu w zależności od typu elementu if (element.tagName === 'SPAN') { text = (element.textContent || '').trim(); } else if (element.tagName === 'A') { text = (element.querySelector('span')?.textContent || element.textContent || '').trim(); } else { text = (element.textContent || element.value || '').trim(); } if (!text) return; try { await tmCopyToClipboard(text); const old = element.title; element.title = `Skopiowano ${fieldName}: ${text}`; element.classList.add('tm-copied'); setTimeout(() => { element.title = old || `Kliknij aby skopiować ${fieldName}`; element.classList.remove('tm-copied'); }, 1200); log(`Skopiowano ${fieldName}:`, text); } catch (error) { console.error(`[Tampermonkey KFZ] Błąd podczas kopiowania ${fieldName}:`, error); element.title = 'Błąd podczas kopiowania'; } }, true); } // ——— Kopiowanie pól podsumowania (z konwersją na wartości dodatnie) ——— function makeSummaryFieldCopyable(element, fieldName) { if (!element || element.dataset.tmSummaryCopyable) return; element.dataset.tmSummaryCopyable = '1'; element.classList.add('tm-copyable'); element.style.cursor = 'copy'; element.title = `Kliknij aby skopiować ${fieldName}`; element.addEventListener('click', async (e) => { e.stopPropagation(); let text = (element.textContent || '').trim(); if (!text) return; // Konwertuj na wartość dodatnią const positiveValue = makePositive(text); try { await tmCopyToClipboard(positiveValue); const old = element.title; element.title = `Skopiowano ${fieldName}: ${positiveValue}`; element.classList.add('tm-copied'); setTimeout(() => { element.title = old || `Kliknij aby skopiować ${fieldName}`; element.classList.remove('tm-copied'); }, 1200); log(`Skopiowano ${fieldName}:`, positiveValue); } catch (error) { console.error(`[Tampermonkey KFZ] Błąd podczas kopiowania ${fieldName}:`, error); element.title = 'Błąd podczas kopiowania'; } }, true); } // ——— Znajdowanie kolumn po title ——— function findColumnByTitle(table, title) { const headers = table.querySelectorAll('thead th'); log(`Szukanie kolumny "${title}" w ${headers.length} nagłówkach`); for (let i = 0; i < headers.length; i++) { const headerTitle = headers[i].getAttribute('title'); const headerText = headers[i].textContent?.trim(); log(`Nagłówek ${i}: title="${headerTitle}", text="${headerText}"`); if (headerTitle === title) { log(`Znaleziono kolumnę "${title}" na pozycji ${i}`); return i; } } log(`Nie znaleziono kolumny "${title}"`); return -1; } // ——— Główna funkcja dodawania kopiowania ——— function addCopyFunctionality(table) { log('Dodawanie funkcjonalności kopiowania do tabeli'); log('Selektor tabeli:', table.className, table.tagName); // Iteruj przez wszystkie wiersze danych const rows = table.querySelectorAll('tbody tr.o_data_row'); log(`Znaleziono ${rows.length} wierszy danych`); if (rows.length === 0) { // Spróbuj alternatywnych selektorów const altRows = table.querySelectorAll('tbody tr'); log(`Alternatywnie znaleziono ${altRows.length} wierszy (wszystkie)`); altRows.forEach((row, rowIndex) => { log(`Wiersz ${rowIndex}:`, row.className, row.children.length, 'komórek'); }); } rows.forEach((row, rowIndex) => { log(`Przetwarzanie wiersza ${rowIndex} z ${row.children.length} komórkami`); // Dla każdej kolumny do kopiowania Object.entries(COPYABLE_COLUMNS).forEach(([title, dataName]) => { const colIndex = findColumnByTitle(table, title); if (colIndex === -1) { return; } const cell = row.children[colIndex]; if (!cell) { log(`Brak komórki w wierszu ${rowIndex}, kolumna ${colIndex}`); return; } log(`Dodawanie kopiowania do komórki [${rowIndex}, ${colIndex}] "${title}": "${cell.textContent?.trim()}"`); // Specjalne traktowanie dla kolumny Produkt if (title === 'Produkt') { makeProductCellCopyable(cell); } else { makeCellCopyable(cell); } }); }); } // ——— Dodawanie kopiowania dla pól formularza ——— function addFormFieldsCopyFunctionality() { log('Dodawanie kopiowania dla pól formularza...'); Object.entries(COPYABLE_FORM_FIELDS).forEach(([fieldName, displayName]) => { // Szukaj różnych selektorów dla pól let selectors = [ `span[name="${fieldName}"]`, `a[name="${fieldName}"]`, `input[name="${fieldName}"]`, `[name="${fieldName}"] span`, `[name="${fieldName}"]` ]; // Specjalne selektory dla konkretnych pól if (fieldName === 'invoice_name') { selectors = [ 'span[name="invoice_name"]', 'input[name="invoice_name"]', '[name="invoice_name"]' ]; } else if (fieldName === 'name') { selectors = [ 'span[name="name"]', 'input[name="name"]', '[name="name"]', 'h1 span[name="name"]' ]; } else if (fieldName === 'partner_vat') { selectors = [ 'span[name="partner_vat"]', 'input[name="partner_vat"]', '[name="partner_vat"]', 'span[name="vat"]', 'input[name="vat"]', '[name="vat"]' ]; } else if (fieldName === 'note' || fieldName === 'narration') { selectors = [ `span[name="${fieldName}"]`, `textarea[name="${fieldName}"]`, `div[name="${fieldName}"]`, `[name="${fieldName}"]` ]; } let found = false; for (const selector of selectors) { try { const element = document.querySelector(selector); if (element && element.textContent?.trim()) { log(`Znaleziono pole ${displayName} (${fieldName}):`, element.tagName, element.textContent?.trim()); makeFormFieldCopyable(element, displayName); found = true; break; } } catch (error) { log(`Błąd selektora "${selector}":`, error.message); } } // Jeśli nie znaleziono standardowymi selektorami, spróbuj szukać po etykietach if (!found) { if (fieldName === 'partner_vat') { const labels = document.querySelectorAll('label'); for (const label of labels) { if (label.textContent?.includes('NIP') || label.textContent?.includes('VAT')) { const nextElement = label.nextElementSibling?.querySelector('span') || label.parentElement?.nextElementSibling?.querySelector('span'); if (nextElement && nextElement.textContent?.trim()) { log(`Znaleziono ${displayName} przez etykietę:`, nextElement.textContent?.trim()); makeFormFieldCopyable(nextElement, displayName); break; } } } } else if (fieldName === 'note' || fieldName === 'narration') { const labels = document.querySelectorAll('label'); for (const label of labels) { if (label.textContent?.includes('Uwagi') || label.textContent?.includes('Notatki') || label.textContent?.includes('Opis')) { const nextElement = label.nextElementSibling?.querySelector('span, textarea, div') || label.parentElement?.nextElementSibling?.querySelector('span, textarea, div'); if (nextElement && nextElement.textContent?.trim()) { log(`Znaleziono ${displayName} przez etykietę:`, nextElement.textContent?.trim()); makeFormFieldCopyable(nextElement, displayName); break; } } } } } }); } // ——— Dodawanie kopiowania dla pól podsumowania ——— function addSummaryFieldsCopyFunctionality() { log('Dodawanie kopiowania dla pól podsumowania...'); Object.entries(COPYABLE_SUMMARY_FIELDS).forEach(([fieldName, displayName]) => { // Szukaj różnych selektorów dla pól podsumowania const selectors = [ `span[name="${fieldName}"]`, `[name="${fieldName}"]`, `.oe_subtotal_footer_separator[name="${fieldName}"]` ]; for (const selector of selectors) { const element = document.querySelector(selector); if (element) { log(`Znaleziono pole ${displayName} (${fieldName}):`, element.textContent?.trim()); makeSummaryFieldCopyable(element, displayName); break; } } }); // Dodatkowe szukanie VAT - znaczek kopiowania przy wartościach, nie nazwach const vatRows = document.querySelectorAll('tr:has(.oe_tax_group_name)'); vatRows.forEach((row, index) => { const nameElement = row.querySelector('.oe_tax_group_name'); const valueElement = row.querySelector('.oe_tax_group_amount_value, .oe_tax_group_editable span'); if (nameElement && valueElement && nameElement.textContent?.includes('VAT')) { const vatName = nameElement.textContent?.trim() || `VAT ${index + 1}`; log(`Znaleziono VAT "${vatName}":`, valueElement.textContent?.trim()); makeSummaryFieldCopyable(valueElement, vatName); } }); // Fallback - jeśli powyższe nie zadziała, szukaj bezpośrednio wartości VAT const vatValues = document.querySelectorAll('.oe_tax_group_amount_value'); vatValues.forEach((element, index) => { if (!element.dataset.tmSummaryCopyable) { log(`Znaleziono wartość VAT ${index + 1}:`, element.textContent?.trim()); makeSummaryFieldCopyable(element, `VAT ${index + 1}`); } }); } // ——— Lokalizowanie docelowego miejsca dla przycisku ——— function findActionsContainer() { log('Szukanie kontenera dla przycisku...'); // PRIORYTET 1: Dokładnie jak w idea-orders.user.js - szukaj notebook_page_130 const byId = document.getElementById('notebook_page_130'); if (byId) { log('Znaleziono notebook_page_130'); return byId; } // PRIORYTET 2: Szukaj przycisku load_from_pricelist i jego kontener .tab-pane const pricelistBtn = document.querySelector('button[name="load_from_pricelist"]'); if (pricelistBtn) { const container = pricelistBtn.closest('.tab-pane') || pricelistBtn.parentElement; log('Znaleziono kontener przez load_from_pricelist:', container.id || container.className); return container; } // PRIORYTET 3: Szukaj w .tab-content > .tab-pane (gdzie są zakładki) const tabPanes = document.querySelectorAll('.tab-content .tab-pane'); for (const pane of tabPanes) { // Szukaj zakładki z przyciskami (load_from_pricelist lub button[name="523"]) if (pane.querySelector('button[name="load_from_pricelist"], button[name="523"]')) { log('Znaleziono zakładkę z przyciskami:', pane.id || pane.className); return pane; } } // PRIORYTET 4: Szukaj konkretnych zakładek KFZ - notebook_page_669 (Pozycje faktury) const kfzTab = document.getElementById('notebook_page_669'); if (kfzTab) { log('Znaleziono notebook_page_669 (Pozycje faktury)'); return kfzTab; } log('Nie znaleziono odpowiedniego kontenera'); return null; } // ——— Przycisk przełączania trybu kopiowania produktów ——— function addCopyModeToggleButton() { const host = findActionsContainer(); if (!host) { log('Nie znaleziono kontenera dla przycisku'); return; } const idBtn = 'tm-kfz-copy-mode-toggle-button'; if (host.querySelector('#' + idBtn)) { log('Przycisk już istnieje'); return; } log('Tworzenie przycisku przełączania trybu kopiowania...'); const btn = document.createElement('button'); btn.type = 'button'; btn.id = idBtn; btn.className = 'btn btn-secondary'; // Dokładnie jak w idea-orders const label = document.createElement('span'); btn.appendChild(label); const refresh = () => { const copyOnlySKU = GM_getValue(COPY_MODE_KEY, true); label.textContent = copyOnlySKU ? 'Kopiuję: SKU' : 'Kopiuję: Pełna nazwa'; btn.title = copyOnlySKU ? 'Kliknij aby przełączyć na kopiowanie pełnej nazwy produktu' : 'Kliknij aby przełączyć na kopiowanie tylko SKU produktu'; log('Odświeżono przycisk, tryb:', copyOnlySKU ? 'SKU' : 'Pełna nazwa'); }; btn.addEventListener('click', () => { const newMode = !GM_getValue(COPY_MODE_KEY, true); GM_setValue(COPY_MODE_KEY, newMode); refresh(); log('Zmieniono tryb kopiowania na:', newMode ? 'SKU' : 'Pełna nazwa'); }); // Dokładnie ta sama logika umieszczania jak w idea-orders.user.js const massBtn = host.querySelector('button[name="523"]'); if (massBtn && massBtn.parentElement) { massBtn.insertAdjacentElement('afterend', btn); log('Dodano przycisk obok button[name="523"]'); } else { host.appendChild(btn); log('Dodano przycisk do kontenera:', host.className || host.tagName); } refresh(); } // ——— Menu Tampermonkey ——— GM_registerMenuCommand('Ustawienia KFZ ▶', () => { const choice = prompt( 'Ustawienia KFZ (wpisz numer):\n' + '1) Przełącz tryb kopiowania (SKU ⇄ Pełna nazwa)\n' + '2) Test kopiowania produktów\n' + '3) Wyświetl aktualny tryb kopiowania' ); if (!choice) return; switch (choice.trim()) { case '1': const currentMode = GM_getValue(COPY_MODE_KEY, true); GM_setValue(COPY_MODE_KEY, !currentMode); const newModeText = !currentMode ? 'SKU' : 'Pełna nazwa'; alert(`Tryb kopiowania zmieniony na: ${newModeText}`); break; case '2': const tables = document.querySelectorAll(TABLE_SEL); if (tables.length > 0) { addCopyFunctionality(tables[0]); alert('Funkcja kopiowania została zastosowana do tabeli KFZ.'); } else { alert('Nie znaleziono tabeli KFZ na tej stronie.'); } break; case '3': const mode = GM_getValue(COPY_MODE_KEY, true); alert(`Aktualny tryb kopiowania: ${mode ? 'SKU' : 'Pełna nazwa'}`); break; default: // nic } }); // ——— Główna procedura ——— function processOnce() { log('=== ROZPOCZĘCIE processOnce ==='); if (!isKFZForm()) { log('Nie jest to formularz KFZ - kończenie'); return; } log('Wykryto formularz KFZ, inicjalizacja...'); const tables = document.querySelectorAll(TABLE_SEL); log(`Znaleziono ${tables.length} tabel z selektorem: ${TABLE_SEL}`); if (!tables.length) { // Spróbuj alternatywnych selektorów const altTables = document.querySelectorAll('table.o_list_table'); log(`Alternatywnie znaleziono ${altTables.length} tabel z selektorem: table.o_list_table`); const allTables = document.querySelectorAll('table'); log(`Wszystkich tabel na stronie: ${allTables.length}`); allTables.forEach((table, i) => { log(`Tabela ${i}:`, table.className); }); addCopyModeToggleButton(); return; } tables.forEach((table, i) => { log(`Przetwarzanie tabeli ${i}:`, table.className); addCopyFunctionality(table); }); // Dodaj kopiowanie dla pól formularza i podsumowania addFormFieldsCopyFunctionality(); addSummaryFieldsCopyFunctionality(); addCopyModeToggleButton(); log('=== ZAKOŃCZENIE processOnce ==='); } // ——— Start + reagowanie na zmiany ——— let debounceTimer = null; const debounce = (fn, ms = 150) => { clearTimeout(debounceTimer); debounceTimer = setTimeout(fn, ms); }; // Inicjalizacja po załadowaniu DOM if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => debounce(processOnce, 150)); } else { debounce(processOnce, 150); } // Reagowanie na zmiany w DOM i hash window.addEventListener('hashchange', () => debounce(processOnce, 250)); new MutationObserver(() => debounce(processOnce, 150)) .observe(document.documentElement, { childList: true, subtree: true }); log('Skrypt KFZ załadowany'); })();