spree_multi_currency 1.0.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 %>