302 lines
13 KiB
JavaScript
302 lines
13 KiB
JavaScript
// ==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 });
|
||
})();
|