# frozen_string_literal: true module Calculator class Weight < Spree::Calculator preference :unit_from_list, :string, default: "kg" preference :per_unit, :decimal, default: 0.0 def self.description I18n.t('spree.weight') end def set_preference(name, value) if name == :unit_from_list && ["kg", "lb"].exclude?(value) calculable.errors.add(:preferred_unit_from_list, I18n.t(:calculator_preferred_unit_error)) else __send__ self.class.preference_setter_method(name), value end end def compute(object) line_items = line_items_for object (total_weight(line_items) * preferred_per_unit).round(2) end def preferred_unit_from_list_values ["kg", "lb"] end private def total_weight(line_items) line_items.to_a.sum do |line_item| line_item_weight(line_item) end end def line_item_weight(line_item) if final_weight_volume_present?(line_item) weight_per_final_weight_volume(line_item) else weight_per_variant(line_item) * line_item.quantity end end def weight_per_variant(line_item) if variant_unit(line_item) == 'weight' # Convert unit_value to the preferred unit convert_weight(line_item.variant&.unit_value) else line_item.variant&.weight || 0 end end def weight_per_final_weight_volume(line_item) if variant_unit(line_item) == 'weight' # Convert final_weight_volume to the preferred unit convert_weight(line_item.final_weight_volume) else weight_per_variant(line_item) * quantity_implied_in_final_weight_volume(line_item) end end # Example: 2 (line_item.quantity) wine glasses of 125mL (line_item.variant.unit_value) # Customer ends up getting 350mL (line_item.final_weight_volume) of wine # that represent 2.8 (quantity_implied_in_final_weight_volume) glasses of wine def quantity_implied_in_final_weight_volume(line_item) return line_item.quantity if line_item.variant.unit_value.to_f.zero? (1.0 * line_item.final_weight_volume / line_item.variant.unit_value).round(3) end def final_weight_volume_present?(line_item) line_item.respond_to?(:final_weight_volume) && line_item.final_weight_volume.present? end def variant_unit(line_item) line_item.variant.product&.variant_unit end def convert_weight(value) return 0 unless value && ["kg", "lb"].include?(preferences[:unit_from_list]) if preferences[:unit_from_list] == "kg" value / 1000 elsif preferences[:unit_from_list] == "lb" value / 453.6 end end end end