Module: LineItemExtension

Defined in:
app/models/line_item_extension.rb

Overview

Association extension for has_many :line_items collections (mixed in
wherever the Models::Pickable concern declares line_items).

Adds collection-level helpers that the rest of the ERP relies on
without paying an N+1: heating-system aggregations
(#primary_heating_system_product_line, #primary_heating_element_spacing),
active-line filtering (#active_lines, #active_parent_lines,
#active_goods_lines, #active_services_lines, #active_shipping_lines),
preloads for the coupon engine (#with_discount_preloads), and a
stable MD5 #signature used to detect "is this packing list still
the same one we already shipped?" for shipment matching.

Instance Method Summary collapse

Instance Method Details

#active_goods_linesObject



107
108
109
# File 'app/models/line_item_extension.rb', line 107

def active_goods_lines
  active_lines.select(&:is_goods?)
end

#active_linesObject



58
59
60
61
# File 'app/models/line_item_extension.rb', line 58

def active_lines
  # uniq is a safety in case multiple of the same lines are added to the collection
  to_a.reject { |li| li.destroyed? or li.marked_for_destruction? or li.frozen? }.uniq
end

#active_lines_for_packagingObject



81
82
83
84
85
86
87
88
89
# File 'app/models/line_item_extension.rb', line 81

def active_lines_for_packaging
  active_lines.select do |li|
    li.is_goods? &&
      (
        (li.children_count.to_i.zero? && !li.parent&.catalog_item&.pack_at_kit_level&.to_b) ||
        li.catalog_item.pack_at_kit_level.to_b
      )
  end
end

#active_lines_without_kit_parentsObject



77
78
79
# File 'app/models/line_item_extension.rb', line 77

def active_lines_without_kit_parents
  active_lines.reject { |li| li.children_count.present? and li.children_count > 0 }
end

#active_non_shipping_linesObject



99
100
101
# File 'app/models/line_item_extension.rb', line 99

def active_non_shipping_lines
  active_lines.reject(&:is_shipping?)
end

#active_parent_linesObject



63
64
65
# File 'app/models/line_item_extension.rb', line 63

def active_parent_lines
  active_lines.reject { |li| li.parent_id.present? }
end

#active_services_linesObject



111
112
113
# File 'app/models/line_item_extension.rb', line 111

def active_services_lines
  active_lines.select(&:is_service?)
end

#active_shipping_linesObject



103
104
105
# File 'app/models/line_item_extension.rb', line 103

def active_shipping_lines
  active_lines.select(&:is_shipping?)
end

#active_tax_unclassed_linesObject



115
116
117
# File 'app/models/line_item_extension.rb', line 115

def active_tax_unclassed_lines
  active_lines.select(&:is_tax_unclassed?)
end

#any_require_cable_spacing?Boolean

Returns:

  • (Boolean)


37
38
39
40
# File 'app/models/line_item_extension.rb', line 37

def any_require_cable_spacing?
  # see if ant line items require cable spacing
  heating_elements.any? { |li| li.item&.is_cable_system? }
end

#changed_for_shipping?Boolean

Returns:

  • (Boolean)


54
55
56
# File 'app/models/line_item_extension.rb', line 54

def changed_for_shipping?
  (active_goods_lines + active_services_lines).any? { |li| li.destroyed? or li.marked_for_destruction? or li.new_record? or li.quantity_changed? or li.catalog_item_id_changed? }
end

#goods_changed?Boolean

Returns:

  • (Boolean)


127
128
129
# File 'app/models/line_item_extension.rb', line 127

def goods_changed?
  lines_changed.any?(&:is_goods?)
end

#grouped_by_categoryObject



46
47
48
# File 'app/models/line_item_extension.rb', line 46

def grouped_by_category
  product_category_sorted.group_by(&:category_name)
end

#has_goods_lines?Boolean

Returns:

  • (Boolean)


123
124
125
# File 'app/models/line_item_extension.rb', line 123

def has_goods_lines?
  active_goods_lines.present?
end

#has_goods_without_shipping?Boolean

Returns:

  • (Boolean)


131
132
133
# File 'app/models/line_item_extension.rb', line 131

def has_goods_without_shipping?
  (has_goods_lines? and !has_shipping_line?)
end

#has_shipping_line?Boolean

Returns:

  • (Boolean)


119
120
121
# File 'app/models/line_item_extension.rb', line 119

def has_shipping_line?
  active_shipping_lines.present?
end

#has_underlayment?Boolean

Returns:

  • (Boolean)


42
43
44
# File 'app/models/line_item_extension.rb', line 42

def has_underlayment?
  to_a.map(&:item_id).intersect?(Item.underlayments.ids)
end

#lines_changedObject



91
92
93
# File 'app/models/line_item_extension.rb', line 91

def lines_changed
  to_a.select { |li| li.changed? or li.destroyed? or li.marked_for_destruction? }
end

#lines_changed?Boolean

Returns:

  • (Boolean)


95
96
97
# File 'app/models/line_item_extension.rb', line 95

def lines_changed?
  to_a.any? { |li| li.changed? or li.destroyed? or li.marked_for_destruction? or li.new_record? }
end

#primary_heating_element_spacingObject



31
32
33
34
35
# File 'app/models/line_item_extension.rb', line 31

def primary_heating_element_spacing
  # just count the occurents of line items for a given heating_element_spacing
  he_spacings = heating_elements.filter_map { |li| li.item&.spec(:heating_element_spacing)&.dig(:raw) }
  he_spacings.tally.max_by(&:last)&.first
end

#primary_heating_system_product_lineObject



18
19
20
21
22
23
# File 'app/models/line_item_extension.rb', line 18

def primary_heating_system_product_line
  # just count the occurrences of line items for a given heating system type
  hs_product_lines = heating_elements.parents_only.select { |li| li.item.primary_product_line&.get_first_heating_system_type.present? }.filter_map { |li| li.item.primary_product_line.get_first_heating_system_type }
  hs_product_lines = heating_elements.select { |li| li.item.primary_product_line&.get_first_heating_system_type.present? }.filter_map { |li| li.item.primary_product_line.get_first_heating_system_type } if hs_product_lines.empty?
  hs_product_lines.tally.max_by(&:last)&.first
end

#primary_heating_system_voltageObject



25
26
27
28
29
# File 'app/models/line_item_extension.rb', line 25

def primary_heating_system_voltage
  # just count the occurents of line items for a given heating system voltage
  hs_volts = heating_elements.filter_map { |li| li.item&.voltage }
  hs_volts.tally.max_by(&:last)&.first
end

#signatureObject



135
136
137
# File 'app/models/line_item_extension.rb', line 135

def signature
  Delivery.md5_hash_items(active_lines)
end

#ungroupedObject



50
51
52
# File 'app/models/line_item_extension.rb', line 50

def ungrouped
  where(room_configuration_id: nil)
end

#versionObject



14
15
16
# File 'app/models/line_item_extension.rb', line 14

def version
  all.maximum(:updated_at).to_i
end

#with_discount_preloadsObject

Preload associations commonly needed for coupon qualification and discount calculations.
This prevents N+1 queries when:

  • item: checking item specifications (sqft, linear_ft), product line, etc.
  • catalog_item: calculating msrp_total
  • line_discounts: checking if discount applies to line_item, calculating discounted_total
    Returns a relation - call .active_parent_lines after this to get the filtered array.


73
74
75
# File 'app/models/line_item_extension.rb', line 73

def with_discount_preloads
  includes(:item, :catalog_item, :line_discounts)
end