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)
Constant Summary
Constants included from Models::Schedulable
Models::Schedulable::SIMPLE_FORM_OPTIONS
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:) ⇒ void
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:) ⇒ void
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']) ⇒ void
Get junction boxes by SKU (for snow melt).
-
.get_power(catalog_id:, product_line_id:) ⇒ void
Get power modules (relays, relay panels) for a product line.
-
.get_relay_panels(catalog_id:, skus: ItemConstants::RELAY_PANEL_SKUS) ⇒ void
Get relay panels by SKU (shared across all product lines).
-
.get_rough_in_kits(catalog_id:, skus:) ⇒ void
Get rough-in kits.
-
.get_sensors(catalog_id:, product_line_id:) ⇒ void
Get sensors for a product line.
-
.get_smart_services(catalog_id:, skus: ItemConstants::SMART_SERVICE_SKUS) ⇒ void
Get smart services (installation services).
-
.get_snow_melt_controls(catalog_id:, skus:, successor_to_original: {}) ⇒ Object
Get snow melt controls by SKU.
-
.get_snow_melt_sensors(catalog_id:, skus: ItemConstants::SNOWMELT_SENSOR_SKUS) ⇒ void
Get snow melt sensors by SKU.
-
.get_underlayments(catalog_id:) ⇒ void
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::Schedulable
Methods included from Models::AfterCommittable
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:) ⇒ void
This method returns an undefined value.
Get accessories for a product line
266 267 268 269 270 271 272 273 274 275 |
# File 'app/models/view_quote_bom_item.rb', line 266 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)
294 295 296 297 298 |
# File 'app/models/view_quote_bom_item.rb', line 294 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:) ⇒ void
This method returns an undefined value.
Get controls (thermostats) for a product line
221 222 223 224 225 226 227 228 229 230 |
# File 'app/models/view_quote_bom_item.rb', line 221 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']) ⇒ void
This method returns an undefined value.
Get junction boxes by SKU (for snow melt)
332 333 334 |
# File 'app/models/view_quote_bom_item.rb', line 332 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:) ⇒ void
This method returns an undefined value.
Get power modules (relays, relay panels) for a product line
236 237 238 239 240 241 242 243 244 245 |
# File 'app/models/view_quote_bom_item.rb', line 236 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) ⇒ void
This method returns an undefined value.
Get relay panels by SKU (shared across all product lines)
281 282 283 284 285 286 287 |
# File 'app/models/view_quote_bom_item.rb', line 281 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:) ⇒ void
This method returns an undefined value.
Get rough-in kits
340 341 342 |
# File 'app/models/view_quote_bom_item.rb', line 340 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:) ⇒ void
This method returns an undefined value.
Get sensors for a product line
251 252 253 254 255 256 257 258 259 260 |
# File 'app/models/view_quote_bom_item.rb', line 251 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) ⇒ void
This method returns an undefined value.
Get smart services (installation services)
348 349 350 |
# File 'app/models/view_quote_bom_item.rb', line 348 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
Adds 'original_bundle_sku' required by SnowMeltingControlsFinder
Get snow melt controls by SKU
305 306 307 308 309 310 311 312 313 314 315 316 317 318 |
# File 'app/models/view_quote_bom_item.rb', line 305 def self.get_snow_melt_controls(catalog_id:, skus:, successor_to_original: {}) bundle_skus = ItemConstants::SNOWMELT_CONTROL_BUNDLES.pluck(: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) ⇒ void
This method returns an undefined value.
Get snow melt sensors by SKU
324 325 326 |
# File 'app/models/view_quote_bom_item.rb', line 324 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:) ⇒ void
This method returns an undefined value.
Get underlayments (items under the Underlayment product line hierarchy)
Includes Prodeso membranes, ThermalSheet, Cork, CeraZorb
356 357 358 359 360 361 362 |
# File 'app/models/view_quote_bom_item.rb', line 356 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
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 |
# File 'app/models/view_quote_bom_item.rb', line 372 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(&:catalog_item_id) quantities_by_id = line_items.to_h { |li| [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.filter_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 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 |