mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-05 13:35:21 +02:00
Nested Categories (#1561)
* Prepare entry search for nested categories * Subcategory implementation * Remove caching for test stability
This commit is contained in:
parent
a4d10097d5
commit
77def1db40
31 changed files with 297 additions and 234 deletions
|
@ -60,6 +60,10 @@ class Account::Entry < ApplicationRecord
|
|||
end
|
||||
|
||||
class << self
|
||||
def search(params)
|
||||
Account::EntrySearch.new(params).build_query(all)
|
||||
end
|
||||
|
||||
# arbitrary cutoff date to avoid expensive sync operations
|
||||
def min_supported_date
|
||||
30.years.ago.to_date
|
||||
|
@ -141,49 +145,7 @@ class Account::Entry < ApplicationRecord
|
|||
Money.new(total, currency)
|
||||
end
|
||||
|
||||
def search(params)
|
||||
query = all
|
||||
query = query.where("account_entries.name ILIKE ?", "%#{sanitize_sql_like(params[:search])}%") if params[:search].present?
|
||||
query = query.where("account_entries.date >= ?", params[:start_date]) if params[:start_date].present?
|
||||
query = query.where("account_entries.date <= ?", params[:end_date]) if params[:end_date].present?
|
||||
|
||||
if params[:types].present?
|
||||
query = query.where(marked_as_transfer: false) unless params[:types].include?("transfer")
|
||||
|
||||
if params[:types].include?("income") && !params[:types].include?("expense")
|
||||
query = query.where("account_entries.amount < 0")
|
||||
elsif params[:types].include?("expense") && !params[:types].include?("income")
|
||||
query = query.where("account_entries.amount >= 0")
|
||||
end
|
||||
end
|
||||
|
||||
if params[:amount].present? && params[:amount_operator].present?
|
||||
case params[:amount_operator]
|
||||
when "equal"
|
||||
query = query.where("ABS(ABS(account_entries.amount) - ?) <= 0.01", params[:amount].to_f.abs)
|
||||
when "less"
|
||||
query = query.where("ABS(account_entries.amount) < ?", params[:amount].to_f.abs)
|
||||
when "greater"
|
||||
query = query.where("ABS(account_entries.amount) > ?", params[:amount].to_f.abs)
|
||||
end
|
||||
end
|
||||
|
||||
if params[:accounts].present? || params[:account_ids].present?
|
||||
query = query.joins(:account)
|
||||
end
|
||||
|
||||
query = query.where(accounts: { name: params[:accounts] }) if params[:accounts].present?
|
||||
query = query.where(accounts: { id: params[:account_ids] }) if params[:account_ids].present?
|
||||
|
||||
# Search attributes on each entryable to further refine results
|
||||
entryable_ids = entryable_search(params)
|
||||
query = query.where(entryable_id: entryable_ids) unless entryable_ids.nil?
|
||||
|
||||
query
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def entryable_search(params)
|
||||
entryable_ids = []
|
||||
entryable_search_performed = false
|
||||
|
|
59
app/models/account/entry_search.rb
Normal file
59
app/models/account/entry_search.rb
Normal file
|
@ -0,0 +1,59 @@
|
|||
class Account::EntrySearch
|
||||
include ActiveModel::Model
|
||||
include ActiveModel::Attributes
|
||||
|
||||
attribute :search, :string
|
||||
attribute :amount, :string
|
||||
attribute :amount_operator, :string
|
||||
attribute :types, :string
|
||||
attribute :accounts, :string
|
||||
attribute :account_ids, :string
|
||||
attribute :start_date, :string
|
||||
attribute :end_date, :string
|
||||
|
||||
class << self
|
||||
def from_entryable_search(entryable_search)
|
||||
new(entryable_search.attributes.slice(*attribute_names))
|
||||
end
|
||||
end
|
||||
|
||||
def build_query(scope)
|
||||
query = scope
|
||||
|
||||
query = query.where("account_entries.name ILIKE :search OR account_entries.enriched_name ILIKE :search",
|
||||
search: "%#{ActiveRecord::Base.sanitize_sql_like(search)}%"
|
||||
) if search.present?
|
||||
query = query.where("account_entries.date >= ?", start_date) if start_date.present?
|
||||
query = query.where("account_entries.date <= ?", end_date) if end_date.present?
|
||||
|
||||
if types.present?
|
||||
query = query.where(marked_as_transfer: false) unless types.include?("transfer")
|
||||
|
||||
if types.include?("income") && !types.include?("expense")
|
||||
query = query.where("account_entries.amount < 0")
|
||||
elsif types.include?("expense") && !types.include?("income")
|
||||
query = query.where("account_entries.amount >= 0")
|
||||
end
|
||||
end
|
||||
|
||||
if amount.present? && amount_operator.present?
|
||||
case amount_operator
|
||||
when "equal"
|
||||
query = query.where("ABS(ABS(account_entries.amount) - ?) <= 0.01", amount.to_f.abs)
|
||||
when "less"
|
||||
query = query.where("ABS(account_entries.amount) < ?", amount.to_f.abs)
|
||||
when "greater"
|
||||
query = query.where("ABS(account_entries.amount) > ?", amount.to_f.abs)
|
||||
end
|
||||
end
|
||||
|
||||
if accounts.present? || account_ids.present?
|
||||
query = query.joins(:account)
|
||||
end
|
||||
|
||||
query = query.where(accounts: { name: accounts }) if accounts.present?
|
||||
query = query.where(accounts: { id: account_ids }) if account_ids.present?
|
||||
|
||||
query
|
||||
end
|
||||
end
|
|
@ -8,26 +8,8 @@ class Account::Trade < ApplicationRecord
|
|||
validates :qty, presence: true
|
||||
validates :price, :currency, presence: true
|
||||
|
||||
class << self
|
||||
def search(_params)
|
||||
all
|
||||
end
|
||||
|
||||
def requires_search?(_params)
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def sell?
|
||||
qty < 0
|
||||
end
|
||||
|
||||
def buy?
|
||||
qty > 0
|
||||
end
|
||||
|
||||
def unrealized_gain_loss
|
||||
return nil if sell?
|
||||
return nil if qty.negative?
|
||||
current_price = security.current_price
|
||||
return nil if current_price.nil?
|
||||
|
||||
|
|
|
@ -12,52 +12,7 @@ class Account::Transaction < ApplicationRecord
|
|||
|
||||
class << self
|
||||
def search(params)
|
||||
query = all
|
||||
if params[:categories].present?
|
||||
if params[:categories].exclude?("Uncategorized")
|
||||
query = query
|
||||
.joins(:category)
|
||||
.where(categories: { name: params[:categories] })
|
||||
else
|
||||
query = query
|
||||
.left_joins(:category)
|
||||
.where(categories: { name: params[:categories] })
|
||||
.or(query.where(category_id: nil))
|
||||
end
|
||||
end
|
||||
|
||||
query = query.joins(:merchant).where(merchants: { name: params[:merchants] }) if params[:merchants].present?
|
||||
|
||||
if params[:tags].present?
|
||||
query = query.joins(:tags)
|
||||
.where(tags: { name: params[:tags] })
|
||||
.distinct
|
||||
end
|
||||
|
||||
query
|
||||
Account::TransactionSearch.new(params).build_query(all)
|
||||
end
|
||||
|
||||
def requires_search?(params)
|
||||
searchable_keys.any? { |key| params.key?(key) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def searchable_keys
|
||||
%i[categories merchants tags]
|
||||
end
|
||||
end
|
||||
|
||||
def eod_balance
|
||||
entry.amount_money
|
||||
end
|
||||
|
||||
private
|
||||
def account
|
||||
entry.account
|
||||
end
|
||||
|
||||
def daily_transactions
|
||||
account.entries.account_transactions
|
||||
end
|
||||
end
|
||||
|
|
42
app/models/account/transaction_search.rb
Normal file
42
app/models/account/transaction_search.rb
Normal file
|
@ -0,0 +1,42 @@
|
|||
class Account::TransactionSearch
|
||||
include ActiveModel::Model
|
||||
include ActiveModel::Attributes
|
||||
|
||||
attribute :search, :string
|
||||
attribute :amount, :string
|
||||
attribute :amount_operator, :string
|
||||
attribute :types, array: true
|
||||
attribute :accounts, array: true
|
||||
attribute :account_ids, array: true
|
||||
attribute :start_date, :string
|
||||
attribute :end_date, :string
|
||||
attribute :categories, array: true
|
||||
attribute :merchants, array: true
|
||||
attribute :tags, array: true
|
||||
|
||||
# Returns array of Account::Entry objects to stay consistent with partials, which only deal with Account::Entry
|
||||
def build_query(scope)
|
||||
query = scope
|
||||
|
||||
if categories.present?
|
||||
if categories.exclude?("Uncategorized")
|
||||
query = query
|
||||
.joins(:category)
|
||||
.where(categories: { name: categories })
|
||||
else
|
||||
query = query
|
||||
.left_joins(:category)
|
||||
.where(categories: { name: categories })
|
||||
.or(query.where(category_id: nil))
|
||||
end
|
||||
end
|
||||
|
||||
query = query.joins(:merchant).where(merchants: { name: merchants }) if merchants.present?
|
||||
|
||||
query = query.joins(:tags).where(tags: { name: tags }) if tags.present?
|
||||
|
||||
entries_scope = Account::Entry.account_transactions.where(entryable_id: query.select(:id))
|
||||
|
||||
Account::EntrySearch.from_entryable_search(self).build_query(entries_scope)
|
||||
end
|
||||
end
|
|
@ -1,13 +1,3 @@
|
|||
class Account::Valuation < ApplicationRecord
|
||||
include Account::Entryable
|
||||
|
||||
class << self
|
||||
def search(_params)
|
||||
all
|
||||
end
|
||||
|
||||
def requires_search?(_params)
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue