futhr/spree-postal-service

View on GitHub
lib/spree/calculator/shipping/postal_service.rb

Summary

Maintainability
A
45 mins
Test Coverage
module Spree
  class Calculator
    module Shipping
      class PostalService < Spree::ShippingCalculator

        preference :weight_table,    :string,  default: '1 2 5 10 20'
        preference :price_table,     :string,  default: '6 9 12 15 18'
        preference :max_item_weight, :decimal, default: 18
        preference :max_item_width,  :decimal, default: 60
        preference :max_item_length, :decimal, default: 120
        preference :max_price,       :decimal, default: 120
        preference :handling_max,    :decimal, default: 50
        preference :handling_fee,    :decimal, default: 10
        preference :default_weight,  :decimal, default: 1

        class << self
          def description
            Spree.t(:postal_service)
          end

          def register
            super
          end
        end

        attr_accessor :line_items

        def item_oversized?(variant)
          sizes = [
            variant.width ? variant.width : 0,
            variant.depth ? variant.depth : 0,
            variant.height ? variant.height : 0
          ].sort.reverse

          return true if sizes[0] > preferred_max_item_length # Longest side.
          return true if sizes[1] > preferred_max_item_width  # Second longest side.
          false
        end

        # Determine if weight or size goes over bounds.
        def available?(package)
          package.order.variants.each do |variant|
            return false if item_within_bounds?(variant.weight) # 18
            return false if item_oversized?(variant)
          end
          true
        end

        # As order_or_line_items we always get line items, as calculable we have
        # Coupon, ShippingMethod or ShippingRate.
        def compute(package)
          @line_items ||= package.order.line_items
          total_price  = compute_total_price
          total_weight = compute_total_weight
          shipping     = 0

          return 0.0 if total_price > preferred_max_price

          while total_weight > weights.last # In several packages if need be.
            total_weight -= weights.last
            shipping += prices.last
          end

          [shipping, prices[compute_index(total_weight)], handling_fee(total_price)].compact.sum
        end

        private

        def compute_total_weight
          line_items.map do |item|
            weight = item.variant.weight > 0 ? item.variant.weight : preferred_default_weight
            item.quantity * weight
          end.reduce(:+)
        end

        def compute_total_price
          line_items.map { |item| item.price * item.quantity }.reduce(:+)
        end

        def compute_index(total_weight)
          index = weights.length - 2
          while index >= 0
            break if total_weight > weights[index]
            index -= 1
          end
          index + 1
        end

        def item_within_bounds?(weight)
          weight && weight > preferred_max_item_weight
        end

        def handling_fee(total_price)
          preferred_handling_max < total_price ? 0 : preferred_handling_fee
        end

        def prices
          @prices ||= preferred_price_table.split.map(&:to_f)
        end

        def weights
          @weights ||= preferred_weight_table.split.map(&:to_f)
        end
      end
    end
  end
end