Class: Coupon

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

Overview

== Schema Information

Table name: coupons
Database name: primary

id :integer not null, primary key
allow_non_domestic_shipping :boolean
allow_oversized_items :boolean default(FALSE), not null
amount_goods :decimal(10, 2)
amount_services :decimal(10, 2)
amount_services_ar :float is an Array
amount_shipping :decimal(10, 2)
auto_apply :boolean default(FALSE), not null
auto_buying_group_exclude :boolean
buying_group_ids_exclude :boolean default(FALSE), not null
calculation_type_goods :string(5)
calculation_type_services :string(5)
calculation_type_shipping :string(5)
code :string(255)
description :text
details :text
effective_date :date
exclude_opportunity_reception_types_filters :boolean default(FALSE), not null
exclude_opportunity_types_filters :boolean default(FALSE), not null
exclude_order_reception_types_filters :boolean default(FALSE), not null
exclude_order_types_filters :boolean default(FALSE), not null
exclude_state_codes :boolean default(FALSE), not null
exclusion_level :integer default("not_exclusive"), not null
expiration_date :date
first_time_customers_only :boolean default(FALSE), not null
is_inactive :boolean default(FALSE), not null
last_used_at :datetime
legacy_amount_goods :string(100)
legacy_amount_services :string(100)
legacy_amount_shipping :string(100)
opportunity_reception_types_filter :string default([]), is an Array
opportunity_types_filter :string default([]), is an Array
order_reception_types_filter :string(255) default([]), is an Array
order_types_filter :string(255) default([]), is an Array
p5 :string(100)
p6 :string(100)
position :integer
profile_ids_exclude :boolean
promo_page_content_html :text
promo_tracking :boolean default(FALSE), not null
role_ids_restriction :integer default([]), is an Array
sale_page_position :integer default(0), not null
serial_numbers_only :boolean default(FALSE), not null
ships_economy_only :boolean default(FALSE), not null
single_use_per_customer :boolean default(FALSE), not null
state_codes :string(255) default([]), is an Array
store_id_restriction :integer
tier :integer default(3), not null
title :text
type :string(50)
created_at :datetime
updated_at :datetime
auto_apply_customer_filter_id :integer
creator_id :integer
customer_filter_id :integer
image_id :integer
publication_id :integer
source_id :integer
updater_id :integer

Indexes

by_auto_ap_st_codes (auto_apply,state_codes)
idx_code (code)
idx_type_is_inactive (type,is_inactive)
index_coupons_on_auto_apply_customer_filter_id (auto_apply_customer_filter_id)
index_coupons_on_customer_filter_id (customer_filter_id)
index_coupons_on_image_id (image_id)
index_coupons_on_position (position)
index_coupons_on_single_use_per_customer (single_use_per_customer)
index_coupons_on_type_and_id (type,id)

Foreign Keys

coupons_auto_apply_customer_filter_id_fk (auto_apply_customer_filter_id => customer_filters.id)
coupons_customer_filter_id_fk (customer_filter_id => customer_filters.id)
fk_rails_... (image_id => digital_assets.id)

Defined Under Namespace

Classes: ApplyCoupon, CatalogItemPricing, DeleteDiscount, ItemizableDiscountCalculator, LineItemDiscountAllocator, MsrpAllocator, OverlappingPromoValidator, PromoItemSync, Qualifier, QueryBuilder, Tier1BaseItemPricing, Tier2ProgramPricing, Tier3PromotionalPricing

Constant Summary collapse

INSTRUCTIONS =
'Placeholder'
VALID_PRICE_MATRIX_CALCULATIONS =
['%', '$', 'DP', 'MP']
FIXED_DOLLAR_CALCULATIONS =
{
  'Flat Amount Off' => '$',
  'Adjustable (+ or -)' => '?',
  'Adjustable (Discount Only)' => '-?',
  'Adjustable (Charge Only)' => '+?'
}
FIXED_DOLLAR_CALCULATIONS_TYPES =
FIXED_DOLLAR_CALCULATIONS.map { |_k, v| v }
CORE_CALCULATIONS =
FIXED_DOLLAR_CALCULATIONS.merge({
  'Percentage Off' => '%',
  'Flat Amount Off Per Unit' => '$1',
  'Percentage Off MSRP' => '%M',
  'Percentage Off Catalog Price with VAT' => '%V',
  'Percentage Off per Min Qty Met' => '%1',
  'Force Discount Unit Price' => 'DP',
  'Force MSRP' => 'MP',
  'Promo Matrix' => 'MX'
})
CALCULATION_TYPES_GOODS =
CORE_CALCULATIONS.merge({
  'Sq.Ft Multiplier for MSRP' => 'SF',
  'Linear Ft Multiplier for MSRP' => 'LF',
  'Sq.Ft Multiplier after base discount' => 'SD',
  'Linear Ft Multiplier after base discount' => 'LD'
})
CALCULATION_TYPES_SERVICES =
CORE_CALCULATIONS
CALCULATION_TYPES_SHIPPING =
{ 'Percentage Off' => '%', 'Flat Amount Off' => '$',
'Flat Rate Shipping' => '_R', 'Adjustable (Discount or Charge)' => '?',
'Adjustable (Discount Only)' => '-?', 'Adjustable (Charge Only)' => '+?' }
ADJUSTABLE_CALCULATIONS =
['?', '-?', '+?']
FREE_ONLINE_SHIPPING_COUPON_CODES =
%w[FREEGNDONLINE FREEGNDONLINECA FREE_ECONOMY_ONLINE_USA FREE_ECONOMY_ONLINE_CAN]
FREE_ECONOMY_SHIPPING_ONLINE_COUPON_CODES_BY_COUNTRY =
{ USA: 'FREE_ECONOMY_ONLINE_USA', CAN: 'FREE_ECONOMY_ONLINE_CAN' }
ECONOMY_SHIPPING_MATCH_CRM_COUPON_CODE =
'ECONOMY_SHIPPING_MATCH_CRM'
SKIP_BLACKLISTING_COUPON_CODES =

this blacklisting just breaks things when holding/release. Just skip for these codes!

[ECONOMY_SHIPPING_MATCH_CRM_COUPON_CODE] + FREE_ECONOMY_SHIPPING_ONLINE_COUPON_CODES_BY_COUNTRY.values

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Instance Attribute Summary collapse

Belongs to collapse

Methods included from Models::Auditable

#creator, #updater

Has many collapse

Delegated Instance Attributes collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Models::HtmlContent

#sanitize_html_fragment

Methods included from Models::Auditable

#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#amount_goods_overrideObject

Returns the value of attribute amount_goods_override.



132
133
134
# File 'app/models/coupon.rb', line 132

def amount_goods_override
  @amount_goods_override
end

#amount_services_overrideObject

Returns the value of attribute amount_services_override.



132
133
134
# File 'app/models/coupon.rb', line 132

def amount_services_override
  @amount_services_override
end

#amount_shipping_overrideObject

Returns the value of attribute amount_shipping_override.



132
133
134
# File 'app/models/coupon.rb', line 132

def amount_shipping_override
  @amount_shipping_override
end

#serial_numberObject

Returns the value of attribute serial_number.



132
133
134
# File 'app/models/coupon.rb', line 132

def serial_number
  @serial_number
end

#ship_to_continental_regionsObject

Returns the value of attribute ship_to_continental_regions.



132
133
134
# File 'app/models/coupon.rb', line 132

def ship_to_continental_regions
  @ship_to_continental_regions
end

Class Method Details

.activeActiveRecord::Relation<Coupon>

A relation of Coupons that are active. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



171
# File 'app/models/coupon.rb', line 171

scope :active, -> { where.not(is_inactive: true) }

.active_and_futureActiveRecord::Relation<Coupon>

A relation of Coupons that are active and future. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



189
# File 'app/models/coupon.rb', line 189

scope :active_and_future, -> { active.where.not(effective_date: nil).where(Coupon[:effective_date].gt(Date.current)) }

.affect_goodsActiveRecord::Relation<Coupon>

A relation of Coupons that are affect goods. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



193
# File 'app/models/coupon.rb', line 193

scope :affect_goods, -> { where.not(calculation_type_goods: nil) }

.affect_servicesActiveRecord::Relation<Coupon>

A relation of Coupons that are affect services. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



194
# File 'app/models/coupon.rb', line 194

scope :affect_services, -> { where.not(calculation_type_services: nil) }

.affect_shippingActiveRecord::Relation<Coupon>

A relation of Coupons that are affect shipping. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



195
# File 'app/models/coupon.rb', line 195

scope :affect_shipping, -> { where.not(calculation_type_shipping: nil) }

.auto_appliedActiveRecord::Relation<Coupon>

A relation of Coupons that are auto applied. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



173
# File 'app/models/coupon.rb', line 173

scope :auto_applied, -> { where(auto_apply: true) }

.by_codeActiveRecord::Relation<Coupon>

A relation of Coupons that are by code. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



170
# File 'app/models/coupon.rb', line 170

scope :by_code, -> { order(:code) }

.calculation_types_for_goods_selectObject



293
294
295
# File 'app/models/coupon.rb', line 293

def self.calculation_types_for_goods_select
  CALCULATION_TYPES_GOODS.to_a.sort
end

.calculation_types_for_services_selectObject



297
298
299
# File 'app/models/coupon.rb', line 297

def self.calculation_types_for_services_select
  CALCULATION_TYPES_SERVICES.to_a.sort
end

.calculation_types_for_shipping_selectObject



301
302
303
# File 'app/models/coupon.rb', line 301

def self.calculation_types_for_shipping_select
  CALCULATION_TYPES_SHIPPING.to_a.sort
end

.coupon_type_for_selectObject



274
275
276
# File 'app/models/coupon.rb', line 274

def self.coupon_type_for_select
  coupon_types.invert
end

.coupon_typesObject



270
271
272
# File 'app/models/coupon.rb', line 270

def self.coupon_types
  ['Coupon::Tier1BaseItemPricing', 'Coupon::Tier2ProgramPricing', 'Coupon::Tier3PromotionalPricing'].index_with { |t| t.constantize.friendly_type_name }
end

.customer_accessibleActiveRecord::Relation<Coupon>

A relation of Coupons that are customer accessible. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



188
# File 'app/models/coupon.rb', line 188

scope :customer_accessible, -> { tier3.active.non_auto_applied }

.customer_filters_for_selectObject



313
314
315
# File 'app/models/coupon.rb', line 313

def self.customer_filters_for_select
  CustomerFilter.joins(:coupons).order(CustomerFilter[:name]).distinct.pluck(:name, :id)
end

.discounted_priceActiveRecord::Relation<Coupon>

A relation of Coupons that are discounted price. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



176
# File 'app/models/coupon.rb', line 176

scope :discounted_price, -> { where(amount_goods: 'DP') }

.exclude_ships_economy_onlyActiveRecord::Relation<Coupon>

A relation of Coupons that are exclude ships economy only. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



199
# File 'app/models/coupon.rb', line 199

scope :exclude_ships_economy_only, -> { where.not(ships_economy_only: true) }

.exclusion_level_for_selectObject



278
279
280
# File 'app/models/coupon.rb', line 278

def self.exclusion_level_for_select
  exclusion_levels.map { |k, _v| [k.humanize, k] }
end

.expiredActiveRecord::Relation<Coupon>

A relation of Coupons that are expired. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



180
# File 'app/models/coupon.rb', line 180

scope :expired, ->(date = Date.current) { where.not(expiration_date: nil).where(Coupon[:expiration_date].lteq(date)) }

.fixed_dollarActiveRecord::Relation<Coupon>

A relation of Coupons that are fixed dollar. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



190
191
192
# File 'app/models/coupon.rb', line 190

scope :fixed_dollar, -> {
  where(calculation_type_goods: Coupon::FIXED_DOLLAR_CALCULATIONS_TYPES).or(where(calculation_type_services: Coupon::FIXED_DOLLAR_CALCULATIONS_TYPES)).or(where(calculation_type_shipping: Coupon::FIXED_DOLLAR_CALCULATIONS_TYPES))
}

.for_publicActiveRecord::Relation<Coupon>

A relation of Coupons that are for public. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



182
# File 'app/models/coupon.rb', line 182

scope :for_public, -> { tier3.where(Coupon[:role_ids_restriction].contains([Role.find_by(name: 'customer').id]).or(Coupon[:role_ids_restriction].eq([])).or(Coupon[:role_ids_restriction].eq(nil))) }

.free_online_shippingActiveRecord::Relation<Coupon>

A relation of Coupons that are free online shipping. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



196
# File 'app/models/coupon.rb', line 196

scope :free_online_shipping, -> { where(code: Coupon::FREE_ONLINE_SHIPPING_COUPON_CODES) }

.friendly_calculation_name(calculation_type) ⇒ Object



286
287
288
289
290
291
# File 'app/models/coupon.rb', line 286

def self.friendly_calculation_name(calculation_type)
  CORE_CALCULATIONS.invert[calculation_type] ||
    CALCULATION_TYPES_GOODS.invert[calculation_type] ||
    CALCULATION_TYPES_SERVICES.invert[calculation_type] ||
    CALCULATION_TYPES_SHIPPING.invert[calculation_type] || 'Unknown'
end

.friendly_price_matrix_calculation_namesObject



282
283
284
# File 'app/models/coupon.rb', line 282

def self.friendly_price_matrix_calculation_names
  Coupon::VALID_PRICE_MATRIX_CALCULATIONS.map { |c| Coupon.friendly_calculation_name(c) }.to_sentence
end

.in_effect_onActiveRecord::Relation<Coupon>

A relation of Coupons that are in effect on. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



178
# File 'app/models/coupon.rb', line 178

scope :in_effect_on, ->(date = Date.current) { active.where(Coupon[:effective_date].eq(nil).or(Coupon[:effective_date].lteq(date))).where(Coupon[:expiration_date].eq(nil).or(Coupon[:expiration_date].gteq(date))) }

.inactiveActiveRecord::Relation<Coupon>

A relation of Coupons that are inactive. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



172
# File 'app/models/coupon.rb', line 172

scope :inactive, -> { where(is_inactive: true) }

.instantiate_by_type(type) ⇒ Object



262
263
264
# File 'app/models/coupon.rb', line 262

def self.instantiate_by_type(type)
  (coupon_types.keys & [type])[0].constantize
end

.msrp_priceActiveRecord::Relation<Coupon>

A relation of Coupons that are msrp price. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



177
# File 'app/models/coupon.rb', line 177

scope :msrp_price, -> { where(amount_goods: %w[MP SF LF]) }

.non_auto_appliedActiveRecord::Relation<Coupon>

A relation of Coupons that are non auto applied. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



174
# File 'app/models/coupon.rb', line 174

scope :non_auto_applied, -> { where(auto_apply: false) }

.not_expiredActiveRecord::Relation<Coupon>

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

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



181
# File 'app/models/coupon.rb', line 181

scope :not_expired, ->(date = Date.current) { where.not(expiration_date: nil).where(Coupon[:expiration_date].gt(date)) }

.not_in_effect_onActiveRecord::Relation<Coupon>

A relation of Coupons that are not in effect on. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



179
# File 'app/models/coupon.rb', line 179

scope :not_in_effect_on, ->(date = Date.current) { where(Coupon[:effective_date].eq(nil).or(Coupon[:effective_date].gt(date))).where(Coupon[:expiration_date].eq(nil).or(Coupon[:expiration_date].lt(date))) }

.order_types_for_selectObject



266
267
268
# File 'app/models/coupon.rb', line 266

def self.order_types_for_select
  %w[SO CO ST]
end

.promoActiveRecord::Relation<Coupon>

A relation of Coupons that are promo. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



200
# File 'app/models/coupon.rb', line 200

scope :promo, -> { where(promo_tracking: true) }

.promo_for_sale_pageActiveRecord::Relation<Coupon>

A relation of Coupons that are promo for sale page. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



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

scope :promo_for_sale_page, -> {
  # A little hardcoding for now until the customer filter are better defined
  excluded_codes = []
  excluded_codes << 'FREE_ECONOMY_ONLINE_USA' unless I18n.locale == :'en-US'
  excluded_codes << 'FREE_ECONOMY_ONLINE_CAN' unless I18n.locale.in?(%i[en-CA fr-CA])
  active.in_effect_on.where.not(code: excluded_codes).where(Coupon[:sale_page_position].gt(0)).order(sale_page_position: :asc, created_at: :desc)
}

.promo_matrixActiveRecord::Relation<Coupon>

A relation of Coupons that are promo matrix. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



186
# File 'app/models/coupon.rb', line 186

scope :promo_matrix, -> { where(calculation_type_goods: 'MX').or(where(calculation_type_services: 'MX')).or(where(calculation_type_shipping: 'MX')) }

.select_optionsObject



305
306
307
# File 'app/models/coupon.rb', line 305

def self.select_options
  all.order(:code).pluck(:code, :id)
end

.ships_economy_onlyActiveRecord::Relation<Coupon>

A relation of Coupons that are ships economy only. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



198
# File 'app/models/coupon.rb', line 198

scope :ships_economy_only, -> { where(ships_economy_only: true) }

.sortedActiveRecord::Relation<Coupon>

A relation of Coupons that are sorted. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



169
# File 'app/models/coupon.rb', line 169

scope :sorted, -> { order(Coupon[:type], Coupon[:position]) }

.tier1ActiveRecord::Relation<Coupon>

A relation of Coupons that are tier1. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



183
# File 'app/models/coupon.rb', line 183

scope :tier1, -> { where(type: 'Coupon::Tier1BaseItemPricing') }

.tier1and3ActiveRecord::Relation<Coupon>

A relation of Coupons that are tier1and3. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



187
# File 'app/models/coupon.rb', line 187

scope :tier1and3, -> { where(type: ['Coupon::Tier1BaseItemPricing', 'Coupon::Tier3PromotionalPricing']) }

.tier2ActiveRecord::Relation<Coupon>

A relation of Coupons that are tier2. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



184
# File 'app/models/coupon.rb', line 184

scope :tier2, -> { where(type: 'Coupon::Tier2ProgramPricing') }

.tier2_select_optionsObject



309
310
311
# File 'app/models/coupon.rb', line 309

def self.tier2_select_options
  tier2.order(:title).pluck(:title, :id)
end

.tier3ActiveRecord::Relation<Coupon>

A relation of Coupons that are tier3. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



185
# File 'app/models/coupon.rb', line 185

scope :tier3, -> { where(type: 'Coupon::Tier3PromotionalPricing') }

.with_economy_shipping_match_crmActiveRecord::Relation<Coupon>

A relation of Coupons that are with economy shipping match crm. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



197
# File 'app/models/coupon.rb', line 197

scope :with_economy_shipping_match_crm, -> { where(code: Coupon::ECONOMY_SHIPPING_MATCH_CRM_COUPON_CODE) }

.with_publicationActiveRecord::Relation<Coupon>

A relation of Coupons that are with publication. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



175
# File 'app/models/coupon.rb', line 175

scope :with_publication, -> { where.not(publication_id: nil) }

Instance Method Details

#adjustableObject



419
420
421
# File 'app/models/coupon.rb', line 419

def adjustable
  goods_adjustable or services_adjustable or shipping_adjustable
end

#all_applicable_catalog_itemsObject



450
451
452
453
454
455
456
457
# File 'app/models/coupon.rb', line 450

def all_applicable_catalog_items
  # Our items ids (real-time from ltree):
  item_ids = all_applicable_item_ids
  # Our catalogs:
  catalog_ids = all_catalog_ids || []
  # Intersect
  CatalogItem.joins(:item).where(items: { id: item_ids }, catalog_id: catalog_ids).available_for_edi_feeds
end

#all_applicable_item_idsObject

Returns all applicable item IDs using real-time ltree queries.
No caching needed - ltree queries are fast (~1-2ms per filter).



437
438
439
# File 'app/models/coupon.rb', line 437

def all_applicable_item_ids
  product_filters.where(apply_discount: true).flat_map { |pf| pf.applicable_item_ids_set.to_a }.uniq
end

#all_catalog_idsObject

Alias for Customer_filter#all_catalog_ids

Returns:

  • (Object)

    Customer_filter#all_catalog_ids

See Also:



244
# File 'app/models/coupon.rb', line 244

delegate :all_catalog_ids, to: :customer_filter, allow_nil: true

#apply_source_to_customer(customer) ⇒ Object



577
578
579
580
581
582
# File 'app/models/coupon.rb', line 577

def apply_source_to_customer(customer)
  return unless source

  # For customer i do an update attribute to get a paper trail hit
  customer.update_attribute(:source_id, source_id)
end

#apply_source_to_opportunity(opportunity) ⇒ Object



594
595
596
597
598
# File 'app/models/coupon.rb', line 594

def apply_source_to_opportunity(opportunity)
  return unless source && opportunity.sales_opportunity?

  opportunity.update_column(:source_id, source_id)
end

#apply_source_to_order(order) ⇒ Object



584
585
586
587
588
589
590
591
592
# File 'app/models/coupon.rb', line 584

def apply_source_to_order(order)
  return unless source && order && order.is_sales_order?

  if order.persisted?
    order.update_column(:source_id, source_id)
  else
    order.source_id = source_id
  end
end

#auto_apply_customer_filterCustomerFilter



140
# File 'app/models/coupon.rb', line 140

belongs_to :auto_apply_customer_filter, inverse_of: :auto_apply_coupons, class_name: 'CustomerFilter', optional: true

#blacklistable?Boolean

Every coupon by default is blacklistable if they are auto apply
This behavior can be overriden in each class of coupon
e.g. tier2 coupon is always true

Returns:

  • (Boolean)


411
412
413
414
415
416
417
# File 'app/models/coupon.rb', line 411

def blacklistable?
  if SKIP_BLACKLISTING_COUPON_CODES.include?(code)
    false
  else
    auto_apply
  end
end

#coupon_active?Boolean

Returns:

  • (Boolean)


353
354
355
# File 'app/models/coupon.rb', line 353

def coupon_active?
  status == :active
end

#coupon_expired?Boolean

Returns:

  • (Boolean)


345
346
347
# File 'app/models/coupon.rb', line 345

def coupon_expired?
  status == :expired
end

#coupon_inactive?Boolean

Returns:

  • (Boolean)


341
342
343
# File 'app/models/coupon.rb', line 341

def coupon_inactive?
  status == :inactive
end

#coupon_not_yet_in_effect?Boolean

Returns:

  • (Boolean)


349
350
351
# File 'app/models/coupon.rb', line 349

def coupon_not_yet_in_effect?
  status == :not_yet_in_effect
end

#coupon_promo_sync_alertObject



357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# File 'app/models/coupon.rb', line 357

def coupon_promo_sync_alert
  msg = nil
  if coupon_active? || coupon_not_yet_in_effect?
    if (missing_catalog_items = missing_catalog_items_not_in_promotion_product_filter).present?
      msg = %(There are potentially #{missing_catalog_items.size} catalog items in your product filter that need to be synchronized,
      run Synchronize Promo Items to correct)
    elsif (missing_items = promotional_item_ids_not_in_product_filter).present?
      msg = %(There are #{missing_items.size} items in your promotional catalog that are not part of your filter,
        run Synchronize Promo Items to correct)
    end
  elsif (coupon_expired? || coupon_inactive?) && promotional_view_product_catalog_items.present?
    msg = %(Coupon is inactive or expired, synchronize promo items to cleanup catalog item references)
  end
  msg
end

#coupon_serial_numbersActiveRecord::Relation<CouponSerialNumber>

Returns:

See Also:



147
# File 'app/models/coupon.rb', line 147

has_many :coupon_serial_numbers, dependent: :destroy, inverse_of: :coupon

#customer_can_apply?Boolean

Returns:

  • (Boolean)


573
574
575
# File 'app/models/coupon.rb', line 573

def customer_can_apply?
  (is_inactive != true) and (auto_apply != true) and public? and tier3?
end

#customer_filterCustomerFilter



139
# File 'app/models/coupon.rb', line 139

belongs_to :customer_filter, inverse_of: :coupons, optional: true

#customer_ids_txtObject



565
566
567
# File 'app/models/coupon.rb', line 565

def customer_ids_txt
  (customer_ids || []).join(',')
end

#customer_ids_txt=(val) ⇒ Object



561
562
563
# File 'app/models/coupon.rb', line 561

def customer_ids_txt=(val)
  self.customer_ids = val # attribute normalizer will clean this up.
end

#deactivateObject



487
488
489
# File 'app/models/coupon.rb', line 487

def deactivate
  update({ is_inactive: true })
end

#deep_dupObject



250
251
252
253
254
255
256
257
258
259
260
# File 'app/models/coupon.rb', line 250

def deep_dup
  deep_clone(
    include: :product_filters,
    except: %i[effective_date expiration_date]
  ) do |_original, copy|
    if copy.is_a?(Coupon)
      copy.code = "#{code}_COPY"
      copy.is_inactive = true
    end
  end
end

#discountsActiveRecord::Relation<Discount>

Returns:

See Also:



144
# File 'app/models/coupon.rb', line 144

has_many :discounts, inverse_of: :coupon

#exclusive?Boolean

Returns:

  • (Boolean)


246
247
248
# File 'app/models/coupon.rb', line 246

def exclusive?
  !not_exclusive?
end

#flat_amount_goodsObject



537
538
539
# File 'app/models/coupon.rb', line 537

def flat_amount_goods
  (amount_goods_override || amount_goods).to_f.round(2)
end

#flat_amount_servicesObject



545
546
547
# File 'app/models/coupon.rb', line 545

def flat_amount_services
  (amount_services_override || amount_services).to_f.round(2)
end

#flat_amount_shippingObject



541
542
543
# File 'app/models/coupon.rb', line 541

def flat_amount_shipping
  (amount_shipping_override || amount_shipping).to_f.round(2)
end

#future_promotional_catalog_itemsActiveRecord::Relation<CatalogItem>

Returns:

See Also:



149
# File 'app/models/coupon.rb', line 149

has_many :future_promotional_catalog_items, class_name: 'CatalogItem', foreign_key: :new_coupon_id, dependent: :nullify, inverse_of: :new_coupon

#goods_adjustableObject



423
424
425
# File 'app/models/coupon.rb', line 423

def goods_adjustable
  ADJUSTABLE_CALCULATIONS.include? calculation_type_goods
end

#imageImage

Returns:

See Also:



142
# File 'app/models/coupon.rb', line 142

belongs_to :image, optional: true

#instructionsObject



483
484
485
# File 'app/models/coupon.rb', line 483

def instructions
  self.class::INSTRUCTIONS
end

#is_goods_price_forcing?Boolean

Returns:

  • (Boolean)


525
526
527
# File 'app/models/coupon.rb', line 525

def is_goods_price_forcing?
  %w[DP MP].include? calculation_type_goods
end

#is_msrp_goods_based?Boolean

Returns:

  • (Boolean)


533
534
535
# File 'app/models/coupon.rb', line 533

def is_msrp_goods_based?
  %w[SF LF].include? calculation_type_goods
end

#is_price_forcing?Boolean

Returns:

  • (Boolean)


521
522
523
# File 'app/models/coupon.rb', line 521

def is_price_forcing?
  is_goods_price_forcing? or is_services_price_forcing?
end

#is_services_price_forcing?Boolean

Returns:

  • (Boolean)


529
530
531
# File 'app/models/coupon.rb', line 529

def is_services_price_forcing?
  %w[DP MP].include? calculation_type_services
end

#label_for(n) ⇒ Object



477
478
479
480
481
# File 'app/models/coupon.rb', line 477

def label_for(n)
  eval("self.class::LABEL_P#{n}")
rescue StandardError
  nil
end

#line_discountsActiveRecord::Relation<LineDiscount>

Returns:

See Also:



145
# File 'app/models/coupon.rb', line 145

has_many :line_discounts, inverse_of: :coupon

#localized_promo_page_content_html(locale = nil, preview_mode: false) ⇒ Object



385
386
387
388
389
390
391
392
393
394
395
# File 'app/models/coupon.rb', line 385

def localized_promo_page_content_html(locale = nil, preview_mode: false)
  return unless promo_page_content_html.present?

  locale ||= I18n.locale
  Liquid::ParseEnvironment.parse(promo_page_content_html).render(
    'preview_mode' => preview_mode,
    'locale' => locale.to_s,
    'canada' => %i[en-CA fr-CA].include?(locale.to_sym),
    'usa' => locale.to_sym == :'en-US'
  )
end

#missing_catalog_items_not_in_promotion_product_filterObject



459
460
461
# File 'app/models/coupon.rb', line 459

def missing_catalog_items_not_in_promotion_product_filter
  all_applicable_catalog_items.where.not(id: promotional_view_product_catalog_items.pluck(:id))
end

#percentage_off_goodsObject



549
550
551
# File 'app/models/coupon.rb', line 549

def percentage_off_goods
  (amount_goods_override || amount_goods).to_f / 100
end

#percentage_off_servicesObject



557
558
559
# File 'app/models/coupon.rb', line 557

def percentage_off_services
  (amount_services_override || amount_services).to_f / 100
end

#percentage_off_shippingObject



553
554
555
# File 'app/models/coupon.rb', line 553

def percentage_off_shipping
  (amount_shipping_override || amount_shipping).to_f / 100
end

#product_filter_item_ids_not_in_catalog_itemsObject



463
464
465
# File 'app/models/coupon.rb', line 463

def product_filter_item_ids_not_in_catalog_items
  all_applicable_item_ids - promotional_view_product_catalog_items.pluck(:item_id)
end

#product_filtersActiveRecord::Relation<ProductFilter>

Returns:

See Also:



146
# File 'app/models/coupon.rb', line 146

has_many :product_filters, as: :resource

#promotion_matrix_calculation?Boolean

Returns:

  • (Boolean)


517
518
519
# File 'app/models/coupon.rb', line 517

def promotion_matrix_calculation?
  [calculation_type_goods, calculation_type_services, calculation_type_shipping].include?('MX')
end

#promotional_catalog_itemsActiveRecord::Relation<CatalogItem>

Returns:

See Also:



148
# File 'app/models/coupon.rb', line 148

has_many :promotional_catalog_items, class_name: 'CatalogItem', dependent: :nullify, inverse_of: :coupon

#promotional_item_ids_not_in_product_filterObject



467
468
469
# File 'app/models/coupon.rb', line 467

def promotional_item_ids_not_in_product_filter
  promotional_view_product_catalog_items.pluck(:item_id) - all_applicable_item_ids
end

#promotional_view_product_catalog_itemsObject

Returns the view product catalog items applicable for this coupon



442
443
444
445
446
447
448
# File 'app/models/coupon.rb', line 442

def promotional_view_product_catalog_items
  if persisted?
    ViewProductCatalog.where(ViewProductCatalog[:coupon_id].eq(id).or(ViewProductCatalog[:future_coupon_id].eq(id)))
  else
    ViewProductCatalog.none
  end
end

#public?Boolean

Returns:

  • (Boolean)


569
570
571
# File 'app/models/coupon.rb', line 569

def public?
  role_ids_restriction.empty? or role_ids_restriction.include?(Role.find_by(name: 'customer').id)
end

#publicationItem

Returns:

See Also:



138
# File 'app/models/coupon.rb', line 138

belongs_to :publication, class_name: 'Item', optional: true

#qualifying_line_items?(itemizable) ⇒ Boolean

Returns:

  • (Boolean)


500
501
502
503
504
505
506
507
508
509
510
511
# File 'app/models/coupon.rb', line 500

def qualifying_line_items?(itemizable)
  # Preload associations to prevent N+1 queries on item, catalog_item, and item_specification
  # during qualification calculations (sqft, linear_ft, msrp, etc.)
  lines = itemizable.line_items.with_discount_preloads.active_parent_lines
  ProductFilter::LineQualifier.new(lines, product_filters,
                                   {
                                     code:,
                                     coupon_tier: tier,
                                     allow_non_domestic_shipping: allow_non_domestic_shipping?,
                                     exclusion_level:
                                   }).qualifying_line_items?
end

#reactivateObject



491
492
493
# File 'app/models/coupon.rb', line 491

def reactivate
  update({ is_inactive: false })
end

#require_amount_goods?Boolean

Returns:

  • (Boolean)


600
601
602
# File 'app/models/coupon.rb', line 600

def require_amount_goods?
  calculation_type_goods.present? && !goods_adjustable && !calculation_type_goods == 'MX'
end

#require_amount_services?Boolean

Returns:

  • (Boolean)


604
605
606
# File 'app/models/coupon.rb', line 604

def require_amount_services?
  calculation_type_services.present? && !services_adjustable && !calculation_type_services == 'MX'
end

#require_amount_shipping?Boolean

Returns:

  • (Boolean)


608
609
610
# File 'app/models/coupon.rb', line 608

def require_amount_shipping?
  calculation_type_shipping.present? && !shipping_adjustable && !calculation_type_shipping == 'MX'
end

#require_product_filter?Boolean

Returns:

  • (Boolean)


513
514
515
# File 'app/models/coupon.rb', line 513

def require_product_filter?
  is_price_forcing? and !(code == 'SMARTPRESETFREEBG')
end

#select_options_for(n) ⇒ Object



471
472
473
474
475
# File 'app/models/coupon.rb', line 471

def select_options_for(n)
  eval("self.class::OPTIONS_P#{n}")
rescue StandardError
  nil
end

#services_adjustableObject



427
428
429
# File 'app/models/coupon.rb', line 427

def services_adjustable
  ADJUSTABLE_CALCULATIONS.include? calculation_type_services
end

#shipping_adjustableObject



431
432
433
# File 'app/models/coupon.rb', line 431

def shipping_adjustable
  ADJUSTABLE_CALCULATIONS.include? calculation_type_shipping
end

#sourceSource

Returns:

See Also:



141
# File 'app/models/coupon.rb', line 141

belongs_to :source, optional: true

#statusObject



329
330
331
332
333
334
335
336
337
338
339
# File 'app/models/coupon.rb', line 329

def status
  if is_inactive
    :inactive
  elsif expiration_date && Date.current > expiration_date
    :expired
  elsif effective_date && Date.current < effective_date
    :not_yet_in_effect
  else
    :active
  end
end

#sticky?Boolean

Sticky coupons remain even when $0

Returns:

  • (Boolean)


496
497
498
# File 'app/models/coupon.rb', line 496

def sticky?
  is_price_forcing?
end

#tier1?Boolean

Returns:

  • (Boolean)


317
318
319
# File 'app/models/coupon.rb', line 317

def tier1?
  type == 'Coupon::Tier1BaseItemPricing'
end

#tier2?Boolean

Returns:

  • (Boolean)


321
322
323
# File 'app/models/coupon.rb', line 321

def tier2?
  type == 'Coupon::Tier2ProgramPricing'
end

#tier3?Boolean

Returns:

  • (Boolean)


325
326
327
# File 'app/models/coupon.rb', line 325

def tier3?
  type == 'Coupon::Tier3PromotionalPricing'
end

#to_sObject



373
374
375
376
377
378
379
380
381
382
383
# File 'app/models/coupon.rb', line 373

def to_s
  [
    code,
    ("G(#{amount_goods}#{calculation_type_goods})" if calculation_type_goods),
    ("SV(#{amount_services}#{calculation_type_services})" if calculation_type_services),
    ("SH(#{amount_shipping}#{calculation_type_shipping})" if calculation_type_shipping),
    ("From #{effective_date || 'Anytime'}" if effective_date),
    ("Until #{expiration_date}" if expiration_date),
    title
  ].compact.join(' - ')
end

#versions_for_audit_trail(_params = {}) ⇒ Object



397
398
399
400
401
402
403
404
405
406
# File 'app/models/coupon.rb', line 397

def versions_for_audit_trail(_params = {})
  query_sql = %q{
                  (item_type = 'Coupon' and item_id = :id)
                  OR
                  (item_type = 'ProductFilter'
                    AND reference_data @> '{"resource_type": "Coupon"}'
                    AND reference_data @> '{"resource_id": :id}')
              }
  RecordVersion.where(query_sql, id:)
end