fix(accounting): 'Save & mark paid' actually pays; incoming invoices appear in tax/export

#1 Triage 'Save & mark paid' now marks the invoice paid directly (categorize +
   markInboundPaid with the entered reference) instead of opening the pay dialog
   and leaving it unpaid. Removed the PayModal chain.
#2 Cost side missed captured incoming invoices: the query required
   currency='CHF', but email/upload invoices often have a null currency →
   silently excluded. Now include null-currency rows (treated as the report
   currency). Also replaced COALESCE(invoice_date, created_at) with a split
   date filter (invoice_date BETWEEN, else created_at range) to avoid the
   mixed date/timestamp comparison risk on Postgres. Same fix in the ledger
   export (buildPostings).
en/de: categorizedPaidToast.
This commit is contained in:
Luca
2026-06-12 18:20:55 +02:00
parent 663daf50ff
commit 3b70a09773
5 changed files with 39 additions and 23 deletions
+8 -3
View File
@@ -297,10 +297,15 @@ async function buildPostings({ from, to, currency } = {}) {
.modify((q) => {
if (hasCatCol) q.leftJoin('expense_categories', 'inbound_documents.category_id', 'expense_categories.id');
})
.whereRaw('COALESCE(inbound_documents.invoice_date, inbound_documents.created_at) >= ? AND COALESCE(inbound_documents.invoice_date, inbound_documents.created_at) <= ?', [from, toEnd])
.where('inbound_documents.currency', cur)
.where((qb) => {
qb.whereBetween('inbound_documents.invoice_date', [from, to])
.orWhere((q2) => q2.whereNull('inbound_documents.invoice_date')
.andWhere('inbound_documents.created_at', '>=', from)
.andWhere('inbound_documents.created_at', '<=', toEnd));
})
.where((qb) => { qb.where('inbound_documents.currency', cur).orWhereNull('inbound_documents.currency'); })
.whereNotIn('inbound_documents.status', ['declined', 'duplicate'])
.orderByRaw('COALESCE(inbound_documents.invoice_date, inbound_documents.created_at) asc')
.orderBy('inbound_documents.created_at', 'asc')
.select(
'inbound_documents.id', 'inbound_documents.invoice_number', 'inbound_documents.invoice_date',
'inbound_documents.created_at', 'inbound_documents.supplier_name', 'inbound_documents.tax_treatment',
+14 -3
View File
@@ -213,10 +213,21 @@ async function loadCosts({ from, to, cur }) {
if (await db.schema.hasTable('inbound_documents')) {
const inbound = await db('inbound_documents')
.leftJoin('events', 'inbound_documents.event_id', 'events.id')
.whereRaw('COALESCE(inbound_documents.invoice_date, inbound_documents.created_at) >= ? AND COALESCE(inbound_documents.invoice_date, inbound_documents.created_at) <= ?', [from, toEnd])
.where('inbound_documents.currency', cur)
// Date in range: invoice_date (a DATE) when set, else created_at (a
// TIMESTAMP). Split instead of COALESCE so we never compare mixed
// date/timestamp types (a Postgres error the mocked tests can't see).
.where((qb) => {
qb.whereBetween('inbound_documents.invoice_date', [from, to])
.orWhere((q2) => q2.whereNull('inbound_documents.invoice_date')
.andWhere('inbound_documents.created_at', '>=', from)
.andWhere('inbound_documents.created_at', '<=', toEnd));
})
// Currency match, but INCLUDE rows with no currency set — captured
// invoices (email/upload) often have a null currency; treat them as the
// report currency rather than silently dropping them from the cost side.
.where((qb) => { qb.where('inbound_documents.currency', cur).orWhereNull('inbound_documents.currency'); })
.whereNotIn('inbound_documents.status', ['declined', 'duplicate'])
.orderByRaw('COALESCE(inbound_documents.invoice_date, inbound_documents.created_at) asc')
.orderBy('inbound_documents.created_at', 'asc')
.select(
'inbound_documents.id',
'inbound_documents.invoice_date',