subledger 0.7.7
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/LICENSE.txt +12 -0
- data/README.md +136 -0
- data/Rakefile +5 -0
- data/bin/subledger +6 -0
- data/lib/subledger/actor.rb +32 -0
- data/lib/subledger/collection_name.rb +25 -0
- data/lib/subledger/domain/account.rb +168 -0
- data/lib/subledger/domain/balance.rb +102 -0
- data/lib/subledger/domain/book.rb +111 -0
- data/lib/subledger/domain/category.rb +157 -0
- data/lib/subledger/domain/control.rb +180 -0
- data/lib/subledger/domain/formatters.rb +31 -0
- data/lib/subledger/domain/identity.rb +159 -0
- data/lib/subledger/domain/journal_entry.rb +293 -0
- data/lib/subledger/domain/key.rb +113 -0
- data/lib/subledger/domain/line.rb +272 -0
- data/lib/subledger/domain/org.rb +110 -0
- data/lib/subledger/domain/report.rb +247 -0
- data/lib/subledger/domain/report_rendering.rb +233 -0
- data/lib/subledger/domain/roles/activatable.rb +11 -0
- data/lib/subledger/domain/roles/archivable.rb +11 -0
- data/lib/subledger/domain/roles/attributable.rb +14 -0
- data/lib/subledger/domain/roles/collectable.rb +175 -0
- data/lib/subledger/domain/roles/creatable.rb +58 -0
- data/lib/subledger/domain/roles/describable.rb +33 -0
- data/lib/subledger/domain/roles/describable_report_rendering.rb +50 -0
- data/lib/subledger/domain/roles/identifiable.rb +15 -0
- data/lib/subledger/domain/roles/postable.rb +54 -0
- data/lib/subledger/domain/roles/progressable.rb +11 -0
- data/lib/subledger/domain/roles/readable.rb +34 -0
- data/lib/subledger/domain/roles/restable.rb +69 -0
- data/lib/subledger/domain/roles/storable.rb +30 -0
- data/lib/subledger/domain/roles/timeable.rb +18 -0
- data/lib/subledger/domain/roles/updatable.rb +35 -0
- data/lib/subledger/domain/roles/versionable.rb +35 -0
- data/lib/subledger/domain/roles.rb +16 -0
- data/lib/subledger/domain/value/credit.rb +16 -0
- data/lib/subledger/domain/value/debit.rb +16 -0
- data/lib/subledger/domain/value/zero.rb +24 -0
- data/lib/subledger/domain/value.rb +111 -0
- data/lib/subledger/domain.rb +95 -0
- data/lib/subledger/exception_handler.rb +65 -0
- data/lib/subledger/interface/client.rb +295 -0
- data/lib/subledger/interface/dispatcher.rb +20 -0
- data/lib/subledger/interface.rb +2 -0
- data/lib/subledger/path.rb +106 -0
- data/lib/subledger/rest.rb +128 -0
- data/lib/subledger/server.rb +3 -0
- data/lib/subledger/store/api/errors.rb +95 -0
- data/lib/subledger/store/api/roles/activate.rb +21 -0
- data/lib/subledger/store/api/roles/archive.rb +21 -0
- data/lib/subledger/store/api/roles/balance.rb +39 -0
- data/lib/subledger/store/api/roles/categories.rb +51 -0
- data/lib/subledger/store/api/roles/collect.rb +58 -0
- data/lib/subledger/store/api/roles/create.rb +26 -0
- data/lib/subledger/store/api/roles/create_and_post.rb +35 -0
- data/lib/subledger/store/api/roles/create_identity.rb +39 -0
- data/lib/subledger/store/api/roles/create_line.rb +24 -0
- data/lib/subledger/store/api/roles/first_and_last_line.rb +31 -0
- data/lib/subledger/store/api/roles/post.rb +25 -0
- data/lib/subledger/store/api/roles/progress.rb +21 -0
- data/lib/subledger/store/api/roles/read.rb +19 -0
- data/lib/subledger/store/api/roles/reports.rb +77 -0
- data/lib/subledger/store/api/roles/update.rb +24 -0
- data/lib/subledger/store/api/store.rb +103 -0
- data/lib/subledger/store/api.rb +20 -0
- data/lib/subledger/store.rb +236 -0
- data/lib/subledger/supervisor.rb +21 -0
- data/lib/subledger/uuid.rb +52 -0
- data/lib/subledger/version.rb +4 -0
- data/lib/subledger.rb +234 -0
- data/spec/spec_helper.rb +77 -0
- data/spec/subledger_account_spec.rb +354 -0
- data/spec/subledger_book_spec.rb +130 -0
- data/spec/subledger_category_spec.rb +203 -0
- data/spec/subledger_control_spec.rb +43 -0
- data/spec/subledger_identity_spec.rb +47 -0
- data/spec/subledger_journal_entry_spec.rb +417 -0
- data/spec/subledger_key_spec.rb +43 -0
- data/spec/subledger_org_spec.rb +68 -0
- data/spec/subledger_report_spec.rb +295 -0
- data/spec/subledger_spec.rb +101 -0
- data/subledger.gemspec +52 -0
- metadata +205 -0
data/.gitignore
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
Copyright (c) 2014, Subledger, Inc.
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
5
|
+
|
6
|
+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
7
|
+
|
8
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
9
|
+
|
10
|
+
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
11
|
+
|
12
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
[![Build Status](https://magnum.travis-ci.com/boocx2/subledger.png?token=xpzDphMqDV2co7CrgGkL&branch=reporting)](https://magnum.travis-ci.com/boocx2/subledger)
|
2
|
+
|
3
|
+
Signup:
|
4
|
+
|
5
|
+
gem install subledger
|
6
|
+
|
7
|
+
subledger
|
8
|
+
|
9
|
+
Welcome to Subledger, the world's first in-app accounting API!
|
10
|
+
|
11
|
+
To stop this script at any time simply press control-C.
|
12
|
+
|
13
|
+
We require your email address for verification.
|
14
|
+
|
15
|
+
Please enter your email address:
|
16
|
+
> your@email.com
|
17
|
+
|
18
|
+
creating key...created!
|
19
|
+
|
20
|
+
Your key: Gid1vE0lStRnPNCx9GFmL5
|
21
|
+
|
22
|
+
You *must* keep it 100% secure, or your data may be accessed by others.
|
23
|
+
|
24
|
+
Would you like your key written into ~/.subledger for you?
|
25
|
+
This prevents the need for you to enter it or put it in source code.
|
26
|
+
> n
|
27
|
+
|
28
|
+
OK, please store your key and secret securely.
|
29
|
+
|
30
|
+
> y
|
31
|
+
|
32
|
+
Your key has been written to ~/.subledger
|
33
|
+
|
34
|
+
Your key is not yet ready to use, it must be verified via email.
|
35
|
+
|
36
|
+
Please enter the verification code we sent you via email:
|
37
|
+
> V6XxRwP5j9ldaeDjOpbkv7
|
38
|
+
|
39
|
+
Verifying key...verified!
|
40
|
+
|
41
|
+
Sorry, that verification code does not match your key. Please try again.
|
42
|
+
|
43
|
+
Next, we'll create an organization.
|
44
|
+
|
45
|
+
The organization name will be present in URLs, and must be unique.
|
46
|
+
|
47
|
+
What organization name would you like to use?
|
48
|
+
> subledger
|
49
|
+
|
50
|
+
creating organization "subledger"...created!
|
51
|
+
|
52
|
+
Your key has been granted access to your new organization.
|
53
|
+
|
54
|
+
Next we'll create a book in the subledger organization to create accounts in.
|
55
|
+
|
56
|
+
A book tracks the state of one dimension of your business.
|
57
|
+
|
58
|
+
The most common book tracks the state of currency in your business.
|
59
|
+
|
60
|
+
What would you like to name your book? We'd suggest Dollars, Euros, Yen, etc.
|
61
|
+
> USD
|
62
|
+
|
63
|
+
creating book "USD"...created!
|
64
|
+
|
65
|
+
Would you like to create a very basic set of accounting accounts?
|
66
|
+
> n
|
67
|
+
|
68
|
+
> y
|
69
|
+
|
70
|
+
creating account "1000 Assets"...created!
|
71
|
+
creating account "2000 Liabilities"...created!
|
72
|
+
creating account "3000 Equity"...created!
|
73
|
+
creating account "4000 Income"...created!
|
74
|
+
creating account "5000 Cost of goods sold"...created!
|
75
|
+
creating account "6000 Expenses"...created!
|
76
|
+
|
77
|
+
Would you like to become virtually wealthy?
|
78
|
+
> n
|
79
|
+
|
80
|
+
Ok, just remember: early to bed, early to rise, makes a man healthy, wealthy and wise! :-)
|
81
|
+
|
82
|
+
> y
|
83
|
+
|
84
|
+
posting opening balances...posted!
|
85
|
+
|
86
|
+
Congratulations, you've successfully setup Subledger!
|
87
|
+
|
88
|
+
Your private and secure API explorer now exists at:
|
89
|
+
|
90
|
+
https://api.subledger.com/
|
91
|
+
|
92
|
+
You'll need to enter your key_id and secret where indicated.
|
93
|
+
|
94
|
+
Post Signup:
|
95
|
+
|
96
|
+
Gemfile:
|
97
|
+
|
98
|
+
gem install subledger
|
99
|
+
|
100
|
+
Client setup:
|
101
|
+
|
102
|
+
subledger = Subledger.new :key_id => '',
|
103
|
+
:secret => '',
|
104
|
+
:org_id => '',
|
105
|
+
:book_id => ''
|
106
|
+
|
107
|
+
Basic accounting objects:
|
108
|
+
|
109
|
+
debit = subledger.debit '1'
|
110
|
+
credit = subledger.credit '1'
|
111
|
+
zero = subledger.zero '0'
|
112
|
+
|
113
|
+
debit + credit == zero
|
114
|
+
debit + debit + credit == debit
|
115
|
+
debit + credit + credit == credit
|
116
|
+
|
117
|
+
book = subject.books.create :description => 'description of book', # required
|
118
|
+
:reference => 'http://url.of.choice' # optional, must be a URL if present
|
119
|
+
|
120
|
+
book = subledger.books.read :id => book.id
|
121
|
+
|
122
|
+
book.update :description => '',
|
123
|
+
:reference => ''
|
124
|
+
|
125
|
+
book.archive
|
126
|
+
book.activate
|
127
|
+
|
128
|
+
subledger.books.collect :state => :active|:archived,
|
129
|
+
:action => :before|:ending|:starting|:after,
|
130
|
+
:description => 'description',
|
131
|
+
:limit => 25
|
132
|
+
|
133
|
+
subledger.books.collect :state => :active|:archived,
|
134
|
+
:action => :preceding|:following,
|
135
|
+
:id => another_book_in_this_org.id,
|
136
|
+
:limit => 25
|
data/Rakefile
ADDED
data/bin/subledger
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'celluloid/autostart'
|
2
|
+
|
3
|
+
Celluloid.logger = Subledger::LOG
|
4
|
+
|
5
|
+
module Subledger
|
6
|
+
class Actor
|
7
|
+
include Celluloid
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def worker type
|
12
|
+
worker_actor = nil
|
13
|
+
|
14
|
+
while ( worker_actor = Celluloid::Actor[ type ] ).nil?
|
15
|
+
sleep 0.1
|
16
|
+
end
|
17
|
+
|
18
|
+
worker_actor
|
19
|
+
end
|
20
|
+
|
21
|
+
CLIENT_HASH = 'client_hash'
|
22
|
+
ORG_ID = 'org_id'
|
23
|
+
BOOK_ID = 'book_id'
|
24
|
+
|
25
|
+
def client_for args
|
26
|
+
client_hash = args[CLIENT_HASH]
|
27
|
+
|
28
|
+
Subledger.new :org_id => client_hash[ORG_ID],
|
29
|
+
:book_id => client_hash[BOOK_ID]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Subledger
|
2
|
+
module CollectionName
|
3
|
+
def self.without_state args
|
4
|
+
anchor = args[:anchor]
|
5
|
+
klass = anchor.class
|
6
|
+
|
7
|
+
if klass == Domain::PostedLine and not anchor.account.nil?
|
8
|
+
:account_lines
|
9
|
+
else
|
10
|
+
klass.root_klass.collection_name
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.with_state args
|
15
|
+
klass = args[:klass]
|
16
|
+
state = args[:state]
|
17
|
+
|
18
|
+
if klass == Domain::PostedLine and not args[:account].nil?
|
19
|
+
:account_lines
|
20
|
+
else
|
21
|
+
"#{state}_#{klass.root_klass.collection_name}".to_sym
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
module Subledger
|
2
|
+
module Domain
|
3
|
+
class AccountError < Error; end
|
4
|
+
|
5
|
+
class Account
|
6
|
+
|
7
|
+
include Domain
|
8
|
+
|
9
|
+
include Roles::Attributable
|
10
|
+
include Roles::Describable
|
11
|
+
include Roles::Identifiable
|
12
|
+
include Roles::Storable
|
13
|
+
include Roles::Versionable
|
14
|
+
|
15
|
+
include Roles::Creatable
|
16
|
+
include Roles::Readable
|
17
|
+
include Roles::Updatable
|
18
|
+
|
19
|
+
include Roles::Collectable
|
20
|
+
|
21
|
+
include Roles::Activatable
|
22
|
+
include Roles::Archivable
|
23
|
+
|
24
|
+
include Roles::Restable
|
25
|
+
|
26
|
+
attr_reader :book
|
27
|
+
attr_accessor :normal_balance
|
28
|
+
|
29
|
+
def self.post_keys
|
30
|
+
[ :description, :reference, :normal_balance ]
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.patch_keys
|
34
|
+
[ :id, :description, :reference, :normal_balance, :version ]
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.root_klass
|
38
|
+
Account
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.sub_klasses
|
42
|
+
[ active_klass, archived_klass ]
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.active_klass
|
46
|
+
ActiveAccount
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.archived_klass
|
50
|
+
ArchivedAccount
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.first_and_last_line args
|
54
|
+
store = args[:store]
|
55
|
+
client = args[:client]
|
56
|
+
|
57
|
+
id = args[:id]
|
58
|
+
|
59
|
+
if id.nil?
|
60
|
+
raise AccountError, 'cannot get first and last lines without an :id'
|
61
|
+
end
|
62
|
+
|
63
|
+
args.merge! :account => client.accounts( :id => id )
|
64
|
+
|
65
|
+
store.first_and_last_line args
|
66
|
+
end
|
67
|
+
|
68
|
+
def initialize args
|
69
|
+
describable args
|
70
|
+
identifiable args
|
71
|
+
storable args
|
72
|
+
versionable args
|
73
|
+
|
74
|
+
@book = args[:book]
|
75
|
+
@normal_balance = args[:normal_balance]
|
76
|
+
end
|
77
|
+
|
78
|
+
def line args
|
79
|
+
client.posted_lines( args.merge! :account => self )
|
80
|
+
end
|
81
|
+
|
82
|
+
def lines args={ }, &block
|
83
|
+
args.merge! :action => args[:action] || :ending,
|
84
|
+
:state => args[:state] || :posted,
|
85
|
+
:effective_at => args[:effective_at] || Time.now.utc,
|
86
|
+
:account => self
|
87
|
+
|
88
|
+
client.account_lines.collect args, &block
|
89
|
+
end
|
90
|
+
|
91
|
+
def balance args
|
92
|
+
at = args[:at]
|
93
|
+
|
94
|
+
unless at.kind_of? Time
|
95
|
+
raise AccountError, ':at must be a Time'
|
96
|
+
end
|
97
|
+
|
98
|
+
store.account_balance :store => store,
|
99
|
+
:client => client,
|
100
|
+
:account => self,
|
101
|
+
:at => at.utc
|
102
|
+
end
|
103
|
+
|
104
|
+
def first_and_last_line
|
105
|
+
client.accounts.first_and_last_line :id => self.id
|
106
|
+
end
|
107
|
+
|
108
|
+
class Entity < Grape::Entity
|
109
|
+
root 'accounts', 'account'
|
110
|
+
|
111
|
+
expose :id, :documentation => { :type => 'string', :desc => 'account ID' }
|
112
|
+
|
113
|
+
expose :book, :format_with => :id,
|
114
|
+
:documentation => { :type => 'string', :desc => 'book ID' }
|
115
|
+
|
116
|
+
expose :description, :documentation => { :type => 'string', :desc => 'description' }
|
117
|
+
|
118
|
+
expose :reference, :documentation => { :type => 'string', :desc => 'reference URI' }
|
119
|
+
|
120
|
+
expose :normal_balance, :format_with => :normal_balance,
|
121
|
+
:documentation => { :type => 'string', :desc => 'normal balance type' }
|
122
|
+
|
123
|
+
expose :version, :documentation => { :type => 'integer', :desc => 'version' }
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
ACCEPTABLE_NORMAL_BALANCES = [ Debit, Credit ]
|
129
|
+
|
130
|
+
def self.raise_unless_creatable args
|
131
|
+
|
132
|
+
book = args[:book]
|
133
|
+
|
134
|
+
if book.nil? or not book.kind_of? Book
|
135
|
+
raise AccountError, ':book is required and must be a Book'
|
136
|
+
elsif UUID.invalid? book.id
|
137
|
+
raise AccountError, ':book must have a valid :id'
|
138
|
+
end
|
139
|
+
|
140
|
+
normal_balance = args[:normal_balance]
|
141
|
+
|
142
|
+
if normal_balance.nil? or not ACCEPTABLE_NORMAL_BALANCES.include? normal_balance
|
143
|
+
raise AccountError, ":normal_balance is required and must be one of #{ACCEPTABLE_NORMAL_BALANCES.join(', ')}"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class ArchivedAccount < Account
|
149
|
+
class Entity < Account::Entity
|
150
|
+
root 'archived_accounts', 'archived_account'
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.sub_klasses
|
154
|
+
[ archived_klass ]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
class ActiveAccount < Account
|
159
|
+
class Entity < Account::Entity
|
160
|
+
root 'active_accounts', 'active_account'
|
161
|
+
end
|
162
|
+
|
163
|
+
def self.sub_klasses
|
164
|
+
[ active_klass ]
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Subledger
|
2
|
+
module Domain
|
3
|
+
class BalanceError < Error; end
|
4
|
+
|
5
|
+
class Balance
|
6
|
+
|
7
|
+
# Implements Valuable directly: #value, #amount, #+ and #==
|
8
|
+
# Implements Attributable directly: #attributes
|
9
|
+
|
10
|
+
include Domain
|
11
|
+
|
12
|
+
include Roles::Restable
|
13
|
+
|
14
|
+
def initialize args={ :debit_value => Zero.new, :credit_value => Zero.new }
|
15
|
+
if args[:debit_value].nil? or args[:credit_value].nil?
|
16
|
+
raise BalanceError, ':debit_value and :credit_value required'
|
17
|
+
end
|
18
|
+
|
19
|
+
@debit_amount = args[:debit_value].amount
|
20
|
+
@credit_amount = args[:credit_value].amount
|
21
|
+
end
|
22
|
+
|
23
|
+
def debit_value
|
24
|
+
debit_amount.zero? ? Zero.new : Debit.new( debit_amount )
|
25
|
+
end
|
26
|
+
|
27
|
+
def credit_value
|
28
|
+
credit_amount.zero? ? Zero.new : Credit.new( credit_amount )
|
29
|
+
end
|
30
|
+
|
31
|
+
def balanced?
|
32
|
+
value == Zero.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def value
|
36
|
+
if debit_amount == credit_amount
|
37
|
+
Zero.new
|
38
|
+
elsif debit_amount > credit_amount
|
39
|
+
Debit.new( debit_amount - credit_amount )
|
40
|
+
else
|
41
|
+
Credit.new( credit_amount - debit_amount )
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def amount
|
46
|
+
value.amount
|
47
|
+
end
|
48
|
+
|
49
|
+
def + other
|
50
|
+
klass = other.class
|
51
|
+
|
52
|
+
debit_amount = @debit_amount
|
53
|
+
credit_amount = @credit_amount
|
54
|
+
|
55
|
+
if klass == Balance
|
56
|
+
debit_amount += other.debit_value.amount
|
57
|
+
credit_amount += other.credit_value.amount
|
58
|
+
elsif klass == Debit
|
59
|
+
debit_amount += other.amount
|
60
|
+
elsif klass == Credit
|
61
|
+
credit_amount += other.amount
|
62
|
+
elsif klass == Zero
|
63
|
+
return self.dup
|
64
|
+
elsif other.kind_of? Line
|
65
|
+
return self + other.value
|
66
|
+
else
|
67
|
+
raise BalanceError, "cannot add #{other.class.name} to a Balance"
|
68
|
+
end
|
69
|
+
|
70
|
+
self.class.new :debit_value => debit_amount.zero? ? Zero.new : Debit.new(debit_amount),
|
71
|
+
:credit_value => credit_amount.zero? ? Zero.new : Credit.new(credit_amount)
|
72
|
+
end
|
73
|
+
|
74
|
+
def == other
|
75
|
+
debit_value == other.debit_value and credit_value == other.credit_value
|
76
|
+
end
|
77
|
+
|
78
|
+
def attributes
|
79
|
+
{ :debit_value => debit_value,
|
80
|
+
:credit_value => credit_value,
|
81
|
+
:value => value }
|
82
|
+
end
|
83
|
+
|
84
|
+
class Entity < Grape::Entity
|
85
|
+
root 'balances', 'balance'
|
86
|
+
|
87
|
+
expose :debit_value, :format_with => :value,
|
88
|
+
:documentation => { :type => 'string', :desc => 'Total debits' }
|
89
|
+
|
90
|
+
expose :credit_value, :format_with => :value,
|
91
|
+
:documentation => { :type => 'string', :desc => 'Total credits' }
|
92
|
+
|
93
|
+
expose :value, :format_with => :value,
|
94
|
+
:documentation => { :type => 'string', :desc => 'Total combined debits and credits' }
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
attr_reader :debit_amount, :credit_amount
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Subledger
|
2
|
+
module Domain
|
3
|
+
class BookError < Error; end
|
4
|
+
|
5
|
+
class Book
|
6
|
+
|
7
|
+
include Domain
|
8
|
+
|
9
|
+
include Roles::Attributable
|
10
|
+
include Roles::Describable
|
11
|
+
include Roles::Identifiable
|
12
|
+
include Roles::Storable
|
13
|
+
include Roles::Versionable
|
14
|
+
|
15
|
+
include Roles::Creatable
|
16
|
+
include Roles::Readable
|
17
|
+
include Roles::Updatable
|
18
|
+
|
19
|
+
include Roles::Collectable
|
20
|
+
|
21
|
+
include Roles::Activatable
|
22
|
+
include Roles::Archivable
|
23
|
+
|
24
|
+
include Roles::Restable
|
25
|
+
|
26
|
+
attr_reader :org
|
27
|
+
|
28
|
+
def self.post_keys
|
29
|
+
[ :org, :description, :reference ]
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.patch_keys
|
33
|
+
# TODO should :id be in patch_keys?
|
34
|
+
|
35
|
+
[ :id, :description, :reference, :version ]
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.root_klass
|
39
|
+
Book
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.sub_klasses
|
43
|
+
[ active_klass, archived_klass ]
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.active_klass
|
47
|
+
ActiveBook
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.archived_klass
|
51
|
+
ArchivedBook
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize args
|
55
|
+
describable args
|
56
|
+
identifiable args
|
57
|
+
storable args
|
58
|
+
versionable args
|
59
|
+
|
60
|
+
@org = args[:org]
|
61
|
+
end
|
62
|
+
|
63
|
+
class Entity < Grape::Entity
|
64
|
+
root 'books', 'book'
|
65
|
+
|
66
|
+
expose :id, :documentation => { :type => 'string', :desc => 'book ID' }
|
67
|
+
|
68
|
+
expose :org, :format_with => :id,
|
69
|
+
:documentation => { :type => 'string', :desc => 'org ID' }
|
70
|
+
|
71
|
+
expose :description, :documentation => { :type => 'string', :desc => 'description' }
|
72
|
+
|
73
|
+
expose :reference, :documentation => { :type => 'string', :desc => 'reference URI' }
|
74
|
+
|
75
|
+
expose :version, :documentation => { :type => 'integer', :desc => 'version' }
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def self.raise_unless_creatable args
|
81
|
+
org = args[:org]
|
82
|
+
|
83
|
+
if org.nil? or not org.kind_of? Org
|
84
|
+
raise BookError, ':org is required and must be an Org'
|
85
|
+
elsif UUID.invalid? org.id
|
86
|
+
raise BookError, ':org must have a valid :id'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class ArchivedBook < Book
|
92
|
+
class Entity < Book::Entity
|
93
|
+
root 'archived_books', 'archived_book'
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.sub_klasses
|
97
|
+
[ archived_klass ]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class ActiveBook < Book
|
102
|
+
class Entity < Book::Entity
|
103
|
+
root 'active_books', 'active_book'
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.sub_klasses
|
107
|
+
[ active_klass ]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|