@@ -35,7 +35,6 @@ let crEndDate = null;
let stPeriods = [ ] ;
let stEditingPeriodId = null ; // current period in detail dialog, null = new
let stAccounts = [ ] ; // cached account list for pickers
let expStartDate = null ;
let expEndDate = null ;
@@ -879,14 +878,15 @@ function renderTaxPeriodsTable() {
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 if ( pStatus === 'external' || pStatus === 'paid' ) { statusHtml = 'Paid ' ; statusColor = 'bg-green -100 text-green -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' ? 'P aid in QBO' : ( p . booked _at ? formatDate ( p . booked _at ) : '— ' ) ;
const paidOn = ( pStatus === 'external' || pStatus === 'p aid' || pStatus === 'booked ' )
? ( p . booked _at ? formatDate ( p . booked _at ) : '—' ) : '—' ;
return `
<tr class="border-t hover:bg-gray-50">
@@ -953,13 +953,6 @@ async function openTaxPeriodDetail(startDate, endDate, existingPeriod) {
return ;
}
if ( ! stAccounts . length ) {
try {
const accts = await window . API . accounting . getAccounts ( ) || [ ] ;
stAccounts = Array . isArray ( accts ) ? accts : [ ] ;
} catch ( e ) { stAccounts = [ ] ; }
}
const reportHtml = taxData ? . Rows ? renderQboReport ( taxData ) : ` <p class="text-sm text-gray-500">No tax data for this period.</p> ` ;
const parsed = parseTaxDataFromReport ( taxData ) ;
@@ -975,21 +968,6 @@ async function openTaxPeriodDetail(startDate, endDate, existingPeriod) {
const isOpen = periodStatus === 'open' ;
const isEditable = isOpen ;
const bankOpts = stAccounts
. filter ( a => a . accountType === 'Bank' || a . accountType === 'Credit Card' )
. map ( a => ` <option value=" ${ a . id } " ${ a . id === existingPeriod ? . bank _account _id ? 'selected' : '' } > ${ escapeHtml ( a . name ) } ( ${ a . accountType } )</option> ` ) . join ( '' ) ;
const liabilityOpts = stAccounts
. filter ( a => a . accountType === 'Other Current Liability' )
. map ( a => ` <option value=" ${ a . id } " ${ a . id === existingPeriod ? . sales _tax _payable _id ? 'selected' : '' } > ${ escapeHtml ( a . name ) } </option> ` ) . join ( '' ) ;
const adjustOpts = stAccounts
. filter ( a => a . accountType === 'Income' || a . accountType === 'Expense' || a . classification === 'Revenue' || a . classification === 'Expense' )
. map ( a => {
const sel = a . id === existingPeriod ? . adjustment _account _id ? 'selected' : '' ;
const name = ( a . fullyQualifiedName || a . name || '' ) ;
const isDiscount = name . toLowerCase ( ) . includes ( 'discount' ) ;
return ` <option value=" ${ a . id } " ${ sel } data-hint=" ${ isDiscount ? 'discount' : '' } "> ${ escapeHtml ( name ) } ${ isDiscount ? ' ★' : '' } </option> ` ;
} ) . join ( '' ) ;
const today = todayISO ( ) ;
const [ y , m ] = startDate . split ( '-' ) . map ( Number ) ;
const monthLabel = new Date ( y , m - 1 ) . toLocaleDateString ( 'en-US' , { year : 'numeric' , month : 'long' } ) ;
@@ -1010,71 +988,43 @@ async function openTaxPeriodDetail(startDate, endDate, existingPeriod) {
<span id="st-net-due" class="text-lg font-bold text-gray-900"><strong>Net Due:</strong> ${ fmtMoney ( netPaid ) } </span>
</div>
${ isEditable ? `
<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)" ${ isEditable ? '' : 'disabled' }
<input type="number" step="0.01" id="st-adjustment" value=" ${ adjustments !== 0 ? adjustments : '' } " placeholder="e.g. 6.15 (discount)"
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" ${ isEditable ? '' : 'disabled' }
<input type="text" id="st-adjustment-reason" value=" ${ escapeHtml ( adjReason ) } " placeholder="e.g. Timely filing discount"
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" ${ isEditable ? '' : 'disabled' } >
<option value="">— Select income account —</option>
${ adjustOpts }
</select>
</div>
</div>
<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" ${ 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" ${ 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 } " ${ isEditable ? '' : 'disabled' }
<label class="block text-xs font-medium text-gray-700 mb-1">Paid Date (QBO) </label>
<input type="date" id="st-paid-date" value=" ${ today } "
class="w-full px-3 py-1.5 border border-gray-300 rounded-md text-sm">
</div>
</div>
${ 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> ` : '' }
<p class="text-xs text-gray-500 font-mono" id="st-je-bank">Credit Bank : ${ fmtMoney ( netPaid ) } </p>
<p class="text-sm"><strong>Summary :</strong></p>
<p class="text-xs text-gray-500">Tax Collected : ${ fmtMoney ( taxCollected ) } </p>
${ adjustments > 0 ? ` <p class="text-xs text-gray-500">Discount: \u 2212 ${ fmtMoney ( adjustments ) } </p> ` : '' }
${ adjustments < 0 ? ` <p class="text-xs text-gray-500"> Penalty: + $ ${ Math . abs ( adjustments ) . toFixed ( 2 )} </p> ` : '' }
<p class="text-xs text-gray-500 font-semibold">Net Due : ${ fmtMoney ( netPaid ) } </p>
</div>
<div class="flex gap-2">
<button onclick="window.accountingView.saveTaxPeriodDraft()"
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()"
<button onclick="window.accountingView.markPeriodPaid ()"
class="px-4 py-2 bg-green-600 text-white rounded-md text-sm font-semibold hover:bg-green-700">
✅ Record Payment in QBO
✅ Mark as Paid
</button>
</div>
</div>
@@ -1084,7 +1034,7 @@ async function openTaxPeriodDetail(startDate, endDate, existingPeriod) {
</div>
` : `
<div class="border-t pt-3 mt-1">
<p class="text-sm text-blue -700 font-semibold">📋 Marked as paid externally on ${ formatDate ( existingPeriod . booked _at ) } </p>
<p class="text-sm text-green -700 font-semibold">✅ Paid on ${ formatDate ( existingPeriod . booked _at ) } </p>
</div>
` }
</div>
@@ -1125,28 +1075,17 @@ export function updateTaxPreview() {
const jeLines = document . getElementById ( 'st-je-lines' ) ;
if ( jeLines ) {
jeLines . innerHTML = `
<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>
${ adj > 0 ? ` <p class="text-xs text-gray-500 font-mono">Credit Discount: ${ fmtMoney ( adj ) } </p> ` : '' }
${ adj < 0 ? ` <p class="text-xs text-gray-500 font-mono">Debit Penalty: ${ fmtMoney ( Math . abs ( adj ) ) } </p> ` : '' }
<p class="text-xs text-gray-500 font-mono">Credit Bank : ${ fmtMoney ( netPaid ) } </p> ` ;
<p class="text-sm"><strong>Summary :</strong></p>
<p class="text-xs text-gray-500">Tax Collected : ${ fmtMoney ( taxCollected ) } </p>
${ adj > 0 ? ` <p class="text-xs text-gray-500">Discount: \u 2212 ${ fmtMoney ( adj ) } </p> ` : '' }
${ adj < 0 ? ` <p class="text-xs text-gray-500"> Penalty: + $ ${ Math . abs ( adj ) . toFixed ( 2 )} </p> ` : '' }
<p class="text-xs text-gray-500 font-semibold">Net Due : ${ fmtMoney ( netPaid ) } </p> ` ;
}
}
export async function saveTaxPeriodDraft ( ) {
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 ( ! stCurrentTaxData ) return alert ( 'No tax data loaded.' ) ;
@@ -1160,13 +1099,8 @@ export async function saveTaxPeriodDraft() {
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'
} ) ;
await loadTaxPeriods ( ) ;
if ( period ? . period _start ) {
@@ -1177,116 +1111,18 @@ export async function saveTaxPeriodDraft() {
}
}
export async function recordTaxPayment ( ) {
export async function markPeriodPaid ( ) {
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 : '' ;
const txnDate = document . getElementById ( 'st-payment-date' ) . value ;
if ( ! txnDate ) return alert ( 'Please select a payment date.' ) ;
const paidDate = document . getElementById ( 'st-paid-date' ) . value ;
if ( ! paidDate ) return alert ( 'Please select a paid date.' ) ;
if ( ! stCurrentTaxData ) return alert ( 'No tax data loaded.' ) ;
const taxCollected = stCurrentTaxData . taxCollected ;
const netPaid = taxCollected - adjAmount ;
if ( ! payableId ) return alert ( 'Please select the Sales Tax Payable account.' ) ;
if ( ! bankId ) return alert ( 'Please select a bank account.' ) ;
const preview = ` Journal Entry on ${ txnDate } : \n \n ` +
` Debit Sales Tax Payable: $ ${ taxCollected . toFixed ( 2 ) } \n ` +
( adjAmount > 0 ? ` Credit Discount: $ ${ adjAmount . toFixed ( 2 ) } ( ${ adjReason || 'Adjustment' } ) \n ` : '' ) +
( adjAmount < 0 ? ` Debit Penalty: $ ${ Math . abs ( adjAmount ) . toFixed ( 2 ) } ( ${ adjReason || 'Adjustment' } ) \n ` : '' ) +
` Credit Bank: $ ${ netPaid . toFixed ( 2 ) } \n \n ` +
( adjAmount > 0 ? ` Debits ( ${ taxCollected . toFixed ( 2 ) } ) = Credits ( ${ adjAmount . toFixed ( 2 ) } + ${ netPaid . toFixed ( 2 ) } = ${ ( adjAmount + netPaid ) . toFixed ( 2 ) } ) ` :
adjAmount < 0 ? ` Debits ( ${ taxCollected . toFixed ( 2 ) } + ${ Math . abs ( adjAmount ) . toFixed ( 2 ) } = ${ ( taxCollected + Math . abs ( adjAmount ) ) . toFixed ( 2 ) } ) = Credits ( ${ netPaid . toFixed ( 2 ) } ) ` :
` Debits = Credits = ${ taxCollected . toFixed ( 2 ) } ` ) +
'\n\nRecord this in QBO?' ;
if ( ! confirm ( preview ) ) return ;
let periodId = stEditingPeriodId ;
try {
const period = 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 : netPaid ,
bank _account _id : bankId || null ,
bank _account _name : bankName || null ,
sales _tax _payable _id : payableId || null ,
sales _tax _payable _name : payableName || null
} ) ;
periodId = period . id ;
} catch ( e ) {
return alert ( 'Failed to save period before payment: ' + e . message ) ;
}
if ( typeof showSpinner === 'function' ) showSpinner ( 'Recording tax payment in QBO…' ) ;
try {
const result = await window . API . accounting . recordTaxPayment ( periodId , {
txnDate ,
taxCollected ,
adjustmentAmount : adjAmount ,
netPaid ,
salesTaxPayableId : payableId ,
salesTaxPayableName : payableName ,
adjustmentAccountId : adjAccountId || null ,
adjustmentAccountName : adjAccountName || null ,
adjustmentReason : adjReason || null ,
bankAccountId : bankId ,
bankAccountName : bankName
} ) ;
if ( result . error ) return alert ( 'Failed: ' + result . error ) ;
await loadTaxPeriods ( ) ;
const updated = stPeriods . find ( p => p . id === periodId ) ;
if ( updated ) {
await openTaxPeriodDetail ( updated . period _start , updated . period _end , updated ) ;
}
} catch ( e ) {
alert ( 'QBO payment failed: ' + e . message ) ;
} finally {
if ( typeof hideSpinner === 'function' ) hideSpinner ( ) ;
}
}
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 netPaid = stCurrentTaxData . taxCollected - adjAmount ;
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 \n No Journal Entry will be created — this only records the status in the app. \n \n Tax Collected: $ ${ stCurrentTaxData . taxCollected . toFixed ( 2 ) } \n Adjustment: $ ${ adjAmount . toFixed ( 2 ) } \n Net Due: $ ${ ( stCurrentTaxData . taxCollected - adjAmount ) . toFixed ( 2 ) } ` ) ) return ;
if ( ! stCurrentTaxData ) return alert ( 'No tax data loaded.' ) ;
if ( ! confirm ( ` Mark this period as paid? \n \n Paid Date: ${ paidDate } \n Tax Collected: $ ${ stCurrentTaxData . taxCollected . toFixed ( 2 ) } \n Adjustment: $ ${ adjAmount . toFixed ( 2 ) } \n Net Paid: $ ${ netPaid . toFixed ( 2 ) } \n \n This does NOT write to QBO — record the payment in QBO first. ` ) ) return ;
try {
await window . API . accounting . upsertTaxPeriod ( {
@@ -1298,13 +1134,7 @@ export async function markTaxPaidExternal() {
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 ,
net _paid : netPaid ,
status : 'open'
} ) ;
} catch ( e ) {
@@ -1312,7 +1142,7 @@ export async function markTaxPaidExternal() {
}
try {
const result = await window . API . accounting . markTaxPaidExternal ( stEditingPeriodId ) ;
await window . API . accounting . markTaxPaidExternal ( stEditingPeriodId , paidDate );
await loadTaxPeriods ( ) ;
const updated = stPeriods . find ( p => p . id === stEditingPeriodId ) ;
if ( updated ) {
@@ -1413,8 +1243,7 @@ window.accountingView = {
closeTaxPeriodDetail ,
updateTaxPreview ,
saveTaxPeriodDraft ,
recordTaxPayment ,
markTaxPaidExternal ,
markPeriodPaid ,
loadCustomerRevenue ,
exportCustomerRevenuePdf ,
exportProfitLossPdf ,