spree_multi_currency 1.0.4 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/.coveralls.yml +0 -0
  2. data/.gitignore +1 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +21 -3
  6. data/README.markdown +8 -0
  7. data/Rakefile +4 -2
  8. data/app/assets/javascripts/admin/spree_multi_currency.js +0 -0
  9. data/app/assets/javascripts/store/spree_multi_currency.js +0 -0
  10. data/app/assets/stylesheets/admin/spree_multi_currency.css +0 -0
  11. data/app/assets/stylesheets/store/spree_multi_currency.css +0 -0
  12. data/app/controllers/spree/admin/currencies_controller.rb +3 -1
  13. data/app/controllers/spree/admin/currency_converters_controller.rb +2 -0
  14. data/app/controllers/spree/base_controller_decorator.rb +5 -2
  15. data/app/controllers/spree/currency_controller.rb +7 -2
  16. data/app/helpers/base_helper_decorator.rb +8 -0
  17. data/app/helpers/number_helper_decorator.rb +19 -17
  18. data/app/models/spree/adjustment_decorator.rb +2 -0
  19. data/app/models/spree/controller_helper_order.rb +11 -0
  20. data/app/models/spree/currency.rb +82 -38
  21. data/app/models/spree/currency_converter.rb +2 -0
  22. data/app/models/spree/line_item_decorator.rb +19 -6
  23. data/app/models/spree/money_decorator.rb +15 -0
  24. data/app/models/spree/order_decorator.rb +63 -55
  25. data/app/models/spree/product_decorator.rb +33 -0
  26. data/app/models/spree/stock/estimator_decorator.rb +18 -0
  27. data/app/models/spree/variant_decorator.rb +93 -16
  28. data/app/overrides/add_currencies_admin_configurations_menu.rb +8 -7
  29. data/app/overrides/add_currency_selection.rb +15 -0
  30. data/app/views/spree/admin/currencies/new.html.erb +4 -4
  31. data/app/views/spree/admin/currency_converters/edit.html.erb +4 -4
  32. data/app/views/spree/admin/currency_converters/new.html.erb +3 -3
  33. data/config/routes.rb +2 -0
  34. data/db/migrate/20101109134351_create_currencies.rb +2 -0
  35. data/db/migrate/20101109134453_create_currency_converters.rb +2 -0
  36. data/db/seeds.rb +2 -0
  37. data/features/step_definitions/product.rb +2 -0
  38. data/features/support/env.rb +2 -0
  39. data/features/support/paths.rb +2 -0
  40. data/lib/generators/spree_multi_currency/install/install_generator.rb +27 -15
  41. data/lib/spree_multi_currency.rb +13 -9
  42. data/lib/spree_multi_currency/engine.rb +6 -0
  43. data/lib/tasks/spree_multi_currency.rake +57 -38
  44. data/spec/changing_currency_spec.rb +44 -15
  45. data/spec/controllers/spree/currency_controller_spec.rb +16 -0
  46. data/spec/features/buy_spec.rb +103 -0
  47. data/spec/helpers/number_helper_spec.rb +21 -0
  48. data/spec/models/spree/currency_spec.rb +32 -0
  49. data/spec/models/spree/variant_spec.rb +32 -0
  50. data/spec/spec_helper.rb +50 -10
  51. data/spree_multi_currency.gemspec +2 -4
  52. metadata +29 -34
  53. data.tar.gz.sig +0 -3
  54. data/Versionfile +0 -9
  55. metadata.gz.sig +0 -1
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Spree
2
4
  class CurrencyConverter < ActiveRecord::Base
3
5
  belongs_to :currency
@@ -1,13 +1,26 @@
1
+ # encoding: utf-8
2
+
1
3
  Spree::LineItem.class_eval do
2
- extend Spree::MultiCurrency
3
- multi_currency :price
4
4
 
5
- def copy_price
6
- self.price = variant.reade_attribute(:price) if variant && price.nil?
5
+ # redefine spree/core/app/models/spree/line_item.rb
6
+ def single_money
7
+ calculate_money(price)
8
+ end
9
+
10
+ # redefine spree/core/app/models/spree/line_item.rb
11
+ def money
12
+ calculate_money(amount)
7
13
  end
8
14
 
9
- def raw_amount
10
- read_attribute(:price) * self.quantity
15
+ def calculate_money(var)
16
+ current_cur = Spree::Currency.current
17
+ var_in_current_currency = Spree::Currency.convert(var,
18
+ currency,
19
+ current_cur.char_code)
20
+ Spree::Money.new(var_in_current_currency, { currency: current_cur })
11
21
  end
12
22
 
23
+ alias display_total money
24
+ alias display_amount money
25
+
13
26
  end
@@ -0,0 +1,15 @@
1
+ Spree::Money.class_eval do
2
+
3
+ def initialize(amount, options = {})
4
+ @money = ::Money.parse([amount, Spree::Currency.current.char_code].join)
5
+ @options = {}
6
+ @options[:with_currency] = Spree::Config[:display_currency]
7
+ @options[:symbol_position] = Spree::Config[:currency_symbol_position].to_sym
8
+ @options[:no_cents] = Spree::Config[:hide_cents]
9
+ @options[:decimal_mark] = Spree::Config[:currency_decimal_mark]
10
+ @options[:thousands_separator] = Spree::Config[:currency_thousands_separator]
11
+ @options.merge!(options)
12
+ # Must be a symbol because the Money gem doesn't do the conversion
13
+ @options[:symbol_position] = @options[:symbol_position].to_sym
14
+ end
15
+ end
@@ -1,83 +1,91 @@
1
+ # encoding: utf-8
2
+
3
+ Spree::OrderContents.class_eval do
4
+
5
+ def add_to_line_item(line_item, variant, quantity, currency = nil, shipment = nil)
6
+ if line_item
7
+ line_item.target_shipment = shipment
8
+ line_item.quantity += quantity.to_i
9
+ line_item.currency = currency unless currency.nil?
10
+ line_item.save
11
+ else
12
+ line_item = Spree::LineItem.new(quantity: quantity)
13
+ line_item.target_shipment = shipment
14
+ line_item.variant = variant
15
+ if currency
16
+ line_item.currency = currency unless currency.nil?
17
+ line_item.price = variant.price_in(currency).amount
18
+ else
19
+ line_item.price = variant.price
20
+ end
21
+ order.line_items << line_item
22
+ line_item
23
+ end
24
+
25
+ order.reload
26
+ line_item
27
+ end
28
+
29
+ end
30
+
1
31
  Spree::Order.class_eval do
2
32
  extend Spree::MultiCurrency
3
33
  multi_currency :item_total, :total,
4
- :rate_at_date => lambda{ |t| t.created_at },
5
- :only_read => true
6
-
34
+ rate_at_date: lambda { |t| t.created_at },
35
+ only_read: true
7
36
 
8
37
  def update_totals
9
38
  # update_adjustments
10
39
  self.payment_total = payments.completed.map(&:amount).sum
11
- self.item_total = line_items.map(&:raw_amount).sum
40
+ self.item_total = line_items.map(&:amount).sum
12
41
  self.adjustment_total = adjustments.map(&:amount).sum
13
42
  self.total = read_attribute(:item_total) + adjustment_total
14
43
  end
15
44
 
45
+ # this will return only the highest shipping cost
46
+ # if the calculator fixed price (per item) was used.
47
+ # not tested with any other calculators
16
48
  def rate_hash
17
- @rate_hash ||= available_shipping_methods(:front_end).collect do |ship_method|
49
+ highest_cost = 0
50
+ available_shipping_methods(:front_end).map do |ship_method|
18
51
  next unless cost = ship_method.calculator.compute(self)
19
- { :id => ship_method.id,
20
- :shipping_method => ship_method,
21
- :name => ship_method.name,
22
- :cost => Spree::Currency.conversion_to_current(cost)
23
- }
24
- end.compact.sort_by{|r| r[:cost]}
52
+ if cost > highest_cost
53
+ highest_cost = cost
54
+ @ship_method = ship_method
55
+ end
56
+ end
57
+ @rate_hash ||= [{ id: @ship_method.id,
58
+ shipping_method: @ship_method,
59
+ name: @ship_method.name,
60
+ cost: highest_cost }]
25
61
  end
26
62
 
27
63
  def update!
28
64
  update_totals
29
- update_payment_state
65
+ updater.update_payment_state
30
66
 
31
67
  # give each of the shipments a chance to update themselves
32
- shipments.each { |shipment| shipment.update!(self) }#(&:update!)
33
- update_shipment_state
34
- update_adjustments
35
- # update totals a second time in case updated adjustments have an effect on the total
68
+ shipments.each { |shipment| shipment.update!(self) }
69
+ updater.update_shipment_state
70
+ updater.update_adjustments
71
+ # update totals a second time in case updated adjustments
72
+ # have an effect on the total
36
73
  update_totals
37
74
  update_attributes_without_callbacks({
38
- :payment_state => payment_state,
39
- :shipment_state => shipment_state,
40
- :item_total => read_attribute(:item_total),
41
- :adjustment_total => adjustment_total,
42
- :payment_total => payment_total,
43
- :total => read_attribute(:total)
75
+ payment_state: payment_state,
76
+ shipment_state: shipment_state,
77
+ item_total: read_attribute(:item_total),
78
+ adjustment_total: adjustment_total,
79
+ payment_total: payment_total,
80
+ total: read_attribute(:total)
44
81
  })
45
82
 
46
- #ensure checkout payment always matches order total
47
- if payment and payment.checkout? and payment.amount != total
48
- payment.update_attributes_without_callbacks(:amount => total)
83
+ # ensure checkout payment always matches order total
84
+ # FIXME implement for partitial payments
85
+ if payment? && payments.first.checkout? && payments.first.amount != total
86
+ payments.first.update_attributes_without_callbacks(amount: total)
49
87
  end
50
88
 
51
89
  update_hooks.each { |hook| self.send hook }
52
90
  end
53
-
54
- def add_variant(variant, quantity = 1)
55
- current_item = contains?(variant)
56
- if current_item
57
- current_item.quantity += quantity
58
- current_item.save
59
- else
60
- current_item = Spree::LineItem.new(:quantity => quantity)
61
- current_item.variant = variant
62
- current_item.price = variant.read_attribute(:price)
63
- self.line_items << current_item
64
- end
65
-
66
- # populate line_items attributes for additional_fields entries
67
- # that have populate => [:line_item]
68
- Spree::Variant.additional_fields.select { |f| !f[:populate].nil? && f[:populate].include?(:line_item) }.each do |field|
69
- value = ''
70
-
71
- if field[:only].nil? || field[:only].include?(:variant)
72
- value = variant.send(field[:name].gsub(' ', '_').downcase)
73
- elsif field[:only].include?(:product)
74
- value = variant.product.send(field[:name].gsub(' ', '_').downcase)
75
- end
76
- current_item.update_attribute(field[:name].gsub(' ', '_').downcase, value)
77
- end
78
-
79
- self.reload
80
- current_item
81
- end
82
-
83
91
  end
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+
3
+ Spree::Product.class_eval do
4
+
5
+ # Can't use add_search_scope for this as it needs a default argument
6
+ def self.available(available_on = nil, currency = nil)
7
+ scope = joins(:master => :prices).where("#{Spree::Product.quoted_table_name}.available_on <= ?", available_on || Time.now)
8
+ unless Spree::Config.show_products_without_price
9
+ # should render product with any not null price
10
+ scope = scope.where('spree_prices.amount IS NOT NULL')
11
+ end
12
+ scope
13
+ end
14
+
15
+ # FIXME may be not require remove it from array
16
+ search_scopes.delete(:available)
17
+ search_scopes << :available
18
+ end
19
+
20
+ Spree::Core::Search::Base.class_eval do
21
+ def retrieve_products
22
+ @products_scope = get_base_scope
23
+ curr_page = page || 1
24
+
25
+ @products = @products_scope.includes([:master => :prices])
26
+ unless Spree::Config.show_products_without_price
27
+ @products = @products.where("spree_prices.amount IS NOT NULL")
28
+ end
29
+ @products = @products.page(curr_page).per(per_page)
30
+ end
31
+
32
+ end
33
+
@@ -0,0 +1,18 @@
1
+ Spree::Stock::Estimator.class_eval do
2
+
3
+ # currency not make sence
4
+ # redefined spree/core/app/models/spree/stock/estimator.rb
5
+ # may be i something miss, but looks like should be
6
+ # deleted shipping method if calculator have NOT currency
7
+ def shipping_methods(package)
8
+ shipping_methods = package.shipping_methods
9
+ shipping_methods.delete_if do |ship_method|
10
+ !ship_method.calculator.available?(package) ||
11
+ !ship_method.include?(order.ship_address) ||
12
+ ship_method.calculator.preferences[:currency].nil?
13
+ end
14
+ shipping_methods
15
+ end
16
+
17
+ end
18
+
@@ -1,22 +1,99 @@
1
+ # encoding: utf-8
2
+
1
3
  Spree::Variant.class_eval do
2
4
  extend Spree::MultiCurrency
3
- multi_currency :price, :cost_price
5
+ multi_currency :cost_price
6
+
7
+ # if save variant - require save prices
8
+ after_save :save_price
9
+
10
+
11
+ # get spree_price for current currency or
12
+ # basic or
13
+ # any other
14
+ def get_price
15
+ char_code = current_char_code
16
+ current_price = prices.where(currency: char_code).first
17
+ if current_price && current_price.amount.present?
18
+ amount = current_price.amount
19
+ return amount
20
+ else
21
+ basic_char = Spree::Currency.basic.try(:char_code)
22
+ basic_price = prices.where(currency: basic_char).first
23
+ if basic_price
24
+ amount = basic_price.amount
25
+ return Spree::Currency.conversion_to_current(amount)
26
+ else
27
+ spree_price = prices.first
28
+ amount = spree_price.amount
29
+ res = Spree::Currency.convert(amount, spree_price.currency, char_code)
30
+ return res
31
+ end
32
+ end
33
+ end
34
+
35
+ # FIXME - may be will used in other classes
36
+ def current_char_code
37
+ Spree::Currency.current.try(:char_code) || Spree::Config[:currency]
38
+ end
39
+
40
+ # prices stored in spree_prices
41
+ def price
42
+ attr = read_attribute(:price)
43
+ if attr.nil? && !new_record?
44
+ get_price
45
+ else
46
+ attr
47
+ end
48
+ end
49
+
50
+ # assign price
51
+ # if new record - save to attribute
52
+ # if saved - create price
53
+ def price=(value)
54
+ write_attribute(:price,value)
55
+ if !new_record?
56
+ cur = current_char_code
57
+ base_price = prices.where(currency: cur).first
58
+ if base_price
59
+ base_price.amount = value
60
+ else
61
+ prices.new(amount: value,currency: cur)
62
+ end
63
+ end
64
+ end
65
+
66
+
67
+ # redefine spree method from spree/core/app/models/spree/variant.rb
4
68
  def price_in(currency)
5
- # will use internal currency, parametr will ignored
6
- currency = Spree::Currency.current
7
- prices.select{ |price| price.currency == currency }.first || Spree::Price.new(:variant_id => self.id, :currency => currency, :amount => self.cost_price)
69
+ if currency.is_a?(String)
70
+ char_code = currency
71
+ else
72
+ char_code = currency.char_code
73
+ end
74
+ res = prices.where( currency: char_code ).first
75
+ if res.blank? || res.amount.nil? || res.amount.to_i == 0
76
+ res = Spree::Price.new(variant_id: self.id,
77
+ currency: currency,
78
+ amount: get_price)
79
+ end
80
+ res
8
81
  end
9
- end
10
82
 
11
- Spree::Money.class_eval do
12
- def initialize(amount, options={})
13
- @money = ::Money.parse([amount, (Spree::Currency.current.char_code)].join)
14
- @options = {}
15
- @options[:with_currency] = true if Spree::Config[:display_currency]
16
- @options[:symbol_position] = Spree::Config[:currency_symbol_position].to_sym
17
- @options[:no_cents] = true if Spree::Config[:hide_cents]
18
- @options.merge!(options)
19
- # Must be a symbol because the Money gem doesn't do the conversion
20
- @options[:symbol_position] = @options[:symbol_position].to_sym
21
- end
83
+ private
84
+
85
+ def save_price
86
+ char_code = current_char_code
87
+ spree_price = self.prices.where(currency: char_code).first
88
+ if spree_price.blank?
89
+ spree_price = self.prices.new(currency: char_code)
90
+ end
91
+ spree_price.amount = read_attribute(:price)
92
+ if spree_price &&
93
+ (spree_price.changed? ||
94
+ spree_price.new_record? ||
95
+ spree_price.amount.present? )
96
+ spree_price.save!
97
+ end
98
+ end
22
99
  end
@@ -1,10 +1,11 @@
1
- Deface::Override.new(:virtual_path => "spree/admin/shared/_configuration_menu",
2
- :name => "currencies_admin_configurations_menu",
3
- :insert_bottom => "ul[data-hook='admin_configurations_sidebar_menu']",
4
- :disabled => false,
5
- :text => "
6
- <% if current_user.has_spree_role?(:admin) %>
1
+ # encoding: utf-8
2
+ Deface::Override.new(virtual_path: "spree/admin/shared/_configuration_menu",
3
+ name: "currencies_admin_configurations_menu",
4
+ insert_bottom: "ul[data-hook='admin_configurations_sidebar_menu']",
5
+ disabled: false,
6
+ text: "
7
+ <% if spree_current_user.has_spree_role?(:admin) %>
7
8
  <%= configurations_sidebar_menu_item t(:currency_settings), admin_currencies_path %>
8
9
  <%= configurations_sidebar_menu_item t(:currency_converters_settings), admin_currency_converters_path %>
9
10
  <% end %>
10
- ")
11
+ ")
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+ Deface::Override.new(virtual_path: "spree/shared/_main_nav_bar",
3
+ name: "currencies_admin_configurations_menu",
4
+ insert_bottom: "ul#main-nav-bar",
5
+ disabled: false,
6
+ text: "
7
+ <li><%= select_tag 'currency', options_for_select(Spree::Currency.all_currencies, Spree::Currency.current.char_code) %></li>
8
+ <script>
9
+ $('#currency').on('change', function(event){
10
+ window.location = '/currency/' + $(this).val();
11
+
12
+ });
13
+ </script>
14
+
15
+ ")
@@ -4,13 +4,13 @@
4
4
 
5
5
  <% content_for :page_actions do %>
6
6
  <li>
7
- <%= button_link_to t("back_to_currencies"), collection_url, :icon => 'icon-arrow-left' %>
7
+ <%= button_link_to t('back_to_currencies'), collection_url, :icon => 'icon-arrow-left' %>
8
8
  </li>
9
9
  <% end %>
10
10
 
11
11
  <%= form_for(:currency, :url => collection_url) do |f| %>
12
12
  <fieldset class="no-border-top">
13
- <%= render :partial => "form", :locals => { :f => f } %>
14
- <%= render :partial => 'spree/admin/shared/new_resource_links' %>
13
+ <%= render 'form', :f => f %>
14
+ <%= render 'spree/admin/shared/new_resource_links' %>
15
15
  </fieldset>
16
- <% end %>
16
+ <% end %>
@@ -4,13 +4,13 @@
4
4
 
5
5
  <% content_for :page_actions do %>
6
6
  <li>
7
- <%= button_link_to t("back_to_currency_converters"), collection_url, :icon => 'icon-arrow-left' %>
7
+ <%= button_link_to t('back_to_currency_converters'), collection_url, :icon => 'icon-arrow-left' %>
8
8
  </li>
9
9
  <% end %>
10
10
 
11
11
  <%= form_for(:currency_converter, :url => object_url, :html => { :method => :put }) do |f| %>
12
12
  <fieldset class="no-border-top">
13
- <%= render :partial => "form", :locals => { :f => f } %>
14
- <%= render :partial => 'spree/admin/shared/edit_resource_links' %>
13
+ <%= render 'form', :f => f %>
14
+ <%= render 'spree/admin/shared/edit_resource_links' %>
15
15
  </fieldset>
16
- <% end %>
16
+ <% end %>