This commit is contained in:
2026-02-19 22:02:22 -06:00
parent 49aeff8cb6
commit 410faee6d1
4 changed files with 142 additions and 149 deletions

View File

@@ -1,11 +1,10 @@
// payment-modal.js — ES Module v3
// Invoice payments only: multi-invoice, partial, editable amounts
// Downpayment is handled separately in customer view
// payment-modal.js — ES Module v3 (clean)
// Invoice payments: multi-invoice, partial, overpay
// No downpayment functionality
let bankAccounts = [];
let paymentMethods = [];
let selectedInvoices = []; // { invoice, payAmount }
let customerCredit = 0; // Unapplied credit from QBO
let dataLoaded = false;
// ============================================================
@@ -32,7 +31,6 @@ async function loadQboData() {
export async function openPaymentModal(invoiceIds = []) {
await loadQboData();
selectedInvoices = [];
customerCredit = 0;
for (const id of invoiceIds) {
try {
@@ -47,18 +45,6 @@ export async function openPaymentModal(invoiceIds = []) {
} catch (e) { console.error('Error loading invoice:', id, e); }
}
// Check customer credit if we have invoices
if (selectedInvoices.length > 0) {
const custQboId = selectedInvoices[0].invoice.customer_qbo_id;
if (custQboId) {
try {
const res = await fetch(`/api/qbo/customer-credit/${custQboId}`);
const data = await res.json();
customerCredit = data.credit || 0;
} catch (e) { /* ignore */ }
}
}
ensureModalElement();
renderModalContent();
document.getElementById('payment-modal').classList.add('active');
@@ -68,7 +54,6 @@ export function closePaymentModal() {
const modal = document.getElementById('payment-modal');
if (modal) modal.classList.remove('active');
selectedInvoices = [];
customerCredit = 0;
}
// ============================================================
@@ -146,22 +131,10 @@ function renderModalContent() {
const accountOptions = bankAccounts.map(a => `<option value="${a.id}">${a.name}</option>`).join('');
const filtered = paymentMethods.filter(p => /check|ach/i.test(p.name));
const methods = (filtered.length > 0 ? filtered : paymentMethods);
const methods = filtered.length > 0 ? filtered : paymentMethods;
const methodOptions = methods.map(p => `<option value="${p.id}">${p.name}</option>`).join('');
const today = new Date().toISOString().split('T')[0];
// Credit banner
let creditBanner = '';
if (customerCredit > 0) {
creditBanner = `
<div class="bg-green-50 border border-green-200 rounded-lg p-3 mb-4">
<p class="text-sm text-green-800">
💰 <strong>Customer has $${customerCredit.toFixed(2)} unapplied credit.</strong>
This can be applied in QBO when processing the payment.
</p>
</div>`;
}
modal.innerHTML = `
<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-4">
@@ -173,8 +146,6 @@ function renderModalContent() {
</button>
</div>
${creditBanner}
<!-- Invoice List -->
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">Invoices</label>
@@ -285,13 +256,11 @@ function updateTotal() {
const payTotal = selectedInvoices.reduce((s, si) => s + si.payAmount, 0);
const invTotal = selectedInvoices.reduce((s, si) => s + parseFloat(si.invoice.total), 0);
totalEl.textContent = `$${payTotal.toFixed(2)}`;
if (noteEl) {
if (payTotal > invTotal && invTotal > 0) {
const overpay = payTotal - invTotal;
noteEl.textContent = `⚠️ Overpayment of $${overpay.toFixed(2)} will be stored as customer credit in QBO.`;
noteEl.textContent = `⚠️ Overpayment of $${(payTotal - invTotal).toFixed(2)} will be stored as customer credit in QBO.`;
noteEl.classList.remove('hidden');
} else {
noteEl.classList.add('hidden');
@@ -336,7 +305,6 @@ async function submitPayment() {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
mode: 'invoice',
invoice_payments: selectedInvoices.map(si => ({
invoice_id: si.invoice.id,
amount: si.payAmount