mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-19 13:19:39 +02:00
* Domain model sketch
* Scaffold out rules domain
* Migrations
* Remove existing data enrichment for clean slate
* Sketch out business logic and basic tests
* Simplify rule scope building and action executions
* Get generator working again
* Basic implementation + tests
* Remove manual merchant management (rules will replace)
* Revert "Remove manual merchant management (rules will replace)"
This reverts commit 83dcbd9ff0
.
* Family and Provider merchants model
* Fix brakeman warnings
* Fix notification loader
* Update notification position
* Add Rule action and condition registries
* Rule form with compound conditions and tests
* Split out notification types, add CTA type
* Rules form builder and Stimulus controller
* Clean up rule registry domain
* Clean up rules stimulus controller
* CTA message for rule when user changes transaction category
* Fix tests
* Lint updates
* Centralize notifications in Notifiable concern
* Implement category rule prompts with auto backoff and option to disable
* Fix layout bug caused by merge conflict
* Initialize rule with correct action for category CTA
* Add rule deletions, get rules working
* Complete dynamic rule form, split Stimulus controllers by resource
* Fix failing tests
* Change test password to avoid chromium conflicts
* Update integration tests
* Centralize all test password references
* Add re-apply rule action
* Rule confirm modal
* Run migrations
* Trigger rule notification after inline category updates
* Clean up rule styles
* Basic attribute locking for rules
* Apply attribute locks on user edits
* Log data enrichments, only apply rules to unlocked attributes
* Fix merge errors
* Additional merge conflict fixes
* Form UI improvements, ignore attribute locks on manual rule application
* Batch AI auto-categorization of transactions
* Auto merchant detection, ai enrichment in batches
* Fix Plaid merchant assignments
* Plaid category matching
* Cleanup 1
* Test cleanup
* Remove stale route
* Fix desktop chat UI issues
* Fix mobile nav styling issues
129 lines
4.1 KiB
Ruby
129 lines
4.1 KiB
Ruby
class Category < ApplicationRecord
|
|
has_many :transactions, dependent: :nullify, class_name: "Transaction"
|
|
has_many :import_mappings, as: :mappable, dependent: :destroy, class_name: "Import::Mapping"
|
|
|
|
belongs_to :family
|
|
|
|
has_many :budget_categories, dependent: :destroy
|
|
has_many :subcategories, class_name: "Category", foreign_key: :parent_id, dependent: :nullify
|
|
belongs_to :parent, class_name: "Category", optional: true
|
|
|
|
validates :name, :color, :lucide_icon, :family, presence: true
|
|
validates :name, uniqueness: { scope: :family_id }
|
|
|
|
validate :category_level_limit
|
|
validate :nested_category_matches_parent_classification
|
|
|
|
before_save :inherit_color_from_parent
|
|
|
|
scope :alphabetically, -> { order(:name) }
|
|
scope :roots, -> { where(parent_id: nil) }
|
|
scope :incomes, -> { where(classification: "income") }
|
|
scope :expenses, -> { where(classification: "expense") }
|
|
|
|
COLORS = %w[#e99537 #4da568 #6471eb #db5a54 #df4e92 #c44fe9 #eb5429 #61c9ea #805dee #6ad28a]
|
|
|
|
UNCATEGORIZED_COLOR = "#737373"
|
|
TRANSFER_COLOR = "#444CE7"
|
|
PAYMENT_COLOR = "#db5a54"
|
|
TRADE_COLOR = "#e99537"
|
|
|
|
class Group
|
|
attr_reader :category, :subcategories
|
|
|
|
delegate :name, :color, to: :category
|
|
|
|
def self.for(categories)
|
|
categories.select { |category| category.parent_id.nil? }.map do |category|
|
|
new(category, category.subcategories)
|
|
end
|
|
end
|
|
|
|
def initialize(category, subcategories = nil)
|
|
@category = category
|
|
@subcategories = subcategories || []
|
|
end
|
|
end
|
|
|
|
class << self
|
|
def icon_codes
|
|
%w[bus circle-dollar-sign ambulance apple award baby battery lightbulb bed-single beer bluetooth book briefcase building credit-card camera utensils cooking-pot cookie dices drama dog drill drum dumbbell gamepad-2 graduation-cap house hand-helping ice-cream-cone phone piggy-bank pill pizza printer puzzle ribbon shopping-cart shield-plus ticket trees]
|
|
end
|
|
|
|
def bootstrap!
|
|
default_categories.each do |name, color, icon, classification|
|
|
find_or_create_by!(name: name) do |category|
|
|
category.color = color
|
|
category.classification = classification
|
|
category.lucide_icon = icon
|
|
end
|
|
end
|
|
end
|
|
|
|
def uncategorized
|
|
new(
|
|
name: "Uncategorized",
|
|
color: UNCATEGORIZED_COLOR,
|
|
lucide_icon: "circle-dashed"
|
|
)
|
|
end
|
|
|
|
private
|
|
def default_categories
|
|
[
|
|
[ "Income", "#e99537", "circle-dollar-sign", "income" ],
|
|
[ "Loan Payments", "#6471eb", "credit-card", "expense" ],
|
|
[ "Fees", "#6471eb", "credit-card", "expense" ],
|
|
[ "Entertainment", "#df4e92", "drama", "expense" ],
|
|
[ "Food & Drink", "#eb5429", "utensils", "expense" ],
|
|
[ "Shopping", "#e99537", "shopping-cart", "expense" ],
|
|
[ "Home Improvement", "#6471eb", "house", "expense" ],
|
|
[ "Healthcare", "#4da568", "pill", "expense" ],
|
|
[ "Personal Care", "#4da568", "pill", "expense" ],
|
|
[ "Services", "#4da568", "briefcase", "expense" ],
|
|
[ "Gifts & Donations", "#61c9ea", "hand-helping", "expense" ],
|
|
[ "Transportation", "#df4e92", "bus", "expense" ],
|
|
[ "Travel", "#df4e92", "plane", "expense" ],
|
|
[ "Rent & Utilities", "#db5a54", "lightbulb", "expense" ]
|
|
]
|
|
end
|
|
end
|
|
|
|
def inherit_color_from_parent
|
|
if subcategory?
|
|
self.color = parent.color
|
|
end
|
|
end
|
|
|
|
def replace_and_destroy!(replacement)
|
|
transaction do
|
|
transactions.update_all category_id: replacement&.id
|
|
destroy!
|
|
end
|
|
end
|
|
|
|
def parent?
|
|
subcategories.any?
|
|
end
|
|
|
|
def subcategory?
|
|
parent.present?
|
|
end
|
|
|
|
private
|
|
def category_level_limit
|
|
if (subcategory? && parent.subcategory?) || (parent? && subcategory?)
|
|
errors.add(:parent, "can't have more than 2 levels of subcategories")
|
|
end
|
|
end
|
|
|
|
def nested_category_matches_parent_classification
|
|
if subcategory? && parent.classification != classification
|
|
errors.add(:parent, "must have the same classification as its parent")
|
|
end
|
|
end
|
|
|
|
def monetizable_currency
|
|
family.currency
|
|
end
|
|
end
|