Class: Quote

Overview

== Schema Information

Table name: quotes
Database name: primary

id :integer not null, primary key
ancestors_count :integer
bill_shipping_to_customer :boolean
children_count :integer
complete_datetime :datetime
currency :string(255)
descendants_count :integer
disable_auto_coupon :boolean default(FALSE), not null
discount :decimal(10, 2)
expiration_date :date
expiration_notice_sent :boolean default(FALSE), not null
first_view_date :datetime
hidden :boolean default(FALSE), not null
hold_for_transmission :boolean default(FALSE), not null
jde_b_ab :integer
jde_s_ab :integer
line_offset :decimal(10, 2)
line_total :decimal(10, 2)
line_total_discounted :decimal(10, 2)
ltl_freight :boolean
ltl_freight_guaranteed :boolean
max_discount_override :decimal(4, 2)
min_profit_markup :integer default(0)
override_coupon_date :date
override_coupon_date_without_limits :boolean default(FALSE), not null
override_line_lock :boolean default(FALSE), not null
position :integer default(100)
pricing_program_description :string(255)
pricing_program_discount :integer
priority :string(255)
quote_type :string(2)
recalculate_discounts :boolean default(FALSE), not null
recalculate_shipping :boolean default(TRUE), not null
reference_number :string(255) not null
requires_upgrade :boolean
retrieving_shipping_costs :boolean
saturday_delivery :boolean default(FALSE), not null
shipping_cost :decimal(10, 2)
shipping_coupon :decimal(10, 2)
shipping_issue_alerted :boolean
shipping_method :string(255)
ships_economy :boolean default(FALSE), not null
signature_confirmation :boolean default(FALSE), not null
single_origin :boolean default(FALSE), not null
state :string(255)
suffix :string(255)
tax_exempt :boolean default(FALSE), not null
tax_offset :decimal(10, 2)
tax_total :decimal(10, 2)
taxable_total :decimal(12, 2)
total :decimal(10, 2)
uploads_count :integer default(0)
created_at :datetime
updated_at :datetime
billing_address_id :integer
buying_group_id :integer
creator_id :integer
legacy_proto_iq_room_id :integer
opportunity_id :integer
parent_id :integer
resource_tax_rate_id :integer
rma_id :integer
shipping_address_id :integer
updater_id :integer
visit_id :bigint

Indexes

idx_quotes_reference_number_trgm_gist (reference_number) USING gist
idx_quotes_rma_id (rma_id)
index_quotes_on_legacy_proto_iq_room_id (legacy_proto_iq_room_id)
index_quotes_on_parent_id (parent_id)
index_quotes_on_reference_number (reference_number) UNIQUE
index_quotes_on_suffix (suffix) USING gin
index_quotes_on_visit_id (visit_id) WHERE (visit_id IS NOT NULL) USING hash
quotes_aasm_state_index (state)
quotes_opportunity_id_index (opportunity_id)
quotes_shipping_address_id_idx (shipping_address_id)

Foreign Keys

fk_rails_... (shipping_address_id => addresses.id)
fk_rails_... (visit_id => visits.id) ON DELETE => nullify

Defined Under Namespace

Classes: AssignLargeOppActivity, CombinedPdfGenerator, ConvertToOrder, Copier, LetterPdfGenerator, Mover, QuoteCompletedHandler, Reviser

Constant Summary collapse

SALES_QUOTE =
'SQ'
MARKETING_QUOTE =
'MQ'
TECH_QUOTE =
'TQ'
INSTANT_QUOTE =
'IQ'
QUOTE_TYPES =
%w[SQ MQ TQ IQ].freeze
REFERENCE_NUMBER_PATTERN =
Regexp.new("^(#{QUOTE_TYPES.join('|')})(\\d+)(-R\\d+)?$", Regexp::IGNORECASE)
CORK_MIN_QTY =
1
MIN_INSTALL_SQ_FT =
88
CUSTOMER_CANCELABLE_STATES =
%i[pending].freeze
CANCELABLE_STATES =
CUSTOMER_CANCELABLE_STATES + %i[pending_project_details awaiting_completed_installation_plans awaiting_transmission]
CLOSED_STATES =
%i[complete cancelled].freeze
CAN_REQUEST_PRE_PACK_STATES =
%i[pending pre_pack awaiting_transmission profit_review].freeze

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Instance Attribute Summary collapse

Attributes included from Models::Profitable

#min_profit_markup

Attributes included from Models::Itemizable

#force_total_reset, #total_reset

Delegated Instance Attributes collapse

Belongs to collapse

Methods included from Models::Auditable

#creator, #updater

Methods included from Models::ShipQuotable

#shipping_address

Methods included from Models::TaxableResource

#resource_tax_rate

Methods included from Models::Itemizable

#account_specialist

Has many collapse

Methods included from Models::ShipQuotable

#deliveries, #shipments

Methods included from Models::Pickable

#line_discounts, #line_items

Methods included from Models::Itemizable

#coupons, #discounts

Has and belongs to many collapse

Methods included from Models::MultiRoom

#room_configurations

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Models::CouponDateOverridable

#override_coupon_date_limit

Methods included from Models::LegacyRateRequest

#last_shipping_rate_request_result, #last_shipping_rate_request_result=

Methods included from Models::Auditable

#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record

Methods included from Models::Profitable

#default_sales_markup, #profit_margins_met?, #profitable_line_items, #profitable_status, #profitable_total_discounted, #profitable_total_estimated_cost, #profitable_total_estimated_line_cost, #profitable_total_profit, #profitable_total_profit_margin, #profitable_total_profit_markup, #validate_min_profit_markup?

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

#add_line_items_to_all_rooms, #add_lines_for_room_configuration, #all_rooms_complete?, #all_rooms_complete_or_cancelled?, #all_rooms_complete_or_cancelled_or_draft?, #all_rooms_in_design?, #all_rooms_ppd?, #any_room_ppd?, #any_rooms_in_design?, #get_operating_costs, #get_recommended_materials, #heated_sq_ft, #prioritize_room, #remove_line_items_from_all_rooms, #remove_lines_for_room_configuration, #replace_line_items_in_all_rooms, #suggested_items, #suggested_services, #synchronization_targets

Methods included from Models::ShipQuotable

#carrier, #chosen_shipping_method, #days_commitment, #determine_origin_address, #everything_in_stock?, #friendly_shipping_method, #is_drop_ship?, #is_override?, #is_warehouse_pickup?, #line_items_grouped_by_deliveries_quoting, #line_items_match_deliveries_if_any, #need_to_pre_pack_reasons, #need_to_recalculate_shipping, #one_time_shipping_address, #one_time_shipping_address=, #qualifies_for_cod?, #refresh_deliveries_quoting, #reset_deliveries_shipping_option, #reset_shipping, #retrieve_friendly_shipping_method, #retrieve_shipping_costs, #ship_quoted, #ship_weight, #shipping_non_db_default_but_db_customer?, #shipping_signature_confirmation_non_db_default_but_db_customer?, #shipping_via_wy_but_has_shipping_account?, #ships_economy_package?, #ships_freight?, #ships_freight_but_address_not_freight_ready?, #should_ship_freight?, #should_ship_freight_but_address_not_freight_ready?, #validate_deliveries

Methods included from Models::ShipMeasurable

#cartons_total, #crates_total, #pallets_total, #ship_freight_class_from_shipments, #ship_volume_from_shipments, #ship_volume_from_shipments_in_cubic_feet, #ship_weight_from_shipments, #shipment_set, #shipments_for_measure

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, #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, #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::TaxableResource

#apply_tax_rate_to_line_items, #build_tax_params, #calculate_tax_for_all_lines, #copy_tax_rate, #effective_date, #get_rates_for_line, #get_tax_rate, #manual_rate_goods, #manual_rate_services, #manual_rate_shipping, #origin_address, #refresh_tax_rate, #resource_not_taxable?, #set_initial_tax_rate, #should_refresh_tax_rate?, #state_code, #state_code_sym, #taxes_grouped_by_rate, #taxes_grouped_by_type

Methods included from Models::Itemizable

#add_line_item, #additional_items, #assign_sequence, #billing_entity, #breakdown_of_prices, #calculate_actual_insured_value, #calculate_discounts, #calculate_shipping_cost, #coupon_search, #customer_applied_coupons, #customer_can_apply_coupon?, #discounts_changed?, #discounts_grouped_by_coupon, #discounts_subtotal, #effective_discount, #effective_shipping_discount, #has_kits?, #has_kits_or_serial_numbers?, #has_serial_numbers?, #is_credit_order?, #line_items_requiring_serial_number, #line_items_with_counters, #line_total_plus_tax, #perform_db_total, #purge_empty_quoting_deliveries, #purge_shipping_when_no_other_lines, #remove_line_item, #require_total_reset?, #reset_discount, #set_for_recalc, #set_signature_confirmation_on_shipping_address_change, #set_totals, #shipping_conditions_changed?, #shipping_discounted, #shipping_method_changed?, #should_recalculate_shipping?, #smartinstall_data, #smartsupport_data, #subtotal_cogs, #sync_shipping_line, #total_cogs

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

#publish_event

Instance Attribute Details

#do_not_detect_shippingObject

Returns the value of attribute do_not_detect_shipping.



145
146
147
# File 'app/models/quote.rb', line 145

def do_not_detect_shipping
  @do_not_detect_shipping
end

#do_not_set_totalsObject

Returns the value of attribute do_not_set_totals.



145
146
147
# File 'app/models/quote.rb', line 145

def do_not_set_totals
  @do_not_set_totals
end

#max_discount_overrideObject (readonly)



187
# File 'app/models/quote.rb', line 187

validates :max_discount_override, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100, allow_nil: true }

#opportunity_idObject (readonly)



184
# File 'app/models/quote.rb', line 184

validates :opportunity_id, presence: { if: proc { |q| q.validate_opportunity_and_prepared_for } }

#suffixObject (readonly)



185
# File 'app/models/quote.rb', line 185

validates :suffix, length: { maximum: 150 }

#validate_opportunity_and_prepared_forObject

Returns the value of attribute validate_opportunity_and_prepared_for.



145
146
147
# File 'app/models/quote.rb', line 145

def validate_opportunity_and_prepared_for
  @validate_opportunity_and_prepared_for
end

Class Method Details

.activeActiveRecord::Relation<Quote>

A relation of Quotes that are active. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Quote>)

See Also:



199
# File 'app/models/quote.rb', line 199

scope :active, -> { where.not(state: 'cancelled') }

.auto_quote_from_room(room, options = {}) ⇒ Object



842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
# File 'app/models/quote.rb', line 842

def self.auto_quote_from_room(room, options = {})
  force_single_origin = options[:force_single_origin] || true
  do_not_set_shipping_address = options[:do_not_set_shipping_address] || false
  target_state_event = options[:state_event] || 'complete'

  q = Quote.new(opportunity_id: room.opportunity_id)
  q.shipping_address_id = room.customer.shipping_address_id if !room.installation_postal_code && (do_not_set_shipping_address == false) && room.customer && room.customer.shipping_address.present?
  q.single_origin = true if force_single_origin
  q.room_configuration_ids = [room.id]
  q.do_not_detect_shipping = true

  # First save the quote WITHOUT calculating discounts to get an ID
  # Discounts require itemizable_id to be set, which only exists after the quote is persisted
  q.save

  # Now that the quote has an ID, trigger discount calculation (tier2 pricing, etc.)
  # The quote must be persisted before discounts can be created
  if q.persisted?
    q.recalculate_discounts = true
    q.force_total_reset = true
    q.do_not_detect_shipping = true # Still skip shipping detection
    q.save
  end

  # Now apply the target state event (typically 'complete')
  if q.persisted? && target_state_event.present? && q.respond_to?("#{target_state_event}!")
    q.do_not_set_totals = true # Discounts already calculated, don't recalculate
    q.send("#{target_state_event}!")
  end

  q
end

.by_reference_numberActiveRecord::Relation<Quote>

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

Returns:

  • (ActiveRecord::Relation<Quote>)

See Also:



195
# File 'app/models/quote.rb', line 195

scope :by_reference_number, -> { order(:reference_number).reverse_order }

.completed_quotesActiveRecord::Relation<Quote>

A relation of Quotes that are completed quotes. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Quote>)

See Also:



191
# File 'app/models/quote.rb', line 191

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

.contains_coupon_idsActiveRecord::Relation<Quote>

A relation of Quotes that are contains coupon ids. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Quote>)

See Also:



198
# File 'app/models/quote.rb', line 198

scope :contains_coupon_ids, ->(coupon_ids) { where("EXISTS (select discounts.id from discounts where discounts.coupon_id IN (?) and discounts.itemizable_type = 'Quote' and discounts.itemizable_id = quotes.id)", coupon_ids) }

.contains_item_idsActiveRecord::Relation<Quote>

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

Returns:

  • (ActiveRecord::Relation<Quote>)

See Also:



197
# File 'app/models/quote.rb', line 197

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

.count_by_incomplete_state_by_rep(rep_id = nil, sales_rep_type_id_label = 'primary_sales_rep_id') ⇒ Object



781
782
783
784
785
786
787
# File 'app/models/quote.rb', line 781

def self.count_by_incomplete_state_by_rep(rep_id = nil, sales_rep_type_id_label = 'primary_sales_rep_id')
  if rep_id.nil?
    count(:state, conditions: ["state not in ('complete','cancelled')"], group: :state)
  else
    count(:state, conditions: ["state not in ('complete','cancelled') and " + sales_rep_type_id_label.to_s + ' = ?', rep_id], group: :state)
  end
end

.formatted_resources_query(quotes) ⇒ Object



452
453
454
# File 'app/models/quote.rb', line 452

def self.formatted_resources_query(quotes)
  quotes.select('quotes.reference_number,quotes.id,quotes.created_at,quotes.state,opportunities.name as opportunity_name').joins(:opportunity).order('reference_number desc')
end

.ignore_fixture_attributesObject



444
445
446
# File 'app/models/quote.rb', line 444

def self.ignore_fixture_attributes
  %w[last_rate_request_xml last_rate_response_xml]
end

.in_quotingActiveRecord::Relation<Quote>

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

Returns:

  • (ActiveRecord::Relation<Quote>)

See Also:



201
# File 'app/models/quote.rb', line 201

scope :in_quoting, -> { where(state: %w[awaiting_completed_installation_plans pre_pack awaiting_transmission]) }

.last_quote_update_cache_keyObject



448
449
450
# File 'app/models/quote.rb', line 448

def self.last_quote_update_cache_key
  Quote.select('id,updated_at').order(:updated_at).reverse_order.first.cache_key
end

.last_revisionsActiveRecord::Relation<Quote>

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

Returns:

  • (ActiveRecord::Relation<Quote>)

See Also:



196
# File 'app/models/quote.rb', line 196

scope :last_revisions, -> { where("NOT EXISTS (select id from quotes q2 where q2.parent_id = quotes.id and quotes.state <> 'cancelled')") }

.like_lookupActiveRecord::Relation<Quote>

A relation of Quotes that are like lookup. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Quote>)

See Also:



203
# File 'app/models/quote.rb', line 203

scope :like_lookup, ->(q) { where('quotes.reference_number ILIKE :term', { term: "%#{q}%" }) }

.lookupActiveRecord::Relation<Quote>

A relation of Quotes that are lookup. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Quote>)

See Also:



202
# File 'app/models/quote.rb', line 202

scope :lookup, ->(q) { where('quotes.reference_number ILIKE :term', { term: q }) }

.most_recent_firstActiveRecord::Relation<Quote>

A relation of Quotes that are most recent first. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Quote>)

See Also:



194
# File 'app/models/quote.rb', line 194

scope :most_recent_first, -> { order(:created_at).reverse_order }

.not_convertedActiveRecord::Relation<Quote>

A relation of Quotes that are not converted. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Quote>)

See Also:



200
# File 'app/models/quote.rb', line 200

scope :not_converted, -> { where("not exists(select 1 from orders where quote_id = quotes.id and orders.state = 'invoiced')") }

.open_quotesActiveRecord::Relation<Quote>

A relation of Quotes that are open quotes. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Quote>)

See Also:



190
# File 'app/models/quote.rb', line 190

scope :open_quotes, -> { where.not(state: %w[complete cancelled]) }

.pendingActiveRecord::Relation<Quote>

A relation of Quotes that are pending. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Quote>)

See Also:



192
# File 'app/models/quote.rb', line 192

scope :pending, -> { where(state: 'pending') }

.sales_quotesActiveRecord::Relation<Quote>

A relation of Quotes that are sales quotes. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Quote>)

See Also:



206
# File 'app/models/quote.rb', line 206

scope :sales_quotes, -> { where(quote_type: SALES_QUOTE) }

.states_for_selectObject



440
441
442
# File 'app/models/quote.rb', line 440

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

.with_contact_point_categoryActiveRecord::Relation<Quote>

A relation of Quotes that are with contact point category. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Quote>)

See Also:



204
# File 'app/models/quote.rb', line 204

scope :with_contact_point_category, ->(category) { where('exists(select 1 from contact_points_quotes cq inner join contact_points cp on cp.id = cq.contact_point_id and cq.quote_id = quotes.id where cp.category = ?)', category) }

.with_emailActiveRecord::Relation<Quote>

A relation of Quotes that are with email. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Quote>)

See Also:



205
# File 'app/models/quote.rb', line 205

scope :with_email, -> { with_contact_point_category(ContactPoint::EMAIL) }

.with_line_itemsActiveRecord::Relation<Quote>

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

Returns:

  • (ActiveRecord::Relation<Quote>)

See Also:



193
# File 'app/models/quote.rb', line 193

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

.without_quofusActiveRecord::Relation<Quote>

A relation of Quotes that are without quofus. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Quote>)

See Also:



207
208
209
210
211
212
213
214
215
216
# File 'app/models/quote.rb', line 207

scope :without_quofus, -> {
  where(%{
           not exists(
             select 1 from activities a
             where (
               (a.resource_id = quotes.opportunity_id and a.resource_type = 'Opportunity')
               or (a.resource_id = quotes.id and a.resource_type = 'Quote')
             ) and a.activity_type_id IN (?)
           ) }, ActivityTypeConstants::QUOFUS_IDS)
}

Instance Method Details

#active_ordersActiveRecord::Relation<Order>

Returns:

  • (ActiveRecord::Relation<Order>)

See Also:



162
# File 'app/models/quote.rb', line 162

has_many    :active_orders, -> { where(Order[:state].not_in(%w[cancelled fraudulent])) }, class_name: 'Order'

#activitiesActiveRecord::Relation<Activity>

Returns:

See Also:



164
# File 'app/models/quote.rb', line 164

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

#addendumsObject



582
583
584
585
586
587
588
589
590
591
592
# File 'app/models/quote.rb', line 582

def addendums
  list = []
  current_month = Date.current.strftime('%B-%Y')
  # Use with_all_tags to require BOTH 'for-quote' AND the time-based tag (AND logic)
  publications = Item.publications.with_all_tags('for-quote', current_month.downcase)
                     .or(Item.publications.with_all_tags('for-quote', 'permanent'))
  publications.each do |p|
    list << p.upload
  end
  list.uniq.compact
end

#address_bookObject



936
937
938
# File 'app/models/quote.rb', line 936

def address_book
  [customer.store.warehouse_address] + customer.addresses
end

#adjust_status_based_on_room_status(rc) ⇒ Object



702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
# File 'app/models/quote.rb', line 702

def adjust_status_based_on_room_status(rc)
  Rails.logger.info "Quote Ref and ID #{reference_number}, #{id}, quote.adjust_status_based_on_room_status: self.state: #{state}, rc: #{rc.reference_number}, #{rc.id}, rc state: #{rc.state}, DateTime: #{Time.current}"
  self.do_not_set_totals = true
  if rc.complete? || rc.cancelled?
    Rails.logger.info "Quote Ref and ID #{reference_number}, #{id}, quote.adjust_status_based_on_room_status, BEFORE installation_plans_complete: self.state: #{state}, rc: #{rc.reference_number}, rc state: #{rc.state}"
    res = reload.installation_plans_complete unless complete?
    Rails.logger.info "Quote Ref and ID #{reference_number}, #{id}, quote.adjust_status_based_on_room_status, AFTER installation_plans_complete: self.state: #{state}, rc: #{rc.reference_number}, rc state: #{rc.state}, result of installation_plans_complete: #{res}"
  elsif rc.draft?
    pending_project_details if can_pending_project_details? && awaiting_completed_installation_plans?
  elsif rc.in_design?
    Rails.logger.info "Quote Ref and ID #{reference_number}, #{id}, quote.adjust_status_based_on_room_status, BEFORE ready_for_design: self.state: #{state}, rc: #{rc.reference_number}, rc state: #{rc.state}"
    res = ready_for_design
    Rails.logger.info "Quote Ref and ID #{reference_number}, #{id}, quote.adjust_status_based_on_room_status, AFTER ready_for_design: self.state: #{state}, rc: #{rc.reference_number}, rc state: #{rc.state}, result of ready_for_design: #{res}"
  end
end

#all_activitiesObject

show all activities linked to this quote as well as its parent opportunity



932
933
934
# File 'app/models/quote.rb', line 932

def all_activities
  opportunity.linked_activities
end

#all_orders_sold?Boolean

Returns:

  • (Boolean)


523
524
525
# File 'app/models/quote.rb', line 523

def all_orders_sold?
  orders.any? && orders.all?(&:invoiced?)
end

#all_participantsObject

Alias for Opportunity#all_participants

Returns:

  • (Object)

    Opportunity#all_participants

See Also:



537
# File 'app/models/quote.rb', line 537

delegate :all_participants, to: :opportunity

#all_suggested_itemsObject



547
548
549
550
551
552
553
554
555
556
557
558
# File 'app/models/quote.rb', line 547

def all_suggested_items
  goods = suggested_items
  services = suggested_services
  unique_goods = []
  goods.each do |_rc, lines|
    lines.each do |li|
      unique_goods << li unless unique_goods.any? { |si| si.item == li.item }
    end
  end
  unique_goods_skus = unique_goods.compact.uniq.collect(&:sku)
  { goods:, services:, unique_goods:, unique_goods_skus: }
end

#all_support_casesObject



422
423
424
425
426
# File 'app/models/quote.rb', line 422

def all_support_cases
  support_case_ids
  linked_support_cases.pluck(:id)
  SupportCase.where(id: support_case_ids).order(Arel.sql('support_cases.case_number desc'))
end

#applies_for_smartinstallObject



948
949
950
951
952
953
# File 'app/models/quote.rb', line 948

def applies_for_smartinstall
  return false # added by Roman Aug 19th until service refactor
  return true if installation_is_within_range? && customer.is_homeowner? && has_selected_heated_items?

  false
end

#applies_for_smartsupportObject



955
956
957
958
959
# File 'app/models/quote.rb', line 955

def applies_for_smartsupport
  return true if customer.is_dealer_or_trade_pro_for_locator? && has_selected_heated_items?

  false
end

#apply_tier2_pricing?Boolean

Whether or not to apply the tier2 pricing (customer discount) by default

Returns:

  • (Boolean)


414
415
416
# File 'app/models/quote.rb', line 414

def apply_tier2_pricing?
  is_sales_quote?
end

#available_contact_pointsObject



606
607
608
609
610
611
# File 'app/models/quote.rb', line 606

def available_contact_points
  cps = opportunity.primary_party.contact_points.order(:position).to_a
  cps += opportunity.customer.contact_points.order(:position).to_a
  cps += opportunity.opportunity_participants.map { |op| op.party.contact_points.order(:position).to_a }.flatten
  cps.uniq
end

#available_contact_points_grouped_for_selectObject



613
614
615
616
617
618
# File 'app/models/quote.rb', line 613

def available_contact_points_grouped_for_select
  available_contact_points.each_with_object({}) do |cp, hsh|
    hsh[cp.party.full_name] ||= []
    hsh[cp.party.full_name] << ["#{cp.detail} (#{cp.category})", cp.id]
  end
end

#best_contact_pointObject



594
595
596
597
598
599
600
601
602
603
604
# File 'app/models/quote.rb', line 594

def best_contact_point
  contact_points.transmittable.first || (begin
    contact.contact_points.transmittable.first
  rescue StandardError
    nil
  end) || (begin
    customer.contact_points.transmittable.first
  rescue StandardError
    nil
  end)
end

#billing_addressObject

Alias for Customer#billing_address

Returns:

  • (Object)

    Customer#billing_address

See Also:



152
# File 'app/models/quote.rb', line 152

delegate    :billing_address, :company, to: :customer

#build_activityObject



418
419
420
# File 'app/models/quote.rb', line 418

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

#buying_groupBuyingGroup



157
# File 'app/models/quote.rb', line 157

belongs_to  :buying_group, optional: true

#calculate_expiration_dateObject



665
666
667
668
669
670
671
672
# File 'app/models/quote.rb', line 665

def calculate_expiration_date
  expirations = [] + (begin
    coupons.map(&:expiration_date).compact
  rescue StandardError
    []
  end) + [created_at + 60.days]
  expirations.min.to_date
end

#can_be_moved?Boolean

Returns:

  • (Boolean)


428
429
430
# File 'app/models/quote.rb', line 428

def can_be_moved?
  orders.active.non_carts.blank?
end

#can_be_transmitted?Boolean

Returns:

  • (Boolean)


514
515
516
# File 'app/models/quote.rb', line 514

def can_be_transmitted?
  awaiting_transmission? || pending_project_details? || complete?
end

#can_convert?Object

Alias for Convert_to_order_service#can_convert?

Returns:

  • (Object)

    Convert_to_order_service#can_convert?

See Also:



744
# File 'app/models/quote.rb', line 744

delegate :can_convert?, to: :convert_to_order_service

#cancelable?Boolean

Returns:

  • (Boolean)


773
774
775
# File 'app/models/quote.rb', line 773

def cancelable?
  orders.not_cancelled.empty?
end

#catalogObject

Alias for Customer#catalog

Returns:

  • (Object)

    Customer#catalog

See Also:



151
# File 'app/models/quote.rb', line 151

delegate    :catalog, to: :customer

#check_pre_packObject



1033
1034
1035
# File 'app/models/quote.rb', line 1033

def check_pre_pack
  request_estimated_packaging if awaiting_transmission? && can_request_estimated_packaging? && !pre_pack? && !deliveries.all? { |d| d.pre_pack? } && stop_for_pre_pack?
end

#communicationsActiveRecord::Relation<Communication>

Returns:

See Also:



165
# File 'app/models/quote.rb', line 165

has_many    :communications, as: :resource, dependent: :nullify

#companyObject

Alias for Customer#company

Returns:

  • (Object)

    Customer#company

See Also:



152
# File 'app/models/quote.rb', line 152

delegate    :billing_address, :company, to: :customer

#contactObject

Alias for Opportunity#contact

Returns:

  • (Object)

    Opportunity#contact

See Also:



149
# File 'app/models/quote.rb', line 149

delegate    :customer, :contact, :primary_party, to: :opportunity

#contact_pointsActiveRecord::Relation<ContactPoint>

Returns:

See Also:



170
# File 'app/models/quote.rb', line 170

has_and_belongs_to_many :contact_points, inverse_of: :quotes

#convert_to_order_serviceObject



740
741
742
# File 'app/models/quote.rb', line 740

def convert_to_order_service
  @convert_to_order_service ||= Quote::ConvertToOrder.new(self)
end

#countryObject



1041
1042
1043
1044
1045
# File 'app/models/quote.rb', line 1041

def country
  customer.catalog.store.country
rescue StandardError
  nil
end


736
737
738
# File 'app/models/quote.rb', line 736

def crm_link
  UrlHelper.instance.quote_path(self)
end

#currency_symbolObject



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

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

#customerObject

Alias for Opportunity#customer

Returns:

  • (Object)

    Opportunity#customer

See Also:



149
# File 'app/models/quote.rb', line 149

delegate    :customer, :contact, :primary_party, to: :opportunity

#deep_dupObject



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'app/models/quote.rb', line 114

def deep_dup
  deep_clone(include: :contact_points) do |original, copy|
    next unless copy.is_a?(Quote)

    copy.state = 'pending'
    original.line_items.parents_only.non_shipping.each do |li|
      li_attrs = li.attributes.dup.symbolize_keys
      li_attrs = li_attrs.except(:id, :resource_type, :resource_id,
                                 :delivery_id, :tax_total, :tax_rate,
                                 :tax_type, :resource_tax_rate_id,
                                 :original_delivery_id, :children_count, :parent_id)
      li_attrs[:original_delivery_id] = li[:delivery_id]
      if li_attrs[:discounted_price].to_f.zero? && li_attrs[:price].to_f.positive?
        li_attrs[:discounted_price] = li_attrs[:price]
      end
      copy.line_items.new(li_attrs)
    end
  end
end

#dependents(limit: 5) ⇒ Object

A quote with communications to a customer cannot be deleted
A quote used as a parent of another quote is also restricted (clear the children first)
A quote with orders cannot be deleted



757
758
759
760
761
762
763
# File 'app/models/quote.rb', line 757

def dependents(limit: 5)
  {
    orders: orders.limit(limit),
    children: children.limit(limit),
    communications: communications.limit(limit)
  }
end

#editing_locked?Boolean

Returns:

  • (Boolean)


657
658
659
# File 'app/models/quote.rb', line 657

def editing_locked?
  cancelled? || complete?
end

#effective_date_for_couponObject



456
457
458
# File 'app/models/quote.rb', line 456

def effective_date_for_coupon
  override_coupon_date.presence || complete_datetime.try(:to_date) || created_at.try(:to_date) || Date.current
end

#exclude_manually_initiated_event?(_event) ⇒ Boolean

Returns:

  • (Boolean)


436
437
438
# File 'app/models/quote.rb', line 436

def exclude_manually_initiated_event?(_event)
  false
end

#extra_plans_file_name(with_extension = true) ⇒ Object



564
565
566
# File 'app/models/quote.rb', line 564

def extra_plans_file_name(with_extension = true)
  "quote_#{reference_number}_plans#{'.pdf' if with_extension}"
end

#file_name(with_extension = true) ⇒ Object



560
561
562
# File 'app/models/quote.rb', line 560

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

#get_next_reference_numberObject



889
890
891
892
893
894
895
896
# File 'app/models/quote.rb', line 889

def get_next_reference_number
  ref_base = begin
    self.class.connection.execute("SELECT nextval('quotes_reference_numbers_seq') AS reference_number").first['reference_number']
  rescue StandardError
    nil
  end
  "#{quote_type}#{ref_base}"
end

#goods_product_line_idsObject



409
410
411
# File 'app/models/quote.rb', line 409

def goods_product_line_ids
  line_items.goods.joins(:item).where.not(Item[:primary_product_line_id].eq(nil)).pluck(Arel.sql('distinct items.primary_product_line_id'))
end

#has_discounts?Boolean

Returns:

  • (Boolean)


578
579
580
# File 'app/models/quote.rb', line 578

def has_discounts?
  line_items.any?(&:is_discounted?)
end

#has_no_instant_quoting_roomsObject



906
907
908
909
# File 'app/models/quote.rb', line 906

def has_no_instant_quoting_rooms
  # Legacy IQ validation - no longer needed after IQ system deprecated
  true
end

#has_onsite_smartsupport?Boolean

Returns:

  • (Boolean)


967
968
969
970
971
# File 'app/models/quote.rb', line 967

def has_onsite_smartsupport?
  return true if service_distance.present? && (service_distance < SMART_SERVICES_MAX_DISTANCE)

  false
end

#has_pre_pack_deliveries?Boolean

Returns:

  • (Boolean)


1018
1019
1020
# File 'app/models/quote.rb', line 1018

def has_pre_pack_deliveries?
  pre_pack? || deliveries.any?(&:pre_pack?)
end

#has_selected_heated_items?Boolean

Returns:

  • (Boolean)


973
974
975
976
977
978
979
980
981
# File 'app/models/quote.rb', line 973

def has_selected_heated_items?
  if customer.is_homeowner?
    smartinstall_data.present?
  elsif customer.is_dealer_or_trade_pro_for_locator?
    smartsupport_info.present?
  else
    false
  end
end

#installation_country_isoObject

Alias for Opportunity#installation_country_iso

Returns:

  • (Object)

    Opportunity#installation_country_iso

See Also:



154
# File 'app/models/quote.rb', line 154

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:



154
# File 'app/models/quote.rb', line 154

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

#installation_is_within_range?Boolean

Returns:

  • (Boolean)


983
984
985
# File 'app/models/quote.rb', line 983

def installation_is_within_range?
  service_distance.present? && (service_distance <= SMART_SERVICES_MAX_DISTANCE) # within 100 miles from office
end

#installation_postal_codeObject

Alias for Opportunity#installation_postal_code

Returns:

  • (Object)

    Opportunity#installation_postal_code

See Also:



154
# File 'app/models/quote.rb', line 154

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

#installation_state_codeObject

Alias for Opportunity#installation_state_code

Returns:

  • (Object)

    Opportunity#installation_state_code

See Also:



154
# File 'app/models/quote.rb', line 154

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

#insulation_sq_ftObject



815
816
817
818
819
820
821
# File 'app/models/quote.rb', line 815

def insulation_sq_ft
  sqft = 0
  room_configurations.each do |rc|
    sqft += rc.insulation_surface.to_i if rc.room_type&.is_indoor? && (rc.insulation_surface.to_i > 0)
  end
  sqft
end

#is_first_quote?Boolean

Returns:

  • (Boolean)


807
808
809
# File 'app/models/quote.rb', line 807

def is_first_quote?
  parent_id.nil?
end

#is_revision?Boolean

Returns:

  • (Boolean)


811
812
813
# File 'app/models/quote.rb', line 811

def is_revision?
  parent_id.present?
end

#is_sales_quote?Boolean

Returns:

  • (Boolean)


803
804
805
# File 'app/models/quote.rb', line 803

def is_sales_quote?
  quote_type == SALES_QUOTE
end

#is_smart_service_quote?Boolean

Returns:

  • (Boolean)


987
988
989
# File 'app/models/quote.rb', line 987

def is_smart_service_quote?
  line_items.non_shipping.all?(&:is_smart_service?)
end

#linked_support_casesActiveRecord::Relation<LinkedSupportCase>

Returns:

  • (ActiveRecord::Relation<LinkedSupportCase>)

See Also:



167
# File 'app/models/quote.rb', line 167

has_many    :linked_support_cases, through: :room_configurations, source: :support_cases

#local_sales_repObject

Alias for Customer#local_sales_rep

Returns:

  • (Object)

    Customer#local_sales_rep

See Also:



153
# File 'app/models/quote.rb', line 153

delegate    :primary_sales_rep, :secondary_sales_rep, :local_sales_rep, to: :customer


1037
1038
1039
# File 'app/models/quote.rb', line 1037

def lookup_link
  "https://#{WEB_HOSTNAME}/quote-lookup?quote_id=#{id}"
end

#main_repObject



625
626
627
# File 'app/models/quote.rb', line 625

def main_rep
  primary_sales_rep
end

#material_alertsActiveRecord::Relation<MaterialAlert>

Returns:

See Also:



168
# File 'app/models/quote.rb', line 168

has_many    :material_alerts, dependent: :destroy

#messaging_logsActiveRecord::Relation<MessagingLog>

Returns:

See Also:



166
# File 'app/models/quote.rb', line 166

has_many    :messaging_logs, dependent: :destroy, as: :resource

#nameObject



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

def name
  "Quote ##{reference_number}"
end

#ok_to_customer_cancel?Boolean

Returns:

  • (Boolean)


769
770
771
# File 'app/models/quote.rb', line 769

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

#ok_to_delete?Boolean

Returns:

  • (Boolean)


765
766
767
# File 'app/models/quote.rb', line 765

def ok_to_delete?
  dependents.values.flatten.empty?
end

#open_activities_counterObject



1005
1006
1007
# File 'app/models/quote.rb', line 1005

def open_activities_counter
  all_activities.open_activities.visible_by_default.size
end

#opportunityOpportunity



156
# File 'app/models/quote.rb', line 156

belongs_to  :opportunity, optional: true

#ordersActiveRecord::Relation<Order>

Returns:

  • (ActiveRecord::Relation<Order>)

See Also:



161
# File 'app/models/quote.rb', line 161

has_many    :orders

#participants_options_for_selectObject



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

def participants_options_for_select
  opportunity&.opportunity_participants&.map { |scp| [scp.party.full_name, scp.party_id] }
end

#post_communication_queued_hook(_params = {}) ⇒ Object



527
528
529
530
# File 'app/models/quote.rb', line 527

def post_communication_queued_hook(_params = {})
  # Handles post transmission
  complete if can_complete?
end

#post_communication_sent_hook(_params = {}) ⇒ Object



532
533
534
535
# File 'app/models/quote.rb', line 532

def post_communication_sent_hook(_params = {})
  # Handles post transmission
  complete if can_complete?
end

#pre_packable?Boolean

Returns:

  • (Boolean)


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

def pre_packable?
  deliveries.any? && deliveries.any? { |d| d.shipments.any? { |s| !s.packed_or_pre_packed? } }
end

#prefix_for_reference_numberObject



902
903
904
# File 'app/models/quote.rb', line 902

def prefix_for_reference_number
  "#{opportunity.opportunity_type}Q"
end

#prepared_for_nameObject



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

def prepared_for_name
  (contact || customer).name
end

#prevent_recalculate_shipping?Boolean

Returns:

  • (Boolean)


911
912
913
# File 'app/models/quote.rb', line 911

def prevent_recalculate_shipping?
  %i[complete cancelled].include?(state.to_sym) || retrieving_shipping_costs?
end

#pricing_program_descriptionObject



923
924
925
# File 'app/models/quote.rb', line 923

def pricing_program_description
  tier2_program_pricing_coupon.try(:title) || 'MSRP/Catalog'
end

#pricing_program_discountObject



927
928
929
# File 'app/models/quote.rb', line 927

def pricing_program_discount
  (tier2_program_pricing_coupon.try(:amount_goods).presence || 0).to_i
end

#primary_partyObject

Alias for Opportunity#primary_party

Returns:

  • (Object)

    Opportunity#primary_party

See Also:



149
# File 'app/models/quote.rb', line 149

delegate    :customer, :contact, :primary_party, to: :opportunity

#primary_sales_repObject

Alias for Customer#primary_sales_rep

Returns:

  • (Object)

    Customer#primary_sales_rep

See Also:



153
# File 'app/models/quote.rb', line 153

delegate    :primary_sales_rep, :secondary_sales_rep, :local_sales_rep, to: :customer

#ready_to_complete?Boolean

Returns:

  • (Boolean)


568
569
570
571
572
# File 'app/models/quote.rb', line 568

def ready_to_complete?
  # here we want good shipping address (allow nil if instant_quoting), and any cancelled rooms to be deleted or resolved before we can get a complete and convertable quote
  # shipping_address_nil_or_valid? &&
  all_rooms_complete_or_cancelled_or_draft?
end

#recipient_emailObject



485
486
487
488
489
# File 'app/models/quote.rb', line 485

def recipient_email
  contact_points.emails.first&.detail ||
    primary_party&.email ||
    customer&.email
end

#recipient_nameObject



481
482
483
# File 'app/models/quote.rb', line 481

def recipient_name
  primary_party&.name || shipping_address&.person_name || customer&.name
end

#reference_number_with_nameObject



718
719
720
# File 'app/models/quote.rb', line 718

def reference_number_with_name
  [reference_number, suffix].compact.join(' - ')
end

#reference_number_with_opp_nameObject



726
727
728
# File 'app/models/quote.rb', line 726

def reference_number_with_opp_name
  "#{reference_number} #{opportunity.try(:name)}"
end

#reference_number_with_opp_name_when_specifiedObject



730
731
732
733
734
# File 'app/models/quote.rb', line 730

def reference_number_with_opp_name_when_specified
  s = "#{reference_number}"
  s << " #{opportunity.name}" unless opportunity.nil? || opportunity.unknown_job_name?
  s
end

#request_plans_or_completeObject



629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
# File 'app/models/quote.rb', line 629

def request_plans_or_complete
  return if complete? || cancelled?

  # Let's see if this is still necessary, as far as i can find it's only on quotes_controller # enter_rooms and room destroy that
  # this is called and the room association is already up to date at that point
  # room_configurations.each(&:reload)
  self.do_not_set_totals = true
  log_prefix = "Quote(#{id}):request_plans_or_complete"
  # If no room but line items, let's go straight to awaiting transmission
  if room_configurations.empty? && line_items.present?
    logger.info "#{log_prefix} no rooms but line present. attempting ready to transmit"
    ready_to_transmit! if can_ready_to_transmit?
  # If all rooms are complete then set quote to be awaiting_transmission
  elsif can_installation_plans_complete?
    logger.info "#{log_prefix} installation plan can complete, trying"
    installation_plans_complete!
  elsif can_ready_for_design?
    logger.info "#{log_prefix} can ready for design, trying"
    ready_for_design!
  elsif can_pending_project_details?
    logger.info "#{log_prefix} no line items and nothing else happening, go back to draft"
    pending_project_details!
  else
    logger.info "#{log_prefix} reverting to draft instant quoting rooms"
    # Legacy IQ cleanup removed - no longer needed after IQ system deprecated
  end
end

#reset_discount_on_ships_economy_changeObject (protected)



1070
1071
1072
1073
1074
1075
# File 'app/models/quote.rb', line 1070

def reset_discount_on_ships_economy_change
  return unless saved_change_to_ships_economy?

  reset_discount(reset_item_pricing: false)
  true
end

#rmaRma

Returns:

See Also:



158
# File 'app/models/quote.rb', line 158

belongs_to  :rma, optional: true

#schedule_follow_up(follow_up_task_type: nil) ⇒ Object

Schedule follow up on quote



876
877
878
879
880
881
882
883
884
885
886
887
# File 'app/models/quote.rb', line 876

def schedule_follow_up(follow_up_task_type: nil)
  # Don't create follow-up activities for guest customers without contact info
  # These are anonymous Quote Builder users who haven't provided email/phone
  return if customer.guest? && !opportunity.emailable? && !opportunity.voice_callable?

  return if opportunity.activities.quofu.open_activities.present?

  # cancel oppfu follow ups
  opportunity.cancel_oppfu
  # recreate a quofu centric follow up
  opportunity.create_follow_up_activity(follow_up_task_type:)
end

#secondary_sales_repObject

Alias for Customer#secondary_sales_rep

Returns:

  • (Object)

    Customer#secondary_sales_rep

See Also:



153
# File 'app/models/quote.rb', line 153

delegate    :primary_sales_rep, :secondary_sales_rep, :local_sales_rep, to: :customer

#selection_nameObject



831
832
833
# File 'app/models/quote.rb', line 831

def selection_name
  reference_number_with_name
end

#send_profit_review_notificationObject



944
945
946
# File 'app/models/quote.rb', line 944

def send_profit_review_notification
  InternalMailer.quote_profit_review_notification(self).deliver_later
end

#senderObject



793
794
795
796
797
798
799
800
801
# File 'app/models/quote.rb', line 793

def sender
  if primary_sales_rep
    primary_sales_rep
  elsif opportunity.primary_sales_rep
    opportunity.primary_sales_rep
  elsif customer.primary_sales_rep
    customer.primary_sales_rep
  end
end

#service_distanceObject



991
992
993
994
# File 'app/models/quote.rb', line 991

def service_distance
  zip_code = opportunity.installation_postal_code
  SmartServicesController.helpers.calculate_distance_from_lz(zip_code)
end

#set_currencyObject



680
681
682
683
684
685
686
687
# File 'app/models/quote.rb', line 680

def set_currency
  store_currency = begin
    opportunity.customer.store.currency
  rescue StandardError
    nil
  end
  self.currency = store_currency if store_currency
end

#set_default_quote_typeObject (protected)



1063
1064
1065
1066
1067
1068
# File 'app/models/quote.rb', line 1063

def set_default_quote_type
  self.quote_type ||= prefix_for_reference_number
  return unless quote_type_changed? && reference_number.present?

  self.reference_number = "#{quote_type}#{reference_number[2..50]}"
end

#set_expiration_dateObject (protected)



1049
1050
1051
1052
1053
# File 'app/models/quote.rb', line 1049

def set_expiration_date
  return if complete?

  self.expiration_date = calculate_expiration_date
end

#set_min_profit_markupObject (protected)



1055
1056
1057
1058
1059
1060
1061
# File 'app/models/quote.rb', line 1055

def set_min_profit_markup
  self.min_profit_markup = if quote_type == 'SQ'
                             default_sales_markup
                           else
                             0
                           end
end

#set_opportunity_valueObject



823
824
825
# File 'app/models/quote.rb', line 823

def set_opportunity_value
  opportunity&.calculate_value
end

#set_priority(save = true) ⇒ Object



689
690
691
692
693
694
695
696
697
698
699
700
# File 'app/models/quote.rb', line 689

def set_priority(save = true)
  highest_priority = begin
    RoomConfiguration::PRIORITIES[room_configurations.map(&:priority_urgency_factor).min]
  rescue StandardError
    'standard'
  end
  if save
    update_attribute(:priority, highest_priority)
  else
    self.priority = highest_priority
  end
end

#set_reference_numberObject



898
899
900
# File 'app/models/quote.rb', line 898

def set_reference_number
  self.reference_number ||= get_next_reference_number
end

#shipping_address_nil_or_valid?Boolean

Returns:

  • (Boolean)


574
575
576
# File 'app/models/quote.rb', line 574

def shipping_address_nil_or_valid?
  shipping_address.nil? || shipping_address.valid?
end

#smartsupport_infoObject



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

def smartsupport_info
  return nil if service_distance.nil?

  smartsupport_data(service_distance)
end

#sms_enabled_numbersObject



539
540
541
# File 'app/models/quote.rb', line 539

def sms_enabled_numbers
  ContactPoint.joins(:party).merge(all_participants).sms_numbers.order(:detail).map(&:formatted_for_sms).uniq
end

#sms_messagesObject



543
544
545
# File 'app/models/quote.rb', line 543

def sms_messages
  SmsMessage.for_numbers(sms_enabled_numbers)
end

#sold?Boolean

Returns:

  • (Boolean)


750
751
752
# File 'app/models/quote.rb', line 750

def sold?
  orders.non_carts.active.present?
end

#special_ordering_instructionsObject



674
675
676
677
678
# File 'app/models/quote.rb', line 674

def special_ordering_instructions
  customer.billing_address.party.ordering_instructions
rescue StandardError
  nil
end

#stop_for_pre_pack?Boolean

Returns:

  • (Boolean)


1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
# File 'app/models/quote.rb', line 1022

def stop_for_pre_pack?
  return false unless can_request_estimated_packaging?
  return false if shipping_address.nil?
  return false if customer.is_e_commerce_misc?
  return false if line_items.empty?
  return false if room_configurations.present? && !room_configurations.all?(&:transmittable_and_settled?)
  return false if deliveries.all? { |d| (d.shipments.packed.any? && d.all_lines_allocated_to_shipments?) || d.is_smart_service? }

  need_to_pre_pack_reasons.any?
end

#stop_for_profit_review?Boolean

Returns:

  • (Boolean)


1009
1010
1011
1012
1013
1014
1015
1016
# File 'app/models/quote.rb', line 1009

def stop_for_profit_review?
  # Etailer orders are immune
  return false if customer.is_e_commerce_misc?
  return false if line_items.empty?
  return false if room_configurations.present? && !room_configurations.all?(&:transmittable?)

  !profit_margins_met?
end

#storeObject

Alias for Customer#store

Returns:

  • (Object)

    Customer#store

See Also:



150
# File 'app/models/quote.rb', line 150

delegate    :store, to: :customer

#support_casesActiveRecord::Relation<SupportCase>

Returns:

See Also:



171
# File 'app/models/quote.rb', line 171

has_and_belongs_to_many :support_cases

#tier2_program_pricingObject



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

def tier2_program_pricing
  discounts.tier2.first
end

#tier2_program_pricing_couponObject



919
920
921
# File 'app/models/quote.rb', line 919

def tier2_program_pricing_coupon
  tier2_program_pricing.coupon if tier2_program_pricing
end

#to_liquidObject



405
406
407
# File 'app/models/quote.rb', line 405

def to_liquid
  Liquid::QuoteDrop.new self
end

#to_order(order = nil, txid = nil) ⇒ Object



746
747
748
# File 'app/models/quote.rb', line 746

def to_order(order = nil, txid = nil)
  convert_to_order_service.convert(order, txid)
end

#to_sObject



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

def to_s
  "Quote # #{reference_number_with_name}"
end

#total_ampsObject



1000
1001
1002
# File 'app/models/quote.rb', line 1000

def total_amps
  room_configurations.to_a.sum { |rc| rc.calculate_total_amps.round(2) }&.round(2)
end

#track_profit?Boolean

Returns:

  • (Boolean)


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

def track_profit?
  profitable_line_items.present? && is_sales_quote?
end

#tracking_email_addressObject



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

def tracking_email_address
  domain = Rails.application.config.x.email_domain
  prefix = 'quo'
  encrypted_id = Encryption.encrypt_string(id.to_s)
  "#{prefix}+id#{encrypted_id}@#{domain}"
end

#uploadsActiveRecord::Relation<Upload>

Returns:

  • (ActiveRecord::Relation<Upload>)

See Also:



163
# File 'app/models/quote.rb', line 163

has_many    :uploads, as: :resource, dependent: :destroy

#versions_for_audit_trail(_params = {}) ⇒ Object



460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
# File 'app/models/quote.rb', line 460

def versions_for_audit_trail(_params = {})
  query_sql = %q{
                (item_type = 'Quote' and item_id = :id)
                OR (
                  item_type = 'LineItem'
                    AND reference_data @> '{"resource_type": "Quote"}'
                    AND reference_data @> '{"resource_id": :id}'
                )
                OR (
                  item_type = 'Delivery'
                    AND reference_data @> '{"quote_id": :id}'
                )
                OR (
                  item_type = 'Discount'
                    AND reference_data @> '{"itemizable_id": :id}'
                    AND reference_data @> '{"itemizable_type": "Quote"}'
                )
              }
  RecordVersion.where(query_sql, id:)
end

#visitVisit

Returns:

See Also:



159
# File 'app/models/quote.rb', line 159

belongs_to  :visit, optional: true

#will_complete?Boolean

Returns:

  • (Boolean)


491
492
493
494
495
496
# File 'app/models/quote.rb', line 491

def will_complete?
  line_items.any? &&
    shipping_address_nil_or_valid? &&
    all_rooms_complete_or_cancelled_or_draft? &&
    profit_margins_met?
end

#will_installation_plans_complete?Boolean

Returns:

  • (Boolean)


498
499
500
# File 'app/models/quote.rb', line 498

def will_installation_plans_complete?
  room_configurations.exists? && all_rooms_complete_or_cancelled_or_draft? && line_items.present?
end

#will_pending_project_details?Boolean

Returns:

  • (Boolean)


518
519
520
521
# File 'app/models/quote.rb', line 518

def will_pending_project_details?
  awaiting_completed_installation_plans? ||
    (line_items.empty? && room_configurations.any?(&:draft?))
end

#will_ready_for_design?Boolean

Returns:

  • (Boolean)


502
503
504
# File 'app/models/quote.rb', line 502

def will_ready_for_design?
  room_configurations.exists? && any_rooms_in_design?
end

#will_ready_to_transmit?Boolean

Returns:

  • (Boolean)


506
507
508
509
510
511
512
# File 'app/models/quote.rb', line 506

def will_ready_to_transmit?
  line_items.any? &&
    shipping_address_nil_or_valid? &&
    profit_margins_met? &&
    !stop_for_pre_pack? &&
    (room_configurations.empty? || room_configurations.all?(&:transmittable?))
end

#zonesObject



620
621
622
623
# File 'app/models/quote.rb', line 620

def zones
  # Zones are combinations of room configurations which are controlled together and regular rooms
  # Ramie: I'd say this is an unfortunate choice of name. we already have a well developed concept of room zones, rectangular areas within a room that are heateded areas, along with the model, methods and etc.
end