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,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gera
4
+ class DirectionRateHistoryInterval < ApplicationRecord
5
+ include HistoryIntervalConcern
6
+
7
+ # Их не надо подключать, потому что иначе при создании записи
8
+ # ActiveRercord проверяет есить ли они в базе
9
+ #
10
+ # belongs_to :payment_system_from, class_name: 'PaymentSystem'
11
+ # belongs_to :payment_system_to, class_name: 'PaymentSystem'
12
+
13
+ def self.create_by_interval!(interval_from, interval_to = nil)
14
+ interval_to ||= interval_from + INTERVAL
15
+ DirectionRate
16
+ .where('created_at >= ? and created_at < ?', interval_from, interval_to)
17
+ .group(:ps_from_id, :ps_to_id)
18
+ .pluck(:ps_from_id, :ps_to_id, 'min(rate_value)', 'max(rate_value)', 'min(rate_percent)', 'max(rate_percent)')
19
+ .each do |ps_from_id, ps_to_id, min_rate, max_rate, min_comission, max_comission|
20
+
21
+ next if ps_from_id == ps_to_id
22
+
23
+ create!(
24
+ payment_system_from_id: ps_from_id,
25
+ payment_system_to_id: ps_to_id,
26
+ min_rate: min_rate, max_rate: max_rate,
27
+ min_comission: min_comission, max_comission: max_comission,
28
+ interval_from: interval_from, interval_to: interval_to
29
+ )
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gera
4
+ class DirectionRateHistoryIntervalFilter
5
+ include Virtus.model strict: true
6
+ include ActiveModel::Conversion
7
+ extend ActiveModel::Naming
8
+ include ActiveModel::Validations
9
+
10
+ attribute :payment_system_from_id, Integer, default: ->(_a, _b) { Gera::PaymentSystem.first.id }
11
+ attribute :payment_system_to_id, Integer, default: ->(_a, _b) { Gera::PaymentSystem.first.id }
12
+ attribute :value_type, String, default: 'rate'
13
+
14
+ def payment_system_from
15
+ @payment_system_from ||= Gera::PaymentSystem.find payment_system_from_id
16
+ end
17
+
18
+ def payment_system_to
19
+ @payment_system_to ||= Gera::PaymentSystem.find payment_system_to_id
20
+ end
21
+
22
+ def to_param
23
+ to_hash
24
+ end
25
+
26
+ def persisted?
27
+ false
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gera
4
+ class DirectionRateSnapshot < ApplicationRecord
5
+ has_many :direction_rates, foreign_key: :snapshot_id
6
+ end
7
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Комиссия для расчета конечного курса между платежными системами
4
+ # TODO: Пеерименовать в Direction
5
+ # Комиссии по направлениям платежных систем
6
+ # если в парсере операторы изменили курс/комиссию, то эта комиссия
7
+ # устанвливается сначала сюда, потом растекается по остальным
8
+ #
9
+ # * value - само значение комиссии
10
+ # * cor1/cor2 - границы коридора
11
+ #
12
+ # * position - позиция в best которую нужно установить?
13
+ # * on_notif - ???
14
+ # * on_corridor - в коридоре?
15
+ module Gera
16
+ class ExchangeRate < ApplicationRecord
17
+ include Authority::Abilities
18
+
19
+ DEFAULT_COMISSION = 50
20
+
21
+ include Mathematic
22
+ include DirectionSupport
23
+
24
+ belongs_to :payment_system_from, foreign_key: :income_payment_system_id, class_name: 'Gera::PaymentSystem'
25
+ belongs_to :payment_system_to, foreign_key: :outcome_payment_system_id, class_name: 'Gera::PaymentSystem'
26
+
27
+ scope :ordered, -> { order :id }
28
+ scope :enabled, -> { where is_enabled: true }
29
+
30
+ scope :with_payment_systems, lambda {
31
+ includes(:payment_system_from, :payment_system_to)
32
+ .joins(:payment_system_from, :payment_system_to)
33
+ }
34
+
35
+ scope :available, lambda {
36
+ with_payment_systems
37
+ .enabled
38
+ .where("#{PaymentSystem.table_name}.income_enabled and payment_system_tos_gera_exchange_rates.outcome_enabled")
39
+ .where("#{table_name}.income_payment_system_id <> #{table_name}.outcome_payment_system_id")
40
+ }
41
+
42
+ after_commit :update_direction_rates, if: -> { previous_changes.key?('value') }
43
+
44
+ before_create do
45
+ self.in_cur = payment_system_from.currency.to_s
46
+ self.out_cur = payment_system_to.currency.to_s
47
+ self.comission ||= DEFAULT_COMISSION
48
+ end
49
+
50
+ validates :commission, presence: true
51
+
52
+ delegate :rate, :currency_rate, to: :direction_rate
53
+
54
+ alias_attribute :ps_from_id, :income_payment_system_id
55
+ alias_attribute :ps_to_id, :outcome_payment_system_id
56
+ alias_attribute :payment_system_from_id, :income_payment_system_id
57
+ alias_attribute :payment_system_to_id, :outcome_payment_system_id
58
+
59
+ alias_attribute :comission, :value
60
+ alias_attribute :commission, :value
61
+ alias_attribute :comission_percents, :value
62
+
63
+ alias_attribute :income_payment_system, :payment_system_from
64
+ alias_attribute :outcome_payment_system, :payment_system_to
65
+
66
+ def self.list_rates
67
+ order('id asc').each_with_object({}) do |er, h|
68
+ h[er.income_payment_system_id] ||= {}
69
+ h[er.income_payment_system_id][er.outcome_payment_system_id] = h.value
70
+ end
71
+ end
72
+
73
+ def available?
74
+ is_enabled?
75
+ end
76
+
77
+ def update_finite_rate!(finite_rate)
78
+ update! comission: calculate_comission(finite_rate, currency_rate.rate_value)
79
+ end
80
+
81
+ def custom_inspect
82
+ {
83
+ value: value,
84
+ exchange_rate_id: id,
85
+ payment_system_to: payment_system_to.to_s,
86
+ payment_system_from: payment_system_from.to_s,
87
+ out_currency: out_currency.to_s,
88
+ in_currency: in_currency.to_s
89
+ }.to_s
90
+ end
91
+
92
+ def currency_pair
93
+ @currency_pair ||= CurrencyPair.new in_currency, out_currency
94
+ end
95
+
96
+ def out_currency
97
+ Money::Currency.find out_cur
98
+ end
99
+
100
+ def currency_to
101
+ out_currency
102
+ end
103
+
104
+ def currency_from
105
+ in_currency
106
+ end
107
+
108
+ def in_currency
109
+ Money::Currency.find in_cur
110
+ end
111
+
112
+ def finite_rate
113
+ direction_rate.rate
114
+ end
115
+
116
+ def to_s
117
+ [in_currency, out_currency].join '/'
118
+ end
119
+
120
+ def direction_rate
121
+ Universe.direction_rates_repository.find_direction_rate_by_exchange_rate_id id
122
+ end
123
+
124
+ private
125
+
126
+ def update_direction_rates
127
+ DirectionsRatesWorker.perform_async(exchange_rate_id: id)
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Курсы внешних систем
4
+ module Gera
5
+ class ExternalRate < ApplicationRecord
6
+ include CurrencyPairSupport
7
+
8
+ belongs_to :source, class_name: 'RateSource'
9
+ belongs_to :snapshot, class_name: 'ExternalRateSnapshot'
10
+
11
+ scope :ordered, -> { order :cur_from, :cur_to }
12
+
13
+ before_validation do
14
+ self.source ||= snapshot.try :rate_source
15
+ end
16
+
17
+ before_validation :upcase_currencies
18
+
19
+ # TODO: validate cur_from, cur_to из списка разрешенных
20
+
21
+ delegate :actual_for, to: :snapshot
22
+
23
+ def direction_rate
24
+ Universe.direction_rates_repository.find_direction_rate_by_exchange_rate_id id
25
+ end
26
+
27
+ def dump
28
+ as_json(only: %i[id cur_from cur_to rate_value source_id created_at])
29
+ end
30
+
31
+ private
32
+
33
+ def upcase_currencies
34
+ self.cur_from = cur_from.to_s.upcase if cur_from.present?
35
+ self.cur_to = cur_to.to_s.upcase if cur_to.present?
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gera
4
+ class ExternalRateSnapshot < ApplicationRecord
5
+ belongs_to :rate_source
6
+
7
+ has_many :external_rates, foreign_key: :snapshot_id
8
+
9
+ scope :ordered, -> { order 'actual_for desc' }
10
+ scope :last_actuals_by_rate_sources, -> { where id: group(:rate_source_id).maximum(:id).values }
11
+
12
+ before_save do
13
+ self.actual_for ||= Time.zone.now
14
+ end
15
+
16
+ def to_s
17
+ "snapshot[#{id}]:#{rate_source}:#{actual_for}"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gera
4
+ class PaymentSystem < ApplicationRecord
5
+ include ::Archivable
6
+ include Gera::Mathematic
7
+ include Authority::Abilities
8
+
9
+ scope :ordered, -> { order :priority }
10
+ scope :enabled, -> { where 'income_enabled>0 or outcome_enabled>0' }
11
+ scope :disabled, -> { where income_enabled: false, outcome_enabled: false }
12
+ scope :available, -> { where is_available: true }
13
+
14
+ # TODO: move to kassa-admin
15
+ enum total_computation_method: %i[regular_fee reverse_fee]
16
+ enum transfer_comission_payer: %i[user shop], _prefix: :transfer_comission_payer
17
+
18
+ validates :name, presence: true, uniqueness: true
19
+ validates :currency, presence: true
20
+
21
+ before_create do
22
+ self.priority = self.class.maximum(:priority).to_i + 1
23
+ end
24
+
25
+ after_create :create_exchange_rates
26
+
27
+ delegate :iso_code, to: :currency, prefix: true, allow_nil: true
28
+
29
+ alias_attribute :archived_at, :deleted_at
30
+ alias_attribute :enable_income, :income_enabled
31
+ alias_attribute :enable_outcome, :outcome_enabled
32
+
33
+ # TODO: rename type_cy to currency
34
+ def currency
35
+ return unless type_cy
36
+
37
+ @currency ||= Money::Currency.find_by_local_id(type_cy) || raise("Не найдена валюта #{type_cy}")
38
+ end
39
+
40
+ def currency=(cur)
41
+ cur = Money::Currency.find cur unless cur.is_a? Money::Currency
42
+ self.type_cy = cur.is_a?(Money::Currency) ? cur.local_id : nil
43
+ end
44
+
45
+ def to_s
46
+ name
47
+ end
48
+
49
+ # TODO: move to kassa-admin
50
+ def total_with_fee(money)
51
+ calculate_total(money: money, fee: transfer_fee)
52
+ end
53
+
54
+ def unverified_total_with_fee(money)
55
+ calculate_total(money: money, fee: unverified_transfer_fee)
56
+ end
57
+
58
+ private
59
+
60
+ def calculate_total(money:, fee:)
61
+ if fee.computation_method == 'regular_fee'
62
+ calculate_total_using_regular_comission(money, fee.amount)
63
+ elsif fee.computation_method == 'reverse_fee'
64
+ calculate_total_using_reverse_comission(money, fee.amount)
65
+ else
66
+ raise NotImplementedError, "Нет расчета для #{fee.computation_method}"
67
+ end
68
+ end
69
+
70
+ def transfer_fee
71
+ OpenStruct.new(
72
+ amount: income_fee,
73
+ computation_method: total_computation_method
74
+ ).freeze
75
+ end
76
+
77
+ def unverified_transfer_fee
78
+ OpenStruct.new(
79
+ amount: unverified_income_fee,
80
+ computation_method: total_computation_method
81
+ ).freeze
82
+ end
83
+
84
+ DEFAULT_COMMISSION = 10
85
+
86
+ def create_exchange_rates
87
+ PaymentSystem.pluck(:id).each do |foreign_id|
88
+ ExchangeRate
89
+ .create_with(commission: DEFAULT_COMMISSION)
90
+ .find_or_create_by(payment_system_from_id: id, payment_system_to_id: foreign_id)
91
+
92
+ ExchangeRate
93
+ .create_with(commission: DEFAULT_COMMISSION)
94
+ .find_or_create_by(payment_system_from_id: foreign_id, payment_system_to_id: id)
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gera
4
+ class RateSource < ApplicationRecord
5
+ include Authority::Abilities
6
+ extend CurrencyPairGenerator
7
+ RateNotFound = Class.new StandardError
8
+
9
+ has_many :snapshots, class_name: 'ExternalRateSnapshot'
10
+ has_many :external_rates, foreign_key: :source_id
11
+
12
+ belongs_to :actual_snapshot, class_name: 'ExternalRateSnapshot', optional: true
13
+
14
+ scope :ordered, -> { order :priority }
15
+ scope :enabled, -> { where is_enabled: true }
16
+
17
+ scope :enabled_for_cross_rates, -> { enabled }
18
+
19
+ validates :key, presence: true, uniqueness: true
20
+
21
+ before_create do
22
+ self.priority ||= RateSource.maximum(:priority).to_i + 1
23
+ end
24
+
25
+ before_validation do
26
+ self.title ||= self.class.name.underscore
27
+ self.key ||= self.class.name.underscore
28
+ end
29
+
30
+ delegate :supported_currencies, :available_pairs, to: :class
31
+
32
+ def self.supported_currencies
33
+ raise 'not implemented'
34
+ end
35
+
36
+ def self.available_pairs
37
+ generate_pairs_from_currencies supported_currencies
38
+ end
39
+
40
+ def self.get!
41
+ where(type: name).take!
42
+ end
43
+
44
+ def find_rate_by_currency_pair!(pair)
45
+ find_rate_by_currency_pair(pair) || raise(RateNotFound, pair)
46
+ end
47
+
48
+ def find_rate_by_currency_pair(pair)
49
+ actual_rates.find_by_currency_pair pair
50
+ end
51
+
52
+ def to_s
53
+ name
54
+ end
55
+
56
+ def actual_rates
57
+ external_rates.where(snapshot_id: actual_snapshot_id)
58
+ end
59
+
60
+ def to_s
61
+ title
62
+ end
63
+
64
+ def is_currency_supported?(cur)
65
+ cur = Money::Currency.find cur unless cur.is_a? Money::Currency
66
+ supported_currencies.include? cur
67
+ end
68
+
69
+ private
70
+
71
+ def validate_currency!(*curs)
72
+ curs.each do |cur|
73
+ raise "Источник #{self} не поддерживает валюту #{cur}" unless is_currency_supported? cur
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gera
4
+ class RateSourceAuto < RateSource
5
+ def build_currency_rate(pair)
6
+ build_same(pair) ||
7
+ build_from_source(manual, pair) ||
8
+ build_from_source(cbr, pair) ||
9
+ build_from_source(exmo, pair) ||
10
+ build_cross(pair)
11
+ end
12
+
13
+ private
14
+
15
+ def build_from_source(source, pair, allow_inverse = true)
16
+ source.build_currency_rate pair, allow_inverse
17
+ end
18
+
19
+ def build_same(pair)
20
+ return unless pair.same?
21
+
22
+ CurrencyRate.new currency_pair: pair, rate_value: 1, mode: :same
23
+ end
24
+ end
25
+ end