// ==UserScript== // @name IDEAERP - Zamówienia Zakupu // @namespace http://tampermonkey.net/ // @version 1.0 // @description Kopiowanie elementów na stronach zamówień zakupu - 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/purchase // @updateURL https://n8n.emma.net.pl/webhook/purchase // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_registerMenuCommand // ==/UserScript== /* ========================OPIS============================== WERSJA 1.0 - Kopiowanie elementów zamówień zakupu DZIAŁANIE SKRYPTU: - Kopiowanie nazwy zamówienia - kliknij na nazwę zamówienia (h1) aby skopiować - Kopiowanie produktów z kolumny "Produkt" - kliknij na produkt aby skopiować SKU lub pełną nazwę - Tryb kopiowania SKU/pełna nazwa - przełączalny przycisk - Obsługa błędów w funkcjach kopiowania zgodnie z zasadami .rules - Spójny styl wizualny ikon kopiowania (pomarańczowy kolor #ff9800) AUTOMATYCZNE AKTUALIZACJE: - Tampermonkey automatycznie sprawdza dostępność nowych wersji - Jednoklikowa aktualizacja bez konieczności ręcznego pobierania - Zachowanie ustawień użytkownika podczas aktualizacji - Kompatybilność z systemem n8n do zarządzania wersjami CHANGELOG: v1.0 (2024) - PIERWSZA WERSJA SKRYPTU ZAKUPOWEGO - Utworzono dedykowany skrypt dla zamówień zakupu (purchase.order) - Dodano kopiowanie nazwy zamówienia (h1) z ikoną ⧉ - Dodano kopiowanie produktów z kolumny "Produkt" z ikoną ⧉ - Dodano tryb kopiowania SKU/pełna nazwa z przyciskiem przełączania - Dodano funkcję extractSKU() do wyodrębniania SKU z nawiasów kwadratowych - Dodano style CSS dla elementów h1 i td z ikonami kopiowania - Dodano funkcje makeOrderNameCopyable() i makeProductCellCopyable() - Obsługa błędów w funkcjach kopiowania zgodnie z zasadami .rules - Spójny styl wizualny ikon kopiowania (pomarańczowy kolor #ff9800) - Podwyższenie numeru wersji do 1.0 AUTOR: Adam Grodecki DATA: 2024 LICENCJA: Własność EMMA SPÓŁKA Z OGRANICZONĄ ODPOWIEDZIALNOŚCIĄ ================================================================ */ (function() { 'use strict'; // ——— Stałe ——— const COPY_MODE_KEY = 'tm_purchase_copy_mode'; const INIT_FLAG = 'tm_purchase_init_flag'; // ——— Style CSS ——— GM_addStyle(` /* Kopiowanie nazwy zamówienia – ikona dla h1 */ h1.tm-copyable { cursor: copy; } h1.tm-copyable::after { content: '⧉'; /* symbol kopiowania */ display: inline-block; /* w tej samej linii co tekst */ margin-left: 8px; /* odstęp od tekstu */ font-size: 14px; /* większy rozmiar dla h1 */ line-height: 1; color: #ff9800; /* pomarańczowy */ opacity: .75; /* widoczna także bez hovera */ vertical-align: baseline; pointer-events: none; transition: opacity .15s ease; } h1.tm-copyable:hover::after { opacity: 1; } h1.tm-copyable.tm-copied { outline: 2px solid #ff9800; background-image: linear-gradient(90deg, rgba(255,152,0,.12), rgba(255,152,0,0)); } /* Kopiowanie produktów – ikona tuż za tekstem (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 tekst */ margin-left: 6px; /* mały odstęp od tekstu */ 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)); } /* Przycisk przełączania trybu kopiowania w zakładkach */ .tm-copy-mode-btn { background: #ff9800 !important; color: white !important; border: none !important; padding: 8px 12px !important; border-radius: 4px !important; font-size: 12px !important; cursor: pointer !important; margin-left: 10px !important; display: inline-block !important; visibility: visible !important; opacity: 1 !important; } .tm-copy-mode-btn:hover { background: #f57c00 !important; } `); // ——— Pomocnicze ——— function isPurchaseOrderForm() { const params = new URLSearchParams((location.hash || '').replace(/^#/, '')); const model = params.get('model'); const viewType = params.get('view_type'); return model === 'purchase.order' && viewType === 'form'; } async function tmCopyToClipboard(text) { try { await navigator.clipboard.writeText(text); } catch { const ta = document.createElement('textarea'); ta.value = text; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); } } // ——— Funkcja wyodrębniania SKU z nawiasów kwadratowych ——— function extractSKU(productText) { const match = productText.match(/\[([^\]]+)\]/); return match ? match[1] : productText; } // ——— Kopiowanie nazwy zamówienia ——— function makeOrderNameCopyable(h1Element) { if (!h1Element || h1Element.dataset.tmOrderNameCopyable) return; h1Element.dataset.tmOrderNameCopyable = '1'; h1Element.classList.add('tm-copyable'); h1Element.style.cursor = 'pointer'; h1Element.title = 'Kliknij aby skopiować nazwę zamówienia'; h1Element.addEventListener('click', async (e) => { e.stopPropagation(); const orderName = (h1Element.textContent || '').trim(); if (!orderName) return; try { await tmCopyToClipboard(orderName); const old = h1Element.title; h1Element.title = `Skopiowano nazwę zamówienia: ${orderName}`; h1Element.classList.add('tm-copied'); setTimeout(() => { h1Element.title = old || 'Kliknij aby skopiować nazwę zamówienia'; h1Element.classList.remove('tm-copied'); }, 1200); } catch (error) { console.error('[Tampermonkey] Błąd podczas kopiowania nazwy zamówienia:', error); h1Element.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); } catch (error) { console.error('[Tampermonkey] Błąd podczas kopiowania produktu:', error); td.title = 'Błąd podczas kopiowania'; } }, true); // capture=true } // ——— Lokalizowanie docelowego miejsca dla przycisku ——— function findActionsContainer() { // Znajdź kontener przycisków poniżej zakładek const massBtn = document.querySelector('button[name="523"]'); if (massBtn) return massBtn.closest('.tab-pane') || massBtn.parentElement; // Fallback - znajdź kontener o_form_buttons const buttonsContainer = document.querySelector('.o_form_buttons'); if (buttonsContainer) return buttonsContainer; return null; } // ——— Dodaj przycisk przełączania trybu kopiowania ——— function addCopyModeToggleButton() { const host = findActionsContainer(); if (!host) { console.log('[Tampermonkey] Nie znaleziono kontenera akcji, pomijam dodawanie przycisku'); return; } const idBtn = 'tm-purchase-copy-mode-toggle-button'; if (host.querySelector('#' + idBtn)) { console.log('[Tampermonkey] Przycisk już istnieje, pomijam'); return; } console.log('[Tampermonkey] Znaleziono kontener akcji:', host); console.log('[Tampermonkey] Tworzę przycisk przełączania trybu kopiowania'); const btn = document.createElement('button'); btn.type = 'button'; btn.id = idBtn; btn.className = 'btn btn-secondary'; 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'; console.log('[Tampermonkey] Tryb kopiowania:', copyOnlySKU ? 'SKU' : 'Pełna nazwa'); }; btn.addEventListener('click', () => { const currentMode = GM_getValue(COPY_MODE_KEY, true); GM_setValue(COPY_MODE_KEY, !currentMode); refresh(); console.log('[Tampermonkey] Tryb kopiowania zmieniony na:', !currentMode ? 'SKU' : 'Pełna nazwa'); }); // Dodaj przycisk do kontenera przycisków (zawsze poniżej zakładek) const massBtn = host.querySelector('button[name="523"]'); if (massBtn && massBtn.parentElement) { massBtn.insertAdjacentElement('afterend', btn); console.log('[Tampermonkey] Przycisk dodany obok przycisku Masowa zmiana'); } else { host.appendChild(btn); console.log('[Tampermonkey] Przycisk dodany do kontenera akcji'); } refresh(); } // ——— Główna procedura ——— function processOnce() { if (!isPurchaseOrderForm()) return; if (!GM_getValue(INIT_FLAG)) { GM_setValue(INIT_FLAG, true); } // Dodaj kopiowanie nazwy zamówienia const orderNameSpan = document.querySelector('h1 span[name="name"]'); if (orderNameSpan) { const orderNameH1 = orderNameSpan.closest('h1'); if (orderNameH1) { makeOrderNameCopyable(orderNameH1); } } // Dodaj kopiowanie produktów z kolumny "Produkt" // Komórki produktów mają klasę o_list_many2one i są w 3. kolumnie tabeli const productCells = document.querySelectorAll('td.o_data_cell.o_field_cell.o_list_many2one.o_readonly_modifier.o_required_modifier'); productCells.forEach(cell => { // Sprawdź czy to rzeczywiście komórka produktu (zawiera nawiasy kwadratowe z SKU) const text = cell.textContent || ''; if (text.includes('[') && text.includes(']')) { makeProductCellCopyable(cell); } }); // Dodaj przycisk przełączania trybu kopiowania addCopyModeToggleButton(); } // ——— Start + reagowanie na zmiany ——— let deb = null; const debounce = (fn, ms = 150) => { clearTimeout(deb); deb = setTimeout(fn, ms); }; window.addEventListener('load', () => debounce(processOnce, 150)); window.addEventListener('hashchange', () => debounce(processOnce, 250)); new MutationObserver(() => debounce(processOnce, 150)).observe(document.documentElement, { childList: true, subtree: true }); })();