double_entry 2.0.0.beta5 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +44 -2
- data/README.md +30 -11
- data/double_entry.gemspec +4 -4
- data/lib/double_entry/line.rb +2 -0
- data/lib/double_entry/line_metadata.rb +1 -1
- data/lib/double_entry/transfer.rb +2 -0
- data/lib/double_entry/validation/account_fixer.rb +1 -1
- data/lib/double_entry/validation/line_check.rb +33 -17
- data/lib/double_entry/version.rb +1 -1
- data/lib/double_entry.rb +2 -1
- metadata +19 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 94cf9e4e7931a05b37250722ced311b4cf272bd96a716f36ebec81eaccf1d8fb
|
4
|
+
data.tar.gz: d55cd976db1dd8c2ccc93658664701a9d6743ff83606c6ece1eaaf3c08d3a9b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5383c40fc2354d1699c63770b2a4b74a664daa9f84645b4497a2c66aa9410de7405dddd51a7c30ccdc8052a035cee346efa63dae9d72ad8269f3126f56a3a81f
|
7
|
+
data.tar.gz: 22d01c13479911a6c6d57e3ba65952ef0b5693508136e7e6147eda6b1a90332f5d3a32f7247e6d3b53043427b7c47b94e71bc8f1b963f465bf0566c022af7a69
|
data/CHANGELOG.md
CHANGED
@@ -7,7 +7,50 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
-
|
10
|
+
[Unreleased]: https://github.com/envato/double_entry/compare/v2.0.1...HEAD
|
11
|
+
|
12
|
+
## [2.0.1] - 2023-11-01
|
13
|
+
|
14
|
+
### Fixed
|
15
|
+
|
16
|
+
- Resolve Rails 7.1 coder deprecation warning ([#219]).
|
17
|
+
|
18
|
+
[2.0.1]: https://github.com/envato/double_entry/compare/v2.0.0...v2.0.1
|
19
|
+
[#219]: https://github.com/envato/double_entry/pull/219
|
20
|
+
|
21
|
+
## [2.0.0] - 2023-10-25
|
22
|
+
|
23
|
+
### Fixed
|
24
|
+
|
25
|
+
- Ensure LineCheck and AccountFixer can work correctly with unscoped accounts ([#207]).
|
26
|
+
- Fixes for running on Ruby 3 ([#212]).
|
27
|
+
|
28
|
+
### Changed
|
29
|
+
|
30
|
+
- Return `[credit, debit]` from `DoubleEntry.transfer` ([#190]).
|
31
|
+
- Run the test suite against Rails 6.1, 7.0, 7.1, and Ruby 3.1, 3.2 ([#203], [#214], [#217]).
|
32
|
+
- Migrate CI to run on GitHub Actions ([#205])
|
33
|
+
|
34
|
+
### Removed
|
35
|
+
|
36
|
+
- Removed support for Rails < 6.1, and Ruby < 3.0 ([#215], [#217]).
|
37
|
+
|
38
|
+
### Added
|
39
|
+
|
40
|
+
- Add `credit` and `debit` scopes to the `Line` model ([#192]).
|
41
|
+
|
42
|
+
[2.0.0]: https://github.com/envato/double_entry/compare/v2.0.0.beta5...v2.0.0
|
43
|
+
[#190]: https://github.com/envato/double_entry/pull/190
|
44
|
+
[#192]: https://github.com/envato/double_entry/pull/192
|
45
|
+
[#203]: https://github.com/envato/double_entry/pull/203
|
46
|
+
[#205]: https://github.com/envato/double_entry/pull/205
|
47
|
+
[#207]: https://github.com/envato/double_entry/pull/207
|
48
|
+
[#212]: https://github.com/envato/double_entry/pull/212
|
49
|
+
[#214]: https://github.com/envato/double_entry/pull/214
|
50
|
+
[#215]: https://github.com/envato/double_entry/pull/215
|
51
|
+
[#217]: https://github.com/envato/double_entry/pull/217
|
52
|
+
|
53
|
+
## [2.0.0.beta5] - 2021-02-24
|
11
54
|
|
12
55
|
### Changed
|
13
56
|
|
@@ -471,7 +514,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
471
514
|
|
472
515
|
- Library released as Open Source!
|
473
516
|
|
474
|
-
[Unreleased]: https://github.com/envato/double_entry/compare/v2.0.0.beta5...HEAD
|
475
517
|
[2.0.0.beta5]: https://github.com/envato/double_entry/compare/v2.0.0.beta4...v2.0.0.beta5
|
476
518
|
[2.0.0.beta4]: https://github.com/envato/double_entry/compare/v2.0.0.beta3...v2.0.0.beta4
|
477
519
|
[2.0.0.beta3]: https://github.com/envato/double_entry/compare/v2.0.0.beta2...v2.0.0.beta3
|
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
[![License MIT](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://github.com/envato/double_entry/blob/master/LICENSE.md)
|
5
5
|
[![Gem Version](https://badge.fury.io/rb/double_entry.svg)](http://badge.fury.io/rb/double_entry)
|
6
|
-
[![Build Status](https://
|
6
|
+
[![Build Status](https://github.com/envato/double_entry/actions/workflows/ci-workflow.yml/badge.svg)](https://github.com/envato/double_entry/actions/workflows/ci-workflow.yml)
|
7
7
|
[![Code Climate](https://codeclimate.com/github/envato/double_entry/badges/gpa.svg)](https://codeclimate.com/github/envato/double_entry)
|
8
8
|
|
9
9
|
![Show me the Money](http://24.media.tumblr.com/tumblr_m3bwbqNJIG1rrgbmqo1_500.gif)
|
@@ -13,7 +13,7 @@ Keep track of all the monies!
|
|
13
13
|
DoubleEntry is an accounting system based on the principles of a
|
14
14
|
[Double-entry Bookkeeping](http://en.wikipedia.org/wiki/Double-entry_bookkeeping_system)
|
15
15
|
system. While this gem acts like a double-entry bookkeeping system, as it creates
|
16
|
-
two entries in the database for each transfer, it does *not* enforce accounting rules.
|
16
|
+
two entries in the database for each transfer, it does *not* enforce accounting rules, other than optionally ensuring a balance is positive, and through an allowlist of approved transfers.
|
17
17
|
|
18
18
|
DoubleEntry uses the [Money gem](https://github.com/RubyMoney/money) to encapsulate operations on currency values.
|
19
19
|
|
@@ -22,18 +22,15 @@ DoubleEntry uses the [Money gem](https://github.com/RubyMoney/money) to encapsul
|
|
22
22
|
DoubleEntry is tested against:
|
23
23
|
|
24
24
|
Ruby
|
25
|
-
* 2.3.x
|
26
|
-
* 2.4.x
|
27
|
-
* 2.5.x
|
28
|
-
* 2.6.x
|
29
25
|
* 2.7.x
|
26
|
+
* 3.0.x
|
27
|
+
* 3.1.x
|
28
|
+
* 3.2.x
|
30
29
|
|
31
30
|
Rails
|
32
|
-
* 4.2.x
|
33
|
-
* 5.0.x
|
34
|
-
* 5.1.x
|
35
|
-
* 5.2.x
|
36
31
|
* 6.0.x
|
32
|
+
* 6.1.x
|
33
|
+
* 7.0.x
|
37
34
|
|
38
35
|
Databases
|
39
36
|
* MySQL
|
@@ -164,7 +161,15 @@ See [DoubleEntry::Locking](lib/double_entry/locking.rb) for more info.
|
|
164
161
|
|
165
162
|
### Account Checker/Fixer
|
166
163
|
|
167
|
-
|
164
|
+
DoubleEntry tries really hard to make sure that stored account balances reflect the running balances from the `double_entry_lines` table, but there is always the unlikely possibility that something will go wrong and the calculated balance might get out of sync with the actual running balance of the lines.
|
165
|
+
|
166
|
+
DoubleEntry therefore provides a couple of tools to give you some confidence that things are working as expected.
|
167
|
+
|
168
|
+
The `DoubleEntry::Validation::LineCheck` will check the `double_entry_lines` table to make sure that the `balance` column correctly reflects the calculated running balance, and that the `double_entry_account_balances` table has the correct value in the `balance` column. If either one of these turn out to be incorrect then it will write an entry into the `double_entry_line_checks` table reporting on the differences.
|
169
|
+
|
170
|
+
You can alternatively pass a `fixer` to the `DoubleEntry::Validation::LineCheck.perform` method which will try and correct the balances. This gem provides the `DoubleEntry::Validation::AccountFixer` class which will correct the balance if it's out of sync.
|
171
|
+
|
172
|
+
Using these classes is optional and both are provided for additional safety checks. If you want to make use of them then it's recommended to run them in a scheduled job, somewhere on the order of hourly to daily, depending on transaction volume. Keep in mind that this process locks accounts as it inspects their balances, so it will prevent new transactions from being written for a short time.
|
168
173
|
|
169
174
|
Here are examples that could go in your scheduled job, depending on your needs:
|
170
175
|
|
@@ -245,6 +250,20 @@ DoubleEntry.configure do |config|
|
|
245
250
|
end
|
246
251
|
```
|
247
252
|
|
253
|
+
## Testing with RSpec
|
254
|
+
|
255
|
+
Transfering money needs to be run as a top level transaction. This conflicts with RSpec's default behavior of creating a new transaction for every test, causing an exception of type `DoubleEntry::Locking::LockMustBeOutermostTransaction` to be raised. This behavior may be disabled by adding the following lines into your `rails_helper.rb`.
|
256
|
+
|
257
|
+
```ruby
|
258
|
+
RSpec.configure do |config|
|
259
|
+
# ...
|
260
|
+
# This first line should already be there. You will need to add the second one
|
261
|
+
config.use_transactional_fixtures = true
|
262
|
+
DoubleEntry::Locking.configuration.running_inside_transactional_fixtures = true
|
263
|
+
# ...
|
264
|
+
end
|
265
|
+
```
|
266
|
+
|
248
267
|
## Jackhammer
|
249
268
|
|
250
269
|
Run a concurrency test on the code.
|
data/double_entry.gemspec
CHANGED
@@ -24,12 +24,12 @@ Gem::Specification.new do |gem|
|
|
24
24
|
f.match(%r{^(?:double_entry.gemspec|README|LICENSE|CHANGELOG|lib/)})
|
25
25
|
end
|
26
26
|
gem.require_paths = ['lib']
|
27
|
-
gem.required_ruby_version = '>=
|
27
|
+
gem.required_ruby_version = '>= 3'
|
28
28
|
|
29
|
-
gem.add_dependency 'activerecord', '>=
|
30
|
-
gem.add_dependency 'activesupport', '>=
|
29
|
+
gem.add_dependency 'activerecord', '>= 6.1.0'
|
30
|
+
gem.add_dependency 'activesupport', '>= 6.1.0'
|
31
31
|
gem.add_dependency 'money', '>= 6.0.0'
|
32
|
-
gem.add_dependency 'railties', '>=
|
32
|
+
gem.add_dependency 'railties', '>= 6.1.0'
|
33
33
|
|
34
34
|
gem.add_development_dependency 'mysql2'
|
35
35
|
gem.add_development_dependency 'pg'
|
data/lib/double_entry/line.rb
CHANGED
@@ -57,6 +57,8 @@ module DoubleEntry
|
|
57
57
|
class Line < ActiveRecord::Base
|
58
58
|
belongs_to :detail, polymorphic: true, required: false
|
59
59
|
has_many :metadata, class_name: 'DoubleEntry::LineMetadata' unless -> { DoubleEntry.config.json_metadata }
|
60
|
+
scope :credits, -> { where('amount > 0') }
|
61
|
+
scope :debits, -> { where('amount < 0') }
|
60
62
|
scope :with_id_greater_than, ->(id) { where('id > ?', id) }
|
61
63
|
|
62
64
|
def amount
|
@@ -76,6 +76,7 @@ module DoubleEntry
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def process(amount, options)
|
79
|
+
credit = debit = nil
|
79
80
|
from_account = options[:from]
|
80
81
|
to_account = options[:to]
|
81
82
|
code = options[:code]
|
@@ -91,6 +92,7 @@ module DoubleEntry
|
|
91
92
|
credit, debit = create_lines(amount, code, detail, from_account, to_account, metadata)
|
92
93
|
create_line_metadata(credit, debit, metadata) if metadata && !DoubleEntry.config.json_metadata
|
93
94
|
end
|
95
|
+
[credit, debit]
|
94
96
|
end
|
95
97
|
|
96
98
|
def create_lines(amount, code, detail, from_account, to_account, metadata)
|
@@ -48,34 +48,50 @@ module DoubleEntry
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def running_balance_correct?(line, log)
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
51
|
+
previous_line = find_previous_line(line.account.identifier.to_s, line.scope, line.id)
|
52
|
+
|
53
|
+
previous_balance = previous_line.length == 1 ? previous_line[0].balance : Money.zero(line.account.currency)
|
54
|
+
|
55
|
+
if line.balance != (line.amount + previous_balance)
|
56
|
+
log << line_error_message(line, previous_line, previous_balance)
|
57
|
+
end
|
58
|
+
|
59
|
+
line.balance == previous_balance + line.amount
|
60
|
+
end
|
60
61
|
|
62
|
+
def find_previous_line(identifier, scope, id)
|
61
63
|
# yes, it needs to be find_by_sql, because any other find will be affected
|
62
64
|
# by the find_each call in perform!
|
63
|
-
|
65
|
+
|
66
|
+
if scope.nil?
|
67
|
+
Line.find_by_sql([<<-SQL, identifier, id])
|
68
|
+
SELECT * FROM #{Line.quoted_table_name} #{force_index}
|
69
|
+
WHERE account = ?
|
70
|
+
AND scope IS NULL
|
71
|
+
AND id < ?
|
72
|
+
ORDER BY id DESC
|
73
|
+
LIMIT 1
|
74
|
+
SQL
|
75
|
+
else
|
76
|
+
Line.find_by_sql([<<-SQL, identifier, scope, id])
|
64
77
|
SELECT * FROM #{Line.quoted_table_name} #{force_index}
|
65
78
|
WHERE account = ?
|
66
79
|
AND scope = ?
|
67
80
|
AND id < ?
|
68
81
|
ORDER BY id DESC
|
69
82
|
LIMIT 1
|
70
|
-
|
71
|
-
|
72
|
-
previous_balance = previous_line.length == 1 ? previous_line[0].balance : Money.zero(line.account.currency)
|
73
|
-
|
74
|
-
if line.balance != (line.amount + previous_balance)
|
75
|
-
log << line_error_message(line, previous_line, previous_balance)
|
83
|
+
SQL
|
76
84
|
end
|
85
|
+
end
|
77
86
|
|
78
|
-
|
87
|
+
def force_index
|
88
|
+
# Another work around for the MySQL 5.1 query optimiser bug that causes the ORDER BY
|
89
|
+
# on the query to fail in some circumstances, resulting in an old balance being
|
90
|
+
# returned. This was biting us intermittently in spec runs.
|
91
|
+
# See http://bugs.mysql.com/bug.php?id=51431
|
92
|
+
return '' unless Line.connection.adapter_name.match(/mysql/i)
|
93
|
+
|
94
|
+
'FORCE INDEX (lines_scope_account_id_idx)'
|
79
95
|
end
|
80
96
|
|
81
97
|
def line_error_message(line, previous_line, previous_balance)
|
data/lib/double_entry/version.rb
CHANGED
data/lib/double_entry.rb
CHANGED
@@ -64,7 +64,7 @@ module DoubleEntry
|
|
64
64
|
# @example Transfer $20 from a user's checking to savings account
|
65
65
|
# checking_account = DoubleEntry.account(:checking, scope: user)
|
66
66
|
# savings_account = DoubleEntry.account(:savings, scope: user)
|
67
|
-
# DoubleEntry.transfer(
|
67
|
+
# credit, debit = DoubleEntry.transfer(
|
68
68
|
# 20.dollars,
|
69
69
|
# from: checking_account,
|
70
70
|
# to: savings_account,
|
@@ -80,6 +80,7 @@ module DoubleEntry
|
|
80
80
|
# type of transfer. As specified in the transfer configuration.
|
81
81
|
# @option options :detail [ActiveRecord::Base] ActiveRecord model
|
82
82
|
# associated (via a polymorphic association) with the transfer.
|
83
|
+
# @return [[Line, Line]] The credit & debit (in that order) created by the transfer
|
83
84
|
# @raise [DoubleEntry::TransferIsNegative] The amount is less than zero.
|
84
85
|
# @raise [DoubleEntry::TransferNotAllowed] A transfer between these
|
85
86
|
# accounts with the provided code is not allowed. Check configuration.
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: double_entry
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Envato
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-11-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 6.1.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 6.1.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activesupport
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 6.1.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 6.1.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: money
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: 6.1.0
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
68
|
+
version: 6.1.0
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: mysql2
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -220,7 +220,7 @@ dependencies:
|
|
220
220
|
- - ">="
|
221
221
|
- !ruby/object:Gem::Version
|
222
222
|
version: '0'
|
223
|
-
description:
|
223
|
+
description:
|
224
224
|
email:
|
225
225
|
- rubygems@envato.com
|
226
226
|
executables: []
|
@@ -256,10 +256,10 @@ licenses:
|
|
256
256
|
- MIT
|
257
257
|
metadata:
|
258
258
|
bug_tracker_uri: https://github.com/envato/double_entry/issues
|
259
|
-
changelog_uri: https://github.com/envato/double_entry/blob/v2.0.
|
260
|
-
documentation_uri: https://www.rubydoc.info/gems/double_entry/2.0.
|
261
|
-
source_code_uri: https://github.com/envato/double_entry/tree/v2.0.
|
262
|
-
post_install_message:
|
259
|
+
changelog_uri: https://github.com/envato/double_entry/blob/v2.0.1/CHANGELOG.md
|
260
|
+
documentation_uri: https://www.rubydoc.info/gems/double_entry/2.0.1
|
261
|
+
source_code_uri: https://github.com/envato/double_entry/tree/v2.0.1
|
262
|
+
post_install_message:
|
263
263
|
rdoc_options: []
|
264
264
|
require_paths:
|
265
265
|
- lib
|
@@ -267,16 +267,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
267
267
|
requirements:
|
268
268
|
- - ">="
|
269
269
|
- !ruby/object:Gem::Version
|
270
|
-
version:
|
270
|
+
version: '3'
|
271
271
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
272
272
|
requirements:
|
273
|
-
- - "
|
273
|
+
- - ">="
|
274
274
|
- !ruby/object:Gem::Version
|
275
|
-
version:
|
275
|
+
version: '0'
|
276
276
|
requirements: []
|
277
|
-
|
278
|
-
|
279
|
-
signing_key:
|
277
|
+
rubygems_version: 3.4.21
|
278
|
+
signing_key:
|
280
279
|
specification_version: 4
|
281
280
|
summary: Tools to build your double entry financial ledger
|
282
281
|
test_files: []
|