Class: Spiff

Inherits:
ApplicationRecord show all
Includes:
Models::Auditable, Models::LiquidMethods
Defined in:
app/models/spiff.rb

Overview

== Schema Information

Table name: spiffs
Database name: primary

id :integer not null, primary key
begin_date :date
buying_group_ids :integer is an Array
catalog_ids :integer is an Array
enrollment_period :integer
expire_date :date
max_enrollments :integer
name :string(255)
survey_url :string(255)
created_at :datetime
updated_at :datetime

Constant Summary

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Constants included from Schedulable

Schedulable::SIMPLE_FORM_OPTIONS

Instance Attribute Summary collapse

Has many collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Models::Auditable

#all_skipped_columns, #audit_reference_data, #creator, #should_not_save_version, #stamp_record, #updater

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#begin_dateObject (readonly)



23
# File 'app/models/spiff.rb', line 23

validates :name, :begin_date, :expire_date, presence: true

#expire_dateObject (readonly)



23
# File 'app/models/spiff.rb', line 23

validates :name, :begin_date, :expire_date, presence: true

#nameObject (readonly)



23
# File 'app/models/spiff.rb', line 23

validates :name, :begin_date, :expire_date, presence: true

Class Method Details

.activeActiveRecord::Relation<Spiff>

A relation of Spiffs that are active. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Spiff>)

See Also:



31
# File 'app/models/spiff.rb', line 31

scope :active, -> { where("spiffs.begin_date <= '#{Date.current}' and spiffs.expire_date >= '#{Date.current}'") }

.active_or_recentActiveRecord::Relation<Spiff>

A relation of Spiffs that are active or recent. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Spiff>)

See Also:



30
# File 'app/models/spiff.rb', line 30

scope :active_or_recent, -> { where("spiffs.expire_date >= '#{Date.current - 1.month}'") }

.active_spiff_select(buying_group_id) ⇒ Array<Array(String, Integer)>

[name, id] pairs of currently-active SPIFFs whose
buying_group_ids array contains buying_group_id.

Parameters:

  • buying_group_id (Integer)

Returns:

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


91
92
93
# File 'app/models/spiff.rb', line 91

def self.active_spiff_select(buying_group_id)
  Spiff.active.where("buying_group_ids like '%#{buying_group_id}%'").map { |n| [n.name, n.id] }
end

.enrollment_period_optionsArray<Array(String, Integer)>

Allowed enrollment-window lengths for the SPIFF form
(enrollment_period field, in days).

Returns:

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


66
67
68
# File 'app/models/spiff.rb', line 66

def self.enrollment_period_options
  [['30 days', 30], ['60 days', 60], ['90 days', 90]]
end

.for_buying_group_idActiveRecord::Relation<Spiff>

A relation of Spiffs that are for buying group id. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Spiff>)

See Also:



39
# File 'app/models/spiff.rb', line 39

scope :for_buying_group_id, ->(buying_group_id) { where.overlap(buying_group_ids: buying_group_id).or(where('cardinality(buying_group_ids) = 0')) }

.for_catalog_idActiveRecord::Relation<Spiff>

A relation of Spiffs that are for catalog id. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Spiff>)

See Also:



40
# File 'app/models/spiff.rb', line 40

scope :for_catalog_id, ->(catalog_id) { where.overlap(catalog_ids: catalog_id).or(where('cardinality(catalog_ids) = 0')) }

.in_useActiveRecord::Relation<Spiff>

A relation of Spiffs that are in use. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Spiff>)

See Also:



33
# File 'app/models/spiff.rb', line 33

scope :in_use, -> { where('id in (select distinct(p.spiff_id) from parties p where p.spiff_id is not null)') }

.not_buying_group_specificActiveRecord::Relation<Spiff>

A relation of Spiffs that are not buying group specific. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Spiff>)

See Also:



37
# File 'app/models/spiff.rb', line 37

scope :not_buying_group_specific, -> { where(buying_group_ids: []) }

.not_catalog_specificActiveRecord::Relation<Spiff>

A relation of Spiffs that are not catalog specific. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Spiff>)

See Also:



38
# File 'app/models/spiff.rb', line 38

scope :not_catalog_specific, -> { where(catalog_ids: []) }

.not_expiredActiveRecord::Relation<Spiff>

A relation of Spiffs that are not expired. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Spiff>)

See Also:



32
# File 'app/models/spiff.rb', line 32

scope :not_expired, -> { where("spiffs.expire_date >= '#{Date.current}'") }

.recently_endedActiveRecord::Relation<Spiff>

A relation of Spiffs that are recently ended. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Spiff>)

See Also:



34
35
36
# File 'app/models/spiff.rb', line 34

scope :recently_ended, -> {
  where("spiffs.expire_date <= '#{Date.current}' and spiffs.expire_date >= '#{(Date.current - 1.month).at_beginning_of_month}'")
}

.select_optionsArray<Array(String, Integer)>

[label, id] pairs of every SPIFF, ordered by name and
annotated with the date range, for the SPIFF picker.

Returns:

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


82
83
84
# File 'app/models/spiff.rb', line 82

def self.select_options
  Spiff.order(:name).map { |n| ["#{n.name} (#{n.begin_date.to_fs(:crm_date_only)} - #{n.expire_date.to_fs(:crm_date_only)})", n.id] }
end

.select_options_for_contact(contact, include_spiff_id) ⇒ Array<Array(String, Integer)>

[name_with_period, id] pairs of SPIFFs valid for the
contact, optionally pinning an extra SPIFF (used to keep the
currently-selected option visible even after it expires).

Parameters:

  • contact (Contact)
  • include_spiff_id (Integer, nil)

Returns:

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


102
103
104
105
106
# File 'app/models/spiff.rb', line 102

def self.select_options_for_contact(contact, include_spiff_id)
  scope = valid_for_contact(contact).to_a
  scope << Spiff.find(include_spiff_id) if include_spiff_id
  scope.uniq.sort.map { |s| [s.name_with_period, s.id] }
end

.valid_for_contact(contact) ⇒ ActiveRecord::Relation<Spiff>

SPIFFs valid for the contact's parent Customer. Sales reps
use this when enrolling individual contacts (installers).

Parameters:

Returns:

  • (ActiveRecord::Relation<Spiff>)


74
75
76
77
# File 'app/models/spiff.rb', line 74

def self.valid_for_contact(contact)
  customer = contact.customer
  valid_for_customer(customer)
end

.valid_for_customer(customer) ⇒ ActiveRecord::Relation<Spiff>

SPIFFs that the Customer is eligible for: not yet expired,
restricted to the customer's catalog and (if any) buying
group. Both filters fall through when the SPIFF is "open"
(no catalog / buying-group restriction).

Parameters:

Returns:

  • (ActiveRecord::Relation<Spiff>)


57
58
59
60
61
# File 'app/models/spiff.rb', line 57

def self.valid_for_customer(customer)
  spiffs = not_expired.for_catalog_id(customer.catalog_id)
  spiffs = spiffs.for_buying_group_id(customer.buying_group_id) if customer.buying_group_id
  spiffs
end

Instance Method Details

#contactsActiveRecord::Relation<Contact>

Returns:

  • (ActiveRecord::Relation<Contact>)

See Also:



25
# File 'app/models/spiff.rb', line 25

has_many :contacts

#eligible_reward(itemizable) ⇒ BigDecimal

Total reward earned by itemizable (an Order or
LineItem) — sum of every linked SpiffReward's computed
amount. Used by Liquid templates exposed to enrollees.

Parameters:

Returns:

  • (BigDecimal)


158
159
160
161
162
163
164
# File 'app/models/spiff.rb', line 158

def eligible_reward(itemizable)
  reward = BigDecimal('0')
  spiff_rewards.each do |r|
    reward += r.eligible_reward(itemizable)
  end
  reward
end

#eligible_rewards(itemizable) ⇒ Hash{String=>BigDecimal}

Same as #eligible_reward but broken out by reward name —
{ "Tier 1" => 25.00, "Bonus" => 5.00 } — for showing the
rep how the payout was constructed.

Parameters:

Returns:

  • (Hash{String=>BigDecimal})


172
173
174
175
176
177
178
# File 'app/models/spiff.rb', line 172

def eligible_rewards(itemizable)
  rewards = {}
  spiff_rewards.each do |r|
    rewards[r.name] = r.eligible_reward(itemizable) if r.eligible_reward(itemizable) > 0
  end
  rewards
end

#expired?Boolean

Returns:

  • (Boolean)


131
132
133
# File 'app/models/spiff.rb', line 131

def expired?
  expire_date && expire_date <= Date.current
end

#get_bg_short_nameString?

7-character abbreviation of the first associated buying-group
name (with spaces stripped) — used as a slug in
#invoice_number. Returns nil for SPIFFs that aren't tied
to a buying group.

Returns:

  • (String, nil)


202
203
204
205
206
207
208
# File 'app/models/spiff.rb', line 202

def get_bg_short_name
  return nil if buying_group_ids.blank?

  bg = BuyingGroup.find(buying_group_ids.first)
  y = Object.new.extend(ActionView::Helpers::TextHelper)
  y.truncate(bg.name.delete(' '), length: 7, omission: '')
end

#invoice_number(order) ⇒ String

Synthetic invoice number for the rep-payout invoice that
writes off a SPIFF earning. Format:
SPIFF-<BG-SHORT>-<MMYY>-<ORDER#>.

Parameters:

Returns:

  • (String)


186
187
188
# File 'app/models/spiff.rb', line 186

def invoice_number(order)
  ['SPIFF', get_bg_short_name, begin_date.strftime('%m%y'), order.reference_number].compact.join('-')
end

#is_active?Boolean

Whether the SPIFF is past its expiration date.

Returns:

  • (Boolean)


192
193
194
# File 'app/models/spiff.rb', line 192

def is_active?
  expire_date >= Date.current
end

#name_with_periodString

SPIFF name annotated with its enrollment-period length
(when set) and an (expired) tag for past-end-date rows.
Used in dropdowns shared between active and historical SPIFFs.

Returns:

  • (String)


145
146
147
148
149
150
# File 'app/models/spiff.rb', line 145

def name_with_period
  res = name
  res << " (#{enrollment_period} day period)" if enrollment_period.present?
  res << ' (expired)' if expired?
  res
end

#ordersActiveRecord::Relation<Order>

Returns:

  • (ActiveRecord::Relation<Order>)

See Also:



28
# File 'app/models/spiff.rb', line 28

has_many :orders, through: :spiff_enrollments

#spiff_enrollmentsActiveRecord::Relation<SpiffEnrollment>

Returns:

See Also:



27
# File 'app/models/spiff.rb', line 27

has_many :spiff_enrollments

#spiff_rewardsActiveRecord::Relation<SpiffReward>

Returns:

See Also:



26
# File 'app/models/spiff.rb', line 26

has_many :spiff_rewards, dependent: :destroy

#to_sString

Returns:

  • (String)


136
137
138
# File 'app/models/spiff.rb', line 136

def to_s
  "#{name} [#{id}]"
end

#total_revenue(spiff_state = %w[awaiting_payment paid])) ⇒ BigDecimal

Total Order revenue (gross total) attached to this SPIFF in
one of the given states. Defaults to "earned but not yet
paid out" totals.

Parameters:

  • spiff_state (Array<String>) (defaults to: %w[awaiting_payment paid]))

Returns:

  • (BigDecimal)


114
115
116
117
# File 'app/models/spiff.rb', line 114

def total_revenue(spiff_state = %w[awaiting_payment paid])
  spiff_enrollment_ids = spiff_enrollments.map(&:id)
  Order.where(spiff_enrollment_id: spiff_enrollment_ids, spiff_state: spiff_state).sum(:total)
end

#total_rewards(spiff_state = %w[awaiting_payment paid])) ⇒ BigDecimal

Total payout the SPIFF would owe across orders in the given
state(s) — sums SpiffReward#eligible_reward per order.

Parameters:

  • spiff_state (Array<String>) (defaults to: %w[awaiting_payment paid]))

Returns:

  • (BigDecimal)


124
125
126
127
128
# File 'app/models/spiff.rb', line 124

def total_rewards(spiff_state = %w[awaiting_payment paid])
  spiff_enrollment_ids = spiff_enrollments.map(&:id)
  orders = Order.where(spiff_enrollment_id: spiff_enrollment_ids, spiff_state: spiff_state)
  orders.to_a.sum { |o| o.spiff_enrollment.spiff.eligible_reward(o) }
end