Class: Report::ChannelRevenue::Command

Inherits:
BaseCommand show all
Defined in:
app/services/report/channel_revenue/command.rb

Overview

Criteria + orchestration for the Channel Revenue report
(/reports/channel_revenue).

The report compares gross revenue per sales channel across two date
ranges: period 1 (the baseline) and period 2 (the current period). Two
preset selects drive the ranges so an in-progress month/quarter/year is
never silently compared against a full prior span: the current period
ends at Date.current and the comparison defaults to the same period last
year with matched day counts.

Constant Summary collapse

PERIOD_PRESETS =

Period shortcut presets; they drive period 2 (the "current" period).
The to-date presets end at Date.current so an open month/quarter/year is
never silently compared against a full prior span.

{
  'month_to_date'   => 'Month to date',
  'last_month'      => 'Last month',
  'quarter_to_date' => 'Quarter to date',
  'last_quarter'    => 'Last quarter',
  'year_to_date'    => 'Year to date',
  'last_year'       => 'Last year'
}.freeze
COMPARISON_PRESETS =

Comparison presets; they drive period 1 (the baseline period).
"Matched days" keeps the same calendar day count in both periods,
"matched business days" keeps the same working-day count (Mon-Fri minus
US company holidays), and "full period" compares against the complete
calendar unit (month/quarter/year) the current period belongs to.

{
  'same_period_last_year'                => 'Same period last year (matched days)',
  'same_period_last_year_business_days'  => 'Same period last year (matched business days)',
  'same_period_last_year_full'           => 'Same period last year (full period)',
  'previous_period'                      => 'Previous period (matched days)',
  'previous_period_full'                 => 'Previous period (full period)'
}.freeze
PERIOD_PRESET_UNITS =

Calendar unit each period preset belongs to. Needed for the "full
period" comparisons: quarter to date and month to date produce the same
dates early in a quarter, so the unit cannot always be inferred from
the dates alone.

{
  'month_to_date'   => :month,
  'last_month'      => :month,
  'quarter_to_date' => :quarter,
  'last_quarter'    => :quarter,
  'year_to_date'    => :year,
  'last_year'       => :year
}.freeze
CALENDAR_COMPANY_IDS =

Sales companies whose calendars appear in the business-day breakdown.

[Company::USA, Company::CAN, Company::NLD].freeze

Constants inherited from BaseCommand

BaseCommand::ARRAY_TYPE_NAMES

Instance Attribute Summary

Attributes inherited from BaseCommand

#results

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from BaseCommand

#[], #attributes, #email_parameters, #email_partial, #initialize

Methods included from Heatwave::AttributeNormalizing

normalize

Constructor Details

This class inherits a constructor from Report::BaseCommand

Class Method Details

.comparison_preset_optionsArray<Array(String, String)>

Options for the "Compare to" select in the criteria form.

Returns:

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

    [label, value] pairs



89
90
91
# File 'app/services/report/channel_revenue/command.rb', line 89

def self.comparison_preset_options
  COMPARISON_PRESETS.map { |value, label| [label, value] }
end

.period_preset_optionsArray<Array(String, String)>

Options for the "Period" select in the criteria form.

Returns:

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

    [label, value] pairs



82
83
84
# File 'app/services/report/channel_revenue/command.rb', line 82

def self.period_preset_options
  PERIOD_PRESETS.map { |value, label| [label, value] }
end

Instance Method Details

#business_day_breakdown(range) ⇒ Hash

Structured breakdown of business days in +range+ for the UI: count,
named holidays on the matching calendar, per-day kind chips, and
per-company counts so multi-company holiday differences are visible.

Parameters:

  • range (Range, nil)

Returns:

  • (Hash)


228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'app/services/report/channel_revenue/command.rb', line 228

def business_day_breakdown(range)
  empty = { 'count' => 0, 'matching_label' => matching_company_label, 'holidays' => [], 'days' => [], 'by_company' => {} }
  # begin/end, not first/last — first raises RangeError on a beginless
  # range (AppSignal #4933)
  return empty if range&.begin.blank? || range&.end.blank?

  match_id = matching_company_id
  matching_holidays = holiday_payload.select do |h|
    h['company_id'] == match_id && range.cover?(Date.iso8601(h['date']))
  end
  holiday_by_date = matching_holidays.index_by { |h| h['date'] }

  days = range.map do |date|
    iso = date.iso8601
    hol = holiday_by_date[iso]
    kind = if hol then 'holiday'
           elsif date.on_weekend? then 'weekend'
           else 'business'
           end
    {
      'date' => iso,
      'day' => date.day,
      'kind' => kind,
      'label' => hol ? hol['name'] : date.strftime('%a')
    }
  end

  # Only show companies that are selected on the report (or all calendars
  # when the multi-select is empty / defaults).
  selected = Array(company_ids).map(&:to_i)
  shown = calendar_companies.select { |c| selected.blank? || selected.include?(c['id']) }
  shown = calendar_companies if shown.empty?

  by_company = shown.to_h do |company|
    [company['label'], working_days_for_company(range, company['id'])]
  end

  {
    'count' => working_days_in(range),
    'matching_label' => matching_company_label,
    'holidays' => matching_holidays,
    'days' => days,
    'by_company' => by_company
  }
end

#calendar_companiesArray<Hash>

Returns { 'id' => Integer, 'label' => String } for the
calendars shown in the per-company breakdown.

Returns:

  • (Array<Hash>)

    { 'id' => Integer, 'label' => String } for the
    calendars shown in the per-company breakdown



193
194
195
196
197
# File 'app/services/report/channel_revenue/command.rb', line 193

def calendar_companies
  @calendar_companies ||= Company.where(id: CALENDAR_COMPANY_IDS).map do |company|
    { 'id' => company.id, 'label' => company.short_name.presence || company.country_iso3 }
  end
end

#executeHash?

Runs the report query when the criteria are valid.

Returns:

  • (Hash, nil)

    the report result hash, nil when invalid



104
105
106
107
108
109
110
111
112
113
114
115
# File 'app/services/report/channel_revenue/command.rb', line 104

def execute
  return unless valid?

  @results = Report::ChannelRevenue::Query.report(
                      period1_range: period1_range,
                      period2_range: period2_range,
                      primary_sales_rep_ids: primary_sales_rep_ids.presence,
                      invoice_types: invoice_types,
                      company_ids: company_ids,
                      consolidated_currency: consolidated_currency
                    )
end

#holiday_payloadArray<Hash>

Holidays for USA/CAN/NLD — all rows, not a sliding window. Custom report
ranges are unbounded, so truncating would desync the breakdown from
working_days_for_company (which reads the full calendar).

Returns:

  • (Array<Hash>)

    each with string keys date/name/company_id/company



178
179
180
181
182
183
184
185
186
187
188
189
# File 'app/services/report/channel_revenue/command.rb', line 178

def holiday_payload
  @holiday_payload ||= Company.where(id: CALENDAR_COMPANY_IDS).includes(:company_holidays).flat_map do |company|
    company.company_holidays.map do |holiday|
      {
        'date' => holiday.holiday_date.iso8601,
        'name' => holiday.holiday_name.to_s,
        'company_id' => company.id,
        'company' => company.short_name.presence || company.country_iso3
      }
    end
  end
end

#invoice_types=(new_invoice_types) ⇒ Array<String>

Dedupe and drop blank invoice types on assignment.

Parameters:

  • new_invoice_types (Array<String>, nil)

    submitted invoice types

Returns:

  • (Array<String>)

    the normalized invoice types



97
98
99
# File 'app/services/report/channel_revenue/command.rb', line 97

def invoice_types=(new_invoice_types)
  super((new_invoice_types || []).filter_map(&:presence).uniq)
end

#matching_company_idInteger

Holiday calendar that drives matched-business-day comparisons.
Sole selected company → that company's calendar; multiple → USA if
included, otherwise the first selected sales company.

Returns:

  • (Integer)


204
205
206
207
208
209
210
211
# File 'app/services/report/channel_revenue/command.rb', line 204

def matching_company_id
  ids = Array(company_ids).map(&:to_i).select { |id| CALENDAR_COMPANY_IDS.include?(id) }
  ids = CALENDAR_COMPANY_IDS.dup if ids.empty?
  return ids.first if ids.size == 1
  return Company::USA if ids.include?(Company::USA)

  ids.first
end

#matching_company_labelString

Returns short label for the matching calendar (e.g. "WY-CA").

Returns:

  • (String)

    short label for the matching calendar (e.g. "WY-CA")



275
276
277
# File 'app/services/report/channel_revenue/command.rb', line 275

def matching_company_label
  calendar_companies.find { |c| c['id'] == matching_company_id }&.dig('label') || 'USA'
end

#period1_humanizedString

Returns baseline range rendered for the results header.

Returns:

  • (String)

    baseline range rendered for the results header



128
129
130
# File 'app/services/report/channel_revenue/command.rb', line 128

def period1_humanized
  "From #{period1_range.first} until #{period1_range.last}"
end

#period1_locked?Boolean

Whether the comparison date row is locked to the selected comparison
preset (editable only when "Custom" is chosen).

Returns:

  • (Boolean)


158
159
160
# File 'app/services/report/channel_revenue/command.rb', line 158

def period1_locked?
  COMPARISON_PRESETS.key?(comparison_preset)
end

#period1_rangeRange

Returns baseline (comparison) date range.

Returns:

  • (Range)

    baseline (comparison) date range



118
119
120
# File 'app/services/report/channel_revenue/command.rb', line 118

def period1_range
  (period1_gteq && period1_lteq && period1_gteq)..period1_lteq
end

#period1_working_daysInteger

Returns working days (Mon-Fri, US company holidays excluded)
inside the baseline period.

Returns:

  • (Integer)

    working days (Mon-Fri, US company holidays excluded)
    inside the baseline period



139
140
141
# File 'app/services/report/channel_revenue/command.rb', line 139

def period1_working_days
  @period1_working_days ||= working_days_in(period1_range)
end

#period2_humanizedString

Returns current period range rendered for the results header.

Returns:

  • (String)

    current period range rendered for the results header



133
134
135
# File 'app/services/report/channel_revenue/command.rb', line 133

def period2_humanized
  "From #{period2_range.first} until #{period2_range.last}"
end

#period2_locked?Boolean

Whether the current-period date row is locked to the selected period
preset (editable only when "Custom" is chosen).

Returns:

  • (Boolean)


166
167
168
# File 'app/services/report/channel_revenue/command.rb', line 166

def period2_locked?
  PERIOD_PRESETS.key?(period_preset)
end

#period2_rangeRange

Returns current period date range.

Returns:

  • (Range)

    current period date range



123
124
125
# File 'app/services/report/channel_revenue/command.rb', line 123

def period2_range
  (period2_gteq && period2_lteq && period2_gteq)..period2_lteq
end

#period2_working_daysInteger

Returns working days (Mon-Fri, US company holidays excluded)
inside the current period.

Returns:

  • (Integer)

    working days (Mon-Fri, US company holidays excluded)
    inside the current period



145
146
147
# File 'app/services/report/channel_revenue/command.rb', line 145

def period2_working_days
  @period2_working_days ||= working_days_in(period2_range)
end

#success?Boolean

Returns true when the criteria are valid and the query ran.

Returns:

  • (Boolean)

    true when the criteria are valid and the query ran



150
151
152
# File 'app/services/report/channel_revenue/command.rb', line 150

def success?
  valid? && @results.present?
end

#us_holiday_datesArray<Date>

Matching-company holiday dates — used for matched-business-day comparisons.

Returns:

  • (Array<Date>)


216
217
218
219
220
# File 'app/services/report/channel_revenue/command.rb', line 216

def us_holiday_dates
  holiday_payload
    .select { |h| h['company_id'] == matching_company_id }
    .map { |h| Date.iso8601(h['date']) }
end