Class: HeatingSystemCalculator::HeatingSystemItems

Inherits:
Object
  • Object
show all
Defined in:
app/services/heating_system_calculator/heating_system_items.rb

Overview

Service object: heating system items.

Constant Summary collapse

JUNCTION_BOX_COLD_LEAD_MAX_NUMBERS =

Junction box cold lead max numbers.

[3, 8, 8].freeze
SCREWS_PER_STRIP_WITH_OVERAGE =

TZ Cable concrete subfloor installation constants
Screws per strip with 10% overage factor

2.2
TAPE_FEET_PER_STRIP_WITH_OVERAGE =

Tape feet per strip with 10% overage factor

1.1
CONCRETE_SCREW_PACK_LARGE_SIZE =

Large screw pack contains 100 screws (includes drill bit)

100
CONCRETE_SCREW_PACK_SMALL_SIZE =

Small screw pack contains 20 screws

20
TZ_CABLE_TAPE_ROLL_LENGTH_FT =

Double-sided tape roll length in feet

26
SQUARE_INCHES_PER_SQUARE_FOOT =

Unit conversion: square inches per square foot (12 * 12)

144.0
FLOORWARMING_TSTAT_SORT_ORDER_BY_SKU =

Floorwarming tstat sort order by sku.

{
  'UTN5-4999' => 0,
  'UDG5-4999-WY' => 1,
  'UDG4-4999' => 2,
  'UWG4-4999' => 3
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ HeatingSystemItems

Returns a new instance of HeatingSystemItems.



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 34

def initialize(options = {})
  @heating_system = options[:heating_system]
  @error = { error_status: :missing_heating_system, error_message: 'Heating System is missing.' } if @heating_system.nil?
  @heated_area = options[:heated_area]
  @error = { error_status: :missing_heated_area, error_message: 'Heated Area is missing.' } if @heated_area.nil?
  return if @error

  @element_set = get_element_set
  @error = { error_status: :no_matching_elements, error_message: 'No matching heating elements.' } if @element_set.empty?
  @control_set = get_control_set
  @error = { error_status: :no_matching_controls, error_message: 'No matching controls.' } if @control_set.empty?
  @power_set = get_power_set
  @sensor_set = get_sensor_set
  @accessory_set = get_accessory_set
  @underlayment_set = get_underlayment_set
  @rough_in_kit_for_tstat = get_rough_in_kit_for_tstat
  @rough_in_kit_for_power_module = get_rough_in_kit_for_power_module
  @smart_service_set = get_smart_service_set
end

Instance Attribute Details

#accessory_setObject (readonly)

Returns the value of attribute accessory_set.



31
32
33
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 31

def accessory_set
  @accessory_set
end

#control_setObject (readonly)

Returns the value of attribute control_set.



31
32
33
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 31

def control_set
  @control_set
end

#element_setObject (readonly)

Returns the value of attribute element_set.



31
32
33
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 31

def element_set
  @element_set
end

#errorObject (readonly)

Returns the value of attribute error.



31
32
33
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 31

def error
  @error
end

#power_setObject (readonly)

Returns the value of attribute power_set.



31
32
33
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 31

def power_set
  @power_set
end

#rough_in_kit_for_power_moduleObject (readonly)

Returns the value of attribute rough_in_kit_for_power_module.



31
32
33
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 31

def rough_in_kit_for_power_module
  @rough_in_kit_for_power_module
end

#rough_in_kit_for_tstatObject (readonly)

Returns the value of attribute rough_in_kit_for_tstat.



31
32
33
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 31

def rough_in_kit_for_tstat
  @rough_in_kit_for_tstat
end

#sensor_setObject (readonly)

Returns the value of attribute sensor_set.



31
32
33
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 31

def sensor_set
  @sensor_set
end

#smart_service_setObject (readonly)

Returns the value of attribute smart_service_set.



31
32
33
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 31

def smart_service_set
  @smart_service_set
end

#underlayment_setObject (readonly)

Returns the value of attribute underlayment_set.



31
32
33
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 31

def underlayment_set
  @underlayment_set
end

Instance Method Details

#floor_warming_tstat_sort_order_for_hash(item) ⇒ Object

==========================================================================
Sorting Helpers



225
226
227
228
229
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 225

def floor_warming_tstat_sort_order_for_hash(item)
  sort_order = FLOORWARMING_TSTAT_SORT_ORDER_BY_SKU[item['sku']]
  sort_order ||= item['price']
  sort_order
end

#get_accessory_setObject

==========================================================================
Accessory Retrieval



129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 129

def get_accessory_set
  # Return empty if no product line selected (e.g., step 0)
  return [] if @heating_system.heating_system_product_line.blank?

  # Snow melt has complex junction box handling - use AR
  return get_accessory_set_snow_melt if @heating_system.heating_system_product_line.snow_melt?

  # Standard path: use materialized view
  ViewQuoteBomItem.get_accessories(
    catalog_id: @heating_system.catalog.id,
    product_line_id: @heating_system.heating_system_product_line.id
  )
end

#get_accessory_set_snow_meltObject



212
213
214
215
216
217
218
219
220
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 212

def get_accessory_set_snow_melt
  # Use matview for accessories + junction boxes
  accessories = ViewQuoteBomItem.get_accessories(
    catalog_id: @heating_system.catalog.id,
    product_line_id: @heating_system.heating_system_product_line.id
  )
  junction_boxes = ViewQuoteBomItem.get_junction_boxes(catalog_id: @heating_system.catalog.id)
  accessories + junction_boxes
end

#get_control_setObject

==========================================================================
Control Retrieval



76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 76

def get_control_set
  # Return empty if no product line selected (e.g., step 0)
  return [] if @heating_system.heating_system_product_line.blank?

  # Snow melt has complex successor item logic - use AR
  return get_control_set_snow_melt if @heating_system.heating_system_product_line.snow_melt?

  # Standard path: use materialized view
  ViewQuoteBomItem.get_controls(
    catalog_id: @heating_system.catalog.id,
    product_line_id: @heating_system.heating_system_product_line.id
  ).sort_by { |item| floor_warming_tstat_sort_order_for_hash(item) }
end

#get_control_set_snow_meltObject

==========================================================================
AR Fallback Methods (for snow melt successor item logic)



172
173
174
175
176
177
178
179
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/heating_system_items.rb', line 172

def get_control_set_snow_melt
  catalog_id = @heating_system.catalog.id

  # Step 1: Filter SKUs based on business rules (no DB queries)
  candidate_skus = ItemConstants::SNOWMELT_CONTROL_BUNDLES.filter_map do |bundle|
    next if bundle[:name] == 'Economy' && @heating_system.floor_type.seo_key == 'paver'
    next if bundle[:name] == 'Value' && @heating_system.voltage&.id.to_i > 240

    bundle[:sku]
  end

  # Step 2: Batch-check which SKUs exist in matview (single query)
  existing_skus = ViewQuoteBomItem.for_catalog(catalog_id).where(sku: candidate_skus).pluck(:sku)
  missing_skus = candidate_skus - existing_skus

  # Step 3: For missing SKUs, batch-check for successors (only if any missing)
  # Track mapping from successor SKU to original bundle SKU for relay panel logic
  successor_to_original = {}
  if missing_skus.any?
    # Load all potentially missing catalog items with associations in one query
    missing_items = CatalogItem.includes(store_item: { item: :successor_item })
                               .joins(store_item: :item)
                               .where(catalog_id: catalog_id, items: { sku: missing_skus })

    missing_items.each do |ci|
      original_sku = ci.item&.sku
      if ci.item&.successor_item.present? && (sci = ci.get_successor_online_catalog_item).present?
        successor_to_original[sci.sku] = original_sku
      end
    end
  end

  # Use matview for final fetch, passing the successor mapping
  ViewQuoteBomItem.get_snow_melt_controls(
    catalog_id: catalog_id,
    skus: existing_skus + successor_to_original.keys,
    successor_to_original: successor_to_original
  )
end

#get_element_setObject

==========================================================================
Element Retrieval - Uses materialized view for optimal performance



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 57

def get_element_set
  # Return empty if no product line selected (e.g., step 0)
  return [] if @heating_system.heating_system_product_line.blank?

  # Tire tracks legacy filters to 24" wide elements only
  width_filter = @heated_area.coverage_state == 'tire_tracks_legacy' ? 24.0 : nil

  ViewQuoteBomItem.get_elements(
    catalog_id: @heating_system.catalog.id,
    product_line_url: @heating_system.heating_system_product_line.slug_ltree.to_s,
    voltage_id: @heating_system.voltage&.id,
    cable_spacing: @heating_system.cable_spacing,
    width_filter: width_filter
  )
end

#get_power_setObject

==========================================================================
Power Module Retrieval



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 93

def get_power_set
  # Return empty if no product line selected (e.g., step 0)
  return [] if @heating_system.heating_system_product_line.blank?

  # Snow melt uses specific relay panel SKUs
  return ViewQuoteBomItem.get_relay_panels(catalog_id: @heating_system.catalog.id) if @heating_system.heating_system_product_line.snow_melt?

  # Standard path: use materialized view
  power_items = ViewQuoteBomItem.get_power(
    catalog_id: @heating_system.catalog.id,
    product_line_id: @heating_system.heating_system_product_line.id
  )
  relay_panels = ViewQuoteBomItem.get_relay_panels(catalog_id: @heating_system.catalog.id)
  power_items + relay_panels
end

#get_rough_in_kit_for_power_moduleObject



158
159
160
161
162
163
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 158

def get_rough_in_kit_for_power_module
  ViewQuoteBomItem.get_rough_in_kits(
    catalog_id: @heating_system.catalog.id,
    skus: [ItemConstants::ROUGH_IN_KIT_FOR_POWER_MODULE_SKU]
  ).first
end

#get_rough_in_kit_for_tstatObject



151
152
153
154
155
156
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 151

def get_rough_in_kit_for_tstat
  ViewQuoteBomItem.get_rough_in_kits(
    catalog_id: @heating_system.catalog.id,
    skus: [ItemConstants::ROUGH_IN_KIT_FOR_TSTAT_SKU]
  ).first
end

#get_sensor_setObject

==========================================================================
Sensor Retrieval



112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 112

def get_sensor_set
  # Return empty if no product line selected (e.g., step 0)
  return [] if @heating_system.heating_system_product_line.blank?

  # Snow melt uses specific sensor SKUs
  return ViewQuoteBomItem.get_snow_melt_sensors(catalog_id: @heating_system.catalog.id) if @heating_system.heating_system_product_line.snow_melt?

  # Standard path: use materialized view
  ViewQuoteBomItem.get_sensors(
    catalog_id: @heating_system.catalog.id,
    product_line_id: @heating_system.heating_system_product_line.id
  )
end

#get_smart_service_setObject



165
166
167
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 165

def get_smart_service_set
  ViewQuoteBomItem.get_smart_services(catalog_id: @heating_system.catalog.id)
end

#get_underlayment_setObject

==========================================================================
Underlayment, Rough-in Kits, and Smart Services
These always use AR due to special handling requirements



147
148
149
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 147

def get_underlayment_set
  ViewQuoteBomItem.get_underlayments(catalog_id: @heating_system.catalog.id)
end

#line_items_to_bom_controls(line_item_elements) ⇒ Object



239
240
241
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 239

def line_items_to_bom_controls(line_item_elements)
  ViewQuoteBomItem.line_items_to_bom(line_item_elements)
end

#line_items_to_bom_elements(line_item_elements) ⇒ Object

==========================================================================
Line Item Converters (used by RoomConfiguration model)
Uses matview for efficient batch lookup



235
236
237
# File 'app/services/heating_system_calculator/heating_system_items.rb', line 235

def line_items_to_bom_elements(line_item_elements)
  ViewQuoteBomItem.line_items_to_bom(line_item_elements, cable_spacing: @heating_system&.cable_spacing)
end