sales tax 2.

This commit is contained in:
2026-05-31 13:03:27 -05:00
parent 3517c9aec2
commit 8672eb6340
6 changed files with 123 additions and 20 deletions

View File

@@ -0,0 +1,2 @@
ALTER TABLE sales_tax_periods ADD COLUMN status VARCHAR(20) DEFAULT 'open';
UPDATE sales_tax_periods SET status = 'booked' WHERE qbo_journal_entry_id IS NOT NULL;

View File

@@ -152,6 +152,9 @@ const API = {
recordTaxPayment: (periodId, data) => fetch(`/api/accounting/sales-tax/periods/${periodId}/record`, { recordTaxPayment: (periodId, data) => fetch(`/api/accounting/sales-tax/periods/${periodId}/record`, {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data)
}).then(r => r.json()), }).then(r => r.json()),
markTaxPaidExternal: (periodId) => fetch(`/api/accounting/sales-tax/periods/${periodId}/mark-external`, {
method: 'POST'
}).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()),

View File

@@ -762,22 +762,28 @@ function renderTaxPeriodsTable() {
} }
const rows = stPeriods.map(p => { const rows = stPeriods.map(p => {
const status = p.qbo_journal_entry_id const pStatus = p.status || (p.qbo_journal_entry_id ? 'booked' : 'open');
? `<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-semibold bg-green-100 text-green-800">Paid</span>` let statusHtml, statusColor;
: `<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-semibold bg-yellow-100 text-yellow-800">Open</span>`; 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 { statusHtml = 'Open'; statusColor = 'bg-yellow-100 text-yellow-800'; }
const monthLabel = new Date(p.period_start).toLocaleDateString('en-US', { year: 'numeric', month: 'long' }); const monthLabel = new Date(p.period_start).toLocaleDateString('en-US', { year: 'numeric', month: 'long' });
const adj = parseFloat(p.adjustment_amount) || 0; const adj = parseFloat(p.adjustment_amount) || 0;
const adjStr = adj !== 0 ? (adj > 0 ? `$${adj.toFixed(2)}` : `+$${Math.abs(adj).toFixed(2)}`) : '—'; 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 netPaid = parseFloat(p.net_paid) || parseFloat(p.tax_collected) || 0;
const paidOn = pStatus === 'external' ? 'Paid in QBO' : (p.booked_at ? formatDate(p.booked_at) : '—');
return ` return `
<tr class="border-t hover:bg-gray-50"> <tr class="border-t hover:bg-gray-50">
<td class="px-3 py-2 text-sm font-medium text-gray-800">${monthLabel}</td> <td class="px-3 py-2 text-sm font-medium text-gray-800">${monthLabel}</td>
<td class="px-3 py-2 text-sm">${status}</td> <td class="px-3 py-2 text-sm">
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-semibold ${statusColor}">${statusHtml}</span>
</td>
<td class="px-3 py-2 text-sm text-right">${fmtMoney(parseFloat(p.tax_collected) || 0)}</td> <td class="px-3 py-2 text-sm text-right">${fmtMoney(parseFloat(p.tax_collected) || 0)}</td>
<td class="px-3 py-2 text-sm text-right">${adjStr}</td> <td class="px-3 py-2 text-sm text-right">${adjStr}</td>
<td class="px-3 py-2 text-sm text-right font-semibold">${fmtMoney(netPaid)}</td> <td class="px-3 py-2 text-sm text-right font-semibold">${fmtMoney(netPaid)}</td>
<td class="px-3 py-2 text-sm text-gray-500">${p.booked_at ? formatDate(p.booked_at) : '—'}</td> <td class="px-3 py-2 text-sm text-gray-500">${paidOn}</td>
<td class="px-3 py-2 text-sm text-center"> <td class="px-3 py-2 text-sm text-center">
<button onclick="window.accountingView.openTaxPeriod(${p.id})" <button onclick="window.accountingView.openTaxPeriod(${p.id})"
class="text-blue-600 hover:text-blue-800 text-xs font-medium">View summary</button> class="text-blue-600 hover:text-blue-800 text-xs font-medium">View summary</button>
@@ -851,7 +857,9 @@ async function openTaxPeriodDetail(startDate, endDate, existingPeriod) {
const adjustments = existingPeriod?.adjustment_amount != null ? parseFloat(existingPeriod.adjustment_amount) || 0 : 0; const adjustments = existingPeriod?.adjustment_amount != null ? parseFloat(existingPeriod.adjustment_amount) || 0 : 0;
const adjReason = existingPeriod?.adjustment_reason || ''; const adjReason = existingPeriod?.adjustment_reason || '';
const netPaid = taxCollected - adjustments; const netPaid = taxCollected - adjustments;
const isPaid = existingPeriod && existingPeriod.qbo_journal_entry_id; const periodStatus = existingPeriod?.status || (existingPeriod?.qbo_journal_entry_id ? 'booked' : 'open');
const isOpen = periodStatus === 'open';
const isEditable = isOpen;
const bankOpts = stAccounts const bankOpts = stAccounts
.filter(a => a.accountType === 'Bank' || a.accountType === 'Credit Card') .filter(a => a.accountType === 'Bank' || a.accountType === 'Credit Card')
@@ -891,19 +899,19 @@ async function openTaxPeriodDetail(startDate, endDate, existingPeriod) {
<div class="grid grid-cols-1 md:grid-cols-3 gap-3 mb-3"> <div class="grid grid-cols-1 md:grid-cols-3 gap-3 mb-3">
<div> <div>
<label class="block text-xs font-medium text-gray-700 mb-1">Adjustment Amount</label> <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)" ${isPaid ? 'disabled' : ''} <input type="number" step="0.01" id="st-adjustment" value="${adjustments !== 0 ? adjustments : ''}" placeholder="e.g. 6.15 (discount)" ${isEditable ? '' : 'disabled'}
oninput="window.accountingView.updateTaxPreview()" oninput="window.accountingView.updateTaxPreview()"
class="w-full px-3 py-1.5 border border-gray-300 rounded-md text-sm"> 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> <p class="text-xs text-gray-400 mt-0.5">Positive = discount (reduces net due)</p>
</div> </div>
<div> <div>
<label class="block text-xs font-medium text-gray-700 mb-1">Reason</label> <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" ${isPaid ? 'disabled' : ''} <input type="text" id="st-adjustment-reason" value="${escapeHtml(adjReason)}" placeholder="e.g. Timely filing discount" ${isEditable ? '' : 'disabled'}
class="w-full px-3 py-1.5 border border-gray-300 rounded-md text-sm"> class="w-full px-3 py-1.5 border border-gray-300 rounded-md text-sm">
</div> </div>
<div> <div>
<label class="block text-xs font-medium text-gray-700 mb-1">Adjustment Account</label> <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" ${isPaid ? 'disabled' : ''}> <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> <option value="">— Select income account —</option>
${adjustOpts} ${adjustOpts}
</select> </select>
@@ -913,33 +921,32 @@ async function openTaxPeriodDetail(startDate, endDate, existingPeriod) {
<div class="grid grid-cols-1 md:grid-cols-3 gap-3 mb-3"> <div class="grid grid-cols-1 md:grid-cols-3 gap-3 mb-3">
<div> <div>
<label class="block text-xs font-medium text-gray-700 mb-1">Bank Account (payment source)</label> <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" ${isPaid ? 'disabled' : ''}> <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> <option value="">— Select bank account —</option>
${bankOpts} ${bankOpts}
</select> </select>
</div> </div>
<div> <div>
<label class="block text-xs font-medium text-gray-700 mb-1">Sales Tax Payable Account</label> <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" ${isPaid ? 'disabled' : ''}> <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> <option value="">— Select liability account —</option>
${liabilityOpts} ${liabilityOpts}
</select> </select>
</div> </div>
<div> <div>
<label class="block text-xs font-medium text-gray-700 mb-1">Payment Date</label> <label class="block text-xs font-medium text-gray-700 mb-1">Payment Date</label>
<input type="date" id="st-payment-date" value="${today}" ${isPaid ? 'disabled' : ''} <input type="date" id="st-payment-date" value="${today}" ${isEditable ? '' : 'disabled'}
class="w-full px-3 py-1.5 border border-gray-300 rounded-md text-sm"> class="w-full px-3 py-1.5 border border-gray-300 rounded-md text-sm">
</div> </div>
</div> </div>
${!isPaid ? ` ${isEditable ? `
<div class="flex items-center justify-between border-t pt-3 mt-1"> <div class="flex items-center justify-between border-t pt-3 mt-1">
<div id="st-je-lines"> <div id="st-je-lines">
<p class="text-sm"><strong>Journal Entry lines:</strong></p> <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> <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">Credit Discount: ${fmtMoney(adjustments)}</p>` : ''}
${adjustments < 0 ? `<p class="text-xs text-gray-500 font-mono">Debit Penalty: ${fmtMoney(Math.abs(adjustments))}</p>` : ''} ${adjustments < 0 ? `<p class="text-xs text-gray-500 font-mono">Debit Penalty: ${fmtMoney(Math.abs(adjustments))}</p>` : ''}
${adjustments === 0 ? `` : ''}
<p class="text-xs text-gray-500 font-mono" id="st-je-bank">Credit Bank: ${fmtMoney(netPaid)}</p> <p class="text-xs text-gray-500 font-mono" id="st-je-bank">Credit Bank: ${fmtMoney(netPaid)}</p>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
@@ -947,15 +954,23 @@ async function openTaxPeriodDetail(startDate, endDate, existingPeriod) {
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"> 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 💾 Save Draft
</button> </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.recordTaxPayment()"
class="px-4 py-2 bg-green-600 text-white rounded-md text-sm font-semibold hover:bg-green-700"> class="px-4 py-2 bg-green-600 text-white rounded-md text-sm font-semibold hover:bg-green-700">
✅ Record Payment in QBO ✅ Record Payment in QBO
</button> </button>
</div> </div>
</div> </div>
` : periodStatus === 'booked' ? `
<div class="border-t pt-3 mt-1">
<p class="text-sm text-green-700 font-semibold">✅ Booked — Journal Entry #${escapeHtml(existingPeriod.qbo_journal_entry_id)} on ${formatDate(existingPeriod.booked_at)}</p>
</div>
` : ` ` : `
<div class="border-t pt-3 mt-1"> <div class="border-t pt-3 mt-1">
<p class="text-sm text-green-700 font-semibold">✅ Paid — Journal Entry #${escapeHtml(existingPeriod.qbo_journal_entry_id)} on ${formatDate(existingPeriod.booked_at)}</p> <p class="text-sm text-blue-700 font-semibold">📋 Marked as paid externally on ${formatDate(existingPeriod.booked_at)}</p>
</div> </div>
`} `}
</div> </div>
@@ -1138,6 +1153,62 @@ export async function recordTaxPayment() {
} }
} }
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 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\nNo Journal Entry will be created — this only records the status in the app.\n\nTax Collected: $${stCurrentTaxData.taxCollected.toFixed(2)}\nAdjustment: $${adjAmount.toFixed(2)}\nNet Due: $${(stCurrentTaxData.taxCollected - adjAmount).toFixed(2)}`)) return;
if (!stCurrentTaxData) return alert('No tax data loaded.');
try {
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: 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'
});
} catch (e) {
return alert('Failed to save period: ' + e.message);
}
try {
const result = await window.API.accounting.markTaxPaidExternal(stEditingPeriodId);
await loadTaxPeriods();
const updated = stPeriods.find(p => p.id === stEditingPeriodId);
if (updated) {
await openTaxPeriodDetail(updated.period_start, updated.period_end, updated);
}
} catch (e) {
alert('Failed: ' + e.message);
}
}
function parseTaxDataFromReport(taxData) { function parseTaxDataFromReport(taxData) {
if (!taxData?.Rows?.Row) return { totalSales: 0, nontaxableSales: 0, taxableSales: 0, taxCollected: 0 }; if (!taxData?.Rows?.Row) return { totalSales: 0, nontaxableSales: 0, taxableSales: 0, taxCollected: 0 };
const rows = Array.isArray(taxData.Rows.Row) ? taxData.Rows.Row : []; const rows = Array.isArray(taxData.Rows.Row) ? taxData.Rows.Row : [];
@@ -1228,5 +1299,6 @@ window.accountingView = {
closeTaxPeriodDetail, closeTaxPeriodDetail,
updateTaxPreview, updateTaxPreview,
saveTaxPeriodDraft, saveTaxPeriodDraft,
recordTaxPayment recordTaxPayment,
markTaxPaidExternal
}; };

View File

@@ -378,6 +378,7 @@ CREATE TABLE public.sales_tax_periods (
sales_tax_payable_id character varying(50), sales_tax_payable_id character varying(50),
sales_tax_payable_name character varying(200), sales_tax_payable_name character varying(200),
qbo_journal_entry_id character varying(50), qbo_journal_entry_id character varying(50),
status character varying(20) DEFAULT 'open',
booked_at timestamp without time zone, booked_at timestamp without time zone,
created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, updated_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP,

View File

@@ -118,6 +118,12 @@ router.post('/sales-tax/periods/:id/record', async (req, res) => {
} catch (err) { handleQboError(err, res, 'sales-tax-record'); } } catch (err) { handleQboError(err, res, 'sales-tax-record'); }
}); });
router.post('/sales-tax/periods/:id/mark-external', async (req, res) => {
try {
res.json(await accountingService.markTaxPaidExternal(req.params.id));
} catch (err) { handleQboError(err, res, 'sales-tax-mark-external'); }
});
// ════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════
// Phase 2 Lieferung 1 — Sync + Cache-Reads // Phase 2 Lieferung 1 — Sync + Cache-Reads
// ════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════

View File

@@ -527,8 +527,8 @@ async function upsertTaxPeriod(period) {
(period_start, period_end, total_sales, nontaxable_sales, taxable_sales, tax_collected, (period_start, period_end, total_sales, nontaxable_sales, taxable_sales, tax_collected,
adjustment_amount, adjustment_reason, adjustment_account_id, adjustment_account_name, adjustment_amount, adjustment_reason, adjustment_account_id, adjustment_account_name,
net_paid, bank_account_id, bank_account_name, net_paid, bank_account_id, bank_account_name,
sales_tax_payable_id, sales_tax_payable_name) sales_tax_payable_id, sales_tax_payable_name, status)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)
ON CONFLICT (period_start, period_end) DO UPDATE SET ON CONFLICT (period_start, period_end) DO UPDATE SET
total_sales = EXCLUDED.total_sales, total_sales = EXCLUDED.total_sales,
nontaxable_sales = EXCLUDED.nontaxable_sales, nontaxable_sales = EXCLUDED.nontaxable_sales,
@@ -543,6 +543,7 @@ async function upsertTaxPeriod(period) {
bank_account_name = COALESCE(sales_tax_periods.bank_account_name, EXCLUDED.bank_account_name), 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_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), sales_tax_payable_name = COALESCE(sales_tax_periods.sales_tax_payable_name, EXCLUDED.sales_tax_payable_name),
status = COALESCE(sales_tax_periods.status, EXCLUDED.status),
updated_at = CURRENT_TIMESTAMP updated_at = CURRENT_TIMESTAMP
RETURNING *`, RETURNING *`,
[ [
@@ -551,7 +552,8 @@ async function upsertTaxPeriod(period) {
period.adjustment_amount || 0, period.adjustment_reason || null, period.adjustment_amount || 0, period.adjustment_reason || null,
period.adjustment_account_id || null, period.adjustment_account_name || 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.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 period.sales_tax_payable_id || null, period.sales_tax_payable_name || null,
period.status || 'open'
] ]
); );
return result.rows[0]; return result.rows[0];
@@ -637,7 +639,7 @@ async function createTaxPaymentJE({
await pool.query( await pool.query(
`UPDATE sales_tax_periods `UPDATE sales_tax_periods
SET qbo_journal_entry_id = $1, booked_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP SET qbo_journal_entry_id = $1, status = 'booked', booked_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP
WHERE id = $2`, WHERE id = $2`,
[je.Id, periodId] [je.Id, periodId]
); );
@@ -645,6 +647,22 @@ async function createTaxPaymentJE({
return { qbo_journal_entry_id: je.Id, qbo_sync_token: je.SyncToken }; return { qbo_journal_entry_id: je.Id, qbo_sync_token: je.SyncToken };
} }
async function markTaxPaidExternal(periodId) {
const result = await pool.query(
`UPDATE sales_tax_periods
SET status = 'external', booked_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP
WHERE id = $1 AND status = 'open'
RETURNING *`,
[periodId]
);
if (result.rows.length === 0) {
const existing = await pool.query('SELECT status FROM sales_tax_periods WHERE id = $1', [periodId]);
const currentStatus = existing.rows[0]?.status || 'unknown';
throw new Error(`Cannot mark as paid: period is already ${currentStatus}`);
}
return result.rows[0];
}
// ════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════
// Phase 2 Lieferung 1 — Caches und Sync // Phase 2 Lieferung 1 — Caches und Sync
// ════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════
@@ -1700,6 +1718,7 @@ module.exports = {
getTaxPeriods, getTaxPeriods,
upsertTaxPeriod, upsertTaxPeriod,
createTaxPaymentJE, createTaxPaymentJE,
markTaxPaidExternal,
normalizeTransactionListReport, normalizeTransactionListReport,
// Phase 2 Lieferung 1 — Sync // Phase 2 Lieferung 1 — Sync