fix(crm): recognise imported-invoice revenue on issue_date, not paid_at
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.
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user