2024-06-20 08:15:09 -04:00
class Category < ApplicationRecord
2025-04-14 11:40:34 -04:00
has_many :transactions , dependent : :nullify , class_name : " Transaction "
2024-10-01 10:47:59 -04:00
has_many :import_mappings , as : :mappable , dependent : :destroy , class_name : " Import::Mapping "
2024-12-20 11:37:26 -05:00
2024-03-07 19:15:50 +01:00
belongs_to :family
2025-01-16 14:36:37 -05:00
has_many :budget_categories , dependent : :destroy
2025-02-20 15:03:30 +01:00
has_many :subcategories , class_name : " Category " , foreign_key : :parent_id , dependent : :nullify
2024-12-20 11:37:26 -05:00
belongs_to :parent , class_name : " Category " , optional : true
2025-02-24 21:08:05 +05:00
validates :name , :color , :lucide_icon , :family , presence : true
2024-10-24 11:02:27 -04:00
validates :name , uniqueness : { scope : :family_id }
2024-03-15 12:21:59 -07:00
2024-12-20 11:37:26 -05:00
validate :category_level_limit
2025-01-16 14:36:37 -05:00
validate :nested_category_matches_parent_classification
2024-03-07 19:15:50 +01:00
2025-02-24 21:08:05 +05:00
before_save :inherit_color_from_parent
2025-01-30 16:49:31 -05:00
2024-04-16 12:44:31 -06:00
scope :alphabetically , - > { order ( :name ) }
2025-01-30 14:35:30 -03:00
scope :roots , - > { where ( parent_id : nil ) }
2025-01-16 14:36:37 -05:00
scope :incomes , - > { where ( classification : " income " ) }
scope :expenses , - > { where ( classification : " expense " ) }
2024-04-16 12:44:31 -06:00
2024-04-04 17:29:50 -04:00
COLORS = %w[ # e99537 # 4da568 # 6471eb # db5a54 # df4e92 # c44fe9 # eb5429 # 61c9ea # 805dee # 6ad28a ]
UNCATEGORIZED_COLOR = " # 737373 "
2025-01-07 09:41:24 -05:00
TRANSFER_COLOR = " # 444CE7 "
PAYMENT_COLOR = " # db5a54 "
TRADE_COLOR = " # e99537 "
2024-04-04 17:29:50 -04:00
2024-12-20 11:37:26 -05:00
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
2025-01-16 14:36:37 -05:00
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
2024-12-20 11:37:26 -05:00
def bootstrap_defaults
2025-01-16 14:36:37 -05:00
default_categories . each do | name , color , icon |
2024-12-20 11:37:26 -05:00
find_or_create_by! ( name : name ) do | category |
category . color = color
2025-01-16 14:36:37 -05:00
category . classification = " income " if name == " Income "
category . lucide_icon = icon
2024-12-20 11:37:26 -05:00
end
end
2024-03-07 19:15:50 +01:00
end
2025-01-16 14:36:37 -05:00
def uncategorized
new (
name : " Uncategorized " ,
color : UNCATEGORIZED_COLOR ,
lucide_icon : " circle-dashed "
)
end
2024-12-20 11:37:26 -05:00
private
def default_categories
[
2025-01-16 14:36:37 -05:00
[ " Income " , " # e99537 " , " circle-dollar-sign " ] ,
[ " Housing " , " # 6471eb " , " house " ] ,
[ " Entertainment " , " # df4e92 " , " drama " ] ,
[ " Food & Drink " , " # eb5429 " , " utensils " ] ,
[ " Shopping " , " # e99537 " , " shopping-cart " ] ,
[ " Healthcare " , " # 4da568 " , " pill " ] ,
[ " Insurance " , " # 6471eb " , " piggy-bank " ] ,
[ " Utilities " , " # db5a54 " , " lightbulb " ] ,
[ " Transportation " , " # df4e92 " , " bus " ] ,
[ " Education " , " # eb5429 " , " book " ] ,
[ " Gifts & Donations " , " # 61c9ea " , " hand-helping " ] ,
[ " Subscriptions " , " # 805dee " , " credit-card " ]
2024-12-20 11:37:26 -05:00
]
end
2024-03-07 19:15:50 +01:00
end
2025-01-30 16:49:31 -05:00
def inherit_color_from_parent
if subcategory?
self . color = parent . color
end
end
2024-05-02 07:24:31 -06:00
def replace_and_destroy! ( replacement )
transaction do
transactions . update_all category_id : replacement & . id
destroy!
end
end
2025-01-30 14:35:30 -03:00
def parent?
subcategories . any?
end
2024-12-20 11:37:26 -05:00
def subcategory?
parent . present?
end
2024-03-07 19:15:50 +01:00
2024-12-20 11:37:26 -05:00
private
def category_level_limit
2025-01-30 14:35:30 -03:00
if ( subcategory? && parent . subcategory? ) || ( parent? && subcategory? )
2024-12-20 11:37:26 -05:00
errors . add ( :parent , " can't have more than 2 levels of subcategories " )
end
2024-08-23 10:06:24 -04:00
end
2025-01-16 14:36:37 -05:00
def nested_category_matches_parent_classification
if subcategory? && parent . classification != classification
errors . add ( :parent , " must have the same classification as its parent " )
end
end
2025-02-21 11:57:59 -05:00
def monetizable_currency
family . currency
end
2024-03-07 19:15:50 +01:00
end