osm 1.0.6 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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