Class: HeatingSystemCalculator::AccessoriesFinder
- Inherits:
-
BaseItemFinder
- Object
- BaseItemFinder
- HeatingSystemCalculator::AccessoriesFinder
- Defined in:
- app/services/heating_system_calculator/accessories_finder.rb
Overview
Service object: accessories finder.
Constant Summary collapse
- CONCRETE_ONLY_CABLE_ACCESSORY_SKUS =
SKUs that only make sense when fixing strips are attached to a concrete slab
(you can't nail/staple into concrete, so you need the screws, drill bit, and
double-sided tape). Fixing strips themselves are NOT concrete-only — they're
required for any non-membrane install (plywood, OSB, concrete, etc.). [ ItemConstants::TZ_CABLE_TAPE_SKU, ItemConstants::TZ_CABLE_CONCRETE_SCREW_LARGE_SKU, ItemConstants::TZ_CABLE_CONCRETE_SCREW_SMALL_SKU, ItemConstants::TZ_CABLE_CONCRETE_DRILL_BIT_SKU ].freeze
Instance Attribute Summary collapse
-
#accessories ⇒ Object
readonly
Returns the value of attribute accessories.
-
#error ⇒ Object
readonly
Returns the value of attribute error.
Instance Method Summary collapse
- #calculate_elements_qty_and_tz_cable_accessories_qty(elements) ⇒ Object
- #calculate_tstat_qty_and_pm_control_qty(controls) ⇒ Object
- #find_accessories(elements, controls) ⇒ Object
-
#initialize(options) ⇒ AccessoriesFinder
constructor
A new instance of AccessoriesFinder.
- #select_floor_heating_accessories(elements, controls) ⇒ Object
- #select_snow_melting_accessories(elements, _controls) ⇒ Object
- #select_underlayment(_elements, _controls) ⇒ Object
Methods inherited from BaseItemFinder
#get_number_of_poles_from_consolidated_elements, #get_total_from_solution
Constructor Details
#initialize(options) ⇒ AccessoriesFinder
Returns a new instance of AccessoriesFinder.
8 9 10 11 12 13 14 15 |
# File 'app/services/heating_system_calculator/accessories_finder.rb', line 8 def initialize() @heated_area = [:heated_area] @heating_system = [:heating_system] @accessory_set = [:accessory_set] @underlayment_set = [:underlayment_set] @rough_in_kit_for_tstat = [:rough_in_kit_for_tstat] @rough_in_kit_for_power_module = [:rough_in_kit_for_power_module] end |
Instance Attribute Details
#accessories ⇒ Object (readonly)
Returns the value of attribute accessories.
6 7 8 |
# File 'app/services/heating_system_calculator/accessories_finder.rb', line 6 def accessories @accessories end |
#error ⇒ Object (readonly)
Returns the value of attribute error.
6 7 8 |
# File 'app/services/heating_system_calculator/accessories_finder.rb', line 6 def error @error end |
Instance Method Details
#calculate_elements_qty_and_tz_cable_accessories_qty(elements) ⇒ Object
123 124 125 126 127 128 129 130 131 132 133 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 172 173 174 175 176 177 178 |
# File 'app/services/heating_system_calculator/accessories_finder.rb', line 123 def calculate_elements_qty_and_tz_cable_accessories_qty(elements) # decide on quantities, generally one per heating element except for TZ cable guides qty = 0 tz_cable_accessories_qtys = {} # decide if we find tz cable strips and related accessories find_tz_cable_strips = RoomConfiguration::PUBLIC_TZ_CABLE_SPACINGS.include?(@heating_system.cable_spacing) && !@heating_system.membrane_recommended ? true : false # Batch load all element items upfront to avoid N+1 queries element_skus = elements.pluck('sku').compact items_by_sku = Item.where(sku: element_skus).index_by(&:sku) elements.each do |e| qty += e['qty'] || 1 next unless @heating_system.heating_system_product_line.is_cable_system? item = items_by_sku[e['sku']] next unless item && (item.is_tz_thin_cable? || item.is_tz_cable?) && find_tz_cable_strips cable_spacing = @heating_system.cable_spacing.to_f.round number_of_strips = item.calculate_number_of_fixing_strips_at_cable_spacing(cable_spacing) if number_of_strips&.> 0 tz_cable_accessories_qtys[ItemConstants::TZ_CABLE_GRIPSTRIP_SKU] ||= 0 tz_cable_accessories_qtys[ItemConstants::TZ_CABLE_GRIPSTRIP_SKU] += number_of_strips end end num_strips = ItemConstants::TZ_CABLE_STRIP_SKUS.sum { |sku| tz_cable_accessories_qtys[sku].to_i } if @heating_system.sub_floor_type&.seo_key&.index('concrete') && num_strips.positive? # we have concrete subfloor and TZ cable, we need concrete drill bits, screws and tape # figure out the number of screws num_screws = (num_strips * HeatingSystemItems::SCREWS_PER_STRIP_WITH_OVERAGE).round # figure out the length of tape length_tape = (num_strips * HeatingSystemItems::TAPE_FEET_PER_STRIP_WITH_OVERAGE).round qtys_by_sku = {} # calculate number of screw packs and drill bit of various skus # if we are most of the way into a 100 pack take that because of the included drill bit large_pack_size = HeatingSystemItems::CONCRETE_SCREW_PACK_LARGE_SIZE small_pack_size = HeatingSystemItems::CONCRETE_SCREW_PACK_SMALL_SIZE qtys_by_sku[ItemConstants::TZ_CABLE_CONCRETE_SCREW_LARGE_SKU] = (num_screws / large_pack_size).round # figure out if we need any small packs to reach the number of screws needed num_exc_screws = (num_screws - (large_pack_size * qtys_by_sku[ItemConstants::TZ_CABLE_CONCRETE_SCREW_LARGE_SKU])) qtys_by_sku[ItemConstants::TZ_CABLE_CONCRETE_SCREW_SMALL_SKU] = (num_exc_screws.to_f / small_pack_size).ceil if num_exc_screws.positive? # figure out if we need to enclose a drill bit qtys_by_sku[ItemConstants::TZ_CABLE_CONCRETE_DRILL_BIT_SKU] = 1 if qtys_by_sku[ItemConstants::TZ_CABLE_CONCRETE_SCREW_LARGE_SKU].zero? # calculate number of tape items of various skus # for now we assume only the TZ cable double-sided tape tape_roll_length = HeatingSystemItems::TZ_CABLE_TAPE_ROLL_LENGTH_FT qtys_by_sku[ItemConstants::TZ_CABLE_TAPE_SKU] = (length_tape.to_f / tape_roll_length).ceil # iterate over these to populate tz_cable_accessories_qtys, but only when qty > 0 qtys_by_sku.each do |sku, q| tz_cable_accessories_qtys[sku] = q if q.to_i.positive? end end [qty, tz_cable_accessories_qtys] end |
#calculate_tstat_qty_and_pm_control_qty(controls) ⇒ Object
111 112 113 114 115 116 117 118 119 120 121 |
# File 'app/services/heating_system_calculator/accessories_finder.rb', line 111 def calculate_tstat_qty_and_pm_control_qty(controls) tstat_qty = 1 pm_control_qty = 0 # controls are an array of control hashes with a bundle sku, so group by that and take the first (nSpire series tstats use power modules in exactly the same way so they are all the same) which will yield an array with two values: a bundle sku and an array of control hashes, count the pms in the array of control hashes if controls.any? { |c| c['symbol'] == 'RELAY' } controls.each do |c| pm_control_qty += c['qty'] if c['symbol'] == 'RELAY' end end [tstat_qty, pm_control_qty] end |
#find_accessories(elements, controls) ⇒ Object
17 18 19 20 21 22 23 24 25 26 |
# File 'app/services/heating_system_calculator/accessories_finder.rb', line 17 def find_accessories(elements, controls) @accessories = [] if @heating_system.heating_system_product_line.snow_melt? select_snow_melting_accessories(elements, controls) else select_floor_heating_accessories(elements, controls) select_underlayment(elements, controls) end self end |
#select_floor_heating_accessories(elements, controls) ⇒ Object
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'app/services/heating_system_calculator/accessories_finder.rb', line 57 def select_floor_heating_accessories(elements, controls) # calculate number of rough-in kits with 2 conduits for the tstat and with 1 conduit for each pm tstat_qty, pm_control_qty = calculate_tstat_qty_and_pm_control_qty(controls) # calculate quantities of elements and tz cable accessories, like strips, tape and screws qty, tz_cable_accessories_qtys = calculate_elements_qty_and_tz_cable_accessories_qty(elements) # Batch load all accessory items upfront to avoid N+1 queries accessory_skus = @accessory_set.pluck('sku').compact items_by_sku = Item.where(sku: accessory_skus) .includes(:primary_product_line, :exclusive_item_group) .index_by(&:sku) # Concrete-only attachment accessories (screws, drill bit, tape) only apply # when fixing strips are being attached to a concrete slab. The inner calc # already excludes them for non-concrete subfloors, so this guard is mostly # belt-and-suspenders — but it also drops them when membrane is used. subfloor_is_concrete = @heating_system.sub_floor_type&.seo_key == 'existing-concrete-slab' show_concrete_only_accessories = !@heating_system.membrane_recommended && subfloor_is_concrete # populate the accessories @accessory_set.each do |accessory| next unless (acc_item = items_by_sku[accessory['sku']]) next if CONCRETE_ONLY_CABLE_ACCESSORY_SKUS.include?(accessory['sku']) && !show_concrete_only_accessories if @heating_system.heating_system_product_line.is_cable_system? && tz_cable_accessories_qtys[accessory['sku']].to_i.positive? # @accessories << accessory.merge({'qty' => tz_cable_accessories_qtys[accessory['sku']], 'symbol' => 'CABLESTRIP'}) @accessories << accessory.merge({ 'qty' => tz_cable_accessories_qtys[accessory['sku']], 'symbol' => 'CABLESTRIP', 'price' => 0.0 }) # force TC stripts to be free with a quoted system # CABLESTRIP accessories (TC-GRIPSTRIP, TC-STRIP-PL) must ONLY enter via the IF branch above, # where find_tz_cable_strips correctly gates them on membrane_recommended. The elsif below # uses primary_product_line.is_cable_system? which returns false for ruler_cable / thin_cable # (path_includes?('cable') matches '.cable' as a segment, not '_cable' as a suffix), so # without this guard a TC-GRIPSTRIP whose primary_product_line is floor_heating.tempzone.ruler_cable # leaks into Cable + Prodeso quotes with qty=1. See: quote IQR1616897. elsif accessory['symbol'].to_s.upcase != 'CABLESTRIP' && !acc_item.primary_product_line.is_cable_system? && !acc_item.is_membrane? # here we have non tz cable accessories @accessories << if acc_item.exclusive_item_group # installation kits, these need the exclusive groupings since you only choose one within the group ie radio button accessory.merge({ 'qty' => 1, 'exclusive_group_type' => acc_item.exclusive_item_group.key, 'exclusive_group_name' => acc_item.exclusive_item_group.name }) elsif acc_item.is_circuit_check? # circuit check: 1 per heating element accessory.merge({ 'qty' => qty }) else # any other accessory accessory.merge({ 'qty' => 1 }) end end end # treat tstat rough in kits separately since they are differently categorized @accessories << @rough_in_kit_for_tstat.merge({ 'qty' => tstat_qty }) if tstat_qty.positive? && @rough_in_kit_for_tstat # treat power module rough in kits separately since they are differently categorized return unless pm_control_qty.positive? @accessories << @rough_in_kit_for_power_module.merge({ 'qty' => pm_control_qty }) if @rough_in_kit_for_power_module end |
#select_snow_melting_accessories(elements, _controls) ⇒ Object
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'app/services/heating_system_calculator/accessories_finder.rb', line 28 def select_snow_melting_accessories(elements, _controls) # add SMP sku if present accessory = @accessory_set.find { |a| a['sku'] == 'SMP' } @accessories << accessory.merge({ 'qty' => 1 }) if accessory # add junction box sku based on the number of heating elements selected_junction_box = 0 selected_junction_box_num_cold_leads = HeatingSystemCalculator::HeatingSystemItems::JUNCTION_BOX_COLD_LEAD_MAX_NUMBERS[0] elements_count = elements.to_a.sum { |e| e['qty'] } HeatingSystemCalculator::HeatingSystemItems::JUNCTION_BOX_COLD_LEAD_MAX_NUMBERS.each_with_index do |jb_num_cold_leads, i| selected_junction_box = i selected_junction_box_num_cold_leads = HeatingSystemCalculator::HeatingSystemItems::JUNCTION_BOX_COLD_LEAD_MAX_NUMBERS[i] break if jb_num_cold_leads >= elements_count end accessory = @accessory_set.find { |a| a['sku'] == ItemConstants::JUNCTION_BOX_SKUS[selected_junction_box] } qty = (elements_count.to_f / selected_junction_box_num_cold_leads).ceil @accessories << accessory.merge({ 'qty' => qty }) if accessory end |
#select_underlayment(_elements, _controls) ⇒ Object
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'app/services/heating_system_calculator/accessories_finder.rb', line 180 def select_underlayment(_elements, _controls) = HeatingElementProductLineOption.get_suggested_underlayments({ sqft: @heated_area.insulation_surface, sub_floor_type_id: @heating_system.sub_floor_type&.id, floor_type_id: @heating_system.floor_type.id, heating_system_type_name: @heating_system.heating_system_type_name, catalog_id: @heating_system.catalog.id, store_id: @heating_system.catalog.store_id, ideal_cable_spacing: @heating_system.cable_spacing, membrane_recommended: @heating_system.membrane_recommended }) .each do |sku, line| underlayment = @underlayment_set.find { |u| u['sku'] == sku } if underlayment # Calculate area from width x length if not pre-computed (width/length in inches) unit_area = underlayment['area'] || (underlayment['width'].to_f * underlayment['length'].to_f / HeatingSystemItems::SQUARE_INCHES_PER_SQUARE_FOOT) total_sqft = (unit_area * line[:quantity]).round name_appended_blurb = total_sqft.positive? ? ", total sq.ft.: #{total_sqft}" : '' # Mark as recommended since it came from get_suggested_underlayments @accessories << underlayment.merge({ 'qty' => line[:quantity], 'name' => "#{underlayment['name']}#{name_appended_blurb}", 'recommended' => true }) else @error = { error_status: :underlayment_not_found, error_message: "Required underlayment with SKU #{sku} not found." } return self end end end |