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.
- checksums.yaml +7 -0
- data/LICENSE +674 -0
- data/README.md +67 -0
- data/Rakefile +43 -0
- data/app/assets/config/gera_manifest.js +2 -0
- data/app/assets/javascripts/gera/application.js +14 -0
- data/app/assets/stylesheets/gera/application.css +15 -0
- data/app/authorizers/gera/application_authorizer.rb +6 -0
- data/app/authorizers/gera/currency_rate_authorizer.rb +9 -0
- data/app/authorizers/gera/currency_rate_mode_authorizer.rb +7 -0
- data/app/authorizers/gera/direction_rate_authorizer.rb +8 -0
- data/app/authorizers/gera/exchange_rate_authorizer.rb +8 -0
- data/app/authorizers/gera/payment_system_authorizer.rb +13 -0
- data/app/authorizers/gera/rate_source_authorizer.rb +7 -0
- data/app/controllers/gera/application_controller.rb +30 -0
- data/app/controllers/gera/currencies_controller.rb +8 -0
- data/app/controllers/gera/currency_rate_history_intervals_controller.rb +53 -0
- data/app/controllers/gera/currency_rate_mode_snapshots_controller.rb +83 -0
- data/app/controllers/gera/currency_rate_modes_controller.rb +60 -0
- data/app/controllers/gera/currency_rates_controller.rb +70 -0
- data/app/controllers/gera/direction_rate_history_intervals_controller.rb +62 -0
- data/app/controllers/gera/direction_rates_controller.rb +28 -0
- data/app/controllers/gera/exchange_rates_controller.rb +25 -0
- data/app/controllers/gera/external_rate_snapshots_controller.rb +35 -0
- data/app/controllers/gera/external_rates_controller.rb +28 -0
- data/app/controllers/gera/payment_systems_controller.rb +78 -0
- data/app/controllers/gera/rate_sources_controller.rb +8 -0
- data/app/decorators/gera/currency_rate_decorator.rb +148 -0
- data/app/decorators/gera/direction_rate_decorator.rb +47 -0
- data/app/decorators/gera/payment_system_decorator.rb +19 -0
- data/app/helpers/gera/application_helper.rb +110 -0
- data/app/helpers/gera/currency_rate_helper.rb +19 -0
- data/app/helpers/gera/currency_rate_modes_helper.rb +73 -0
- data/app/helpers/gera/direction_rate_helper.rb +36 -0
- data/app/helpers/gera/exchange_rates_helper.rb +21 -0
- data/app/jobs/gera/application_job.rb +6 -0
- data/app/models/concerns/gera/currency_pair_generator.rb +14 -0
- data/app/models/concerns/gera/currency_pair_support.rb +42 -0
- data/app/models/concerns/gera/currency_rate_mode_builder_support.rb +35 -0
- data/app/models/concerns/gera/direction_support.rb +14 -0
- data/app/models/concerns/gera/history_interval_concern.rb +46 -0
- data/app/models/gera/application_record.rb +8 -0
- data/app/models/gera/cbr_external_rate.rb +13 -0
- data/app/models/gera/cross_rate_mode.rb +13 -0
- data/app/models/gera/currency_rate.rb +78 -0
- data/app/models/gera/currency_rate_history_interval.rb +25 -0
- data/app/models/gera/currency_rate_history_interval_filter.rb +30 -0
- data/app/models/gera/currency_rate_mode.rb +20 -0
- data/app/models/gera/currency_rate_mode_snapshot.rb +26 -0
- data/app/models/gera/currency_rate_snapshot.rb +12 -0
- data/app/models/gera/direction.rb +44 -0
- data/app/models/gera/direction_rate.rb +136 -0
- data/app/models/gera/direction_rate_history_interval.rb +33 -0
- data/app/models/gera/direction_rate_history_interval_filter.rb +30 -0
- data/app/models/gera/direction_rate_snapshot.rb +7 -0
- data/app/models/gera/exchange_rate.rb +130 -0
- data/app/models/gera/external_rate.rb +38 -0
- data/app/models/gera/external_rate_snapshot.rb +20 -0
- data/app/models/gera/payment_system.rb +98 -0
- data/app/models/gera/rate_source.rb +77 -0
- data/app/models/gera/rate_source_auto.rb +25 -0
- data/app/models/gera/rate_source_bitfinex.rb +9 -0
- data/app/models/gera/rate_source_cbr.rb +13 -0
- data/app/models/gera/rate_source_cbr_avg.rb +6 -0
- data/app/models/gera/rate_source_exmo.rb +9 -0
- data/app/models/gera/rate_source_manual.rb +13 -0
- data/app/views/application/_exchange_rate_monitor.slim +1 -0
- data/app/views/gera/application/_attributes_table.slim +5 -0
- data/app/views/gera/application/_currencies_table.slim +18 -0
- data/app/views/gera/application/_currency_rate_mode_snapshots.slim +6 -0
- data/app/views/gera/application/_direction_rate.slim +6 -0
- data/app/views/gera/application/_directions_submenu.slim +11 -0
- data/app/views/gera/application/_directions_table.slim +15 -0
- data/app/views/gera/application/_exchange_rate.slim +6 -0
- data/app/views/gera/application/_external_rate.slim +6 -0
- data/app/views/gera/application/_header.slim +12 -0
- data/app/views/gera/application/_table.slim +28 -0
- data/app/views/gera/application/_topnav.slim +21 -0
- data/app/views/gera/application/direction_details.slim +75 -0
- data/app/views/gera/application/page.html.slim +1 -0
- data/app/views/gera/currencies/index.html.slim +20 -0
- data/app/views/gera/currency_rate_history_intervals/_filter.slim +11 -0
- data/app/views/gera/currency_rate_history_intervals/index.slim +39 -0
- data/app/views/gera/currency_rate_mode_snapshots/_snapshot.slim +20 -0
- data/app/views/gera/currency_rate_mode_snapshots/edit.html.slim +30 -0
- data/app/views/gera/currency_rate_modes/_cross_rate_mode_fields.slim +21 -0
- data/app/views/gera/currency_rate_modes/_currency_rate_mode.slim +7 -0
- data/app/views/gera/currency_rate_modes/_form.slim +29 -0
- data/app/views/gera/currency_rate_modes/edit.slim +11 -0
- data/app/views/gera/currency_rate_modes/index.html.slim +12 -0
- data/app/views/gera/currency_rates/_currency_rate.slim +22 -0
- data/app/views/gera/currency_rates/_currency_rate_humanized.slim +35 -0
- data/app/views/gera/currency_rates/diff.slim +12 -0
- data/app/views/gera/currency_rates/index.slim +27 -0
- data/app/views/gera/currency_rates/modes.slim +14 -0
- data/app/views/gera/currency_rates/show.slim +4 -0
- data/app/views/gera/direction_rate_history_intervals/_filter.slim +11 -0
- data/app/views/gera/direction_rate_history_intervals/index.slim +41 -0
- data/app/views/gera/direction_rates/_direction_rate_example_calculator.slim +28 -0
- data/app/views/gera/direction_rates/diff.slim +19 -0
- data/app/views/gera/direction_rates/index.slim +26 -0
- data/app/views/gera/direction_rates/legacy.html.slim +5 -0
- data/app/views/gera/direction_rates/minimals.slim +5 -0
- data/app/views/gera/direction_rates/show.slim +14 -0
- data/app/views/gera/external_rate_snapshots/index.slim +25 -0
- data/app/views/gera/external_rate_snapshots/show.slim +22 -0
- data/app/views/gera/external_rates/_flat_list.slim +22 -0
- data/app/views/gera/external_rates/index.html.slim +28 -0
- data/app/views/gera/external_rates/show.slim +1 -0
- data/app/views/gera/payment_systems/_fields.slim +4 -0
- data/app/views/gera/payment_systems/_fields_table.slim +18 -0
- data/app/views/gera/payment_systems/_form.slim +9 -0
- data/app/views/gera/payment_systems/edit.slim +7 -0
- data/app/views/gera/payment_systems/index.slim +3 -0
- data/app/views/gera/payment_systems/new.slim +9 -0
- data/app/views/gera/payment_systems/show.slim +1 -0
- data/app/views/gera/rate_sources/_list.slim +13 -0
- data/app/views/gera/rate_sources/_rate_source.slim +23 -0
- data/app/views/gera/rate_sources/index.html.slim +6 -0
- data/app/views/layouts/application.html.slim +15 -0
- data/app/views/layouts/gera/blank.html.slim +10 -0
- data/app/workers/concerns/gera/rates_worker.rb +87 -0
- data/app/workers/gera/bitfinex_rates_worker.rb +65 -0
- data/app/workers/gera/cbr_avg_rates_worker.rb +42 -0
- data/app/workers/gera/cbr_rates_worker.rb +192 -0
- data/app/workers/gera/create_history_intervals_worker.rb +38 -0
- data/app/workers/gera/currency_rates_worker.rb +64 -0
- data/app/workers/gera/directions_rates_worker.rb +51 -0
- data/app/workers/gera/exmo_rates_worker.rb +62 -0
- data/app/workers/gera/purge_currency_rates_worker.rb +21 -0
- data/app/workers/gera/purge_direction_rates_worker.rb +30 -0
- data/config/currencies.yml +559 -0
- data/config/routes.rb +30 -0
- data/db/migrate/20180912130000_setup.rb +228 -0
- data/db/migrate/20190314114844_add_is_available_to_payment_systems.rb +7 -0
- data/db/migrate/20190315113046_add_icon_url_to_payment_systems.rb +7 -0
- data/db/migrate/20190315132137_add_commission_to_payment_systems.rb +7 -0
- data/lib/banks/currency_exchange.rb +12 -0
- data/lib/builders/currency_rate_auto_builder.rb +38 -0
- data/lib/builders/currency_rate_builder.rb +60 -0
- data/lib/builders/currency_rate_cross_builder.rb +70 -0
- data/lib/builders/currency_rate_direct_builder.rb +26 -0
- data/lib/gera.rb +32 -0
- data/lib/gera/bitfinex_fetcher.rb +36 -0
- data/lib/gera/configuration.rb +47 -0
- data/lib/gera/currencies_purger.rb +28 -0
- data/lib/gera/currency_pair.rb +93 -0
- data/lib/gera/engine.rb +14 -0
- data/lib/gera/mathematic.rb +156 -0
- data/lib/gera/money_support.rb +89 -0
- data/lib/gera/numeric.rb +25 -0
- data/lib/gera/railtie.rb +8 -0
- data/lib/gera/rate.rb +22 -0
- data/lib/gera/rate_from_multiplicator.rb +54 -0
- data/lib/gera/repositories/currency_rate_modes_repository.rb +37 -0
- data/lib/gera/repositories/currency_rates_repository.rb +25 -0
- data/lib/gera/repositories/direction_rates_repository.rb +46 -0
- data/lib/gera/repositories/exchange_rates_repository.rb +23 -0
- data/lib/gera/repositories/payment_systems_repository.rb +21 -0
- data/lib/gera/repositories/universe.rb +47 -0
- data/lib/gera/version.rb +3 -0
- data/lib/tasks/auto_generate_diagram.rake +6 -0
- data/lib/tasks/gera_tasks.rake +4 -0
- 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
|
data/lib/gera/engine.rb
ADDED
|
@@ -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
|