Class: Shipping::CreateSuggestedShipment

Inherits:
BaseService show all
Defined in:
app/services/shipping/create_suggested_shipment.rb

Instance Method Summary collapse

Methods inherited from BaseService

#initialize, #log_debug, #log_error, #log_info, #log_warning, #logger, #options, #tagged_logger

Constructor Details

This class inherits a constructor from BaseService

Instance Method Details

#build_item_map_from_line_items(line_items) ⇒ Object

Returns a hash, item_id as key, mapping to array of line item id and quantities



91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'app/services/shipping/create_suggested_shipment.rb', line 91

def build_item_map_from_line_items(line_items)
  item_map = {}
  # We will keep track of all line items to allocate
  line_items.each do |li|
    # Break kit items
    items = li.item.get_kit_items if li.item.is_kit?
    items ||= [li.item]
    items.each do |i|
      item_map[i.id] ||= {}
      item_map[i.id][li.id] = li.quantity.abs
    end
  end
  item_map
end

#process(delivery) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
# File 'app/services/shipping/create_suggested_shipment.rb', line 3

def process(delivery)
  # Shortcut
  return [] if delivery.is_service_only?
  suggested_shipments = []
  Shipment.transaction do

    line_items_to_pack = delivery.packable_active_lines

    # Delete only fresh suggested shipments (container_code nil = never physically packed by warehouse).
    # Suggested shipments with a container_code were previously packed and measured by the warehouse
    # — they survive a CR Hold / shipping-method change so staff don't have to re-enter weights.
    delivery.shipments.suggested.where(container_code: [nil, '']).delete_all

    # If any non-voided shipments remain (packed, awaiting_label, label_complete, or
    # previously-measured suggested shipments) return them directly without rebuilding.
    remaining_shipments = delivery.shipments.non_voided
    return remaining_shipments if remaining_shipments.present?

    # Try to find a matching packaging from previous order or use legacy packing model
    packing_solution = Shipping::DeterminePackaging.new.process(delivery: delivery,
                                                             is_freight: delivery.ships_ltl_freight?)

    item_map = {}
    if packing_solution.packages.all? { |p| p.contents.present? }
      item_map = build_item_map_from_line_items(line_items_to_pack)
    elsif packing_solution.packages.size == 1 && line_items_to_pack.any?
      item_map = build_item_map_from_line_items(line_items_to_pack)
      synthesize_single_box_contents(packing_solution.packages.first, line_items_to_pack)
    end
    packing_solution.packages.each do |p|
      suggested_shipment = delivery.shipments.create(weight: p.weight,
                                                    length: p.length,
                                                    width: p.width,
                                                    height: p.height,
                                                    is_legacy: false,
                                                    is_manual: false,
                                                    flat_rate_package_type: p.flat_rate_package_type,
                                                    container_type: p.container_type || Shipment.container_types.keys.first,  # 'carton'
                                                    state: 'suggested')
      if !suggested_shipment.valid? || !suggested_shipment.persisted?
        logger.error "Could not create suggested shipment: #{suggested_shipment.errors_to_s}"
      else
        if item_map.present? && p.contents.present?
          # map shipment contents
          p.contents.each do |package_content|
            # this returns an array of [line item id, quantities]
            qty_remaining_to_allocate = package_content.quantity
            # Sometimes garbage data or mismatch can cause an item to be fully allocated already
            break if item_map[package_content.item_id].nil?
            item_map[package_content.item_id].each do |(line_item_id, line_quantity)|
              allocatable_qty = [qty_remaining_to_allocate,line_quantity].min
              sc = suggested_shipment.shipment_contents.where(line_item_id: line_item_id).first_or_initialize
              sc.quantity = allocatable_qty
              sc.save
              # Remove or reduce the quantity from this line
              item_map[package_content.item_id][line_item_id] -= allocatable_qty
              # If the line is fully allocated, we remove this entry
              if item_map[package_content.item_id][line_item_id] <= 0
                item_map[package_content.item_id].delete(line_item_id)
              end
              # and remove the item id if its all allocated
              if item_map[package_content.item_id].empty?
                item_map.delete(package_content.item_id)
              end
            end
            # If we allocated everything in this package we move on
            break if qty_remaining_to_allocate == 0
          end
        end

        suggested_shipments << suggested_shipment
      end
    end
  end
  suggested_shipments
end

#synthesize_single_box_contents(package, line_items) ⇒ Object

For single-box solutions where the packing history has dims but no per-box
item mapping, synthesize contents from the delivery's line items so the
allocation loop can assign them to the one shipment.



83
84
85
86
87
88
# File 'app/services/shipping/create_suggested_shipment.rb', line 83

def synthesize_single_box_contents(package, line_items)
  line_items.each do |li|
    items = li.item.is_kit? ? li.item.get_kit_items : [li.item]
    items.each { |i| package.add_content(i.id, li.quantity.abs) }
  end
end