better states

This commit is contained in:
2026-05-01 16:52:58 -05:00
parent bce3999643
commit 950d50b354
4 changed files with 339 additions and 45 deletions

View File

@@ -69,19 +69,121 @@ function getMonthName(i) {
return ['January','February','March','April','May','June','July','August','September','October','November','December'][i];
}
function isPaid(inv) { return !!inv.paid_date; }
function isDraft(inv) { return !inv.qbo_id; }
function isOverdue(inv) { return !isPaid(inv) && !isPartiallyPaid(inv) && daysSince(inv.invoice_date) > OVERDUE_DAYS; }
// ============================================================
// Status Helpers
// ============================================================
function isPaid(inv) {
return !!inv.paid_date;
}
function isDraft(inv) {
return !inv.qbo_id;
}
function isPartiallyPaid(inv) {
const amountPaid = parseFloat(inv.amount_paid) || 0;
const balance = parseFloat(inv.balance) ?? ((parseFloat(inv.total) || 0) - amountPaid);
return !inv.paid_date && amountPaid > 0 && balance > 0;
}
function isSent(inv) {
return !!inv.qbo_id && !isPaid(inv) && !isPartiallyPaid(inv) && !isOverdue(inv) && inv.email_status === 'sent';
function getLastSentDate(inv) {
const sentDates = Array.isArray(inv.sent_dates) ? inv.sent_dates.filter(Boolean) : [];
if (sentDates.length > 0) {
return sentDates[sentDates.length - 1];
}
return null;
}
function getTermDays(inv) {
const terms = String(inv.terms || '').toLowerCase();
if (terms.includes('due on receipt') || terms.includes('upon receipt')) {
return 0;
}
const match = terms.match(/net\s*(\d+)/i);
if (match) {
return parseInt(match[1], 10);
}
// Default in deiner App ist Net 30
return 30;
}
function addDays(dateValue, days) {
const d = parseLocalDate(dateValue);
if (!d) return null;
d.setDate(d.getDate() + days);
return d;
}
function isBeforeToday(dateObj) {
if (!dateObj) return false;
const today = new Date();
today.setHours(0, 0, 0, 0);
const compare = new Date(dateObj);
compare.setHours(0, 0, 0, 0);
return compare < today;
}
function getEffectiveDueDate(inv) {
const lastSentDate = getLastSentDate(inv);
// Wichtig: Nie versendete Rechnungen können nicht overdue sein.
if (!lastSentDate) {
return null;
}
// Wenn due_date vorhanden ist, darf es genutzt werden,
// aber nur nachdem die Rechnung tatsächlich gesendet wurde.
if (inv.due_date) {
return parseLocalDate(inv.due_date);
}
return addDays(lastSentDate, getTermDays(inv));
}
function getOverdueDays(inv) {
const dueDate = getEffectiveDueDate(inv);
if (!dueDate) return 0;
const today = new Date();
today.setHours(0, 0, 0, 0);
dueDate.setHours(0, 0, 0, 0);
return Math.max(0, Math.floor((today - dueDate) / 86400000));
}
function isOverdue(inv) {
return !!inv.qbo_id
&& !isPaid(inv)
&& !isPartiallyPaid(inv)
&& !!getLastSentDate(inv)
&& isBeforeToday(getEffectiveDueDate(inv));
}
function isSent(inv) {
return !!inv.qbo_id
&& !isPaid(inv)
&& !isPartiallyPaid(inv)
&& !isOverdue(inv)
&& inv.email_status === 'sent';
}
function isOpen(inv) {
return !!inv.qbo_id && !isPaid(inv) && !isPartiallyPaid(inv) && !isOverdue(inv) && inv.email_status !== 'sent';
return !!inv.qbo_id
&& !isPaid(inv)
&& !isPartiallyPaid(inv)
&& !isOverdue(inv)
&& inv.email_status !== 'sent';
}
function saveSettings() {
@@ -235,7 +337,7 @@ function renderInvoiceRow(invoice) {
}
statusBadge += `<span class="inline-block px-2 py-0.5 text-xs font-semibold rounded-full bg-yellow-100 text-yellow-800" title="Paid: $${amountPaid.toFixed(2)} / Balance: $${balance.toFixed(2)}">Partial</span>`;
} else if (overdue) {
statusBadge = `<span class="inline-block px-2 py-0.5 text-xs font-semibold rounded-full bg-red-100 text-red-800" title="${daysSince(invoice.invoice_date)} days">Overdue</span>`;
statusBadge = `<span class="inline-block px-2 py-0.5 text-xs font-semibold rounded-full bg-red-100 text-red-800" title="${getOverdueDays(invoice)} days overdue">Overdue</span>`;
} else if (hasQbo && invoice.email_status === 'sent') {
statusBadge = `<span class="inline-block px-2 py-0.5 text-xs font-semibold rounded-full bg-cyan-200 text-cyan-800">Sent</span>`;
} else if (hasQbo) {