calc taxes on your own

This commit is contained in:
2026-05-31 11:14:12 -05:00
parent b19a167306
commit 0292e4bd93

View File

@@ -368,11 +368,108 @@ async function getBalanceSheet({ asOfDate, accountingMethod = 'Accrual' } = {})
}
async function getTaxSummary({ startDate, endDate, accountingMethod = 'Accrual' } = {}) {
const url = buildReportUrl('TaxSummary', { start_date: startDate, end_date: endDate, accounting_method: accountingMethod });
const response = await makeQboApiCall({ url, method: 'GET' });
const data = getJson(response);
throwIfFault(data, 'TaxSummary report');
return data;
const { companyId, baseUrl } = getClientInfo();
const qboQuery = (sql) => {
const url = `${baseUrl}/v3/company/${companyId}/query?query=${encodeURIComponent(sql)}&minorversion=${QBO_MINOR_VERSION}`;
return makeQboApiCall({ url, method: 'GET' });
};
const rateMap = {};
try {
const rateRes = await qboQuery('SELECT * FROM TaxRate MAXRESULTS 1000');
const rateData = getJson(rateRes);
throwIfFault(rateData, 'TaxRate query');
const rates = rateData?.QueryResponse?.TaxRate || [];
for (const r of rates) {
rateMap[r.Id] = {
name: r.Name || `Tax Rate ${r.Id}`,
rateValue: parseFloat(r.RateValue) || 0
};
}
} catch (e) {
console.error('TaxRate query failed, proceeding without rate names:', e.message);
}
const whereClause = `TxnDate >= '${startDate}' AND TxnDate <= '${endDate}'`;
const agg = {};
for (const entity of ['Invoice', 'SalesReceipt']) {
try {
const res = await qboQuery(`SELECT * FROM ${entity} WHERE ${whereClause} MAXRESULTS 1000`);
const data = getJson(res);
throwIfFault(data, `${entity} query`);
const docs = data?.QueryResponse?.[entity] || [];
for (const doc of docs) {
const taxDetail = doc.TxnTaxDetail;
if (!taxDetail || !taxDetail.TaxLine) continue;
const taxLines = Array.isArray(taxDetail.TaxLine) ? taxDetail.TaxLine : [taxDetail.TaxLine];
for (const line of taxLines) {
const detail = line.TaxLineDetail;
if (!detail || !detail.TaxRateRef) continue;
const rateId = String(detail.TaxRateRef.value);
const taxable = parseFloat(detail.NetAmountTaxable) || 0;
const collected = parseFloat(line.Amount) || 0;
if (!agg[rateId]) agg[rateId] = { taxableSales: 0, taxCollected: 0 };
agg[rateId].taxableSales += taxable;
agg[rateId].taxCollected += collected;
}
}
} catch (e) {
console.error(`${entity} query failed:`, e.message);
}
}
const rows = Object.entries(agg).map(([rateId, amounts]) => {
const info = rateMap[rateId] || { name: `Tax Rate ${rateId}`, rateValue: 0 };
const ratePct = info.rateValue.toFixed(3).replace(/0+$/, '').replace(/\.$/, '');
return {
type: 'Section',
Header: {
ColData: [
{ value: info.name },
{ value: '' },
{ value: '' },
{ value: `${ratePct}%` }
]
},
Summary: {
ColData: [
{ value: 'Total' },
{ value: amounts.taxableSales.toFixed(2) },
{ value: amounts.taxCollected.toFixed(2) },
{ value: '' }
]
}
};
});
rows.sort((a, b) => {
const na = a.Header.ColData[0].value;
const nb = b.Header.ColData[0].value;
if (na.includes('State') && !nb.includes('State')) return -1;
if (!na.includes('State') && nb.includes('State')) return 1;
return na.localeCompare(nb);
});
return {
Header: {
ReportName: 'Sales Tax Liability',
StartPeriod: startDate,
EndPeriod: endDate,
ReportBasis: accountingMethod
},
Columns: {
Column: [
{ ColTitle: '', ColType: 'Text' },
{ ColTitle: 'Taxable Sales', ColType: 'Money' },
{ ColTitle: 'Tax Collected', ColType: 'Money' },
{ ColTitle: 'Tax Rate', ColType: 'Percent' }
]
},
Rows: {
Row: rows
}
};
}
// ════════════════════════════════════════════════════════════════════