double_entry 0.8.0 → 0.9.0
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 +4 -4
- data/lib/double_entry/account.rb +22 -2
- data/lib/double_entry/configuration.rb +18 -2
- data/lib/double_entry/errors.rb +3 -0
- data/lib/double_entry/reporting/aggregate.rb +18 -29
- data/lib/double_entry/reporting/aggregate_array.rb +13 -8
- data/lib/double_entry/transfer.rb +15 -3
- data/lib/double_entry/version.rb +1 -1
- data/lib/generators/double_entry/install/templates/migration.rb +32 -32
- data/spec/double_entry/account_spec.rb +61 -28
- data/spec/double_entry/configuration_spec.rb +29 -0
- data/spec/double_entry/line_spec.rb +19 -19
- data/spec/double_entry/locking_spec.rb +14 -2
- data/spec/double_entry/transfer_spec.rb +33 -12
- data/spec/double_entry_spec.rb +1 -2
- data/spec/support/schema.rb +38 -43
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bee621c780c82e734a95964749650675683f1460
|
4
|
+
data.tar.gz: 326d9d02768905beede8b785215b1eb164a4c8cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e98f6f1a3b11a5bd9f8b4e384eb3dff8a6cd56b40ae3f8b430565fdbc601e3532a6cc95f8fdacc469457425533745d99a7b6cc8accaf4a6dd67b0d2b27ed41b0
|
7
|
+
data.tar.gz: 945ba3173e2b170e1cee74fcb89e31f8ce4a2245fa75dbe6efa267850196c7470495ffd3b4f47bae23acef6aa89722e7ca0379a53ff927ca7f8e49d9727fdb48
|
data/lib/double_entry/account.rb
CHANGED
@@ -3,13 +3,23 @@ module DoubleEntry
|
|
3
3
|
class Account
|
4
4
|
|
5
5
|
class << self
|
6
|
-
attr_writer :accounts
|
6
|
+
attr_writer :accounts, :scope_identifier_max_length, :account_identifier_max_length
|
7
7
|
|
8
8
|
# @api private
|
9
9
|
def accounts
|
10
10
|
@accounts ||= Set.new
|
11
11
|
end
|
12
12
|
|
13
|
+
# @api private
|
14
|
+
def scope_identifier_max_length
|
15
|
+
@scope_identifier_max_length ||= 23
|
16
|
+
end
|
17
|
+
|
18
|
+
# @api private
|
19
|
+
def account_identifier_max_length
|
20
|
+
@account_identifier_max_length ||= 31
|
21
|
+
end
|
22
|
+
|
13
23
|
# @api private
|
14
24
|
def account(identifier, options = {})
|
15
25
|
account = accounts.find(identifier, options[:scope].present?)
|
@@ -128,7 +138,12 @@ module DoubleEntry
|
|
128
138
|
private
|
129
139
|
|
130
140
|
def ensure_scope_is_valid
|
131
|
-
scope_identity
|
141
|
+
identity = scope_identity
|
142
|
+
if identity && identity.length > Account.scope_identifier_max_length
|
143
|
+
raise ScopeIdentifierTooLongError.new(
|
144
|
+
"scope identifier '#{identity}' is too long. Please limit it to #{Account.scope_identifier_max_length} characters."
|
145
|
+
)
|
146
|
+
end
|
132
147
|
end
|
133
148
|
end
|
134
149
|
|
@@ -139,6 +154,11 @@ module DoubleEntry
|
|
139
154
|
@scope_identifier = args[:scope_identifier]
|
140
155
|
@positive_only = args[:positive_only]
|
141
156
|
@currency = args[:currency] || Money.default_currency
|
157
|
+
if identifier.length > Account.account_identifier_max_length
|
158
|
+
raise AccountIdentifierTooLongError.new(
|
159
|
+
"account identifier '#{identifier}' is too long. Please limit it to #{Account.account_identifier_max_length} characters."
|
160
|
+
)
|
161
|
+
end
|
142
162
|
end
|
143
163
|
|
144
164
|
def scoped?
|
@@ -3,8 +3,24 @@ module DoubleEntry
|
|
3
3
|
include Configurable
|
4
4
|
|
5
5
|
class Configuration
|
6
|
-
|
7
|
-
delegate
|
6
|
+
|
7
|
+
delegate(
|
8
|
+
:accounts,
|
9
|
+
:accounts=,
|
10
|
+
:scope_identifier_max_length,
|
11
|
+
:scope_identifier_max_length=,
|
12
|
+
:account_identifier_max_length,
|
13
|
+
:account_identifier_max_length=,
|
14
|
+
:to => "DoubleEntry::Account",
|
15
|
+
)
|
16
|
+
|
17
|
+
delegate(
|
18
|
+
:transfers,
|
19
|
+
:transfers=,
|
20
|
+
:code_max_length,
|
21
|
+
:code_max_length=,
|
22
|
+
:to => "DoubleEntry::Transfer",
|
23
|
+
)
|
8
24
|
|
9
25
|
def define_accounts
|
10
26
|
yield accounts
|
data/lib/double_entry/errors.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
module DoubleEntry
|
3
3
|
class UnknownAccount < RuntimeError; end
|
4
|
+
class AccountIdentifierTooLongError < RuntimeError; end
|
5
|
+
class ScopeIdentifierTooLongError < RuntimeError; end
|
4
6
|
class TransferNotAllowed < RuntimeError; end
|
5
7
|
class TransferIsNegative < RuntimeError; end
|
8
|
+
class TransferCodeTooLongError < RuntimeError; end
|
6
9
|
class DuplicateAccount < RuntimeError; end
|
7
10
|
class DuplicateTransfer < RuntimeError; end
|
8
11
|
class AccountWouldBeSentNegative < RuntimeError; end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
module DoubleEntry
|
3
3
|
module Reporting
|
4
4
|
class Aggregate
|
5
|
-
attr_reader :function, :account, :code, :range, :options, :filter
|
5
|
+
attr_reader :function, :account, :code, :range, :options, :filter, :currency
|
6
6
|
|
7
7
|
def initialize(function, account, code, options)
|
8
8
|
@function = function.to_s
|
@@ -13,6 +13,7 @@ module DoubleEntry
|
|
13
13
|
@options = options
|
14
14
|
@range = options[:range]
|
15
15
|
@filter = options[:filter]
|
16
|
+
@currency = DoubleEntry::Account.currency(account)
|
16
17
|
end
|
17
18
|
|
18
19
|
def amount(force_recalculation = false)
|
@@ -24,18 +25,12 @@ module DoubleEntry
|
|
24
25
|
end
|
25
26
|
end
|
26
27
|
|
27
|
-
def formatted_amount
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
def self.formatted_amount(function, amount, currency)
|
32
|
-
safe_amount = amount || 0
|
33
|
-
|
34
|
-
case function.to_s
|
35
|
-
when 'count'
|
36
|
-
safe_amount
|
28
|
+
def formatted_amount(amount = amount)
|
29
|
+
amount ||= 0
|
30
|
+
if function == "count"
|
31
|
+
amount
|
37
32
|
else
|
38
|
-
Money.new(
|
33
|
+
Money.new(amount, currency)
|
39
34
|
end
|
40
35
|
end
|
41
36
|
|
@@ -46,10 +41,6 @@ module DoubleEntry
|
|
46
41
|
aggregate.amount if aggregate
|
47
42
|
end
|
48
43
|
|
49
|
-
def currency
|
50
|
-
DoubleEntry::Account.currency(account)
|
51
|
-
end
|
52
|
-
|
53
44
|
def clear_old_aggregates
|
54
45
|
LineAggregate.delete_all(field_hash)
|
55
46
|
end
|
@@ -75,28 +66,26 @@ module DoubleEntry
|
|
75
66
|
# otherwise they will get excruciatingly slow to calculate
|
76
67
|
# as the year progresses. (I am thinking mainly of the 'current' year.)
|
77
68
|
# Combining monthly aggregates will mean that the figure will be partially memoized
|
78
|
-
|
79
|
-
when 'average'
|
69
|
+
if function == "average"
|
80
70
|
calculate_yearly_average
|
81
71
|
else
|
82
|
-
zero =
|
83
|
-
|
72
|
+
zero = formatted_amount(0)
|
84
73
|
result = (1..12).inject(zero) do |total, month|
|
85
|
-
total += Reporting.aggregate(
|
86
|
-
|
74
|
+
total += Reporting.aggregate(
|
75
|
+
function, account, code,
|
76
|
+
:range => MonthRange.new(:year => range.year, :month => month),
|
77
|
+
:filter => filter,
|
78
|
+
)
|
87
79
|
end
|
88
|
-
|
89
|
-
result = result.cents if result.class == Money
|
90
|
-
result
|
80
|
+
result.is_a?(Money) ? result.cents : result
|
91
81
|
end
|
92
82
|
end
|
93
83
|
|
94
84
|
def calculate_yearly_average
|
95
85
|
# need this seperate function, because an average of averages is not the correct average
|
96
|
-
|
97
|
-
|
98
|
-
count = Reporting.aggregate(:count, account, code,
|
99
|
-
:range => YearRange.new(:year => range.year), :filter => filter)
|
86
|
+
year_range = YearRange.new(:year => range.year)
|
87
|
+
sum = Reporting.aggregate(:sum, account, code, :range => year_range, :filter => filter)
|
88
|
+
count = Reporting.aggregate(:count, account, code, :range => year_range, :filter => filter)
|
100
89
|
(count == 0) ? 0 : (sum / count).cents
|
101
90
|
end
|
102
91
|
|
@@ -9,16 +9,17 @@ module DoubleEntry
|
|
9
9
|
#
|
10
10
|
# For example, you could request all sales
|
11
11
|
# broken down by month and it would return an array of values
|
12
|
-
attr_reader :function, :account, :code, :filter, :range_type, :start, :finish
|
12
|
+
attr_reader :function, :account, :code, :filter, :range_type, :start, :finish, :currency
|
13
13
|
|
14
14
|
def initialize(function, account, code, options)
|
15
|
-
@function = function
|
15
|
+
@function = function.to_s
|
16
16
|
@account = account
|
17
17
|
@code = code
|
18
18
|
@filter = options[:filter]
|
19
19
|
@range_type = options[:range_type]
|
20
20
|
@start = options[:start]
|
21
21
|
@finish = options[:finish]
|
22
|
+
@currency = DoubleEntry::Account.currency(account)
|
22
23
|
|
23
24
|
retrieve_aggregates
|
24
25
|
fill_in_missing_aggregates
|
@@ -47,15 +48,14 @@ module DoubleEntry
|
|
47
48
|
def retrieve_aggregates
|
48
49
|
raise ArgumentError.new("Invalid range type '#{range_type}'") unless %w(year month week day hour).include? range_type
|
49
50
|
@aggregates = LineAggregate.
|
50
|
-
where(:function => function
|
51
|
+
where(:function => function).
|
51
52
|
where(:range_type => 'normal').
|
52
53
|
where(:account => account.to_s).
|
53
54
|
where(:code => code.to_s).
|
54
55
|
where(:filter => filter.inspect).
|
55
56
|
where(LineAggregate.arel_table[range_type].not_eq(nil)).
|
56
|
-
|
57
|
-
hash[result.key] =
|
58
|
-
hash
|
57
|
+
each_with_object({}) do |hash, result|
|
58
|
+
hash[result.key] = formatted_amount(result.amount)
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
@@ -63,8 +63,13 @@ module DoubleEntry
|
|
63
63
|
TimeRangeArray.make(range_type, start, finish)
|
64
64
|
end
|
65
65
|
|
66
|
-
def
|
67
|
-
|
66
|
+
def formatted_amount(amount)
|
67
|
+
amount ||= 0
|
68
|
+
if function == "count"
|
69
|
+
amount
|
70
|
+
else
|
71
|
+
Money.new(amount, currency)
|
72
|
+
end
|
68
73
|
end
|
69
74
|
end
|
70
75
|
end
|
@@ -3,13 +3,18 @@ module DoubleEntry
|
|
3
3
|
class Transfer
|
4
4
|
|
5
5
|
class << self
|
6
|
-
attr_writer :transfers
|
6
|
+
attr_writer :transfers, :code_max_length
|
7
7
|
|
8
8
|
# @api private
|
9
9
|
def transfers
|
10
10
|
@transfers ||= Set.new
|
11
11
|
end
|
12
12
|
|
13
|
+
# @api private
|
14
|
+
def code_max_length
|
15
|
+
@code_max_length ||= 47
|
16
|
+
end
|
17
|
+
|
13
18
|
# @api private
|
14
19
|
def transfer(amount, options = {})
|
15
20
|
raise TransferIsNegative if amount < Money.zero
|
@@ -54,10 +59,17 @@ module DoubleEntry
|
|
54
59
|
end
|
55
60
|
end
|
56
61
|
|
57
|
-
|
62
|
+
attr_reader :code, :from, :to
|
58
63
|
|
59
64
|
def initialize(attributes)
|
60
|
-
|
65
|
+
@code = attributes[:code]
|
66
|
+
@from = attributes[:from]
|
67
|
+
@to = attributes[:to]
|
68
|
+
if code.length > Transfer.code_max_length
|
69
|
+
raise TransferCodeTooLongError.new(
|
70
|
+
"transfer code '#{code}' is too long. Please limit it to #{Transfer.code_max_length} characters."
|
71
|
+
)
|
72
|
+
end
|
61
73
|
end
|
62
74
|
|
63
75
|
def process(amount, from, to, code, detail)
|
data/lib/double_entry/version.rb
CHANGED
@@ -2,56 +2,56 @@ class CreateDoubleEntryTables < ActiveRecord::Migration
|
|
2
2
|
|
3
3
|
def self.up
|
4
4
|
create_table "double_entry_account_balances", :force => true do |t|
|
5
|
-
t.string
|
6
|
-
t.string
|
7
|
-
t.integer
|
5
|
+
t.string "account", :limit => 31, :null => false
|
6
|
+
t.string "scope", :limit => 23
|
7
|
+
t.integer "balance", :null => false
|
8
8
|
t.timestamps
|
9
9
|
end
|
10
10
|
|
11
|
-
add_index "double_entry_account_balances", ["account"],
|
11
|
+
add_index "double_entry_account_balances", ["account"], :name => "index_account_balances_on_account"
|
12
12
|
add_index "double_entry_account_balances", ["scope", "account"], :name => "index_account_balances_on_scope_and_account", :unique => true
|
13
13
|
|
14
14
|
create_table "double_entry_lines", :force => true do |t|
|
15
|
-
t.string
|
16
|
-
t.string
|
17
|
-
t.string
|
18
|
-
t.integer
|
19
|
-
t.integer
|
20
|
-
t.integer
|
21
|
-
t.string
|
22
|
-
t.string
|
23
|
-
t.integer
|
24
|
-
t.string
|
15
|
+
t.string "account", :limit => 31, :null => false
|
16
|
+
t.string "scope", :limit => 23
|
17
|
+
t.string "code", :limit => 47, :null => false
|
18
|
+
t.integer "amount", :null => false
|
19
|
+
t.integer "balance", :null => false
|
20
|
+
t.integer "partner_id"
|
21
|
+
t.string "partner_account", :limit => 31, :null => false
|
22
|
+
t.string "partner_scope", :limit => 23
|
23
|
+
t.integer "detail_id"
|
24
|
+
t.string "detail_type"
|
25
25
|
t.timestamps
|
26
26
|
end
|
27
27
|
|
28
|
-
add_index "double_entry_lines", ["account", "code", "created_at"],
|
29
|
-
add_index "double_entry_lines", ["account", "created_at"],
|
28
|
+
add_index "double_entry_lines", ["account", "code", "created_at"], :name => "lines_account_code_created_at_idx"
|
29
|
+
add_index "double_entry_lines", ["account", "created_at"], :name => "lines_account_created_at_idx"
|
30
30
|
add_index "double_entry_lines", ["scope", "account", "created_at"], :name => "lines_scope_account_created_at_idx"
|
31
|
-
add_index "double_entry_lines", ["scope", "account", "id"],
|
31
|
+
add_index "double_entry_lines", ["scope", "account", "id"], :name => "lines_scope_account_id_idx"
|
32
32
|
|
33
33
|
create_table "double_entry_line_aggregates", :force => true do |t|
|
34
|
-
t.string
|
35
|
-
t.string
|
36
|
-
t.string
|
37
|
-
t.string
|
38
|
-
t.integer
|
39
|
-
t.integer
|
40
|
-
t.integer
|
41
|
-
t.integer
|
42
|
-
t.integer
|
43
|
-
t.integer
|
44
|
-
t.string
|
45
|
-
t.string
|
34
|
+
t.string "function", :limit => 15, :null => false
|
35
|
+
t.string "account", :limit => 31, :null => false
|
36
|
+
t.string "code", :limit => 47
|
37
|
+
t.string "scope", :limit => 23
|
38
|
+
t.integer "year"
|
39
|
+
t.integer "month"
|
40
|
+
t.integer "week"
|
41
|
+
t.integer "day"
|
42
|
+
t.integer "hour"
|
43
|
+
t.integer "amount", :null => false
|
44
|
+
t.string "filter"
|
45
|
+
t.string "range_type", :limit => 15, :null => false
|
46
46
|
t.timestamps
|
47
47
|
end
|
48
48
|
|
49
49
|
add_index "double_entry_line_aggregates", ["function", "account", "code", "year", "month", "week", "day"], :name => "line_aggregate_idx"
|
50
50
|
|
51
51
|
create_table "double_entry_line_checks", :force => true do |t|
|
52
|
-
t.integer
|
53
|
-
t.boolean
|
54
|
-
t.text
|
52
|
+
t.integer "last_line_id", :null => false
|
53
|
+
t.boolean "errors_found", :null => false
|
54
|
+
t.text "log"
|
55
55
|
t.timestamps
|
56
56
|
end
|
57
57
|
|
@@ -4,21 +4,54 @@ module DoubleEntry
|
|
4
4
|
describe Account do
|
5
5
|
let(:identity_scope) { ->(value) { value } }
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
describe "::new" do
|
8
|
+
context "given an identifier 31 characters in length" do
|
9
|
+
let(:identifier) { "xxxxxxxx 31 characters xxxxxxxx" }
|
10
|
+
specify do
|
11
|
+
expect { Account.new(:identifier => identifier) }.to_not raise_error
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context "given an identifier 32 characters in length" do
|
16
|
+
let(:identifier) { "xxxxxxxx 32 characters xxxxxxxxx" }
|
17
|
+
specify do
|
18
|
+
expect { Account.new(:identifier => identifier) }.to raise_error AccountIdentifierTooLongError, /'#{identifier}'/
|
19
|
+
end
|
20
|
+
end
|
12
21
|
end
|
13
22
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
23
|
+
describe Account::Instance do
|
24
|
+
it "is sortable" do
|
25
|
+
account = Account.new(:identifier => "savings", :scope_identifier => identity_scope)
|
26
|
+
a = Account::Instance.new(:account => account, :scope => "123")
|
27
|
+
b = Account::Instance.new(:account => account, :scope => "456")
|
28
|
+
expect([b, a].sort).to eq [a, b]
|
29
|
+
end
|
30
|
+
|
31
|
+
it "is hashable" do
|
32
|
+
account = Account.new(:identifier => "savings", :scope_identifier => identity_scope)
|
33
|
+
a1 = Account::Instance.new(:account => account, :scope => "123")
|
34
|
+
a2 = Account::Instance.new(:account => account, :scope => "123")
|
35
|
+
b = Account::Instance.new(:account => account, :scope => "456")
|
36
|
+
|
37
|
+
expect(a1.hash).to eq a2.hash
|
38
|
+
expect(a1.hash).to_not eq b.hash
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "::new" do
|
42
|
+
let(:account) { Account.new(:identifier => "x", :scope_identifier => identity_scope) }
|
43
|
+
subject(:initialize_account_instance) { Account::Instance.new(:account => account, :scope => scope) }
|
44
|
+
|
45
|
+
context "given a scope identifier 23 characters in length" do
|
46
|
+
let(:scope) { "xxxx 23 characters xxxx" }
|
47
|
+
specify { expect { initialize_account_instance }.to_not raise_error }
|
48
|
+
end
|
19
49
|
|
20
|
-
|
21
|
-
|
50
|
+
context "given a scope identifier 24 characters in length" do
|
51
|
+
let(:scope) { "xxxx 24 characters xxxxx" }
|
52
|
+
specify { expect { initialize_account_instance }.to raise_error ScopeIdentifierTooLongError, /'#{scope}'/ }
|
53
|
+
end
|
54
|
+
end
|
22
55
|
end
|
23
56
|
|
24
57
|
describe "currency" do
|
@@ -32,27 +65,27 @@ module DoubleEntry
|
|
32
65
|
expect(DoubleEntry::Account::Instance.new(:account => account).currency).to eq("AUD")
|
33
66
|
end
|
34
67
|
end
|
35
|
-
end
|
36
68
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
69
|
+
describe Account::Set do
|
70
|
+
describe "#define" do
|
71
|
+
context "given a 'savings' account is defined" do
|
72
|
+
before { subject.define(:identifier => "savings") }
|
73
|
+
its(:first) { should be_an Account }
|
74
|
+
its("first.identifier") { should eq "savings" }
|
75
|
+
end
|
43
76
|
end
|
44
|
-
end
|
45
77
|
|
46
|
-
|
47
|
-
|
78
|
+
describe "#active_record_scope_identifier" do
|
79
|
+
subject(:scope) { Account::Set.new.active_record_scope_identifier(ar_class) }
|
48
80
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
81
|
+
context "given ActiveRecordScopeFactory is stubbed" do
|
82
|
+
let(:scope_identifier) { double(:scope_identifier) }
|
83
|
+
let(:scope_factory) { double(:scope_factory, :scope_identifier => scope_identifier) }
|
84
|
+
let(:ar_class) { double(:ar_class) }
|
85
|
+
before { allow(Account::ActiveRecordScopeFactory).to receive(:new).with(ar_class).and_return(scope_factory) }
|
54
86
|
|
55
|
-
|
87
|
+
it { should eq scope_identifier }
|
88
|
+
end
|
56
89
|
end
|
57
90
|
end
|
58
91
|
end
|
@@ -5,6 +5,35 @@ describe DoubleEntry::Configuration do
|
|
5
5
|
its(:accounts) { should be_a DoubleEntry::Account::Set }
|
6
6
|
its(:transfers) { should be_a DoubleEntry::Transfer::Set }
|
7
7
|
|
8
|
+
describe "max lengths" do
|
9
|
+
context "given a max length has not been set" do
|
10
|
+
its(:code_max_length) { should be 47 }
|
11
|
+
its(:scope_identifier_max_length) { should be 23 }
|
12
|
+
its(:account_identifier_max_length) { should be 31 }
|
13
|
+
end
|
14
|
+
|
15
|
+
context "given a code max length of 10 has been set" do
|
16
|
+
before { subject.code_max_length = 10 }
|
17
|
+
its(:code_max_length) { should be 10 }
|
18
|
+
end
|
19
|
+
|
20
|
+
context "given a scope identifier max length of 11 has been set" do
|
21
|
+
before { subject.scope_identifier_max_length = 11 }
|
22
|
+
its(:scope_identifier_max_length) { should be 11 }
|
23
|
+
end
|
24
|
+
|
25
|
+
context "given an account identifier max length of 9 has been set" do
|
26
|
+
before { subject.account_identifier_max_length = 9 }
|
27
|
+
its(:account_identifier_max_length) { should be 9 }
|
28
|
+
end
|
29
|
+
|
30
|
+
after do
|
31
|
+
subject.code_max_length = nil
|
32
|
+
subject.scope_identifier_max_length = nil
|
33
|
+
subject.account_identifier_max_length = nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
8
37
|
describe "#define_accounts" do
|
9
38
|
it "yields the accounts set" do
|
10
39
|
expect { |block|
|
@@ -6,7 +6,7 @@ describe DoubleEntry::Line do
|
|
6
6
|
end
|
7
7
|
|
8
8
|
describe "persistance" do
|
9
|
-
let(:
|
9
|
+
let(:line_to_persist) {
|
10
10
|
DoubleEntry::Line.new(
|
11
11
|
:amount => Money.new(10_00),
|
12
12
|
:balance => Money.zero,
|
@@ -18,11 +18,13 @@ describe DoubleEntry::Line do
|
|
18
18
|
let(:account) { DoubleEntry.account(:test, :scope => "17") }
|
19
19
|
let(:partner_account) { DoubleEntry.account(:test, :scope => "72") }
|
20
20
|
let(:code) { :test_code }
|
21
|
-
subject { DoubleEntry::Line.last }
|
22
21
|
|
23
|
-
|
24
|
-
|
22
|
+
subject(:persisted_line) do
|
23
|
+
line_to_persist.save!
|
24
|
+
line_to_persist.reload
|
25
|
+
end
|
25
26
|
|
27
|
+
describe "attributes" do
|
26
28
|
context "given code = :the_code" do
|
27
29
|
let(:code) { :the_code }
|
28
30
|
its(:code) { should eq :the_code }
|
@@ -30,7 +32,7 @@ describe DoubleEntry::Line do
|
|
30
32
|
|
31
33
|
context "given code = nil" do
|
32
34
|
let(:code) { nil }
|
33
|
-
|
35
|
+
specify { expect { line_to_persist.save! }.to raise_error }
|
34
36
|
end
|
35
37
|
|
36
38
|
context "given account = :test, 54 " do
|
@@ -52,22 +54,20 @@ describe DoubleEntry::Line do
|
|
52
54
|
end
|
53
55
|
end
|
54
56
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
}
|
57
|
+
context 'when balance is sent negative' do
|
58
|
+
let(:account) {
|
59
|
+
DoubleEntry.account(:savings, :scope => '17', :positive_only => true)
|
60
|
+
}
|
60
61
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
62
|
+
let(:line) {
|
63
|
+
DoubleEntry::Line.new(
|
64
|
+
:balance => Money.new(-1),
|
65
|
+
:account => account,
|
66
|
+
)
|
67
|
+
}
|
67
68
|
|
68
|
-
|
69
|
-
|
70
|
-
end
|
69
|
+
it 'raises AccountWouldBeSentNegative exception' do
|
70
|
+
expect { line.save }.to raise_error DoubleEntry::AccountWouldBeSentNegative
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
@@ -51,8 +51,20 @@ describe DoubleEntry::Locking do
|
|
51
51
|
end
|
52
52
|
|
53
53
|
it "takes the balance for new account balance records from the lines table" do
|
54
|
-
DoubleEntry::Line.create!(
|
55
|
-
|
54
|
+
DoubleEntry::Line.create!(
|
55
|
+
:account => @account_a,
|
56
|
+
:partner_account => @account_b,
|
57
|
+
:amount => Money.new(3_00),
|
58
|
+
:balance => Money.new(3_00),
|
59
|
+
:code => :test,
|
60
|
+
)
|
61
|
+
DoubleEntry::Line.create!(
|
62
|
+
:account => @account_a,
|
63
|
+
:partner_account => @account_b,
|
64
|
+
:amount => Money.new(7_00),
|
65
|
+
:balance => Money.new(10_00),
|
66
|
+
:code => :test,
|
67
|
+
)
|
56
68
|
|
57
69
|
expect do
|
58
70
|
DoubleEntry::Locking.lock_accounts(@account_a) { }
|
@@ -1,17 +1,38 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'spec_helper'
|
3
|
-
|
4
|
-
describe
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
:
|
9
|
-
|
10
|
-
|
3
|
+
module DoubleEntry
|
4
|
+
describe Transfer do
|
5
|
+
|
6
|
+
describe "::new" do
|
7
|
+
context "given a code 47 characters in length" do
|
8
|
+
let(:code) { "xxxxxxxxxxxxxxxx 47 characters xxxxxxxxxxxxxxxx" }
|
9
|
+
specify do
|
10
|
+
expect { Transfer.new(:code => code) }.to_not raise_error
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context "given a code 48 characters in length" do
|
15
|
+
let(:code) { "xxxxxxxxxxxxxxxx 48 characters xxxxxxxxxxxxxxxxx" }
|
16
|
+
specify do
|
17
|
+
expect { Transfer.new(:code => code) }.to raise_error TransferCodeTooLongError, /'#{code}'/
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe Transfer::Set do
|
23
|
+
describe "#define" do
|
24
|
+
before do
|
25
|
+
subject.define(
|
26
|
+
:code => "code",
|
27
|
+
:from => double(:identifier => "from"),
|
28
|
+
:to => double(:identifier => "to"),
|
29
|
+
)
|
30
|
+
end
|
31
|
+
its(:first) { should be_a Transfer }
|
32
|
+
its("first.code") { should eq "code" }
|
33
|
+
its("first.from.identifier") { should eq "from" }
|
34
|
+
its("first.to.identifier") { should eq "to" }
|
35
|
+
end
|
11
36
|
end
|
12
|
-
its(:first) { should be_a DoubleEntry::Transfer }
|
13
|
-
its("first.code") { should eq "code" }
|
14
|
-
its("first.from.identifier") { should eq "from" }
|
15
|
-
its("first.to.identifier") { should eq "to" }
|
16
37
|
end
|
17
38
|
end
|
data/spec/double_entry_spec.rb
CHANGED
@@ -173,9 +173,8 @@ describe DoubleEntry do
|
|
173
173
|
accounts.define(:identifier => :b)
|
174
174
|
end
|
175
175
|
|
176
|
-
description = ->(line) { "Money goes #{line.decrease? ? 'out' : 'in'}: #{line.amount.format}" }
|
177
176
|
config.define_transfers do |transfers|
|
178
|
-
transfers.define(:code => :xfer, :from => :a, :to => :b
|
177
|
+
transfers.define(:code => :xfer, :from => :a, :to => :b)
|
179
178
|
end
|
180
179
|
end
|
181
180
|
|
data/spec/support/schema.rb
CHANGED
@@ -2,68 +2,63 @@ ActiveRecord::Schema.define do
|
|
2
2
|
self.verbose = false
|
3
3
|
|
4
4
|
create_table "double_entry_account_balances", :force => true do |t|
|
5
|
-
t.string
|
6
|
-
t.string
|
7
|
-
t.integer
|
8
|
-
t.
|
9
|
-
t.datetime "updated_at"
|
5
|
+
t.string "account", :limit => 31, :null => false
|
6
|
+
t.string "scope", :limit => 23
|
7
|
+
t.integer "balance", :null => false
|
8
|
+
t.timestamps
|
10
9
|
end
|
11
10
|
|
12
|
-
add_index "double_entry_account_balances", ["account"],
|
11
|
+
add_index "double_entry_account_balances", ["account"], :name => "index_account_balances_on_account"
|
13
12
|
add_index "double_entry_account_balances", ["scope", "account"], :name => "index_account_balances_on_scope_and_account", :unique => true
|
14
13
|
|
15
14
|
create_table "double_entry_lines", :force => true do |t|
|
16
|
-
t.string
|
17
|
-
t.string
|
18
|
-
t.string
|
19
|
-
t.integer
|
20
|
-
t.integer
|
21
|
-
t.integer
|
22
|
-
t.string
|
23
|
-
t.string
|
24
|
-
t.integer
|
25
|
-
t.string
|
26
|
-
t.
|
27
|
-
t.datetime "updated_at"
|
15
|
+
t.string "account", :limit => 31, :null => false
|
16
|
+
t.string "scope", :limit => 23
|
17
|
+
t.string "code", :limit => 47, :null => false
|
18
|
+
t.integer "amount", :null => false
|
19
|
+
t.integer "balance", :null => false
|
20
|
+
t.integer "partner_id"
|
21
|
+
t.string "partner_account", :limit => 31, :null => false
|
22
|
+
t.string "partner_scope", :limit => 23
|
23
|
+
t.integer "detail_id"
|
24
|
+
t.string "detail_type"
|
25
|
+
t.timestamps
|
28
26
|
end
|
29
27
|
|
30
|
-
add_index "double_entry_lines", ["account", "code", "created_at"],
|
31
|
-
add_index "double_entry_lines", ["account", "created_at"],
|
28
|
+
add_index "double_entry_lines", ["account", "code", "created_at"], :name => "lines_account_code_created_at_idx"
|
29
|
+
add_index "double_entry_lines", ["account", "created_at"], :name => "lines_account_created_at_idx"
|
32
30
|
add_index "double_entry_lines", ["scope", "account", "created_at"], :name => "lines_scope_account_created_at_idx"
|
33
|
-
add_index "double_entry_lines", ["scope", "account", "id"],
|
31
|
+
add_index "double_entry_lines", ["scope", "account", "id"], :name => "lines_scope_account_id_idx"
|
34
32
|
|
35
33
|
create_table "double_entry_line_aggregates", :force => true do |t|
|
36
|
-
t.string
|
37
|
-
t.string
|
38
|
-
t.string
|
39
|
-
t.string
|
40
|
-
t.integer
|
41
|
-
t.integer
|
42
|
-
t.integer
|
43
|
-
t.integer
|
44
|
-
t.integer
|
45
|
-
t.integer
|
46
|
-
t.
|
47
|
-
t.
|
48
|
-
t.
|
49
|
-
t.string "range_type"
|
34
|
+
t.string "function", :limit => 15, :null => false
|
35
|
+
t.string "account", :limit => 31, :null => false
|
36
|
+
t.string "code", :limit => 47
|
37
|
+
t.string "scope", :limit => 23
|
38
|
+
t.integer "year"
|
39
|
+
t.integer "month"
|
40
|
+
t.integer "week"
|
41
|
+
t.integer "day"
|
42
|
+
t.integer "hour"
|
43
|
+
t.integer "amount", :null => false
|
44
|
+
t.string "filter"
|
45
|
+
t.string "range_type", :limit => 15, :null => false
|
46
|
+
t.timestamps
|
50
47
|
end
|
51
48
|
|
52
49
|
add_index "double_entry_line_aggregates", ["function", "account", "code", "year", "month", "week", "day"], :name => "line_aggregate_idx"
|
53
50
|
|
54
51
|
create_table "double_entry_line_checks", :force => true do |t|
|
55
|
-
t.integer
|
56
|
-
t.boolean
|
57
|
-
t.text
|
58
|
-
t.
|
59
|
-
t.datetime "updated_at"
|
52
|
+
t.integer "last_line_id", :null => false
|
53
|
+
t.boolean "errors_found", :null => false
|
54
|
+
t.text "log"
|
55
|
+
t.timestamps
|
60
56
|
end
|
61
57
|
|
62
58
|
# test table only
|
63
59
|
create_table "users", :force => true do |t|
|
64
|
-
t.string
|
65
|
-
t.
|
66
|
-
t.datetime "updated_at"
|
60
|
+
t.string "username", :null => false
|
61
|
+
t.timestamps
|
67
62
|
end
|
68
63
|
|
69
64
|
add_index "users", ["username"], :name => "index_users_on_username", :unique => true
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: double_entry
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Anthony Sellitti
|
@@ -15,7 +15,7 @@ authors:
|
|
15
15
|
autorequire:
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
|
-
date: 2014-
|
18
|
+
date: 2014-12-08 00:00:00.000000000 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: money
|