Class: BudgetsController

Inherits:
CrmController show all
Defined in:
app/controllers/budgets_controller.rb

Overview

== Schema Information

Table name: budgets

id :integer not null, primary key
description :string
company_id :integer
ledger_account_id :integer
business_unit_id :integer
supplier_id :integer
ledger_project_id :integer
year :integer
quarter :integer
month :integer
currency :string
amount :decimal(12, 2)
consolidated_amount :decimal(12, 2)
exchange_rate :decimal(, )
is_revenue :boolean
all_ledger_company_account_ids_1 :text default([]), is an Array
all_ledger_company_account_ids_2 :text default([]), is an Array
creator_id :integer
updater_id :integer
created_at :datetime not null
updated_at :datetime not null

Constant Summary

Constants included from Controllers::ReferenceFindable

Controllers::ReferenceFindable::ID_EMBEDDED_PATTERNS

Constants included from Controllers::AnalyticsEvents

Controllers::AnalyticsEvents::MAX_QUEUED_EVENTS, Controllers::AnalyticsEvents::SESSION_KEY

Constants included from Controllers::ErrorRendering

Controllers::ErrorRendering::NON_CONTENT_PATH_PREFIXES

Constants included from Www::SeoHelper

Www::SeoHelper::AWARDS, Www::SeoHelper::CA_ADDRESS, Www::SeoHelper::CA_BUSINESS_HOURS, Www::SeoHelper::CA_CONTACT_POINT, Www::SeoHelper::CA_CURRENCIES, Www::SeoHelper::CA_DESCRIPTION, Www::SeoHelper::CA_FOUNDING_DATE, Www::SeoHelper::CA_GLOBAL_LOCATION_NUMBER, Www::SeoHelper::CA_LEGAL_NAME, Www::SeoHelper::CA_LOCAL_BUSINESS, Www::SeoHelper::CA_ONLINE_STORE, Www::SeoHelper::CA_RETURN_POLICY, Www::SeoHelper::CA_SALES_DEPARTMENT, Www::SeoHelper::CA_SERVICE_AREA, Www::SeoHelper::CA_URL, Www::SeoHelper::CA_VAT_ID, Www::SeoHelper::CA_WAREHOUSE_DEPARTMENT, Www::SeoHelper::CA_WAREHOUSE_HOURS, Www::SeoHelper::COMPANY_EMAIL, Www::SeoHelper::COMPANY_LOGO, Www::SeoHelper::COMPANY_NAME, Www::SeoHelper::COMPANY_SLOGAN, Www::SeoHelper::EXPERTISE, Www::SeoHelper::FAX_NUMBER, Www::SeoHelper::GS1_COMPANY_PREFIX, Www::SeoHelper::ISO6523_CODE, Www::SeoHelper::PAYMENT_METHODS, Www::SeoHelper::PHONE_NUMBER, Www::SeoHelper::PRIMARY_NAICS, Www::SeoHelper::REFUND_TYPE, Www::SeoHelper::RETURN_FEES, Www::SeoHelper::RETURN_METHOD, Www::SeoHelper::RETURN_POLICY_CATEGORY, Www::SeoHelper::SECONDARY_NAICS, Www::SeoHelper::SOCIAL_PROFILES, Www::SeoHelper::US_ADDRESS, Www::SeoHelper::US_BUSINESS_HOURS, Www::SeoHelper::US_CONTACT_POINT, Www::SeoHelper::US_CURRENCIES, Www::SeoHelper::US_DESCRIPTION, Www::SeoHelper::US_FOUNDING_DATE, Www::SeoHelper::US_GLOBAL_LOCATION_NUMBER, Www::SeoHelper::US_IMAGE, Www::SeoHelper::US_LEGAL_NAME, Www::SeoHelper::US_LOCAL_BUSINESS, Www::SeoHelper::US_ONLINE_STORE, Www::SeoHelper::US_RETURN_POLICY, Www::SeoHelper::US_SALES_DEPARTMENT, Www::SeoHelper::US_SERVICE_AREA, Www::SeoHelper::US_TAX_ID, Www::SeoHelper::US_URL, Www::SeoHelper::US_WAREHOUSE_DEPARTMENT, Www::SeoHelper::US_WAREHOUSE_HOURS

Constants included from IconHelper

IconHelper::CUSTOM_ICON_MAP, IconHelper::CUSTOM_SVG_DIR, IconHelper::DEFAULT_FAMILY

Instance Method Summary collapse

Methods inherited from CrmController

#access_denied, #context_id, #context_object, #crm_home_path, #current_ability, #default_url_options, #download_temp, #get_tempfile_path_for_download, #init_status_job_collector, #initialize_crm_lazy_chunks, #persist_enqueued_status_jobs, #record_not_found, #redirect_to_job_or_fallback, #render_edit_action, #set_context, #set_download_path, #stash_file_for_temp_download, #sync_admin_presence_cookie

Methods inherited from ApplicationController

#account_impersonated?, #add_to_flash, #after_sign_in_path_for, #bypass_forgery_protection?, #chat_enabled?, #cloudflare_cleared?, #default_catalog, #default_url_options, #enable_turbo_frames, #find_publication, #fix_invalid_accept_header, #init_js_utils, #is_globals_call?, #layout_by_resource, #locale_store, #redirect_to, #require_employee_for_crm, #set_base_host, #set_real_ip, #set_report_errors_for, #should_render_layout?, #stamp_impersonation_context, #warmlyyours_canada_ip?, #warmlyyours_ip?, #y

Methods included from Controllers::ReturnPathHandling

#check_for_return_path, #redirect_to_return_path_or_default

Methods included from Controllers::AnalyticsEvents

#consume_queued_analytics_events, #track_event

Methods included from Controllers::DeviceDetection

#device_detector, #is_ie?

Methods included from Controllers::SubdomainDetection

#is_crm_request?, #is_www_request?, #json_request?

Methods included from Controllers::TurboSafeRedirect

#redirect_to

Methods included from Controllers::TrackingDetection

#bot_request?, #gdpr_country?, #gdpr_country_data, #prevent_bots, #set_tracking_cookie, #track_visitor?

Methods included from Controllers::AcceleratedFileSending

#send_file_accelerated, #send_upload_accelerated

Methods included from Controllers::ErrorRendering

#excp_string, #mail_to_for_error_reporting, #render_400, #render_404, #render_406, #render_410, #render_500, #render_invalid_authenticity_token, #render_ip_spoof_error, #render_unpermitted_parameters, #safe_referer_or_fallback

Methods included from Controllers::TurnstileVerification

#load_turnstile_script_tag, #turnstile_lazy_widget, #turnstile_script_tag, #turnstile_widget, #validate_turnstile!

Methods included from Controllers::CloudflareCaching

edge_cached, #edge_cached_action?, #reset_cloudflare_cache, #set_cloudflare_cache, #skip_edge_cache!, #skip_session

Methods included from Controllers::Webpackable

#preload_webpack_fonts, #webpack_css_include, #webpack_css_url, #webpack_js_include, #wpd_is_running?

Methods included from Controllers::Localizable

#cloudflare_country_locale, #determine_request_locale, #geocoder_locale, #guest_user_locale_check, #locale_optional_www_auth_path?, #param_locale, #set_locale, #set_request_locale, #skip_localization?, #warmlyyours_ip_locale

Methods included from Controllers::Authenticable

#access_denied, #authenticate_account, #authenticate_account!, #authenticate_account_from_login_token!, #check_is_a_manager, #check_is_a_sales_manager, #check_is_an_admin, #check_is_an_employee, #check_party, #clear_mismatched_guest_user, #create_guest_user, #credentials?, #current_or_guest_user, #current_or_guest_user_id_read_only, #current_user, #devise_mapping, #fully_logged_in?, #generate_bot_id, #guest_user, #identifiable?, #init_current_user, #initialize_guest, #load_context_user, #logging_in, #resource, #resource_name, #restrict_access_for_non_employees, #scrubbed_request_path, #user_object, #warn_on_session_guest_id_leak

Methods included from ApplicationHelper

#better_number_to_currency, #check_force_logout, #check_or_cross, #check_or_times, #embedded_tab_frame_id, #error_messages, #general_disclaimer_on_product_installation_and_local_codes, #gridjs_from_html_table, #gridjs_table, #is_wy_ip, #line_break, #parent_layout, #pass_or_fail, #render_error_messages_list, #render_video_card, #resolved_auth_form_turbo_frame, #return_path_or, #safe_css_color, #set_return_path_if_present, #set_section_if_present, #tab_frame_id, #to_underscore, #track_page?, #turbo_section_wrapper, #turbo_tabs_request?, #url_on_same_domain_as_request, #widget_index_daily_focus_index_path, #working_hours?, #yes_or_no, #yes_or_no_highlighted, #yes_or_no_with_check_or_cross, #youtube_video

Methods included from UppyUploaderHelper

#file_uploader, #image_uploader, #large_file_uploader_s3, #lead_sketch_uploader, #rma_image_uploader, #rma_image_uploader_s3, #uppy_uploader, #video_uploader

Methods included from Www::ImagesHelper

#image_asset_tag, #image_asset_url

Methods included from Www::SeoHelper

#add_page_schema, #add_webpage_schema, #canada?, #company_social_links, #ensure_context_json, #json_ld_script_tag, #local_business_schema, #online_store_id, #online_store_schema, #page_main_entity, #page_main_entity_json, #render_auto_collection_page_schema, #render_collection_page_schema, #render_local_business_schema, #render_online_store_schema, #render_page_schemas, #render_page_video_schemas, #render_webpage_schema, #render_webpage_schema_with_collections, #usa?

Methods included from UrlsHelper

#catalog_breadcrumb_links, #catalog_link, #catalog_link_for_product_line, #catalog_link_for_sku, #cms_link, #delocalized_path, #path_to_sales_product_sku, #path_to_sales_product_sku_for_product_line, #path_to_sales_product_sku_for_product_line_slug, #product_line_from_catalog_link, #protocol_neutral_url, #sanitize_external_url, #valid_external_url?

Methods included from IconHelper

#account_nav_icon, #fa_icon, #star_rating_html

Instance Method Details

#createObject

POST /budgets — persist a single budget row.



56
57
58
59
60
61
62
63
64
# File 'app/controllers/budgets_controller.rb', line 56

def create
  @budget = Budget.new(params[:budget])

  if @budget.save
    redirect_to(@budget, info: 'Budget was successfully created.')
  else
    render :new, status: :unprocessable_content
  end
end

#destroyObject

DELETE /budgets/:id — destroy the budget row.



83
84
85
86
87
88
# File 'app/controllers/budgets_controller.rb', line 83

def destroy
  @budget = Budget.find(params[:id])

  @budget.destroy
  redirect_to_return_path_or_default(budgets_url)
end

#do_importObject

POST /budgets/do_import — parse the uploaded CSV via
Budget.summary_import_from_csv and bounce to the budget
report (refresh runs synchronously inside the import, so by the
time the user lands the numbers are fresh).



183
184
185
186
187
188
189
190
191
192
193
# File 'app/controllers/budgets_controller.rb', line 183

def do_import
  upload_params = params[:upload]
  if upload_params.nil? # upload_params[:budget_csv].blank?
    flash.now[:error] = 'No file was uploaded'
    render :import, status: :unprocessable_content
  else
    Budget.summary_import_from_csv(upload_params[:budget_csv].path)
    flash[:info] = 'Budget imported, report is being refreshed'
    redirect_to '/reports/budget'
  end
end

#download_sample_csvObject

GET /budgets/download_sample_csv — serve the canonical
bulk-import template so users start from the right column layout.



197
198
199
200
# File 'app/controllers/budgets_controller.rb', line 197

def download_sample_csv
  file_path = Rails.root.join('data/import_templates/accounting/budget_sample.csv')
  send_file_accelerated(file_path, download: true, preserve_source: true)
end

#editObject

GET /budgets/:id/edit — render the edit form.



51
52
53
# File 'app/controllers/budgets_controller.rb', line 51

def edit
  @budget = Budget.find(params[:id])
end

#importObject

GET /budgets/import — render the bulk-import form.



177
# File 'app/controllers/budgets_controller.rb', line 177

def import; end

#indexObject

GET /budgets — there's no flat list; redirect into the
budget-vs-actual report which is the canonical entry point.



35
36
37
# File 'app/controllers/budgets_controller.rb', line 35

def index
  redirect_to '/reports/budget'
end

#newObject

GET /budgets/new — render the new-budget form.



46
47
48
# File 'app/controllers/budgets_controller.rb', line 46

def new
  @budget = Budget.new
end

#refresh_reportObject

POST /budgets/refresh_report — recompute the budget-vs-actual
snapshot. With explicit year/month we run an incremental
upsert (or full delete-reinsert when ?full=true); without
them we kick off a full-year refresh in the background.



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'app/controllers/budgets_controller.rb', line 94

def refresh_report
  if params[:year] && params[:month]
    job_options = { current_user_id: @context_user.id, year: params[:year], month: params[:month] }

    # Use incremental refresh (upserts) by default for faster performance
    # Full refresh (delete-reinsert) can be forced with ?full=true
    job_id = if params[:full].present?
               BudgetRefresherWorkerWithStatus.perform_async(job_options)
             else
               BudgetRefresherIncrementalWorker.perform_async(job_options)
             end
    redirect_to job_path(job_id)
  else
    BudgetRefresherAllWorker.perform_async
    flash[:info] = 'All budget reports from current year sent to background processor to be refreshed.'
    redirect_to root_path
  end
end

#showObject

GET /budgets/:id — show a single Budget row (one company /
ledger-account / period / supplier-or-project combination).



41
42
43
# File 'app/controllers/budgets_controller.rb', line 41

def show
  @budget = Budget.find(params[:id])
end

#show_budgets_for_factObject

GET /budgets/show_budgets_for_fact — drill-down counterpart to
#show_ledger_entries_for_fact: takes the same fact cell and
opens the budget rows that summed to it (so the user can edit
the budgeted figure directly). accumulated=true shows every
month in the YTD window rather than just the fact's month.



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'app/controllers/budgets_controller.rb', line 159

def show_budgets_for_fact
  fact = Analytic::BudgetFact.find(params[:fact_id])
  dimension = fact.budget_dimension
  query_params = {}
  query_params[:company_id_in] = dimension.company_id == 'all' ? [1, 2] : dimension.company_id
  query_params[:ledger_account_id_in] = dimension.
  query_params[:business_unit_id_in] = dimension.business_unit_id unless dimension.business_unit_id.nil?
  query_params[:ledger_project_id_in] = dimension.ledger_project_id unless dimension.ledger_project_id.nil? || (dimension.ledger_project_id == 0)
  query_params[:supplier_id_eq] = dimension.supplier_id unless dimension.supplier_id.nil? || (dimension.supplier_id == 0)
  query_params[:year_in] = fact.year
  query_params[:month_in] = params[:accumulated] == true ? (1..fact.month).to_a : fact.month

  qt = BudgetSearch.instantiate_query_template(nil, { title: 'link', auto_count: false, aggregate_method: :none, query_params: query_params })

  redirect_to execute_search_path(qt.to_params)
end

#show_ledger_entries_for_factObject

GET /budgets/show_ledger_entries_for_fact — drill-down from a
BudgetFact cell on the report into the underlying GL entries
that built it. Translates the budget dimension (company,
account, business-unit, project, supplier) into the matching
LedgerEntrySearch params and redirects there. The period
param picks the comparison window (current vs previous year,
month vs accumulated YTD).



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'app/controllers/budgets_controller.rb', line 120

def show_ledger_entries_for_fact
  fact = Analytic::BudgetFact.find(params[:fact_id])
  dimension = fact.budget_dimension
  query_params = {}
  query_params[:lt_company_id_in] = dimension.company_id == 'all' ? [1, 2] : dimension.company_id
  query_params[:ledger_company_account_id_in] = dimension.
  query_params[:business_unit_id_in] = dimension.business_unit_id unless dimension.business_unit_id.nil?
  query_params[:ledger_detail_project_id_in] = dimension.ledger_project_id unless dimension.ledger_project_id.nil? || (dimension.ledger_project_id == 0)
  query_params[:supplier_id_eq] = dimension.supplier_id unless dimension.supplier_id.nil? || (dimension.supplier_id == 0)
  query_params[:lt_transaction_date_gteq] = case params[:period]
                                            when 'current-month'
                                              Date.new(fact.year, fact.month).beginning_of_month.to_fs(:db)
                                            when 'current-accumulated'
                                              Date.new(fact.year, fact.month).beginning_of_year.to_fs(:db)
                                            when 'previous-month'
                                              Date.new(fact.year, fact.month).last_year.beginning_of_month.to_fs(:db)
                                            when 'previous-accumulated'
                                              Date.new(fact.year, fact.month).last_year.beginning_of_year.to_fs(:db)
                                            end
  query_params[:lt_transaction_date_lteq] = case params[:period]
                                            when 'current-month'
                                              Date.new(fact.year, fact.month).end_of_month.to_fs(:db)
                                            when 'current-accumulated'
                                              Date.new(fact.year, fact.month).end_of_month.to_fs(:db)
                                            when 'previous-month'
                                              Date.new(fact.year, fact.month).last_year.end_of_month.to_fs(:db)
                                            when 'previous-accumulated'
                                              Date.new(fact.year, fact.month).last_year.end_of_month.to_fs(:db)
                                            end

  qt = LedgerEntrySearch.instantiate_query_template(nil, { title: 'link', auto_count: false, aggregate_method: :none, query_params: query_params })
  redirect_to execute_search_path(qt.to_params)
end

#updateObject

PATCH/PUT /budgets/:id — update a budget row. Responds to JSON
for the inline-edit grid on the budget report.



68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'app/controllers/budgets_controller.rb', line 68

def update
  @budget = Budget.find(params[:id])

  respond_to do |format|
    if @budget.update(params[:budget])
      format.html { redirect_to_return_path_or_default(budget_path(@budget)) }
      format.json { render json: @budget }
    else
      format.html { render :edit, status: :unprocessable_content }
      format.json { render json: @budget }
    end
  end
end