ledger-rest 2.0.3 → 3.0.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.
Files changed (41) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +2 -0
  3. data/Gemfile +1 -1
  4. data/Guardfile +4 -22
  5. data/{LICENSE.txt → LICENSE} +1 -1
  6. data/README.md +62 -11
  7. data/config.ru +1 -1
  8. data/ledger-rest.gemspec +17 -17
  9. data/ledger-rest.yml.example +3 -0
  10. data/lib/ledger-rest/app.rb +21 -34
  11. data/lib/ledger-rest/core_ext.rb +4 -3
  12. data/lib/ledger-rest/git.rb +8 -7
  13. data/lib/ledger-rest/ledger.rb +29 -24
  14. data/lib/ledger-rest/ledger/balance.rb +48 -15
  15. data/lib/ledger-rest/ledger/budget.rb +35 -16
  16. data/lib/ledger-rest/ledger/entry.rb +1 -4
  17. data/lib/ledger-rest/ledger/parser.rb +41 -37
  18. data/lib/ledger-rest/ledger/register.rb +31 -24
  19. data/lib/ledger-rest/ledger/transaction.rb +33 -28
  20. data/lib/ledger-rest/transactions.py +47 -0
  21. data/lib/ledger-rest/version.rb +2 -1
  22. data/spec/files/append.ledger +13 -0
  23. data/spec/files/budget.ledger +4 -0
  24. data/spec/files/main.ledger +4 -0
  25. data/spec/files/opening_balance.ledger +4 -0
  26. data/spec/files/transactions.ledger +11 -0
  27. data/spec/ledger-rest.yml +3 -0
  28. data/spec/ledger-rest/api/accounts_spec.rb +19 -0
  29. data/spec/ledger-rest/api/balance_spec.rb +117 -0
  30. data/spec/ledger-rest/api/budget_spec.rb +110 -0
  31. data/spec/ledger-rest/api/payees_spec.rb +19 -0
  32. data/spec/ledger-rest/api/register_spec.rb +233 -0
  33. data/spec/ledger-rest/api/transactions_spec.rb +201 -0
  34. data/spec/ledger-rest/api/version_spec.rb +13 -0
  35. data/spec/ledger-rest/ledger/balance_spec.rb +124 -0
  36. data/spec/ledger-rest/ledger/parser_spec.rb +184 -117
  37. data/spec/ledger-rest/ledger/transaction_spec.rb +38 -31
  38. data/spec/spec_helper.rb +22 -0
  39. data/spec/support/deep_eq_matcher.rb +149 -0
  40. metadata +52 -31
  41. data/ledger-rest.org +0 -22
@@ -0,0 +1,47 @@
1
+ import ledger
2
+ import json
3
+ import sys
4
+
5
+ filename = sys.argv[1]
6
+ query = sys.argv[2]
7
+
8
+ transactions = []
9
+ last_xact = None
10
+ for match in ledger.read_journal(filename).query(query):
11
+ if match.xact != last_xact:
12
+ transaction = {}
13
+ transaction['payee'] = match.xact.payee
14
+ transaction['date'] = match.xact.date.strftime('%Y/%m/%d')
15
+
16
+ if match.xact.code != None:
17
+ transaction['code'] = match.xact.code
18
+
19
+ if match.xact.note != None:
20
+ transaction['note'] = match.xact.note.strip()
21
+
22
+ if match.xact.aux_date != None:
23
+ transaction['effective_date'] = match.xact.aux_date.strftime('%Y/%m/%d')
24
+
25
+ if str(match.xact.state) == 'Cleared':
26
+ transaction['cleared'] = True
27
+ transaction['pending'] = False
28
+ elif str(match.xact.state) == 'Pending':
29
+ transaction['cleared'] = False
30
+ transaction['pending'] = True
31
+ else:
32
+ transaction['cleared'] = False
33
+ transaction['pending'] = False
34
+
35
+ transaction['posts'] = []
36
+ for post in match.xact.posts():
37
+ new_post = {}
38
+ new_post['account'] = str(post.account)
39
+ new_post['amount'] = float(post.amount)
40
+ new_post['commodity'] = str(post.amount.commodity)
41
+ transaction['posts'].append(new_post)
42
+
43
+ transactions.append(transaction)
44
+
45
+ last_xact = match.xact
46
+
47
+ print json.dumps(transactions)
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  module LedgerRest
2
- VERSION = "2.0.3"
3
+ VERSION = '3.0.0'
3
4
  end
@@ -0,0 +1,13 @@
1
+ 2013/12/03 NaveenaPath
2
+ Expenses:Restaurants 9.00EUR
3
+ Assets:Cash
4
+
5
+ 2013/12/05 Shikgoo
6
+ Expenses:Restaurants 12.00EUR
7
+ Liabilities:Max Mustermann
8
+ ; meta_info: Some interesting meta information
9
+
10
+ 2013/12/10 Bioladen Tegeler Straße
11
+ ; Sonnenblumenkernbrot
12
+ Assets:Reimbursements:Hans Maulwurf 3.10EUR
13
+ Assets:Cash
@@ -0,0 +1,4 @@
1
+ ~ Monthly
2
+ Expenses:Flat 450EUR
3
+ Expenses:Restaurants 100EUR
4
+ Assets
@@ -0,0 +1,4 @@
1
+ include opening_balance.ledger
2
+ include append.ledger
3
+ include budget.ledger
4
+ include transactions.ledger
@@ -0,0 +1,4 @@
1
+ 2013/01/01 * Opening Balance
2
+ Assets:Giro 2000EUR
3
+ Assets:Reimbursements:Hans Maulwurf 19EUR
4
+ Equity:Opening Balances
@@ -0,0 +1,11 @@
1
+ 2013/12/01=2013/12/02 * Sparkasse
2
+ Assets:Cash 50.00EUR
3
+ Assets:Giro
4
+
5
+ 2013/12/04 Trattoria La Vialla
6
+ Expenses:Restaurants 32.20EUR
7
+ Assets:Cash
8
+
9
+ 2013/12/06 Customer X
10
+ Assets:Giro 1200.00EUR
11
+ Income:Invoice
@@ -0,0 +1,3 @@
1
+ ledger_file: "spec/files/main.ledger"
2
+ ledger_bin: "/bin/ledger"
3
+ ledger_append_file: "spec/files/append.ledger"
@@ -0,0 +1,19 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe '/accounts' do
5
+ it 'returns accounts' do
6
+ get '/accounts'
7
+
8
+ JSON.parse(last_response.body).should =~
9
+ [
10
+ 'Assets:Cash',
11
+ 'Assets:Giro',
12
+ 'Assets:Reimbursements:Hans Maulwurf',
13
+ 'Equity:Opening Balances',
14
+ 'Expenses:Restaurants',
15
+ 'Income:Invoice',
16
+ 'Liabilities:Max Mustermann'
17
+ ]
18
+ end
19
+ end
@@ -0,0 +1,117 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe '/balance' do
5
+ context 'normally' do
6
+ let(:valid_json) do
7
+ {
8
+ 'accounts' =>
9
+ [
10
+ {
11
+ 'total' => '3177.80EUR',
12
+ 'name' => 'Assets',
13
+ 'depth' => 1,
14
+ 'fullname' => 'Assets',
15
+ 'accounts' =>
16
+ [
17
+ {
18
+ 'total' => '5.70EUR',
19
+ 'name' => 'Cash',
20
+ 'depth' => 2,
21
+ 'fullname' => 'Assets:Cash'
22
+ },
23
+ {
24
+ 'total' => '3150.00EUR',
25
+ 'name' => 'Giro',
26
+ 'depth' => 2,
27
+ 'fullname' => 'Assets:Giro'
28
+ },
29
+ {
30
+ 'total' => '22.10EUR',
31
+ 'name' => 'Reimbursements',
32
+ 'depth' => 2,
33
+ 'fullname' => 'Assets:Reimbursements',
34
+ 'accounts' =>
35
+ [
36
+ {
37
+ 'total' => '22.10EUR',
38
+ 'name' => 'Hans Maulwurf',
39
+ 'depth' => 3,
40
+ 'fullname' => 'Assets:Reimbursements:Hans Maulwurf'
41
+ }
42
+ ]
43
+ }
44
+ ]
45
+ },
46
+ {
47
+ 'total' => '-12.00EUR',
48
+ 'name' => 'Liabilities',
49
+ 'depth' => 1,
50
+ 'fullname' => 'Liabilities',
51
+ 'accounts' =>
52
+ [
53
+ {
54
+ 'total' => '-12.00EUR',
55
+ 'name' => 'Max Mustermann',
56
+ 'depth' => 2,
57
+ 'fullname' => 'Liabilities:Max Mustermann'
58
+ }
59
+ ]
60
+ }
61
+ ],
62
+ 'total' => '3165.80EUR'
63
+ }
64
+ end
65
+
66
+ it 'expands and wraps accounts' do
67
+ get '/balance', query: 'Assets Liabilities'
68
+ JSON.parse(last_response.body).should deep_eq valid_json
69
+ end
70
+ end
71
+
72
+ context 'with --flat query' do
73
+ let(:valid_json) do
74
+ {
75
+ 'accounts' =>
76
+ [
77
+ {
78
+ 'total' => '5.70EUR',
79
+ 'name' => 'Cash',
80
+ 'depth' => 2,
81
+ 'fullname' => 'Assets:Cash'
82
+ }, {
83
+ 'total' => '3150.00EUR',
84
+ 'name' => 'Giro',
85
+ 'depth' => 2,
86
+ 'fullname' => 'Assets:Giro'
87
+ }, {
88
+ 'total' => '22.10EUR',
89
+ 'name' => 'Reimbursements',
90
+ 'depth' => 2,
91
+ 'fullname' => 'Assets:Reimbursements'
92
+ }, {
93
+ 'total' => '22.10EUR',
94
+ 'name' => 'Hans Maulwurf',
95
+ 'depth' => 3,
96
+ 'fullname' => 'Assets:Reimbursements:Hans Maulwurf'
97
+ }, {
98
+ 'total' => '-12.00EUR',
99
+ 'name' => 'Liabilities',
100
+ 'depth' => 1,
101
+ 'fullname' => 'Liabilities'
102
+ }, {
103
+ 'total' => '-12.00EUR',
104
+ 'name' => 'Max Mustermann',
105
+ 'depth' => 2,
106
+ 'fullname' => 'Liabilities:Max Mustermann' }
107
+ ],
108
+ 'total' => '3165.80EUR'
109
+ }
110
+ end
111
+
112
+ it 'does not wrap accounts' do
113
+ get '/balance', query: '--flat Assets Liabilities'
114
+ JSON.parse(last_response.body).should deep_eq valid_json
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,110 @@
1
+ require 'spec_helper'
2
+
3
+ describe '/budget' do
4
+ context 'with query' do
5
+ let(:valid_json) do
6
+ {
7
+ 'accounts' =>
8
+ [
9
+ {
10
+ 'total' => '3177.80EUR',
11
+ 'budget' => '-6600.00EUR',
12
+ 'difference' => '9777.80EUR',
13
+ 'percentage' => '-48%',
14
+ 'account' => 'Assets'
15
+ }, {
16
+ 'total' => '53.20EUR',
17
+ 'budget' => '6600.00EUR',
18
+ 'difference' => '-6546.80EUR',
19
+ 'percentage' => '1%',
20
+ 'account' => 'Expenses'
21
+ }, {
22
+ 'total' => '0',
23
+ 'budget' => '5400.00EUR',
24
+ 'difference' => '-5400.00EUR',
25
+ 'percentage' => '0%',
26
+ 'account' => 'Expenses:Flat'
27
+ }, {
28
+ 'total' => '53.20EUR',
29
+ 'budget' => '1200.00EUR',
30
+ 'difference' => '-1146.80EUR',
31
+ 'percentage' => '4%',
32
+ 'account' => 'Expenses:Restaurants'
33
+ }
34
+ ],
35
+ 'total' => '3231.00EUR',
36
+ 'budget' => '0',
37
+ 'difference' => '3231.00EUR',
38
+ 'percentage' => '0'
39
+ }
40
+ end
41
+
42
+ it 'responds with the right budget' do
43
+ get '/budget'
44
+
45
+ JSON.parse(last_response.body).should deep_eq valid_json
46
+ end
47
+ end
48
+
49
+ context 'querying single account' do
50
+ let(:valid_json) do
51
+ {
52
+ 'accounts' =>
53
+ [
54
+ {
55
+ 'total' => '53.20EUR',
56
+ 'budget' => '100.00EUR',
57
+ 'difference' => '-46.80EUR',
58
+ 'percentage' => '53%',
59
+ 'account' => 'Expenses:Restaurants'
60
+ }
61
+ ]
62
+ }
63
+ end
64
+
65
+ it 'responds with the right budget' do
66
+ get '/budget', query: 'Restaurants'
67
+
68
+ JSON.parse(last_response.body).should deep_eq valid_json
69
+ end
70
+ end
71
+
72
+ context 'querying single account' do
73
+ let(:valid_json) do
74
+ {
75
+ 'accounts' =>
76
+ [
77
+ {
78
+ 'total' => '53.20EUR',
79
+ 'budget' => '550.00EUR',
80
+ 'difference' => '-496.80EUR',
81
+ 'percentage' => '10%',
82
+ 'account' => 'Expenses'
83
+ }, {
84
+ 'total' => '0',
85
+ 'budget' => '450.00EUR',
86
+ 'difference' => '-450.00EUR',
87
+ 'percentage' => '0%',
88
+ 'account' => 'Expenses:Flat'
89
+ }, {
90
+ 'total' => '53.20EUR',
91
+ 'budget' => '100.00EUR',
92
+ 'difference' => '-46.80EUR',
93
+ 'percentage' => '53%',
94
+ 'account' => 'Expenses:Restaurants'
95
+ }
96
+ ],
97
+ 'total' => '53.20EUR',
98
+ 'budget' => '550.00EUR',
99
+ 'difference' => '-496.80EUR',
100
+ 'percentage' => '10%',
101
+ }
102
+ end
103
+
104
+ it 'responds with the right budget' do
105
+ get '/budget', query: 'Expenses'
106
+
107
+ JSON.parse(last_response.body).should deep_eq valid_json
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,19 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe '/payees' do
5
+ it 'returns payees' do
6
+ get '/payees'
7
+
8
+ JSON.parse(last_response.body).should =~
9
+ [
10
+ 'Bioladen Tegeler Straße',
11
+ 'Customer X',
12
+ 'NaveenaPath',
13
+ 'Opening Balance',
14
+ 'Shikgoo',
15
+ 'Sparkasse',
16
+ 'Trattoria La Vialla'
17
+ ]
18
+ end
19
+ end
@@ -0,0 +1,233 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe '/register' do
5
+ context 'normal' do
6
+ let(:valid_response) do
7
+ [
8
+ {
9
+ 'date' => '2013/12/01',
10
+ 'effective_date' => '2013/12/02',
11
+ 'code' => nil,
12
+ 'cleared' => true,
13
+ 'pending' => false,
14
+ 'payee' => 'Sparkasse',
15
+ 'postings' =>
16
+ [
17
+ { 'account' => 'Assets:Cash',
18
+ 'amount' => '50',
19
+ 'total' => '50',
20
+ 'commodity' => 'EUR'
21
+ },
22
+ {
23
+ 'account' => 'Assets:Giro',
24
+ 'amount' => '-50',
25
+ 'total' => '-50',
26
+ 'commodity' => 'EUR'
27
+ }
28
+ ]
29
+ }
30
+ ]
31
+ end
32
+
33
+ it 'shows all fields' do
34
+ get '/register', query: '-p "2013-12-01"'
35
+ JSON.parse(last_response.body).should deep_eq valid_response
36
+ end
37
+ end
38
+
39
+ context 'daily report' do
40
+ let(:valid_response) do
41
+ [
42
+ {
43
+ 'beginning' => '2013/12/03',
44
+ 'end' => '2013/12/03',
45
+ 'postings' =>
46
+ [
47
+ {
48
+ 'account' => 'Expenses:Restaurants',
49
+ 'amount' => '9',
50
+ 'total' => '9',
51
+ 'commodity' => 'EUR'
52
+ }
53
+ ]
54
+ }, {
55
+ 'beginning' => '2013/12/04',
56
+ 'end' => '2013/12/04',
57
+ 'postings' =>
58
+ [
59
+ {
60
+ 'account' => 'Expenses:Restaurants',
61
+ 'amount' => '32.2',
62
+ 'total' => '32.2',
63
+ 'commodity' => 'EUR'
64
+ }
65
+ ]
66
+ }, {
67
+ 'beginning' => '2013/12/05',
68
+ 'end' => '2013/12/05',
69
+ 'postings' =>
70
+ [
71
+ {
72
+ 'account' => 'Expenses:Restaurants',
73
+ 'amount' => '12',
74
+ 'total' => '12',
75
+ 'commodity' => 'EUR'
76
+ }
77
+ ]
78
+ }
79
+ ]
80
+ end
81
+
82
+ it 'shows beginning, end and postings with account, amount and total' do
83
+ get '/register', query: '--daily ^Expenses'
84
+ JSON.parse(last_response.body).should deep_eq valid_response
85
+ end
86
+ end
87
+
88
+ context 'weekly report' do
89
+ let(:valid_response) do
90
+ [
91
+ {
92
+ 'beginning' => '2013/12/01',
93
+ 'end' => '2013/12/07',
94
+ 'postings' =>
95
+ [
96
+ {
97
+ 'account' => 'Expenses:Restaurants',
98
+ 'amount' => '53.2',
99
+ 'total' => '53.2',
100
+ 'commodity' => 'EUR'
101
+ }
102
+ ]
103
+ }
104
+ ]
105
+ end
106
+
107
+ it 'shows beginning, end and postings with account, amount and total' do
108
+ get '/register', query: '-W ^Expenses'
109
+ JSON.parse(last_response.body).should deep_eq valid_response
110
+ end
111
+ end
112
+
113
+ context 'monthly report' do
114
+ let(:valid_response) do
115
+ [
116
+ {
117
+ 'beginning' => '2013/12/01',
118
+ 'end' => '2013/12/31',
119
+ 'postings' =>
120
+ [
121
+ {
122
+ 'account' => 'Expenses:Restaurants',
123
+ 'amount' => '53.2',
124
+ 'total' => '53.2',
125
+ 'commodity' => 'EUR'
126
+ }
127
+ ]
128
+ }
129
+ ]
130
+ end
131
+
132
+ it 'shows beginning, end and postings with account, amount and total' do
133
+ get '/register', query: '-M ^Expenses'
134
+ JSON.parse(last_response.body).should deep_eq valid_response
135
+ end
136
+ end
137
+
138
+ context 'quarterly report' do
139
+ let(:valid_response) do
140
+ [
141
+ {
142
+ 'beginning' => '2013/10/01',
143
+ 'end' => '2013/12/31',
144
+ 'postings' =>
145
+ [
146
+ {
147
+ 'account' => 'Expenses:Restaurants',
148
+ 'amount' => '53.2',
149
+ 'total' => '53.2',
150
+ 'commodity' => 'EUR'
151
+ }
152
+ ]
153
+ }
154
+ ]
155
+ end
156
+
157
+ it 'shows beginning, end and postings with account, amount and total' do
158
+ get '/register', query: '--quarterly ^Expenses'
159
+ JSON.parse(last_response.body).should deep_eq valid_response
160
+ end
161
+ end
162
+
163
+ context 'yearly report' do
164
+ let(:valid_response) do
165
+ [
166
+ {
167
+ 'beginning' => '2013/01/01',
168
+ 'end' => '2013/12/31',
169
+ 'postings' =>
170
+ [
171
+ {
172
+ 'account' => 'Expenses:Restaurants',
173
+ 'amount' => '53.2',
174
+ 'total' => '53.2',
175
+ 'commodity' => 'EUR'
176
+ }
177
+ ]
178
+ }
179
+ ]
180
+ end
181
+
182
+ it 'shows beginning, end and postings with account, amount and total' do
183
+ get '/register', query: '-Y ^Expenses'
184
+ JSON.parse(last_response.body).should deep_eq valid_response
185
+ end
186
+ end
187
+
188
+ context 'report by payee' do
189
+ let(:valid_response) do
190
+ [
191
+ {
192
+ 'payee' => 'NaveenaPath',
193
+ 'postings' =>
194
+ [
195
+ {
196
+ 'account' => 'Expenses:Restaurants',
197
+ 'amount' => '9',
198
+ 'total' => '9',
199
+ 'commodity' => 'EUR'
200
+ }
201
+ ]
202
+ }, {
203
+ 'payee' => 'Shikgoo',
204
+ 'postings' =>
205
+ [
206
+ {
207
+ 'account' => 'Expenses:Restaurants',
208
+ 'amount' => '12',
209
+ 'total' => '12',
210
+ 'commodity' => 'EUR'
211
+ }
212
+ ]
213
+ }, {
214
+ 'payee' => 'Trattoria La Vialla',
215
+ 'postings' =>
216
+ [
217
+ {
218
+ 'account' => 'Expenses:Restaurants',
219
+ 'amount' => '32.2',
220
+ 'total' => '32.2',
221
+ 'commodity' => 'EUR'
222
+ }
223
+ ]
224
+ }
225
+ ]
226
+ end
227
+
228
+ it 'shows beginning, end and postings with account, amount and total' do
229
+ get '/register', query: '--by-payee ^Expenses'
230
+ JSON.parse(last_response.body).should deep_eq valid_response
231
+ end
232
+ end
233
+ end