update
This commit is contained in:
@@ -1,15 +1,12 @@
|
||||
// payment-modal.js — ES Module für das Payment Recording Modal
|
||||
// Ermöglicht: Auswahl mehrerer Rechnungen, Check/ACH, Deposit To Konto
|
||||
// Fixes: Correct CSS class 'modal', local DB payment storage
|
||||
|
||||
// ============================================================
|
||||
// State
|
||||
// ============================================================
|
||||
let bankAccounts = [];
|
||||
let paymentMethods = [];
|
||||
let selectedInvoices = []; // Array of invoice objects
|
||||
let isOpen = false;
|
||||
|
||||
// Cache QBO reference data (nur einmal laden)
|
||||
let selectedInvoices = [];
|
||||
let dataLoaded = false;
|
||||
|
||||
// ============================================================
|
||||
@@ -35,109 +32,72 @@ async function loadQboData() {
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Open / Close Modal
|
||||
// Open / Close
|
||||
// ============================================================
|
||||
|
||||
export async function openPaymentModal(invoiceIds = []) {
|
||||
// Lade QBO-Daten falls noch nicht geschehen
|
||||
await loadQboData();
|
||||
|
||||
// Lade die ausgewählten Rechnungen
|
||||
if (invoiceIds.length > 0) {
|
||||
selectedInvoices = [];
|
||||
for (const id of invoiceIds) {
|
||||
try {
|
||||
const res = await fetch(`/api/invoices/${id}`);
|
||||
const data = await res.json();
|
||||
if (data.invoice) {
|
||||
selectedInvoices.push(data.invoice);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error loading invoice:', id, e);
|
||||
selectedInvoices = [];
|
||||
for (const id of invoiceIds) {
|
||||
try {
|
||||
const res = await fetch(`/api/invoices/${id}`);
|
||||
const data = await res.json();
|
||||
if (data.invoice) {
|
||||
selectedInvoices.push(data.invoice);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error loading invoice:', id, e);
|
||||
}
|
||||
}
|
||||
|
||||
renderModal();
|
||||
ensureModalElement();
|
||||
renderModalContent();
|
||||
document.getElementById('payment-modal').classList.add('active');
|
||||
isOpen = true;
|
||||
}
|
||||
|
||||
export function closePaymentModal() {
|
||||
const modal = document.getElementById('payment-modal');
|
||||
if (modal) modal.classList.remove('active');
|
||||
isOpen = false;
|
||||
selectedInvoices = [];
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Add/Remove Invoices from selection
|
||||
// DOM
|
||||
// ============================================================
|
||||
|
||||
export async function addInvoiceToPayment(invoiceId) {
|
||||
if (selectedInvoices.find(inv => inv.id === invoiceId)) return; // already selected
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/invoices/${invoiceId}`);
|
||||
const data = await res.json();
|
||||
if (data.invoice) {
|
||||
// Validierung: Muss QBO-verknüpft sein
|
||||
if (!data.invoice.qbo_id) {
|
||||
alert('Diese Rechnung ist noch nicht in QBO. Bitte erst exportieren.');
|
||||
return;
|
||||
}
|
||||
// Validierung: Alle müssen zum selben Kunden gehören
|
||||
if (selectedInvoices.length > 0 && data.invoice.customer_id !== selectedInvoices[0].customer_id) {
|
||||
alert('Alle Rechnungen eines Payments müssen zum selben Kunden gehören.');
|
||||
return;
|
||||
}
|
||||
selectedInvoices.push(data.invoice);
|
||||
renderInvoiceList();
|
||||
updateTotal();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error adding invoice:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function removeInvoiceFromPayment(invoiceId) {
|
||||
selectedInvoices = selectedInvoices.filter(inv => inv.id !== invoiceId);
|
||||
renderInvoiceList();
|
||||
updateTotal();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Rendering
|
||||
// ============================================================
|
||||
|
||||
function renderModal() {
|
||||
function ensureModalElement() {
|
||||
let modal = document.getElementById('payment-modal');
|
||||
|
||||
if (!modal) {
|
||||
modal = document.createElement('div');
|
||||
modal.id = 'payment-modal';
|
||||
modal.className = 'modal-overlay';
|
||||
// Verwende GLEICHE Klasse wie die existierenden Modals
|
||||
modal.className = 'modal fixed inset-0 bg-black bg-opacity-50 z-50 justify-center items-start pt-10 overflow-y-auto';
|
||||
document.body.appendChild(modal);
|
||||
}
|
||||
}
|
||||
|
||||
function renderModalContent() {
|
||||
const modal = document.getElementById('payment-modal');
|
||||
if (!modal) return;
|
||||
|
||||
const accountOptions = bankAccounts.map(acc =>
|
||||
`<option value="${acc.id}">${acc.name}</option>`
|
||||
).join('');
|
||||
|
||||
const methodOptions = paymentMethods
|
||||
.filter(pm => ['Check', 'ACH'].includes(pm.name) || pm.name.toLowerCase().includes('check') || pm.name.toLowerCase().includes('ach'))
|
||||
.map(pm => `<option value="${pm.id}">${pm.name}</option>`)
|
||||
.join('');
|
||||
|
||||
// Falls keine Filter-Treffer, alle anzeigen
|
||||
const allMethodOptions = paymentMethods.map(pm =>
|
||||
// Zeige Check und ACH bevorzugt, aber alle als Fallback
|
||||
const filteredMethods = paymentMethods.filter(pm =>
|
||||
pm.name.toLowerCase().includes('check') || pm.name.toLowerCase().includes('ach')
|
||||
);
|
||||
const methodsToShow = filteredMethods.length > 0 ? filteredMethods : paymentMethods;
|
||||
const methodOptions = methodsToShow.map(pm =>
|
||||
`<option value="${pm.id}">${pm.name}</option>`
|
||||
).join('');
|
||||
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
|
||||
modal.innerHTML = `
|
||||
<div class="modal-content" style="max-width: 700px;">
|
||||
<div class="bg-white rounded-lg shadow-2xl w-full max-w-2xl mx-auto p-8">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-2xl font-bold text-gray-800">💰 Record Payment</h2>
|
||||
<button onclick="window.paymentModal.close()" class="text-gray-400 hover:text-gray-600">
|
||||
@@ -149,18 +109,8 @@ function renderModal() {
|
||||
|
||||
<!-- Selected Invoices -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Invoices to pay</label>
|
||||
<div id="payment-invoice-list" class="border border-gray-200 rounded-lg max-h-48 overflow-y-auto">
|
||||
<!-- Wird dynamisch gefüllt -->
|
||||
</div>
|
||||
<div class="mt-2 flex items-center gap-2">
|
||||
<input type="number" id="payment-add-invoice-id" placeholder="Invoice ID hinzufügen..."
|
||||
class="flex-1 px-3 py-1.5 border border-gray-300 rounded-md text-sm">
|
||||
<button onclick="window.paymentModal.addById()"
|
||||
class="px-3 py-1.5 bg-blue-600 text-white rounded-md text-sm hover:bg-blue-700">
|
||||
+ Add
|
||||
</button>
|
||||
</div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Invoices</label>
|
||||
<div id="payment-invoice-list" class="border border-gray-200 rounded-lg max-h-48 overflow-y-auto"></div>
|
||||
</div>
|
||||
|
||||
<!-- Payment Details -->
|
||||
@@ -179,7 +129,7 @@ function renderModal() {
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Payment Method</label>
|
||||
<select id="payment-method"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md bg-white focus:ring-blue-500 focus:border-blue-500">
|
||||
${methodOptions || allMethodOptions}
|
||||
${methodOptions}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
@@ -244,52 +194,43 @@ function renderInvoiceList() {
|
||||
function updateTotal() {
|
||||
const totalEl = document.getElementById('payment-total');
|
||||
if (!totalEl) return;
|
||||
|
||||
const total = selectedInvoices.reduce((sum, inv) => sum + parseFloat(inv.total), 0);
|
||||
totalEl.textContent = `$${total.toFixed(2)}`;
|
||||
|
||||
// Submit-Button deaktivieren wenn keine Rechnungen
|
||||
const submitBtn = document.getElementById('payment-submit-btn');
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = selectedInvoices.length === 0;
|
||||
submitBtn.classList.toggle('opacity-50', selectedInvoices.length === 0);
|
||||
}
|
||||
}
|
||||
|
||||
function removeInvoiceFromPayment(invoiceId) {
|
||||
selectedInvoices = selectedInvoices.filter(inv => inv.id !== invoiceId);
|
||||
renderInvoiceList();
|
||||
updateTotal();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Submit Payment
|
||||
// Submit
|
||||
// ============================================================
|
||||
|
||||
async function submitPayment() {
|
||||
if (selectedInvoices.length === 0) {
|
||||
alert('Bitte mindestens eine Rechnung auswählen.');
|
||||
return;
|
||||
}
|
||||
if (selectedInvoices.length === 0) return;
|
||||
|
||||
const paymentDate = document.getElementById('payment-date').value;
|
||||
const reference = document.getElementById('payment-reference').value;
|
||||
const methodId = document.getElementById('payment-method').value;
|
||||
const depositToId = document.getElementById('payment-deposit-to').value;
|
||||
const methodSelect = document.getElementById('payment-method');
|
||||
const depositSelect = document.getElementById('payment-deposit-to');
|
||||
const methodId = methodSelect.value;
|
||||
const methodName = methodSelect.options[methodSelect.selectedIndex]?.text || '';
|
||||
const depositToId = depositSelect.value;
|
||||
const depositToName = depositSelect.options[depositSelect.selectedIndex]?.text || '';
|
||||
|
||||
if (!paymentDate) {
|
||||
alert('Bitte ein Zahlungsdatum angeben.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!methodId || !depositToId) {
|
||||
alert('Bitte Payment Method und Deposit To auswählen.');
|
||||
if (!paymentDate || !methodId || !depositToId) {
|
||||
alert('Bitte alle Felder ausfüllen.');
|
||||
return;
|
||||
}
|
||||
|
||||
const total = selectedInvoices.reduce((sum, inv) => sum + parseFloat(inv.total), 0);
|
||||
const invoiceNums = selectedInvoices.map(i => i.invoice_number || `ID:${i.id}`).join(', ');
|
||||
|
||||
if (!confirm(`Payment von $${total.toFixed(2)} für Rechnung(en) ${invoiceNums} an QBO senden?`)) {
|
||||
return;
|
||||
}
|
||||
if (!confirm(`Payment $${total.toFixed(2)} für #${invoiceNums} an QBO senden?`)) return;
|
||||
|
||||
const submitBtn = document.getElementById('payment-submit-btn');
|
||||
const origText = submitBtn.innerHTML;
|
||||
submitBtn.innerHTML = '⏳ Wird gesendet...';
|
||||
submitBtn.disabled = true;
|
||||
|
||||
@@ -302,7 +243,9 @@ async function submitPayment() {
|
||||
payment_date: paymentDate,
|
||||
reference_number: reference,
|
||||
payment_method_id: methodId,
|
||||
deposit_to_account_id: depositToId
|
||||
payment_method_name: methodName,
|
||||
deposit_to_account_id: depositToId,
|
||||
deposit_to_account_name: depositToName
|
||||
})
|
||||
});
|
||||
|
||||
@@ -311,12 +254,7 @@ async function submitPayment() {
|
||||
if (response.ok) {
|
||||
alert(`✅ ${result.message}`);
|
||||
closePaymentModal();
|
||||
// Invoice-Liste aktualisieren
|
||||
if (window.invoiceView) {
|
||||
window.invoiceView.loadInvoices();
|
||||
} else if (typeof window.loadInvoices === 'function') {
|
||||
window.loadInvoices();
|
||||
}
|
||||
if (window.invoiceView) window.invoiceView.loadInvoices();
|
||||
} else {
|
||||
alert(`❌ Fehler: ${result.error}`);
|
||||
}
|
||||
@@ -324,33 +262,18 @@ async function submitPayment() {
|
||||
console.error('Payment error:', error);
|
||||
alert('Netzwerkfehler beim Payment.');
|
||||
} finally {
|
||||
submitBtn.innerHTML = origText;
|
||||
submitBtn.innerHTML = '💰 Record Payment in QBO';
|
||||
submitBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Helper: Add by ID from input field
|
||||
// ============================================================
|
||||
|
||||
async function addInvoiceById() {
|
||||
const input = document.getElementById('payment-add-invoice-id');
|
||||
const id = parseInt(input.value);
|
||||
if (!id) return;
|
||||
|
||||
await addInvoiceToPayment(id);
|
||||
input.value = '';
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Expose to window
|
||||
// Expose
|
||||
// ============================================================
|
||||
|
||||
window.paymentModal = {
|
||||
open: openPaymentModal,
|
||||
close: closePaymentModal,
|
||||
submit: submitPayment,
|
||||
addInvoice: addInvoiceToPayment,
|
||||
removeInvoice: removeInvoiceFromPayment,
|
||||
addById: addInvoiceById
|
||||
removeInvoice: removeInvoiceFromPayment
|
||||
};
|
||||
Reference in New Issue
Block a user