mirror of
https://github.com/we-promise/sure.git
synced 2026-08-05 16:42:18 +00:00
* fix(insights): respect recurring_transactions_disabled in subscription_audit SubscriptionAuditGenerator queried family.recurring_transactions directly, so disabling recurring-transaction detection (Settings -> Recurring Transactions) never stopped already-identified rows from surfacing "recurring charge overdue" insights on the Insights feed — the family-wide flag was already checked at both call sites in IdentifyRecurringTransactionsJob, just not here. Returning [] early is enough for existing insights to self-clean up: produced_types is a class-level declaration, so GenerateInsightsJob still counts subscription_audit as a succeeded type and expires any insight whose dedup_key wasn't regenerated on the next nightly run. * docs(insights): document why cash_flow_warning skips the recurring-disabled guard Answers jjmata's open review question. Unlike SubscriptionAuditGenerator, recurring transactions here are one input into a broader cash-flow projection, not the insight's entire subject — so it intentionally keeps using the last-known identified set rather than gating on family.recurring_transactions_disabled?. No behavior change.
53 lines
1.8 KiB
Ruby
53 lines
1.8 KiB
Ruby
# Surfaces recurring expenses that are well past their expected date — the
|
|
# provider may have raised the price, the charge may have moved, or the user
|
|
# may be paying for something that quietly stopped (or should stop).
|
|
class Insight::Generators::SubscriptionAuditGenerator < Insight::Generator
|
|
produces "subscription_audit"
|
|
|
|
OVERDUE_DAYS = 45
|
|
MAX_INSIGHTS = 3
|
|
|
|
def generate
|
|
return [] if family.recurring_transactions_disabled?
|
|
|
|
overdue_recurring.map do |recurring|
|
|
name = recurring.merchant&.name.presence || recurring.name
|
|
amount = Money.new(recurring.amount, recurring.currency).format
|
|
days_overdue = (Date.current - recurring.next_expected_date).to_i
|
|
|
|
build_insight(
|
|
insight_type: "subscription_audit",
|
|
priority: "medium",
|
|
title: I18n.t("insights.titles.subscription_audit", name: name),
|
|
template_key: "subscription_audit",
|
|
facts: {
|
|
name: name,
|
|
amount: amount,
|
|
days_overdue: days_overdue,
|
|
expected_on: I18n.l(recurring.next_expected_date)
|
|
},
|
|
# days_overdue is deliberately left out: it changes every night, and
|
|
# metadata drift would resurrect insights the user already dismissed.
|
|
metadata: {
|
|
recurring_transaction_id: recurring.id,
|
|
amount: round(recurring.amount, 2),
|
|
expected_on: recurring.next_expected_date.iso8601
|
|
},
|
|
dedup_key: "subscription_audit:#{recurring.id}"
|
|
)
|
|
end
|
|
end
|
|
|
|
private
|
|
def overdue_recurring
|
|
family.recurring_transactions
|
|
.includes(:merchant)
|
|
.active
|
|
.where(destination_account_id: nil)
|
|
.where("amount > 0")
|
|
.where("next_expected_date < ?", OVERDUE_DAYS.days.ago.to_date)
|
|
.order(:next_expected_date)
|
|
.limit(MAX_INSIGHTS)
|
|
end
|
|
end
|