double_entry 1.0.1 → 2.0.0.beta5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +497 -0
- data/README.md +107 -44
- data/double_entry.gemspec +22 -49
- data/lib/active_record/locking_extensions.rb +3 -3
- data/lib/active_record/locking_extensions/log_subscriber.rb +1 -1
- data/lib/double_entry.rb +29 -21
- data/lib/double_entry/account.rb +39 -46
- data/lib/double_entry/account_balance.rb +20 -3
- data/lib/double_entry/balance_calculator.rb +5 -5
- data/lib/double_entry/configurable.rb +1 -0
- data/lib/double_entry/configuration.rb +8 -2
- data/lib/double_entry/errors.rb +13 -13
- data/lib/double_entry/line.rb +7 -6
- data/lib/double_entry/locking.rb +5 -5
- data/lib/double_entry/transfer.rb +37 -30
- data/lib/double_entry/validation.rb +1 -0
- data/lib/double_entry/validation/account_fixer.rb +36 -0
- data/lib/double_entry/validation/line_check.rb +25 -43
- data/lib/double_entry/version.rb +1 -1
- data/lib/generators/double_entry/install/install_generator.rb +22 -1
- data/lib/generators/double_entry/install/templates/initializer.rb +20 -0
- data/lib/generators/double_entry/install/templates/migration.rb +45 -55
- metadata +35 -256
- data/.gitignore +0 -32
- data/.rspec +0 -2
- data/.travis.yml +0 -29
- data/.yardopts +0 -2
- data/Gemfile +0 -2
- data/Rakefile +0 -15
- data/lib/double_entry/reporting.rb +0 -181
- data/lib/double_entry/reporting/aggregate.rb +0 -110
- data/lib/double_entry/reporting/aggregate_array.rb +0 -76
- data/lib/double_entry/reporting/day_range.rb +0 -42
- data/lib/double_entry/reporting/hour_range.rb +0 -45
- data/lib/double_entry/reporting/line_aggregate.rb +0 -16
- data/lib/double_entry/reporting/line_aggregate_filter.rb +0 -79
- data/lib/double_entry/reporting/month_range.rb +0 -94
- data/lib/double_entry/reporting/time_range.rb +0 -59
- data/lib/double_entry/reporting/time_range_array.rb +0 -49
- data/lib/double_entry/reporting/week_range.rb +0 -107
- data/lib/double_entry/reporting/year_range.rb +0 -40
- data/script/jack_hammer +0 -210
- data/script/setup.sh +0 -8
- data/spec/active_record/locking_extensions_spec.rb +0 -110
- data/spec/double_entry/account_balance_spec.rb +0 -7
- data/spec/double_entry/account_spec.rb +0 -130
- data/spec/double_entry/balance_calculator_spec.rb +0 -88
- data/spec/double_entry/configuration_spec.rb +0 -50
- data/spec/double_entry/line_spec.rb +0 -80
- data/spec/double_entry/locking_spec.rb +0 -214
- data/spec/double_entry/performance/double_entry_performance_spec.rb +0 -32
- data/spec/double_entry/performance/reporting/aggregate_performance_spec.rb +0 -50
- data/spec/double_entry/reporting/aggregate_array_spec.rb +0 -123
- data/spec/double_entry/reporting/aggregate_spec.rb +0 -205
- data/spec/double_entry/reporting/line_aggregate_filter_spec.rb +0 -90
- data/spec/double_entry/reporting/line_aggregate_spec.rb +0 -39
- data/spec/double_entry/reporting/month_range_spec.rb +0 -139
- data/spec/double_entry/reporting/time_range_array_spec.rb +0 -169
- data/spec/double_entry/reporting/time_range_spec.rb +0 -45
- data/spec/double_entry/reporting/week_range_spec.rb +0 -103
- data/spec/double_entry/reporting_spec.rb +0 -181
- data/spec/double_entry/transfer_spec.rb +0 -93
- data/spec/double_entry/validation/line_check_spec.rb +0 -99
- data/spec/double_entry_spec.rb +0 -428
- data/spec/generators/double_entry/install/install_generator_spec.rb +0 -30
- data/spec/spec_helper.rb +0 -118
- data/spec/support/accounts.rb +0 -21
- data/spec/support/blueprints.rb +0 -43
- data/spec/support/database.example.yml +0 -21
- data/spec/support/database.travis.yml +0 -24
- data/spec/support/double_entry_spec_helper.rb +0 -27
- data/spec/support/gemfiles/Gemfile.rails-3.2.x +0 -8
- data/spec/support/gemfiles/Gemfile.rails-4.1.x +0 -6
- data/spec/support/gemfiles/Gemfile.rails-4.2.x +0 -5
- data/spec/support/gemfiles/Gemfile.rails-5.0.x +0 -5
- data/spec/support/performance_helper.rb +0 -26
- data/spec/support/reporting_configuration.rb +0 -6
- data/spec/support/schema.rb +0 -74
data/lib/double_entry/account.rb
CHANGED
@@ -1,93 +1,86 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
require 'forwardable'
|
3
|
+
|
2
4
|
module DoubleEntry
|
3
5
|
class Account
|
4
6
|
class << self
|
5
|
-
|
7
|
+
attr_accessor :scope_identifier_max_length, :account_identifier_max_length
|
8
|
+
attr_writer :accounts
|
6
9
|
|
7
10
|
# @api private
|
8
11
|
def accounts
|
9
12
|
@accounts ||= Set.new
|
10
13
|
end
|
11
14
|
|
12
|
-
# @api private
|
13
|
-
def scope_identifier_max_length
|
14
|
-
@scope_identifier_max_length ||= 23
|
15
|
-
end
|
16
|
-
|
17
|
-
# @api private
|
18
|
-
def account_identifier_max_length
|
19
|
-
@account_identifier_max_length ||= 31
|
20
|
-
end
|
21
|
-
|
22
15
|
# @api private
|
23
16
|
def account(identifier, options = {})
|
24
|
-
account = accounts.find(identifier, options[:scope].present?)
|
25
|
-
Instance.new(:
|
17
|
+
account = accounts.find(identifier, (options[:scope].present? || options[:scope_identity].present?))
|
18
|
+
Instance.new(account: account, scope: options[:scope], scope_identity: options[:scope_identity])
|
26
19
|
end
|
27
20
|
|
28
21
|
# @api private
|
29
22
|
def currency(identifier)
|
30
|
-
accounts.
|
23
|
+
accounts.find_without_scope(identifier).try(:currency)
|
31
24
|
end
|
32
25
|
end
|
33
26
|
|
34
27
|
# @api private
|
35
|
-
class Set
|
28
|
+
class Set
|
29
|
+
extend Forwardable
|
30
|
+
|
31
|
+
delegate [:each, :map] => :all
|
32
|
+
|
36
33
|
def define(attributes)
|
37
|
-
|
34
|
+
Account.new(attributes).tap do |account|
|
35
|
+
if find_without_scope(account.identifier)
|
36
|
+
fail DuplicateAccount
|
37
|
+
else
|
38
|
+
backing_collection[account.identifier] = account
|
39
|
+
end
|
40
|
+
end
|
38
41
|
end
|
39
42
|
|
40
43
|
def find(identifier, scoped)
|
41
|
-
found_account =
|
42
|
-
account.identifier == identifier && account.scoped? == scoped
|
43
|
-
end
|
44
|
-
fail UnknownAccount, "account: #{identifier} scoped?: #{scoped}" unless found_account
|
45
|
-
found_account
|
46
|
-
end
|
44
|
+
found_account = find_without_scope(identifier)
|
47
45
|
|
48
|
-
|
49
|
-
|
50
|
-
fail DuplicateAccount
|
46
|
+
if found_account && found_account.scoped? == scoped
|
47
|
+
found_account
|
51
48
|
else
|
52
|
-
|
49
|
+
fail UnknownAccount, "account: #{identifier} scoped?: #{scoped}"
|
53
50
|
end
|
54
51
|
end
|
55
52
|
|
56
|
-
def
|
57
|
-
|
53
|
+
def find_without_scope(identifier)
|
54
|
+
backing_collection[identifier]
|
58
55
|
end
|
59
|
-
end
|
60
56
|
|
61
|
-
|
62
|
-
|
63
|
-
@active_record_class = active_record_class
|
57
|
+
def all
|
58
|
+
backing_collection.values
|
64
59
|
end
|
65
60
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
value.id
|
71
|
-
when String, Fixnum
|
72
|
-
value
|
73
|
-
else
|
74
|
-
fail AccountScopeMismatchError, "Expected instance of `#{@active_record_class}`, received instance of `#{value.class}`"
|
75
|
-
end
|
76
|
-
end
|
61
|
+
private
|
62
|
+
|
63
|
+
def backing_collection
|
64
|
+
@backing_collection ||= Hash.new
|
77
65
|
end
|
78
66
|
end
|
79
67
|
|
80
68
|
class Instance
|
81
69
|
attr_reader :account, :scope
|
82
|
-
delegate :identifier, :scope_identifier, :scoped?, :positive_only, :negative_only, :currency, :
|
70
|
+
delegate :identifier, :scope_identifier, :scoped?, :positive_only, :negative_only, :currency, to: :account
|
83
71
|
|
84
72
|
def initialize(args)
|
85
73
|
@account = args[:account]
|
86
74
|
@scope = args[:scope]
|
75
|
+
@scope_identity = args[:scope_identity]
|
87
76
|
ensure_scope_is_valid
|
88
77
|
end
|
89
78
|
|
90
79
|
def scope_identity
|
80
|
+
@scope_identity || call_scope_identifier
|
81
|
+
end
|
82
|
+
|
83
|
+
def call_scope_identifier
|
91
84
|
scope_identifier.call(scope).to_s if scoped?
|
92
85
|
end
|
93
86
|
|
@@ -138,7 +131,7 @@ module DoubleEntry
|
|
138
131
|
|
139
132
|
def ensure_scope_is_valid
|
140
133
|
identity = scope_identity
|
141
|
-
if identity && identity.length > Account.scope_identifier_max_length
|
134
|
+
if identity && Account.scope_identifier_max_length && identity.length > Account.scope_identifier_max_length
|
142
135
|
fail ScopeIdentifierTooLongError,
|
143
136
|
"scope identifier '#{identity}' is too long. Please limit it to #{Account.scope_identifier_max_length} characters."
|
144
137
|
end
|
@@ -153,7 +146,7 @@ module DoubleEntry
|
|
153
146
|
@positive_only = args[:positive_only]
|
154
147
|
@negative_only = args[:negative_only]
|
155
148
|
@currency = args[:currency] || Money.default_currency
|
156
|
-
if identifier.length > Account.account_identifier_max_length
|
149
|
+
if Account.account_identifier_max_length && identifier.length > Account.account_identifier_max_length
|
157
150
|
fail AccountIdentifierTooLongError,
|
158
151
|
"account identifier '#{identifier}' is too long. Please limit it to #{Account.account_identifier_max_length} characters."
|
159
152
|
end
|
@@ -8,7 +8,7 @@ module DoubleEntry
|
|
8
8
|
#
|
9
9
|
# Account balances are created on demand when transfers occur.
|
10
10
|
class AccountBalance < ActiveRecord::Base
|
11
|
-
delegate :currency, :
|
11
|
+
delegate :currency, to: :account
|
12
12
|
|
13
13
|
def balance
|
14
14
|
self[:balance] && Money.new(self[:balance], currency)
|
@@ -25,13 +25,30 @@ module DoubleEntry
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def account
|
28
|
-
DoubleEntry.account(self[:account].to_sym, :
|
28
|
+
DoubleEntry.account(self[:account].to_sym, scope_identity: self[:scope])
|
29
29
|
end
|
30
30
|
|
31
31
|
def self.find_by_account(account, options = {})
|
32
|
-
scope = where(:
|
32
|
+
scope = where(scope: account.scope_identity, account: account.identifier.to_s)
|
33
33
|
scope = scope.lock(true) if options[:lock]
|
34
34
|
scope.first
|
35
35
|
end
|
36
|
+
|
37
|
+
# Identify the scopes with the given account identifier holding at least
|
38
|
+
# the provided minimum balance.
|
39
|
+
#
|
40
|
+
# @example Find users with at least $1,000,000 in their savings accounts
|
41
|
+
# DoubleEntry::AccountBalance.scopes_with_minimum_balance_for_account(
|
42
|
+
# 1_000_000.dollars,
|
43
|
+
# :savings,
|
44
|
+
# ) # might return the user ids: [ '1423', '12232', '34729' ]
|
45
|
+
# @param [Money] minimum_balance Minimum account balance a scope must have
|
46
|
+
# to be included in the result set.
|
47
|
+
# @param [Symbol] account_identifier
|
48
|
+
# @return [Array<String>] Scopes
|
49
|
+
#
|
50
|
+
def self.scopes_with_minimum_balance_for_account(minimum_balance, account_identifier)
|
51
|
+
where(account: account_identifier).where('balance >= ?', minimum_balance.fractional).pluck(:scope)
|
52
|
+
end
|
36
53
|
end
|
37
54
|
end
|
@@ -79,18 +79,18 @@ module DoubleEntry
|
|
79
79
|
# @api private
|
80
80
|
class RelationBuilder
|
81
81
|
attr_reader :options
|
82
|
-
delegate :account, :scope, :scope?, :from, :to, :between?, :at, :at?, :codes, :code?, :
|
82
|
+
delegate :account, :scope, :scope?, :from, :to, :between?, :at, :at?, :codes, :code?, to: :options
|
83
83
|
|
84
84
|
def initialize(options)
|
85
85
|
@options = options
|
86
86
|
end
|
87
87
|
|
88
88
|
def build
|
89
|
-
lines = Line.where(:
|
89
|
+
lines = Line.where(account: account)
|
90
90
|
lines = lines.where('created_at <= ?', at) if at?
|
91
|
-
lines = lines.where(:
|
92
|
-
lines = lines.where(:
|
93
|
-
lines = lines.where(:
|
91
|
+
lines = lines.where(created_at: from..to) if between?
|
92
|
+
lines = lines.where(code: codes) if code?
|
93
|
+
lines = lines.where(scope: scope) if scope?
|
94
94
|
lines
|
95
95
|
end
|
96
96
|
end
|
@@ -3,6 +3,12 @@ module DoubleEntry
|
|
3
3
|
include Configurable
|
4
4
|
|
5
5
|
class Configuration
|
6
|
+
attr_accessor :json_metadata
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@json_metadata = false
|
10
|
+
end
|
11
|
+
|
6
12
|
delegate(
|
7
13
|
:accounts,
|
8
14
|
:accounts=,
|
@@ -10,7 +16,7 @@ module DoubleEntry
|
|
10
16
|
:scope_identifier_max_length=,
|
11
17
|
:account_identifier_max_length,
|
12
18
|
:account_identifier_max_length=,
|
13
|
-
:
|
19
|
+
to: 'DoubleEntry::Account',
|
14
20
|
)
|
15
21
|
|
16
22
|
delegate(
|
@@ -18,7 +24,7 @@ module DoubleEntry
|
|
18
24
|
:transfers=,
|
19
25
|
:code_max_length,
|
20
26
|
:code_max_length=,
|
21
|
-
:
|
27
|
+
to: 'DoubleEntry::Transfer',
|
22
28
|
)
|
23
29
|
|
24
30
|
def define_accounts
|
data/lib/double_entry/errors.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
module DoubleEntry
|
3
|
-
class
|
4
|
-
class
|
5
|
-
class
|
6
|
-
class
|
7
|
-
class
|
8
|
-
class
|
9
|
-
class
|
10
|
-
class
|
11
|
-
class
|
12
|
-
class
|
13
|
-
class
|
14
|
-
class
|
15
|
-
class
|
3
|
+
class DoubleEntryError < RuntimeError; end
|
4
|
+
class UnknownAccount < DoubleEntryError; end
|
5
|
+
class AccountIdentifierTooLongError < DoubleEntryError; end
|
6
|
+
class ScopeIdentifierTooLongError < DoubleEntryError; end
|
7
|
+
class TransferNotAllowed < DoubleEntryError; end
|
8
|
+
class TransferIsNegative < DoubleEntryError; end
|
9
|
+
class TransferCodeTooLongError < DoubleEntryError; end
|
10
|
+
class DuplicateAccount < DoubleEntryError; end
|
11
|
+
class DuplicateTransfer < DoubleEntryError; end
|
12
|
+
class AccountWouldBeSentNegative < DoubleEntryError; end
|
13
|
+
class AccountWouldBeSentPositiveError < DoubleEntryError; end
|
14
|
+
class MismatchedCurrencies < DoubleEntryError; end
|
15
|
+
class MissingAccountError < DoubleEntryError; end
|
16
16
|
end
|
data/lib/double_entry/line.rb
CHANGED
@@ -55,8 +55,9 @@ module DoubleEntry
|
|
55
55
|
# by account, or account and code, over a particular period.
|
56
56
|
#
|
57
57
|
class Line < ActiveRecord::Base
|
58
|
-
belongs_to :detail, :
|
59
|
-
has_many :metadata, :
|
58
|
+
belongs_to :detail, polymorphic: true, required: false
|
59
|
+
has_many :metadata, class_name: 'DoubleEntry::LineMetadata' unless -> { DoubleEntry.config.json_metadata }
|
60
|
+
scope :with_id_greater_than, ->(id) { where('id > ?', id) }
|
60
61
|
|
61
62
|
def amount
|
62
63
|
self[:amount] && Money.new(self[:amount], currency)
|
@@ -74,12 +75,12 @@ module DoubleEntry
|
|
74
75
|
self[:balance] = (money && money.fractional)
|
75
76
|
end
|
76
77
|
|
77
|
-
def save(
|
78
|
+
def save(**)
|
78
79
|
check_balance_will_remain_valid
|
79
80
|
super
|
80
81
|
end
|
81
82
|
|
82
|
-
def save!(
|
83
|
+
def save!(**)
|
83
84
|
check_balance_will_remain_valid
|
84
85
|
super
|
85
86
|
end
|
@@ -101,7 +102,7 @@ module DoubleEntry
|
|
101
102
|
end
|
102
103
|
|
103
104
|
def account
|
104
|
-
DoubleEntry.account(self[:account].to_sym, :
|
105
|
+
DoubleEntry.account(self[:account].to_sym, scope_identity: scope)
|
105
106
|
end
|
106
107
|
|
107
108
|
def currency
|
@@ -116,7 +117,7 @@ module DoubleEntry
|
|
116
117
|
end
|
117
118
|
|
118
119
|
def partner_account
|
119
|
-
DoubleEntry.account(self[:partner_account].to_sym, :
|
120
|
+
DoubleEntry.account(self[:partner_account].to_sym, scope_identity: partner_scope)
|
120
121
|
end
|
121
122
|
|
122
123
|
def partner
|
data/lib/double_entry/locking.rb
CHANGED
@@ -32,14 +32,14 @@ module DoubleEntry
|
|
32
32
|
#
|
33
33
|
# The transaction must be the outermost transaction to ensure data integrity. A
|
34
34
|
# LockMustBeOutermostTransaction will be raised if it isn't.
|
35
|
-
def self.lock_accounts(*accounts)
|
35
|
+
def self.lock_accounts(*accounts, &block)
|
36
36
|
lock = Lock.new(accounts)
|
37
37
|
|
38
38
|
if lock.in_a_locked_transaction?
|
39
39
|
lock.ensure_locked!
|
40
|
-
|
40
|
+
block.call
|
41
41
|
else
|
42
|
-
lock.perform_lock(&
|
42
|
+
lock.perform_lock(&block)
|
43
43
|
end
|
44
44
|
|
45
45
|
rescue ActiveRecord::StatementInvalid => exception
|
@@ -149,7 +149,7 @@ module DoubleEntry
|
|
149
149
|
# If one or more account balance records don't exist, set
|
150
150
|
# accounts_with_balances to the corresponding accounts, and return false.
|
151
151
|
def grab_locks
|
152
|
-
account_balances = @accounts.map { |account| AccountBalance.find_by_account(account, :
|
152
|
+
account_balances = @accounts.map { |account| AccountBalance.find_by_account(account, lock: true) }
|
153
153
|
|
154
154
|
if account_balances.any?(&:nil?)
|
155
155
|
@accounts_without_balances = @accounts.zip(account_balances).
|
@@ -168,7 +168,7 @@ module DoubleEntry
|
|
168
168
|
# Get the initial balance from the lines table.
|
169
169
|
balance = account.balance
|
170
170
|
# Try to create the balance record, but ignore it if someone else has done it in the meantime.
|
171
|
-
AccountBalance.create_ignoring_duplicates!(:
|
171
|
+
AccountBalance.create_ignoring_duplicates!(account: account, balance: balance)
|
172
172
|
end
|
173
173
|
end
|
174
174
|
end
|
@@ -1,19 +1,17 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
require 'forwardable'
|
3
|
+
|
2
4
|
module DoubleEntry
|
3
5
|
class Transfer
|
4
6
|
class << self
|
5
|
-
|
7
|
+
attr_accessor :code_max_length
|
8
|
+
attr_writer :transfers
|
6
9
|
|
7
10
|
# @api private
|
8
11
|
def transfers
|
9
12
|
@transfers ||= Set.new
|
10
13
|
end
|
11
14
|
|
12
|
-
# @api private
|
13
|
-
def code_max_length
|
14
|
-
@code_max_length ||= 47
|
15
|
-
end
|
16
|
-
|
17
15
|
# @api private
|
18
16
|
def transfer(amount, options = {})
|
19
17
|
fail TransferIsNegative if amount.negative?
|
@@ -25,37 +23,43 @@ module DoubleEntry
|
|
25
23
|
end
|
26
24
|
|
27
25
|
# @api private
|
28
|
-
class Set
|
26
|
+
class Set
|
27
|
+
extend Forwardable
|
28
|
+
delegate [:each, :map] => :all
|
29
|
+
|
29
30
|
def define(attributes)
|
30
|
-
|
31
|
+
Transfer.new(attributes).tap do |transfer|
|
32
|
+
key = [transfer.from, transfer.to, transfer.code]
|
33
|
+
if _find(*key)
|
34
|
+
fail DuplicateTransfer
|
35
|
+
else
|
36
|
+
backing_collection[key] = transfer
|
37
|
+
end
|
38
|
+
end
|
31
39
|
end
|
32
40
|
|
33
|
-
def find(
|
34
|
-
_find(
|
41
|
+
def find(from_account, to_account, code)
|
42
|
+
_find(from_account.identifier, to_account.identifier, code)
|
35
43
|
end
|
36
44
|
|
37
|
-
def find!(
|
38
|
-
find(
|
39
|
-
fail TransferNotAllowed, [
|
45
|
+
def find!(from_account, to_account, code)
|
46
|
+
find(from_account, to_account, code).tap do |transfer|
|
47
|
+
fail TransferNotAllowed, [from_account.identifier, to_account.identifier, code].inspect unless transfer
|
40
48
|
end
|
41
49
|
end
|
42
50
|
|
43
|
-
def
|
44
|
-
|
45
|
-
fail DuplicateTransfer
|
46
|
-
else
|
47
|
-
super(transfer)
|
48
|
-
end
|
51
|
+
def all
|
52
|
+
backing_collection.values
|
49
53
|
end
|
50
54
|
|
51
55
|
private
|
52
56
|
|
57
|
+
def backing_collection
|
58
|
+
@backing_collection ||= Hash.new
|
59
|
+
end
|
60
|
+
|
53
61
|
def _find(from, to, code)
|
54
|
-
|
55
|
-
transfer.from == from &&
|
56
|
-
transfer.to == to &&
|
57
|
-
transfer.code == code
|
58
|
-
end
|
62
|
+
backing_collection[[from, to, code]]
|
59
63
|
end
|
60
64
|
end
|
61
65
|
|
@@ -65,7 +69,7 @@ module DoubleEntry
|
|
65
69
|
@code = attributes[:code]
|
66
70
|
@from = attributes[:from]
|
67
71
|
@to = attributes[:to]
|
68
|
-
if code.length > Transfer.code_max_length
|
72
|
+
if Transfer.code_max_length && code.length > Transfer.code_max_length
|
69
73
|
fail TransferCodeTooLongError,
|
70
74
|
"transfer code '#{code}' is too long. Please limit it to #{Transfer.code_max_length} characters."
|
71
75
|
end
|
@@ -84,12 +88,12 @@ module DoubleEntry
|
|
84
88
|
fail MismatchedCurrencies, "Mismatched currency (#{to_account.currency} <> #{from_account.currency})"
|
85
89
|
end
|
86
90
|
Locking.lock_accounts(from_account, to_account) do
|
87
|
-
credit, debit = create_lines(amount, code, detail, from_account, to_account)
|
88
|
-
create_line_metadata(credit, debit, metadata) if metadata
|
91
|
+
credit, debit = create_lines(amount, code, detail, from_account, to_account, metadata)
|
92
|
+
create_line_metadata(credit, debit, metadata) if metadata && !DoubleEntry.config.json_metadata
|
89
93
|
end
|
90
94
|
end
|
91
95
|
|
92
|
-
def create_lines(amount, code, detail, from_account, to_account)
|
96
|
+
def create_lines(amount, code, detail, from_account, to_account, metadata)
|
93
97
|
credit, debit = Line.new, Line.new
|
94
98
|
|
95
99
|
credit_balance = Locking.balance_for_locked_account(from_account)
|
@@ -103,6 +107,7 @@ module DoubleEntry
|
|
103
107
|
credit.code, debit.code = code, code
|
104
108
|
credit.detail, debit.detail = detail, detail
|
105
109
|
credit.balance, debit.balance = credit_balance.balance, debit_balance.balance
|
110
|
+
credit.metadata, debit.metadata = metadata, metadata if DoubleEntry.config.json_metadata
|
106
111
|
|
107
112
|
credit.partner_account, debit.partner_account = to_account, from_account
|
108
113
|
|
@@ -115,8 +120,10 @@ module DoubleEntry
|
|
115
120
|
|
116
121
|
def create_line_metadata(credit, debit, metadata)
|
117
122
|
metadata.each_pair do |key, value|
|
118
|
-
|
119
|
-
|
123
|
+
Array(value).each do |each_value|
|
124
|
+
LineMetadata.create!(line: credit, key: key, value: each_value)
|
125
|
+
LineMetadata.create!(line: debit, key: key, value: each_value)
|
126
|
+
end
|
120
127
|
end
|
121
128
|
end
|
122
129
|
end
|