// ==UserScript== // @name Idea Invoice Famica // @namespace http://tampermonkey.net/ // @version 0.2 // @description Rozszerza funkcjonalność faktur IdeaERP o dodawanie numeru EORI i ulepszone drukowanie/generowanie PDF dla faktur Famica Ltd. // @author You // @match https://emma.ideaerp.pl/my/invoices* // @icon https://emma.ideaerp.pl/web/image/res.company/1/favicon/ // @downloadURL https://n8n.emma.net.pl/webhook/invoice-famica // @updateURL https://n8n.emma.net.pl/webhook/invoice-famica // @grant none // ==/UserScript== /** * IDEA INVOICE FAMICA - Skrypt Rozszerzający Przetwarzanie Faktur * * Ten skrypt użytkownika Tampermonkey rozszerza interfejs faktur IdeaERP poprzez * dodanie trzech niestandardowych przycisków usprawniających workflow przetwarzania faktur. * * FUNKCJE: * - Dodaje numer EORI (GB174521806000) do informacji o nabywcy na fakturach * - Ulepszone drukowanie z prawidłową widocznością nagłówków tabel i cieniowania * - Funkcjonalność generowania PDF z zachowaniem formatowania * * SZCZEGÓŁY TECHNICZNE: * - Działa w systemie wyświetlania faktur opartym na iframe * - Używa wstrzykiwania CSS do wymuszenia dostosowania kolorów przy drukowaniu * - Implementuje obsługę MediaQueryList kompatybilną z różnymi przeglądarkami * - Zapobiega duplikowaniu przycisków poprzez znaczniki dataset * * SPOSÓB UŻYCIA: * 1. "Dodaj EORI" - Dodaje numer EORI do sekcji nabywcy na fakturze * 2. "Wydrukuj z EORI" - Otwiera okno drukowania z ulepszonym formatowaniem * 3. "Pobierz z EORI" - Prowadzi użytkownika przez zapisanie faktury jako PDF z formatowaniem * * HISTORIA ZMIAN: * v0.2 (2025-09-24) * - Dodano ulepszoną funkcjonalność drukowania z poprawkami widoczności nagłówków tabel * - Zaimplementowano zachowanie cieniowania wierszy w wydruku * - Dodano funkcjonalność pobierania PDF z instrukcją dla użytkownika * - Naprawiono problemy kompatybilności MediaQueryList między przeglądarkami * - Ulepszona obsługa błędów i mechanizmy czyszczenia * * v0.1 (2025-09-24) * - Pierwsze wydanie z podstawową funkcjonalnością dodawania EORI * - Podstawowe tworzenie przycisków i manipulacja zawartością iframe * - Implementacja MutationObserver do wykrywania dynamicznej zawartości */ (function() { 'use strict'; function addButtons() { const targetDivs = document.querySelectorAll('.o_download_pdf.btn-toolbar'); targetDivs.forEach(targetDiv => { // Check if buttons are already added if (targetDiv.dataset.customButtonsAdded) { return; } // Create "Dodaj EORI" button const eoriButtonGroup = document.createElement('div'); eoriButtonGroup.className = 'btn-group flex-grow-1 mr-1 mb-1'; const eoriButton = document.createElement('a'); eoriButton.className = 'btn btn-secondary btn-block'; eoriButton.href = '#'; eoriButton.innerHTML = ' Dodaj EORI'; eoriButton.onclick = function(e) { e.preventDefault(); const iframe = document.getElementById('invoice_html'); if (!iframe || !iframe.contentWindow) { alert('Ramka z fakturą nie została znaleziona.'); return false; } const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; const nabywcaDiv = iframeDoc.querySelector('.i_nabywca.col-4'); if (!nabywcaDiv) { alert('Sekcja nabywcy nie została znaleziona w fakturze.'); return false; } // Check if EORI is already added if (iframeDoc.querySelector('.eori-number')) { alert('Numer EORI został już dodany.'); return false; } const br = iframeDoc.createElement('br'); const eoriTextNode = iframeDoc.createTextNode('EORI Number: '); const eoriSpan = iframeDoc.createElement('span'); eoriSpan.className = 'eori-number'; eoriSpan.innerText = 'GB174521806000'; nabywcaDiv.appendChild(br); nabywcaDiv.appendChild(eoriTextNode); nabywcaDiv.appendChild(eoriSpan); return false; }; eoriButtonGroup.appendChild(eoriButton); // Create "Wydrukuj z EORI" button const printEoriButtonGroup = document.createElement('div'); printEoriButtonGroup.className = 'btn-group flex-grow-1 mb-1'; const printEoriButton = document.createElement('a'); printEoriButton.className = 'btn btn-secondary btn-block'; printEoriButton.href = '#'; printEoriButton.innerHTML = ' Wydrukuj z EORI'; printEoriButton.onclick = function(e) { e.preventDefault(); const iframe = document.getElementById('invoice_html'); if (iframe && iframe.contentWindow) { const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; const iframeWin = iframe.contentWindow; // Inject style to force printing background colors and shadows const style = iframeDoc.createElement('style'); style.id = 'print-fix-styles'; style.innerHTML = ` @media print { body, body * { -webkit-print-color-adjust: exact !important; color-adjust: exact !important; print-color-adjust: exact !important; } .table-striped tbody tr:nth-of-type(odd) { background-color: #f9f9f9 !important; } thead, thead *, thead th, thead th * { color: black !important; -webkit-text-fill-color: black !important; visibility: visible !important; } } `; iframeDoc.head.appendChild(style); const removePrintStyles = () => { const styleToRemove = iframeDoc.getElementById('print-fix-styles'); if (styleToRemove) { iframeDoc.head.removeChild(styleToRemove); } }; const mediaQueryList = iframeWin.matchMedia('print'); const handler = (mql) => { if (!mql.matches) { removePrintStyles(); // Use the correct method based on browser support try { if (mql.removeEventListener) { mql.removeEventListener('change', handler); } else if (mql.removeListener) { mql.removeListener(handler); } } catch (e) { // Ignore errors when removing listeners console.log('Error removing print listener:', e); } } }; try { if (mediaQueryList.addEventListener) { mediaQueryList.addEventListener('change', handler); } else if (mediaQueryList.addListener) { mediaQueryList.addListener(handler); } } catch (e) { console.log('Error adding print listener:', e); } iframeWin.focus(); iframeWin.print(); } else { window.print(); } return false; }; printEoriButtonGroup.appendChild(printEoriButton); // Create "Pobierz z EORI" button const downloadEoriButtonGroup = document.createElement('div'); downloadEoriButtonGroup.className = 'btn-group flex-grow-1 mr-1 mb-1'; const downloadEoriButton = document.createElement('a'); downloadEoriButton.className = 'btn btn-secondary btn-block'; downloadEoriButton.href = '#'; downloadEoriButton.innerHTML = ' Pobierz z EORI'; downloadEoriButton.onclick = function(e) { e.preventDefault(); const iframe = document.getElementById('invoice_html'); if (iframe && iframe.contentWindow) { const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; const iframeWin = iframe.contentWindow; // Inject style to force printing background colors and shadows const style = iframeDoc.createElement('style'); style.id = 'download-fix-styles'; style.innerHTML = ` @media print { body, body * { -webkit-print-color-adjust: exact !important; color-adjust: exact !important; print-color-adjust: exact !important; } .table-striped tbody tr:nth-of-type(odd) { background-color: #f9f9f9 !important; } thead, thead *, thead th, thead th * { color: black !important; -webkit-text-fill-color: black !important; visibility: visible !important; } } `; iframeDoc.head.appendChild(style); // Show instruction to user alert('W oknie drukowania wybierz "Zapisz jako PDF" jako drukarkę docelową.'); // Create a function to handle PDF saving const saveAsPdf = () => { iframeWin.print(); // Clean up styles after a delay setTimeout(() => { const styleToRemove = iframeDoc.getElementById('download-fix-styles'); if (styleToRemove) { iframeDoc.head.removeChild(styleToRemove); } }, 1000); }; iframeWin.focus(); setTimeout(saveAsPdf, 500); } else { window.print(); } return false; }; downloadEoriButtonGroup.appendChild(downloadEoriButton); // Append new buttons to the target div targetDiv.appendChild(eoriButtonGroup); targetDiv.appendChild(printEoriButtonGroup); targetDiv.appendChild(downloadEoriButtonGroup); // Mark as buttons added targetDiv.dataset.customButtonsAdded = 'true'; }); } // Use MutationObserver to detect when new content is added to the page const observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.addedNodes.length) { addButtons(); } }); }); observer.observe(document.body, { childList: true, subtree: true }); })();