osm 1.0.6 → 1.2.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.
@@ -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