Class: RoomConfiguration::Copier

Inherits:
Object
  • Object
show all
Defined in:
app/services/room_configuration/copier.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(room_configurations, options = {}) ⇒ Copier

Returns a new instance of Copier.

Parameters:

  • room_configurations (Array<RoomConfiguration>)

    the room configurations to copy

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

    configurable options

Options Hash (options):

  • logger (Logger)

    logger to use (defaults to Rails.logger)

  • skip_room_plan_generation (Boolean)

    skip generating room plans for the copies



9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'app/services/room_configuration/copier.rb', line 9

def initialize(room_configurations, options = {})
  @logger = options[:logger] || Rails.logger
  @options = options
  @room_configurations = room_configurations.to_a
  @skip_room_plan_generation = options[:skip_room_plan_generation]
  controller_rooms = @room_configurations.filter_map(&:controlled_by).uniq
  missing_rooms = controller_rooms - @room_configurations
  if missing_rooms.present?
    logger.info "#{missing_rooms.size} controller rooms were added"
    @room_configurations += missing_rooms
  end
  logger.info "#{@room_configurations.size} total rooms loaded into copier"
end

Instance Attribute Details

#loggerObject (readonly)

Returns the value of attribute logger.



3
4
5
# File 'app/services/room_configuration/copier.rb', line 3

def logger
  @logger
end

#resultsObject (readonly)

Returns the value of attribute results.



3
4
5
# File 'app/services/room_configuration/copier.rb', line 3

def results
  @results
end

#room_configurationsObject (readonly)

Returns the value of attribute room_configurations.



3
4
5
# File 'app/services/room_configuration/copier.rb', line 3

def room_configurations
  @room_configurations
end

Class Method Details

.generate_all_plans(copied_rooms) ⇒ Object



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

def self.generate_all_plans(copied_rooms)
  copied_rooms.each do |new_rc|
    # Since this non critical, we want to log errors but not explode without returning the result
    if Rails.env.development?
      new_rc.generate_all_plans
    else
      GenerateRoomPlansWorker.perform_async(new_rc.id)
    end
  rescue StandardError => e
    msg = "Error in room copy when generating floorplans for #{new_rc.id}, you might need to call generate_all_plans manually"
    logger.error msg
    ErrorReporting.error(e, message: msg)
  end
end

Instance Method Details

#copy_to(opportunity) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'app/services/room_configuration/copier.rb', line 23

def copy_to(opportunity, &)
  room_map = {} # old_room_id => new_room_id, this is used for the controller portion
  # Take each room that is not already present in the target opportunity
  target_rooms = @room_configurations.reject { |rc| rc.opportunity == opportunity }
  if (rejected_rooms = @room_configurations - target_rooms).present?
    logger.warn "#{rejected_rooms.map(&:reference_number).join(', ')} were rejected because they already belong in the target opportunity #{opportunity.reference_number}"
  end
  logger.error "There are no valid rooms left to copy" if target_rooms.empty?
  # We are going to process those that are not controlled first, since those are likely the controller
  # rooms and we will need their id in the room_map when we process their subordinates
  # We're also going to process the parent room if they're specified for the same reason so we
  # can preserve their hierarchy
  target_rooms = target_rooms.sort_by { |rc| [rc.controlled_by_id.to_i, rc.parent_id.to_i] } # nils are first since they convert to 0
  total_rooms_to_copy = target_rooms.size
  copied_rooms = []

  RoomConfiguration.transaction do
    all_rooms_in_opportunity = opportunity.room_configurations.to_a
    target_rooms.each_with_index do |rc, index|
      yield(index + 1, total_rooms_to_copy, "Copying #{rc}") if block_given?
      new_rc = rc.deep_dup
      new_rc.reference_number = nil
      new_rc.opportunity_id = opportunity.id
      new_rc.set_reference_number
      new_rc.quoted_heating_system_pl_id = rc.quoted_heating_system_pl_id
      # if rc.legacy_vignette_plan_image_id.present?
      #   new_rc.vignette = true
      #   new_rc.legacy_vignette_plan_image_id = rc.legacy_vignette_plan_image_id
      #   new_rc.legacy_vignette_room_layout_image_id = rc.legacy_vignette_room_layout_image_id
      # end
      new_rc.parent_id = room_map[rc.id] # Only will be a value if the parent room was included in the copy
      new_rc.controlled_by_id = room_map[rc.id]
      old_name = new_rc.name
      # If the name already exists in this opportunity, we can rename
      if all_rooms_in_opportunity.find { |r| r.name == new_rc.name }
        new_rc.name = opportunity.next_room_name(room_type: new_rc.room_type,
                                                heating_system_product_line: new_rc.heating_system_product_line)
      end
      new_rc.save!
      new_rc.reload
      # Now fix the catalog if needed, with strict mode an exception will be raised if there's no match in the target catalog
      CatalogItem::Remapper.new(new_rc, { strict_mode: true, target_catalog: opportunity.customer.catalog, logger: logger })

      # We keep track of all the rooms copied thus far and combine with the existing rooms from the opportunity
      # Mostly for name collision detection
      all_rooms_in_opportunity << new_rc
      # Keep track of the new room id, this is important we return this as a result
      room_map[rc.id] = new_rc.id

      # The line items are cloned and reset.
      # A room owns its lines (resource_type='RoomConfiguration'); its quotes/orders hold
      # *mirrors* of the same lines (resource_type='Quote'/'Order' + room_configuration_id),
      # kept in step by Pickable#synchronize_lines. Copying both sets doubles every line, so
      # the room's own lines win and the mirrors are only a fallback for the rare room that
      # owns none.
      items_to_copy = rc.line_items.parents_only.to_a
      if items_to_copy.empty?
        items_to_copy = LineItem.where(room_configuration_id: rc.id)
                                .where.not(resource_type: 'RoomConfiguration')
                                .parents_only.to_a
      end

      items_to_copy.each do |li|
        new_li = li.deep_dup
        new_li.room_configuration_id = nil
        new_li.resource = nil
        # Ensure discounted_price is set if it was incorrectly left at 0
        new_li.discounted_price = new_li.price if new_li.discounted_price.to_f.zero? && new_li.price.to_f.positive?
        new_rc.line_items << new_li
      end

      # Keep references of old pdf which have inaccurate info now
      new_rc.uploads.where(category: %w[electrical_plan_pdf installation_plan_pdf]).find_each { |u| u.update(category: 'archive', note: "Was #{u.category}") }

      clone_upload_for_copy(rc.installation_plan_image, new_rc)
      clone_upload_for_copy(rc.room_layout_image, new_rc)
      new_rc.quick_note "This room was copied from #{rc.reference_number}. Its original name was #{old_name}"

      copied_rooms << new_rc
    end
  end

  # Generate the plans again, we do this outside transaction this ensures that our rooms are persisted to the db
  self.class.generate_all_plans(copied_rooms) unless @skip_room_plan_generation

  OpenStruct.new(room_copy_results: room_map).freeze
end