/** * refund-modal.js — Record Vendor Refund * * Erzeugt einen QBO Deposit gegen die ursprüngliche Expense-Kategorie. * Für den Fall: Geld kam tatsächlich zurück aufs Bank-/Kreditkartenkonto. */ import '../utils/api.js'; let modalEl = null; let onSavedCb = null; let vendors = []; let expenseAccounts = []; let paymentAccounts = []; let selectedVendorId = null; let selectedVendorName = ''; let isSaving = false; function todayISO() { return new Date().toISOString().split('T')[0]; } function escapeHtml(s) { if (s == null) return ''; return String(s) .replace(/&/g, '&').replace(//g, '>') .replace(/"/g, '"').replace(/'/g, '''); } function escapeAttr(s) { return String(s || '') .replace(/&/g, '&').replace(/"/g, '"') .replace(//g, '>'); } export async function openRefundModal({ onSaved } = {}) { onSavedCb = onSaved || null; selectedVendorId = null; selectedVendorName = ''; isSaving = false; try { [vendors, expenseAccounts, paymentAccounts] = await Promise.all([ window.API.accounting.getVendors('', 1000), window.API.accounting.getExpenseAccounts(), window.API.accounting.getPaymentAccounts() ]); for (const [name, data] of [['vendors', vendors], ['expenseAccounts', expenseAccounts], ['paymentAccounts', paymentAccounts]]) { if (data && data.error) { alert(`Failed to load ${name}: ${data.error}\n\nTry "Sync from QBO" first.`); return; } } } catch (err) { alert('Failed to load refund modal data: ' + err.message); return; } renderModal(); } function closeModal() { if (modalEl) { modalEl.remove(); modalEl = null; } } function renderModal() { closeModal(); const html = ` `; document.body.insertAdjacentHTML('beforeend', html); modalEl = document.getElementById('refund-modal'); } function onVendorInput() { const inputEl = document.getElementById('ref-vendor-search'); const dropdownEl = document.getElementById('ref-vendor-dropdown'); const q = (inputEl.value || '').trim().toLowerCase(); if (selectedVendorName && inputEl.value !== selectedVendorName) { selectedVendorId = null; selectedVendorName = ''; document.getElementById('ref-vendor-id').value = ''; } const filtered = q ? vendors.filter(v => v.displayName.toLowerCase().includes(q)).slice(0, 50) : vendors.slice(0, 50); let html = ''; if (filtered.length === 0) { html = `
No vendor found.
`; } else { html = filtered.map(v => `
${escapeHtml(v.displayName)}
${v.email ? `
${escapeHtml(v.email)}
` : ''}
`).join(''); } dropdownEl.innerHTML = html; dropdownEl.classList.remove('hidden'); if (!dropdownEl._outsideHandlerInstalled) { dropdownEl._outsideHandlerInstalled = true; document.addEventListener('click', (e) => { if (!dropdownEl.contains(e.target) && e.target !== inputEl) { dropdownEl.classList.add('hidden'); } }); } } function selectVendor(id, name) { selectedVendorId = id; selectedVendorName = name; document.getElementById('ref-vendor-search').value = name; document.getElementById('ref-vendor-id').value = id; document.getElementById('ref-vendor-dropdown').classList.add('hidden'); } async function save() { if (isSaving) return; hideError(); if (!selectedVendorId) return showError('Please select a vendor.'); const depositAccountId = document.getElementById('ref-deposit-account').value; if (!depositAccountId) return showError('Please select the deposit account.'); const categoryAccountId = document.getElementById('ref-category').value; if (!categoryAccountId) return showError('Please select the original expense category.'); const txnDate = document.getElementById('ref-date').value; if (!txnDate) return showError('Please enter a date.'); const amount = parseFloat(document.getElementById('ref-amount').value); if (!isFinite(amount) || amount <= 0) { return showError('Please enter a positive refund amount.'); } const payload = { vendorId: selectedVendorId, depositAccountId, categoryAccountId, txnDate, amount, refNo: document.getElementById('ref-ref-no').value.trim() || null, memo: document.getElementById('ref-memo').value.trim() || null }; isSaving = true; const btn = document.getElementById('ref-save-btn'); btn.disabled = true; btn.textContent = 'Saving…'; try { const result = await window.API.accounting.createRefund(payload); if (result.error) { showError(result.error); return; } if (typeof onSavedCb === 'function') { try { onSavedCb(result); } catch (e) { console.warn('onSaved cb threw:', e); } } closeModal(); } catch (err) { showError(err.message || 'Save failed'); } finally { isSaving = false; if (btn) { btn.disabled = false; btn.textContent = 'Record Refund'; } } } function showError(msg) { const el = document.getElementById('ref-error'); if (el) { el.textContent = msg; el.classList.remove('hidden'); } } function hideError() { const el = document.getElementById('ref-error'); if (el) el.classList.add('hidden'); } window.refundModal = { open: openRefundModal, close: closeModal, onVendorInput, selectVendor, save };