// ==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');
})();