osm 1.2.14 → 1.2.15.dev

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZGU3ZmNhNzhmMTBlNDY4ZmJjMTU2YmUwNmRiN2NlODgxNGYyZWZjYg==
4
+ OTc4ZGUzMmFjNjdhZjNhY2M0MzdjNGJhODk4MGJhOTY1NjM2YmJlMA==
5
5
  data.tar.gz: !binary |-
6
- NTY0OGM2NDkzOWVlMWJhOTU5MWNiZjJkMGIyZjUwMjRmY2I1ZGNlMg==
6
+ ZmIyMGUyZDVlNzU5MmFkNjUwNTJiNGJiODJjZDc1MDZjZDJhMWMzYg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- OTY5Njg0MmZhYjkwZDBlYjNiZWIwMWE5OGRkNDYyMmM0M2ZiNDU2MDQ2MTY4
10
- ZTczNDI4ZjdhZDVlMGVhY2E3ZGI0MGZhYjBlOWRmNjU3YzliMjJlNGU1MWEy
11
- Yzg3MDg3ZDkzYzBhNmI2M2Y1MzU1YWYxOWZlNzVhOTFlOWM0MWM=
9
+ ZDViMmI1NDdhZThkMDcxYzc4Nzk1ZDZiNjM5MDJiMThhNDA0ZWU3YjQyNjdk
10
+ YTJlYjYyZTU1ZDczMWJlMmY5Yjc3NmZhNWIzZjIwNDllZWIwM2Q3MDEwNzZj
11
+ ODg4NjI5YzFmOWI0N2NiOGQ1ODg0ZmRlYmJkMWI1ZDAwMGNkMzQ=
12
12
  data.tar.gz: !binary |-
13
- Nzc4ZjljYTQ1YzVhNDM3MTlhYTUwZDY1YWRmOWQ2MjFmOTllNTZmZmI0NTZi
14
- ZjZkMjVhNWQ0YWViNDAzN2RjNDkxYWI2N2M5NTQyOGNiZDllZTNlMjEzMjhl
15
- MzY4ZGRmNDFkYjdhNGIwN2Q3Zjk5ZjgyOTI1OGNiMjhkMjA4ZjU=
13
+ ZWIxMTU3NmVmMmNkOWI2MTc0OGJjNjA3MGRiODY2YTZhMTExN2UyMTdkOWY0
14
+ ZWNjNTdhZDE5MGE3MTc1MzBhZDAxYWJlMjdiNTQ1MGZiMDZiZDNkMDRjOTVk
15
+ NmFmODIyMGExOWI5ZTg0NGQ1NTg4ZTE5NzMwNjYzMTY5ZWIyZjY=
data/CHANGELOG.md CHANGED
@@ -1,3 +1,35 @@
1
+ ## Version 1.2.15
2
+
3
+ * Add support for census and giftaid link generation for members
4
+ * Add :osm_staging as a site to point the gem at. Really only useful for gem development.
5
+ * Update to match OSM's new badge system:
6
+ * Retrieving badge stock
7
+ * Updating badge stock
8
+ * Fetching badge data
9
+ * Fetching due badges (added stock\_levels attribute, since OSM now gives it)
10
+ * Fetching & updating badge links for Events
11
+ * Fetching & updating badge links for Activities
12
+ * Fetching & updating badge links for Meetings
13
+ * Summary now returns all started/completed/awarded badges (it can't filter by type) so can only be called from Osm::Badge
14
+ * Osm::Badge
15
+ * osm\_key, osm\_long\_key and competion criteria attributes are gone
16
+ * id, version, identifier, group\_name, latest, user\_id, levels, sharing attributes added
17
+ * OSM Changed how completion criteria are retrieved (this gem now uses a special peice of OSM's API just for this purpose) so:
18
+ * Osm::Badge attributes sections\_needed, total\_needed and needed\_from\_section are gone
19
+ * Osm::Badge::Data
20
+ * mark\_awarded method now only marks as awarded (the optional mark\_as parameter is gone)
21
+ * mark\_not\_awarded method added
22
+ * mark\_due and mark\_not\_due methods added
23
+ * completed attribute renamed to due
24
+ * sections_gained method renamed to modules_gained, now returns an array of letters
25
+ * gained_in_sections renamed to gained_in_modules
26
+ * Osm::Event::BadgeLink
27
+ * Attributes added: badge\_name, badge\_id, badge\_version, requirement\_id
28
+ * Attributes removed: badge\_label, badge\_key, requirement\_key
29
+ * Attributes untouched: badge\_type, badge\_section, data, requirement\_label
30
+ * Osm::Activity::Badge attributes now match Osm::Event::BadgeLink
31
+ * Osm::Meeting::BadgeLink attributes now match Osm::Event::badgeLink
32
+
1
33
  ## Version 1.2.14
2
34
 
3
35
  * Fix fetching sections when user doesn't have access to any
data/lib/osm/activity.rb CHANGED
@@ -156,12 +156,14 @@ module Osm
156
156
  end
157
157
  (data['badges'].is_a?(Array) ? data['badges'] : []).each do |badge_data|
158
158
  attributes[:badges].push Badge.new(
159
- :activity_id => Osm::to_i_or_nil(badge_data['activityid']),
160
- :section_type => badge_data['section'].to_sym,
161
- :type => badge_data['badgetype'].to_sym,
162
- :badge => badge_data['badge'],
163
- :requirement => badge_data['columnname'],
164
- :label => badge_data['label']
159
+ :badge_type => badge_data['badgetype'].to_sym,
160
+ :badge_section => badge_data['section'].to_sym,
161
+ :badge_name => badge_data['badgeLongName'],
162
+ :badge_id => Osm::to_i_or_nil(badge_data['badge_id']),
163
+ :badge_version => Osm::to_i_or_nil(badge_data['badge_version']),
164
+ :requirement_id => Osm::to_i_or_nil(badge_data['column_id']),
165
+ :requirement_label => badge_data['columnnameLongName'],
166
+ :data => badge_data['data'],
165
167
  )
166
168
  end
167
169
  (data['versions'].is_a?(Array) ? data['versions'] : []).each do |version_data|
@@ -241,7 +243,23 @@ module Osm
241
243
  'location' => location,
242
244
  'sections' => sections.to_json,
243
245
  'tags' => tags.to_json,
244
- 'links' => badges.map{ |b| {'activityid'=>b.activity_id.to_s, 'section'=>b.section_type, 'badgetype'=>b.type, 'badge'=>b.badge, 'columnname'=>b.requirement, 'label'=>b.label}}.to_json,
246
+ 'links' => badges.map{ |b|
247
+ {
248
+ 'badge_id' => b.badge_id.to_s,
249
+ 'badge_version' => b.badge_version.to_s,
250
+ 'column_id' => b.requirement_id.to_s,
251
+ 'badge' => nil,
252
+ 'badgeLongName' => b.badge_name,
253
+ 'columnname' => nil,
254
+ 'columnnameLongName' => b.requirement_label,
255
+ 'data' => b.data,
256
+ 'section' => b.badge_section,
257
+ 'sectionLongName' => nil,
258
+ 'sections' => sections.map{ |s| s.to_s },
259
+ 'badgetype' => b.badge_type.to_s,
260
+ 'badgetypeLongName' => nil,
261
+ }
262
+ }.to_json,
245
263
  'shared' => shared,
246
264
  'sectionid' => section.to_i,
247
265
  'secretEdit' => secret_update,
@@ -309,43 +327,56 @@ module Osm
309
327
  include ActiveModel::MassAssignmentSecurity if ActiveModel::VERSION::MAJOR < 4
310
328
  include ActiveAttr::Model
311
329
 
312
- # @!attribute [rw] activity_id
313
- # @return [Fixnum] the activity being done
314
- # @!attribute [rw] section_type
315
- # @return [Symbol] the section the badge 'belongs' to
316
- # @!attribute [rw] type
330
+ # @!attribute [rw] badge_type
317
331
  # @return [Symbol] the type of badge
318
- # @!attribute [rw] badge
319
- # @return [String] short name of the badge
320
- # @!attribute [rw] requirement
321
- # @return [String] OSM reference to this badge requirement
322
- # @!attribute [rw] label
323
- # @return [String] human readable label for the requirement
324
-
325
- attribute :activity_id, :type => Integer
326
- attribute :section_type
327
- attribute :type
328
- attribute :badge, :type => String
329
- attribute :requirement, :type => String
330
- attribute :label, :type => String
332
+ # @!attribute [rw] badge_section
333
+ # @return [Symbol] the section type that the badge belongs to
334
+ # @!attribute [rw] requirement_label
335
+ # @return [String] human firendly requirement label
336
+ # @!attribute [rw] data
337
+ # @return [String] what to put in the column when the badge records are updated
338
+ # @!attribute [rw] badge_name
339
+ # @return [String] the badge's name
340
+ # @!attribute [rw] badge_id
341
+ # @return [Fixnum] the badge's ID in OSM
342
+ # @!attribute [rw] badge_version
343
+ # @return [Fixnum] the version of the badge
344
+ # @!attribute [rw] requirement_id
345
+ # @return [Fixnum] the requirement's ID in OSM
346
+
347
+ attribute :badge_type, :type => Object
348
+ attribute :badge_section, :type => Object
349
+ attribute :requirement_label, :type => String
350
+ attribute :data, :type => String
351
+ attribute :badge_name, :type => String
352
+ attribute :badge_id, :type => Integer
353
+ attribute :badge_version, :type => Integer
354
+ attribute :requirement_id, :type => Integer
331
355
 
332
356
  if ActiveModel::VERSION::MAJOR < 4
333
- attr_accessible :activity_id, :section_type, :type, :badge, :requirement, :label
357
+ attr_accessible :badge_type, :badge_section, :requirement_label, :data, :badge_name, :badge_id, :badge_version, :requirement_id
334
358
  end
335
359
 
336
- validates_numericality_of :activity_id, :only_integer=>true, :greater_than=>0
337
- validates_presence_of :badge
338
- validates_presence_of :requirement
339
- validates_presence_of :label
340
-
341
- validates_each :type, :section_type do |record, attr, value|
342
- record.errors.add(attr, 'must be a Symbol') unless value.is_a?(Symbol)
343
- end
360
+ validates_presence_of :badge_name
361
+ validates_inclusion_of :badge_section, :in => [:beavers, :cubs, :scouts, :explorers, :staged]
362
+ validates_inclusion_of :badge_type, :in => [:core, :staged, :activity, :challenge]
363
+ validates_numericality_of :badge_id, :only_integer=>true, :greater_than=>0
364
+ validates_numericality_of :badge_version, :only_integer=>true, :greater_than_or_equal_to=>0
365
+ validates_numericality_of :requirement_id, :only_integer=>true, :greater_than=>0, :allow_nil=>true
344
366
 
345
367
  # @!method initialize
346
- # Initialize a new Badge
368
+ # Initialize a new Meeting::Activity
347
369
  # @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
348
370
 
371
+ # Compare BadgeLink based on section, type, badge_name, requirement_label, data
372
+ def <=>(another)
373
+ [:badge_section, :badge_type, :badge_name, :requirement_label].each do |attribute|
374
+ result = self.try(:data) <=> another.try(:data)
375
+ return result unless result == 0
376
+ end
377
+ return self.try(:data) <=> another.try(:data)
378
+ end
379
+
349
380
  end # Class Activity::Badge
350
381
 
351
382
  class Version
data/lib/osm/api.rb CHANGED
@@ -12,6 +12,7 @@ module Osm
12
12
  BASE_URLS = {
13
13
  :osm => 'https://www.onlinescoutmanager.co.uk',
14
14
  :ogm => 'http://www.onlineguidemanager.co.uk',
15
+ :osm_staging => 'http://staging.onlinescoutmanager.co.uk'
15
16
  }
16
17
 
17
18
 
@@ -29,9 +30,9 @@ module Osm
29
30
  # @option options [Boolean] :debug if true debugging info is output (optional, default = false)
30
31
  # @return nil
31
32
  def self.configure(options)
32
- raise ArgumentError, ':default_site does not exist in options hash or is invalid, this should be set to either :osm or :ogm' unless [:osm, :ogm].include?(options[:default_site])
33
- raise ArgumentError, ':osm and/or :ogm must be present' if options[:osm].nil? && options[:ogm].nil?
34
- [:osm, :ogm].each do |api_key|
33
+ raise ArgumentError, ':default_site does not exist in options hash or is invalid, this should be set to either :osm or :ogm' unless [:osm, :ogm, :osm_staging].include?(options[:default_site])
34
+ raise ArgumentError, ":#{options[:default_site]} does not exist in options hash" if options[options[:default_site]].nil?
35
+ [:osm, :ogm, :osm_staging].each do |api_key|
35
36
  if options[api_key]
36
37
  api_data = options[api_key]
37
38
  raise ArgumentError, ":#{api_key} must be a Hash" unless api_data.is_a?(Hash)
@@ -46,6 +47,7 @@ module Osm
46
47
  @@api_details = {
47
48
  :osm => (options[:osm] || {}),
48
49
  :ogm => (options[:ogm] || {}),
50
+ :osm_staging => (options[:osm_staging] || {})
49
51
  }
50
52
  nil
51
53
  end
@@ -71,7 +73,7 @@ module Osm
71
73
  def initialize(user_id, secret, site=@@site)
72
74
  raise ArgumentError, 'You must pass a secret (get this by using the authorize method)' if secret.nil?
73
75
  raise ArgumentError, 'You must pass a user_id (get this by using the authorize method)' if user_id.nil?
74
- raise ArgumentError, 'site is invalid, if passed it should be either :osm or :ogm, if not passed then you forgot to run Api.configure' unless [:osm, :ogm].include?(site)
76
+ raise ArgumentError, 'site is invalid, if passed it should be either :osm or :ogm, if not passed then you forgot to run Api.configure' unless [:osm, :ogm, :osm_staging].include?(site)
75
77
 
76
78
  @site = site
77
79
  set_user(user_id, secret)
@@ -209,7 +211,7 @@ module Osm
209
211
  # @raise [Osm::Error] If an error was returned by OSM
210
212
  # @raise [Osm::ConnectionError] If an error occured connecting to OSM
211
213
  def self.perform_query(site, url, api_data={})
212
- raise ArgumentError, 'site is invalid, this should be set to either :osm or :ogm' unless [:osm, :ogm].include?(site)
214
+ raise ArgumentError, 'site is invalid, this should be set to either :osm or :ogm' unless [:osm, :ogm, :osm_staging].include?(site)
213
215
 
214
216
  data = api_data.merge({
215
217
  'apiid' => @@api_details[site][:id],
data/lib/osm/badge.rb CHANGED
@@ -7,47 +7,58 @@ module Osm
7
7
  # @return [String] the name of the badge
8
8
  # @!attribute [rw] requirement_notes
9
9
  # @return [String] a description of the badge
10
- # @!attribute [rw] osm_key
11
- # @return [String] the key for the badge in OSM
12
- # @!attribute [rw] osm_long_key
13
- # @return [String] the long key for the badge in osm (used for getting stock)
14
- # @!attribute [rw] sections_needed
15
- # @return [Fixnum]
16
- # @!attribute [rw] total_needed
17
- # @return [Fixnum]
18
- # @!attribute [rw] needed_from_section
19
- # @return [Hash]
20
10
  # @!attribute [rw] requirements
21
- # @return [Array<Osm::Badge::Requirement>]
11
+ # @return [Array<Osm::Badge::Requirement>] the requirements of the badge
12
+ # @!attribute [rw] id
13
+ # @return [Fixnum] the badge's id in OSM
14
+ # @!attribute [rw] version
15
+ # @return [Fixnum] the version of the badge
16
+ # @!attribute [rw] identifier
17
+ # @return [String] the identifier used by OSM for this badge & version
18
+ # @!attribute [rw] group_name
19
+ # @return [String] what group (if any) this badge belongs to (eg Core, Partnership), used only for display sorting
20
+ # @!attribute [rw] latest
21
+ # @return [Boolean] whether this is the latest version of the badge
22
+ # @!attribute [rw] sharing
23
+ # @return [Symbol] the sharing status of this badge (:draft, :private, :optin, :default_locked, :optin_locked)
24
+ # @!attribute [rw] user_id
25
+ # @return [Fixnum] the OSM user who created this (version of the) badge
26
+ # @!attribute [rw] levels
27
+ # @return [Array<Fixnum>, nil] the levels available, nil if it's a single level badge
22
28
 
23
29
  attribute :name, :type => String
24
30
  attribute :requirement_notes, :type => String
25
- attribute :osm_key, :type => String
26
- attribute :osm_long_key, :type => String
27
- attribute :sections_needed, :type => Integer
28
- attribute :total_needed, :type => Integer
29
- attribute :needed_from_section, :type => Object
30
31
  attribute :requirements, :type => Object
32
+ attribute :id, :type => Integer
33
+ attribute :version, :type => Integer
34
+ attribute :identifier, :type => String
35
+ attribute :group_name, :type => String
36
+ attribute :latest, :type => Boolean
37
+ attribute :sharing, :type => Object
38
+ attribute :user_id, :type => Integer
39
+ attribute :levels, :type => Object
40
+ attribute :completion_criteria, :type => Object
31
41
 
32
42
  if ActiveModel::VERSION::MAJOR < 4
33
- attr_accessible :name, :requirement_notes, :osm_key, :osm_long_key, :sections_needed, :total_needed, :needed_from_section, :requirements
43
+ attr_accessible :name, :requirement_notes, :requirements, :id, :version, :identifier, :group_name, :latest, :sharing, :user_id, :levels, :completion_criteria
34
44
  end
35
45
 
36
- validates_numericality_of :sections_needed, :only_integer=>true, :greater_than_or_equal_to=>-1
37
- validates_numericality_of :total_needed, :only_integer=>true, :greater_than_or_equal_to=>-1
38
46
  validates_presence_of :name
39
47
  validates_presence_of :requirement_notes
40
- validates_presence_of :osm_key
41
- validates_presence_of :osm_long_key
42
- validates :needed_from_section, :hash => {:key_type => String, :value_type => Fixnum}
48
+ validates_presence_of :id
49
+ validates_presence_of :version
50
+ validates_presence_of :identifier
51
+ validates_inclusion_of :sharing, :in => [:draft, :private, :optin, :optin_locked, :default_locked]
52
+ validates_presence_of :user_id
43
53
  validates :requirements, :array_of => {:item_type => Osm::Badge::Requirement, :item_valid => true}
54
+ validates_inclusion_of :latest, :in => [true, false]
55
+ validates :levels, :array_of => {:item_type => Fixnum}, :allow_nil => true
44
56
 
45
57
 
46
58
  # @!method initialize
47
59
  # Initialize a new Badge
48
60
  # @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
49
61
 
50
-
51
62
  # Get badges
52
63
  # @param [Osm::Api] api The api to use to make the request
53
64
  # @param [Osm::Section, Fixnum, #to_i] section The section (or its ID) to get the due badges for
@@ -67,24 +78,45 @@ module Osm
67
78
 
68
79
  term_id = Osm::Term.get_current_term_for_section(api, section, options).to_i
69
80
  badges = []
81
+ badge_sharing_map = {
82
+ 'draft' => :draft,
83
+ 'private' => :private,
84
+ 'optin' => :optin,
85
+ 'optin-locked' => :optin_locked,
86
+ 'default-locked' => :default_locked
87
+ }
70
88
 
71
- data = api.perform_query("challenges.php?action=getInitialBadges&type=#{type}&sectionid=#{section.id}&section=#{section_type}&termid=#{term_id}")
89
+ data = api.perform_query("ext/badges/records/?action=getBadgeStructureByType&section=#{section_type}&type_id=#{type_id}&term_id=#{term_id}&section_id=#{section.id}")
72
90
  badge_order = data["badgeOrder"].to_s.split(',')
73
91
  structures = data["structure"] || {}
74
92
  details = data["details"] || {}
93
+
75
94
  badge_order.each do |b|
76
95
  structure = structures[b]
77
96
  detail = details[b]
78
97
  config = ActiveSupport::JSON.decode(detail['config'] || '{}')
79
98
 
80
99
  badge = new(
100
+ :id => detail['badge_id'],
101
+ :version => detail['badge_version'],
102
+ :identifier => detail['badge_identifier'],
81
103
  :name => detail['name'],
82
104
  :requirement_notes => detail['description'],
83
- :osm_key => detail['shortname'],
84
- :osm_long_key => detail['table'],
85
- :sections_needed => config['sectionsneeded'].to_i,
86
- :total_needed => config['totalneeded'].to_i,
87
- :needed_from_section => (config['sections'] || {}).inject({}) { |h,(k,v)| h[k] = v.to_i; h },
105
+ :group_name => detail['group_name'],
106
+ :latest => detail['latest'].to_i.eql?(1),
107
+ :sharing => badge_sharing_map[detail['sharing']],
108
+ :user_id => Osm.to_i_or_nil(detail['userid']),
109
+ :levels => config['levelslist'],
110
+ :completion_criteria => {
111
+ :min_modules_required => config['numModulesRequired'].to_i,
112
+ :fields_required => (config['columnsRequired'] || []).map{ |i| {id: Osm.to_i_or_nil(i['id']), min: i['min'].to_i} },
113
+ :badges_required => (config['badgesRequired'] || []).map{ |i| {id: Osm.to_i_or_nil(i['id']), version: i['version'].to_i} },
114
+ :min_requirements_completed => config['minRequirementsCompleted'].to_i,
115
+ :requires => config['requires'],
116
+ :add_columns_to_module => Osm.to_i_or_nil(config['addcolumns']),
117
+ :levels_column => Osm.to_i_or_nil(config['levels_column_id']),
118
+ :show_letters => !!config['shownumbers'],
119
+ },
88
120
  )
89
121
 
90
122
  requirements = []
@@ -93,11 +125,13 @@ module Osm
93
125
  :badge => badge,
94
126
  :name => r['name'],
95
127
  :description => r['tooltip'],
96
- :field => r['field'],
97
- :editable => r['editable'].eql?('true'),
128
+ :module_letter => r['module'],
129
+ :field => Osm::to_i_or_nil(r['field']),
130
+ :editable => r['editable'].to_s.eql?('true'),
98
131
  )
99
132
  end
100
133
  badge.requirements = requirements
134
+ badge.completion_criteria[:modules] = module_completion_data(api, badge, options)
101
135
 
102
136
  badges.push badge
103
137
  end
@@ -113,26 +147,41 @@ module Osm
113
147
  # @!macro options_get
114
148
  # @return [Array<Hash>]
115
149
  def self.get_summary_for_section(api, section, term=nil, options={})
116
- raise Error, 'This method must be called on one of the subclasses (CoreBadge, ChallengeBadge, StagedBadge or ActivityBadge)' if type.nil?
150
+ raise Error, 'This method must NOT be called on one of the subclasses(CoreBadge, ChallengeBadge, StagedBadge or ActivityBadge)' unless type.nil?
117
151
  require_ability_to(api, :read, :badge, section, options)
118
152
  section = Osm::Section.get(api, section, options) unless section.is_a?(Osm::Section)
119
153
  term_id = (term.nil? ? Osm::Term.get_current_term_for_section(api, section, options) : term).to_i
120
- cache_key = ['badge-summary', section.id, term_id, type]
154
+ cache_key = ['badge-summary', section.id, term_id]
121
155
 
122
156
  if !options[:no_cache] && Osm::Model.cache_exist?(api, cache_key)
123
157
  return cache_read(api, cache_key)
124
158
  end
125
159
 
126
160
  summary = []
127
- data = api.perform_query("challenges.php?action=summary&section=#{section.type}&sectionid=#{section.id}&termid=#{term_id}&type=#{type}")
161
+ data = api.perform_query("ext/badges/records/summary/?action=get&mode=verbose&section=#{section.type}&sectionid=#{section.id}&termid=#{term_id}")
128
162
  data['items'].each do |item|
129
163
  new_item = {
130
164
  :first_name => item['firstname'],
131
165
  :last_name => item['lastname'],
166
+ :name => "#{item['firstname']} #{item['lastname']}",
167
+ :member_id => Osm.to_i_or_nil(item['scout_id']),
132
168
  }
133
- (item.keys - ['firstname', 'lastname']).each do |key|
134
- new_item[key] = item[key]
169
+
170
+ badge_data = Hash[item.to_a.select{ |k,v| !!k.match(/\d+_\d+/) }]
171
+ badge_data.each do |badge_identifier, status|
172
+ if status.is_a?(String)
173
+ # Possible statuses: 'Started', 'Due', 'Awarded', 'Due Lvl ?' & 'Awarded Lvl ?'
174
+ case status[0]
175
+ when 'S'
176
+ new_item[badge_identifier] = :started
177
+ when 'D'
178
+ new_item[badge_identifier] = :due
179
+ when 'A'
180
+ new_item[badge_identifier] = :awarded
181
+ end
182
+ end
135
183
  end
184
+
136
185
  summary.push new_item
137
186
  end
138
187
 
@@ -151,23 +200,24 @@ module Osm
151
200
  Osm::Model.require_ability_to(api, :read, :badge, section, options)
152
201
  section = Osm::Section.get(api, section, options) unless section.is_a?(Osm::Section)
153
202
  term_id = (term.nil? ? Osm::Term.get_current_term_for_section(api, section, options) : term).to_i
154
- cache_key = ['badge_data', section.id, term_id, osm_key]
203
+ cache_key = ['badge_data', section.id, term_id, id, version]
155
204
 
156
205
  if !options[:no_cache] && cache_exist?(api, cache_key)
157
206
  return cache_read(api, cache_key)
158
207
  end
159
208
 
160
209
  datas = []
161
- data = api.perform_query("challenges.php?termid=#{term_id}&type=#{type}&section=#{section.type}&c=#{osm_key}&sectionid=#{section.id}")
210
+ data = api.perform_query("ext/badges/records/?action=getBadgeRecords&term_id=#{term_id}&section=#{section.type}&badge_id=#{id}&section_id=#{section.id}&badge_version=#{version}")
211
+
162
212
  data['items'].each do |d|
163
213
  datas.push Osm::Badge::Data.new(
164
214
  :member_id => d['scoutid'],
165
215
  :first_name => d['firstname'],
166
216
  :last_name => d['lastname'],
167
- :completed => d['completed'].to_i,
217
+ :due => d['completed'].to_i,
168
218
  :awarded => d['awarded'].to_i,
169
219
  :awarded_date => Osm.parse_date(d['awardeddate']),
170
- :requirements => d.select{ |k,v| k.include?('_') },
220
+ :requirements => Hash[d.map{ |k,v| [k.to_i, v] }].except(0),
171
221
  :section_id => section.id,
172
222
  :badge => self,
173
223
  )
@@ -177,14 +227,94 @@ module Osm
177
227
  return datas
178
228
  end
179
229
 
180
- # Compare Badge based on name then osm_key
230
+
231
+ def has_levels?
232
+ !levels.nil?
233
+ end
234
+
235
+ def module_map
236
+ @module_map ||= Hash[
237
+ *completion_criteria[:modules].map{ |m|
238
+ [m[:module_id], m[:module_letter], m[:module_letter], m[:module_id]]
239
+ }.flatten
240
+ ].except('z')
241
+ end
242
+
243
+ def needed_per_module
244
+ @needed_per_module ||= Hash[*completion_criteria[:modules].map{ |m|
245
+ [m[:module_id], m[:min_required], m[:module_letter], m[:min_required]]
246
+ }.flatten].except('z')
247
+ end
248
+
249
+
250
+ # Compare Badge based on name then id then version (desc)
181
251
  def <=>(another)
182
252
  result = self.name <=> another.try(:name)
183
- result = self.osm_key <=> another.try(:osm_key) if result == 0
253
+ result = self.id <=> another.try(:id) if result == 0
254
+ result = another.try(:version) <=> self.version if result == 0
184
255
  return result
185
256
  end
186
257
 
187
258
 
259
+ private
260
+ # return an array of hashes representing the modules of the badge
261
+ def self.module_completion_data(api, badge, options={})
262
+ fetched_this_time = @module_completion_data.nil? # Flag to ensure we only get the data once (at most) per invocation
263
+ @module_completion_data = get_module_completion_data(api, options) if fetched_this_time
264
+
265
+ if @module_completion_data[badge.id].nil? && !fetched_this_time
266
+ @module_completion_data = fetch_from_osm
267
+ fetched_this_time = true
268
+ end
269
+ data = @module_completion_data[badge.id]
270
+ raise ArgumentError, "That badge does't exist (bad ID)." if data.nil?
271
+
272
+ if data[badge.version].nil? && !fetched_this_time
273
+ @module_completion_data = fetch_from_osm
274
+ data = @module_completion_data[badge.id]
275
+ fetched_this_time = true
276
+ end
277
+ data = data[badge.version]
278
+ raise ArgumentError, "That badge does't exist (bad version)." if data.nil?
279
+ return data
280
+ end
281
+
282
+ # Return a 2 dimensional hash/array (badge ID, badge version) of hashes representing the modules
283
+ def self.get_module_completion_data(api, options={})
284
+ cache_key = ['badge_module_completion_data']
285
+ if !options[:no_cache] && cache_exist?(api, cache_key)
286
+ return cache_read(api, cache_key)
287
+ end
288
+
289
+ osm_data = api.perform_query('ext/badges/records/?action=_getModuleDetails')
290
+ osm_data = (osm_data || {})['items'] || []
291
+ osm_data.map! do |i|
292
+ {
293
+ badge_id: Osm.to_i_or_nil(i['badge_id']),
294
+ badge_version: Osm.to_i_or_nil(i['badge_version']),
295
+ module_id: Osm.to_i_or_nil(i['module_id']),
296
+ module_letter: i['module_letter'],
297
+ min_required: i['num_required'].to_i,
298
+ custom_columns: i['custom_columns'].to_i,
299
+ completed_into_column: i['completed_into_column_id'].to_i.eql?(0) ? nil : i['completed_into_column_id'].to_i,
300
+ numeric_into_column: i['numeric_into_column_id'].to_i.eql?(0) ? nil : i['numeric_into_column_id'].to_i,
301
+ add_column_id_to_numeric: i['add_column_id_to_numeric'].to_i.eql?(0) ? nil : i['add_column_id_to_numeric'].to_i,
302
+ }
303
+ end
304
+
305
+ data = {}
306
+ osm_data.each do |i|
307
+ id, version = i.values_at(:badge_id, :badge_version)
308
+ data[id] ||= []
309
+ data[id][version] ||= []
310
+ data[id][version].push i
311
+ end
312
+
313
+ cache_write(api, cache_key, data, {expires_in: 864000}) # Expire in 24 hours as this data changes really slowly
314
+ return data
315
+ end
316
+
317
+ public
188
318
  def self.type
189
319
  nil
190
320
  end
@@ -212,22 +342,24 @@ module Osm
212
342
  # @!attribute [rw] description
213
343
  # @return [String] a description of the badge
214
344
  # @!attribute [rw] field
215
- # @return [String] the field for the requirement (passed to OSM)
345
+ # @return [Fixnum] the field for the requirement (passed to OSM)
216
346
  # @!attribute [rw] editable
217
347
  # @return [Boolean]
218
348
 
219
349
  attribute :badge, :type => Object
220
350
  attribute :name, :type => String
221
351
  attribute :description, :type => String
222
- attribute :field, :type => String
352
+ attribute :module_letter, :type => String
353
+ attribute :field, :type => Integer
223
354
  attribute :editable, :type => Boolean
224
355
 
225
356
  if ActiveModel::VERSION::MAJOR < 4
226
- attr_accessible :name, :description, :field, :editable, :badge
357
+ attr_accessible :name, :description, :field, :editable, :badge, :module_letter
227
358
  end
228
359
 
229
360
  validates_presence_of :name
230
361
  validates_presence_of :description
362
+ validates_presence_of :module_letter
231
363
  validates_presence_of :field
232
364
  validates_presence_of :badge
233
365
  validates_inclusion_of :editable, :in => [true, false]
@@ -244,7 +376,7 @@ module Osm
244
376
  end
245
377
 
246
378
  def inspect
247
- Osm.inspect_instance(self, options={:replace_with => {'badge' => :osm_key}})
379
+ Osm.inspect_instance(self, options={:replace_with => {'badge' => :identifier}})
248
380
  end
249
381
 
250
382
  end # Class Requirement
@@ -257,8 +389,8 @@ module Osm
257
389
  # @return [Fixnum] the member's first name
258
390
  # @!attribute [rw] last_name
259
391
  # @return [Fixnum] Ithe member's last name
260
- # @!attribute [rw] completed
261
- # @return [Fixnum] whether this badge has been completed (i.e. it is due?), number indicates stage if appropriate
392
+ # @!attribute [rw] due
393
+ # @return [Fixnum] whether this badge is due according to OSM, number indicates stage if appropriate
262
394
  # @!attribute [rw] awarded
263
395
  # @return [Date] the last stage awarded
264
396
  # @!attribute [rw] awarded_date
@@ -273,7 +405,7 @@ module Osm
273
405
  attribute :member_id, :type => Integer
274
406
  attribute :first_name, :type => String
275
407
  attribute :last_name, :type => String
276
- attribute :completed, :type => Integer, :default => 0
408
+ attribute :due, :type => Integer, :default => 0
277
409
  attribute :awarded, :type => Integer, :default => 0
278
410
  attribute :awarded_date, :type => Date, :default => nil
279
411
  attribute :requirements, :type => Object, :default => DirtyHashy.new
@@ -281,23 +413,18 @@ module Osm
281
413
  attribute :badge, :type => Object
282
414
 
283
415
  if ActiveModel::VERSION::MAJOR < 4
284
- attr_accessible :member_id, :first_name, :last_name, :completed, :awarded, :awarded_date, :requirements, :section_id, :badge
416
+ attr_accessible :member_id, :first_name, :last_name, :due, :awarded, :awarded_date, :requirements, :section_id, :badge
285
417
  end
286
418
 
287
419
  validates_presence_of :badge
288
420
  validates_presence_of :first_name
289
421
  validates_presence_of :last_name
290
- validates_numericality_of :completed, :only_integer=>true, :greater_than_or_equal_to=>0
422
+ validates_numericality_of :due, :only_integer=>true, :greater_than_or_equal_to=>0
291
423
  validates_numericality_of :awarded, :only_integer=>true, :greater_than_or_equal_to=>0
292
424
  validates_numericality_of :member_id, :only_integer=>true, :greater_than=>0
293
425
  validates_numericality_of :section_id, :only_integer=>true, :greater_than=>0
294
- validates :requirements, :hash => {:key_type => String, :value_type => String}
426
+ validates :requirements, :hash => {:key_type => Fixnum, :value_type => String}
295
427
 
296
- STAGES = {
297
- 'nightsaway' => [1, 2, 3, 4, 5, 10, 15, 20, 35, 50, 75, 100, 125, 150, 175, 200],
298
- 'hikes' => [1, 2, 5, 10, 15, 20, 35, 50],
299
- 'timeonthewater' => [1, 2, 5, 10, 15, 20, 35, 50],
300
- }
301
428
 
302
429
  # @!method initialize
303
430
  # Initialize a new Badge
@@ -316,169 +443,180 @@ module Osm
316
443
  # @return [Fixnum] the total number of requirements considered gained
317
444
  def total_gained
318
445
  count = 0
319
- requirements.each do |field, data|
320
- next unless reguiremet_met?(data)
446
+ requirements.each do |field_id, data|
447
+ next unless requirement_met?(data)
321
448
  count += 1
322
449
  end
323
450
  return count
324
451
  end
325
452
 
326
- # Get the total number of sections gained
327
- # @return [Hash]
328
- def sections_gained
329
- required = badge.needed_from_section
330
- gained = gained_in_sections
331
- count = 0
332
-
333
- required.each do |section, needed|
334
- next if gained[section] < needed
335
- count += 1
453
+ # Get the number of modules gained
454
+ # @return [Fixnum]
455
+ def modules_gained
456
+ needed = badge.needed_per_module
457
+ modules = []
458
+
459
+ gained_in_modules.each do |module_id, gained|
460
+ next unless module_id.is_a?(Fixnum)
461
+ next if gained < needed[module_id]
462
+ module_letter = badge.module_map[module_id]
463
+ modules.push module_letter unless module_letter >= 'y'
336
464
  end
337
- return count
465
+
466
+ return modules
338
467
  end
339
468
 
340
- # Get the number of requirements gained in each section
469
+ # Get the number of requirements gained in each module
341
470
  # @return [Hash]
342
- def gained_in_sections
471
+ def gained_in_modules
343
472
  count = {}
344
- requirements.each do |field, data|
345
- field = field.split('_')[0]
346
- unless field.eql?('y')
347
- count[field] ||= 0
348
- next unless reguiremet_met?(data)
349
- count[field] += 1
350
- else
351
- # A total 'section'
352
- count['y'] = data.to_i
353
- end
473
+ badge.requirements.each do |requirement|
474
+ count[requirement.module_letter] ||= 0
475
+ next unless requirement_met?(requirements[requirement.field])
476
+ count[requirement.module_letter] += 1
354
477
  end
355
- return count
356
- end
357
-
358
- # Check if this badge is due (according data retrieved from OSM)
359
- # @return [Boolean] whether the badge is due to the member
360
- def due?
361
- completed > awarded
478
+ Hash[*count.map{ |k,v| [badge.module_map[k], v, k, v] }.flatten]
362
479
  end
363
480
 
364
481
 
365
482
  # Check if this badge has been earnt
366
- # @return [Boolean] whether the badge is due to the member
483
+ # @return [Boolean] whether the badge has been earnt (ignores other badge's and their requirements which might be needed)
367
484
  def earnt?
368
- if badge.type == :staged
369
- return (earnt > awarded)
370
- end
371
- return false if (completed.eql?(1) && awarded.eql?(1))
372
- return true if (completed.eql?(1) && awarded.eql?(0))
373
- if badge.sections_needed == -1 # require all sections
374
- return (sections_gained == badge.needed_from_section.keys.size)
485
+ if badge.has_levels?
486
+ return earnt > awarded
375
487
  else
376
- return (total_gained >= badge.total_needed) && (sections_gained >= badge.sections_needed)
488
+ return false if (due.eql?(1) && awarded.eql?(1))
489
+ return true if (due.eql?(1) && awarded.eql?(0))
490
+
491
+ criteria = badge.completion_criteria
492
+ earnt = true
493
+ if criteria[:min_modules_required] > 0
494
+ earnt &= (modules_gained.size >= criteria[:min_modules_required])
495
+ end
496
+ if criteria[:min_requirements_completed] > 0
497
+ earnt &= (total_gained >= criteria[:min_requirements_completed])
498
+ end
499
+ if criteria[:requires]
500
+ # [['a'], ['b', 'c']] = a and (b or c)
501
+ requires = criteria[:requires].clone
502
+ modules = modules_gained
503
+ requires.map!{ |a| a.map{ |b| modules.include?(b) } } # Replace letters with true/false
504
+ requires.map!{ |a| a.include?(true) } # Replace each combination with true/false
505
+ earnt &= !requires.include?(false) # Only earnt if all combinations are met
506
+ end
507
+ criteria[:badges_required].each do |b|
508
+ # {:id => ###, :version => #}
509
+ #TODO
510
+ end
511
+ criteria[:fields_required].each do |c|
512
+ # {:id => ###, :min => #}
513
+ #TODO
514
+ end
515
+ return earnt
377
516
  end
378
517
  end
379
518
 
519
+
380
520
  # Get what stage which has most recently been earnt
381
521
  # (using #earnt? will tell you if it's still due (not yet awarded))
382
522
  # @return [Fixnum] the stage which has most recently been due
383
523
  def earnt
384
- unless badge.type == :staged
524
+ unless badge.has_levels?
385
525
  return earnt? ? 1 : 0
386
526
  end
387
- if STAGES.keys.include?(badge.osm_key)
388
- total_done = requirements['y_01']
389
- stages = STAGES[badge.osm_key]
390
- stages.reverse_each do |stage|
391
- return stage if total_done >= stage
527
+
528
+ levels_column = badge.completion_criteria[:levels_column_id]
529
+ unless badge.completion_criteria[:show_letters] # It's a hikes, nights type badge
530
+ badge.levels.reverse_each do |level|
531
+ return level if requirements[levels_column].to_i >= level
392
532
  end
393
- else
394
- (awarded..5).reverse_each do |stage|
395
- group = 'abcde'[stage - 1]
396
- if gained_in_sections[group] >= badge.needed_from_section[group]
397
- return stage
398
- end
533
+ else # It's an activity type badge
534
+ modules = modules_gained
535
+ letters = ('a'..'z').to_a
536
+ (awarded..badge.levels.last).reverse_each do |level|
537
+ return level if modules.include?(letters[level - 1])
399
538
  end
400
539
  end
401
540
  return 0
402
541
  end
403
542
 
543
+
404
544
  # Check if this badge has been started
405
545
  # @return [Boolean] whether the badge has been started by the member (always false if the badge has been completed)
406
546
  def started?
407
- return (started > completed) if badge.type.eql?(:staged) # It's a staged badge
408
- return false if completed?
547
+ if badge.has_levels?
548
+ return (started > due)
549
+ end
550
+ return false if due?
409
551
  requirements.each do |key, value|
410
- case key.split('_')[0]
411
- when 'a'
412
- return true if reguiremet_met?(value)
413
- when 'y'
414
- return true if (value.to_i > 0)
415
- end
552
+ return true if requirement_met?(value)
416
553
  end
417
554
  return false
418
555
  end
419
556
 
557
+
420
558
  # Get which stage has been started
421
559
  # @return [Fixnum] which stage of the badge has been started by the member (lowest)
422
560
  def started
423
- unless badge.type == :staged
561
+ unless badge.has_levels?
424
562
  return started? ? 1 : 0
563
+ end
564
+ unless badge.completion_criteria[:show_letters]
565
+ # Nights, Hikes or Water
566
+ done = requirements[badge.completion_criteria[:levels_column_id]].to_i
567
+ levels = badge.levels # e.g. [0,1,2,3,4,5,10]
568
+ return 0 if levels.include?(done) # Has achieved a level (and not started next )
569
+ return 0 if done >= levels[-1] # No more levels to do
570
+ (1..(levels.size-1)).to_a.reverse_each do |i| # indexes from last to 2nd
571
+ this_level = levels[i]
572
+ previous_level = levels[i-1]
573
+ return this_level if (done < this_level && done > previous_level) # this_level has been started (and not finished)
574
+ end
575
+ return 0 # No reason we should ever get here
425
576
  else
426
- # Staged badge
427
- if STAGES.keys.include?(badge.osm_key) # Special staged badges
428
- stages = STAGES[badge.osm_key]
429
- done = requirements['y_01'].to_i
430
- return 0 if done < stages[0] # Not started the first stage
431
- return 0 if done >= stages[stages.size - 1] # No more stages can be started
432
- (1..stages.size-1).reverse_each do |index|
433
- if (done < stages[index]) && (done > stages[index-1])
434
- return stages[index]
435
- end
436
- end
437
- else
438
- # 'Normal' staged badge
439
- return 0 if completed == 5 || awarded == 5 # No more stages can be started
440
- start_group = 'abcde'[completed] # Requirements use the group letter to denote stage
441
- started = 'z'
442
- requirements.each do |key, value|
443
- next if key[0] < start_group # This stage is marked as completed
444
- next if key[0] > started # This stage is after the stage currently started
445
- started = key[0] unless value.blank? || value.to_s[0].downcase.eql?('x')
577
+ # 'Normal' staged
578
+ letters = ('a'..'z').to_a
579
+ top_level = badge.levels[-1]
580
+ return 0 if due == top_level || awarded == top_level # No more levels to do
581
+ ((due + 1)..top_level).reverse_each do |level|
582
+ badge.requirements.each do |requirement|
583
+ next unless requirement.module_letter.eql?(letters[level - 1]) # Not interested in other levels
584
+ return level if requirement_met?(requirements[requirement.field])
446
585
  end
447
- return started.eql?('z') ? 0 : 'abcde'.index(started)+1
448
586
  end
449
- return 0
587
+ return 0 # No levels started
450
588
  end
451
589
  end
452
590
 
591
+
453
592
  # Mark the badge as awarded in OSM
454
593
  # @param [Osm::Api] api The api to use to make the request
455
594
  # @param [Date] date The date to mark the badge as awarded
456
- # @param [Fixnum] level The level of the badge to award (1 for non-staged badges)
457
- # @param [Symbol] mark_as :awarded or :due
595
+ # @param [Fixnum] level The level of the badge to award (1 for non-staged badges), setting the level to 0 unawards the badge
458
596
  # @return [Boolean] whether the data was updated in OSM
459
- def mark_awarded(api, date=Date.today, level=completed, mark_as=:awarded)
597
+ def mark_awarded(api, date=Date.today, level=due)
460
598
  raise ArgumentError, 'date is not a Date' unless date.is_a?(Date)
461
- raise ArgumentError, 'mark_as is not an allowed value, use :awarded or :du' unless [:awarded, :due].include?(mark_as)
462
599
  raise ArgumentError, 'level can not be negative' if level < 0
463
600
  section = Osm::Section.get(api, section_id)
464
601
  require_ability_to(api, :write, :badge, section)
465
602
 
466
603
  date_formatted = date.strftime(Osm::OSM_DATE_FORMAT)
467
-
468
- result = api.perform_query("challenges.php?action=award", {
469
- 'dateAwarded' => date_formatted,
604
+ entries = [{
605
+ 'badge_id' => badge.id.to_s,
606
+ 'badge_version' => badge.version.to_s,
607
+ 'scout_id' => member_id.to_s,
608
+ 'level' => level.to_s
609
+ }]
610
+
611
+ result = api.perform_query("ext/badges/records/?action=awardBadge", {
612
+ 'date' => date_formatted,
470
613
  'sectionid' => section_id,
471
- 'section' => section.type,
472
- 'chal' => badge.osm_key,
473
- 'type' => badge.type,
474
- 'stagedLevel' => level,
475
- 'due' => mark_as,
614
+ 'entries' => entries.to_json
476
615
  })
477
- updated = result.is_a?(Array) &&
478
- result[0].is_a?(Hash) &&
479
- (result[0]['sid'].to_i == member_id) &&
480
- (result[0]['awarded'].to_i == level) &&
481
- (result[0]['awardeddate'] == date_formatted)
616
+ updated = result.is_a?(Hash) &&
617
+ (result['scoutid'].to_i == member_id) &&
618
+ (result['awarded'].to_i == level) &&
619
+ (result['awardeddate'] == date_formatted)
482
620
 
483
621
  if updated
484
622
  awarded = level
@@ -487,12 +625,41 @@ module Osm
487
625
  return updated
488
626
  end
489
627
 
628
+ # Mark the badge as not awarded in OSM
629
+ # @param [Osm::Api] api The api to use to make the request
630
+ # @return [Boolean] whether the data was updated in OSM
631
+ def mark_not_awarded(api)
632
+ mark_awarded(api, Date.today, 0)
633
+ end
634
+
635
+
490
636
  # Mark the badge as due in OSM
491
637
  # @param [Osm::Api] api The api to use to make the request
492
- # @param [Fixnum] level The level of the badge to mark as due (1 for non-staged badges)
638
+ # @param [Fixnum] level The level of the badge to award (1 for non-staged badges), setting the level to 0 unawards the badge
639
+ # @return [Boolean] whether the data was updated in OSM
640
+ def mark_due(api, level=completed)
641
+ raise ArgumentError, 'level can not be negative' if level < 0
642
+ section = Osm::Section.get(api, section_id)
643
+ require_ability_to(api, :write, :badge, section)
644
+
645
+ result = api.perform_query("ext/badges/records/?action=overrideCompletion", {
646
+ 'section_id' => section.id,
647
+ 'badge_id' => badge.id,
648
+ 'badge_version' => badge.version,
649
+ 'scoutid' => member_id,
650
+ 'level' => level
651
+ })
652
+ updated = result.is_a?(Hash) &&
653
+ (result['scoutid'].to_i == member_id) &&
654
+ (result['completed'].to_i == level)
655
+ return updated
656
+ end
657
+
658
+ # Mark the badge as not due in OSM
659
+ # @param [Osm::Api] api The api to use to make the request
493
660
  # @return [Boolean] whether the data was updated in OSM
494
- def mark_due(api, level)
495
- mark_awarded(api, Date.today, level, :due)
661
+ def mark_not_due(api)
662
+ mark_due(api, 0)
496
663
  end
497
664
 
498
665
  # Update data in OSM
@@ -504,37 +671,47 @@ module Osm
504
671
  section = Osm::Section.get(api, section_id)
505
672
  require_ability_to(api, :write, :badge, section)
506
673
 
507
- updated = true
508
- editable_fields = badge.requirements.select{ |r| r.editable }.map{ |r| r.field}
674
+ # Update requirements that changed
675
+ requirements_updated = true
676
+ editable_fields = badge.requirements.select{ |r| r.editable }.map{ |r| r.field }
509
677
  requirements.changes.each do |field, (was,now)|
510
678
  if editable_fields.include?(field)
511
- result = api.perform_query("challenges.php?type=#{badge.class.type}&section=#{section.type}", {
512
- 'action' => 'updatesingle',
513
- 'id' => member_id,
514
- 'col' => field,
515
- 'value' => now,
516
- 'chal' => badge.osm_key,
517
- 'sectionid' => section_id,
679
+ result = api.perform_query("ext/badges/records/?action=updateSingleRecord", {
680
+ 'scoutid' => member_id,
681
+ 'section_id' => section_id,
682
+ 'badge_id' => badge.id,
683
+ 'badge_version' => badge.version,
684
+ 'field' => field,
685
+ 'value' => now
518
686
  })
519
- updated = false unless result.is_a?(Hash) &&
520
- (result['sid'].to_i == member_id) &&
521
- (result[field] == now)
687
+ requirements_updated = false unless result.is_a?(Hash) &&
688
+ (result['scoutid'].to_i == member_id) &&
689
+ (result[field.to_s] == now)
522
690
  end
523
691
  end
524
692
 
525
- if updated
693
+ if requirements_updated
526
694
  requirements.clean_up!
527
695
  end
528
696
 
697
+ # Update due if it changed
698
+ due_updated = true
699
+ if changed_attributes.include?('due')
700
+ due_updated = mark_due(api, due)
701
+ end
702
+
703
+ # Update awarded if it changed
704
+ awarded_updated = true
529
705
  if changed_attributes.include?('awarded') || changed_attributes.include?('awarded_date')
530
- if mark_awarded(api, awarded_date, awarded)
531
- reset_changed_attributes
532
- else
533
- updated = false
534
- end
706
+ awarded_updated = mark_awarded(api, awarded_date, awarded)
535
707
  end
536
708
 
537
- return updated
709
+ # reset changed attributes if everything was updated ok
710
+ if due_updated && awarded_updated
711
+ reset_changed_attributes
712
+ end
713
+
714
+ return requirements_updated && due_updated && awarded_updated
538
715
  end
539
716
 
540
717
  # Compare Badge::Data based on badge, section_id then member_id
@@ -546,11 +723,11 @@ module Osm
546
723
  end
547
724
 
548
725
  def inspect
549
- Osm.inspect_instance(self, options={:replace_with => {'badge' => :osm_key}})
726
+ Osm.inspect_instance(self, options={:replace_with => {'badge' => :name}})
550
727
  end
551
728
 
552
729
  private
553
- def reguiremet_met?(data)
730
+ def requirement_met?(data)
554
731
  return false if data == 0
555
732
  !(data.blank? || data.to_s[0].downcase.eql?('x'))
556
733
  end
@@ -565,6 +742,9 @@ module Osm
565
742
  def self.type
566
743
  :core
567
744
  end
745
+ def self.type_id
746
+ 4
747
+ end
568
748
  end # Class CoreBadge
569
749
 
570
750
  class ChallengeBadge < Osm::Badge
@@ -572,6 +752,9 @@ module Osm
572
752
  def self.type
573
753
  :challenge
574
754
  end
755
+ def self.type_id
756
+ 1
757
+ end
575
758
  end # Class ChallengeBadge
576
759
 
577
760
  class StagedBadge < Osm::Badge
@@ -579,6 +762,9 @@ module Osm
579
762
  def self.type
580
763
  :staged
581
764
  end
765
+ def self.type_id
766
+ 3
767
+ end
582
768
  end # Class StagedBadge
583
769
 
584
770
  class ActivityBadge < Osm::Badge
@@ -586,6 +772,9 @@ module Osm
586
772
  def self.type
587
773
  :activity
588
774
  end
775
+ def self.type_id
776
+ 2
777
+ end
589
778
  def self.subscription_required
590
779
  :silver
591
780
  end