calc taxes on your own
This commit is contained in:
@@ -368,11 +368,108 @@ async function getBalanceSheet({ asOfDate, accountingMethod = 'Accrual' } = {})
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getTaxSummary({ startDate, endDate, accountingMethod = 'Accrual' } = {}) {
|
async function getTaxSummary({ startDate, endDate, accountingMethod = 'Accrual' } = {}) {
|
||||||
const url = buildReportUrl('TaxSummary', { start_date: startDate, end_date: endDate, accounting_method: accountingMethod });
|
const { companyId, baseUrl } = getClientInfo();
|
||||||
const response = await makeQboApiCall({ url, method: 'GET' });
|
const qboQuery = (sql) => {
|
||||||
const data = getJson(response);
|
const url = `${baseUrl}/v3/company/${companyId}/query?query=${encodeURIComponent(sql)}&minorversion=${QBO_MINOR_VERSION}`;
|
||||||
throwIfFault(data, 'TaxSummary report');
|
return makeQboApiCall({ url, method: 'GET' });
|
||||||
return data;
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ════════════════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════════════════
|
||||||
|
|||||||
Reference in New Issue
Block a user