spree_multi_currency 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data.tar.gz.sig +3 -0
  2. data/.gitignore +5 -0
  3. data/Gemfile +11 -0
  4. data/LICENSE +0 -0
  5. data/README.markdown +196 -0
  6. data/Rakefile +28 -0
  7. data/Versionfile +9 -0
  8. data/app/controllers/spree/admin/currencies_controller.rb +11 -0
  9. data/app/controllers/spree/admin/currency_converters_controller.rb +4 -0
  10. data/app/controllers/spree/base_controller_decorator.rb +12 -0
  11. data/app/controllers/spree/currency_controller.rb +16 -0
  12. data/app/helpers/number_helper_decorator.rb +40 -0
  13. data/app/models/spree/adjustment_decorator.rb +4 -0
  14. data/app/models/spree/currency.rb +119 -0
  15. data/app/models/spree/currency_converter.rb +14 -0
  16. data/app/models/spree/line_item_decorator.rb +13 -0
  17. data/app/models/spree/order_decorator.rb +83 -0
  18. data/app/models/spree/variant_decorator.rb +22 -0
  19. data/app/overrides/add_currencies_admin_configurations_menu.rb +10 -0
  20. data/app/views/spree/admin/currencies/_form.html.erb +40 -0
  21. data/app/views/spree/admin/currencies/edit.html.erb +16 -0
  22. data/app/views/spree/admin/currencies/index.html.erb +44 -0
  23. data/app/views/spree/admin/currencies/new.html.erb +16 -0
  24. data/app/views/spree/admin/currency_converters/_form.html.erb +35 -0
  25. data/app/views/spree/admin/currency_converters/edit.html.erb +16 -0
  26. data/app/views/spree/admin/currency_converters/index.html.erb +42 -0
  27. data/app/views/spree/admin/currency_converters/new.html.erb +16 -0
  28. data/config/cucumber.yml +10 -0
  29. data/config/locales/currencies.yml +51 -0
  30. data/config/locales/en_multi_currency.yml +28 -0
  31. data/config/locales/ru_multi_currency.yml +26 -0
  32. data/config/routes.rb +8 -0
  33. data/db/migrate/20101109134351_create_currencies.rb +25 -0
  34. data/db/migrate/20101109134453_create_currency_converters.rb +15 -0
  35. data/db/sample/currencies.yml +1603 -0
  36. data/db/sample/currency_converters.yml +689 -0
  37. data/db/seeds.rb +2 -0
  38. data/features/products.feature +32 -0
  39. data/features/step_definitions/product.rb +124 -0
  40. data/features/step_definitions/ror_ringer.jpeg +0 -0
  41. data/features/support/env.rb +16 -0
  42. data/features/support/paths.rb +42 -0
  43. data/lib/generators/spree_multi_currency/install/install_generator.rb +29 -0
  44. data/lib/spree_multi_currency.rb +40 -0
  45. data/lib/spree_multi_currency/engine.rb +34 -0
  46. data/lib/tasks/spree_multi_currency.rake +99 -0
  47. data/script/rails +5 -0
  48. data/spec/changing_currency_spec.rb +18 -0
  49. data/spec/spec_helper.rb +17 -0
  50. data/spree_multi_currency.gemspec +32 -0
  51. metadata +226 -0
  52. metadata.gz.sig +1 -0
data/db/seeds.rb ADDED
@@ -0,0 +1,2 @@
1
+ # Use this file to load your own seed data from extensions.
2
+ # See the db/seeds.rb file in the Spree core for some ideas on what you can do here.
@@ -0,0 +1,32 @@
1
+ Feature: Visiting products and change currency
2
+
3
+ Background:
4
+ Given the following taxonomies exist:
5
+ | name |
6
+ | Brand |
7
+ | Categories |
8
+ Given the custom taxons and custom products exist
9
+
10
+ Scenario: show page
11
+ When I go to the home page
12
+ When change currency to "usd"
13
+ When I click first link from selector "ul.product-listing a"
14
+ Then I should see "$17.99"
15
+ When change currency to "rub"
16
+ When I click first link from selector "ul.product-listing a"
17
+ Then I should see "494.76 руб."
18
+
19
+ Scenario: make order and change currency between each step
20
+ When I go to the home page
21
+ And I click first link from selector "ul.product-listing a"
22
+ And push "add to cart"
23
+ When click checkout
24
+ When I fill in "order_email" with "test@test.com"
25
+ And I press Continue
26
+ And I fill shipping address with correct data
27
+ And I fill billing address with correct data
28
+ And I press Save and Continue
29
+ And I choose "UPS Ground" as shipping method
30
+ And I enter valid credit card details
31
+ Then I should see "630"
32
+ Then show page
@@ -0,0 +1,124 @@
1
+ Given /^the custom taxons and custom products exist$/ do
2
+ Fixtures.reset_cache
3
+ fixtures_folder = File.join(Rails.root.to_s, 'db', 'sample')
4
+ fixtures = Dir[File.join(fixtures_folder, '*.yml')].map {|f| File.basename(f, '.yml') }
5
+ Fixtures.create_fixtures(fixtures_folder, fixtures)
6
+ =begin
7
+ PaymentMethod.create(
8
+ :name => 'Credit Card',
9
+ :description => 'Bogus payment gateway for development.',
10
+ :environment => 'cucumber',
11
+ :active => true,
12
+ :type => Gateway::Bogus)
13
+ =end
14
+ end
15
+
16
+ Then /^verify products listing for top search result$/ do
17
+ page.all('ul.product-listing li').size.should == 1
18
+ tmp = page.all('ul.product-listing li a').map(&:text).flatten.compact
19
+ tmp.delete("")
20
+ tmp.sort!.should == ["Ruby on Rails Ringer T-shirt $17.99"]
21
+ end
22
+
23
+ Then /^verify products listing for Ruby on Rails brand$/ do
24
+ page.all('ul.product-listing li').size.should == 7
25
+ tmp = page.all('ul.product-listing li a').map(&:text).flatten.compact
26
+ tmp.delete("")
27
+ array = ["Ruby on Rails Bag $22.99",
28
+ "Ruby on Rails Baseball Jersey $19.99",
29
+ "Ruby on Rails Jr. Spaghetti $19.99",
30
+ "Ruby on Rails Mug $13.99",
31
+ "Ruby on Rails Ringer T-shirt $17.99",
32
+ "Ruby on Rails Stein $16.99",
33
+ "Ruby on Rails Tote $15.99"]
34
+ tmp.sort!.should == array
35
+ end
36
+
37
+ Then /^verify products listing for Ruby brand$/ do
38
+ page.all('ul.product-listing li').size.should == 1
39
+ tmp = page.all('ul.product-listing li a').map(&:text).flatten.compact
40
+ tmp.delete("")
41
+ tmp.sort!.should == ["Ruby Baseball Jersey $19.99"]
42
+ end
43
+
44
+ Then /^verify products listing for Apache brand$/ do
45
+ page.all('ul.product-listing li').size.should == 1
46
+ tmp = page.all('ul.product-listing li a').map(&:text).flatten.compact
47
+ tmp.delete("")
48
+ tmp.sort!.should == ["Apache Baseball Jersey $19.99"]
49
+ end
50
+
51
+ Then /^verify products listing for Clothing category$/ do
52
+ page.all('ul.product-listing li').size.should == 5
53
+ tmp = page.all('ul.product-listing li a').map(&:text).flatten.compact
54
+ tmp.delete("")
55
+ tmp.sort!.should == ["Apache Baseball Jersey $19.99",
56
+ "Ruby Baseball Jersey $19.99",
57
+ "Ruby on Rails Baseball Jersey $19.99",
58
+ "Ruby on Rails Jr. Spaghetti $19.99",
59
+ "Ruby on Rails Ringer T-shirt $17.99"]
60
+ end
61
+
62
+ Then /^verify products listing for Bags category$/ do
63
+ page.all('ul.product-listing li').size.should == 2
64
+ tmp = page.all('ul.product-listing li a').map(&:text).flatten.compact
65
+ tmp.delete("")
66
+ tmp.sort!.should == ["Ruby on Rails Bag $22.99", "Ruby on Rails Tote $15.99"]
67
+ end
68
+
69
+ Then /^verify products listing for Mugs category$/ do
70
+ page.all('ul.product-listing li').size.should == 2
71
+ tmp = page.all('ul.product-listing li a').map(&:text).flatten.compact
72
+ tmp.delete("")
73
+ tmp.sort!.should == ["Ruby on Rails Mug $13.99", "Ruby on Rails Stein $16.99"]
74
+ end
75
+
76
+ Then /^verify products listing for price range search 15-18$/ do
77
+ page.all('ul.product-listing li').size.should == 3
78
+ tmp = page.all('ul.product-listing li a').map(&:text).flatten.compact
79
+ tmp.delete("")
80
+ tmp.sort!.should == ["Ruby on Rails Ringer T-shirt $17.99", "Ruby on Rails Stein $16.99", "Ruby on Rails Tote $15.99"]
81
+ end
82
+
83
+ Then /^verify products listing for price range search 18 and above$/ do
84
+ page.all('ul.product-listing li').size.should == 3
85
+ tmp = page.all('ul.product-listing li a').map(&:text).flatten.compact
86
+ tmp.delete("")
87
+ tmp.sort!.should == ["Ruby on Rails Bag $22.99",
88
+ "Ruby on Rails Baseball Jersey $19.99",
89
+ "Ruby on Rails Jr. Spaghetti $19.99"]
90
+ end
91
+
92
+
93
+ Then /^I should get a "(\d+) ([^"]+)" response$/ do |http_status, message|
94
+ #response.status.should == "#{http_status} #{message}" # webrat
95
+ page.driver.status_code.should == http_status.to_i # capybara
96
+ end
97
+
98
+ When /^change currency to "([^"]*)"$/ do |arg1|
99
+ visit "/currency/#{arg1}"
100
+ end
101
+
102
+ Then /^show page$/ do
103
+ save_and_open_page
104
+ end
105
+
106
+ When /^push "add to cart"$/ do
107
+ page.find(:xpath, "//button").click
108
+ end
109
+
110
+ When /^click checkout$/ do
111
+ page.find(:xpath,"//a[@href='/checkout']").click
112
+ end
113
+
114
+ When /^I press Continue$/ do
115
+ page.find(:xpath,"//input[@value='Continue']").click
116
+ end
117
+
118
+ When /^I press Save and Continue$/ do
119
+ page.find(:xpath,"//input[@value='Save and Continue']").click
120
+ end
121
+
122
+ Then /^sleep$/ do
123
+ sleep 100
124
+ end
@@ -0,0 +1,16 @@
1
+ FEATURES_PATH = File.expand_path('../..', __FILE__)
2
+ # require define path to spree project
3
+ ENV['SPREE_GEM_PATH'] = "/home/dima/project/spree"
4
+ # or define spree as gem in Gemfile
5
+ # and decomment this
6
+ # gemfile = Pathname.new("Gemfile").expand_path
7
+ # lockfile = gemfile.dirname.join('Gemfile.lock')
8
+ # definition = Bundler::Definition.build(gemfile, lockfile, nil)
9
+ # sc=definition.index.search "spree"
10
+ # ENV['SPREE_GEM_PATH'] = sc[0].loaded_from.gsub(/\/[a-z_]*.gemspec$/,'')
11
+
12
+
13
+ # load shared env with features
14
+ puts "#{ENV['SPREE_GEM_PATH']}/features/support/env"
15
+ require File.expand_path("#{ENV['SPREE_GEM_PATH']}/features/support/env", __FILE__)
16
+ Capybara.default_driver = :selenium
@@ -0,0 +1,42 @@
1
+ module NavigationHelpers
2
+ # Maps a name to a path. Used by the
3
+ #
4
+ # When /^I go to (.+)$/ do |page_name|
5
+ #
6
+ # step definition in web_steps.rb
7
+ #
8
+ def path_to(page_name)
9
+ case page_name
10
+
11
+ when /the home\s?page/
12
+ '/'
13
+ when /the admin home page/
14
+ admin_path
15
+ when /the sign in page/
16
+ new_user_session_path
17
+ when /the sign up page/
18
+ new_user_registration_path
19
+ when /an invalid taxon page/
20
+ "/t/totally_bogus_taxon"
21
+
22
+
23
+ # Add more mappings here.
24
+ # Here is an example that pulls values out of the Regexp:
25
+ #
26
+ # when /^(.*)'s profile page$/i
27
+ # user_profile_path(User.find_by_login($1))
28
+
29
+ else
30
+ begin
31
+ page_name =~ /the (.*) page/
32
+ path_components = $1.split(/\s+/)
33
+ self.send(path_components.push('path').join('_').to_sym)
34
+ rescue Object => e
35
+ raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
36
+ "Now, go and add a mapping in #{__FILE__}"
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ World(NavigationHelpers)
@@ -0,0 +1,29 @@
1
+ module SpreeMultiCurrency
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+
5
+ # def add_javascripts
6
+ # append_file "app/assets/javascripts/store/all.js", "//= require store/spree_multi_lingual\n"
7
+ # append_file "app/assets/javascripts/admin/all.js", "//= require admin/spree_multi_lingual\n"
8
+ # end
9
+ #
10
+ # def add_stylesheets
11
+ # inject_into_file "app/assets/stylesheets/store/all.css", " *= require store/spree_multi_lingual\n", :before => /\*\//, :verbose => true
12
+ # inject_into_file "app/assets/stylesheets/admin/all.css", " *= require admin/spree_multi_lingual\n", :before => /\*\//, :verbose => true
13
+ # end
14
+
15
+ def add_migrations
16
+ run 'bundle exec rake railties:install:migrations FROM=spree_multi_currency'
17
+ end
18
+
19
+ def run_migrations
20
+ res = ask "Would you like to run the migrations now? [Y/n]"
21
+ if res == "" || res.downcase == "y"
22
+ run 'bundle exec rake db:migrate'
23
+ else
24
+ puts "Skipping rake db:migrate, don't forget to run it!"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,40 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'spree/core'
3
+ require 'spree_multi_currency/engine'
4
+ module Spree::MultiCurrency
5
+
6
+ # Order.class_eval do
7
+ # extend MultiCurrency
8
+ # multi_currency :item_total, :total,
9
+ # :rate_at_date => lambda{ |t| t.created_at },
10
+ # :only_read => true
11
+ # only_read - define just the getter method (and not the setter)
12
+ # rate_at_date - use the exchange rate at the specified date
13
+ def multi_currency(*args)
14
+ options = args.extract_options!
15
+ [args].flatten.compact.each do |number_field|
16
+ define_method(number_field.to_sym) do
17
+ if options.has_key?(:rate_at_date) && options[:rate_at_date].is_a?(Proc)
18
+ Spree::Currency.conversion_to_current(
19
+ read_attribute(number_field.to_sym),
20
+ { :date => options[:rate_at_date].call(self) }
21
+ )
22
+ else
23
+ Spree::Currency.conversion_to_current(read_attribute(number_field.to_sym))
24
+ end
25
+ end
26
+
27
+ define_method("base_#{number_field}") do
28
+ read_attribute(number_field.to_sym)
29
+ end
30
+
31
+ unless options[:only_read]
32
+ define_method(:"#{number_field}=") do |value|
33
+ write_attribute(number_field.to_sym, Spree::Currency.conversion_from_current(value))
34
+ end
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+
@@ -0,0 +1,34 @@
1
+ module SpreeMultiCurrency
2
+ # mattr_reader :languages
3
+ # @@languages = [:en]
4
+ #
5
+ # def self.languages=(locales=[])
6
+ # I18n.available_locales = locales
7
+ # @@languages = locales
8
+ # end
9
+
10
+ class Engine < Rails::Engine
11
+ engine_name 'spree_multi_currency'
12
+
13
+ config.autoload_paths += %W(#{config.root}/lib)
14
+
15
+ # use rspec for tests
16
+ config.generators do |g|
17
+ g.test_framework :rspec
18
+ end
19
+
20
+ def self.activate
21
+ Dir.glob(File.join(File.dirname(__FILE__), "../../app/**/*_decorator*.rb")) do |c|
22
+ Rails.configuration.cache_classes ? require(c) : load(c)
23
+ end
24
+ end
25
+
26
+ # after rails config/initializers loading, setup spree_multi_lingual's language by getting
27
+ # I18n.available_locales but it returns only [:en]
28
+ # initializer "spree_multi_lingual.environment", :after => :load_config_initializers do |app|
29
+ # SpreeMultiLingual.languages = I18n.available_locales
30
+ # end
31
+
32
+ config.to_prepare &method(:activate).to_proc
33
+ end
34
+ end
@@ -0,0 +1,99 @@
1
+ # encoding: utf-8
2
+
3
+ require 'open-uri'
4
+ require 'nokogiri'
5
+ # add custom rake tasks here
6
+ namespace :spree_multi_currency do
7
+
8
+ namespace :currency do
9
+ desc "Общероссийский классификатор валют (сокращ. ОКВ) - http://ru.wikipedia.org/wiki/Общероссийский_классификатор_валют"
10
+
11
+ task :okv => :environment do
12
+ url = "http://ru.wikipedia.org/wiki/%D0%9E%D0%B1%D1%89%D0%B5%D1%80%D0%BE%D1%81%D1%81%D0%B8%D0%B9%D1%81%D0%BA%D0%B8%D0%B9_%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%82%D0%BE%D1%80_%D0%B2%D0%B0%D0%BB%D1%8E%D1%82"
13
+ data = Nokogiri::HTML.parse(open(url))
14
+ keys = [:char_code, :num_code, :discharge, :name, :countries ]
15
+ data.css("table:first tr")[1..-1].map{ |d|
16
+ Hash[*keys.zip(d.css("td").map {|x| x.text.strip }).flatten]
17
+ }.each { |n|
18
+ Spree::Currency.find_by_num_code(n[:num_code]) || Spree::Currency.create(n.except(:discharge).except(:countries))
19
+ }
20
+
21
+ end
22
+
23
+ desc "Load currency ISO4217 http://en.wikipedia.org/wiki/ISO_4217"
24
+ task :iso4217 => :environment do
25
+ url = "http://en.wikipedia.org/wiki/ISO_4217"
26
+ data = Nokogiri::HTML.parse(open(url))
27
+ keys = [:char_code, :num_code, :discharge, :name, :countries ]
28
+ data.css("table:eq(2) tr")[1..-1].map{|d|
29
+ Hash[*keys.zip(d.css("td").map {|x| x.text.strip }).flatten]
30
+ }.each { |n|
31
+ Spree::Currency.find_by_num_code(n[:num_code]) || Spree::Currency.create(n.except(:discharge).except(:countries))
32
+ }
33
+ end
34
+
35
+ end
36
+
37
+ namespace :rates do
38
+ desc "Курс Сбербанка РФ http://www.cbr.ru"
39
+ task :cbr => :environment do
40
+ rub = Spree::Currency.get("643", { :num_code => "643", :char_code => "RUB", :name => "Российский рубль"})
41
+ rub.basic!
42
+ url = "http://www.cbr.ru/scripts/XML_daily.asp?date_req=#{Time.now.strftime('%d/%m/%Y')}"
43
+ data = Nokogiri::XML.parse(open(url))
44
+ date_str = data.xpath("//ValCurs").attr("Date").to_s
45
+ date = Date.strptime(date_str, (date_str =~ /\./ ? '%d.%m.%Y' : '%d/%m/%y'))
46
+ data.xpath("//ValCurs/Valute").each do |valute|
47
+ char_code = valute.xpath("./CharCode").text.to_s
48
+ num_code = valute.xpath("./NumCode").text.to_s
49
+ name = valute.xpath("./Name").text.to_s
50
+ value = valute.xpath("./Value").text.gsub(',','.').to_f
51
+ nominal = valute.xpath("./Nominal").text
52
+ currency = Spree::Currency.get(num_code, { :num_code => num_code, :char_code => char_code, :name => name})
53
+ currency && Spree::CurrencyConverter.add(currency, date, value, nominal)
54
+ end
55
+ end
56
+
57
+ desc "Rates from European Central Bank"
58
+ task :ecb, [:load_currencies] => :environment do |t, args|
59
+ Rake::Task["spree_multi_currency:currency:iso4217"].invoke if args.load_currencies
60
+ euro = Spree::Currency.get("978", { :num_code => "978", :char_code => "EUR", :name => "Euro"})
61
+ euro.basic!
62
+ url = 'http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml'
63
+ data = Nokogiri::XML.parse(open(url))
64
+ date = Date.strptime(data.xpath('gesmes:Envelope/xmlns:Cube/xmlns:Cube').attr("time").to_s, "%Y-%m-%d")
65
+ data.xpath('gesmes:Envelope/xmlns:Cube/xmlns:Cube//xmlns:Cube').each do |exchange_rate|
66
+ char_code = exchange_rate.attribute("currency").value.to_s.strip
67
+ nominal, value = exchange_rate.attribute("rate").value.to_f, 1
68
+ currency = Spree::Currency.find_by_char_code(char_code)
69
+ currency && Spree::CurrencyConverter.add(currency, date, value, nominal)
70
+ end
71
+
72
+ end
73
+
74
+ desc "Rates from Google"
75
+ task :google, [:currency, :load_currencies] => :environment do |t, args|
76
+ Rake::Task["spree_multi_currency:currency:iso4217"].invoke if args.load_currencies
77
+ default_currency = Spree::Currency.where("char_code = :currency_code or num_code = :currency_code", :currency_code => args.currency.upcase || 978).first ||
78
+ Spree::Currency.get("978", { :num_code => "978", :char_code => "EUR", :name => "Euro"})
79
+ default_currency.basic!
80
+ date = Time.now
81
+ puts "Loads currency data from Google using #{default_currency}"
82
+ Spree::Currency.all.each do |currency|
83
+ unless currency == default_currency
84
+ url = "http://www.google.com/ig/calculator?hl=en&q=1#{ currency.char_code }%3D%3F#{ default_currency.char_code }"
85
+ puts url
86
+ @data = JSON.parse(open(url).read.gsub(/lhs:|rhs:|error:|icc:/){ |x| "\"#{x[0..-2]}\":"})
87
+ if @data["error"].blank?
88
+ @value = BigDecimal(@data["rhs"].split(' ')[0])
89
+ Spree::CurrencyConverter.add(currency, date, @value, 1)
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+
97
+
98
+ end
99
+
data/script/rails ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ ENGINE_PATH = File.expand_path('../..', __FILE__)
5
+ load File.expand_path('../../spec/dummy/script/rails', __FILE__)
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Currencies changing" do
4
+
5
+ it "changes price when changing locale" do
6
+ rub = Spree::Currency.create(:name => "rubles", :char_code => "RUB", :num_code => 623, :locale => "ru", :basic => false)
7
+ usd = Spree::Currency.create(:name => "dollars", :char_code => "USD", :num_code => 624, :locale => "en", :basic => true)
8
+ Spree::CurrencyConverter.create(:nominal => 32, :value => 1.0, :currency => rub, :date_req => Time.now)
9
+ product = FactoryGirl.create(:product, :price => 1)
10
+ variant = Spree::Variant.last
11
+ I18n.locale = "en"
12
+ variant.price.should eql 1.0
13
+ I18n.locale = "ru"
14
+ Spree::Currency.current!
15
+ Spree::Currency.current.should eql rub
16
+ variant.price.to_f.should eql 32.0
17
+ end
18
+ end