Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add zip code and max weight validation options. Made 0 value disable some validations. #3

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
47 changes: 45 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ A postal service is delivers based on weight only(*). Like most post services in
This spree extension adds a spree-calculator to model this.

Other features:
- size and weight restrictions can be specified
- size and weight restrictions by item and by order can be specified
- you can also add zip code restrictions
- you specify a weight/price table
- prices in the price table can be by weight unit (kg)
- handling fee may be added ( with a maximum when it won't be applied anymore)
- multi-parcel shipments are automatically created
- you can specify a maximum order price, orders over this will not be charged
Expand All @@ -30,24 +32,65 @@ http://localhost:3000/admin/shipping_methods/new

and use "Postal" as calculator.

The size/weight "table" must have the same amount of (space separated) entries.
The price/weight "table" must have the same amount of (space separated) entries.

Optionally add your own locale to config/locales/ (and if you do, send it to me)

Settings:
========

- Weights "table": A space separated list of weights (must have the same amount of entries as the Prices "table")
- Prices "table": A space separated list of prices (must have the same amount of entries as the Weights "table")
- Price by weight unit?: Indicates if the prices in the price list are by weight unit. If true, then the weight of the order will be multiplied by the price setted for that weight value.
- e.g.: Weights: "1 2 5"; Prices: "5 3 2"; Price By Weight Unit? true. If we have a order with a weight of 2.5kg then the total price will be 7,5. If the "Price By Weight Unit?" was setted to false, the total price would be 3.
- Max weight of one item enable?: Enables the "Max weight of one item" verification.
- Max weight of one item: Max weight of any item in the order may have to enable the shipping method.
- Max width of one item enabled?: Enables the "Max width of one item" verification.
- Max width of one item: Max width that any item in the order may have to enable the shipping method.
- Max length of one item enabled?: Enables the "Max length of one item" verification.
- Max length of one item: Max length that any item in the order may have to enable the shipping method.
- Max total weight enabled?: Enables the "Max total weight" verification.
- Max total weight: Max total weight of the order to enable the shipping method.
- Min total weight enabled?: Enables the "Min total weight" verification.
- Min total weight: Min total weight of the order to enable the shipping method.
- Maximum total of the order enabled?: Enables the "Maximum total of the order" verification.
- Maximum total of the order: Order price after which the shipping cost will be 0.
- Amount, over which handling fee won't be applied: Self explained.
- Handling fee: The handling fee.
- Default weight: The default weight to be used on any product that doesn't have a weight value defined.
- Zipcode handling (empty field - does not apply, exact, starts, ends, contains): When the value is one of "exact", "starts", "ends" or "contains", it will validate the zipcode of the shipping adress and enable the shipping method.
- When the value is "starts", the shipping adress zipcode must equal to any of the defined zipcodes in the Zipcode(s) field;
- When the value is "starts", the shipping adress zipcode must start with any of the defined zipcode "parts" in the Zipcode(s) field;
- When the value is "ends", the shipping adress zipcode must end with any of the defined zipcode "parts" in the Zipcode(s) field;
- When the value is "contains", the shipping adress zipcode must contains any of the defined zipcode "parts" in the Zipcode(s) field;
- Zipcode separator: The separator to be used when specifying several zipcodes in the "Zipcode(s)" field.
- Zipcode(s): Zipcode(s), or parts of them, to be used to check if the shipping method is available. When using several zipcodes, the separator must be the one indicated in the "Zipcode separator field".

Example:
=======

With the default settings (measurements in kg and cm):

- Max weight of one item enabled: true
- Max weight of one item: 18
- Max width of one item enabled: true
- Max width of one item: 60
- Max length of one item enabled: true
- Max length of one item: 90
- Default weight: 1kg (applies when product weight is 0)
- Handling fee: 10
- Amount, over which handling fee won't be applied: 50
- Max total of the order: 120.0
- Max total weight of the order enabled: false
- Max total weight of the order: 120.0
- Min total weight of the order enabled: false
- Min total weight the order: 0
- Weights (space separated): 1 2 5 10 20
- Prices (space separated): 6 9 12 15 18
- Price by weight unit?: false
- Zipcode handling:
- Zipcode separator: |
- Zipcode:

Applies?
-------
Expand Down
13 changes: 12 additions & 1 deletion config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,18 @@
max_item_weight: Max weight of one item
max_item_width: Max width of one item
max_item_length: Max length of one item
max_total_weight: Max total weight
handling_fee: Handling fee
handling_max: Amount, over which handling fee won't be applied
min_price: Minimum total of the order
max_price: Maximum total of the order
default_weight: Default weight
zipcode_handling: Zipcode handling (empty field - does not apply, exact, starts, ends, contains)
zipcodes: Zipcode(s)
max_item_weight_enabled: Max weight of one item enabled?
max_item_width_enabled: Max width of one item enabled?
max_item_length_enabled: Max length of one item enabled?
max_total_weight_enabled: Max total weight enabled?
max_price_enabled: Maximum total of the order enabled?
price_table_by_weight_unit: Price by weight unit?
min_total_weight_enabled: Min total weight enabled?
min_total_weight: Min total weight
13 changes: 12 additions & 1 deletion config/locales/fi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,19 @@
max_item_weight: Max weight of one item
max_item_width: Max width of one item
max_item_length: Max length of one item
max_total_weight: Max total weight
handling_fee: Handling fee
handling_max: Amount, over which handling fee won't be applied
min_price: Minimum total of the order
max_price: Maximum total of the order
default_weight: Default weight
zipcode_handling: Zipcode handling (empty field - does not apply, exact, starts, ends, contains)
zipcodes: Zipcode(s)
max_item_weight_enabled: Max weight of one item enabled?
max_item_width_enabled: Max width of one item enabled?
max_item_length_enabled: Max length of one item enabled?
max_total_weight_enabled: Max total weight enabled?
max_price_enabled: Maximum total of the order enabled?
price_table_by_weight_unit: Price by weight unit?
min_total_weight_enabled: Min total weight enabled?
min_total_weight: Min total weight

17 changes: 14 additions & 3 deletions config/locales/pt-PT.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,20 @@
weight_table: Pesos (separados por espaços)
postal_service: Serviço Postal
max_item_weight: Peso máximo por item
max_item_width: Peso máxima por item
max_item_width: Largura máxima por item
max_item_length: Comprimento máximo por item
max_total_weight: Peso total máximo
handling_fee: Taxa de tratamento
handling_max: Valor a partir do qual a taxa de tratamento não é aplicada
min_price: Preço mínimo
default_weight: Peso por omissão
max_price: Preço máximo
default_weight: Peso por omissão
zipcode_handling: Tratamento Código Postal (campo vazio - não activo, exact - exacto, starts - começa por, ends - termina em, contains - contém)
zipcodes: Código(s) Postal(is)
max_item_weight_enabled: Peso máximo por item activo?
max_item_width_enabled: Largura máxima por item activo?
max_item_length_enabled: Comprimento máximo por item activo?
max_total_weight_enabled: Peso total máximo activo?
max_price_enabled: Preço máximo activo?
price_table_by_weight_unit: Preço por unidade de peso?
min_total_weight_enabled: Preço total minímo activo?
min_total_weight: Preço total minímo
93 changes: 86 additions & 7 deletions lib/spree/calculator/postal_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,35 @@
class Spree::Calculator::PostalService < Spree::Calculator
preference :weight_table, :string, :default => "1 2 5 10 20"
preference :price_table, :string, :default => "6 9 12 15 18"
preference :price_table_by_weight_unit, :boolean, :default => false
preference :max_item_weight_enabled, :boolean, :default => true
preference :max_item_weight, :decimal, :default => 18
preference :max_item_width_enabled, :boolean, :default => true
preference :max_item_width, :decimal, :default => 60
preference :max_item_length_enabled, :boolean, :default => true
preference :max_item_length, :decimal, :default => 120
preference :max_total_weight_enabled, :boolean, :default => false
preference :max_total_weight, :decimal, :default => 0
preference :min_total_weight_enabled, :boolean, :default => false
preference :min_total_weight, :decimal, :default => 0
preference :max_price_enabled, :boolean, :default => false
preference :max_price, :decimal, :default => 120
preference :handling_max, :decimal, :default => 50
preference :handling_fee, :decimal, :default => 10
preference :default_weight, :decimal, :default => 1
preference :zipcode_handling, :string, :default => nil
preference :zipcode_separator, :string, :default => '|'
preference :zipcodes, :string, :default => nil

attr_accessible :preferred_weight_table, :preferred_price_table, :preferred_max_item_weight, :preferred_max_item_width, :preferred_max_item_length, :preferred_max_price, :preferred_handling_max, :preferred_handling_fee, :preferred_default_weight
attr_accessible :preferred_weight_table, :preferred_price_table, :preferred_max_item_weight,
:preferred_max_item_width, :preferred_max_item_length, :preferred_max_price,
:preferred_handling_max, :preferred_handling_fee, :preferred_default_weight,
:preferred_zipcode_handling, :preferred_zipcode_separator, :preferred_zipcodes,
:preferred_max_total_weight, :preferred_max_item_weight_enabled,
:preferred_max_item_width_enabled, :preferred_max_item_length_enabled,
:preferred_max_total_weight_enabled, :preferred_max_price_enabled,
:preferred_price_table_by_weight_unit, :preferred_min_total_weight_enabled,
:preferred_min_total_weight

def self.description
"Postal"
Expand All @@ -22,20 +42,71 @@ def self.register
# ShippingMethod.register_calculator(self)
end

def order_total_weight(order)
return @total_weight if @total_weight
@total_weight = 0
order.line_items.each do |item| # determine total price and weight
@total_weight += item.quantity * (item.variant.weight || self.preferred_default_weight)
end
return @total_weight
end

def zipcodes
return @zipcodes if @zipcodes
return [""] if self.preferred_zipcodes.blank? || self.preferred_zipcode_separator.blank?

self.preferred_zipcodes.downcase.split(self.preferred_zipcode_separator)
end

def handle_zipcode?(order)
return true if self.preferred_zipcode_handling.blank?
result = false
zipcodes.each do |zipcode|
if(self.preferred_zipcode_handling == 'exact')
result = zipcode == order.ship_address.zipcode.downcase
end
if(self.preferred_zipcode_handling == 'starts')
result = order.ship_address.zipcode.downcase.start_with?(zipcode)
end
if(self.preferred_zipcode_handling == 'ends')
result = order.ship_address.zipcode.downcase.end_with?(zipcode)
end
if(self.preferred_zipcode_handling == 'contains')
result = order.ship_address.zipcode.downcase.include?(zipcode)
end
break if result
end
return result
end

def item_oversized? item
return false if self.preferred_max_item_length_enabled && self.preferred_max_item_width_enabled == 0
variant = item.variant
sizes = [ variant.width ? variant.width : 0 , variant.depth ? variant.depth : 0 , variant.height ? variant.height : 0 ].sort!
#puts "Sizes " + sizes.join(" ")
return true if sizes[0] > self.preferred_max_item_length
return true if sizes[0] > self.preferred_max_item_width
return true if self.preferred_max_item_length_enabled && sizes[0] > self.preferred_max_item_length
return true if self.preferred_max_item_width_enabled && sizes[0] > self.preferred_max_item_width
return false
end

def total_overweight? order
return false if !self.preferred_max_total_weight_enabled
return order_total_weight(order) > self.preferred_max_total_weight
end

def total_underweight? order
return false if !self.preferred_min_total_weight_enabled
return order_total_weight(order) <= self.preferred_min_total_weight
end

def available?(order)
return false if !handle_zipcode?(order)
order.line_items.each do |item| # determine if weight or size goes over bounds
return false if item.variant.weight and item.variant.weight > self.preferred_max_item_weight
return false if self.preferred_max_item_weight_enabled && item.variant.weight && item.variant.weight > self.preferred_max_item_weight
return false if item_oversized?( item )
end
return false if total_overweight?(order)
return false if total_underweight?(order)
return true
end

Expand All @@ -54,7 +125,7 @@ def compute(order)
end
puts "Weight " + total_weight.to_s if debug
puts "Price " + total_price.to_s if debug
return 0.0 if total_price > self.preferred_max_price
return 0.0 if self.preferred_max_price_enabled && total_price > self.preferred_max_price
# determine handling fee
puts "Handling max " + self.preferred_handling_max.to_s if debug
handling_fee = self.preferred_handling_max < total_price ? 0 : self.preferred_handling_fee
Expand All @@ -63,15 +134,23 @@ def compute(order)
puts weights.join(" ") if debug
while total_weight > weights.last # in several packages if need be
total_weight -= weights.last
shipping += prices.last
if(self.preferred_price_table_by_weight_unit)
shipping += prices.last * weights.last
else
shipping += prices.last
end
end
puts "Shipping " + shipping.to_s if debug
index = weights.length - 2
while index >= 0
break if total_weight > weights[index]
index -= 1
end
shipping += prices[index + 1]
if(self.preferred_price_table_by_weight_unit)
shipping += prices[index + 1] * total_weight
else
shipping += prices[index + 1]
end
puts "Shipping " + shipping.to_s if debug

return shipping + handling_fee
Expand Down