double_entry 1.0.1 → 2.0.0.beta5
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 +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
|