osm 1.0.6 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,425 @@
1
+ module Osm
2
+
3
+ class Invoice < Osm::Model
4
+ # @!attribute [rw] id
5
+ # @return [Fixnum] The OSM ID for the invoice
6
+ # @!attribute [rw] section_id
7
+ # @return [Fixnum] The OSM ID for the section the invoice belongs to
8
+ # @!attribute [rw] name
9
+ # @return [String] The name given to the invoice
10
+ # @!attribute [rw] extra_details
11
+ # @return [String] Any extra details added to the invoice
12
+ # @!attribute [rw] date
13
+ # @return [Date] When the invoice was created
14
+ # @!attribute [rw] archived
15
+ # @return [Boolean] Whether the invoice has been archived
16
+ # @!attribute [rw] finalised
17
+ # @return [Boolean] Whether the invoice has been finalised
18
+
19
+ attribute :id, :type => Integer
20
+ attribute :section_id, :type => Integer
21
+ attribute :name, :type => String
22
+ attribute :extra_details, :type => String, :default => ''
23
+ attribute :date, :type => Date, :default => Date.today
24
+ attribute :archived, :type => Boolean, :default => false
25
+ attribute :finalised, :type => Boolean, :default => false
26
+
27
+ attr_accessible :id, :section_id, :name, :extra_details, :date, :archived, :finalised
28
+
29
+ validates_numericality_of :id, :only_integer=>true, :greater_than=>0, :unless => Proc.new { |r| r.id.nil? }
30
+ validates_numericality_of :section_id, :only_integer=>true, :greater_than=>0
31
+ validates_presence_of :name
32
+ validates_presence_of :date
33
+ validates_inclusion_of :archived, :in => [true, false]
34
+ validates_inclusion_of :finalised, :in => [true, false]
35
+
36
+
37
+ # @!method initialize
38
+ # Initialize a new Budget
39
+ # @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
40
+
41
+
42
+ # Get invoices for a section
43
+ # @param [Osm::Api] api The api to use to make the request
44
+ # @param [Osm::Section, Fixnum, #to_i] section The section (or its ID) to get the invoices for
45
+ # @!macro options_get
46
+ # @option options [Boolean] :include_archived (optional) if true then archived invoices will also be returned
47
+ # @return [Array<Osm::Invoice>]
48
+ def self.get_for_section(api, section, options={})
49
+ require_ability_to(api, :read, :finance, section, options)
50
+ section_id = section.to_i
51
+ cache_key = ['invoice_ids', section_id]
52
+ invoices = nil
53
+
54
+ if !options[:no_cache] && cache_exist?(api, cache_key)
55
+ ids = cache_read(api, cache_key)
56
+ invoices = get_from_ids(api, ids, 'invoice', section, options, :get_for_section)
57
+ end
58
+
59
+ if invoices.nil?
60
+ data = api.perform_query("finances.php?action=getInvoices&sectionid=#{section_id}&showArchived=true")
61
+ invoices = Array.new
62
+ ids = Array.new
63
+ unless data['items'].nil?
64
+ data['items'].map { |i| i['invoiceid'].to_i }.each do |invoice_id|
65
+ invoice_data = api.perform_query("finances.php?action=getInvoice&sectionid=#{section_id}&invoiceid=#{invoice_id}")
66
+ invoice = self.new_invoice_from_data(invoice_data)
67
+ invoices.push invoice
68
+ ids.push invoice.id
69
+ cache_write(api, ['invoice', invoice.id], invoice)
70
+ end
71
+ end
72
+ cache_write(api, cache_key, ids)
73
+ end
74
+
75
+ return invoices if options[:include_archived]
76
+ return invoices.reject do |invoice|
77
+ invoice.archived?
78
+ end
79
+ end
80
+
81
+ # Get an invoice
82
+ # @param [Osm::Api] api The api to use to make the request
83
+ # @param [Osm::Section, Fixnum, #to_i] section The section (or its ID) to get the events for
84
+ # @param [Fixnum, #to_i] invoice_id The id of the invoice to get
85
+ # @!macro options_get
86
+ # @return [Osm::Invoice, nil] the invoice (or nil if it couldn't be found
87
+ def self.get(api, section, invoice_id, options={})
88
+ require_ability_to(api, :read, :events, section, options)
89
+ section_id = section.to_i
90
+ invoice_id = invoice_id.to_i
91
+ cache_key = ['invoice', invoice_id]
92
+
93
+ if !options[:no_cache] && cache_exist?(api, cache_key)
94
+ return cache_read(api, cache_key)
95
+ end
96
+
97
+ invoice_data = api.perform_query("finances.php?action=getInvoice&sectionid=#{section_id}&invoiceid=#{invoice_id}")
98
+ return self.new_invoice_from_data(invoice_data)
99
+ end
100
+
101
+
102
+ # Create the invoice in OSM
103
+ # @return [Boolean] Whether the invoice was created in OSM
104
+ # @raise [Osm::ObjectIsInvalid] If the Invoice is invalid
105
+ # @raise [Osm::Error] If the invoice already exists in OSM
106
+ def create(api)
107
+ raise Osm::Error, 'the invoice already exists in OSM' unless id.nil?
108
+ raise Osm::ObjectIsInvalid, 'invoice is invalid' unless valid?
109
+ Osm::Model.require_ability_to(api, :write, :finance, section_id)
110
+
111
+ data = api.perform_query("finances.php?action=addInvoice&sectionid=#{section_id}", {
112
+ 'name' => name,
113
+ 'extra' => extra_details,
114
+ 'date' => date.strftime(Osm::OSM_DATE_FORMAT),
115
+ })
116
+ if data.is_a?(Hash) && !data['id'].nil?
117
+ # The cached invoices for the section will be out of date - remove them
118
+ cache_delete(api, ['invoice_ids', section_id])
119
+ self.id = data['id'].to_i
120
+ return true
121
+ end
122
+ return false
123
+ end
124
+
125
+ # Update the invoice in OSM
126
+ # @param [Osm::Api] api The api to use to make the request
127
+ # @return [Boolan] whether the invoice was successfully updated or not
128
+ # @raise [Osm::ObjectIsInvalid] If the Invoice is invalid
129
+ def update(api)
130
+ raise Osm::ObjectIsInvalid, 'invoice is invalid' unless valid?
131
+ require_ability_to(api, :write, :finance, section_id)
132
+
133
+ data = api.perform_query("finances.php?action=addInvoice&sectionid=#{section_id}", {
134
+ 'invoiceid' => id,
135
+ 'name' => name,
136
+ 'extra' => extra_details,
137
+ 'date' => date.strftime(Osm::OSM_DATE_FORMAT),
138
+ })
139
+
140
+ if data.is_a?(Hash) && data['ok'].eql?(true)
141
+ reset_changed_attributes
142
+ # The cached invoice will be out of date - remove it
143
+ cache_delete(api, ['invoice', self.id])
144
+ return true
145
+ end
146
+ return false
147
+ end
148
+
149
+ # Delete the invoice from OSM
150
+ # @return [Boolean] Whether the invoice was deleted from OSM
151
+ def delete(api)
152
+ Osm::Model.require_ability_to(api, :write, :finance, section_id)
153
+ return false if finalised?
154
+
155
+ data = api.perform_query("finances.php?action=deleteInvoice&sectionid=#{section_id}", {
156
+ 'invoiceid' => id,
157
+ })
158
+ if (data.is_a?(Hash) && data['ok'].eql?(true))
159
+ # The cached invoices for the section will be out of date - remove them
160
+ cache_delete(api, ['invoice_ids', section_id])
161
+ cache_delete(api, ['invoice', self.id])
162
+ return true
163
+ end
164
+ return false
165
+ end
166
+
167
+ # Archive the invoice in OSM, updating the archived attribute if successful.
168
+ # If the archived attribute is true then nothing happens and false is returned.
169
+ # @return [Boolean] Whether the invoice was archived in OSM
170
+ # @raise [Osm::Error] If the invoice does not already exist in OSM
171
+ def archive(api)
172
+ Osm::Model.require_ability_to(api, :write, :finance, section_id)
173
+ raise Osm::Error, 'the invoice does not already exist in OSM' if id.nil?
174
+ return false if archived?
175
+
176
+ data = api.perform_query("finances.php?action=deleteInvoice&sectionid=#{section_id}", {
177
+ 'invoiceid' => id,
178
+ 'archived' => 1,
179
+ })
180
+ if (data.is_a?(Hash) && data['ok'].eql?(true))
181
+ self.archived = true
182
+ # The cached invoice for the section will be out of date - remove it
183
+ cache_delete(api, ['invoice', self.id])
184
+ return true
185
+ end
186
+ return false
187
+ end
188
+
189
+ # Finalise the invoice in OSM, updating the finalised attribute if successful.
190
+ # If the finalised attribute is true then nothing happens and false is returned.
191
+ # @return [Boolean] Whether the invoice was finalised in OSM
192
+ # @raise [Osm::Error] If the invoice does not already exist in OSM
193
+ def finalise(api)
194
+ Osm::Model.require_ability_to(api, :write, :finance, section_id)
195
+ raise Osm::Error, 'the invoice does not already exist in OSM' if id.nil?
196
+ return false if finalised?
197
+
198
+ data = api.perform_query("finances.php?action=finaliseInvoice&sectionid=#{section_id}&invoiceid=#{id}")
199
+ if (data.is_a?(Hash) && data['ok'].eql?(true))
200
+ self.finalised = true
201
+ # The cached invoice for the section will be out of date - remove it
202
+ cache_delete(api, ['invoice', self.id])
203
+ return true
204
+ end
205
+ return false
206
+ end
207
+
208
+ # Get items for the invoice
209
+ # @param [Osm::Api] api The api to use to make the request
210
+ # @!macro options_get
211
+ # @return [Array<Osm::Invoice::Item>]
212
+ def get_items(api, options={})
213
+ require_ability_to(api, :read, :finance, section_id, options)
214
+ cache_key = ['invoice_items', id]
215
+
216
+ if !options[:no_cache] && cache_exist?(api, cache_key)
217
+ return cache_read(api, cache_key)
218
+ end
219
+
220
+ items = Array.new
221
+ data = api.perform_query("finances.php?action=getInvoiceRecords&invoiceid=#{id}&sectionid=#{section_id}&dateFormat=generic")
222
+ data['items'].each do |item|
223
+ items.push Osm::Invoice::Item.new(
224
+ :id => Osm::to_i_or_nil(item['id']),
225
+ :invoice => self,
226
+ :record_id => Osm::to_i_or_nil(item['recordid']),
227
+ :date => Osm::parse_date(item['entrydate']),
228
+ :amount => item['amount'],
229
+ :type => item['type'].to_s.downcase.to_sym,
230
+ :payto => item['payto_userid'].to_s.strip,
231
+ :description => item['comments'],
232
+ :budget_name => item['categoryid'],
233
+ )
234
+ end
235
+
236
+ cache_write(api, cache_key, items)
237
+ return items
238
+ end
239
+
240
+ # Compare Invoice based on section_id, name then date
241
+ def <=>(another)
242
+ result = self.section_id <=> another.try(:section_id)
243
+ result = self.name <=> another.try(:name) if result == 0
244
+ result = self.date <=> another.try(:date) if result == 0
245
+ return result
246
+ end
247
+
248
+
249
+ private
250
+ def self.new_invoice_from_data(invoice_data)
251
+ invoice_data = invoice_data['invoice']
252
+ return nil unless invoice_data.is_a?(Hash)
253
+ Osm::Invoice.new(
254
+ :id => Osm::to_i_or_nil(invoice_data['invoiceid']),
255
+ :section_id => Osm::to_i_or_nil(invoice_data['sectionid']),
256
+ :name => invoice_data['name'],
257
+ :extra_details => invoice_data['extra'],
258
+ :date => Osm::parse_date(invoice_data['entrydate']),
259
+ :archived => invoice_data['archived'].eql?('1'),
260
+ :finalised => invoice_data['finalised'].eql?('1'),
261
+ )
262
+ end
263
+
264
+
265
+
266
+ class Item < Osm::Model
267
+ # @!attribute [rw] id
268
+ # @return [Fixnum] The OSM ID for the invoice item
269
+ # @!attribute [rw] invoice
270
+ # @return [Osm::Invoice] The Osm::Invoice the item belongs to
271
+ # @!attribute [rw] record_id
272
+ # @return [Fixnum] The id of the item within the invoice
273
+ # @!attribute [rw] date
274
+ # @return [Fixnum] The date the item was paid/received
275
+ # @!attribute [rw] amount
276
+ # @return [Fixnum] The amount of the transaction
277
+ # @!attribute [rw] type
278
+ # @return [Fixnum] The type of transaction (:expense or :income)
279
+ # @!attribute [rw] payto
280
+ # @return [Fixnum] Who paid/reimbursed
281
+ # @!attribute [rw] description
282
+ # @return [Fixnum] A description for the transaction
283
+ # @!attribute [rw] budget_name
284
+ # @return [Fixnum] The name of the budget this item is assigned to
285
+
286
+ attribute :id, :type => Integer
287
+ attribute :invoice, :type => Object
288
+ attribute :record_id, :type => Integer
289
+ attribute :date, :type => Date, :default => lambda { Date.today }
290
+ attribute :amount, :type => String, :default => '0.00'
291
+ attribute :type, :type => Object
292
+ attribute :payto, :type => String
293
+ attribute :description, :type => String
294
+ attribute :budget_name, :type => String, :default => 'Default'
295
+
296
+ attr_accessible :id, :invoice, :record_id, :date, :amount, :type, :payto, :description, :budget_name
297
+
298
+ validates_numericality_of :id, :only_integer=>true, :greater_than=>0, :unless => Proc.new { |r| r.id.nil? }
299
+ validates_numericality_of :record_id, :only_integer=>true, :greater_than=>0, :unless => Proc.new { |r| r.record_id.nil? }
300
+ validates_presence_of :invoice
301
+ validates_presence_of :date
302
+ validates_presence_of :payto
303
+ validates_presence_of :description
304
+ validates_presence_of :budget_name
305
+ validates_inclusion_of :type, :in => [:expense, :income]
306
+ validates_format_of :amount, :with => /\A\d+\.\d{2}\Z/
307
+
308
+
309
+ # @!method initialize
310
+ # Initialize a new Budget
311
+ # @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
312
+
313
+
314
+ # Create the item in OSM
315
+ # @return [Boolean] Whether the item was created in OSM
316
+ # @raise [Osm::ObjectIsInvalid] If the Item is invalid
317
+ # @raise [Osm::Error] If the invoice item already exists in OSM
318
+ def create(api)
319
+ raise Osm::Error, 'the invoice item already exists in OSM' unless id.nil?
320
+ raise Osm::ObjectIsInvalid, 'invoice item is invalid' unless valid?
321
+ Osm::Model.require_ability_to(api, :write, :finance, invoice.section_id)
322
+
323
+ last_item = invoice.get_items(api, {:no_cache=>true}).sort{ |a,b| a.record_id <=> b.record_id }[-1]
324
+
325
+ data = api.perform_query("finances.php?action=addRecord&invoiceid=#{invoice.id}&sectionid=#{invoice.section_id}")
326
+ if data.is_a?(Hash) && data['ok'].eql?(true)
327
+ new_item = invoice.get_items(api, {:no_cache => true}).sort{ |a,b| a.record_id <=> b.record_id }[-1]
328
+ if !new_item.nil? && (last_item.try(:id) != new_item.try(:id))
329
+ # The cached invoice items for the section will be out of date - remove them
330
+ cache_delete(api, ['invoice_items', invoice.id])
331
+ self.id = new_item.id
332
+ self.record_id = new_item.record_id
333
+ # Update attributes in OSM
334
+ [['amount', amount], ['comments', description], ['type', type.to_s.titleize], ['payto_userid', payto], ['categoryid', budget_name], ['entrydate', date.strftime(Osm::OSM_DATE_FORMAT)]].each do |osm_name, value|
335
+ api.perform_query("finances.php?action=updateRecord&sectionid=#{invoice.section_id}&dateFormat=generic", {
336
+ 'section_id' => invoice.section_id,
337
+ 'invoiceid' => invoice.id,
338
+ 'recordid' => record_id,
339
+ 'row' => 0,
340
+ 'column' => osm_name,
341
+ 'value' => value,
342
+ })
343
+ end
344
+ return true
345
+ end
346
+ end
347
+ return false
348
+ end
349
+
350
+ # Update invoice item in OSM
351
+ # @param [Osm::Api] api The api to use to make the request
352
+ # @return [Boolean] whether the update succedded
353
+ # @raise [Osm::ObjectIsInvalid] If the Invoice is invalid
354
+ def update(api)
355
+ require_ability_to(api, :write, :finance, invoice.section_id)
356
+ raise Osm::ObjectIsInvalid, 'invoice item is invalid' unless valid?
357
+
358
+ updated = true
359
+ to_update = Array.new
360
+ to_update.push ['amount', amount] if changed_attributes.include?('amount')
361
+ to_update.push ['comments', description] if changed_attributes.include?('description')
362
+ to_update.push ['type', type.to_s.titleize] if changed_attributes.include?('type')
363
+ to_update.push ['payto_userid', payto] if changed_attributes.include?('payto')
364
+ to_update.push ['categoryid', budget_name] if changed_attributes.include?('budget_name')
365
+ to_update.push ['entrydate', date.strftime(Osm::OSM_DATE_FORMAT)] if changed_attributes.include?('date')
366
+ to_update.each do |osm_name, value|
367
+ data = api.perform_query("finances.php?action=updateRecord&sectionid=#{invoice.section_id}&dateFormat=generic", {
368
+ 'section_id' => invoice.section_id,
369
+ 'invoiceid' => invoice.id,
370
+ 'recordid' => record_id,
371
+ 'row' => 0,
372
+ 'column' => osm_name,
373
+ 'value' => value,
374
+ })
375
+ updated &&= (data.is_a?(Hash) && data[osm_name].to_s.eql?(value.to_s))
376
+ end
377
+
378
+ if updated
379
+ reset_changed_attributes
380
+ # The cached items for the invoice will be out of date - remove them
381
+ cache_delete(api, ['invoice_items', invoice.id])
382
+ return true
383
+ else
384
+ return false
385
+ end
386
+ end
387
+
388
+ # Delete invoice item from OSM
389
+ # @param [Osm::Api] api The api to use to make the request
390
+ # @return [Boolean] whether the delete succedded
391
+ def delete(api)
392
+ require_ability_to(api, :write, :finance, invoice.section_id)
393
+
394
+ data = api.perform_query("finances.php?action=deleteEntry&sectionid=#{invoice.section_id}", {
395
+ 'id' => id,
396
+ })
397
+
398
+ if data.is_a?(Hash) && data['ok']
399
+ # The cached invoice items for the section will be out of date - remove them
400
+ cache_delete(api, ['invoice_items', invoice.id])
401
+ return true
402
+ end
403
+ return false
404
+ end
405
+
406
+ # Get value of this item for easy totaling
407
+ # @return [Float]
408
+ def value
409
+ return amount.to_f if type.eql?(:income)
410
+ return -amount.to_f if type.eql?(:expense)
411
+ return 0.0
412
+ end
413
+
414
+ # Compare Invoice Item based on invoice then date
415
+ def <=>(another)
416
+ result = self.invoice <=> another.try(:invoice)
417
+ result = self.date <=> another.try(:date) if result == 0
418
+ return result
419
+ end
420
+
421
+ end # class Invoice::Item
422
+
423
+ end # class Invoice
424
+
425
+ end # Module
data/lib/osm/member.rb CHANGED
@@ -367,18 +367,15 @@ module Osm
367
367
  return "#{first_name}#{seperator.to_s}#{last_name}"
368
368
  end
369
369
 
370
- # Get the My.SCOUT link for this member
370
+ # Get the Key to use in My.SCOUT links for this member
371
371
  # @param [Osm::Api] api The api to use to make the request
372
- # @param [Symbol] link_to The page in My.SCOUT to link to (:payments, :events, :programme, :badges or :notice)
373
- # @return [String] the link for this member's My.SCOUT
372
+ # @return [String] the key
374
373
  # @raise [Osm::ObjectIsInvalid] If the Member is invalid
375
- # @raise [Osm::ArgumentIsInvalid] If link_to is not an allowed Symbol
376
374
  # @raise [Osm::Error] if the member does not already exist in OSM or the member's My.SCOUT key could not be retrieved from OSM
377
- def myscout_link(api, link_to=:badges)
375
+ def myscout_link_key(api)
378
376
  raise Osm::ObjectIsInvalid, 'member is invalid' unless valid?
379
377
  require_ability_to(api, :read, :member, section_id)
380
378
  raise Osm::Error, 'the member does not already exist in OSM' if id.nil?
381
- raise Osm::ArgumentIsInvalid, 'link_to is invalid' unless [:payments, :events, :programme, :badges, :notice].include?(link_to)
382
379
 
383
380
  if @myscout_link_key.nil?
384
381
  data = api.perform_query("api.php?action=getMyScoutKey&sectionid=#{section_id}&scoutid=#{self.id}")
@@ -386,7 +383,26 @@ module Osm
386
383
  @myscout_link_key = data['key']
387
384
  end
388
385
 
389
- return "https://www.onlinescoutmanager.co.uk/parents/#{link_to}.php?sc=#{self.id}&se=#{section_id}&c=#{@myscout_link_key}"
386
+ return @myscout_link_key
387
+ end
388
+
389
+ # Get the My.SCOUT link for this member
390
+ # @param [Osm::Api] api The api to use to make the request
391
+ # @param [Symbol] link_to The page in My.SCOUT to link to (:payments, :events, :programme, :badges, :notice or :details)
392
+ # @param [#to_i] item_id Allows you to link to a specfic item (only for :events)
393
+ # @return [String] the link for this member's My.SCOUT
394
+ # @raise [Osm::ObjectIsInvalid] If the Member is invalid
395
+ # @raise [Osm::ArgumentIsInvalid] If link_to is not an allowed Symbol
396
+ # @raise [Osm::Error] if the member does not already exist in OSM or the member's My.SCOUT key could not be retrieved from OSM
397
+ def myscout_link(api, link_to=:badges, item_id=nil)
398
+ raise Osm::ObjectIsInvalid, 'member is invalid' unless valid?
399
+ require_ability_to(api, :read, :member, section_id)
400
+ raise Osm::Error, 'the member does not already exist in OSM' if id.nil?
401
+ raise Osm::ArgumentIsInvalid, 'link_to is invalid' unless [:payments, :events, :programme, :badges, :notice, :details].include?(link_to)
402
+
403
+ link = "https://www.onlinescoutmanager.co.uk/parents/#{link_to}.php?sc=#{self.id}&se=#{section_id}&c=#{myscout_link_key(api)}"
404
+ link += "&e=#{item_id.to_i}" if item_id && link_to.eql?(:events)
405
+ return link
390
406
  end
391
407
 
392
408
  # Compare Activity based on section_id, grouping_id, grouping_leader (descending), last_name then first_name
@@ -395,7 +411,7 @@ module Osm
395
411
  result = self.grouping_id <=> another.try(:grouping_id) if result == 0
396
412
  result = -(self.grouping_leader <=> another.try(:grouping_leader)) if result == 0
397
413
  result = self.last_name <=> another.try(:last_name) if result == 0
398
- result = self.first_name <=> another.try(:last_name) if result == 0
414
+ result = self.first_name <=> another.try(:first_name) if result == 0
399
415
  return result
400
416
  end
401
417