From 5e79c69cda93c2aef8176768d29c5ea47a95c90d Mon Sep 17 00:00:00 2001 From: Luca <102960244+Luca-Timo@users.noreply.github.com> Date: Wed, 3 Jun 2026 17:02:50 +0200 Subject: [PATCH] fix(crm): recognise imported-invoice revenue on issue_date, not paid_at MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dashboard revenue windows (30/90/365 days) keyed purely on paid_at. Imported historical invoices therefore landed in the recent window whenever their paid_at sat there — notably legacy rows imported before commit c6b8cc9 began anchoring an import's paid_at to its issue_date, which still carry an import-time paid_at. Recognise imported invoices (imported_pdf_path NOT NULL) on their issue_date instead; native invoices keep cash-basis paid_at. No data migration needed — fixes already-imported year-old invoices too. --- backend/src/routes/adminDashboard.js | 29 +++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/backend/src/routes/adminDashboard.js b/backend/src/routes/adminDashboard.js index 7169e264..7140223d 100644 --- a/backend/src/routes/adminDashboard.js +++ b/backend/src/routes/adminDashboard.js @@ -430,17 +430,36 @@ router.get('/crm-stats', adminAuth, async (req, res) => { } // Revenue windows: sum of `paid_amount_minor` for invoices - // marked PAID where paid_at falls inside the window. Using - // paid_amount (not total) so partial payments are tracked - // accurately. Stornos excluded — they're never status='paid' - // in normal flow but the guard is defensive. + // marked PAID inside the window. Using paid_amount (not total) + // so partial payments are tracked accurately. Stornos excluded + // — they're never status='paid' in normal flow but the guard + // is defensive. + // + // Recognition date differs by origin: + // • imported (historical) invoices → recognise on issue_date, + // their true economic date. A year-old imported invoice must + // never land in the rolling "last 30/90 days" window. This is + // robust even for rows imported BEFORE the import route began + // anchoring paid_at to issue_date (commit c6b8cc9) — those + // legacy rows still carry an import-time paid_at, so keying on + // issue_date is what actually fixes them. + // • native invoices → recognise on paid_at (cash-basis). const winSum = async (cutoff) => { + const cutoffDateStr = cutoff.toISOString().slice(0, 10); const row = await db('invoices') .where('status', 'paid') - .where('paid_at', '>=', cutoff) .andWhere(function() { this.whereNot('kind', 'storno').orWhereNull('kind'); }) + .andWhere(function() { + this.where(function() { + this.whereNotNull('imported_pdf_path') + .andWhere('issue_date', '>=', cutoffDateStr); + }).orWhere(function() { + this.whereNull('imported_pdf_path') + .andWhere('paid_at', '>=', cutoff); + }); + }) .sum('paid_amount_minor as total') .first(); return Number(row?.total || 0);