diff --git a/HISTORY.md b/HISTORY.md index e4ac013..a1046c7 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -3,6 +3,7 @@ ### π Tracker & bill-modal hardening +- **[Notifications] "Reminder days before" was a dead setting β the notifier ignored it** β every bill has a `reminder_days_before` column (default 3) and the bill modal exposed a "Reminder Days" control for subscriptions, but `services/notificationService.js` used a hard-coded schedule (early reminder always at exactly 3 days out) and never read the column. A user who set "remind me 7 days before" still only got the fixed 3-day/1-day/today reminders. The early reminder now fires at the bill's own `reminder_days_before` lead (only when β₯ 2 days, so it never collides with the 1-day/same-day reminders), and the email subject + body say "due in N days" using that value. The lead-time selection was pulled into a pure, exported `reminderTypeFor(bill, diffDays)` so it's unit-tested directly (`tests/notificationLeadTime.test.js`) β default 3 stays backwards-compatible. The **"Reminder Days Before" control now shows for every bill** (not just subscriptions), and saving a non-subscription bill no longer clobbers the column back to 3. (Tracker BM3) - **[Bill modal/SimpleFIN] Importing bank payments didn't refresh the payment list or the Tracker** β the two flows in the bill modal that *create* payments β **Sync** (`syncBillSimplefinPayments`) and a **merchant-rule historical import** (`onRulesChanged` β `importHistoricalPayments`) β only reloaded the linked-transactions list, unlike the unmatch handlers which correctly reload payments *and* linked transactions *and* call `onSave`. So after importing, say, 3 payments from bank history, the modal's Payment History stayed stale and the Tracker row behind it kept showing "due/overdue" even though the bill was now covered β until you closed and reopened. Both paths now `await Promise.all([loadPayments(), loadLinkedTransactions()])` then `onSave?.()`, matching the unmatch pattern, so imported payments appear immediately and the Tracker updates live. (The SimpleFIN *search/preview/candidate* flow was already correct.) (Tracker BM4) - **[Tracker/SimpleFIN] Bank card's "unpaid this month" and "remaining" over-counted off-month bills** β `buildBankTracking` (`services/trackerService.js`) summed `expected_amount` for *all* active unpaid bills via SQL with no occurrence gate, so an annual or off-month quarterly bill inflated `unpaid_this_month` (and therefore the bank `remaining`) even though the Tracker rows beside it correctly excluded it β the same class of bug as QA-B5-02, still live on the bank path. `getTracker` now derives the unpaid total from the already-gated rows (via `resolveDueDate`), netting partial payments, and passes it into `buildBankTracking`. Also made `summary.remaining` / `total_remaining` use the bank card's own remaining when bank tracking is on (they previously used manual starting-amount math even in bank mode, disagreeing with safe-to-spend), and switched a stray `balance / 100` to `fromCents`. New test file `tests/trackerService.test.js` covers the gating fix, summary totals, the bank-mode remaining agreement, centsβdollars integrity, and `getOverdueCount` gating β the dense Tracker aggregation had no dedicated tests before. (Tracker T1) - **[Payments] Quick-pay could create duplicate payments and double-drop the balance** β `POST /api/payments/quick` (the one-click "pay" behind every Tracker row) had **no duplicate guard** and its INSERT + balance update weren't atomic, unlike `POST /api/payments/bulk`. A double-click, a retry, or two open tabs made a *second* payment for the same bill/date/amount and applied the balance drop twice; a failure between the INSERT and the balance write left a payment with no balance adjustment. Quick-pay now checks the same `bill_id + paid_date + amount` composite key (returning the existing payment idempotently, HTTP 200) and wraps the INSERT + `applyBalanceDelta` in a single `db.transaction`. A different amount on the same day is still a legitimate new payment. Test: `tests/paymentsQuickRoute.test.js`. (Tracker X1) diff --git a/client/components/BillModal.jsx b/client/components/BillModal.jsx index 3cda120..d64864b 100644 --- a/client/components/BillModal.jsx +++ b/client/components/BillModal.jsx @@ -559,7 +559,7 @@ export default function BillModal({ bill, initialBill, categories, onClose, onSa auto_mark_paid: canAutoMarkPaid && autoMarkPaid, is_subscription: isSubscription, subscription_type: isSubscription ? subscriptionType : null, - reminder_days_before: isSubscription ? parseInt(reminderDaysBefore || '3', 10) : 3, + reminder_days_before: parseInt(reminderDaysBefore || '3', 10), subscription_source: sourceBill?.subscription_source || 'manual', subscription_detected_at: sourceBill?.subscription_detected_at, has_2fa: has2fa, @@ -788,8 +788,8 @@ export default function BillModal({ bill, initialBill, categories, onClose, onSa {isSubscription && ( -
0-30 days before renewal.
-+ Get an early reminder this many days before the due date (0-30). Also needs reminders enabled in Settings. +
+