Class: AccountingDashboardController

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

Overview

Controller: accounting dashboard.

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

#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.



162
163
164
165
166
167
168
169
170
171
172
173
# File 'app/controllers/accounting_dashboard_controller.rb', line 162

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.



179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'app/controllers/accounting_dashboard_controller.rb', line 179

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.



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

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.



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'app/controllers/accounting_dashboard_controller.rb', line 133

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.



63
64
65
66
67
68
69
70
71
72
# File 'app/controllers/accounting_dashboard_controller.rb', line 63

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).



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

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.



119
120
121
122
123
124
125
126
127
# File 'app/controllers/accounting_dashboard_controller.rb', line 119

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).



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

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.



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

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).



76
77
78
# File 'app/controllers/accounting_dashboard_controller.rb', line 76

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.



104
105
106
107
108
109
110
111
112
113
# File 'app/controllers/accounting_dashboard_controller.rb', line 104

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.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'app/controllers/accounting_dashboard_controller.rb', line 13

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.



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

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