Class: Crm::Reports::BudgetController

Inherits:
ReportsController
  • Object
show all
Defined in:
app/controllers/crm/reports/budget_controller.rb

Constant Summary collapse

MONETARY_FACT_FIELDS =

Fact fields that represent monetary amounts (integers) and must be
multiplied by the exchange rate when converting currencies.
Percentage fields are ratios and stay unchanged.

%i[
  budget_month actual_month budget_month_diff
  budget_accumulated actual_accumulated budget_accumulated_diff
  actual_month_previous actual_month_previous_diff
  actual_accumulated_previous actual_accumulated_previous_diff
].freeze
PERCENTAGE_FORMULAS =

Formulas for recalculating percentage fields after merging facts.
{ percentage_field => [diff_field, base_field] }

{
  budget_month_percent: %i[budget_month_diff budget_month],
  budget_accumulated_percent: %i[budget_accumulated_diff budget_accumulated],
  actual_month_previous_percent: %i[actual_month_previous_diff actual_month],
  actual_accumulated_previous_percent: %i[actual_accumulated_previous_diff actual_accumulated]
}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.company_currenciesObject

Maps company ID (string) → native currency code, built from the database.
e.g. { "1" => "USD", "2" => "CAD", "4" => "EUR" }



23
24
25
26
# File 'app/controllers/crm/reports/budget_controller.rb', line 23

def self.company_currencies
  @company_currencies ||= Company.sales_companies.pluck(:id, :currency)
                                 .to_h { |id, cur| [id.to_s, cur] }
end

.consolidated_currencyObject

The consolidated (reporting) currency used when viewing multiple companies.
Reads from Company#consolidated_currency, falls back to the global constant.



30
31
32
# File 'app/controllers/crm/reports/budget_controller.rb', line 30

def self.consolidated_currency
  @consolidated_currency ||= Company.sales_companies.pick(:consolidated_currency).presence || CONSOLIDATED_CURRENCY
end

Instance Method Details

#childrenObject

Turbo Frame endpoint: returns child dimension rows on-demand when the
user expands a drill-down row. Avoids pre-rendering ~6,500 hidden
supplier-level rows in the initial HTML.



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'app/controllers/crm/reports/budget_controller.rb', line 63

def children
  authorize!(:report, Budget)
  set_filter_params

  dimension_id = params[:dimension_id].to_i
  level = (params[:level] || 1).to_i
  parent_row_id = params[:parent_row_id]

  # Load budget facts for the current period
  @budget_facts = Analytic::BudgetFact
    .where(month: @month, year: @year)
    .index_by(&:budget_dimension_id)

  if @merge_companies
    merge_company_facts!
  elsif @convert_currency
    apply_currency_conversion!
  end

  # Load children of this dimension (select only needed columns)
  children = Analytic::BudgetDimension
    .where(company_id: @data_company_id, parent_id: dimension_id)
    .select(:id, :description, :parent_id, :budget_group_id, :supplier_id)
    .order(:description)
    .to_a

  # Check grandchildren existence for expand buttons (single query)
  child_ids = children.map(&:id)
  @dimension_children_by_parent = if child_ids.any?
                                    Analytic::BudgetDimension
                                      .where(company_id: @data_company_id, parent_id: child_ids)
                                      .select(:id, :description, :parent_id, :budget_group_id, :supplier_id)
                                      .order(:description)
                                      .group_by(&:parent_id)
                                  else
                                    {}
                                  end

  render partial: "budget_children_rows",
         locals: { children: children, level: level, parent_row_id: parent_row_id }
end

#showObject



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'app/controllers/crm/reports/budget_controller.rb', line 34

def show
  authorize!(:report, Budget)
  set_filter_params

  # Build cache key BEFORE loading heavy data.
  # On cache hit the view skips re-rendering the table, but we still need
  # the data for filter dropdowns and the "Last generated" badge.
  @budget_table_cache_key = budget_table_cache_key

  # Always load lightweight "last generated" timestamp for the badge
  @last_generated_at = Analytic::BudgetFact
    .where(month: @month, year: @year)
    .maximum(:created_at)

  # Only load heavy report data when the fragment cache is cold.
  # On cache hit this skips ~40k AR object instantiations + hash building.
  unless fragment_exist?(@budget_table_cache_key)
    load_budget_data
    if @merge_companies
      merge_company_facts!
    elsif @convert_currency
      apply_currency_conversion!
    end
  end
end