Class: CatalogItem
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- CatalogItem
- Includes:
- Memery, Models::Auditable, Models::CatalogItemAmazonHelper, Models::CatalogItemWalmartHelper, Models::CatalogItemWayfairHelper, Models::EdgeCachePurgeable, Models::SaleDiscountable, Models::Translatable, PgSearch::Model
- Defined in:
- app/models/catalog_item.rb
Overview
== Schema Information
Table name: catalog_items
Database name: primary
id :integer not null, primary key
alternate_warehouse_stock_fraction :integer
alternate_warehouse_stock_reporting_max :integer
amazon_desired_product_type :string
amazon_fba_labeling :enum
amazon_fba_sku :string
amazon_fnsku :string
amazon_info_datetime :datetime
amazon_item_suppression_pull_datetime :datetime
amazon_listing_reported_product_type :string
amazon_product_schema :string(30)
amazon_reported_product_type :string
amount :decimal(8, 2)
amz_last_buy_box_winner_change :datetime
amz_min_seller_price_override :decimal(8, 2)
backup_tppn :string
business_discount_override :integer
clearance :boolean default(FALSE), not null
discontinued_date :date
discontinued_part_number :string
google_feed :boolean default(FALSE), not null
google_feed_title :string
google_report :jsonb
is_amz_buy_box_winner :boolean default(FALSE), not null
is_amz_featured_merchant :boolean default(FALSE), not null
item_suppressed_status :string
item_suppressed_status_message :string
last_inventory_advice_sent_json :jsonb
last_price_advice_sent_json :jsonb
legacy_fulfillment :integer default(0), not null
legacy_sale_price_effective_date :date
legacy_sale_price_expiration_date :date
margin_required :decimal(, )
max_discount :integer default(100)
min_stock_to_report :integer
new_price :decimal(8, 2)
new_price_effective_date :date
new_sale_price :decimal(8, 2)
old_amount :decimal(10, 2)
pack_at_kit_level :boolean default(FALSE), not null
parent_sku :string
pending_discontinue_date :date
position :integer default(100)
price_editable :boolean default(FALSE), not null
price_updated_at :datetime
quantity_1_lower_bound :integer
quantity_1_price_discount :decimal(6, 2)
quantity_1_price_type :enum
quantity_2_lower_bound :integer
quantity_2_price_discount :decimal(6, 2)
quantity_2_price_type :enum
quantity_3_lower_bound :integer
quantity_3_price_discount :decimal(6, 2)
quantity_3_price_type :enum
quantity_4_lower_bound :integer
quantity_4_price_discount :decimal(6, 2)
quantity_4_price_type :enum
quantity_5_lower_bound :integer
quantity_5_price_discount :decimal(6, 2)
quantity_5_price_type :enum
quantity_discount_price_type :enum
reserve_stock :integer
retail_price :decimal(8, 2)
retailer_information :jsonb
retailer_price_updated_at :datetime
retailer_requested_cost :decimal(10, 2)
sale_price :decimal(8, 2)
skip_url_checks :boolean default(FALSE), not null
state :string(30) default("active"), not null
third_party_description :text
third_party_name :string(255)
third_party_part_number :string(255)
third_party_product_type :string
third_party_promo_part_number :string
third_party_short_description :text
third_party_sku :string
translations :jsonb
url :string
url_last_checked :datetime
url_valid :boolean default(FALSE), not null
created_at :datetime
updated_at :datetime
catalog_id :integer
coupon_id :integer
new_coupon_id :integer
store_item_id :integer not null
Indexes
index_catalog_items_on_amazon_fnsku (amazon_fnsku) UNIQUE
index_catalog_items_on_catalog_id_and_store_item_id (catalog_id,store_item_id) UNIQUE
index_catalog_items_on_catalog_id_and_third_party_sku (catalog_id,third_party_sku) UNIQUE
index_catalog_items_on_catalog_id_and_tpn (catalog_id,third_party_promo_part_number) UNIQUE
index_catalog_items_on_coupon_id (coupon_id)
index_catalog_items_on_new_coupon_id (new_coupon_id)
index_catalog_items_on_parent_sku (parent_sku)
index_catalog_items_on_state_and_store_item_id_and_catalog_id (state,store_item_id,catalog_id)
index_catalog_items_on_state_and_url_valid (state,url_valid)
index_catalog_items_on_store_item_id_and_state (store_item_id,state)
index_catalog_items_on_third_party_part_number_and_catalog_id (third_party_part_number,catalog_id) UNIQUE WHERE (third_party_part_number IS NOT NULL)
index_catalog_items_on_third_party_sku_and_catalog_id (third_party_sku,catalog_id) UNIQUE WHERE (third_party_sku IS NOT NULL)
Foreign Keys
catalog_items_catalog_id_fkey (catalog_id => catalogs.id)
catalog_items_store_item_id_fk (store_item_id => store_items.id) ON DELETE => cascade
fk_rails_... (coupon_id => coupons.id)
fk_rails_... (new_coupon_id => coupons.id) ON DELETE => nullify
Defined Under Namespace
Classes: CouponAndSalePriceValidator, Onboarder, PriceSync, PriceUpdatedHandler, QuantityBoundsValidator, Remapper, ScheduledPriceChanger
Constant Summary collapse
- HIDDEN_STATES =
Recognised hidden states.
%w[active_hidden discontinued pending_client_review invalid_catalog_item pending_onboarding].freeze
- EDI_FEED_STATUSES =
Recognised edi feed statuses.
%w[active require_vendor_update pending_onboarding pending_vendor_update pending_discontinue].freeze
- EDI_DELTA_FEED_CATEGORIES =
Omitting an item from a feed automatically will trigger a removal, therefore pending_discontinue should be excluded
from selection %w[inventory_advice price_advice].freeze
- ORCHESTRATOR_STATES =
Recognised orchestrator states — what the Mirakl product-data and price feeds
send (viafor_orchestrator_keys).pending_discontinueIS sent: the listing still has to be maintained while
remaining stock sells through.discontinuedis NOT — we have stopped selling
it, so it must never be pushed to a marketplace again.discontinuedused to be in this list, which inverted the meaning of retiring
an item: it moved a catalog item INTO the feed rather than out of it, because
pending_onboardingis absent. Discontinuing the Ember accessories on ADEO
therefore fed an unmappable thermostat to the product presenter and took the
whole ADEO product feed down (AppSignal #6521). %w[active require_vendor_update pending_vendor_update pending_discontinue].freeze
- ORDER_MATCHABLE_STATES =
States an inbound EDI order is allowed to match against.
Wider than EDI_FEED_STATUSES on both ends:
active_hiddenitems are not
fed but are still ordered off stale retailer listings, and
pending_onboardingis matched because the order itself proves the listing
is live — see #onboarded_by_order!. A state we don't match raises out of the
line-item build and rolls back the WHOLE order batch, so this list errs
toward accepting the order and warning
(#state_requires_edi_warning_on_order?) over refusing it. %w[active active_hidden require_vendor_update pending_vendor_update pending_discontinue pending_onboarding].freeze
- REPRICE_RULES_RAISING =
Rules that route through the raising / lowering services (SQL-usable).
%w[up_only up_only_preserve_buy_box auto_buy_box].freeze
- REPRICE_RULES_LOWERING =
Reprice rules lowering.
%w[down_only_buy_box auto_buy_box].freeze
- REPRICE_REVERT_WINDOW =
How long after a raise a Buy-Box loss still triggers an automatic revert.
7.days
Constants included from Models::CatalogItemWalmartHelper
Models::CatalogItemWalmartHelper::WALMART_ALREADY_RETIRED_ERROR_CODE
Constants included from Models::CatalogItemAmazonHelper
Models::CatalogItemAmazonHelper::AMAZON_DEFAULT_BUSINESS_DISCOUNT, Models::CatalogItemAmazonHelper::AMAZON_LISTING_LANGUAGES, Models::CatalogItemAmazonHelper::AMAZON_MARKETPLACE_LISTING_LOCALES, Models::CatalogItemAmazonHelper::AMAZON_MIN_PROFIT_MARGIN
Constants included from RouteUrlHelpers
Constants included from Models::Auditable
Models::Auditable::ALWAYS_IGNORED
Constants included from Models::Schedulable
Models::Schedulable::SIMPLE_FORM_OPTIONS
Instance Attribute Summary collapse
-
#add_kit_components_to_catalog ⇒ Object
Returns the value of attribute add_kit_components_to_catalog.
-
#alternate_warehouse_stock_fraction ⇒ Object
readonly
Validates alternate warehouse stock fraction.
-
#alternate_warehouse_stock_reporting_max ⇒ Object
readonly
Validates alternate warehouse stock reporting max.
-
#amount ⇒ Object
readonly
Validates amount.
-
#item_id ⇒ Object
Item id.
-
#max_discount ⇒ Object
readonly
Validates max discount.
-
#new_price ⇒ Object
readonly
Validates new price, new price effective date.
-
#new_price_effective_date ⇒ Object
readonly
Validates new price effective date, new price.
-
#reserve_stock ⇒ Object
readonly
Validates reserve stock.
-
#skip_check_kit_components ⇒ Object
Returns the value of attribute skip_check_kit_components.
-
#skip_create_kit_components ⇒ Object
Returns the value of attribute skip_create_kit_components.
-
#store_ids ⇒ Object
Store ids.
-
#third_party_name_en ⇒ Object
readonly
Validates third party name en, third party name en required.
-
#third_party_name_fr ⇒ Object
readonly
Validates third party name fr, third party name fr required.
-
#third_party_part_number ⇒ Object
readonly
validates :store_item_id, uniqueness: { scope: :catalog_id }.
-
#third_party_sku ⇒ Object
readonly
Validates third party sku, third party sku required.
-
#third_party_sku_variant_id ⇒ Object
readonly
Marketplace variant selector (e.g. Wayfair piid): a given variant must map to at most one of our catalog items per catalog.
Attributes included from Models::CatalogItemAmazonHelper
Attributes included from Models::Translatable
#do_not_compact_translation_container
Belongs to collapse
-
#catalog ⇒ Catalog
Catalog.
-
#coupon ⇒ Coupon
Coupon.
-
#new_coupon ⇒ Coupon
New coupon.
-
#store_item ⇒ StoreItem
Store item.
-
#variant_group ⇒ VariantGroup
The channel row (VariantGroup amazon override / Wayfair listing row) this catalog_item lists under.
Methods included from Models::Auditable
Has one collapse
-
#amazon_marketplace ⇒ AmazonMarketplace
Amazon marketplace.
-
#exported_catalog_item ⇒ ExportedCatalogItem
Exported catalog item.
-
#item ⇒ Item
Item.
-
#store ⇒ Store
Store.
Has many collapse
-
#amazon_catalog_item_flags ⇒ ActiveRecord::Relation<AmazonCatalogItemFlag>
Amazon catalog item flags.
-
#amazon_sales_rank_data_points ⇒ ActiveRecord::Relation<AmazonSalesRankDataPoint>
Amazon sales rank data points.
-
#edi_communication_logs ⇒ ActiveRecord::Relation<EdiCommunicationLog>
Edi communication logs.
-
#edi_documents ⇒ ActiveRecord::Relation<EdiDocument>
Edi documents.
-
#line_items ⇒ ActiveRecord::Relation<LineItem>
Line items.
-
#listing_issues ⇒ ActiveRecord::Relation<ListingIssue>
Listing issues.
-
#reprice_reference_catalogs ⇒ ActiveRecord::Relation<RepriceReferenceCatalog>
Reprice reference catalogs.
-
#reprice_references ⇒ ActiveRecord::Relation<CatalogItemRepriceReference>
Reprice references.
-
#retailer_probes ⇒ ActiveRecord::Relation<CatalogItemRetailerProbe>
Retailer probes.
-
#site_maps ⇒ ActiveRecord::Relation<SiteMap>
Site maps.
Has and belongs to many collapse
-
#amazon_browse_nodes ⇒ ActiveRecord::Relation<AmazonBrowseNode>
The associated amazon browse nodes.
-
#store_items ⇒ ActiveRecord::Relation<StoreItem>
The associated store items.
Delegated Instance Attributes collapse
-
#all_amazon_image_profiles ⇒ Object
Alias for to: :item#all_amazon_image_profiles.
-
#currency ⇒ Object
Alias for Catalog#currency.
-
#currency_symbol ⇒ Object
Alias for Catalog#currency_symbol.
-
#is_available_to_public ⇒ Object
Alias for to: :item#is_available_to_public.
-
#is_cable_accessory? ⇒ Object
Alias for to: :item#is_cable_accessory?.
-
#is_cable_fit_guide? ⇒ Object
Alias for to: :item#is_cable_fit_guide?.
-
#is_circuit_check? ⇒ Object
Alias for to: :item#is_circuit_check?.
-
#is_control? ⇒ Object
Alias for to: :item#is_control?.
-
#is_heating_element? ⇒ Object
Alias for to: :item#is_heating_element?.
-
#is_membrane? ⇒ Object
Alias for to: :item#is_membrane?.
-
#is_publication? ⇒ Object
Alias for to: :item#is_publication?.
-
#is_roughin_kit? ⇒ Object
Alias for to: :item#is_roughin_kit?.
-
#is_snow_melt_plaque? ⇒ Object
Alias for to: :item#is_snow_melt_plaque?.
-
#is_underlayment? ⇒ Object
Alias for to: :item#is_underlayment?.
-
#oversize? ⇒ Object
Alias for to: :item#oversize?.
-
#parent_catalog ⇒ Object
Alias for Catalog#parent_catalog.
-
#prefix: :new_sale_price_effective_date ⇒ Object
Alias for New_coupon#effective_date.
-
#prefix: :new_sale_price_expiration_date ⇒ Object
Alias for New_coupon#expiration_date.
-
#prefix: :sale_price_effective_date ⇒ Object
Alias for Coupon#effective_date.
-
#prefix: :sale_price_expiration_date ⇒ Object
Alias for Coupon#expiration_date.
-
#primary_image ⇒ Object
Alias for to: :item#primary_image.
-
#primary_product_line ⇒ Object
Alias for to: :item#primary_product_line.
-
#primary_product_line_id ⇒ Object
Alias for to: :item#primary_product_line_id.
-
#product_category ⇒ Object
Alias for to: :item#product_category.
-
#product_lines ⇒ Object
Alias for to: :item#product_lines.
-
#public_description_html ⇒ Object
Alias for to: :item#public_description_html.
-
#public_name ⇒ Object
Alias for to: :item#public_name.
-
#qty_available ⇒ Object
Alias for Store_item#qty_available.
-
#qty_available_outside_order ⇒ Object
Alias for Store_item#qty_available_outside_order.
-
#seo_description ⇒ Object
Alias for to: :item#seo_description.
-
#seo_keywords ⇒ Object
Alias for to: :item#seo_keywords.
-
#sku ⇒ Object
Alias for to: :item#sku.
-
#spec ⇒ Object
Alias for to: :item#spec.
-
#spec_output ⇒ Object
Alias for to: :item#spec_output.
-
#specifications ⇒ Object
Alias for to: :item#specifications.
-
#specifications_grouped ⇒ Object
Alias for to: :item#specifications_grouped.
-
#tax_class ⇒ Object
Alias for to: :item#tax_class.
-
#tax_rate ⇒ Object
Alias for Catalog#tax_rate.
-
#unit_cogs ⇒ Object
Alias for Store_item#unit_cogs.
-
#upc ⇒ Object
Alias for to: :item#upc.
-
#variant_axis_tokens ⇒ Object
Alias for to: :item#variant_axis_tokens.
Methods included from Models::CatalogItemAmazonHelper
#amazon_description, #amazon_feature_1, #amazon_feature_2, #amazon_feature_3, #amazon_feature_4, #amazon_feature_5, #amazon_feature_6, #amazon_feature_7, #amazon_feature_8, #amazon_feature_9, #amazon_generic_keyword, #amazon_target_keywords, #amazon_title, #title_for_amazon
Class Method Summary collapse
-
.accessories ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are accessories.
-
.active ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are active.
-
.available_for_edi_feeds ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are available for edi feeds.
-
.available_for_edi_feeds_by_states ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are available for edi feeds by states.
-
.by_skus ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are by skus.
-
.by_upcs ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are by upcs.
-
.calculate_discounted_price(price_before_discount, discount, max_discount = 1.0) ⇒ Object
Calculate discounted price.
-
.catalog_items_for_select ⇒ Object
Catalog items for select.
-
.complimentary_of ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are complimentary of.
-
.controls ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are controls.
-
.coupons_for_future_promo_select ⇒ Object
Coupons for future promo select.
-
.coupons_for_promo_select ⇒ Object
Coupons for promo select.
-
.discontinued ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are discontinued.
-
.edi_delta_feeds_for_partner_inventory_advice ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are edi delta feeds for partner inventory advice.
-
.edi_delta_feeds_for_partner_price_advice ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are edi delta feeds for partner price advice.
-
.for_edi_feeds ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are for edi feeds.
-
.for_edi_feeds_by_states ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are for edi feeds by states.
-
.for_google_feed ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are for google feed.
-
.for_online_catalog ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are for online catalog.
-
.for_online_catalog_or_non_web_accessible_with_successor_item ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are for online catalog or non web accessible with successor item.
-
.for_orchestrator_keys ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are for orchestrator keys.
-
.for_product_category_path ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are for product category path.
-
.for_product_line_path ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are for product line path.
-
.for_where_to_buy_list ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are for where to buy list.
-
.has_successor_item ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are has successor item.
-
.heating_elements ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are heating elements.
-
.hidden_from_catalog ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are hidden from catalog.
-
.in_stock ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are in stock.
-
.in_stock_with_alternate_warehouse_store_items ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are in stock with alternate warehouse store items.
-
.inactive ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are inactive.
-
.install_kits ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are install kits.
-
.insulations ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are insulations.
-
.integration_kits ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are integration kits.
-
.last_custom_mat_catalog_item(catalog_id, volts) ⇒ Object
Last custom mat catalog item.
-
.main_catalogs ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are main catalogs.
-
.non_publications ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are non publications.
-
.non_web_accessible ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are non web accessible.
-
.not_discontinued ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are not discontinued.
-
.not_hidden_from_catalog ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are not hidden from catalog.
-
.not_pending_onboarding ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are not pending onboarding.
-
.order_by_spec ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are order by spec.
-
.ordered_by_sku ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are ordered by sku.
-
.out_of_stock ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are out of stock.
-
.pending_onboarding ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are pending onboarding.
-
.power_modules ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are power modules.
-
.primary_catalogs_first ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are primary catalogs first.
-
.primary_catalogs_first_then_by_state_and_name ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are primary catalogs first then by state and name.
-
.probeable ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are probeable.
-
.public_catalog_items ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are public catalog items.
-
.relay_panels ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are relay panels.
-
.sensors ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are sensors.
-
.spare_parts ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are spare parts.
-
.state_options_for_select(show_all_states = false) ⇒ Array<Array>
[label, id]pairs for select dropdowns. -
.thermostats ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are thermostats.
-
.towel_warmers ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are towel warmers.
-
.web_accessible ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are web accessible.
-
.with_item ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are with item.
-
.with_item_and_classifications ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are with item and classifications.
-
.with_item_condition_new ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are with item condition new.
-
.with_product_category ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are with product category.
-
.with_product_lines ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are with product lines.
-
.with_product_specification ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are with product specification.
-
.with_third_party_number ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are with third party number.
Instance Method Summary collapse
-
#alternate_warehouse_stock_fraction_overridden? ⇒ Boolean
Whether this item overrides the catalog's alternate-warehouse share.
-
#alternate_warehouse_store_items ⇒ Object
Alternate warehouse store items.
-
#amazon_variant_catalog_items ⇒ Object
Amazon variant catalog items.
-
#amount_currency_symbol ⇒ Object
Amount currency symbol.
-
#available_in_edi_feed? ⇒ Boolean
Whether the record available in edi feed.
-
#available_stores ⇒ Object
Available stores.
-
#bom_price ⇒ Object
Bom price.
-
#calculate_minimum_price_for_margin(target_margin) ⇒ Object
Provide a target margin below a 100.
-
#calculate_profit_margin(price) ⇒ Object
Calculate profit margin.
-
#calculate_sale_price(percentage_off, include_vat: false, from_msrp: false) ⇒ Object
Calculates the sale price based on a percentage off (0-100), specify also if price with VAT should be used as the starting amount.
-
#calculated_updated_price_from_parent ⇒ Object
Calculated updated price from parent.
-
#catalog_discount_human ⇒ Object
Catalog discount human.
-
#catalog_items_in_same_catalog(starting_scope = nil) ⇒ Object
Catalog items in same catalog.
-
#check_for_price_update ⇒ Object
protected
Check for price update.
-
#check_kit_components_available ⇒ Object
protected
Check kit components available.
-
#check_ok_to_destroy ⇒ Object
protected
Check ok to destroy.
-
#check_store_item_is_available ⇒ Object
protected
Check store item is available.
-
#check_upc ⇒ Object
protected
Check upc.
-
#complete_state ⇒ Object
Complete state.
-
#content_locales_to_render ⇒ Object
Content locales to render.
-
#create_component_catalog_items ⇒ Object
Create component catalog items.
-
#create_single_component_in_catalog_item(store_id, location, component_item) ⇒ Object
Create single component in catalog item.
-
#deep_dup ⇒ Object
A deep-copied, unsaved duplicate of the record.
-
#dependent_catalog_items(filtered_by_catalog_id = nil) ⇒ Object
Find all similar catalog items in the child catalogs, optionally filter only to one specific catalog.
-
#discounted_price(customer = nil, target = nil) ⇒ Object
Discounted price.
-
#diverging_current_price_vs_proposed_price? ⇒ Boolean
Whether the record diverging current price vs proposed price.
-
#edi_customer_ids_for_catalog ⇒ Object
Returns the list of EDI customer IDs for this catalog, cached for reuse.
-
#edi_offer_new_sale_price ⇒ Object
Edi offer new sale price.
-
#edi_offer_price ⇒ Object
EDI offer prices.
-
#edi_offer_sale_price ⇒ Object
Edi offer sale price.
-
#edi_orchestrators_for_catalog ⇒ Object
Returns orchestrators for EDI customers in this catalog, cached for reuse Uses the class-level orchestrator cache for fast lookups.
-
#edi_partner_sku ⇒ Object
Edi partner sku.
-
#effective_alternate_warehouse_stock_fraction ⇒ Float
Resolves the alternate-warehouse share reported externally, as a decimal.
-
#effective_seo_description(char_limit: nil) ⇒ Object
Effective seo description.
-
#european? ⇒ Boolean
Whether the record european.
-
#get_successor_online_catalog_item ⇒ Object
Get successor online catalog item.
-
#in_google_feed? ⇒ Boolean
Whether the record in google feed.
-
#in_hide_from_feed_state? ⇒ Boolean
Whether the record in hide from feed state.
-
#in_main_catalog? ⇒ Boolean
protected
Whether the record in main catalog.
-
#inventory_message_enabled? ⇒ Boolean
Whether the record inventory message enabled.
-
#inventory_message_processors ⇒ Object
Retrieves all inventory message processors for edi customers carrying this item This can be used to push inventory at once to all channels for one item.
-
#is_discontinued? ⇒ Boolean
(also: #is_discontinued)
Whether the record is discontinued.
-
#item_is_web_accessible? ⇒ Boolean
Whether the record item is web accessible.
-
#listing_message_enabled? ⇒ Boolean
Whether the record listing message enabled.
-
#listing_message_processors ⇒ Object
Retrieves all listing message processors for edi customers carrying this item This can be used to patch listing data at once to all channels for one item.
-
#map_difference ⇒ Object
Difference between retail price and MAP (negative = violation).
-
#map_price ⇒ Object
MAP (Minimum Advertised Price) based on catalog's map_percentage setting Only meaningful for vendor catalogs where retailer controls pricing.
-
#map_to_stores(store_ids) ⇒ Object
This method creates a link between a catalog item and additional stores It is useful for amazon FBA where we keep track of the stock in amazon's warehouses Simply pass an array an store id to map the catalog item to and it will do the rest Note that this method never removes the primary store item for a catalog item.
-
#map_violation? ⇒ Boolean
Check if the retailer's current price is below MAP.
-
#missing_kit_components? ⇒ Boolean
Whether the record missing kit components.
-
#msrp ⇒ Object
Msrp.
-
#msrp_with_vat ⇒ Object
Msrp with vat.
-
#name(locale: nil) ⇒ String
The name of the record.
-
#new_sale_price_with_vat ⇒ Object
New sale price with vat.
-
#next_available(use_store_item: nil, use_alternate_warehouse: false) ⇒ Object
Returns next available for internal CRM use (100% real stock) discontinued and pending discontinue item will report nil for next available.
-
#next_available_by_warehouse(use_alternate_warehouse: false) ⇒ Object
Next available by warehouse.
-
#next_available_by_warehouse_with_depth_limit(use_alternate_warehouse: false, max_depth: 10, current_depth: 0) ⇒ Object
Version with depth limit to prevent infinite recursion.
-
#next_available_with_depth_limit(use_store_item: nil, use_alternate_warehouse: false, max_depth: 10, current_depth: 0) ⇒ Object
Version with depth limit to prevent infinite recursion (100% real stock for internal use).
-
#notify_of_price_update ⇒ Object
protected
Notify of price update.
-
#ok_to_destroy? ⇒ Boolean
Whether the record ok to destroy.
-
#on_order(use_alternate_warehouse: false) ⇒ Object
On order.
-
#on_order_for_store_item(use_store_item: nil, use_alternate_warehouse: false) ⇒ Object
Returns on order quantities for internal CRM use (100% real stock) discontinued and pending discontinue item will report 0 for on order.
-
#onboarded_by_order! ⇒ Object
A retailer only transmits an order for a SKU it is actually selling, so an inbound EDI order is the strongest evidence we ever get that a listing is live — stronger than a price probe or a sitemap scan.
-
#out_of_stock(use_threshhold = nil) ⇒ Object
Out of stock.
-
#parent_catalog_currency_symbol ⇒ Object
Parent catalog currency symbol.
-
#parent_catalog_discount ⇒ Object
Parent catalog discount.
-
#parent_catalog_item ⇒ Object
If a parent_catalog exists as defined in the catalog of this catalog item then find the matching catalog item by store item id which should be similar.
-
#parent_catalog_item_amount ⇒ Object
Parent catalog item amount.
-
#percentage_off_from_msrp ⇒ Object
Percentage off from msrp.
-
#price_message_enabled? ⇒ Boolean
Whether the record price message enabled.
-
#price_message_processors ⇒ Object
Retrieves all price message processors for edi customers carrying this item This can be used to push price at once to all channels for one item.
-
#price_out_of_date? ⇒ Boolean
This method determines if the catalog item is out of date compared to its parent catalog item, return true/false or nil if not applicable.
-
#price_updated? ⇒ Boolean
protected
Whether the record price updated.
-
#price_with_vat ⇒ Object
Price with vat.
-
#primary_catalog ⇒ Object
Primary catalog.
-
#probeable? ⇒ Boolean
Whether the retailer-probe pipeline may call this listing right now.
-
#product_category_visible_in_feed? ⇒ Boolean
Whether the record product category visible in feed.
-
#product_stock_status ⇒ Object
Product stock status.
-
#profit_margin ⇒ Object
Profit margin.
-
#profit_margin_new_price ⇒ Object
Profit margin new price.
-
#profit_margin_new_sale_price ⇒ Object
Profit margin new sale price.
-
#profit_margin_retailer_requested_cost ⇒ Object
Profit margin retailer requested cost.
-
#profit_margin_sale_price ⇒ Object
Profit margin sale price.
-
#publish_buy_box_change ⇒ Object
protected
Announce a Buy Box win/loss flip, whoever wrote it (orchestrator pulls today; an ANY_OFFER_CHANGED SQS handler once that subscription lands) — see Events::AmazonBuyBoxChanged.
-
#purge_edge_cache ⇒ Object
protected
Purge edge cache.
-
#push_inventory_message ⇒ Object
Performs an integration push where applicable for stock to partner carrying this catalog item.
-
#push_price_message ⇒ Object
Push price message.
-
#real_stock(use_store_item: nil, use_alternate_warehouse: false) ⇒ Object
Returns REAL stock available (100% of all warehouses) for internal CRM use Use this for cycle counts, pick items, order management, quotes, etc.
-
#refurbished ⇒ Object
Refurbished.
-
#reported_name ⇒ String?
Product name reported to external retailers and product feeds (Wayfair, CommerceHub, generic feed), cleaned through Heatwave::Normalizers.feed_safe so trademark glyphs and smart punctuation never reach a marketplace.
-
#reported_name_for_google ⇒ String?
Google Shopping feed title: the per-item +google_feed_title+ override (cleaned) when present, otherwise the already-cleaned #reported_name.
-
#reported_stock(use_store_item: nil, use_alternate_warehouse: false, safety_stock: nil) ⇒ Integer
Calculates stock available for external reporting.
-
#reported_stocks(use_alternate_warehouse: false) ⇒ Object
Returns a hash of store ids and reported stock available in each { 'WarmlyYours-CA': 14, 'WarmlyYours-US': 20 }.
-
#reported_vendor_sku(_orchestrator_partner = nil) ⇒ Object
Reported vendor sku.
-
#reset_edi_delta_feed_fields ⇒ Object
Reset edi delta feed fields.
-
#retail_price_currency_symbol ⇒ Object
Retail price currency symbol.
-
#root_catalog_item ⇒ Object
Root catalog item.
-
#sale_price_in_effect?(exclude_effective_date: false, date_to_check: nil) ⇒ Boolean
Whether the record sale price in effect.
-
#sale_price_percentage_off ⇒ Object
Returns the percentage off (0-100) of an item comparing sale price to catalog price.
-
#sale_price_percentage_off=(percentage_off) ⇒ Object
Assign a percentage off (0-100) and the sale price will be calculated based on the catalog item price without VAT.
-
#sale_price_reset_if_no_coupon ⇒ Object
protected
You cannot have a sale price without a coupon, so before anything, we remove these.
-
#sale_price_with_vat ⇒ Object
Sale price with vat.
-
#sale_price_with_vat_percentage_off ⇒ Object
Returns the percentage off (0-100) of an item comparing sale price with VAT to catalog price with VAT.
-
#sale_price_with_vat_percentage_off=(percentage_off) ⇒ Object
Assign a percentage off (0-100) and the sale price will be calculated based on the catalog item price with VAT included.
-
#sellable_online_qty(use_store_item: nil) ⇒ Integer
Units actually offerable to shoppers for THIS catalog: the primary warehouse's available stock minus the reserve buffer, never negative.
-
#seo_title ⇒ Object
Seo title.
-
#set_or_clear_discontinued_date ⇒ Object
protected
Set or clear discontinued date.
-
#should_appear_in_feed? ⇒ Boolean
Whether the record should appear in feed.
-
#siblings ⇒ Object
Siblings.
-
#single_sku? ⇒ Boolean
Whether the record single sku.
-
#sku_and_name ⇒ Object
Sku and name.
-
#state_requires_edi_warning_on_order? ⇒ Boolean
Whether the record state requires edi warning on order.
-
#store_items_by_warehouse ⇒ Object
Store items by warehouse.
-
#third_party_name_en_required? ⇒ Boolean
protected
Whether the record third party name en required.
-
#third_party_name_fr_required? ⇒ Boolean
protected
Whether the record third party name fr required.
-
#third_party_part_number_label ⇒ Object
Third party part number label.
-
#third_party_part_number_required? ⇒ Boolean
protected
Whether the record third party part number required.
-
#third_party_sku_required? ⇒ Boolean
protected
Whether the record third party sku required.
-
#to_s ⇒ String
String representation of the record.
-
#tweak_ebay_sku ⇒ Object
Tweak ebay sku.
-
#unprobeable_reason ⇒ String?
Why #probeable? said no, for logs.
-
#upc_required? ⇒ Boolean
protected
Whether the record upc required.
-
#update_price_from_parent ⇒ Object
When the catalog item state moves from pending client update to active, the price is updated.
-
#variant_group_in_catalog_marketplace ⇒ Object
protected
Amazon channel rows are per-marketplace: the linked VariantGroup amazon row must target this catalog's marketplace.
-
#variants(_catalog_items_scope: nil, include_self: false, axis_filters: {}) ⇒ Object
Variants.
Methods included from Models::EdgeCachePurgeable
#edge_cache_urls, #enqueue_edge_cache_purge
Methods included from Models::CatalogItemWayfairHelper
#wayfair_catalog_item?, #wayfair_class_diverged?, #wayfair_effective_taxonomy_category_id, #wayfair_primary_product_url, #wayfair_product_url, #wayfair_pull_taxonomy_schema, #wayfair_sync_age, #wayfair_synced?, #wayfair_taxonomy_available?, #wayfair_taxonomy_category_id_in_effect, wayfairs
Methods included from Models::CatalogItemWalmartHelper
#walmart_catalog_item?, #walmart_retire_item, walmarts
Methods included from Models::CatalogItemAmazonHelper
#amalytix_tags, #amazon_asin, #amazon_business_price, #amazon_business_price_factor, #amazon_business_price_fba, #amazon_business_price_with_tax, #amazon_business_price_with_tax_fba, #amazon_catalog_item?, #amazon_current_images, #amazon_delete_information, #amazon_effective_business_price_discount, #amazon_effective_desired_product_type, #amazon_effective_product_schema, #amazon_gated_locales, #amazon_item_cost, #amazon_json_generator, #amazon_label_requirements, #amazon_list_price, #amazon_locales, #amazon_lowest_quantity_discount_price, #amazon_lowest_quantity_discounted_price, #amazon_maximum_seller_allowed_price_with_tax, #amazon_maximum_seller_allowed_price_with_tax_fba, #amazon_minimum_profit_margin, #amazon_minimum_seller_allowed_price_with_tax, #amazon_minimum_seller_allowed_price_with_tax_fba, #amazon_patch_information, #amazon_price_with_tax, #amazon_price_with_tax_fba, #amazon_product_data, #amazon_product_type_in_effect, #amazon_product_type_incoherent?, #amazon_product_url, #amazon_pull_buy_box_status, #amazon_pull_catalog_information, #amazon_pull_listing_information, #amazon_pull_listing_schema, #amazon_put_information, #amazon_quantity_discount_price, #amazon_repricing_floor_with_tax, #amazon_sale_price_with_tax, #amazon_sale_price_with_tax_fba, #amazon_schema, #amazon_seller_item?, #amazon_send_patch_listing_information, #amazon_send_put_listing_information, #amazon_vendor_code, #amazon_vendor_item?, amazons, amazons_sellers, amazons_sellers_with_asins, amazons_with_asins, #amz_available_attributes, #api_ready_state?, #broadcast_amazon_dashboard_update, by_asins, #extract_amazon_procurement_cost_price, #extract_retailer_information, #fba_discount, #get_amz_all_patches, #get_amz_attribute, #get_amz_attributes, #has_amazon_fba?, #map_price_with_tax, #max_discount_allowed, #max_discount_allowed_dollars, #max_discount_allowed_percentage, #profit_margin_amazon_minimum_below_target, #profit_marging_amazon_maximum_seller_allowed_price, #profit_marging_amazon_minimum_seller_allowed_price, #ready_to_print_amazon_fba_labels?, #reprice_allows_lowering?, #reprice_allows_raising?, #reprice_revert_due?, #reprice_revert_last_raise!, #reprice_reverts_on_buy_box_loss?, #reprice_settings_amazon_only, #reprice_settings_customized?, #reset_reprice_raise_bookkeeping, #retailer_information_first_locale, with_asin
Methods included from Models::SaleDiscountable
#effective_price, #money_effective_price, #money_price, #money_sale_price
Methods included from Models::Translatable
#compact_translation_container, skip_compaction_for
Methods included from Models::Auditable
#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record
Methods inherited from ApplicationRecord
ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation
Methods included from Models::Schedulable
Methods included from Models::AfterCommittable
Methods included from Models::EventPublishable
Instance Attribute Details
#add_kit_components_to_catalog ⇒ Object
Returns the value of attribute add_kit_components_to_catalog.
182 183 184 |
# File 'app/models/catalog_item.rb', line 182 def add_kit_components_to_catalog @add_kit_components_to_catalog end |
#alternate_warehouse_stock_fraction ⇒ Object (readonly)
Validates alternate warehouse stock fraction.
Validations:
- Numericality ({ only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 100 })
- Allow_nil
351 |
# File 'app/models/catalog_item.rb', line 351 validates :alternate_warehouse_stock_fraction, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 100 }, allow_nil: true |
#alternate_warehouse_stock_reporting_max ⇒ Object (readonly)
Validates alternate warehouse stock reporting max.
Validations:
- Numericality ({ only_integer: true, greater_than: 0 })
- Allow_nil
349 |
# File 'app/models/catalog_item.rb', line 349 validates :alternate_warehouse_stock_reporting_max, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true |
#amount ⇒ Object (readonly)
Validates amount.
Validations:
- Presence
- Numericality ({ greater_than_or_equal_to: 0.0 })
340 |
# File 'app/models/catalog_item.rb', line 340 validates :amount, presence: true, numericality: { greater_than_or_equal_to: 0.0 } |
#item_id ⇒ Object
Item id.
835 836 837 |
# File 'app/models/catalog_item.rb', line 835 def item_id store_item&.item_id end |
#max_discount ⇒ Object (readonly)
Validates max discount.
Validations:
- Numericality ({ greater_than_or_equal_to: 0, less_than_or_equal_to: 100 })
- Allow_nil
353 |
# File 'app/models/catalog_item.rb', line 353 validates :max_discount, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100 }, allow_nil: true |
#new_price ⇒ Object (readonly)
Validates new price, new price effective date.
Validations:
- Presence ({ if: :new_price_effective_date, message: 'Must be specified alongside new price effective date' })
336 |
# File 'app/models/catalog_item.rb', line 336 validates :new_price, presence: { if: :new_price_effective_date, message: 'Must be specified alongside new price effective date' } |
#new_price_effective_date ⇒ Object (readonly)
Validates new price effective date, new price.
Validations:
- Presence ({ if: :new_price, message: 'Must be specified alongside new price' })
338 |
# File 'app/models/catalog_item.rb', line 338 validates :new_price_effective_date, presence: { if: :new_price, message: 'Must be specified alongside new price' } |
#reserve_stock ⇒ Object (readonly)
Validates reserve stock.
Validations:
- Numericality ({ only_integer: true, greater_than_or_equal_to: 0 })
- Allow_nil
347 |
# File 'app/models/catalog_item.rb', line 347 validates :reserve_stock, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, allow_nil: true |
#skip_check_kit_components ⇒ Object
Returns the value of attribute skip_check_kit_components.
182 183 184 |
# File 'app/models/catalog_item.rb', line 182 def skip_check_kit_components @skip_check_kit_components end |
#skip_create_kit_components ⇒ Object
Returns the value of attribute skip_create_kit_components.
182 183 184 |
# File 'app/models/catalog_item.rb', line 182 def skip_create_kit_components @skip_create_kit_components end |
#store_ids ⇒ Object
Store ids.
830 831 832 |
# File 'app/models/catalog_item.rb', line 830 def store_ids store_items.map(&:store_id) end |
#third_party_name_en ⇒ Object (readonly)
Validates third party name en, third party name en required.
Validations:
- Presence ({ if: :third_party_name_en_required? })
332 |
# File 'app/models/catalog_item.rb', line 332 validates :third_party_name_en, presence: { if: :third_party_name_en_required? } |
#third_party_name_fr ⇒ Object (readonly)
Validates third party name fr, third party name fr required.
Validations:
- Presence ({ if: :third_party_name_fr_required? })
334 |
# File 'app/models/catalog_item.rb', line 334 validates :third_party_name_fr, presence: { if: :third_party_name_fr_required? } |
#third_party_part_number ⇒ Object (readonly)
validates :store_item_id, uniqueness: { scope: :catalog_id }
Validations:
- Presence ({ if: :third_party_part_number_required? })
322 |
# File 'app/models/catalog_item.rb', line 322 validates :third_party_part_number, presence: { if: :third_party_part_number_required? } |
#third_party_sku ⇒ Object (readonly)
Validates third party sku, third party sku required.
Validations:
- Presence ({ if: :third_party_sku_required? })
324 |
# File 'app/models/catalog_item.rb', line 324 validates :third_party_sku, presence: { if: :third_party_sku_required? } |
#third_party_sku_variant_id ⇒ Object (readonly)
Marketplace variant selector (e.g. Wayfair piid): a given variant must map to
at most one of our catalog items per catalog. allow_nil so the many single /
non-variant items (no selector) coexist — a blank selector is normalized to
nil (see the normalizes block below) so a form-submitted "" doesn't trip
this against another blank item. The partial DB index is the backstop.
Validations:
- Uniqueness ({ scope: :catalog_id })
- Allow_nil
330 |
# File 'app/models/catalog_item.rb', line 330 validates :third_party_sku_variant_id, uniqueness: { scope: :catalog_id }, allow_nil: true |
Class Method Details
.accessories ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are accessories. Active Record Scope
428 |
# File 'app/models/catalog_item.rb', line 428 scope :accessories, -> { with_item.where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_ACCESSORIES)) } |
.active ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are active. Active Record Scope
401 |
# File 'app/models/catalog_item.rb', line 401 scope :active, -> { where(state: %w[active active_hidden]) } |
.available_for_edi_feeds ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are available for edi feeds. Active Record Scope
450 |
# File 'app/models/catalog_item.rb', line 450 scope :available_for_edi_feeds, -> { available_for_edi_feeds_by_states(EDI_FEED_STATUSES) } |
.available_for_edi_feeds_by_states ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are available for edi feeds by states. Active Record Scope
454 455 456 457 |
# File 'app/models/catalog_item.rb', line 454 scope :available_for_edi_feeds_by_states, ->(states) { states_to_use = states.presence || EDI_FEED_STATUSES where(state: states_to_use) } |
.by_skus ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are by skus. Active Record Scope
407 |
# File 'app/models/catalog_item.rb', line 407 scope :by_skus, ->(*skus) { with_item.where(items: { sku: [skus].flatten.uniq.compact }) } |
.by_upcs ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are by upcs. Active Record Scope
408 |
# File 'app/models/catalog_item.rb', line 408 scope :by_upcs, ->(*upcs) { with_item.where(items: { upc: [upcs].flatten.uniq.compact }) } |
.calculate_discounted_price(price_before_discount, discount, max_discount = 1.0) ⇒ Object
Calculate discounted price.
597 598 599 600 601 |
# File 'app/models/catalog_item.rb', line 597 def self.calculate_discounted_price(price_before_discount, discount, max_discount = 1.0) price_before_discount ||= 0.0 discount_factor = 1.0 - [max_discount, discount].min (price_before_discount * discount_factor).round(2) end |
.catalog_items_for_select ⇒ Object
Catalog items for select.
581 582 583 |
# File 'app/models/catalog_item.rb', line 581 def self.catalog_items_for_select CatalogItem.includes([:catalog, { store_item: :item }]).order('catalogs.name,items.sku').map { |ci| ["#{ci.sku} - #{ci.name.first(30)} - #{ci.catalog.name}", ci.id] } end |
.complimentary_of ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are complimentary of. Active Record Scope
400 |
# File 'app/models/catalog_item.rb', line 400 scope :complimentary_of, ->(pl) { with_product_lines.where('product_lines.id' => pl.full_complimentary_product_line_ids) } |
.controls ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are controls. Active Record Scope
422 |
# File 'app/models/catalog_item.rb', line 422 scope :controls, -> { with_item.where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_CONTROLS)) } |
.coupons_for_future_promo_select ⇒ Object
Coupons for future promo select.
612 613 614 615 616 617 |
# File 'app/models/catalog_item.rb', line 612 def self.coupons_for_future_promo_select coupons = [] coupons += Coupon.active_and_future.tier1.where(calculation_type_goods: 'MX') coupons += Coupon.active_and_future.tier3 coupons.uniq.map { |c| [c.to_s, c.id] }.sort end |
.coupons_for_promo_select ⇒ Object
Coupons for promo select.
604 605 606 607 608 609 |
# File 'app/models/catalog_item.rb', line 604 def self.coupons_for_promo_select coupons = [] coupons += Coupon.active.tier1.where(calculation_type_goods: 'MX') coupons += Coupon.active.tier3 coupons.uniq.map { |c| [c.code, c.id] }.sort end |
.discontinued ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are discontinued. Active Record Scope
404 |
# File 'app/models/catalog_item.rb', line 404 scope :discontinued, -> { where(state: %w[discontinued]) } |
.edi_delta_feeds_for_partner_inventory_advice ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are edi delta feeds for partner inventory advice. Active Record Scope
459 460 461 462 463 464 465 466 467 468 469 |
# File 'app/models/catalog_item.rb', line 459 scope :edi_delta_feeds_for_partner_inventory_advice, ->(partner) { # Here we want to a delta query based on store_items.last_inventory_updated_at where(<<~SQL.squish, partner: partner) store_items.last_inventory_updated_at IS NULL OR (catalog_items.last_inventory_advice_sent_json ->> :partner) IS NULL OR ( store_items.last_inventory_updated_at::timestamptz >= (catalog_items.last_inventory_advice_sent_json ->> :partner)::timestamptz ) SQL } |
.edi_delta_feeds_for_partner_price_advice ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are edi delta feeds for partner price advice. Active Record Scope
470 471 472 473 474 475 476 477 478 479 480 |
# File 'app/models/catalog_item.rb', line 470 scope :edi_delta_feeds_for_partner_price_advice, ->(partner) { # Here we want to a delta query based on catalog_items.price_updated_at where(<<~SQL.squish, partner: partner) catalog_items.price_updated_at IS NULL OR (catalog_items.last_price_advice_sent_json ->> :partner) IS NULL OR ( catalog_items.price_updated_at::timestamptz >= (catalog_items.last_price_advice_sent_json ->> :partner)::timestamptz ) SQL } |
.for_edi_feeds ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are for edi feeds. Active Record Scope
451 |
# File 'app/models/catalog_item.rb', line 451 scope :for_edi_feeds, -> { available_for_edi_feeds.with_item.ordered_by_sku } |
.for_edi_feeds_by_states ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are for edi feeds by states. Active Record Scope
458 |
# File 'app/models/catalog_item.rb', line 458 scope :for_edi_feeds_by_states, ->(states) { available_for_edi_feeds_by_states(states).with_item.ordered_by_sku } |
.for_google_feed ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are for google feed. Active Record Scope
438 |
# File 'app/models/catalog_item.rb', line 438 scope :for_google_feed, -> { for_online_catalog } |
.for_online_catalog ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are for online catalog. Active Record Scope
434 435 436 437 |
# File 'app/models/catalog_item.rb', line 434 scope :for_online_catalog, -> { public_catalog_items .merge(Item.goods.non_publications) } |
.for_online_catalog_or_non_web_accessible_with_successor_item ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are for online catalog or non web accessible with successor item. Active Record Scope
442 443 444 445 446 447 448 |
# File 'app/models/catalog_item.rb', line 442 scope :for_online_catalog_or_non_web_accessible_with_successor_item, -> { ids = for_online_catalog.ids successor_item_ids = non_web_accessible.has_successor_item.pluck(Arel.sql('distinct items.successor_item_id')) successor_item_skus = Item.active.where(id: successor_item_ids).pluck(:sku) successor_item_cis = by_skus(successor_item_skus).where(state: 'active').ids where(catalog_items: { id: (ids + successor_item_cis).uniq }) } |
.for_orchestrator_keys ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are for orchestrator keys. Active Record Scope
452 |
# File 'app/models/catalog_item.rb', line 452 scope :for_orchestrator_keys, ->(orchestrator_keys) { where(state: ORCHESTRATOR_STATES).with_item.ordered_by_sku.joins(:catalog).merge(Catalog.where(orchestrator_key: orchestrator_keys)) } |
.for_product_category_path ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are for product category path. Active Record Scope
417 418 419 |
# File 'app/models/catalog_item.rb', line 417 scope :for_product_category_path, ->(slug_path) { slug_path.blank? ? none : with_item.where(Item[:pc_path_slugs].ltree_descendant(slug_path)) } |
.for_product_line_path ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are for product line path. Active Record Scope
414 415 416 |
# File 'app/models/catalog_item.rb', line 414 scope :for_product_line_path, ->(slug_path) { slug_path.blank? ? none : with_item.where(Item[:primary_pl_path_slugs].ltree_descendant(slug_path)) } |
.for_where_to_buy_list ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are for where to buy list. Active Record Scope
495 496 497 |
# File 'app/models/catalog_item.rb', line 495 scope :for_where_to_buy_list, ->(parent_catalog_id) { joins(:catalog).where(state: 'active').where.not(catalog_id: [1, 2, 74]).where.not(url: nil).where(Catalog[:parent_catalog_id].eq(parent_catalog_id)).order(:position, Catalog[:name]).pluck(Arel.sql('COALESCE(catalogs.public_name, catalogs.name), catalog_items.url')) } |
.has_successor_item ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are has successor item. Active Record Scope
441 |
# File 'app/models/catalog_item.rb', line 441 scope :has_successor_item, -> { with_item.where.not(items: { successor_item_id: nil }) } |
.heating_elements ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are heating elements. Active Record Scope
421 |
# File 'app/models/catalog_item.rb', line 421 scope :heating_elements, -> { with_item.where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_HEATING_ELEMENTS)) } |
.hidden_from_catalog ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are hidden from catalog. Active Record Scope
481 |
# File 'app/models/catalog_item.rb', line 481 scope :hidden_from_catalog, -> { where(state: HIDDEN_STATES) } |
.in_stock ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are in stock. Active Record Scope
485 |
# File 'app/models/catalog_item.rb', line 485 scope :in_stock, -> { with_item.where('store_items.qty_available - COALESCE(catalog_items.reserve_stock, 0) > 0 OR items.always_available_online IS TRUE') } |
.in_stock_with_alternate_warehouse_store_items ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are in stock with alternate warehouse store items. Active Record Scope
486 487 488 489 490 491 |
# File 'app/models/catalog_item.rb', line 486 scope :in_stock_with_alternate_warehouse_store_items, -> { with_item.where("(store_items.qty_available + (select round(coalesce(sum(si.qty_available),0),0) from store_items si inner join stores s on si.store_id = s.id and s.owner = 'warmlyyours' where si.store_id <> store_items.store_id and si.item_id = store_items.item_id and si.location = 'AVAILABLE')) - COALESCE(catalog_items.reserve_stock, 0) > 0 OR items.always_available_online is true") } |
.inactive ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are inactive. Active Record Scope
402 |
# File 'app/models/catalog_item.rb', line 402 scope :inactive, -> { where.not(state: %w[active active_hidden]) } |
.install_kits ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are install kits. Active Record Scope
431 |
# File 'app/models/catalog_item.rb', line 431 scope :install_kits, -> { for_product_line_path(LtreePaths::PL_FLOOR_HEATING_TEMPZONE_INSTALLATION_KITS).merge(accessories) } |
.insulations ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are insulations. Active Record Scope
429 |
# File 'app/models/catalog_item.rb', line 429 scope :insulations, -> { with_item.where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_ACCESSORIES_INSULATIONS)) } |
.integration_kits ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are integration kits. Active Record Scope
427 |
# File 'app/models/catalog_item.rb', line 427 scope :integration_kits, -> { with_item.where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_POWER)).where(Item[:primary_pl_path_slugs].ltree_descendant(LtreePaths::PL_FLOOR_HEATING_CONTROL_INTEGRATION)) } |
.last_custom_mat_catalog_item(catalog_id, volts) ⇒ Object
Last custom mat catalog item.
1339 1340 1341 1342 1343 1344 1345 1346 |
# File 'app/models/catalog_item.rb', line 1339 def self.last_custom_mat_catalog_item(catalog_id, volts) for_product_category_path(LtreePaths::PC_HEATING_ELEMENTS) .for_product_line_path(LtreePaths::PL_FLOOR_HEATING_TEMPZONE_CUSTOM_MAT) .where(catalog_id:) .where.not(items: { supplier_item_id: nil }) .where('items.rendered_product_specifications @> ?', { voltage: { raw: volts } }.to_json) .order('items.created_at DESC').first end |
.main_catalogs ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are main catalogs. Active Record Scope
449 |
# File 'app/models/catalog_item.rb', line 449 scope :main_catalogs, -> { where(catalog_id: Catalog.main_catalog_ids) } |
.non_publications ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are non publications. Active Record Scope
395 |
# File 'app/models/catalog_item.rb', line 395 scope :non_publications, -> { with_item.merge(Item.goods.non_publications) } |
.non_web_accessible ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are non web accessible. Active Record Scope
439 |
# File 'app/models/catalog_item.rb', line 439 scope :non_web_accessible, -> { main_catalogs.with_item.merge(Item.goods.non_publications).where('(items.is_discontinued IS TRUE) OR (store_items.is_discontinued IS TRUE) OR (catalog_items.state IN (?))', HIDDEN_STATES) } |
.not_discontinued ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are not discontinued. Active Record Scope
403 |
# File 'app/models/catalog_item.rb', line 403 scope :not_discontinued, -> { where.not(state: %w[discontinued]) } |
.not_hidden_from_catalog ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are not hidden from catalog. Active Record Scope
482 |
# File 'app/models/catalog_item.rb', line 482 scope :not_hidden_from_catalog, -> { where.not(state: HIDDEN_STATES) } |
.not_pending_onboarding ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are not pending onboarding. Active Record Scope
405 |
# File 'app/models/catalog_item.rb', line 405 scope :not_pending_onboarding, -> { where.not(state: %w[pending_onboarding]) } |
.order_by_spec ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are order by spec. Active Record Scope
494 |
# File 'app/models/catalog_item.rb', line 494 scope :order_by_spec, ->(spec, datatype = 'varchar', direction = 'asc') { joins(store_item: :item).merge(Item.order_by_spec(spec, datatype, direction)) } |
.ordered_by_sku ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are ordered by sku. Active Record Scope
397 |
# File 'app/models/catalog_item.rb', line 397 scope :ordered_by_sku, -> { with_item.order(Item[:sku]) } |
.out_of_stock ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are out of stock. Active Record Scope
492 |
# File 'app/models/catalog_item.rb', line 492 scope :out_of_stock, -> { with_item.where('store_items.qty_available - COALESCE(catalog_items.reserve_stock, 0) <= 0 AND items.always_available_online IS NOT TRUE') } |
.pending_onboarding ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are pending onboarding. Active Record Scope
406 |
# File 'app/models/catalog_item.rb', line 406 scope :pending_onboarding, -> { where(state: %w[pending_onboarding]) } |
.power_modules ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are power modules. Active Record Scope
426 |
# File 'app/models/catalog_item.rb', line 426 scope :power_modules, -> { with_item.where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_POWER_MODULES)) } |
.primary_catalogs_first ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are primary catalogs first. Active Record Scope
371 372 373 374 |
# File 'app/models/catalog_item.rb', line 371 scope :primary_catalogs_first, -> { in_order_of(:catalog_id, CatalogConstants::ALL_MAIN_CATALOG_IDS, filter: false) .order(Arel.sql('catalogs.name ASC')) } |
.primary_catalogs_first_then_by_state_and_name ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are primary catalogs first then by state and name. Active Record Scope
375 376 377 378 379 |
# File 'app/models/catalog_item.rb', line 375 scope :primary_catalogs_first_then_by_state_and_name, -> { in_order_of(:catalog_id, CatalogConstants::ALL_MAIN_CATALOG_IDS, filter: false) .in_order_of(:state, %w[active pending_client_review pending_onboarding require_vendor_update pending_vendor_update pending_discontinue active_hidden discontinued invalid_catalog_item], filter: false) .order(Arel.sql('catalogs.name ASC')) } |
.probeable ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are probeable. Active Record Scope
388 389 390 391 |
# File 'app/models/catalog_item.rb', line 388 scope :probeable, lambda { where(skip_url_checks: false) .where(probe_backoff_until: [nil, ..Time.current]) } |
.public_catalog_items ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are public catalog items. Active Record Scope
409 |
# File 'app/models/catalog_item.rb', line 409 scope :public_catalog_items, -> { with_item.merge(StoreItem.active).active.not_hidden_from_catalog } |
.relay_panels ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are relay panels. Active Record Scope
424 |
# File 'app/models/catalog_item.rb', line 424 scope :relay_panels, -> { with_item.where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_POWER_RELAY_PANELS)) } |
.sensors ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are sensors. Active Record Scope
425 |
# File 'app/models/catalog_item.rb', line 425 scope :sensors, -> { with_item.where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_SENSORS)) } |
.spare_parts ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are spare parts. Active Record Scope
432 |
# File 'app/models/catalog_item.rb', line 432 scope :spare_parts, -> { with_item.where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_SPARE_PARTS)) } |
.state_options_for_select(show_all_states = false) ⇒ Array<Array>
Returns [label, id] pairs for select dropdowns.
587 588 589 590 591 |
# File 'app/models/catalog_item.rb', line 587 def self.(show_all_states = false) state_syms = CatalogItem.state_machines[:state].states.map { |s| s.name.to_sym } state_syms -= [:discontinued] unless show_all_states state_syms.map { |s| [s.to_s.humanize, s.to_s] }.sort # discontinued is only for Heatwave bg job end |
.thermostats ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are thermostats. Active Record Scope
423 |
# File 'app/models/catalog_item.rb', line 423 scope :thermostats, -> { with_item.where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_CONTROLS_THERMOSTATS)) } |
.towel_warmers ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are towel warmers. Active Record Scope
430 |
# File 'app/models/catalog_item.rb', line 430 scope :towel_warmers, -> { with_item.where(Item[:pc_path_slugs].ltree_descendant(LtreePaths::PC_TOWEL_WARMERS)) } |
.web_accessible ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are web accessible. Active Record Scope
440 |
# File 'app/models/catalog_item.rb', line 440 scope :web_accessible, -> { main_catalogs.with_item.merge(Item.goods.non_publications).where('(items.is_discontinued IS FALSE) AND (store_items.is_discontinued IS FALSE) AND NOT(catalog_items.state IN (?))', HIDDEN_STATES) } |
.with_item ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are with item. Active Record Scope
393 |
# File 'app/models/catalog_item.rb', line 393 scope :with_item, -> { joins(store_item: :item) } |
.with_item_and_classifications ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are with item and classifications. Active Record Scope
394 |
# File 'app/models/catalog_item.rb', line 394 scope :with_item_and_classifications, -> { joins(store_item: { item: %i[primary_product_line product_category] }).includes(store_item: { item: %i[primary_product_line product_category] }) } |
.with_item_condition_new ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are with item condition new. Active Record Scope
396 |
# File 'app/models/catalog_item.rb', line 396 scope :with_item_condition_new, -> { with_item.merge(Item.active.condition_new) } |
.with_product_category ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are with product category. Active Record Scope
399 |
# File 'app/models/catalog_item.rb', line 399 scope :with_product_category, -> { includes(store_item: { item: :product_category }).references(store_item: { item: :product_category }) } |
.with_product_lines ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are with product lines. Active Record Scope
398 |
# File 'app/models/catalog_item.rb', line 398 scope :with_product_lines, -> { includes(store_item: { item: :product_lines }).references(store_item: { item: :product_lines }) } |
.with_product_specification ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are with product specification. Active Record Scope
493 |
# File 'app/models/catalog_item.rb', line 493 scope :with_product_specification, ->(token, value, grouping = nil) { with_item.merge(Item.with_product_specification(token, value, grouping)) } |
.with_third_party_number ⇒ ActiveRecord::Relation<CatalogItem>
A relation of CatalogItems that are with third party number. Active Record Scope
433 |
# File 'app/models/catalog_item.rb', line 433 scope :with_third_party_number, -> { where.not(third_party_part_number: [nil, '']) } |
Instance Method Details
#all_amazon_image_profiles ⇒ Object
Alias for
to: :item#all_amazon_image_profiles
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#alternate_warehouse_stock_fraction_overridden? ⇒ Boolean
Whether this item overrides the catalog's alternate-warehouse share.
1100 1101 1102 |
# File 'app/models/catalog_item.rb', line 1100 def alternate_warehouse_stock_fraction_overridden? alternate_warehouse_stock_fraction.present? end |
#alternate_warehouse_store_items ⇒ Object
Alternate warehouse store items.
1256 1257 1258 |
# File 'app/models/catalog_item.rb', line 1256 def alternate_warehouse_store_items StoreItem.where(item_id: store_item.item_id).where.not(store_id: store_item.store_id).available.joins(:store).merge(Store.warmlyyours_warehouses) end |
#amazon_browse_nodes ⇒ ActiveRecord::Relation<AmazonBrowseNode>
Returns the associated amazon browse nodes.
259 |
# File 'app/models/catalog_item.rb', line 259 has_and_belongs_to_many :amazon_browse_nodes |
#amazon_catalog_item_flags ⇒ ActiveRecord::Relation<AmazonCatalogItemFlag>
Amazon catalog item flags.
247 |
# File 'app/models/catalog_item.rb', line 247 has_many :amazon_catalog_item_flags, dependent: :destroy |
#amazon_marketplace ⇒ AmazonMarketplace
Amazon marketplace.
237 |
# File 'app/models/catalog_item.rb', line 237 has_one :amazon_marketplace, through: :catalog |
#amazon_sales_rank_data_points ⇒ ActiveRecord::Relation<AmazonSalesRankDataPoint>
Amazon sales rank data points.
249 |
# File 'app/models/catalog_item.rb', line 249 has_many :amazon_sales_rank_data_points, dependent: :delete_all |
#amazon_variant_catalog_items ⇒ Object
Amazon variant catalog items.
1441 1442 1443 1444 1445 1446 1447 |
# File 'app/models/catalog_item.rb', line 1441 def amazon_variant_catalog_items # Siblings sharing THIS catalog_item's per-marketplace Amazon parent # listing (VariantGroup amazon row), within the same catalog. return CatalogItem.none unless variant_group_id catalog_items_in_same_catalog.where(variant_group_id:).includes(:item) end |
#amount_currency_symbol ⇒ Object
Amount currency symbol.
939 940 941 |
# File 'app/models/catalog_item.rb', line 939 def amount_currency_symbol catalog.currency_symbol end |
#available_in_edi_feed? ⇒ Boolean
Returns whether the record available in edi feed.
840 841 842 |
# File 'app/models/catalog_item.rb', line 840 def available_in_edi_feed? EDI_FEED_STATUSES.include?(state) end |
#available_stores ⇒ Object
Available stores.
825 826 827 |
# File 'app/models/catalog_item.rb', line 825 def available_stores Store.where(company_id: catalog.company_id).order(:name) end |
#bom_price ⇒ Object
Bom price.
929 930 931 |
# File 'app/models/catalog_item.rb', line 929 def bom_price retail_price || amount end |
#calculate_minimum_price_for_margin(target_margin) ⇒ Object
Provide a target margin below a 100
671 672 673 674 675 676 677 678 |
# File 'app/models/catalog_item.rb', line 671 def calculate_minimum_price_for_margin(target_margin) return unless unit_cogs&.positive? && target_margin # skip non-positive unit_cogs raise ArgumentError, 'Target margin must be less than 100%' if target_margin >= 100 minimum_price = unit_cogs / (1 - (target_margin / 100.0)) minimum_price.round(2) # Round to 2 decimal places for currency formatting end |
#calculate_profit_margin(price) ⇒ Object
Calculate profit margin.
662 663 664 665 666 667 |
# File 'app/models/catalog_item.rb', line 662 def calculate_profit_margin(price) return unless unit_cogs && price profit = (price - unit_cogs) ((profit / price) * 100).round(2) end |
#calculate_sale_price(percentage_off, include_vat: false, from_msrp: false) ⇒ Object
Calculates the sale price based on a percentage off (0-100), specify also if price with VAT should be used as the starting amount
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 |
# File 'app/models/catalog_item.rb', line 1489 def calculate_sale_price(percentage_off, include_vat: false, from_msrp: false) return nil unless percentage_off.present? && percentage_off.positive? raise 'sale price percentage off must be between 0 and 100' unless (1..100).cover?(percentage_off) starting_amount = if from_msrp if include_vat msrp_with_vat else msrp end elsif include_vat price_with_vat else amount end if percentage_off.nil? && starting_amount.nil? msg = "calculate_sale_price: Catalog Item ID #{id} cannot have sale price calculated, percentage off: #{percentage_off}, starting_amount: #{starting_amount}" ErrorReporting.warning(msg) logger.warn msg return nil end calculated_sale_price = (starting_amount * (1.0 - (percentage_off.to_f / 100))).round(2) calculated_sale_price /= (1.0 + tax_rate) if include_vat && tax_rate calculated_sale_price.round(2) end |
#calculated_updated_price_from_parent ⇒ Object
Calculated updated price from parent.
1401 1402 1403 1404 1405 1406 1407 1408 1409 |
# File 'app/models/catalog_item.rb', line 1401 def calculated_updated_price_from_parent return unless (parent_amount = parent_catalog_item&.amount) # Determine discount to use discount = parent_catalog_discount || 0.0 return unless discount.between?(0.0, 1.0) (parent_amount * (1.0 - discount)).round(2) end |
#catalog ⇒ Catalog
Catalog.
220 |
# File 'app/models/catalog_item.rb', line 220 belongs_to :catalog, touch: true, inverse_of: :catalog_items |
#catalog_discount_human ⇒ Object
Catalog discount human.
1009 1010 1011 1012 1013 |
# File 'app/models/catalog_item.rb', line 1009 def catalog_discount_human return unless catalog.parent_catalog_discount "#{(catalog.parent_catalog_discount * 100).round(2)} %" end |
#catalog_items_in_same_catalog(starting_scope = nil) ⇒ Object
Catalog items in same catalog.
1430 1431 1432 1433 1434 1435 1436 1437 1438 |
# File 'app/models/catalog_item.rb', line 1430 def catalog_items_in_same_catalog(starting_scope = nil) starting_scope ||= CatalogItem.active CatalogItem.includes(:catalog) .where(catalog_id:) .with_item .with_product_lines .with_product_category .order(ProductCategory[:priority], CatalogItem[:amount]) end |
#check_for_price_update ⇒ Object (protected)
Check for price update.
1665 1666 1667 1668 1669 1670 |
# File 'app/models/catalog_item.rb', line 1665 def check_for_price_update return unless amount_changed? self.price_updated_at = Time.current self.old_amount = amount_was end |
#check_kit_components_available ⇒ Object (protected)
Check kit components available.
1638 1639 1640 1641 1642 |
# File 'app/models/catalog_item.rb', line 1638 def check_kit_components_available return unless missing_kit_components? errors.add(:base, 'All kit components must be present in the target catalog') end |
#check_ok_to_destroy ⇒ Object (protected)
Check ok to destroy.
1660 1661 1662 |
# File 'app/models/catalog_item.rb', line 1660 def check_ok_to_destroy errors.add(:base, 'Cannot destroy because there are dependent line_items, please discontinue instead') unless ok_to_destroy? end |
#check_store_item_is_available ⇒ Object (protected)
Check store item is available.
1633 1634 1635 |
# File 'app/models/catalog_item.rb', line 1633 def check_store_item_is_available errors.add(:store_item_id, 'Store Item must be part of an available location') if store_item && !store_item.available_location? end |
#check_upc ⇒ Object (protected)
Check upc.
1704 1705 1706 1707 1708 1709 1710 |
# File 'app/models/catalog_item.rb', line 1704 def check_upc # An active public item needs a upc return unless upc_required? && item.upc.blank? item.update_upc = true item.save end |
#complete_state ⇒ Object
Complete state.
924 925 926 |
# File 'app/models/catalog_item.rb', line 924 def complete_state "#{human_state_name} - #{HIDDEN_STATES.include?(state) ? 'not public' : 'public'}" end |
#content_locales_to_render ⇒ Object
Content locales to render.
1354 1355 1356 |
# File 'app/models/catalog_item.rb', line 1354 def content_locales_to_render (Mobility.available_locales & ([I18n.default_locale] + (catalog.locales || []))).uniq.sort end |
#coupon ⇒ Coupon
Coupon.
222 |
# File 'app/models/catalog_item.rb', line 222 belongs_to :coupon, optional: true |
#create_component_catalog_items ⇒ Object
Create component catalog items.
681 682 683 684 685 686 687 688 |
# File 'app/models/catalog_item.rb', line 681 def create_component_catalog_items store_id = store_item.store_id location = store_item.location item.kit_components.each do |component_item| create_single_component_in_catalog_item(store_id, location, component_item) end true end |
#create_single_component_in_catalog_item(store_id, location, component_item) ⇒ Object
Create single component in catalog item.
694 695 696 697 698 699 700 701 702 703 704 |
# File 'app/models/catalog_item.rb', line 694 def create_single_component_in_catalog_item(store_id, location, component_item) si = StoreItem.where(item_id: component_item.id, store_id:, location:).first si = StoreItem.create!(item_id: component_item.id, store_id:, location:, qty_on_hand: 0, qty_committed: 0, handling_charge: 0, unit_cogs: 0) if si.nil? existing = CatalogItem.where(catalog_id:, store_item_id: si.id).first return existing if existing.present? # Catalog Item already exists parent_catalog_item = parent_catalog.nil? ? nil : CatalogItem.where(catalog: parent_catalog, store_item: si).first price = parent_catalog_item&.amount price *= (1 - parent_catalog_discount) if price && parent_catalog_discount CatalogItem.create!(catalog_id:, store_item_id: si.id, amount: price || 0, max_discount: 100, state: 'active_hidden') end |
#currency ⇒ Object
Alias for Catalog#currency
265 266 267 268 269 |
# File 'app/models/catalog_item.rb', line 265 delegate :parent_catalog, :currency, :currency_symbol, :tax_rate, to: :catalog |
#currency_symbol ⇒ Object
Alias for Catalog#currency_symbol
265 266 267 268 269 |
# File 'app/models/catalog_item.rb', line 265 delegate :parent_catalog, :currency, :currency_symbol, :tax_rate, to: :catalog |
#deep_dup ⇒ Object
Returns a deep-copied, unsaved duplicate of the record.
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'app/models/catalog_item.rb', line 162 def deep_dup deep_clone(except: %i[third_party_part_number third_party_promo_part_number third_party_sku new_price new_price_effective_date coupon_id sale_price new_coupon_id new_sale_price price_updated_at parent_sku clearance]) do |_original, copy| # clearance is NOT NULL with a false default; reset the clone to false # rather than nil, which would violate the constraint on save. copy.clearance = false end end |
#dependent_catalog_items(filtered_by_catalog_id = nil) ⇒ Object
Find all similar catalog items in the child catalogs, optionally filter only
to one specific catalog
1041 1042 1043 1044 1045 |
# File 'app/models/catalog_item.rb', line 1041 def dependent_catalog_items(filtered_by_catalog_id = nil) catalog.children.select { |c| filtered_by_catalog_id.nil? || c.id == filtered_by_catalog_id }.map do |child_cat| child_cat.catalog_items.where(store_item_id:) end.flatten.uniq end |
#discounted_price(customer = nil, target = nil) ⇒ Object
Discounted price.
1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 |
# File 'app/models/catalog_item.rb', line 1323 def discounted_price(customer = nil, target = nil) discount = (begin target.pricing_program_discount_factor rescue StandardError nil end) || (begin customer.pricing_program_discount_factor rescue StandardError nil end) CatalogItem.calculate_discounted_price(amount, discount, 1.0) end |
#diverging_current_price_vs_proposed_price? ⇒ Boolean
Returns whether the record diverging current price vs proposed price.
620 621 622 623 624 625 |
# File 'app/models/catalog_item.rb', line 620 def diverging_current_price_vs_proposed_price? return false unless parent_catalog_discount return false unless (cup = calculated_updated_price_from_parent) amount != cup end |
#edi_communication_logs ⇒ ActiveRecord::Relation<EdiCommunicationLog>
Edi communication logs.
245 |
# File 'app/models/catalog_item.rb', line 245 has_many :edi_communication_logs, through: :edi_documents |
#edi_customer_ids_for_catalog ⇒ Object
Returns the list of EDI customer IDs for this catalog, cached for reuse
860 861 862 |
# File 'app/models/catalog_item.rb', line 860 def edi_customer_ids_for_catalog @edi_customer_ids_for_catalog ||= catalog.customers.where(id: Edi::BaseOrchestrator.customer_ids_edi_enabled).ids end |
#edi_documents ⇒ ActiveRecord::Relation<EdiDocument>
Edi documents.
243 |
# File 'app/models/catalog_item.rb', line 243 has_many :edi_documents, dependent: :destroy |
#edi_offer_new_sale_price ⇒ Object
Edi offer new sale price.
818 819 820 821 822 |
# File 'app/models/catalog_item.rb', line 818 def edi_offer_new_sale_price return new_sale_price_with_vat if tax_rate.present? new_sale_price.to_f.positive? ? new_sale_price : nil end |
#edi_offer_price ⇒ Object
EDI offer prices. European Mirakl partners carry a catalog tax_rate and
list VAT-inclusive, so the *_with_vat helpers apply. Tax-exclusive
markets (e.g. Best Buy Canada — CAD, no VAT) have a nil tax_rate, which
makes those helpers return nil; for those, fall back to the plain pre-tax
price/sale so the offer feed isn't sent priceless.
806 807 808 |
# File 'app/models/catalog_item.rb', line 806 def edi_offer_price tax_rate.present? ? price_with_vat : amount end |
#edi_offer_sale_price ⇒ Object
Edi offer sale price.
811 812 813 814 815 |
# File 'app/models/catalog_item.rb', line 811 def edi_offer_sale_price return sale_price_with_vat if tax_rate.present? sale_price.to_f.positive? ? sale_price : nil end |
#edi_orchestrators_for_catalog ⇒ Object
Returns orchestrators for EDI customers in this catalog, cached for reuse
Uses the class-level orchestrator cache for fast lookups
866 867 868 869 870 |
# File 'app/models/catalog_item.rb', line 866 def edi_orchestrators_for_catalog @edi_orchestrators_for_catalog ||= edi_customer_ids_for_catalog.filter_map do |customer_id| Edi::BaseOrchestrator.orchestrator_for_customer_id(customer_id) end.uniq(&:partner) end |
#edi_partner_sku ⇒ Object
Edi partner sku.
714 715 716 |
# File 'app/models/catalog_item.rb', line 714 def edi_partner_sku third_party_part_number.presence || third_party_sku.presence || sku end |
#effective_alternate_warehouse_stock_fraction ⇒ Float
Resolves the alternate-warehouse share reported externally, as a decimal.
Logic Details
For EXTERNAL reporting only (feeds, website, EDI) — never internal CRM
figures, which use #real_stock. Resolution is most-specific-first: this
item's nullable integer-percent override, then the catalog's channel-wide
percentage. New catalogs explicitly persist 0% when it is not configured.
1094 1095 1096 |
# File 'app/models/catalog_item.rb', line 1094 def effective_alternate_warehouse_stock_fraction (alternate_warehouse_stock_fraction || catalog.alternate_warehouse_stock_fraction) / 100.0 end |
#effective_seo_description(char_limit: nil) ⇒ Object
Effective seo description.
917 918 919 920 921 |
# File 'app/models/catalog_item.rb', line 917 def effective_seo_description(char_limit: nil) d = seo_description.presence || item.effective_seo_description(char_limit:) || +'' d << " (#{item.sku})" unless Regexp.new(item.sku).match(d) d end |
#european? ⇒ Boolean
Returns whether the record european.
1080 1081 1082 |
# File 'app/models/catalog_item.rb', line 1080 def european? catalog.country&.eu_country? || false end |
#exported_catalog_item ⇒ ExportedCatalogItem
Exported catalog item.
233 |
# File 'app/models/catalog_item.rb', line 233 has_one :exported_catalog_item, dependent: :delete |
#get_successor_online_catalog_item ⇒ Object
Get successor online catalog item.
1573 1574 1575 1576 |
# File 'app/models/catalog_item.rb', line 1573 def get_successor_online_catalog_item res = catalog.catalog_items.for_online_catalog.by_skus(item.successor_item.sku).first if !item_is_web_accessible? && item.successor_item.present? res end |
#in_google_feed? ⇒ Boolean
Returns whether the record in google feed.
1619 1620 1621 |
# File 'app/models/catalog_item.rb', line 1619 def in_google_feed? self.class.for_google_feed.exists?(id: id) end |
#in_hide_from_feed_state? ⇒ Boolean
Returns whether the record in hide from feed state.
1455 1456 1457 |
# File 'app/models/catalog_item.rb', line 1455 def in_hide_from_feed_state? HIDDEN_STATES.include?(state) end |
#in_main_catalog? ⇒ Boolean (protected)
Returns whether the record in main catalog.
1713 1714 1715 |
# File 'app/models/catalog_item.rb', line 1713 def in_main_catalog? Catalog.main_catalog_ids.include?(catalog_id) end |
#inventory_message_enabled? ⇒ Boolean
Returns whether the record inventory message enabled.
845 846 847 |
# File 'app/models/catalog_item.rb', line 845 def .present? end |
#inventory_message_processors ⇒ Object
Retrieves all inventory message processors for edi customers carrying this item
This can be used to push inventory at once to all channels for one item
874 875 876 877 878 |
# File 'app/models/catalog_item.rb', line 874 def edi_orchestrators_for_catalog.filter_map do |o| o. if o. && o.respond_to?(:inventory_message_processor) end end |
#is_available_to_public ⇒ Object
Alias for
to: :item#is_available_to_public
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#is_cable_accessory? ⇒ Object
Alias for
to: :item#is_cable_accessory?
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#is_cable_fit_guide? ⇒ Object
Alias for
to: :item#is_cable_fit_guide?
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#is_circuit_check? ⇒ Object
Alias for
to: :item#is_circuit_check?
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#is_control? ⇒ Object
Alias for
to: :item#is_control?
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#is_discontinued? ⇒ Boolean Also known as: is_discontinued
Returns whether the record is discontinued.
1579 1580 1581 |
# File 'app/models/catalog_item.rb', line 1579 def is_discontinued? discontinued? || pending_discontinue? end |
#is_heating_element? ⇒ Object
Alias for
to: :item#is_heating_element?
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#is_membrane? ⇒ Object
Alias for
to: :item#is_membrane?
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#is_publication? ⇒ Object
Alias for
to: :item#is_publication?
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#is_roughin_kit? ⇒ Object
Alias for
to: :item#is_roughin_kit?
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#is_snow_melt_plaque? ⇒ Object
Alias for
to: :item#is_snow_melt_plaque?
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#is_underlayment? ⇒ Object
Alias for
to: :item#is_underlayment?
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#item ⇒ Item
Item.
231 |
# File 'app/models/catalog_item.rb', line 231 has_one :item, through: :store_item, inverse_of: :catalog_items |
#item_is_web_accessible? ⇒ Boolean
Returns whether the record item is web accessible.
1566 1567 1568 1569 1570 |
# File 'app/models/catalog_item.rb', line 1566 def item_is_web_accessible? !in_hide_from_feed_state? && !item&.is_discontinued && !store_item&.is_discontinued end |
#line_items ⇒ ActiveRecord::Relation<LineItem>
Line items.
239 |
# File 'app/models/catalog_item.rb', line 239 has_many :line_items, dependent: :restrict_with_error |
#listing_issues ⇒ ActiveRecord::Relation<ListingIssue>
Listing issues.
251 |
# File 'app/models/catalog_item.rb', line 251 has_many :listing_issues, dependent: :destroy |
#listing_message_enabled? ⇒ Boolean
Returns whether the record listing message enabled.
855 856 857 |
# File 'app/models/catalog_item.rb', line 855 def .present? end |
#listing_message_processors ⇒ Object
Retrieves all listing message processors for edi customers carrying this item
This can be used to patch listing data at once to all channels for one item
892 893 894 895 896 |
# File 'app/models/catalog_item.rb', line 892 def edi_orchestrators_for_catalog.filter_map do |o| o. if o.respond_to?(:listing_message_processor) && o. end end |
#map_difference ⇒ Object
Difference between retail price and MAP (negative = violation)
1032 1033 1034 1035 1036 |
# File 'app/models/catalog_item.rb', line 1032 def map_difference return unless retail_price.present? && map_price.present? (retail_price - map_price).round(2) end |
#map_price ⇒ Object
MAP (Minimum Advertised Price) based on catalog's map_percentage setting
Only meaningful for vendor catalogs where retailer controls pricing
1017 1018 1019 1020 1021 |
# File 'app/models/catalog_item.rb', line 1017 def map_price return if msrp.blank? (msrp * catalog.map_percentage).round(2) end |
#map_to_stores(store_ids) ⇒ Object
This method creates a link between a catalog item and additional stores
It is useful for amazon FBA where we keep track of the stock in amazon's warehouses
Simply pass an array an store id to map the catalog item to and it will do the rest
Note that this method never removes the primary store item for a catalog item
747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 |
# File 'app/models/catalog_item.rb', line 747 def map_to_stores(store_ids) return if store_ids.blank? store_ids.uniq.each do |sid| s = Store.find(sid) # If store id already exist, nothing to do next if store_items.find { |si| si.store_id == s.id } # if not quick build if catalog.company_id != s.company_id errors.add(:base, "Store #{s.id} #{s.short_name} does not belong to same company as catalog item #{id}") elsif (si = StoreItem.where(store_id: s.id, item_id: item.id, location: 'AVAILABLE').first) # See if we already have this si # Then link it store_items << si else # Create a new one (includes implicit linking) store_items.create(store_id: s.id, item_id: item.id, qty_on_hand: 0, qty_committed: 0, unit_cogs: amount, location: 'AVAILABLE') end end # Any stores to remove? store_items.available.each do |si| next if store_item.store_id == si.store_id # The primary store is never removable next if si.store_id.in?(store_ids) # This store is not in the selected list, try to delete it errors.add(:base, "Store Item #{si.id} cannot be deleted due to the presence of stock") unless si.ok_to_destroy? && si.destroy end store_items end |
#map_violation? ⇒ Boolean
Check if the retailer's current price is below MAP
1024 1025 1026 1027 1028 1029 |
# File 'app/models/catalog_item.rb', line 1024 def map_violation? return false unless catalog.retailer_type_vendor? return false unless retail_price.present? && map_price.present? retail_price < map_price end |
#missing_kit_components? ⇒ Boolean
Returns whether the record missing kit components.
1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 |
# File 'app/models/catalog_item.rb', line 1542 def missing_kit_components? return false unless item&.is_kit? kit_component_item_skus = item.kit_components.pluck(:sku) kit_components_count = kit_component_item_skus.size catalog_items = CatalogItem.joins(store_item: :item).where(catalog_id:, items: { sku: kit_component_item_skus }, store_items: { location: store_item.location }) catalog_item_skus = catalog_items.pluck(:sku) catalog_items_count = catalog_item_skus.size if kit_components_count == catalog_items_count false else logger.error "Missing kit components in catalog #{catalog_id} for #{item.sku}: #{kit_component_item_skus - catalog_item_skus}" true end end |
#msrp ⇒ Object
Msrp.
990 991 992 |
# File 'app/models/catalog_item.rb', line 990 def msrp root_catalog_item.try(:amount) end |
#msrp_with_vat ⇒ Object
Msrp with vat.
995 996 997 998 999 |
# File 'app/models/catalog_item.rb', line 995 def msrp_with_vat return if tax_rate.blank? root_catalog_item.try(:price_with_vat) end |
#name(locale: nil) ⇒ String
Returns the name of the record.
1361 1362 1363 1364 1365 1366 1367 1368 |
# File 'app/models/catalog_item.rb', line 1361 def name(locale: nil) I18n.with_locale(locale || I18n.locale) do n = third_party_name.presence n ||= item.amazon_title if amazon_catalog_item? n ||= item&.public_name n end end |
#new_coupon ⇒ Coupon
New coupon.
224 |
# File 'app/models/catalog_item.rb', line 224 belongs_to :new_coupon, optional: true, class_name: 'Coupon' |
#new_sale_price_with_vat ⇒ Object
New sale price with vat.
793 794 795 796 797 798 799 |
# File 'app/models/catalog_item.rb', line 793 def new_sale_price_with_vat return if tax_rate.blank? res = ((tax_rate + 1) * (new_sale_price || 0.0)).round(2) res = nil if res == 0.0 res end |
#next_available(use_store_item: nil, use_alternate_warehouse: false) ⇒ Object
Returns next available for internal CRM use (100% real stock)
discontinued and pending discontinue item will report nil for next available
1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 |
# File 'app/models/catalog_item.rb', line 1200 def next_available(use_store_item: nil, use_alternate_warehouse: false) return nil if discontinued? || pending_discontinue? use_store_item ||= store_item next_available_hsh_arr = [use_store_item.next_available] if use_alternate_warehouse # What's next available in the other warehouse? Include 100% (real stock) with 1 week delay alternate_warehouse_store_items.each do |alternate_si| alt_next_available_hsh = alternate_si.next_available if alt_next_available_hsh.present? alt_next_available_hsh = OpenStruct.new(next_available_qty: alt_next_available_hsh[:next_available_qty], next_available_date: (alt_next_available_hsh[:next_available_date] + 1.week)).freeze end next_available_hsh_arr << alt_next_available_hsh end end # here we discard all the nil entries and sort by next available date, returning the first entry, i.e. the next available next_available_entry_to_use = next_available_hsh_arr.compact.min_by { |h| h[:next_available_date] } return nil if next_available_entry_to_use.blank? next_available_entry_to_use end |
#next_available_by_warehouse(use_alternate_warehouse: false) ⇒ Object
Next available by warehouse.
1284 1285 1286 1287 1288 |
# File 'app/models/catalog_item.rb', line 1284 def next_available_by_warehouse(use_alternate_warehouse: false) store_items_by_warehouse.to_h do |si| [si.store.name, next_available(use_store_item: si, use_alternate_warehouse:)] end end |
#next_available_by_warehouse_with_depth_limit(use_alternate_warehouse: false, max_depth: 10, current_depth: 0) ⇒ Object
Version with depth limit to prevent infinite recursion
1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 |
# File 'app/models/catalog_item.rb', line 1294 def next_available_by_warehouse_with_depth_limit(use_alternate_warehouse: false, max_depth: 10, current_depth: 0) # Prevent infinite recursion if current_depth >= max_depth Rails.logger.warn "Maximum depth limit (#{max_depth}) reached for catalog item #{id} (#{item&.sku}). Possible circular reference in kit structure." return {} end store_items_by_warehouse.to_h do |si| [si.store.name, next_available_with_depth_limit(use_store_item: si, use_alternate_warehouse:, max_depth:, current_depth: current_depth + 1)] end end |
#next_available_with_depth_limit(use_store_item: nil, use_alternate_warehouse: false, max_depth: 10, current_depth: 0) ⇒ Object
Version with depth limit to prevent infinite recursion (100% real stock for internal use)
1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 |
# File 'app/models/catalog_item.rb', line 1228 def next_available_with_depth_limit(use_store_item: nil, use_alternate_warehouse: false, max_depth: 10, current_depth: 0) # Prevent infinite recursion if current_depth >= max_depth Rails.logger.warn "Maximum depth limit (#{max_depth}) reached for catalog item #{id} (#{item&.sku}). Possible circular reference in kit structure." return nil end use_store_item ||= store_item next_available_hsh_arr = [use_store_item.next_available] if use_alternate_warehouse # What's next available in the other warehouse? Include 100% (real stock) with 1 week delay alternate_warehouse_store_items.each do |alternate_si| alt_next_available_hsh = alternate_si.next_available_with_depth_limit(max_depth: max_depth, current_depth: current_depth + 1) if alt_next_available_hsh.present? alt_next_available_hsh = OpenStruct.new(next_available_qty: alt_next_available_hsh[:next_available_qty], next_available_date: (alt_next_available_hsh[:next_available_date] + 1.week)).freeze end next_available_hsh_arr << alt_next_available_hsh end end # here we discard all the nil entries and sort by next available date, returning the first entry, i.e. the next available next_available_entry_to_use = next_available_hsh_arr.compact.min_by { |h| h[:next_available_date] } return nil if next_available_entry_to_use.blank? next_available_entry_to_use end |
#notify_of_price_update ⇒ Object (protected)
Notify of price update.
1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 |
# File 'app/models/catalog_item.rb', line 1673 def notify_of_price_update Rails.configuration.event_store.publish( Events::PriceUpdated.new(data: { catalog_item_id: id, price_was: old_amount, price_now: amount }), stream_name: "CatalogItem-#{id}" ) end |
#ok_to_destroy? ⇒ Boolean
Returns whether the record ok to destroy.
1048 1049 1050 |
# File 'app/models/catalog_item.rb', line 1048 def ok_to_destroy? !line_items.exists? end |
#on_order(use_alternate_warehouse: false) ⇒ Object
On order.
1276 1277 1278 1279 1280 |
# File 'app/models/catalog_item.rb', line 1276 def on_order(use_alternate_warehouse: false) store_items_by_warehouse.to_h do |si| [si.store.name, on_order_for_store_item(use_store_item: si, use_alternate_warehouse:)] end end |
#on_order_for_store_item(use_store_item: nil, use_alternate_warehouse: false) ⇒ Object
Returns on order quantities for internal CRM use (100% real stock)
discontinued and pending discontinue item will report 0 for on order
1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 |
# File 'app/models/catalog_item.rb', line 1180 def on_order_for_store_item(use_store_item: nil, use_alternate_warehouse: false) return 0 if discontinued? || pending_discontinue? use_store_item ||= store_item on_order_arr = use_store_item.on_order if use_alternate_warehouse # What's on order for the other warehouses? Include 100% (real stock) with 1 week delay alternate_warehouse_store_items.each do |alternate_si| alt_on_order_arr = alternate_si.on_order alt_on_order_arr.each { |h| h[:promised_delivery_date] = (h[:promised_delivery_date] + 1.week) } on_order_arr.concat(alt_on_order_arr) end end on_order_arr end |
#onboarded_by_order! ⇒ Object
A retailer only transmits an order for a SKU it is actually selling, so an
inbound EDI order is the strongest evidence we ever get that a listing is
live — stronger than a price probe or a sitemap scan. Promote the item
rather than leaving it parked in onboarding, where it is invisible to the
inventory feed.
Returns false (no raise) when the transition can't run: any other state is
already order-legitimate, and a validation failure must not take down an
order batch we have otherwise accepted.
Origin: 22 Rona catalog items were moved to pending_onboarding on
2026-06-22 because their product ids were absent from Rona's sitemap. Rona
sold TRT120-KIT-OP-3.0x10 on 2026-08-10 and the order batch failed on an
"Unknown vendor sku" raise.
1604 1605 1606 |
# File 'app/models/catalog_item.rb', line 1604 def onboarded_by_order! pending_onboarding? && items_are_onboarded end |
#out_of_stock(use_threshhold = nil) ⇒ Object
Out of stock.
1054 1055 1056 1057 1058 1059 1060 |
# File 'app/models/catalog_item.rb', line 1054 def out_of_stock(use_threshhold = nil) # Use catalog_item.reserve_stock instead of legacy item threshold stock_reserved = use_threshhold || reserve_stock.to_i return true if store_item && (store_item.qty_available - stock_reserved) <= 0 false end |
#oversize? ⇒ Object
Alias for
to: :item#oversize?
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#parent_catalog ⇒ Object
Alias for Catalog#parent_catalog
265 266 267 268 269 |
# File 'app/models/catalog_item.rb', line 265 delegate :parent_catalog, :currency, :currency_symbol, :tax_rate, to: :catalog |
#parent_catalog_currency_symbol ⇒ Object
Parent catalog currency symbol.
960 961 962 963 964 |
# File 'app/models/catalog_item.rb', line 960 def parent_catalog_currency_symbol return unless parent_catalog parent_catalog.currency_symbol end |
#parent_catalog_discount ⇒ Object
Parent catalog discount.
628 629 630 631 632 633 |
# File 'app/models/catalog_item.rb', line 628 def parent_catalog_discount return 0.0 unless catalog.parent_catalog return catalog.parent_catalog_discount_refurb if item.condition_refurbished? catalog.parent_catalog_discount end |
#parent_catalog_item ⇒ Object
If a parent_catalog exists as defined in the catalog of this catalog item
then find the matching catalog item by store item id which should be similar
968 969 970 971 972 |
# File 'app/models/catalog_item.rb', line 968 def parent_catalog_item return unless parent_catalog parent_catalog.catalog_items.where(store_item_id:).first end |
#parent_catalog_item_amount ⇒ Object
Parent catalog item amount.
975 976 977 978 979 |
# File 'app/models/catalog_item.rb', line 975 def parent_catalog_item_amount return unless (pci = parent_catalog_item) pci.amount end |
#percentage_off_from_msrp ⇒ Object
Percentage off from msrp.
1002 1003 1004 1005 1006 |
# File 'app/models/catalog_item.rb', line 1002 def percentage_off_from_msrp return unless msrp && effective_price ((1.0 - (effective_price / msrp)) * 100).round(2) end |
#prefix: :new_sale_price_effective_date ⇒ Object
Alias for New_coupon#effective_date
315 316 317 318 319 |
# File 'app/models/catalog_item.rb', line 315 delegate :effective_date, :expiration_date, to: :new_coupon, prefix: :new_sale_price, allow_nil: true |
#prefix: :new_sale_price_expiration_date ⇒ Object
Alias for New_coupon#expiration_date
315 316 317 318 319 |
# File 'app/models/catalog_item.rb', line 315 delegate :effective_date, :expiration_date, to: :new_coupon, prefix: :new_sale_price, allow_nil: true |
#prefix: :sale_price_effective_date ⇒ Object
Alias for Coupon#effective_date
309 310 311 312 313 |
# File 'app/models/catalog_item.rb', line 309 delegate :effective_date, :expiration_date, to: :coupon, prefix: :sale_price, allow_nil: true |
#prefix: :sale_price_expiration_date ⇒ Object
Alias for Coupon#expiration_date
309 310 311 312 313 |
# File 'app/models/catalog_item.rb', line 309 delegate :effective_date, :expiration_date, to: :coupon, prefix: :sale_price, allow_nil: true |
#price_message_enabled? ⇒ Boolean
Returns whether the record price message enabled.
850 851 852 |
# File 'app/models/catalog_item.rb', line 850 def .present? end |
#price_message_processors ⇒ Object
Retrieves all price message processors for edi customers carrying this item
This can be used to push price at once to all channels for one item
883 884 885 886 887 |
# File 'app/models/catalog_item.rb', line 883 def edi_orchestrators_for_catalog.filter_map do |o| o. if o. && o.respond_to?(:price_message_processor) end end |
#price_out_of_date? ⇒ Boolean
This method determines if the catalog item is out of date compared to its parent catalog item, return true/false or nil if not applicable.
949 950 951 952 953 954 955 956 957 |
# File 'app/models/catalog_item.rb', line 949 def price_out_of_date? return false unless (pci = parent_catalog_item) # Not applicable if we don't have a parent catalog item return false unless (parent_price_updated_at = pci.price_updated_at) # Not applicable unless the parent catalog item has a price update return false unless catalog.price_sync_timed? # Don't care if not a timed catalog return true unless price_updated_at # Nil value will need to assume an out of date price if parent has one price_age_in_days = parent_price_updated_at - price_updated_at # should be an integer price_age_in_days >= catalog.price_sync_delay # Price is out of date when the catalog price delay is met or exceeded end |
#price_updated? ⇒ Boolean (protected)
Returns whether the record price updated.
1685 1686 1687 |
# File 'app/models/catalog_item.rb', line 1685 def price_updated? saved_changes.key?('amount') end |
#price_with_vat ⇒ Object
Price with vat.
777 778 779 780 781 |
# File 'app/models/catalog_item.rb', line 777 def price_with_vat return if tax_rate.blank? ((tax_rate + 1) * (amount || 0.0)).round(2) end |
#primary_catalog ⇒ Object
Primary catalog.
738 739 740 |
# File 'app/models/catalog_item.rb', line 738 def primary_catalog catalog_id.in?(CatalogConstants::ALL_MAIN_CATALOG_IDS) end |
#primary_image ⇒ Object
Alias for
to: :item#primary_image
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#primary_product_line ⇒ Object
Alias for
to: :item#primary_product_line
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#primary_product_line_id ⇒ Object
Alias for
to: :item#primary_product_line_id
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#probeable? ⇒ Boolean
Whether the retailer-probe pipeline may call this listing right now. Row
equivalent of the probeable scope — see it for why the two gates stay
separate.
723 724 725 |
# File 'app/models/catalog_item.rb', line 723 def probeable? !skip_url_checks? && !probe_backoff_until&.future? end |
#product_category ⇒ Object
Alias for
to: :item#product_category
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#product_category_visible_in_feed? ⇒ Boolean
Returns whether the record product category visible in feed.
1465 1466 1467 |
# File 'app/models/catalog_item.rb', line 1465 def product_category_visible_in_feed? product_category&.available_to_public end |
#product_lines ⇒ Object
Alias for
to: :item#product_lines
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#product_stock_status ⇒ Object
Product stock status.
1307 1308 1309 1310 1311 1312 1313 |
# File 'app/models/catalog_item.rb', line 1307 def product_stock_status if item.always_available_online? || !out_of_stock 'InStock' else 'OutOfStock' end end |
#profit_margin ⇒ Object
Profit margin.
636 637 638 |
# File 'app/models/catalog_item.rb', line 636 def profit_margin calculate_profit_margin amount end |
#profit_margin_new_price ⇒ Object
Profit margin new price.
656 657 658 |
# File 'app/models/catalog_item.rb', line 656 def profit_margin_new_price calculate_profit_margin new_price end |
#profit_margin_new_sale_price ⇒ Object
Profit margin new sale price.
651 652 653 |
# File 'app/models/catalog_item.rb', line 651 def profit_margin_new_sale_price calculate_profit_margin new_sale_price end |
#profit_margin_retailer_requested_cost ⇒ Object
Profit margin retailer requested cost.
641 642 643 |
# File 'app/models/catalog_item.rb', line 641 def profit_margin_retailer_requested_cost calculate_profit_margin retailer_requested_cost end |
#profit_margin_sale_price ⇒ Object
Profit margin sale price.
646 647 648 |
# File 'app/models/catalog_item.rb', line 646 def profit_margin_sale_price calculate_profit_margin sale_price end |
#public_description_html ⇒ Object
Alias for
to: :item#public_description_html
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#public_name ⇒ Object
Alias for
to: :item#public_name
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#publish_buy_box_change ⇒ Object (protected)
Announce a Buy Box win/loss flip, whoever wrote it (orchestrator pulls
today; an ANY_OFFER_CHANGED SQS handler once that subscription lands) —
see Events::AmazonBuyBoxChanged.
1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 |
# File 'app/models/catalog_item.rb', line 1692 def publish_buy_box_change publish_event( Events::AmazonBuyBoxChanged, data: { catalog_item_id: id, won: is_amz_buy_box_winner, previous_won: saved_change_to_is_amz_buy_box_winner.first } ) end |
#purge_edge_cache ⇒ Object (protected)
Purge edge cache.
1754 1755 1756 1757 1758 1759 1760 |
# File 'app/models/catalog_item.rb', line 1754 def purge_edge_cache return unless saved_changes? # only need to do this for main catalogs return unless Catalog.main_catalog_ids.include?(catalog_id) site_maps.each(&:purge_edge_cache) end |
#push_inventory_message ⇒ Object
Performs an integration push where applicable for stock to partner carrying this catalog item
900 901 902 |
# File 'app/models/catalog_item.rb', line 900 def .map { |imp| imp.process(catalog_items: CatalogItem.where(id:)) }.flatten end |
#push_price_message ⇒ Object
Push price message.
905 906 907 908 |
# File 'app/models/catalog_item.rb', line 905 def logger.warn "No price message processors found for catalog item #{id}" if .empty? .map { |imp| imp.process(catalog_items: CatalogItem.where(id:)) }.flatten end |
#qty_available ⇒ Object
Alias for Store_item#qty_available
304 305 306 307 |
# File 'app/models/catalog_item.rb', line 304 delegate :qty_available, :qty_available_outside_order, :unit_cogs, to: :store_item |
#qty_available_outside_order ⇒ Object
Alias for Store_item#qty_available_outside_order
304 305 306 307 |
# File 'app/models/catalog_item.rb', line 304 delegate :qty_available, :qty_available_outside_order, :unit_cogs, to: :store_item |
#real_stock(use_store_item: nil, use_alternate_warehouse: false) ⇒ Object
Returns REAL stock available (100% of all warehouses) for internal CRM use
Use this for cycle counts, pick items, order management, quotes, etc.
1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 |
# File 'app/models/catalog_item.rb', line 1108 def real_stock(use_store_item: nil, use_alternate_warehouse: false) use_store_item ||= store_item qty_available = use_store_item.qty_available.to_i if use_alternate_warehouse alternate_warehouse_store_items.each do |alternate_si| qty_available += alternate_si.qty_available.to_i end end qty_available end |
#refurbished ⇒ Object
Refurbished.
1559 1560 1561 1562 1563 |
# File 'app/models/catalog_item.rb', line 1559 def refurbished return unless (irv = item.refurbished_version) irv.catalog_items.where(catalog_id:).not_hidden_from_catalog.first end |
#reported_name ⇒ String?
Product name reported to external retailers and product feeds (Wayfair,
CommerceHub, generic feed), cleaned through Heatwave::Normalizers.feed_safe
so trademark glyphs and smart punctuation never reach a marketplace.
1380 1381 1382 |
# File 'app/models/catalog_item.rb', line 1380 def reported_name Heatwave::Normalizers.feed_safe(name) end |
#reported_name_for_google ⇒ String?
Google Shopping feed title: the per-item +google_feed_title+ override
(cleaned) when present, otherwise the already-cleaned #reported_name.
1387 1388 1389 |
# File 'app/models/catalog_item.rb', line 1387 def reported_name_for_google Heatwave::Normalizers.feed_safe(google_feed_title.presence) || reported_name end |
#reported_stock(use_store_item: nil, use_alternate_warehouse: false, safety_stock: nil) ⇒ Integer
Calculates stock available for external reporting.
Logic Details
Discontinued items report zero. When alternate-warehouse reporting is
enabled, the configured share of alternate stock is added. The primary
warehouse reserve is withheld only when the alternate warehouse contributes
no reportable stock; contributing alternate stock acts as the safety buffer.
1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 |
# File 'app/models/catalog_item.rb', line 1132 def reported_stock(use_store_item: nil, use_alternate_warehouse: false, safety_stock: nil) return 0 if discontinued? || pending_discontinue? safety_stock ||= reserve_stock.to_i use_store_item ||= store_item qty_available = use_store_item.qty_available.to_i alternate_stock_reported = use_alternate_warehouse ? reported_alternate_warehouse_stock : 0 qty_available += alternate_stock_reported # The reserve buffer exists to stop us overselling the primary warehouse. If # the alternate warehouse is actually contributing stock, that backup IS the # buffer — so report what we genuinely have rather than withholding twice. # Only skipped when the alternate contributed something; an empty alternate # warehouse leaves the reserve fully in force. # # Without this, an item with 1 on hand and a reserve of 1 reports 0 even # when the other warehouse is holding 12 (real case: WSHC-120-00057 on # Wayfair-CA, 2026-07-30). qty_available -= safety_stock unless alternate_stock_reported.positive? qty_available = [qty_available, 0].max # If our always available stock is set we will use that value if the qty available is below if use_store_item.permanent_qty_available&.zero? 0 else [qty_available, min_stock_to_report, use_store_item.permanent_qty_available].compact.max end end |
#reported_stocks(use_alternate_warehouse: false) ⇒ Object
Returns a hash of store ids and reported stock available in each
{ 'WarmlyYours-CA': 14, 'WarmlyYours-US': 20 }
1268 1269 1270 1271 1272 |
# File 'app/models/catalog_item.rb', line 1268 def reported_stocks(use_alternate_warehouse: false) store_items_by_warehouse.to_h do |si| [si.store.name, reported_stock(use_store_item: si, use_alternate_warehouse:)] end end |
#reported_vendor_sku(_orchestrator_partner = nil) ⇒ Object
Reported vendor sku.
1372 1373 1374 |
# File 'app/models/catalog_item.rb', line 1372 def reported_vendor_sku(_orchestrator_partner = nil) third_party_sku.presence || sku end |
#reprice_reference_catalogs ⇒ ActiveRecord::Relation<RepriceReferenceCatalog>
Reprice reference catalogs.
257 |
# File 'app/models/catalog_item.rb', line 257 has_many :reprice_reference_catalogs, through: :reprice_references, source: :catalog |
#reprice_references ⇒ ActiveRecord::Relation<CatalogItemRepriceReference>
Reprice references.
255 |
# File 'app/models/catalog_item.rb', line 255 has_many :reprice_references, class_name: 'CatalogItemRepriceReference', dependent: :destroy |
#reset_edi_delta_feed_fields ⇒ Object
Reset edi delta feed fields.
1609 1610 1611 1612 1613 1614 1615 1616 |
# File 'app/models/catalog_item.rb', line 1609 def reset_edi_delta_feed_fields EDI_DELTA_FEED_CATEGORIES.each do |category| = :"last_#{category}_sent_json" if respond_to?() && try().present? # if this column exists in CatalogItem, then let's reset it, which ensures it will get sent in the next schedule EDI feed for that category of EDI message update_column(, {}) end end end |
#retail_price_currency_symbol ⇒ Object
Retail price currency symbol.
944 945 946 |
# File 'app/models/catalog_item.rb', line 944 def retail_price_currency_symbol catalog.currency_symbol end |
#retailer_probes ⇒ ActiveRecord::Relation<CatalogItemRetailerProbe>
Retailer probes.
253 |
# File 'app/models/catalog_item.rb', line 253 has_many :retailer_probes, class_name: 'CatalogItemRetailerProbe', dependent: :destroy |
#root_catalog_item ⇒ Object
Root catalog item.
982 983 984 985 986 987 |
# File 'app/models/catalog_item.rb', line 982 def root_catalog_item root_catalog = catalog.root return self unless root_catalog != catalog root_catalog.catalog_items.find_by(store_item_id:) end |
#sale_price_in_effect?(exclude_effective_date: false, date_to_check: nil) ⇒ Boolean
Returns whether the record sale price in effect.
1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 |
# File 'app/models/catalog_item.rb', line 1472 def sale_price_in_effect?(exclude_effective_date: false, date_to_check: nil) return false if sale_price.blank? unless exclude_effective_date date_to_check ||= Date.current # If today is before the sale start, don't send return false if sale_price_effective_date && date_to_check < sale_price_effective_date # If today is past the sale expiration, don't send return false if sale_price_expiration_date && date_to_check > sale_price_expiration_date end true end |
#sale_price_percentage_off ⇒ Object
Returns the percentage off (0-100) of an item comparing sale price to catalog price
1522 1523 1524 1525 1526 |
# File 'app/models/catalog_item.rb', line 1522 def sale_price_percentage_off return 0.0 unless sale_price.present? && amount.present? && amount.positive? ((1.0 - (sale_price / amount)) * 100).round(2) end |
#sale_price_percentage_off=(percentage_off) ⇒ Object
Assign a percentage off (0-100) and the sale price will be calculated based on the catalog item price without VAT
1517 1518 1519 |
# File 'app/models/catalog_item.rb', line 1517 def sale_price_percentage_off=(percentage_off) calculate_sale_price(percentage_off) end |
#sale_price_reset_if_no_coupon ⇒ Object (protected)
You cannot have a sale price without a coupon, so before anything, we remove these
1626 1627 1628 1629 1630 |
# File 'app/models/catalog_item.rb', line 1626 def sale_price_reset_if_no_coupon return if coupon.present? self.sale_price = nil end |
#sale_price_with_vat ⇒ Object
Sale price with vat.
784 785 786 787 788 789 790 |
# File 'app/models/catalog_item.rb', line 784 def sale_price_with_vat return if tax_rate.blank? res = ((tax_rate + 1) * (sale_price || 0.0)).round(2) res = nil if res == 0.0 res end |
#sale_price_with_vat_percentage_off ⇒ Object
Returns the percentage off (0-100) of an item comparing sale price with VAT to catalog price with VAT
1535 1536 1537 1538 1539 |
# File 'app/models/catalog_item.rb', line 1535 def sale_price_with_vat_percentage_off return 0.0 if sale_price_with_vat.blank? ((1.0 - (sale_price_with_vat / price_with_vat)) * 100).round(2) end |
#sale_price_with_vat_percentage_off=(percentage_off) ⇒ Object
Assign a percentage off (0-100) and the sale price will be calculated based on the catalog item price with VAT included
1530 1531 1532 |
# File 'app/models/catalog_item.rb', line 1530 def sale_price_with_vat_percentage_off=(percentage_off) calculate_sale_price(percentage_off, include_vat: true) end |
#sellable_online_qty(use_store_item: nil) ⇒ Integer
Units actually offerable to shoppers for THIS catalog: the primary
warehouse's available stock minus the reserve buffer, never negative.
This is the reserve math behind #out_of_stock and therefore
#product_stock_status — the In / Out of Stock status shoppers see (which
additionally honors the item's always_available_online override).
Alternate-warehouse stock is deliberately NOT counted here — it only
contributes to #reported_stock for external feeds.
1072 1073 1074 1075 1076 1077 |
# File 'app/models/catalog_item.rb', line 1072 def sellable_online_qty(use_store_item: nil) use_store_item ||= store_item return 0 unless use_store_item [use_store_item.qty_available.to_i - reserve_stock.to_i, 0].max end |
#seo_description ⇒ Object
Alias for
to: :item#seo_description
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#seo_keywords ⇒ Object
Alias for
to: :item#seo_keywords
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#seo_title ⇒ Object
Seo title.
911 912 913 |
# File 'app/models/catalog_item.rb', line 911 def seo_title item.effective_seo_title end |
#set_or_clear_discontinued_date ⇒ Object (protected)
Set or clear discontinued date.
1743 1744 1745 1746 1747 1748 1749 1750 1751 |
# File 'app/models/catalog_item.rb', line 1743 def set_or_clear_discontinued_date return unless state_changed? if %w[pending_discontinue discontinued].include?(state) && %w[active active_hidden pending_onboarding pending_vendor_update invalid_catalog_item].include?(state_was) self.discontinued_date = Date.current elsif %w[pending_discontinue discontinued].include?(state_was) && %w[active active_hidden pending_onboarding pending_vendor_update invalid_catalog_item].include?(state) self.discontinued_date = nil end end |
#should_appear_in_feed? ⇒ Boolean
Returns whether the record should appear in feed.
1460 1461 1462 |
# File 'app/models/catalog_item.rb', line 1460 def should_appear_in_feed? !in_hide_from_feed_state? && product_category_visible_in_feed? end |
#siblings ⇒ Object
Siblings.
1413 1414 1415 |
# File 'app/models/catalog_item.rb', line 1413 def siblings CatalogItem.where(catalog_id:) end |
#single_sku? ⇒ Boolean
Returns whether the record single sku.
1450 1451 1452 |
# File 'app/models/catalog_item.rb', line 1450 def single_sku? variants(include_self: true).size <= 1 end |
#site_maps ⇒ ActiveRecord::Relation<SiteMap>
Site maps.
241 |
# File 'app/models/catalog_item.rb', line 241 has_many :site_maps, as: :resource, dependent: :destroy |
#sku ⇒ Object
Alias for
to: :item#sku
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#sku_and_name ⇒ Object
Sku and name.
1316 1317 1318 |
# File 'app/models/catalog_item.rb', line 1316 def sku_and_name "#{item.sku} - #{item.name}" end |
#spec ⇒ Object
Alias for
to: :item#spec
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#spec_output ⇒ Object
Alias for
to: :item#spec_output
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#specifications ⇒ Object
Alias for
to: :item#specifications
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#specifications_grouped ⇒ Object
Alias for
to: :item#specifications_grouped
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#state_requires_edi_warning_on_order? ⇒ Boolean
Returns whether the record state requires edi warning on order.
1585 1586 1587 1588 |
# File 'app/models/catalog_item.rb', line 1585 def state_requires_edi_warning_on_order? # here we warn when this item is in a state that should not be ordered via EDI, but allow order to go through %w[active require_vendor_update].include?(state) != true end |
#store ⇒ Store
Store.
235 |
# File 'app/models/catalog_item.rb', line 235 has_one :store, through: :store_item |
#store_item ⇒ StoreItem
Store item.
218 |
# File 'app/models/catalog_item.rb', line 218 belongs_to :store_item, inverse_of: :catalog_items |
#store_items ⇒ ActiveRecord::Relation<StoreItem>
Returns the associated store items.
261 |
# File 'app/models/catalog_item.rb', line 261 has_and_belongs_to_many :store_items, inverse_of: :catalog_items |
#store_items_by_warehouse ⇒ Object
Store items by warehouse.
1261 1262 1263 |
# File 'app/models/catalog_item.rb', line 1261 def store_items_by_warehouse store_items.joins(:store).includes(:store).merge(Store.warmlyyours_warehouses) end |
#tax_class ⇒ Object
Alias for
to: :item#tax_class
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#tax_rate ⇒ Object
Alias for Catalog#tax_rate
265 266 267 268 269 |
# File 'app/models/catalog_item.rb', line 265 delegate :parent_catalog, :currency, :currency_symbol, :tax_rate, to: :catalog |
#third_party_name_en_required? ⇒ Boolean (protected)
Returns whether the record third party name en required.
1733 1734 1735 |
# File 'app/models/catalog_item.rb', line 1733 def third_party_name_en_required? active? && catalog.third_party_name_en_required end |
#third_party_name_fr_required? ⇒ Boolean (protected)
Returns whether the record third party name fr required.
1738 1739 1740 |
# File 'app/models/catalog_item.rb', line 1738 def third_party_name_fr_required? active? && catalog.third_party_name_fr_required end |
#third_party_part_number_label ⇒ Object
Third party part number label.
1349 1350 1351 |
# File 'app/models/catalog_item.rb', line 1349 def third_party_part_number_label catalog.determine_retailer_part_number_label end |
#third_party_part_number_required? ⇒ Boolean (protected)
Returns whether the record third party part number required.
1723 1724 1725 |
# File 'app/models/catalog_item.rb', line 1723 def third_party_part_number_required? active? && catalog.third_party_part_number_required end |
#third_party_sku_required? ⇒ Boolean (protected)
Returns whether the record third party sku required.
1728 1729 1730 |
# File 'app/models/catalog_item.rb', line 1728 def third_party_sku_required? active? && catalog.third_party_sku_required end |
#to_s ⇒ String
Returns string representation of the record.
934 935 936 |
# File 'app/models/catalog_item.rb', line 934 def to_s "CatalogItem[#{id}]" end |
#tweak_ebay_sku ⇒ Object
Tweak ebay sku.
707 708 709 710 711 |
# File 'app/models/catalog_item.rb', line 707 def tweak_ebay_sku return unless catalog.orchestrator_key == 'ebay_us' update(third_party_sku: item.sku.tr('.', '*')) end |
#unit_cogs ⇒ Object
Alias for Store_item#unit_cogs
304 305 306 307 |
# File 'app/models/catalog_item.rb', line 304 delegate :qty_available, :qty_available_outside_order, :unit_cogs, to: :store_item |
#unprobeable_reason ⇒ String?
Why #probeable? said no, for logs.
730 731 732 733 734 735 |
# File 'app/models/catalog_item.rb', line 730 def unprobeable_reason return 'skip_url_checks set by a human' if skip_url_checks? return "probe backoff until #{probe_backoff_until.iso8601}" if probe_backoff_until&.future? nil end |
#upc ⇒ Object
Alias for
to: :item#upc
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#upc_required? ⇒ Boolean (protected)
Returns whether the record upc required.
1718 1719 1720 |
# File 'app/models/catalog_item.rb', line 1718 def upc_required? available_in_edi_feed? && catalog.require_upc? end |
#update_price_from_parent ⇒ Object
When the catalog item state moves from pending client update to active, the price is updated
1392 1393 1394 1395 1396 1397 1398 |
# File 'app/models/catalog_item.rb', line 1392 def update_price_from_parent return unless parent_catalog_item push_price_service = Catalog::PushCatalogItemPrice.new result = push_price_service.process(parent_catalog_item, target_catalog_id: catalog_id) result.all_price_pushed? end |
#variant_axis_tokens ⇒ Object
Alias for
to: :item#variant_axis_tokens
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/catalog_item.rb', line 271 delegate :sku, :upc, :public_name, :primary_product_line, :primary_product_line_id, :product_category, :product_lines, :is_available_to_public, :oversize?, :is_circuit_check?, :is_underlayment?, :is_snow_melt_plaque?, :is_roughin_kit?, :is_cable_fit_guide?, :is_cable_accessory?, :is_membrane?, :is_heating_element?, :is_publication?, :is_control?, :primary_image, :seo_keywords, :seo_description, :public_description_html, :variant_axis_tokens, :spec, :spec_output, :specifications, :specifications_grouped, :tax_class, :all_amazon_image_profiles, allow_nil: true, to: :item |
#variant_group ⇒ VariantGroup
The channel row (VariantGroup amazon override / Wayfair listing row) this
catalog_item lists under. Replaces the legacy amazon_variation FK, whose
data survives as the legacy_amazon_variation_id backup column (no
association — Maintenance::LegacyAmazonVariation reads it).
229 |
# File 'app/models/catalog_item.rb', line 229 belongs_to :variant_group, optional: true, inverse_of: :catalog_items |
#variant_group_in_catalog_marketplace ⇒ Object (protected)
Amazon channel rows are per-marketplace: the linked VariantGroup amazon
row must target this catalog's marketplace. Non-amazon rows (default
families, Wayfair listing rows) may link freely.
1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 |
# File 'app/models/catalog_item.rb', line 1647 def variant_group_in_catalog_marketplace return if variant_group_id.blank? return errors.add(:variant_group, 'must exist') unless variant_group return unless variant_group.amazon_channel? catalog_marketplace_id = catalog&.amazon_marketplace_id return if catalog_marketplace_id.blank? return if variant_group.catalog&.amazon_marketplace_id == catalog_marketplace_id errors.add(:variant_group, 'must belong to the same Amazon marketplace as this catalog') end |
#variants(_catalog_items_scope: nil, include_self: false, axis_filters: {}) ⇒ Object
Variants.
1421 1422 1423 1424 1425 1426 |
# File 'app/models/catalog_item.rb', line 1421 def variants(_catalog_items_scope: nil, include_self: false, axis_filters: {}) item_variants = item.item_grouping_info(include_self:, axis_filters:)&.variants return CatalogItem.none if item_variants.blank? catalog_items_in_same_catalog(CatalogItem.for_online_catalog).merge(item_variants).includes(:item) end |