gera 0.3.4

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 (164) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +674 -0
  3. data/README.md +67 -0
  4. data/Rakefile +43 -0
  5. data/app/assets/config/gera_manifest.js +2 -0
  6. data/app/assets/javascripts/gera/application.js +14 -0
  7. data/app/assets/stylesheets/gera/application.css +15 -0
  8. data/app/authorizers/gera/application_authorizer.rb +6 -0
  9. data/app/authorizers/gera/currency_rate_authorizer.rb +9 -0
  10. data/app/authorizers/gera/currency_rate_mode_authorizer.rb +7 -0
  11. data/app/authorizers/gera/direction_rate_authorizer.rb +8 -0
  12. data/app/authorizers/gera/exchange_rate_authorizer.rb +8 -0
  13. data/app/authorizers/gera/payment_system_authorizer.rb +13 -0
  14. data/app/authorizers/gera/rate_source_authorizer.rb +7 -0
  15. data/app/controllers/gera/application_controller.rb +30 -0
  16. data/app/controllers/gera/currencies_controller.rb +8 -0
  17. data/app/controllers/gera/currency_rate_history_intervals_controller.rb +53 -0
  18. data/app/controllers/gera/currency_rate_mode_snapshots_controller.rb +83 -0
  19. data/app/controllers/gera/currency_rate_modes_controller.rb +60 -0
  20. data/app/controllers/gera/currency_rates_controller.rb +70 -0
  21. data/app/controllers/gera/direction_rate_history_intervals_controller.rb +62 -0
  22. data/app/controllers/gera/direction_rates_controller.rb +28 -0
  23. data/app/controllers/gera/exchange_rates_controller.rb +25 -0
  24. data/app/controllers/gera/external_rate_snapshots_controller.rb +35 -0
  25. data/app/controllers/gera/external_rates_controller.rb +28 -0
  26. data/app/controllers/gera/payment_systems_controller.rb +78 -0
  27. data/app/controllers/gera/rate_sources_controller.rb +8 -0
  28. data/app/decorators/gera/currency_rate_decorator.rb +148 -0
  29. data/app/decorators/gera/direction_rate_decorator.rb +47 -0
  30. data/app/decorators/gera/payment_system_decorator.rb +19 -0
  31. data/app/helpers/gera/application_helper.rb +110 -0
  32. data/app/helpers/gera/currency_rate_helper.rb +19 -0
  33. data/app/helpers/gera/currency_rate_modes_helper.rb +73 -0
  34. data/app/helpers/gera/direction_rate_helper.rb +36 -0
  35. data/app/helpers/gera/exchange_rates_helper.rb +21 -0
  36. data/app/jobs/gera/application_job.rb +6 -0
  37. data/app/models/concerns/gera/currency_pair_generator.rb +14 -0
  38. data/app/models/concerns/gera/currency_pair_support.rb +42 -0
  39. data/app/models/concerns/gera/currency_rate_mode_builder_support.rb +35 -0
  40. data/app/models/concerns/gera/direction_support.rb +14 -0
  41. data/app/models/concerns/gera/history_interval_concern.rb +46 -0
  42. data/app/models/gera/application_record.rb +8 -0
  43. data/app/models/gera/cbr_external_rate.rb +13 -0
  44. data/app/models/gera/cross_rate_mode.rb +13 -0
  45. data/app/models/gera/currency_rate.rb +78 -0
  46. data/app/models/gera/currency_rate_history_interval.rb +25 -0
  47. data/app/models/gera/currency_rate_history_interval_filter.rb +30 -0
  48. data/app/models/gera/currency_rate_mode.rb +20 -0
  49. data/app/models/gera/currency_rate_mode_snapshot.rb +26 -0
  50. data/app/models/gera/currency_rate_snapshot.rb +12 -0
  51. data/app/models/gera/direction.rb +44 -0
  52. data/app/models/gera/direction_rate.rb +136 -0
  53. data/app/models/gera/direction_rate_history_interval.rb +33 -0
  54. data/app/models/gera/direction_rate_history_interval_filter.rb +30 -0
  55. data/app/models/gera/direction_rate_snapshot.rb +7 -0
  56. data/app/models/gera/exchange_rate.rb +130 -0
  57. data/app/models/gera/external_rate.rb +38 -0
  58. data/app/models/gera/external_rate_snapshot.rb +20 -0
  59. data/app/models/gera/payment_system.rb +98 -0
  60. data/app/models/gera/rate_source.rb +77 -0
  61. data/app/models/gera/rate_source_auto.rb +25 -0
  62. data/app/models/gera/rate_source_bitfinex.rb +9 -0
  63. data/app/models/gera/rate_source_cbr.rb +13 -0
  64. data/app/models/gera/rate_source_cbr_avg.rb +6 -0
  65. data/app/models/gera/rate_source_exmo.rb +9 -0
  66. data/app/models/gera/rate_source_manual.rb +13 -0
  67. data/app/views/application/_exchange_rate_monitor.slim +1 -0
  68. data/app/views/gera/application/_attributes_table.slim +5 -0
  69. data/app/views/gera/application/_currencies_table.slim +18 -0
  70. data/app/views/gera/application/_currency_rate_mode_snapshots.slim +6 -0
  71. data/app/views/gera/application/_direction_rate.slim +6 -0
  72. data/app/views/gera/application/_directions_submenu.slim +11 -0
  73. data/app/views/gera/application/_directions_table.slim +15 -0
  74. data/app/views/gera/application/_exchange_rate.slim +6 -0
  75. data/app/views/gera/application/_external_rate.slim +6 -0
  76. data/app/views/gera/application/_header.slim +12 -0
  77. data/app/views/gera/application/_table.slim +28 -0
  78. data/app/views/gera/application/_topnav.slim +21 -0
  79. data/app/views/gera/application/direction_details.slim +75 -0
  80. data/app/views/gera/application/page.html.slim +1 -0
  81. data/app/views/gera/currencies/index.html.slim +20 -0
  82. data/app/views/gera/currency_rate_history_intervals/_filter.slim +11 -0
  83. data/app/views/gera/currency_rate_history_intervals/index.slim +39 -0
  84. data/app/views/gera/currency_rate_mode_snapshots/_snapshot.slim +20 -0
  85. data/app/views/gera/currency_rate_mode_snapshots/edit.html.slim +30 -0
  86. data/app/views/gera/currency_rate_modes/_cross_rate_mode_fields.slim +21 -0
  87. data/app/views/gera/currency_rate_modes/_currency_rate_mode.slim +7 -0
  88. data/app/views/gera/currency_rate_modes/_form.slim +29 -0
  89. data/app/views/gera/currency_rate_modes/edit.slim +11 -0
  90. data/app/views/gera/currency_rate_modes/index.html.slim +12 -0
  91. data/app/views/gera/currency_rates/_currency_rate.slim +22 -0
  92. data/app/views/gera/currency_rates/_currency_rate_humanized.slim +35 -0
  93. data/app/views/gera/currency_rates/diff.slim +12 -0
  94. data/app/views/gera/currency_rates/index.slim +27 -0
  95. data/app/views/gera/currency_rates/modes.slim +14 -0
  96. data/app/views/gera/currency_rates/show.slim +4 -0
  97. data/app/views/gera/direction_rate_history_intervals/_filter.slim +11 -0
  98. data/app/views/gera/direction_rate_history_intervals/index.slim +41 -0
  99. data/app/views/gera/direction_rates/_direction_rate_example_calculator.slim +28 -0
  100. data/app/views/gera/direction_rates/diff.slim +19 -0
  101. data/app/views/gera/direction_rates/index.slim +26 -0
  102. data/app/views/gera/direction_rates/legacy.html.slim +5 -0
  103. data/app/views/gera/direction_rates/minimals.slim +5 -0
  104. data/app/views/gera/direction_rates/show.slim +14 -0
  105. data/app/views/gera/external_rate_snapshots/index.slim +25 -0
  106. data/app/views/gera/external_rate_snapshots/show.slim +22 -0
  107. data/app/views/gera/external_rates/_flat_list.slim +22 -0
  108. data/app/views/gera/external_rates/index.html.slim +28 -0
  109. data/app/views/gera/external_rates/show.slim +1 -0
  110. data/app/views/gera/payment_systems/_fields.slim +4 -0
  111. data/app/views/gera/payment_systems/_fields_table.slim +18 -0
  112. data/app/views/gera/payment_systems/_form.slim +9 -0
  113. data/app/views/gera/payment_systems/edit.slim +7 -0
  114. data/app/views/gera/payment_systems/index.slim +3 -0
  115. data/app/views/gera/payment_systems/new.slim +9 -0
  116. data/app/views/gera/payment_systems/show.slim +1 -0
  117. data/app/views/gera/rate_sources/_list.slim +13 -0
  118. data/app/views/gera/rate_sources/_rate_source.slim +23 -0
  119. data/app/views/gera/rate_sources/index.html.slim +6 -0
  120. data/app/views/layouts/application.html.slim +15 -0
  121. data/app/views/layouts/gera/blank.html.slim +10 -0
  122. data/app/workers/concerns/gera/rates_worker.rb +87 -0
  123. data/app/workers/gera/bitfinex_rates_worker.rb +65 -0
  124. data/app/workers/gera/cbr_avg_rates_worker.rb +42 -0
  125. data/app/workers/gera/cbr_rates_worker.rb +192 -0
  126. data/app/workers/gera/create_history_intervals_worker.rb +38 -0
  127. data/app/workers/gera/currency_rates_worker.rb +64 -0
  128. data/app/workers/gera/directions_rates_worker.rb +51 -0
  129. data/app/workers/gera/exmo_rates_worker.rb +62 -0
  130. data/app/workers/gera/purge_currency_rates_worker.rb +21 -0
  131. data/app/workers/gera/purge_direction_rates_worker.rb +30 -0
  132. data/config/currencies.yml +559 -0
  133. data/config/routes.rb +30 -0
  134. data/db/migrate/20180912130000_setup.rb +228 -0
  135. data/db/migrate/20190314114844_add_is_available_to_payment_systems.rb +7 -0
  136. data/db/migrate/20190315113046_add_icon_url_to_payment_systems.rb +7 -0
  137. data/db/migrate/20190315132137_add_commission_to_payment_systems.rb +7 -0
  138. data/lib/banks/currency_exchange.rb +12 -0
  139. data/lib/builders/currency_rate_auto_builder.rb +38 -0
  140. data/lib/builders/currency_rate_builder.rb +60 -0
  141. data/lib/builders/currency_rate_cross_builder.rb +70 -0
  142. data/lib/builders/currency_rate_direct_builder.rb +26 -0
  143. data/lib/gera.rb +32 -0
  144. data/lib/gera/bitfinex_fetcher.rb +36 -0
  145. data/lib/gera/configuration.rb +47 -0
  146. data/lib/gera/currencies_purger.rb +28 -0
  147. data/lib/gera/currency_pair.rb +93 -0
  148. data/lib/gera/engine.rb +14 -0
  149. data/lib/gera/mathematic.rb +156 -0
  150. data/lib/gera/money_support.rb +89 -0
  151. data/lib/gera/numeric.rb +25 -0
  152. data/lib/gera/railtie.rb +8 -0
  153. data/lib/gera/rate.rb +22 -0
  154. data/lib/gera/rate_from_multiplicator.rb +54 -0
  155. data/lib/gera/repositories/currency_rate_modes_repository.rb +37 -0
  156. data/lib/gera/repositories/currency_rates_repository.rb +25 -0
  157. data/lib/gera/repositories/direction_rates_repository.rb +46 -0
  158. data/lib/gera/repositories/exchange_rates_repository.rb +23 -0
  159. data/lib/gera/repositories/payment_systems_repository.rb +21 -0
  160. data/lib/gera/repositories/universe.rb +47 -0
  161. data/lib/gera/version.rb +3 -0
  162. data/lib/tasks/auto_generate_diagram.rake +6 -0
  163. data/lib/tasks/gera_tasks.rake +4 -0
  164. metadata +765 -0
@@ -0,0 +1,26 @@
1
+ require_relative 'currency_rate_builder'
2
+
3
+ module Gera
4
+ class CurrencyRateDirectBuilder < CurrencyRateBuilder
5
+ attribute :source #, RateSource
6
+
7
+ private
8
+
9
+ def build
10
+ raise Error, "Источником (#{source}) валюта #{currency_pair.cur_from} не поддерживается" unless source.is_currency_supported? currency_pair.cur_from
11
+ raise Error, "Источником (#{source}) валюта #{currency_pair.cur_to} не поддерживается" unless source.is_currency_supported? currency_pair.cur_to
12
+
13
+ external_rate = source.find_rate_by_currency_pair currency_pair
14
+
15
+ raise Error, "В источнике (#{source}) не найден курс для #{currency_pair}" unless external_rate.present?
16
+
17
+ CurrencyRate.new(
18
+ currency_pair: currency_pair,
19
+ rate_value: external_rate.rate_value,
20
+ rate_source: source,
21
+ mode: :direct,
22
+ external_rate_id: external_rate.id
23
+ )
24
+ end
25
+ end
26
+ end
data/lib/gera.rb ADDED
@@ -0,0 +1,32 @@
1
+ require 'money'
2
+ require 'require_all'
3
+ require 'percentable'
4
+
5
+ require 'sidekiq'
6
+ require 'auto_logger'
7
+
8
+ require "gera/version"
9
+ require 'gera/numeric'
10
+
11
+ require "gera/configuration"
12
+ require "gera/mathematic"
13
+ require 'gera/bitfinex_fetcher'
14
+ require 'gera/currency_pair'
15
+ require 'gera/rate'
16
+ require 'gera/money_support'
17
+
18
+
19
+ module Gera
20
+ CURRENCIES_PATH = File.expand_path("../config/currencies.yml", __dir__)
21
+
22
+ extend Configuration
23
+ end
24
+
25
+ require_rel 'banks'
26
+ require_rel 'builders'
27
+ require_rel 'gera/repositories'
28
+
29
+ if defined? ::Rails::Railtie
30
+ require "gera/engine"
31
+ require "gera/railtie"
32
+ end
@@ -0,0 +1,36 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'rest-client'
4
+ require 'virtus'
5
+
6
+ module Gera
7
+ class BitfinexFetcher
8
+ API_URL = 'https://api.bitfinex.com/v1/pubticker/'
9
+
10
+ include Virtus.model strict: true
11
+
12
+ # Например btcusd
13
+ attribute :ticker, String
14
+
15
+ def perform
16
+ response = RestClient::Request.execute url: url, method: :get, verify_ssl: false
17
+
18
+ raise response.code unless response.code == 200
19
+ JSON.parse response.body
20
+ end
21
+
22
+ private
23
+
24
+ def url
25
+ API_URL + ticker
26
+ end
27
+
28
+ def http
29
+ Net::HTTP.new(uri.host, uri.port).tap do |http|
30
+ http.use_ssl = true
31
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
32
+ http
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,47 @@
1
+ module Gera
2
+ # Gera configuration module.
3
+ # This is extended by Gera to provide configuration settings.
4
+ module Configuration
5
+
6
+ # Start a Gera configuration block in an initializer.
7
+ #
8
+ # example: Provide a default currency for the application
9
+ # Gera.configure do |config|
10
+ # config.default_currency = :eur
11
+ # end
12
+ def configure
13
+ yield self
14
+ end
15
+
16
+ # @param [Class] Декоратор для PaymentSystem
17
+ mattr_accessor :payment_system_decorator
18
+ def payment_system_decorator
19
+ @@payment_system_decorator || PaymentSystemDecorator
20
+ end
21
+
22
+ # @param [Symbol] Валюта для кросс-расчетов по-умолчанию
23
+ mattr_accessor :default_cross_currency
24
+ @@default_cross_currency = :usd
25
+
26
+ def default_cross_currency
27
+ return @@default_cross_currency if @@default_cross_currency.is_a? Money::Currency
28
+ Money::Currency.find! @@default_cross_currency
29
+ end
30
+
31
+ # @param [Hash] Набор кросс-валют для расчета
32
+ mattr_accessor :cross_pairs
33
+ # В данном примере курс к KZT считать через RUB
34
+ @@cross_pairs = { kzt: :rub }
35
+
36
+ def cross_pairs
37
+ h = {}
38
+ @@cross_pairs.each do |k, v|
39
+ h[Money::Currency.find!(k)] = Money::Currency.find! v
40
+ end
41
+ h
42
+ end
43
+ end
44
+ end
45
+
46
+ # Пример:
47
+ # https://github.com/RubyMoney/money-rails/blob/master/lib/money-rails/configuration.rb
@@ -0,0 +1,28 @@
1
+ module Gera
2
+ module CurrenciesPurger
3
+ def self.purge_all(env)
4
+ raise unless env == Rails.env
5
+
6
+ if Rails.env.prodiction?
7
+ puts 'Disable all sidekiqs'
8
+ Sidekiq::Cron::Job.all.each(&:disable!)
9
+ sleep 2
10
+ end
11
+
12
+ DirectionRateSnapshot.batch_purge if DirectionRateSnapshot.table_exists?
13
+ DirectionRate.batch_purge
14
+
15
+ ExternalRate.batch_purge
16
+ ExternalRateSnapshot.batch_purge
17
+
18
+ CurrencyRate.batch_purge
19
+ RateSource.update_all actual_snapshot_id: nil
20
+ CurrencyRateSnapshot.batch_purge
21
+
22
+ if Rails.env.prodiction?
23
+ puts 'Enable all sidekiqs'
24
+ Sidekiq::Cron::Job.all.each(&:enable!)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,93 @@
1
+ require 'virtus'
2
+ require 'money'
3
+
4
+ module Gera
5
+ # Валютная пара
6
+ #
7
+ class CurrencyPair
8
+ include Virtus.value_object strict: true
9
+
10
+ values do
11
+ attribute :cur_from, Money::Currency
12
+ attribute :cur_to, Money::Currency
13
+ end
14
+
15
+ # Варианты:
16
+ #
17
+ # new cur_from: :rub, cur_to: usd
18
+ # new :rub, :usd
19
+ # new 'rub/usd'
20
+ def initialize(*args)
21
+ if args.first.is_a? Hash
22
+ super(args.first).freeze
23
+
24
+ elsif args.count ==1
25
+ initialize(*args.first.split(/[\/_-]/)).freeze
26
+
27
+ elsif args.count == 2
28
+ super(cur_from: args[0], cur_to: args[1]).freeze
29
+
30
+ else
31
+ raise "WTF? #{args}"
32
+ end
33
+ end
34
+
35
+ delegate :include?, :first, :last, :second, :join, to: :to_a
36
+
37
+ def self.all
38
+ @all ||= Money::Currency.all.each_with_object([]) { |cur_from, list|
39
+ Money::Currency.all.each { |cur_to| list << CurrencyPair.new(cur_from, cur_to) }
40
+ }.freeze
41
+ end
42
+
43
+ def inspect
44
+ to_s
45
+ end
46
+
47
+ def inverse
48
+ self.class.new cur_to, cur_from
49
+ end
50
+
51
+ def cur_to=(value)
52
+ if value.is_a? Money::Currency
53
+ super value
54
+ else
55
+ super Money::Currency.find(value) || raise("Не известная валюта #{value} в cur_to")
56
+ end
57
+ end
58
+
59
+ def cur_from=(value)
60
+ if value.is_a? Money::Currency
61
+ super value
62
+ else
63
+ super Money::Currency.find(value) || raise("Не известная валюта #{value} в cur_from")
64
+ end
65
+ end
66
+
67
+ def change_to(cur)
68
+ CurrencyPair.new cur_from, cur
69
+ end
70
+
71
+ def change_from(cur)
72
+ CurrencyPair.new cur, cur_to
73
+ end
74
+
75
+ def same?
76
+ cur_from == cur_to
77
+ end
78
+
79
+ def to_a
80
+ [cur_from, cur_to]
81
+ end
82
+
83
+ # Для людей
84
+ def to_s
85
+ join '/'
86
+ end
87
+
88
+ # Для машин
89
+ def key
90
+ join '_'
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,14 @@
1
+ module Gera
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Gera
4
+
5
+ paths.add "app/workers", eager_load: true
6
+ paths.add "app/authorizers", eager_load: true
7
+
8
+ # Идея отсюда - https://github.com/thoughtbot/factory_bot_rails/pull/149/files?diff=split&short_path=04c6e90
9
+ #
10
+ initializer 'gera.factories', after: 'factory_bot.set_factory_paths' do
11
+ FactoryBot.definition_file_paths << File.expand_path('../../../factories', __FILE__) if defined?(FactoryBot)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,156 @@
1
+ require 'virtus'
2
+
3
+ module Gera
4
+ module Mathematic
5
+ class Result
6
+ include Virtus.model strict: true
7
+
8
+ # Входные параметры
9
+ #
10
+ attribute :base_rate, Float # Базовая ставка
11
+ attribute :comission, Float # Комиссия обменника
12
+ attribute :ps_interest, Float # Интерес платежной системы (%)
13
+ attribute :income_amount, Float # Сумма во входящей валюте которую клиент закинул
14
+
15
+ # Расчетные параметры
16
+ #
17
+ attribute :finite_rate, Float # Конечная ставка
18
+ attribute :finite_amount, Float # сумма которую получет пользователь на счет
19
+ attribute :ps_amount, Float # Сумма интереса платежной системы в выходящей валюте
20
+ attribute :outcome_amount, Float # Сумма которая уйдет с выходящего кошелька (что получит клиент + комиссия ПС)
21
+ attribute :profit_amount, Float # чистая прибыл обменика
22
+ end
23
+
24
+ def calculate_profits(base_rate:,
25
+ comission:,
26
+ ps_interest:,
27
+ income_amount:
28
+ )
29
+
30
+ finite_rate = calculate_finite_rate base_rate, comission
31
+ finite_amount = income_amount * finite_rate
32
+ ps_amount = ps_interest.percent_of finite_amount
33
+ outcome_amount = ps_amount + finite_amount
34
+
35
+ Result.new(
36
+ base_rate: base_rate,
37
+ comission: comission,
38
+ ps_interest: ps_interest,
39
+ income_amount: income_amount,
40
+
41
+ finite_rate: finite_rate,
42
+ finite_amount: finite_amount, # сумма которую получет пользователь на счет
43
+ ps_amount: ps_amount,
44
+ outcome_amount: outcome_amount, # сумма которую нужно перевести чтобы хватило и платежной системе и пользователю сколько обещали
45
+ profit_amount: income_amount - outcome_amount / base_rate
46
+ ).freeze
47
+ end
48
+
49
+ # Отдает базовую тавку из конечной и комиссии
50
+ #
51
+ def calculate_base_rate(finite_rate, comission)
52
+ if finite_rate >= 1
53
+ 100.0 * finite_rate / (100.0 - comission.to_f)
54
+ else
55
+ 100.0 * finite_rate / (100.0 - comission.to_f)
56
+ end
57
+ end
58
+
59
+ # Отдает комиссию исходя из конечной и базовой ставки
60
+ #
61
+ def calculate_comission(finite_rate, base_rate)
62
+ if finite_rate <= 1
63
+ a = 1.0 / finite_rate.to_f - 1.0 / base_rate.to_f
64
+ (a.as_percentage_of(1.0 / finite_rate.to_f).to_f * 100)
65
+ else
66
+ (base_rate.to_f - finite_rate.to_f).as_percentage_of(base_rate.to_f).to_f * 100
67
+ end
68
+ end
69
+
70
+ # Конечная ставка из базовой
71
+ #
72
+ def calculate_finite_rate(base_rate, comission)
73
+ if base_rate <= 1
74
+ base_rate.to_f * (1.0 - comission.to_f/100)
75
+ else
76
+ base_rate - comission.to_percent
77
+ end
78
+ end
79
+
80
+ # На сколько абсолютных процентов отличается число b от числа a
81
+ #
82
+ def diff_percents(a, b)
83
+ percent_value = (a.to_d / 100.0)
84
+ res = 100.0 - (b.to_d / percent_value)
85
+ res = -res if res < 0
86
+ res.to_percent
87
+ end
88
+
89
+ # Отдает сумму которую получим в результате обмена
90
+ # rate - курсовой мультимликатор
91
+ # amount - входящая сумма
92
+ # to_currency - валютя в которой нужно получить результат
93
+ #
94
+ def money_exchange(rate, amount, to_currency)
95
+ fractional = BigDecimal(amount.fractional.to_s) / (
96
+ BigDecimal(amount.currency.subunit_to_unit.to_s) /
97
+ BigDecimal(to_currency.subunit_to_unit.to_s)
98
+ )
99
+
100
+ res = fractional * rate
101
+ res = res.round(0, BigDecimal::ROUND_DOWN)
102
+ Money.new(res, to_currency)
103
+ end
104
+
105
+ # Обратный обмен. Когда у нас есть курс и сумма которую мы хотим
106
+ # получить и мы вычисляем из какой суммы на входе она получится
107
+ #
108
+ def money_reverse_exchange(rate, amount, to_currency)
109
+ fractional = BigDecimal(amount.fractional.to_s) / (
110
+ BigDecimal(amount.currency.subunit_to_unit.to_s) /
111
+ BigDecimal(to_currency.subunit_to_unit.to_s)
112
+ )
113
+
114
+ res = fractional / rate
115
+ res = res.round(0, BigDecimal::ROUND_UP)
116
+ Money.new(res, to_currency)
117
+ end
118
+
119
+ # Рассчет суммы с комиссей. Процент комиссии считается от изначальной суммы.
120
+ # Комиссия указывается в процентах.
121
+ #
122
+ def calculate_total_using_regular_comission(amount, comission)
123
+ amount + (amount * comission / 100.0)
124
+ end
125
+
126
+ # Рассчет суммы с обратной комиссей. Процент комиссии считается от итоговой суммы
127
+ # Комиссия указывается в процентах.
128
+ #
129
+ def calculate_total_using_reverse_comission(amount, comission)
130
+ 100.0 / (100 - comission) * amount
131
+ end
132
+
133
+ private
134
+
135
+ # Отдает комиссию исходя из конечной и базовой ставки
136
+ #
137
+ def modern_base_rate_percent(finite_rate, base_rate)
138
+ if finite_rate <= 1
139
+ (1.0/finite_rate.to_f - 1.0/base_rate.to_f) / (1.0/base_rate.to_f/100)
140
+ else
141
+ (base_rate - finite_rate.to_f) / (base_rate.to_f/100)
142
+ end
143
+ end
144
+
145
+ def modern_calcualte_finite_rate(base_rate, comission)
146
+ # Через обратный процент
147
+ # base_rate / (1.0 + comission/100.0)
148
+ #
149
+ if base_rate <= 1
150
+ 1.0 / ( 1.0 / base_rate + comission.to_percent )
151
+ else
152
+ base_rate - comission.to_percent
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,89 @@
1
+ module Gera
2
+ module MoneySupport
3
+ def self.init
4
+ # Убираем все валюты
5
+ Money::Currency.all.each do |cur|
6
+ Money::Currency.unregister cur.id.to_s
7
+ end
8
+
9
+ Psych.load( File.read CURRENCIES_PATH ).each { |key, cur| Money::Currency.register cur.symbolize_keys }
10
+
11
+ # Создают константы-валюты, типа RUB, USD и тп
12
+ Money::Currency.all.each do |cur|
13
+ Object.const_set cur.iso_code, cur
14
+ end
15
+
16
+ # Gera::Hooks.init
17
+ end
18
+
19
+ module CurrencyExtend
20
+ # TODO Вынести в app
21
+ attr_reader :local_id
22
+
23
+ # TODO отказаться
24
+ attr_reader :authorized_round
25
+
26
+ # TODO Вынести в базу в app
27
+ def minimal_output_value
28
+ Money.from_amount @minimal_output_value, self
29
+ end
30
+
31
+ # TODO Вынести в базу в app
32
+ def minimal_input_value
33
+ Money.from_amount @minimal_input_value, self
34
+ end
35
+
36
+ def is_crypto?
37
+ !!@is_crypto
38
+ end
39
+
40
+ def initialize_data!
41
+ super
42
+
43
+ data = self.class.table[@id]
44
+
45
+ @is_crypto = data[:is_crypto]
46
+ @local_id = data[:local_id]
47
+ @minimal_input_value = data[:minimal_input_value]
48
+ @minimal_output_value = data[:minimal_output_value]
49
+ @authorized_round = data[:authorized_round]
50
+ end
51
+ end
52
+
53
+ class ::Money::Currency
54
+ prepend CurrencyExtend
55
+
56
+ def self.find!(query)
57
+ find(query) || raise("No found currency (#{query.inspect})")
58
+ end
59
+
60
+ # TODO Вынести в app
61
+ #
62
+ def self.find_by_local_id(local_id)
63
+ local_id = local_id.to_i
64
+ id, _ = self.table.find{|key, currency| currency[:local_id] == local_id}
65
+ new(id)
66
+ rescue UnknownCurrency
67
+ nil
68
+ end
69
+
70
+ def self.all_crypto
71
+ @all_crypto ||= all.select(&:is_crypto?)
72
+ end
73
+
74
+ def zero_money
75
+ Money.from_amount(0, self)
76
+ end
77
+ end
78
+
79
+ class ::Money
80
+ # TODO Отказаться
81
+ # Это сумма, до которой разрешено безопасное округление
82
+ # при приеме суммы от клиента
83
+ def authorized_round
84
+ return self unless currency.authorized_round.is_a? Numeric
85
+ Money.from_amount to_f.round(currency.authorized_round), currency
86
+ end
87
+ end
88
+ end
89
+ end