tampermonkey/idea/idea-payment-register.user.js
2025-12-15 19:42:21 +01:00

309 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ==UserScript==
// @name IDEAERP - Rejestracja Płatności
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Dodano kopiowanie kwoty w oknie rejestracji płatności
// @match https://emma.ideaerp.pl/web*
// @icon https://emma.ideaerp.pl/web/image/res.company/1/favicon/
// @downloadURL https://n8n.emma.net.pl/webhook/idea-payment-register
// @updateURL https://n8n.emma.net.pl/webhook/idea-payment-register
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_registerMenuCommand
// ==/UserScript==
/* ========================OPIS==============================
WERSJA 1.0 - Kopiowanie kwoty płatności
DZIAŁANIE SKRYPTU:
- Wykrywa okno modalne "Rejestruj płatność"
- Dodaje ikonę kopiowania (⧉) przy polu "Kwota" (amount)
- Kliknięcie kopiuje wartość do schowka
- Obsługa błędów i logowanie
CHANGELOG:
v1.0 (2024) - PIERWSZA WERSJA
- Dodano obsługę okna modalnego
- Dodano kopiowanie pola amount
- Stylizacja zgodna z innymi skryptami
============================================================ */
(function () {
'use strict';
// ——— Style CSS ———
GM_addStyle(`
/* Kopiowanie ikona tuż za liczbą (inline), mniejsza, pomarańczowa */
.tm-copyable-icon {
cursor: copy;
display: inline-block;
margin-left: 8px;
font-size: 14px;
line-height: 1;
color: #ff9800;
opacity: .75;
vertical-align: middle;
transition: opacity .15s ease;
}
.tm-copyable-icon:hover { opacity: 1; }
.tm-copied {
color: #4caf50 !important; /* Zielony po skopiowaniu */
}
`);
// ——— Schowek ———
async function tmCopyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
return true;
} catch (err) {
console.error('[Tampermonkey] Clipboard API failed, trying fallback', err);
try {
const ta = document.createElement('textarea');
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
return true;
} catch (e) {
console.error('[Tampermonkey] Fallback copy failed', e);
return false;
}
}
}
// ——— Główna logika ———
function processModal(modal) {
if (modal.dataset.tmProcessed) return;
// Sprawdź czy to modal płatności
const title = modal.querySelector('.modal-title');
const isPaymentModal = title && (title.textContent.includes('Rejestruj płatność') || title.textContent.includes('Register Payment'));
if (!isPaymentModal) {
// Jeśli nie ma tytułu, sprawdzamy czy są pola specyficzne dla płatności
if (!modal.querySelector('input[name="amount"]')) return;
}
console.log('[Tampermonkey] Przetwarzanie modalu płatności...');
// Lista pól do obsłużenia
const fieldsToProcess = [
{ name: 'amount', label: 'Kwota' },
{ name: 'amount_div', label: 'Kwota' }, // Fallback dla struktury z amount_div
{ name: 'payment_date', label: 'Data płatności' },
{ name: 'communication', label: 'Notatka' },
{ name: 'ref', label: 'Referencja' }
];
fieldsToProcess.forEach(field => {
// Priorytet dla inputa - szukamy najpierw inputa
let element = modal.querySelector(`input[name="${field.name}"]`);
// Jeśli nie ma inputa, szukamy div lub span
if (!element) {
element = modal.querySelector(`div[name="${field.name}"], span[name="${field.name}"]`);
}
if (element) {
console.log(`[Tampermonkey] Znaleziono pole ${field.name}:`, element);
addCopyIcon(element, field.label);
// Dla pola kwoty dodaj dodatkowy wiersz z VAT
if (field.name === 'amount' || field.name === 'amount_div') {
addVatRow(element, modal);
}
}
});
modal.dataset.tmProcessed = '1';
}
function addVatRow(amountElement, modal) {
// Znajdź wiersz tabeli (tr) zawierający pole kwoty
const amountRow = amountElement.closest('tr');
if (!amountRow) return;
// Sprawdź czy wiersz VAT już istnieje
if (amountRow.nextElementSibling && amountRow.nextElementSibling.classList.contains('tm-vat-row')) return;
// Ustal element z wartością (input) - priorytet dla inputa wewnątrz kontenera
let valueElement = amountElement;
if (amountElement.tagName !== 'INPUT' && amountElement.querySelector('input')) {
valueElement = amountElement.querySelector('input');
}
const getAmountValue = () => {
let valStr = '';
if (valueElement.tagName === 'INPUT') {
valStr = valueElement.value;
} else {
valStr = valueElement.textContent;
}
// Używamy tej samej logiki czyszczenia co przy kopiowaniu
valStr = valStr.replace(/[^\d,.-]/g, '').replace(',', '.');
return parseFloat(valStr) || 0;
};
// Oblicz VAT (Brutto -> VAT)
// Wzór: VAT = Brutto * 23 / 123
const calculateVat = (val) => {
if (!val) return '0,00';
const vat = (val * 23) / 123;
return vat.toFixed(2).replace('.', ',');
};
const amountVal = getAmountValue();
const calculatedValue = calculateVat(amountVal);
// Stwórz nowy wiersz
const tr = document.createElement('tr');
tr.className = 'tm-vat-row';
tr.innerHTML = `
<td class="o_td_label">
<label class="o_form_label">Kwota VAT (obl.)</label>
</td>
<td style="width: 100%; display: flex; align-items: center;">
<span class="tm-vat-value" style="margin-right: 5px; font-weight: bold;">${calculatedValue}</span>
<span class="tm-copyable-icon" title="Kliknij aby skopiować Kwota VAT">⧉</span>
</td>
`;
// Wstaw za wierszem kwoty
amountRow.parentNode.insertBefore(tr, amountRow.nextSibling);
// Obsługa kopiowania dla nowego pola
const copyIcon = tr.querySelector('.tm-copyable-icon');
const valueSpan = tr.querySelector('.tm-vat-value');
copyIcon.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
const val = valueSpan.textContent;
if (val) {
const success = await tmCopyToClipboard(val);
if (success) {
copyIcon.classList.add('tm-copied');
setTimeout(() => copyIcon.classList.remove('tm-copied'), 1200);
}
}
});
// Nasłuchiwanie zmian w polu kwoty (jeśli to input)
if (valueElement.tagName === 'INPUT') {
valueElement.addEventListener('input', () => {
const newVal = getAmountValue();
valueSpan.textContent = calculateVat(newVal);
});
}
}
function addCopyIcon(element, label) {
// Ustal element docelowy dla ikony i kontener
let targetContainer = element.parentElement;
let valueElement = element;
let isDatepicker = false;
// Jeśli element to div/span (np. datepicker wrapper), ale zawiera inputa, to chcemy inputa
if (element.tagName !== 'INPUT' && element.querySelector('input')) {
valueElement = element.querySelector('input');
// W przypadku datepickera (div), on sam jest kontenerem
targetContainer = element;
}
// Sprawdź czy to datepicker
if (targetContainer.classList.contains('o_datepicker')) {
isDatepicker = true;
// Dla datepickera wychodzimy poziom wyżej (do td), żeby nie psuć wewnętrznego layoutu (ikona kalendarza)
if (targetContainer.parentElement) {
targetContainer = targetContainer.parentElement;
}
}
// Sprawdź czy ikona już nie istnieje w kontenerze
if (targetContainer && targetContainer.querySelector('.tm-copyable-icon')) return;
const icon = document.createElement('span');
icon.className = 'tm-copyable-icon';
icon.textContent = '⧉';
icon.title = `Kliknij aby skopiować ${label}`;
icon.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
let valueToCopy = '';
if (valueElement.tagName === 'INPUT') {
valueToCopy = valueElement.value;
} else {
valueToCopy = valueElement.textContent;
}
// Czyszczenie wartości
if (label === 'Kwota') {
valueToCopy = valueToCopy.replace(/[^\d,.-]/g, '').trim();
} else {
valueToCopy = valueToCopy.trim();
}
if (valueToCopy) {
const success = await tmCopyToClipboard(valueToCopy);
if (success) {
icon.classList.add('tm-copied');
icon.title = `Skopiowano: ${valueToCopy}`;
setTimeout(() => {
icon.classList.remove('tm-copied');
icon.title = `Kliknij aby skopiować ${label}`;
}, 1200);
}
}
});
// Wstawianie ikony i stylowanie
if (targetContainer) {
// Wymuś flex na kontenerze, żeby ikona była obok
targetContainer.style.display = 'flex';
targetContainer.style.alignItems = 'center';
// Jeśli to datepicker, element datepickera (dziecko) powinien zająć resztę miejsca
if (isDatepicker) {
const datepickerDiv = targetContainer.querySelector('.o_datepicker');
if (datepickerDiv) {
datepickerDiv.style.flex = '1';
}
}
targetContainer.appendChild(icon);
} else {
// Fallback
element.appendChild(icon);
}
}
// ——— Obserwator zmian DOM ———
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType === 1) {
// Sprawdź czy dodano modal
if (node.classList.contains('modal') || node.classList.contains('o_technical_modal') || node.querySelector('.modal-dialog')) {
// Poczekaj chwilę na renderowanie zawartości
setTimeout(() => processModal(node), 500);
}
// Czasami modal jest już w DOM, ale zmienia się jego zawartość
const modal = node.closest('.modal');
if (modal) {
setTimeout(() => processModal(modal), 500);
}
}
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
})();