osm 1.2.14 → 1.2.15.dev

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.
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