Class: ViewQuoteBomItem
- Inherits:
-
ApplicationViewRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- ApplicationViewRecord
- ViewQuoteBomItem
- Defined in:
- app/models/view_quote_bom_item.rb
Overview
== Schema Information
Table name: view_quote_bom_items
Database name: primary
id :integer
all_product_line_path_slugs :ltree is an Array
all_product_line_paths :ltree is an Array
amps :float
area_sqft :float
area_sqin :float
coupon_description :text
coupon_expiration :date
coupon_title :text
effective_public_name :string
effective_short_description :string
effective_tag_line :string
feature_1 :string
feature_2 :string
feature_3 :string
feature_4 :string
feature_5 :string
hero_url :text
img_url :text
is_dual_voltage :boolean
item_type :text
last_updated_at :datetime
length :float
length_ft :float
name :string(255)
price :float
product_category_path_ids :ltree
product_category_path_slugs :ltree
product_category_url :string(255)
product_line_path_ids :ltree
product_line_path_slugs :ltree
product_line_slug_ltree :ltree
product_line_tagline :string
qty_available :integer
quote_builder_video_uid :string
sale_in_effect :boolean
sale_price :float
short_description :string(120)
sku :string
symbol :text
third_party_part_number :string(255)
third_party_sku :string
unlimited_inventory :boolean
watts :float
width :float
width_ft :float
catalog_id :integer
coupon_id :integer
item_id :integer
primary_product_line_id :integer
store_item_id :integer
voltage_id :integer
Indexes
index_view_quote_bom_items_on_all_pl_path_slugs (all_product_line_path_slugs) USING gin
index_view_quote_bom_items_on_all_pl_paths_gist (all_product_line_paths) USING gist
index_view_quote_bom_items_on_catalog_type (catalog_id,item_type)
index_view_quote_bom_items_on_id (id) UNIQUE
index_view_quote_bom_items_on_last_updated (last_updated_at)
index_view_quote_bom_items_on_pc_path_gist (product_category_path_ids) USING gist
index_view_quote_bom_items_on_pc_path_slugs_gist (product_category_path_slugs) USING gist
index_view_quote_bom_items_on_pl_path_gist (product_line_path_ids) USING gist
index_view_quote_bom_items_on_pl_path_slugs_gist (product_line_path_slugs) USING gist
index_view_quote_bom_items_on_pl_slug_ltree (product_line_slug_ltree) USING gist
index_view_quote_bom_items_on_sku (sku)
index_view_quote_bom_items_on_voltage (voltage_id) WHERE (item_type = 'element'::text)
Class Method Summary collapse
-
.accessories ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are accessories.
-
.controls ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are controls.
-
.elements ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are elements.
-
.for_any_product_line_path ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are for any product line path.
-
.for_catalog ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are for catalog.
-
.for_product_line_hierarchy ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are for product line hierarchy.
-
.for_voltage ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are for voltage.
-
.get_accessories(catalog_id:, product_line_id:) ⇒ Object
Get accessories for a product line.
-
.get_by_skus(catalog_id:, skus:, item_type: nil) ⇒ Array<Hash>
Get items by SKU (for snow melt controls, sensors, junction boxes).
-
.get_controls(catalog_id:, product_line_id:) ⇒ Object
Get controls (thermostats) for a product line.
-
.get_elements(catalog_id:, product_line_url:, voltage_id: nil, cable_spacing: nil, width_filter: nil) ⇒ Array<Hash>
Get heating elements for a product line (uses ltree descendant matching).
-
.get_junction_boxes(catalog_id:, skus: ItemConstants::JUNCTION_BOX_SKUS + ['SMP']) ⇒ Object
Get junction boxes by SKU (for snow melt).
-
.get_power(catalog_id:, product_line_id:) ⇒ Object
Get power modules (relays, relay panels) for a product line.
-
.get_relay_panels(catalog_id:, skus: ItemConstants::RELAY_PANEL_SKUS) ⇒ Object
Get relay panels by SKU (shared across all product lines).
-
.get_rough_in_kits(catalog_id:, skus:) ⇒ Object
Get rough-in kits.
-
.get_sensors(catalog_id:, product_line_id:) ⇒ Object
Get sensors for a product line.
-
.get_smart_services(catalog_id:, skus: ItemConstants::SMART_SERVICE_SKUS) ⇒ Object
Get smart services (installation services).
-
.get_snow_melt_controls(catalog_id:, skus:, successor_to_original: {}) ⇒ Object
Get snow melt controls by SKU Note: Adds 'original_bundle_sku' required by SnowMeltingControlsFinder.
-
.get_snow_melt_sensors(catalog_id:, skus: ItemConstants::SNOWMELT_SENSOR_SKUS) ⇒ Object
Get snow melt sensors by SKU.
-
.get_underlayments(catalog_id:) ⇒ Object
Get underlayments (items under the Underlayment product line hierarchy) Includes Prodeso membranes, ThermalSheet, Cork, CeraZorb.
-
.line_items_to_bom(line_items, cable_spacing: nil) ⇒ Array<Hash>
Convert line items to BOM format with quantities.
-
.order_by_area ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are order by area.
-
.order_by_price ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are order by price.
-
.order_by_watts ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are order by watts.
-
.power_modules ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are power modules.
-
.refresh ⇒ Object
Refresh the materialized view (concurrently to avoid locking) Called by MatviewRefreshWorker on schedule.
-
.sensors ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are sensors.
-
.services ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are services.
-
.to_bom_array ⇒ Object
Bulk convert to BOM format (avoids N+1 by using select).
Instance Method Summary collapse
-
#build_features_array ⇒ Object
Build array of non-nil features for display.
-
#to_bom_hash ⇒ Object
Convert a row to the hash format expected by HeatingSystemCalculator.
Methods inherited from ApplicationViewRecord
Methods inherited from ApplicationRecord
ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation
Methods included from Models::EventPublishable
Class Method Details
.accessories ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are accessories. Active Record Scope
84 |
# File 'app/models/view_quote_bom_item.rb', line 84 scope :accessories, -> { where(item_type: 'accessory') } |
.controls ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are controls. Active Record Scope
81 |
# File 'app/models/view_quote_bom_item.rb', line 81 scope :controls, -> { where(item_type: 'control') } |
.elements ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are elements. Active Record Scope
80 |
# File 'app/models/view_quote_bom_item.rb', line 80 scope :elements, -> { where(item_type: 'element') } |
.for_any_product_line_path ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are for any product line path. Active Record Scope
104 105 106 107 108 109 |
# File 'app/models/view_quote_bom_item.rb', line 104 scope :for_any_product_line_path, ->(ltree_paths) { paths = [ltree_paths].flatten.compact return none if paths.empty? where('all_product_line_paths && ARRAY[?]::ltree[]', paths) } |
.for_catalog ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are for catalog. Active Record Scope
91 |
# File 'app/models/view_quote_bom_item.rb', line 91 scope :for_catalog, ->(catalog_id) { where(catalog_id: catalog_id) } |
.for_product_line_hierarchy ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are for product line hierarchy. Active Record Scope
95 96 97 |
# File 'app/models/view_quote_bom_item.rb', line 95 scope :for_product_line_hierarchy, ->(ltree_path) { where(ViewQuoteBomItem[:product_line_path_ids].ltree_descendant(ltree_path)) } |
.for_voltage ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are for voltage. Active Record Scope
100 |
# File 'app/models/view_quote_bom_item.rb', line 100 scope :for_voltage, ->(voltage_id) { where(voltage_id: voltage_id) } |
.get_accessories(catalog_id:, product_line_id:) ⇒ Object
Get accessories for a product line
254 255 256 257 258 259 260 261 262 263 |
# File 'app/models/view_quote_bom_item.rb', line 254 def self.get_accessories(catalog_id:, product_line_id:) pl_path = product_line_ltree_path(product_line_id) return [] if pl_path.blank? for_catalog(catalog_id) .accessories .for_any_product_line_path(pl_path) .order_by_price .to_bom_array end |
.get_by_skus(catalog_id:, skus:, item_type: nil) ⇒ Array<Hash>
Get items by SKU (for snow melt controls, sensors, junction boxes)
279 280 281 282 283 |
# File 'app/models/view_quote_bom_item.rb', line 279 def self.get_by_skus(catalog_id:, skus:, item_type: nil) scope = for_catalog(catalog_id).where(sku: skus) scope = scope.where(item_type: item_type) if item_type.present? scope.order_by_price.to_bom_array end |
.get_controls(catalog_id:, product_line_id:) ⇒ Object
Get controls (thermostats) for a product line
218 219 220 221 222 223 224 225 226 227 |
# File 'app/models/view_quote_bom_item.rb', line 218 def self.get_controls(catalog_id:, product_line_id:) pl_path = product_line_ltree_path(product_line_id) return [] if pl_path.blank? for_catalog(catalog_id) .controls .for_any_product_line_path(pl_path) .order_by_price .to_bom_array end |
.get_elements(catalog_id:, product_line_url:, voltage_id: nil, cable_spacing: nil, width_filter: nil) ⇒ Array<Hash>
Get heating elements for a product line (uses ltree descendant matching)
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'app/models/view_quote_bom_item.rb', line 194 def self.get_elements(catalog_id:, product_line_url:, voltage_id: nil, cable_spacing: nil, width_filter: nil) pl_path = product_line_ltree_path_by_url(product_line_url) return [] if pl_path.blank? # Use ltree <@ operator to find elements where product_line_path_ids is descendant of parent scope = for_catalog(catalog_id) .elements .for_product_line_hierarchy(pl_path) .order_by_watts scope = scope.for_voltage(voltage_id) if voltage_id.present? scope = scope.where(width: width_filter) if width_filter.present? scope.map do |item| bom = item.to_bom_hash # Recalculate area for cables based on cable_spacing bom['area'] = ((item.length / 12.0) * (cable_spacing / 12.0)).round(2) if cable_spacing.present? && cable_spacing.positive? && item.length.present? bom end end |
.get_junction_boxes(catalog_id:, skus: ItemConstants::JUNCTION_BOX_SKUS + ['SMP']) ⇒ Object
Get junction boxes by SKU (for snow melt)
311 312 313 |
# File 'app/models/view_quote_bom_item.rb', line 311 def self.get_junction_boxes(catalog_id:, skus: ItemConstants::JUNCTION_BOX_SKUS + ['SMP']) get_by_skus(catalog_id: catalog_id, skus: skus) end |
.get_power(catalog_id:, product_line_id:) ⇒ Object
Get power modules (relays, relay panels) for a product line
230 231 232 233 234 235 236 237 238 239 |
# File 'app/models/view_quote_bom_item.rb', line 230 def self.get_power(catalog_id:, product_line_id:) pl_path = product_line_ltree_path(product_line_id) return [] if pl_path.blank? for_catalog(catalog_id) .power_modules .for_any_product_line_path(pl_path) .order_by_price .to_bom_array end |
.get_relay_panels(catalog_id:, skus: ItemConstants::RELAY_PANEL_SKUS) ⇒ Object
Get relay panels by SKU (shared across all product lines)
266 267 268 269 270 271 272 |
# File 'app/models/view_quote_bom_item.rb', line 266 def self.get_relay_panels(catalog_id:, skus: ItemConstants::RELAY_PANEL_SKUS) for_catalog(catalog_id) .power_modules .where(sku: skus) .order_by_price .to_bom_array end |
.get_rough_in_kits(catalog_id:, skus:) ⇒ Object
Get rough-in kits
316 317 318 |
# File 'app/models/view_quote_bom_item.rb', line 316 def self.get_rough_in_kits(catalog_id:, skus:) get_by_skus(catalog_id: catalog_id, skus: skus, item_type: 'tool') end |
.get_sensors(catalog_id:, product_line_id:) ⇒ Object
Get sensors for a product line
242 243 244 245 246 247 248 249 250 251 |
# File 'app/models/view_quote_bom_item.rb', line 242 def self.get_sensors(catalog_id:, product_line_id:) pl_path = product_line_ltree_path(product_line_id) return [] if pl_path.blank? for_catalog(catalog_id) .sensors .for_any_product_line_path(pl_path) .order_by_price .to_bom_array end |
.get_smart_services(catalog_id:, skus: ItemConstants::SMART_SERVICE_SKUS) ⇒ Object
Get smart services (installation services)
321 322 323 |
# File 'app/models/view_quote_bom_item.rb', line 321 def self.get_smart_services(catalog_id:, skus: ItemConstants::SMART_SERVICE_SKUS) get_by_skus(catalog_id: catalog_id, skus: skus, item_type: 'service') end |
.get_snow_melt_controls(catalog_id:, skus:, successor_to_original: {}) ⇒ Object
Get snow melt controls by SKU
Note: Adds 'original_bundle_sku' required by SnowMeltingControlsFinder
290 291 292 293 294 295 296 297 298 299 300 301 302 303 |
# File 'app/models/view_quote_bom_item.rb', line 290 def self.get_snow_melt_controls(catalog_id:, skus:, successor_to_original: {}) bundle_skus = ItemConstants::SNOWMELT_CONTROL_BUNDLES.map { |b| b[:sku] }.to_set get_by_skus(catalog_id: catalog_id, skus: skus, item_type: 'control').map do |control| # For snow melt controls, add the original_bundle_sku used by SnowMeltingControlsFinder # Priority: 1) Direct bundle SKU match, 2) Successor mapping, 3) nil original_bundle_sku = if bundle_skus.include?(control['sku']) control['sku'] else successor_to_original[control['sku']] end control.merge('original_bundle_sku' => original_bundle_sku) end end |
.get_snow_melt_sensors(catalog_id:, skus: ItemConstants::SNOWMELT_SENSOR_SKUS) ⇒ Object
Get snow melt sensors by SKU
306 307 308 |
# File 'app/models/view_quote_bom_item.rb', line 306 def self.get_snow_melt_sensors(catalog_id:, skus: ItemConstants::SNOWMELT_SENSOR_SKUS) get_by_skus(catalog_id: catalog_id, skus: skus, item_type: 'sensor') end |
.get_underlayments(catalog_id:) ⇒ Object
Get underlayments (items under the Underlayment product line hierarchy)
Includes Prodeso membranes, ThermalSheet, Cork, CeraZorb
327 328 329 330 331 332 333 |
# File 'app/models/view_quote_bom_item.rb', line 327 def self.get_underlayments(catalog_id:) for_catalog(catalog_id) .accessories .where(ViewQuoteBomItem[:product_line_path_slugs].ltree_descendant(LtreePaths::PL_FLOOR_HEATING_UNDERLAYMENT)) .order_by_price .to_bom_array end |
.line_items_to_bom(line_items, cable_spacing: nil) ⇒ Array<Hash>
Convert line items to BOM format with quantities
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 |
# File 'app/models/view_quote_bom_item.rb', line 343 def self.line_items_to_bom(line_items, cable_spacing: nil) return [] if line_items.blank? # Get catalog item IDs and quantities catalog_item_ids = line_items.map { |li| li.catalog_item_id } quantities_by_id = line_items.each_with_object({}) { |li, h| h[li.catalog_item_id] = li.quantity } # Fetch all matching items from matview in one query items_by_id = where(id: catalog_item_ids).index_by(&:id) # Build BOM array preserving order line_items.map do |li| item = items_by_id[li.catalog_item_id] next nil unless item bom = item.to_bom_hash bom['qty'] = quantities_by_id[li.catalog_item_id] # Recalculate area for cables bom['area'] = ((item.length / 12.0) * (cable_spacing / 12.0)).round(2) if cable_spacing.present? && cable_spacing.positive? && item.length.present? && item.item_type == 'element' bom end.compact end |
.order_by_area ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are order by area. Active Record Scope
117 |
# File 'app/models/view_quote_bom_item.rb', line 117 scope :order_by_area, -> { order(area_sqft: :asc) } |
.order_by_price ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are order by price. Active Record Scope
115 |
# File 'app/models/view_quote_bom_item.rb', line 115 scope :order_by_price, -> { order(price: :asc) } |
.order_by_watts ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are order by watts. Active Record Scope
116 |
# File 'app/models/view_quote_bom_item.rb', line 116 scope :order_by_watts, -> { order(watts: :asc) } |
.power_modules ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are power modules. Active Record Scope
82 |
# File 'app/models/view_quote_bom_item.rb', line 82 scope :power_modules, -> { where(item_type: 'power') } |
.refresh ⇒ Object
Refresh the materialized view (concurrently to avoid locking)
Called by MatviewRefreshWorker on schedule
125 126 127 |
# File 'app/models/view_quote_bom_item.rb', line 125 def self.refresh Scenic.database.refresh_materialized_view(table_name, concurrently: true, cascade: false) end |
.sensors ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are sensors. Active Record Scope
83 |
# File 'app/models/view_quote_bom_item.rb', line 83 scope :sensors, -> { where(item_type: 'sensor') } |
.services ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are services. Active Record Scope
85 |
# File 'app/models/view_quote_bom_item.rb', line 85 scope :services, -> { where(item_type: 'service') } |
.to_bom_array ⇒ Object
Bulk convert to BOM format (avoids N+1 by using select)
179 180 181 |
# File 'app/models/view_quote_bom_item.rb', line 179 def self.to_bom_array all.map(&:to_bom_hash) end |
Instance Method Details
#build_features_array ⇒ Object
Build array of non-nil features for display
174 175 176 |
# File 'app/models/view_quote_bom_item.rb', line 174 def build_features_array [feature_1, feature_2, feature_3, feature_4, feature_5].compact end |
#to_bom_hash ⇒ Object
Convert a row to the hash format expected by HeatingSystemCalculator
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'app/models/view_quote_bom_item.rb', line 134 def to_bom_hash { 'sku' => sku, 'name' => name, 'description' => short_description, 'short_description' => short_description, 'img' => img_url, 'hero' => hero_url, 'volts' => voltage_id.to_f, 'is_dual_voltage' => is_dual_voltage, 'price' => price&.round(2), 'cat_id' => id, 'class' => 'Item', 'third_party_part_number' => third_party_part_number, 'third_party_sku' => third_party_sku, 'sale_price_in_effect' => sale_in_effect, 'sale_price' => sale_price&.round(2), 'sale_title' => coupon_title, 'sale_description' => coupon_description, 'sale_expiration' => coupon_expiration, 'sale_coupon_id' => coupon_id, 'symbol' => symbol, 'tagline' => product_line_tagline, # Element-specific fields 'width' => width, 'length' => length, 'area' => area_sqft, 'watts' => watts, 'amps' => amps, # Feature fields (for benefits display) 'features' => build_features_array, # Inherited product line metadata (from ltree ancestors) 'product_line_name' => effective_public_name, 'product_line_tagline' => effective_tag_line, 'product_line_description' => effective_short_description, 'product_line_video_uid' => quote_builder_video_uid } end |