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 :enum 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 =

Instructions.

'Placeholder'.freeze
VALID_PRICE_MATRIX_CALCULATIONS =

Valid price matrix calculations.

['%', '$', 'DP', 'MP'].freeze
FIXED_DOLLAR_CALCULATIONS =

Fixed dollar calculations.

{
  'Flat Amount Off' => '$',
  'Adjustable (+ or -)' => '?',
  'Adjustable (Discount Only)' => '-?',
  'Adjustable (Charge Only)' => '+?'
}.freeze
FIXED_DOLLAR_CALCULATIONS_TYPES =

Recognised fixed dollar calculations types.

FIXED_DOLLAR_CALCULATIONS.map { |_k, v| v }
CORE_CALCULATIONS =

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 =

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 =

Calculation types services.

CORE_CALCULATIONS
CALCULATION_TYPES_SHIPPING =

Calculation types shipping.

{ 'Percentage Off' => '%', 'Flat Amount Off' => '$',
'Flat Rate Shipping' => '_R', 'Adjustable (Discount or Charge)' => '?',
'Adjustable (Discount Only)' => '-?', 'Adjustable (Charge Only)' => '+?' }.freeze
ADJUSTABLE_CALCULATIONS =

Adjustable calculations.

['?', '-?', '+?'].freeze
FREE_ONLINE_SHIPPING_COUPON_CODES =

Free online shipping coupon codes.

%w[FREEGNDONLINE FREEGNDONLINECA FREE_ECONOMY_ONLINE_USA FREE_ECONOMY_ONLINE_CAN].freeze
FREE_ECONOMY_SHIPPING_ONLINE_COUPON_CODES_BY_COUNTRY =

Free economy shipping online coupon codes by country.

{ USA: 'FREE_ECONOMY_ONLINE_USA', CAN: 'FREE_ECONOMY_ONLINE_CAN' }.freeze
ECONOMY_SHIPPING_MATCH_CRM_COUPON_CODE =

Economy shipping match crm coupon code.

'ECONOMY_SHIPPING_MATCH_CRM'.freeze
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

Constants included from Schedulable

Schedulable::SIMPLE_FORM_OPTIONS

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 Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#amount_goods_overrideObject

Returns the value of attribute amount_goods_override.



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

def amount_goods_override
  @amount_goods_override
end

#amount_services_overrideObject

Returns the value of attribute amount_services_override.



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

def amount_services_override
  @amount_services_override
end

#amount_shipping_overrideObject

Returns the value of attribute amount_shipping_override.



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

def amount_shipping_override
  @amount_shipping_override
end

#serial_numberObject

Returns the value of attribute serial_number.



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

def serial_number
  @serial_number
end

#ship_to_continental_regionsObject

Returns the value of attribute ship_to_continental_regions.



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

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:



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

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:



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

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:



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

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:



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

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:



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

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:



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

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:



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

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

.calculation_types_for_goods_selectObject



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

def self.calculation_types_for_goods_select
  CALCULATION_TYPES_GOODS.to_a.sort
end

.calculation_types_for_services_selectObject



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

def self.calculation_types_for_services_select
  CALCULATION_TYPES_SERVICES.to_a.sort
end

.calculation_types_for_shipping_selectObject



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

def self.calculation_types_for_shipping_select
  CALCULATION_TYPES_SHIPPING.to_a.sort
end

.coupon_type_for_selectObject



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

def self.coupon_type_for_select
  coupon_types.invert
end

.coupon_typesObject



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

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:



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

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

.customer_filters_for_selectObject



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

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:



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

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:



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

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

.exclusion_level_for_selectObject



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

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:



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

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:



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

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:



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

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:



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

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

.friendly_calculation_name(calculation_type) ⇒ Object



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

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



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

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:



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

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:



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

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

.instantiate_by_type(type) ⇒ Object



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

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:



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

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:



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

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:



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

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:



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

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



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

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:



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

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:



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

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:



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

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

.select_optionsObject



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

def self.select_options
  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:



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

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:



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

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:



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

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:



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

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:



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

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

.tier2_select_optionsObject



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

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:



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

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:



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

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:



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

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

Instance Method Details

#adjustableObject



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

def adjustable
  goods_adjustable or services_adjustable or shipping_adjustable
end

#all_applicable_catalog_itemsObject



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

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



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

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:



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

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

#apply_source_to_customer(customer) ⇒ Object



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

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



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

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



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

def apply_source_to_order(order)
  return unless source && 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



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

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)


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

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

#coupon_active?Boolean

Returns:

  • (Boolean)


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

def coupon_active?
  status == :active
end

#coupon_expired?Boolean

Returns:

  • (Boolean)


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

def coupon_expired?
  status == :expired
end

#coupon_inactive?Boolean

Returns:

  • (Boolean)


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

def coupon_inactive?
  status == :inactive
end

#coupon_not_yet_in_effect?Boolean

Returns:

  • (Boolean)


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

def coupon_not_yet_in_effect?
  status == :not_yet_in_effect
end

#coupon_promo_sync_alertObject



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

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:



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

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

#customer_can_apply?Boolean

Returns:

  • (Boolean)


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

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

#customer_filterCustomerFilter



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

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

#customer_ids_txtObject



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

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

#customer_ids_txt=(val) ⇒ Object



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

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

#deactivateObject



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

def deactivate
  update({ is_inactive: true })
end

#deep_dupObject



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

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:



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

has_many :discounts, inverse_of: :coupon

#exclusive?Boolean

Returns:

  • (Boolean)


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

def exclusive?
  !not_exclusive?
end

#flat_amount_goodsObject



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

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

#flat_amount_servicesObject



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

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

#flat_amount_shippingObject



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

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

#future_promotional_catalog_itemsActiveRecord::Relation<CatalogItem>

Returns:

See Also:



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

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

#goods_adjustableObject



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

def goods_adjustable
  ADJUSTABLE_CALCULATIONS.include? calculation_type_goods
end

#imageImage

Returns:

See Also:



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

belongs_to :image, optional: true

#instructionsObject



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

def instructions
  self.class::INSTRUCTIONS
end

#is_goods_price_forcing?Boolean

Returns:

  • (Boolean)


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

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

#is_msrp_goods_based?Boolean

Returns:

  • (Boolean)


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

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

#is_price_forcing?Boolean

Returns:

  • (Boolean)


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

def is_price_forcing?
  is_goods_price_forcing? or is_services_price_forcing?
end

#is_services_price_forcing?Boolean

Returns:

  • (Boolean)


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

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

#label_for(n) ⇒ Object



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

def label_for(n)
  self.class.const_get("LABEL_P#{n}")
rescue NameError
  nil
end

#line_discountsActiveRecord::Relation<LineDiscount>

Returns:

See Also:



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

has_many :line_discounts, inverse_of: :coupon

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



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

def localized_promo_page_content_html(locale = nil, preview_mode: false)
  return if promo_page_content_html.blank?

  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



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

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



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

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

#percentage_off_servicesObject



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

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

#percentage_off_shippingObject



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

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

#product_filter_item_ids_not_in_catalog_itemsObject



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

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:



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

has_many :product_filters, as: :resource

#promotion_matrix_calculation?Boolean

Returns:

  • (Boolean)


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

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

#promotional_catalog_itemsActiveRecord::Relation<CatalogItem>

Returns:

See Also:



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

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

#promotional_item_ids_not_in_product_filterObject



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

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



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

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)


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

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

#publicationItem

Returns:

See Also:



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

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

#qualifying_line_items?(itemizable) ⇒ Boolean

Returns:

  • (Boolean)


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

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



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

def reactivate
  update({ is_inactive: false })
end

#require_amount_goods?Boolean

Returns:

  • (Boolean)


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

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

#require_amount_services?Boolean

Returns:

  • (Boolean)


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

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

#require_amount_shipping?Boolean

Returns:

  • (Boolean)


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

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

#require_product_filter?Boolean

Returns:

  • (Boolean)


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

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

#select_options_for(n) ⇒ Object



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

def select_options_for(n)
  self.class.const_get("OPTIONS_P#{n}")
rescue NameError
  nil
end

#services_adjustableObject



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

def services_adjustable
  ADJUSTABLE_CALCULATIONS.include? calculation_type_services
end

#shipping_adjustableObject



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

def shipping_adjustable
  ADJUSTABLE_CALCULATIONS.include? calculation_type_shipping
end

#sourceSource

Returns:

See Also:



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

belongs_to :source, optional: true

#statusObject



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

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)


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

def sticky?
  is_price_forcing?
end

#tier1?Boolean

Returns:

  • (Boolean)


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

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

#tier2?Boolean

Returns:

  • (Boolean)


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

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

#tier3?Boolean

Returns:

  • (Boolean)


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

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

#to_sObject



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

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



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

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_json)
              }
  RecordVersion.where(query_sql, id:, resource_id_json: { resource_id: id }.to_json)
end