mirror of
https://github.com/we-promise/sure.git
synced 2026-05-12 23:25:00 +00:00
* Extract Entry.uncategorized_transactions scope, remove Family#uncategorized_transaction_count Adds a single Entry.uncategorized_transactions scope containing the shared conditions (transactions join, active accounts, category nil, not transfer kinds, not excluded). All callers now use this scope: - Entry.uncategorized_matching builds on it - Transaction::Grouper::ByMerchantOrName#uncategorized_entries uses it - categorizes_controller#uncategorized_entries_for uses it (also fixes missing status/excluded filters that were silently absent before) - Both controllers replace Current.family.uncategorized_transaction_count with Current.accessible_entries.uncategorized_transactions.count so the button count and wizard count both respect account sharing Family#uncategorized_transaction_count removed as it is now unused and was family-scoped rather than user-scoped. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Scope assign_entry write to Current.accessible_entries Replaces unscoped Entry.where(id:) with Current.accessible_entries.where(id:) so the write path is consistent with the find above it. Not exploitable given the find would 404 first, but removes the pattern inconsistency. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add privacy-sensitive class to amounts in categorize wizard Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Extract uncategorized_count helper in CategorizesController Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix comment on uncategorized_transactions scope to mention draft accounts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Use uncategorized_count helper in assign_entry action Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
49 lines
1.2 KiB
Ruby
49 lines
1.2 KiB
Ruby
class Transaction::Grouper::ByMerchantOrName < Transaction::Grouper
|
|
def self.call(entries, limit: 20, offset: 0)
|
|
new(entries).call(limit: limit, offset: offset)
|
|
end
|
|
|
|
def initialize(entries)
|
|
@entries = entries
|
|
end
|
|
|
|
def call(limit: 20, offset: 0)
|
|
uncategorized_entries
|
|
.group_by { |entry| grouping_key_for(entry) }
|
|
.map { |key, entries| build_group(key, entries) }
|
|
.sort_by { |g| [ -g.entries.size, g.display_name ] }
|
|
.drop([ offset, 0 ].max)
|
|
.first(limit)
|
|
end
|
|
|
|
private
|
|
|
|
attr_reader :entries
|
|
|
|
def uncategorized_entries
|
|
entries
|
|
.uncategorized_transactions
|
|
.includes(entryable: :merchant)
|
|
.order(entries: { date: :desc })
|
|
end
|
|
|
|
def grouping_key_for(entry)
|
|
name = entry.entryable.merchant&.name.presence || entry.name
|
|
type = entry.amount.negative? ? "income" : "expense"
|
|
[ name, type ]
|
|
end
|
|
|
|
def build_group(key, entries)
|
|
name, type = key
|
|
merchant = entries.find { |e| e.entryable.merchant.present? }&.entryable&.merchant
|
|
|
|
Transaction::Grouper::Group.new(
|
|
grouping_key: name,
|
|
display_name: name,
|
|
entries: entries,
|
|
merchant: merchant,
|
|
transaction_type: type
|
|
)
|
|
end
|
|
end
|