Class: HeatingElementProductLineOption

Inherits:
ApplicationRecord show all
Extended by:
ActionView::Helpers::NumberHelper
Includes:
Models::Auditable
Defined in:
app/models/heating_element_product_line_option.rb

Overview

== Schema Information

Table name: heating_element_product_line_options
Database name: primary

id :integer not null, primary key
application_type :string(255)
environment :string(255)
ideal_cable_spacing :float
instant_quotable :boolean
is_public :boolean
membrane_recommended :boolean
membrane_type :string
priority :integer
require_cable_spacing :boolean
require_expansion_joint :boolean
underlayment_r_value :string(255)
underlayment_recommended :boolean
underlayment_type :string(255)
created_at :datetime not null
updated_at :datetime not null
creator_id :integer
floor_type_id :integer
product_line_id :integer
room_type_id :integer
store_id :integer
sub_floor_type_id :integer
updater_id :integer

Indexes

by_env_sid_rtid_ftid (environment,store_id,room_type_id,floor_type_id)
by_env_sid_sftid (environment,store_id,sub_floor_type_id)
by_ftid_plid_sftid (floor_type_id,product_line_id,sub_floor_type_id)
idx_floor_type_sub_floor_type_store (floor_type_id,sub_floor_type_id,store_id)
idx_is_public (is_public)
index_heating_element_product_line_options_on_creator_id (creator_id)
index_heating_element_product_line_options_on_instant_quotable (instant_quotable)
index_heating_element_product_line_options_on_store_id (store_id)
index_heating_element_product_line_options_on_updater_id (updater_id)
pl_id_mem_rec (product_line_id,membrane_recommended)
rt_id_sft_id_ft_id (room_type_id,sub_floor_type_id,floor_type_id)
sft_id_pl_id_mr (sub_floor_type_id,product_line_id,membrane_recommended)

Constant Summary collapse

ENVIRONMENTS_HASH =
{
  'Indoor' => { key: 'indoor', application_type: 'FH', base_product_line_url: 'floor-heating', default_room_type_seo_key: 'bathroom', default_floor_type_seo_key: 'tile-marble-or-stone' },
  'Outdoor' => { key: 'outdoor', application_type: 'SMRD', base_product_line_url: 'snow-melting', default_room_type_seo_key: 'driveway', default_floor_type_seo_key: 'asphalt' },
  'Countertop' => { key: 'countertop', application_type: 'CH', base_product_line_url: 'countertop-heater', default_room_type_seo_key: 'countertop', default_floor_type_seo_key: 'granite-countertop' },
  'Roof' => { key: 'roof', application_type: 'SMRD', base_product_line_url: 'roof-and-gutter-deicing', default_room_type_seo_key: 'roof', default_floor_type_seo_key: 'asphalt-shingle' },
  'Pipe' => { key: 'pipe', application_type: 'PIPE', base_product_line_url: 'pipe-freeze-protection', default_room_type_seo_key: 'pipe', default_floor_type_seo_key: 'pipe' },
  'Custom Mirror' => { key: 'custom-mirror', application_type: 'CUSTOM_MIRROR', base_product_line_url: 'led-mirror', default_room_type_seo_key: 'custom-mirror', default_floor_type_seo_key: 'custom-mirror' }
}
PUBLIC_ENVIRONMENTS =
%w[Indoor Outdoor]
PUBLIC_ENVIRONMENT_KEYS =
PUBLIC_ENVIRONMENTS.map { |e| ENVIRONMENTS_HASH[e][:key] }
PUBLIC_ENVIRONMENTS_HASH =
ENVIRONMENTS_HASH.select { |k, _v| PUBLIC_ENVIRONMENTS.include?(k) }
APPLICATION_TYPES =

["FH", "SM", "CH", "RD", "PIPE"]

ENVIRONMENTS_HASH.map { |_k, v| v[:application_type] }.uniq
ENVIRONMENTS =

["Indoor", "Outdoor", "Countertop", "Roof", "Pipe"]

ENVIRONMENTS_HASH.map { |k, _v| k }
DEFAULT_SUB_FLOOR_TYPE =
'Unconfirmed/Other'
PUBLIC_SUB_FLOOR_TYPE_KEYS =
SubFloorType.active.by_name.map { |sf| sf.seo_key }
DEFAULT_FLOOR_TYPE_BY_ENVIRONMENT =
{
  'Indoor' => 'Tile, Marble or Stone',
  'Outdoor' => 'Asphalt (new)',
  'Countertop' => 'Granite countertop',
  'Roof' => 'Asphalt shingle',
  'Pipe' => 'Pipe',
  'Custom Mirror' => 'Custom Mirror'
}
PUBLIC_FLOOR_TYPE_KEYS =

ENVIRONMENTS_HASH.map{|k,v| [k,FloorType.find_by(seo_key: v[:default_floor_type_seo_key])&.name]}.to_h

%w[asphalt
existing-asphalt
bamboo-floating
bamboo-glued
bamboo-nailed
carpet-glued
carpet
concrete
existing-concrete
cork
engineered-floating
engineered-glued
engineered-nailed
unfinished-stamped-or-colored-concrete
wood-glued
wood-nailed
heated-balcony
heated-bench
laminate-click-together-floating
laminate-glued-together-floating
laminate-nailed
other
paver
resilients-vinyl-and-luxury-vinyl-tile-lvt
tile-marble-or-stone]
UNDERLAYMENT_TYPES =

FloorType.active.by_environment(PUBLIC_ENVIRONMENTS).by_name.map{|f| f.seo_key}

['ThermalSheet']
MEMBRANE_TYPES =
%w[Prodeso Ditra]
PRODUCT_LINE_BY_HEATING_SYSTEM_TYPE =
{
  'TempZone Flex Roll' => 'floor-heating-tempzone-flex-roll',
  'TempZone Easy Mat' => 'floor-heating-tempzone-easy-mat',
  'TempZone Cable' => 'floor-heating-tempzone-cable',
  'TempZone Ruler Cable' => 'floor-heating-tempzone-ruler-cable',
  'TempZone Thin Cable' => 'floor-heating-tempzone-thin-cable',
  'Environ Flex Roll' => 'floor-heating-environ-flex-roll',
  'Environ Easy Mat' => 'floor-heating-environ-easy-mat',
  'Slab Heat Cable' => 'floor-heating-slab-heat-cable',
  'Slab Heat Mat' => 'floor-heating-slab-heat-mat',
  'Snow Melt Cable' => 'snow-melting-cable',
  'Snow Melt Mat' => 'snow-melting-mat',
  'Snow Melt PowerMat' => 'snowmelt-powermat',
  'Snow Melt OmniMat' => 'snowmelt-omnimat',
  'Snow Melt EcoMat' => 'snowmelt-ecomat'
}
LEGACY_PRODUCT_BY_HEATING_SYSTEM_TYPE =
{ 'TempZone Flex Roll' => 'TZ', 'TempZone Easy Mat' => 'TZEM', 'TempZone Cable' => 'TZC', 'TempZone Thin Cable' => 'TZTC', 'Environ Flex Roll' => 'EVFR', 'Environ Easy Mat' => 'EV2',
'Slab Heat Cable' => 'SHC', 'Slab Heat Mat' => 'SHM', 'Snow Melt Cable' => 'SMC', 'Snow Melt Mat' => 'SMM' }
HEATING_SYSTEM_TYPE_BY_LEGACY_PRODUCT =
{ 'TZ' => 'TempZone Flex Roll', 'TZEM' => 'TempZone Easy Mat', 'TZC' => 'TempZone Cable', 'TZTC' => 'TempZone Thin Cable', 'EV2' => 'Environ Flex Roll', 'EVFR' => 'Environ Flex Roll',
'SMC' => 'Snow Melt Cable', 'SMM' => 'Snow Melt Mat', 'SHC' => 'Slab Heat Cable', 'SHM' => 'Slab Heat Mat' }
CALLOUTS_BY_HEATING_SYSTEM =
{
  'TempZone Flex Roll' => ['Full coverage for any size space, such as kitchens, living rooms, or basements',
                           'Ideal for in-field flexibility',
                           'Single & twin conductor available',
                           '15 watts per sq. ft., 1/8" thick'],
  'TempZone Easy Mat' => ["Floor heating for specific larger areas 'blanket' coverage, such as a family room or bedroom",
                          'No-cut one piece, rectangular mat',
                          '120 Volt / 240 Volt',
                          '15 watts per sq. ft., 1/8" thick'],
  'TempZone Cable' => ['At 3.75 inch spacing: 12 Watts per sq ft. (potentially a primary heat source)',
                       'At 5 inch spacing: 9 Watts per sq ft.',
                       'Ideal for in-field flexibility',
                       'Available in 120v or 240v systems',
                       '3.7 Watts per linear ft'],
  'TempZone Cable 3.75' => ['10-12 Watts per sq ft. (potentially a primary heat source)',
                            'Ideal for in-field flexibility',
                            'Available in 120v or 240v systems',
                            '3.75 Watts per linear ft'],
  'TempZone Cable 5' => ['8-9 Watts per sq ft.',
                         'Ideal for in-field flexibility',
                         'Available in 120v or 240v systems',
                         '3.75 Watts per linear ft'],
  'TempZone Ruler Cable' => ['At 3.75 inch spacing: 12 Watts per sq ft. (potentially a primary heat source)',
                             'At 5 inch spacing: 9 Watts per sq ft.',
                             'Ideal for in-field flexibility',
                             'Available in 120v or 240v systems',
                             '3.7 Watts per linear ft'],
  'TempZone Ruler Cable 3.75' => ['10-12 Watts per sq ft. (potentially a primary heat source)',
                                  'Ideal for in-field flexibility',
                                  'Available in 120v or 240v systems',
                                  '3.75 Watts per linear ft'],
  'TempZone Ruler Cable 5' => ['8-9 Watts per sq ft.',
                               'Ideal for in-field flexibility',
                               'Available in 120v or 240v systems',
                               '3.75 Watts per linear ft'],
  'TempZone Thin Cable' => ['At 3 inch spacing: 12 Watts per sq ft. (potentially a primary heat source)',
                            'At 4 inch spacing: 9 Watts per sq ft.',
                            'Ideal for in-field flexibility',
                            'Available in 120v or 240v systems',
                            '3 Watts per linear ft'],
  'TempZone Thin Cable 3' => ['10-12 Watts per sq ft. (potentially a primary heat source)',
                              'Ideal for in-field flexibility',
                              'Available in 120v or 240v systems',
                              '3 Watts per linear ft'],
  'TempZone Thin Cable 4' => ['8-9 Watts per sq ft.',
                              'Ideal for in-field flexibility',
                              'Available in 120v or 240v systems',
                              '3 Watts per linear ft'],
  'Environ Flex Roll' => ['Ideal for in-field flexibility',
                          'Full coverage for any size space, commonly installed beneath carpet & laminate',
                          '12 watts per sq. ft.'],
  'Environ Easy Mat' => ["Floor heating for specific larger areas 'blanket' coverage, such as a family room",
                         'No-cut one piece',
                         '12 watts per sq. ft.'],
  'Snow Melt Cable' => ['Installs in asphalt, concrete, or under pavers in mortar',
                        'Perfect for driveways, patios, walkways and stairs',
                        'Available in 120V or 240V systems',
                        'Ultimate in flexible installation with free-form cable'],
  'Snow Melt Mat' => ['Installs in asphalt, concrete, or under pavers in mortar',
                      'Perfect for driveways, patios, walkways and stairs',
                      'Available in 120V or 240V systems',
                      'Flexible installation with Flex Roll technology'],
  'Slab Heat Cable' => ['Installs directly in concrete',
                        'Available in 120v or 240v systems',
                        '6 Watts per lineal ft / 20 Watts per sq ft.',
                        'Ultimate in flexible installation with free-form cable'],
  'Slab Heat Mat' => ['Installs directly in concrete',
                      'Available in 120v or 240v systems',
                      '6 Watts per lineal ft / 20 Watts per sq ft.',
                      'Flexible installation with Flex Roll technology']
}
CALLOUTS_BY_HEATING_SYSTEM_LIST =
{
  'TempZone Flex Roll' => ['25 Year Warranty', '1/8" super-thin mat'],
  'TempZone Easy Mat' => ['25 Year Warranty', '1/8" super-thin mat'],
  'TempZone Shower Mat' => ['25 Year Warranty', '1/8" super-thin mat'],
  'TempZone Custom Mat' => ['25 Year Warranty', '1/8" super-thin mat'],
  'TempZone Cable' => ['25 Year Warranty', '3/16" robust cable'],
  'TempZone Ruler Cable' => ['25 Year Warranty', '3/16" robust cable'],
  'TempZone Thin Cable' => ['25 Year Warranty', '1/8" super-thin cable'],
  'TempZone Thin Cable 3.0' => ['25 Year Warranty', '1/8" super-thin cable'],
  'TempZone Cable 3.0' => ['25 Year Warranty', '3/16" robust cable'], # to support legacy
  'TempZone Cable 3.5' => ['25 Year Warranty', '3/16" robust cable'],
  'TempZone Cable 3.75' => ['25 Year Warranty', '3/16" robust cable'],
  'TempZone Ruler Cable 3.0' => ['25 Year Warranty', '3/16" robust cable'], # to support legacy
  'TempZone Ruler Cable 3.5' => ['25 Year Warranty', '3/16" robust cable'],
  'TempZone Ruler Cable 3.75' => ['25 Year Warranty', '3/16" robust cable'],
  'TempZone Thin Cable 3.5' => ['25 Year Warranty', '1/8" super-thin cable'], # to support legacy
  'TempZone Thin Cable 3.75' => ['25 Year Warranty', '1/8" super-thin cable'], # to support legacy
  'TempZone Thin Cable 4.0' => ['25 Year Warranty', '1/8" super-thin cable'],
  'TempZone Cable 4.0' => ['25 Year Warranty', '3/16" robust cable'], # to support legacy
  'TempZone Cable 4.75' => ['25 Year Warranty', '3/16" robust cable'],
  'TempZone Cable 5.0' => ['25 Year Warranty', '3/16" robust cable'],
  'TempZone Ruler Cable 4.0' => ['25 Year Warranty', '3/16" robust cable'], # to support legacy
  'TempZone Ruler Cable 4.75' => ['25 Year Warranty', '3/16" robust cable'],
  'TempZone Ruler Cable 5.0' => ['25 Year Warranty', '3/16" robust cable'],
  'TempZone Thin Cable 4.75' => ['25 Year Warranty', '1/8" super-thin cable'], # to support legacy
  'TempZone Thin Cable 5.0' => ['25 Year Warranty', '1/8" super-thin cable'], # to support legacy
  'Environ Flex Roll' => ['10 Year Warranty under Laminate & Floating Hardwood, 5 Year under Carpet', '1/16" super-thin mat'],
  'Environ Easy Mat' => ['10 Year Warranty under Laminate & Floating Hardwood, 5 Year under Carpet', '1/16" super-thin mat'],
  'Snow Melt Cable' => ['10 Year Warranty', 'Energy efficient twin-conductor heating cable'],
  'Snow Melt Mat' => ['10 Year Warranty', 'Energy efficient twin-conductor heating mat'],
  'Slab Heat Cable' => ['10 Year Warranty', 'Energy efficient twin-conductor heating cable'],
  'Slab Heat Mat' => ['10 Year Warranty', 'Energy efficient twin-conductor heating cable']
}
WARRANTY_BY_HEATING_SYSTEM_LIST =
{
  'TempZone Flex Roll' => '25 Years',
  'TempZone Easy Mat' => '25 Years',
  'TempZone Custom Mat' => '25 Years',
  'TempZone Shower Mat' => '25 Years',
  'TempZone Cable' => '25 Years',
  'TempZone Ruler Cable' => '25 Years',
  'TempZone Thin Cable' => '25 Years',
  'TempZone Thin Cable 3.0' => '25 Years',
  'TempZone Cable 3.0' => '25 Years', # to support legacy
  'TempZone Cable 3.5' => '25 Years',
  'TempZone Cable 3.75' => '25 Years',
  'TempZone Thin Cable 3.5' => '25 Years', # to support legacy
  'TempZone Thin Cable 3.75' => '25 Years', # to support legacy
  'TempZone Thin Cable 4.0' => '25 Years',
  'TempZone Cable 4.0' => '25 Years', # to support legacy
  'TempZone Cable 4.75' => '25 Years',
  'TempZone Cable 5.0' => '25 Years',
  'TempZone Thin Cable 4.75' => '25 Years', # to support legacy
  'TempZone Thin Cable 5.0' => '25 Years', # to support legacy
  'Environ Flex Roll' => '10 Years under Laminate & Floating Hardwood, 5 Years under Carpet',
  'Environ Easy Mat' => '10 Years under Laminate & Floating Hardwood, 5 Years under Carpet',
  'Snow Melt Cable' => '10 Years',
  'Snow Melt Mat' => '10 Years',
  'Slab Heat Cable' => '10 Years',
  'Slab Heat Mat' => '10 Years'
}
WATTS_PER_SQFT_BY_HEATING_SYSTEM_LIST =
{
  'TempZone Flex Roll' => '15',
  'TempZone Easy Mat' => '15',
  'TempZone Custom Mat' => '15',
  'TempZone Shower Mat' => '15',
  'TempZone Cable' => '10-12',
  'TempZone Ruler Cable' => '10-12',
  'TempZone Thin Cable' => '10-12',
  'TempZone Thin Cable 3.0' => '10-12',
  'TempZone Cable 3.0' => '12-15', # to support legacy
  'TempZone Cable 3.5' => '11-14',
  'TempZone Cable 3.75' => '10-12',
  'TempZone Ruler Cable 3.0' => '12-15', # to support legacy
  'TempZone Ruler Cable 3.5' => '11-14',
  'TempZone Ruler Cable 3.75' => '10-12',
  'TempZone Thin Cable 3.5' => '10-12', # to support legacy
  'TempZone Thin Cable 3.75' => '9-11', # to support legacy
  'TempZone Thin Cable 4.0' => '8-9',
  'TempZone Cable 4.0' => '10-12', # to support legacy
  'TempZone Cable 4.75' => '9-10',
  'TempZone Cable 5.0' => '7-9',
  'TempZone Ruler Cable 4.0' => '10-12', # to support legacy
  'TempZone Ruler Cable 4.75' => '9-10',
  'TempZone Ruler Cable 5.0' => '7-9',
  'TempZone Thin Cable 4.75' => '7-9', # to support legacy
  'TempZone Thin Cable 5.0' => '6-8', # to support legacy
  'Environ Flex Roll' => '12',
  'Environ Easy Mat' => '12',
  'Snow Melt Cable' => '50',
  'Snow Melt Mat' => '50',
  'Slab Heat Cable' => '20',
  'Slab Heat Mat' => '20'
}
PRICE_PER_SQFT_BY_HEATING_SYSTEM_LIST =
{
  'TempZone Flex Roll' => '$10 - $12 per sq. ft.',
  'TempZone Easy Mat' => '$10 - $12 per sq. ft.',
  'TempZone Custom Mat' => '$15 - $30 per sq. ft.',
  'TempZone Shower Mat' => '$15 - $20 per sq. ft.',
  'TempZone Cable' => '$15 - $20 per sq. ft.',
  'TempZone Ruler Cable' => '$15 - $20 per sq. ft.',
  'TempZone Thin Cable' => '$8 - $10 per sq. ft.',
  'TempZone Thin Cable 3.0' => '$8 - $10 per sq. ft.',
  'TempZone Cable 3.0' => '$19 - $25 per sq. ft.', # to support legacy
  'TempZone Cable 3.5' => '$16 - $22 per sq. ft.',
  'TempZone Cable 3.75' => '$15 - $20 per sq. ft.',
  'TempZone Ruler Cable 3.0' => '$19 - $25 per sq. ft.', # to support legacy
  'TempZone Ruler Cable 3.5' => '$16 - $22 per sq. ft.',
  'TempZone Ruler Cable 3.75' => '$15 - $20 per sq. ft.',
  'TempZone Thin Cable 3.5' => '$7 - $10 per sq. ft.', # to support legacy
  'TempZone Thin Cable 3.75' => '$6 - $8 per sq. ft.', # to support legacy
  'TempZone Thin Cable 4.0' => '$6 - $8 per sq. ft.',
  'TempZone Cable 4.0' => '$14 - $19 per sq. ft.', # to support legacy
  'TempZone Cable 4.75' => '$13 - $18 per sq. ft.',
  'TempZone Cable 5.0' => '$12 - $17 per sq. ft.',
  'TempZone Ruler Cable 4.0' => '$14 - $19 per sq. ft.', # to support legacy
  'TempZone Ruler Cable 4.75' => '$13 - $18 per sq. ft.',
  'TempZone Ruler Cable 5.0' => '$12 - $17 per sq. ft.',
  'TempZone Thin Cable 4.75' => '$5 - $7 per sq. ft.', # to support legacy
  'TempZone Thin Cable 5.0' => '$5 - $7 per sq. ft.', # to support legacy
  'Environ Flex Roll' => '$10 - $12 per sq. ft.',
  'Environ Easy Mat' => '$10 - $12 per sq. ft.',
  'Snow Melt Cable' => '$8 - $16 per sq. ft.',
  'Snow Melt Mat' => '$10 - $20 per sq. ft.',
  'Slab Heat Cable' => '$8 - $16 per sq. ft.',
  'Slab Heat Mat' => '$10 - $20 per sq. ft.'
}
CABLE_SPACING_BY_HEATING_SYSTEM_LIST =
{
  'TempZone Flex Roll' => '3.0',
  'TempZone Easy Mat' => '3.0',
  'TempZone Shower Mat' => '3.0',
  'TempZone Custom Mat' => '3.0',
  'TempZone Cable' => '3.0" to 5.0',
  'TempZone Ruler Cable' => '3.0" to 5.0',
  'TempZone Thin Cable' => '3.0" to 5.0',
  'TempZone Thin Cable 3.0' => '3.0',
  'TempZone Cable 3.0' => '3.0', # to support legacy
  'TempZone Cable 3.5' => '3.5',
  'TempZone Cable 3.75' => '3.75',
  'TempZone Ruler Cable 3.0' => '3.0', # to support legacy
  'TempZone Ruler Cable 3.5' => '3.5',
  'TempZone Ruler Cable 3.75' => '3.75',
  'TempZone Thin Cable 3.5' => '3.5', # to support legacy
  'TempZone Thin Cable 3.75' => '3.75', # to support legacy
  'TempZone Thin Cable 4.0' => '4.0',
  'TempZone Cable 4.0' => '4.0', # to support legacy
  'TempZone Cable 4.75' => '4.75',
  'TempZone Cable 5.0' => '5.0',
  'TempZone Ruler Cable 4.0' => '4.0', # to support legacy
  'TempZone Ruler Cable 4.75' => '4.75',
  'TempZone Ruler Cable 5.0' => '5.0',
  'TempZone Thin Cable 4.75' => '4.75', # to support legacy
  'TempZone Thin Cable 5.0' => '5.0', # to support legacy
  'Environ Flex Roll' => '3.0',
  'Environ Easy Mat' => '3.0',
  'Snow Melt Cable' => '3.0',
  'Snow Melt Mat' => '3.0',
  'Slab Heat Cable' => '3.0',
  'Slab Heat Mat' => '3.0'
}
INSTALLATION_BLURB_BY_HEATING_SYSTEM_LIST =
{
  'TempZone Flex Roll' => 'Cut/Turn Installation',
  'TempZone Easy Mat' => 'Spot Heat Placement',
  'TempZone Shower Mat' => 'Spot Heat Placement',
  'TempZone Custom Mat' => 'Spot Heat Placement',
  'TempZone Cable' => 'Free-Form: Membrane or Fixing Strips',
  'TempZone Ruler Cable' => 'Free-Form: Membrane or Fixing Strips',
  'TempZone Thin Cable' => 'Free-Form Installation',
  'TempZone Thin Cable 3.0' => 'Free-Form Installation',
  'TempZone Cable 3.0' => 'Free-Form Installation', # to support legacy
  'TempZone Cable 3.5' => 'Free-Form Installation',
  'TempZone Cable 3.75' => 'Free-Form Installation',
  'TempZone Ruler Cable 3.0' => 'Free-Form Installation', # to support legacy
  'TempZone Ruler Cable 3.5' => 'Free-Form Installation',
  'TempZone Ruler Cable 3.75' => 'Free-Form Installation',
  'TempZone Thin Cable 3.5' => 'Free-Form Installation', # to support legacy
  'TempZone Thin Cable 3.75' => 'Free-Form Installation', # to support legacy
  'TempZone Thin Cable 4.0' => 'Free-Form Installation',
  'TempZone Cable 4.0' => 'Free-Form Installation', # to support legacy
  'TempZone Cable 4.75' => 'Free-Form Installation',
  'TempZone Cable 5.0' => 'Free-Form Installation',
  'TempZone Ruler Cable 4.0' => 'Free-Form Installation', # to support legacy
  'TempZone Ruler Cable 4.75' => 'Free-Form Installation',
  'TempZone Ruler Cable 5.0' => 'Free-Form Installation',
  'TempZone Thin Cable 4.75' => 'Free-Form Installation', # to support legacy
  'TempZone Thin Cable 5.0' => 'Free-Form Installation', # to support legacy
  'Environ Flex Roll' => 'Cut/Turn Installation',
  'Environ Easy Mat' => 'Spot Heat Placement',
  'Snow Melt Cable' => 'Free-Form Installation',
  'Snow Melt Mat' => 'Cut/Turn Installation',
  'Slab Heat Cable' => 'Free-Form Installation',
  'Slab Heat Mat' => 'Cut/Turn Installation'
}
INSTALLATION_LEVEL_BY_HEATING_SYSTEM_LIST =
{
  'TempZone Flex Roll' => 'medium',
  'TempZone Easy Mat' => 'easy',
  'TempZone Shower Mat' => 'easy',
  'TempZone Custom Mat' => 'easy',
  'TempZone Cable' => 'easy',
  'TempZone Ruler Cable' => 'easy',
  'TempZone Thin Cable' => 'moderate',
  'TempZone Cable 3.0' => 'moderate', # to support legacy
  'TempZone Ruler Cable 3.0' => 'moderate', # to support legacy
  'TempZone Thin Cable 3.0' => 'moderate',
  'TempZone Cable 3.5' => 'easy',
  'TempZone Cable 3.75' => 'easy',
  'TempZone Ruler Cable 3.5' => 'easy',
  'TempZone Ruler Cable 3.75' => 'easy',
  'TempZone Thin Cable 3.5' => 'easy', # to support legacy
  'TempZone Thin Cable 3.75' => 'easy', # to support legacy
  'TempZone Cable 4.0' => 'moderate', # to support legacy
  'TempZone Ruler Cable 4.0' => 'moderate', # to support legacy
  'TempZone Thin Cable 4.0' => 'moderate',
  'TempZone Cable 4.75' => 'easy',
  'TempZone Cable 5.0' => 'easy',
  'TempZone Ruler Cable 4.75' => 'easy',
  'TempZone Ruler Cable 5.0' => 'easy',
  'TempZone Thin Cable 4.75' => 'easy', # to support legacy
  'TempZone Thin Cable 5.0' => 'easy', # to support legacy
  'Environ Flex Roll' => 'medium',
  'Environ Easy Mat' => 'easy',
  'Snow Melt Cable' => 'moderate',
  'Snow Melt Mat' => 'medium',
  'Slab Heat Cable' => 'moderate',
  'Slab Heat Mat' => 'medium'
}
FEATURES_BY_CONTROL_SKU_LIST =
{
  'Indoor' => {
    'Programmable' => {
      'TH114-AF-GA' => false,
      'TH115-AF-GA-08' => true,
      'TH115-AF-12VDC' => true,
      'UTN4-4999' => false,
      'UTN5-4999' => false,
      'UDG4-4999' => true,
      'UDG4-4999-WY' => true,
      'UDG4-4999-B' => true,
      'UWG4-4999' => true,
      'UWG5-4999-WY' => true,
      'UWG4-4999-B' => true
    },
    'Built-In GFCI' => {
      'TH114-AF-GA' => true,
      'TH115-AF-GA-08' => true,
      'TH115-AF-12VDC' => true,
      'UTN4-4999' => true,
      'UTN5-4999' => true,
      'UDG4-4999' => true,
      'UDG4-4999-WY' => true,
      'UDG4-4999-B' => true,
      'UWG4-4999' => true,
      'UWG5-4999-WY' => true,
      'UWG4-4999-B' => true
    },
    'Comes with floor sensor' => {
      'TH114-AF-GA' => true,
      'TH115-AF-GA-08' => true,
      'TH115-AF-12VDC' => true,
      'UTN4-4999' => true,
      'UTN5-4999' => true,
      'UDG4-4999' => true,
      'UDG4-4999-WY' => true,
      'UDG4-4999-B' => true,
      'UWG4-4999' => true,
      'UWG5-4999-WY' => true,
      'UWG4-4999-B' => true
    },
    'Dual Voltage' => {
      'TH114-AF-GA' => true,
      'TH115-AF-GA-08' => true,
      'TH115-AF-12VDC' => true,
      'UTN4-4999' => true,
      'UTN5-4999' => true,
      'UDG4-4999' => true,
      'UDG4-4999-WY' => true,
      'UDG4-4999-B' => true,
      'UWG4-4999' => true,
      'UWG5-4999-WY' => true,
      'UWG4-4999-B' => true
    },
    'Digital Temperature Readout' => {
      'TH114-AF-GA' => true,
      'TH115-AF-GA-08' => true,
      'TH115-AF-12VDC' => true,
      'UTN4-4999' => true,
      'UTN5-4999' => true,
      'UDG4-4999' => true,
      'UDG4-4999-WY' => true,
      'UWG5-4999-WY' => true,
      'UDG4-4999-B' => true,
      'UWG4-4999' => true,
      'UWG4-4999-B' => true
    },
    'Touchscreen' => {
      'TH114-AF-GA' => false,
      'TH115-AF-GA-08' => false,
      'TH115-AF-12VDC' => false,
      'UTN4-4999' => false,
      'UTN5-4999' => true,
      'UDG4-4999' => true,
      'UDG4-4999-WY' => true,
      'UWG5-4999-WY' => true,
      'UDG4-4999-B' => true,
      'UWG4-4999' => true,
      'UWG4-4999-B' => true
    },
    'Control via Phone/Web' => {
      'TH114-AF-GA' => false,
      'TH115-AF-GA-08' => false,
      'TH115-AF-12VDC' => false,
      'UTN4-4999' => false,
      'UTN5-4999' => false,
      'UDG4-4999' => false,
      'UDG4-4999-WY' => false,
      'UWG4-4999' => true,
      'UWG5-4999-WY' => true,
      'UWG4-4999-B' => true
    },
    'Maximum Load: 15 Amps' => {
      'TH114-AF-GA' => true,
      'TH115-AF-GA-08' => true,
      'TH115-AF-12VDC' => true,
      'UTN4-4999' => true,
      'UTN5-4999' => true,
      'UDG4-4999' => true,
      'UDG4-4999-WY' => true,
      'UDG4-4999-B' => true,
      'UWG4-4999' => true,
      'UWG5-4999-WY' => true,
      'UWG4-4999-B' => true
    },
    '3 Year Warranty' => {
      'TH114-AF-GA' => true,
      'TH115-AF-GA-08' => true,
      'TH115-AF-12VDC' => true,
      'UTN4-4999' => true,
      'UTN5-4999' => true,
      'UDG4-4999' => true,
      'UDG4-4999-WY' => true,
      'UDG4-4999-B' => true,
      'UWG4-4999' => true,
      'UWG5-4999-WY' => true,
      'UWG4-4999-B' => true
    }
  },
  'Outdoor' => {
    'Automatic' => {
      'SCP-120' => true,
      'SCA-DUAL' => true,
      'SCE-120' => true,
      'SCV-DUAL' => true,
      'SCM-120' => false
    },
    'Outdoor Rated' => {
      'SCP-120' => true,
      'SCA-DUAL' => true,
      'SCE-120' => false,
      'SCV-DUAL' => true,
      'SCM-120' => false
    },
    'Moisture Sensor' => {
      'SCP-120' => true,
      'SCA-DUAL' => true,
      'SCE-120' => true,
      'SCV-DUAL' => true,
      'SCM-120' => false
    },
    'Air Temp Sensor' => {
      'SCP-120' => true,
      'SCA-DUAL' => true,
      'SCE-120' => true,
      'SCV-DUAL' => true,
      'SCM-120' => false
    },
    'Slab Temp Sensor' => {
      'SCP-120' => true,
      'SCA-DUAL' => false,
      'SCE-120' => true,
      'SCV-DUAL' => false,
      'SCM-120' => false
    },
    'Digital Temp Readout' => {
      'SCP-120' => false,
      'SCA-DUAL' => false,
      'SCE-120' => true,
      'SCV-DUAL' => false,
      'SCM-120' => false
    },
    'Adjust After-Run Time' => {
      'SCP-120' => true,
      'SCA-DUAL' => true,
      'SCE-120' => true,
      'SCV-DUAL' => true,
      'SCM-120' => false
    },
    'Reliable Manual Control' => {
      'SCM-120' => true
    },
    'Timer Adjustable Up To 12 Hours' => {
      'SCM-120' => true
    },
    'Remote Control via Smart Device' => {
      'SCW-120' => true
    },
    'Works with IFTTT Service' => {
      'SCW-120' => true
    },
    'On/Off Switch for Manual Operation' => {
      'SCW-120' => true
    },
    '2 Year Warranty' => {
      'SCP-120' => true,
      'SCA-DUAL' => true,
      'SCE-120' => true,
      'SCV-DUAL' => true,
      'SCM-120' => true,
      'SCW-120' => true
    }
  }
}
IQ_PRODUCT_HEADER_IMAGE_URL_BY_HEATING_SYSTEM_LIST =
{
  'TempZone Flex Roll' => 'https://ik.warmlyyours.com/img/tempzone-flex-roll-cable-c16e8f.png?tr=w-400,h-400,cm-pad_resize,bg-FFFFFF',
  'TempZone Easy Mat' => 'https://ik.warmlyyours.com/img/iq-tempzone-easy-mat-thumbnail-b412fa.jpeg?tr=w-400,h-400,cm-pad_resize,bg-FFFFFF',
  'TempZone Shower Mat' => 'https://ik.warmlyyours.com/img/iq-tempzone-shower-mat-f0bfbc.png?tr=w-400,h-400,cm-pad_resize,bg-FFFFFF',
  'TempZone Cable' => 'https://ik.warmlyyours.com/img/tempzone-cable-with-grip-strips-qcungu?tr=w-400,h-400,cm-pad_resize,bg-FFFFFF',
  'TempZone Ruler Cable' => 'https://ik.warmlyyours.com/img/iq-tempzone-ruler-cable-with-grip-strips-vmswnq.jpeg?tr=w-400,h-400,cm-pad_resize,bg-FFFFFF',
  'TempZone Thin Cable' => 'https://ik.warmlyyours.com/img/tempzone-cable-with-grip-strips-qcungu?tr=w-400,h-400,cm-pad_resize,bg-FFFFFF',
  'Environ Flex Roll' => 'https://ik.warmlyyours.com/img/iq-environ-flex-roll-thumbnail-c253e0.png?tr=w-400,h-400,cm-pad_resize,bg-FFFFFF',
  'Environ Easy Mat' => 'https://ik.warmlyyours.com/img/iq-environ-easy-mat-thumbnail-b8b7eb.png?tr=w-400,h-400,cm-pad_resize,bg-FFFFFF',
  'Snow Melt Cable' => 'https://ik.warmlyyours.com/img/iq-snow-melt-cable-thumbnail-2ff059.png?tr=w-400,h-400,cm-pad_resize,bg-FFFFFF',
  'Snow Melt Mat' => 'https://ik.warmlyyours.com/img/iq-snow-melting-mat-thumbnail-a0e07c.png?tr=w-400,h-400,cm-pad_resize,bg-FFFFFF',
  'Slab Heat Cable' => 'https://ik.warmlyyours.com/img/iq-slab-heating-cable-thumbnail-aa33fe.png?tr=w-400,h-400,cm-pad_resize,bg-FFFFFF',
  'Slab Heat Mat' => 'https://ik.warmlyyours.com/img/iq-slab-heating-mat-b130ec.png?tr=w-400,h-400,cm-pad_resize,bg-FFFFFF'
}
IQ_PRODUCT_DESCRIPTION_BY_HEATING_SYSTEM_LIST =
{
  'TempZone Flex Roll' => 'Cut-and-Turn Format for Easy Installation',
  'TempZone Easy Mat' => 'Targeted Heat Coverage for Small Areas',
  'TempZone Shower Mat' => 'Targeted Heat Coverage for Shower Areas',
  'TempZone Thin Cable 3.0' => 'Maximum Flexibility at a Minimal Cost',
  'TempZone Cable 3.0' => 'Maximum Flexibility at a Minimal Cost', # to support legacy
  'TempZone Cable 3.5' => 'Maximum Flexibility with Easy Installation',
  'TempZone Cable 3.75' => 'Maximum Flexibility with Easy Installation',
  'TempZone Ruler Cable 3.0' => 'Maximum Flexibility at a Minimal Cost', # to support legacy
  'TempZone Ruler Cable 3.5' => 'Maximum Flexibility with Easy Installation',
  'TempZone Ruler Cable 3.75' => 'Maximum Flexibility with Easy Installation',
  'TempZone Thin Cable 3.5' => 'Maximum Flexibility with Easy Installation', # to support legacy
  'TempZone Thin Cable 3.75' => 'Maximum Flexibility with Easy Installation', # to support legacy
  'TempZone Thin Cable 4.0' => 'Maximum Flexibility at a Minimal Cost',
  'TempZone Cable 4.0' => 'Maximum Flexibility at a Minimal Cost', # to support legacy
  'TempZone Cable 4.75' => 'Maximum Flexibility with Easy Installation',
  'TempZone Cable 5.0' => 'Maximum Flexibility with Easy Installation',
  'TempZone Ruler Cable 4.0' => 'Maximum Flexibility at a Minimal Cost', # to support legacy
  'TempZone Ruler Cable 4.75' => 'Maximum Flexibility with Easy Installation',
  'TempZone Ruler Cable 5.0' => 'Maximum Flexibility with Easy Installation',
  'TempZone Thin Cable 4.75' => 'Maximum Flexibility with Easy Installation', # to support legacy
  'TempZone Thin Cable 5.0' => 'Maximum Flexibility with Easy Installation', # to support legacy
  'Environ Flex Roll' => 'Cut/Turn Installation for Maximum Flexibility',
  'Environ Easy Mat' => 'Easy & Quick Installation in Large Spaces',
  'Snow Melt Cable' => 'Maximum Flexibility at a Minimal Cost',
  'Snow Melt Mat' => 'Cut-and-Turn Format for Easy Installation',
  'Slab Heat Cable' => 'Maximum Flexibility at a Minimal Cost',
  'Slab Heat Mat' => 'Cut-and-Turn Format for Easy Installation'
}
IQ_IMAGE_NAMES_BY_HEATING_SYSTEM =
{
  'TempZone Flex Roll' => 'tempzone.png',
  'TempZone Easy Mat' => 'tempzone.png',
  'TempZone Shower Mat' => 'tempzone.png',
  'TempZone Cable' => 'tempzone.png',
  'TempZone Ruler Cable' => 'tempzone.png',
  'TempZone Thin Cable' => 'tempzone.png',
  'Environ Flex Roll' => 'environ.png',
  'Environ Easy Mat' => 'environ.png',
  'Snow Melt Cable' => 'snow-melt-cable.png',
  'Snow Melt Mat' => 'snow-melt-mat.png',
  'Slab Heat Cable' => 'slab-cable.png',
  'Slab Heat Mat' => 'slab-mat.png'
}
DEFAULT_EXPANSION_JOINT_SPACING =
20
CABLE_SPACING_EXTRA_COPY =
{ '3.0' => '(fixing strips)',
'3.5' => '(DITRA-HEAT)',
'3.75' => '(Prodeso)',
'4.0' => '(fixing strips)',
'4.75' => '(DITRA-HEAT)',
'5.0' => '(fixing strips/Prodeso)' }
COLDEST_MONTH_AVE_TEMP_THRESHOLD =
40.0

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Instance Attribute Summary collapse

Belongs to collapse

Methods included from Models::Auditable

#creator, #updater

Delegated Instance Attributes collapse

Class Method Summary collapse

Instance Method Summary collapse

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::EventPublishable

#publish_event

Instance Attribute Details

#priorityObject (readonly)



58
# File 'app/models/heating_element_product_line_option.rb', line 58

validates :priority, :product_line, :floor_type, presence: true

Class Method Details

.application_type_select_optionsObject



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

def self.application_type_select_options
  APPLICATION_TYPES.map { |o| o }
end

.cable_spacing_for_select(cs_options = {}) ⇒ Object



820
821
822
823
# File 'app/models/heating_element_product_line_option.rb', line 820

def self.cable_spacing_for_select(cs_options = {})
  options = { unit: 'in' }.merge(cs_options)
  tz_cable_spacing_for_select(options) + shc_cable_spacing_for_select(options) + smc_cable_spacing_for_select(options)
end

.compact_prioritiesObject



1168
1169
1170
1171
1172
# File 'app/models/heating_element_product_line_option.rb', line 1168

def self.compact_priorities
  HeatingElementProductLineOption.readable_order.each_with_index do |heplo, i|
    heplo.update_attribute(:priority, i)
  end
end

.const_missing(name) ⇒ Object

Keep constant for backward compatibility, but delegate to method



93
94
95
96
97
98
99
# File 'app/models/heating_element_product_line_option.rb', line 93

def self.const_missing(name)
  if name == :DEFAULT_ROOM_TYPE_BY_ENVIRONMENT
    default_room_type_by_environment
  else
    super
  end
end

.default_cable_spacings(installation_postal_code = nil, sub_floor_type = nil, _floor_type = nil) ⇒ Object



838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
# File 'app/models/heating_element_product_line_option.rb', line 838

def self.default_cable_spacings(installation_postal_code = nil, sub_floor_type = nil, _floor_type = nil)
  cable_spacings = if sub_floor_type
                     [3.75, 4.0] # these are the default starting TZ cable spacings calculated for the IQ, first is with membrane, second is without
                   else
                     [4.0, 3.0] # these are the default starting SM cable spacings calculated for the IQ, first is default, second is to compete with lower wattage systems out there
                   end
  if installation_postal_code && sub_floor_type && avg_temp = AverageMonthlyTemperature.get_from_postal_code(installation_postal_code)

    coldest_month_ave_temp = avg_temp[:average_temperatures]&.min
    if coldest_month_ave_temp && (coldest_month_ave_temp >= COLDEST_MONTH_AVE_TEMP_THRESHOLD) && (sub_floor_type&.seo_key == 'wood')
      # 4 " spacing for wood subfloors in these zones
      cable_spacings = [3.75, 4.0] # these are the default starting TZ cable spacings calculated for the IQ, first is with membrane, second is without
    end
  end
  cable_spacings
end

.default_room_type_by_environmentObject

Lazily load room type mappings to avoid N+1 queries at class load time



84
85
86
87
88
89
90
# File 'app/models/heating_element_product_line_option.rb', line 84

def self.default_room_type_by_environment
  @default_room_type_by_environment ||= begin
    seo_keys = ENVIRONMENTS_HASH.values.map { |v| v[:default_room_type_seo_key] }
    room_types_by_key = RoomType.where(seo_key: seo_keys).index_by(&:seo_key)
    ENVIRONMENTS_HASH.transform_values { |v| room_types_by_key[v[:default_room_type_seo_key]]&.name }
  end
end

.environment_select_optionsObject



781
782
783
# File 'app/models/heating_element_product_line_option.rb', line 781

def self.environment_select_options
  ENVIRONMENTS
end

.get_default_floor_type_id_for_environment(env) ⇒ Object



766
767
768
# File 'app/models/heating_element_product_line_option.rb', line 766

def self.get_default_floor_type_id_for_environment(env)
  FloorType.find_by(name: DEFAULT_FLOOR_TYPE_BY_ENVIRONMENT[env]).id
end

.get_default_product_line_by_heating_system_type(system) ⇒ Object



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

def self.get_default_product_line_by_heating_system_type(system)
  pl = nil
  url = PRODUCT_LINE_BY_HEATING_SYSTEM_TYPE[system]
  pl = ProductLine.find_by(slug_ltree: LtreePaths.slug_ltree_from_legacy_hyphen_url(url))&.id if url
  pl
end

.get_default_room_type_id_for_environment(env) ⇒ Object



754
755
756
# File 'app/models/heating_element_product_line_option.rb', line 754

def self.get_default_room_type_id_for_environment(env)
  RoomType.find_by(name: DEFAULT_ROOM_TYPE_BY_ENVIRONMENT[env]).id
end

.get_default_room_type_key_for_environment(env) ⇒ Object



758
759
760
# File 'app/models/heating_element_product_line_option.rb', line 758

def self.get_default_room_type_key_for_environment(env)
  RoomType.find_by(name: DEFAULT_ROOM_TYPE_BY_ENVIRONMENT[env]).seo_key
end

.get_default_subfloor_type_idObject



762
763
764
# File 'app/models/heating_element_product_line_option.rb', line 762

def self.get_default_subfloor_type_id
  SubFloorType.find_by(name: DEFAULT_SUB_FLOOR_TYPE).id
end

.get_field_array_from_options(assoc_sym, options = {}) ⇒ Object



930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
# File 'app/models/heating_element_product_line_option.rb', line 930

def self.get_field_array_from_options(assoc_sym, options = {})
  # Check if this is a direct field on the model or an association
  is_direct_field = !column_names.include?("#{assoc_sym}_id")

  # Build the base query with filters and prioritized ordering
  # The .prioritized scope is important because callers often use .first to get the highest-priority value
  base_query = HeatingElementProductLineOption.joins(:product_line).prioritized
  options.each do |k, v|
    base_query = base_query.where(k => v) unless k == assoc_sym || v.nil?
  end

  if is_direct_field
    # For direct fields (like :environment, :ideal_cable_spacing), pluck and get unique values
    # We can't use DISTINCT with ORDER BY on non-selected columns, so we pluck and uniq in Ruby
    base_query.pluck(assoc_sym).uniq
  else
    # For associations, get distinct foreign key values
    base_query.pluck("#{assoc_sym}_id").uniq
  end
end

.get_product_line_for_room(rc) ⇒ Object



975
976
977
978
979
980
981
982
# File 'app/models/heating_element_product_line_option.rb', line 975

def self.get_product_line_for_room(rc)
  res = nil
  opt = HeatingElementProductLineOption.all.where(floor_type_id: rc.floor_type_id)
  opt = opt.where(sub_floor_type_id: rc.sub_floor_type_id) if rc.sub_floor_type_id
  opt = opt.prioritized.first
  res = opt.product_line if opt
  res
end

.get_product_line_from_heating_system_type_code(code) ⇒ Object



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

def self.get_product_line_from_heating_system_type_code(code)
  pl = nil
  name = HEATING_SYSTEM_TYPE_BY_LEGACY_PRODUCT[code]
  pl_id = get_default_product_line_by_heating_system_type(name)
  pl = ProductLine.find(pl_id) if pl_id.present?
  pl
end

.get_product_line_from_heating_system_type_name(name) ⇒ Object



960
961
962
963
964
965
# File 'app/models/heating_element_product_line_option.rb', line 960

def self.get_product_line_from_heating_system_type_name(name)
  pl = nil
  pl_id = get_default_product_line_by_heating_system_type(name)
  pl = ProductLine.find(pl_id) if pl_id.present?
  pl
end

.get_relay_panels_from_poles(poles) ⇒ Object



1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
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
# File 'app/models/heating_element_product_line_option.rb', line 1116

def self.get_relay_panels_from_poles(poles)
  # get proper relay panels based on number of poles
  relay_panels_sku_qty = {}
  relay_panels_sku_qty['RLY-4PL-CC'] = 0
  relay_panels_sku_qty['RLY-8PL-CC'] = 0
  relay_panels_sku_qty['RLY-12PL-CC'] = 0
  return relay_panels_sku_qty unless poles > 0

  if poles <= 4
    relay_panels_sku_qty['RLY-4PL-CC'] = 1
  elsif poles <= 8
    relay_panels_sku_qty['RLY-8PL-CC'] = 1
  elsif poles <= 12
    relay_panels_sku_qty['RLY-12PL-CC'] = 1
  elsif poles <= 16
    relay_panels_sku_qty['RLY-8PL-CC'] = 2
  elsif poles > 16
    relay_panels_sku_qty = {}
    qty_4 = 0
    qty_8 = 0
    qty_12 = if (poles % 12) > 8
               (poles / 12).ceil
             else
               (poles / 12).floor
             end
    poles_left = poles - (12 * qty_12)
    if poles_left > 4
      qty_8 = if (poles_left % 8) > 4
                (poles_left / 8).ceil
              else
                (poles_left / 8).floor
              end
      poles_left -= (8 * qty_8)
    end
    qty_4 = (poles_left / 4).ceil if poles_left > 0
    relay_panels_sku_qty['RLY-12PL-CC'] = qty_12 if qty_12 > 0
    relay_panels_sku_qty['RLY-8PL-CC'] = qty_8 if qty_8 > 0
    relay_panels_sku_qty['RLY-4PL-CC'] = qty_4 if qty_4 > 0
  end
  relay_panels_sku_qty
end

.get_suggested_underlayments(options) ⇒ Object



989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
# File 'app/models/heating_element_product_line_option.rb', line 989

def self.get_suggested_underlayments(options)
  logger.debug("get_suggested_underlayments, options: #{options.inspect}")
  raise 'Insulation Sqft must be greater than 0' unless options[:sqft].to_i > 0

  u_lines = {}
  add_one_extra = options[:add_one_extra]
  add_one_extra = false if add_one_extra.nil?
  options[:product_line_id] ||= HeatingElementProductLineOption.get_product_line_from_heating_system_type_name(options[:heating_system_type_name]) if options[:heating_system_type_name]
  opt = HeatingElementProductLineOption.all
  opt = opt.where(floor_type_id: options[:floor_type_id]) unless options[:floor_type_id].blank?
  opt = opt.where(product_line_id: options[:product_line_id]) unless options[:product_line_id].blank?
  opt = opt.where(sub_floor_type_id: options[:sub_floor_type_id]) unless options[:sub_floor_type_id].blank?
  opt = opt.where(ideal_cable_spacing: options[:ideal_cable_spacing]) unless options[:ideal_cable_spacing].blank?
  opt = opt.where(membrane_recommended: options[:membrane_recommended]) unless options[:membrane_recommended].nil? # keep .nil? for boolean - false is a valid value
  opt = opt.prioritized.first
  add_thermalsheet = opt && opt.underlayment_recommended && opt.underlayment_type == 'ThermalSheet'
  add_prodeso = opt && opt.membrane_recommended && opt.membrane_type.index('Prodeso')
  if add_thermalsheet && (thermalsheet_item = Item.find_by(sku: ItemConstants::THERMALSHEET_SHEET_SKU))
    thermalsheet_units = thermalsheet_item.get_quantity_from_sqft(options[:sqft], add_one_extra)
    if thermalsheet_units > 0
      u_lines[thermalsheet_item.sku] = {
        quantity: thermalsheet_units,
        name: thermalsheet_item.name,
        detailed_description: thermalsheet_item.detailed_description,
        item_id: thermalsheet_item.id
      }
    end
  end
  if add_prodeso
    # Calculate Prodeso membrane quantities - compare rolls-only vs sheets-only, pick cheaper
    # Rule: never mix rolls and sheets, use whichever option is more cost-effective
    catalog_id = options[:catalog_id] || 1
    area_sqft = options[:sqft].to_f

    prodeso_items = Item.membranes.includes(:catalog_items).index_by(&:sku)

    # Calculate sheets-only cost (use 10.8 sqft sheets)
    sheet_item = prodeso_items[ItemConstants::MEMBRANE_SHEET_SKU]
    sheet_sqft = sheet_item&.sqft || 10.8
    sheet_price = sheet_item&.catalog_items&.detect { |ci| ci.catalog_id == catalog_id }&.amount || 59.0
    sheets_needed = (area_sqft / sheet_sqft).ceil
    sheets_cost = sheets_needed * sheet_price

    # Calculate rolls-only cost (use 54 and/or 162 sqft rolls)
    roll_54 = prodeso_items[ItemConstants::MEMBRANE_SMALL_SKU]
    roll_162 = prodeso_items[ItemConstants::MEMBRANE_LARGE_SKU]
    roll_54_sqft = roll_54&.sqft || 54.0
    roll_162_sqft = roll_162&.sqft || 162.0
    roll_54_price = roll_54&.catalog_items&.detect { |ci| ci.catalog_id == catalog_id }&.amount || 209.0
    roll_162_price = roll_162&.catalog_items&.detect { |ci| ci.catalog_id == catalog_id }&.amount || 509.0

    # Find optimal roll combination using cost optimization
    # Try different combinations and pick the cheapest
    best_rolls_qty = nil
    best_rolls_cost = Float::INFINITY

    # Helper: ceiling that's forgiving of tiny remainders (< 1 sqft gap is acceptable)
    forgiving_ceil = ->(area, unit_sqft) {
      ratio = area / unit_sqft
      (ratio - ratio.floor) < 0.02 ? ratio.floor.to_i : ratio.ceil.to_i
    }

    # Option 1: Use only 54 sqft rolls
    qty_54_only = [forgiving_ceil.call(area_sqft, roll_54_sqft), 1].max
    cost_54_only = qty_54_only * roll_54_price
    if cost_54_only < best_rolls_cost
      best_rolls_cost = cost_54_only
      best_rolls_qty = { ItemConstants::MEMBRANE_SMALL_SKU => qty_54_only }
    end

    # Option 2: Use 162 sqft rolls + 54 sqft rolls for remainder
    if area_sqft > roll_54_sqft
      qty_162 = (area_sqft / roll_162_sqft).floor
      remainder = area_sqft - (qty_162 * roll_162_sqft)
      qty_54 = remainder > 0 ? forgiving_ceil.call(remainder, roll_54_sqft) : 0

      cost_mixed = (qty_162 * roll_162_price) + (qty_54 * roll_54_price)
      if cost_mixed < best_rolls_cost
        best_rolls_cost = cost_mixed
        best_rolls_qty = {}
        best_rolls_qty[ItemConstants::MEMBRANE_LARGE_SKU] = qty_162 if qty_162.positive?
        best_rolls_qty[ItemConstants::MEMBRANE_SMALL_SKU] = qty_54 if qty_54.positive?
      end

      # Option 3: Round up to one more 162 roll (might be cheaper than multiple 54s)
      qty_162_rounded = forgiving_ceil.call(area_sqft, roll_162_sqft)
      cost_162_only = qty_162_rounded * roll_162_price
      if cost_162_only < best_rolls_cost
        best_rolls_cost = cost_162_only
        best_rolls_qty = { ItemConstants::MEMBRANE_LARGE_SKU => qty_162_rounded }
      end
    end

    rolls_qty = best_rolls_qty
    rolls_cost = best_rolls_cost

    rolls_cost = (rolls_qty[ItemConstants::MEMBRANE_LARGE_SKU] || 0) * roll_162_price + (rolls_qty[ItemConstants::MEMBRANE_SMALL_SKU] || 0) * roll_54_price

    # Rule: If area >= smallest roll size, always use rolls (more practical for installation)
    # Otherwise, compare cost and pick cheaper option
    use_rolls = area_sqft >= roll_54_sqft || rolls_cost < sheets_cost

    if use_rolls
      rolls_qty.each do |sku, qty|
        next unless qty.positive?

        item = prodeso_items[sku]
        u_lines[sku] = {
          quantity: qty,
          name: item.name,
          detailed_description: item.detailed_description,
          item_id: item.id
        }
      end
    elsif sheet_item
      u_lines[sheet_item.sku] = {
        quantity: sheets_needed,
        name: sheet_item.name,
        detailed_description: sheet_item.detailed_description,
        item_id: sheet_item.id
      }
    end
  end
  logger.debug("get_suggested_underlayments, u_lines: #{u_lines.inspect}")
  u_lines
end

.get_suggested_underlayments_for_room(rc) ⇒ Object



984
985
986
987
# File 'app/models/heating_element_product_line_option.rb', line 984

def self.get_suggested_underlayments_for_room(rc)
  HeatingElementProductLineOption.get_suggested_underlayments({ sqft: rc.insulation_surface, sub_floor_type_id: rc.sub_floor_type_id, floor_type_id: rc.floor_type_id, product_line_id: rc.heating_system_product_line_id,
                                                                catalog_id: rc.catalog.id })
end

.ideal_cable_spacing_select_optionsObject



789
790
791
# File 'app/models/heating_element_product_line_option.rb', line 789

def self.ideal_cable_spacing_select_options
  cable_spacing_for_select
end

.instant_quotable_environmentsObject

Cached list of environments that support instant quoting



111
112
113
# File 'app/models/heating_element_product_line_option.rb', line 111

def self.instant_quotable_environments
  @instant_quotable_environments ||= get_field_array_from_options(:environment, { instant_quotable: true }).uniq
end

.membrane_type_select_optionsObject



797
798
799
# File 'app/models/heating_element_product_line_option.rb', line 797

def self.membrane_type_select_options
  MEMBRANE_TYPES.map { |o| [o] }
end

.next_priorityObject



1164
1165
1166
# File 'app/models/heating_element_product_line_option.rb', line 1164

def self.next_priority
  (HeatingElementProductLineOption.maximum(:priority) || 0) + 1
end

.options_for_select(assoc_sym, assoc_sort_field = 'name', assoc_name_method = 'name', options = {}, include_blank = false, assoc_key = 'id') ⇒ Object



889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
# File 'app/models/heating_element_product_line_option.rb', line 889

def self.options_for_select(assoc_sym, assoc_sort_field = 'name', assoc_name_method = 'name', options = {}, include_blank = false, assoc_key = 'id')
  Rails.logger.debug("HeatingElementProductLineOption.options_for_select", assoc_sym: assoc_sym, assoc_sort_field: assoc_sort_field)

  assoc_fk = "#{assoc_sym}_id"

  # Build the base query with filters
  base_query = HeatingElementProductLineOption.all
  options.each do |k, v|
    base_query = base_query.where(k => v) unless k == assoc_sym || v.nil?
  end

  # Get distinct association IDs that match the filters
  matching_ids = base_query.where.not(assoc_fk => nil).distinct.pluck(assoc_fk)

  return [] if matching_ids.empty?

  # Query the association table directly for just the fields we need
  assoc_class = reflect_on_association(assoc_sym).klass
  order_clause = assoc_sort_field ? { assoc_sort_field => :asc } : {}

  # Check if assoc_name_method is a database column or a Ruby method
  # If it's a method (like heating_system_type_name), we need to load objects and call the method
  is_column = assoc_class.column_names.include?(assoc_name_method.to_s)
  is_key_column = assoc_class.column_names.include?(assoc_key.to_s)

  if is_column && is_key_column
    # Efficient path: both are columns, use pluck
    returned_options = assoc_class
      .where(id: matching_ids)
      .order(order_clause)
      .pluck(assoc_name_method, assoc_key)
  else
    # Fallback path: one or both are methods, load objects
    records = assoc_class.where(id: matching_ids).order(order_clause)
    returned_options = records.map { |r| [r.public_send(assoc_name_method), r.public_send(assoc_key)] }
  end

  returned_options.insert(0, ['----', nil]) if returned_options.any? && include_blank == true
  returned_options
end

.options_scope(options = {}) ⇒ Object



951
952
953
954
955
956
957
958
# File 'app/models/heating_element_product_line_option.rb', line 951

def self.options_scope(options = {})
  Rails.logger.debug("HeatingElementProductLineOption.options_scope")
  res = HeatingElementProductLineOption.includes(:product_line, :sub_floor_type, :floor_type)
  (options || {}).each do |k, v|
    res = res.where(k => v) if v.present?
  end
  res
end

.populate_optionsObject

DEPRECATED USED ONLY FOR MIGRATION



1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
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
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
# File 'app/models/heating_element_product_line_option.rb', line 1184

def self.populate_options # DEPRECATED USED ONLY FOR MIGRATION
  application_types = HeatingElementProductLineOption::APPLICATION_TYPES
  env_by_app_type = { 'FH' => ['Indoor'], 'SM' => ['Outdoor'], 'CH' => ['Countertop'] }
  heating_system_types = ['TempZone Flex Roll', 'TempZone Easy Mat', 'Environ Flex Roll', 'Environ Easy Mat', 'Slab Heat Cable', 'Slab Heat Mat', 'Snow Melt Cable', 'Snow Melt Mat']
  heating_system_type_pl_ids = [12, 102, 99, 21, 25, 28, 37, 40]
  priority = 0
  application_types.each do |application_type|
    # puts "application_type: #{application_type}"
    env_by_app_type[application_type].each do |environment|
      # puts "environment: #{environment}"
      sub_floor_type_ids ||= SubFloorType.active.by_name.map { |sf| sf.id }
      sub_floor_type_ids = [nil] if environment == 'Outdoor' # sub floor only applies to Indoor
      sub_floor_type_ids.each do |sub_floor_type_id|
        sub_floor_type = nil
        sub_floor_type = SubFloorType.find(sub_floor_type_id) if sub_floor_type_id
        # puts "sub_floor_type: #{sub_floor_type ? sub_floor_type.name : 'nil'}"
        floor_types = FloorType.active.by_environment(environment)
        floor_types.each do |floor_type|
          # puts "floor_type: #{floor_type.name}"
          # Need to exclude unfinished stamped concrete (id 5) except for newly poured concrete sub floor (id 3)
          if floor_type.id == 5 and sub_floor_type.id != 3
          # puts "skipping floor_type floor_type: #{floor_type.name} and sub_floor_type: #{sub_floor_type ? sub_floor_type.name : 'nil'}"
          else
            require_expansion_joint = floor_type.require_expansion_joint? # use floor type to determine this for now
            # puts "require_expansion_joint: #{require_expansion_joint}"
            # get all public and not public (by passing for_iq=nil) heating_types for floor_type
            HeatingSystem.heating_system_types_for_floor_type(floor_type, room_type = nil, for_iq = nil).each do |heating_system_type|
              # get heating system type's corresponding product line
              # puts "heating_system_type: #{heating_system_type}"
              ind = heating_system_types.index(heating_system_type)
              product_line = ProductLine.find(heating_system_type_pl_ids[ind])
              # puts "product_line: #{product_line.name}"
              # use legacy HeatingSystem to populate various options
              heating_system = HeatingSystem.new(heating_system_type)
              require_cable_spacing = heating_system.require_cable_spacing?
              # puts "require_cable_spacing: #{require_cable_spacing}"
              ideal_cable_spacing = nil
              ideal_cable_spacing = heating_system.ideal_cable_spacing(floor_type) if require_cable_spacing
              # puts "ideal_cable_spacing: #{ideal_cable_spacing}"
              is_public = false
              heating_system_types_public = HeatingSystem.heating_system_types_for_floor_type(floor_type, room_type = nil, for_iq = true)
              # figure out if public by passing in for_iq true and seeing current heating_system_type is in there
              is_public = true if heating_system_types_public.include?(heating_system_type)
              # puts "is_public: #{is_public}"
              # find insulation/underlayment options, here we reproduce some of the logic in floor_type's suggested_underlayment method
              underlayment_recommended = false
              add_thermalsheet = false
              product_type = begin
                heating_system.code
              rescue StandardError
                nil
              end
              product_type ||= floor_type.product # default to floor type's recommendation
              if %w[EV2 TZ EVFR TZEM].include? product_type &&
                                               !((sub_floor_type and sub_floor_type.name.to_s.downcase.index('wood')) || floor_type.is_carpet?)
                add_thermalsheet = true
              end
              underlayment_recommended = true if add_thermalsheet
              # puts "underlayment_recommended: #{underlayment_recommended}"
              underlayment_type = nil
              underlayment_type = 'ThermalSheet' if add_thermalsheet
              # puts "underlayment_type: #{underlayment_type}"
              # for underlayment_r_value, just use this if then else, which is the only sense I can make out from the table produced by Scott here: https://docs.google.com/a/warmlyyours.com/spreadsheet/ccc?key=0AlWFu-aXWRVZdGxxaFBjamVSLVVKaFFiSGt0XzJOamc#gid=1
              floor_type_name_lower_case = floor_type.name.downcase
              if heating_system_type.downcase.index('tempzone') and (floor_type_name_lower_case.index('bamboo') or floor_type_name_lower_case.index('') or floor_type_name_lower_case.index('carpet') or floor_type_name_lower_case.index('engineered') or floor_type_name_lower_case.index('laminate'))
                'L'
              end
              'H' if heating_system_type.downcase.index('environ') and floor_type_name_lower_case.index('carpet')
              # puts "underlayment_r_value: #{underlayment_r_value}"
              # great, now create the damn thing
              priority += 1
              # puts "priority: #{priority}"
              attributes = { priority: priority, application_type: application_type, environment: environment, sub_floor_type_id: sub_floor_type_id, floor_type_id: floor_type.id, product_line_id: product_line.id,
                             is_public: is_public, require_expansion_joint: require_expansion_joint, require_cable_spacing: require_cable_spacing, ideal_cable_spacing: ideal_cable_spacing, underlayment_recommended: underlayment_recommended, underlayment_type: underlayment_type }
              # puts "creating HeatingElementProductLineOption: attributes: #{attributes.inspect}"
              HeatingElementProductLineOption.create(attributes)
            end
          end
        end
      end
    end
  end
end

.prioritizedActiveRecord::Relation<HeatingElementProductLineOption>

A relation of HeatingElementProductLineOptions that are prioritized. Active Record Scope

Returns:

See Also:



66
# File 'app/models/heating_element_product_line_option.rb', line 66

scope :prioritized, -> { joins(:product_line).order('heating_element_product_line_options.is_public DESC, heating_element_product_line_options.priority, product_lines.priority') }

.product_line_is_iq_supported?(pl) ⇒ Boolean

Returns:

  • (Boolean)


1174
1175
1176
1177
1178
# File 'app/models/heating_element_product_line_option.rb', line 1174

def self.product_line_is_iq_supported?(pl)
  res = false
  res = PRODUCT_LINE_BY_HEATING_SYSTEM_TYPE[pl.heating_system_type_name].present? if pl and pl.heating_system_type_name.present?
  res
end

.product_line_options_for_selectObject



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

def self.product_line_options_for_select
  HeatingElementProductLineOption.joins(:product_line).pluck(Arel.sql('distinct product_lines.lineage_expanded, product_lines.id')).sort
end

.public_environment_select_optionsObject



785
786
787
# File 'app/models/heating_element_product_line_option.rb', line 785

def self.public_environment_select_options
  PUBLIC_ENVIRONMENTS_HASH.map { |k, _v| k }
end

.public_room_type_keysObject



101
102
103
# File 'app/models/heating_element_product_line_option.rb', line 101

def self.public_room_type_keys
  @public_room_type_keys ||= RoomType.by_environment(PUBLIC_ENVIRONMENTS).pluck(:seo_key)
end

.readable_orderActiveRecord::Relation<HeatingElementProductLineOption>

A relation of HeatingElementProductLineOptions that are readable order. Active Record Scope

Returns:

See Also:



63
64
65
# File 'app/models/heating_element_product_line_option.rb', line 63

scope :readable_order, -> {
  joins(:floor_type).order('heating_element_product_line_options.application_type ASC, heating_element_product_line_options.environment ASC, heating_element_product_line_options.sub_floor_type_id ASC, floor_types.name ASC, heating_element_product_line_options.priority ASC').readonly(false)
}

.reset_cached_constants!Object

Keep constant for backward compatibility



116
117
118
119
120
121
# File 'app/models/heating_element_product_line_option.rb', line 116

def self.reset_cached_constants!
  @default_room_type_by_environment = nil
  @public_room_type_keys = nil
  @valid_membrane_types = nil
  @instant_quotable_environments = nil
end

.shc_cable_spacing_for_select(cs_options = {}) ⇒ Object



831
832
833
834
835
836
# File 'app/models/heating_element_product_line_option.rb', line 831

def self.shc_cable_spacing_for_select(cs_options = {})
  options = { unit: 'in' }.merge(cs_options)
  [["SHC 12 watts/sqft - #{RubyUnits::Unit.new('6.0 in').to_s(options[:unit])}", 6.0],
   ["SHC 15 watts/sqft - #{RubyUnits::Unit.new('5.0 in').to_s(options[:unit])}", 5.0], ["SHC 17 watts/sqft - #{RubyUnits::Unit.new('4.0 in').to_s(options[:unit])}", 4.0],
   ["SHC 20 watts/sqft - #{RubyUnits::Unit.new('3.0 in').to_s(options[:unit])}", 3.0]]
end

.smc_cable_spacing_for_select(cs_options = {}) ⇒ Object



825
826
827
828
829
# File 'app/models/heating_element_product_line_option.rb', line 825

def self.smc_cable_spacing_for_select(cs_options = {})
  options = { unit: 'in' }.merge(cs_options)
  [["SMC 25 watts/sqft - #{RubyUnits::Unit.new('6.0 in').to_s(options[:unit])}", 6.0], ["SMC up to 31.5 watts/sqft - #{RubyUnits::Unit.new('5.0 in').to_s(options[:unit])}", 5.0],
   ["SMC up to 33 watts/sqft - #{RubyUnits::Unit.new('4.5 in').to_s(options[:unit])}", 4.5], ["SMC up to 38.5 watts/sqft - #{RubyUnits::Unit.new('4.0 in').to_s(options[:unit])}", 4.0], ["SMC up to 43 watts/sqft - #{RubyUnits::Unit.new('3.5 in').to_s(options[:unit])}", 3.5], ["SMC up to 50 watts/sqft - #{RubyUnits::Unit.new('3.0 in').to_s(options[:unit])}", 3.0], ["SMC up to 60 watts/sqft - #{RubyUnits::Unit.new('2.5 in').to_s(options[:unit])}", 2.5]]
end

.spacing_to_unit(spacing_in_inches, unit = 'in') ⇒ Object



873
874
875
876
877
878
# File 'app/models/heating_element_product_line_option.rb', line 873

def self.spacing_to_unit(spacing_in_inches, unit = 'in')
  n = RubyUnits::Unit.new(spacing_in_inches, 'in')
  n = n.convert_to(unit) if unit != 'in'
  n = number_with_precision(n.scalar, strip_insignificant_zeros: true)
  "#{n} #{unit}"
end

.tz_cable_spacing_for_select(cs_options = {}) ⇒ Object



855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
# File 'app/models/heating_element_product_line_option.rb', line 855

def self.tz_cable_spacing_for_select(cs_options = {})
  options = { unit: 'in', tz_cable_pl_url: ProductLineUrls::FLOOR_HEATING_TEMPZONE_CABLE }.merge(cs_options)
  opts = if options[:sftid] and sft = SubFloorType.find(options[:sftid]).name and sft.index('Concrete')
           # disable 4 inch and greater spacing for concrete subfloors
           []
         else
           [["TZ (Prodeso membrane) #{tz_cable_watts_per_sqft(5.0, options)} watts/sqft - #{spacing_to_unit(5.0, options[:unit])} spacing", 5.0],
            ["TZ (DITRA membrane) #{tz_cable_watts_per_sqft(4.75, options)} watts/sqft - #{spacing_to_unit(4.75, options[:unit])} spacing", 4.75],
            ["TZ #{tz_cable_watts_per_sqft(4.0, options)} watts/sqft - #{spacing_to_unit(4.0, options[:unit])} spacing", 4.0]]
         end
  opts += [["TZ (Prodeso membrane / Free form) #{tz_cable_watts_per_sqft(3.75, options)} watts/sqft - #{spacing_to_unit(3.75, options[:unit])} spacing", 3.75]]
  opts += [["TZ (DITRA membrane / Free form) #{tz_cable_watts_per_sqft(3.5, options)} watts/sqft - #{spacing_to_unit(3.5, options[:unit])} spacing", 3.5]]
  opts += [["TZ #{tz_cable_watts_per_sqft(3.0, options)} watts/sqft - #{spacing_to_unit(3.0, options[:unit])} spacing", 3.0]]
  opts += [["TZ (Alternating 2/3 stud Prodeso Membrane 2.5/3.75\" Spacing) #{tz_cable_watts_per_sqft(3.125, options)} average watts/sqft - average #{spacing_to_unit(3.125, options[:unit])} spacing", 3.125]] unless options[:is_public] == true
  opts += [["TZ (Alternating 2/3 stud DITRA Membrane 2.3/3.5\" Spacing) #{tz_cable_watts_per_sqft(2.9, options)} average watts/sqft - average #{spacing_to_unit(2.9, options[:unit])} spacing", 2.9]] unless options[:is_public] == true
  opts
end

.tz_cable_watts_per_sqft(cs, options) ⇒ Object

cs = cable spacing, decimal in inch. So 3.5 for 3" 1/2



881
882
883
884
885
886
887
# File 'app/models/heating_element_product_line_option.rb', line 881

def self.tz_cable_watts_per_sqft(cs, options)
  slug = LtreePaths.slug_ltree_from_legacy_hyphen_url(options[:tz_cable_pl_url]) || options[:tz_cable_pl_url].to_s
  tz_cable_pl = ProductLine.find_by(slug_ltree: slug)
  watts_per_sqft = RoomConfiguration::WATTS_PER_SQUARE_FOOT["#{tz_cable_pl.heating_system_type_name} #{cs}"]
  watts_per_sqft ||= (RoomConfiguration::WATTS_PER_SQUARE_FOOT["#{tz_cable_pl.heating_system_type_name}"] * 3.0 / cs).round(1)
  watts_per_sqft
end

.underlayment_type_select_optionsObject



793
794
795
# File 'app/models/heating_element_product_line_option.rb', line 793

def self.underlayment_type_select_options
  UNDERLAYMENT_TYPES.map { |o| [o] }
end

.valid_membrane_typesObject

Cached list of valid membrane types to avoid expensive DISTINCT queries on every request



106
107
108
# File 'app/models/heating_element_product_line_option.rb', line 106

def self.valid_membrane_types
  @valid_membrane_types ||= pluck(Arel.sql('distinct membrane_type')).compact
end

.voltage_options_for_select(heating_system_product_line, include_24 = false, environment = nil, use_smc_voltages = nil) ⇒ Object



801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
# File 'app/models/heating_element_product_line_option.rb', line 801

def self.voltage_options_for_select(heating_system_product_line, include_24 = false, environment = nil, use_smc_voltages = nil)
  hs_name = heating_system_product_line&.heating_system_type_name
  if /TempZone Custom Mat/i.match?(hs_name)
    res = [120, 208, 240]
  elsif /TempZone/i.match?(hs_name)
    res = [120, 240]
    res += [24] if include_24 == true
  elsif /Countertop Heater/i.match?(hs_name)
    res = []
  elsif (hs_name =~ /Snow/i && hs_name =~ /Cable/i) or hs_name =~ /Pipe Freeze Protection/i or hs_name =~ /Roof & Gutter/i
    res = [120, 208, 240, 277]
  else
    res = [120, 240]
    res += [24] if include_24 == true
    res += [208, 277] if environment.to_s.downcase == 'outdoor' and use_smc_voltages
  end
  res
end

Instance Method Details

#floor_typeFloorType

Returns:

See Also:

Validations:



53
# File 'app/models/heating_element_product_line_option.rb', line 53

belongs_to :floor_type, inverse_of: :heating_element_product_line_options, optional: true

#heating_system_type_nameObject

Alias for Product_line#heating_system_type_name

Returns:

  • (Object)

    Product_line#heating_system_type_name

See Also:



61
# File 'app/models/heating_element_product_line_option.rb', line 61

delegate :heating_system_type_name, to: :product_line

#name_with_cable_spacingObject



1268
1269
1270
1271
1272
1273
1274
1275
# File 'app/models/heating_element_product_line_option.rb', line 1268

def name_with_cable_spacing
  extra = ''
  if ideal_cable_spacing.present?
    extra = " at #{ideal_cable_spacing} inch spacing"
    extra += " #{CABLE_SPACING_EXTRA_COPY[ideal_cable_spacing.to_s]}" if product_line.slug_ltree.to_s.include?('tempzone.cable')
  end
  "#{product_line.heating_system_type_name}#{extra}"
end

#product_lineProductLine

Validations:



54
# File 'app/models/heating_element_product_line_option.rb', line 54

belongs_to :product_line, inverse_of: :heating_element_product_line_options, optional: true

#product_line_is_heating_system_typeObject

Alias for Product_line#is_heating_system_type

Returns:

  • (Object)

    Product_line#product_line_is_heating_system_type

See Also:



1162
# File 'app/models/heating_element_product_line_option.rb', line 1162

delegate :is_heating_system_type, to: :product_line, prefix: true

#room_typeRoomType

Returns:

See Also:



56
# File 'app/models/heating_element_product_line_option.rb', line 56

belongs_to :room_type, inverse_of: :heating_element_product_line_options, optional: true

#storeStore

Returns:

See Also:



55
# File 'app/models/heating_element_product_line_option.rb', line 55

belongs_to :store, inverse_of: :heating_element_product_line_options, optional: true

#sub_floor_typeSubFloorType



52
# File 'app/models/heating_element_product_line_option.rb', line 52

belongs_to :sub_floor_type, inverse_of: :heating_element_product_line_options, optional: true

#summaryObject



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

def summary
  "#{is_public ? 'Public' : 'Internal'}>#{application_type}>#{environment}>#{sub_floor_type.name + '>' if sub_floor_type}#{floor_type.name}>#{product_line.heating_system_type_name}"
end