multi_currencies 1.0.0 → 1.0.2
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.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.markdown +77 -0
- data/Rakefile +2 -0
- data/app/controllers/currency_controller.rb +14 -0
- data/app/controllers/spree/base_controller_decorator.rb +12 -0
- data/app/decorators/line_item_decorator.rb +9 -0
- data/app/decorators/order_decorator.rb +45 -0
- data/app/decorators/variant_decorator.rb +4 -0
- data/app/helpers/number_helper_decorator.rb +41 -0
- data/app/models/currency.rb +75 -22
- data/app/views/admin/currencies/_form.html.erb +1 -1
- data/app/views/admin/currencies/index.html.erb +1 -1
- data/app/views/admin/currency_converters/_form.html.erb +1 -1
- data/config/locales/currency_rub.yml +10 -0
- data/config/locales/currency_usd.yml +21 -0
- data/config/locales/en_multi_currency.yml +26 -0
- data/config/locales/ru_multi_currency.yml +26 -0
- data/config/routes.rb +8 -0
- data/db/migrate/20101109134351_create_currencies.rb +24 -0
- data/db/migrate/20101109134453_create_currency_converters.rb +15 -0
- data/db/seeds.rb +2 -0
- data/lib/multi_currencies.rb +0 -57
- data/lib/multi_currency.rb +33 -0
- data/lib/tasks/multi_currencies.rake +90 -14
- data/multi_currencies.gemspec +31 -0
- data/spec/spec_helper.rb +31 -0
- metadata +97 -30
- data/README.md +0 -13
- data/app/helpers/admin/currencies_helper.rb +0 -2
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
= Multi Currency
|
2
|
+
|
3
|
+
Support different currency and recalculate price from one to another
|
4
|
+
===========================================
|
5
|
+
Installation
|
6
|
+
---------
|
7
|
+
Add to Gemfile
|
8
|
+
gem "multi_currencies", :git => "git://github.com/pronix/spree-multi-currency.git"
|
9
|
+
|
10
|
+
Run
|
11
|
+
---
|
12
|
+
rake multi_currencies:install:migrations
|
13
|
+
rake db:migrate
|
14
|
+
|
15
|
+
Load currencies:
|
16
|
+
---------------
|
17
|
+
rake multi_currencies:currency:iso4217 # Load currency ISO4217 http://en.wikipedia.org/wiki/ISO_4217
|
18
|
+
rake multi_currencies:currency:okv # Общероссийский классификатор валют...
|
19
|
+
|
20
|
+
Load rates:
|
21
|
+
----------
|
22
|
+
rake multi_currencies:rates:cbr # Курс Сбербанка РФ http://www.cbr.ru
|
23
|
+
rake "multi_currencies:rates:ecb[load_currencies]" # Rates from European Central Bank
|
24
|
+
rake "multi_currencies:rates:google[currency,load_currencies]" # Rates from Google
|
25
|
+
|
26
|
+
|
27
|
+
Settings
|
28
|
+
---------
|
29
|
+
In admin block, configuration menu add two tables currency and currency conversion rate
|
30
|
+
In reference currency enters the list of currencies, indicate if one of the major currencies (in the currency keeps all prices). Each currency assign corresponding locale.
|
31
|
+
In Exchange Rates, provides information on the price of the currency on a specified date to the basic currency(from russian central bank).
|
32
|
+
In the exchange rates set date, currency, and face value of the currency in the base currency.
|
33
|
+
To fill in the exchange rate, you can use task for download exchange rates from the site of the Central Bank (http://www.cbr.ru):
|
34
|
+
rake multi_currencies:rates:cbr, as in this problem, loading the list of currencies.
|
35
|
+
|
36
|
+
В справочнике Валюты заносим список валют, указываем одну из валют основной (в этой валюте хранятся все цены). Каждой валюте назначаем соответствующую локаль.
|
37
|
+
В справчнике Курсы валют, содержиться информация о цене валюты на определенную дату к основной валюте.
|
38
|
+
В курсе валют указываеться дата, валюта, номинал и стоимость валюты в основной валюте.
|
39
|
+
Для заполнения курса валют, можно воспользоватьбся задачей загрузки курса валют с сайта ЦБ(http://www.cbr.ru):
|
40
|
+
rake multi_currencies:rates:cbr, так же в этой задаче идет загрузка списка валют.
|
41
|
+
|
42
|
+
Смена валюты
|
43
|
+
-------------
|
44
|
+
По умолчанию валюта выбирается от текущей локали сайта.
|
45
|
+
Так же можно сменить локаль по адресу http://[domain]/currency/[isocode], <%= link_to "eur", currency_path(:eur) %>
|
46
|
+
|
47
|
+
isocode: eur, usd, rub (цифровой код прописанные в справочнике валюты)
|
48
|
+
После смены валюты через url перестает работать смена валюты на основание текущей локали.
|
49
|
+
|
50
|
+
Формат вывода валюты
|
51
|
+
--------------------
|
52
|
+
Формат для валюты прописан в локализации, для каждой валюты нужно описать свою локализацию (прописаны eur, usd, rub):
|
53
|
+
Пример для usd, eur
|
54
|
+
---
|
55
|
+
currency_USD: &usd
|
56
|
+
number:
|
57
|
+
currency:
|
58
|
+
format:
|
59
|
+
format: "%u%n"
|
60
|
+
unit: "$"
|
61
|
+
separator: "."
|
62
|
+
delimiter: ","
|
63
|
+
precision: 2
|
64
|
+
significant: false
|
65
|
+
strip_insignificant_zeros: false
|
66
|
+
|
67
|
+
currency_EUR:
|
68
|
+
<<: *usd
|
69
|
+
number:
|
70
|
+
currency:
|
71
|
+
format:
|
72
|
+
format: "%u%n"
|
73
|
+
unit: "€"
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
class CurrencyController < Spree::BaseController
|
2
|
+
|
3
|
+
def set
|
4
|
+
if @currency = Currency.find_by_char_code(params[:id].to_s.upcase)
|
5
|
+
session[:currency_id] = params[:id].to_s.upcase.to_sym
|
6
|
+
Currency.current!(@currency)
|
7
|
+
flash.notice = t(:currency_changed)
|
8
|
+
else
|
9
|
+
flash[:error] = t(:currency_not_found)
|
10
|
+
end
|
11
|
+
|
12
|
+
redirect_back_or_default(root_path)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
Order.class_eval do
|
2
|
+
extend MultiCurrency
|
3
|
+
multi_currency :item_total, :total,
|
4
|
+
:rate_at_date => lambda{ |t| t.created_at },
|
5
|
+
:only_read => true
|
6
|
+
|
7
|
+
|
8
|
+
def update_totals
|
9
|
+
# update_adjustments
|
10
|
+
self.payment_total = payments.completed.map(&:amount).sum
|
11
|
+
self.item_total = line_items.map(&:raw_amount).sum
|
12
|
+
self.adjustment_total = adjustments.map(&:amount).sum
|
13
|
+
self.total = read_attribute(:item_total) + adjustment_total
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def update!
|
18
|
+
update_totals
|
19
|
+
update_payment_state
|
20
|
+
|
21
|
+
# give each of the shipments a chance to update themselves
|
22
|
+
shipments.each { |shipment| shipment.update!(self) }#(&:update!)
|
23
|
+
update_shipment_state
|
24
|
+
update_adjustments
|
25
|
+
# update totals a second time in case updated adjustments have an effect on the total
|
26
|
+
update_totals
|
27
|
+
update_attributes_without_callbacks({
|
28
|
+
:payment_state => payment_state,
|
29
|
+
:shipment_state => shipment_state,
|
30
|
+
:item_total => read_attribute(:item_total),
|
31
|
+
:adjustment_total => adjustment_total,
|
32
|
+
:payment_total => payment_total,
|
33
|
+
:total => read_attribute(:total)
|
34
|
+
})
|
35
|
+
|
36
|
+
#ensure checkout payment always matches order total
|
37
|
+
if payment and payment.checkout? and payment.amount != total
|
38
|
+
payment.update_attributes_without_callbacks(:amount => total)
|
39
|
+
end
|
40
|
+
|
41
|
+
update_hooks.each { |hook| self.send hook }
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ActionView
|
2
|
+
module Helpers
|
3
|
+
module NumberHelper
|
4
|
+
|
5
|
+
def number_to_currency(number, options = {})
|
6
|
+
return nil if number.nil?
|
7
|
+
|
8
|
+
options.symbolize_keys!
|
9
|
+
options[:locale] = "currency_#{Currency.current.char_code}"
|
10
|
+
defaults = I18n.translate('number.format', :locale => options[:locale], :default => {})
|
11
|
+
currency = I18n.translate('number.currency.format', :locale => options[:locale], :default => {})
|
12
|
+
defaults = DEFAULT_CURRENCY_VALUES.merge(defaults).merge!(currency)
|
13
|
+
defaults[:negative_format] = "-" + options[:format] if options[:format]
|
14
|
+
options = defaults.merge!(options)
|
15
|
+
|
16
|
+
unit = options.delete(:unit)
|
17
|
+
format = options.delete(:format)
|
18
|
+
|
19
|
+
if number.to_f < 0
|
20
|
+
format = options.delete(:negative_format)
|
21
|
+
number = number.respond_to?("abs") ? number.abs : number.sub(/^-/, '')
|
22
|
+
end
|
23
|
+
|
24
|
+
begin
|
25
|
+
value = number_with_precision(number, options.merge(:raise => true))
|
26
|
+
format.gsub(/%n/, value).gsub(/%u/, unit).html_safe
|
27
|
+
rescue InvalidNumberError => e
|
28
|
+
if options[:raise]
|
29
|
+
raise
|
30
|
+
else
|
31
|
+
formatted_number = format.gsub(/%n/, e.number).gsub(/%u/, unit)
|
32
|
+
e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/app/models/currency.rb
CHANGED
@@ -1,43 +1,96 @@
|
|
1
|
+
require "money"
|
1
2
|
class Currency < ActiveRecord::Base
|
2
|
-
|
3
|
+
|
4
|
+
has_many :currency_converters do
|
5
|
+
def get_rate(date)
|
6
|
+
last(:conditions => ["date_req <= ?", date])
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
default_scope :order => "currencies.locale"
|
11
|
+
scope :locale, lambda{|str| where("locale like ?", "%#{str}%")}
|
12
|
+
after_save :reset_basic_currency
|
13
|
+
|
14
|
+
def basic!
|
15
|
+
self.class.update_all(:basic => false) && update_attribute(:basic, true)
|
16
|
+
end
|
17
|
+
|
18
|
+
def locale=(locales)
|
19
|
+
write_attribute(:locale, [locales].flatten.compact.join(','))
|
20
|
+
end
|
21
|
+
|
22
|
+
def locale(need_split = true)
|
23
|
+
need_split ? read_attribute(:locale).to_s.split(',') : read_attribute(:locale).to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
# Сбрасываем для всех валют флаг "основная", кроме текущей если она установлена как основная
|
27
|
+
#
|
28
|
+
def reset_basic_currency
|
29
|
+
self.class.where("id != ?", self.id).update_all(:basic => false) if self.basic?
|
30
|
+
end
|
31
|
+
|
3
32
|
class << self
|
4
|
-
|
5
|
-
|
6
|
-
|
33
|
+
|
34
|
+
# Текущая валюта
|
35
|
+
#
|
36
|
+
def current(current_locale = I18n.locale )
|
37
|
+
@current ||= locale(current_locale).first
|
38
|
+
@current
|
39
|
+
end
|
40
|
+
|
41
|
+
def current!(current_locale = nil )
|
42
|
+
@current = current_locale.is_a?(Currency) ? current_locale : locale(current_locale||I18n.locale).first
|
43
|
+
end
|
44
|
+
|
45
|
+
def load_rate(options= {})
|
46
|
+
current(options[:locale] || I18n.locale)
|
47
|
+
basic
|
48
|
+
|
49
|
+
if @rate = @current.currency_converters.get_rate(options[:date] || Time.now)
|
50
|
+
add_rate(@basic.char_code, @current.char_code, @rate.nominal/@rate.value.to_f)
|
51
|
+
add_rate(@current.char_code, @basic.char_code, @rate.value.to_f)
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
def convert(value, from, to)
|
57
|
+
( Money.new(value.to_f * 10000, from).exchange_to(to).to_f / 100).round(2)
|
7
58
|
end
|
8
59
|
|
9
60
|
# Конвертируем сумму к валюте текущей локале
|
10
61
|
# Если валюта или локаль не найдена то возвращается та же сумма
|
11
|
-
#
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
current_currency = current(_locale).currency_converters.last(:conditions => ["date_req <= ?", _date])
|
17
|
-
return price if current_currency.blank?
|
18
|
-
(price / current_currency.value.to_f) * current_currency.nominal.to_i
|
62
|
+
# Money.new(value.to_f, "Основаня").exchange_to("К Текущей").to_f
|
63
|
+
#Currency.conversion_to_current(100, :locale => "ru")
|
64
|
+
def conversion_to_current(value, options = { })
|
65
|
+
load_rate(options)
|
66
|
+
convert(value, @basic.char_code, @current.char_code)
|
19
67
|
end
|
20
68
|
|
21
69
|
# Конвертируем значение из валюты текущей локали к основной валюте
|
22
70
|
# в параметрах можно указать локаль из которой можно делать конвертацияю :locale
|
23
71
|
# и дату курса :date, курс берется последний найденный до указанной даты
|
24
|
-
#
|
72
|
+
#Currency.conversion_from_current(100, :locale => "ru")
|
25
73
|
def conversion_from_current(value, options={})
|
26
|
-
|
27
|
-
|
28
|
-
return value if current(_locale).nil? || current(_locale).basic?
|
29
|
-
current_currency = current(_locale).currency_converters.last(:conditions => ["date_req <= ?", _date])
|
30
|
-
return value if current_currency.blank?
|
31
|
-
return (value/current_currency.nominal.to_f)*current_currency.value
|
74
|
+
load_rate(options)
|
75
|
+
convert(value, @current.char_code, @basic.char_code)
|
32
76
|
end
|
33
77
|
|
78
|
+
|
34
79
|
# Основная валюта
|
35
80
|
def basic
|
36
|
-
first(:conditions => { :basic => true })
|
37
|
-
|
81
|
+
@basic ||= first(:conditions => { :basic => true })
|
82
|
+
end
|
38
83
|
|
39
84
|
def get(num_code, options ={ })
|
40
85
|
find_by_num_code(num_code) || create(options)
|
41
86
|
end
|
42
|
-
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def add_rate(from, to, rate)
|
91
|
+
Money.add_rate(from, to, rate.to_f ) unless Money.default_bank.get_rate(from, to)
|
92
|
+
end
|
93
|
+
|
94
|
+
end # end class << self
|
95
|
+
|
43
96
|
end
|
@@ -32,7 +32,7 @@
|
|
32
32
|
<td><%= t("currency_locale") %>: </td>
|
33
33
|
<td>
|
34
34
|
<%= error_message_on :currency, :locale %>
|
35
|
-
<%= f.select :locale, I18n.available_locales.map{|x| [x,x] } %>
|
35
|
+
<%= f.select :locale, I18n.available_locales.map{|x| [x,x] }, {}, { :multiple => true, :size => 10 } %>
|
36
36
|
</td>
|
37
37
|
</tr>
|
38
38
|
|
@@ -0,0 +1,21 @@
|
|
1
|
+
---
|
2
|
+
currency_USD: &usd
|
3
|
+
number:
|
4
|
+
currency:
|
5
|
+
format:
|
6
|
+
format: "%u%n"
|
7
|
+
unit: "$"
|
8
|
+
separator: "."
|
9
|
+
delimiter: ","
|
10
|
+
precision: 2
|
11
|
+
significant: false
|
12
|
+
strip_insignificant_zeros: false
|
13
|
+
|
14
|
+
currency_EUR:
|
15
|
+
<<: *usd
|
16
|
+
number:
|
17
|
+
currency:
|
18
|
+
format:
|
19
|
+
format: "%u%n"
|
20
|
+
unit: "€"
|
21
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
---
|
2
|
+
en:
|
3
|
+
currency_changed: Currency changed
|
4
|
+
currency_not_found: Currency not found
|
5
|
+
currency_settings: Currency Settings
|
6
|
+
currency_converters_settings: Currency Converters
|
7
|
+
currency_description: Reference Currency
|
8
|
+
currency_converters_description: Currency Exchange Rates
|
9
|
+
add_currency_converter: Add currency exchange rate
|
10
|
+
currency_converter: Currency Converter
|
11
|
+
currency_converter_date_req: Date
|
12
|
+
currency_converter_currency: Currency
|
13
|
+
currency_converter_nominal: Nominal
|
14
|
+
currency_converter_value: Value
|
15
|
+
new_currency_converter: New
|
16
|
+
editing_currency_converter: Edit
|
17
|
+
add_currency: Add currency
|
18
|
+
list_currency: List of currencies
|
19
|
+
currency_name: Currency
|
20
|
+
currency_code: Code
|
21
|
+
currency_num_code: ISO number code
|
22
|
+
currency_char_code: ISO char code
|
23
|
+
currency_locale: Locale
|
24
|
+
currency_basic: Basic currency
|
25
|
+
editing_currency: Edit
|
26
|
+
new_currency: Add
|
@@ -0,0 +1,26 @@
|
|
1
|
+
---
|
2
|
+
ru:
|
3
|
+
currency_changed: Валюта изменена
|
4
|
+
currency_not_found: Валюта не найдена
|
5
|
+
currency_settings: Валюты
|
6
|
+
currency_converters_settings: Курсы валют
|
7
|
+
currency_description: Настройка справочника валют
|
8
|
+
currency_converters_description: Настройка справочника курса валют
|
9
|
+
add_currency_converter: Добавить курс
|
10
|
+
currency_converter: Курсы валют
|
11
|
+
currency_converter_date_req: Дата
|
12
|
+
currency_converter_currency: Валюта
|
13
|
+
currency_converter_nominal: Номинал
|
14
|
+
currency_converter_value: Значение
|
15
|
+
new_currency_converter: Новый курс валюты
|
16
|
+
editing_currency_converter: Редактирование курса валюты
|
17
|
+
add_currency: Добавить валюту
|
18
|
+
list_currency: Список валют
|
19
|
+
currency_name: Валюта
|
20
|
+
currency_code: Код
|
21
|
+
currency_num_code: Цифровой код
|
22
|
+
currency_char_code: Строковый код
|
23
|
+
currency_locale: Локаль
|
24
|
+
currency_basic: Основная
|
25
|
+
editing_currency: Редактирование валюты
|
26
|
+
new_currency: Добавление валюты
|
data/config/routes.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
class CreateCurrencies < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :currencies do |t|
|
4
|
+
t.string :num_code, :null => false
|
5
|
+
t.string :char_code, :null => false
|
6
|
+
t.string :name, :null => false
|
7
|
+
t.boolean :basic, :default => false
|
8
|
+
t.string :locale
|
9
|
+
t.timestamps
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.down
|
14
|
+
drop_table :currencies
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# <Valute ID="R01235">
|
19
|
+
# <NumCode>840</NumCode>
|
20
|
+
# <CharCode>USD</CharCode>
|
21
|
+
# <Nominal>1</Nominal>
|
22
|
+
# <Name>Доллар США</Name>
|
23
|
+
# <Value>30,8612</Value>
|
24
|
+
# </Valute>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreateCurrencyConverters < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :currency_converters do |t|
|
4
|
+
t.integer :currency_id, :null => false
|
5
|
+
t.datetime :date_req, :null => false
|
6
|
+
t.float :nominal, :null => false, :default => 1
|
7
|
+
t.float :value, :null => false
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.down
|
13
|
+
drop_table :currency_converters
|
14
|
+
end
|
15
|
+
end
|
data/db/seeds.rb
ADDED
data/lib/multi_currencies.rb
CHANGED
@@ -10,63 +10,6 @@ module MultiCurrencies
|
|
10
10
|
Dir.glob(File.join(File.dirname(__FILE__), "../app/**/*_decorator*.rb")) do |c|
|
11
11
|
Rails.env.production? ? require(c) : load(c)
|
12
12
|
end
|
13
|
-
Variant.class_eval do
|
14
|
-
|
15
|
-
def price
|
16
|
-
Currency.conversion_to_current(read_attribute(:price))
|
17
|
-
end
|
18
|
-
|
19
|
-
def price=(value)
|
20
|
-
conversion_value = Currency.conversion_from_current(value)
|
21
|
-
write_attribute(:price, conversion_value)
|
22
|
-
end
|
23
|
-
|
24
|
-
end
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
Order.class_eval do
|
29
|
-
def total
|
30
|
-
Currency.conversion_to_current(read_attribute(:total))
|
31
|
-
end
|
32
|
-
|
33
|
-
def total=(value)
|
34
|
-
conversion_value = Currency.conversion_from_current(value)
|
35
|
-
write_attribute(:total, conversion_value)
|
36
|
-
end
|
37
|
-
|
38
|
-
def item_total
|
39
|
-
Currency.conversion_to_current(read_attribute(:item_total))
|
40
|
-
end
|
41
|
-
|
42
|
-
def item_total=(value)
|
43
|
-
conversion_value = Currency.conversion_from_current(value)
|
44
|
-
write_attribute(:item_total, conversion_value)
|
45
|
-
end
|
46
|
-
|
47
|
-
def credit_total
|
48
|
-
Currency.conversion_to_current(read_attribute(:credit_total))
|
49
|
-
end
|
50
|
-
|
51
|
-
def credit_total=(value)
|
52
|
-
conversion_value = Currency.conversion_from_current(value)
|
53
|
-
write_attribute(:credit_total, conversion_value)
|
54
|
-
end
|
55
|
-
|
56
|
-
|
57
|
-
end
|
58
|
-
|
59
|
-
LineItem.class_eval do
|
60
|
-
def price
|
61
|
-
Currency.conversion_to_current(read_attribute(:price))
|
62
|
-
end
|
63
|
-
|
64
|
-
def price=(value)
|
65
|
-
conversion_value = Currency.conversion_from_current(value)
|
66
|
-
write_attribute(:price, conversion_value)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
13
|
end
|
71
14
|
|
72
15
|
config.to_prepare &method(:activate).to_proc
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module MultiCurrency
|
2
|
+
|
3
|
+
# Order.class_eval do
|
4
|
+
# extend MultiCurrency
|
5
|
+
# multi_currency :item_total, :total,
|
6
|
+
# :rate_at_date => lambda{ |t| t.created_at },
|
7
|
+
# :only_read => true
|
8
|
+
# only_read - выполнять перевод из одной валюты в другую только для вывода
|
9
|
+
# rate_at_date - использовать курс валюты на дату
|
10
|
+
def multi_currency(*args)
|
11
|
+
options = args.extract_options!
|
12
|
+
[args].flatten.compact.each do |number_field|
|
13
|
+
|
14
|
+
define_method(number_field.to_sym) do
|
15
|
+
if options.has_key?(:rate_at_date) && options[:rate_at_date].is_a?(Proc)
|
16
|
+
Currency.conversion_to_current(read_attribute(number_field.to_sym),
|
17
|
+
{ :date => options[:rate_at_date].call(self) })
|
18
|
+
else
|
19
|
+
Currency.conversion_to_current(read_attribute(number_field.to_sym))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
unless options[:only_read]
|
24
|
+
define_method(:"#{number_field}=") do |value|
|
25
|
+
write_attribute(number_field.to_sym, Currency.conversion_from_current(value))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
@@ -1,20 +1,96 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'nokogiri'
|
1
3
|
# add custom rake tasks here
|
2
4
|
namespace :multi_currencies do
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
5
|
+
|
6
|
+
namespace :currency do
|
7
|
+
desc "Общероссийский классификатор валют (сокращ. ОКВ) - http://ru.wikipedia.org/wiki/Общероссийский_классификатор_валют"
|
8
|
+
|
9
|
+
task :okv => :environment do
|
10
|
+
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"
|
11
|
+
data = Nokogiri::HTML.parse(open(url))
|
12
|
+
keys = [:char_code, :num_code, :discharge, :name, :countries ]
|
13
|
+
data.css("table:first tr")[1..-1].map{ |d|
|
14
|
+
Hash[*keys.zip(d.css("td").map {|x| x.text.strip }).flatten]
|
15
|
+
}.each { |n|
|
16
|
+
Currency.find_by_num_code(n[:num_code]) || Currency.create(n.except(:discharge).except(:countries))
|
17
|
+
}
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "Load currency ISO4217 http://en.wikipedia.org/wiki/ISO_4217"
|
22
|
+
task :iso4217 => :environment do
|
23
|
+
url = "http://en.wikipedia.org/wiki/ISO_4217"
|
24
|
+
data = Nokogiri::HTML.parse(open(url))
|
25
|
+
keys = [:char_code, :num_code, :discharge, :name, :countries ]
|
26
|
+
data.css("table:eq(2) tr")[1..-1].map{|d|
|
27
|
+
Hash[*keys.zip(d.css("td").map {|x| x.text.strip }).flatten]
|
28
|
+
}.each { |n|
|
29
|
+
Currency.find_by_num_code(n[:num_code]) || Currency.create(n.except(:discharge).except(:countries))
|
30
|
+
}
|
17
31
|
end
|
32
|
+
|
18
33
|
end
|
19
34
|
|
35
|
+
namespace :rates do
|
36
|
+
desc "Курс Сбербанка РФ http://www.cbr.ru"
|
37
|
+
task :cbr => :environment do
|
38
|
+
rub = Currency.get("643", { :num_code => "643", :char_code => "RUB", :name => "Российский рубль"})
|
39
|
+
rub.basic!
|
40
|
+
url = "http://www.cbr.ru/scripts/XML_daily.asp?date_req=#{Time.now.strftime('%d/%m/%Y')}"
|
41
|
+
data = Nokogiri::XML.parse(open(url))
|
42
|
+
date_str = data.xpath("//ValCurs").attr("Date").to_s
|
43
|
+
date = Date.strptime(date_str, (date_str =~ /\./ ? '%d.%m.%Y' : '%d/%m/%y'))
|
44
|
+
data.xpath("//ValCurs/Valute").each do |valute|
|
45
|
+
char_code = valute.xpath("./CharCode").text.to_s
|
46
|
+
num_code = valute.xpath("./NumCode").text.to_s
|
47
|
+
name = valute.xpath("./Name").text.to_s
|
48
|
+
value = valute.xpath("./Value").text.gsub(',','.').to_f
|
49
|
+
nominal = valute.xpath("./Nominal").text
|
50
|
+
currency = Currency.get(num_code, { :num_code => num_code, :char_code => char_code, :name => name})
|
51
|
+
currency && CurrencyConverter.add(currency, date, value, nominal)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Rates from European Central Bank"
|
56
|
+
task :ecb, [:load_currencies] => :environment do |t, args|
|
57
|
+
Rake::Task["multi_currencies:currency:iso4217"].invoke if args.load_currencies
|
58
|
+
euro = Currency.get("978", { :num_code => "978", :char_code => "EUR", :name => "Euro"})
|
59
|
+
euro.basic!
|
60
|
+
url = 'http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml'
|
61
|
+
data = Nokogiri::XML.parse(open(url))
|
62
|
+
date = Date.strptime(data.xpath('gesmes:Envelope/xmlns:Cube/xmlns:Cube').attr("time").to_s, "%Y-%m-%d")
|
63
|
+
data.xpath('gesmes:Envelope/xmlns:Cube/xmlns:Cube//xmlns:Cube').each do |exchange_rate|
|
64
|
+
char_code = exchange_rate.attribute("currency").value.to_s.strip
|
65
|
+
value, nimonal = exchange_rate.attribute("rate").value.to_f, 1
|
66
|
+
currency = Currency.find_by_char_code(char_code)
|
67
|
+
currency && CurrencyConverter.add(currency, date, value, nominal)
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
desc "Rates from Google"
|
73
|
+
task :google, [:currency, :load_currencies] => :environment do |t, args|
|
74
|
+
Rake::Task["multi_currencies:currency:iso4217"].invoke if args.load_currencies
|
75
|
+
default_currency = Currency.where(" char_code = :currency_code or num_code = :currency_code",
|
76
|
+
:currency_code => args.currency || 978).first ||
|
77
|
+
Currency.get("978", { :num_code => "978", :char_code => "EUR", :name => "Euro"})
|
78
|
+
default_currency.basic!
|
79
|
+
date = Time.now
|
80
|
+
Currency.all.each { |currency|
|
81
|
+
unless currency == default_currency
|
82
|
+
url = "http://www.google.com/ig/calculator?hl=en&q=1#{ currency.char_code }%3D%3F#{ default_currency.char_code }"
|
83
|
+
@data = JSON.parse(open(url).read.gsub(/lhs:|rhs:|error:|icc:/){ |x| "\"#{x[0..-2]}\":"})
|
84
|
+
if @data["error"].blank?
|
85
|
+
@value = BigDecimal(@data["rhs"].split(' ')[0])
|
86
|
+
CurrencyConverter.add(currency, date, @value, 1)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
|
20
95
|
end
|
96
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.platform = Gem::Platform::RUBY
|
6
|
+
s.name = 'multi_currencies'
|
7
|
+
s.version = '1.0.2'
|
8
|
+
s.summary = 'Add gem summary here'
|
9
|
+
s.required_ruby_version = '>= 1.8.7'
|
10
|
+
s.authors = ["Maxim"]
|
11
|
+
s.email = ["parallel588@gmail.com"]
|
12
|
+
s.homepage = ""
|
13
|
+
s.summary = %q{spree-multi-currency}
|
14
|
+
s.description = %q{spree-multi-currency}
|
15
|
+
|
16
|
+
s.rubyforge_project = "spree-multi-currency"
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
|
22
|
+
s.require_path = ['lib']
|
23
|
+
|
24
|
+
|
25
|
+
s.add_dependency('spree_core', '>= 0.30.0')
|
26
|
+
s.add_dependency('nokogiri', '>= 1.4.4')
|
27
|
+
s.add_dependency('money', '>= 3.6.1')
|
28
|
+
s.add_dependency('json', '>= 1.5.1')
|
29
|
+
|
30
|
+
s.add_development_dependency("rspec", ">= 2.5.0")
|
31
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# This file is copied to ~/spec when you run 'ruby script/generate rspec'
|
2
|
+
# from the project root directory.
|
3
|
+
ENV["RAILS_ENV"] ||= 'test'
|
4
|
+
require File.expand_path("../../../config/environment", __FILE__)
|
5
|
+
require 'rspec/rails'
|
6
|
+
require 'fabrication'
|
7
|
+
|
8
|
+
# Requires supporting files with custom matchers and macros, etc,
|
9
|
+
# in ./support/ and its subdirectories.
|
10
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
# == Mock Framework
|
14
|
+
#
|
15
|
+
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
|
16
|
+
#
|
17
|
+
# config.mock_with :mocha
|
18
|
+
# config.mock_with :flexmock
|
19
|
+
# config.mock_with :rr
|
20
|
+
config.mock_with :rspec
|
21
|
+
|
22
|
+
config.fixture_path = "#{::Rails.root}/spec/fixtures"
|
23
|
+
|
24
|
+
#config.include Devise::TestHelpers, :type => :controller
|
25
|
+
# If you're not using ActiveRecord, or you'd prefer not to run each of your
|
26
|
+
# examples within a transaction, comment the following line or assign false
|
27
|
+
# instead of true.
|
28
|
+
config.use_transactional_fixtures = true
|
29
|
+
end
|
30
|
+
|
31
|
+
@configuration ||= AppConfiguration.find_or_create_by_name("Default configuration")
|
metadata
CHANGED
@@ -1,21 +1,21 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: multi_currencies
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 19
|
5
|
+
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 1.0.
|
9
|
+
- 2
|
10
|
+
version: 1.0.2
|
11
11
|
platform: ruby
|
12
|
-
authors:
|
13
|
-
|
12
|
+
authors:
|
13
|
+
- Maxim
|
14
14
|
autorequire:
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2011-04-24 00:00:00 +04:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -42,17 +42,65 @@ dependencies:
|
|
42
42
|
requirements:
|
43
43
|
- - ">="
|
44
44
|
- !ruby/object:Gem::Version
|
45
|
-
hash:
|
45
|
+
hash: 15
|
46
46
|
segments:
|
47
47
|
- 1
|
48
48
|
- 4
|
49
|
+
- 4
|
50
|
+
version: 1.4.4
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: money
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 29
|
62
|
+
segments:
|
49
63
|
- 3
|
64
|
+
- 6
|
50
65
|
- 1
|
51
|
-
version:
|
66
|
+
version: 3.6.1
|
52
67
|
type: :runtime
|
53
|
-
version_requirements: *
|
54
|
-
|
68
|
+
version_requirements: *id003
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: json
|
71
|
+
prerelease: false
|
72
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
hash: 1
|
78
|
+
segments:
|
79
|
+
- 1
|
80
|
+
- 5
|
81
|
+
- 1
|
82
|
+
version: 1.5.1
|
83
|
+
type: :runtime
|
84
|
+
version_requirements: *id004
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: rspec
|
87
|
+
prerelease: false
|
88
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
hash: 27
|
94
|
+
segments:
|
95
|
+
- 2
|
96
|
+
- 5
|
97
|
+
- 0
|
98
|
+
version: 2.5.0
|
99
|
+
type: :development
|
100
|
+
version_requirements: *id005
|
101
|
+
description: spree-multi-currency
|
55
102
|
email:
|
103
|
+
- parallel588@gmail.com
|
56
104
|
executables: []
|
57
105
|
|
58
106
|
extensions: []
|
@@ -60,35 +108,54 @@ extensions: []
|
|
60
108
|
extra_rdoc_files: []
|
61
109
|
|
62
110
|
files:
|
63
|
-
-
|
111
|
+
- .gitignore
|
112
|
+
- Gemfile
|
64
113
|
- LICENSE
|
65
|
-
-
|
66
|
-
-
|
67
|
-
-
|
68
|
-
-
|
69
|
-
- app/
|
114
|
+
- README.markdown
|
115
|
+
- Rakefile
|
116
|
+
- app/controllers/admin/currencies_controller.rb
|
117
|
+
- app/controllers/admin/currency_converters_controller.rb
|
118
|
+
- app/controllers/currency_controller.rb
|
119
|
+
- app/controllers/spree/base_controller_decorator.rb
|
120
|
+
- app/decorators/line_item_decorator.rb
|
121
|
+
- app/decorators/order_decorator.rb
|
122
|
+
- app/decorators/variant_decorator.rb
|
70
123
|
- app/helpers/admin/currency_converters_helper.rb
|
124
|
+
- app/helpers/number_helper_decorator.rb
|
71
125
|
- app/models/currency.rb
|
72
126
|
- app/models/currency_converter.rb
|
127
|
+
- app/views/admin/currencies/_form.html.erb
|
73
128
|
- app/views/admin/currencies/edit.html.erb
|
74
129
|
- app/views/admin/currencies/index.html.erb
|
75
|
-
- app/views/admin/currencies/_form.html.erb
|
76
130
|
- app/views/admin/currencies/new.html.erb
|
131
|
+
- app/views/admin/currency_converters/_form.html.erb
|
77
132
|
- app/views/admin/currency_converters/edit.html.erb
|
78
133
|
- app/views/admin/currency_converters/index.html.erb
|
79
|
-
- app/views/admin/currency_converters/_form.html.erb
|
80
134
|
- app/views/admin/currency_converters/new.html.erb
|
81
|
-
-
|
82
|
-
-
|
135
|
+
- config/locales/currency_rub.yml
|
136
|
+
- config/locales/currency_usd.yml
|
137
|
+
- config/locales/en_multi_currency.yml
|
138
|
+
- config/locales/ru_multi_currency.yml
|
139
|
+
- config/routes.rb
|
140
|
+
- db/migrate/20101109134351_create_currencies.rb
|
141
|
+
- db/migrate/20101109134453_create_currency_converters.rb
|
142
|
+
- db/seeds.rb
|
143
|
+
- lib/multi_currencies.rb
|
144
|
+
- lib/multi_currencies_hooks.rb
|
145
|
+
- lib/multi_currency.rb
|
146
|
+
- lib/tasks/install.rake
|
147
|
+
- lib/tasks/multi_currencies.rake
|
148
|
+
- multi_currencies.gemspec
|
149
|
+
- spec/spec_helper.rb
|
83
150
|
has_rdoc: true
|
84
|
-
homepage:
|
151
|
+
homepage: ""
|
85
152
|
licenses: []
|
86
153
|
|
87
154
|
post_install_message:
|
88
155
|
rdoc_options: []
|
89
156
|
|
90
157
|
require_paths:
|
91
|
-
- lib
|
158
|
+
- - lib
|
92
159
|
required_ruby_version: !ruby/object:Gem::Requirement
|
93
160
|
none: false
|
94
161
|
requirements:
|
@@ -109,12 +176,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
176
|
segments:
|
110
177
|
- 0
|
111
178
|
version: "0"
|
112
|
-
requirements:
|
113
|
-
|
114
|
-
rubyforge_project:
|
115
|
-
rubygems_version: 1.
|
179
|
+
requirements: []
|
180
|
+
|
181
|
+
rubyforge_project: spree-multi-currency
|
182
|
+
rubygems_version: 1.4.2
|
116
183
|
signing_key:
|
117
184
|
specification_version: 3
|
118
|
-
summary:
|
119
|
-
test_files:
|
120
|
-
|
185
|
+
summary: spree-multi-currency
|
186
|
+
test_files:
|
187
|
+
- spec/spec_helper.rb
|
data/README.md
DELETED