sales tax
This commit is contained in:
@@ -139,6 +139,10 @@ const API = {
|
|||||||
const params = new URLSearchParams({ asOfDate, accountingMethod });
|
const params = new URLSearchParams({ asOfDate, accountingMethod });
|
||||||
return fetch('/api/accounting/reports/balance-sheet?' + params.toString()).then(r => r.json());
|
return fetch('/api/accounting/reports/balance-sheet?' + params.toString()).then(r => r.json());
|
||||||
},
|
},
|
||||||
|
getTaxSummary: (startDate, endDate, accountingMethod = 'Accrual') => {
|
||||||
|
const params = new URLSearchParams({ startDate, endDate, accountingMethod });
|
||||||
|
return fetch('/api/accounting/reports/tax-summary?' + params.toString()).then(r => r.json());
|
||||||
|
},
|
||||||
|
|
||||||
// Phase 2 Lieferung 1 — Sync + Cache-Reads
|
// Phase 2 Lieferung 1 — Sync + Cache-Reads
|
||||||
syncAccounts: () => fetch('/api/accounting/sync-accounts', { method: 'POST' }).then(r => r.json()),
|
syncAccounts: () => fetch('/api/accounting/sync-accounts', { method: 'POST' }).then(r => r.json()),
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ let plAccountingMethod = 'Accrual';
|
|||||||
let bsAsOfDate = null;
|
let bsAsOfDate = null;
|
||||||
let bsAccountingMethod = 'Accrual';
|
let bsAccountingMethod = 'Accrual';
|
||||||
|
|
||||||
|
let tsMonth = null; // 'YYYY-MM' — selected month for tax summary
|
||||||
|
let tsAccountingMethod = 'Accrual';
|
||||||
|
|
||||||
let expStartDate = null;
|
let expStartDate = null;
|
||||||
let expEndDate = null;
|
let expEndDate = null;
|
||||||
let expOnlyMine = false;
|
let expOnlyMine = false;
|
||||||
@@ -47,9 +50,19 @@ function fmtMoney(n) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function todayISO() { return new Date().toISOString().split('T')[0]; }
|
function todayISO() { return new Date().toISOString().split('T')[0]; }
|
||||||
function firstOfMonthISO() {
|
function firstOfMonthISO(year, month) {
|
||||||
|
const d = year != null ? new Date(year, month, 1) : new Date();
|
||||||
|
return year != null ? d.toISOString().split('T')[0] : new Date(d.getFullYear(), d.getMonth(), 1).toISOString().split('T')[0];
|
||||||
|
}
|
||||||
|
function lastOfMonthISO(year, month) {
|
||||||
|
const d = year != null ? new Date(year, month + 1, 0) : new Date();
|
||||||
|
return year != null ? d.toISOString().split('T')[0] : new Date(d.getFullYear(), d.getMonth() + 1, 0).toISOString().split('T')[0];
|
||||||
|
}
|
||||||
|
function prevMonthISO() {
|
||||||
const d = new Date();
|
const d = new Date();
|
||||||
return new Date(d.getFullYear(), d.getMonth(), 1).toISOString().split('T')[0];
|
const m = d.getMonth() === 0 ? 11 : d.getMonth() - 1;
|
||||||
|
const y = d.getMonth() === 0 ? d.getFullYear() - 1 : d.getFullYear();
|
||||||
|
return `${y}-${String(m + 1).padStart(2, '0')}`;
|
||||||
}
|
}
|
||||||
function firstOfYearISO() {
|
function firstOfYearISO() {
|
||||||
const d = new Date();
|
const d = new Date();
|
||||||
@@ -416,6 +429,7 @@ export function injectReportsControls() {
|
|||||||
if (!plStartDate) plStartDate = firstOfYearISO();
|
if (!plStartDate) plStartDate = firstOfYearISO();
|
||||||
if (!plEndDate) plEndDate = todayISO();
|
if (!plEndDate) plEndDate = todayISO();
|
||||||
if (!bsAsOfDate) bsAsOfDate = todayISO();
|
if (!bsAsOfDate) bsAsOfDate = todayISO();
|
||||||
|
if (!tsMonth) tsMonth = prevMonthISO();
|
||||||
|
|
||||||
c.innerHTML = `
|
c.innerHTML = `
|
||||||
${makeCollapsible('Reports', 'reports-section-body')}
|
${makeCollapsible('Reports', 'reports-section-body')}
|
||||||
@@ -455,6 +469,22 @@ export function injectReportsControls() {
|
|||||||
<div id="bs-result"></div>
|
<div id="bs-result"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200">
|
||||||
|
<div class="px-4 py-3 border-b bg-gray-50"><h3 class="font-semibold text-gray-800">Sales Tax (QBO)</h3></div>
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="flex flex-wrap items-end gap-3 mb-3">
|
||||||
|
<div><label class="block text-xs font-medium text-gray-700 mb-1">Month</label>
|
||||||
|
<input type="month" id="ts-month" value="${tsMonth}" class="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">Method</label>
|
||||||
|
<select id="ts-method" class="px-3 py-1.5 border border-gray-300 rounded-md text-sm">
|
||||||
|
<option value="Accrual" ${tsAccountingMethod === 'Accrual' ? 'selected' : ''}>Accrual</option>
|
||||||
|
<option value="Cash" ${tsAccountingMethod === 'Cash' ? 'selected' : ''}>Cash</option>
|
||||||
|
</select></div>
|
||||||
|
<button onclick="window.accountingView.loadTaxSummary()" class="px-3 py-1.5 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700">Run</button>
|
||||||
|
</div>
|
||||||
|
<div id="ts-result"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
@@ -482,6 +512,21 @@ export async function loadBalanceSheet() {
|
|||||||
} catch (err) { showError('bs-result', err.message || 'Failed to load Balance Sheet'); }
|
} catch (err) { showError('bs-result', err.message || 'Failed to load Balance Sheet'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function loadTaxSummary() {
|
||||||
|
tsMonth = document.getElementById('ts-month').value;
|
||||||
|
tsAccountingMethod = document.getElementById('ts-method').value;
|
||||||
|
if (!tsMonth) return showError('ts-result', 'Please select a month.');
|
||||||
|
const [y, m] = tsMonth.split('-').map(Number);
|
||||||
|
const startDate = firstOfMonthISO(y, m - 1);
|
||||||
|
const endDate = lastOfMonthISO(y, m - 1);
|
||||||
|
showLoading('ts-result', 'Loading Sales Tax Liability from QBO…');
|
||||||
|
try {
|
||||||
|
const data = await window.API.accounting.getTaxSummary(startDate, endDate, tsAccountingMethod);
|
||||||
|
if (data.error) return showError('ts-result', data.error);
|
||||||
|
document.getElementById('ts-result').innerHTML = renderQboReport(data);
|
||||||
|
} catch (err) { showError('ts-result', err.message || 'Failed to load Tax Summary'); }
|
||||||
|
}
|
||||||
|
|
||||||
function renderQboReport(report) {
|
function renderQboReport(report) {
|
||||||
if (!report || !report.Header) return `<p class="text-sm text-gray-500">No report data.</p>`;
|
if (!report || !report.Header) return `<p class="text-sm text-gray-500">No report data.</p>`;
|
||||||
const cols = (report.Columns && report.Columns.Column) || [];
|
const cols = (report.Columns && report.Columns.Column) || [];
|
||||||
@@ -724,6 +769,7 @@ window.accountingView = {
|
|||||||
loadRegister,
|
loadRegister,
|
||||||
loadProfitLoss,
|
loadProfitLoss,
|
||||||
loadBalanceSheet,
|
loadBalanceSheet,
|
||||||
|
loadTaxSummary,
|
||||||
loadExpenses,
|
loadExpenses,
|
||||||
openNewExpense,
|
openNewExpense,
|
||||||
openNewRefund,
|
openNewRefund,
|
||||||
|
|||||||
@@ -85,6 +85,16 @@ router.get('/reports/balance-sheet', async (req, res) => {
|
|||||||
} catch (err) { handleQboError(err, res, 'balance-sheet'); }
|
} catch (err) { handleQboError(err, res, 'balance-sheet'); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.get('/reports/tax-summary', async (req, res) => {
|
||||||
|
try {
|
||||||
|
res.json(await accountingService.getTaxSummary({
|
||||||
|
startDate: req.query.startDate,
|
||||||
|
endDate: req.query.endDate,
|
||||||
|
accountingMethod: req.query.accountingMethod || 'Accrual'
|
||||||
|
}));
|
||||||
|
} catch (err) { handleQboError(err, res, 'tax-summary'); }
|
||||||
|
});
|
||||||
|
|
||||||
// ════════════════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════════════════
|
||||||
// Phase 2 Lieferung 1 — Sync + Cache-Reads
|
// Phase 2 Lieferung 1 — Sync + Cache-Reads
|
||||||
// ════════════════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════════════════
|
||||||
|
|||||||
@@ -367,6 +367,14 @@ async function getBalanceSheet({ asOfDate, accountingMethod = 'Accrual' } = {})
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
// ════════════════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════════════════
|
||||||
// Phase 2 Lieferung 1 — Caches und Sync
|
// Phase 2 Lieferung 1 — Caches und Sync
|
||||||
// ════════════════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════════════════
|
||||||
@@ -1418,6 +1426,7 @@ module.exports = {
|
|||||||
getRegister,
|
getRegister,
|
||||||
getProfitAndLoss,
|
getProfitAndLoss,
|
||||||
getBalanceSheet,
|
getBalanceSheet,
|
||||||
|
getTaxSummary,
|
||||||
normalizeTransactionListReport,
|
normalizeTransactionListReport,
|
||||||
|
|
||||||
// Phase 2 Lieferung 1 — Sync
|
// Phase 2 Lieferung 1 — Sync
|
||||||
|
|||||||
Reference in New Issue
Block a user