From a97d656e9202ed08297a8f8916ba4715bb8bdf41 Mon Sep 17 00:00:00 2001 From: null Date: Sat, 6 Jun 2026 14:06:28 -0500 Subject: [PATCH] fix(match-suggestions): use rejection timestamps and share late attribution helper --- services/billMerchantRuleService.js | 23 ++++++++++++----------- services/cleanupService.js | 2 +- services/matchSuggestionService.js | 24 +++++++----------------- 3 files changed, 20 insertions(+), 29 deletions(-) diff --git a/services/billMerchantRuleService.js b/services/billMerchantRuleService.js index 73e103a..3cc51c0 100644 --- a/services/billMerchantRuleService.js +++ b/services/billMerchantRuleService.js @@ -16,6 +16,18 @@ function merchantMatches(txMerchant, ruleMerchant) { wordBoundary(txMerchant).test(ruleMerchant); } +// Detects when a payment posted just after month end but the bill was due in the +// prior month. Grace window: up to `graceDays` days into the new month. +// Module-scoped so both applyMerchantRules and syncBillPaymentsFromSimplefin can use it. +function lateAttributionCandidate(paidDateStr, dueDayOfMonth, graceDays = 5) { + const paid = new Date(paidDateStr + 'T00:00:00'); + const dayOfMonth = paid.getDate(); + if (dayOfMonth > graceDays) return null; + const prevMonthLastDay = new Date(paid.getFullYear(), paid.getMonth(), 0); + if (dueDayOfMonth > prevMonthLastDay.getDate()) return null; + return prevMonthLastDay.toISOString().slice(0, 10); // suggested prior-month date +} + // Persist a merchant→bill rule so future synced transactions auto-match. function addMerchantRule(db, userId, billId, merchant) { const normalized = normalizeMerchant(merchant); @@ -35,17 +47,6 @@ function addMerchantRule(db, userId, billId, merchant) { // merchant rules, create payments, and mark the transactions matched. // Returns { matched: number }. function applyMerchantRules(db, userId) { - // Detects when a payment posted just after month end but the bill was due in the prior month. - // Grace window: up to LATE_ATTR_DAYS days into the new month. - function lateAttributionCandidate(paidDateStr, dueDayOfMonth, graceDays = 5) { - const paid = new Date(paidDateStr + 'T00:00:00'); - const dayOfMonth = paid.getDate(); - if (dayOfMonth > graceDays) return null; - const prevMonthLastDay = new Date(paid.getFullYear(), paid.getMonth(), 0); - if (dueDayOfMonth > prevMonthLastDay.getDate()) return null; - return prevMonthLastDay.toISOString().slice(0, 10); // suggested prior-month date - } - let rules; try { rules = db.prepare(` diff --git a/services/cleanupService.js b/services/cleanupService.js index 4f958e9..09ad3bc 100644 --- a/services/cleanupService.js +++ b/services/cleanupService.js @@ -218,7 +218,7 @@ async function runAllCleanup() { // Prune match suggestion rejections older than 90 days try { const { changes } = getDb().prepare( - "DELETE FROM match_suggestion_rejections WHERE created_at <= datetime('now', '-90 days')" + "DELETE FROM match_suggestion_rejections WHERE rejected_at <= datetime('now', '-90 days')" ).run(); tasks.suggestion_rejections = { pruned: changes }; } catch { tasks.suggestion_rejections = { pruned: 0 }; } diff --git a/services/matchSuggestionService.js b/services/matchSuggestionService.js index 89203b8..7657b1f 100644 --- a/services/matchSuggestionService.js +++ b/services/matchSuggestionService.js @@ -226,23 +226,13 @@ function loadBills(db, userId) { } function loadRejections(db, userId) { - try { - const rows = db.prepare(` - SELECT transaction_id, bill_id - FROM match_suggestion_rejections - WHERE user_id = ? - AND created_at > datetime('now', '-90 days') - `).all(userId); - return new Set(rows.map(row => suggestionId(row.transaction_id, row.bill_id))); - } catch { - // Fall back to all rejections if created_at column doesn't exist yet - try { - const rows = db.prepare(` - SELECT transaction_id, bill_id FROM match_suggestion_rejections WHERE user_id = ? - `).all(userId); - return new Set(rows.map(row => suggestionId(row.transaction_id, row.bill_id))); - } catch { return new Set(); } - } + const rows = db.prepare(` + SELECT transaction_id, bill_id + FROM match_suggestion_rejections + WHERE user_id = ? + AND rejected_at > datetime('now', '-90 days') + `).all(userId); + return new Set(rows.map(row => suggestionId(row.transaction_id, row.bill_id))); } function loadPriorMatchKeys(db, userId) {