Class: Catalog

Inherits:
ApplicationRecord show all
Includes:
CatalogConstants, Models::Auditable, Models::Lineage
Defined in:
app/models/catalog.rb

Overview

== Schema Information

Table name: catalogs
Database name: primary

id :integer not null, primary key
alternate_warehouse_stock_fraction :integer default(25), not null
bom_enabled :boolean default(FALSE), not null
country_iso3 :string(3) not null
currency :string(255)
currency_symbol :string(255)
default_catalog_item_state :string(30)
external_price_check_enabled :boolean default(FALSE), not null
for_admin :boolean default(FALSE), not null
is_active_third_party_sku_filter :boolean default(FALSE), not null
is_discontinued :boolean default(FALSE), not null
locales :string default([]), not null, is an Array
map_percentage :decimal(5, 4) default(0.8), not null
name :string(255)
orchestrator_key :string
parent_catalog_discount :decimal(5, 4)
parent_catalog_discount_refurb :decimal(5, 4) default(0.0)
price_sync_delay :integer
price_sync_policy :enum default("price_sync_manual"), not null
probe_cadence_days :integer default(1), not null
public_name :string
require_upc :boolean default(FALSE), not null
retailer_part_number_label :string
retailer_type :enum default("marketplace"), not null
shipping_preferences :jsonb
third_party_name_en_required :boolean default(FALSE), not null
third_party_name_fr_required :boolean default(FALSE), not null
third_party_part_number_required :boolean default(FALSE), not null
third_party_sku_filter_regex :string
third_party_sku_required :boolean default(FALSE), not null
url_template :string
vendor_code :string
website :string
created_at :datetime
updated_at :datetime
amazon_marketplace_id :integer
company_id :integer
parent_catalog_id :integer
sales_manager_employee_record_id :integer
store_id :integer

Indexes

catalogs_company_id_idx (company_id)
catalogs_parent_catalog_id_idx (parent_catalog_id)
catalogs_sales_manager_employee_record_id_idx (sales_manager_employee_record_id)
index_catalogs_on_alternate_warehouse_stock_fraction (alternate_warehouse_stock_fraction)
index_catalogs_on_amazon_marketplace_id (amazon_marketplace_id)
index_catalogs_on_bom_enabled (bom_enabled)
index_catalogs_on_name (name)
index_catalogs_on_store_id (store_id)

Foreign Keys

catalogs_company_id_fkey (company_id => companies.id)
catalogs_sales_manager_employee_record_id_fk (sales_manager_employee_record_id => employee_records.id)
catalogs_store_id_fkey (store_id => stores.id)
fk_rails_... (amazon_marketplace_id => amazon_marketplaces.id)
fk_rails_... (country_iso3 => countries.iso3)
fk_rails_... (parent_catalog_id => catalogs.id)

Defined Under Namespace

Modules: AmazonCompetitiveSignal Classes: AmazonBuyBoxService, AmazonImageBackfillService, AmazonPriceLoweringService, AmazonPriceRaisingService, AmazonPricingAutomationService, AssignCatalogItem, AssignCatalogToCustomer, CatalogCloner, CreateCatalogItem, PullAmazonCatalogSuppressionData, PullAmazonCatalogsListingsData, PushCatalogItemPrice, SwitchItemizableCatalog, SynchronizeCatalogPrices, UpdateCatalog, UpdateCatalogItem, ValidateCatalogItems

Constant Summary collapse

EXCLUDABLE_DIRECT_CARRIERS =

Convenience: all carrier exclusion options for select dropdowns

%w[SpeedeeDelivery USPS UPS FedEx Canadapost Purolator Canpar RlCarriers Freightquote].freeze
EXCLUDABLE_MARKETPLACE_CARRIERS =

Excludable marketplace carriers.

%w[USPS FEDEX UPS SPEEDEE ONTRAC LASERSHIP DHL AMAZON_SHIPPING].freeze

Constants included from CatalogConstants

CatalogConstants::ALL_MAIN_CATALOG_IDS, CatalogConstants::AMAZON_CATALOG_IDS, CatalogConstants::AMAZON_CA_CATALOG_IDS, CatalogConstants::AMAZON_EU_CATALOG_IDS, CatalogConstants::AMAZON_NA_SELLER_IDS, CatalogConstants::AMAZON_SC_BE_CATALOG_ID, CatalogConstants::AMAZON_SC_CATALOG_IDS, CatalogConstants::AMAZON_SC_CA_CATALOG_ID, CatalogConstants::AMAZON_SC_DE_CATALOG_ID, CatalogConstants::AMAZON_SC_ES_CATALOG_ID, CatalogConstants::AMAZON_SC_FR_CATALOG_ID, CatalogConstants::AMAZON_SC_IT_CATALOG_ID, CatalogConstants::AMAZON_SC_NL_CATALOG_ID, CatalogConstants::AMAZON_SC_PL_CATALOG_ID, CatalogConstants::AMAZON_SC_SE_CATALOG_ID, CatalogConstants::AMAZON_SC_UK_CATALOG_ID, CatalogConstants::AMAZON_SC_US_CATALOG_ID, CatalogConstants::AMAZON_SELLER_IDS, CatalogConstants::AMAZON_US_CATALOG_IDS, CatalogConstants::AMAZON_VC_CATALOG_IDS, CatalogConstants::AMAZON_VC_CA_CATALOG_ID, CatalogConstants::AMAZON_VC_CA_CATALOG_IDS, CatalogConstants::AMAZON_VC_DIRECT_FULFILLMENT_CATALOG_IDS, CatalogConstants::AMAZON_VC_US_CATALOG_IDS, CatalogConstants::AMAZON_VC_US_WASN4_CATALOG_ID, CatalogConstants::AMAZON_VC_US_WAX7V_CATALOG_ID, CatalogConstants::AMAZON_VC_WAT0F_CA_CATALOG_ID, CatalogConstants::AMAZON_VC_WAT4D_CA_CATALOG_ID, CatalogConstants::AMAZON_VENDOR_CODE_TO_CATALOG_ID, CatalogConstants::BESTBUY_CANADA, CatalogConstants::BUILD_COM, CatalogConstants::CANADIAN_TIRE, CatalogConstants::CA_CATALOG_ID, CatalogConstants::COSTCO_CANADA, CatalogConstants::COSTCO_CATALOGS, CatalogConstants::COSTCO_USA, CatalogConstants::EU_CATALOG_ID, CatalogConstants::HOME_DEPOT_CANADA, CatalogConstants::HOME_DEPOT_CATALOGS, CatalogConstants::HOME_DEPOT_USA, CatalogConstants::HOUZZ, CatalogConstants::LOCALE_TO_CATALOG, CatalogConstants::LOWES_CANADA, CatalogConstants::LOWES_USA, CatalogConstants::RONA_CANADA, CatalogConstants::US_CATALOG_ID, CatalogConstants::WALMART_CATALOGS, CatalogConstants::WALMART_SELLER_CANADA, CatalogConstants::WALMART_SELLER_USA, CatalogConstants::WAYFAIR_CANADA, CatalogConstants::WAYFAIR_CATALOGS, CatalogConstants::WAYFAIR_GERMANY, CatalogConstants::WAYFAIR_USA

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Constants included from Models::Schedulable

Models::Schedulable::SIMPLE_FORM_OPTIONS

Instance Attribute Summary collapse

Belongs to collapse

Methods included from Models::Auditable

#creator, #updater

Has and belongs to many collapse

Has many collapse

Delegated Instance Attributes collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from CatalogConstants

amazon_catalog?, amazon_seller_catalog?, costco_catalog?, home_depot_catalog?, walmart_catalog?, wayfair_catalog?

Methods included from Models::Lineage

#ancestors, #ancestors_ids, #children_and_roots, #descendants, #descendants_ids, #ensure_non_recursive_lineage, #family_members, #generate_full_name, #generate_full_name_array, #lineage, #lineage_array, #lineage_simple, #root, #root_id, #self_ancestors_and_descendants, #self_ancestors_and_descendants_ids, #self_and_ancestors, #self_and_ancestors_ids, #self_and_children, #self_and_descendants, #self_and_descendants_ids, #self_and_siblings, #self_and_siblings_ids, #siblings, #siblings_ids

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::Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#currencyObject (readonly)

Validates name, currency, currency symbol.

Validations:



119
# File 'app/models/catalog.rb', line 119

validates :name, :currency, :currency_symbol, presence: true

#currency_symbolObject (readonly)

Validates name, currency, currency symbol.

Validations:



119
# File 'app/models/catalog.rb', line 119

validates :name, :currency, :currency_symbol, presence: true

#default_catalog_item_stateObject (readonly)

Validates default catalog item state.

Validations:



123
# File 'app/models/catalog.rb', line 123

validates :default_catalog_item_state, presence: true

#explode_product_linesObject

Returns the value of attribute explode_product_lines.



182
183
184
# File 'app/models/catalog.rb', line 182

def explode_product_lines
  @explode_product_lines
end

#nameObject (readonly)

Validates name, currency, currency symbol.

Validations:



119
# File 'app/models/catalog.rb', line 119

validates :name, :currency, :currency_symbol, presence: true

#parent_catalog_idObject (readonly)

Validates parent catalog id, price sync timed.

Validations:

  • Presence ({ if: :price_sync_timed?, message: 'When price sync timed is set, a parent catalog is required' })


125
# File 'app/models/catalog.rb', line 125

validates :parent_catalog_id, presence: { if: :price_sync_timed?, message: 'When price sync timed is set, a parent catalog is required' }

#price_sync_delayObject (readonly)

Validates price sync delay, price sync timed.

Validations:



121
# File 'app/models/catalog.rb', line 121

validates :price_sync_delay, presence: { if: :price_sync_timed? }

#price_sync_policyObject (readonly)

Validates price sync policy.

Validations:

  • Inclusion ({ in: %w[price_sync_timed], message: 'must be timed if a price sync delay is specified', if: :price_sync_delay })


127
128
# File 'app/models/catalog.rb', line 127

validates :price_sync_policy, inclusion: { in: %w[price_sync_timed], message: 'must be timed if a price sync delay is specified',
if: :price_sync_delay }

Class Method Details

.activeActiveRecord::Relation<Catalog>

A relation of Catalogs that are active. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Catalog>)

See Also:



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

scope :active, -> { where(is_discontinued: false) }

.amazon_catalog_orderActiveRecord::Relation<Catalog>

A relation of Catalogs that are amazon catalog order. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Catalog>)

See Also:



156
157
158
159
# File 'app/models/catalog.rb', line 156

scope :amazon_catalog_order, -> {
  ids = AMAZON_SC_CATALOG_IDS + AMAZON_VC_CATALOG_IDS
  where(id: ids).in_order_of(:id, ids)
}

.amazon_sellersActiveRecord::Relation<Catalog>

A relation of Catalogs that are amazon sellers. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Catalog>)

See Also:



150
# File 'app/models/catalog.rb', line 150

scope :amazon_sellers, -> { where(id: AMAZON_SELLER_IDS) }

.amazonsActiveRecord::Relation<Catalog>

A relation of Catalogs that are amazons. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Catalog>)

See Also:



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

scope :amazons, -> { where(id: AMAZON_CATALOG_IDS) }

.bom_enabledActiveRecord::Relation<Catalog>

A relation of Catalogs that are bom enabled. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Catalog>)

See Also:



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

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

.ca_catalogObject

Ca catalog.



203
204
205
# File 'app/models/catalog.rb', line 203

def self.ca_catalog
  Catalog.find(CA_CATALOG_ID).freeze
end

.catalog_for_party(party) ⇒ Object

Catalog for party.

Parameters:

  • party (Object)

    the party



234
235
236
# File 'app/models/catalog.rb', line 234

def self.catalog_for_party(party)
  party&.catalog || Catalog.locale_to_catalog(I18n.locale)
end

.catalog_id_to_locale(catalog_id) ⇒ Object

Catalog id to locale.

Parameters:

  • catalog_id (Integer)

    the catalog id



228
229
230
# File 'app/models/catalog.rb', line 228

def self.catalog_id_to_locale(catalog_id)
  { US_CATALOG_ID => 'en-US', CA_CATALOG_ID => 'en-CA' }[catalog_id] || 'en-US'
end

.default_for_country(country_iso3_or_iso2) ⇒ Object

Default for country.

Parameters:

  • country_iso3_or_iso2 (Object)

    the country iso3 or iso2



254
255
256
257
258
259
260
261
262
263
264
# File 'app/models/catalog.rb', line 254

def self.default_for_country(country_iso3_or_iso2)
  # TODO: I18n: Add other countries here or store catalog in country table
  case country_iso3_or_iso2&.upcase
  when 'CA', 'CAN'
    Catalog.ca_catalog
  when *Country::EU_COUNTRIES_ISO
    Catalog.eu_catalog
  else
    Catalog.us_catalog
  end
end

.eu_catalogObject

Eu catalog.



208
209
210
# File 'app/models/catalog.rb', line 208

def self.eu_catalog
  Catalog.find(EU_CATALOG_ID).freeze
end

.for_amazon_seller_marketplace_identifier(marketplace_identifier) ⇒ Object

For amazon seller marketplace identifier.

Parameters:

  • marketplace_identifier (Object)

    the marketplace identifier



282
283
284
285
286
287
# File 'app/models/catalog.rb', line 282

def self.for_amazon_seller_marketplace_identifier(marketplace_identifier)
  amz_mp = AmazonMarketplace.find_by(marketplace_identifier: marketplace_identifier)
  return nil unless amz_mp

  Catalog.amazon_sellers.where(amazon_marketplace_id: amz_mp.id).first
end

.for_countryActiveRecord::Relation<Catalog>

A relation of Catalogs that are for country. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Catalog>)

See Also:



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

scope :for_country, ->(country) { includes(:store).where(stores: { country_iso3: country }) }

.for_google_feedActiveRecord::Relation<Catalog>

A relation of Catalogs that are for google feed. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Catalog>)

See Also:



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

scope :for_google_feed, -> { main_catalogs }

.locale_to_catalog(locale = nil) ⇒ Object

Locale to catalog.

Parameters:

  • locale (Object) (defaults to: nil)

    the locale



221
222
223
224
# File 'app/models/catalog.rb', line 221

def self.locale_to_catalog(locale = nil)
  locale ||= I18n.locale
  Catalog.find(locale_to_catalog_id(locale))
end

.locale_to_catalog_id(locale = nil) ⇒ Object

Locale to catalog id.

Parameters:

  • locale (Object) (defaults to: nil)

    the locale



214
215
216
217
# File 'app/models/catalog.rb', line 214

def self.locale_to_catalog_id(locale = nil)
  locale ||= I18n.locale
  LOCALE_TO_CATALOG[locale.to_sym] || US_CATALOG_ID
end

.main_catalog_idsObject

These are the catalog that are offered on our site



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

def self.main_catalog_ids
  [US_CATALOG_ID, CA_CATALOG_ID]
end

.main_catalogsActiveRecord::Relation<Catalog>

A relation of Catalogs that are main catalogs. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Catalog>)

See Also:



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

scope :main_catalogs, -> { where(id: Catalog.main_catalog_ids) }

.non_adminActiveRecord::Relation<Catalog>

A relation of Catalogs that are non admin. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Catalog>)

See Also:



143
# File 'app/models/catalog.rb', line 143

scope :non_admin, -> { where(for_admin: false) }

.options_for_select(default_scope = nil, prioritize_main_catalogs: true, show_store_info: false) ⇒ Array<Array>

Returns [label, id] pairs for select dropdowns.

Parameters:

  • default_scope (Object) (defaults to: nil)

    the default scope

  • prioritize_main_catalogs (Object) (defaults to: true)

    the prioritize main catalogs

  • show_store_info (Object) (defaults to: false)

    the show store info

Returns:

  • (Array<Array>)

    [label, id] pairs for select dropdowns



242
243
244
245
246
247
248
249
250
# File 'app/models/catalog.rb', line 242

def self.options_for_select(default_scope = nil, prioritize_main_catalogs: true, show_store_info: false)
  catalogs = (default_scope || Catalog.active.non_admin).includes(:store)
  catalogs = catalogs.order("catalogs.id NOT IN (#{Catalog.main_catalog_ids.join(',')})".sql_safe) if prioritize_main_catalogs
  catalogs.order(:name).map do |c|
    n = c.name
    n << " (#{c.store.short_name})" if show_store_info
    [n, c.id]
  end
end

.price_alert_catalog_idsObject

Catalogs kept out of the "other catalogs" summary bucket in the daily
Catalog Price Changes email: the main WarmlyYours catalogs (reported
line-by-line in that email) plus the Amazon Seller Central catalogs
(omitted there and reported separately in the Amazon Repricing Report).



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

def self.price_alert_catalog_ids
  main_catalog_ids + AMAZON_SC_CATALOG_IDS
end

.seller_na_catalogsActiveRecord::Relation<Catalog>

A relation of Catalogs that are seller na catalogs. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Catalog>)

See Also:



151
# File 'app/models/catalog.rb', line 151

scope :seller_na_catalogs, -> { where(id: AMAZON_NA_SELLER_IDS) }

.target_catalog_options_for_selectObject

Used in mass action assignment, showing the % off from the master catalog if any



267
268
269
270
271
272
273
274
275
276
277
278
# File 'app/models/catalog.rb', line 267

def self.target_catalog_options_for_select
  # where.not(id: main_catalog_ids).where.not(parent_catalog_id: nil)
  Catalog.order(:name).map do |catalog|
    n = catalog.name
    if catalog.parent_catalog
      n << " - #{catalog.parent_catalog_discount * 100} % off" if catalog.parent_catalog_discount&.positive?
      n << " - #{catalog.parent_catalog_discount_refurb * 100} % off (refurb)" if catalog.parent_catalog_discount_refurb&.positive?
    end

    [n, catalog.id]
  end
end

.us_catalogObject

Us catalog.



198
199
200
# File 'app/models/catalog.rb', line 198

def self.us_catalog
  Catalog.find(US_CATALOG_ID).freeze
end

.vendorsActiveRecord::Relation<Catalog>

A relation of Catalogs that are vendors. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Catalog>)

See Also:



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

scope :vendors, -> { where(retailer_type: :vendor) }

.with_orchestrator_keyActiveRecord::Relation<Catalog>

A relation of Catalogs that are with orchestrator key. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Catalog>)

See Also:



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

scope :with_orchestrator_key, -> { where.not(orchestrator_key: [nil, '']) }

Instance Method Details

#amazon_catalog?Boolean

Returns whether the record amazon catalog.

Returns:

  • (Boolean)

    whether the record amazon catalog



332
333
334
335
# File 'app/models/catalog.rb', line 332

def amazon_catalog?
  # id.in?(AMAZON_CATALOG_IDS)
  amazon_marketplace.present?
end

#amazon_fulfillable?Boolean

Returns whether the record amazon fulfillable.

Returns:

  • (Boolean)

    whether the record amazon fulfillable



338
339
340
# File 'app/models/catalog.rb', line 338

def amazon_fulfillable?
  id.in?(AMAZON_SELLER_IDS)
end

#amazon_marketplaceAmazonMarketplace?

Returns the amazon marketplace this record belongs to.

Returns:



92
# File 'app/models/catalog.rb', line 92

belongs_to :amazon_marketplace, optional: true, inverse_of: :catalogs

#amazon_sales_rank_data_pointsActiveRecord::Relation<AmazonSalesRankDataPoint>

Returns the associated amazon sales rank data points.

Returns:



108
# File 'app/models/catalog.rb', line 108

has_many :amazon_sales_rank_data_points, through: :catalog_items

#apply_default_countryObject

Apply default country.



540
541
542
543
544
# File 'app/models/catalog.rb', line 540

def apply_default_country
  return unless store

  self.country_iso3 ||= store.country.iso3
end

#apply_default_localesObject

Apply default locales.



530
531
532
533
534
535
536
537
# File 'app/models/catalog.rb', line 530

def apply_default_locales
  return if locales.present?

  default_locales = []
  default_locales = store.country.locales_for_country if store
  default_locales += parent_catalog.locales || [] if parent_catalog
  self.locales = default_locales & I18n.available_locales
end

#carrier_excluded?(carrier_name) ⇒ Boolean

Returns true if the given direct carrier name is excluded for this catalog.
Carrier names match ShippingOption#carrier values (e.g. "SpeedeeDelivery", "USPS", "FedEx").

Parameters:

  • carrier_name (Object)

    the carrier name

Returns:

  • (Boolean)


494
495
496
497
498
# File 'app/models/catalog.rb', line 494

def carrier_excluded?(carrier_name)
  return false if excluded_carriers.blank?

  excluded_carriers.any? { |ec| ec.casecmp?(carrier_name.to_s) }
end

#catalog_itemsActiveRecord::Relation<CatalogItem>

Returns the associated catalog items.

Returns:

  • (ActiveRecord::Relation<CatalogItem>)

    the associated catalog items



104
# File 'app/models/catalog.rb', line 104

has_many :catalog_items, dependent: :destroy, inverse_of: :catalog

#check_for_item_restrictions?Boolean

This is for channel specific items (custom catalog)
Should this catalog requires check in pick item to make sure they don't pick
a restricted catalog item

Returns:

  • (Boolean)


292
293
294
# File 'app/models/catalog.rb', line 292

def check_for_item_restrictions?
  is_main_catalog?
end

#companyCompany

Returns the company this record belongs to.

Returns:

  • (Company)

    the company this record belongs to



84
# File 'app/models/catalog.rb', line 84

belongs_to :company, inverse_of: :catalogs

#countryCountry

Returns the country this record belongs to.

Returns:

  • (Country)

    the country this record belongs to



90
# File 'app/models/catalog.rb', line 90

belongs_to :country, foreign_key: 'country_iso3', primary_key: 'iso3'

#couponsActiveRecord::Relation<Coupon>

Returns the associated coupons.

Returns:

  • (ActiveRecord::Relation<Coupon>)

    the associated coupons



114
# File 'app/models/catalog.rb', line 114

has_many :coupons, through: :customer_filters

#customer_filtersActiveRecord::Relation<CustomerFilter>

Returns the associated customer filters.

Returns:

  • (ActiveRecord::Relation<CustomerFilter>)

    the associated customer filters



95
# File 'app/models/catalog.rb', line 95

has_and_belongs_to_many :customer_filters

#customer_service_managerObject

Alias for Employee#customer_service_manager

Returns:

  • (Object)

    Employee#customer_service_manager

See Also:



432
# File 'app/models/catalog.rb', line 432

delegate :customer_service_manager, to: :Employee

#customersActiveRecord::Relation<Customer>

Returns the associated customers.

Returns:

  • (ActiveRecord::Relation<Customer>)

    the associated customers



100
# File 'app/models/catalog.rb', line 100

has_many :customers

#data_pointsActiveRecord::Relation<CatalogDataPoint>

Returns the associated data points.

Returns:



106
# File 'app/models/catalog.rb', line 106

has_many :data_points, class_name: 'CatalogDataPoint', dependent: :delete_all

#deep_dupObject

Returns a deep-copied, unsaved duplicate of the record.

Returns:

  • (Object)

    a deep-copied, unsaved duplicate of the record



188
189
190
191
192
193
194
195
# File 'app/models/catalog.rb', line 188

def deep_dup
  deep_clone(
    include: :catalog_items,
    except: %i[created_at updated_at]
  ) do |original, copy|
    copy.name = "#{original.name} copy" if copy.is_a?(Catalog)
  end
end

#determine_retailer_part_number_labelObject

Determine retailer part number label.



435
436
437
# File 'app/models/catalog.rb', line 435

def determine_retailer_part_number_label
  retailer_part_number_label || 'Third Party Part Number'
end

#discontinue_catalog_itemsObject

Discontinue catalog items.



421
422
423
424
425
# File 'app/models/catalog.rb', line 421

def discontinue_catalog_items
  return unless is_discontinued_changed? && is_discontinued?

  catalog_items.each(&:discontinue)
end

#is_amazon?Boolean

Returns whether the record is amazon.

Returns:

  • (Boolean)

    whether the record is amazon



447
448
449
# File 'app/models/catalog.rb', line 447

def is_amazon?
  CatalogConstants::AMAZON_CATALOG_IDS.include?(id)
end

#is_amazon_seller_central?Boolean

Returns whether the record is amazon seller central.

Returns:

  • (Boolean)

    whether the record is amazon seller central



452
453
454
# File 'app/models/catalog.rb', line 452

def is_amazon_seller_central?
  CatalogConstants::AMAZON_SC_CATALOG_IDS.include?(id)
end

#is_amazon_vendor_central?Boolean

Returns whether the record is amazon vendor central.

Returns:

  • (Boolean)

    whether the record is amazon vendor central



457
458
459
# File 'app/models/catalog.rb', line 457

def is_amazon_vendor_central?
  CatalogConstants::AMAZON_VC_CATALOG_IDS.include?(id)
end

#is_costco?Boolean

True for Costco catalogs, whose prices are probed per-variant via
Costco's JSON APIs rather than an Oxylabs page scrape.

Returns:

  • (Boolean)

See Also:



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

def is_costco?
  CatalogConstants::COSTCO_CATALOGS.include?(id)
end

#is_main_catalog?Boolean

Returns whether the record is main catalog.

Returns:

  • (Boolean)

    whether the record is main catalog



327
328
329
# File 'app/models/catalog.rb', line 327

def is_main_catalog?
  Catalog.main_catalog_ids.include?(id)
end

#itemsActiveRecord::Relation<Item>

Returns the associated items.

Returns:

  • (ActiveRecord::Relation<Item>)

    the associated items



112
# File 'app/models/catalog.rb', line 112

has_many :items, through: :catalog_items

#load_orchestratorEdi::BaseOrchestrator?

Returns the EDI orchestrator that manages this catalog's listings.

A catalog can be shared by several EDI customers — e.g. the US seller
account plus a satellite marketplace (Amazon Mexico books its orders
through the US catalog 76). Only the primary channel manages
listings/inventory/pricing; satellite partners disable those feeds and
exist for order capture only (resolved separately via
Edi::OrderEventProcessor#load_orchestrator by the order's customer).

customers has no inherent order, so the previous .first picked an
arbitrary partner from the heap. Once the MX satellite was attached to
catalog 76 (2026-05-29) a listing PUT could non-deterministically resolve
to MX — whose listing feed is disabled — and crash (AppSignal #5904).
Resolve deterministically: prefer a partner that actually manages
listings, falling back to the lowest customer id for stability.

Returns:

  • (Edi::BaseOrchestrator, nil)

    the managing orchestrator, or nil if
    no customer maps to an EDI partner



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

def load_orchestrator
  orchestrators = customers.sort_by(&:id).filter_map { |c| Edi::BaseOrchestrator.orchestrator_for_customer_id(c.id) }
  orchestrators.find { |o| o.try(:listing_message_enabled?) } || orchestrators.first
end

#locale_for_catalogObject

Locale for catalog.



380
381
382
383
384
385
386
387
388
# File 'app/models/catalog.rb', line 380

def locale_for_catalog
  # TODO: I18n: multi countries and multi locale
  case currency
  when 'CAD'
    :'en-CA'
  else
    :'en-US'
  end
end

#locales_for_catalogObject

Better method which takes into consideration that certain country will have multiple locales



391
392
393
# File 'app/models/catalog.rb', line 391

def locales_for_catalog
  (locales.presence || country&.locales_for_country || store.country.locales_for_country) & I18n.available_locales
end

#marketplace_catalog?Boolean

Returns whether the record marketplace catalog.

Returns:

  • (Boolean)

    whether the record marketplace catalog



304
305
306
# File 'app/models/catalog.rb', line 304

def marketplace_catalog?
  is_main_catalog?
end

#marketplace_rate_excluded?(service_code) ⇒ Boolean

Returns true if a marketplace rate's service code should be excluded.
Checks both the general excluded_marketplace_carriers list and the
independent sww_usps_excluded toggle for SWW USPS specifically.

Parameters:

  • service_code (String)

    e.g. "SWW_USPS_GROUND_ADVANTAGE", "AMZBS_SPEEDEE_DELIVERY"

Returns:

  • (Boolean)


506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
# File 'app/models/catalog.rb', line 506

def marketplace_rate_excluded?(service_code)
  code = service_code.to_s.upcase
  is_sww = code.start_with?('SWW_')
  is_amzbs = code.start_with?('AMZBS_')
  return false unless is_sww || is_amzbs

  # Independent SWW USPS toggle
  return true if is_sww && sww_usps_excluded? && code.start_with?('SWW_USPS')

  # General marketplace carrier exclusions
  return false if excluded_marketplace_carriers.blank?

  prefix = is_sww ? 'SWW_' : 'AMZBS_'
  excluded_marketplace_carriers.any? do |carrier|
    code.start_with?("#{prefix}#{carrier.upcase}")
  end
end

#ok_to_destroy?Boolean

Returns whether the record ok to destroy.

Returns:

  • (Boolean)

    whether the record ok to destroy



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

def ok_to_destroy?
  !LineItem.joins(:catalog_item).merge(catalog_items).exists?
end

#ordersActiveRecord::Relation<Order>

Returns the associated orders.

Returns:

  • (ActiveRecord::Relation<Order>)

    the associated orders



102
# File 'app/models/catalog.rb', line 102

has_many :orders, through: :customers

#parent_catalogCatalog?

Returns the parent catalog this record belongs to.

Returns:

  • (Catalog, nil)

    the parent catalog this record belongs to



88
# File 'app/models/catalog.rb', line 88

belongs_to :parent_catalog, class_name: 'Catalog', optional: true

#parent_catalog_options_for_selectArray<Array>

Returns [label, id] pairs for select dropdowns.

Returns:

  • (Array<Array>)

    [label, id] pairs for select dropdowns



373
374
375
376
377
# File 'app/models/catalog.rb', line 373

def parent_catalog_options_for_select
  catalogs = Catalog.main_catalogs
  catalogs = catalogs.where(store_id:) if store_id
  catalogs.pluck(:name, :id)
end

#product_line_ids_for_analysisObject

Product line ids for analysis.



414
415
416
# File 'app/models/catalog.rb', line 414

def product_line_ids_for_analysis
  (product_lines_ids_from_catalog_items + product_line_ids).uniq.sort
end

#product_linesActiveRecord::Relation<ProductLine>

Returns the associated product lines.

Returns:

  • (ActiveRecord::Relation<ProductLine>)

    the associated product lines



97
# File 'app/models/catalog.rb', line 97

has_and_belongs_to_many :product_lines

#product_lines_defined?Boolean

Returns whether the record product lines defined.

Returns:

  • (Boolean)

    whether the record product lines defined



356
357
358
# File 'app/models/catalog.rb', line 356

def product_lines_defined?
  product_line_ids.present?
end

#product_lines_for_sales_portalObject

This method is used to render a list of product line publicly available on /all_products
We look at all product line available to sales portal, then remove any that don't have any
items returned by the standard product page api query



299
300
301
# File 'app/models/catalog.rb', line 299

def product_lines_for_sales_portal
  ProductLine.for_index
end

#product_lines_from_catalog_itemsObject

Product lines from catalog items.



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

def product_lines_from_catalog_items
  raw_sql = %{
    exists(select 1 from item_product_lines ipl
           inner join items i on i.id = ipl.item_id
           inner join store_items si on si.item_id = i.id
           inner join catalog_items ci on ci.store_item_id = si.id and ci.catalog_id = ?
           where ipl.product_line_id = product_lines.id and ipl.position = 1
         )
  }
  ProductLine.where(raw_sql, id).order(:lineage_expanded)
end

#product_lines_ids_from_catalog_itemsObject

Product lines ids from catalog items.



409
410
411
# File 'app/models/catalog.rb', line 409

def product_lines_ids_from_catalog_items
  product_lines_from_catalog_items.ids
end

#public_catalog_itemsObject

Alias for Catalog_items#public_catalog_items

Returns:

  • (Object)

    Catalog_items#public_catalog_items

See Also:



418
# File 'app/models/catalog.rb', line 418

delegate :public_catalog_items, to: :catalog_items

#reported_nameObject

Reported name.



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

def reported_name
  public_name.presence || name.presence
end

#sales_managerEmployeeRecord?

Returns the sales manager this record belongs to.

Returns:



86
# File 'app/models/catalog.rb', line 86

belongs_to :sales_manager, foreign_key: 'sales_manager_employee_record_id', class_name: 'EmployeeRecord', optional: true

#storeStore

Returns the store this record belongs to.

Returns:

  • (Store)

    the store this record belongs to



82
# File 'app/models/catalog.rb', line 82

belongs_to :store, inverse_of: :catalogs

#store_items_for_selectObject

Store items for select.



361
362
363
# File 'app/models/catalog.rb', line 361

def store_items_for_select
  store.store_items.active.available.joins(:item).includes(:item).order('items.sku').map { |si| ["#{si.item.sku} - #{si.item.name}", si.id] }
end

#storesActiveRecord::Relation<Store>

Returns the associated stores.

Returns:

  • (ActiveRecord::Relation<Store>)

    the associated stores



116
# File 'app/models/catalog.rb', line 116

has_many :stores, -> { distinct }, through: :catalog_items

#stores_for_selectObject

Stores for select.



366
367
368
369
370
# File 'app/models/catalog.rb', line 366

def stores_for_select
  return Store.none unless company

  company.stores.order(:name).pluck(:name, :id)
end

#stripe_keyObject

Stripe key.



440
441
442
443
444
# File 'app/models/catalog.rb', line 440

def stripe_key
  Payment::Apis::Stripe.publishable_key(currency)
rescue ArgumentError
  nil
end

#tax_rateObject

Tax rate.



343
344
345
# File 'app/models/catalog.rb', line 343

def tax_rate
  country&.tax_rate&.goods
end

#tax_rate_stringObject

Tax rate string.



348
349
350
# File 'app/models/catalog.rb', line 348

def tax_rate_string
  "#{tax_rate * 100}%" if tax_rate.present?
end

#to_sString

Returns string representation of the record.

Returns:

  • (String)

    string representation of the record



353
# File 'app/models/catalog.rb', line 353

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

#view_product_catalogsActiveRecord::Relation<ViewProductCatalog>

Returns the associated view product catalogs.

Returns:



110
# File 'app/models/catalog.rb', line 110

has_many :view_product_catalogs