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
@@ -0,0 +1,110 @@
|
|
1
|
+
module Subledger
|
2
|
+
module Domain
|
3
|
+
class OrgError < Error; end
|
4
|
+
|
5
|
+
class Org
|
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::Activatable
|
20
|
+
include Roles::Archivable
|
21
|
+
|
22
|
+
include Roles::Restable
|
23
|
+
|
24
|
+
attr_accessor :bucket_name
|
25
|
+
|
26
|
+
def self.root_klass
|
27
|
+
Org
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.post_keys
|
31
|
+
[ :description, :reference, :bucket_name ]
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.patch_keys
|
35
|
+
[ :id, :description, :reference, :bucket_name, :version ]
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.sub_klasses
|
39
|
+
[ active_klass, archived_klass ]
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.active_klass
|
43
|
+
ActiveOrg
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.archived_klass
|
47
|
+
ArchivedOrg
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.create args
|
51
|
+
org = super
|
52
|
+
|
53
|
+
args[:store].add_initial_controls_for org
|
54
|
+
|
55
|
+
org
|
56
|
+
end
|
57
|
+
|
58
|
+
def initialize args
|
59
|
+
describable args
|
60
|
+
identifiable args
|
61
|
+
storable args
|
62
|
+
versionable args
|
63
|
+
|
64
|
+
@bucket_name = args[:bucket_name]
|
65
|
+
end
|
66
|
+
|
67
|
+
class Entity < Grape::Entity
|
68
|
+
root 'orgs', 'org'
|
69
|
+
|
70
|
+
expose :id, :documentation => { :type => 'string', :desc => 'org ID' }
|
71
|
+
|
72
|
+
expose :description, :documentation => { :type => 'string', :desc => 'description' }
|
73
|
+
|
74
|
+
expose :reference, :documentation => { :type => 'string', :desc => 'reference URI' }
|
75
|
+
|
76
|
+
expose :bucket_name, :documentation => { :type => 'string', :desc => 'backup URI' }
|
77
|
+
|
78
|
+
expose :version, :documentation => { :type => 'integer', :desc => 'version' }
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def self.raise_unless_creatable args
|
84
|
+
store = args[:store]
|
85
|
+
|
86
|
+
store.raise_unless_bucket_name_valid args
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class ArchivedOrg < Org
|
91
|
+
class Entity < Org::Entity
|
92
|
+
root 'archived_orgs', 'archived_org'
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.sub_klasses
|
96
|
+
[ archived_klass ]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class ActiveOrg < Org
|
101
|
+
class Entity < Org::Entity
|
102
|
+
root 'active_orgs', 'active_org'
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.sub_klasses
|
106
|
+
[ active_klass ]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,247 @@
|
|
1
|
+
module Subledger
|
2
|
+
module Domain
|
3
|
+
class ReportError < Error; end
|
4
|
+
|
5
|
+
class Report
|
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
|
+
|
28
|
+
def self.post_keys
|
29
|
+
[ :description, :reference ]
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.patch_keys
|
33
|
+
[ :id, :description, :reference, :version ]
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.root_klass
|
37
|
+
Report
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.sub_klasses
|
41
|
+
[ active_klass, archived_klass ]
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.active_klass
|
45
|
+
ActiveReport
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.archived_klass
|
49
|
+
ArchivedReport
|
50
|
+
end
|
51
|
+
|
52
|
+
def initialize args
|
53
|
+
describable args
|
54
|
+
identifiable args
|
55
|
+
storable args
|
56
|
+
versionable args
|
57
|
+
|
58
|
+
@book = args[:book]
|
59
|
+
end
|
60
|
+
|
61
|
+
class Entity < Grape::Entity
|
62
|
+
root 'reports', 'report'
|
63
|
+
|
64
|
+
expose :id, :documentation => { :type => 'string', :desc => 'report ID' }
|
65
|
+
|
66
|
+
expose :book, :format_with => :id,
|
67
|
+
:documentation => { :type => 'string', :desc => 'book ID' }
|
68
|
+
|
69
|
+
expose :description, :documentation => { :type => 'string', :desc => 'description' }
|
70
|
+
|
71
|
+
expose :reference, :documentation => { :type => 'string', :desc => 'reference URI' }
|
72
|
+
|
73
|
+
expose :version, :documentation => { :type => 'integer', :desc => 'version' }
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def self.raise_unless_creatable args
|
79
|
+
|
80
|
+
book = args[:book]
|
81
|
+
|
82
|
+
if book.nil? or not book.kind_of? Book
|
83
|
+
raise BookError, ':book is required and must be a Book'
|
84
|
+
elsif UUID.invalid? book.id
|
85
|
+
raise BookError, ':book must have a valid :id'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def structure
|
90
|
+
store.report_structure_for self
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class ArchivedReport < Report
|
95
|
+
class Entity < Report::Entity
|
96
|
+
root 'archived_reports', 'archived_report'
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.sub_klasses
|
100
|
+
[ archived_klass ]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class ActiveReport < Report
|
105
|
+
class Entity < Report::Entity
|
106
|
+
root 'active_reports', 'active_report'
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.sub_klasses
|
110
|
+
[ active_klass ]
|
111
|
+
end
|
112
|
+
|
113
|
+
def categories &block
|
114
|
+
begin
|
115
|
+
store.collect_categories_for_report self, &block
|
116
|
+
rescue Store::CollectError => e
|
117
|
+
raise ReportError, e
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def attach args
|
122
|
+
validate_attach args
|
123
|
+
|
124
|
+
category = args[:category]
|
125
|
+
parent = args[:parent]
|
126
|
+
|
127
|
+
if category.nil? or category.id.nil?
|
128
|
+
raise ReportError, ':category is required and must have an id'
|
129
|
+
end
|
130
|
+
|
131
|
+
if not parent.nil? and parent.id.nil?
|
132
|
+
raise ReportError, ':parent must have an id'
|
133
|
+
end
|
134
|
+
|
135
|
+
begin
|
136
|
+
store.attach_category_to_report :report => self,
|
137
|
+
:category => category,
|
138
|
+
:parent => parent
|
139
|
+
rescue Store::AttachError => e
|
140
|
+
raise ReportError, e
|
141
|
+
end
|
142
|
+
|
143
|
+
category
|
144
|
+
end
|
145
|
+
|
146
|
+
def detach args
|
147
|
+
category = args[:category]
|
148
|
+
|
149
|
+
if category.nil? or category.id.nil?
|
150
|
+
raise ReportError, ':category is required and must have an id'
|
151
|
+
end
|
152
|
+
|
153
|
+
begin
|
154
|
+
store.detach_category_from_report :report => self,
|
155
|
+
:category => category
|
156
|
+
rescue Store::DetachError => e
|
157
|
+
raise ReportError, e
|
158
|
+
end
|
159
|
+
|
160
|
+
category
|
161
|
+
end
|
162
|
+
|
163
|
+
def render args
|
164
|
+
at = args[:at]
|
165
|
+
|
166
|
+
if at.nil? or not at.kind_of? Time
|
167
|
+
raise ReportError, ':at is required and must be a Time'
|
168
|
+
end
|
169
|
+
|
170
|
+
building_report_rendering = client.building_report_rendering :effective_at => at,
|
171
|
+
:report => self
|
172
|
+
|
173
|
+
store.render args.merge! :building_report_rendering => building_report_rendering
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
|
178
|
+
def add_categories_to report_hash, sub_structure, effective_at
|
179
|
+
categories_hash = report_hash[:categories] = { }
|
180
|
+
|
181
|
+
categories_balance = Balance.new
|
182
|
+
|
183
|
+
sub_structure.each do |category, category_hash|
|
184
|
+
categories_hash[category.id] = category.serializable_hash
|
185
|
+
|
186
|
+
category_balance = add_categories_to categories_hash[category.id], category_hash, effective_at
|
187
|
+
|
188
|
+
category_balance += add_accounts_to categories_hash[category.id], category, effective_at
|
189
|
+
|
190
|
+
categories_hash[category.id][:balance] = category_balance.serializable_hash
|
191
|
+
|
192
|
+
categories_balance += category_balance
|
193
|
+
end
|
194
|
+
|
195
|
+
categories_balance
|
196
|
+
end
|
197
|
+
|
198
|
+
def add_accounts_to report_category_hash, category, effective_at
|
199
|
+
accounts_hash = report_category_hash[:accounts] = { }
|
200
|
+
|
201
|
+
accounts_balance = Balance.new
|
202
|
+
|
203
|
+
category.accounts.each do |account|
|
204
|
+
accounts_hash[account.id] = account.serializable_hash
|
205
|
+
|
206
|
+
account_balance = add_balance_to accounts_hash[account.id], account, effective_at
|
207
|
+
|
208
|
+
accounts_balance += account_balance
|
209
|
+
end
|
210
|
+
|
211
|
+
accounts_balance
|
212
|
+
end
|
213
|
+
|
214
|
+
def add_balance_to report_category_account_hash, account, effective_at
|
215
|
+
balance = account.balance :at => effective_at
|
216
|
+
|
217
|
+
report_category_account_hash[:balance] = balance.serializable_hash
|
218
|
+
|
219
|
+
balance
|
220
|
+
end
|
221
|
+
|
222
|
+
def validate_attach args
|
223
|
+
if UUID.invalid? id
|
224
|
+
raise ReportError, ':report must have a valid :id'
|
225
|
+
end
|
226
|
+
|
227
|
+
category = args[:category]
|
228
|
+
|
229
|
+
if category.nil? or not category.kind_of? Category
|
230
|
+
raise ReportError, ':category is required and must be a Category'
|
231
|
+
elsif UUID.invalid? category.id
|
232
|
+
raise ReportError, ':category must have a valid :id'
|
233
|
+
end
|
234
|
+
|
235
|
+
parent = args[:parent]
|
236
|
+
|
237
|
+
unless parent.nil?
|
238
|
+
if not parent.kind_of? Category
|
239
|
+
raise ReportError, ':parent must be a Category'
|
240
|
+
elsif UUID.invalid? parent.id
|
241
|
+
raise ReportError, ':parent must have a valid :id'
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
@@ -0,0 +1,233 @@
|
|
1
|
+
module Subledger
|
2
|
+
module Domain
|
3
|
+
class ReportRenderingError < Error; end
|
4
|
+
|
5
|
+
class ReportRendering
|
6
|
+
|
7
|
+
include Domain
|
8
|
+
|
9
|
+
include Roles::Attributable
|
10
|
+
include Roles::DescribableReportRendering
|
11
|
+
include Roles::Identifiable
|
12
|
+
include Roles::Storable
|
13
|
+
include Roles::Timeable
|
14
|
+
|
15
|
+
include Roles::Creatable
|
16
|
+
include Roles::Readable
|
17
|
+
|
18
|
+
include Roles::Collectable
|
19
|
+
|
20
|
+
include Roles::Restable
|
21
|
+
|
22
|
+
attr_reader :book, :report, :effective_at, :rendered_at,
|
23
|
+
:balanced_accounts, :total_accounts
|
24
|
+
|
25
|
+
def self.post_keys
|
26
|
+
[ :description, :reference, :report, :effective_at ]
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.root_klass
|
30
|
+
ReportRendering
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.sub_klasses
|
34
|
+
[ building_klass, completed_klass ]
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.building_klass
|
38
|
+
BuildingReportRendering
|
39
|
+
end
|
40
|
+
|
41
|
+
# TODO create uses active_klass, should use creation_klass
|
42
|
+
def self.active_klass
|
43
|
+
building_klass
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.completed_klass
|
47
|
+
CompletedReportRendering
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize args
|
51
|
+
describable_report_rendering args
|
52
|
+
identifiable args
|
53
|
+
storable args
|
54
|
+
|
55
|
+
@book = args[:book]
|
56
|
+
@report = args[:report]
|
57
|
+
@effective_at = round args[:effective_at]
|
58
|
+
@rendered_at = utc_or_now args[:rendered_at]
|
59
|
+
|
60
|
+
@balanced_accounts = args[:balanced_accounts] || 0
|
61
|
+
@total_accounts = args[:total_accounts] || 0
|
62
|
+
|
63
|
+
specialized_initialization args
|
64
|
+
end
|
65
|
+
|
66
|
+
def progress
|
67
|
+
if total_accounts.zero?
|
68
|
+
0
|
69
|
+
else
|
70
|
+
( ( balanced_accounts.to_f / total_accounts ) * 99 ).to_i
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class Entity < Grape::Entity
|
75
|
+
root 'report_renderings', 'report_rendering'
|
76
|
+
|
77
|
+
expose :id
|
78
|
+
expose :book, :format_with => :id
|
79
|
+
expose :report, :format_with => :id
|
80
|
+
expose :description
|
81
|
+
expose :reference
|
82
|
+
expose :effective_at, :format_with => :time_full_second
|
83
|
+
expose :total_accounts, :format_with => :integer
|
84
|
+
expose :progress, :format_with => :progress
|
85
|
+
expose :rendered_at, :format_with => :time
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def self.raise_unless_creatable args
|
91
|
+
|
92
|
+
book = args[:book]
|
93
|
+
|
94
|
+
if book.nil? or not book.kind_of? Book
|
95
|
+
raise ReportRenderingError, ':book is required and must be a Book'
|
96
|
+
elsif UUID.invalid? book.id
|
97
|
+
raise ReportRenderingError, ':book must have a valid :id'
|
98
|
+
end
|
99
|
+
|
100
|
+
report = args[:report]
|
101
|
+
|
102
|
+
if report.nil? or not report.kind_of? Report
|
103
|
+
raise ReportRenderingError, ':report is required and must be a Report'
|
104
|
+
elsif UUID.invalid? report.id
|
105
|
+
raise ReportRenderingError, ':report must have a valid :id'
|
106
|
+
end
|
107
|
+
|
108
|
+
effective_at = args[:effective_at]
|
109
|
+
|
110
|
+
if effective_at.nil? or not effective_at.kind_of? Time
|
111
|
+
raise ReportRenderingError, ':effective_at is required and must be a Time'
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def round effective_at
|
116
|
+
Time.at( utc_or_nil( effective_at ).to_i ).utc
|
117
|
+
end
|
118
|
+
|
119
|
+
TOTAL_ACCOUNTS_SEMAPHORE = Mutex.new
|
120
|
+
|
121
|
+
def increase_total_accounts_by count
|
122
|
+
TOTAL_ACCOUNTS_SEMAPHORE.synchronize do
|
123
|
+
@total_accounts += count
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
BALANCED_ACCOUNTS_SEMAPHORE = Mutex.new
|
128
|
+
|
129
|
+
def increase_balanced_accounts_by count
|
130
|
+
BALANCED_ACCOUNTS_SEMAPHORE.synchronize do
|
131
|
+
@balanced_accounts += count
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def specialized_initialization args
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class BuildingReportRendering < ReportRendering
|
140
|
+
class Entity < ReportRendering::Entity
|
141
|
+
root 'building_report_renderings', 'building_report_rendering'
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.sub_klasses
|
145
|
+
[ building_klass ]
|
146
|
+
end
|
147
|
+
|
148
|
+
# TODO this should probably move to Domain module
|
149
|
+
|
150
|
+
def to_json
|
151
|
+
MultiJson.dump serializable_hash
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def complete args
|
157
|
+
store.send :become, :becomable => self,
|
158
|
+
:klass => self.class.completed_klass,
|
159
|
+
:extra_args => args
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
class CompletedReportRendering < ReportRendering
|
164
|
+
attr_reader :completed_at, :categories, :balance, :warnings
|
165
|
+
|
166
|
+
def self.sub_klasses
|
167
|
+
[ completed_klass ]
|
168
|
+
end
|
169
|
+
|
170
|
+
def progress
|
171
|
+
100
|
172
|
+
end
|
173
|
+
|
174
|
+
def to_json
|
175
|
+
@json ||= if location[0..4] == 's3://'
|
176
|
+
ExceptionHandler.new( :name => 'completed_report_rendering to_json' ).with_retry do
|
177
|
+
s3_bucket.objects[id].read
|
178
|
+
end
|
179
|
+
elsif location[0..6] == 'file://'
|
180
|
+
File.open( location[7..-1], 'r' ).read
|
181
|
+
else
|
182
|
+
raise ReportRenderingError, "unknown URL scheme: #{location}"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def categories
|
187
|
+
@categories ||= as_hash['categories']
|
188
|
+
end
|
189
|
+
|
190
|
+
def balance
|
191
|
+
balance_hash = { 'balance' => as_hash['balance'] }
|
192
|
+
|
193
|
+
@balance ||= Rest.to_balance balance_hash, client
|
194
|
+
end
|
195
|
+
|
196
|
+
def warnings
|
197
|
+
@warnings ||= as_hash['warnings']
|
198
|
+
end
|
199
|
+
|
200
|
+
class Entity < ReportRendering::Entity
|
201
|
+
root 'completed_report_renderings', 'completed_report_rendering'
|
202
|
+
|
203
|
+
expose :completed_at, :format_with => :time
|
204
|
+
expose :categories
|
205
|
+
expose :balance, :format_with => :balance
|
206
|
+
expose :warnings
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
attr_reader :location
|
212
|
+
|
213
|
+
def s3_bucket
|
214
|
+
@s3_bucket ||= Store::Aws.s3_client.buckets[RENDERED_REPORTS_BUCKET]
|
215
|
+
end
|
216
|
+
|
217
|
+
def as_hash
|
218
|
+
@hash ||= MultiJson.load to_json
|
219
|
+
end
|
220
|
+
|
221
|
+
def specialized_initialization args
|
222
|
+
@completed_at = utc_or_nil args[:completed_at]
|
223
|
+
@location = args[:location]
|
224
|
+
@json = args[:json]
|
225
|
+
|
226
|
+
@balance = nil
|
227
|
+
@categories = nil
|
228
|
+
@hash = nil
|
229
|
+
@warnings = nil
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Subledger
|
2
|
+
module Domain
|
3
|
+
module Roles
|
4
|
+
module Attributable
|
5
|
+
def attributes
|
6
|
+
instance_variables.inject( { } ) do | attrs, ivar |
|
7
|
+
attrs[ ivar.to_s[1..-1].to_sym ] = instance_variable_get ivar
|
8
|
+
attrs
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|