785 lines
33 KiB
JavaScript
785 lines
33 KiB
JavaScript
// ==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: <div class="tab-content"> 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');
|
||
})();
|