Class: RoomConfiguration

Inherits:
ApplicationRecord show all
Extended by:
FriendlyId
Includes:
ActionView::Helpers::NumberHelper, Models::Auditable, Models::HeatLossable, Models::Lineage, Models::LiquidMethods, Models::Notable, Models::Pickable
Defined in:
app/models/room_configuration.rb

Overview

== Schema Information

Table name: room_configurations
Database name: primary

id :integer not null, primary key
additional_notes :text
alternate_heplo_ids :integer default([]), is an Array
ancestors_count :integer
asset :string(255)
average_ceiling_height :decimal(5, 2)
cable_spacing :decimal(4, 2)
cad_engine_status :integer
cad_engine_xml :text
ceiling_sq_ft :decimal(8, 2)
children_count :integer
coverage_state :enum
currency :string(255)
depth_above_cable :string(255)
depth_below_cable :string(255)
descendants_count :integer
desired_temperature :float default(68.0), not null
ev2_coverage :string(255)
ev2help :boolean default(FALSE)
expansion_joint_spacing :integer
first_view_date :datetime
floorplan_xml :text
from_edit :boolean
full_coverage_estimate :float
has_new_floorplan :boolean default(TRUE)
heating_requirement :enum default("unknown"), not null
hidden :boolean default(FALSE)
image_re_rendered_with_good_im :boolean
installation_date :date
installation_plan_cable_spacing :decimal(4, 2)
installation_plan_not_required :boolean default(FALSE)
installation_plan_scale :string(255)
insulation_surface :integer
is_rendered :boolean default(FALSE)
last_bom :json
last_heat_loss :json
legacy_iq_from_crm :boolean
name :string(255) not null
name_with_config :string(1000)
notes :text
number_fireplaces :integer default(0), not null
other_floor_type_label :string(255)
paver_thickness :string(255)
plan_created_at :datetime
position :integer default(100)
preferred_shipping_method :string(255)
priority :string(255) default("standard")
quotation_expiration_date :datetime
reception_date :datetime
reception_type :string(255) default("Unspecified"), not null
reference_number :string
requires_render :boolean default(FALSE)
requires_upgrade :boolean
room_length :string(255)
room_width :string(255)
sealing_factor :string(255) default("average"), not null
selected_membrane_cable_spacing :float
selected_strips_cable_spacing :float
service_panel_note :string
set_in :string(255)
show_non_public_heplos :boolean
situation :enum default("unknown"), not null
skip_expansion_joint_validation :boolean
slug :string
square_footage :integer
state :string(255)
underlayment_material :string(255)
vignette :boolean default(FALSE), not null
wyi_alert_sent :boolean
created_at :datetime
updated_at :datetime
contact_point_id :integer
controlled_by_id :integer
creator_id :integer
deleter_id :integer
designer_employee_id :integer
electrician_id :integer
floor_type_id :integer
floorplanner_employee_id :integer
heating_system_product_line_id :integer
heating_system_type_id :integer
installation_address_id :integer
installation_plan_qc_employee_id :integer
installation_plan_voltage_id :integer
installer_id :integer
labeler_employee_id :integer
legacy_proto_iq_room_id :integer
legacy_selected_instant_quote_id :integer
measure_job_id :integer
opportunity_id :integer not null
parent_id :integer
quoted_heating_system_pl_id :integer
retail_outlet_id :integer
room_event_id :integer
room_plan_id :integer
room_type_id :integer
showcase_id :integer
sub_floor_type_id :integer
system_owner_id :integer
updater_id :integer
visit_id :bigint
voltage_id :integer

Indexes

index_room_configurations_on_cad_engine_status (cad_engine_status)
index_room_configurations_on_contact_point_id (contact_point_id) USING hash
index_room_configurations_on_creator_id (creator_id)
index_room_configurations_on_designer_employee_id (designer_employee_id) USING hash
index_room_configurations_on_floorplanner_employee_id (floorplanner_employee_id) USING hash
index_room_configurations_on_heating_system_product_line_id (heating_system_product_line_id)
index_room_configurations_on_heating_system_type_id (heating_system_type_id)
index_room_configurations_on_image_re_rendered_with_good_im (image_re_rendered_with_good_im)
index_room_configurations_on_installation_address_id (installation_address_id) USING hash
index_room_configurations_on_labeler_employee_id (labeler_employee_id) USING hash
index_room_configurations_on_legacy_iq_from_crm (legacy_iq_from_crm)
index_room_configurations_on_legacy_proto_iq_room_id (legacy_proto_iq_room_id)
index_room_configurations_on_measure_job_id (measure_job_id)
index_room_configurations_on_opportunity_id (opportunity_id)
index_room_configurations_on_parent_id (parent_id)
index_room_configurations_on_quoted_heating_system_pl_id (quoted_heating_system_pl_id)
index_room_configurations_on_reception_type (reception_type)
index_room_configurations_on_reference_number (reference_number) USING gin
index_room_configurations_on_room_plan_id (room_plan_id) USING hash
index_room_configurations_on_room_type_id (room_type_id)
index_room_configurations_on_showcase_id (showcase_id)
index_room_configurations_on_slug (slug) USING hash
index_room_configurations_on_state (state) USING hash
index_room_configurations_on_sub_floor_type_id (sub_floor_type_id)
index_room_configurations_on_vignette (vignette)
index_room_configurations_on_visit_id (visit_id) WHERE (visit_id IS NOT NULL) USING hash
index_room_configurations_reference_number_like (reference_number)
room_configurations_controlled_by_id_idx (controlled_by_id)
room_configurations_unique_reference_number (reference_number) UNIQUE

Foreign Keys

fk_rails_... (contact_point_id => contact_points.id)
fk_rails_... (controlled_by_id => room_configurations.id)
fk_rails_... (designer_employee_id => parties.id)
fk_rails_... (floor_type_id => floor_types.id)
fk_rails_... (floorplanner_employee_id => parties.id)
fk_rails_... (heating_system_product_line_id => product_lines.id)
fk_rails_... (installation_address_id => addresses.id)
fk_rails_... (labeler_employee_id => parties.id)
fk_rails_... (opportunity_id => opportunities.id) ON DELETE => cascade
fk_rails_... (quoted_heating_system_pl_id => product_lines.id)
fk_rails_... (room_plan_id => room_plans.id) ON DELETE => nullify
fk_rails_... (room_type_id => room_types.id)
fk_rails_... (showcase_id => showcase_legacies.id)
fk_rails_... (sub_floor_type_id => sub_floor_types.id)
fk_rails_... (visit_id => visits.id) ON DELETE => nullify

Defined Under Namespace

Classes: AbortUpdateRhc, CalculateQuote, Copier, OperatingCostsCalculator

Constant Summary collapse

MC_FLOORTYPE_ID_BY_WY_FLOORTYPE_ID =

WY floor types
1: Bamboo (Nailed), 2: Carpet, 3: Tile and Stone, 4: Engineered (Floating), 5: Pouring a new indoor Concrete Slab, 6: Laminate (Click Together Floating), 7: Marble, 8: Resilients, 9: Stone, 10: Vinyl, 11: Wood (Nailed), 12: Wood (Glued), 13: Bamboo (Floating), 14: Bamboo (Glued), 15: Engineered (Glued), 16: Engineered (Nailed), 17: Laminate (Glued Together Floating), 18: Laminate (Nailed), 19: Other, 20: Asphalt, 21: Paver, 22: Pouring a new outdoor Concrete Slab, 23: Carpet (Glued), 24: Cork, 25: Pouring a new outdoor Concrete Slab with a Surface Covering

{ '1' => '7', '2' => '1', '3' => '4', '4' => '5', '5' => '6', '6' => '5', '7' => '4', '8' => '3', '9' => '4', '10' => '2', '11' => '7', '12' => '3', '13' => '3', '14' => '3', '15' => '5', '16' => '5',
'17' => '5', '19' => '3', '20' => '6', '21' => '6', '22' => '6', '23' => '1', '24' => '3', '25' => '6' }.freeze
MC_ROOMTYPE_ID_BY_WY_ROOMTYPE_ID =

WY room types
1: Basement, 2: Bathroom, 3: Master Bathroom, 4: Bedroom, 5: Master Bedroom, 6: Breakfast Nook, 7: Dining Room, 8: Entry/Hallway, 9: Family Room, 10: Kitchen, 11: Laundry, 12: Living Room, 13: Recreation/Media Room, 14: Mudroom, 15: Office, 16: Powder Room, 17: Sunroom, 18: Other, 19: Shower/Sauna, 20: Driveway, 21: Sidewalk, 22: Stairs, 23: Patio, 24: Garage, 25: Ensuite, 26: Landing, 27: Entry, 28: Exit, 29: Walkway, 30: Courtyard, 31: Ramp, 32: Porch, 33: Carport, 34: Bridge, 35: Valet, 36: Addition, 37: Enclosed Porch, 38: Three Season Room, 39: Four Season Room, 40: Home Theater Room, 41: Shop Floor

{ '1' => '28', '2' => '17', '3' => '31', '4' => '6', '5' => '30', '6' => '20', '7' => '1', '8' => '2', '9' => '14', '10' => '18', '11' => '26', '12' => '0', '13' => '16', '14' => '34', '15' => '38', '16' => '35',
'17' => '14', '18' => '43', '19' => '17', '22' => '5', '23' => '27', '24' => '34', '25' => '36', '26' => '2', '27' => '2', '28' => '2', '32' => '27', '36' => '27', '37' => '27', '38' => '27', '39' => '16', '40' => '16', '41' => '29' }.freeze
MC_ROOMNAME_BY_MC_ROOMTYPE_ID =

Returns:

  • (Object)
{ '0' => 'Living Room', '1' => 'Dining Room', '2' => 'Foyer', '3' => 'Lower Hall', '4' => 'Upper Hall', '5' => 'Upper Stairs', '6' => 'Bedroom 1', '7' => 'Closet 1', '8' => 'Bedroom 2', '9' => 'Closet 2',
'10' => 'Bedroom 3', '11' => 'Closet 3', '12' => 'Bedroom 4', '13' => 'Closet 4', '14' => 'Family/Great Room', '15' => 'Lower Stairs', '16' => 'Den', '17' => 'Bathroom 1', '18' => 'Kitchen', '19' => 'Dinette', '20' => 'Nook', '21' => 'Kitchen Hallway', '22' => 'Kitchen Foyer', '23' => 'Kitchen Entrance', '24' => 'Kitchen Stairs', '25' => 'Kitchen Landing', '26' => 'Laundry', '27' => 'Porch', '28' => 'Basement', '29' => 'Commercial Office', '30' => 'Master Bedroom', '31' => 'Master Bathroom', '32' => 'Master Closet', '33' => 'Pantry', '34' => 'Utility', '35' => 'Bathroom 2', '36' => 'Bathroom 3', '37' => 'Basement Stairs', '38' => 'Home Office', '39' => 'Basement Bedroom 1', '40' => 'Basement Bedroom 2', '41' => 'Basement Bathroom', '42' => 'Basement Family Room', '43' => 'Basement Misc.' }.freeze
TYPICAL_SQFT_BY_WY_ROOMTYPE_ID =

Returns:

  • (Object)
{ '1' => 500, '2' => 50, '3' => 100, '4' => 200, '5' => 400, '6' => 100, '7' => 200, '8' => 50, '9' => 300, '10' => 300, '11' => 50, '12' => 300, '13' => 300, '14' => 100, '15' => 150, '16' => 40, '17' => 200,
'18' => 100, '19' => 30, '20' => 250, '21' => 200, '22' => 75, '23' => 150, '24' => 200, '25' => 50, '26' => 75, '27' => 75, '28' => 75, '29' => 100, '30' => 200, '31' => 25, '32' => 200, '33' => 75, '34' => 75, '35' => 75, '36' => 75, '37' => 150, '38' => 150, '39' => 150, '40' => 200, '41' => 300 }.freeze
PRIORITIES =

Returns:

  • (Object)
['same day order', 'same day', 'next day', '2 days', 'standard'].freeze
CAD_ENGINE_STATUS =

Returns:

  • (Object)
{ 'Pending Design Conversion' => 1, 'In CAD Conversion' => 2, 'CAD Conversion Complete' => 3, 'Error' => 4 }.freeze
CUSTOMER_CANCELABLE_STATES =

Returns:

  • (Object)
%i[draft complete].freeze
CANCELABLE_STATES =

Returns:

  • (Object)
CUSTOMER_CANCELABLE_STATES + %i[in_design awaiting_quality_control]
CLOSED_STATES =

Returns:

  • (Object)
%i[complete cancelled].freeze
REFERENCE_NUMBER_PATTERN =

Returns:

  • (Object)
/^(IQR\d+)(-V)?(\d+)?$|^(IP\d+)(-R)?(\d+)?$/i
TEMPERATURE_OPTIONS =

Returns:

  • (Object)
[['Low', 60.0], ['Average', 68.0], ['Warm', 72.0], ['Hot', 80.0]].freeze
WATTS_PER_SQUARE_FOOT =

Returns:

  • (Object)
{ 'TempZone Flex Roll' => 15.0,
'TempZone Easy Mat' => 15.0,
'TempZone Thin Cable' => 12.0,
'TempZone Thin Cable 3' => 12.0,
'TempZone Thin Cable 4' => 9.0,
'TempZone Cable' => 14.8,
'TempZone Cable 3.5' => 12.7,
'TempZone Cable 3.75' => 11.84,
'TempZone Cable 4' => 11.125,
'TempZone Cable 4.75' => 9.3,
'TempZone Cable 5' => 8.9,
'TempZone Cable 3.125' => 14.4,
'TempZone Ruler Cable' => 14.8,
'TempZone Ruler Cable 3.5' => 12.7,
'TempZone Ruler Cable 3.75' => 11.84,
'TempZone Ruler Cable 4' => 11.125,
'TempZone Ruler Cable 4.75' => 9.3,
'TempZone Ruler Cable 5' => 8.9,
'TempZone Ruler Cable 3.125' => 14.4,
'Environ Flex Roll' => 12.0,
'Environ Easy Mat' => 12.0,
'Slab Heat Mat' => 20.0,
'Slab Heat Cable' => 20.0,
'Snow Melt Cable' => 50.0,
'Snow Melt PoweMat' => 50.0,
'Snow Melt OmniMat' => 38.5,
'Snow Melt EcoMat' => 31.5,
'Snow Melt Mat' => 50.0 }.freeze
DEFAULT_ECONOMY_AMB_TEMP =

Returns:

  • (Object)
63.0
PUBLIC_TZ_CABLE_SPACINGS =

Returns:

  • (Object)
[3.0, 4.0, 5.0].freeze
PUBLIC_PRODESO_CABLE_SPACINGS =

Returns:

  • (Object)
[3.75, 5.0].freeze
PUBLIC_DITRA_CABLE_SPACINGS =

Returns:

  • (Object)
[3.5, 4.75].freeze
PUBLIC_CABLE_SPACINGS =

Returns:

  • (Object)
PUBLIC_TZ_CABLE_SPACINGS + PUBLIC_PRODESO_CABLE_SPACINGS + PUBLIC_DITRA_CABLE_SPACINGS
PUBLIC_SH_CABLE_SPACINGS =

Returns:

  • (Object)
[3.0, 4.0, 5.0, 6.0].freeze
PUBLIC_SM_CABLE_SPACINGS =

Returns:

  • (Object)
[3.0].freeze
ROOM_ICON_BY_STATE =

Returns:

  • (Object)
{
  'draft' => 'pen-square',
  'in_design' => 'cogs',
  'awaiting_quality_control' => 'cogs',
  'complete' => 'check-square',
  'cancelled' => 'close'
}.freeze
FALLBACK_ROOM_ICON =

Returns:

  • (Object)
'object-group'
EMPTY_ALTERNATE_HEATING_SYSTEMS_BLURB =

Returns:

  • (Object)
{ 'engineering' => 'Leave empty for unrestricted heating systems.' }.freeze
ENGINEERING_QUOTING_STATES =

Returns:

  • (Object)
%i[draft in_design awaiting_quality_control complete].freeze

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Constants included from HeatLossableConstants

HeatLossableConstants::BTU_PER_HOUR_PER_WATT, HeatLossableConstants::HIGH_U_VALUE_THRESHOLD, HeatLossableConstants::HLC_PRIMARY_OK_THRESHOLD, HeatLossableConstants::INFILTRATION_AREA_FACTORS, HeatLossableConstants::INFILTRATION_FIREPLACE_FACTORS, HeatLossableConstants::INFILTRATION_SEALING_FACTORS, HeatLossableConstants::PIE_CHART_FLAG_COLORS, HeatLossableConstants::PIE_CHART_OK_COLORS

Constants included from Models::Schedulable

Models::Schedulable::SIMPLE_FORM_OPTIONS

Instance Attribute Summary collapse

Belongs to collapse

Methods included from Models::Auditable

#creator, #updater

Has many collapse

Methods included from Models::Pickable

#line_discounts, #line_items

Has one collapse

Has and belongs to many collapse

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 included from Models::HeatLossable

#children_heat_loss_recursive, #get_children_heat_loss, #get_self_and_children_heat_loss, #get_u_value, #heat_loss_children_recursive, #insulation_type, #reset_room_last_heat_loss, #self_and_children_heat_loss_recursive

Methods included from Models::Lineage

#ancestors, #ancestors_ids, #children_and_roots, #descendants, #descendants_ids, #ensure_non_recursive_lineage, #family_members, #generate_full_name, #generate_full_name_array, #lineage, #lineage_array, #lineage_simple, #root, #root_id, #self_ancestors_and_descendants, #self_ancestors_and_descendants_ids, #self_and_ancestors, #self_and_ancestors_ids, #self_and_children, #self_and_descendants, #self_and_descendants_ids, #self_and_siblings, #self_and_siblings_ids, #siblings, #siblings_ids

Methods included from Models::Pickable

#all_my_publications, #append_suggested_items, #append_suggested_materials, #control_capacity, #determine_catalog_for_picking, #determine_skus_to_filter, #discounted_shipping_total, #electrical_heating_elements, #fix_catalog, #get_material_alerts, #has_custom_products?, #invalidate_material_alerts!, #line_items_grouped_by_room_configuration, #lines_match_pick_catalog_on_flip, #meets_custom_products_threshold?, #pricing_program_discount_factor, #purge_empty_line_items, #soft_recalc, #subtotal, #subtotal_after_trade_discount_without_shipping, #subtotal_discounted_without_shipping, #subtotal_msrp_without_shipping, #switching_pick_catalog?, #synchronize_lines, #total_amps_by_product_line, #total_coverage_by_product_line, #total_linear_feet_by_product_line, #total_spec_by_product_line, #total_watts_by_product_line, #underlayments

Methods included from Models::Notable

#quick_note

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#currencyvoid (readonly)

This method returns an undefined value.

Validates currency.



471
# File 'app/models/room_configuration.rb', line 471

validates :currency, presence: true

#enter_heat_loss_infoObject

Returns the value of attribute enter_heat_loss_info.



172
173
174
# File 'app/models/room_configuration.rb', line 172

def enter_heat_loss_info
  @enter_heat_loss_info
end

#insulation_surfaceObject (readonly)

Minimum 9 sq.ft (roughly a 3x3 closet) for heat loss calculations

Validations (if => #entering_heat_loss_info? ):

  • Numericality ({ less_than_or_equal_to: 9999.9, greater_than_or_equal_to: 9.0, message: 'must be at least 9 sq.ft (roughly a small closet)' })


467
468
# File 'app/models/room_configuration.rb', line 467

validates :insulation_surface, numericality: { less_than_or_equal_to: 9999.9, greater_than_or_equal_to: 9.0,
message: 'must be at least 9 sq.ft (roughly a small closet)' }, if: :entering_heat_loss_info?

#namevoid (readonly)

This method returns an undefined value.

Validates name.



474
# File 'app/models/room_configuration.rb', line 474

validates :name, presence: true

#new_noteObject

Returns the value of attribute new_note.



172
173
174
# File 'app/models/room_configuration.rb', line 172

def new_note
  @new_note
end

#reception_typevoid (readonly)

This method returns an undefined value.

Validates reception type.



464
# File 'app/models/room_configuration.rb', line 464

validates :reception_type, presence: true

#reuse_upload_idObject

Returns the value of attribute reuse_upload_id.



172
173
174
# File 'app/models/room_configuration.rb', line 172

def reuse_upload_id
  @reuse_upload_id
end

#updating_installation_plansObject

Returns the value of attribute updating_installation_plans.



172
173
174
# File 'app/models/room_configuration.rb', line 172

def updating_installation_plans
  @updating_installation_plans
end

#validate_budgetObject

Returns the value of attribute validate_budget.



172
173
174
# File 'app/models/room_configuration.rb', line 172

def validate_budget
  @validate_budget
end

Class Method Details

.activeActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are active. Active Record Scope

Returns:

See Also:



504
# File 'app/models/room_configuration.rb', line 504

scope :active, -> { where("room_configurations.state != 'cancelled'") }

.all_reception_types_for_selectObject

All reception types for select.

Returns:

  • (Object)


722
723
724
725
# File 'app/models/room_configuration.rb', line 722

def self.all_reception_types_for_select
  # Include legacy types for historical data display
  RoomConfigurationConstants::ALL_RECEPTION_TYPES.to_h
end

.by_created_atActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are by created at. Active Record Scope

Returns:

See Also:



491
# File 'app/models/room_configuration.rb', line 491

scope :by_created_at, -> { order('room_configurations.created_at DESC') }

.by_reference_numberActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are by reference number. Active Record Scope

Returns:

See Also:



496
# File 'app/models/room_configuration.rb', line 496

scope :by_reference_number, -> { order('room_configurations.reference_number desc') }

.category_options_for_selectObject

Category options for select.

Returns:

  • (Object)


624
625
626
# File 'app/models/room_configuration.rb', line 624

def self.category_options_for_select
  [['Installation Plan - Image', 'installation_plan_image'], ['Installation Plan - CAD', 'installation_plan_cad'], ['Installation Plan Heated Area - Image', 'installation_heated_area']]
end

.completeActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are complete. Active Record Scope

Returns:

See Also:



492
# File 'app/models/room_configuration.rb', line 492

scope :complete, -> { where(state: 'complete').by_created_at }

.complete_and_awaiting_transmissionActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are complete and awaiting transmission. Active Record Scope

Returns:

See Also:



493
# File 'app/models/room_configuration.rb', line 493

scope :complete_and_awaiting_transmission, -> { joins(:quotes).where("room_configurations.state = 'complete' AND quotes.state NOT IN ('complete','cancelled')").by_created_at }

.contains_item_idsActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are contains item ids. Active Record Scope

Returns:

See Also:



501
# File 'app/models/room_configuration.rb', line 501

scope :contains_item_ids, ->(item_ids) { where("EXISTS (select 1 from line_items li where li.resource_id = #{table_name}.id and li.resource_type = '#{name}' and li.item_id IN (?))", item_ids) }

.default_attributes(store_id = 1) ⇒ Object

Default attributes.

Parameters:

  • store_id (Object) (defaults to: 1)

Returns:

  • (Object)


731
732
733
734
735
736
737
738
# File 'app/models/room_configuration.rb', line 731

def self.default_attributes(store_id = 1)
  env = HeatingElementProductLineOption::ENVIRONMENTS.first
  rt_id = HeatingElementProductLineOption.get_default_room_type_id_for_environment(env)
  sft_id = HeatingElementProductLineOption.get_default_subfloor_type_id
  ft_id = HeatingElementProductLineOption.get_default_floor_type_id_for_environment(env)
  hs_pl_id = HeatingElementProductLineOption.get_field_array_from_options(:product_line_id, environment: env, sub_floor_type_id: sft_id, floor_type_id: ft_id, is_public: true, store_id:).first
  { coverage_state: 'full', room_type_id: rt_id, sub_floor_type_id: sft_id, floor_type_id: ft_id, heating_system_product_line_id: hs_pl_id }
end

.determine_style(opportunity, current_opportunity) ⇒ Object

Determine style.

Parameters:

  • opportunity (Object)
  • current_opportunity (Object)

Returns:

  • (Object)


687
688
689
690
691
692
693
# File 'app/models/room_configuration.rb', line 687

def self.determine_style(opportunity, current_opportunity)
  if opportunity.id == current_opportunity.id
    ''
  else
    'display:none'
  end
end

.dt_roomsActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are dt rooms. Active Record Scope

Returns:

See Also:



506
# File 'app/models/room_configuration.rb', line 506

scope :dt_rooms, -> { where(reception_type: 'DesignTool') }

.formatted_resources_query(room_configurations) ⇒ Object

Formatted resources query.

Parameters:

  • room_configurations (Object)

Returns:

  • (Object)


744
745
746
747
748
749
# File 'app/models/room_configuration.rb', line 744

def self.formatted_resources_query(room_configurations)
  room_configurations
    .select('room_configurations.id, room_configurations.reference_number, room_configurations.name, ' \
            'room_configurations.created_at, room_configurations.state, opportunities.name as opportunity_name')
    .order('room_configurations.reference_number desc')
end

.group_name_to_key(group_name) ⇒ Object

Legacy IQ option group methods removed

Parameters:

  • group_name (Object)


3040
3041
3042
# File 'app/models/room_configuration.rb', line 3040

def self.group_name_to_key(group_name)
  group_name.to_s.downcase.strip.underscore.tr(' ', '_').gsub('__', '_')
end

.has_no_quotesActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are has no quotes. Active Record Scope

Returns:

See Also:



502
# File 'app/models/room_configuration.rb', line 502

scope :has_no_quotes, -> { where.missing(:quotes) }

.has_uploads_of_categoryActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are has uploads of category. Active Record Scope

Returns:

See Also:



503
# File 'app/models/room_configuration.rb', line 503

scope :has_uploads_of_category, ->(category) { where("EXISTS(select 1 from uploads where (uploads.resource_id = room_configurations.id and uploads.resource_type = 'RoomConfiguration' and uploads.category = ?) )", category) }

.heating_element_count_sqlObject

Builds a correlated subquery for heating element quantity count
Uses ltree with hardcoded path constant for zero DB lookups



313
314
315
316
317
318
319
320
# File 'app/models/room_configuration.rb', line 313

def self.heating_element_count_sql
  LineItem
    .joins(:item)
    .where(LineItem.arel_table[:room_configuration_id].eq(arel_table[:id]))
    .merge(Item.heating_elements)
    .select(Arel.sql('COALESCE(SUM(line_items.quantity), 0)'))
    .to_sql
end

.image_needs_re_rendering_with_good_imActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are image needs re rendering with good im. Active Record Scope

Returns:

See Also:



509
# File 'app/models/room_configuration.rb', line 509

scope :image_needs_re_rendering_with_good_im, -> { where('image_re_rendered_with_good_im IS NOT TRUE') }

.in_cad_processingActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are in cad processing. Active Record Scope

Returns:

See Also:



494
# File 'app/models/room_configuration.rb', line 494

scope :in_cad_processing, -> { where(state: 'in_cad_processing').by_created_at }

.in_quotingActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are in quoting. Active Record Scope

Returns:

See Also:



508
# File 'app/models/room_configuration.rb', line 508

scope :in_quoting, -> { where(state: %w[in_design awaiting_quality_control]) }

.is_ditra_cable_spacing?(cs) ⇒ Object

Whether is ditra cable spacing.

Parameters:

  • cs (Object)

Returns:

  • (Object)


1102
1103
1104
1105
1106
1107
1108
# File 'app/models/room_configuration.rb', line 1102

def self.is_ditra_cable_spacing?(cs)
  if PUBLIC_DITRA_CABLE_SPACINGS.index(cs)
    true
  else
    false
  end
end

.is_membrane_cable_spacing?(cs) ⇒ Object

Whether is membrane cable spacing.

Parameters:

  • cs (Object)

Returns:

  • (Object)


1082
1083
1084
# File 'app/models/room_configuration.rb', line 1082

def self.is_membrane_cable_spacing?(cs)
  is_prodeso_cable_spacing?(cs) || is_ditra_cable_spacing?(cs)
end

.is_prodeso_cable_spacing?(cs) ⇒ Object

Whether is prodeso cable spacing.

Parameters:

  • cs (Object)

Returns:

  • (Object)


1090
1091
1092
1093
1094
1095
1096
# File 'app/models/room_configuration.rb', line 1090

def self.is_prodeso_cable_spacing?(cs)
  if PUBLIC_PRODESO_CABLE_SPACINGS.index(cs)
    true
  else
    false
  end
end

.last_revision_reference_number(root_reference_number) ⇒ Object

Last revision reference number.

Parameters:

  • root_reference_number (Object)

Returns:

  • (Object)


649
650
651
652
653
654
655
# File 'app/models/room_configuration.rb', line 649

def self.last_revision_reference_number(root_reference_number)
  revision_letter = root_reference_number.index('IQR') ? 'V' : 'R'
  RoomConfiguration.where(RoomConfiguration[:reference_number].matches("#{root_reference_number}-#{revision_letter}%"))
                   .order(:id)
                   .reverse_order
                   .limit(1).pluck(:reference_number).try(:first)
end

.last_revisionsActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are last revisions. Active Record Scope

Returns:

See Also:



497
# File 'app/models/room_configuration.rb', line 497

scope :last_revisions, -> { where(parent_id: nil) }

.lookupActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are lookup. Active Record Scope

Returns:

See Also:



505
# File 'app/models/room_configuration.rb', line 505

scope :lookup, ->(q) { where('room_configurations.reference_number like :term_wild', term_wild: "%#{q}%") }

.non_draftsActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are non drafts. Active Record Scope

Returns:

See Also:



498
# File 'app/models/room_configuration.rb', line 498

scope :non_drafts, -> { where.not(state: 'draft') }

.non_revisionsActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are non revisions. Active Record Scope

Returns:

See Also:



500
# File 'app/models/room_configuration.rb', line 500

scope :non_revisions, -> { last_revisions }

.order_by_heating_elements_countActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are order by heating elements count. Active Record Scope

Returns:

See Also:



519
# File 'app/models/room_configuration.rb', line 519

scope :order_by_heating_elements_count, -> { order(Arel.sql("(#{heating_element_count_sql})")) }

.reception_type_for_selectObject

Reception type for select.

Returns:

  • (Object)


712
713
714
715
716
717
# File 'app/models/room_configuration.rb', line 712

def self.reception_type_for_select
  # Use the centralized constant, adding any model-specific types
  RoomConfigurationConstants::ACTIVE_RECEPTION_TYPES.merge(
    'Room API' => 'RoomAPI'
  )
end

.reception_type_valid_valuesObject

Reception type valid values.

Returns:

  • (Object)


2690
2691
2692
# File 'app/models/room_configuration.rb', line 2690

def self.reception_type_valid_values
  @reception_type_valid_values ||= RoomConfiguration.reception_type_for_select.values
end

.rev_tag(rc) ⇒ Object

Rev tag.

Parameters:

  • rc (Object)

Returns:

  • (Object)


669
670
671
672
673
674
675
676
677
678
679
680
# File 'app/models/room_configuration.rb', line 669

def self.rev_tag(rc)
  root_ref_nbr = root_reference_number(rc)
  last_revision_ref = last_revision_reference_number(root_ref_nbr)
  new_rev = 1
  if last_revision_ref.present?
    last_revision_split = last_revision_ref.scan(RoomConfiguration::REFERENCE_NUMBER_PATTERN).flatten.compact
    last_revision_nbr = last_revision_split.try(:[], 2)
    new_rev += last_revision_nbr.to_i if last_revision_nbr.present?
  end
  revision_letter = root_ref_nbr.index('IQR') ? 'V' : 'R'
  "#{revision_letter}#{new_rev}"
end

.revision_reference_number(rc) ⇒ Object

Revision reference number.

Parameters:

  • rc (Object)

Returns:

  • (Object)


641
642
643
# File 'app/models/room_configuration.rb', line 641

def self.revision_reference_number(rc)
  "#{root_reference_number(rc)}-#{rev_tag(rc)}"
end

.root_reference_number(rc) ⇒ Object

Root reference number.

Parameters:

  • rc (Object)

Returns:

  • (Object)


661
662
663
# File 'app/models/room_configuration.rb', line 661

def self.root_reference_number(rc)
  rc.reference_number.scan(REFERENCE_NUMBER_PATTERN).flatten.compact.try(:[], 0)
end

.rp_roomsActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are rp rooms. Active Record Scope

Returns:

See Also:



507
# File 'app/models/room_configuration.rb', line 507

scope :rp_rooms, -> { where(reception_type: 'DesignTool2') }

.states_for_selectObject

States for select.

Returns:

  • (Object)


617
618
619
# File 'app/models/room_configuration.rb', line 617

def self.states_for_select
  state_machines[:state].states.map { |s| [s.human_name, s.value] }
end

.template_options_for_selectObject

Template options for select.

Returns:

  • (Object)


631
632
633
# File 'app/models/room_configuration.rb', line 631

def self.template_options_for_select
  %w[letter_landscape].map { |s| [s.humanize.gsub('Legacy letter portrait', 'Legacy letter portrait (ONLY USE for legacy plans)'), s] }
end

.update_or_create_from_rhc_args(rhc_args, options, customer, system_name, room_layout_image_params = nil) ⇒ Hash

Creates (or reuses, by share key) a room configuration from
Instant Quote rhc args, calculating the quote and adding the
selected line items.

Parameters:

  • rhc_args (Hash)

    args for CalculateQuote

  • options (Hash)
  • customer (Customer)
  • system_name (String, nil)

    name for the created opportunity

  • room_layout_image_params (Hash, nil) (defaults to: nil)

    room layout image params

Options Hash (options):

  • share_key (String)

    share key used to find an
    existing room configuration

  • selected_element_set (Integer) — default: 0

    index of the
    chosen element set in the calculated output

  • selected_control (Integer)

    selected control
    catalog item id

  • selected_kit (Integer)

    selected kit catalog item id

  • selected_accessories (Array) — default: []

    selected
    accessory catalog item ids

Returns:

  • (Hash)


3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
# File 'app/models/room_configuration.rb', line 3094

def self.update_or_create_from_rhc_args(rhc_args, options, customer, system_name, room_layout_image_params = nil)
  return_result = {}
  begin
    RoomConfiguration.transaction do
      if (room = customer.room_configurations.where(additional_notes: options[:share_key]).first) && (opportunity = room.opportunity) && (opportunity.opportunity_reception_type == 'IQ')
        # found a matching room and opportunity, use those rather than creating new ones
        opportunity.update(name: system_name) if system_name.present?
        raise AbortUpdateRhc, "opportunity.errors.full_messages: #{opportunity.errors_to_s}" unless opportunity.valid?
      else
        rhc_results = RoomConfiguration::CalculateQuote.call(rhc_args).to_hash.deep_symbolize_keys
        rhc_results[:output].sort_by! { |item| item[:price] }
        opportunity = customer.opportunities.create(name: system_name || OpportunityConstants::IQ_PROJECT_NAME, opportunity_reception_type: 'IQ')
        raise AbortUpdateRhc, "opportunity.errors.full_messages: #{opportunity.errors_to_s}" unless opportunity.valid?

        room_plan = RoomPlan.find(rhc_args[:custom_room_plan]) if rhc_args[:custom_room_plan].present?
        room_plan_id = room_plan&.owned_or_cloned(customer)&.id # here we just want to make sure that a saved room configuration's room plan is owned by the room configuration's customer. So same room plan if owned otherwise cloned and set to be owned by room configuration's customer.
        room = opportunity.room_configurations.create(
          name: opportunity.next_room_name(room_base_name: rhc_args[:room_type].name),
          room_type: rhc_args[:room_type],
          sub_floor_type_id: rhc_args[:sub_floor_type_id],
          floor_type: rhc_args[:floor_type],
          square_footage: rhc_args[:square_footage],
          insulation_surface: rhc_args[:insulation_surface],
          zones: rhc_args[:zones].map { |args| Zone.create(width: args[:width]&.to_s, length: args[:length]&.to_s) },
          expansion_joint_spacing: rhc_args[:expansion_joint_spacing],
          skip_expansion_joint_validation: rhc_args[:skip_expansion_joint].to_b,
          cable_spacing: rhc_results[:output][options[:selected_element_set] || 0][:cable_spacing],
          voltage: rhc_args[:voltage],
          coverage_state: rhc_args[:coverage_state],
          reception_type: rhc_args[:custom_room_plan].to_i.positive? ? 'DesignTool2' : 'IQ',
          room_plan_id:,
          additional_notes: options[:share_key],
          heating_system_product_line: ProductLine.find_by(
            slug_ltree: LtreePaths.slug_ltree_from_legacy_hyphen_url(
              rhc_results[:output][options[:selected_element_set] || 0][:heating_system_type_url]
            )
          )
        )
        raise AbortUpdateRhc, "room.errors.full_messages: #{room.errors_to_s}" unless room.valid?

        room.apply_line_items_from_rhc_results(rhc_results, options)
        room.save
        raise AbortUpdateRhc, "room.errors.full_messages: #{room.errors_to_s}" unless room.valid?
      end
      res = true
      if room_layout_image_params.present?
        room.room_layout_image = room_layout_image_params
        res = room.save
      end
      return_result = { success: res, room: }
    rescue AbortUpdateRhc => e
      return_result = { success: false, errors: e.to_s }
    end
    return_result
  end
end

.upload_categories_for_selectObject

Upload categories for select.

Returns:

  • (Object)


698
699
700
701
702
703
704
705
706
707
# File 'app/models/room_configuration.rb', line 698

def self.upload_categories_for_select
  [['Room Layout', 'room_layout'],
   ['Design Guideline', 'design_guideline'],
   ['Tracer Solution (CAD)', 'tracer_solution_cad'],
   ['Tracer Solution (Image)', 'tracer_solution_image'],
   %w[Other other],
   ['Installation Plan (CAD)', 'installation_plan_cad'],
   ['Installation Plan (Image)', 'installation_plan_image'],
   ['Warranty Card', 'warranty_card']]
end

.vignettes_for_product_line_idActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are vignettes for product line id. Active Record Scope

Returns:

See Also:



511
# File 'app/models/room_configuration.rb', line 511

scope :vignettes_for_product_line_id, ->(pl_id) { where(vignette: true).where('quoted_heating_system_pl_id = ? OR heating_system_product_line_id = ?', pl_id, pl_id).order(created_at: :desc) }

.vignettes_for_product_line_urlActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are vignettes for product line url. Active Record Scope

Returns:

See Also:



512
513
514
515
516
# File 'app/models/room_configuration.rb', line 512

scope :vignettes_for_product_line_url, ->(url) {
  slug = LtreePaths.slug_ltree_from_legacy_hyphen_url(url)
  pl = ProductLine.find_by(slug_ltree: slug)
  pl ? vignettes_for_product_line_id(pl.id) : none
}

.with_heating_elements_countActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are with heating elements count. Active Record Scope

Returns:

See Also:



520
# File 'app/models/room_configuration.rb', line 520

scope :with_heating_elements_count, -> { select_append(Arel.sql("(#{heating_element_count_sql}) as heating_element_count")) }

.with_item_idActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are with item id. Active Record Scope

Returns:

See Also:



517
# File 'app/models/room_configuration.rb', line 517

scope :with_item_id, ->(item_id) { where("EXISTS(SELECT 1 FROM line_items rli WHERE rli.resource_type = 'RoomConfiguration' AND rli.resource_id = room_configurations.id AND rli.item_id = ?)", item_id) }

.with_line_itemsActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are with line items. Active Record Scope

Returns:

See Also:



495
# File 'app/models/room_configuration.rb', line 495

scope :with_line_items, -> { includes(line_items: { catalog_item: { store_item: :item } }) }

.with_single_heating_elementActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are with single heating element. Active Record Scope

Returns:

See Also:



518
# File 'app/models/room_configuration.rb', line 518

scope :with_single_heating_element, -> { where("(#{heating_element_count_sql}) = 1") }

.with_vignetteActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are with vignette. Active Record Scope

Returns:

See Also:



510
# File 'app/models/room_configuration.rb', line 510

scope :with_vignette, -> { where(vignette: true) }

.without_revisionsActiveRecord::Relation<RoomConfiguration>

A relation of RoomConfigurations that are without revisions. Active Record Scope

Returns:

See Also:



499
# File 'app/models/room_configuration.rb', line 499

scope :without_revisions, -> { where('NOT EXISTS (select id from room_configurations rc2 where rc2.parent_id = room_configurations.id)') }

Instance Method Details

#activitiesActiveRecord::Associations::CollectionProxy

The associated activities.

Returns:

  • (ActiveRecord::Associations::CollectionProxy)


397
# File 'app/models/room_configuration.rb', line 397

has_many    :activities, as: :resource, dependent: :nullify

#add_bom_line_item(bom_item) ⇒ Object

Adds a line item from a BOM hash (from HeatingSystemCalculator)

Parameters:

  • bom_item (Hash)

    A BOM item with 'cat_id', 'qty', and 'price' keys



1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
# File 'app/models/room_configuration.rb', line 1714

def add_bom_line_item(bom_item)
  return if bom_item['cat_id'].blank?

  cat_item = CatalogItem.find_by(id: bom_item['cat_id'])
  return unless cat_item

  add_line_item(
    catalog_item_id: cat_item.id,
    quantity: bom_item['qty'] || 1,
    price: cat_item.amount,
    discounted_price: cat_item.discounted_price(customer, self),
    do_not_autosave: true
  )
end

#add_line_item(options) ⇒ LineItem

Adds (or increments) a line item for the given catalog item.

Parameters:

  • options (Hash)

    LineItem attributes, plus the control keys
    below

Options Hash (options):

  • catalog_item_id (Integer)

    catalog item to add
    (required)

  • quantity (Integer) — default: 1

    quantity to add

  • do_not_autosave (Boolean) — default: false

    skip saving the
    line item

  • keep_line_items_separate (Boolean)

    force a separate
    line item instead of merging quantities

Returns:



1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
# File 'app/models/room_configuration.rb', line 1674

def add_line_item(options)
  do_not_autosave = options.delete(:do_not_autosave)
  ci = CatalogItem.find options[:catalog_item_id]
  keep_line_items_separate = options.delete(:keep_line_items_separate)
  # any item with sku in SINGLE_QUANTITY_SKUS defaults to be a separate line item unless overridden with keep_line_items_separate option
  keep_line_items_separate ||= Item::SINGLE_QUANTITY_SKUS.include?(ci.sku) if keep_line_items_separate.nil?
  options[:quantity] ||= 1
  line_item = begin
    line_items.find { |li| li.catalog_item_id == options[:catalog_item_id].to_i }
  rescue StandardError
    nil
  end
  if line_item && !keep_line_items_separate
    line_item.quantity += options[:quantity].to_i
  else
    line_item = LineItem.new(options)
    line_item.price ||= line_item.catalog_item.amount
    line_items.build(line_item.attributes)
  end
  line_item.save unless do_not_autosave
  line_item
end

#add_selected_line_items_from_rhc_elements(rhc_results) ⇒ Object

Add selected line items from rhc elements.

Parameters:

  • rhc_results (Object)

Returns:

  • (Object)


3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
# File 'app/models/room_configuration.rb', line 3176

def add_selected_line_items_from_rhc_elements(rhc_results)
  rhc_results[:includedItems].each do |rhc_group|
    cat_item = CatalogItem.find(rhc_group[:cat_id])
    line_item_options = { quantity: rhc_group[:qty],
                          price: cat_item.amount,
                          discounted_price: cat_item.discounted_price(customer, self),
                          catalog_item_id: cat_item.id,
                          do_not_autosave: true }
    add_line_item(line_item_options)
  end
end

#add_selected_line_items_from_rhc_group(rhc_results, group, inds) ⇒ Object

Add selected line items from rhc group.

Parameters:

  • rhc_results (Object)
  • group (Object)
  • inds (Object)

Returns:

  • (Object)


3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
# File 'app/models/room_configuration.rb', line 3194

def add_selected_line_items_from_rhc_group(rhc_results, group, inds)
  inds.each do |ind|
    rhc_group = rhc_results[group][ind] || {}
    (rhc_group[:includedItems] || []).each do |rhc_included_item|
      cat_item = CatalogItem.find(rhc_included_item[:cat_id])
      line_item_options = { quantity: rhc_included_item[:qty],
                            price: cat_item.amount,
                            discounted_price: cat_item.discounted_price(customer, self),
                            catalog_item_id: cat_item.id,
                            do_not_autosave: true }
      add_line_item(line_item_options)
    end
  end
end

#add_selected_line_items_from_rhc_kits(rhc_results, group, inds) ⇒ Object

Add selected line items from rhc kits.

Parameters:

  • rhc_results (Object)
  • group (Object)
  • inds (Object)

Returns:

  • (Object)


3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
# File 'app/models/room_configuration.rb', line 3215

def add_selected_line_items_from_rhc_kits(rhc_results, group, inds)
  # here we do not process the kit included items, since those are only for show, just process the single selected kit
  inds.each do |ind|
    rhc_group = rhc_results[group][ind]
    cat_item = CatalogItem.find(rhc_group[:cat_id])
    line_item_options = { quantity: rhc_group[:qty],
                          price: cat_item.amount,
                          discounted_price: cat_item.discounted_price(customer, self),
                          catalog_item_id: cat_item.id,
                          do_not_autosave: true }
    add_line_item(line_item_options)
  end
end

#allow_heat_loss_calculation?Object

Whether allow heat loss calculation.

Returns:

  • (Object)


2730
2731
2732
# File 'app/models/room_configuration.rb', line 2730

def allow_heat_loss_calculation?
  (area > 0.0) && installation_postal_code.present? && under_floor && under_floor.id.present? && ceiling && ceiling.id.present?
end

#alternate_heplo_namesObject

Alternate heplo names.

Returns:

  • (Object)


1353
1354
1355
# File 'app/models/room_configuration.rb', line 1353

def alternate_heplo_names
  alternate_heplo_ids.map { |heplo_id| HeatingElementProductLineOption.find(heplo_id).name_with_cable_spacing }.join(', ')
end

#apply_line_items_from_rhc_results(rhc_results, options) ⇒ void

This method returns an undefined value.

Adds line items for the selected element set, control, kit, and
accessories from CalculateQuote results.

Parameters:

  • rhc_results (Hash)

    symbolized CalculateQuote result hash

  • options (Hash)

Options Hash (options):

  • selected_element_set (Integer) — default: 0

    index of the
    chosen element set in the output

  • selected_control (Integer)

    selected control
    catalog item id

  • selected_kit (Integer)

    selected kit catalog item id

  • selected_accessories (Array) — default: []

    selected
    accessory catalog item ids



3164
3165
3166
3167
3168
3169
3170
# File 'app/models/room_configuration.rb', line 3164

def apply_line_items_from_rhc_results(rhc_results, options)
  rhc_result = rhc_results[:output][options[:selected_element_set] || 0]
  add_selected_line_items_from_rhc_elements(rhc_result)
  add_selected_line_items_from_rhc_group(rhc_result, :controls, [options[:selected_control]].compact)
  add_selected_line_items_from_rhc_kits(rhc_result, :kits, [options[:selected_kit]].compact)
  add_selected_line_items_from_rhc_group(rhc_result, :accessories, options[:selected_accessories] || [])
end

#areaObject

Area.

Returns:

  • (Object)


2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
# File 'app/models/room_configuration.rb', line 2221

def area
  ((begin
    under_floor.area
  rescue StandardError
    nil
  end) || (begin
    ceiling.area
  rescue StandardError
    nil
  end) || insulation_surface || rectangular_area || square_footage).to_f
end

#belongs_only_to_legacy_quotes?Object

Whether belongs only to legacy quotes.

Returns:

  • (Object)


1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
# File 'app/models/room_configuration.rb', line 1968

def belongs_only_to_legacy_quotes?
  res = false
  legacy_quotes_num = 0
  quotes.each do |q|
    next unless q.reference_number

    legacy_quotes_num += 1 if q.reference_number.to_i < 300_000
  end
  res = true if legacy_quotes_num == quotes.length && legacy_quotes_num.positive?
  res
end

#billing_entityObject

Billing entity.

Returns:

  • (Object)


864
865
866
# File 'app/models/room_configuration.rb', line 864

def billing_entity
  opportunity.customer.billing_entity
end

#build_activityObject

Build activity.

Returns:

  • (Object)


771
772
773
# File 'app/models/room_configuration.rb', line 771

def build_activity
  activities.build resource: self, party: primary_party
end

#cable_spacing_mismatch?Object

Whether cable spacing mismatch.

Returns:

  • (Object)


845
846
847
848
849
850
# File 'app/models/room_configuration.rb', line 845

def cable_spacing_mismatch?
  return false unless require_cable_spacing?
  return false if installation_plan_cable_spacing.blank?

  cable_spacing != installation_plan_cable_spacing
end

#cad_filesActiveRecord::Associations::CollectionProxy

The associated cad files.

Returns:

  • (ActiveRecord::Associations::CollectionProxy)


391
# File 'app/models/room_configuration.rb', line 391

has_many    :cad_files, -> { where(category: 'installation_plan_cad') }, as: :resource, class_name: 'Upload', dependent: :destroy

#calculate_heat_lossObject

Calculate heat loss.

Returns:

  • (Object)


2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
# File 'app/models/room_configuration.rb', line 2796

def calculate_heat_loss
  # Assumptions:
  # - Constant hours of comfort/economy operation per weekday/weekend from thermostat defaults
  # - WY system is primary heat source; warm up/cool off time is negligible
  # - Heat supplied equals heat loss at ambient temperature in stable state
  # - Economy mode heat loss is proportional to comfort mode via temperature ratio
  # - Comfort ambient temperature is user's desired temperature
  # economy ambient temperature is the default thermostat default ambient setting
  # use average number of weekdays vs weekends per month, derived from averaging actual number in 4 year cycle

  # set up results
  res = {}
  res['hlc_averages'] = []
  res['hlc_ranges'] = []
  res['heat_supplied'] = []
  res['hlc_by_object'] = []
  res['avg_temperatures'] = []
  res['avg_temperature_ranges'] = []
  res['ambient_temperatures'] = []
  res['ambient_temperature_ranges'] = []
  res['operating_cost'] = []
  res['loss_exceeds_heat_supply'] = []
  res['recommendations'] = []
  res['recommendation_notes'] = []
  res['operating_hrs'] = []
  res['operating_cost'] = []
  # set initial coldest heat loss for pie chart, want worst case at coldest month of the year at comfort operation
  tavg_hlh_coldest = nil
  # choose highest initial temperature
  tavg_coldest = 1000.0
  # decide if we use room's heating system or theoretical based on 80% coverage of room area using the room's heating system, default to TZ Flex Roll
  if get_heat_supplied == 0.0
    hstn = heating_system_type_name.to_s
    hstn = "#{heating_system_type_name} #{cable_spacing.round}" if cable_spacing.present? && heating_system_type_name.to_s.index('TempZone')
    heat_supplied = ((WATTS_PER_SQUARE_FOOT[hstn || 'TempZone Flex Roll'] || 12.0) * 0.8 * area * BTU_PER_HOUR_PER_WATT).round
  else
    heat_supplied = get_heat_supplied.round
  end
  # set up initial counts of when heat supply meets heat loss
  num_months_heat_supplied_meets_loss = 0
  # get average monthly temperature data using room's location
  temp_data = AverageMonthlyTemperature.get_from_postal_code(installation_postal_code)
  return { error: temp_data[:message] } if temp_data[:status] != :ok

  # get utility rate $ cost per kwH using room's location
  rate_data = ElectricityRate.get_from_postal_code(installation_postal_code)
  return { error: rate_data[:message] } if rate_data[:status] != :ok

  # here we favor opportunity rate if defined, fall back to the ElectricityRate from postal code
  # Default to $0.15/kWh if no rate available (US national average)
  if opportunity&.electricity_rate
    avg_cost_per_kwh = opportunity.electricity_rate
    electricity_rate_blurb = '(from your project settings)'
  elsif rate_data[:average_rate].present?
    avg_cost_per_kwh = rate_data[:average_rate]
    electricity_rate_blurb = '(from government published utility rates based on your location)'
  else
    avg_cost_per_kwh = ElectricityRate::US_AVERAGE_RATE
    electricity_rate_blurb = '(using US national average rate)'
  end
  # set up initial operating costs
  operating_cost_annual = 0.0
  operating_cost_monthly_coldest = 0.0
  hl_max = 0.0
  hl_sources = heat_loss_children_recursive([])
  # iterate over the months
  12.times do |i|
    # get average daily outside temperature for the month
    tavg = temp_data[:average_temperatures][i]
    # get average daily outside temperature variation for the month and use to calculate average min and max temperatures
    tdelta = temp_data[:temperature_variations][i]
    tmin = tavg - tdelta
    tmax = tavg + tdelta
    # puts "calculate_heat_loss, tavg: #{tavg}"
    # puts "calculate_heat_loss, tmin: #{tmin}"
    # puts "calculate_heat_loss, tmax: #{tmax}"
    # calculate heat loss per source during comfort heating at desired temperature, this will be used for the pie chart
    tavg_hlh_comfort = self_and_children_heat_loss_recursive(desired_temperature, tavg, [])
    # calculte total heat losses at comfort and economy inside temperatures and the avg, min and max outside temperatures
    hl_avg_comfort = tavg_hlh_comfort.sum { |o| o[:heat_loss] }
    hl_avg_economy = get_self_and_children_heat_loss(DEFAULT_ECONOMY_AMB_TEMP, tavg)
    hl_min_comfort = get_self_and_children_heat_loss(desired_temperature, tmax)
    hl_min_economy = get_self_and_children_heat_loss(DEFAULT_ECONOMY_AMB_TEMP, tmax)
    hl_max_comfort = get_self_and_children_heat_loss(desired_temperature, tmin)
    hl_max_economy = get_self_and_children_heat_loss(DEFAULT_ECONOMY_AMB_TEMP, tmin)
    # then get weighted average for these based on the thermostat default heating periods and number of week days vs week end days
    hl_avg = get_weighted_average_heat_loss(hl_avg_comfort, hl_avg_economy)
    hl_min = get_weighted_average_heat_loss(hl_min_comfort, hl_min_economy)
    hl_max = get_weighted_average_heat_loss(hl_max_comfort, hl_max_economy)
    # store these heat losses and max WY heat supplied per month
    res['hlc_averages'] << { 'heat_loss' => hl_avg }
    res['hlc_ranges'] << { 'range' => [hl_min, hl_max] }
    res['heat_supplied'] << { 'heat_supplied' => heat_supplied }
    if (heat_supplied >= hl_avg * (100.0 + HLC_PRIMARY_OK_THRESHOLD[:percent]) / 100.0) && (heat_supplied >= (hl_avg + HLC_PRIMARY_OK_THRESHOLD[:btu_per_hr]))
      # if we meet or exceed heat loss we are good
      # increment num_months_heat_supplied_meets_loss
      num_months_heat_supplied_meets_loss += 1
      # store ambient temperature reached as desired
      res['ambient_temperatures'] << { 'temperature' => desired_temperature }
      # store ambient temperature range of +- delta of 3 degrees, for fuzziness
      res['ambient_temperature_ranges'] << { 'range' => [(desired_temperature.to_f - 3.0).round, (desired_temperature.to_f + 3.0).round] }
      # store loss_exceeds_heat_supply flag as false for hilighting on charts
      res['loss_exceeds_heat_supply'] << { 'exceeds' => false }
    else
      # if we do NOT meet or exceed heat loss
      # calculate reachable ambient comfort temperature based on steady state where heat loss will equal heat supplied, at that temp, ie based on linear ratio
      tamb = desired_temperature.to_f * (heat_supplied / hl_avg_comfort).to_f
      # store ambient temperature reached
      res['ambient_temperatures'] << { 'temperature' => tamb.round }
      # store ambient temperature range of +- delta of 3 degrees, for fuzziness
      res['ambient_temperature_ranges'] << { 'range' => [(tamb - 3.0).round, (tamb + 3.0).round] }
      # store loss_exceeds_heat_supply flag as true for hilighting on charts
      res['loss_exceeds_heat_supply'] << { 'exceeds' => true }
    end
    # store avg outside temperature with range of +- delta
    res['avg_temperatures'] << { 'temperature' => tavg }
    res['avg_temperature_ranges'] << { 'range' => [tmin, tmax] }
    operating_hrs = RoomConfiguration::OperatingCostsCalculator.get_monthly_operation_kwh(hl_avg_comfort, hl_avg_economy)
    # accumulate annual operation cost for this month by calculating kwH and using utility rate
    operation_cost_this_month = [operating_hrs * avg_cost_per_kwh, 0.0].max
    res['operating_hrs'] << operating_hrs
    res['operating_cost'] << operation_cost_this_month
    operating_cost_annual += operation_cost_this_month
    # if this is a colder month store the heat loss at comfort temp
    next unless tavg < tavg_coldest

    tavg_hlh_coldest = tavg_hlh_comfort
    tavg_coldest = tavg
    operating_cost_monthly_coldest = operation_cost_this_month
    hl_max = hl_max_comfort
  end
  # for the coldest month at comfort mode, show each heat loss source,
  ok_col_ind = 0
  flag_col_ind = 0
  tavg_hlh_coldest.each do |o|
    hlc_by_object = { 'name' => o[:object].heat_loss_name.to_s, 'heat_loss' => o[:heat_loss] }
    if begin
      (o[:object].get_u_value > Models::HeatLossable::HIGH_U_VALUE_THRESHOLD)
    rescue StandardError
      false
    end
      hlc_by_object['color'] = Models::HeatLossable::PIE_CHART_FLAG_COLORS[flag_col_ind]
      flag_col_ind += 1
      flag_col_ind = 0 if flag_col_ind >= Models::HeatLossable::PIE_CHART_FLAG_COLORS.length
    else
      hlc_by_object['color'] = Models::HeatLossable::PIE_CHART_OK_COLORS[ok_col_ind]
      ok_col_ind += 1
      ok_col_ind = 0 if ok_col_ind >= Models::HeatLossable::PIE_CHART_OK_COLORS.length
    end
    res['hlc_by_object'] << hlc_by_object
  end
  # append recommendations and results
  res['recommendations'] << if get_heat_supplied == 0.0
                              "A WarmlyYours #{heating_system_type_name || 'TempZone Flex Roll'} system with 80% coverage would supply #{heat_supplied} BTU/hour."
                            else
                              "The WarmlyYours #{heating_system_type_name} system in this room can supply #{heat_supplied} BTU/hour."
                            end
  if num_months_heat_supplied_meets_loss >= RoomConfigurationConstants::MONTHS_FOR_PRIMARY_HEAT
    res['recommendations'] << 'The WarmlyYours system above can be the primary heat source for this room.'
  else
    if num_months_heat_supplied_meets_loss >= RoomConfigurationConstants::MONTHS_FOR_THREE_SEASON_HEAT
      recos = []
      reco_tzfr = (heating_system_type_name != 'TempZone Flex Roll') && heating_system_type_name.to_s.index('TempZone')
      flex_roll_watts = WATTS_PER_SQUARE_FOOT['TempZone Flex Roll'].to_i
      recos << "using a TempZone Flex Roll system which can supply #{flex_roll_watts} watts/sq.ft." if reco_tzfr
      recos << 'increasing the coverage of your heated areas'
      hs_needed = hl_max - heat_supplied
      recos << "or adding a supplemental heat source of #{hs_needed.round.abs} BTU/hour (or #{(hs_needed / BTU_PER_HOUR_PER_WATT).round.abs} watts) on the coldest months"
      res['recommendations'] << "The WarmlyYours system above can be the primary heat source for this room for three seasons. To reach your desired ambient temperature, please consider #{recos.join(', ')}"
    else
      res['recommendations'] << 'The WarmlyYours system above can only be a supplemental heat source for this room. You will need a primary heat source to reach your desired ambient temperature.'
    end
    hlc_need_insulation = []
    hl_sources.each do |o|
      hlc_need_insulation << o if o.get_u_value > Models::HeatLossable::HIGH_U_VALUE_THRESHOLD
    end
    if hlc_need_insulation.any?
      res['recommendations'] << "You may also consider replacing or adding insulation to the following: #{hlc_need_insulation.map(&:heat_loss_name).join(', ')}. #{hlc_need_insulation.length > 1 ? 'They are' : 'It is'} contributing to a greater than normal heat loss."
    end
    if line_items.underlayments.empty? && under_floor.under_floor_type&.needs_underlayment? && insulation_surface.to_i.positive? && (under_floor.get_u_value > Models::HeatLossable::HIGH_U_VALUE_THRESHOLD)
      underlayment_sugg_lis = HeatingElementProductLineOption.get_suggested_underlayments_for_room(self)
      underlayment_sugg = underlayment_sugg_lis.map { |k, h| "#{h[:quantity]} X #{h[:name]}, SKU: #{k}" }
      res['recommendations'] << "You may also consider adding underlayment to your WarmlyYours heating system. We recommend: #{underlayment_sugg.join(', ')}" unless underlayment_sugg.empty?
    end
  end
  res['recommendations'] << "The annual operating costs* of this WarmlyYours system will be: #{ActionController::Base.helpers.number_to_currency(operating_cost_annual.round(2), unit: currency_symbol)}."
  res['recommendations'] << "The monthly operating cost of this system on the coldest month will be: #{ActionController::Base.helpers.number_to_currency(operating_cost_monthly_coldest.round(2), unit: currency_symbol)}."
  res['recommendations'] << "Operating costs assume an average annual residential electricity rate of: #{ActionController::Base.helpers.number_to_currency(avg_cost_per_kwh.round(2), unit: currency_symbol)}/kilowatt-hours #{electricity_rate_blurb}."
  res['recommendations'] << "Operating costs also assume heating hours per day on a monthly basis to achieve the desired 'comfort' ambient temperature for the following warming periods: Monday-Friday: 6:00 a.m.-8:30 a.m. and 5:00 p.m.-11:00 p.m., and Saturday-Sunday: 6:00 a.m.-11:00 p.m. and assuming an 'economy'/non heating hour ambient temperature of #{DEFAULT_ECONOMY_AMB_TEMP} F or #{PresetJob.fahrenheit_to_celsius(DEFAULT_ECONOMY_AMB_TEMP)} C"
  res['recommendation_notes'] << 'WarmlyYours makes no claim or warrant as to the amount of floor/room temperature rise, the time to reach a given floor/room temperature or final floor/room temperature due to the innumerable variations in building construction and environmental conditions. This heat loss calculation is an estimate based on the inputs given, and average temperature forecast data.'
  res['operating_cost_annual'] = operating_cost_annual
  # hlc_h = hstorify_heat_loss(res)
  update_attribute(:last_heat_loss, res)
  res
end

#calculate_operating_cost_rate_using_hepObject

Calculate operating cost rate using hep.

Returns:

  • (Object)


2420
2421
2422
2423
2424
2425
2426
2427
2428
# File 'app/models/room_configuration.rb', line 2420

def calculate_operating_cost_rate_using_hep
  hep_tfr = Item::HeatingElementPlan.new_from_room_configuration(self)
  res = if hep_tfr.electrical_consumption.nil?
          nil
        else
          hep_tfr.electrical_consumption[:rate]
        end
  res.to_f.round(2)
end

#calculate_total_ampsObject

Calculate total amps.

Returns:

  • (Object)


2069
2070
2071
2072
# File 'app/models/room_configuration.rb', line 2069

def calculate_total_amps
  # Include :item to avoid N+1 when accessing he.item.amps
  heating_elements.includes(:item).sum { |he| he.quantity * he.item.amps.to_f }.round(2)
end

#calculate_total_kilowattsObject

Calculate total kilowatts.

Returns:

  • (Object)


2084
2085
2086
# File 'app/models/room_configuration.rb', line 2084

def calculate_total_kilowatts
  calculate_total_watts / 1000.0
end

#calculate_total_wattsObject

Calculate total watts.

Returns:

  • (Object)


2077
2078
2079
# File 'app/models/room_configuration.rb', line 2077

def calculate_total_watts
  heating_elements.to_a.sum { |he| he.quantity * he.item.watts.to_f }
end

#can_add_to_cart?Object

Whether can add to cart.

Returns:

  • (Object)


2381
2382
2383
2384
2385
2386
# File 'app/models/room_configuration.rb', line 2381

def can_add_to_cart?
  %w[draft complete in_design].include?(state) && line_items.non_shipping.present? &&
    quotes.any? do |q|
      q.awaiting_transmission? || q.complete?
    end
end

#can_calculate_heat_loss?Object

Whether can calculate heat loss.

Returns:

  • (Object)


2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
# File 'app/models/room_configuration.rb', line 2737

def can_calculate_heat_loss?
  status = :ok
  errors = []
  self.enter_heat_loss_info = true
  if valid?
    heat_loss_children_recursive([]).each do |c|
      unless c.valid?
        errors << "#{c.name}: #{c.errors_to_s}"
        status = :error
      end
    end
    if status == :ok
      temp_data = AverageMonthlyTemperature.get_from_postal_code(installation_postal_code)
      if temp_data[:status] != :ok
        errors << temp_data[:message]
        status = :error
      end
    end
    if status == :ok
      rate_data = ElectricityRate.get_from_postal_code(installation_postal_code)
      if rate_data[:status] != :ok
        errors << rate_data[:message]
        status = :error
      end
    end
  else
    status = :error
  end
  { status:, errors: }
end

#can_have_installation_plan?Object

Whether can have installation plan.

Returns:

  • (Object)


1256
1257
1258
# File 'app/models/room_configuration.rb', line 1256

def can_have_installation_plan?
  in_design? || awaiting_quality_control?
end

#can_have_room_layout?Object

Whether can have room layout.

Returns:

  • (Object)


1249
1250
1251
# File 'app/models/room_configuration.rb', line 1249

def can_have_room_layout?
  draft? || in_design?
end

#cancelable?Object

Whether cancelable.

Returns:

  • (Object)


1240
1241
1242
1243
1244
# File 'app/models/room_configuration.rb', line 1240

def cancelable?
  (state.nil? || CANCELABLE_STATES.include?(state.to_sym)) &&
    quotes.active.empty? &&
    orders.not_cancelled.empty?
end

#catalogObject

Alias for Customer#catalog

Returns:

  • (Object)

    Customer#catalog

See Also:



484
# File 'app/models/room_configuration.rb', line 484

delegate :catalog, to: :customer

#ceilingCeiling?

The associated ceiling.

Returns:



416
# File 'app/models/room_configuration.rb', line 416

has_one    :ceiling, dependent: :destroy

#check_room_layout_or_note_attachedObject

Check room layout or note attached.

Returns:

  • (Object)


1170
1171
1172
1173
1174
1175
# File 'app/models/room_configuration.rb', line 1170

def check_room_layout_or_note_attached
  # puts "reception_type: #{reception_type}"
  result = room_layout_or_note_attached?
  errors.add(:base, 'Room Layout or some design guide note or dimensions must be attached to room for engineering.') if result == false
  result
end

#companyObject

Alias for Customer#company

Returns:

  • (Object)

    Customer#company

See Also:



483
# File 'app/models/room_configuration.rb', line 483

delegate :company, to: :customer

#config_nameObject

Config name.

Returns:

  • (Object)


1601
1602
1603
1604
1605
1606
1607
1608
1609
# File 'app/models/room_configuration.rb', line 1601

def config_name
  new_name = []
  new_name << floor_type.name.to_s if floor_type
  new_name << ", #{pretty_coverage_state_description}" if coverage_state
  new_name << ", #{heplo_summary}" if heplo_summary
  new_name << " (#{voltage_id}V)" if voltage_id
  new_name << " #{revision}" if revision
  new_name.compact.uniq.join
end

#config_prettyObject

Config pretty.

Returns:

  • (Object)


1614
1615
1616
1617
1618
1619
# File 'app/models/room_configuration.rb', line 1614

def config_pretty
  new_name = []
  new_name << heplo_summary(true).to_s if heplo_summary(true)
  new_name << " (#{voltage_id}V)" if voltage_id
  new_name.compact.uniq.join
end

#control_voltageObject

Control voltage.

Returns:

  • (Object)


2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
# File 'app/models/room_configuration.rb', line 2099

def control_voltage
  res = nil
  rc = controlled_by || self
  # yeesh, we moved old smartstats etc out of thermostats!
  thermostats = (rc.line_items.thermostats.to_a + rc.line_items.smartstats.to_a + rc.line_items.easystats.to_a)
  if thermostats.any? && thermostats.to_a.all? { |li| !li.item.is_dual_voltage? } && (thermostats.filter_map { |li| li.item.voltage }.uniq.length == 1)
    tstat = thermostats.first.item
    res = tstat.voltage
  elsif rc.line_items.integration_kits&.to_a&.all? { |li| !li.item.is_dual_voltage? } && (rc.line_items.integration_kits.map { |li| li.item.voltage }.uniq.length == 1)
    int_kit = rc.line_items.integration_kits.first.item
    res = int_kit.voltage
  end
  res
end

#controlled_byRoomConfiguration?

The associated controlled by.

Returns:



339
# File 'app/models/room_configuration.rb', line 339

belongs_to  :controlled_by, class_name: 'RoomConfiguration', optional: true

#controlled_roomsActiveRecord::Associations::CollectionProxy

The associated controlled rooms.

Returns:

  • (ActiveRecord::Associations::CollectionProxy)


382
# File 'app/models/room_configuration.rb', line 382

has_many    :controlled_rooms, class_name: 'RoomConfiguration', foreign_key: 'controlled_by_id', dependent: :nullify

#controlled_rooms_by_quote(quote) ⇒ Object

Controlled rooms by quote.

Parameters:

  • quote (Object)

Returns:

  • (Object)


1273
1274
1275
# File 'app/models/room_configuration.rb', line 1273

def controlled_rooms_by_quote(quote)
  controlled_rooms.select { |r| quote.room_configurations.include? r }
end

#coverage_percentageObject

Coverage percentage.

Returns:

  • (Object)


3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
# File 'app/models/room_configuration.rb', line 3047

def coverage_percentage
  max_pct = RoomConfigurationConstants::MAX_COVERAGE_PERCENTAGE
  if last_bom.present? && last_bom['coverage_percentage'].present?
    last_bom['coverage_percentage']
  elsif square_footage.present? && installation_sqft.present? && square_footage.positive? && installation_sqft.positive?
    [(max_pct * installation_sqft / square_footage).round, max_pct.to_i].min
  elsif installation_sqft.present? && installation_sqft.positive?
    max_pct
  end
end

Create and link vignette installation plan image.

Returns:

  • (Object)


3061
3062
3063
# File 'app/models/room_configuration.rb', line 3061

def create_and_link_vignette_installation_plan_image
  update(vignette: true)
end

#create_heatloss_followup_activity(user, upload_link) ⇒ Object

Create heatloss followup activity.

Parameters:

  • user (Object)
  • upload_link (Object)

Returns:

  • (Object)


2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
# File 'app/models/room_configuration.rb', line 2312

def create_heatloss_followup_activity(user, upload_link)
  # unless you've already got one
  return if activities.open_activities.where(activity_type_id: ActivityTypeConstants::HEATLOS).any?

  Activity.create(lock_target_datetime: true,
                  activity_type_id: ActivityTypeConstants::HEATLOS,
                  target_datetime: 30.seconds.from_now,
                  assigned_resource: user,
                  party: customer,
                  resource: self,
                  notes: "Heat Loss Customer Form, please generate a Heat Loss Report, see attachments tab or #{upload_link}.")
end

#create_revision(copy_line_items = false, copy_room_layout = false, copy_installation_plan = false) ⇒ Object

Create revision.

Parameters:

  • copy_line_items (Object) (defaults to: false)
  • copy_room_layout (Object) (defaults to: false)
  • copy_installation_plan (Object) (defaults to: false)

Returns:

  • (Object)


1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
# File 'app/models/room_configuration.rb', line 1835

def create_revision(copy_line_items = false, copy_room_layout = false, copy_installation_plan = false)
  new_rc = nil
  RoomConfiguration.transaction do
    new_rc = deep_dup
    new_rc.plan_created_at = copy_installation_plan ? Date.current : nil
    new_rc.save!
    if copy_line_items
      line_items.parents_only.each do |li|
        new_li = li.deep_dup
        new_li.children_count = nil
        new_li.room_configuration = new_rc
        new_rc.line_items << new_li
      end
    end

    if copy_room_layout && (room_layout_upload = uploads.room_layouts.first)
      new_room_layout_upload = room_layout_upload.clone_with_file(new_rc)
      new_room_layout_upload&.save!
    end
    if copy_installation_plan
      if (old_install_plan_cad = begin
        uploads.installation_plan_cads.first
      rescue StandardError
        nil
      end)
        install_plan_cad = old_install_plan_cad.clone_with_file(new_rc)
        install_plan_cad&.save!
      end
      if (old_install_plan_image = uploads.installation_plan_images.first)
        install_plan_image = old_install_plan_image.clone_with_file(new_rc)
        install_plan_image&.save!
      end
    end
  end
  new_rc
end

Crm link.

Returns:

  • (Object)


2055
2056
2057
# File 'app/models/room_configuration.rb', line 2055

def crm_link
  UrlHelper.instance.room_configuration_path(self)
end

#currency_symbolObject

Currency symbol.

Returns:

  • (Object)


2023
2024
2025
# File 'app/models/room_configuration.rb', line 2023

def currency_symbol
  Money::Currency.new(currency).symbol
end

#customerObject

Alias for Opportunity#customer

Returns:

  • (Object)

    Opportunity#customer

See Also:



482
# File 'app/models/room_configuration.rb', line 482

delegate :customer, to: :opportunity

#deep_dupObject

Deep dup.

Returns:

  • (Object)


190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'app/models/room_configuration.rb', line 190

def deep_dup
  deep_clone(
    include: :zones,
    except: %i[reference_number parent_id plan_created_at created_at creator_id updated_at updater_id
               floorplanner_employee_id designer_employee_id labeler_employee_id
               installation_plan_qc_employee_id quoted_heating_system_pl_id installation_plan_cable_spacing]
  ) do |original, copy|
    if copy.is_a?(RoomConfiguration)
      copy.state = 'draft'
      copy.vignette = false
      copy.parent_id = original.id
      copy.reference_number = RoomConfiguration.revision_reference_number(original)
    end
  end
end

#deletable?(current_user) ⇒ Object

Whether deletable.

Parameters:

  • current_user (Object)

Returns:

  • (Object)


1233
1234
1235
# File 'app/models/room_configuration.rb', line 1233

def deletable?(current_user)
  (state != 'complete') || current_user.has_role?('engineering_manager')
end

#design_tool_params_hshObject

Design tool params hsh.

Returns:

  • (Object)


2367
2368
2369
# File 'app/models/room_configuration.rb', line 2367

def design_tool_params_hsh
  DesignTool::Configurator.new(room_configuration: self).to_hsh
end

#designer_employeeEmployee?

The associated designer employee.

Returns:



345
# File 'app/models/room_configuration.rb', line 345

belongs_to  :designer_employee, class_name: 'Employee', optional: true

#dimension_labelObject

Dimension label.

Returns:

  • (Object)


2199
2200
2201
2202
2203
2204
2205
2206
2207
# File 'app/models/room_configuration.rb', line 2199

def dimension_label
  if room_length && room_width
    "#{room_length_raw} x #{room_width_raw}"
  elsif room_length
    room_length_raw.to_s
  elsif room_width
    room_width_raw.to_s
  end
end

#dimensions_labelObject

Dimensions label.

Returns:

  • (Object)


1591
1592
1593
1594
1595
1596
# File 'app/models/room_configuration.rb', line 1591

def dimensions_label
  res = nil
  res_arr = zones.map(&:dimension_label)
  res = res_arr.join(', ') unless res_arr.empty?
  res
end

#easy_mat?Object

Alias for Heating_system_product_line#easy_mat?

Returns:

  • (Object)

    Heating_system_product_line#easy_mat?

See Also:



487
# File 'app/models/room_configuration.rb', line 487

delegate :flex_roll?, :easy_mat?, :environ_flex_roll?, to: :heating_system_product_line

#editing_locked?Object

Whether editing locked.

Returns:

  • (Object)


947
948
949
# File 'app/models/room_configuration.rb', line 947

def editing_locked?
  complete? || cancelled? || orders.room_not_pickable.exists?
end

#effective_voltage(voltage_only = false) ⇒ Object

Effective voltage.

Parameters:

  • voltage_only (Object) (defaults to: false)

Returns:

  • (Object)


835
836
837
838
839
840
# File 'app/models/room_configuration.rb', line 835

def effective_voltage(voltage_only = false)
  v = line_items.primary_heating_system_voltage
  return (v || RoomConfigurationConstants::DEFAULT_VOLTAGE).round if voltage_only

  "#{(v || RoomConfigurationConstants::DEFAULT_VOLTAGE).round} Volts"
end

#electrical_file_name(with_extension = true) ⇒ Object

Electrical file name.

Parameters:

  • with_extension (Object) (defaults to: true)

Returns:

  • (Object)


2092
2093
2094
# File 'app/models/room_configuration.rb', line 2092

def electrical_file_name(with_extension = true)
  "electrical_plan_#{reference_number}#{'.pdf' if with_extension}"
end

#electrical_plan_pdfObject

Electrical plan pdf.

Returns:

  • (Object)


1530
1531
1532
# File 'app/models/room_configuration.rb', line 1530

def electrical_plan_pdf
  uploads.electrical_plan_pdfs.sorted_by_position.first
end

#electricianCustomer?

The associated electrician.

Returns:



367
# File 'app/models/room_configuration.rb', line 367

belongs_to  :electrician, class_name: 'Customer', optional: true, deprecated: true

#element_pole_assignmentsActiveRecord::Associations::CollectionProxy

The associated element pole assignments.

Returns:

  • (ActiveRecord::Associations::CollectionProxy)


412
# File 'app/models/room_configuration.rb', line 412

has_many    :element_pole_assignments, dependent: :destroy

#email_for_plan_request_confirmationObject

Email for plan request confirmation.

Returns:

  • (Object)


2295
2296
2297
# File 'app/models/room_configuration.rb', line 2295

def email_for_plan_request_confirmation
  party_for_plan_request_confirmation.email || customer.email || party_for_plan_request_confirmation.&.email || customer.&.email
end

#engineer_signature_tagObject

Creates an engineer signature tag for installation plan



1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
# File 'app/models/room_configuration.rb', line 1981

def engineer_signature_tag
  tag = []
  begin
    tag << floorplanner_employee.short_name
  rescue StandardError
    '-'
  end
  begin
    tag << designer_employee.short_name
  rescue StandardError
    '-'
  end
  begin
    tag << labeler_employee.short_name
  rescue StandardError
    '-'
  end
  begin
    tag << installation_plan_qc_employee.short_name
  rescue StandardError
    '-'
  end
  tag.join('/')
end

#entering_heat_loss_info?Object

Whether entering heat loss info.

Returns:

  • (Object)


2697
2698
2699
# File 'app/models/room_configuration.rb', line 2697

def entering_heat_loss_info?
  enter_heat_loss_info
end

#environ_flex_roll?Object

Alias for Heating_system_product_line#environ_flex_roll?

Returns:

  • (Object)

    Heating_system_product_line#environ_flex_roll?

See Also:



487
# File 'app/models/room_configuration.rb', line 487

delegate :flex_roll?, :easy_mat?, :environ_flex_roll?, to: :heating_system_product_line

#environmentObject

Alias for Room_type#environment

Returns:

  • (Object)

    Room_type#environment

See Also:



485
# File 'app/models/room_configuration.rb', line 485

delegate :environment, to: :room_type

#ev2_volume_discount(msrp = false) ⇒ Object

Ev2 volume discount.

Parameters:

  • msrp (Object) (defaults to: false)

Returns:

  • (Object)


1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
# File 'app/models/room_configuration.rb', line 1799

def ev2_volume_discount(msrp = false)
  discount = BigDecimal(0)
  ev2_subtotal = subtotal_ev2(msrp)
  if (ev2_subtotal >= RoomConfigurationConstants::EV2_VOLUME_DISCOUNT_THRESHOLD_LOW) && (ev2_subtotal < RoomConfigurationConstants::EV2_VOLUME_DISCOUNT_THRESHOLD_HIGH)
    discount = (ev2_subtotal * RoomConfigurationConstants::EV2_VOLUME_DISCOUNT_PERCENT_LOW) / 100
  elsif ev2_subtotal >= RoomConfigurationConstants::EV2_VOLUME_DISCOUNT_THRESHOLD_HIGH
    discount = (ev2_subtotal * RoomConfigurationConstants::EV2_VOLUME_DISCOUNT_PERCENT_HIGH) / 100
  end
  discount
end

#event_flow_nameObject

Legacy iqd_total method removed - IQ system deprecated



1359
1360
1361
# File 'app/models/room_configuration.rb', line 1359

def event_flow_name
  event_flow.try(:name)
end

#exclude_manually_initiated_event?(_event) ⇒ Object

Whether exclude manually initiated event.

Parameters:

  • _event (Object)

Returns:

  • (Object)


610
611
612
# File 'app/models/room_configuration.rb', line 610

def exclude_manually_initiated_event?(_event)
  false
end

#exterior_wallsActiveRecord::Associations::CollectionProxy

The associated exterior walls.

Returns:

  • (ActiveRecord::Associations::CollectionProxy)


422
# File 'app/models/room_configuration.rb', line 422

has_many   :exterior_walls, -> { order('exterior_walls.id ASC NULLS LAST') }, dependent: :destroy

#file_name(with_extension = true) ⇒ Object

File name.

Parameters:

  • with_extension (Object) (defaults to: true)

Returns:

  • (Object)


2676
2677
2678
# File 'app/models/room_configuration.rb', line 2676

def file_name(with_extension = true)
  "installation_plan_#{reference_number}#{'.pdf' if with_extension}"
end

Finds accessories recommended for this room's heating elements and
controls.

Parameters:

  • options (Hash) (defaults to: {})

Options Hash (options):

  • container (#room_configuration_ids)

    container (e.g.
    an opportunity) whose controlled rooms are included

Returns:

  • (Array)

    recommended accessories, empty on error or when no
    heating system type applies



2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
# File 'app/models/room_configuration.rb', line 2456

def find_recommended_accessories(options = {})
  return [] if get_heating_system_type.blank?

  hsc = HeatingSystemCalculator::BaseCalculator.new(room_configuration: self)
  return [] if hsc.heating_system_items.nil? || hsc.heating_system_items.error

  elements = hsc.heating_system_items.line_items_to_bom_elements(line_items.heating_elements.with_associations)
  controlled_rooms_to_use = controlled_rooms
  controlled_rooms_to_use = controlled_rooms.select { |rc| options[:container].room_configuration_ids.include?(rc.id) } if options[:container].respond_to?(:room_configurations)
  controlled_rooms_to_use.each do |rc|
    elements += hsc.heating_system_items.line_items_to_bom_elements(rc.line_items.heating_elements.with_associations)
  end
  high_voltage_control_line_items = if line_items.power_modules.any?
                                      line_items.power_modules
                                    elsif line_items.integration_kits.any?
                                      line_items.integration_kits
                                    else
                                      line_items.thermostats
                                    end
  controls = hsc.heating_system_items.line_items_to_bom_controls(high_voltage_control_line_items.with_associations)
  accessories_res = hsc.find_accessories(elements, controls)
  return [] if accessories_res.error

  accessories_res.accessories
end

#flex_roll?Object

Alias for Heating_system_product_line#flex_roll?

Returns:

  • (Object)

    Heating_system_product_line#flex_roll?

See Also:



487
# File 'app/models/room_configuration.rb', line 487

delegate :flex_roll?, :easy_mat?, :environ_flex_roll?, to: :heating_system_product_line

#floor_planObject

Floor plan.

Returns:

  • (Object)


1434
1435
1436
# File 'app/models/room_configuration.rb', line 1434

def floor_plan
  installation_plan_image || room_layout_image
end

#floor_plan_display_room_configurationsActiveRecord::Associations::CollectionProxy

The associated floor plan display room configurations.

Returns:

  • (ActiveRecord::Associations::CollectionProxy)


406
# File 'app/models/room_configuration.rb', line 406

has_many    :floor_plan_display_room_configurations, dependent: :destroy

#floor_plan_displaysActiveRecord::Associations::CollectionProxy

The associated floor plan displays.

Returns:

  • (ActiveRecord::Associations::CollectionProxy)


409
# File 'app/models/room_configuration.rb', line 409

has_many    :floor_plan_displays, through: :floor_plan_display_room_configurations

#floor_typeFloorType?

The associated floor type.

Returns:



324
# File 'app/models/room_configuration.rb', line 324

belongs_to  :floor_type, optional: true

#floor_type_nameObject

Floor type name.

Returns:

  • (Object)


1366
1367
1368
# File 'app/models/room_configuration.rb', line 1366

def floor_type_name
  floor_type.try(:name)
end

#floorplan_quote_parent_line_itemsObject

Floorplan quote parent line items.

Returns:

  • (Object)


1294
1295
1296
1297
1298
1299
# File 'app/models/room_configuration.rb', line 1294

def floorplan_quote_parent_line_items
  # quotes.last_revisions.first.line_items.non_shipping.parents_only.product_category_sorted
  quotes.first.line_items.non_shipping.parents_only.product_category_sorted
rescue StandardError
  nil
end

#floorplanner_employeeEmployee?

The associated floorplanner employee.

Returns:



342
# File 'app/models/room_configuration.rb', line 342

belongs_to  :floorplanner_employee, class_name: 'Employee', optional: true

#generate_all_plansObject

Generate all plans.

Returns:

  • (Object)


2497
2498
2499
2500
2501
2502
2503
2504
# File 'app/models/room_configuration.rb', line 2497

def generate_all_plans
  %i[generate_installation_plan_pdf generate_electrical_plan_pdf generate_multizone_plan_pdf].each do |method|
    public_send(method)
  rescue StandardError => e
    Rails.logger.error "[RoomConfiguration#generate_all_plans] #{method} failed for RC #{id}: #{e.class} - #{e.message}"
    Appsignal.send_error(e)
  end
end

#generate_bill_of_materialsObject

Generate bill of materials.

Returns:

  • (Object)


2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
# File 'app/models/room_configuration.rb', line 2328

def generate_bill_of_materials
  bill_of_materials = HeatingSystemCalculator::BaseCalculator.new(
    room_configuration: self
  ).calculate_system
  unless line_items.empty?
    line_items.destroy_all
    reload
  end
  return false unless bill_of_materials['found_solution']

  self.last_bom = hstorify_bom(bill_of_materials)
  populate_line_items_from_bom_results(bill_of_materials)
  true
end

#generate_electrical_plan_pdf(suggested_skus = [], preview = false, version: 'v2') ⇒ Object

Generate electrical plan pdf.

Parameters:

  • suggested_skus (Object) (defaults to: [])
  • preview (Object) (defaults to: false)
  • version (Object) (defaults to: 'v2')

Returns:

  • (Object)


2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
# File 'app/models/room_configuration.rb', line 2654

def generate_electrical_plan_pdf(suggested_skus = [], preview = false, version: 'v2')
  return nil if is_countertop? || line_items.empty?

  uploads.electrical_plan_pdfs.destroy_all unless preview

  klass = version == 'v1' ? Pdf::Document::ElectricalPlanLegacy : Pdf::Document::ElectricalPlan
  pdf_data = klass.new(suggested_skus, self).call

  return pdf_data if preview.to_b

  save_path = File.join(Rails.application.config.x.temp_storage_path.to_s, electrical_file_name)
  File.binwrite(save_path, pdf_data)

  upload = Upload.uploadify(save_path, 'electrical_plan_pdf', self, electrical_file_name)
  uploads << upload
  upload
end

#generate_individual_install_plan_pdf(plan, version: 'v2') ⇒ Object

Generate individual install plan pdf.

Parameters:

  • plan (Object)
  • version (Object) (defaults to: 'v2')

Returns:

  • (Object)


2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
# File 'app/models/room_configuration.rb', line 2559

def generate_individual_install_plan_pdf(plan, version: 'v2')
  klass = version == 'v1' ? Pdf::Document::RoomConfigurationLegacy : Pdf::Document::RoomConfiguration
  pdf_data = klass.call(self, plan).pdf

  file_name = "installation_plan_#{id}-#{plan.id}.pdf"
  Rails.root.join('tmp', file_name).binwrite(pdf_data)

  pdf_data
rescue StandardError => e
  Rails.logger.error "[RoomConfiguration#generate_individual_install_plan_pdf] " \
                     "Failed for RC #{id}, Upload #{plan.id}: #{e.class} - #{e.message}"
  Appsignal.send_error(e)
  nil
end

#generate_installation_plan_pdf(version: 'v2') ⇒ Object

Generate installation plan pdf.

Parameters:

  • version (Object) (defaults to: 'v2')

Returns:

  • (Object)


2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
# File 'app/models/room_configuration.rb', line 2532

def generate_installation_plan_pdf(version: 'v2')
  return unless uploads.installation_plan_images.present? || uploads.installation_heated_areas.present?

  pdf = PdfCombinator.new
  uploads.installation_plan_images.each do |plan|
    pdf_data = generate_individual_install_plan_pdf(plan, version: version)
    pdf << pdf_data if pdf_data
  end
  uploads.installation_heated_areas.each do |plan|
    pdf_data = generate_individual_install_plan_pdf(plan, version: version)
    pdf << pdf_data if pdf_data
  end
  return if pdf.pages.empty?

  pdf.save
  old_upload_ids = uploads.installation_plan_pdfs.ids
  upload = Upload.uploadify_from_data(file_name:, data: pdf.to_pdf, category: 'installation_plan_pdf', resource: self)
  uploads << upload
  Upload.destroy_by(id: old_upload_ids) if old_upload_ids.present?
  upload
end

#generate_multizone_plan_pdf(version: 'v2') ⇒ Object

Generate multizone plan pdf.

Parameters:

  • version (Object) (defaults to: 'v2')

Returns:

  • (Object)


2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
# File 'app/models/room_configuration.rb', line 2598

def generate_multizone_plan_pdf(version: 'v2')
  return if uploads.multizone_plans.blank?

  pdf = PdfCombinator.new
  uploads.multizone_plans.each do |plan|
    pdf_data = generate_individual_install_plan_pdf(plan, version: version)
    pdf << pdf_data if pdf_data
  end
  return if pdf.pages.empty?

  pdf.save
  old_upload_ids = uploads.multizone_plan_pdfs.ids
  upload = Upload.uploadify_from_data(file_name:, data: pdf.to_pdf, category: 'multizone_plan_pdf', resource: self)
  uploads << upload
  Upload.destroy_by(id: old_upload_ids) if old_upload_ids.present?
  upload
end

#get_bom_elements_from_line_itemsObject

Get bom elements from line items.

Returns:

  • (Object)


2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
# File 'app/models/room_configuration.rb', line 2130

def get_bom_elements_from_line_items
  bom_elements = []
  heating_elements.each do |he|
    cat_item = he.catalog_item
    helem = cat_item.item
    bom_helement = {
      'sku' => helem.sku,
      'family_name' => (begin
        helem.product_lines.first.name
      rescue StandardError
        ''
      end),
      'width' => helem.width.to_f,
      'length' => helem['length'].to_f, # don't ask me why
      'area' => helem.width.to_f * helem['length'].to_f / 144.0, # sq ft
      'watts' => helem.watts.to_f,
      'volts' => helem.voltage.to_f,
      'is_dual_voltage' => helem.is_dual_voltage?,
      'amps' => helem.amps.to_f,
      'description' => helem.name,
      'price' => cat_item.bom_price.to_f,
      'class' => helem.class.to_s,
      'third_party_part_number' => cat_item.third_party_part_number,
      'third_party_sku' => cat_item.third_party_sku,
      'qty' => he.quantity,
      'cat_id' => cat_item.id
    }
    bom_elements << bom_helement
  end
  { 'elements' => bom_elements }
end

#get_file(plan, resource, category, files_transferred) ⇒ Object

Get file.

Parameters:

  • plan (Object)
  • resource (Object)
  • category (Object)
  • files_transferred (Object)

Returns:

  • (Object)


1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
# File 'app/models/room_configuration.rb', line 1315

def get_file(plan, resource, category, files_transferred)
  file = plan.get_file
  unless file.nil?
    upload = Upload.new(resource:, attachment: file, category:, created_at: plan.date_uploaded)
    if upload.save
      plan.new_upload_id = upload.id
      files_transferred << upload
    end
  end
  plan.transfer_attempted = true
  plan.save
  files_transferred
end

#get_heat_loss(inside_temperature, outside_temperature) ⇒ Object

Get heat loss.

Parameters:

  • inside_temperature (Object)
  • outside_temperature (Object)

Returns:

  • (Object)


2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
# File 'app/models/room_configuration.rb', line 2773

def get_heat_loss(inside_temperature, outside_temperature)
  fireplace_factor = INFILTRATION_FIREPLACE_FACTORS[sealing_factor.to_sym]
  hl_area = [ceiling.area, under_floor.area].max
  area_factor = nil
  INFILTRATION_AREA_FACTORS[:area_thresholds].each_with_index do |a, i|
    area_factor = INFILTRATION_AREA_FACTORS[sealing_factor.to_sym][i] if hl_area >= a
  end
  # puts "get_heat_loss, outside_temperature, area_factor, fireplace_factor: #{outside_temperature}, #{area_factor}, #{fireplace_factor}"
  # puts "get_heat_loss, hl_area, number_fireplaces, desired_temperature: #{hl_area}, #{number_fireplaces}, #{desired_temperature}"
  # puts "get_heat_loss, ceiling.height: #{ceiling.height}"
  (area_factor + (number_fireplaces * fireplace_factor)) * hl_area * ceiling.height * 0.0167 * 1.1 * (inside_temperature - outside_temperature)
end

#get_heat_suppliedObject

Get heat supplied.

Returns:

  • (Object)


2789
2790
2791
# File 'app/models/room_configuration.rb', line 2789

def get_heat_supplied
  calculate_total_watts * BTU_PER_HOUR_PER_WATT
end

#get_heating_system_typeObject

Get heating system type.

Returns:

  • (Object)


1123
1124
1125
# File 'app/models/room_configuration.rb', line 1123

def get_heating_system_type
  heating_system_product_line || line_items.primary_heating_system_product_line || quoted_heating_system_pl
end

#get_name_with_configObject

Get name with config.

Returns:

  • (Object)


1572
1573
1574
1575
1576
# File 'app/models/room_configuration.rb', line 1572

def get_name_with_config
  new_name = name&.squish.to_s
  new_name += " - #{config_name}"
  new_name
end

#get_operating_costsObject

Get operating costs.

Returns:

  • (Object)


2399
2400
2401
2402
# File 'app/models/room_configuration.rb', line 2399

def get_operating_costs
  occ = RoomConfiguration::OperatingCostsCalculator.new(self)
  occ.get_operating_costs
end

#get_or_generate_electrical_plan_pdf(version: 'v2', force: false) ⇒ Object

Get or generate electrical plan pdf.

Parameters:

  • version (Object) (defaults to: 'v2')
  • force (Object) (defaults to: false)

Returns:

  • (Object)


2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
# File 'app/models/room_configuration.rb', line 2633

def get_or_generate_electrical_plan_pdf(version: 'v2', force: false)
  return generate_electrical_plan_pdf([], false, version: version) if version == 'v1' || force

  plan = uploads.electrical_plan_pdfs.first
  return generate_electrical_plan_pdf([], false, version: version) unless plan&.attachment

  # Verify the file still exists in storage - regenerate if missing
  plan.attachment.data
  plan
rescue Dragonfly::Job::Fetch::NotFound
  Rails.logger.warn "Electrical plan PDF missing from storage for room #{reference_number}, regenerating..."
  plan.destroy
  generate_electrical_plan_pdf([], false, version: version)
end

#get_or_generate_installation_plan_pdf(version: 'v2', force: false) ⇒ Object

+version+ — 'v2' (default, current/modern generator) or 'v1' (LEGACY generator).
v1 always regenerates (cache is keyed by category, not by style) so we never
serve a v2-style cached PDF when v1 was requested.
+force+ — skip the cache and rebuild from current data (e.g. after a
plan-template code change). Off by default; the cached upload is served.

Parameters:

  • version (Object) (defaults to: 'v2')
  • force (Object) (defaults to: false)


2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
# File 'app/models/room_configuration.rb', line 2513

def get_or_generate_installation_plan_pdf(version: 'v2', force: false)
  return generate_installation_plan_pdf(version: version) if version == 'v1' || force

  plan = uploads.installation_plan_pdfs.first
  return generate_installation_plan_pdf(version: version) unless plan&.attachment

  # Verify the file still exists in storage - regenerate if missing
  plan.attachment.data
  plan
rescue Dragonfly::Job::Fetch::NotFound
  Rails.logger.warn "Installation plan PDF missing from storage for room #{reference_number}, regenerating..."
  plan.destroy
  generate_installation_plan_pdf(version: version)
end

#get_or_generate_multizone_plan_pdf(version: 'v2', force: false) ⇒ Object

Get or generate multizone plan pdf.

Parameters:

  • version (Object) (defaults to: 'v2')
  • force (Object) (defaults to: false)

Returns:

  • (Object)


2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
# File 'app/models/room_configuration.rb', line 2579

def get_or_generate_multizone_plan_pdf(version: 'v2', force: false)
  return generate_multizone_plan_pdf(version: version) if version == 'v1' || force

  plan = uploads.multizone_plan_pdfs.first
  return generate_multizone_plan_pdf(version: version) unless plan&.attachment

  # Verify the file still exists in storage - regenerate if missing
  plan.attachment.data
  plan
rescue Dragonfly::Job::Fetch::NotFound
  Rails.logger.warn "Multizone plan PDF missing from storage for room #{reference_number}, regenerating..."
  plan.destroy
  generate_multizone_plan_pdf(version: version)
end

Recommended accessories for this room's heating elements, or an
empty array when not applicable.

Parameters:

Options Hash (options):

Returns:

  • (Array)


2438
2439
2440
2441
2442
2443
2444
2445
2446
# File 'app/models/room_configuration.rb', line 2438

def get_recommended_materials(options = {})
  recommended_accessories = []
  # does the room have heating elements?
  if line_items.present? && line_items.heating_elements.present? && controlled_by.blank? && get_heating_system_type.present?
    # yes, so test if recommended accessories are all present
    recommended_accessories = find_recommended_accessories(options)
  end
  recommended_accessories
end

#get_selected_floor_warming_line_itemsObject

Get selected floor warming line items.

Returns:

  • (Object)


1786
1787
1788
1789
1790
1791
1792
1793
# File 'app/models/room_configuration.rb', line 1786

def get_selected_floor_warming_line_items
  l_items = get_selected_line_items
  selected_floor_warming_line_items = []
  l_items.each do |li|
    selected_floor_warming_line_items << li if li.is_floor_heating_product?
  end
  selected_floor_warming_line_items
end

#get_selected_line_itemsObject

Get selected line items.

Returns:

  • (Object)


1779
1780
1781
# File 'app/models/room_configuration.rb', line 1779

def get_selected_line_items
  line_items.where('is_checked = true')
end

#get_weighted_average_heat_loss(hl_comfort, hl_economy) ⇒ Object

Get weighted average heat loss.

Parameters:

  • hl_comfort (Object)
  • hl_economy (Object)

Returns:

  • (Object)


3007
3008
3009
3010
3011
# File 'app/models/room_configuration.rb', line 3007

def get_weighted_average_heat_loss(hl_comfort, hl_economy)
  # use weighted average by calculating heat loss times hours at comfort vs economy then divide by total hours in a given month
  res = RoomConfigurationConstants::AVERAGE_HOURS
  ((hl_comfort * res[:avg_num_comfort_hrs_per_month]) + (hl_economy * res[:avg_num_economy_hrs_per_month])) / res[:avg_num_hrs_per_month]
end

#grouped_cable_spacing_options_for_selectObject

Grouped cable spacing options for select.

Returns:

  • (Object)


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
# File 'app/models/room_configuration.rb', line 1003

def grouped_cable_spacing_options_for_select
  # here we want to return a single cable spacing for non TZ cable systems
  grouped_options = { heating_system_product_line.name.to_s => [["#{HeatingElementProductLineOption::CABLE_SPACING_BY_HEATING_SYSTEM_LIST[heating_system_type_name_with_tz_cable_spacing]}'' Spacing with #{begin
    heating_system_product_line.name
  rescue StandardError
    'n/a'
  end}", 3.0]] }
  if cable_spacing.present? && heating_system_type_name.to_s.index('TempZone') && heating_system_type_name.to_s.index('Cable')
    # Here we return either fixing strip spacings, or the membrane spacings
    if RoomConfiguration.is_membrane_cable_spacing?(cable_spacing)
      included_blurb = ''
      if RoomConfiguration.is_prodeso_cable_spacing?(cable_spacing)
        included_blurb = '(not included)'
        included_blurb = '(included)' if has_prodeso?
      end
      grouped_options = {
        'Install with Prodeso Membrane' => PUBLIC_PRODESO_CABLE_SPACINGS.map { |cs| ["#{cs}'' for Prodeso Membrane #{included_blurb}", cs] },
        'Install with DITRA-HEAT Membrane' => PUBLIC_DITRA_CABLE_SPACINGS.map { |cs| ["#{cs}'' for DITRA-HEAT Membrane (sold separately)", cs] }
      }
    else
      included_blurb = '(not included)'
      included_blurb = '(included)' if has_strips?
      grouped_options = {
        'Install with Fixing Strips' => PUBLIC_TZ_CABLE_SPACINGS.map { |cs| ["#{cs}'' for Fixing Strips #{included_blurb}", cs] }
      }
    end
  end
  grouped_options
end

#guess_under_floor_type_idObject

Guess under floor type id.

Returns:

  • (Object)


3016
3017
3018
3019
3020
3021
3022
3023
3024
# File 'app/models/room_configuration.rb', line 3016

def guess_under_floor_type_id
  res = nil
  if sub_floor_type && sub_floor_type.name.downcase.index('concrete')
    res = UnderFloorType.where(UnderFloorType[:name].matches('%concrete%')).limit(1).first.id
  elsif room_type && room_type.name.downcase.index('basement')
    res = UnderFloorType.where(UnderFloorType[:name].matches('%basement floor%')).limit(1).first.id
  end
  res
end

#has_heat_loss_report_pdf?Object

Whether has heat loss report pdf.

Returns:

  • (Object)


1523
1524
1525
# File 'app/models/room_configuration.rb', line 1523

def has_heat_loss_report_pdf?
  uploads.exists?(category: 'heat_loss_report')
end

#has_installation_plan_cad?Object

Whether has installation plan cad.

Returns:

  • (Object)


1565
1566
1567
# File 'app/models/room_configuration.rb', line 1565

def has_installation_plan_cad?
  installation_plan_cad.present?
end

#has_installation_plan_image?Object

Whether has installation plan image.

Returns:

  • (Object)


1558
1559
1560
# File 'app/models/room_configuration.rb', line 1558

def has_installation_plan_image?
  installation_plan_image.present?
end

#has_installation_plans?Object

Whether has installation plans.

Returns:

  • (Object)


1551
1552
1553
# File 'app/models/room_configuration.rb', line 1551

def has_installation_plans?
  has_installation_plan_image? && has_installation_plan_cad?
end

#has_items_that_require_cable_spacing?Object

Whether has items that require cable spacing.

Returns:

  • (Object)


989
990
991
# File 'app/models/room_configuration.rb', line 989

def has_items_that_require_cable_spacing?
  line_items.any_require_cable_spacing?
end

#has_notes?Object

Whether has notes.

Returns:

  • (Object)


1201
1202
1203
# File 'app/models/room_configuration.rb', line 1201

def has_notes?
  new_note.present? || activities.notes_only.exists?
end

#has_plans?Object

Whether has plans.

Returns:

  • (Object)


2619
2620
2621
2622
2623
2624
2625
2626
# File 'app/models/room_configuration.rb', line 2619

def has_plans?
  # Optimize to avoid N+1: use a single query when uploads aren't preloaded
  if uploads.loaded?
    uploads.any? { |u| %w[installation_plan_image multizone_plan electrical_plan_pdf].include?(u.category) }
  else
    uploads.exists?(category: %w[installation_plan_image multizone_plan electrical_plan_pdf])
  end
end

#has_prodeso?Object

Whether has prodeso.

Returns:

  • (Object)


1036
1037
1038
# File 'app/models/room_configuration.rb', line 1036

def has_prodeso?
  line_items.membranes.any?
end

#has_room_layout_file?Object

Whether has room layout file.

Returns:

  • (Object)


1544
1545
1546
# File 'app/models/room_configuration.rb', line 1544

def has_room_layout_file?
  room_layout_image.present?
end

#has_strips?Object

Whether has strips.

Returns:

  • (Object)


1043
1044
1045
# File 'app/models/room_configuration.rb', line 1043

def has_strips?
  line_items.accessories.any? { |li| ItemConstants::TZ_CABLE_STRIP_SKUS.include?(li.sku) }
end

#has_zone_information?Object

Whether has zone information.

Returns:

  • (Object)


1208
1209
1210
# File 'app/models/room_configuration.rb', line 1208

def has_zone_information?
  zones.exists? || (room_length.present? && room_width.present?)
end

#heat_loss_childrenObject

Heat loss children.

Returns:

  • (Object)


2715
2716
2717
2718
# File 'app/models/room_configuration.rb', line 2715

def heat_loss_children
  # room must have an under_floor and ceiling to proceed
  [under_floor || build_under_floor, ceiling || build_ceiling] + exterior_walls
end

#heat_loss_nameObject

Heat loss name.

Returns:

  • (Object)


2723
2724
2725
# File 'app/models/room_configuration.rb', line 2723

def heat_loss_name
  'Misc. Infiltration'
end

#heat_loss_report_pdfObject

Heat loss report pdf.

Returns:

  • (Object)


1516
1517
1518
# File 'app/models/room_configuration.rb', line 1516

def heat_loss_report_pdf
  uploads.where(category: 'heat_loss_report').order(:created_at).first
end

#heating_elementsObject

Alias for Line_items#heating_elements

Returns:

  • (Object)

    Line_items#heating_elements

See Also:



481
# File 'app/models/room_configuration.rb', line 481

delegate :heating_elements, :others, to: :line_items

#heating_system_product_lineProductLine

where product line is marked as a heating_element_system, optional: true



371
# File 'app/models/room_configuration.rb', line 371

belongs_to  :heating_system_product_line, class_name: 'ProductLine', optional: true

#heating_system_type_nameObject

Heating system type name.

Returns:

  • (Object)


1130
1131
1132
# File 'app/models/room_configuration.rb', line 1130

def heating_system_type_name
  get_heating_system_type&.heating_system_type_name
end

#heating_system_type_name_includes?(product) ⇒ Object

Whether heating system type name includes.

Parameters:

  • product (Object)

Returns:

  • (Object)


1147
1148
1149
# File 'app/models/room_configuration.rb', line 1147

def heating_system_type_name_includes?(product)
  heating_system_type_name.present? && heating_system_type_name.to_s.index(product)
end

#heating_system_type_name_with_tz_cable_spacingObject

Heating system type name with tz cable spacing.

Returns:

  • (Object)


1137
1138
1139
1140
1141
# File 'app/models/room_configuration.rb', line 1137

def heating_system_type_name_with_tz_cable_spacing
  res = heating_system_type_name.to_s
  res = "#{heating_system_type_name} #{cable_spacing}" if cable_spacing.present? && heating_system_type_name.to_s.index('TempZone') && heating_system_type_name.to_s.index('Cable')
  res
end

#heplo_summary(pretty = nil) ⇒ Object

Heplo summary.

Parameters:

  • pretty (Object) (defaults to: nil)

Returns:

  • (Object)


1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
# File 'app/models/room_configuration.rb', line 1641

def heplo_summary(pretty = nil)
  return unless heating_system_type_name

  res = heating_system_type_name
  if cable_spacing
    res += " @#{cable_spacing}''" unless pretty
    res += " at #{cable_spacing}'' cable spacing #{HeatingElementProductLineOption::CABLE_SPACING_EXTRA_COPY[cable_spacing.to_s]}" if pretty
  end
  res
end

#ideal_cable_spacingObject

Ideal cable spacing.

Returns:

  • (Object)


996
997
998
# File 'app/models/room_configuration.rb', line 996

def ideal_cable_spacing
  HeatingElementProductLineOption.get_field_array_from_options(:ideal_cable_spacing, sub_floor_type_id:, floor_type_id:, product_line_id: heating_system_product_line_id).first
end

#in_design_or_later?Object

Whether in design or later.

Returns:

  • (Object)


1304
1305
1306
# File 'app/models/room_configuration.rb', line 1304

def in_design_or_later?
  in_design? || awaiting_quality_control? || complete? || cancelled?
end

#installation_addressAddress

Legacy Installation Record data, migrated onto Warranty v2
(doc/tasks/202608051319_INSTALLATION_RECORDS_TO_WARRANTIES.md). Deprecated
to prove them dead in traffic before the columns drop.

Returns:

See Also:



358
# File 'app/models/room_configuration.rb', line 358

belongs_to  :installation_address, class_name: 'Address', optional: true, deprecated: true

#installation_country_isoObject

Alias for Opportunity#installation_country_iso

Returns:

  • (Object)

    Opportunity#installation_country_iso

See Also:



486
# File 'app/models/room_configuration.rb', line 486

delegate :installation_postal_code, :installation_state_code, :installation_country_iso, :installation_country_iso3, to: :opportunity

#installation_country_iso3Object

Alias for Opportunity#installation_country_iso3

Returns:

  • (Object)

    Opportunity#installation_country_iso3

See Also:



486
# File 'app/models/room_configuration.rb', line 486

delegate :installation_postal_code, :installation_state_code, :installation_country_iso, :installation_country_iso3, to: :opportunity

#installation_heated_area_sqftObject

this could be different than the installation sq.ft when we deal with snow melt/slab heat mats



1757
1758
1759
1760
1761
1762
1763
# File 'app/models/room_configuration.rb', line 1757

def installation_heated_area_sqft
  (begin
    line_items.without_children.heating_elements.sum { |li| li.catalog_item.item.calculate_heating_sqft(cable_spacing: installation_plan_cable_spacing || cable_spacing, heating_area: true) * li.quantity }.ceil
  rescue StandardError
    0
  end)
end

#installation_plan_and_bom_attached?Object

Whether installation plan and bom attached.

Returns:

  • (Object)


885
886
887
888
889
# File 'app/models/room_configuration.rb', line 885

def installation_plan_and_bom_attached?
  (installation_plans_attached? || installation_plan_not_required) &&
    installation_plan_voltage_id.present? &&
    (require_cable_spacing? ? installation_plan_cable_spacing.present? : true)
end

#installation_plan_blocking_reasonsObject

Returns human-readable reasons why design_complete / complete cannot fire.
Empty array means the room is ready to advance.



893
894
895
896
897
898
899
900
901
902
903
# File 'app/models/room_configuration.rb', line 893

def installation_plan_blocking_reasons
  return [] unless in_design?
  return [] if installation_plan_and_bom_attached?

  reasons = []
  reasons << 'Installation plan CAD file and image must be attached' unless installation_plan_not_required || installation_plans_attached?
  reasons << 'Installation plan voltage must be selected' if installation_plan_voltage_id.blank?
  reasons << 'Installation plan cable spacing must be set' if require_cable_spacing? && installation_plan_cable_spacing.blank?
  reasons << "Installation plan cable spacing (#{installation_plan_cable_spacing}\") does not match the room cable spacing (#{cable_spacing}\")" if cable_spacing_mismatch?
  reasons
end

#installation_plan_cadObject

Installation plan cad.

Returns:

  • (Object)


1492
1493
1494
# File 'app/models/room_configuration.rb', line 1492

def installation_plan_cad
  uploads.installation_plan_cads.sorted_by_position.first
end

#installation_plan_cad=(attributes = {}) ⇒ Object

Installation plan cad.

Parameters:

  • attributes (Object) (defaults to: {})

Returns:

  • (Object)


1500
1501
1502
1503
1504
# File 'app/models/room_configuration.rb', line 1500

def installation_plan_cad=(attributes = {})
  return if attributes['attachment'].blank?

  uploads.build(attributes.merge(category: 'installation_plan_cad'))
end

#installation_plan_imageObject

Installation plan image.

Returns:

  • (Object)


1475
1476
1477
# File 'app/models/room_configuration.rb', line 1475

def installation_plan_image
  uploads.installation_plan_images.sorted_by_position.first
end

#installation_plan_image=(attributes = {}) ⇒ Object

Installation plan image.

Parameters:

  • attributes (Object) (defaults to: {})

Returns:

  • (Object)


1483
1484
1485
1486
1487
# File 'app/models/room_configuration.rb', line 1483

def installation_plan_image=(attributes = {})
  return if attributes['attachment'].blank?

  uploads.build(attributes.merge(category: 'installation_plan_image'))
end

#installation_plan_pdfObject

Installation plan pdf.

Returns:

  • (Object)


1509
1510
1511
# File 'app/models/room_configuration.rb', line 1509

def installation_plan_pdf
  uploads.installation_plan_pdfs.sorted_by_position.first
end

#installation_plan_qc_employeeEmployee?

The associated installation plan qc employee.

Returns:



351
# File 'app/models/room_configuration.rb', line 351

belongs_to  :installation_plan_qc_employee, class_name: 'Employee', optional: true

#installation_plan_reference_numberObject

Installation plan reference number.

Returns:

  • (Object)


1945
1946
1947
1948
1949
# File 'app/models/room_configuration.rb', line 1945

def installation_plan_reference_number
  installation_plan_cad.reference_number
rescue StandardError
  nil
end

#installation_plan_voltageVoltage?

The associated installation plan voltage.

Returns:



336
# File 'app/models/room_configuration.rb', line 336

belongs_to  :installation_plan_voltage, class_name: 'Voltage', optional: true

#installation_plans_attached?Object

Whether installation plans attached.

Returns:

  • (Object)


908
909
910
# File 'app/models/room_configuration.rb', line 908

def installation_plans_attached?
  uploads.installation_plan_cads.any? && uploads.installation_plan_images.any?
end

#installation_postal_codeObject

Alias for Opportunity#installation_postal_code

Returns:

  • (Object)

    Opportunity#installation_postal_code

See Also:



486
# File 'app/models/room_configuration.rb', line 486

delegate :installation_postal_code, :installation_state_code, :installation_country_iso, :installation_country_iso3, to: :opportunity

#installation_sqftObject

Installation sqft.

Returns:

  • (Object)


1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
# File 'app/models/room_configuration.rb', line 1743

def installation_sqft
  (begin
    # Use in-memory filter to avoid extra queries when associations are eager loaded.
    effective_cable_spacing = installation_plan_cable_spacing || cable_spacing
    line_items
      .to_a
      .select { |li| (li.children_count.nil? || li.children_count.zero?) && li.item&.is_heating_element? }.sum { |li| li.item.calculate_sqft(cable_spacing: effective_cable_spacing) * li.quantity }
      .ceil
  rescue StandardError
    0
  end)
end

#installation_state_codeObject

Alias for Opportunity#installation_state_code

Returns:

  • (Object)

    Opportunity#installation_state_code

See Also:



486
# File 'app/models/room_configuration.rb', line 486

delegate :installation_postal_code, :installation_state_code, :installation_country_iso, :installation_country_iso3, to: :opportunity

#installed_itemsActiveRecord::Associations::CollectionProxy

The associated installed items.

Returns:

  • (ActiveRecord::Associations::CollectionProxy)


403
# File 'app/models/room_configuration.rb', line 403

has_many    :installed_items, deprecated: true

#installed_product_linesActiveRecord::Associations::CollectionProxy

The associated installed product lines.

Returns:

  • (ActiveRecord::Associations::CollectionProxy)


435
# File 'app/models/room_configuration.rb', line 435

has_and_belongs_to_many :installed_product_lines, class_name: 'ProductLine', join_table: :installed_product_lines, association_foreign_key: :product_line_id, deprecated: true

#installerCustomer?

The associated installer.

Returns:



364
# File 'app/models/room_configuration.rb', line 364

belongs_to  :installer, class_name: 'Customer', optional: true, deprecated: true

#invalidate_design_guidelinesObject

Invalidate design guidelines.

Returns:

  • (Object)


1263
1264
1265
1266
1267
# File 'app/models/room_configuration.rb', line 1263

def invalidate_design_guidelines
  uploads.where(category: 'design_guideline').find_each do |ip|
    ip.update(category: "invalid_#{ip.category}")
  end
end

#is_cable_with_options?(options = {}) ⇒ Boolean

Whether this configuration is a cable system matching the given
installation type and cable spacing.

Parameters:

  • options (Hash) (defaults to: {})

Options Hash (options):

  • installation_type (String)

    one of 'membrane',
    'strips', 'snow_melt', 'slab_heat'

  • cable_spacing (Numeric)

    cable spacing to compare
    against

Returns:

  • (Boolean)


1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
# File 'app/models/room_configuration.rb', line 1063

def is_cable_with_options?(options = {})
  res = false
  if cable_spacing.present? && heating_system_type_name.to_s.index('TempZone') && heating_system_type_name.to_s.index('Cable')
    installation_type = if RoomConfiguration.is_membrane_cable_spacing?(cable_spacing)
                          'membrane'
                        else
                          'strips'
                        end
    res = cable_spacing == options[:cable_spacing] if options[:installation_type] == installation_type
  elsif cable_spacing.present? && (heating_system_type_name.to_s.index('Snow') || heating_system_type_name.to_s.index('Slab')) && heating_system_type_name.to_s.index('Cable')
    res = cable_spacing == options[:cable_spacing] if %w[snow_melt slab_heat].include?(options[:installation_type])
  end
  res
end

#is_countertop?Object

Whether is countertop.

Returns:

  • (Object)


2683
2684
2685
# File 'app/models/room_configuration.rb', line 2683

def is_countertop?
  heating_system_type_name_includes?('Countertop') || (room_type.environment == 'Countertop')
end

#is_in_a_completed_quote?Object

Whether is in a completed quote.

Returns:

  • (Object)


1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
# File 'app/models/room_configuration.rb', line 1954

def is_in_a_completed_quote?
  res = false
  quotes.each do |q|
    if q.state == 'complete'
      res = true
      break
    end
  end
  res
end

#is_snow_or_ice_melting?Object

Whether is snow or ice melting.

Returns:

  • (Object)


1113
1114
1115
1116
1117
1118
# File 'app/models/room_configuration.rb', line 1113

def is_snow_or_ice_melting?
  heating_system_product_line&.self_and_ancestors&.any? do |pl|
    slt = pl.slug_ltree.to_s
    slt.include?('snow_melting') || slt.start_with?('roof_and_gutter_deicing')
  end
end

#labeler_employeeEmployee?

The associated labeler employee.

Returns:



348
# File 'app/models/room_configuration.rb', line 348

belongs_to  :labeler_employee, class_name: 'Employee', optional: true

#main_repObject

Main rep.

Returns:

  • (Object)


1287
1288
1289
# File 'app/models/room_configuration.rb', line 1287

def main_rep
  opportunity&.primary_sales_rep
end

#material_alertsActiveRecord::Associations::CollectionProxy

The associated material alerts.

Returns:

  • (ActiveRecord::Associations::CollectionProxy)


400
# File 'app/models/room_configuration.rb', line 400

has_many    :material_alerts, dependent: :destroy

#max_ambient_temperatureObject

Max ambient temperature.

Returns:

  • (Object)


2704
2705
2706
2707
2708
2709
2710
# File 'app/models/room_configuration.rb', line 2704

def max_ambient_temperature
  if floor_type&.is_temperature_limited?
    RoomConfigurationConstants::MAX_AMBIENT_TEMP_LIMITED
  else
    RoomConfigurationConstants::MAX_AMBIENT_TEMP_DEFAULT
  end
end

#meta_descriptionObject

Meta description.

Returns:

  • (Object)


1398
# File 'app/models/room_configuration.rb', line 1398

def meta_description; end

#meta_titleObject

Meta title.

Returns:

  • (Object)


1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
# File 'app/models/room_configuration.rb', line 1380

def meta_title
  n = opportunity.name.titleize.singularize.squish
  n << " #{name}."
  size = ''
  if installation_sqft.positive?
    size = "#{installation_sqft} sq.ft."
  elsif insulation_surface.present?
    size = "#{insulation_surface} sq.ft."
  end
  n << " #{size}" if size.present?
  n << " #{room_type.name.titleize.squish}" unless n.downcase.include?(room_type.name.downcase)
  n << " with #{heplo_summary}"
  n
end

#name_with_customer_opp_and_roomObject

Name with customer opp and room.

Returns:

  • (Object)


1346
1347
1348
# File 'app/models/room_configuration.rb', line 1346

def name_with_customer_opp_and_room
  "#{opportunity.try(:customer).try(:full_name)} - #{name_with_room_and_opp}"
end

#name_with_dimensionsObject

Name with dimensions.

Returns:

  • (Object)


1581
1582
1583
1584
1585
1586
# File 'app/models/room_configuration.rb', line 1581

def name_with_dimensions
  new_name = "#{name&.squish} - Plan: #{reference_number}"
  new_name += "(#{dimensions_label})" unless dimensions_label.nil?
  new_name += " - Spacing = #{cable_spacing}\" o.c" unless cable_spacing.nil?
  new_name
end

#name_with_roomObject

Name with room.

Returns:

  • (Object)


1332
1333
1334
# File 'app/models/room_configuration.rb', line 1332

def name_with_room
  "#{name.to_s.strip} (#{reference_number})"
end

#name_with_room_and_oppObject

Name with room and opp.

Returns:

  • (Object)


1339
1340
1341
# File 'app/models/room_configuration.rb', line 1339

def name_with_room_and_opp
  "#{opportunity.try(:name)} - #{name_with_room}"
end

#notify_rep_of_room_completionObject

Notify rep of room completion.

Returns:

  • (Object)


2374
2375
2376
# File 'app/models/room_configuration.rb', line 2374

def notify_rep_of_room_completion
  TrainingMailer.same_day_design_complete(self).deliver_later
end

#ok_to_customer_cancel?Object

Whether ok to customer cancel.

Returns:

  • (Object)


1187
1188
1189
# File 'app/models/room_configuration.rb', line 1187

def ok_to_customer_cancel?
  CUSTOMER_CANCELABLE_STATES.include?(state.to_sym) && quotes.active.empty? && orders.not_cancelled.empty?
end

#ok_to_delete?Object

Whether ok to delete.

Returns:

  • (Object)


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

def ok_to_delete?
  draft? && quotes.empty? && orders.empty?
end

#operating_cost_in_cents_per_dayObject

Operating cost in cents per day.

Returns:

  • (Object)


2407
2408
2409
2410
2411
2412
2413
2414
2415
# File 'app/models/room_configuration.rb', line 2407

def operating_cost_in_cents_per_day
  # Fallback calculation: hours * kilowatts * cents_per_kwh
  hours = RoomConfigurationConstants::ASSUMED_OPERATING_HOURS_PER_DAY
  rate_cents = ElectricityRate::US_AVERAGE_RATE * RoomConfigurationConstants::CENTS_PER_DOLLAR
  res = (hours * calculate_total_kilowatts * rate_cents).round
  oc = get_operating_costs
  res = (RoomConfigurationConstants::CENTS_PER_DOLLAR * oc[:operating_cost_by_coldest_month] / RoomConfigurationConstants::SIMPLE_DAYS_PER_MONTH).round if oc[:status] == 'ok'
  res
end

#opportunityOpportunity?

The associated opportunity.

Returns:



354
# File 'app/models/room_configuration.rb', line 354

belongs_to  :opportunity, inverse_of: :room_configurations

#orderObject

Order.

Returns:

  • (Object)


1410
1411
1412
1413
1414
# File 'app/models/room_configuration.rb', line 1410

def order
  return nil if line_items.empty?

  line_items.where.not(order_id: nil).first&.order
end

#orderable?Object

Whether orderable.

Returns:

  • (Object)


1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
# File 'app/models/room_configuration.rb', line 1215

def orderable?
  # If someone on IQ wants to order a room without installation plan why not?
  # room_can_be_ordered = true
  # if line_items.goods.empty?
  #   # room can't be ordered unless it has goods line items
  #   room_can_be_ordered = false
  # elsif web_room? and room_layout_attached? and !installation_plans_attached?
  #   # otherwise if it's a web room and has a room layout, it should also have an installation plan
  #   room_can_be_ordered = false
  # end
  # return room_can_be_ordered
  line_items.goods.present? || line_items.services.present?
end

#ordersActiveRecord::Associations::CollectionProxy

The associated orders.

Returns:

  • (ActiveRecord::Associations::CollectionProxy)


429
# File 'app/models/room_configuration.rb', line 429

has_and_belongs_to_many :orders

#other_revisions_and_configurationsObject

Other revisions and configurations.

Returns:

  • (Object)


2048
2049
2050
# File 'app/models/room_configuration.rb', line 2048

def other_revisions_and_configurations
  opportunity.room_configurations.to_a - [self]
end

#othersObject

Alias for Line_items#others

Returns:

  • (Object)

    Line_items#others

See Also:



481
# File 'app/models/room_configuration.rb', line 481

delegate :heating_elements, :others, to: :line_items

#party_for_plan_request_confirmationObject

Party for plan request confirmation.

Returns:

  • (Object)


2286
2287
2288
2289
2290
# File 'app/models/room_configuration.rb', line 2286

def party_for_plan_request_confirmation
  p = opportunity.contact unless opportunity.contact&.inactive?
  p ||= opportunity.customer
  p
end

#plan_thumbnailObject

Plan thumbnail.

Returns:

  • (Object)


802
803
804
805
806
807
808
# File 'app/models/room_configuration.rb', line 802

def plan_thumbnail
  if (upload = floor_plan && upload && upload.attachment.present? && upload.is_image? && (img_url = upload.image_url('92x61>')))
    return { path: img_url, id: upload.id }
  end

  { path: nil, id: nil }
end

#pole_assignments_possible?Object

Whether pole assignments possible.

Returns:

  • (Object)


1050
1051
1052
# File 'app/models/room_configuration.rb', line 1050

def pole_assignments_possible?
  line_items.heating_elements.present? && line_items.relay_panels.present?
end

#populate_line_items_from_bom_results(bom_results) ⇒ Object

Populates line items from HeatingSystemCalculator BOM results

Parameters:

  • bom_results (Hash)

    The bill of materials hash from HeatingSystemCalculator



2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
# File 'app/models/room_configuration.rb', line 2345

def populate_line_items_from_bom_results(bom_results)
  # Add heating elements
  (bom_results['elements'] || []).each do |element|
    add_bom_line_item(element)
  end

  # Add controls (thermostats, relays, etc.)
  (bom_results['controls'] || []).each do |control|
    add_bom_line_item(control)
  end

  # Add accessories
  (bom_results['accessories'] || []).each do |accessory|
    add_bom_line_item(accessory)
  end

  save
end

#pretty_coverage_state_descriptionObject

Pretty coverage state description.

Returns:

  • (Object)


1633
1634
1635
# File 'app/models/room_configuration.rb', line 1633

def pretty_coverage_state_description
  coverage_state.to_s.humanize.titleize
end

#primary_partyObject

Primary party.

Returns:

  • (Object)


933
934
935
# File 'app/models/room_configuration.rb', line 933

def primary_party
  opportunity.try(:primary_party)
end

#priority_urgency_factorObject

Priority urgency factor.

Returns:

  • (Object)


1280
1281
1282
# File 'app/models/room_configuration.rb', line 1280

def priority_urgency_factor
  PRIORITIES.index(priority)
end

#promotion_discount(msrp = false) ⇒ Object

Promotion discount.

Parameters:

  • msrp (Object) (defaults to: false)

Returns:

  • (Object)


1814
1815
1816
1817
1818
1819
# File 'app/models/room_configuration.rb', line 1814

def promotion_discount(msrp = false)
  discount = BigDecimal(0)
  promo = promotion_that_applies
  discount = promo.apply_to(subtotal_floor_warming(msrp) - ev2_volume_discount(msrp), party.store.id) if promo
  discount
end

#promotion_that_applies(msrp = false) ⇒ Object

Promotion that applies.

Parameters:

  • msrp (Object) (defaults to: false)

Returns:

  • (Object)


1825
1826
1827
# File 'app/models/room_configuration.rb', line 1825

def promotion_that_applies(msrp = false)
  Promotion.that_applies(subtotal_floor_warming(msrp) - ev2_volume_discount(msrp), party.store.id)
end

#public_pathObject

Public path.

Returns:

  • (Object)


2062
2063
2064
# File 'app/models/room_configuration.rb', line 2062

def public_path
  UrlHelper.instance.my_room_path(self)
end

#quoted_heating_system_plProductLine

where product line is marked as a heating_element_system, optional: true



372
# File 'app/models/room_configuration.rb', line 372

belongs_to  :quoted_heating_system_pl, class_name: 'ProductLine', optional: true

#quotesActiveRecord::Associations::CollectionProxy

The associated quotes.

Returns:

  • (ActiveRecord::Associations::CollectionProxy)


426
# File 'app/models/room_configuration.rb', line 426

has_and_belongs_to_many :quotes

#ready_for_engineering?Object

Whether ready for engineering.

Returns:

  • (Object)


827
828
829
# File 'app/models/room_configuration.rb', line 827

def ready_for_engineering?
  check_room_layout_or_note_attached
end

#rectangular_areaObject

Rectangular area.

Returns:

  • (Object)


2212
2213
2214
2215
2216
# File 'app/models/room_configuration.rb', line 2212

def rectangular_area
  (RubyUnits::Unit.new(room_width) * RubyUnits::Unit.new(room_length)).to_s('%0.2f')
rescue StandardError
  nil
end

#refresh_plans(delay: nil) ⇒ Object

Refresh plans.

Parameters:

  • delay (Object) (defaults to: nil)

Returns:

  • (Object)


2486
2487
2488
2489
2490
2491
2492
# File 'app/models/room_configuration.rb', line 2486

def refresh_plans(delay: nil)
  if delay&.positive?
    GenerateRoomPlansWorker.perform_in(delay, id)
  else
    GenerateRoomPlansWorker.perform_async(id)
  end
end

#release_orders_awaiting_completed_installation_plansObject

Release orders awaiting completed installation plans.

Returns:

  • (Object)


968
969
970
# File 'app/models/room_configuration.rb', line 968

def release_orders_awaiting_completed_installation_plans
  reload.orders.awaiting_completed_installation_plans.each(&:release_order)
end

#remove_line_item(line_item, quantity = 1) ⇒ Object

Remove line item.

Parameters:

  • line_item (Object)
  • quantity (Object) (defaults to: 1)

Returns:

  • (Object)


1702
1703
1704
1705
1706
1707
1708
1709
1710
# File 'app/models/room_configuration.rb', line 1702

def remove_line_item(line_item, quantity = 1)
  if line_item.quantity > quantity
    line_item.quantity -= quantity
    line_item.save
  else
    line_items.delete(line_item)
    line_item.destroy
  end
end

#require_cable_spacing?Object

Whether require cable spacing.

Returns:

  • (Object)


982
983
984
# File 'app/models/room_configuration.rb', line 982

def require_cable_spacing?
  HeatingElementProductLineOption.get_field_array_from_options(:require_cable_spacing, sub_floor_type_id:, floor_type_id:, product_line_id: heating_system_product_line_id).first
end

#require_expansion_joint?Object

Whether require expansion joint.

Returns:

  • (Object)


975
976
977
# File 'app/models/room_configuration.rb', line 975

def require_expansion_joint?
  HeatingElementProductLineOption.get_field_array_from_options(:require_expansion_joint, sub_floor_type_id:, floor_type_id:, room_type_id:).first
end

#reset_last_heat_lossObject

Reset last heat loss.

Returns:

  • (Object)


2995
2996
2997
2998
2999
3000
# File 'app/models/room_configuration.rb', line 2995

def reset_last_heat_loss
  logger.info "reset_last_heat_loss: desired_temperature_changed?: #{desired_temperature_changed?}"
  logger.info "reset_last_heat_loss: sealing_factor_changed?: #{sealing_factor_changed?}"
  logger.info "reset_last_heat_loss: number_fireplaces_changed?: #{number_fireplaces_changed?}"
  self.last_heat_loss = nil
end

#retail_outletCustomer?

The associated retail outlet.

Returns:



370
# File 'app/models/room_configuration.rb', line 370

belongs_to  :retail_outlet, class_name: 'Customer', optional: true, deprecated: true

#reusable_layoutsObject

Reusable layouts.

Returns:

  • (Object)


813
814
815
816
817
818
819
820
821
822
# File 'app/models/room_configuration.rb', line 813

def reusable_layouts
  return Upload.none unless opportunity_id

  layouts = []
  layouts += Upload.room_layouts.joins("INNER JOIN room_configurations ON room_configurations.id = uploads.resource_id AND uploads.resource_type = 'RoomConfiguration'").where(
    'room_configurations.opportunity_id = ? AND room_configurations.id <> ?', opportunity_id, id.to_i
  )
  layouts += Upload.room_layouts.where("resource_type = 'Opportunity' and resource_id = ?", opportunity_id)
  layouts.sort_by(&:created_at).reverse
end

#revisionObject

Revision.

Returns:

  • (Object)


1624
1625
1626
1627
1628
# File 'app/models/room_configuration.rb', line 1624

def revision
  reference_number.split('-')[1]
rescue StandardError
  nil
end

#room_configurations_for_controlObject

Room configurations for control.

Returns:

  • (Object)


1154
1155
1156
1157
1158
# File 'app/models/room_configuration.rb', line 1154

def room_configurations_for_control
  return RoomConfiguration.none if controlled_rooms.present?

  opportunity.room_configurations.where.not(id: id.to_i).where(controlled_by_id: nil).order(:name, :reference_number)
end

#room_configurations_for_control_selectObject

Room configurations for control select.

Returns:

  • (Object)


1163
1164
1165
# File 'app/models/room_configuration.rb', line 1163

def room_configurations_for_control_select
  room_configurations_for_control.pluck(:name, :reference_number, :id).map { |(r0, r1, r2)| ["#{r0} (#{r1})", r2] }
end

#room_layout_attached?Boolean

This validation is called on new object, hence you must parse the array instead of using activerecord

Returns:

  • (Boolean)


920
921
922
# File 'app/models/room_configuration.rb', line 920

def room_layout_attached?
  uploads.find { |u| u.category == 'room_layout' } || room_plan_attached?
end

#room_layout_imageObject

Room layout image.

Returns:

  • (Object)


1441
1442
1443
# File 'app/models/room_configuration.rb', line 1441

def room_layout_image
  room_layout_images.first
end

#room_layout_image=(attributes = {}) ⇒ Object

Room layout image.

Parameters:

  • attributes (Object) (defaults to: {})

Returns:

  • (Object)


1449
1450
1451
1452
1453
# File 'app/models/room_configuration.rb', line 1449

def room_layout_image=(attributes = {})
  return if attributes['attachment'].blank?

  uploads.build(attributes.merge(category: 'room_layout'))
end

#room_layout_imagesActiveRecord::Associations::CollectionProxy

The associated room layout images.

Returns:

  • (ActiveRecord::Associations::CollectionProxy)


388
# File 'app/models/room_configuration.rb', line 388

has_many    :room_layout_images, -> { room_layouts.most_recent_first }, as: :resource, class_name: 'Upload'

#room_layout_or_note_attached?Object

Whether room layout or note attached.

Returns:

  • (Object)


1194
1195
1196
# File 'app/models/room_configuration.rb', line 1194

def room_layout_or_note_attached?
  room_layout_attached? || has_notes? || has_zone_information?
end

#room_length_rawObject

Room length raw.

Returns:

  • (Object)


2190
2191
2192
2193
2194
# File 'app/models/room_configuration.rb', line 2190

def room_length_raw
  RubyUnits::Unit.new(room_length)
rescue StandardError
  ''
end

#room_length_raw=(measurement) ⇒ Object

Room length raw.

Parameters:

  • measurement (Object)

Returns:

  • (Object)


2183
2184
2185
# File 'app/models/room_configuration.rb', line 2183

def room_length_raw=(measurement)
  self.room_length = measurement
end

#room_planRoomPlan?

The associated room plan.

Returns:



375
# File 'app/models/room_configuration.rb', line 375

belongs_to  :room_plan, inverse_of: :room_configurations, optional: true

#room_plan_attached?Object

Whether room plan attached.

Returns:

  • (Object)


915
916
917
# File 'app/models/room_configuration.rb', line 915

def room_plan_attached?
  room_plan.present? && room_plan.is_designable?
end

#room_plan_editing_locked?Object

Whether room plan editing locked.

Returns:

  • (Object)


954
955
956
# File 'app/models/room_configuration.rb', line 954

def room_plan_editing_locked?
  in_design? || awaiting_quality_control? || complete? || cancelled? || orders.room_not_pickable.exists?
end

#room_prefixObject

Room prefix.

Returns:

  • (Object)


2040
2041
2042
2043
# File 'app/models/room_configuration.rb', line 2040

def room_prefix
  # Legacy IQ rooms keep 'IQR' prefix for reference number consistency, new rooms use 'IP'
  reception_type == 'IQ' ? 'IQR' : 'IP'
end

#room_typeRoomType?

The associated room type.

Returns:



330
# File 'app/models/room_configuration.rb', line 330

belongs_to  :room_type, optional: true

#room_width_rawObject

Room width raw.

Returns:

  • (Object)


2173
2174
2175
2176
2177
# File 'app/models/room_configuration.rb', line 2173

def room_width_raw
  RubyUnits::Unit.new(room_width)
rescue StandardError
  ''
end

#room_width_raw=(measurement) ⇒ Object

Room width raw.

Parameters:

  • measurement (Object)

Returns:

  • (Object)


2166
2167
2168
# File 'app/models/room_configuration.rb', line 2166

def room_width_raw=(measurement)
  self.room_width = measurement
end

#select_default_line_itemsObject

Select default line items.

Returns:

  • (Object)


1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
# File 'app/models/room_configuration.rb', line 1419

def select_default_line_items
  line_items.each do |line_item|
    line_item.update(is_checked: true)
  end
  return unless thermostats.length > 1

  thermostats.each do |t|
    t.update(is_checked: false)
  end
  thermostats.first.update(is_checked: true)
end

#send_online_plan_request_confirmationObject

Send online plan request confirmation.

Returns:

  • (Object)


2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
# File 'app/models/room_configuration.rb', line 2260

def send_online_plan_request_confirmation
  if email_for_plan_request_confirmation
    sender = customer.primary_sales_rep
    co = CommunicationBuilder.new(
      resource: self,
      sender_party: sender,
      sender: (sender.nil? ? INFO_EMAIL : nil),
      recipient_party: party_for_plan_request_confirmation,
      emails: email_for_plan_request_confirmation,
      recipient_name: party_for_plan_request_confirmation.name,
      template_system_code: 'PLAN_REQUEST_CONF',
      bcc: sender&.email
    ).create
    if co.draft?
      { status_code: :error, status_message: co.errors_to_s }
    else
      { status_code: :ok, status_message: "Online Plan Request confirmation e-mail sent to #{customer.name} #{email_for_plan_request_confirmation}." }
    end
  else
    { status_code: :error, status_message: "Can't send: contact/customer e-mail is blank!" }
  end
end

#send_same_day_design_alertObject

Send same day design alert.

Returns:

  • (Object)


2302
2303
2304
2305
# File 'app/models/room_configuration.rb', line 2302

def send_same_day_design_alert
  TrainingMailer.same_day_design_request(self).deliver_later
  update(wyi_alert_sent: true)
end

#send_to_engineering_and_create_crm_quoteObject

Send to engineering and create crm quote.

Returns:

  • (Object)


2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
# File 'app/models/room_configuration.rb', line 2241

def send_to_engineering_and_create_crm_quote
  res = false
  send_to_engineering
  unless (q = quotes.open_quotes.last)
    q = Quote.auto_quote_from_room(self, state_event: 'reset_to_pending', force_single_origin: true, do_not_set_shipping_address: true)
  end
  q.ready_for_design unless q.awaiting_completed_installation_plans?
  res = true if in_design? && quotes.open_quotes.any?(&:awaiting_completed_installation_plans?) && valid? && q.valid?
  # send a confirmation email and account invite if they don't already have one
  if res
    send_online_plan_request_confirmation
    party_for_plan_request_confirmation.(email_for_plan_request_confirmation, ignore_if_exist: true) unless party_for_plan_request_confirmation..present? && email_for_plan_request_confirmation.present?
  end
  res
end

#set_currencyObject

Set currency.

Returns:

  • (Object)


2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
# File 'app/models/room_configuration.rb', line 2009

def set_currency
  return if currency.present? # Skip if already set to avoid loading opportunity

  cur = begin
    customer.store.currency
  rescue StandardError
    nil
  end
  self.currency = cur if cur
end

#set_quoted_heating_system_product_lineObject

Set quoted heating system product line.

Returns:

  • (Object)


3029
3030
3031
3032
3033
3034
3035
# File 'app/models/room_configuration.rb', line 3029

def set_quoted_heating_system_product_line
  li_hs_pl = line_items.primary_heating_system_product_line
  self.quoted_heating_system_pl_id = li_hs_pl.id if li_hs_pl.present?
  hs_pl = HeatingElementProductLineOption.get_product_line_for_room(self)
  self.quoted_heating_system_pl_id ||= hs_pl.id if hs_pl.present?
  save
end

#set_reference_numberObject

Set reference number.

Returns:

  • (Object)


2030
2031
2032
2033
2034
2035
# File 'app/models/room_configuration.rb', line 2030

def set_reference_number
  return if reference_number.present?

  seq = RoomConfiguration.next_sequence_value('installation_plan_reference_numbers_seq')
  self.reference_number = "#{room_prefix}#{seq}"
end

#should_generate_new_friendly_id?Object

Whether should generate new friendly id.

Returns:

  • (Object)


1403
1404
1405
# File 'app/models/room_configuration.rb', line 1403

def should_generate_new_friendly_id?
  slug.nil? || name_changed? || super
end

#show_cable_spacing_message?Object

Whether show cable spacing message.

Returns:

  • (Object)


878
879
880
# File 'app/models/room_configuration.rb', line 878

def show_cable_spacing_message?
  in_design? && require_cable_spacing? && installation_plan_cable_spacing.nil?
end

#show_voltage_message?Object

Whether show voltage message.

Returns:

  • (Object)


871
872
873
# File 'app/models/room_configuration.rb', line 871

def show_voltage_message?
  in_design? && installation_plan_voltage_id.nil?
end

#slug_candidatesObject

Slug candidates.

Returns:

  • (Object)


1655
1656
1657
1658
1659
1660
# File 'app/models/room_configuration.rb', line 1655

def slug_candidates
  [
    [:name_with_config],
    %i[name_with_config id]
  ]
end

#state_description(describe_state = nil) ⇒ Object

State description.

Parameters:

  • describe_state (Object) (defaults to: nil)

Returns:

  • (Object)


779
780
781
# File 'app/models/room_configuration.rb', line 779

def state_description(describe_state = nil)
  RoomConfigurationConstants::STATE_DESCRIPTION[describe_state || state]
end

#statusObject

Status.

Returns:

  • (Object)


1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
# File 'app/models/room_configuration.rb', line 1931

def status
  case state
  when 'new'
    'new'
  when 'complete'
    'complete'
  else
    'in_design'
  end
end

#sub_floor_typeSubFloorType?

The associated sub floor type.

Returns:



327
# File 'app/models/room_configuration.rb', line 327

belongs_to  :sub_floor_type, optional: true

#sub_floor_type_nameObject

Sub floor type name.

Returns:

  • (Object)


1373
1374
1375
# File 'app/models/room_configuration.rb', line 1373

def sub_floor_type_name
  sub_floor_type.try(:name)
end

#support_casesActiveRecord::Associations::CollectionProxy

The associated support cases.

Returns:

  • (ActiveRecord::Associations::CollectionProxy)


432
# File 'app/models/room_configuration.rb', line 432

has_and_belongs_to_many :support_cases

#synchronization_targetsObject

Retrieves valid synchronization targets when a room's line item are changed, which records
get the updated items.



926
927
928
# File 'app/models/room_configuration.rb', line 926

def synchronization_targets
  [quotes.reject(&:editing_locked?), orders.reject(&:editing_locked?)].flatten.compact
end

#system_ownerCustomer?

The associated system owner.

Returns:



361
# File 'app/models/room_configuration.rb', line 361

belongs_to  :system_owner, class_name: 'Customer', optional: true, deprecated: true

#temperature_options_for_selectObject

Temperature options for select.

Returns:

  • (Object)


2391
2392
2393
2394
# File 'app/models/room_configuration.rb', line 2391

def temperature_options_for_select
  temps = *(TEMPERATURE_OPTIONS.first.last.to_i..TEMPERATURE_OPTIONS.last.last.to_i) # get more fine tuned selection here
  temps.select { |t| t < max_ambient_temperature }.map { |t| ["#{t.round}°F / #{PresetJob.fahrenheit_to_celsius(t)}°C", t] }
end

#template_options_for_selectObject

Alias for Class#template_options_for_select

Returns:

  • (Object)

    Class#template_options_for_select

See Also:



635
# File 'app/models/room_configuration.rb', line 635

delegate :template_options_for_select, to: :class

#to_paramObject

This prevents friendly id from automatically using slug when passing object, slug
should be manually set



2123
2124
2125
# File 'app/models/room_configuration.rb', line 2123

def to_param
  id.to_s
end

#to_sObject

To s.

Returns:

  • (Object)


2117
2118
2119
# File 'app/models/room_configuration.rb', line 2117

def to_s
  name_with_room
end

#totalObject

implement total for a room configuration which is just the subtotal



2234
2235
2236
# File 'app/models/room_configuration.rb', line 2234

def total
  subtotal
end

#total_lengthObject

Total length.

Returns:

  • (Object)


1768
1769
1770
1771
1772
1773
1774
# File 'app/models/room_configuration.rb', line 1768

def total_length
  (begin
    line_items.without_children.heating_elements.sum { |li| li.catalog_item.item.length.to_f * li.quantity }
  rescue StandardError
    nil
  end)
end

#tracer_solutionsObject

Tracer solutions.

Returns:

  • (Object)


1537
1538
1539
# File 'app/models/room_configuration.rb', line 1537

def tracer_solutions
  uploads.tracer_solutions.sorted_by_position.first
end

#transmittable?Object

Whether transmittable.

Returns:

  • (Object)


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

def transmittable?
  !state.in?(%w[cancelled in_design awaiting_quality_control])
end

#transmittable_and_settled?Object

Whether transmittable and settled.

Returns:

  • (Object)


761
762
763
764
765
766
# File 'app/models/room_configuration.rb', line 761

def transmittable_and_settled?
  transmittable? && (updated_at <= 2.minutes.ago) && # prevent multiple changes from causing multiple pre-packs, assume a transmittable room that hasn't chamged, nor its line items, in ~2 min
    line_items.goods.parents_only.all? do |li|
      li.updated_at <= 2.minutes.ago
    end
end

#tz_installation_sqftObject

Tz installation sqft.

Returns:

  • (Object)


1732
1733
1734
1735
1736
1737
1738
# File 'app/models/room_configuration.rb', line 1732

def tz_installation_sqft
  (begin
    line_items.without_children.tempzone_only.sum { |li| li.catalog_item.item.calculate_sqft(cable_spacing:) * li.quantity }.ceil
  rescue StandardError
    0
  end)
end

#under_floorUnderFloor?

The associated under floor.

Returns:



419
# File 'app/models/room_configuration.rb', line 419

has_one    :under_floor, dependent: :destroy

Unlink vignette installation plan image.

Returns:

  • (Object)


3068
3069
3070
# File 'app/models/room_configuration.rb', line 3068

def unlink_vignette_installation_plan_image
  update(vignette: false)
end

#update_quote_statusObject

Update quote status.

Returns:

  • (Object)


961
962
963
# File 'app/models/room_configuration.rb', line 961

def update_quote_status
  reload.quotes.open_quotes.each { |quote| quote.adjust_status_based_on_room_status(self) }
end

#uploadsActiveRecord::Associations::CollectionProxy

The associated uploads.

Returns:

  • (ActiveRecord::Associations::CollectionProxy)


385
# File 'app/models/room_configuration.rb', line 385

has_many    :uploads, -> { order(position: :asc, created_at: :desc) }, as: :resource, dependent: :destroy

#valid_cable_spacingObject

Valid cable spacing.

Returns:

  • (Object)


1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
# File 'app/models/room_configuration.rb', line 1889

def valid_cable_spacing
  # puts "valid_cable_spacing: require_cable_spacing?: #{require_cable_spacing?}"
  return unless require_cable_spacing?

  if !draft? && !in_design? && !awaiting_quality_control?
    min = RoomConfigurationConstants::CABLE_SPACING_MIN_INCHES
    max = RoomConfigurationConstants::CABLE_SPACING_MAX_INCHES
    errors.add(:cable_spacing, "must be within #{min} and #{max} inches for #{heating_system_type_name}.") if (cable_spacing.to_f < min) || (cable_spacing.to_f > max)
  end
  return unless installation_plan_cable_spacing.nil? && updating_installation_plans == true

  errors.add(:installation_cable_spacing, "must be present for #{heating_system_type_name}.")
end

#valid_expansion_joint_spacingObject

Valid expansion joint spacing.

Returns:

  • (Object)


1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
# File 'app/models/room_configuration.rb', line 1906

def valid_expansion_joint_spacing
  # puts "valid_expansion_joint_spacing: require_expansion_joint?: #{require_expansion_joint?}"
  # puts "valid_expansion_joint_spacing: must be within #{min} and #{max} feet for #{floor_type.name}"
  # puts "valid_expansion_joint_spacing: expansion_joint_spacing.to_f: #{expansion_joint_spacing.to_f}"
  if require_expansion_joint? && (skip_expansion_joint_validation != true) && ((expansion_joint_spacing.to_f < RoomConfigurationConstants::MIN_CONCRETE_EXPANSION_JOINT_SPACING) || (expansion_joint_spacing.to_f > RoomConfigurationConstants::MAX_CONCRETE_EXPANSION_JOINT_SPACING))
    errors.add(:expansion_joint_spacing, "must be within #{RoomConfigurationConstants::MIN_CONCRETE_EXPANSION_JOINT_SPACING} and #{RoomConfigurationConstants::MAX_CONCRETE_EXPANSION_JOINT_SPACING} feet for #{floor_type.name}.")
    return false
  end
  true
end

#valid_insulation_surfaceObject

Valid insulation surface.

Returns:

  • (Object)


1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
# File 'app/models/room_configuration.rb', line 1875

def valid_insulation_surface
  # Legacy Flash Design Tool rooms store the total room footprint in square_footage
  # (copied from the Instant Quote), not the heated area. The comparison is only
  # meaningful for DesignTool2 and zone-based rooms where square_footage is the
  # actual heated area. Skip for any room that carries a Flash DT floorplan_xml.
  return if floorplan_xml.present?
  return unless room_type&.is_indoor? && insulation_surface.present? && square_footage.present? && (square_footage > insulation_surface)

  errors.add(:insulation_surface, "must be greater than heated area square footage of #{square_footage.round(1)}.")
end

#versions_for_audit_trail(_params = {}) ⇒ Object

Versions for audit trail.

Parameters:

  • _params (Object) (defaults to: {})

Returns:

  • (Object)


787
788
789
790
791
792
793
794
795
796
797
# File 'app/models/room_configuration.rb', line 787

def versions_for_audit_trail(_params = {})
  query_sql = %q{
                (item_type = 'RoomConfiguration' AND item_id = :id)
                OR (
                  item_type = 'LineItem'
                    AND reference_data @> '{"resource_type": "RoomConfiguration"}'
                    AND reference_data @> :resource_id_json
                )
              }
  RecordVersion.where(query_sql, id:, resource_id_json: { resource_id: id }.to_json)
end

#vignette_valid_only_for_completed_rooms_with_plansObject

Vignette valid only for completed rooms with plans.

Returns:

  • (Object)


1920
1921
1922
1923
1924
1925
1926
# File 'app/models/room_configuration.rb', line 1920

def vignette_valid_only_for_completed_rooms_with_plans
  vignette_ok = false
  vignette_ok = true if complete? && has_installation_plans?
  return unless vignette && !vignette_ok

  errors.add(:vignette, 'can only be set for completed rooms with plans.')
end

#visitVisit?

The associated visit.

Returns:



378
# File 'app/models/room_configuration.rb', line 378

belongs_to  :visit, optional: true

#voltageVoltage?

The associated voltage.

Returns:



333
# File 'app/models/room_configuration.rb', line 333

belongs_to  :voltage, optional: true

#voltage_mismatch?Object

Whether voltage mismatch.

Returns:

  • (Object)


855
856
857
858
859
# File 'app/models/room_configuration.rb', line 855

def voltage_mismatch?
  return false if installation_plan_voltage_id.nil?

  effective_voltage(true) != installation_plan_voltage_id
end

#warranty_cardObject

Warranty card.

Returns:

  • (Object)


1458
1459
1460
# File 'app/models/room_configuration.rb', line 1458

def warranty_card
  uploads.warranty_cards.first
end

#warranty_card=(attributes = {}) ⇒ Object

Warranty card.

Parameters:

  • attributes (Object) (defaults to: {})

Returns:

  • (Object)


1466
1467
1468
1469
1470
# File 'app/models/room_configuration.rb', line 1466

def warranty_card=(attributes = {})
  return if attributes['attachment'].blank?

  uploads.build(attributes.merge(category: 'warranty_card'))
end

#web_room?Object

Whether web room.

Returns:

  • (Object)


940
941
942
# File 'app/models/room_configuration.rb', line 940

def web_room?
  (creator.is_a?(Customer) || creator.nil?) && ((parent && root.web_room?) || parent.nil?) && %w[DesignTool2 DesignTool QuoteBuilder Upload MeasureComp IQ].include?(reception_type) # quote rooms have a slightly different more rep driven workflow
end

#zonesActiveRecord::Associations::CollectionProxy

The associated zones.

Returns:

  • (ActiveRecord::Associations::CollectionProxy)


394
# File 'app/models/room_configuration.rb', line 394

has_many    :zones, dependent: :destroy