Module: SearchesHelper

Includes:
RouteUrlHelpers
Included in:
ExportedCatalogItemPacket
Defined in:
app/helpers/searches_helper.rb

Overview

View helper: searches.

Constant Summary

Constants included from RouteUrlHelpers

RouteUrlHelpers::ROUTES

Instance Method Summary collapse

Instance Method Details

#adv_sortable(column, title = nil, _options = {}) ⇒ Object



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'app/helpers/searches_helper.rb', line 165

def adv_sortable(column, title = nil, _options = {})
  title ||= column.to_s.titleize
  title = title.html_safe
  # Virtual composite columns (presenter-only, e.g. SupportCaseSearch's
  # :participants / :rooms) have no SQL expression and can't be used in an
  # ORDER BY — render a plain header instead of a sort link so clicking it
  # can't generate a query that raises PG::UndefinedColumn (AppSignal #6124).
  return (:th, title) unless @search.sortable_column?(column)

  direction = 'ASC'
  # Some of our columns are composite column constituded of other columns, we retrieve them to compare if we should highlight the table header
  composite_order_columns = @search.all_composite_columns[column.to_sym].try(:[], :select) || []
  # Is our table header column present in the current sort columns of the query or is it present and part of a composite set.
  if (sort_direction = @search.sort_columns_to_hash[column.to_sym] || @search.sort_columns_to_hash.keys.intersect?(composite_order_columns))
    index = @search.sort_columns_to_hash.keys.index(column.to_sym)
    title = ((:sup, " [#{index + 1}] ") + title) if index
    if %i[desc DESC].include?(sort_direction)
      title = fa_icon('chevron-down', text: title)
    else # :asc
      title = fa_icon('chevron-up', text: title)
      direction = 'DESC'
    end
    style = 'font-weight:bolder'
  end
  parameters = request.params.compact_blank
  parameters = parameters.symbolize_keys if parameters.present?
  parameters[:sort] = column
  parameters[:direction] = direction
   :th, link_to(title, parameters), style: style
end

#cross_reference_customer_search_collectionObject



246
247
248
249
250
# File 'app/helpers/searches_helper.rb', line 246

def cross_reference_customer_search_collection
  CustomerSearch.where(employee_id: @context_user.id)
                .select('(select count(*) from search_results sr where sr.search_id = searches.id) as selected_results, searches.*')
                .where('(select count(*) from search_results sr where sr.search_id = searches.id) > 0').map { |s| ["#{s.effective_name} (#{s.selected_results} selected)", s.id] }
end

#current_user_recent_searches(user = current_user) ⇒ Object



7
8
9
# File 'app/helpers/searches_helper.rb', line 7

def current_user_recent_searches(user = current_user)
  user.searches.where(persist: false).order(:updated_at).reverse_order.limit(15)
end

#field_wrap(form, name, display = nil) ⇒ Object



196
197
198
199
200
201
202
203
# File 'app/helpers/searches_helper.rb', line 196

def field_wrap(form, name, display = nil)
  display ||= name.to_s.titleize

   :div, class: 'field' do
    form.label(name, "#{display}:") +
      yield
  end
end

#get_num_records(search_template, options) ⇒ Integer, ...

Number of records to display on a search link badge.

Parameters:

  • search_template (Object)

    instantiated query template for the search

  • options (Hash)

    aggregation/display options

Options Hash (options):

  • aggregate_method (Symbol)

    :sum or :count (defaults to :count)

  • aggregate_column (Symbol, String)

    column to sum when aggregate_method is :sum

  • num_records (Integer)

    pre-computed record count to use instead of the template's result set length

Returns:

  • (Integer, Numeric, nil)


46
47
48
49
50
51
52
53
54
# File 'app/helpers/searches_helper.rb', line 46

def get_num_records(search_template, options)
  case options[:aggregate_method]
  when :sum
    num_records = search_template.result_set.sum(options[:aggregate_column]) if options[:aggregate_column].present?
  when :count
    num_records = options[:num_records] || search_template.result_set_length
  end
  num_records
end

#hash_to_form_params(hash) ⇒ Object



137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'app/helpers/searches_helper.rb', line 137

def hash_to_form_params(hash)
  cleaned_hash = hash.compact_blank
  pairs = cleaned_hash.to_query.split(Rack::Utils::DEFAULT_SEP)
  flattened_hash_params = {} # This allows duplicate keys which is what we want for array input tags
  flattened_hash_params.compare_by_identity
  pairs.map do |pair|
    key, value = pair.split('=', 2).map { |str| Rack::Utils.unescape(str) }
    next if value.blank?

    flattened_hash_params[key] = value
  end
  flattened_hash_params
end

#line_items_search_fields(f) ⇒ Object



209
210
211
# File 'app/helpers/searches_helper.rb', line 209

def line_items_search_fields(f)
  render partial: '/searches/line_items_criteria', locals: { f: f }
end

#outlet_purchase_catalog_collectionArray<Array(String, Integer)>

Channels an outlet purchase can have come through, for the opportunity
search filter. Only catalogs that actually carry a linked invoice appear —
the full catalog list is 121 entries, nearly all of which will never match.

Returns:

  • (Array<Array(String, Integer)>)

    [name, catalog_id] pairs



257
258
259
260
261
# File 'app/helpers/searches_helper.rb', line 257

def outlet_purchase_catalog_collection
  used_catalogs = CustomerOutletPurchase.joins(invoice: :customer).select('parties.catalog_id')

  Catalog.where(id: used_catalogs).order(:name).pluck(:name, :id)
end

#permitted_search_typesObject



17
18
19
# File 'app/helpers/searches_helper.rb', line 17

def permitted_search_types
  Search.options.select { |so| can?(:read, so[1].constantize.new) }
end

#permitted_search_types_classesObject



21
22
23
# File 'app/helpers/searches_helper.rb', line 21

def permitted_search_types_classes
  Search.options_classes.select { |search_klass| can?(:read, search_klass.new) }
end

Builds a search button/link for a query template, with an optional
record-count badge.

Parameters:

  • search_klass (Class)

    search class to instantiate the query template from

  • name_key (Symbol, String, nil) (defaults to: nil)

    named query template key, or nil for a default template

  • options (Hash) (defaults to: {})

    link and badge options

Options Hash (options):

  • aggregate_method (Symbol)

    :sum or :count for the record count (defaults to :count)

  • aggregate_column (Symbol, String)

    column to sum when aggregate_method is :sum

  • num_records (Integer)

    pre-computed record count to display

  • show_zero (Boolean)

    show the badge even when the count is zero

  • currency_symbol (String)

    currency unit for :currency format (defaults to '$')

  • precision (Integer)

    decimal precision for :currency format (defaults to 2)

  • format (Symbol)

    :currency formats the count as currency

  • counter_class (String)

    CSS class for the count badge

  • zero_label (String)

    label shown instead of a zero count

  • mute_zero (Boolean)

    de-emphasize a zero-count badge

  • class (String)

    CSS class(es) overriding the default button styling

  • in_menu (Boolean)

    style the link as a dropdown menu item

  • link_only (Boolean)

    render a plain link to the search instead of a submit button

  • style (String)

    inline style for the button

Returns:

  • (Array(String, Integer, nil))

    the link element and the record count



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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'app/helpers/searches_helper.rb', line 77

def query_template_link(search_klass, name_key = nil, options = {})
  options[:aggregate_method] ||= :count
  qt = search_klass.instantiate_query_template(name_key, options)
  link_names = []
  if qt.show_count? || options[:num_records].present?
    num_records = get_num_records qt, options
    if num_records.to_i.positive? || options[:show_zero]
      record_badge = number_to_currency(num_records, unit: options[:currency_symbol] || '$', precision: options.fetch(:precision, 2)) if options[:format] == :currency
      record_badge ||= num_records.to_i
      link_names << (:span, qt&.title&.html_safe) if qt.title.present?
      if options[:counter_class].present?
        counter_class = options[:counter_class]
      elsif link_names.present? # If we have one title entry, then we display badge style
        counter_class = 'ms-2 badge bg-primary'
      end
      if options[:zero_label] && num_records.to_f.zero?
        record_badge = options[:zero_label]
        counter_class = [counter_class, 'text-secondary'].compact.join(' ')
      elsif options[:mute_zero] && num_records.to_f.zero?
        counter_class = [counter_class, 'text-secondary'].compact.join(' ')
      end
      link_names << (:span, record_badge, class: counter_class) if record_badge.present?
    elsif qt.title
      link_names << (:span, qt.title)
    end
  elsif qt.title
    link_names << (:span, qt.title)
  end

  # Test if we are passing a class, then that overrides everything, we test for nil since a blank class
  # might mean we want no default behavior
  css_classes = []
  if options[:class].present?
    css_classes += options[:class].to_s.split
  elsif options[:in_menu].to_b # Apply in menu styling
    css_classes += %w[dropdown-item justify-content-between align-items-center d-flex]
  else
    css_classes = %w[btn btn-link p-0 m-0]
  end

  css_classes << 'btn' if css_classes.find { |cc| cc&.starts_with?('btn-') } && css_classes.exclude?('btn')

  if options[:link_only]
    link_element = link_to(num_records, execute_search_url(qt.to_params))
  else
    css_classes = css_classes.uniq.filter_map(&:presence)
    btn_options = {
      method: :post,
      class: css_classes.join(' '),
      style: options[:style],
      params: hash_to_form_params(qt.to_params)
}.compact
    link_element = button_to(searches_path, **btn_options) { link_names.compact.join.html_safe }
  end
  [
    link_element,
    num_records
  ]
end

#result_search_selector(r) ⇒ Object

Row selector checkbox on search result tables.

Class .search-result-row is the cascade-target hook for check-all
(header → rows). The change action then fires checkbox-persist#rowChange
which POSTs the diff to SearchesController#record. Both controllers
are stacked on the wrapping div in _search_results.html.erb.



157
158
159
160
161
162
163
# File 'app/helpers/searches_helper.rb', line 157

def result_search_selector(r)
   :td, check_box_tag(:resource_id,
                                 "#{r.class.main_resource_class}|#{r.id}",
                                 r.search_selected,
                                 class: 'search-result-row',
                                 data: { action: 'change->checkbox-persist#rowChange' })
end

#rma_items_search_fields(f) ⇒ Object



213
214
215
# File 'app/helpers/searches_helper.rb', line 213

def rma_items_search_fields(f)
  render partial: '/searches/rma_items_criteria', locals: { f: f }
end

#sales_rep_fields(f, fields: nil) ⇒ Object



205
206
207
# File 'app/helpers/searches_helper.rb', line 205

def sales_rep_fields(f, fields: nil)
  render partial: '/searches/sales_rep_fields', locals: { f: f, fields: fields }
end

#search_breadcrumb(search) ⇒ Object



11
12
13
14
15
# File 'app/helpers/searches_helper.rb', line 11

def search_breadcrumb(search)
  (:li, link_to(search.type.to_s.titleize.pluralize,
                           employee_searches_path(@context_user, type: search.type.to_s))) +
    (:li, link_to("#{search.type.to_s.titleize} #{search.id}", search_path(search)))
end

#search_range_text_field(form, name, display = nil, options = {}) ⇒ String

Between-or-equal range filter field for a search form.

Parameters:

  • form (SimpleForm::FormBuilder)

    the search form builder

  • name (Symbol, String)

    query param name

  • display (String, nil) (defaults to: nil)

    label text (defaults to the name)

  • options (Hash) (defaults to: {})

    field options

Options Hash (options):

  • class (String)

    CSS class for the input ('datepicker' renders a date range)

Returns:

  • (String)

    rendered input HTML



285
286
287
# File 'app/helpers/searches_helper.rb', line 285

def search_range_text_field(form, name, display = nil, options = {})
  form.input name.to_sym, label: display || name, as: options[:class] == 'datepicker' ? :between_or_equal_date : :between_or_equal
end

#search_select_field(form, name, display = nil, list = [%w[Yes y], %w[No n]], options = {}) ⇒ String

Tom-select filter field for a search form.

Parameters:

  • form (SimpleForm::FormBuilder)

    the search form builder

  • name (Symbol, String)

    query param name

  • display (String, nil) (defaults to: nil)

    label text (defaults to the name titleized)

  • list (Array<Array>) (defaults to: [%w[Yes y], %w[No n]])

    [label, value] option pairs (defaults to Yes/No)

  • options (Hash) (defaults to: {})

    field options

Options Hash (options):

  • multiple (Boolean)

    allow multiple selections (defaults to true)

  • include_blank (Boolean)

    include a blank option (defaults to true)

  • style (String)

    inline style for the input

  • hint (String)

    hint text shown under the input

Returns:

  • (String)

    rendered input HTML



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'app/helpers/searches_helper.rb', line 229

def search_select_field(form, name, display = nil, list = [%w[Yes y], %w[No n]], options = {})
  options[:multiple] = true if options[:multiple].nil?
  options[:include_blank] = true if options[:include_blank].nil?
  form.input name,
             label: display,
             as: :tom_select,
             collection: list,
             selected: @search.query_params[name],
             include_blank: options[:include_blank],
             input_html: {
               multiple: options[:multiple],
               style: options[:style]
             },
             required: false,
             hint: options[:hint]
end

#search_text_field(form, name, display = nil, options = {}) ⇒ String

Text filter field for a search form, pre-filled from the current query params.

Parameters:

  • form (SimpleForm::FormBuilder)

    the search form builder

  • name (Symbol, String)

    query param name

  • display (String, nil) (defaults to: nil)

    label text

  • options (Hash) (defaults to: {})

    field options

Options Hash (options):

  • class (String)

    CSS class for the input

  • style (String)

    inline style for the input

  • hint (String)

    hint text shown under the input

Returns:

  • (String)

    rendered input HTML



273
274
275
# File 'app/helpers/searches_helper.rb', line 273

def search_text_field(form, name, display = nil, options = {})
  form.input name, label: display, input_html: { value: @search.query_params[name], class: options[:class], style: options[:style] }, required: false, hint: options[:hint]
end


25
26
27
28
29
30
31
32
33
34
35
36
# File 'app/helpers/searches_helper.rb', line 25

def simple_search_link(search_klass, query_params: {}, selected_columns: nil, num_records: nil, format: nil, title: nil, link_class: nil, auto_count: false)
  query_template_link(search_klass, nil,
                      {
                        query_params: query_params,
                        selected_columns: selected_columns,
                        num_records: num_records,
                        format: format,
                        title: title,
                        class: link_class,
                        auto_count: auto_count
                      }.compact)&.first
end

#yes_no_boolean_field(form, name, display) ⇒ Object



289
290
291
292
293
294
295
296
297
298
# File 'app/helpers/searches_helper.rb', line 289

def yes_no_boolean_field(form, name, display)
  html_opts = { multiple: false, style: 'width:150px' }
  form.input name, label: display,
                   as: :select,
                   selected: @search.query_params[name],
                   include_blank: true,
                   collection: [%w[Yes true], %w[No false]],
                   input_html: html_opts,
                   required: false
end