Class: AccountingDashboardController

Inherits:
CrmController show all
Includes:
Controllers::RequireTurboFrame
Defined in:
app/controllers/accounting_dashboard_controller.rb

Overview

Controller: accounting dashboard.

Constant Summary collapse

TAB_ACTIONS =

Tab fragments never render standalone — non-frame hits redirect to the
transmissions dashboard with the tab preselected.

%i[tab_email_or_fax tab_portal tab_mail tab_mixed tab_no_channel_set tab_replacement_invoice tab_missing_edi_810].freeze

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

Instance Method Summary collapse

Methods included from Controllers::RequireTurboFrame

require_in_pane_tab_frames, require_turbo_frame

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, #touch_employee_last_seen

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?, #skip_layout_for_turbo_frame?, #stamp_impersonation_context, #tab_frame_breakout_request?, #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, #registration_lead_type, #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 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?

Instance Method Details

#acknowledge_missing_edi_810Object

POST /accounting/acknowledge_missing_edi_810 — mass-mark the
selected EDI-810-missing invoices as transmitted (effectively
"we know about it, stop bothering us"). Each invoice fires its
transmit! state-machine event when it's still in a
transmittable state.



169
170
171
172
173
174
175
176
177
178
179
180
# File 'app/controllers/accounting_dashboard_controller.rb', line 169

def acknowledge_missing_edi_810
  invoice_ids = params[:invoice_ids].to_a.map(&:to_i).reject(&:zero?)
  if invoice_ids.empty?
    flash[:warning] = 'No invoices selected.'
  else
    Invoice.transaction do
      Invoice.where(id: invoice_ids).find_each { |invoice| invoice.transmit! if invoice.can_transmit? }
    end
    flash[:info] = "#{invoice_ids.size} invoice(s) marked as ignored and removed from this list."
  end
  redirect_to accounting_transmissions_path(since: params[:since], tab: 'missing_edi_810')
end

#bulk_regenerate_edi_810Object

POST /accounting/bulk_regenerate_edi_810 — re-fire the EDI-810
creation pipeline for every selected invoice via
Edi::InvoiceEventProcessor. Bulk equivalent of the per-invoice
InvoicesController#regenerate_edi_documents action.



186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'app/controllers/accounting_dashboard_controller.rb', line 186

def bulk_regenerate_edi_810
  authorize! :update, Invoice

  invoice_ids = params[:invoice_ids].to_a.map(&:to_i).reject(&:zero?)
  if invoice_ids.empty?
    flash[:warning] = 'No invoices selected.'
  else
    processor = Edi::InvoiceEventProcessor.new
    Invoice.where(id: invoice_ids).find_each { |invoice| processor.process_creation(invoice) }
    flash[:info] = "EDI documents queued for regeneration on #{invoice_ids.size} invoice(s)."
  end
  redirect_to accounting_transmissions_path(since: params[:since], tab: 'missing_edi_810')
end

#indexObject

GET /accounting — landing page for the accounting team's daily
workflow. The page itself is mostly turbo-frame slots that fetch
the per-tab content lazily.



14
# File 'app/controllers/accounting_dashboard_controller.rb', line 14

def index; end

#tab_countObject

GET /accounting/tab_count — count-only endpoint behind each tab
badge in the dashboard. Returns just the integer count for the
requested tab, applying the same filter the corresponding tab
action would apply.



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'app/controllers/accounting_dashboard_controller.rb', line 140

def tab_count
  q = params[:q].to_h
  @tab = params[:tab].to_s
  @count = case @tab
           when 'email_or_fax'
             ViewAccountingTransmission.ransack(q.merge('transmission_type_in' => ['Email/Fax'])).result.count
           when 'portal'
             ViewAccountingTransmission.ransack(q.merge('transmission_type_in' => ['Portal'])).result.count
           when 'mail'
             ViewAccountingTransmission.ransack(q.merge('transmission_type_in' => ['Mail'])).result.count
           when 'mixed'
             ViewAccountingTransmission.ransack(q.merge('transmission_type_not_in' => ['Email/Fax', 'Portal', 'Mail', 'No Channel Set'])).result.count
           when 'no_channel_set'
             ViewAccountingTransmission.ransack(q.merge('transmission_type_in' => ['No Channel Set'])).result.count
           when 'replacement_invoice'
             ViewAccountingTransmission.ransack(q.merge('doc_type_in' => ['INV'])).result.count
           when 'missing_edi_810'
             Invoice.missing_edi_810.where(invoices: { created_at: 90.days.ago.. }).count
           else
             0
           end
  render layout: false
end

#tab_email_or_faxObject

GET /accounting/tab_email_or_fax — turbo-frame body for the
Email/Fax tab. Pulls every doc whose customer transmits via email
or fax, including the related Communications so the per-row
"transmission error" tooltip doesn't N+1.



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

def tab_email_or_fax
  @email_fax_query = ViewAccountingTransmission.ransack(params[:q])
  @email_fax_query.sorts = 'document_date asc' if @email_fax_query.sorts.empty?
  @email_fax_documents = @email_fax_query.result
                                         .except(:order)
                                         .order("#{@email_fax_query.sorts.first.name} #{@email_fax_query.sorts.first.dir} NULLS LAST")
                                         .load
  preload_unsent_communications(@email_fax_documents)
  render 'tab_email_or_fax', layout: should_render_layout?
end

#tab_mailObject

GET /accounting/tab_mail — turbo-frame body for the Mail tab
(customers who require physical printed-and-mailed documents).



89
90
91
# File 'app/controllers/accounting_dashboard_controller.rb', line 89

def tab_mail
  render 'tab_mail', layout: should_render_layout?
end

#tab_missing_edi_810Object

GET /accounting/tab_missing_edi_810 — turbo-frame body for the
"EDI 810 missing" tab (invoices that should have transmitted as an
EDI 810 but didn't). Defaults to the last 90 days but takes a
since param to expand the window.



126
127
128
129
130
131
132
133
134
# File 'app/controllers/accounting_dashboard_controller.rb', line 126

def tab_missing_edi_810
  @since = params[:since].present? ? Date.parse(params[:since]) : 90.days.ago.to_date
  @invoices = Invoice.missing_edi_810
                     .where(invoices: { created_at: @since.. })
                     .includes(:order, :customer)
                     .order(created_at: :desc)
                     .load
  render 'tab_missing_edi_810', layout: should_render_layout?
end

#tab_mixedObject

GET /accounting/tab_mixed — turbo-frame body for the Mixed tab
(customers whose contact-points span more than one transmission
channel, requiring per-doc handling).



96
97
98
# File 'app/controllers/accounting_dashboard_controller.rb', line 96

def tab_mixed
  render 'tab_mixed', layout: should_render_layout?
end

#tab_no_channel_setObject

GET /accounting/tab_no_channel_set — turbo-frame body for docs
whose customer has no transmission channel configured at all.
These need a setup task before they can move forward.



103
104
105
# File 'app/controllers/accounting_dashboard_controller.rb', line 103

def tab_no_channel_set
  render 'tab_no_channel_set', layout: should_render_layout?
end

#tab_portalObject

GET /accounting/tab_portal — turbo-frame body for the Portal tab
(customers whose docs are picked up via partner portal, e.g. SPS).



83
84
85
# File 'app/controllers/accounting_dashboard_controller.rb', line 83

def tab_portal
  render 'tab_portal', layout: should_render_layout?
end

#tab_replacement_invoiceObject

GET /accounting/tab_replacement_invoice — turbo-frame body for
the "replacement invoice" tab. Shows full-RMA-funded INV docs
whose RMA is still awaiting return after 15+ days, so accounting
can chase the customer for the return or write it off.



111
112
113
114
115
116
117
118
119
120
# File 'app/controllers/accounting_dashboard_controller.rb', line 111

def tab_replacement_invoice
  @replacement_invoice_query = ViewAccountingTransmission.ransack(params[:q])
  @replacement_invoice_query.sorts = 'document_date asc' if @replacement_invoice_query.sorts.empty?
  @replacement_invoice_documents = @replacement_invoice_query.result
                                                             .except(:order)
                                                             .order("#{@replacement_invoice_query.sorts.first.name} #{@replacement_invoice_query.sorts.first.dir} NULLS LAST")
                                                             .load
  preload_unsent_communications(@replacement_invoice_documents)
  render 'tab_replacement_invoice', layout: should_render_layout?
end

#transmissionsObject

GET /accounting/transmissions — combined queue view of every
outbound accounting document (invoice, credit memo) waiting on
transmission. Each tab is rendered separately by the tab_*
actions; this action just sets up the shared search struct.



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'app/controllers/accounting_dashboard_controller.rb', line 20

def transmissions
  params[:q] ||= {}

  # @tabs = {
  #   email:  {title: 'Email/Fax', query: {transmission_type_in: ['Email/Fax'], rma_funding_not_eq: 'full'}},
  #   portal: {title: 'Portal', query: {transmission_type_in: ['Portal'], rma_funding_not_eq: 'full'}},
  #   mail:   {title: 'Mail', query: {transmission_type_in: ['Mail'], rma_funding_not_eq: 'full'}},
  #   mixed:  {title: 'Mixed', query: {transmission_type_not_in: ['Email/Fax','Portal','Mail','No Channel Set'], rma_funding_not_eq: 'full'}},
  #   none:   {title: 'No Channel Set', query: {transmission_type_in: ['No Channel Set'], rma_funding_not_eq: 'full'}},
  #   rma:    {title: 'Replacement Invoice', query: {doc_type_in: ['INV'], rma_funding_eq: 'full', state_eq: 'unpaid', rma_state_eq: 'awaiting_return', document_date_lteq: 15.days.ago}}
  # }
  # @tabs.each {|tab, options| options[:count] = ViewAccountingTransmission.ransack(params[:q].merge(options[:query])).result.count}
  # @selected_tab = (params[:tab] || :email).to_sym

  @q = OpenStruct.new(params[:q])
end

#transmit_accounting_documentsObject

POST /accounting/transmit_accounting_documents — bulk action on
the documents the user checked off in the transmissions list.
Three submit buttons land here: Transmit actually sends them,
Mark As Transmitted flips their state without sending,
Print produces a single combined PDF and exposes it via
session[:download_path] so the next page picks it up.



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'app/controllers/accounting_dashboard_controller.rb', line 43

def transmit_accounting_documents
  redirect_to(accounting_transmissions_path(q: parsed_transmission_query, tab: params[:tab]), alert: 'You must select one or more documents') and return if params[:documents].nil?

  transmitter = AccountingDocumentTransmitter.new(params[:documents])
  case params[:commit]
  when 'Transmit'
    result = transmitter.transmit
    flash[:info] = result[:success_message]
    flash[:warning] = result[:error_message]
  when 'Mark As Transmitted'
    transmitter.mark_transmitted
    flash[:info] = 'Selected documents have been marked as transmitted.'
  when 'Print'
    upload = transmitter.print_docs
    if upload.nil?
      flash[:error] = 'Unable to print documents'
    else
      session[:download_path] = upload_url(upload.id)
    end
  end
  redirect_to accounting_transmissions_path(q: parsed_transmission_query, tab: params[:tab])
end