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.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,33 @@
1
+ ## Version 1.2.0
2
+
3
+ * Trying to fetch the currrent Term for a Section which doesn;t have one now raises an Osm::Error::NoCurrentTerm instead of an Osm::Error
4
+ * Add personal details options to Section:
5
+ * myscout\_details attribute [Boolean] for whether personal details are enabled
6
+ * myscout\_details\_expires attribute [Date] for expiry date of subscription
7
+ * myscout\_details\_email\_changes\_to attribute [String] where to send update emails to
8
+ * Osm::Member
9
+ * myscout\_link method now accepts :details to get a link to the "Perosnal details" page
10
+ * myscout\_link method can now link to a specific event by also passing in the id (optional 3rd parameter)
11
+ * Addition of myscout\_link\_key method to get the member's unique key for use in myscout links
12
+ * Osm::Section
13
+ * subscription\_level\_name method is marked as depricated, ready for removal in version 2.0 -> replace with Osm::SUBSCRIPTION\_LEVEL\_NAMES[section.subscription\_level]
14
+ * myscout\_programme\_show attribute added - how much of the programme do parents see?
15
+ * Addition of two new constants:
16
+ * Osm::SUBSCRIPTION\_LEVEL\_NAMES - an Array of Strings containing the human name of each subscription level (starts with "Unknown" to make indexing work nicely)
17
+ * Osm::SUBSCRIPTION\_LEVELS - an Array of Symbols for each level (starts with nil to make indexing work nicely)
18
+ * Register - get\_structure now only includes dates
19
+ * Addition of attendance\_reminder attribute to event
20
+ * Abillity to send Sms messages to multiple numbers
21
+ * Add Gift Aid:
22
+ * Get Payments
23
+ * Update Payments
24
+ * Get Payment/Member Data
25
+ * Update Payment/Member Data
26
+ * Add Finances:
27
+ * Budget (Get, Add, Update, Delete)
28
+ * Invoices (Get, Add, Update, Delete)
29
+ * Invoice Items (Get, Add, Update, Delete)
30
+
1
31
  ## Version 1.0.6
2
32
 
3
33
  * Add badge\_links to Meeting
data/README.md CHANGED
@@ -32,7 +32,7 @@ Use the [Online Scout Manager](https://www.onlinescoutmanager.co.uk) API.
32
32
  Add to your Gemfile and run the `bundle` command to install it.
33
33
 
34
34
  ```ruby
35
- gem 'osm', '~> 1.0'
35
+ gem 'osm', '~> 1.2'
36
36
  ```
37
37
 
38
38
  Configure the gem during the initalization of the app (e.g. if using rails then config/initializers/osm.rb would look like):
@@ -90,6 +90,7 @@ however it should be noted that when the OSM API adds a feature it can be diffic
90
90
  * Details for each badge
91
91
  * Requirements for evening
92
92
  * Badge stock levels
93
+ * Budgets (Gold required)
93
94
  * Due Badges
94
95
  * Evening
95
96
  * Event (Silver required)
@@ -98,7 +99,10 @@ however it should be noted that when the OSM API adds a feature it can be diffic
98
99
  * Event Attendance (Silver required)
99
100
  * Flexi Record Data
100
101
  * Flexi Record Structure
102
+ * Gift Aid Data
103
+ * Gift Aid Structure
101
104
  * Groupings (e.g. Sixes, Patrols)
105
+ * Invoices (Gold required)
102
106
  * Members
103
107
  * Notepad
104
108
  * Notepads
@@ -116,39 +120,48 @@ however it should be noted that when the OSM API adds a feature it can be diffic
116
120
  * Activity
117
121
  * Badges (Silver required for activity, Bronze for core, challenge and staged):
118
122
  * Which requirements each member has met
123
+ * Budget (Gold required)
119
124
  * Evening
120
125
  * Event (Silver required)
121
126
  * Event Attendance (Silver required)
122
127
  * Event Column (Silver required)
123
128
  * Flexi Record Column
124
129
  * Flexi Record Data
130
+ * Gift Aid Payment
125
131
  * Grouping
132
+ * Invoices (Gold required)
126
133
  * Member
127
134
  * Register Attendance
128
135
 
129
136
  ### Create
137
+ * Budget (Gold required)
130
138
  * Evening
131
139
  * Event (Silver required)
132
140
  * Event Column (Silver required)
133
- * Member
134
141
  * Flexi Record Column
142
+ * Gift Aid Payment
143
+ * Invoices (Gold required)
144
+ * Member
135
145
 
136
146
  ### Delete
147
+ * Budget (Gold required)
137
148
  * Evening
138
149
  * Event (Silver required)
139
150
  * Event Column (Silver required)
140
151
  * Flexi Record Column
152
+ * Invoices (Gold required)
141
153
 
142
154
  ### Actions
143
155
  * Authorise an app to use the API on a user's behalf
144
156
  * Add activity to programme
145
157
  * Send an SMS to member(s)
146
158
 
159
+ ## Parts not/never supported
160
+ * Campsite Directory
147
161
 
148
162
  ## Parts of the OSM API currently NOT supported (may not be an exhaustive list):
149
163
 
150
164
  See the [Roadmap page in the wiki](https://github.com/robertgauld/osm/wiki/Roadmap) for more details.
151
165
 
152
- * Gift aid (Everything) (Gold required) [issue 75]
153
- * Finances (Everything) (Gold required) [issues 76 & 77]]
154
166
  * MyScout (Everything)
167
+ * Adult Section Specific Stuff
data/lib/osm.rb CHANGED
@@ -11,9 +11,10 @@ module Osm
11
11
  # Declare exceptions
12
12
  class Error < Exception; end
13
13
  class ConnectionError < Error; end
14
- class Forbidden < Error; end
14
+ class Forbidden < Osm::Error; end
15
15
  class ArgumentIsInvalid < ArgumentError; end
16
16
  class ObjectIsInvalid < Error; end
17
+ class Osm::Error::NoCurrentTerm < Osm::Error; end
17
18
 
18
19
  private
19
20
  # Set constants
@@ -26,6 +27,8 @@ module Osm
26
27
  OSM_DATETIME_FORMAT_HUMAN = '%d/%m/%Y %H:%M:%S'
27
28
  OSM_TIME_REGEX = /\A(?:[0-1][0-9]|2[0-3]):[0-5][0-9]\Z/
28
29
  OSM_DATE_REGEX = /\A(?:[1-9]\d{3}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1]))|(?:(?:0?[1-9]|[1-2][0-9]|3[0-1])\/(?:0?[1-9]|1[0-2])\/(?:\d{2}|[1-9]\d{3}))\Z/
30
+ SUBSCRIPTION_LEVEL_NAMES = ['Unknown', 'Bronze', 'Silver', 'Gold', 'Gold+']
31
+ SUBSCRIPTION_LEVELS = [nil, :bronze, :silver, :gold, :gold_plus]
29
32
  end
30
33
 
31
34
  # Require file for this gem
data/lib/osm/budget.rb ADDED
@@ -0,0 +1,136 @@
1
+ module Osm
2
+
3
+ class Budget < Osm::Model
4
+ # @!attribute [rw] id
5
+ # @return [Fixnum] The OSM ID for the budget
6
+ # @!attribute [rw] section_id
7
+ # @return [Fixnum] The OSM ID for the section the budget belongs to
8
+ # @!attribute [rw] name
9
+ # @return [String] The name of the budget
10
+
11
+ attribute :id, :type => Integer
12
+ attribute :section_id, :type => Integer
13
+ attribute :name, :type => String
14
+
15
+ attr_accessible :id, :section_id, :name
16
+
17
+ validates_numericality_of :id, :only_integer=>true, :greater_than=>0, :unless => Proc.new { |r| r.id.nil? }
18
+ validates_numericality_of :section_id, :only_integer=>true, :greater_than=>0
19
+ validates_presence_of :name
20
+
21
+
22
+ # @!method initialize
23
+ # Initialize a new Budget
24
+ # @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
25
+
26
+
27
+ # Get budgets for a section
28
+ # @param [Osm::Api] api The api to use to make the request
29
+ # @param [Osm::Section, Fixnum, #to_i] section The section (or its ID) to get the structure for
30
+ # @!macro options_get
31
+ # @return [Array<Osm::Budget>] representing the donations made
32
+ def self.get_for_section(api, section, options={})
33
+ Osm::Model.require_ability_to(api, :read, :finance, section, options)
34
+ section_id = section.to_i
35
+ cache_key = ['budgets', section_id]
36
+
37
+ if !options[:no_cache] && Osm::Model.cache_exist?(api, cache_key)
38
+ return Osm::Model.cache_read(api, cache_key)
39
+ end
40
+
41
+ data = api.perform_query("finances.php?action=getCategories&sectionid=#{section_id}")
42
+
43
+ budgets = []
44
+ data = data['items']
45
+ if data.is_a?(Array)
46
+ data.each do |budget|
47
+ budgets.push Budget.new(
48
+ :id => Osm::to_i_or_nil(budget['categoryid']),
49
+ :section_id => Osm::to_i_or_nil(budget['sectionid']),
50
+ :name => budget['name'],
51
+ )
52
+ end
53
+ end
54
+
55
+ Osm::Model.cache_write(api, cache_key, budgets) unless budgets.nil?
56
+ return budgets
57
+ end
58
+
59
+
60
+ # Create the budget in OSM
61
+ # @param [Osm::Api] api The api to use to make the request
62
+ # @return [Boolean] whether the budget was created
63
+ # @raise [Osm::ObjectIsInvalid] If the Budget is invalid
64
+ # @raise [Osm::Error] If the budget already exists in OSM
65
+ def create(api)
66
+ raise Osm::Error, 'the budget already exists in OSM' unless id.nil?
67
+ raise Osm::ObjectIsInvalid, 'budget is invalid' unless valid?
68
+ Osm::Model.require_ability_to(api, :write, :finance, section_id)
69
+
70
+ data = api.perform_query("finances.php?action=addCategory&sectionid=#{section_id}")
71
+ if data.is_a?(Hash) && data['ok'].eql?(true)
72
+ # The cached budgets for the section will be out of date - remove them
73
+ cache_delete(api, ['budgets', section_id])
74
+ budgets = Budget.get_for_section(api, section_id, {:no_cache => true})
75
+ budget = budgets.sort.select{ |b| b.name.eql?('** Unnamed **') }[-1]
76
+ return false if budget.nil? # a new blank budget was NOT created
77
+ budget.name = name
78
+ if budget.update(api)
79
+ self.id = budget.id
80
+ return true
81
+ end
82
+ end
83
+ return false
84
+ end
85
+
86
+ # Update budget in OSM
87
+ # @param [Osm::Api] api The api to use to make the request
88
+ # @return [Boolean] whether the budget was updated
89
+ # @raise [Osm::ObjectIsInvalid] If the Budget is invalid
90
+ def update(api)
91
+ raise Osm::ObjectIsInvalid, 'budget is invalid' unless valid?
92
+ Osm::Model.require_ability_to(api, :write, :finance, section_id)
93
+
94
+ data = api.perform_query("finances.php?action=updateCategory&sectionid=#{section_id}", {
95
+ 'categoryid' => id,
96
+ 'column' => 'name',
97
+ 'value' => name,
98
+ 'section_id' => section_id,
99
+ 'row' => 0,
100
+ })
101
+ if (data.is_a?(Hash) && data['ok'].eql?(true))
102
+ # The cached budgets for the section will be out of date - remove them
103
+ cache_delete(api, ['budgets', section_id])
104
+ return true
105
+ end
106
+ return false
107
+ end
108
+
109
+ # Delete budget from OSM
110
+ # @param [Osm::Api] api The api to use to make the request
111
+ # @return [Boolean] whether the budget was deleted
112
+ def delete(api)
113
+ Osm::Model.require_ability_to(api, :write, :finance, section_id)
114
+
115
+ data = api.perform_query("finances.php?action=deleteCategory&sectionid=#{section_id}", {
116
+ 'categoryid' => id,
117
+ })
118
+ if (data.is_a?(Hash) && data['ok'].eql?(true))
119
+ # The cached budgets for the section will be out of date - remove them
120
+ cache_delete(api, ['budgets', section_id])
121
+ return true
122
+ end
123
+ return false
124
+ end
125
+
126
+
127
+ # Compare Budget based on section_id then name
128
+ def <=>(another)
129
+ result = self.section_id <=> another.try(:section_id)
130
+ result = self.name <=> another.try(:name) if result == 0
131
+ return result
132
+ end
133
+
134
+ end # Class Budget
135
+
136
+ end
data/lib/osm/event.rb CHANGED
@@ -37,6 +37,8 @@ module Osm
37
37
  # @return [Fixnum] the maximum number of people who can attend the event (0 = no limit)
38
38
  # @!attendance [rw] attendance_limit_includes_leaders
39
39
  # @return [Boolean] whether the attendance limit includes leaders
40
+ # @!attribute [rw] attendance_reminder
41
+ # @return [Fixnum] how many days before the event to send a reminder to those attending (0 (off), 1, 3, 7, 14, 21, 28)
40
42
  # @!attribute [rw] allow_booking
41
43
  # @return [Boolean] whether booking is allowed through My.SCOUT
42
44
 
@@ -57,11 +59,13 @@ module Osm
57
59
  attribute :reminders, :type => Boolean, :default => true
58
60
  attribute :attendance_limit, :type => Integer, :default => 0
59
61
  attribute :attendance_limit_includes_leaders, :type => Boolean, :default => false
62
+ attribute :attendance_reminder, :type => Integer, :default => 0
60
63
  attribute :allow_booking, :type => Boolean, :default => true
61
64
 
62
65
  attr_accessible :id, :section_id, :name, :start, :finish, :cost, :location, :notes, :archived,
63
66
  :fields, :columns, :notepad, :public_notepad, :confirm_by_date, :allow_changes,
64
- :reminders, :attendance_limit, :attendance_limit_includes_leaders, :allow_booking
67
+ :reminders, :attendance_limit, :attendance_limit_includes_leaders,
68
+ :attendance_reminder, :allow_booking
65
69
 
66
70
  validates_numericality_of :id, :only_integer=>true, :greater_than=>0, :allow_nil => true
67
71
  validates_numericality_of :section_id, :only_integer=>true, :greater_than=>0
@@ -72,6 +76,7 @@ module Osm
72
76
  validates_inclusion_of :reminders, :in => [true, false]
73
77
  validates_inclusion_of :attendance_limit_includes_leaders, :in => [true, false]
74
78
  validates_inclusion_of :allow_booking, :in => [true, false]
79
+ validates_inclusion_of :attendance_reminder, :in => [0, 1, 3, 7, 14, 21, 28]
75
80
  validates_format_of :cost, :with => /\A(?:\d+\.\d{2}|TBC)\Z/
76
81
 
77
82
 
@@ -84,7 +89,7 @@ module Osm
84
89
  # @param [Osm::Api] api The api to use to make the request
85
90
  # @param [Osm::Section, Fixnum, #to_i] section The section (or its ID) to get the events for
86
91
  # @!macro options_get
87
- # @option options [Boolean] :include_archived (optional) if true then archived activities will also be returned
92
+ # @option options [Boolean] :include_archived (optional) if true then archived events will also be returned
88
93
  # @return [Array<Osm::Event>]
89
94
  def self.get_for_section(api, section, options={})
90
95
  require_ability_to(api, :read, :events, section, options)
@@ -122,13 +127,13 @@ module Osm
122
127
  # Get an event
123
128
  # @param [Osm::Api] api The api to use to make the request
124
129
  # @param [Osm::Section, Fixnum, #to_i] section The section (or its ID) to get the events for
125
- # @param [Fixnum] event_id The id of the event to get
130
+ # @param [Fixnum, #to_i] event_id The id of the event to get
126
131
  # @!macro options_get
127
- # @option options [Boolean] :include_archived (optional) if true then archived activities will also be returned
128
132
  # @return [Osm::Event, nil] the event (or nil if it couldn't be found
129
133
  def self.get(api, section, event_id, options={})
130
134
  require_ability_to(api, :read, :events, section, options)
131
135
  section_id = section.to_i
136
+ event_id = event_id.to_i
132
137
  cache_key = ['event', event_id]
133
138
 
134
139
  if !options[:no_cache] && cache_exist?(api, cache_key)
@@ -162,6 +167,7 @@ module Osm
162
167
  'allowChanges' => event.allow_changes ? 'true' : 'false',
163
168
  'disablereminders' => !event.reminders ? 'true' : 'false',
164
169
  'attendancelimit' => event.attendance_limit,
170
+ 'attendancereminder' => event.attendance_reminder,
165
171
  'limitincludesleaders' => event.attendance_limit_includes_leaders ? 'true' : 'false',
166
172
  'allowbooking' => event.allow_booking ? 'true' : 'false',
167
173
  })
@@ -200,6 +206,7 @@ module Osm
200
206
  'allowChanges' => allow_changes ? 'true' : 'false',
201
207
  'disablereminders' => !reminders ? 'true' : 'false',
202
208
  'attendancelimit' => attendance_limit,
209
+ 'attendancereminder' => attendance_reminder,
203
210
  'limitincludesleaders' => attendance_limit_includes_leaders ? 'true' : 'false',
204
211
  'allowbooking' => allow_booking ? 'true' : 'false',
205
212
  })
@@ -392,6 +399,7 @@ module Osm
392
399
  :reminders => !event_data['disablereminders'].eql?('1'),
393
400
  :attendance_limit => event_data['attendancelimit'].to_i,
394
401
  :attendance_limit_includes_leaders => event_data['limitincludesleaders'].eql?('1'),
402
+ :attendance_reminder => event_data['attendancereminder'].to_i,
395
403
  :allow_booking => event_data['allowbooking'].eql?('1'),
396
404
  )
397
405
 
@@ -403,7 +411,6 @@ module Osm
403
411
  end
404
412
  event.columns = columns
405
413
  return event
406
-
407
414
  end
408
415
 
409
416
 
@@ -0,0 +1,292 @@
1
+ module Osm
2
+
3
+ class GiftAid
4
+
5
+ # Get donations
6
+ # @param [Osm::Api] api The api to use to make the request
7
+ # @param [Osm::Section, Fixnum, #to_i] section The section (or its ID) to get the structure for
8
+ # @param [Osm::Term, Fixnum, #to_i, nil] term The term (or its ID) to get the structure for, passing nil causes the current term to be used
9
+ # @!macro options_get
10
+ # @return [Array<Osm::GiftAid::Donation>] representing the donations made
11
+ def self.get_donations(api, section, term=nil, options={})
12
+ Osm::Model.require_ability_to(api, :read, :finance, section, options)
13
+ section_id = section.to_i
14
+ term_id = term.nil? ? Osm::Term.get_current_term_for_section(api, section).id : term.to_i
15
+ cache_key = ['gift_aid_donations', section_id, term_id]
16
+
17
+ if !options[:no_cache] && Osm::Model.cache_exist?(api, cache_key)
18
+ return Osm::Model.cache_read(api, cache_key)
19
+ end
20
+
21
+ data = api.perform_query("giftaid.php?action=getStructure&sectionid=#{section_id}&termid=#{term_id}")
22
+
23
+ structure = []
24
+ if data.is_a?(Array)
25
+ data = (data.size == 2) ? data[1] : []
26
+ if data.is_a?(Hash) && data['rows'].is_a?(Array)
27
+ data['rows'].each do |row|
28
+ structure.push Donation.new(
29
+ :donation_date => Osm::parse_date(row['field']),
30
+ )
31
+ end
32
+ end
33
+ end
34
+
35
+ Osm::Model.cache_write(api, cache_key, structure) unless structure.nil?
36
+ return structure
37
+ end
38
+
39
+ # Get donation data
40
+ # @param [Osm::Api] api The api to use to make the request
41
+ # @param [Osm::Section, Fixnum, #to_i] section The section (or its ID) to get the register for
42
+ # @param [Osm::Term, Fixnum, #to_i, nil] term The term (or its ID) to get the register for, passing nil causes the current term to be used
43
+ # @!macro options_get
44
+ # @return [Array<Osm::GiftAid::Data>] representing the donations of each member
45
+ def self.get_data(api, section, term=nil, options={})
46
+ Osm::Model.require_ability_to(api, :read, :finance, section, options)
47
+ section_id = section.to_i
48
+ term_id = term.nil? ? Osm::Term.get_current_term_for_section(api, section).id : term.to_i
49
+ cache_key = ['gift_aid_data', section_id, term_id]
50
+
51
+ if !options[:no_cache] && Osm::Model.cache_exist?(api, cache_key)
52
+ return Osm::Model.cache_read(api, cache_key)
53
+ end
54
+
55
+ data = api.perform_query("giftaid.php?action=getGrid&sectionid=#{section_id}&termid=#{term_id}")
56
+
57
+ to_return = []
58
+ if data.is_a?(Hash) && data['items'].is_a?(Array)
59
+ data = data['items']
60
+ data.each do |item|
61
+ if item.is_a?(Hash)
62
+ unless item['scoutid'].to_i < 0 # It's a total row
63
+ donations = {}
64
+ item.each do |key, value|
65
+ if key.match(Osm::OSM_DATE_REGEX)
66
+ donations[Osm::parse_date(key)] = value
67
+ end
68
+ end
69
+ to_return.push Osm::GiftAid::Data.new(
70
+ :member_id => Osm::to_i_or_nil(item['scoutid']),
71
+ :grouping_id => Osm::to_i_or_nil(item ['patrolid']),
72
+ :section_id => section_id,
73
+ :first_name => item['firstname'],
74
+ :last_name => item['lastname'],
75
+ :tax_payer_name => item['parentname'],
76
+ :tax_payer_address => item['address'],
77
+ :tax_payer_postcode => item['postcode'],
78
+ :total => item['total'],
79
+ :donations => donations,
80
+ )
81
+ end
82
+ end
83
+ end
84
+ Osm::Model.cache_write(api, cache_key, to_return)
85
+ end
86
+ return to_return
87
+ end
88
+
89
+ # Update information for a donation
90
+ # @param [Hash] data
91
+ # @option data [Osm::Api] :api The api to use to make the request
92
+ # @option data [Osm::Section] :section the section to update the register for
93
+ # @option data [Osm::Term, #to_i, nil] :term The term (or its ID) to get the register for, passing nil causes the current term to be used
94
+ # @option data [Osm::Evening, DateTime, Date] :evening the evening to update the register on
95
+ # @option data [Fixnum, Array<Fixnum>, Osm::Member, Array<Osm::Member>, #to_i, Array<#to_i>] :members the members (or their ids) to update
96
+ # @option data [Date, #strftime] :donation_date the date the donation was made
97
+ # @option data [String, #to_s] :amount the donation amount
98
+ # @option data [String, #to_s] :note the description for the donation
99
+ # @return [Boolean] whether the update succedded
100
+ # @raise [Osm::ArgumentIsInvalid] If data[:section] is missing
101
+ # @raise [Osm::ArgumentIsInvalid] If data[:donation_date] is missing
102
+ # @raise [Osm::ArgumentIsInvalid] If data[:amount] is missing
103
+ # @raise [Osm::ArgumentIsInvalid] If data[:note] is missing
104
+ # @raise [Osm::ArgumentIsInvalid] If data[:members] is missing
105
+ # @raise [Osm::ArgumentIsInvalid] If data[:api] is missing
106
+ def self.update_donation(data={})
107
+ raise Osm::ArgumentIsInvalid, ':section is missing' if data[:section].nil?
108
+ raise Osm::ArgumentIsInvalid, ':donation_date is missing' if data[:donation_date].nil?
109
+ raise Osm::ArgumentIsInvalid, ':amount is missing' if data[:amount].nil?
110
+ raise Osm::ArgumentIsInvalid, ':note is missing' if data[:note].nil?
111
+ raise Osm::ArgumentIsInvalid, ':members is missing' if data[:members].nil?
112
+ raise Osm::ArgumentIsInvalid, ':api is missing' if data[:api].nil?
113
+ api = data[:api]
114
+ Osm::Model.require_ability_to(api, :write, :finance, data[:section])
115
+
116
+ term_id = data[:term].nil? ? Osm::Term.get_current_term_for_section(api, data[:section]).id : data[:term].to_i
117
+ section_id = data[:section].to_i
118
+
119
+ data[:members] = [*data[:members]].map{ |member| member.to_i.to_s } # Make sure it's an Array of Strings
120
+
121
+ response = api.perform_query("giftaid.php?action=update&sectionid=#{section_id}&termid=#{term_id}", {
122
+ 'scouts' => data[:members].inspect,
123
+ 'sectionid' => section_id,
124
+ 'donatedate'=> data[:donation_date].strftime(Osm::OSM_DATE_FORMAT),
125
+ 'amount' => data[:amount],
126
+ 'notes' => data[:note],
127
+ })
128
+
129
+ # The cached donations and data will be out of date - remove them
130
+ Osm::Model.cache_delete(api, ['gift_aid_donations', section_id, term_id])
131
+ Osm::Model.cache_delete(api, ['gift_aid_data', section_id, term_id])
132
+
133
+ return response.is_a?(Array)
134
+ end
135
+
136
+ class Donation < Osm::Model
137
+ # @!attribute [rw] donation_date
138
+ # @return [Date] When the payment was made
139
+
140
+ attribute :donation_date, :type => Date
141
+
142
+ attr_accessible :note, :donation_date
143
+
144
+ validates_presence_of :donation_date
145
+
146
+
147
+ # @!method initialize
148
+ # Initialize a new RegisterField
149
+ # @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
150
+
151
+
152
+ # Compare Payment based on donation_date then note
153
+ def <=>(another)
154
+ return self.donation_date <=> another.try(:donation_date)
155
+ end
156
+
157
+ end # Class GiftAid::Donation
158
+
159
+
160
+ class Data < Osm::Model
161
+ # @!attribute [rw] member_id
162
+ # @return [Fixnum] The OSM ID for the member
163
+ # @!attribute [rw] grouping_id
164
+ # @return [Fixnum] The OSM ID for the member's grouping
165
+ # @!attribute [rw] section_id
166
+ # @return [Fixnum] The OSM ID for the member's section
167
+ # @!attribute [rw] first_name
168
+ # @return [String] The member's first name
169
+ # @!attribute [rw] last_name
170
+ # @return [String] The member's last name
171
+ # @!attribute [rw] tax_payer_name
172
+ # @return [String] The tax payer's name
173
+ # @!attribute [rw] tax_payer_address
174
+ # @return [String] The tax payer's street address
175
+ # @!attribute [rw] tax_payer_postcode
176
+ # @return [String] The tax payer's postcode
177
+ # @!attribute [rw] total
178
+ # @return [String] Total
179
+ # @!attribute [rw] donations
180
+ # @return [DirtyHashy] The data for each payment - keys are the date, values are the value of the payment
181
+
182
+ attribute :member_id, :type => Integer
183
+ attribute :grouping_id, :type => Integer
184
+ attribute :section_id, :type => Integer
185
+ attribute :first_name, :type => String
186
+ attribute :last_name, :type => String
187
+ attribute :tax_payer_name, :type => String
188
+ attribute :tax_payer_address, :type => String
189
+ attribute :tax_payer_postcode, :type => String
190
+ attribute :total, :type => String
191
+ attribute :donations, :type => Object, :default => DirtyHashy.new
192
+
193
+ attr_accessible :member_id, :first_name, :last_name, :section_id, :grouping_id, :total, :tax_payer_name, :tax_payer_address, :tax_payer_postcode, :donations
194
+
195
+ validates_numericality_of :member_id, :only_integer=>true, :greater_than=>0
196
+ validates_numericality_of :grouping_id, :only_integer=>true, :greater_than_or_equal_to=>-2
197
+ validates_numericality_of :section_id, :only_integer=>true, :greater_than=>0
198
+ validates_presence_of :first_name
199
+ validates_presence_of :last_name
200
+
201
+ validates :donations, :hash => {:key_type => Date, :value_type => String}
202
+
203
+
204
+ # @!method initialize
205
+ # Initialize a new registerData
206
+ # @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
207
+ # Override initialize to set @orig_attributes
208
+ old_initialize = instance_method(:initialize)
209
+ define_method :initialize do |*args|
210
+ ret_val = old_initialize.bind(self).call(*args)
211
+ self.donations = DirtyHashy.new(self.donations)
212
+ self.donations.clean_up!
213
+ return ret_val
214
+ end
215
+
216
+
217
+ # Update data in OSM
218
+ # @param [Osm::Api] api The api to use to make the request
219
+ # @return [Boolean] whether the data was updated in OSM
220
+ # @raise [Osm::ObjectIsInvalid] If the Data is invalid
221
+ def update(api)
222
+ raise Osm::ObjectIsInvalid, 'data is invalid' unless valid?
223
+ require_ability_to(api, :write, :finance, section_id)
224
+ term_id = Osm::Term.get_current_term_for_section(api, section_id).id
225
+
226
+ updated = true
227
+ fields = [
228
+ ['tax_payer_name', 'parentname', tax_payer_name],
229
+ ['tax_payer_address', 'address', tax_payer_address],
230
+ ['tax_payer_postcode', 'postcode', tax_payer_postcode],
231
+ ]
232
+ fields.each do |field|
233
+ if changed_attributes.include?(field[0])
234
+ result = api.perform_query("giftaid.php?action=updateScout", {
235
+ 'scoutid' => member_id,
236
+ 'termid' => term_id,
237
+ 'column' => field[1],
238
+ 'value' => field[2],
239
+ 'sectionid' => section_id,
240
+ 'row' => 0,
241
+ })
242
+ if result.is_a?(Hash)
243
+ (result['items'] || []).each do |i|
244
+ if i['scoutid'] == member_id.to_s
245
+ updated = false unless i[field[1]] == field[2]
246
+ end
247
+ end
248
+ end
249
+ end
250
+ end
251
+ reset_changed_attributes if updated
252
+
253
+ donations.changes.each do |date, (was,now)|
254
+ date = date.strftime(Osm::OSM_DATE_FORMAT)
255
+ result = api.perform_query("giftaid.php?action=updateScout", {
256
+ 'scoutid' => member_id,
257
+ 'termid' => term_id,
258
+ 'column' => date,
259
+ 'value' => now,
260
+ 'sectionid' => section_id,
261
+ 'row' => 0,
262
+ })
263
+ if result.is_a?(Hash)
264
+ (result['items'] || []).each do |i|
265
+ if i['scoutid'] == member_id.to_s
266
+ updated = false unless i[date] == now
267
+ end
268
+ end
269
+ end
270
+ end
271
+ donations.clean_up! if updated
272
+
273
+ Osm::Model.cache_delete(api, ['gift_aid_data', section_id, term_id]) if updated
274
+
275
+ return updated
276
+ end
277
+
278
+
279
+ # Compare Data based on section_id, grouping_id, last_name then first_name
280
+ def <=>(another)
281
+ result = self.section_id <=> another.try(:section_id)
282
+ result = self.grouping_id <=> another.try(:grouping_id) if result == 0
283
+ result = self.last_name <=> another.try(:last_name) if result == 0
284
+ result = self.first_name <=> another.try(:last_name) if result == 0
285
+ return result
286
+ end
287
+
288
+ end # Class GiftAid::Data
289
+
290
+ end # Class GiftAid
291
+
292
+ end