sales tax
This commit is contained in:
@@ -510,6 +510,141 @@ async function getTaxSummary({ startDate, endDate, accountingMethod = 'Accrual'
|
||||
};
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// Sales Tax Periods — local source of truth for tax filings
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
|
||||
async function getTaxPeriods() {
|
||||
const result = await pool.query(
|
||||
'SELECT * FROM sales_tax_periods ORDER BY period_start DESC'
|
||||
);
|
||||
return result.rows;
|
||||
}
|
||||
|
||||
async function upsertTaxPeriod(period) {
|
||||
const result = await pool.query(
|
||||
`INSERT INTO sales_tax_periods
|
||||
(period_start, period_end, total_sales, nontaxable_sales, taxable_sales, tax_collected,
|
||||
adjustment_amount, adjustment_reason, adjustment_account_id, adjustment_account_name,
|
||||
net_paid, bank_account_id, bank_account_name,
|
||||
sales_tax_payable_id, sales_tax_payable_name)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15)
|
||||
ON CONFLICT (period_start, period_end) DO UPDATE SET
|
||||
total_sales = EXCLUDED.total_sales,
|
||||
nontaxable_sales = EXCLUDED.nontaxable_sales,
|
||||
taxable_sales = EXCLUDED.taxable_sales,
|
||||
tax_collected = EXCLUDED.tax_collected,
|
||||
adjustment_amount = COALESCE(sales_tax_periods.adjustment_amount, EXCLUDED.adjustment_amount),
|
||||
adjustment_reason = COALESCE(sales_tax_periods.adjustment_reason, EXCLUDED.adjustment_reason),
|
||||
adjustment_account_id = COALESCE(sales_tax_periods.adjustment_account_id, EXCLUDED.adjustment_account_id),
|
||||
adjustment_account_name = COALESCE(sales_tax_periods.adjustment_account_name, EXCLUDED.adjustment_account_name),
|
||||
net_paid = COALESCE(sales_tax_periods.net_paid, EXCLUDED.net_paid),
|
||||
bank_account_id = COALESCE(sales_tax_periods.bank_account_id, EXCLUDED.bank_account_id),
|
||||
bank_account_name = COALESCE(sales_tax_periods.bank_account_name, EXCLUDED.bank_account_name),
|
||||
sales_tax_payable_id = COALESCE(sales_tax_periods.sales_tax_payable_id, EXCLUDED.sales_tax_payable_id),
|
||||
sales_tax_payable_name = COALESCE(sales_tax_periods.sales_tax_payable_name, EXCLUDED.sales_tax_payable_name),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
RETURNING *`,
|
||||
[
|
||||
period.period_start, period.period_end,
|
||||
period.total_sales, period.nontaxable_sales, period.taxable_sales, period.tax_collected,
|
||||
period.adjustment_amount || 0, period.adjustment_reason || null,
|
||||
period.adjustment_account_id || null, period.adjustment_account_name || null,
|
||||
period.net_paid || null, period.bank_account_id || null, period.bank_account_name || null,
|
||||
period.sales_tax_payable_id || null, period.sales_tax_payable_name || null
|
||||
]
|
||||
);
|
||||
return result.rows[0];
|
||||
}
|
||||
|
||||
async function createTaxPaymentJE({
|
||||
periodId, txnDate, taxCollected, adjustmentAmount, netPaid,
|
||||
salesTaxPayableId, salesTaxPayableName,
|
||||
adjustmentAccountId, adjustmentAccountName, adjustmentReason,
|
||||
bankAccountId, bankAccountName
|
||||
}) {
|
||||
const { companyId, baseUrl } = getClientInfo();
|
||||
const url = withMinorVersion(`${baseUrl}/v3/company/${companyId}/journalentry`);
|
||||
|
||||
const lines = [
|
||||
{
|
||||
DetailType: 'JournalEntryLineDetail',
|
||||
Amount: taxCollected,
|
||||
JournalEntryLineDetail: {
|
||||
PostingType: 'Debit',
|
||||
AccountRef: { value: String(salesTaxPayableId), name: salesTaxPayableName }
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
if (adjustmentAmount > 0) {
|
||||
lines.push({
|
||||
DetailType: 'JournalEntryLineDetail',
|
||||
Amount: adjustmentAmount,
|
||||
Description: adjustmentReason || undefined,
|
||||
JournalEntryLineDetail: {
|
||||
PostingType: 'Credit',
|
||||
AccountRef: { value: String(adjustmentAccountId), name: adjustmentAccountName }
|
||||
}
|
||||
});
|
||||
} else if (adjustmentAmount < 0) {
|
||||
lines.push({
|
||||
DetailType: 'JournalEntryLineDetail',
|
||||
Amount: Math.abs(adjustmentAmount),
|
||||
Description: adjustmentReason || undefined,
|
||||
JournalEntryLineDetail: {
|
||||
PostingType: 'Debit',
|
||||
AccountRef: { value: String(adjustmentAccountId), name: adjustmentAccountName }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
lines.push({
|
||||
DetailType: 'JournalEntryLineDetail',
|
||||
Amount: netPaid,
|
||||
JournalEntryLineDetail: {
|
||||
PostingType: 'Credit',
|
||||
AccountRef: { value: String(bankAccountId), name: bankAccountName }
|
||||
}
|
||||
});
|
||||
|
||||
const payload = {
|
||||
TxnDate: txnDate,
|
||||
DocNumber: `STP-${periodId}`,
|
||||
Line: lines,
|
||||
PrivateNote: adjustmentReason ? `Adjustment: ${adjustmentReason}` : undefined
|
||||
};
|
||||
|
||||
let qboResponse;
|
||||
try {
|
||||
const response = await makeQboApiCall({
|
||||
url,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
qboResponse = response;
|
||||
} catch (e) {
|
||||
const errMsg = e.originalMessage || e.message || String(e);
|
||||
throw new Error(`QBO JournalEntry failed: ${errMsg}`);
|
||||
}
|
||||
|
||||
const data = getJson(qboResponse);
|
||||
throwIfFault(data, 'JournalEntry create');
|
||||
|
||||
const je = data.JournalEntry;
|
||||
if (!je || !je.Id) throw new Error('QBO did not return a JournalEntry ID');
|
||||
|
||||
await pool.query(
|
||||
`UPDATE sales_tax_periods
|
||||
SET qbo_journal_entry_id = $1, booked_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $2`,
|
||||
[je.Id, periodId]
|
||||
);
|
||||
|
||||
return { qbo_journal_entry_id: je.Id, qbo_sync_token: je.SyncToken };
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// Phase 2 Lieferung 1 — Caches und Sync
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
@@ -1562,6 +1697,9 @@ module.exports = {
|
||||
getProfitAndLoss,
|
||||
getBalanceSheet,
|
||||
getTaxSummary,
|
||||
getTaxPeriods,
|
||||
upsertTaxPeriod,
|
||||
createTaxPaymentJE,
|
||||
normalizeTransactionListReport,
|
||||
|
||||
// Phase 2 Lieferung 1 — Sync
|
||||
|
||||
Reference in New Issue
Block a user