lib/osm/event.rb
module Osm
class Event < Osm::Model
class BadgeLink < Osm::Model; end # Ensure the constant exists for the validators
class Column < Osm::Model; end # Ensure the constant exists for the validators
LIST_ATTRIBUTES = [:id, :section_id, :name, :start, :finish, :cost, :location, :notes, :archived, :public_notepad, :confirm_by_date, :allow_changes, :reminders, :attendance_limit, :attendance_limit_includes_leaders, :attendance_reminder, :allow_booking]
EXTRA_ATTRIBUTES = [:notepad, :columns, :badges]
SORT_BY = [:start, :name, :id]
# @!attribute [rw] id
# @return [Fixnum] the id for the event
# @!attribute [rw] section_id
# @return [Fixnum] the id for the section
# @!attribute [rw] name
# @return [String] the name of the event
# @!attribute [rw] start
# @return [DateTime] when the event starts
# @!attribute [rw] finish
# @return [DateTime] when the event ends
# @!attribute [rw] cost
# @return [String] the cost of the event (formatted to \d+\.\d{2}) or "TBC"
# @!attribute [rw] location
# @return [String] where the event is
# @!attribute [rw] notes
# @return [String] notes about the event
# @!attribute [rw] archived
# @return [Boolean] if the event has been archived
# @!attribute [rw] badges
# @return [Array<Osm::Event::BadgeLink>] the badge links for the event
# @!attribute [rw] files
# @return [Array<String>] the files attached to this event
# @!attribute [rw] columns
# @return [Array<Osm::Event::Column>] the custom columns for the event
# @!attribute [rw] notepad
# @return [String] notepad for the event
# @!attribute [rw] public_notepad
# @return [String] public notepad (shown in My.SCOUT) for the event
# @!attribute [rw] confirm_by_date
# @return [Date] the date parents can no longer add/change their child's details
# @!attribute [rw] allow_changes
# @return [Boolean] whether parent's can change their child's details
# @!attribute [rw] reminders
# @return [Boolean] whether email reminders are sent for the event
# @!attribute [rw] attendance_limit
# @return [Fixnum] the maximum number of people who can attend the event (0 = no limit)
# @!attendance [rw] attendance_limit_includes_leaders
# @return [Boolean] whether the attendance limit includes leaders
# @!attribute [rw] attendance_reminder
# @return [Fixnum] how many days before the event to send a reminder to those attending (0 (off), 1, 3, 7, 14, 21, 28)
# @!attribute [rw] allow_booking
# @return [Boolean] whether booking is allowed through My.SCOUT
attribute :id, :type => Integer
attribute :section_id, :type => Integer
attribute :name, :type => String
attribute :start, :type => DateTime
attribute :finish, :type => DateTime
attribute :cost, :type => String, :default => 'TBC'
attribute :location, :type => String, :default => ''
attribute :notes, :type => String, :default => ''
attribute :archived, :type => Boolean, :default => false
attribute :badges, :default => []
attribute :files, :default => []
attribute :columns, :default => []
attribute :notepad, :type => String, :default => ''
attribute :public_notepad, :type => String, :default => ''
attribute :confirm_by_date, :type => Date
attribute :allow_changes, :type => Boolean, :default => false
attribute :reminders, :type => Boolean, :default => true
attribute :attendance_limit, :type => Integer, :default => 0
attribute :attendance_limit_includes_leaders, :type => Boolean, :default => false
attribute :attendance_reminder, :type => Integer, :default => 0
attribute :allow_booking, :type => Boolean, :default => true
if ActiveModel::VERSION::MAJOR < 4
attr_accessible :id, :section_id, :name, :start, :finish, :cost, :location, :notes, :archived,
:fields, :badges, :files, :columns, :notepad, :public_notepad, :confirm_by_date,
:allow_changes, :reminders, :attendance_limit, :attendance_limit_includes_leaders,
:attendance_reminder, :allow_booking
end
validates_numericality_of :id, :only_integer=>true, :greater_than=>0, :allow_nil => true
validates_numericality_of :section_id, :only_integer=>true, :greater_than=>0
validates_numericality_of :attendance_limit, :only_integer=>true, :greater_than_or_equal_to=>0
validates_presence_of :name
validates :badges, :array_of => {:item_type => Osm::Event::BadgeLink, :item_valid => true}
validates :columns, :array_of => {:item_type => Osm::Event::Column, :item_valid => true}
validates :files, :array_of => {:item_type => String}
validates_inclusion_of :allow_changes, :in => [true, false]
validates_inclusion_of :reminders, :in => [true, false]
validates_inclusion_of :attendance_limit_includes_leaders, :in => [true, false]
validates_inclusion_of :allow_booking, :in => [true, false]
validates_inclusion_of :attendance_reminder, :in => [0, 1, 3, 7, 14, 21, 28]
validates_format_of :cost, :with => /\A(?:\d+\.\d{2}|TBC)\Z/
# @!method initialize
# Initialize a new Event
# @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
# Get events for a section
# @param [Osm::Api] api The api to use to make the request
# @param [Osm::Section, Fixnum, #to_i] section The section (or its ID) to get the events for
# @!macro options_get
# @option options [Boolean] :include_archived (optional) if true then archived events will also be returned
# @return [Array<Osm::Event>]
def self.get_for_section(api, section, options={})
require_ability_to(api, :read, :events, section, options)
section_id = section.to_i
cache_key = ['events', section_id]
events = nil
if !options[:no_cache] && cache_exist?(api, cache_key)
ids = cache_read(api, cache_key)
events = get_from_ids(api, ids, 'event', section, options, :get_for_section)
end
if events.nil?
data = api.perform_query("events.php?action=getEvents§ionid=#{section_id}&showArchived=true")
events = Array.new
ids = Array.new
unless data['items'].nil?
data['items'].map { |i| i['eventid'].to_i }.each do |event_id|
event_data = api.perform_query("events.php?action=getEvent§ionid=#{section_id}&eventid=#{event_id}")
files_data = api.perform_query("ext/uploads/events/?action=listAttachments§ionid=#{section_id}&eventid=#{event_id}")
files = files_data.is_a?(Hash) ? files_data['files'] : files_data
files = [] unless files.is_a?(Array)
event = self.new_event_from_data(event_data)
event.files = files
events.push event
ids.push event.id
cache_write(api, ['event', event.id], event)
end
end
cache_write(api, cache_key, ids)
end
return events if options[:include_archived]
return events.reject do |event|
event.archived?
end
end
# Get event list for a section (not all details for each event)
# @param [Osm::Api] api The api to use to make the request
# @param [Osm::Section, Fixnum, #to_i] section The section (or its ID) to get the events for
# @!macro options_get
# @return [Array<Hash>]
def self.get_list(api, section, options={})
require_ability_to(api, :read, :events, section, options)
section_id = section.to_i
cache_key = ['events_list', section_id]
events_cache_key = ['events', section_id]
events = nil
unless options[:no_cache]
# Try getting from cache
if cache_exist?(api, cache_key)
return cache_read(api, cache_key)
end
# Try generating from cached events
if cache_exist?(api, events_cache_key)
ids = cache_read(api, events_cache_key)
events = get_from_ids(api, ids, 'event', section, options, :get_for_section).map do |e|
e.attributes.symbolize_keys.select do |k,v|
LIST_ATTRIBUTES.include?(k)
end
end
end
end
# Fetch from OSM
if events.nil?
data = api.perform_query("events.php?action=getEvents§ionid=#{section_id}&showArchived=true")
events = Array.new
unless data['items'].nil?
data['items'].map do |event_data|
events.push(attributes_from_data(event_data))
end
end
end
cache_write(api, cache_key, events)
return events
end
# Get an event
# @param [Osm::Api] api The api to use to make the request
# @param [Osm::Section, Fixnum, #to_i] section The section (or its ID) to get the events for
# @param [Fixnum, #to_i] event_id The id of the event to get
# @!macro options_get
# @return [Osm::Event, nil] the event (or nil if it couldn't be found
def self.get(api, section, event_id, options={})
require_ability_to(api, :read, :events, section, options)
section_id = section.to_i
event_id = event_id.to_i
cache_key = ['event', event_id]
if !options[:no_cache] && cache_exist?(api, cache_key)
return cache_read(api, cache_key)
end
event_data = api.perform_query("events.php?action=getEvent§ionid=#{section_id}&eventid=#{event_id}")
return self.new_event_from_data(event_data)
end
# Create an event in OSM
# If something goes wrong adding badges to OSM then the event returned will have been read from OSM
# @param [Osm::Api] api The api to use to make the request
# @return [Osm::Event, nil] the created event, nil if failed
# @raise [Osm::ObjectIsInvalid] If the Event is invalid
def self.create(api, parameters)
require_ability_to(api, :write, :events, parameters[:section_id])
event = new(parameters)
raise Osm::ObjectIsInvalid, 'event is invalid' unless event.valid?
data = api.perform_query("events.php?action=addEvent§ionid=#{event.section_id}", {
'name' => event.name,
'location' => event.location,
'startdate' => event.start? ? event.start.strftime(Osm::OSM_DATE_FORMAT) : '',
'enddate' => event.finish? ? event.finish.strftime(Osm::OSM_DATE_FORMAT) : '',
'cost' => event.cost_tbc? ? '-1' : event.cost,
'notes' => event.notes,
'starttime' => event.start? ? event.start.strftime(Osm::OSM_TIME_FORMAT) : '',
'endtime' => event.finish? ? event.finish.strftime(Osm::OSM_TIME_FORMAT) : '',
'confdate' => event.confirm_by_date? ? event.confirm_by_date.strftime(Osm::OSM_DATE_FORMAT) : '',
'allowChanges' => event.allow_changes ? 'true' : 'false',
'disablereminders' => !event.reminders ? 'true' : 'false',
'attendancelimit' => event.attendance_limit,
'attendancereminder' => event.attendance_reminder,
'limitincludesleaders' => event.attendance_limit_includes_leaders ? 'true' : 'false',
'allowbooking' => event.allow_booking ? 'true' : 'false',
})
if (data.is_a?(Hash) && data.has_key?('id'))
event.id = data['id'].to_i
# The cached events for the section will be out of date - remove them
cache_delete(api, ['events', event.section_id])
# Add badge links to OSM
badges_created = true
event.badges.each do |badge|
badges_created &= event.add_badge_link(api, badge)
end
if badges_created
cache_write(api, ['event', event.id], event)
return event
else
# Someting went wrong adding badges so return what OSM has
return get(api, event.section_id, event.id)
end
else
return nil
end
end
# Update event in OSM
# @param [Osm::Api] api The api to use to make the request
# @return [Boolean] whether the update succedded (will return true if no updates needed to be made)
def update(api)
require_ability_to(api, :write, :events, section_id)
updated = true
# Update main attributes
update_main_attributes = false
%w{ id name location start finish cost cost_tbc notes confirm_by_date allow_changes reminders attendance_limit attendance_limit_includes_leaders allow_booking }.each do |a|
if changed_attributes.include?(a)
update_main_attributes = true
break # no use checking the others
end
end
if update_main_attributes
data = api.perform_query("events.php?action=addEvent§ionid=#{section_id}", {
'eventid' => id,
'name' => name,
'location' => location,
'startdate' => start? ? start.strftime(Osm::OSM_DATE_FORMAT) : '',
'enddate' => finish? ? finish.strftime(Osm::OSM_DATE_FORMAT) : '',
'cost' => cost_tbc? ? '-1' : cost,
'notes' => notes,
'starttime' => start? ? start.strftime(Osm::OSM_TIME_FORMAT) : '',
'endtime' => finish? ? finish.strftime(Osm::OSM_TIME_FORMAT) : '',
'confdate' => confirm_by_date? ? confirm_by_date.strftime(Osm::OSM_DATE_FORMAT) : '',
'allowChanges' => allow_changes ? 'true' : 'false',
'disablereminders' => !reminders ? 'true' : 'false',
'attendancelimit' => attendance_limit,
'attendancereminder' => attendance_reminder,
'limitincludesleaders' => attendance_limit_includes_leaders ? 'true' : 'false',
'allowbooking' => allow_booking ? 'true' : 'false',
})
updated &= data.is_a?(Hash) && (data['id'].to_i == id)
end
# Private notepad
if changed_attributes.include?('notepad')
data = api.perform_query("events.php?action=saveNotepad§ionid=#{section_id}", {
'eventid' => id,
'notepad' => notepad,
})
updated &= data.is_a?(Hash)
end
# MySCOUT notepad
if changed_attributes.include?('public_notepad')
data = api.perform_query("events.php?action=saveNotepad§ionid=#{section_id}", {
'eventid' => id,
'pnnotepad' => public_notepad,
})
updated &= data.is_a?(Hash)
end
# Badges
if changed_attributes.include?('badges')
original_badges = @original_attributes['badges'] || []
# Deleted badges
badges_to_delete = []
original_badges.each do |badge|
unless badges.include?(badge)
badges_to_delete.push badge
end
end
badges_to_delete.each do |badge|
data = api.perform_query("ext/badges/records/index.php?action=deleteBadgeLink§ionid=#{section_id}", {
'section' => badge.badge_section,
'sectionid' => section_id,
'type' => 'event',
'id' => id,
'badge_id' => badge.badge_id,
'badge_version' => badge.badge_version,
'column_id' => badge.requirement_id,
})
updated &= data.is_a?(Hash) && data['status']
end
# Added badges
badges.each do |badge|
unless original_badges.include?(badge)
updated &= add_badge_link(api, badge)
end
end
end # includes badges
if updated
reset_changed_attributes
# The cached event will be out of date - remove it
cache_delete(api, ['event', id])
return true
else
return false
end
end
# Delete event from OSM
# @param [Osm::Api] api The api to use to make the request
# @return [Boolean] whether the delete succedded
def delete(api)
require_ability_to(api, :write, :events, section_id)
data = api.perform_query("events.php?action=deleteEvent§ionid=#{section_id}&eventid=#{id}")
if data.is_a?(Hash) && data['ok']
cache_delete(api, ['event', id])
return true
end
return false
end
# Get event attendance
# @param [Osm::Api] api The api to use to make the request
# @param [Osm::Term, Fixnum, #to_i, nil] term The term (or its ID) to get the members for, passing nil causes the current term to be used
# @!macro options_get
# @option options [Boolean] :include_archived (optional) if true then archived activities will also be returned
# @return [Array<Osm::Event::Attendance>]
def get_attendance(api, term=nil, options={})
require_ability_to(api, :read, :events, section_id, options)
term_id = term.nil? ? Osm::Term.get_current_term_for_section(api, section_id).id : term.to_i
cache_key = ['event_attendance', id, term_id]
if !options[:no_cache] && cache_exist?(api, cache_key)
return cache_read(api, cache_key)
end
data = api.perform_query("events.php?action=getEventAttendance&eventid=#{id}§ionid=#{section_id}&termid=#{term_id}")
data = data['items'] || []
payment_values = {
'Manual' => :manual,
'Automatic' => :automatic,
}
attending_values = {
'Yes' => :yes,
'No' => :no,
'Invited' => :invited,
'Show in My.SCOUT' => :shown,
'Reserved' => :reserved,
}
attendance = []
data.each_with_index do |item, index|
attendance.push Osm::Event::Attendance.new(
:event => self,
:member_id => Osm::to_i_or_nil(item['scoutid']),
:grouping_id => Osm::to_i_or_nil(item['patrolid'].eql?('') ? nil : item['patrolid']),
:first_name => item['firstname'],
:last_name => item['lastname'],
:date_of_birth => item['dob'].nil? ? nil : Osm::parse_date(item['dob'], :ignore_epoch => true),
:attending => attending_values[item['attending']],
:payment_control => payment_values[item['payment']],
:fields => item.select { |key, value| key.to_s.match(/\Af_\d+\Z/) }
.inject({}){ |h,(k,v)| h[k[2..-1].to_i] = v; h },
:payments => item.select { |key, value| key.to_s.match(/\Ap\d+\Z/) }
.inject({}){ |h,(k,v)| h[k[1..-1].to_i] = v; h },
:row => index,
)
end
cache_write(api, cache_key, attendance)
return attendance
end
# Add a badge link to the event in OSM
# @param [Osm::Api] api The api to use to make the request
# @param [Osm::Event::BadgeLink] link The badge link to add, if column_id is nil then a new column is created with requirement_label as the name
# @return [Boolean] whether the update succedded
def add_badge_link(api, link)
raise Osm::ObjectIsInvalid, 'link is invalid' unless link.valid?
data = api.perform_query("ext/badges/records/index.php?action=linkBadgeToItem§ionid=#{section_id}", {
'section' => link.badge_section,
'sectionid' => section_id,
'type' => 'event',
'id' => id,
'badge_id' => link.badge_id,
'badge_version' => link.badge_version,
'column_id' => link.requirement_id.to_i.eql?(0) ? -2 : link.requirement_id,
'column_data' => link.data,
'new_column_name' => link.requirement_id.to_i.eql?(0) ? link.requirement_label : '',
})
return (data.is_a?(Hash) && data['status'])
end
# Add a column to the event in OSM
# @param [Osm::Api] api The api to use to make the request
# @param [String] label The label for the field in OSM
# @param [String] name The label for the field in My.SCOUT (if this is blank then parents can't edit it)
# @param [Boolean] required Whether the parent is required to enter something
# @return [Boolean] whether the update succedded
# @raise [Osm::ArgumentIsInvalid] If the name is blank
def add_column(api, name, label='', required=false)
require_ability_to(api, :write, :events, section_id)
raise Osm::ArgumentIsInvalid, 'name is invalid' if name.blank?
data = api.perform_query("events.php?action=addColumn§ionid=#{section_id}&eventid=#{id}", {
'columnName' => name,
'parentLabel' => label,
'parentRequire' => (required ? 1 : 0),
})
# The cached events for the section will be out of date - remove them
cache_delete(api, ['events', section_id])
cache_delete(api, ['event', id])
cache_delete(api, ['event_attendance', id])
self.columns = self.class.new_event_from_data(data).columns
return data.is_a?(Hash) && (data['eventid'].to_i == id)
end
# Whether thete is a limit on attendance for this event
# @return [Boolean] whether thete is a limit on attendance for this event
def limited_attendance?
(attendance_limit != 0)
end
# Whether there are spaces left for the event
# @param [Osm::Api] api The api to use to make the request
# @return [Boolean] whether there are spaces left for the event
def spaces?(api)
return true unless limited_attendance?
return attendance_limit > attendees(api)
end
# Get the number of spaces left for the event
# @param [Osm::Api] api The api to use to make the request
# @return [Fixnum, nil] the number of spaces left (nil if there is no attendance limit)
def spaces(api)
return nil unless limited_attendance?
return attendance_limit - attendees(api)
end
# Whether the cost is to be confirmed
# @return [Boolean] whether the cost is TBC
def cost_tbc?
cost.eql?('TBC')
end
# Whether the cost is zero
# @return [Boolean] whether the cost is zero
def cost_free?
cost.eql?('0.00')
end
private
def attendees(api)
attendees = 0
get_attendance(api).each do |a|
attendees += 1 unless attendance_limit_includes_leaders && (a.grouping_id == -2)
end
return attendees
end
def self.attributes_from_data(event_data)
{
:id => Osm::to_i_or_nil(event_data['eventid']),
:section_id => Osm::to_i_or_nil(event_data['sectionid']),
:name => event_data['name'],
:start => Osm::make_datetime(event_data['startdate'], event_data['starttime']),
:finish => Osm::make_datetime(event_data['enddate'], event_data['endtime']),
:cost => event_data['cost'].eql?('-1') ? 'TBC' : event_data['cost'],
:location => event_data['location'],
:notes => event_data['notes'],
:archived => event_data['archived'].eql?('1'),
:public_notepad => event_data['publicnotes'],
:confirm_by_date => Osm::parse_date(event_data['confdate']),
:allow_changes => event_data['allowchanges'].eql?('1'),
:reminders => !event_data['disablereminders'].eql?('1'),
:attendance_limit => event_data['attendancelimit'].to_i,
:attendance_limit_includes_leaders => event_data['limitincludesleaders'].eql?('1'),
:attendance_reminder => event_data['attendancereminder'].to_i,
:allow_booking => event_data['allowbooking'].eql?('1'),
}
end
def self.new_event_from_data(event_data)
event = Osm::Event.new(attributes_from_data(event_data))
event.notepad = event_data['notepad']
columns = []
config_raw = event_data['config']
config_raw = '[]' if config_raw.blank?
column_data = ActiveSupport::JSON.decode(config_raw)
column_data = [] unless column_data.is_a?(Array)
column_data.each do |field|
columns.push Column.new(:id => field['id'], :name => field['name'], :label => field['pL'], :parent_required => field['pR'].to_s.eql?('1'), :event => event)
end
event.columns = columns
badges = []
badges_data = event_data['badgelinks']
badges_data = [] unless badges_data.is_a?(Array)
badges_data.each do |field|
badges.push BadgeLink.new(
badge_type: field['badgetype'].to_sym,
badge_section: field['section'].to_sym,
requirement_id: field['column_id'],
badge_name: field['badgeLongName'],
requirement_label: field['columnnameLongName'],
data: field['data'],
badge_id: field['badge_id'],
badge_version: field['badge_version'],
)
end
event.badges = badges
return event
end
class BadgeLink
include ActiveModel::MassAssignmentSecurity if ActiveModel::VERSION::MAJOR < 4
include ActiveAttr::Model
SORT_BY = [:badge_section, :badge_type, :badge_name, :requirement_label]
# @!attribute [rw] badge_type
# @return [Symbol] the type of badge
# @!attribute [rw] badge_section
# @return [Symbol] the section type that the badge belongs to
# @!attribute [rw] requirement_label
# @return [String] human firendly requirement label
# @!attribute [rw] data
# @return [String] what to put in the column when the badge records are updated
# @!attribute [rw] badge_name
# @return [String] the badge's name
# @!attribute [rw] badge_id
# @return [Fixnum] the badge's ID in OSM
# @!attribute [rw] badge_version
# @return [Fixnum] the version of the badge
# @!attribute [rw] requirement_id
# @return [Fixnum] the requirement's ID in OSM
attribute :badge_type, :type => Object
attribute :badge_section, :type => Object
attribute :requirement_label, :type => String
attribute :data, :type => String
attribute :badge_name, :type => String
attribute :badge_id, :type => Integer
attribute :badge_version, :type => Integer
attribute :requirement_id, :type => Integer
if ActiveModel::VERSION::MAJOR < 4
attr_accessible :badge_type, :badge_section, :requirement_label, :data, :badge_name, :badge_id, :badge_version, :requirement_id
end
validates_presence_of :badge_name
validates_inclusion_of :badge_section, :in => [:beavers, :cubs, :scouts, :explorers, :staged]
validates_inclusion_of :badge_type, :in => [:core, :staged, :activity, :challenge]
validates_numericality_of :badge_id, :only_integer=>true, :greater_than=>0
validates_numericality_of :badge_version, :only_integer=>true, :greater_than_or_equal_to=>0
validates_numericality_of :requirement_id, :only_integer=>true, :greater_than=>0, :allow_nil=>true
# @!method initialize
# Initialize a new Meeting::Activity
# @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
end # Class Event::BadgeLink
class Column < Osm::Model
SORT_BY = [:event, :id]
# @!attribute [rw] id
# @return [String] OSM id for the column
# @!attribute [rw] name
# @return [String] name for the column (displayed in OSM)
# @!attribute [rw] label
# @return [String] label to display in My.SCOUT ("" prevents display in My.SCOUT)
# @!attribute [rw] parent_required
# @return [Boolean] whether the parent is required to enter something
# @!attriute [rw] event
# @return [Osm::Event] the event that this column belongs to
attribute :id, :type => String
attribute :name, :type => String
attribute :label, :type => String, :default => ''
attribute :parent_required, :type => Boolean, :default => false
attribute :event
if ActiveModel::VERSION::MAJOR < 4
attr_accessible :id, :name, :label, :parent_required, :event
end
validates_presence_of :id
validates_presence_of :name
# @!method initialize
# Initialize a new Column
# @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
# Update event column in OSM
# @param [Osm::Api] api The api to use to make the request
# @return [Boolean] if the operation suceeded or not
def update(api)
require_ability_to(api, :write, :events, event.section_id)
data = api.perform_query("events.php?action=renameColumn§ionid=#{event.section_id}&eventid=#{event.id}", {
'columnId' => id,
'columnName' => name,
'pL' => label,
'pR' => (parent_required ? 1 : 0),
})
(ActiveSupport::JSON.decode(data['config']) || []).each do |i|
if i['id'] == id
if i['name'].eql?(name) && (i['pL'].nil? || i['pL'].eql?(label)) && (i['pR'].eql?('1') == parent_required)
reset_changed_attributes
# The cached event will be out of date - remove it
cache_delete(api, ['event', event.id])
# The cached event attedance will be out of date
cache_delete(api, ['event_attendance', event.id])
return true
end
end
end
return false
end
# Delete event column from OSM
# @param [Osm::Api] api The api to use to make the request
# @return [Boolean] whether the delete succedded
def delete(api)
require_ability_to(api, :write, :events, event.section_id)
data = api.perform_query("events.php?action=deleteColumn§ionid=#{event.section_id}&eventid=#{event.id}", {
'columnId' => id
})
(ActiveSupport::JSON.decode(data['config']) || []).each do |i|
return false if i['id'] == id
end
new_columns = []
event.columns.each do |column|
new_columns.push(column) unless column == self
end
event.columns = new_columns
cache_write(api, ['event', event.id], event)
return true
end
def inspect
Osm.inspect_instance(self, options={:replace_with => {'event' => :id}})
end
end # class Column
class Attendance < Osm::Model
SORT_BY = [:event, :row]
# @!attribute [rw] member_id
# @return [Fixnum] OSM id for the member
# @!attribute [rw] grouping__id
# @return [Fixnum] OSM id for the grouping the member is in
# @!attribute [rw] fields
# @return [Hash] Keys are the field's id, values are the field values
# @!attribute [rw] row
# @return [Fixnum] part of the OSM API
# @!attriute [rw] event
# @return [Osm::Event] the event that this attendance applies to
# @!attribute [rw] first_name
# @return [String] the member's first name
# @!attribute [rw] last_name
# @return [String] the member's last name
# @!attribute [rw] date_of_birth
# @return [Date] the member's date of birth
# @!attribute [rw] attending
# @return [Symbol] whether the member is attending (either :yes, :no, :invited, :shown, :reserved or nil)
# @!attribute [rw] payments
# @return [Hash] keys are the payment's id, values are the payment state
# @!attribute [rw] payment_control
# @return [Symbol] whether payments are done manually or automatically (either :manual, :automatic or nil)
attribute :row, :type => Integer
attribute :member_id, :type => Integer
attribute :grouping_id, :type => Integer
attribute :fields, :default => {}
attribute :event
attribute :first_name, :type => String
attribute :last_name, :type => String
attribute :date_of_birth, :type => Date
attribute :attending
attribute :payments, :default => {}
attribute :payment_control
if ActiveModel::VERSION::MAJOR < 4
attr_accessible :member_id, :grouping_id, :fields, :row, :event, :first_name, :last_name, :date_of_birth, :attending, :payments, :payment_control
end
validates_numericality_of :row, :only_integer=>true, :greater_than_or_equal_to=>0
validates_numericality_of :member_id, :only_integer=>true, :greater_than=>0
validates_numericality_of :grouping_id, :only_integer=>true, :greater_than_or_equal_to=>-2
validates :fields, :hash => { :key_type => Fixnum, :value_type => String }
validates :payments, :hash => { :key_type => Fixnum, :value_type => String }
validates_each :event do |record, attr, value|
record.event.valid?
end
validates_presence_of :first_name
validates_presence_of :last_name
validates_presence_of :date_of_birth
validates_inclusion_of :payment_control, :in => [:manual, :automatic, nil]
validates_inclusion_of :attending, :in => [:yes, :no, :invited, :shown, :reserved, nil]
# @!method initialize
# Initialize a new Attendance
# @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
old_initialize = instance_method(:initialize)
define_method :initialize do |*args|
ret_val = old_initialize.bind(self).call(*args)
self.fields = DirtyHashy.new(self.fields)
self.fields.clean_up!
return ret_val
end
# Update event attendance
# @param [Osm::Api] api The api to use to make the request
# @return [Boolean] if the operation suceeded or not
def update(api)
require_ability_to(api, :write, :events, event.section_id)
payment_values = {
:manual => 'Manual',
:automatic => 'Automatic',
}
attending_values = {
:yes => 'Yes',
:no => 'No',
:invited => 'Invited',
:shown => 'Show in My.SCOUT',
:reserved => 'Reserved',
}
updated = true
fields.changes.each do |field, (was,now)|
data = api.perform_query("events.php?action=updateScout", {
'scoutid' => member_id,
'column' => "f_#{field}",
'value' => now,
'sectionid' => event.section_id,
'row' => row,
'eventid' => event.id,
})
updated = false unless data.is_a?(Hash)
end
if changed_attributes.include?('payment_control')
data = api.perform_query("events.php?action=updateScout", {
'scoutid' => member_id,
'column' => 'payment',
'value' => payment_values[payment_control],
'sectionid' => event.section_id,
'row' => row,
'eventid' => event.id,
})
updated = false unless data.is_a?(Hash)
end
if changed_attributes.include?('attending')
data = api.perform_query("events.php?action=updateScout", {
'scoutid' => member_id,
'column' => 'attending',
'value' => attending_values[attending],
'sectionid' => event.section_id,
'row' => row,
'eventid' => event.id,
})
updated = false unless data.is_a?(Hash)
end
if updated
reset_changed_attributes
fields.clean_up!
# The cached event attedance will be out of date
cache_delete(api, ['event_attendance', event.id])
end
return updated
end
# Get audit trail
# @param [Osm::Api] api The api to use to make the request
# @!macro options_get
# @return [Array<Hash>]
def get_audit_trail(api, options={})
require_ability_to(api, :read, :events, event.section_id, options)
cache_key = ['event\_attendance\_audit', event.id, member_id]
if !options[:no_cache] && cache_exist?(api, cache_key)
return cache_read(api, cache_key)
end
data = api.perform_query("events.php?action=getEventAudit§ionid=#{event.section_id}&scoutid=#{member_id}&eventid=#{event.id}")
data ||= []
attending_values = {
'Yes' => :yes,
'No' => :no,
'Invited' => :invited,
'Show in My.SCOUT' => :shown,
'Reserved' => :reserved,
}
trail = []
data.each do |item|
this_item = {
:at => DateTime.strptime(item['date'], '%d/%m/%Y %H:%M'),
:by => item['updatedby'].strip,
:type => item['type'].to_sym,
:description => item['desc'],
:event_id => event.id,
:member_id => member_id,
:event_attendance => self,
}
if this_item[:type].eql?(:detail)
results = this_item[:description].match(/\ASet '(?<label>.+)' to '(?<value>.+)'\Z/)
this_item[:label] = results[:label]
this_item[:value] = results[:value]
end
if this_item[:type].eql?(:attendance)
results = this_item[:description].match(/\AAttendance: (?<attending>.+)\Z/)
this_item[:attendance] = attending_values[results[:attending]]
end
trail.push this_item
end
cache_write(api, cache_key, trail)
return trail
end
# @! method automatic_payments?
# Check wether payments are made automatically for this member
# @return [Boolean]
# @! method manual_payments?
# Check wether payments are made manually for this member
# @return [Boolean]
[:automatic, :manual].each do |payment_control_type|
define_method "#{payment_control_type}_payments?" do
payments == payment_control_type
end
end
# @! method is_attending?
# Check wether the member has said they are attending the event
# @return [Boolean]
# @! method is_not_attending?
# Check wether the member has said they are not attending the event
# @return [Boolean]
# @! method is_invited?
# Check wether the member has been invited to the event
# @return [Boolean]
# @! method is_shown?
# Check wether the member can see the event in My.SCOUT
# @! method is_reserved?
# Check wether the member has reserved a space when one becomes availible
# @return [Boolean]
[:attending, :not_attending, :invited, :shown, :reserved].each do |attending_type|
define_method "is_#{attending_type}?" do
attending == attending_type
end
end
def inspect
Osm.inspect_instance(self, options={:replace_with => {'event' => :id}})
end
end # Class Attendance
end # Class Event
end # Module