sales tax 2.
This commit is contained in:
@@ -762,22 +762,28 @@ function renderTaxPeriodsTable() {
|
||||
}
|
||||
|
||||
const rows = stPeriods.map(p => {
|
||||
const status = p.qbo_journal_entry_id
|
||||
? `<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-semibold bg-green-100 text-green-800">Paid</span>`
|
||||
: `<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-semibold bg-yellow-100 text-yellow-800">Open</span>`;
|
||||
const pStatus = p.status || (p.qbo_journal_entry_id ? 'booked' : 'open');
|
||||
let statusHtml, statusColor;
|
||||
if (pStatus === 'booked') { statusHtml = 'Booked'; statusColor = 'bg-green-100 text-green-800'; }
|
||||
else if (pStatus === 'external') { statusHtml = 'External'; statusColor = 'bg-blue-100 text-blue-800'; }
|
||||
else { statusHtml = 'Open'; statusColor = 'bg-yellow-100 text-yellow-800'; }
|
||||
|
||||
const monthLabel = new Date(p.period_start).toLocaleDateString('en-US', { year: 'numeric', month: 'long' });
|
||||
const adj = parseFloat(p.adjustment_amount) || 0;
|
||||
const adjStr = adj !== 0 ? (adj > 0 ? `−$${adj.toFixed(2)}` : `+$${Math.abs(adj).toFixed(2)}`) : '—';
|
||||
const netPaid = parseFloat(p.net_paid) || parseFloat(p.tax_collected) || 0;
|
||||
const paidOn = pStatus === 'external' ? 'Paid in QBO' : (p.booked_at ? formatDate(p.booked_at) : '—');
|
||||
|
||||
return `
|
||||
<tr class="border-t hover:bg-gray-50">
|
||||
<td class="px-3 py-2 text-sm font-medium text-gray-800">${monthLabel}</td>
|
||||
<td class="px-3 py-2 text-sm">${status}</td>
|
||||
<td class="px-3 py-2 text-sm">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-semibold ${statusColor}">${statusHtml}</span>
|
||||
</td>
|
||||
<td class="px-3 py-2 text-sm text-right">${fmtMoney(parseFloat(p.tax_collected) || 0)}</td>
|
||||
<td class="px-3 py-2 text-sm text-right">${adjStr}</td>
|
||||
<td class="px-3 py-2 text-sm text-right font-semibold">${fmtMoney(netPaid)}</td>
|
||||
<td class="px-3 py-2 text-sm text-gray-500">${p.booked_at ? formatDate(p.booked_at) : '—'}</td>
|
||||
<td class="px-3 py-2 text-sm text-gray-500">${paidOn}</td>
|
||||
<td class="px-3 py-2 text-sm text-center">
|
||||
<button onclick="window.accountingView.openTaxPeriod(${p.id})"
|
||||
class="text-blue-600 hover:text-blue-800 text-xs font-medium">View summary</button>
|
||||
@@ -851,7 +857,9 @@ async function openTaxPeriodDetail(startDate, endDate, existingPeriod) {
|
||||
const adjustments = existingPeriod?.adjustment_amount != null ? parseFloat(existingPeriod.adjustment_amount) || 0 : 0;
|
||||
const adjReason = existingPeriod?.adjustment_reason || '';
|
||||
const netPaid = taxCollected - adjustments;
|
||||
const isPaid = existingPeriod && existingPeriod.qbo_journal_entry_id;
|
||||
const periodStatus = existingPeriod?.status || (existingPeriod?.qbo_journal_entry_id ? 'booked' : 'open');
|
||||
const isOpen = periodStatus === 'open';
|
||||
const isEditable = isOpen;
|
||||
|
||||
const bankOpts = stAccounts
|
||||
.filter(a => a.accountType === 'Bank' || a.accountType === 'Credit Card')
|
||||
@@ -891,19 +899,19 @@ async function openTaxPeriodDetail(startDate, endDate, existingPeriod) {
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-3 mb-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Adjustment Amount</label>
|
||||
<input type="number" step="0.01" id="st-adjustment" value="${adjustments !== 0 ? adjustments : ''}" placeholder="e.g. 6.15 (discount)" ${isPaid ? 'disabled' : ''}
|
||||
<input type="number" step="0.01" id="st-adjustment" value="${adjustments !== 0 ? adjustments : ''}" placeholder="e.g. 6.15 (discount)" ${isEditable ? '' : 'disabled'}
|
||||
oninput="window.accountingView.updateTaxPreview()"
|
||||
class="w-full px-3 py-1.5 border border-gray-300 rounded-md text-sm">
|
||||
<p class="text-xs text-gray-400 mt-0.5">Positive = discount (reduces net due)</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Reason</label>
|
||||
<input type="text" id="st-adjustment-reason" value="${escapeHtml(adjReason)}" placeholder="e.g. Timely filing discount" ${isPaid ? 'disabled' : ''}
|
||||
<input type="text" id="st-adjustment-reason" value="${escapeHtml(adjReason)}" placeholder="e.g. Timely filing discount" ${isEditable ? '' : 'disabled'}
|
||||
class="w-full px-3 py-1.5 border border-gray-300 rounded-md text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Adjustment Account</label>
|
||||
<select id="st-adjustment-account" class="w-full px-3 py-1.5 border border-gray-300 rounded-md text-sm" ${isPaid ? 'disabled' : ''}>
|
||||
<select id="st-adjustment-account" class="w-full px-3 py-1.5 border border-gray-300 rounded-md text-sm" ${isEditable ? '' : 'disabled'}>
|
||||
<option value="">— Select income account —</option>
|
||||
${adjustOpts}
|
||||
</select>
|
||||
@@ -913,33 +921,32 @@ async function openTaxPeriodDetail(startDate, endDate, existingPeriod) {
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-3 mb-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Bank Account (payment source)</label>
|
||||
<select id="st-bank-account" class="w-full px-3 py-1.5 border border-gray-300 rounded-md text-sm" ${isPaid ? 'disabled' : ''}>
|
||||
<select id="st-bank-account" class="w-full px-3 py-1.5 border border-gray-300 rounded-md text-sm" ${isEditable ? '' : 'disabled'}>
|
||||
<option value="">— Select bank account —</option>
|
||||
${bankOpts}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Sales Tax Payable Account</label>
|
||||
<select id="st-payable-account" class="w-full px-3 py-1.5 border border-gray-300 rounded-md text-sm" ${isPaid ? 'disabled' : ''}>
|
||||
<select id="st-payable-account" class="w-full px-3 py-1.5 border border-gray-300 rounded-md text-sm" ${isEditable ? '' : 'disabled'}>
|
||||
<option value="">— Select liability account —</option>
|
||||
${liabilityOpts}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Payment Date</label>
|
||||
<input type="date" id="st-payment-date" value="${today}" ${isPaid ? 'disabled' : ''}
|
||||
<input type="date" id="st-payment-date" value="${today}" ${isEditable ? '' : 'disabled'}
|
||||
class="w-full px-3 py-1.5 border border-gray-300 rounded-md text-sm">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${!isPaid ? `
|
||||
${isEditable ? `
|
||||
<div class="flex items-center justify-between border-t pt-3 mt-1">
|
||||
<div id="st-je-lines">
|
||||
<p class="text-sm"><strong>Journal Entry lines:</strong></p>
|
||||
<p class="text-xs text-gray-500 font-mono">Debit Sales Tax Payable: ${fmtMoney(taxCollected)}</p>
|
||||
${adjustments > 0 ? `<p class="text-xs text-gray-500 font-mono">Credit Discount: ${fmtMoney(adjustments)}</p>` : ''}
|
||||
${adjustments < 0 ? `<p class="text-xs text-gray-500 font-mono">Debit Penalty: ${fmtMoney(Math.abs(adjustments))}</p>` : ''}
|
||||
${adjustments === 0 ? `` : ''}
|
||||
<p class="text-xs text-gray-500 font-mono" id="st-je-bank">Credit Bank: ${fmtMoney(netPaid)}</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
@@ -947,15 +954,23 @@ async function openTaxPeriodDetail(startDate, endDate, existingPeriod) {
|
||||
class="px-3 py-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-md text-sm font-medium border border-gray-300">
|
||||
💾 Save Draft
|
||||
</button>
|
||||
<button onclick="window.accountingView.markTaxPaidExternal()"
|
||||
class="px-3 py-1.5 bg-blue-100 hover:bg-blue-200 text-blue-800 rounded-md text-sm font-medium border border-blue-300">
|
||||
📋 Mark as already paid (external)
|
||||
</button>
|
||||
<button onclick="window.accountingView.recordTaxPayment()"
|
||||
class="px-4 py-2 bg-green-600 text-white rounded-md text-sm font-semibold hover:bg-green-700">
|
||||
✅ Record Payment in QBO
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
` : periodStatus === 'booked' ? `
|
||||
<div class="border-t pt-3 mt-1">
|
||||
<p class="text-sm text-green-700 font-semibold">✅ Booked — Journal Entry #${escapeHtml(existingPeriod.qbo_journal_entry_id)} on ${formatDate(existingPeriod.booked_at)}</p>
|
||||
</div>
|
||||
` : `
|
||||
<div class="border-t pt-3 mt-1">
|
||||
<p class="text-sm text-green-700 font-semibold">✅ Paid — Journal Entry #${escapeHtml(existingPeriod.qbo_journal_entry_id)} on ${formatDate(existingPeriod.booked_at)}</p>
|
||||
<p class="text-sm text-blue-700 font-semibold">📋 Marked as paid externally on ${formatDate(existingPeriod.booked_at)}</p>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
@@ -1138,6 +1153,62 @@ export async function recordTaxPayment() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function markTaxPaidExternal() {
|
||||
if (!stEditingPeriodId) return alert('Please save the period first (Save Draft).');
|
||||
|
||||
const adjAmount = parseFloat(document.getElementById('st-adjustment').value) || 0;
|
||||
const adjReason = document.getElementById('st-adjustment-reason').value.trim();
|
||||
const adjAccountEl = document.getElementById('st-adjustment-account');
|
||||
const adjAccountId = adjAccountEl.value;
|
||||
const adjAccountName = adjAccountId ? adjAccountEl.options[adjAccountEl.selectedIndex]?.text : '';
|
||||
|
||||
const bankEl = document.getElementById('st-bank-account');
|
||||
const bankId = bankEl.value;
|
||||
const bankName = bankId ? bankEl.options[bankEl.selectedIndex]?.text : '';
|
||||
|
||||
const payableEl = document.getElementById('st-payable-account');
|
||||
const payableId = payableEl.value;
|
||||
const payableName = payableId ? payableEl.options[payableEl.selectedIndex]?.text : '';
|
||||
|
||||
if (!confirm(`Mark this period as already paid in QBO (external)?\n\nNo Journal Entry will be created — this only records the status in the app.\n\nTax Collected: $${stCurrentTaxData.taxCollected.toFixed(2)}\nAdjustment: $${adjAmount.toFixed(2)}\nNet Due: $${(stCurrentTaxData.taxCollected - adjAmount).toFixed(2)}`)) return;
|
||||
|
||||
if (!stCurrentTaxData) return alert('No tax data loaded.');
|
||||
|
||||
try {
|
||||
await window.API.accounting.upsertTaxPeriod({
|
||||
period_start: stCurrentTaxData.startDate,
|
||||
period_end: stCurrentTaxData.endDate,
|
||||
total_sales: stCurrentTaxData.totalSales,
|
||||
nontaxable_sales: stCurrentTaxData.nontaxableSales,
|
||||
taxable_sales: stCurrentTaxData.taxableSales,
|
||||
tax_collected: stCurrentTaxData.taxCollected,
|
||||
adjustment_amount: adjAmount,
|
||||
adjustment_reason: adjReason || null,
|
||||
adjustment_account_id: adjAccountId || null,
|
||||
adjustment_account_name: adjAccountName || null,
|
||||
net_paid: stCurrentTaxData.taxCollected - adjAmount,
|
||||
bank_account_id: bankId || null,
|
||||
bank_account_name: bankName || null,
|
||||
sales_tax_payable_id: payableId || null,
|
||||
sales_tax_payable_name: payableName || null,
|
||||
status: 'open'
|
||||
});
|
||||
} catch (e) {
|
||||
return alert('Failed to save period: ' + e.message);
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await window.API.accounting.markTaxPaidExternal(stEditingPeriodId);
|
||||
await loadTaxPeriods();
|
||||
const updated = stPeriods.find(p => p.id === stEditingPeriodId);
|
||||
if (updated) {
|
||||
await openTaxPeriodDetail(updated.period_start, updated.period_end, updated);
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Failed: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function parseTaxDataFromReport(taxData) {
|
||||
if (!taxData?.Rows?.Row) return { totalSales: 0, nontaxableSales: 0, taxableSales: 0, taxCollected: 0 };
|
||||
const rows = Array.isArray(taxData.Rows.Row) ? taxData.Rows.Row : [];
|
||||
@@ -1228,5 +1299,6 @@ window.accountingView = {
|
||||
closeTaxPeriodDetail,
|
||||
updateTaxPreview,
|
||||
saveTaxPeriodDraft,
|
||||
recordTaxPayment
|
||||
recordTaxPayment,
|
||||
markTaxPaidExternal
|
||||
};
|
||||
Reference in New Issue
Block a user