Class: Item

Overview

The Item model represents products in the inventory system.
Items can be physical goods, services, kits, or publications.
Includes pricing, specifications, product categorization, and e-commerce functionality.
== Schema Information

Table name: items(Product catalog table containing SKU and product metadata.)
Database name: primary

id :integer not null, primary key
all_pl_paths_ids :ltree default([]), is an Array
all_pl_paths_slugs :ltree is an Array
alternative_volts :string default([]), is an Array
always_available_online :boolean
amazon_asin :string(10)
amazon_optimized_listing :boolean default(FALSE), not null
base_weight :decimal(8, 4)
box2_shipping_height :decimal(6, 2)
box2_shipping_length :decimal(6, 2)
box2_shipping_weight :decimal(8, 4)
box2_shipping_width :decimal(6, 2)
box3_shipping_height :decimal(6, 2)
box3_shipping_length :decimal(6, 2)
box3_shipping_weight :decimal(8, 4)
box3_shipping_width :decimal(6, 2)
condition :enum default("new")
content_url :string
coo :string(255)
cycle_count_grouping :enum default("warehouse")
detailed_description_html :text
disable_heuristic_accessories :boolean default(FALSE), not null
display_treatment :integer
do_not_replenish :boolean default(FALSE), not null
dropship :boolean
feature_1 :string
feature_2 :string
feature_3 :string
feature_4 :string
feature_5 :string
featured_position :integer default(100), not null
first_sale_date :date
gross_weight :decimal(8, 4)
grouping :string
harmonization_code :string(255)
ideal_cable_spacing :float
ignore_product_line_on_item_view :boolean default(FALSE), not null
is_cable_system :boolean
is_discontinued :boolean default(FALSE), not null
is_kit :boolean default(FALSE), not null
item_category :string(255)
last_sale_date :date
legacy_accessories_specs :jsonb
legacy_image_path :string(255)
legacy_qty_out_of_stock :integer default(2), not null
legacy_ship_line_type :string(255) default("S")
legacy_specifications :string(255)
moq :integer
name :string(255)
option_name :string
oversize :boolean default(FALSE), not null
pc_path_ids :ltree
pc_path_slugs :ltree
pdf_image_descriptions :text
pdf_images_analyzed_at :datetime
popularity :integer default(0)
popularity_offset :integer default(0), not null
primary_pl_path_ids :ltree
primary_pl_path_slugs :ltree
product_packaging_flag_audited :boolean default(FALSE), not null
product_weight_flag_audited :boolean default(FALSE), not null
public_short_name :string(255)
publication_base_name :string
publication_locales :string is an Array
qty_warn_on_stock :integer default(5), not null
redirection_path :string
rendered_product_specifications :jsonb
rendered_product_specifications_at :datetime
requested_counter :integer default(0), not null
require_reservation :boolean
restricted_for_sales :boolean default(FALSE), not null
review_product_packaging_flag :boolean default(FALSE), not null
review_product_weight_flag :boolean default(FALSE), not null
search_keywords :text
search_text :text
search_text_tsv :tsvector
secondary_model_number :string
seo_description :text
seo_keywords :string
seo_title :string
shipping_class :enum default("parcel_service"), not null
shipping_height :decimal(6, 2)
shipping_length :decimal(6, 2)
shipping_weight :decimal(8, 4)
shipping_width :decimal(6, 2)
short_description :string(120)
skip_serial_number_reservation_screen :boolean
sku(WarmlyYours SKU code. Use LIKE filters for product family searches.) :string not null
sku_aliases :string default([]), is an Array
slug :string
support_priority :integer default(1000)
tax_class :string(3) not null
terms_and_conditions :text
terms_of_service :text
translations :jsonb
unique_kit_dimensions :boolean default(FALSE), not null
unlimited_inventory :boolean default(FALSE), not null
unspsc_code :string(8)
uom :string default("each"), not null
upc :string(255)
upc_on_box :boolean default(FALSE), not null
visible_for_support :boolean default(TRUE), not null
created_at :datetime
updated_at :datetime
amazon_variation_id :integer
brand_id :integer
creator_id :integer
exclusive_item_group_id :integer
literature_id :integer
new_item_id :integer
packaging_discrepancy_packing_id :integer
preferred_supplier_id :integer
primary_image_id :integer
primary_product_line_id(Primary product line foreign key for grouping and categorization.) :integer
product_category_id :integer
product_tax_code_id :integer
replacement_for_id :integer
secondary_product_category_id :integer
successor_item_id :integer
supplier_item_id :integer
updater_id :integer
upload_id :integer

Indexes

idx_items_all_pl_paths_ids (all_pl_paths_ids) USING gin
idx_items_pc_path_ids (pc_path_ids) USING gist
idx_items_primary_pl_path_ids (primary_pl_path_ids) USING gist
idx_pc_id_discontinued_kit (product_category_id,is_discontinued,is_kit)
idx_pc_id_is_disc_created (product_category_id,is_discontinued,created_at)
idx_pc_id_literature_id (product_category_id,literature_id)
idx_ppl_id_product_cat_id (primary_product_line_id,product_category_id)
idx_product_cat_id_created_at (product_category_id,created_at)
idx_product_category_id_id (product_category_id,id)
idx_upload_id (upload_id)
index_items_name_like (name)
index_items_on_all_pl_paths_slugs (all_pl_paths_slugs) USING gist
index_items_on_amazon_asin (amazon_asin) UNIQUE
index_items_on_amazon_variation_id (amazon_variation_id)
index_items_on_brand_id (brand_id)
index_items_on_condition (condition)
index_items_on_exclusive_item_group_id (exclusive_item_group_id)
index_items_on_is_discontinued (is_discontinued)
index_items_on_is_kit (is_kit)
index_items_on_literature_id (literature_id)
index_items_on_new_item_id (new_item_id)
index_items_on_pc_path_slugs (pc_path_slugs) USING gist
index_items_on_primary_pl_path_slugs (primary_pl_path_slugs) USING gist
index_items_on_product_tax_code_id (product_tax_code_id)
index_items_on_publication_locales (publication_locales) USING gin
index_items_on_search_text_tsv (search_text_tsv) USING gin
index_items_on_secondary_model_number (secondary_model_number) WHERE (secondary_model_number IS NOT NULL)
index_items_on_secondary_product_category_id (secondary_product_category_id)
index_items_on_sku (sku) UNIQUE
index_items_on_sku_aliases (sku_aliases) USING gin
index_items_on_successor_item_id (successor_item_id)
index_items_on_upc_and_condition (upc,condition) UNIQUE
index_items_sku_like (sku) USING gin
items_product_category_id_condition_primary_product_line_id_idx (product_category_id,condition,primary_product_line_id)
items_rendered_product_specifications_idx (rendered_product_specifications) USING gin
items_supplier_item_id_idx (supplier_item_id)

Foreign Keys

fk_rails_... (amazon_variation_id => amazon_variations.id)
fk_rails_... (brand_id => brands.id)
fk_rails_... (new_item_id => items.id)
fk_rails_... (packaging_discrepancy_packing_id => packings.id) ON DELETE => nullify
fk_rails_... (primary_product_line_id => product_lines.id)
fk_rails_... (product_tax_code_id => product_tax_codes.id)
fk_rails_... (secondary_product_category_id => product_categories.id)
fk_rails_... (successor_item_id => items.id)
items_product_category_id_fk (product_category_id => product_categories.id)
items_supplier_item_id_fk (supplier_item_id => supplier_items.id) ON DELETE => cascade

Defined Under Namespace

Classes: AmazonFnskuBarcode, ArchiveItem, ArticleRetriever, Auditor, AvailabilityDateChecker, Cloner, ConvertElectricalSpecs, CopyRelatedItemsFromSiblings, CustomMatCreator, CycleCountPrioritizer, CycleCountScheduler, DataMatrixBarcode, FloorPlanRetriever, HeatingElementLine, HeatingElementPlan, ImageLibraryPlPcLadder, ImageLibraryRetriever, ImageRetriever, InventoryCommitter, KitAttributeGenerator, KitComposer, KitConsolidator, MapSpecToTemplate, MarketingCycleCountScheduler, PopulateItemPopularity, PrepareNewKitComponent, ProductSpecificationsCloner, PublicationRetriever, ShippingBoxCalculator, SpecFixer, SpecificationsMasher, SpecificationsRetriever, SuggestedItemTool, SupportQrCode, SynchronizeRefurbSpecs, UpcBarcode, UpcMaker, VideoRetriever

Constant Summary collapse

TRANSLATABLE_ATTRIBUTES =

Translatable attributes.

%i[detailed_description_html feature_1 feature_2 feature_3 feature_4 feature_5 name option_name public_short_name
search_keywords seo_description seo_keywords seo_title short_description terms_and_conditions terms_of_service].freeze
TRANSLATION_NAMESPACE =

Translation namespace.

'ItemAttributes'
SKU_REGEXP =

Sku regexp.

/\A\w[\w\-.]+\w\z/
AMAZON_ASIN_REGEXP =

Amazon asin regexp.

/\b[A-Z0-9]{10}\b/
DISPLAY_TREATMENT_HIDE_QUANTITIES =

Display treatment hide quantities.

0
SINGLE_QUANTITY_SKUS =

Put sku in here that needs their own line always

[].freeze
DOUBLE_POLE_VOLTAGES =

Double pole voltages.

[208.0, 240.0].freeze
FORECAST_EXCLUDED_SKU_PREFIXES =

SKU prefixes excluded from demand forecasting (custom/discontinued/special items)

%w[cth-dis- cths- cthx- ffx- tmcs tms-sp].freeze
SMARTPRESET_SERVICE_SKU =

Smartpreset service sku.

%w[SMARTPRESETFORM-USA-A SMARTPRESETFORM-CAN-A SMART-PRESET-FORM-B SMART-PRESET-OJ-FORM-A SMART-PRESET-OJ-FORM-B].freeze
CI_EXIST_CHECK_SQL =

Ci exist check sql.

'select 1 from catalog_items ci inner join store_items si on ci.store_item_id = si.id where si.item_id = items.id and si.is_discontinued = false'

Constants included from Models::Publication

Models::Publication::CACHE_RELEVANT_ATTRIBUTES, Models::Publication::PUBLIC_LOCALES

Constants included from Models::Embeddable

Models::Embeddable::MAX_CONTENT_LENGTH

Constants included from Models::ItemSpecificationHelper

Models::ItemSpecificationHelper::PER_ITEM_SPEC_TOKENS

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Constants included from Schedulable

Schedulable::SIMPLE_FORM_OPTIONS

Instance Attribute Summary collapse

Attributes included from Models::Publication

#available_in_canada, #available_in_usa, #serve_in_locale

Attributes included from Models::Translatable

#do_not_compact_translation_container

Attributes included from Models::ItemLtreeSync

#skip_ltree_sync

Belongs to collapse

Methods included from Models::ItemAmazonHelper

#amazon_variation

Methods included from Models::Auditable

#creator, #updater

Has one collapse

Has many collapse

Methods included from Models::ItemAmazonHelper

#amazon_marketplaces, #amazon_transparency_codes

Methods included from Models::CrossLinkable

#inbound_content_links, #outbound_content_links

Methods included from Models::Publication

#item_embeddings

Methods included from Models::Embeddable

#content_embeddings

Methods included from Models::Taggable

#tag_records, #taggings

Methods included from Models::Kittable

#kit_components, #kit_components_for_specs, #kit_parents, #kit_source_item_relations, #kit_target_item_relations, #kit_target_item_relations_for_specs

Has and belongs to many collapse

Delegated Instance Attributes collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Models::ItemAmazonHelper

#all_amazon_image_profiles, #amazon_description, #amazon_description=, #amazon_feature_1, #amazon_feature_1=, #amazon_feature_2, #amazon_feature_2=, #amazon_feature_3, #amazon_feature_3=, #amazon_feature_4, #amazon_feature_4=, #amazon_feature_5, #amazon_feature_5=, #amazon_feature_6, #amazon_feature_6=, #amazon_feature_7, #amazon_feature_7=, #amazon_feature_8, #amazon_feature_8=, #amazon_feature_9, #amazon_feature_9=, #amazon_features, #amazon_generic_keyword, #amazon_generic_keyword=, #amazon_product_types, #amazon_target_keywords, #amazon_title, #amazon_title=, #create_or_set_amazon_spec_value, #description_for_amazon, #is_amazon_item?, #reset_amazon_fields, #sync_amazon_asins, #title_for_amazon, with_asin

Methods included from Models::CrossLinkable

#content_links_count, #linked_content, #linked_posts, #linked_publications, #linked_showcases, #linked_videos

Methods included from Models::Publication

ai_search_publications, ai_search_warning?, #alias_sku, #analyze_pdf_images!, #available_service_locales, #available_to_all_locales?, #check_redirection_path, clear_ai_search_warning!, #compose_name_with_languages, #content_for_embedding, #cover_image_url, cover_ratio_mismatch, #create_primary_image_from_pdf, #default_publication_logistics, embeddable_content_types, #embeddable_locales, #embedding_content_changed?, #fast_country_discontinue, #file_name_for_download, #friendly_locale_names, #generate_publication_name, #has_literature_changed?, hybrid_search_publications, #locale_for_embedding, #needs_vision_analysis?, #perform_country_discontinue, #product_category_must_be_publication, #publication_available_locales, #publication_base_name_clean_of_language, #publication_cache_relevant_change?, #publication_edge_cache_urls, #publication_pdf_changed?, #publication_sku_check, #publication_url, #publication_visible_to_public?, publications, publications_for_online_portal, publications_for_public, publications_for_public_in_store, publications_for_sales_portal, publications_for_support_portal, #publish_pdf_changed_event, #publish_publication_updated_event, #retrieve_publications_search_text, #secondary_product_category_must_not_be_publication, #set_search_text, #should_queue_embedding?, with_publication_attached

Methods included from Models::HybridSearchable

rrf_ranked_relation

Methods included from Models::Embeddable

#content_for_embedding, embeddable_content_types, #embeddable_locales, #embedding_content_hash, embedding_partition_class, #embedding_stale?, #embedding_type_name, #embedding_vector, #find_content_embedding, #find_similar, #generate_all_embeddings!, #generate_chunked_embeddings!, #generate_embedding!, #has_embedding?, #locale_for_embedding, #needs_chunking?, regenerate_all_embeddings, semantic_search

Methods included from Models::Taggable

#add_tag, all_tags, #has_tag?, normalize_tag_names, not_tagged_with, #remove_tag, #tag_list, #tag_list=, #taggable_type_for_tagging, tagged_with, #tags, #tags=, tags_cloud, tags_exclude, tags_include, with_all_tags, with_any_tags, without_all_tags, without_any_tags

Methods included from Models::Translatable

#compact_translation_container, skip_compaction_for

Methods included from Models::ItemPackageContentHelper

#build_package_content_entries, #condense_group_to_char_limit, #group_entries_for_max_unique_items, #package_content_html, #package_content_short_name, #package_contents_array, #package_contents_friendly

Methods included from Models::ItemSpecificationHelper

#amps, #amps=, #amps_static?, #area, #aspect, #btu_per_hour, #calculate_amps, #calculate_coverage_for_membrane, #calculate_geometric_sqft, #calculate_heated_area_for_panels, #calculate_heated_area_sqft_for_panels, #calculate_heated_area_sqm_for_panels, #calculate_kilowatts, #calculate_number_of_fixing_strips_at_3_in, #calculate_number_of_fixing_strips_at_4_in, #calculate_number_of_fixing_strips_at_5_in, #calculate_number_of_fixing_strips_at_cable_spacing, #calculate_ohms, #calculate_ohms_per_ft, #calculate_size_2d, #calculate_size_2d_ft, #calculate_size_2d_in, #calculate_size_2d_metric_cm, #calculate_size_2d_metric_mm, #calculate_size_3d, #calculate_size_3d_ft, #calculate_size_3d_in, #calculate_size_3d_metric_cm, #calculate_size_3d_metric_mm, #calculate_sqft, #calculate_thermal_power_per_hour, #calculate_volts, #calculate_watts, #calculate_watts_per_sqft, #can_use_amps_for_calculation?, #can_use_ohms_for_calculation?, #can_use_voltage_for_calculation?, #can_use_watts_for_calculation?, #cold_lead_length, #cold_lead_length=, #control_capacity, #coverage_at_3_5_in, #coverage_at_3_75_in, #coverage_at_3_in, #coverage_at_4_5_in, #coverage_at_4_in, #coverage_at_5_in, #coverage_formatted, #create_or_set_spec_value, #finish, #has_spec?, #is_dual_voltage?, #length, #length=, #length_formatted, #linear_ft, #maximum_current, #num_poles, #ohms, #ohms=, #ohms_law_calculator, #ohms_static?, #plan_grouping, #plan_grouping=, #set_spec_value, #spec, #spec_image, #spec_output, #spec_refresh_in_progress, #spec_unit, #spec_value, #specs_for_identifier_label, #sqft, #sqft=, #token_specs_values_for_liquid, #voltage, #voltage=, #voltage_formatted, #voltage_static?, #volts, #volts=, #vop, #watts, #watts=, #watts_formatted, #watts_per_linear_feet, #watts_per_sqft, #watts_per_sqft_at_3_5_in_spacing, #watts_per_sqft_at_3_75_in_spacing, #watts_per_sqft_at_3_in_spacing, #watts_per_sqft_at_4_5_in_spacing, #watts_per_sqft_at_4_in_spacing, #watts_per_sqft_at_5_in_spacing, #watts_static?, #width, #width=, #width_by_length_text, #width_by_length_text_2, #width_by_length_text_3, #width_formatted

Methods included from Models::Kittable

#box_contents, #consolidate_linked_kits, #get_kit_item_ids, #get_kit_items, #get_multi_box_item_contents_item_id_hash_array, #get_self_and_kit_item_ids, #get_self_and_kit_items, #is_part_of_a_kit?, #is_part_of_an_active_kit?, #kit_contents, kits, #shipping_dimensions_changed?, #store_item_for

Methods included from Models::Auditable

#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record

Methods included from Models::ItemLtreeSync

#build_ltree_paths_from_associations, bulk_sync_ltree_paths!, sync_items_for_product_categories!, sync_items_for_product_lines!, #sync_ltree_paths!

Methods included from Models::ItemScopable

by_product_category_id, by_product_category_id_direct, by_product_category_path, by_product_category_path_exact, by_product_category_url, by_product_category_url_exact, by_product_line_id, by_product_line_path, by_product_line_path_full, by_product_line_url, by_product_line_url_full, not_by_product_category_id, not_by_product_line_id

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, 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

#amazon_asinObject (readonly)



319
320
321
# File 'app/models/item.rb', line 319

validates :amazon_asin, uniqueness: true,
format: { with: AMAZON_ASIN_REGEXP, message: 'must be 10 alphanumeric characters starting with a letter' },
allow_blank: true

#name_enObject (readonly)

Here we check specifically for the english name to be present

Validations:



315
# File 'app/models/item.rb', line 315

validates :name_en, :tax_class, presence: true

#refresh_specsObject

These are shortcuts for publications



223
224
225
# File 'app/models/item.rb', line 223

def refresh_specs
  @refresh_specs
end

#skip_item_calcObject

Returns the value of attribute skip_item_calc.



641
642
643
# File 'app/models/item.rb', line 641

def skip_item_calc
  @skip_item_calc
end

#skuObject (readonly)



316
317
318
# File 'app/models/item.rb', line 316

validates :sku, presence: true,
uniqueness: true,
format: { with: SKU_REGEXP, message: 'must only contain A-Z 0-9 - _ . and must end with a number or letter' }

#tax_classObject (readonly)

Here we check specifically for the english name to be present

Validations:



315
# File 'app/models/item.rb', line 315

validates :name_en, :tax_class, presence: true

#update_search_textObject

Returns the value of attribute update_search_text.



641
642
643
# File 'app/models/item.rb', line 641

def update_search_text
  @update_search_text
end

#update_upcObject

Returns the value of attribute update_upc.



641
642
643
# File 'app/models/item.rb', line 641

def update_upc
  @update_upc
end

Class Method Details

.accessoriesActiveRecord::Relation<Item>

A relation of Items that are accessories. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



416
# File 'app/models/item.rb', line 416

scope :accessories,             -> { where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_ACCESSORIES)) }

.activeActiveRecord::Relation<Item>

A relation of Items that are active. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



372
# File 'app/models/item.rb', line 372

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

.active_in_catalog_idsActiveRecord::Relation<Item>

A relation of Items that are active in catalog ids. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



520
521
522
523
524
# File 'app/models/item.rb', line 520

scope :active_in_catalog_ids, ->(*catalog_ids) {
  catalog_ids = [catalog_ids].flatten.filter_map(&:presence).uniq
  active
    .where("exists(#{CI_EXIST_CHECK_SQL} and ci.catalog_id IN (?))", catalog_ids.flatten)
}

.all_localesObject

NOTE: all_tags method provided by Models::Taggable concern



670
671
672
# File 'app/models/item.rb', line 670

def self.all_locales
  pluck(Arel.sql('distinct unnest(publication_locales) as locale')).compact.sort
end

.async_update_all_items_product_specifications(public_only: true, active_only: true, limit: nil, not_updated_since: nil) ⇒ Object



2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
# File 'app/models/item.rb', line 2056

def self.async_update_all_items_product_specifications(public_only: true, active_only: true, limit: nil, not_updated_since: nil)
  # Select items based on the conditions and if they are due for a refresh
  items = Item.non_publications
  items = items.available_to_public if public_only # implies active
  items = items.active unless public_only && !active_only

  # Filter items that haven't been refreshed in the last week
  items = items.where(Item[:rendered_product_specifications_at].eq(nil).or(Item[:rendered_product_specifications_at].lt(not_updated_since))) if not_updated_since

  items = items.limit(limit) if limit
  items.distinct.ids.each { |iid| ItemAttributeWorker.perform_async(iid) }
end

.available_to_publicActiveRecord::Relation<Item>

A relation of Items that are available to public. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



374
# File 'app/models/item.rb', line 374

scope :available_to_public,     -> { active.public_and_active_in_catalog_id(1, 2) }

.available_to_public_for_supportActiveRecord::Relation<Item>

A relation of Items that are available to public for support. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



376
# File 'app/models/item.rb', line 376

scope :available_to_public_for_support, -> { condition_new.where(visible_for_support: true).where(primary_product_line_id: ProductLine.for_support_portal.select(:id)) }

.by_primary_product_line_ids_with_descendantsActiveRecord::Relation<Item>

A relation of Items that are by primary product line ids with descendants. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



536
537
538
539
540
# File 'app/models/item.rb', line 536

scope :by_primary_product_line_ids_with_descendants, ->(*pl_ids) {
  ids = [pl_ids].flatten.compact.map(&:to_i)
  paths = ProductLine.where(id: ids).pluck(:ltree_path_ids).compact
  paths.empty? ? none : where(Item[:primary_pl_path_ids].ltree_descendant(paths))
}

.canonical_searchActiveRecord::Relation<Item>

A relation of Items that are canonical search. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



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

scope :canonical_search,        ->(sku) {
  escaped = Regexp.escape(sku.to_s)
  where('items.sku ~ ?', "^#{escaped}-[A-Za-z]{1,2}$").order(sku: :desc)
}

.category_sortedActiveRecord::Relation<Item>

A relation of Items that are category sorted. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



389
# File 'app/models/item.rb', line 389

scope :category_sorted,         -> { joins(:product_category).order(ProductCategory[:priority], ProductCategory[:name], Item[:sku]) }

.cold_leadsActiveRecord::Relation<Item>

A relation of Items that are cold leads. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



423
# File 'app/models/item.rb', line 423

scope :cold_leads,              -> { where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_SPARE_PARTS_COLD_LEADS)) }

.condition_select_optionsObject



919
920
921
# File 'app/models/item.rb', line 919

def self.condition_select_options
  Item.conditions.keys.map { |c| [c.humanize.titleize, c] }
end

.controlsActiveRecord::Relation<Item>

A relation of Items that are controls. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



394
# File 'app/models/item.rb', line 394

scope :controls,                -> { where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_CONTROLS)) }

.countertop_heatersActiveRecord::Relation<Item>

A relation of Items that are countertop heaters. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



496
# File 'app/models/item.rb', line 496

scope :countertop_heaters,      -> { by_product_category_path(LtreePaths::PC_HEATING_ELEMENTS_COUNTERTOP) }

.custom_matsActiveRecord::Relation<Item>

A relation of Items that are custom mats. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



495
# File 'app/models/item.rb', line 495

scope :custom_mats,             -> { by_product_category_path(LtreePaths::PC_HEATING_ELEMENTS_CUSTOM_MATS) }

.discontinuedActiveRecord::Relation<Item>

A relation of Items that are discontinued. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



373
# File 'app/models/item.rb', line 373

scope :discontinued,            -> { where(is_discontinued: true) }

.dropshipsActiveRecord::Relation<Item>

A relation of Items that are dropships. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



471
# File 'app/models/item.rb', line 471

scope :dropships,               -> { where(dropship: true) }

.easystatsActiveRecord::Relation<Item>

A relation of Items that are easystats. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



408
# File 'app/models/item.rb', line 408

scope :easystats,               -> { controls.where(Item[:primary_pl_path_slugs].ltree_descendant(LtreePaths::PL_FLOOR_HEATING_CONTROL_EASYSTAT)) }

.electrical_plan_controlsActiveRecord::Relation<Item>

A relation of Items that are electrical plan controls. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



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

scope :electrical_plan_controls, -> {
  controls
    .where(Item[:primary_pl_path_slugs].ltree_descendant(LtreePaths::PL_ALL_CONTROLS))
    .where.not(sku: %w[RLY-12PL SCE-ELEC])
}

.excludes_forecast_skusActiveRecord::Relation<Item>

A relation of Items that are excludes forecast skus. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



442
# File 'app/models/item.rb', line 442

scope :excludes_forecast_skus, -> { where.not('items.sku SIMILAR TO ?', "(#{FORECAST_EXCLUDED_SKU_PREFIXES.join('|')})%") }

.floor_heating_controlsActiveRecord::Relation<Item>

A relation of Items that are floor heating controls. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



402
# File 'app/models/item.rb', line 402

scope :floor_heating_controls, -> { controls.where(Item[:primary_pl_path_slugs].ltree_descendant(LtreePaths::PL_FLOOR_HEATING_CONTROL)) }

.floor_heating_elementsActiveRecord::Relation<Item>

A relation of Items that are floor heating elements. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



414
# File 'app/models/item.rb', line 414

scope :floor_heating_elements,  -> { heating_elements.where(Item[:primary_pl_path_slugs].ltree_descendant(LtreePaths::PL_FLOOR_HEATING)) }

.for_support_portalActiveRecord::Relation<Item>

A relation of Items that are for support portal. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



467
# File 'app/models/item.rb', line 467

scope :for_support_portal,      -> { where(product_category_id: ProductCategory.for_support_portal.select(:id)) }

.forecastableActiveRecord::Relation<Item>

A relation of Items that are forecastable. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



444
# File 'app/models/item.rb', line 444

scope :forecastable, -> { active.non_kits.non_publications.non_shipping.excludes_forecast_skus }

.get_infrared_heating_panel_tstat_options_for_web_and_apply_overrides(connection_method, tstat_options_override = [], available_in_catalog_id: nil) ⇒ Object

Here we want a static web page (e.g. app/views/pages/infrared-heating-panels) to use the same source of SKUs as Item does but allow it to override some of the stuff, including the order:



2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
# File 'app/models/item.rb', line 2399

def self.get_infrared_heating_panel_tstat_options_for_web_and_apply_overrides(connection_method, tstat_options_override = [], available_in_catalog_id: nil)
  available_in_catalog_id = Catalog.locale_to_catalog_id(I18n.locale) if available_in_catalog_id.nil?
  num_overrides = tstat_options_override.size # find how many overrides we have for sorting
  base_item_scope = Item.public_and_active_in_catalog_id(available_in_catalog_id)
  controls = if connection_method == :hardwired
               base_item_scope.infrared_heating_panel_controls_hardwired
             else
               base_item_scope.infrared_heating_panel_controls_plug_in
             end
  res = controls.map do |item|
    tstat_options_override.find do |h|
      h[:sku] == item.sku
    end || { sku: item.sku, name: item.name, image: item.primary_image.image_url(width: 300, height: 300) }
  end
  res.sort_by { |h| tstat_options_override.index(h) || num_overrides }
end

.get_quantity_from_items_and_sqft(catalog_id, items, sqft, add_one_extra_of_last_item = false) ⇒ Object



1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
# File 'app/models/item.rb', line 1480

def self.get_quantity_from_items_and_sqft(catalog_id, items, sqft, add_one_extra_of_last_item = false)
  res_arr = []
  cost_arr = []
  2.times do |pass|
    area = sqft
    res = {}
    cost = 0.0
    last_i = items.length - 1
    items.each_with_index do |item, i|
      add_one_extra = if add_one_extra_of_last_item && (i == last_i) && pass.zero? # do this only on first pass
                        1
                      else
                        0
                      end
      qty = add_one_extra
      if item.sqft.to_f > 0.0
        qty += if (i == last_i) || ((pass == 1) && (items[i + 1].sqft < sqft)) # do this on last item or on second pass if the next item isn't big enough
                 (area / item.sqft).ceil.to_i # want more than enough to cover if last item
               else
                 (area / item.sqft).floor.to_i # want what will fit the area with leftover for smaller items
               end
      end
      next unless qty.positive?

      res[item.sku] = qty
      area -= (item.sqft.to_f * qty)
      # we need a pricing weight to choose which is the best result
      ci = item.catalog_items.find { |ci2| ci2.catalog_id == catalog_id } || item.catalog_items.first
      cost += (qty * ci.amount.to_f)
    end
    res_arr << res
    cost_arr << cost
  end
  res_arr[cost_arr.index(cost_arr.min)]
end

.get_towel_warmer_tstat_options_for_web_and_apply_overrides(connection_method, tstat_options_override = [], available_in_catalog_id: nil) ⇒ Object

Here we want a static web page (e.g. app/views/pages/towel-warmer/controls) to use the same source of SKUs as Item does but allow it to override some of the stuff, including the order:



2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
# File 'app/models/item.rb', line 2380

def self.get_towel_warmer_tstat_options_for_web_and_apply_overrides(connection_method, tstat_options_override = [], available_in_catalog_id: nil)
  available_in_catalog_id = Catalog.locale_to_catalog_id(I18n.locale) if available_in_catalog_id.nil?
  num_overrides = tstat_options_override.size # find how many overrides we have for sorting
  base_item_scope = Item.public_and_active_in_catalog_id(available_in_catalog_id)
  controls = if connection_method == :hardwired
               base_item_scope.towel_warmer_controls_hardwired
             else
               base_item_scope.towel_warmer_controls_plug_in
             end
  res = controls.map do |item|
    # here we
    tstat_options_override.find do |h|
      h[:sku] == item.sku
    end || { sku: item.sku, name: item.name, image: item.primary_image.image_url(width: 300, height: 300) }
  end
  res.sort_by { |h| tstat_options_override.index(h) || num_overrides }
end

.goodsActiveRecord::Relation<Item>

A relation of Items that are goods. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



420
# File 'app/models/item.rb', line 420

scope :goods,                   -> { where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_GOODS)) }

.goods_and_services_for_publicActiveRecord::Relation<Item>

A relation of Items that are goods and services for public. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



445
# File 'app/models/item.rb', line 445

scope :goods_and_services_for_public, -> { available_to_public.active.non_publications }

.goods_visible_for_supportActiveRecord::Relation<Item>

A relation of Items that are goods visible for support. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



465
# File 'app/models/item.rb', line 465

scope :goods_visible_for_support, -> { non_publications.condition_new.goods.where(visible_for_support: true) }

.has_floor_sensorActiveRecord::Relation<Item>

A relation of Items that are has floor sensor. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



485
# File 'app/models/item.rb', line 485

scope :has_floor_sensor, -> { with_product_specification(:has_floor_sensor, 'y') }

.heating_elementsActiveRecord::Relation<Item>

A relation of Items that are heating elements. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



392
# File 'app/models/item.rb', line 392

scope :heating_elements,        -> { where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_HEATING_ELEMENTS)) }

.identifiers_searchActiveRecord::Relation<Item>

A relation of Items that are identifiers search. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



552
553
554
555
556
557
558
559
560
561
562
563
564
# File 'app/models/item.rb', line 552

scope :identifiers_search, ->(identifier) {
  where(%{
   items.sku ILIKE :identifier_wild
     OR items.upc = :identifier
     OR items.upc = RIGHT(:identifier,12)
     OR items.secondary_model_number ILIKE :identifier_wild
     OR items.amazon_asin = :identifier
     OR items.sku_aliases @> ARRAY[:identifier]::varchar[]
     OR supplier_items.supplier_sku ILIKE :identifier_wild
 },
        identifier_wild: "#{identifier}%",
        identifier:).left_outer_joins(:supplier_item).order(sanitize_sql_for_order([Arel.sql('items.is_discontinued = true,items.sku <-> ?'), identifier]))
}

.import_translation_keysObject



1884
1885
1886
# File 'app/models/item.rb', line 1884

def self.import_translation_keys
  Item.where.not(translations: {}).find_each(&:import_translation_keys)
end

.in_storeActiveRecord::Relation<Item>

A relation of Items that are in store. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



425
426
427
# File 'app/models/item.rb', line 425

scope :in_store, ->(*store_ids) {
  where('exists(select id from store_items where store_items.item_id = items.id and store_items.store_id IN (?) and store_items.is_discontinued = false)', [store_ids].flatten)
}

.indexableActiveRecord::Relation<Item>

A relation of Items that are indexable. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



514
# File 'app/models/item.rb', line 514

scope :indexable,               -> { not_tagged_with('Noindex') }

.infrared_heating_panel_controls_dual_connectActiveRecord::Relation<Item>

A relation of Items that are infrared heating panel controls dual connect. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



462
# File 'app/models/item.rb', line 462

scope :infrared_heating_panel_controls_dual_connect, -> { where(sku: ItemConstants::INFRARED_HEATING_PANELS_PLUG_IN_CONTROL_SKUS + ItemConstants::INFRARED_HEATING_PANELS_HARDWIRE_CONTROL_SKUS) }

.infrared_heating_panel_controls_hardwiredActiveRecord::Relation<Item>

A relation of Items that are infrared heating panel controls hardwired. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



461
# File 'app/models/item.rb', line 461

scope :infrared_heating_panel_controls_hardwired, -> { where(sku: ItemConstants::INFRARED_HEATING_PANELS_HARDWIRE_CONTROL_SKUS) }

.infrared_heating_panel_controls_plug_inActiveRecord::Relation<Item>

A relation of Items that are infrared heating panel controls plug in. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



459
# File 'app/models/item.rb', line 459

scope :infrared_heating_panel_controls_plug_in, -> { where(sku: ItemConstants::INFRARED_HEATING_PANELS_PLUG_IN_CONTROL_SKUS) }

.infrared_heating_panelsActiveRecord::Relation<Item>

A relation of Items that are infrared heating panels. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



458
# File 'app/models/item.rb', line 458

scope :infrared_heating_panels,          -> { where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_INFRARED_HEATING_PANELS)) }

.infrared_heating_panels_hardwiredActiveRecord::Relation<Item>

A relation of Items that are infrared heating panels hardwired. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



460
# File 'app/models/item.rb', line 460

scope :infrared_heating_panels_hardwired, -> { where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_INFRARED_HEATING_PANELS_HARDWIRED)) }

.install_kitsActiveRecord::Relation<Item>

A relation of Items that are install kits. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



493
# File 'app/models/item.rb', line 493

scope :install_kits,            -> { by_product_line_path(LtreePaths::PL_FLOOR_HEATING_TEMPZONE_INSTALLATION_KITS).merge(accessories) }

.insulationsActiveRecord::Relation<Item>

A relation of Items that are insulations. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



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

scope :insulations,             -> { where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_ACCESSORIES_INSULATIONS)) }

.integration_kitsActiveRecord::Relation<Item>

A relation of Items that are integration kits. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



411
# File 'app/models/item.rb', line 411

scope :integration_kits,        -> { powers.where(Item[:primary_pl_path_slugs].ltree_descendant(LtreePaths::PL_FLOOR_HEATING_CONTROL_INTEGRATION)) }

.item_identifier_label_sizes_select_optionsObject



931
932
933
# File 'app/models/item.rb', line 931

def self.item_identifier_label_sizes_select_options
  [['2.3125" x 4" (S-8505)', 'regular'], ['4" x 6" (S-17044)', 'large']]
end

.last_custom_mat_itemObject



935
936
937
# File 'app/models/item.rb', line 935

def self.last_custom_mat_item
  Item.by_product_line_path(LtreePaths::PL_FLOOR_HEATING_TEMPZONE_CUSTOM_MAT).merge(heating_elements).order('items.id DESC').first
end

.membranesActiveRecord::Relation<Item>

A relation of Items that are membranes. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



494
# File 'app/models/item.rb', line 494

scope :membranes,               -> { where(sku: ItemConstants::MEMBRANE_SKUS) }

.mirror_defoggersActiveRecord::Relation<Item>

A relation of Items that are mirror defoggers. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



404
# File 'app/models/item.rb', line 404

scope :mirror_defoggers,        -> { where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_MIRROR_DEFOGGERS)) }

.mirrorsActiveRecord::Relation<Item>

A relation of Items that are mirrors. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



393
# File 'app/models/item.rb', line 393

scope :mirrors,                 -> { where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_MIRRORS)) }

.missing_shipping_dimensionsActiveRecord::Relation<Item>

A relation of Items that are missing shipping dimensions. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



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

scope :missing_shipping_dimensions, -> {
  arel = Item.arel_table
  goods.where(
    arel[:shipping_length].lteq(0).or(arel[:shipping_length].eq(nil))
      .or(arel[:shipping_width].lteq(0)).or(arel[:shipping_width].eq(nil))
      .or(arel[:shipping_height].lteq(0)).or(arel[:shipping_height].eq(nil))
      .or(arel[:shipping_weight].lteq(0)).or(arel[:shipping_weight].eq(nil))
  )
}

.needs_packaging_reviewedActiveRecord::Relation<Item>

A relation of Items that are needs packaging reviewed. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



550
# File 'app/models/item.rb', line 550

scope :needs_packaging_reviewed, -> { where(review_product_packaging_flag: true) }

.needs_weight_reviewedActiveRecord::Relation<Item>

A relation of Items that are needs weight reviewed. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



549
# File 'app/models/item.rb', line 549

scope :needs_weight_reviewed, -> { where(review_product_weight_flag: true) }

.non_dropshipsActiveRecord::Relation<Item>

A relation of Items that are non dropships. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



472
# File 'app/models/item.rb', line 472

scope :non_dropships,           -> { where('items.dropship = false or items.dropship is null') }

.non_kitsActiveRecord::Relation<Item>

A relation of Items that are non kits. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



436
# File 'app/models/item.rb', line 436

scope :non_kits,         -> { where(is_kit: false) }

.non_publicationsActiveRecord::Relation<Item>

A relation of Items that are non publications. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



435
# File 'app/models/item.rb', line 435

scope :non_publications, -> { where.not(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_PUBLICATIONS)) }

.non_shippingActiveRecord::Relation<Item>

A relation of Items that are non shipping. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



437
# File 'app/models/item.rb', line 437

scope :non_shipping,     -> { where.not(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_SHIPPING_AND_HANDLING)) }

.not_a_target_itemActiveRecord::Relation<Item>

A relation of Items that are not a target item. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



473
# File 'app/models/item.rb', line 473

scope :not_a_target_item,       -> { where.missing(:source_item_relations) }

.oj_programmable_tstatsActiveRecord::Relation<Item>

A relation of Items that are oj programmable tstats. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



406
# File 'app/models/item.rb', line 406

scope :oj_programmable_tstats,  -> { oj_tstats.where(Item[:name].matches('%Programmable%')).where.not(Item[:name].matches('%Non Programmable%')) }

.oj_tstatsActiveRecord::Relation<Item>

A relation of Items that are oj tstats. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



405
# File 'app/models/item.rb', line 405

scope :oj_tstats,               -> { thermostats.where(Item[:sku].matches('%4999%')) }

.order_by_specActiveRecord::Relation<Item>

A relation of Items that are order by spec. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
# File 'app/models/item.rb', line 497

scope :order_by_spec,           ->(spec, datatype = 'varchar', direction = 'asc') {
  if %w[decimal integer numeric].include?(datatype.downcase)
    # For numeric types, extract numbers from text and handle NULL values
    order_clause = "CASE
      WHEN (items.rendered_product_specifications->'#{spec}'->>'raw') ~ '^[0-9]+\\.?[0-9]*$'
        THEN (items.rendered_product_specifications->'#{spec}'->>'raw')::#{datatype}
      WHEN (items.rendered_product_specifications->'#{spec}'->>'raw') ~ '^[0-9]+\\.?[0-9]*\\s+[a-zA-Z]+$'
        THEN (regexp_replace(items.rendered_product_specifications->'#{spec}'->>'raw', '[^0-9\\.]', '', 'g'))::#{datatype}
      ELSE NULL
     END #{direction}"
    order(Arel.sql(order_clause))
  else
    # For non-numeric types, use the original logic
    order(Arel.sql("(items.rendered_product_specifications->'#{spec}'->>'raw')::#{datatype} #{direction}"))
  end
}

.order_towel_warmer_controls_hardwiredActiveRecord::Relation<Item>

A relation of Items that are order towel warmer controls hardwired. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



401
# File 'app/models/item.rb', line 401

scope :order_towel_warmer_controls_hardwired, -> { in_order_of(:sku, ItemConstants::TOWEL_WARMER_HARDWIRE_CONTROL_SKUS) }

.orderable_onlineActiveRecord::Relation<Item>

A relation of Items that are orderable online. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



446
# File 'app/models/item.rb', line 446

scope :orderable_online,        -> { non_publications.available_to_public.active.packageable }

.out_of_stockActiveRecord::Relation<Item>

A relation of Items that are out of stock. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



453
454
455
# File 'app/models/item.rb', line 453

scope :out_of_stock,            -> {
  joins(:store_items).where(StoreItem[:qty_available].lteq(Item[:legacy_qty_out_of_stock])).where(store_items: { location: 'AVAILABLE' })
}

.packageableActiveRecord::Relation<Item>

A relation of Items that are packageable. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



377
# File 'app/models/item.rb', line 377

scope :packageable, -> { where(Item[:shipping_weight].gt(0)).where(Item[:base_weight].gt(0)).where(Item[:shipping_length].gt(0)).where(Item[:shipping_width].gt(0)).where(Item[:shipping_height].gt(0)) }

.power_modulesActiveRecord::Relation<Item>

A relation of Items that are power modules. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



410
# File 'app/models/item.rb', line 410

scope :power_modules,           -> { where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_POWER_MODULES)) }

.powersActiveRecord::Relation<Item>

A relation of Items that are powers. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



412
# File 'app/models/item.rb', line 412

scope :powers,                  -> { where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_POWER)) }

.primary_product_line_slug_ltree_contActiveRecord::Relation<Item>

A relation of Items that are primary product line slug ltree cont. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



542
543
544
545
546
547
# File 'app/models/item.rb', line 542

scope :primary_product_line_slug_ltree_cont, ->(term) {
  sanitized = term.to_s.strip.tr(' ', '_').gsub(/[^a-zA-Z0-9_.]/, '')
  return none if sanitized.blank?

  where(Item[:primary_pl_path_slugs].ltree_descendant(sanitized))
}

.public_and_active_in_any_catalogsActiveRecord::Relation<Item>

A relation of Items that are public and active in any catalogs. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



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

scope :public_and_active_in_any_catalogs, -> {
  active
    .where("exists(#{CI_EXIST_CHECK_SQL} and ci.state NOT IN (?))", CatalogItem::HIDDEN_STATES)
}

.public_and_active_in_catalog_idActiveRecord::Relation<Item>

A relation of Items that are public and active in catalog id. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



525
526
527
528
529
# File 'app/models/item.rb', line 525

scope :public_and_active_in_catalog_id, ->(*catalog_ids) {
  catalog_ids = [catalog_ids].flatten.filter_map(&:presence).uniq
  active
    .where("exists(#{CI_EXIST_CHECK_SQL} and ci.state NOT IN (?) and ci.catalog_id IN (?))", CatalogItem::HIDDEN_STATES, catalog_ids.flatten)
}

.publications_searchActiveRecord::Relation<Item>

A relation of Items that are publications search. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



574
# File 'app/models/item.rb', line 574

scope :publications_search, ->(term) { canonical_search(term) }

.ransackable_scopes(_auth_object = nil) ⇒ Object



682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
# File 'app/models/item.rb', line 682

def self.ransackable_scopes(_auth_object = nil)
  %i[keywords_search by_product_line_id pdf_search
     by_product_category_id
     by_primary_product_line_ids_with_descendants
     active_in_catalog_ids
     public_and_active_in_catalog_id
     sku_and_aliases_search
     tags_include
     ai_search_publications
     hybrid_search_publications
     not_by_product_category_id
     not_by_product_line_id
     tags_exclude
     primary_product_line_slug_ltree_cont
     cover_ratio_mismatch]
end

.relay_panelsActiveRecord::Relation<Item>

A relation of Items that are relay panels. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



409
# File 'app/models/item.rb', line 409

scope :relay_panels,            -> { where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_POWER_RELAY_PANELS)) }

.remote_servicesActiveRecord::Relation<Item>

A relation of Items that are remote services. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



422
# File 'app/models/item.rb', line 422

scope :remote_services,         -> { services.where(Item[:sku].matches('%REMOTE%')) }

.rendered_product_specs_keysObject



2098
2099
2100
# File 'app/models/item.rb', line 2098

def self.rendered_product_specs_keys
  pluck('distinct jsonb_object_keys(rendered_product_specifications)').sort.map(&:to_sym)
end

.rendered_product_specs_options_for_select(spec_key) ⇒ Object



2102
2103
2104
2105
2106
# File 'app/models/item.rb', line 2102

def self.rendered_product_specs_options_for_select(spec_key)
  Item.pluck("distinct rendered_product_specifications->'#{spec_key}'->>'output',rendered_product_specifications->'#{spec_key}'->>'raw'").reject do |v|
    v[0].nil?
  end.sort
end

.reservableActiveRecord::Relation<Item>

A relation of Items that are reservable. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



491
# File 'app/models/item.rb', line 491

scope :reservable,              -> { where(require_reservation: true) }

.reviewableActiveRecord::Relation<Item>

A relation of Items that are reviewable. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



474
# File 'app/models/item.rb', line 474

scope :reviewable,              -> { joins(:product_category, :product_lines).merge(ProductLine.reviewable).merge(ProductCategory.reviewable) }

.rough_in_kitsActiveRecord::Relation<Item>

A relation of Items that are rough in kits. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



492
# File 'app/models/item.rb', line 492

scope :rough_in_kits,           -> { by_product_line_path(LtreePaths::PL_FLOOR_HEATING_ROUGH_IN_KITS).merge(accessories) }

.sensorsActiveRecord::Relation<Item>

A relation of Items that are sensors. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



413
# File 'app/models/item.rb', line 413

scope :sensors,                 -> { where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_SENSORS)) }

.servicesActiveRecord::Relation<Item>

A relation of Items that are services. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



421
# File 'app/models/item.rb', line 421

scope :services,                -> { where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_SERVICES)) }

.shippingActiveRecord::Relation<Item>

A relation of Items that are shipping. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



470
# File 'app/models/item.rb', line 470

scope :shipping,                -> { where(tax_class: 'shp') }

.sku_aliases_searchActiveRecord::Relation<Item>

A relation of Items that are sku aliases search. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



451
# File 'app/models/item.rb', line 451

scope :sku_aliases_search,      ->(sku) { where('items.sku_aliases @> ARRAY[:sku]::varchar[]', sku:) }

.sku_and_aliases_searchActiveRecord::Relation<Item>

A relation of Items that are sku and aliases search. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



452
# File 'app/models/item.rb', line 452

scope :sku_and_aliases_search,  ->(sku) { where('items.sku ILIKE :sku OR items.sku_aliases @> ARRAY[:sku]::varchar[]', sku:) }

.sku_is_membrane?(sku) ⇒ Boolean

Returns:

  • (Boolean)


1416
1417
1418
# File 'app/models/item.rb', line 1416

def self.sku_is_membrane?(sku)
  ItemConstants::MEMBRANE_SKUS.include?(sku)
end

.sku_searchActiveRecord::Relation<Item>

A relation of Items that are sku search. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



566
# File 'app/models/item.rb', line 566

scope :sku_search, ->(sku) { canonical_search(sku) }

.smart_servicesActiveRecord::Relation<Item>

A relation of Items that are smart services. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



516
# File 'app/models/item.rb', line 516

scope :smart_services,           -> { where(Item[:primary_pl_path_slugs].ltree_descendant(LtreePaths::PL_SERVICES)) }

.smartpresetsActiveRecord::Relation<Item>

A relation of Items that are smartpresets. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



456
# File 'app/models/item.rb', line 456

scope :smartpresets,            -> { where(sku: SMARTPRESET_SERVICE_SKU) }

.smartstatsActiveRecord::Relation<Item>

A relation of Items that are smartstats. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



407
# File 'app/models/item.rb', line 407

scope :smartstats,              -> { controls.where(Item[:primary_pl_path_slugs].ltree_descendant(LtreePaths::PL_FLOOR_HEATING_CONTROL_SMARTSTAT)) }

.snow_melting_elementsActiveRecord::Relation<Item>

A relation of Items that are snow melting elements. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



415
# File 'app/models/item.rb', line 415

scope :snow_melting_elements,   -> { heating_elements.where(Item[:primary_pl_path_slugs].ltree_descendant(LtreePaths::PL_SNOW_MELTING)) }

.spare_partsActiveRecord::Relation<Item>

A relation of Items that are spare parts. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



424
# File 'app/models/item.rb', line 424

scope :spare_parts,             -> { where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_SPARE_PARTS)) }

.tax_classes_for_selectObject



927
928
929
# File 'app/models/item.rb', line 927

def self.tax_classes_for_select
  TAX_CLASSES.invert
end

.tempzonesActiveRecord::Relation<Item>

A relation of Items that are tempzones. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



457
# File 'app/models/item.rb', line 457

scope :tempzones,               -> { where(Item[:primary_pl_path_slugs].ltree_descendant(LtreePaths::PL_FLOOR_HEATING_TEMPZONE)).merge(heating_elements) }

.thermostatsActiveRecord::Relation<Item>

A relation of Items that are thermostats. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



395
# File 'app/models/item.rb', line 395

scope :thermostats,             -> { where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_CONTROLS_THERMOSTATS)) }

.toolsActiveRecord::Relation<Item>

A relation of Items that are tools. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



419
# File 'app/models/item.rb', line 419

scope :tools,                   -> { where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_TOOLS)) }

.towel_warmer_controls_dual_connectActiveRecord::Relation<Item>

A relation of Items that are towel warmer controls dual connect. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



403
# File 'app/models/item.rb', line 403

scope :towel_warmer_controls_dual_connect, -> { where(sku: ItemConstants::TOWEL_WARMER_PLUG_IN_CONTROL_SKUS + ItemConstants::TOWEL_WARMER_HARDWIRE_CONTROL_SKUS) }

.towel_warmer_controls_hardwiredActiveRecord::Relation<Item>

A relation of Items that are towel warmer controls hardwired. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



400
# File 'app/models/item.rb', line 400

scope :towel_warmer_controls_hardwired, -> { where(sku: ItemConstants::TOWEL_WARMER_HARDWIRE_CONTROL_SKUS) }

.towel_warmer_controls_plug_inActiveRecord::Relation<Item>

A relation of Items that are towel warmer controls plug in. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



399
# File 'app/models/item.rb', line 399

scope :towel_warmer_controls_plug_in, -> { where(sku: ItemConstants::TOWEL_WARMER_PLUG_IN_CONTROL_SKUS) }

.towel_warmersActiveRecord::Relation<Item>

A relation of Items that are towel warmers. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



396
# File 'app/models/item.rb', line 396

scope :towel_warmers,           -> { where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_TOWEL_WARMERS)) }

.towel_warmers_hardwiredActiveRecord::Relation<Item>

A relation of Items that are towel warmers hardwired. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



398
# File 'app/models/item.rb', line 398

scope :towel_warmers_hardwired, -> { where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_TOWEL_WARMERS_HARDWIRED)) }

.towel_warmers_plug_inActiveRecord::Relation<Item>

A relation of Items that are towel warmers plug in. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



397
# File 'app/models/item.rb', line 397

scope :towel_warmers_plug_in,   -> { where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_TOWEL_WARMERS_PLUG_IN)) }

.underlaymentsActiveRecord::Relation<Item>

A relation of Items that are underlayments. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



469
# File 'app/models/item.rb', line 469

scope :underlayments,           -> { where(Item[:pc_path_slugs].ltree_descendant([LtreePaths::PC_ACCESSORIES_INSULATIONS, LtreePaths::PC_ACCESSORIES_MEMBRANES])) }

.uom_select_optionsObject



923
924
925
# File 'app/models/item.rb', line 923

def self.uom_select_options
  %w[each feet]
end

.upgradesActiveRecord::Relation<Item>

A relation of Items that are upgrades. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



417
# File 'app/models/item.rb', line 417

scope :upgrades,                -> { where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_UPGRADES)) }

.with_all_catalog_items_discontinuedActiveRecord::Relation<Item>

A relation of Items that are with all catalog items discontinued. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
# File 'app/models/item.rb', line 617

scope :with_all_catalog_items_discontinued, -> {
  where(%{
    (
      SELECT COUNT(*)
      FROM catalog_items ci1
      INNER JOIN store_items si1 ON ci1.store_item_id = si1.id
      WHERE si1.item_id = items.id
    ) > 0
     AND
    (
      SELECT COUNT(*)
      FROM catalog_items ci1
      INNER JOIN store_items si1 ON ci1.store_item_id = si1.id
      WHERE si1.item_id = items.id
    ) =
    (
      SELECT COUNT(*)
      FROM catalog_items ci2
      INNER JOIN store_items si2 ON ci2.store_item_id = si2.id
      WHERE si2.item_id = items.id AND ci2.state = 'discontinued'
    )
  })
}

.with_featuresActiveRecord::Relation<Item>

A relation of Items that are with features. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



515
# File 'app/models/item.rb', line 515

scope :with_features,            -> { where.not(feature_1: nil).where.not(feature_2: nil).where.not(feature_3: nil).where.not(feature_4: nil).where.not(feature_5: nil) }

.with_product_specificationActiveRecord::Relation<Item>

A relation of Items that are with product specification. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



475
476
477
478
479
480
481
482
483
484
# File 'app/models/item.rb', line 475

scope :with_product_specification, ->(token, value, grouping = nil, include_absence_of = false) {
  match_hsh = { raw: TypeCoercer.coerce(value) }
  grouping = grouping.to_s.titleize if grouping.is_a?(Symbol)
  match_hsh[:grouping] = grouping if grouping.present?
  test_condition_criteria = +'items.rendered_product_specifications @> :condition_1'
  test_condition_criteria << ' OR NOT (rendered_product_specifications ? :condition_2)' if include_absence_of
  condition_1 = { token.to_s => match_hsh }.to_json
  condition_2 = token.to_s
  where(test_condition_criteria, condition_1:, condition_2:)
}

.with_stockActiveRecord::Relation<Item>

A relation of Items that are with stock. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



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

scope :with_stock, -> {
  where("exists(select id from store_items where store_items.item_id = items.id and qty_on_hand > 0 and store_items.location = 'AVAILABLE')")
}

.with_stock_forActiveRecord::Relation<Item>

A relation of Items that are with stock for. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



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

scope :with_stock_for, ->(store_id) {
  where("exists(select id from store_items where store_items.store_id = ? and store_items.item_id = items.id and qty_on_hand > 0 and store_items.location = 'AVAILABLE')", store_id)
}

Instance Method Details

#all_imagesObject

Find all images linked to this item directly or by combination of product line or category ancestry
This is a simpler method than image retriever



2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
# File 'app/models/item.rb', line 2418

def all_images
  # Start simple find those that have direct links
  query = Image.left_outer_joins(:items)
               .left_outer_joins(:product_lines)
               .left_outer_joins(:product_categories)
  # Conditions as follow
  conditions = []
  plids = product_line_ancestry_ids
  pcids = product_category_ancestry_ids
  # Condition 1 - Image tagged specifically for this item
  conditions << Item[:id].eq(id)
  # Condition 2 are images tagged by any ancestry product line but no specific product category
  conditions << ProductLine[:id].in(plids).and(ProductCategory[:id].eq(nil)) if plids.present?
  # Condition 3 are images tagged by any ancestry product category but no specific product line
  conditions << ProductLine[:id].eq(nil).and(ProductCategory[:id].in(pcids)) if pcids.present?
  # Condition 4 are images tagged by both product line and category ancestries
  conditions << ProductLine[:id].in(plids).and(ProductCategory[:id].in(pcids)) if plids.present? && pcids.present?
  # Create an or condition
  combined_conditions = conditions.reduce { |acc, condition| acc.or(condition) }
  query.where(combined_conditions)
end

#all_my_publications(categories: nil, locale: nil, publication_category_paths: nil) ⇒ Object



1740
1741
1742
# File 'app/models/item.rb', line 1740

def all_my_publications(categories: nil, locale: nil, publication_category_paths: nil)
  Item::PublicationRetriever.new.process(self, categories:, locale:, publication_category_paths:).all_publications
end

#all_site_mapsObject



1955
1956
1957
# File 'app/models/item.rb', line 1955

def all_site_maps
  [] + site_maps.to_a + catalog_item_site_maps.to_a
end

#all_uploadsObject



2476
2477
2478
# File 'app/models/item.rb', line 2476

def all_uploads
  uploads.order(Upload[:created_at].desc)
end

#any_box_changed?Boolean

Returns:

  • (Boolean)


2472
2473
2474
# File 'app/models/item.rb', line 2472

def any_box_changed?
  box1_changed? || box2_changed? || box3_changed?
end

#articlesActiveRecord::Relation<Article>

Returns:

  • (ActiveRecord::Relation<Article>)

See Also:



307
# File 'app/models/item.rb', line 307

has_and_belongs_to_many :articles

#assortment_instructionsActiveRecord::Relation<AssortmentInstruction>

Returns:

See Also:



310
# File 'app/models/item.rb', line 310

has_and_belongs_to_many :assortment_instructions, inverse_of: :items

#async_update_rendered_product_specificationsObject



1951
1952
1953
# File 'app/models/item.rb', line 1951

def async_update_rendered_product_specifications
  ItemAttributeWorker.perform_async(id)
end

#authenticated_url(dimensions = nil) ⇒ Object



864
865
866
# File 'app/models/item.rb', line 864

def authenticated_url(dimensions = nil)
  (dimensions.nil? ? literature.attachment.url : literature.attachment.thumb(dimensions, format: 'png', frame: '0').url) if literature
end

#auto_translate(reset_previous_values: false, locales: nil) ⇒ Object



1808
1809
1810
1811
1812
1813
1814
1815
1816
# File 'app/models/item.rb', line 1808

def auto_translate(reset_previous_values: false, locales: nil)
  locales ||= content_locales_to_render
  Rails.logger.info "Product Specifications for Item #{id}:#{sku} will be rendered in #{locales.join(', ')}"
  # auto_translate_attributes(reset_previous_values: reset_previous_values, locales: locales)
  auto_translate_product_specifications(reset_previous_values:, locales:)
  reload
  update_rendered_product_specifications(locales:)
  :success
end

#auto_translate_attributes(reset_previous_values: false, locales: nil) ⇒ Object



1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
# File 'app/models/item.rb', line 1818

def auto_translate_attributes(reset_previous_values: false, locales: nil)
  # Auto translate attributes from the translatable list
  locales ||= content_locales_to_render
  res = {}
  TRANSLATABLE_ATTRIBUTES.each do |attr_name|
    next unless (english_val = send(:"#{attr_name}_en").presence)

    # We loop through the locales
    res[attr_name] = {}
    locales.each do |locale|
      # If our locale already exists and we specify we do not want to reset value, we can skip
      next if !reset_previous_values && Mobility.with_locale(locale) { send(attr_name.to_sym) }.present?

      # Translate
      localized_val = TranslationKey.translate(english_val, locale, namespace: TRANSLATION_NAMESPACE, auto_create: false, auto_translate: true, resource: self, resource_attribute: attr_name)
      next if localized_val.blank?

      # Store it in the locale
      Mobility.with_locale(locale) { send(:"#{attr_name}=", localized_val, locale:) }
      res[attr_name][locale] = localized_val
    end
  end
  if save && res
    :success
  else
    :error
  end
end

#auto_translate_product_specifications(reset_previous_values: false, locales: nil) ⇒ Object



1847
1848
1849
1850
1851
1852
1853
# File 'app/models/item.rb', line 1847

def auto_translate_product_specifications(reset_previous_values: false, locales: nil)
  locales ||= content_locales_to_render
  product_specifications.each do |ps|
    ps.auto_translate(reset_previous_values:, locales:)
  end
  :success
end

#available_content_localesObject

Returns all locales from the catalog this item is part of



1996
1997
1998
1999
# File 'app/models/item.rb', line 1996

def available_content_locales
  # Join to catalog and find out in which locale we need to generate content, english is implied
  Catalog.eager_load(:catalog_items).merge(catalog_items.not_discontinued).pluck(:locales).flatten.compact.uniq.sort
end

#available_token_options_from_product_specificationsObject



2142
2143
2144
# File 'app/models/item.rb', line 2142

def available_token_options_from_product_specifications
  (rendered_product_specifications || {}).each_with_object({}.with_indifferent_access) { |(rpk, rpv), hsh| hsh[rpk] = rpv[:output] }
end

#base_weight_converted(unit: 'lbs', precision: nil) ⇒ Object



1610
1611
1612
# File 'app/models/item.rb', line 1610

def base_weight_converted(unit: 'lbs', precision: nil)
  express_weight_converted base_weight, unit:, precision:
end

#box1_changed?Boolean

Returns:

  • (Boolean)


2452
2453
2454
# File 'app/models/item.rb', line 2452

def box1_changed?
  %i[shipping_weight shipping_length shipping_width shipping_weight].any? { |fn| send(:"#{fn}_previously_changed?") || send(:"will_save_change_to_#{fn}?") }
end

#box1_defined?Boolean

Returns:

  • (Boolean)


2448
2449
2450
# File 'app/models/item.rb', line 2448

def box1_defined?
  %i[shipping_weight shipping_length shipping_width shipping_weight].all? { |fn| send(fn)&.positive? }
end

#box2_changed?Boolean

Returns:

  • (Boolean)


2460
2461
2462
# File 'app/models/item.rb', line 2460

def box2_changed?
  %i[box2_shipping_weight box2_shipping_length box2_shipping_width box2_shipping_weight].any? { |fn| send(:"#{fn}_previously_changed?") || send(:"will_save_change_to_#{fn}?") }
end

#box2_defined?Boolean

Returns:

  • (Boolean)


2456
2457
2458
# File 'app/models/item.rb', line 2456

def box2_defined?
  %i[box2_shipping_weight box2_shipping_length box2_shipping_width box2_shipping_height].all? { |fn| send(fn)&.positive? }
end

#box2_shipping_dimensions_to_packageObject



1641
1642
1643
1644
1645
1646
# File 'app/models/item.rb', line 1641

def box2_shipping_dimensions_to_package
  return unless box2_defined?

  Shipping::Container.new(length: box2_shipping_length, width: box2_shipping_width, height: box2_shipping_height, weight: box2_shipping_weight,
                          container_type:)
end

#box2_shipping_height_converted(unit: 'in', precision: 0) ⇒ Object



1578
1579
1580
1581
1582
# File 'app/models/item.rb', line 1578

def box2_shipping_height_converted(unit: 'in', precision: 0)
  return unless box2_shipping_height

  RubyUnits::Unit.new("#{box2_shipping_height} in").convert_to(unit.to_s).scalar.to_f.round(precision)
end

#box2_shipping_length_converted(unit: 'in', precision: 0) ⇒ Object



1566
1567
1568
1569
1570
# File 'app/models/item.rb', line 1566

def box2_shipping_length_converted(unit: 'in', precision: 0)
  return unless box2_shipping_length

  RubyUnits::Unit.new("#{box2_shipping_length} in").convert_to(unit.to_s).scalar.to_f.round(precision)
end

#box2_shipping_weight_converted(unit: 'lbs', precision: nil) ⇒ Object



1626
1627
1628
# File 'app/models/item.rb', line 1626

def box2_shipping_weight_converted(unit: 'lbs', precision: nil)
  express_weight_converted box2_shipping_weight, unit:, precision:
end

#box2_shipping_width_converted(unit: 'in', precision: 0) ⇒ Object



1572
1573
1574
1575
1576
# File 'app/models/item.rb', line 1572

def box2_shipping_width_converted(unit: 'in', precision: 0)
  return unless box2_shipping_width

  RubyUnits::Unit.new("#{box2_shipping_width} in").convert_to(unit.to_s).scalar.to_f.round(precision)
end

#box3_changed?Boolean

Returns:

  • (Boolean)


2468
2469
2470
# File 'app/models/item.rb', line 2468

def box3_changed?
  %i[box3_shipping_weight box3_shipping_length box3_shipping_width box3_shipping_weight].any? { |fn| send(:"#{fn}_previously_changed?") || send(:"will_save_change_to_#{fn}?") }
end

#box3_defined?Boolean

Returns:

  • (Boolean)


2464
2465
2466
# File 'app/models/item.rb', line 2464

def box3_defined?
  %i[box3_shipping_weight box3_shipping_length box3_shipping_width box3_shipping_height].all? { |fn| send(fn)&.positive? }
end

#box3_shipping_dimensions_to_packageObject



1648
1649
1650
1651
1652
1653
# File 'app/models/item.rb', line 1648

def box3_shipping_dimensions_to_package
  return unless box3_defined?

  Shipping::Container.new(length: box3_shipping_length, width: box3_shipping_width, height: box3_shipping_height, weight: box3_shipping_weight,
                          container_type:)
end

#box3_shipping_height_converted(unit: 'in', precision: 0) ⇒ Object



1596
1597
1598
1599
1600
# File 'app/models/item.rb', line 1596

def box3_shipping_height_converted(unit: 'in', precision: 0)
  return unless box3_shipping_height

  RubyUnits::Unit.new("#{box3_shipping_height} in").convert_to(unit.to_s).scalar.to_f.round(precision)
end

#box3_shipping_length_converted(unit: 'in', precision: 0) ⇒ Object



1584
1585
1586
1587
1588
# File 'app/models/item.rb', line 1584

def box3_shipping_length_converted(unit: 'in', precision: 0)
  return unless box3_shipping_length

  RubyUnits::Unit.new("#{box3_shipping_length} in").convert_to(unit.to_s).scalar.to_f.round(precision)
end

#box3_shipping_weight_converted(unit: 'lbs', precision: nil) ⇒ Object



1630
1631
1632
# File 'app/models/item.rb', line 1630

def box3_shipping_weight_converted(unit: 'lbs', precision: nil)
  express_weight_converted box3_shipping_weight, unit:, precision:
end

#box3_shipping_width_converted(unit: 'in', precision: 0) ⇒ Object



1590
1591
1592
1593
1594
# File 'app/models/item.rb', line 1590

def box3_shipping_width_converted(unit: 'in', precision: 0)
  return unless box3_shipping_width

  RubyUnits::Unit.new("#{box3_shipping_width} in").convert_to(unit.to_s).scalar.to_f.round(precision)
end

#boxesObject



1003
1004
1005
1006
1007
1008
# File 'app/models/item.rb', line 1003

def boxes
  b = [[shipping_length, shipping_width, shipping_height, shipping_weight]]
  b << [box2_shipping_length, box2_shipping_width, box2_shipping_height, box2_shipping_weight] if box2_defined?
  b << [box3_shipping_length, box3_shipping_width, box3_shipping_height, box3_shipping_weight] if box3_defined?
  b
end

#boxes_shipping_dimensions_and_weights_converted(units: { length: 'in', weight: 'lbs' }, precision: 2) ⇒ Object



1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
# File 'app/models/item.rb', line 1083

def boxes_shipping_dimensions_and_weights_converted(units: { length: 'in', weight: 'lbs' }, precision: 2)
  boxes.map do |b|
    [
      RubyUnits::Unit.new("#{b[0]} in").convert_to(units[:length].to_s).scalar.to_f.round(precision),
      RubyUnits::Unit.new("#{b[1]} in").convert_to(units[:length].to_s).scalar.to_f.round(precision),
      RubyUnits::Unit.new("#{b[2]} in").convert_to(units[:length].to_s).scalar.to_f.round(precision),
      RubyUnits::Unit.new("#{b[3]} lb").convert_to(units[:weight].to_s).scalar.to_f.round(precision)
    ]
  end
end

#boxes_shipping_dimensions_and_weights_display(units: { length: 'in', weight: 'lbs' }, precision: 2) ⇒ Object



1094
1095
1096
1097
1098
1099
1100
1101
# File 'app/models/item.rb', line 1094

def boxes_shipping_dimensions_and_weights_display(units: { length: 'in', weight: 'lbs' }, precision: 2)
  boxes_shipping_dimensions_and_weights_converted(units:, precision:).map do |b|
    # %g drops trailing zeros so 37.0 → "37", 0.27 → "0.27", 0.5 → "0.5".
    # Previous precision: 0 default rounded sub-1″ dims to 0, producing
    # nonsense like "37 in x 25 in x 0 in" for items with fractional height.
    "#{format('%g', b[0])} #{units[:length]} x #{format('%g', b[1])} #{units[:length]} x #{format('%g', b[2])} #{units[:length]} x #{format('%g', b[3])} #{units[:weight]}"
  end
end

#boxes_to_packagesObject



1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
# File 'app/models/item.rb', line 1010

def boxes_to_packages
  packages = []
  boxes.each do |box|
    le, wi, he, we, = box + [0]
    package = Shipping::Container.new(length: le, width: wi, height: he, weight: we, container_type: Shipment.container_type_from_packdim_int(0))
    package.add_content(id, 1)
    packages << package
  end
  packages
end

#brandBrand

Returns:

See Also:



264
# File 'app/models/item.rb', line 264

belongs_to :brand, optional: true

#calculate_ideal_cable_spacingObject



1434
1435
1436
1437
1438
1439
1440
# File 'app/models/item.rb', line 1434

def calculate_ideal_cable_spacing
  res = nil
  if primary_product_line && primary_product_line.get_first_heating_system_type.present?
    res = HeatingElementProductLineOption.get_field_array_from_options(:ideal_cable_spacing, product_line_id: primary_product_line.get_first_heating_system_type.id).first
  end
  res
end

#calculate_is_cable_system?Boolean

Returns:

  • (Boolean)


1425
1426
1427
1428
1429
1430
1431
1432
# File 'app/models/item.rb', line 1425

def calculate_is_cable_system?
  res = nil
  if primary_product_line && primary_product_line.get_first_heating_system_type.present? && HeatingElementProductLineOption.get_field_array_from_options(:require_cable_spacing,
                                                                                                                                                         product_line_id: primary_product_line.get_first_heating_system_type.id).first
    res = true
  end
  res
end

#calculate_item_fieldsObject



985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
# File 'app/models/item.rb', line 985

def calculate_item_fields
  # always set largest shipping dimension to shipping_length, then shipping_width, then shipping_height
  sorted_dims = [shipping_length, shipping_width, shipping_height].sort_by(&:to_f)
  self.shipping_length = sorted_dims[2]
  self.shipping_width = sorted_dims[1]
  self.shipping_height = sorted_dims[0]
  box2_sorted_dims = [box2_shipping_length, box2_shipping_width, box2_shipping_height].sort_by(&:to_f)
  self.box2_shipping_length = box2_sorted_dims[2]
  self.box2_shipping_width = box2_sorted_dims[1]
  self.box2_shipping_height = box2_sorted_dims[0]
  box3_sorted_dims = [box3_shipping_length, box3_shipping_width, box3_shipping_height].sort_by(&:to_f)
  self.box3_shipping_length = box3_sorted_dims[2]
  self.box3_shipping_width = box3_sorted_dims[1]
  self.box3_shipping_height = box3_sorted_dims[0]
  self.ideal_cable_spacing = (calculate_ideal_cable_spacing if (self.is_cable_system = calculate_is_cable_system?))
  true
end

#can_be_packaged?Boolean

Returns:

  • (Boolean)


976
977
978
# File 'app/models/item.rb', line 976

def can_be_packaged?
  Item.packageable.where(id:).present?
end

#canonical_pathObject

Hierarchical canonical path via product line ancestry + SKU.
e.g. "towel-warmer/barcelona/TW-BC-08BS-FS2"



730
731
732
733
734
735
736
737
# File 'app/models/item.rb', line 730

def canonical_path
  return nil unless primary_product_line

  pl_path = primary_product_line.canonical_path
  return nil if pl_path.blank?

  "#{pl_path}/#{sku}"
end

#canonical_skuObject



1695
1696
1697
1698
1699
1700
1701
1702
# File 'app/models/item.rb', line 1695

def canonical_sku
  sku_match = sku.match(/(.*)-([[:alpha:]])$/)
  if sku_match && (sku_match.length == 3)
    sku_match[1]
  else
    sku
  end
end

#canonical_url(locale: I18n.locale) ⇒ Object

Canonical URL with locale prefix.
e.g. "/en-US/towel-warmer/barcelona/TW-BC-08BS-FS2"



741
742
743
744
745
746
# File 'app/models/item.rb', line 741

def canonical_url(locale: I18n.locale)
  path = canonical_path
  return nil unless path

  "/#{locale}/#{path}"
end

#catalog_item_site_mapsActiveRecord::Relation<SiteMap>

Returns:

  • (ActiveRecord::Relation<SiteMap>)

See Also:



293
# File 'app/models/item.rb', line 293

has_many :catalog_item_site_maps, class_name: 'SiteMap', source: :site_maps, through: :catalog_items

#catalog_itemsActiveRecord::Relation<CatalogItem>

Returns:

See Also:



271
# File 'app/models/item.rb', line 271

has_many :catalog_items, through: :store_items, inverse_of: :item

#catalogsActiveRecord::Relation<Catalog>

Returns:

  • (ActiveRecord::Relation<Catalog>)

See Also:



272
# File 'app/models/item.rb', line 272

has_many :catalogs, through: :catalog_items

#check_oversize?(carrier: nil) ⇒ Boolean

Check if item is oversize, optionally for a specific carrier
When no carrier specified, checks all carriers (item is oversize if ANY carrier flags it)

Parameters:

  • carrier (String, nil) (defaults to: nil)

    Optional carrier name (e.g., 'UPS', 'FedEx', 'Purolator', 'Canpar')

Returns:

  • (Boolean)


1681
1682
1683
1684
1685
1686
1687
# File 'app/models/item.rb', line 1681

def check_oversize?(carrier: nil)
  r = run_shipping_audit(carrier:)
  r.any?(&:oversize_conditions?)
rescue StandardError
  logger.error "Oversize check failed on item id #{id}, returning false"
  false
end

#compatible_itemsActiveRecord::Relation<CompatibleItem>

Returns:

  • (ActiveRecord::Relation<CompatibleItem>)

See Also:



298
# File 'app/models/item.rb', line 298

has_many :compatible_items, -> { merge(ItemRelation.replacement_parts) }, through: :source_item_relations, source: :source_item

#container_typeObject



2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
# File 'app/models/item.rb', line 2368

def container_type
  # let's guess, if ships_via_crate? crate, if ships LTL, pallet, otherwise carton
  if ships_via_crate?
    Shipment.container_types.keys[2] # 'crate'
  elsif shipping_class == 'freight_service'
    Shipment.container_types.keys[1] # 'pallet'
  else
    Shipment.container_types.keys.first # 'carton'
  end
end

#content_locales_to_renderObject



2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
# File 'app/models/item.rb', line 2001

def content_locales_to_render
  if is_publication?
    Mobility.available_locales
  else
    locales = (Mobility.available_locales & ([I18n.default_locale] + available_content_locales)).uniq
    # Because en-US and en-CA are at this point the same, we don't bother rendering these, if this changes, this is
    # where you remove the following code.  We'll put french in the same category why not.  Don't solve this problem
    # until we actually have a product sold in france and canada
    locales.delete(:'en-US')
    locales.delete(:'en-CA')
    locales.delete(:'fr-CA') if locales.include?(:fr)
    locales
  end
end

#controllable?Boolean

Returns:

  • (Boolean)


771
772
773
774
775
# File 'app/models/item.rb', line 771

def controllable?
  is_heating_element? ||
    is_towel_warmer? ||
    is_infrared_heating_panel?
end

#country_of_origin_iso3Object



715
716
717
718
719
# File 'app/models/item.rb', line 715

def country_of_origin_iso3
  return unless coo

  ISO3166::Country[coo]&.alpha3
end

#country_of_origin_nameObject



709
710
711
712
713
# File 'app/models/item.rb', line 709

def country_of_origin_name
  return unless coo

  ISO3166::Country[coo]&.iso_short_name
end

#create_template_specificationsObject



2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
# File 'app/models/item.rb', line 2108

def create_template_specifications
  return OpenStruct.new(created: [], errors: ['Missing Product Line or Product Category']) unless primary_product_line_id || product_category_id

  res = Item::SpecificationsRetriever.new.process(self, template: true)
  res_classic = Item::SpecificationsRetriever.new.process(self, template: false)

  ps_created = []
  ps_errors = []
  res.specifications.each do |template_spec|
    # Check if there is already a spec for this, only template if blank
    next if spec_value(template_spec.token).present? || res_classic.specifications.any? { |s| s.token == template_spec.token }

    s = template_spec.dup
    s.product_category_id = nil
    s.product_line_id = nil
    s.template = false
    s.item_ids = [id].compact
    s.template_product_specification_id = template_spec.id
    if s.save
      ps_created << s
    else
      ps_errors << "Template Spec id #{template_spec.id} : #{template_spec} could not be created for item #{id}|#{sku}.  Errors: #{s.errors_to_s}"
    end
  end
  OpenStruct.new(created: ps_created, errors: ps_errors)
end

#current_supplier_costObject



765
766
767
768
769
# File 'app/models/item.rb', line 765

def current_supplier_cost
  return 'not found' if supplier_items.empty? || (current_price = supplier_items.order(created_at: :desc).first&.current_price).nil?

  "#{current_price.unit_cost} #{current_price.currency}"
end

#deep_dupObject



227
228
229
230
231
232
233
234
# File 'app/models/item.rb', line 227

def deep_dup
  deep_clone(
    include: :item_product_lines,
    except: %i[upc upload_id last_sale_date first_sale_date popularity amazon_asin]
  ) do |_original, copy|
    copy.popularity = nil if copy.is_a?(Item)
  end
end

#default_short_descriptionObject



1763
1764
1765
1766
1767
# File 'app/models/item.rb', line 1763

def default_short_description
  return unless (dd = detailed_description.to_s.squish.presence) && dd.present?

  dd.truncate(120, separator: ' ')
end

#detailed_descriptionObject

Creates a text description from the html rendered description



1759
1760
1761
# File 'app/models/item.rb', line 1759

def detailed_description
  html_to_text(rendered_detailed_description_html)
end

#digital_assetsActiveRecord::Relation<DigitalAsset>

Returns:

See Also:



308
# File 'app/models/item.rb', line 308

has_and_belongs_to_many :digital_assets

#direct_packingsActiveRecord::Relation<Packing>

Returns:

  • (ActiveRecord::Relation<Packing>)

See Also:



301
# File 'app/models/item.rb', line 301

has_many :direct_packings, class_name: 'Packing', inverse_of: :item, dependent: :destroy

#direct_product_specificationsActiveRecord::Relation<ProductSpecification>

Returns:

See Also:



312
# File 'app/models/item.rb', line 312

has_and_belongs_to_many :direct_product_specifications, class_name: 'ProductSpecification', dependent: :destroy, inverse_of: :items

#discontinue_self_and_dependentsObject



966
967
968
969
970
971
972
973
974
# File 'app/models/item.rb', line 966

def discontinue_self_and_dependents
  self.class.transaction do
    store_items.each do |si|
      si.catalog_items.each(&:discontinue) # This event updates the state and hides the catalog from feed
      si.update_attribute!(:is_discontinued, true)
    end
    update!(is_discontinued: true, cycle_count_grouping: 'not_set')
  end
end

#discontinue_store_and_catalog_itemsObject



959
960
961
962
963
964
# File 'app/models/item.rb', line 959

def discontinue_store_and_catalog_items
  return if is_publication?

  discontinue_self_and_dependents if is_discontinued?
  true
end

#ean13Object



853
854
855
856
857
858
859
860
861
862
# File 'app/models/item.rb', line 853

def ean13
  return if upc.blank?

  case upc.size
  when 12
    "0#{upc}"
  when 13
    upc
  end
end

#edge_cache_urlsObject

Absolute Cloudflare URLs for this item's pages AND their lazy section
fragments (documents/reviews/…), which Cloudflare caches as separate edge
entries from the page URL (see SiteMap#section_cache_urls). uniq: all_site_maps
merges two associations that can expand to overlapping section URLs.



1974
1975
1976
# File 'app/models/item.rb', line 1974

def edge_cache_urls
  all_site_maps.flat_map { |sm| [sm.url, *sm.section_cache_urls] }.uniq
end

#edi_communication_logsActiveRecord::Relation<EdiCommunicationLog>

Returns:

See Also:



303
# File 'app/models/item.rb', line 303

has_many :edi_communication_logs, through: :edi_documents

#edi_documentsActiveRecord::Relation<EdiDocument>

Returns:

See Also:



302
# File 'app/models/item.rb', line 302

has_many :edi_documents, dependent: :destroy

#effective_nmfc_classObject

Alias for Product_category#effective_nmfc_class

Returns:

  • (Object)

    Product_category#effective_nmfc_class

See Also:



649
650
651
652
653
654
655
656
657
658
# File 'app/models/item.rb', line 649

delegate :is_control?,
:is_upgrade?,
:is_accessory?,
:is_relay_panel?,
:is_custom_mat?,
:effective_nmfc_code,
:effective_nmfc_class,
:is_publication?,
to: :product_category,
allow_nil: true

#effective_nmfc_codeObject

Alias for Product_category#effective_nmfc_code

Returns:

  • (Object)

    Product_category#effective_nmfc_code

See Also:



649
650
651
652
653
654
655
656
657
658
# File 'app/models/item.rb', line 649

delegate :is_control?,
:is_upgrade?,
:is_accessory?,
:is_relay_panel?,
:is_custom_mat?,
:effective_nmfc_code,
:effective_nmfc_class,
:is_publication?,
to: :product_category,
allow_nil: true

#effective_option_nameObject



2296
2297
2298
# File 'app/models/item.rb', line 2296

def effective_option_name
  option_name.presence || public_short_name.presence || name
end

#effective_seo_description(char_limit: nil) ⇒ Object



868
869
870
871
872
873
874
875
876
877
878
879
880
# File 'app/models/item.rb', line 868

def effective_seo_description(char_limit: nil)
  char_limit ||= SEO_META_DESCRIPTION_MAX_LENGTH
  d = seo_description.presence || short_description.presence || public_description_text || name
  return unless d

  # We replace by spaces first in case line feed is used as a sentence separator.
  d.tr!("\n", ' ')
  d.tr!("\r", ' ')
  d.tr!("\t", ' ')
  d.squeeze!(' ')
  d = d.truncate(char_limit, separator: /\s/) if char_limit
  d.squish
end

#effective_seo_keywordsObject



1790
1791
1792
1793
1794
1795
1796
1797
# File 'app/models/item.rb', line 1790

def effective_seo_keywords
  return seo_keywords.presence if seo_keywords.present?

  pl_keywords = primary_product_line&.effective_seo_keywords
  return pl_keywords.join(',') if pl_keywords.is_a?(Array)

  pl_keywords.presence
end

#effective_seo_title(skip_sku: false) ⇒ Object



882
883
884
885
886
887
888
889
890
891
892
893
# File 'app/models/item.rb', line 882

def effective_seo_title(skip_sku: false)
  s = (seo_title.presence || public_short_name.presence || public_name.presence).to_s
  if !skip_sku && (s.size + sku.size + 3) < SEO_TITLE_MAX_LENGTH && s.exclude?(sku)
    # if we have room add the sku
    s << " (#{sku})"
  end
  if s.size > SEO_TITLE_MAX_LENGTH
    # truncate
    s.truncate(SEO_TITLE_MAX_LENGTH, separator: /\s/, omission: '')
  end
  s
end

#exclusive_item_groupExclusiveItemGroup



255
# File 'app/models/item.rb', line 255

belongs_to :exclusive_item_group, inverse_of: :items, optional: true

#express_weight_converted(weight_in_lbs, unit: 'lbs', precision: nil) ⇒ Object



1602
1603
1604
1605
1606
1607
1608
# File 'app/models/item.rb', line 1602

def express_weight_converted(weight_in_lbs, unit: 'lbs', precision: nil)
  return unless weight_in_lbs

  precision ||= (unit == 'g' ? 0 : 2)
  d = RubyUnits::Unit.new("#{weight_in_lbs} lbs")
  d.convert_to(unit.to_s).scalar.to_f.round(precision)
end

#facetObject



2277
2278
2279
2280
2281
2282
2283
2284
2285
# File 'app/models/item.rb', line 2277

def facet
  return unless (pl = primary_product_line&.get_first_show_in_sales_portal_ancestor(exclude_main_line: false))
  return unless (pc = product_category)

  potential_facets = pl.facets.select { |f| f.product_category_ids.to_a.include?(pc.id) }
  return if potential_facets.blank?

  potential_facets.first
end

#facet_sort_keysObject



2292
2293
2294
# File 'app/models/item.rb', line 2292

def facet_sort_keys
  facet&.sort_keys
end

#facet_tokensObject



2288
2289
2290
# File 'app/models/item.rb', line 2288

def facet_tokens
  facet&.grouping_spec_tokens&.reject(&:blank?)
end

#featuresObject

Features are a sub category of specs with feature priority set



1911
1912
1913
1914
1915
1916
1917
# File 'app/models/item.rb', line 1911

def features
  if has_features?
    [feature_1, feature_2, feature_3, feature_4, feature_5].filter_map(&:presence)
  else
    inherited_features # Look for inherited features
  end
end

#fix_nameObject



1769
1770
1771
1772
1773
1774
# File 'app/models/item.rb', line 1769

def fix_name
  # ' is interpreted as ′
  self.name = name.tr("'", '′')
  # " is interpreted as ″
  self.name = name.tr('"', '″')
end

#fixture_keyObject



1442
1443
1444
# File 'app/models/item.rb', line 1442

def fixture_key
  sku.to_s
end

#freight_classObject



1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
# File 'app/models/item.rb', line 1064

def freight_class
  freight_class = nil
  # important: item.base_weight is the better quality number and means Net Weight so the correct number to use when calculating item weight because tare weight (of the box, pallet, crate etc) is added after
  if base_weight.present? && shipping_volume_in_cubic_feet.present?
    pcf = base_weight / shipping_volume_in_cubic_feet
    Shipping::UpsFreight::FREIGHT_CLASS_BY_PCF.each do |fc, pcf_limits|
      if pcf >= pcf_limits[:lower] && pcf < pcf_limits[:upper]
        freight_class = fc.to_i
        break
      end
    end
  end
  freight_class
end

#generate_refurbished_skuObject



1720
1721
1722
# File 'app/models/item.rb', line 1720

def generate_refurbished_sku
  "#{sku}-BTK"
end

#get_quantity_from_sqft(input_sqft, add_one_extra = false) ⇒ Object



1474
1475
1476
1477
1478
# File 'app/models/item.rb', line 1474

def get_quantity_from_sqft(input_sqft, add_one_extra = false)
  qty = add_one_extra ? 1 : 0
  qty += (input_sqft / sqft).ceil.to_i if sqft.to_f > 0.0
  qty
end

#gross_weight_converted(unit: 'lbs', precision: nil) ⇒ Object



1614
1615
1616
# File 'app/models/item.rb', line 1614

def gross_weight_converted(unit: 'lbs', precision: nil)
  express_weight_converted gross_weight, unit:, precision:
end

#gtin13Object



1516
1517
1518
# File 'app/models/item.rb', line 1516

def gtin13
  "0#{upc}"
end

#gtin_typeObject



748
749
750
751
752
# File 'app/models/item.rb', line 748

def gtin_type
  return :missing if upc.blank?

  Item::UpcBarcode.identify_type(upc)
end

#gtin_type_friendlyObject



754
755
756
757
758
759
760
761
762
763
# File 'app/models/item.rb', line 754

def gtin_type_friendly
  case gtin_type
  when :gtin12
    'GTIN-12/UPC'
  when :gtin13
    'GTIN-13/EAN'
  else
    'Unknown/Invalid'
  end
end

#harmonization_code_hs6(with_dot = true) ⇒ Object

Returns the first 6 digits of the harmonization code (HS6)



1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
# File 'app/models/item.rb', line 1525

def harmonization_code_hs6(with_dot = true)
  return nil if harmonization_code.blank?

  digits = harmonization_code.delete('.').first(6)
  if with_dot
    "#{digits[0..3]}.#{digits[4..5]}"
  else
    digits
  end
end

#has_ean13?Boolean

Returns:

  • (Boolean)


849
850
851
# File 'app/models/item.rb', line 849

def has_ean13?
  upc && upc.size == 13
end

#has_features?Boolean

Returns:

  • (Boolean)


1906
1907
1908
# File 'app/models/item.rb', line 1906

def has_features?
  [feature_1, feature_2, feature_3, feature_4, feature_5].any?(&:present?)
end

#has_heating_system?Boolean

Returns:

  • (Boolean)


1458
1459
1460
# File 'app/models/item.rb', line 1458

def has_heating_system?
  primary_product_line.get_first_heating_system_type.present?
end

#has_stock?Boolean

Returns:

  • (Boolean)


945
946
947
# File 'app/models/item.rb', line 945

def has_stock?
  self.class.with_stock.exists?(id:)
end

#has_stock_in_store?(st_id) ⇒ Boolean

Returns:

  • (Boolean)


949
950
951
# File 'app/models/item.rb', line 949

def has_stock_in_store?(st_id)
  self.class.with_stock_for(st_id).exists?(id:)
end

#image_profilesActiveRecord::Relation<ImageProfile>

Returns:

See Also:



304
# File 'app/models/item.rb', line 304

has_many :image_profiles, -> { image_order }

#image_url(dimensions, options = {}) ⇒ Object



1125
1126
1127
1128
# File 'app/models/item.rb', line 1125

def image_url(dimensions, options = {})
  opts = { size: dimensions }.merge(options)
  image_url2(opts)
end

#image_url2(options = {}) ⇒ Object

Use this new form that passes the options straight to the url generator



1117
1118
1119
1120
1121
1122
1123
# File 'app/models/item.rb', line 1117

def image_url2(options = {})
  if primary_image
    primary_image.image_url(options)
  elsif new_item&.primary_image
    new_item.primary_image.image_url(options)
  end
end

#imagesActiveRecord::Relation<Image>

Returns:

  • (ActiveRecord::Relation<Image>)

See Also:



305
# File 'app/models/item.rb', line 305

has_many :images, through: :image_profiles

#import_translation_keysObject

This method will import existing translations or link to existing ones



1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
# File 'app/models/item.rb', line 1856

def import_translation_keys
  tkrs = []
  translations.each do |(locale_string, existing_translations)|
    existing_translations.each do |attr, translation|
      next unless attr.to_sym.in?(TRANSLATABLE_ATTRIBUTES)
      # The presence of resources implies it was imported
      next if translation_key_resources.joins(translation_key: :translations).where(resource_attribute: attr).joins(translation_key: :translations).where(TranslationText[:locale].eq(locale_string)).exists?
      next if (text = TranslationKey.newline_normalize(send(:"#{attr}_en"))).blank?
      next unless TranslationKey.translatable?(text)

      # Does this translation key exists?
      tk = TranslationKey.where(key: text, namespace: TRANSLATION_NAMESPACE).first_or_create!
      # Do we need to import this translation
      tk.translations.where(locale: locale_string.first(2)).first_or_create!(text: translation)
      # And link our new resource
      tkrs << tk.translation_key_resources.where(resource: self, resource_attribute: attr).first_or_create!
    end
  end

  # Import the product specifications
  spec_ids = Item::SpecificationsMasher.new.process(item: self).specifications.values.pluck(:product_specification_id).uniq.compact
  specifications_to_import = ProductSpecification.where(id: spec_ids)

  tkrs += specifications_to_import.map(&:import_translation_keys).flatten.compact

  tkrs
end

#increment_request_counterObject



781
782
783
784
785
# File 'app/models/item.rb', line 781

def increment_request_counter
  return unless persisted?

  Item.update_counters id, requested_counter: 1
end

#inherited_featuresObject

Seek inherited features from the primary product line
To inherit features, a product line's product category must be empty or matching
e.g Tempzone Floor Heating has Heating Elements as a product category
then items belonging to Tempzone Floor Heating must also be heating elements to
inherit its features



1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
# File 'app/models/item.rb', line 1924

def inherited_features
  features_result = []
  if (ppl = primary_product_line) && (pc_url = product_category_url)
    # Only take product lines which have features defined directly
    ppl.self_and_ancestors.select(&:has_features?).each do |pl|
      # If the product line is 'loose' we can use its features
      # The product category of the item must match
      if pl.product_categories.empty? || pl.product_categories.any? { |pc| pc_url.starts_with?(pc.url) }
        features_result = pl.features
        break
      end
    end
  end
  # If we get here there's nothing to inherit
  features_result.filter_map(&:presence)
end

#is_accessory?Object

Alias for Product_category#is_accessory?

Returns:

  • (Object)

    Product_category#is_accessory?

See Also:



649
650
651
652
653
654
655
656
657
658
# File 'app/models/item.rb', line 649

delegate :is_control?,
:is_upgrade?,
:is_accessory?,
:is_relay_panel?,
:is_custom_mat?,
:effective_nmfc_code,
:effective_nmfc_class,
:is_publication?,
to: :product_category,
allow_nil: true

#is_available_to_public?Boolean

Returns:

  • (Boolean)


808
809
810
811
# File 'app/models/item.rb', line 808

def is_available_to_public?
  logger.debug "Checking if #{id} / #{sku} is available to public"
  orderable_view_product_catalogs.present?
end

#is_cable_accessory?Boolean

Returns:

  • (Boolean)


1397
1398
1399
1400
# File 'app/models/item.rb', line 1397

def is_cable_accessory?
  product_category&.is_accessory? &&
    pl_descendant_of_path?(LtreePaths::PL_FLOOR_HEATING_TEMPZONE_THIN_CABLE)
end

#is_cable_fit_guide?Boolean

Returns:

  • (Boolean)


1385
1386
1387
1388
1389
# File 'app/models/item.rb', line 1385

def is_cable_fit_guide?
  product_category&.is_accessory? &&
    pl_descendant_of_path?(LtreePaths::PL_FLOOR_HEATING_TEMPZONE_THIN_CABLE) &&
    name.to_s.downcase.include?('cable fixing plastic strip')
end

#is_cable_heating_element?Boolean

Returns:

  • (Boolean)


1320
1321
1322
# File 'app/models/item.rb', line 1320

def is_cable_heating_element?
  pc_descendant_of_path?(LtreePaths::PC_HEATING_ELEMENTS_HEATING_CABLES)
end

#is_cable_tape?Boolean

Returns:

  • (Boolean)


1391
1392
1393
1394
1395
# File 'app/models/item.rb', line 1391

def is_cable_tape?
  product_category&.is_accessory? &&
    pl_descendant_of_path?(LtreePaths::PL_FLOOR_HEATING_TEMPZONE_THIN_CABLE) &&
    name.to_s.downcase.include?('tape')
end

#is_cerazorb?Boolean

Returns:

  • (Boolean)


1340
1341
1342
# File 'app/models/item.rb', line 1340

def is_cerazorb?
  primary_product_line&.is_cerazorb? && product_category&.is_insulation?
end

#is_circuit_check?Boolean

Returns:

  • (Boolean)


1332
1333
1334
# File 'app/models/item.rb', line 1332

def is_circuit_check?
  sku == ItemConstants::CIRCUIT_CHECK_SKU
end

#is_cold_lead?Boolean

Returns:

  • (Boolean)


1146
1147
1148
# File 'app/models/item.rb', line 1146

def is_cold_lead?
  pc_descendant_of_path?(LtreePaths::PC_SPARE_PARTS_COLD_LEADS)
end

#is_control?Object

Alias for Product_category#is_control?

Returns:

  • (Object)

    Product_category#is_control?

See Also:



649
650
651
652
653
654
655
656
657
658
# File 'app/models/item.rb', line 649

delegate :is_control?,
:is_upgrade?,
:is_accessory?,
:is_relay_panel?,
:is_custom_mat?,
:effective_nmfc_code,
:effective_nmfc_class,
:is_publication?,
to: :product_category,
allow_nil: true

#is_cork?Boolean

Returns:

  • (Boolean)


1336
1337
1338
# File 'app/models/item.rb', line 1336

def is_cork?
  primary_product_line&.is_cork? && product_category&.is_insulation?
end

#is_custom_mat?Object

Alias for Product_category#is_custom_mat?

Returns:

  • (Object)

    Product_category#is_custom_mat?

See Also:



649
650
651
652
653
654
655
656
657
658
# File 'app/models/item.rb', line 649

delegate :is_control?,
:is_upgrade?,
:is_accessory?,
:is_relay_panel?,
:is_custom_mat?,
:effective_nmfc_code,
:effective_nmfc_class,
:is_publication?,
to: :product_category,
allow_nil: true

#is_discontinued_or_publicationObject

Helper for validation: skips weight checks for discontinued items and publications



705
706
707
# File 'app/models/item.rb', line 705

def is_discontinued_or_publication
  is_discontinued? || is_publication?
end

#is_economy_snow_melt_control?Boolean

Returns:

  • (Boolean)


1356
1357
1358
# File 'app/models/item.rb', line 1356

def is_economy_snow_melt_control?
  sku == ItemConstants::ECONOMY_SNOW_MELT_CONTROL_SKU
end

#is_floor_heating_product?Boolean

Returns:

  • (Boolean)


1142
1143
1144
# File 'app/models/item.rb', line 1142

def is_floor_heating_product?
  is_heating_element? && pl_descendant_of_path?(LtreePaths::PL_FLOOR_HEATING)
end

#is_goods?Boolean

Returns:

  • (Boolean)


1274
1275
1276
# File 'app/models/item.rb', line 1274

def is_goods?
  tax_class == 'g'
end

#is_heating_element?Boolean

Returns:

  • (Boolean)


1316
1317
1318
# File 'app/models/item.rb', line 1316

def is_heating_element?
  pc_descendant_of_path?(LtreePaths::PC_HEATING_ELEMENTS)
end

#is_infrared_heating_panel?Boolean

Returns:

  • (Boolean)


1184
1185
1186
# File 'app/models/item.rb', line 1184

def is_infrared_heating_panel?
  pc_descendant_of_path?(LtreePaths::PC_INFRARED_HEATING_PANELS)
end

#is_infrared_heating_panel_dual_connect?Boolean

Returns:

  • (Boolean)


1196
1197
1198
# File 'app/models/item.rb', line 1196

def is_infrared_heating_panel_dual_connect?
  is_infrared_heating_panel? && spec_value(:connection_method) == 'Plug-in or Hardwired'
end

#is_infrared_heating_panel_hardwired?Boolean

Returns:

  • (Boolean)


1192
1193
1194
# File 'app/models/item.rb', line 1192

def is_infrared_heating_panel_hardwired?
  pc_descendant_of_path?(LtreePaths::PC_INFRARED_HEATING_PANELS_HARDWIRED)
end

#is_infrared_heating_panel_hardwired_control?Boolean

Returns:

  • (Boolean)


1204
1205
1206
# File 'app/models/item.rb', line 1204

def is_infrared_heating_panel_hardwired_control?
  sku.in?(ItemConstants::INFRARED_HEATING_PANELS_HARDWIRE_CONTROL_SKUS)
end

#is_infrared_heating_panel_plug_in?Boolean

Returns:

  • (Boolean)


1188
1189
1190
# File 'app/models/item.rb', line 1188

def is_infrared_heating_panel_plug_in?
  pc_descendant_of_path?(LtreePaths::PC_INFRARED_HEATING_PANELS_PLUG_IN)
end

#is_infrared_heating_panel_plug_in_control?Boolean

Returns:

  • (Boolean)


1200
1201
1202
# File 'app/models/item.rb', line 1200

def is_infrared_heating_panel_plug_in_control?
  sku.in?(ItemConstants::INFRARED_HEATING_PANELS_PLUG_IN_CONTROL_SKUS)
end

#is_install_kit?Boolean

Returns:

  • (Boolean)


1158
1159
1160
# File 'app/models/item.rb', line 1158

def is_install_kit?
  sku&.start_with?(ItemConstants::INSTALL_KIT_SKU_PREFIX)
end

#is_junction_box?Boolean

Returns:

  • (Boolean)


1360
1361
1362
# File 'app/models/item.rb', line 1360

def is_junction_box?
  ItemConstants::JUNCTION_BOX_SKUS.include?(sku)
end

#is_led_mirror?Boolean

Returns:

  • (Boolean)


1180
1181
1182
# File 'app/models/item.rb', line 1180

def is_led_mirror?
  is_mirror? && pl_descendant_of_path?(LtreePaths::PL_LED_MIRROR)
end

#is_legacy_relay?Boolean

Returns:

  • (Boolean)


1154
1155
1156
# File 'app/models/item.rb', line 1154

def is_legacy_relay?
  sku&.start_with?(ItemConstants::LEGACY_RELAY_SKU_PREFIX)
end

#is_made_to_order_led_mirror?Boolean

Returns:

  • (Boolean)


1410
1411
1412
1413
1414
# File 'app/models/item.rb', line 1410

def is_made_to_order_led_mirror?
  # here we only care if it shipped by Lumidesign, because some smaller component will just be added to the LED crate
  # rather than take up it's own pallet
  primary_product_line&.is_made_to_order_led_mirror? # && product_category&.is_mirror?
end

#is_membrane?Boolean

Returns:

  • (Boolean)


1402
1403
1404
# File 'app/models/item.rb', line 1402

def is_membrane?
  Item.sku_is_membrane?(sku)
end

#is_mirror?Boolean

Returns:

  • (Boolean)


1176
1177
1178
# File 'app/models/item.rb', line 1176

def is_mirror?
  pc_descendant_of_path?(LtreePaths::PC_MIRRORS)
end

#is_mirror_defogger?Boolean

Returns:

  • (Boolean)


1208
1209
1210
# File 'app/models/item.rb', line 1208

def is_mirror_defogger?
  pc_descendant_of_path?(LtreePaths::PC_MIRROR_DEFOGGERS)
end

#is_oj_programmable_tstat?Boolean

Returns:

  • (Boolean)


1294
1295
1296
# File 'app/models/item.rb', line 1294

def is_oj_programmable_tstat?
  is_thermostat? && LtreePaths::PL_OJ_PROGRAMMABLE_TSTAT_LINES.any? { |path| pl_descendant_of_path?(path) }
end

#is_oj_programmable_wifi_tstat?Boolean

Returns:

  • (Boolean)


1298
1299
1300
# File 'app/models/item.rb', line 1298

def is_oj_programmable_wifi_tstat?
  ItemConstants::OJ_PROGRAMMABLE_WIFI_TSTAT_SKU_PREFIXES.any? { |prefix| sku&.start_with?(prefix) }
end

#is_pipe_freeze_protection?Boolean

Returns:

  • (Boolean)


1242
1243
1244
# File 'app/models/item.rb', line 1242

def is_pipe_freeze_protection?
  is_heating_element? && pl_descendant_of_path?(LtreePaths::PL_PIPE_FREEZE_PROTECTION)
end

#is_power?Boolean

Returns:

  • (Boolean)


1270
1271
1272
# File 'app/models/item.rb', line 1270

def is_power?
  pc_descendant_of_path?(LtreePaths::PC_POWER)
end

#is_power_module?Boolean

Returns:

  • (Boolean)


1150
1151
1152
# File 'app/models/item.rb', line 1150

def is_power_module?
  ItemConstants::POWER_MODULE_SKU_PREFIXES.any? { |prefix| sku&.include?(prefix) }
end

#is_publication?Object

Alias for Product_category#is_publication?

Returns:

  • (Object)

    Product_category#is_publication?

See Also:



649
650
651
652
653
654
655
656
657
658
# File 'app/models/item.rb', line 649

delegate :is_control?,
:is_upgrade?,
:is_accessory?,
:is_relay_panel?,
:is_custom_mat?,
:effective_nmfc_code,
:effective_nmfc_class,
:is_publication?,
to: :product_category,
allow_nil: true

#is_radiator?Boolean

Returns:

  • (Boolean)


777
778
779
# File 'app/models/item.rb', line 777

def is_radiator?
  is_heating_element? || is_towel_warmer? || is_infrared_heating_panel? || is_mirror_defogger?
end

#is_refurbished?Boolean

Returns:

  • (Boolean)


1724
1725
1726
# File 'app/models/item.rb', line 1724

def is_refurbished?
  condition_refurbished?
end

#is_relay_panel?Object

Alias for Product_category#is_relay_panel?

Returns:

  • (Object)

    Product_category#is_relay_panel?

See Also:



649
650
651
652
653
654
655
656
657
658
# File 'app/models/item.rb', line 649

delegate :is_control?,
:is_upgrade?,
:is_accessory?,
:is_relay_panel?,
:is_custom_mat?,
:effective_nmfc_code,
:effective_nmfc_class,
:is_publication?,
to: :product_category,
allow_nil: true

#is_reviewable?Boolean

Returns:

  • (Boolean)


1446
1447
1448
# File 'app/models/item.rb', line 1446

def is_reviewable?
  Item.reviewable.where(id:).present?
end

#is_roughin_kit?Boolean

Returns:

  • (Boolean)


1406
1407
1408
# File 'app/models/item.rb', line 1406

def is_roughin_kit?
  sku&.include?(ItemConstants::ROUGHIN_KIT_SKU_PATTERN)
end

#is_self_regulating_cable?Boolean

Returns:

  • (Boolean)


1170
1171
1172
1173
1174
# File 'app/models/item.rb', line 1170

def is_self_regulating_cable?
  pc_descendant_of_path?(LtreePaths::PC_HEATING_ELEMENTS_HEATING_CABLES) &&
    (pl_descendant_of_path?(LtreePaths::PL_PIPE_FREEZE_PROTECTION_SELF_REG) ||
     pl_descendant_of_path?(LtreePaths::PL_ROOF_GUTTER_DEICING_SELF_REG))
end

#is_service?Boolean

Returns:

  • (Boolean)


1282
1283
1284
# File 'app/models/item.rb', line 1282

def is_service?
  tax_class == 'svc'
end

#is_sh_cable?Boolean

Returns:

  • (Boolean)


1381
1382
1383
# File 'app/models/item.rb', line 1381

def is_sh_cable?
  is_heating_element? && pl_descendant_of_path?(LtreePaths::PL_FLOOR_HEATING_SLAB_HEAT_CABLE)
end

#is_shipping?Boolean

Returns:

  • (Boolean)


1286
1287
1288
# File 'app/models/item.rb', line 1286

def is_shipping?
  tax_class == 'shp'
end

#is_slab_heat_mat?Boolean

Returns:

  • (Boolean)


1324
1325
1326
# File 'app/models/item.rb', line 1324

def is_slab_heat_mat?
  is_heating_element? && pl_descendant_of_path?(LtreePaths::PL_FLOOR_HEATING_SLAB_HEAT_MAT)
end

#is_sm_cable?Boolean

Returns:

  • (Boolean)


1377
1378
1379
# File 'app/models/item.rb', line 1377

def is_sm_cable?
  is_heating_element? && pl_descendant_of_path?(LtreePaths::PL_SNOW_MELTING_CABLE)
end

#is_smartstat?Boolean

Returns:

  • (Boolean)


1290
1291
1292
# File 'app/models/item.rb', line 1290

def is_smartstat?
  ItemConstants::IID_SMARTSTATS.include?(id)
end

#is_snow_melt_plaque?Boolean

Returns:

  • (Boolean)


1352
1353
1354
# File 'app/models/item.rb', line 1352

def is_snow_melt_plaque?
  sku == ItemConstants::SNOW_MELT_PLAQUE_SKU
end

#is_snow_melting_control?Boolean

Returns:

  • (Boolean)


1166
1167
1168
# File 'app/models/item.rb', line 1166

def is_snow_melting_control?
  pl_descendant_of_path?(LtreePaths::PL_SNOW_MELTING_CONTROL)
end

#is_snow_melting_mat?Boolean

Returns:

  • (Boolean)


1328
1329
1330
# File 'app/models/item.rb', line 1328

def is_snow_melting_mat?
  is_heating_element? && pl_descendant_of_path?(LtreePaths::PL_SNOW_MELTING_MAT)
end

#is_snow_melting_product?Boolean

Returns:

  • (Boolean)


1162
1163
1164
# File 'app/models/item.rb', line 1162

def is_snow_melting_product?
  pl_descendant_of_path?(LtreePaths::PL_SNOW_MELTING)
end

#is_spare_parts?Boolean

Returns:

  • (Boolean)


1278
1279
1280
# File 'app/models/item.rb', line 1278

def is_spare_parts?
  pc_descendant_of_path?(LtreePaths::PC_SPARE_PARTS)
end

#is_thermalsheet?Boolean

Returns:

  • (Boolean)


1344
1345
1346
# File 'app/models/item.rb', line 1344

def is_thermalsheet?
  primary_product_line&.is_thermalsheet? && product_category&.is_insulation?
end

#is_thermostat?Boolean

Returns:

  • (Boolean)


1266
1267
1268
# File 'app/models/item.rb', line 1266

def is_thermostat?
  pc_descendant_of_path?(LtreePaths::PC_CONTROLS_THERMOSTATS)
end

#is_towel_warmer?Boolean

Returns:

  • (Boolean)


1234
1235
1236
# File 'app/models/item.rb', line 1234

def is_towel_warmer?
  pc_descendant_of_path?(LtreePaths::PC_TOWEL_WARMERS)
end

#is_towel_warmer_dual_connect?Boolean

Returns:

  • (Boolean)


1246
1247
1248
1249
1250
1251
1252
# File 'app/models/item.rb', line 1246

def is_towel_warmer_dual_connect?
  pc_descendant_of_path?(LtreePaths::PC_TOWEL_WARMERS_DUAL_CONNECTION) ||
    (
      pc_descendant_of_path?(LtreePaths::PC_TOWEL_WARMERS) &&
      spec_value(:connection_method) == 'Plug-in or Hardwired'
    )
end

#is_towel_warmer_hardwired?Boolean

Returns:

  • (Boolean)


1238
1239
1240
# File 'app/models/item.rb', line 1238

def is_towel_warmer_hardwired?
  pc_descendant_of_path?(LtreePaths::PC_TOWEL_WARMERS_HARDWIRED)
end

#is_towel_warmer_hardwired_control?Boolean

Returns:

  • (Boolean)


1262
1263
1264
# File 'app/models/item.rb', line 1262

def is_towel_warmer_hardwired_control?
  sku.in?(ItemConstants::TOWEL_WARMER_HARDWIRE_CONTROL_SKUS)
end

#is_towel_warmer_plug_in?Boolean

Returns:

  • (Boolean)


1254
1255
1256
# File 'app/models/item.rb', line 1254

def is_towel_warmer_plug_in?
  pc_descendant_of_path?(LtreePaths::PC_TOWEL_WARMERS_PLUG_IN)
end

#is_towel_warmer_plug_in_control?Boolean

Returns:

  • (Boolean)


1258
1259
1260
# File 'app/models/item.rb', line 1258

def is_towel_warmer_plug_in_control?
  sku.in?(ItemConstants::TOWEL_WARMER_PLUG_IN_CONTROL_SKUS)
end

#is_tz_cable?Boolean

Returns:

  • (Boolean)


1364
1365
1366
1367
# File 'app/models/item.rb', line 1364

def is_tz_cable?
  is_heating_element? && (pl_descendant_of_path?(LtreePaths::PL_FLOOR_HEATING_TEMPZONE_CABLE) ||
                          pl_descendant_of_path?(LtreePaths::PL_FLOOR_HEATING_TEMPZONE_RULER_CABLE))
end

#is_tz_ruler_cable?Boolean

Returns:

  • (Boolean)


1373
1374
1375
# File 'app/models/item.rb', line 1373

def is_tz_ruler_cable?
  is_heating_element? && pl_descendant_of_path?(LtreePaths::PL_FLOOR_HEATING_TEMPZONE_RULER_CABLE)
end

#is_tz_thin_cable?Boolean

Returns:

  • (Boolean)


1369
1370
1371
# File 'app/models/item.rb', line 1369

def is_tz_thin_cable?
  is_heating_element? && pl_descendant_of_path?(LtreePaths::PL_FLOOR_HEATING_TEMPZONE_THIN_CABLE)
end

#is_underlayment?Boolean

Returns:

  • (Boolean)


1348
1349
1350
# File 'app/models/item.rb', line 1348

def is_underlayment?
  primary_product_line&.is_underlayment? && product_category&.is_insulation?
end

#is_upgrade?Object

Alias for Product_category#is_upgrade?

Returns:

  • (Object)

    Product_category#is_upgrade?

See Also:



649
650
651
652
653
654
655
656
657
658
# File 'app/models/item.rb', line 649

delegate :is_control?,
:is_upgrade?,
:is_accessory?,
:is_relay_panel?,
:is_custom_mat?,
:effective_nmfc_code,
:effective_nmfc_class,
:is_publication?,
to: :product_category,
allow_nil: true

#item_available_localesObject



822
823
824
# File 'app/models/item.rb', line 822

def item_available_locales
  LocaleUtility::SITE_LOCALES.select { |l| orderable_online_in_locale?(l) }
end

#item_group_name(context: :sales) ⇒ Object



2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
# File 'app/models/item.rb', line 2211

def item_group_name(context: :sales)
  pl_slug = case context
            when :sales
              sales_product_line_slug
            when :support
              support_product_line_slug
            else
              raise ArgumentError, "#{context} not supported, :sales or :support expected"
            end

  return if pl_slug.blank?
  return if (pc_slug = product_category_slug).blank?

  grouping_elements = [pl_slug.tr('.', '-').tr('_', '-').split('-'), pc_slug.split('-')].flatten
  grouping_elements.delete('goods')
  grouping_elements = grouping_elements.map(&:singularize)
  # Compress name a bit
  grouping_elements = grouping_elements.each_with_index.map { |el, i| i < 2 ? el.first(2) : el }
  grouping_elements = grouping_elements.compact.uniq
  gn = grouping_elements.join('-').parameterize
  if gn.size > 50
    "#{gn.first(15)}-#{Digest::MD5.hexdigest(gn)}"
  else
    gn
  end
end

#item_grouping_info(include_self: false, facet_filters: {}, context: nil) ⇒ Object



2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
# File 'app/models/item.rb', line 2238

def item_grouping_info(include_self: false, facet_filters: {}, context: nil)
  return if product_category.skip_siblings

  context ||= :sales
  # Use ltree paths for efficient queries (no DB lookup for path resolution)
  use_pl_path = case context
                when :sales
                  sales_product_line_path
                when :support
                  support_product_line_path
                else
                  raise ArgumentError, "#{context} not supported, :sales or :support expected"
                end

  use_pc_path = product_category_path

  if use_pl_path.present?
    # Use ltree path-based scopes (no additional DB lookups)
    variants = Item.active.condition_new.by_product_category_path(use_pc_path).by_product_line_path(use_pl_path).order(:sku)
    # eliminate customs
    variants = variants.joins(:product_category).where.not(ProductCategory[:custom_product].eq(true))
    group_name = item_group_name(context:)
    item_group_id = Digest::MD5.hexdigest(group_name)

    facet_filters.each do |facet, value|
      variants = variants.with_product_specification(facet, value)
    end
  else
    variants = Item.none
  end
  variants = variants.where.not(id: id.to_i) unless include_self
  # Keep legacy field names for backward compatibility but store paths
  result_class = Data.define(:pc_url, :pl_url, :item_group_id, :item_group_name, :variants) do
    def initialize(pc_url: nil, pl_url: nil, item_group_id: nil, item_group_name: nil, variants: nil) = super
  end
  result_class.new(pc_url: use_pc_path, pl_url: use_pl_path, item_group_id:, item_group_name:, variants:)
end

#item_product_linesActiveRecord::Relation<ItemProductLine>

Returns:

See Also:



279
# File 'app/models/item.rb', line 279

has_many :item_product_lines, -> { order(:position) }, inverse_of: :item, dependent: :destroy

#item_relationsActiveRecord::Relation<PublicationItem>

Returns:

See Also:



289
# File 'app/models/item.rb', line 289

has_many :item_relations, class_name: 'PublicationItem', foreign_key: :publication_id, inverse_of: :publication, dependent: :destroy

#item_stateObject



798
799
800
801
802
803
804
805
806
# File 'app/models/item.rb', line 798

def item_state
  if is_discontinued
    'Discontinued'
  elsif is_available_to_public?
    'Published'
  else
    'Private'
  end
end

#line_itemsActiveRecord::Relation<LineItem>

Returns:

See Also:



280
# File 'app/models/item.rb', line 280

has_many :line_items

#literatureLiterature

Returns:

See Also:



254
# File 'app/models/item.rb', line 254

belongs_to :literature, optional: true

#loaded_rendered_product_specificationsObject



2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
# File 'app/models/item.rb', line 2069

def loaded_rendered_product_specifications
  rp_specs = rendered_product_specifications || {}
  # Collect all product specification ids
  ps_ids = rp_specs.values.pluck('product_specification_id').uniq.compact
  # Preload the product specifications
  product_specifications = ProductSpecification.includes(:items, :product_line, :product_category).where(id: ps_ids).index_by(&:id)
  rp_specs.each_value do |v|
    v['product_specification'] = product_specifications[v['product_specification_id']]
  end
  rp_specs
end

#make_upc_codeObject



1520
1521
1522
# File 'app/models/item.rb', line 1520

def make_upc_code
  self.upc = Item::UpcMaker.new.process if upc.blank?
end

#missing_catalogsObject



2440
2441
2442
# File 'app/models/item.rb', line 2440

def missing_catalogs
  Catalog.where.not(id: catalog_items.pluck(:catalog_id)).where(store_id: store_items.pluck(:store_id)).where.not(is_discontinued: true).includes(:store).order(Catalog[:id].in([1, 2]).desc).order(Catalog[:name])
end

#most_recent_revision(public_only = true) ⇒ Object



1689
1690
1691
1692
1693
# File 'app/models/item.rb', line 1689

def most_recent_revision(public_only = true)
  items = Item.publications.where(Item[:sku].matches("#{canonical_sku}%")).where('length(sku) <= ?', canonical_sku.size + 2)
  items = items.available_to_public if public_only
  items.order(sku: :desc).first
end

#new_itemItem

Returns:

See Also:



263
# File 'app/models/item.rb', line 263

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

#new_revision_skuObject



1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
# File 'app/models/item.rb', line 1704

def new_revision_sku
  prev_revs = Item.where(Item[:sku].matches("#{canonical_sku}%")).order(sku: :desc)
  sku_match = prev_revs.first.sku.match(/(.*)-([[:alpha:]])$/)
  if sku_match && (sku_match.length == 3)
    # Get the revision code
    rev_code = sku_match[2]
    # Add one
    new_rev = rev_code.succ
    "#{canonical_sku}-#{new_rev}"
  elsif canonical_sku.present?
    "#{canonical_sku}-A"
  else
    raise "Last revision #{last_rev.id} #{last_rev.sku} could not be parsed for revision (-a, -b, etc.)"
  end
end

#non_refurbished_skuObject



1728
1729
1730
# File 'app/models/item.rb', line 1728

def non_refurbished_sku
  sku.delete_suffix('-BTK')
end

#order_countObject



1744
1745
1746
# File 'app/models/item.rb', line 1744

def order_count
  Order.contains_item_ids([id]).count
end

#orderable_online?Boolean

Returns:

  • (Boolean)


953
954
955
956
957
# File 'app/models/item.rb', line 953

def orderable_online?
  Item.orderable_online.exists?(id:) &&
    store_items.active.available.present? &&
    catalog_items.active.present?
end

#orderable_online_in_locale?(locale = I18n.locale) ⇒ Boolean

Returns:

  • (Boolean)


814
815
816
817
818
819
820
# File 'app/models/item.rb', line 814

def orderable_online_in_locale?(locale = I18n.locale)
  # leverage preloaded reloation if available
  scoped_records = orderable_view_product_catalogs if orderable_view_product_catalogs.loaded?
  # If not loaded, then retrieve only what we need
  scoped_records ||= orderable_view_product_catalogs.select(:catalog_id)
  scoped_records.any? { |vpc| vpc.catalog_id == Catalog.locale_to_catalog_id(locale) }
end

#orderable_view_product_catalogsActiveRecord::Relation<ViewProductCatalog>

Returns:

See Also:



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

has_many :orderable_view_product_catalogs,
-> { visible_to_public },
foreign_key: :item_id,
class_name: 'ViewProductCatalog'

#packaging_discrepancy_packingPacking

Returns:

See Also:



266
# File 'app/models/item.rb', line 266

belongs_to :packaging_discrepancy_packing, class_name: 'Packing', optional: true

#packingsActiveRecord::Relation<Packing>

Returns:

  • (ActiveRecord::Relation<Packing>)

See Also:



309
# File 'app/models/item.rb', line 309

has_and_belongs_to_many :packings, inverse_of: :items

#past_model?Boolean

Returns:

  • (Boolean)


1112
1113
1114
# File 'app/models/item.rb', line 1112

def past_model?
  (tags || []).include?('past-model') || is_discontinued? || primary_product_line&.past_model?
end

#pc_descendant_of_path?(path_slug) ⇒ Boolean

Check if item's product category is or descends from the given path
Uses pc_path_slugs stored directly on item (no DB query)
Example: item.pc_descendant_of_path?(LtreePaths::PC_TOWEL_WARMERS)

Returns:

  • (Boolean)


1226
1227
1228
1229
1230
1231
1232
# File 'app/models/item.rb', line 1226

def pc_descendant_of_path?(path_slug)
  return false if pc_path_slugs.blank? || path_slug.blank?

  slugs = pc_path_slugs.to_s
  path = path_slug.to_s
  slugs == path || slugs.start_with?("#{path}.")
end

#pl_descendant_of_path?(path_slug) ⇒ Boolean

Check if item's primary product line is or descends from the given path
Uses ltree path stored directly on item (no DB query)
Example: item.pl_descendant_of_path?(LtreePaths::PL_FLOOR_HEATING)

Returns:

  • (Boolean)


1215
1216
1217
1218
1219
1220
1221
# File 'app/models/item.rb', line 1215

def pl_descendant_of_path?(path_slug)
  return false if primary_pl_path_slugs.blank? || path_slug.blank?

  slugs = primary_pl_path_slugs.to_s
  path = path_slug.to_s
  slugs == path || slugs.start_with?("#{path}.")
end

#plan_sensitive?Boolean

Plan sensitive items are those tied up to the geometry of an installation plan

Returns:

  • (Boolean)


1108
1109
1110
# File 'app/models/item.rb', line 1108

def plan_sensitive?
  is_heating_element?
end

#preferred_supplierSupplier

Returns:

See Also:



260
# File 'app/models/item.rb', line 260

belongs_to :preferred_supplier, class_name: 'Supplier', optional: true

#primary_imageImage

Returns:

See Also:



256
# File 'app/models/item.rb', line 256

belongs_to :primary_image, class_name: 'Image', optional: true

#primary_image_url(options = {}) ⇒ Object

Returns the primary image URL for display (e.g., cart toast).
Uses WYS_MAIN profile first, falls back to primary_image association.



1136
1137
1138
1139
1140
# File 'app/models/item.rb', line 1136

def primary_image_url(options = {})
  wys_main = image_profiles.find { |p| p.image_type == 'WYS_MAIN' }
  image = wys_main&.image || primary_image
  image&.image_url(options)
end

#primary_product_lineProductLine



258
# File 'app/models/item.rb', line 258

belongs_to :primary_product_line, class_name: 'ProductLine', inverse_of: :primary_items, optional: true

#primary_product_line_slug_ltreeObject



1306
1307
1308
# File 'app/models/item.rb', line 1306

def primary_product_line_slug_ltree
  primary_product_line&.slug_ltree&.to_s
end

#product_categoryProductCategory



251
# File 'app/models/item.rb', line 251

belongs_to :product_category

#product_category_ancestry_idsObject



1736
1737
1738
# File 'app/models/item.rb', line 1736

def product_category_ancestry_ids
  ProductCategory.self_and_ancestors_ids(product_category_id)
end

#product_category_nameObject



895
896
897
# File 'app/models/item.rb', line 895

def product_category_name
  ProductCategoryConstants.name_for(product_category_id)
end

#product_category_pathObject

Returns ltree path for product category grouping (preferred - no DB lookup needed)



2175
2176
2177
2178
2179
2180
2181
2182
2183
# File 'app/models/item.rb', line 2175

def product_category_path
  pc_path = pc_path_slugs.to_s
  # Normalize to parent category for grouping purposes
  return LtreePaths::PC_INFRARED_HEATING_PANELS if pc_path.starts_with?('goods.infrared_heating_panels')
  return LtreePaths::PC_TOWEL_WARMERS if pc_path.starts_with?('goods.towel_warmers') && !pc_path.starts_with?('goods.towel_warmers.dual_connection')
  return 'goods.towel_warmers.dual_connection' if pc_path.starts_with?('goods.towel_warmers.dual_connection')

  pc_path
end

#product_category_priorityObject



899
900
901
# File 'app/models/item.rb', line 899

def product_category_priority
  ProductCategoryConstants.priority_for(product_category_id)
end

#product_category_slugObject

Returns product category URL for grouping (legacy - use product_category_path for new code)



2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
# File 'app/models/item.rb', line 2163

def product_category_slug
  use_pc_url = product_category_url
  use_pc_url = 'goods-infrared-heating-panels' if use_pc_url.starts_with?('goods-infrared-heating-panels')
  if use_pc_url.starts_with?('goods-towel-warmers-dual-connection')
    use_pc_url = 'goods-towel-warmers-dual-connection'
  elsif use_pc_url.starts_with?('goods-towel-warmers')
    use_pc_url = 'goods-towel-warmers'
  end
  use_pc_url
end

#product_category_urlObject



1311
1312
1313
# File 'app/models/item.rb', line 1311

def product_category_url
  product_category&.url
end

#product_line_ancestry_idsObject



1732
1733
1734
# File 'app/models/item.rb', line 1732

def product_line_ancestry_ids
  ProductLine.self_and_ancestors_ids(product_line_ids)
end

#product_line_for_heating_systemObject



1462
1463
1464
1465
1466
1467
1468
# File 'app/models/item.rb', line 1462

def product_line_for_heating_system
  (begin
    primary_product_line.get_first_heating_system_type
  rescue StandardError
    nil
  end)
end

#product_line_for_public_sales_portalObject



1454
1455
1456
# File 'app/models/item.rb', line 1454

def product_line_for_public_sales_portal
  primary_product_line&.get_first_public_for_sales
end

#product_line_for_reviewObject



1450
1451
1452
# File 'app/models/item.rb', line 1450

def product_line_for_review
  primary_product_line&.get_first_reviewable
end

#product_line_ids_constellationObject

This pulls all product line id including inherited for this item, for the root system,
and complimentary_product_line_ids



789
790
791
792
793
794
795
796
# File 'app/models/item.rb', line 789

def product_line_ids_constellation
  pl_ids = []
  pl_ids += product_line_ids || [] # This will implicitly include the root system which is always an ancestor
  pl_ids += root_system_product_line&.complimentary_product_line_ids || [] # The complimentaries of the root system
  pl_ids += primary_product_line&.complimentary_product_line_ids || [] # The complimentaries of only this product line
  pl_ids = pl_ids.compact.uniq
  ProductLine.self_and_ancestors_ids(pl_ids).compact.uniq
end

#product_linesActiveRecord::Relation<ProductLine>

Returns:

See Also:



281
# File 'app/models/item.rb', line 281

has_many :product_lines, through: :item_product_lines

#product_specifications(filters = {}) ⇒ Object

Retrieves all product specifications available to a given item, this does not
retrieve rendered specs but the specs definition used to build them



1801
1802
1803
1804
1805
# File 'app/models/item.rb', line 1801

def product_specifications(filters = {})
  filters[:propagation] ||= %w[unrestricted item]
  res = Item::SpecificationsRetriever.new.process(self, filters)
  res.specifications
end

#product_tax_codeProductTaxCode



261
# File 'app/models/item.rb', line 261

belongs_to :product_tax_code, inverse_of: :items, optional: true

#prune_empty_specsObject



2135
2136
2137
2138
2139
2140
# File 'app/models/item.rb', line 2135

def prune_empty_specs
  ds = direct_product_specifications.empty_non_template_specs
  ds_count = ds.size
  ds.destroy_all
  ds_count
end

#prune_unused_translationsObject



2480
2481
2482
2483
2484
2485
# File 'app/models/item.rb', line 2480

def prune_unused_translations
  valid_locales = content_locales_to_render
  translations.each do |(locale, _translation_attribute)|
    translations.delete(locale) unless locale.to_sym.in?(valid_locales)
  end
end

#public_description_htmlObject

-- Content is trusted, generated from Liquid templates



1781
1782
1783
1784
# File 'app/models/item.rb', line 1781

def public_description_html
  (rendered_detailed_description_html.presence || primary_product_line&.description_html.presence)&.html_safe
  # rubocop:enable Rails/OutputSafety
end

#public_description_textObject



1786
1787
1788
# File 'app/models/item.rb', line 1786

def public_description_text
  detailed_description.presence || primary_product_line&.description.presence
end

#public_file_nameObject



907
908
909
# File 'app/models/item.rb', line 907

def public_file_name
  "#{public_name}.#{Mime::Type.file_extension_of(literature.mime_type) || 'pdf'}"
end

#public_nameObject



903
904
905
# File 'app/models/item.rb', line 903

def public_name
  public_short_name.presence || name
end

#public_specificationsObject

Only specifications intended for the public are retrieved



1898
1899
1900
# File 'app/models/item.rb', line 1898

def public_specifications
  specifications.select { |s| s.output.present? && s.grouping != 'Features' && s.grouping != 'Highlights' && s.visibility == 'open_visibility' }
end

#publication_relationsActiveRecord::Relation<PublicationItem>

Returns:

See Also:



288
# File 'app/models/item.rb', line 288

has_many :publication_relations, class_name: 'PublicationItem', inverse_of: :item, dependent: :destroy

#publish_shipping_dimensions_changed_event(options = {}) ⇒ Object

Publishes Events::ItemShippingDimensionsChanged when shipping-box dims
actually changed on this save. The subscribed async handler
(Shipping::ItemPackingRefreshHandler) runs ItemMd5Extractor in a
background job so the foreground save isn't blocked on Packing.upsert
plus the HABTM item_ids write.

Gate is any_box_changed? only — the broader "no packing exists yet /
existing packing is invalid" conditions from the old inline path are
re-checked inside ItemMd5Extractor on the handler side, so we don't
need a DB hit here just to decide whether to publish.

Replaces the former synchronous set_packaged_items_md5_hash callback.



2312
2313
2314
2315
2316
2317
2318
2319
# File 'app/models/item.rb', line 2312

def publish_shipping_dimensions_changed_event(options = {})
  return unless any_box_changed? || options[:force]

  Rails.configuration.event_store.publish(
    Events::ItemShippingDimensionsChanged.new(data: { item_id: id }),
    stream_name: "Item-#{id}"
  )
end

#purchase_order_itemsActiveRecord::Relation<PurchaseOrderItem>

Returns:

See Also:



299
# File 'app/models/item.rb', line 299

has_many :purchase_order_items

#purge_edge_cache(include_product_line: false) ⇒ Object



1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
# File 'app/models/item.rb', line 1978

def purge_edge_cache(include_product_line: false)
  # This doesn't exist unless staging or production
  return :disabled unless Cache::EdgeCacheUtility.edge_cache_enabled?

  res = []
  urls = edge_cache_urls
  if urls.present?
    Rails.logger.info "Purge Edge Cache called for item id #{id}"
    jid = EdgeCacheWorker.perform_async('urls' => urls)
    res += urls.map { |url| { url:, jid: } }
  end
  if include_product_line && (pl = primary_product_line)
    res += pl.purge_edge_cache
  end
  res
end

#quantities_indicator_visible?Boolean

Returns:

  • (Boolean)


1470
1471
1472
# File 'app/models/item.rb', line 1470

def quantities_indicator_visible?
  display_treatment.nil? || display_treatment != Item::DISPLAY_TREATMENT_HIDE_QUANTITIES
end

#refresh_google_feedObject



2048
2049
2050
2051
2052
2053
2054
# File 'app/models/item.rb', line 2048

def refresh_google_feed
  # Filtering will happen downstream for what is google feed or not
  return if (cids = catalog_items.for_google_feed.ids).blank?

  GoogleFeedGeneratorWorker.perform_async('catalog_item_ids' => catalog_items.for_google_feed.ids)
  cids
end

#refurbished_versionItem

Returns:

See Also:



268
# File 'app/models/item.rb', line 268

has_one :refurbished_version, class_name: 'Item', foreign_key: :new_item_id

Returns:

  • (ActiveRecord::Relation<RelatedSourceItem>)

See Also:



295
# File 'app/models/item.rb', line 295

has_many :related_source_items, through: :source_item_relations, source: :source_item

Returns:

  • (ActiveRecord::Relation<RelatedTargetItem>)

See Also:



296
# File 'app/models/item.rb', line 296

has_many :related_target_items, through: :target_item_relations, source: :target_item

Returns:

  • (ActiveRecord::Relation<RelatedTargetItemsForSpec>)

See Also:



297
# File 'app/models/item.rb', line 297

has_many :related_target_items_for_specs, through: :target_item_relations_for_specs, source: :target_item

#relativesObject

Find all direct siblings and relatives items descendending the product category and product line tree



2147
2148
2149
2150
2151
2152
2153
2154
2155
# File 'app/models/item.rb', line 2147

def relatives
  return Item.none unless persisted? && (product_category_id || primary_product_line_id)

  siblings = Item.all
  siblings = siblings.by_product_category_id(product_category_id) if product_category
  siblings = siblings.where(Item[:primary_pl_path_ids].ltree_descendant(primary_pl_path_ids)) if primary_pl_path_ids.present?
  siblings = siblings.where.not(id:) if id
  siblings.limit(100).order(:sku)
end

#render_product_specifications(tokens: nil) ⇒ Object



1942
1943
1944
1945
1946
1947
1948
1949
# File 'app/models/item.rb', line 1942

def render_product_specifications(tokens: nil)
  res = Item::SpecificationsMasher.new.process(item: self, tokens:).specifications
  if tokens.present? # We will merge the results
    (rendered_product_specifications || {}).merge(res)
  else
    res
  end
end

#rendered_detailed_description_htmlObject



1776
1777
1778
# File 'app/models/item.rb', line 1776

def rendered_detailed_description_html
  @rendered_detailed_description_html ||= (detailed_description_html.present? ? detailed_description_html_template_instance.render(token_specs_values_for_liquid(include_legacy: true)) : nil)
end

#rendered_product_specifications_grouped(locale: Mobility.locale, filter_grouping: nil, sort_order: []) ⇒ Object



2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
# File 'app/models/item.rb', line 2081

def rendered_product_specifications_grouped(locale: Mobility.locale, filter_grouping: nil, sort_order: [])
  Mobility.with_locale(locale) do
    filtered = loaded_rendered_product_specifications
               .select { |_k, v| filter_grouping.nil? || v['grouping'] == filter_grouping }

    sorted = filtered.sort_by do |k, v|
      [
        v['grouping'] || nil, # Sort nil as last
        sort_order.index(k.to_sym) || Float::INFINITY, # Sort by sort_order if present
        v['name'] || nil # Sort nil as last
      ]
    end

    sorted.group_by { |_k, v| v['grouping'] || 'Other' }
  end
end

#replacement_forItem

Returns:

See Also:



257
# File 'app/models/item.rb', line 257

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

#requires_distributor_id_code?Boolean

Returns:

  • (Boolean)


1302
1303
1304
# File 'app/models/item.rb', line 1302

def requires_distributor_id_code?
  sku&.start_with?('UWG4-4999', 'AWG4-4999') && !sku&.include?('-WY')
end

#retrieve_faqsObject



678
679
680
# File 'app/models/item.rb', line 678

def retrieve_faqs
  Item::ArticleRetriever.new(article_types: %w[faq]).process(item: self).articles
end

#returned_rma_itemsActiveRecord::Relation<Rma>

Returns:

  • (ActiveRecord::Relation<Rma>)

See Also:



282
# File 'app/models/item.rb', line 282

has_many :returned_rma_items, class_name: 'Rma', foreign_key: :returned_item_id

#revised_publication_for_publicObject



980
981
982
983
# File 'app/models/item.rb', line 980

def revised_publication_for_public
  revision = most_recent_revision(:public)
  revision == self ? nil : revision
end

#rma_reason_codes(include_advanced: true) ⇒ Object



911
912
913
914
915
916
917
# File 'app/models/item.rb', line 911

def rma_reason_codes(include_advanced: true)
  rrcs = RmaReasonCode.active
  rrcs = rrcs.for_product_category_ids(product_category_id).for_product_line_ids(primary_product_line_id) if product_category_id && primary_product_line_id
  # If we're not requesting advanced, then you only get the basic reason codes
  rrcs = rrcs.where(advanced_reason_code: false) unless include_advanced
  rrcs
end

#root_system_product_lineObject

Alias for Primary_product_line#root_system_product_line

Returns:

  • (Object)

    Primary_product_line#root_system_product_line

See Also:



660
661
662
# File 'app/models/item.rb', line 660

delegate :root_system_product_line,
to: :primary_product_line,
allow_nil: true

#run_shipping_audit(carrier: nil) ⇒ Array<Shipping::PackageAuditor>

Run shipping audit for all boxes, optionally for a specific carrier

Parameters:

  • carrier (String, nil) (defaults to: nil)

    Optional carrier name (e.g., 'UPS', 'FedEx', 'Purolator', 'Canpar')

Returns:



1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
# File 'app/models/item.rb', line 1658

def run_shipping_audit(carrier: nil)
  carriers = carrier.present? ? [carrier] : nil
  r = []
  r1 = Shipping::PackageAuditor.new
  r1.process(shipping_dimensions_to_package, carriers:)
  r2 = r3 = nil
  if box2_defined?
    r2 = Shipping::PackageAuditor.new
    r2.process(box2_shipping_dimensions_to_package, carriers:)
  end
  if box3_defined?
    r3 = Shipping::PackageAuditor.new
    r3.process(box3_shipping_dimensions_to_package, carriers:)
  end
  r << r1
  r << r2 if r2
  r << r3 if r3
  r
end

#safe_nameObject

Returns a name suitable for edit feed with any special character that will
destroy a csv formatting



836
837
838
839
840
841
842
# File 'app/models/item.rb', line 836

def safe_name
  return if name.blank?

  name.gsub("'", 'ft')
      .gsub('"', 'in')
      .gsub(%r{[^0-9A-Za-z.-/]}, ' ').squish
end

#sales_product_line_pathObject

Returns ltree path for sales product line (preferred - reuses already-loaded ProductLine)



2192
2193
2194
2195
2196
# File 'app/models/item.rb', line 2192

def sales_product_line_path
  pl_path = primary_product_line&.get_first_show_in_sales_portal_ancestor_path
  pl_path ||= product_lines.map(&:get_first_show_in_sales_portal_ancestor_path).first
  pl_path
end

#sales_product_line_slugObject



2185
2186
2187
2188
2189
# File 'app/models/item.rb', line 2185

def sales_product_line_slug
  slt = primary_product_line&.get_first_show_in_sales_portal_ancestor_slug_ltree
  slt ||= product_lines.map(&:get_first_show_in_sales_portal_ancestor_slug_ltree).first
  slt
end

#secondary_product_categoryProductCategory



259
# File 'app/models/item.rb', line 259

belongs_to :secondary_product_category, class_name: 'ProductCategory', optional: true

#selection_nameObject



1754
1755
1756
# File 'app/models/item.rb', line 1754

def selection_name
  "#{sku} - #{name}#{' [Discontinued]' if is_discontinued?}"
end

#serial_numbersActiveRecord::Relation<SerialNumber>

Returns:

See Also:



294
# File 'app/models/item.rb', line 294

has_many :serial_numbers, through: :store_items

#set_grouping(force: false) ⇒ Object

Records a grouping string identifier, this speeds up variant grouping lookup for
google and reviews.io api calls.



2323
2324
2325
2326
2327
2328
2329
# File 'app/models/item.rb', line 2323

def set_grouping(force: false)
  return unless is_goods? || is_service?
  return if is_publication?
  return unless force || product_category_id_changed? || primary_product_line_id_changed?

  self.grouping = item_group_name
end

#shift_images(locale: 'en', prefix: nil) ⇒ Object

Shifts images up to fill empty slots in the image type order for this item
This method will move images to fill gaps in the sequence, maintaining the proper order

Parameters:

  • locale (String) (defaults to: 'en')

    The locale for the images (default: 'en')

  • prefix (String, nil) (defaults to: nil)

    Optional prefix to limit shifting to a specific group (e.g., 'AMZ', 'WAL')



2505
2506
2507
# File 'app/models/item.rb', line 2505

def shift_images(locale: 'en', prefix: nil)
  Image::ImageShiftingService.shift_images_for_item(id, locale: locale, prefix: prefix)
end

#shipping_dimensions(unit: 'in', precision: 0) ⇒ Object

unit_system SI = Standard International, e.g Metric
ucs = United States Customary Unit



1538
1539
1540
1541
1542
1543
1544
1545
1546
# File 'app/models/item.rb', line 1538

def shipping_dimensions(unit: 'in', precision: 0)
  return unless shipping_length && shipping_width && shipping_height

  [
    shipping_length_converted(unit:, precision:),
    shipping_width_converted(unit:, precision:),
    shipping_height_converted(unit:, precision:)
  ]
end

#shipping_dimensions_displayObject



1079
1080
1081
# File 'app/models/item.rb', line 1079

def shipping_dimensions_display
  "#{shipping_length}″ (L) x #{shipping_width}″ (W) x #{shipping_height}″ (H)"
end

#shipping_dimensions_to_packageObject



1634
1635
1636
1637
1638
1639
# File 'app/models/item.rb', line 1634

def shipping_dimensions_to_package
  return unless box1_defined?

  Shipping::Container.new(length: shipping_length, width: shipping_width, height: shipping_height, weight: shipping_weight,
                          container_type:)
end

#shipping_height_converted(unit: 'in', precision: 0) ⇒ Object



1560
1561
1562
1563
1564
# File 'app/models/item.rb', line 1560

def shipping_height_converted(unit: 'in', precision: 0)
  return unless shipping_height

  RubyUnits::Unit.new("#{shipping_height} in").convert_to(unit.to_s).scalar.to_f.round(precision)
end

#shipping_length_converted(unit: 'in', precision: 0) ⇒ Object



1548
1549
1550
1551
1552
# File 'app/models/item.rb', line 1548

def shipping_length_converted(unit: 'in', precision: 0)
  return unless shipping_length

  RubyUnits::Unit.new("#{shipping_length} in").convert_to(unit.to_s).scalar.to_f.round(precision)
end

#shipping_optionsActiveRecord::Relation<ShippingOption>

Returns:

See Also:



283
# File 'app/models/item.rb', line 283

has_many :shipping_options

#shipping_volumeObject



1052
1053
1054
1055
1056
# File 'app/models/item.rb', line 1052

def shipping_volume
  boxes.sum { |b| b[0] * b[1] * b[2] }
rescue StandardError
  0
end

#shipping_volume_in_cubic_feetObject



1058
1059
1060
1061
1062
# File 'app/models/item.rb', line 1058

def shipping_volume_in_cubic_feet
  res = nil
  res = shipping_volume / 1728.0 if shipping_volume.present? && shipping_volume.positive?
  res
end

#shipping_weight_converted(unit: 'lbs', precision: nil) ⇒ Object



1622
1623
1624
# File 'app/models/item.rb', line 1622

def shipping_weight_converted(unit: 'lbs', precision: nil)
  express_weight_converted shipping_weight, unit:, precision:
end

#shipping_width_converted(unit: 'in', precision: 0) ⇒ Object



1554
1555
1556
1557
1558
# File 'app/models/item.rb', line 1554

def shipping_width_converted(unit: 'in', precision: 0)
  return unless shipping_width

  RubyUnits::Unit.new("#{shipping_width} in").convert_to(unit.to_s).scalar.to_f.round(precision)
end

#ships_in_single_box?Boolean

Returns:

  • (Boolean)


2444
2445
2446
# File 'app/models/item.rb', line 2444

def ships_in_single_box?
  !(box2_defined? || box3_defined?)
end

#ships_via_crate?Boolean

Returns:

  • (Boolean)


2364
2365
2366
# File 'app/models/item.rb', line 2364

def ships_via_crate?
  is_made_to_order_led_mirror?
end

#ships_via_freight?Boolean

Returns:

  • (Boolean)


1420
1421
1422
1423
# File 'app/models/item.rb', line 1420

def ships_via_freight?
  # adding current pallet/freight shipping rules
  freight_service?
end

#siblingsObject



939
940
941
942
943
# File 'app/models/item.rb', line 939

def siblings
  return Item.none unless primary_product_line_id

  Item.by_product_line_id(primary_product_line_id).where.not(id:)
end

#single_item_packings_from_md5Object

Finds packings of this item but only for a single quantity



2356
2357
2358
2359
2360
2361
2362
# File 'app/models/item.rb', line 2356

def single_item_packings_from_md5
  if (md5 = Shipping::Md5HashItem.process(self).md5)
    packings.where(md5:)
  else
    Packing.none
  end
end

#site_mapsActiveRecord::Relation<SiteMap>

Returns:

  • (ActiveRecord::Relation<SiteMap>)

See Also:



292
# File 'app/models/item.rb', line 292

has_many :site_maps, as: :resource, dependent: :destroy

#sku_and_nameObject



1103
1104
1105
# File 'app/models/item.rb', line 1103

def sku_and_name
  "#{sku} - #{name}"
end

#source_item_relationsActiveRecord::Relation<ItemRelation>

Returns:

See Also:



284
# File 'app/models/item.rb', line 284

has_many :source_item_relations, class_name: 'ItemRelation', foreign_key: :target_item_id, dependent: :destroy

#specific_itemsActiveRecord::Relation<Item>

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



291
# File 'app/models/item.rb', line 291

has_many :specific_items, through: :item_relations, class_name: 'Item', source: :item

#specific_publicationsActiveRecord::Relation<Item>

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



290
# File 'app/models/item.rb', line 290

has_many :specific_publications, through: :publication_relations, class_name: 'Item', source: :publication

#specificationsObject

Retrieves rendered specifications as an object



1889
1890
1891
# File 'app/models/item.rb', line 1889

def specifications
  rendered_product_specifications&.values&.map { |s| OpenStruct.new(s) } || []
end

#specifications_for_item_labelObject



1902
1903
1904
# File 'app/models/item.rb', line 1902

def specifications_for_item_label
  specifications.select { |rps| rps.print_on_item_label && rps.output.present? }.sort_by(&:name)
end

#specifications_groupedObject



1893
1894
1895
# File 'app/models/item.rb', line 1893

def specifications_grouped
  specifications.sort_by { |s| [s.grouping, s.name] }.group_by(&:grouping)
end

#star?Boolean

Makes a star appear in the document library

Returns:

  • (Boolean)


845
846
847
# File 'app/models/item.rb', line 845

def star?
  (tags || []).intersect?(%w[Star star])
end

#store_itemsActiveRecord::Relation<StoreItem>

Returns:

See Also:



270
# File 'app/models/item.rb', line 270

has_many :store_items, inverse_of: :item, dependent: :destroy

#successor_itemItem

Returns:

See Also:



262
# File 'app/models/item.rb', line 262

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

#suggested_item_applies_toObject

Alias for Suggested_item_tool#suggested_item_applies_to

Returns:

  • (Object)

    Suggested_item_tool#suggested_item_applies_to

See Also:



664
665
666
# File 'app/models/item.rb', line 664

delegate :suggested_item_stock_threshold,
:suggested_item_applies_to,
to: :suggested_item_tool

#suggested_item_stock_thresholdObject

Alias for Suggested_item_tool#suggested_item_stock_threshold

Returns:

  • (Object)

    Suggested_item_tool#suggested_item_stock_threshold

See Also:



664
665
666
# File 'app/models/item.rb', line 664

delegate :suggested_item_stock_threshold,
:suggested_item_applies_to,
to: :suggested_item_tool

#superceded_itemsActiveRecord::Relation<Item>

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



273
# File 'app/models/item.rb', line 273

has_many :superceded_items, class_name: 'Item', foreign_key: :successor_item_id

#supplier_itemSupplierItem



252
# File 'app/models/item.rb', line 252

belongs_to :supplier_item, inverse_of: :items, optional: true

#supplier_itemsActiveRecord::Relation<SupplierItem>

Returns:

See Also:



285
# File 'app/models/item.rb', line 285

has_many :supplier_items, inverse_of: :item, dependent: :destroy

#supplier_skusObject



826
827
828
# File 'app/models/item.rb', line 826

def supplier_skus
  ([secondary_model_number] + supplier_items.active.distinct.pluck(:supplier_sku)).filter_map(&:presence).uniq
end

#support_product_line_pathObject

Returns ltree path for support product line (preferred - reuses already-loaded ProductLine)



2205
2206
2207
2208
2209
# File 'app/models/item.rb', line 2205

def support_product_line_path
  pl_path = primary_product_line&.get_first_show_in_support_portal_ancestor_path
  pl_path ||= product_lines.map(&:get_first_show_in_support_portal_ancestor_path).first
  pl_path
end

#support_product_line_slugObject



2198
2199
2200
2201
2202
# File 'app/models/item.rb', line 2198

def support_product_line_slug
  slt = primary_product_line&.get_first_show_in_support_portal_ancestor_slug_ltree
  slt ||= product_lines.map(&:get_first_show_in_support_portal_ancestor_slug_ltree).first
  slt
end

#tags_displayObject



674
675
676
# File 'app/models/item.rb', line 674

def tags_display
  tags.join(', ')
end

#target_item_relationsActiveRecord::Relation<ItemRelation>

Returns:

See Also:



286
# File 'app/models/item.rb', line 286

has_many :target_item_relations, class_name: 'ItemRelation', foreign_key: :source_item_id, dependent: :destroy

#target_item_relations_for_specsActiveRecord::Relation<ItemRelation>

Returns:

See Also:



287
# File 'app/models/item.rb', line 287

has_many :target_item_relations_for_specs, -> { where(include_in_spec: true) }, class_name: 'ItemRelation', foreign_key: :source_item_id

#third_party_skus(limit: 1) ⇒ Object



830
831
832
# File 'app/models/item.rb', line 830

def third_party_skus(limit: 1)
  (catalog_items.where.not(third_party_sku: nil).where.not(CatalogItem[:third_party_sku].matches(sku)).pluck(:third_party_sku) - [sku]).filter_map(&:presence).uniq.sort.first(limit)
end

#thumbnail_url(size: '30x30') ⇒ Object



1130
1131
1132
# File 'app/models/item.rb', line 1130

def thumbnail_url(size: '30x30')
  image_url2(size:, thumbnail: true)
end

#to_liquidObject



2497
2498
2499
# File 'app/models/item.rb', line 2497

def to_liquid
  Liquid::ItemDrop.new self
end

#to_packdimsObject



2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
# File 'app/models/item.rb', line 2331

def to_packdims
  # Organize our packdims, ceiling with decimal precision of 1, also min of 0.1, and sort to make sure that lengthxwidthxheight are in descending order for convention
  sle, swi, she = [[shipping_length.ceil(1), 0.1].max, [shipping_width.ceil(1), 0.1].max, [shipping_height.ceil(1), 0.1].max].sort.reverse
  # Weight gets an extra decimal precision
  swg = [shipping_weight.ceil(2), 0.01].max
  # we store an array of decimal 7,2.  To prevent 0 which would cause a validation issue, the smallest packable weight will be 0.01 lbs (the weight of a standard envelope)
  packdims = [[sle, swi, she, swg, Shipment::PACKDIM_CONTAINER_INTS[container_type]]]
  if box2_defined?
    box2_sle, box2_swi, box2_she = [[box2_shipping_length.ceil(1), 0.1].max, [box2_shipping_width.ceil(1), 0.1].max, [box2_shipping_height.ceil(1), 0.1].max].sort.reverse
    # Weight gets an extra decimal precision
    box2_swg = [box2_shipping_weight.ceil(2), 0.01].max
    # we store an array of decimal 7,2.  To prevent 0 which would cause a validation issue, the smallest packable weight will be 0.01 lbs (the weight of a standard envelope)
    packdims << [box2_sle, box2_swi, box2_she, box2_swg, Shipment::PACKDIM_CONTAINER_INTS[container_type]]
  end
  if box3_defined?
    box3_sle, box3_swi, box3_she = [[box3_shipping_length.ceil(1), 0.1].max, [box3_shipping_width.ceil(1), 0.1].max, [box3_shipping_height.ceil(1), 0.1].max].sort.reverse
    # Weight gets an extra decimal precision
    box3_swg = [box3_shipping_weight.ceil(2), 0.01].max
    # we store an array of decimal 7,2.  To prevent 0 which would cause a validation issue, the smallest packable weight will be 0.01 lbs (the weight of a standard envelope)
    packdims << [box3_sle, box3_swi, box3_she, box3_swg, Shipment::PACKDIM_CONTAINER_INTS[container_type]]
  end
  packdims
end

#to_sObject



1748
1749
1750
1751
1752
# File 'app/models/item.rb', line 1748

def to_s
  s = "#{sku} - #{name}"
  s += ' (Discontinued)' if is_discontinued?
  s
end

#total_shipping_weightObject



2487
2488
2489
# File 'app/models/item.rb', line 2487

def total_shipping_weight
  boxes.sum { |b| b[3].to_f }
end

#total_shipping_weight_converted(unit: 'lbs', precision: nil) ⇒ Object



1618
1619
1620
# File 'app/models/item.rb', line 1618

def total_shipping_weight_converted(unit: 'lbs', precision: nil)
  express_weight_converted total_shipping_weight, unit:, precision:
end

#total_shipping_weight_of_kit_componentsObject



2491
2492
2493
2494
2495
# File 'app/models/item.rb', line 2491

def total_shipping_weight_of_kit_components
  return total_shipping_weight unless is_kit?

  kit_target_item_relations.includes(:target_item).sum { |tir| tir.quantity * tir.target_item.total_shipping_weight }
end

#translation_key_resourcesActiveRecord::Relation<TranslationKeyResource>

Returns:

See Also:



300
# File 'app/models/item.rb', line 300

has_many :translation_key_resources, as: :resource, dependent: :destroy

#update_boxes_from_packages(packages) ⇒ Object



1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
# File 'app/models/item.rb', line 1021

def update_boxes_from_packages(packages)
  # use packages to populate and/or reset boxes
  self.shipping_length = packages[0].try(:length)
  self.shipping_width = packages[0].try(:width)
  self.shipping_height = packages[0].try(:height)
  self.shipping_weight = packages[0].try(:weight)
  if packages.size > 1
    self.box2_shipping_length = packages[1].try(:length)
    self.box2_shipping_width = packages[1].try(:width)
    self.box2_shipping_height = packages[1].try(:height)
    self.box2_shipping_weight = packages[1].try(:weight)
  else
    self.box2_shipping_length = nil
    self.box2_shipping_width = nil
    self.box2_shipping_height = nil
    self.box2_shipping_weight = nil
  end
  if packages.size > 2
    self.box3_shipping_length = packages[2].try(:length)
    self.box3_shipping_width = packages[2].try(:width)
    self.box3_shipping_height = packages[2].try(:height)
    self.box3_shipping_weight = packages[2].try(:weight)
  else
    self.box3_shipping_length = nil
    self.box3_shipping_width = nil
    self.box3_shipping_height = nil
    self.box3_shipping_weight = nil
  end
  save
end

#update_rendered_product_specifications(user_id: nil, tokens: nil, locales: nil, skip_google_feed: true) ⇒ Object



2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
# File 'app/models/item.rb', line 2016

def update_rendered_product_specifications(user_id: nil, tokens: nil, locales: nil, skip_google_feed: true)
  return :skipped if new_record? || is_publication?

  locales ||= content_locales_to_render

  self.refresh_specs = false # prevent looping
  self.skip_item_calc = true # not needed
  # Render for each available locale, always include english
  locales.uniq.each do |locale|
    # If this item is not offered in the locale no need to generate this
    Mobility.with_locale(locale) do
      self.rendered_product_specifications = render_product_specifications(tokens:)
    end
  end
  self.rendered_product_specifications_at = Time.current
  # This makes sure we don't store bad translations that are not supposed to be there
  # this can yield unsavory results when rendering
  prune_unused_translations

  # If an updater_id is specified, we override the stamp
  res = :fail
  CurrentScope.with_user_id(user_id) do
    res = :success if save
  end
  if res == :success
    refresh_google_feed unless skip_google_feed # Done Async
    purge_edge_cache(include_product_line: false) # Async as well
  end

  res
end

#uploadUpload

Returns:

See Also:



253
# File 'app/models/item.rb', line 253

belongs_to :upload, optional: true

#uploadsActiveRecord::Relation<Upload>

Returns:

  • (ActiveRecord::Relation<Upload>)

See Also:



311
# File 'app/models/item.rb', line 311

has_and_belongs_to_many :uploads, dependent: :destroy

#url_pathsObject



721
722
723
724
725
726
# File 'app/models/item.rb', line 721

def url_paths
  p = []
  p += primary_product_line.url_paths
  p << sku
  p.compact
end

#variants(include_self: false) ⇒ Object

A more strict version of relatives, we don't look at descendants, only exact matches



2158
2159
2160
# File 'app/models/item.rb', line 2158

def variants(include_self: false)
  item_grouping_info(include_self:)&.variants || Item.none
end

#versions_for_audit_trail(_params = {}) ⇒ Object



2509
2510
2511
2512
2513
2514
2515
2516
2517
# File 'app/models/item.rb', line 2509

def versions_for_audit_trail(_params = {})
  RecordVersion.where(<<~SQL.squish, id:, item_id_json: { item_id: id }.to_json)
    (item_type = 'Item' AND item_id = :id)
    OR (
      item_type = 'ImageProfile'
      AND reference_data @> :item_id_json
    )
  SQL
end

#view_product_catalogsActiveRecord::Relation<ViewProductCatalog>

Returns:

See Also:



274
# File 'app/models/item.rb', line 274

has_many :view_product_catalogs

#warm_cache(include_product_line: true) ⇒ Object

Finds the associated sitemap (url) end points of this item and its product line
Perform a cache warmup



1961
1962
1963
1964
1965
1966
1967
1968
# File 'app/models/item.rb', line 1961

def warm_cache(include_product_line: true)
  return :disabled unless Cache::EdgeCacheUtility.edge_cache_enabled?

  all_site_maps.each(&:warm_cache)
  return unless include_product_line && (pl = primary_product_line)

  pl.warm_cache
end

#will_be_restricted_for_sales?Boolean

Returns:

  • (Boolean)


699
700
701
702
# File 'app/models/item.rb', line 699

def will_be_restricted_for_sales?
  restricted_for_sales ||
    (product_category&.restricted_for_sales && primary_product_line&.restricted_for_sales)
end