gnucash 1.5.0 → 1.6.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/.gitignore +1 -0
- data/.travis.yml +26 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +78 -26
- data/README.md +5 -1
- data/gnucash.gemspec +1 -1
- data/lib/gnucash/account.rb +41 -1
- data/lib/gnucash/book.rb +171 -0
- data/lib/gnucash/customer.rb +54 -0
- data/lib/gnucash/isin.rb +25 -0
- data/lib/gnucash/security.rb +116 -0
- data/lib/gnucash/version.rb +1 -1
- data/lib/gnucash.rb +3 -0
- data/spec/books/pricedb-fixture.gnucash +0 -0
- data/spec/books/sample-text.gnucash +53 -2
- data/spec/books/sample.gnucash +0 -0
- data/spec/gnucash/account_spec.rb +30 -1
- data/spec/gnucash/customer_spec.rb +42 -0
- data/spec/gnucash/isin_spec.rb +19 -0
- data/spec/gnucash/security_spec.rb +85 -0
- data/spec/gnucash/transaction_spec.rb +1 -1
- data/spec/spec_helper.rb +2 -0
- metadata +15 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 72eb52f6d34ca1767398d5a0bbb0379b311ef87ed8ca7ae9190d7c02b5692e0d
|
|
4
|
+
data.tar.gz: 8f414c48579e293d9539b06c90e79e7a13fba80ffd53f8f07d483e2ee8cfcc1a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ad9bf95fcff49be8da8027e4c15a6246b261c4855e752073be128c883a1511d960791a51d80f4c084f49216d7242fcebf5c25505b4fa502807d1222458b2ad19
|
|
7
|
+
data.tar.gz: 77b49d4963895f3b08a7f8e55b11d5e3e427e3fa5425a80384fe09aad80fb09d17ab0f877b47fbb63ceb09c740d6e56b27719a057afb6e587829a072f74519e4
|
data/.gitignore
CHANGED
data/.travis.yml
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
language: ruby
|
|
2
|
+
|
|
3
|
+
bundler_args: --without debugger
|
|
4
|
+
|
|
5
|
+
cache: bundler
|
|
6
|
+
sudo: false
|
|
7
|
+
|
|
8
|
+
before_install:
|
|
9
|
+
- gem --version
|
|
10
|
+
|
|
11
|
+
script: bundle exec rspec
|
|
12
|
+
|
|
13
|
+
rvm:
|
|
14
|
+
- 2.4.6
|
|
15
|
+
- 2.5.5
|
|
16
|
+
- 2.6.4
|
|
17
|
+
- ruby-head
|
|
18
|
+
|
|
19
|
+
matrix:
|
|
20
|
+
allow_failures:
|
|
21
|
+
- rvm: ruby-head
|
|
22
|
+
|
|
23
|
+
notifications:
|
|
24
|
+
email:
|
|
25
|
+
recipients:
|
|
26
|
+
- niklaus.giger@member.fsf.org
|
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,44 +1,96 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
gnucash (1.
|
|
4
|
+
gnucash (1.6.0)
|
|
5
5
|
nokogiri
|
|
6
6
|
|
|
7
7
|
GEM
|
|
8
8
|
remote: https://rubygems.org/
|
|
9
9
|
specs:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
10
|
+
date (3.5.1)
|
|
11
|
+
debug (1.11.1)
|
|
12
|
+
irb (~> 1.10)
|
|
13
|
+
reline (>= 0.3.8)
|
|
14
|
+
diff-lcs (1.6.2)
|
|
15
|
+
docile (1.4.1)
|
|
16
|
+
erb (6.0.4)
|
|
17
|
+
io-console (0.8.2)
|
|
18
|
+
irb (1.18.0)
|
|
19
|
+
pp (>= 0.6.0)
|
|
20
|
+
prism (>= 1.3.0)
|
|
21
|
+
rdoc (>= 4.0.0)
|
|
22
|
+
reline (>= 0.4.2)
|
|
23
|
+
mini_portile2 (2.8.9)
|
|
24
|
+
nokogiri (1.19.3)
|
|
25
|
+
mini_portile2 (~> 2.8.2)
|
|
26
|
+
racc (~> 1.4)
|
|
27
|
+
nokogiri (1.19.3-aarch64-linux-gnu)
|
|
28
|
+
racc (~> 1.4)
|
|
29
|
+
nokogiri (1.19.3-aarch64-linux-musl)
|
|
30
|
+
racc (~> 1.4)
|
|
31
|
+
nokogiri (1.19.3-arm-linux-gnu)
|
|
32
|
+
racc (~> 1.4)
|
|
33
|
+
nokogiri (1.19.3-arm-linux-musl)
|
|
34
|
+
racc (~> 1.4)
|
|
35
|
+
nokogiri (1.19.3-arm64-darwin)
|
|
36
|
+
racc (~> 1.4)
|
|
37
|
+
nokogiri (1.19.3-x86_64-darwin)
|
|
38
|
+
racc (~> 1.4)
|
|
39
|
+
nokogiri (1.19.3-x86_64-linux-gnu)
|
|
40
|
+
racc (~> 1.4)
|
|
41
|
+
nokogiri (1.19.3-x86_64-linux-musl)
|
|
42
|
+
racc (~> 1.4)
|
|
43
|
+
pp (0.6.3)
|
|
44
|
+
prettyprint
|
|
45
|
+
prettyprint (0.2.0)
|
|
46
|
+
prism (1.9.0)
|
|
47
|
+
psych (5.3.1)
|
|
48
|
+
date
|
|
49
|
+
stringio
|
|
50
|
+
racc (1.8.1)
|
|
51
|
+
rake (13.4.2)
|
|
52
|
+
rdoc (7.2.0)
|
|
53
|
+
erb
|
|
54
|
+
psych (>= 4.0.0)
|
|
55
|
+
tsort
|
|
56
|
+
reline (0.6.3)
|
|
57
|
+
io-console (~> 0.5)
|
|
58
|
+
rspec (3.13.2)
|
|
59
|
+
rspec-core (~> 3.13.0)
|
|
60
|
+
rspec-expectations (~> 3.13.0)
|
|
61
|
+
rspec-mocks (~> 3.13.0)
|
|
62
|
+
rspec-core (3.13.6)
|
|
63
|
+
rspec-support (~> 3.13.0)
|
|
64
|
+
rspec-expectations (3.13.5)
|
|
25
65
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
26
|
-
rspec-support (~> 3.
|
|
27
|
-
rspec-mocks (3.8
|
|
66
|
+
rspec-support (~> 3.13.0)
|
|
67
|
+
rspec-mocks (3.13.8)
|
|
28
68
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
29
|
-
rspec-support (~> 3.
|
|
30
|
-
rspec-support (3.
|
|
31
|
-
simplecov (0.
|
|
69
|
+
rspec-support (~> 3.13.0)
|
|
70
|
+
rspec-support (3.13.7)
|
|
71
|
+
simplecov (0.22.0)
|
|
32
72
|
docile (~> 1.1)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
simplecov-html (0.
|
|
36
|
-
|
|
73
|
+
simplecov-html (~> 0.11)
|
|
74
|
+
simplecov_json_formatter (~> 0.1)
|
|
75
|
+
simplecov-html (0.13.2)
|
|
76
|
+
simplecov_json_formatter (0.1.4)
|
|
77
|
+
stringio (3.2.0)
|
|
78
|
+
tsort (0.2.0)
|
|
79
|
+
yard (0.9.43)
|
|
37
80
|
|
|
38
81
|
PLATFORMS
|
|
82
|
+
aarch64-linux-gnu
|
|
83
|
+
aarch64-linux-musl
|
|
84
|
+
arm-linux-gnu
|
|
85
|
+
arm-linux-musl
|
|
86
|
+
arm64-darwin
|
|
39
87
|
ruby
|
|
88
|
+
x86_64-darwin
|
|
89
|
+
x86_64-linux-gnu
|
|
90
|
+
x86_64-linux-musl
|
|
40
91
|
|
|
41
92
|
DEPENDENCIES
|
|
93
|
+
debug (>= 1.9)
|
|
42
94
|
gnucash!
|
|
43
95
|
rake
|
|
44
96
|
rdoc
|
|
@@ -47,4 +99,4 @@ DEPENDENCIES
|
|
|
47
99
|
yard
|
|
48
100
|
|
|
49
101
|
BUNDLED WITH
|
|
50
|
-
|
|
102
|
+
4.0.11
|
data/README.md
CHANGED
|
@@ -37,7 +37,7 @@ act.transactions.each do |txn|
|
|
|
37
37
|
txn.date,
|
|
38
38
|
txn.value,
|
|
39
39
|
balance,
|
|
40
|
-
txn.description)
|
|
40
|
+
txn.description)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
year = Date.today.year
|
|
@@ -49,6 +49,10 @@ To get the balance of an account, use ```act.balance_on("#{year}-12-31")```.
|
|
|
49
49
|
To get the total balance of an account with all its children accounts,
|
|
50
50
|
use ```act.balance_on("#{year}-12-31", recursive: true)```.
|
|
51
51
|
|
|
52
|
+
## Full YARD Documentation
|
|
53
|
+
|
|
54
|
+
See <https://rubydoc.info/github/holtrop/ruby-gnucash/master>.
|
|
55
|
+
|
|
52
56
|
## Contributing
|
|
53
57
|
|
|
54
58
|
1. Fork it
|
data/gnucash.gemspec
CHANGED
|
@@ -13,7 +13,7 @@ Gem::Specification.new do |gem|
|
|
|
13
13
|
gem.homepage = "https://github.com/holtrop/ruby-gnucash"
|
|
14
14
|
gem.license = "MIT"
|
|
15
15
|
|
|
16
|
-
gem.files = `git ls-files`.split($/)
|
|
16
|
+
gem.files = `git ls-files`.split($/).reject { |f| f == "bin/rdbg" }
|
|
17
17
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
18
18
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
19
19
|
gem.require_paths = ["lib"]
|
data/lib/gnucash/account.rb
CHANGED
|
@@ -15,6 +15,11 @@ module Gnucash
|
|
|
15
15
|
# @return [String] The GUID of the account.
|
|
16
16
|
attr_reader :id
|
|
17
17
|
|
|
18
|
+
# @return [String, nil] Account code (+act:code+), if set in GnuCash.
|
|
19
|
+
#
|
|
20
|
+
# @since 1.6.0
|
|
21
|
+
attr_reader :code
|
|
22
|
+
|
|
18
23
|
# @return [Array<AccountTransaction>]
|
|
19
24
|
# List of transactions associated with this account.
|
|
20
25
|
attr_reader :transactions
|
|
@@ -22,6 +27,16 @@ module Gnucash
|
|
|
22
27
|
# @return [Boolean] Whether the account is a placeholder or not.
|
|
23
28
|
attr_reader :placeholder
|
|
24
29
|
|
|
30
|
+
# @return [String, nil] Commodity namespace (+cmdty:space+) from +act:commodity+, if present.
|
|
31
|
+
#
|
|
32
|
+
# @since 1.6.0
|
|
33
|
+
attr_reader :commodity_space
|
|
34
|
+
|
|
35
|
+
# @return [String, nil] Commodity id (+cmdty:id+) from +act:commodity+, if present.
|
|
36
|
+
#
|
|
37
|
+
# @since 1.6.0
|
|
38
|
+
attr_reader :commodity_id
|
|
39
|
+
|
|
25
40
|
# @since 1.4.0
|
|
26
41
|
#
|
|
27
42
|
# @return [String, nil] The GUID of the parent account, if any.
|
|
@@ -38,6 +53,8 @@ module Gnucash
|
|
|
38
53
|
@type = node.xpath('act:type').text
|
|
39
54
|
@description = node.xpath('act:description').text
|
|
40
55
|
@id = node.xpath('act:id').text
|
|
56
|
+
code_raw = node.at_xpath('act:code')&.text
|
|
57
|
+
@code = (code_raw.nil? || code_raw.empty?) ? nil : code_raw
|
|
41
58
|
@parent_id = node.xpath('act:parent').text
|
|
42
59
|
@parent_id = nil if @parent_id == ""
|
|
43
60
|
@transactions = []
|
|
@@ -46,6 +63,29 @@ module Gnucash
|
|
|
46
63
|
(slot.xpath("slot:key").first.text == "placeholder" and
|
|
47
64
|
slot.xpath("slot:value").first.text == "true")
|
|
48
65
|
end ? true : false
|
|
66
|
+
|
|
67
|
+
cmd = node.at_xpath("act:commodity")
|
|
68
|
+
if cmd
|
|
69
|
+
@commodity_space = cmd.at_xpath("cmdty:space")&.text&.strip
|
|
70
|
+
@commodity_id = cmd.at_xpath("cmdty:id")&.text&.strip
|
|
71
|
+
@commodity_space = nil if @commodity_space.nil? || @commodity_space.empty?
|
|
72
|
+
@commodity_id = nil if @commodity_id.nil? || @commodity_id.empty?
|
|
73
|
+
else
|
|
74
|
+
@commodity_space = nil
|
|
75
|
+
@commodity_id = nil
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Priced {Security} for this account's commodity, if the commodity appears in the
|
|
80
|
+
# book's price database (+gnc:pricedb+). Otherwise +nil+.
|
|
81
|
+
#
|
|
82
|
+
# @since 1.6.0
|
|
83
|
+
#
|
|
84
|
+
# @return [Security, nil]
|
|
85
|
+
def security
|
|
86
|
+
return nil unless @commodity_space && @commodity_id
|
|
87
|
+
|
|
88
|
+
@book.find_security(@commodity_space, @commodity_id)
|
|
49
89
|
end
|
|
50
90
|
|
|
51
91
|
# Return the fully qualified account name.
|
|
@@ -141,7 +181,7 @@ module Gnucash
|
|
|
141
181
|
# @return [Array<Symbol>] Attributes used to build the inspection string
|
|
142
182
|
# @see Gnucash::Support::LightInspect
|
|
143
183
|
def attributes
|
|
144
|
-
%i[id name description type placeholder parent_id]
|
|
184
|
+
%i[id name description type code placeholder parent_id commodity_space commodity_id]
|
|
145
185
|
end
|
|
146
186
|
|
|
147
187
|
private
|
data/lib/gnucash/book.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require "date"
|
|
1
2
|
require "zlib"
|
|
2
3
|
require "nokogiri"
|
|
3
4
|
|
|
@@ -6,12 +7,27 @@ module Gnucash
|
|
|
6
7
|
class Book
|
|
7
8
|
include Support::LightInspect
|
|
8
9
|
|
|
10
|
+
# One row from +gnc:pricedb+.
|
|
11
|
+
#
|
|
12
|
+
# @since 1.6.0
|
|
13
|
+
PriceRow = Struct.new(:commodity_space, :commodity_id, :currency_space, :currency_id, :date, :value)
|
|
14
|
+
|
|
9
15
|
# @return [Array<Account>] Accounts in the book.
|
|
10
16
|
attr_reader :accounts
|
|
11
17
|
|
|
18
|
+
# @return [Array<Account>] Customers in the book.
|
|
19
|
+
# @since 1.6.0
|
|
20
|
+
attr_reader :customers
|
|
21
|
+
|
|
12
22
|
# @return [Array<Transaction>] Transactions in the book.
|
|
13
23
|
attr_reader :transactions
|
|
14
24
|
|
|
25
|
+
# @return [Array<PriceRow>]
|
|
26
|
+
# Raw price-database rows (commodity/currency/value/date). Prefer
|
|
27
|
+
# {Security#value_on} for valuations.
|
|
28
|
+
# @since 1.6.0
|
|
29
|
+
attr_reader :price_rows
|
|
30
|
+
|
|
15
31
|
# @return [Date] Date of the first transaction in the book.
|
|
16
32
|
attr_reader :start_date
|
|
17
33
|
|
|
@@ -36,8 +52,11 @@ module Gnucash
|
|
|
36
52
|
raise "Error: Expected to find one gnc:book entry"
|
|
37
53
|
end
|
|
38
54
|
@book_node = book_nodes.first
|
|
55
|
+
build_customers
|
|
39
56
|
build_accounts
|
|
40
57
|
build_transactions
|
|
58
|
+
build_price_quotes
|
|
59
|
+
build_commodity_isin_index
|
|
41
60
|
finalize
|
|
42
61
|
end
|
|
43
62
|
|
|
@@ -61,6 +80,76 @@ module Gnucash
|
|
|
61
80
|
@accounts.find { |a| a.full_name == full_name }
|
|
62
81
|
end
|
|
63
82
|
|
|
83
|
+
# Return a handle to the Customer object that has the given fully-qualified
|
|
84
|
+
# name.
|
|
85
|
+
#
|
|
86
|
+
# @since 1.6.0
|
|
87
|
+
#
|
|
88
|
+
# @param full_name [String]
|
|
89
|
+
# Fully-qualified customer name (ex: "Joe Doe").
|
|
90
|
+
#
|
|
91
|
+
# @return [Customer, nil] Customer object, or nil if not found.
|
|
92
|
+
def find_customer_by_full_name(full_name)
|
|
93
|
+
@customers.find { |a| a.full_name == full_name }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Return every {Security} that appears in the price database (unique commodity).
|
|
97
|
+
#
|
|
98
|
+
# @since 1.6.0
|
|
99
|
+
#
|
|
100
|
+
# @return [Array<Security>]
|
|
101
|
+
def securities
|
|
102
|
+
@securities ||= @price_rows.map { |r| [r.commodity_space, r.commodity_id] }.uniq.map do |space, id|
|
|
103
|
+
Security.new(self, space, id)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Look up a security by GnuCash commodity +space+ and +id+.
|
|
108
|
+
#
|
|
109
|
+
# @since 1.6.0
|
|
110
|
+
#
|
|
111
|
+
# @return [Security, nil]
|
|
112
|
+
def find_security(space, id)
|
|
113
|
+
return nil unless @price_rows.any? { |r| r.commodity_space == space && r.commodity_id == id }
|
|
114
|
+
Security.new(self, space, id)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Look up a priced security whose commodity defines this ISIN (+cmdty:xcode+ or a slot
|
|
118
|
+
# whose key matches +isin+, e.g. +user:ISIN+). Comparison ignores spaces, hyphens and case.
|
|
119
|
+
#
|
|
120
|
+
# @since 1.6.0
|
|
121
|
+
#
|
|
122
|
+
# @param isin [String] ISIN as stored or typed (e.g. +"US0378331005"+).
|
|
123
|
+
#
|
|
124
|
+
# @return [Security, nil]
|
|
125
|
+
def find_security_by_isin(isin)
|
|
126
|
+
key = ISIN.normalize(isin)
|
|
127
|
+
return nil if key.empty?
|
|
128
|
+
|
|
129
|
+
pair = @isin_index[key]
|
|
130
|
+
return nil unless pair
|
|
131
|
+
|
|
132
|
+
find_security(pair[0], pair[1])
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# ISIN for a commodity if present in the book (+nil+ otherwise).
|
|
136
|
+
#
|
|
137
|
+
# @since 1.6.0
|
|
138
|
+
#
|
|
139
|
+
# @return [String, nil] Normalized ISIN (12 uppercase alphanumeric characters).
|
|
140
|
+
def isin_for_commodity(space, id)
|
|
141
|
+
@isin_for_commodity[[space, id]]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Price-database rows for one commodity (used by {Security#value_on}).
|
|
145
|
+
#
|
|
146
|
+
# @since 1.6.0
|
|
147
|
+
#
|
|
148
|
+
# @return [Array<PriceRow>]
|
|
149
|
+
def quotes_for_commodity(space, id)
|
|
150
|
+
@price_rows.select { |r| r.commodity_space == space && r.commodity_id == id }
|
|
151
|
+
end
|
|
152
|
+
|
|
64
153
|
# Attributes available for inspection
|
|
65
154
|
#
|
|
66
155
|
# @return [Array<Symbol>] Attributes used to build the inspection string
|
|
@@ -78,6 +167,12 @@ module Gnucash
|
|
|
78
167
|
end
|
|
79
168
|
end
|
|
80
169
|
|
|
170
|
+
def build_customers
|
|
171
|
+
@customers = @book_node.xpath('gnc:GncCustomer').map do |customer_node|
|
|
172
|
+
Customer.new(self, customer_node)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
81
176
|
# @return [void]
|
|
82
177
|
def build_transactions
|
|
83
178
|
@start_date = nil
|
|
@@ -90,6 +185,82 @@ module Gnucash
|
|
|
90
185
|
end
|
|
91
186
|
end
|
|
92
187
|
|
|
188
|
+
# @return [void]
|
|
189
|
+
def build_price_quotes
|
|
190
|
+
pricedb = @book_node.at_xpath('gnc:pricedb')
|
|
191
|
+
@price_rows = []
|
|
192
|
+
return unless pricedb
|
|
193
|
+
|
|
194
|
+
pricedb.element_children.each do |node|
|
|
195
|
+
next unless node.element?
|
|
196
|
+
row = parse_price_node(node)
|
|
197
|
+
@price_rows << row if row
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# @return [PriceRow, nil]
|
|
202
|
+
def parse_price_node(node)
|
|
203
|
+
return nil unless node.at_xpath('price:commodity')
|
|
204
|
+
|
|
205
|
+
cmd = node.at_xpath('price:commodity')
|
|
206
|
+
cur = node.at_xpath('price:currency')
|
|
207
|
+
return nil unless cmd && cur
|
|
208
|
+
|
|
209
|
+
commodity_space = cmd.at_xpath('cmdty:space')&.text
|
|
210
|
+
commodity_id = cmd.at_xpath('cmdty:id')&.text
|
|
211
|
+
currency_space = cur.at_xpath('cmdty:space')&.text
|
|
212
|
+
currency_id = cur.at_xpath('cmdty:id')&.text
|
|
213
|
+
return nil if [commodity_space, commodity_id, currency_space, currency_id].any? { |s| s.nil? || s.empty? }
|
|
214
|
+
|
|
215
|
+
ts = node.at_xpath('price:time/ts:date')&.text
|
|
216
|
+
return nil if ts.nil? || ts.empty?
|
|
217
|
+
|
|
218
|
+
date = Date.parse(ts.split(' ').first)
|
|
219
|
+
val_text = node.at_xpath('price:value')&.text
|
|
220
|
+
return nil if val_text.nil? || val_text.empty?
|
|
221
|
+
|
|
222
|
+
PriceRow.new(commodity_space, commodity_id, currency_space, currency_id, date, Value.new(val_text))
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# @return [void]
|
|
226
|
+
def build_commodity_isin_index
|
|
227
|
+
@isin_for_commodity = {}
|
|
228
|
+
@isin_index = {}
|
|
229
|
+
|
|
230
|
+
@book_node.xpath("gnc:commodity").each do |node|
|
|
231
|
+
space = node.at_xpath("cmdty:space")&.text&.strip
|
|
232
|
+
id = node.at_xpath("cmdty:id")&.text&.strip
|
|
233
|
+
next if space.nil? || id.nil? || space.empty? || id.empty?
|
|
234
|
+
|
|
235
|
+
raw = extract_isin_from_commodity_node(node)
|
|
236
|
+
next unless raw
|
|
237
|
+
|
|
238
|
+
key = ISIN.normalize(raw)
|
|
239
|
+
next unless ISIN.valid_format?(key)
|
|
240
|
+
|
|
241
|
+
@isin_for_commodity[[space, id]] = key
|
|
242
|
+
@isin_index[key] ||= [space, id]
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# @return [String, nil] raw ISIN string from XML before normalization
|
|
247
|
+
def extract_isin_from_commodity_node(node)
|
|
248
|
+
node.xpath(".//slot").each do |slot|
|
|
249
|
+
k = slot.at_xpath("slot:key")&.text
|
|
250
|
+
next unless k&.match?(/isin/i)
|
|
251
|
+
|
|
252
|
+
v = slot.at_xpath("slot:value")&.text&.strip
|
|
253
|
+
next if v.nil? || v.empty?
|
|
254
|
+
|
|
255
|
+
return v if ISIN.valid_format?(v)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
xcode = node.at_xpath("cmdty:xcode")&.text&.strip
|
|
259
|
+
return xcode if xcode && ISIN.valid_format?(xcode)
|
|
260
|
+
|
|
261
|
+
nil
|
|
262
|
+
end
|
|
263
|
+
|
|
93
264
|
# @return [void]
|
|
94
265
|
def finalize
|
|
95
266
|
@accounts.sort! do |a, b|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Gnucash
|
|
2
|
+
# Represent a GnuCash customer object.
|
|
3
|
+
# @since 1.6.0
|
|
4
|
+
class Customer
|
|
5
|
+
include Support::LightInspect
|
|
6
|
+
# gnc:GncCustomer
|
|
7
|
+
## id, company, name, addr1, addr2, addr3, addr4, phone, fax, email, notes, shipname, shipaddr1, shipaddr2, shipaddr3, shipaddr4, shiphone, shipfax, shipmail
|
|
8
|
+
|
|
9
|
+
# @return [String] The name of the customer (unqualified).
|
|
10
|
+
attr_reader :name
|
|
11
|
+
|
|
12
|
+
# @return [String] The GUID of the customer.
|
|
13
|
+
attr_reader :guid
|
|
14
|
+
|
|
15
|
+
# @return [String] The ID of the customer.
|
|
16
|
+
attr_reader :id
|
|
17
|
+
|
|
18
|
+
# @return [String] The address of the customer.
|
|
19
|
+
attr_reader :address
|
|
20
|
+
|
|
21
|
+
# @return [String] The shipping address of the customer.
|
|
22
|
+
attr_reader :shipping_address
|
|
23
|
+
|
|
24
|
+
# Create an customer object.
|
|
25
|
+
#
|
|
26
|
+
# @param book [Book] The {Gnucash::Book} containing the customer.
|
|
27
|
+
# @param node [Nokogiri::XML::Node] Nokogiri XML node.
|
|
28
|
+
def initialize(book, node)
|
|
29
|
+
@book = book
|
|
30
|
+
@node = node
|
|
31
|
+
@name = node.xpath('cust:name').text
|
|
32
|
+
@id = node.xpath('cust:id').text
|
|
33
|
+
@guid = node.xpath('cust:guid').text
|
|
34
|
+
@address = node.xpath('cust:addr').text
|
|
35
|
+
@shipping_address = node.xpath('cust:shipaddr').text
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Return the fully qualified customer name.
|
|
39
|
+
#
|
|
40
|
+
# @return [String] Fully qualified customer name.
|
|
41
|
+
def full_name
|
|
42
|
+
name
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Attributes available for inspection
|
|
46
|
+
#
|
|
47
|
+
# @return [Array<Symbol>] Attributes used to build the inspection string
|
|
48
|
+
# @see Gnucash::Support::LightInspect
|
|
49
|
+
def attributes
|
|
50
|
+
%i[id name guid address shipping_address]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/gnucash/isin.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Gnucash
|
|
2
|
+
# Helpers for ISIN (ISO 6166) strings as stored on GnuCash commodities.
|
|
3
|
+
#
|
|
4
|
+
# @since 1.6.0
|
|
5
|
+
module ISIN
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
# @param str [String, nil]
|
|
9
|
+
# @return [String] Uppercase ISIN with spaces and hyphens removed.
|
|
10
|
+
def normalize(str)
|
|
11
|
+
return "" if str.nil? || str.to_s.empty?
|
|
12
|
+
str.to_s.gsub(/[\s-]/, "").upcase
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Rough format check: 12 alphanumeric characters after normalization.
|
|
16
|
+
#
|
|
17
|
+
# @param str [String, nil]
|
|
18
|
+
# @return [Boolean]
|
|
19
|
+
def valid_format?(str)
|
|
20
|
+
s = normalize(str)
|
|
21
|
+
return false if s.length != 12
|
|
22
|
+
s.match?(/\A[A-Z0-9]{12}\z/)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
require "date"
|
|
2
|
+
|
|
3
|
+
module Gnucash
|
|
4
|
+
# Price quote for a security (commodity) from the GnuCash price database: the
|
|
5
|
+
# value of one unit of the security expressed in +currency+ as of +date+.
|
|
6
|
+
#
|
|
7
|
+
# @since 1.6.0
|
|
8
|
+
class SecurityQuote
|
|
9
|
+
include Support::LightInspect
|
|
10
|
+
|
|
11
|
+
# @return [Value] Price of one unit of the security in the quote currency.
|
|
12
|
+
attr_reader :value
|
|
13
|
+
|
|
14
|
+
# @return [String] Commodity namespace (GnuCash "space") of the quote currency.
|
|
15
|
+
attr_reader :currency_space
|
|
16
|
+
|
|
17
|
+
# @return [String] Commodity id of the quote currency (e.g. +"USD"+).
|
|
18
|
+
attr_reader :currency_id
|
|
19
|
+
|
|
20
|
+
# @return [Date] Date of the price in the book (quote time, date-only).
|
|
21
|
+
attr_reader :date
|
|
22
|
+
|
|
23
|
+
def initialize(value:, currency_space:, currency_id:, date:)
|
|
24
|
+
@value = value
|
|
25
|
+
@currency_space = currency_space
|
|
26
|
+
@currency_id = currency_id
|
|
27
|
+
@date = date
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def attributes
|
|
31
|
+
%i[value currency_space currency_id date]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# A security (stock, fund, etc.) identified by its GnuCash commodity
|
|
36
|
+
# +space+ and +id+, with prices loaded from the book's price database.
|
|
37
|
+
#
|
|
38
|
+
# @since 1.6.0
|
|
39
|
+
class Security
|
|
40
|
+
include Support::LightInspect
|
|
41
|
+
|
|
42
|
+
# @return [String] Commodity namespace (e.g. +"NASDAQ"+, +"ISO4217"+).
|
|
43
|
+
attr_reader :space
|
|
44
|
+
|
|
45
|
+
# @return [String] Commodity id (e.g. ticker or currency code).
|
|
46
|
+
attr_reader :id
|
|
47
|
+
|
|
48
|
+
# @param book [Book] Parent book.
|
|
49
|
+
# @param space [String] Commodity space.
|
|
50
|
+
# @param id [String] Commodity id.
|
|
51
|
+
def initialize(book, space, id)
|
|
52
|
+
@book = book
|
|
53
|
+
@space = space
|
|
54
|
+
@id = id
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @return [String, nil] ISIN from the commodity definition (+cmdty:xcode+ or ISIN slot), normalized.
|
|
58
|
+
def isin
|
|
59
|
+
@book.isin_for_commodity(@space, @id)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Return the most recent price quote whose date is on or before the given
|
|
63
|
+
# valuation date.
|
|
64
|
+
#
|
|
65
|
+
# If the security has multiple quote currencies, +currency_space+ and
|
|
66
|
+
# +currency_id+ select one; if omitted, USD (+ISO4217+ / +USD+) is preferred
|
|
67
|
+
# when present, otherwise an arbitrary quote chain is used.
|
|
68
|
+
#
|
|
69
|
+
# @param date [String, Date] Valuation date.
|
|
70
|
+
# @param currency_space [String, nil] Restrict to this quote currency space.
|
|
71
|
+
# @param currency_id [String, nil] Restrict to this quote currency id.
|
|
72
|
+
#
|
|
73
|
+
# @return [SecurityQuote, nil] Quote used for valuation, or nil if none applies.
|
|
74
|
+
def value_on(date, currency_space: nil, currency_id: nil)
|
|
75
|
+
date = Date.parse(date) if date.is_a?(String)
|
|
76
|
+
if (currency_space.nil? ^ currency_id.nil?)
|
|
77
|
+
raise ArgumentError, "currency_space and currency_id must both be set or both omitted"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
quotes = @book.quotes_for_commodity(@space, @id)
|
|
81
|
+
return nil if quotes.empty?
|
|
82
|
+
|
|
83
|
+
filtered =
|
|
84
|
+
if currency_space
|
|
85
|
+
quotes.select { |q| q.currency_space == currency_space && q.currency_id == currency_id }
|
|
86
|
+
else
|
|
87
|
+
quotes
|
|
88
|
+
end
|
|
89
|
+
return nil if filtered.empty?
|
|
90
|
+
|
|
91
|
+
pick_currency = lambda do |list|
|
|
92
|
+
usd = list.select { |q| q.currency_space == "ISO4217" && q.currency_id == "USD" }
|
|
93
|
+
(usd.empty? ? list : usd)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
candidates = currency_space ? filtered : pick_currency.call(filtered)
|
|
97
|
+
return nil if candidates.empty?
|
|
98
|
+
|
|
99
|
+
candidates = candidates.select { |q| q.date <= date }
|
|
100
|
+
return nil if candidates.empty?
|
|
101
|
+
|
|
102
|
+
best = candidates.max_by(&:date)
|
|
103
|
+
|
|
104
|
+
SecurityQuote.new(
|
|
105
|
+
value: best.value,
|
|
106
|
+
currency_space: best.currency_space,
|
|
107
|
+
currency_id: best.currency_id,
|
|
108
|
+
date: best.date
|
|
109
|
+
)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def attributes
|
|
113
|
+
%i[space id isin]
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
data/lib/gnucash/version.rb
CHANGED
data/lib/gnucash.rb
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
require_relative "gnucash/support"
|
|
2
2
|
require_relative "gnucash/account"
|
|
3
|
+
require_relative "gnucash/customer"
|
|
3
4
|
require_relative "gnucash/account_transaction"
|
|
5
|
+
require_relative "gnucash/isin"
|
|
6
|
+
require_relative "gnucash/security"
|
|
4
7
|
require_relative "gnucash/book"
|
|
5
8
|
require_relative "gnucash/transaction"
|
|
6
9
|
require_relative "gnucash/value"
|
|
Binary file
|
|
@@ -33,9 +33,10 @@
|
|
|
33
33
|
<gnc:book version="2.0.0">
|
|
34
34
|
<book:id type="guid">b8178c8d763ec49988c2696d9e41d7f6</book:id>
|
|
35
35
|
<gnc:count-data cd:type="commodity">1</gnc:count-data>
|
|
36
|
-
<gnc:count-data cd:type="account">
|
|
37
|
-
<gnc:count-data cd:type="transaction">
|
|
36
|
+
<gnc:count-data cd:type="account">65</gnc:count-data>
|
|
37
|
+
<gnc:count-data cd:type="transaction">474</gnc:count-data>
|
|
38
38
|
<gnc:count-data cd:type="schedxaction">6</gnc:count-data>
|
|
39
|
+
<gnc:count-data cd:type="gnc:GncCustomer">1</gnc:count-data>
|
|
39
40
|
<gnc:commodity version="2.0.0">
|
|
40
41
|
<cmdty:space>ISO4217</cmdty:space>
|
|
41
42
|
<cmdty:id>USD</cmdty:id>
|
|
@@ -30238,6 +30239,56 @@
|
|
|
30238
30239
|
</gnc:recurrence>
|
|
30239
30240
|
</sx:schedule>
|
|
30240
30241
|
</gnc:schedxaction>
|
|
30242
|
+
<gnc:GncCustomer version="2.0.0">
|
|
30243
|
+
<cust:guid type="guid">0938eaff7545d7ba45fe7b866d60a209</cust:guid>
|
|
30244
|
+
<cust:name>Joe Doe</cust:name>
|
|
30245
|
+
<cust:id>8765</cust:id>
|
|
30246
|
+
<cust:addr version="2.0.0">
|
|
30247
|
+
<addr:name>Joe Doe jr</addr:name>
|
|
30248
|
+
<addr:addr1>Main Street 7</addr:addr1>
|
|
30249
|
+
<addr:addr2>3rd floor</addr:addr2>
|
|
30250
|
+
<addr:addr3>adress 3rd line</addr:addr3>
|
|
30251
|
+
<addr:addr4>adress 4th line</addr:addr4>
|
|
30252
|
+
<addr:phone>+41 55 612 20 54</addr:phone>
|
|
30253
|
+
<addr:fax>+41 55 612 20 55</addr:fax>
|
|
30254
|
+
<addr:email>joe.doe@gmail.com</addr:email>
|
|
30255
|
+
</cust:addr>
|
|
30256
|
+
<cust:shipaddr version="2.0.0">
|
|
30257
|
+
<addr:name>Joe Doe sr</addr:name>
|
|
30258
|
+
<addr:addr1>Main Street 1</addr:addr1>
|
|
30259
|
+
<addr:addr2>line 2</addr:addr2>
|
|
30260
|
+
<addr:addr3>line 3</addr:addr3>
|
|
30261
|
+
<addr:addr4>line 4</addr:addr4>
|
|
30262
|
+
<addr:phone>+41 612 20 55</addr:phone>
|
|
30263
|
+
<addr:fax>+41 612 20 56</addr:fax>
|
|
30264
|
+
<addr:email>joe.doe@gmail-copy.com</addr:email>
|
|
30265
|
+
</cust:shipaddr>
|
|
30266
|
+
<cust:notes>Some remark about the customer</cust:notes>
|
|
30267
|
+
<cust:taxincluded>USEGLOBAL</cust:taxincluded>
|
|
30268
|
+
<cust:active>1</cust:active>
|
|
30269
|
+
<cust:discount>0/1</cust:discount>
|
|
30270
|
+
<cust:credit>0/1</cust:credit>
|
|
30271
|
+
<cust:currency>
|
|
30272
|
+
<cmdty:space>ISO4217</cmdty:space>
|
|
30273
|
+
<cmdty:id>USD</cmdty:id>
|
|
30274
|
+
</cust:currency>
|
|
30275
|
+
<cust:use-tt>0</cust:use-tt>
|
|
30276
|
+
<cust:slots>
|
|
30277
|
+
<slot>
|
|
30278
|
+
<slot:key>last-posted-to-acct</slot:key>
|
|
30279
|
+
<slot:value type="guid">849f778995e8ecf8d4b96940afbbdcd7</slot:value>
|
|
30280
|
+
</slot>
|
|
30281
|
+
<slot>
|
|
30282
|
+
<slot:key>payment</slot:key>
|
|
30283
|
+
<slot:value type="frame">
|
|
30284
|
+
<slot>
|
|
30285
|
+
<slot:key>last_acct</slot:key>
|
|
30286
|
+
<slot:value type="guid">f58e9b550445468e25f277abdeadee91</slot:value>
|
|
30287
|
+
</slot>
|
|
30288
|
+
</slot:value>
|
|
30289
|
+
</slot>
|
|
30290
|
+
</cust:slots>
|
|
30291
|
+
</gnc:GncCustomer>
|
|
30241
30292
|
</gnc:book>
|
|
30242
30293
|
</gnc-v2>
|
|
30243
30294
|
|
data/spec/books/sample.gnucash
CHANGED
|
Binary file
|
|
@@ -70,7 +70,36 @@ module Gnucash
|
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
it "avoid inspection of heavier attributes" do
|
|
73
|
-
expect(@salary.inspect).to eq "#<Gnucash::Account id: efebb6cb617971b0a7f62e9d5a204789, name: Salary, description: Salary, type: INCOME, placeholder: false, parent_id: 35ab61d46f5404895bf5d4949f8a5593>"
|
|
73
|
+
expect(@salary.inspect).to eq "#<Gnucash::Account id: efebb6cb617971b0a7f62e9d5a204789, name: Salary, description: Salary, type: INCOME, code: , placeholder: false, parent_id: 35ab61d46f5404895bf5d4949f8a5593, commodity_space: ISO4217, commodity_id: USD>"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
context "with pricedb-fixture (account linked to a priced security)" do
|
|
77
|
+
before(:all) do
|
|
78
|
+
@pricedb_book = Gnucash.open("spec/books/pricedb-fixture.gnucash")
|
|
79
|
+
@stocks_account = @pricedb_book.find_account_by_full_name("Stocks")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "returns the Security for the account commodity when it appears in the price database" do
|
|
83
|
+
sec = @stocks_account.security
|
|
84
|
+
expect(sec).not_to be_nil
|
|
85
|
+
expect(sec.space).to eq("TEST")
|
|
86
|
+
expect(sec.id).to eq("STK")
|
|
87
|
+
expect(sec.isin).to eq("US0378331005")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "returns nil when the account commodity has no priced security" do
|
|
91
|
+
brokerage = @pricedb_book.find_account_by_full_name("Brokerage")
|
|
92
|
+
expect(brokerage.security).to be_nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "exposes the GnuCash account code (act:code)" do
|
|
96
|
+
expect(@pricedb_book.find_account_by_full_name("Brokerage").code).to eq("98234989234")
|
|
97
|
+
expect(@stocks_account.code).to eq("9823498n ewori oio982394")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "returns nil for code when act:code is absent" do
|
|
101
|
+
expect(@pricedb_book.find_account_by_full_name("Root Account").code).to be_nil
|
|
102
|
+
end
|
|
74
103
|
end
|
|
75
104
|
end
|
|
76
105
|
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Gnucash
|
|
2
|
+
describe Customer do
|
|
3
|
+
@@addr_1 = "\n Joe Doe jr\n Main Street 7\n 3rd floor\n adress 3rd line\n adress 4th line\n +41 55 612 20 54\n +41 55 612 20 55\n joe.doe@gmail.com\n "
|
|
4
|
+
@@addr_2 = "\n Joe Doe sr\n Main Street 1\n line 2\n line 3\n line 4\n +41 612 20 55\n +41 612 20 56\n joe.doe@gmail-copy.com\n "
|
|
5
|
+
|
|
6
|
+
before(:all) do
|
|
7
|
+
# just read the file once
|
|
8
|
+
@book = Gnucash.open("spec/books/sample.gnucash")
|
|
9
|
+
@joe_doe = @book.find_customer_by_full_name("Joe Doe")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "gives access to the name" do
|
|
13
|
+
expect(@joe_doe.name).to eq "Joe Doe"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "gives access to the full name" do
|
|
17
|
+
expect(@joe_doe.full_name).to eq "Joe Doe"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "gives access to the guid" do
|
|
21
|
+
expect(@joe_doe.guid).to eq "0938eaff7545d7ba45fe7b866d60a209"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "gives access to the ID" do
|
|
25
|
+
expect(@joe_doe.id).to eq "8765"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "gives access to the address" do
|
|
29
|
+
expect(@joe_doe.address).to eq @@addr_1
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "gives access to the shipping address" do
|
|
33
|
+
expect(@joe_doe.shipping_address).to eq @@addr_2
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "avoid inspection of heavier attributes" do
|
|
37
|
+
expect(@joe_doe.inspect).to eq "#<Gnucash::Customer id: 8765, name: Joe Doe, guid: 0938eaff7545d7ba45fe7b866d60a209" +
|
|
38
|
+
", address: " + @@addr_1 +
|
|
39
|
+
", shipping_address: " + @@addr_2 + ">"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Gnucash
|
|
2
|
+
describe ISIN do
|
|
3
|
+
describe ".normalize" do
|
|
4
|
+
it "strips spaces and hyphens and uppercases" do
|
|
5
|
+
expect(ISIN.normalize("us 03783-31005")).to eq("US0378331005")
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
describe ".valid_format?" do
|
|
10
|
+
it "accepts 12 alphanumeric characters" do
|
|
11
|
+
expect(ISIN.valid_format?("US0378331005")).to be true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "rejects wrong length" do
|
|
15
|
+
expect(ISIN.valid_format?("US037833100")).to be false
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
module Gnucash
|
|
2
|
+
describe Security do
|
|
3
|
+
before(:all) do
|
|
4
|
+
@book = Gnucash.open("spec/books/pricedb-fixture.gnucash")
|
|
5
|
+
@security = @book.find_security("TEST", "STK")
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
it "loads price rows from the fixture" do
|
|
9
|
+
expect(@book.price_rows.size).to eq(4)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "lists securities from the price database" do
|
|
13
|
+
expect(@book.securities.size).to eq(2)
|
|
14
|
+
ids = @book.securities.map(&:id).sort
|
|
15
|
+
expect(ids).to eq(%w[ESFUND STK])
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "returns nil when the commodity is not priced" do
|
|
19
|
+
expect(@book.find_security("NONE", "X")).to be_nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "ISIN" do
|
|
23
|
+
it "exposes isin from cmdty:xcode" do
|
|
24
|
+
expect(@security.isin).to eq("US0378331005")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "exposes isin from commodity slots" do
|
|
28
|
+
s = @book.find_security("TEST", "ESFUND")
|
|
29
|
+
expect(s.isin).to eq("ES0105046009")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "finds security by ISIN ignoring case and spaces" do
|
|
33
|
+
found = @book.find_security_by_isin("us 03783-31005")
|
|
34
|
+
expect(found.id).to eq("STK")
|
|
35
|
+
expect(@book.find_security_by_isin("ES0105046009").id).to eq("ESFUND")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "returns nil when ISIN is unknown or commodity not priced" do
|
|
39
|
+
expect(@book.find_security_by_isin("DE0000000000")).to be_nil
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "supports light inspect on security and quote" do
|
|
44
|
+
expect(@security.inspect).to include("TEST", "STK", "US0378331005")
|
|
45
|
+
q = @security.value_on(Date.new(2020, 6, 1))
|
|
46
|
+
expect(q.inspect).to include("currency_id", "USD")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
describe "#value_on" do
|
|
50
|
+
it "returns nil when the date is before the first quote" do
|
|
51
|
+
expect(@security.value_on(Date.new(2019, 12, 31))).to be_nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "returns the most recent quote on or before the date" do
|
|
55
|
+
q = @security.value_on(Date.new(2020, 3, 15))
|
|
56
|
+
expect(q.value).to eq(Value.new("10000/100"))
|
|
57
|
+
expect(q.date).to eq(Date.new(2020, 1, 1))
|
|
58
|
+
|
|
59
|
+
q2 = @security.value_on(Date.new(2020, 6, 1))
|
|
60
|
+
expect(q2.value).to eq(Value.new("15000/100"))
|
|
61
|
+
expect(q2.date).to eq(Date.new(2020, 6, 1))
|
|
62
|
+
|
|
63
|
+
q3 = @security.value_on("2020-12-31")
|
|
64
|
+
expect(q3.date).to eq(Date.new(2020, 6, 1))
|
|
65
|
+
expect(q3.value).to eq(Value.new("15000/100"))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "accepts an explicit quote currency" do
|
|
69
|
+
q = @security.value_on(
|
|
70
|
+
Date.new(2020, 12, 31),
|
|
71
|
+
currency_space: "CURRENCY",
|
|
72
|
+
currency_id: "USD"
|
|
73
|
+
)
|
|
74
|
+
expect(q.value.to_f).to eq(150.0)
|
|
75
|
+
expect(q.date).to eq(Date.new(2020, 6, 1))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "raises when only one currency keyword is given" do
|
|
79
|
+
expect {
|
|
80
|
+
@security.value_on(Date.new(2020, 1, 1), currency_space: "CURRENCY")
|
|
81
|
+
}.to raise_error(ArgumentError)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -32,7 +32,7 @@ module Gnucash
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
it "avoid inspection of heavier attributes" do
|
|
35
|
-
expect(@book.transactions.first.inspect).to
|
|
35
|
+
expect(@book.transactions.first.inspect).to match /#<Gnucash::Transaction.*12efba30f14dc6cd4c3ffe2994de8284.*2007-01-01.*Opening Balance/
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gnucash
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Josh Holtrop
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: nokogiri
|
|
@@ -103,6 +102,7 @@ extra_rdoc_files: []
|
|
|
103
102
|
files:
|
|
104
103
|
- ".gitignore"
|
|
105
104
|
- ".rspec"
|
|
105
|
+
- ".travis.yml"
|
|
106
106
|
- CHANGELOG.md
|
|
107
107
|
- Gemfile
|
|
108
108
|
- Gemfile.lock
|
|
@@ -114,16 +114,23 @@ files:
|
|
|
114
114
|
- lib/gnucash/account.rb
|
|
115
115
|
- lib/gnucash/account_transaction.rb
|
|
116
116
|
- lib/gnucash/book.rb
|
|
117
|
+
- lib/gnucash/customer.rb
|
|
118
|
+
- lib/gnucash/isin.rb
|
|
119
|
+
- lib/gnucash/security.rb
|
|
117
120
|
- lib/gnucash/support.rb
|
|
118
121
|
- lib/gnucash/support/light_inspect.rb
|
|
119
122
|
- lib/gnucash/transaction.rb
|
|
120
123
|
- lib/gnucash/value.rb
|
|
121
124
|
- lib/gnucash/version.rb
|
|
125
|
+
- spec/books/pricedb-fixture.gnucash
|
|
122
126
|
- spec/books/sample-text.gnucash
|
|
123
127
|
- spec/books/sample.gnucash
|
|
124
128
|
- spec/gnucash/account_spec.rb
|
|
125
129
|
- spec/gnucash/account_transaction_spec.rb
|
|
126
130
|
- spec/gnucash/book_spec.rb
|
|
131
|
+
- spec/gnucash/customer_spec.rb
|
|
132
|
+
- spec/gnucash/isin_spec.rb
|
|
133
|
+
- spec/gnucash/security_spec.rb
|
|
127
134
|
- spec/gnucash/support/light_inspect_spec.rb
|
|
128
135
|
- spec/gnucash/transaction_spec.rb
|
|
129
136
|
- spec/gnucash/value_spec.rb
|
|
@@ -133,7 +140,6 @@ homepage: https://github.com/holtrop/ruby-gnucash
|
|
|
133
140
|
licenses:
|
|
134
141
|
- MIT
|
|
135
142
|
metadata: {}
|
|
136
|
-
post_install_message:
|
|
137
143
|
rdoc_options: []
|
|
138
144
|
require_paths:
|
|
139
145
|
- lib
|
|
@@ -148,17 +154,19 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
148
154
|
- !ruby/object:Gem::Version
|
|
149
155
|
version: '0'
|
|
150
156
|
requirements: []
|
|
151
|
-
|
|
152
|
-
rubygems_version: 2.7.6
|
|
153
|
-
signing_key:
|
|
157
|
+
rubygems_version: 4.0.11
|
|
154
158
|
specification_version: 4
|
|
155
159
|
summary: Extract data from XML GnuCash data files
|
|
156
160
|
test_files:
|
|
161
|
+
- spec/books/pricedb-fixture.gnucash
|
|
157
162
|
- spec/books/sample-text.gnucash
|
|
158
163
|
- spec/books/sample.gnucash
|
|
159
164
|
- spec/gnucash/account_spec.rb
|
|
160
165
|
- spec/gnucash/account_transaction_spec.rb
|
|
161
166
|
- spec/gnucash/book_spec.rb
|
|
167
|
+
- spec/gnucash/customer_spec.rb
|
|
168
|
+
- spec/gnucash/isin_spec.rb
|
|
169
|
+
- spec/gnucash/security_spec.rb
|
|
162
170
|
- spec/gnucash/support/light_inspect_spec.rb
|
|
163
171
|
- spec/gnucash/transaction_spec.rb
|
|
164
172
|
- spec/gnucash/value_spec.rb
|