diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index f0c41dc53..db76e9d1d 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -154,7 +154,9 @@ class PagesController < ApplicationController percentage = total_income.zero? ? 0 : (val / total_income * 100).round(1) color = ct.category.color.presence || Category::COLORS.sample - idx = add_node.call("income_#{ct.category.id}", ct.category.name, val, percentage, color) + # Use name as fallback key for synthetic categories (no id) + node_key = "income_#{ct.category.id || ct.category.name}" + idx = add_node.call(node_key, ct.category.name, val, percentage, color) links << { source: idx, target: cash_flow_idx, value: val, color: color, percentage: percentage } end @@ -168,7 +170,9 @@ class PagesController < ApplicationController percentage = total_expense.zero? ? 0 : (val / total_expense * 100).round(1) color = ct.category.color.presence || Category::UNCATEGORIZED_COLOR - idx = add_node.call("expense_#{ct.category.id}", ct.category.name, val, percentage, color) + # Use name as fallback key for synthetic categories (no id) + node_key = "expense_#{ct.category.id || ct.category.name}" + idx = add_node.call(node_key, ct.category.name, val, percentage, color) links << { source: cash_flow_idx, target: idx, value: val, color: color, percentage: percentage } end @@ -198,7 +202,8 @@ class PagesController < ApplicationController currency: ct.currency, percentage: ct.weight.round(1), color: ct.category.color.presence || Category::UNCATEGORIZED_COLOR, - icon: ct.category.lucide_icon + icon: ct.category.lucide_icon, + clickable: !ct.category.other_investments? } end diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 8062a1ca4..bfb833acb 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -31,8 +31,8 @@ class ReportsController < ApplicationController # Build trend data (last 6 months) @trends_data = build_trends_data - # Spending patterns (weekday vs weekend) - @spending_patterns = build_spending_patterns + # Net worth metrics + @net_worth_metrics = build_net_worth_metrics # Transactions breakdown @transactions = build_transactions_breakdown @@ -124,11 +124,19 @@ class ReportsController < ApplicationController def build_reports_sections all_sections = [ + { + key: "net_worth", + title: "reports.net_worth.title", + partial: "reports/net_worth", + locals: { net_worth_metrics: @net_worth_metrics }, + visible: Current.family.accounts.any?, + collapsible: true + }, { key: "trends_insights", title: "reports.trends.title", partial: "reports/trends_insights", - locals: { trends_data: @trends_data, spending_patterns: @spending_patterns }, + locals: { trends_data: @trends_data }, visible: Current.family.transactions.any?, collapsible: true }, @@ -310,61 +318,6 @@ class ReportsController < ApplicationController trends end - def build_spending_patterns - # Analyze weekday vs weekend spending - weekday_total = 0 - weekend_total = 0 - weekday_count = 0 - weekend_count = 0 - - # Build query matching income_statement logic: - # Expenses are transactions with positive amounts, regardless of category - expense_transactions = Transaction - .joins(:entry) - .joins(entry: :account) - .where(accounts: { family_id: Current.family.id, status: [ "draft", "active" ] }) - .where(entries: { entryable_type: "Transaction", excluded: false, date: @period.date_range }) - .where(kind: [ "standard", "loan_payment" ]) - .where("entries.amount > 0") # Positive amount = expense (matching income_statement logic) - - # Sum up amounts by weekday vs weekend - expense_transactions.each do |transaction| - entry = transaction.entry - amount = entry.amount.abs - - if entry.date.wday.in?([ 0, 6 ]) # Sunday or Saturday - weekend_total += amount - weekend_count += 1 - else - weekday_total += amount - weekday_count += 1 - end - end - - weekday_avg = weekday_count.positive? ? (weekday_total / weekday_count) : 0 - weekend_avg = weekend_count.positive? ? (weekend_total / weekend_count) : 0 - - { - weekday_total: weekday_total, - weekend_total: weekend_total, - weekday_avg: weekday_avg, - weekend_avg: weekend_avg, - weekday_count: weekday_count, - weekend_count: weekend_count - } - end - - def default_spending_patterns - { - weekday_total: 0, - weekend_total: 0, - weekday_avg: 0, - weekend_avg: 0, - weekday_count: 0, - weekend_count: 0 - } - end - def build_transactions_breakdown # Base query: all transactions in the period # Exclude transfers, one-time, and CC payments (matching income_statement logic) @@ -379,25 +332,55 @@ class ReportsController < ApplicationController # Apply filters transactions = apply_transaction_filters(transactions) + # Get trades in the period (matching income_statement logic) + trades = Trade + .joins(:entry) + .joins(entry: :account) + .where(accounts: { family_id: Current.family.id, status: [ "draft", "active" ] }) + .where(entries: { entryable_type: "Trade", excluded: false, date: @period.date_range }) + .includes(entry: :account, category: []) + # Get sort parameters sort_by = params[:sort_by] || "amount" sort_direction = params[:sort_direction] || "desc" # Group by category and type - all_transactions = transactions.to_a grouped_data = {} + family_currency = Current.family.currency - all_transactions.each do |transaction| + # Process transactions + transactions.each do |transaction| entry = transaction.entry is_expense = entry.amount > 0 type = is_expense ? "expense" : "income" category_name = transaction.category&.name || "Uncategorized" - category_color = transaction.category&.color || "#9CA3AF" + category_color = transaction.category&.color || Category::UNCATEGORIZED_COLOR + + # Convert to family currency + converted_amount = Money.new(entry.amount.abs, entry.currency).exchange_to(family_currency, fallback_rate: 1).amount key = [ category_name, type, category_color ] grouped_data[key] ||= { total: 0, count: 0 } grouped_data[key][:count] += 1 - grouped_data[key][:total] += entry.amount.abs + grouped_data[key][:total] += converted_amount + end + + # Process trades + trades.each do |trade| + entry = trade.entry + is_expense = entry.amount > 0 + type = is_expense ? "expense" : "income" + # Use "Other Investments" for trades without category + category_name = trade.category&.name || Category.other_investments_name + category_color = trade.category&.color || Category::OTHER_INVESTMENTS_COLOR + + # Convert to family currency + converted_amount = Money.new(entry.amount.abs, entry.currency).exchange_to(family_currency, fallback_rate: 1).amount + + key = [ category_name, type, category_color ] + grouped_data[key] ||= { total: 0, count: 0 } + grouped_data[key][:count] += 1 + grouped_data[key][:total] += converted_amount end # Convert to array @@ -438,6 +421,39 @@ class ReportsController < ApplicationController } end + def build_net_worth_metrics + balance_sheet = Current.family.balance_sheet + currency = Current.family.currency + + # Current net worth + current_net_worth = balance_sheet.net_worth + total_assets = balance_sheet.assets.total + total_liabilities = balance_sheet.liabilities.total + + # Get net worth series for the period to calculate change + # The series.trend gives us the change from first to last value in the period + net_worth_series = balance_sheet.net_worth_series(period: @period) + trend = net_worth_series&.trend + + # Get asset and liability groups for breakdown + asset_groups = balance_sheet.assets.account_groups.map do |group| + { name: group.name, total: Money.new(group.total, currency) } + end.reject { |g| g[:total].zero? } + + liability_groups = balance_sheet.liabilities.account_groups.map do |group| + { name: group.name, total: Money.new(group.total, currency) } + end.reject { |g| g[:total].zero? } + + { + current_net_worth: Money.new(current_net_worth, currency), + total_assets: Money.new(total_assets, currency), + total_liabilities: Money.new(total_liabilities, currency), + trend: trend, + asset_groups: asset_groups, + liability_groups: liability_groups + } + end + def apply_transaction_filters(transactions) # Filter by category (including subcategories) if params[:filter_category_id].present? @@ -533,9 +549,19 @@ class ReportsController < ApplicationController transactions = apply_transaction_filters(transactions) - # Group transactions by category, type, and month - breakdown = {} + # Get trades in the period (matching income_statement logic) + trades = Trade + .joins(:entry) + .joins(entry: :account) + .where(accounts: { family_id: Current.family.id, status: [ "draft", "active" ] }) + .where(entries: { entryable_type: "Trade", excluded: false, date: @period.date_range }) + .includes(entry: :account, category: []) + # Group by category, type, and month + breakdown = {} + family_currency = Current.family.currency + + # Process transactions transactions.each do |transaction| entry = transaction.entry is_expense = entry.amount > 0 @@ -543,11 +569,33 @@ class ReportsController < ApplicationController category_name = transaction.category&.name || "Uncategorized" month_key = entry.date.beginning_of_month + # Convert to family currency + converted_amount = Money.new(entry.amount.abs, entry.currency).exchange_to(family_currency, fallback_rate: 1).amount + key = [ category_name, type ] breakdown[key] ||= { category: category_name, type: type, months: {}, total: 0 } breakdown[key][:months][month_key] ||= 0 - breakdown[key][:months][month_key] += entry.amount.abs - breakdown[key][:total] += entry.amount.abs + breakdown[key][:months][month_key] += converted_amount + breakdown[key][:total] += converted_amount + end + + # Process trades + trades.each do |trade| + entry = trade.entry + is_expense = entry.amount > 0 + type = is_expense ? "expense" : "income" + # Use "Other Investments" for trades without category + category_name = trade.category&.name || Category.other_investments_name + month_key = entry.date.beginning_of_month + + # Convert to family currency + converted_amount = Money.new(entry.amount.abs, entry.currency).exchange_to(family_currency, fallback_rate: 1).amount + + key = [ category_name, type ] + breakdown[key] ||= { category: category_name, type: type, months: {}, total: 0 } + breakdown[key][:months][month_key] ||= 0 + breakdown[key][:months][month_key] += converted_amount + breakdown[key][:total] += converted_amount end # Convert to array and sort by type and total (descending) diff --git a/app/models/category.rb b/app/models/category.rb index 1a50db9ae..9561cd4ee 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -31,10 +31,15 @@ class Category < ApplicationRecord COLORS = %w[#e99537 #4da568 #6471eb #db5a54 #df4e92 #c44fe9 #eb5429 #61c9ea #805dee #6ad28a] UNCATEGORIZED_COLOR = "#737373" + OTHER_INVESTMENTS_COLOR = "#e99537" TRANSFER_COLOR = "#444CE7" PAYMENT_COLOR = "#db5a54" TRADE_COLOR = "#e99537" + # Synthetic category name keys for i18n + UNCATEGORIZED_NAME_KEY = "models.category.uncategorized" + OTHER_INVESTMENTS_NAME_KEY = "models.category.other_investments" + class Group attr_reader :category, :subcategories @@ -82,12 +87,30 @@ class Category < ApplicationRecord def uncategorized new( - name: "Uncategorized", + name: I18n.t(UNCATEGORIZED_NAME_KEY), color: UNCATEGORIZED_COLOR, lucide_icon: "circle-dashed" ) end + def other_investments + new( + name: I18n.t(OTHER_INVESTMENTS_NAME_KEY), + color: OTHER_INVESTMENTS_COLOR, + lucide_icon: "trending-up" + ) + end + + # Helper to get the localized name for uncategorized + def uncategorized_name + I18n.t(UNCATEGORIZED_NAME_KEY) + end + + # Helper to get the localized name for other investments + def other_investments_name + I18n.t(OTHER_INVESTMENTS_NAME_KEY) + end + private def default_categories [ @@ -142,6 +165,21 @@ class Category < ApplicationRecord subcategory? ? "#{parent.name} > #{name}" : name end + # Predicate: is this the synthetic "Uncategorized" category? + def uncategorized? + !persisted? && name == I18n.t(UNCATEGORIZED_NAME_KEY) + end + + # Predicate: is this the synthetic "Other Investments" category? + def other_investments? + !persisted? && name == I18n.t(OTHER_INVESTMENTS_NAME_KEY) + end + + # Predicate: is this any synthetic (non-persisted) category? + def synthetic? + uncategorized? || other_investments? + end + private def category_level_limit if (subcategory? && parent.subcategory?) || (parent? && subcategory?) diff --git a/app/models/income_statement.rb b/app/models/income_statement.rb index 17db4cdcb..6c4e16b90 100644 --- a/app/models/income_statement.rb +++ b/app/models/income_statement.rb @@ -68,13 +68,22 @@ class IncomeStatement classification_total = totals.sum(&:total) uncategorized_category = family.categories.uncategorized + other_investments_category = family.categories.other_investments - category_totals = [ *categories, uncategorized_category ].map do |category| + category_totals = [ *categories, uncategorized_category, other_investments_category ].map do |category| subcategory = categories.find { |c| c.id == category.parent_id } - parent_category_total = totals.select { |t| t.category_id == category.id }&.sum(&:total) || 0 + parent_category_total = if category.uncategorized? + # Regular uncategorized: NULL category_id and NOT uncategorized investment + totals.select { |t| t.category_id.nil? && !t.is_uncategorized_investment }&.sum(&:total) || 0 + elsif category.other_investments? + # Other investments: NULL category_id AND is_uncategorized_investment + totals.select { |t| t.category_id.nil? && t.is_uncategorized_investment }&.sum(&:total) || 0 + else + totals.select { |t| t.category_id == category.id }&.sum(&:total) || 0 + end - children_totals = if category == uncategorized_category + children_totals = if category.synthetic? 0 else totals.select { |t| t.parent_category_id == category.id }&.sum(&:total) || 0 diff --git a/app/models/income_statement/totals.rb b/app/models/income_statement/totals.rb index 7d4726b3a..355212486 100644 --- a/app/models/income_statement/totals.rb +++ b/app/models/income_statement/totals.rb @@ -15,13 +15,14 @@ class IncomeStatement::Totals category_id: row["category_id"], classification: row["classification"], total: row["total"], - transactions_count: row["transactions_count"] + transactions_count: row["transactions_count"], + is_uncategorized_investment: row["is_uncategorized_investment"] ) end end private - TotalsRow = Data.define(:parent_category_id, :category_id, :classification, :total, :transactions_count) + TotalsRow = Data.define(:parent_category_id, :category_id, :classification, :total, :transactions_count, :is_uncategorized_investment) def query_sql ActiveRecord::Base.sanitize_sql_array([ @@ -37,6 +38,7 @@ class IncomeStatement::Totals category_id, parent_category_id, classification, + is_uncategorized_investment, SUM(total) as total, SUM(entry_count) as transactions_count FROM ( @@ -44,7 +46,7 @@ class IncomeStatement::Totals UNION ALL #{trades_subquery_sql} ) combined - GROUP BY category_id, parent_category_id, classification; + GROUP BY category_id, parent_category_id, classification, is_uncategorized_investment; SQL end @@ -56,7 +58,8 @@ class IncomeStatement::Totals c.parent_id as parent_category_id, CASE WHEN ae.amount < 0 THEN 'income' ELSE 'expense' END as classification, ABS(SUM(ae.amount * COALESCE(er.rate, 1))) as total, - COUNT(ae.id) as transactions_count + COUNT(ae.id) as transactions_count, + false as is_uncategorized_investment FROM (#{@transactions_scope.to_sql}) at JOIN entries ae ON ae.entryable_id = at.id AND ae.entryable_type = 'Transaction' JOIN accounts a ON a.id = ae.account_id @@ -81,7 +84,8 @@ class IncomeStatement::Totals c.parent_id as parent_category_id, CASE WHEN ae.amount < 0 THEN 'income' ELSE 'expense' END as classification, ABS(SUM(ae.amount * COALESCE(er.rate, 1))) as total, - COUNT(ae.id) as entry_count + COUNT(ae.id) as entry_count, + false as is_uncategorized_investment FROM (#{@transactions_scope.to_sql}) at JOIN entries ae ON ae.entryable_id = at.id AND ae.entryable_type = 'Transaction' JOIN accounts a ON a.id = ae.account_id @@ -101,14 +105,15 @@ class IncomeStatement::Totals def trades_subquery_sql # Get trades for the same family and date range as transactions - # Only include trades that have a category assigned + # Trades without categories appear as "Uncategorized Investments" (separate from regular uncategorized) <<~SQL SELECT c.id as category_id, c.parent_id as parent_category_id, CASE WHEN ae.amount < 0 THEN 'income' ELSE 'expense' END as classification, ABS(SUM(ae.amount * COALESCE(er.rate, 1))) as total, - COUNT(ae.id) as entry_count + COUNT(ae.id) as entry_count, + CASE WHEN t.category_id IS NULL THEN true ELSE false END as is_uncategorized_investment FROM trades t JOIN entries ae ON ae.entryable_id = t.id AND ae.entryable_type = 'Trade' JOIN accounts a ON a.id = ae.account_id @@ -122,8 +127,7 @@ class IncomeStatement::Totals AND a.status IN ('draft', 'active') AND ae.excluded = false AND ae.date BETWEEN :start_date AND :end_date - AND t.category_id IS NOT NULL - GROUP BY c.id, c.parent_id, CASE WHEN ae.amount < 0 THEN 'income' ELSE 'expense' END + GROUP BY c.id, c.parent_id, CASE WHEN ae.amount < 0 THEN 'income' ELSE 'expense' END, CASE WHEN t.category_id IS NULL THEN true ELSE false END SQL end diff --git a/app/views/pages/dashboard/_outflows_donut.html.erb b/app/views/pages/dashboard/_outflows_donut.html.erb index 4b11de35c..ac09ae4d4 100644 --- a/app/views/pages/dashboard/_outflows_donut.html.erb +++ b/app/views/pages/dashboard/_outflows_donut.html.erb @@ -70,13 +70,9 @@
<% outflows_data[:categories].each_with_index do |category, idx| %> - <%= link_to transactions_path(q: { categories: [category[:name]], start_date: period.date_range.first, end_date: period.date_range.last }), - class: "flex items-center justify-between mx-3 p-3 rounded-lg cursor-pointer group gap-3", - data: { - turbo_frame: "_top", - category_id: category[:id], - action: "mouseenter->donut-chart#highlightSegment mouseleave->donut-chart#unhighlightSegment" - } do %> + <% + category_content = capture do + %>
<%= category[:percentage] %>%
<% end %> + + <% if category[:clickable] != false %> + <%= link_to transactions_path(q: { categories: [category[:name]], start_date: period.date_range.first, end_date: period.date_range.last }), + class: "flex items-center justify-between mx-3 p-3 rounded-lg cursor-pointer group gap-3", + data: { + turbo_frame: "_top", + category_id: category[:id], + action: "mouseenter->donut-chart#highlightSegment mouseleave->donut-chart#unhighlightSegment" + } do %> + <%= category_content %> + <% end %> + <% else %> +
+ <%= category_content %> +
+ <% end %> <% if idx < outflows_data[:categories].size - 1 %> <%= render "shared/ruler", classes: "mx-3 lg:mx-4" %> <% end %> diff --git a/app/views/reports/_net_worth.html.erb b/app/views/reports/_net_worth.html.erb new file mode 100644 index 000000000..d7fa0beff --- /dev/null +++ b/app/views/reports/_net_worth.html.erb @@ -0,0 +1,73 @@ +
+ <%# Main Net Worth Stats %> +
+ <%# Current Net Worth %> +
+

<%= t("reports.net_worth.current_net_worth") %>

+

"> + <%= net_worth_metrics[:current_net_worth].format %> +

+
+ + <%# Period Change %> +
+

<%= t("reports.net_worth.period_change") %>

+ <% if net_worth_metrics[:trend] %> + <% trend = net_worth_metrics[:trend] %> +

+ <%= trend.value.format(signify_positive: true) %> +

+

+ <%= trend.value >= 0 ? "+" : "" %><%= trend.percent_formatted %> +

+ <% else %> +

--

+ <% end %> +
+ + <%# Assets vs Liabilities %> +
+

<%= t("reports.net_worth.assets_vs_liabilities") %>

+
+ <%= net_worth_metrics[:total_assets].format %> + - + <%= net_worth_metrics[:total_liabilities].format %> +
+
+
+ + <%# Asset/Liability Breakdown %> +
+ <%# Assets Summary %> +
+

<%= t("reports.net_worth.total_assets") %>

+
+ <% net_worth_metrics[:asset_groups].each do |group| %> +
+ <%= group[:name] %> + <%= group[:total].format %> +
+ <% end %> + <% if net_worth_metrics[:asset_groups].empty? %> +

<%= t("reports.net_worth.no_assets") %>

+ <% end %> +
+
+ + <%# Liabilities Summary %> +
+

<%= t("reports.net_worth.total_liabilities") %>

+
+ <% net_worth_metrics[:liability_groups].each do |group| %> +
+ <%= group[:name] %> + <%= group[:total].format %> +
+ <% end %> + <% if net_worth_metrics[:liability_groups].empty? %> +

<%= t("reports.net_worth.no_liabilities") %>

+ <% end %> +
+
+
+
diff --git a/app/views/reports/_transactions_breakdown.html.erb b/app/views/reports/_transactions_breakdown.html.erb index 00eecc857..c7c14fffe 100644 --- a/app/views/reports/_transactions_breakdown.html.erb +++ b/app/views/reports/_transactions_breakdown.html.erb @@ -85,7 +85,7 @@
<%= group[:category_name] %> - (<%= group[:count] %> <%= t("reports.transactions_breakdown.table.transactions") %>) + (<%= t("reports.transactions_breakdown.table.entries", count: group[:count]) %>)
@@ -139,7 +139,7 @@
<%= group[:category_name] %> - (<%= group[:count] %> <%= t("reports.transactions_breakdown.table.transactions") %>) + (<%= t("reports.transactions_breakdown.table.entries", count: group[:count]) %>)
diff --git a/app/views/reports/_trends_insights.html.erb b/app/views/reports/_trends_insights.html.erb index 0a02928c4..61364c537 100644 --- a/app/views/reports/_trends_insights.html.erb +++ b/app/views/reports/_trends_insights.html.erb @@ -1,4 +1,4 @@ -
+
<%# Month-over-Month Trends %>

@@ -78,121 +78,5 @@

<% end %>
- - <%# Spending Patterns %> -
-

- <%= t("reports.trends.spending_patterns") %> -

- - <% if spending_patterns[:weekday_count] + spending_patterns[:weekend_count] > 0 %> -
- <%# Weekday Spending %> -
-
- <%= icon("calendar", class: "w-5 h-5 text-primary") %> -

<%= t("reports.trends.weekday_spending") %>

-
- -
-
-

<%= t("reports.trends.total") %>

-

- <%= Money.new(spending_patterns[:weekday_total], Current.family.currency).format %> -

-
- -
-

<%= t("reports.trends.avg_per_transaction") %>

-

- <%= Money.new(spending_patterns[:weekday_avg], Current.family.currency).format %> -

-
- -
-

<%= t("reports.trends.transactions") %>

-

- <%= spending_patterns[:weekday_count] %> -

-
-
-
- - <%# Weekend Spending %> -
-
- <%= icon("calendar-check", class: "w-5 h-5 text-primary") %> -

<%= t("reports.trends.weekend_spending") %>

-
- -
-
-

<%= t("reports.trends.total") %>

-

- <%= Money.new(spending_patterns[:weekend_total], Current.family.currency).format %> -

-
- -
-

<%= t("reports.trends.avg_per_transaction") %>

-

- <%= Money.new(spending_patterns[:weekend_avg], Current.family.currency).format %> -

-
- -
-

<%= t("reports.trends.transactions") %>

-

- <%= spending_patterns[:weekend_count] %> -

-
-
-
-
- - <%# Comparison Insight %> - <% if spending_patterns[:weekday_avg] > 0 && spending_patterns[:weekend_avg] > 0 %> -
-
- <%= icon("lightbulb", class: "w-5 h-5 text-warning mt-0.5") %> -
-

- <%= t("reports.trends.insight_title") %> -

-

- <% - weekday = spending_patterns[:weekday_avg].to_f - weekend = spending_patterns[:weekend_avg].to_f - - if weekend > weekday - percent_diff = ((weekend - weekday) / weekday * 100).round(0) - if percent_diff > 20 - message = t("reports.trends.insight_higher_weekend", percent: percent_diff) - else - message = t("reports.trends.insight_similar") - end - elsif weekday > weekend - percent_diff = ((weekday - weekend) / weekend * 100).round(0) - if percent_diff > 20 - message = t("reports.trends.insight_higher_weekday", percent: percent_diff) - else - message = t("reports.trends.insight_similar") - end - else - message = t("reports.trends.insight_similar") - end - %> - <%= message %> -

-
-
-
- <% end %> - <% else %> -
- <%= t("reports.trends.no_spending_data") %> -
- <% end %> -
diff --git a/config/locales/models/category/en.yml b/config/locales/models/category/en.yml new file mode 100644 index 000000000..dc86eba56 --- /dev/null +++ b/config/locales/models/category/en.yml @@ -0,0 +1,6 @@ +--- +en: + models: + category: + uncategorized: Uncategorized + other_investments: Other Investments diff --git a/config/locales/views/reports/en.yml b/config/locales/views/reports/en.yml index 6767fabd2..2d34539f7 100644 --- a/config/locales/views/reports/en.yml +++ b/config/locales/views/reports/en.yml @@ -69,8 +69,8 @@ en: add_transaction: Add Transaction add_account: Add Account transactions_breakdown: - title: Transactions Breakdown - no_transactions: No transactions found for the selected period and filters + title: Activity Breakdown + no_transactions: No activity found for the selected period and filters filters: title: Filters category: Category @@ -102,12 +102,25 @@ en: expense: Expenses income: Income uncategorized: Uncategorized - transactions: transactions + entries: + one: entry + other: entries percentage: "% of Total" pagination: - showing: Showing %{count} transactions + showing: + one: Showing %{count} entry + other: Showing %{count} entries previous: Previous next: Next + net_worth: + title: Net Worth + current_net_worth: Current Net Worth + period_change: Period Change + assets_vs_liabilities: Assets vs Liabilities + total_assets: Assets + total_liabilities: Liabilities + no_assets: No assets + no_liabilities: No liabilities investment_performance: title: Investment Performance portfolio_value: Portfolio Value