fix(match-suggestions): use rejection timestamps and share late attribution helper
This commit is contained in:
parent
99abca9868
commit
a97d656e92
|
|
@ -16,6 +16,18 @@ function merchantMatches(txMerchant, ruleMerchant) {
|
||||||
wordBoundary(txMerchant).test(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.
|
// Persist a merchant→bill rule so future synced transactions auto-match.
|
||||||
function addMerchantRule(db, userId, billId, merchant) {
|
function addMerchantRule(db, userId, billId, merchant) {
|
||||||
const normalized = normalizeMerchant(merchant);
|
const normalized = normalizeMerchant(merchant);
|
||||||
|
|
@ -35,17 +47,6 @@ function addMerchantRule(db, userId, billId, merchant) {
|
||||||
// merchant rules, create payments, and mark the transactions matched.
|
// merchant rules, create payments, and mark the transactions matched.
|
||||||
// Returns { matched: number }.
|
// Returns { matched: number }.
|
||||||
function applyMerchantRules(db, userId) {
|
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;
|
let rules;
|
||||||
try {
|
try {
|
||||||
rules = db.prepare(`
|
rules = db.prepare(`
|
||||||
|
|
|
||||||
|
|
@ -218,7 +218,7 @@ async function runAllCleanup() {
|
||||||
// Prune match suggestion rejections older than 90 days
|
// Prune match suggestion rejections older than 90 days
|
||||||
try {
|
try {
|
||||||
const { changes } = getDb().prepare(
|
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();
|
).run();
|
||||||
tasks.suggestion_rejections = { pruned: changes };
|
tasks.suggestion_rejections = { pruned: changes };
|
||||||
} catch { tasks.suggestion_rejections = { pruned: 0 }; }
|
} catch { tasks.suggestion_rejections = { pruned: 0 }; }
|
||||||
|
|
|
||||||
|
|
@ -226,23 +226,13 @@ function loadBills(db, userId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadRejections(db, userId) {
|
function loadRejections(db, userId) {
|
||||||
try {
|
const rows = db.prepare(`
|
||||||
const rows = db.prepare(`
|
SELECT transaction_id, bill_id
|
||||||
SELECT transaction_id, bill_id
|
FROM match_suggestion_rejections
|
||||||
FROM match_suggestion_rejections
|
WHERE user_id = ?
|
||||||
WHERE user_id = ?
|
AND rejected_at > datetime('now', '-90 days')
|
||||||
AND created_at > datetime('now', '-90 days')
|
`).all(userId);
|
||||||
`).all(userId);
|
return new Set(rows.map(row => suggestionId(row.transaction_id, row.bill_id)));
|
||||||
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(); }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadPriorMatchKeys(db, userId) {
|
function loadPriorMatchKeys(db, userId) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue