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 Schedulable
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:) ⇒ 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 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
85 |
# File 'app/models/view_quote_bom_item.rb', line 85 scope :accessories, -> { where(item_type: 'accessory') } |
.controls ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are controls. Active Record Scope
82 |
# File 'app/models/view_quote_bom_item.rb', line 82 scope :controls, -> { where(item_type: 'control') } |
.elements ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are elements. Active Record Scope
81 |
# File 'app/models/view_quote_bom_item.rb', line 81 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
105 106 107 108 109 110 |
# File 'app/models/view_quote_bom_item.rb', line 105 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
92 |
# File 'app/models/view_quote_bom_item.rb', line 92 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
96 97 98 |
# File 'app/models/view_quote_bom_item.rb', line 96 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
101 |
# File 'app/models/view_quote_bom_item.rb', line 101 scope :for_voltage, ->(voltage_id) { where(voltage_id: voltage_id) } |
.get_accessories(catalog_id:, product_line_id:) ⇒ Object
Get accessories for a product line
255 256 257 258 259 260 261 262 263 264 |
# File 'app/models/view_quote_bom_item.rb', line 255 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)
280 281 282 283 284 |
# File 'app/models/view_quote_bom_item.rb', line 280 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
219 220 221 222 223 224 225 226 227 228 |
# File 'app/models/view_quote_bom_item.rb', line 219 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)
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'app/models/view_quote_bom_item.rb', line 195 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)
312 313 314 |
# File 'app/models/view_quote_bom_item.rb', line 312 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
231 232 233 234 235 236 237 238 239 240 |
# File 'app/models/view_quote_bom_item.rb', line 231 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)
267 268 269 270 271 272 273 |
# File 'app/models/view_quote_bom_item.rb', line 267 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
317 318 319 |
# File 'app/models/view_quote_bom_item.rb', line 317 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
243 244 245 246 247 248 249 250 251 252 |
# File 'app/models/view_quote_bom_item.rb', line 243 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)
322 323 324 |
# File 'app/models/view_quote_bom_item.rb', line 322 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
291 292 293 294 295 296 297 298 299 300 301 302 303 304 |
# File 'app/models/view_quote_bom_item.rb', line 291 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) ⇒ Object
Get snow melt sensors by SKU
307 308 309 |
# File 'app/models/view_quote_bom_item.rb', line 307 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
328 329 330 331 332 333 334 |
# File 'app/models/view_quote_bom_item.rb', line 328 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
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 |
# File 'app/models/view_quote_bom_item.rb', line 344 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
118 |
# File 'app/models/view_quote_bom_item.rb', line 118 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
116 |
# File 'app/models/view_quote_bom_item.rb', line 116 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
117 |
# File 'app/models/view_quote_bom_item.rb', line 117 scope :order_by_watts, -> { order(watts: :asc) } |
.power_modules ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are power modules. Active Record Scope
83 |
# File 'app/models/view_quote_bom_item.rb', line 83 scope :power_modules, -> { where(item_type: 'power') } |
.refresh ⇒ Object
Refresh the materialized view (concurrently to avoid locking)
Called by MatviewRefreshWorker on schedule
126 127 128 |
# File 'app/models/view_quote_bom_item.rb', line 126 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
84 |
# File 'app/models/view_quote_bom_item.rb', line 84 scope :sensors, -> { where(item_type: 'sensor') } |
.services ⇒ ActiveRecord::Relation<ViewQuoteBomItem>
A relation of ViewQuoteBomItems that are services. Active Record Scope
86 |
# File 'app/models/view_quote_bom_item.rb', line 86 scope :services, -> { where(item_type: 'service') } |
.to_bom_array ⇒ Object
Bulk convert to BOM format (avoids N+1 by using select)
180 181 182 |
# File 'app/models/view_quote_bom_item.rb', line 180 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
175 176 177 |
# File 'app/models/view_quote_bom_item.rb', line 175 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
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 172 |
# File 'app/models/view_quote_bom_item.rb', line 135 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 |