diff --git a/lib/sf.rb b/lib/sf.rb index 7ff316b..328d500 100644 --- a/lib/sf.rb +++ b/lib/sf.rb @@ -1,5 +1,28 @@ +require "restforce" require "sf/version" - +require "sf/base" +require "sf/account" +require "sf/contact" +require "sf/contact_affiliation" +require "sf/reg_event" +require "sf/reg_registration" +require "sf/reg_item" module Sf - # Your code goes here... + class << self + def client + if @client.nil? + if File.exist?("#{::Rails.root.to_s}/config/restforce.yml") + sfdc_auth_config = YAML.load_file("#{::Rails.root.to_s}/config/restforce.yml") + @client = Restforce.new :host => sfdc_auth_config['host'], + :username => sfdc_auth_config['username'], + :password => sfdc_auth_config['password'], + :security_token => sfdc_auth_config['security_token'], + :client_id => sfdc_auth_config['client_id'], + :client_secret => sfdc_auth_config['client_secret'] + end + else + @client + end + end + end end diff --git a/lib/sf/account.rb b/lib/sf/account.rb new file mode 100644 index 0000000..b5bddf9 --- /dev/null +++ b/lib/sf/account.rb @@ -0,0 +1,85 @@ +class Sf::Account + include Sf::Base + + FIELDS = ['Id', 'Name', 'Member_Type__c', 'Member_Category__c', 'Member_Join_Date__c', 'Status__c', 'Website'] + FIELDS_SELECT_STR = FIELDS.join(', ') + MEMBER_TYPES = ['R&E Network Member', 'University Member', 'Industry Member', 'Affiliate Member'] + MEMBER_TYPES_STR = MEMBER_TYPES.map{|v| "\'#{v}\'"}.join(', ') + + def initialize(account=nil) + @account = account + end + + def update(args={}) + params = {Id: self.Id} + args.map {|k,v| params[k] = v} + end + + def contacts + Sf::Contact.where({AccountId: self.Id, Status__c: 'Active'}) + end + + def method_missing(method_name, *args, &block) + @account.send(method_name, *args, &block) + end + + def respond_to_missing?(method_name, include_private = false) + @account.respond_to?(method_name, include_private) || super + end + + # Class Methods + def self.find(id) + new Sf.client.find("Account", id) + end + + def self.find_by(args = {}) + where = args.map {|k,v| "#{k} = \'#{v}\'"}.join(" and ") + sobject = Sf.client.query("select #{FIELDS_SELECT_STR} from Account where #{where}").first + if sobject.present? + new sobject + end + end + + def self.intl_partners + build_collection Sf.client.query("select #{FIELDS_SELECT_STR} from Account where Member_Category__c = 'International Partner' order by Name") + end + + def self.members + build_collection Sf.client.query("select #{FIELDS_SELECT_STR} from Account where Member_Type__c IN (#{MEMBER_TYPES_STR}) order by Name") + end + + def self.all + build_collection Sf.client.query("select #{FIELDS_SELECT_STR} from Account order by Name") + end + + def self.community_orgs + build_collection Sf.client.query( + "select #{FIELDS_SELECT_STR} from Account + where Name='Internet2' or InCommon_Participant__c = true + or Member_Type__c IN (#{MEMBER_TYPES_STR}) + or Member_Category__c = 'International Partner' + order by Name" + ) + end + + def self.incommon_participants + build_collection Sf.client.query("select #{FIELDS_SELECT_STR} from Account where InCommon_Participant__c = true order by Name") + end + + def self.internet2 + i2 = find_by({Name: "Internet2", Status__c: "Active"}) + find i2.Id + end + + def self.member?(account_id) + members.map(&:Id).include?(account_id) + end + + def self.inc_part?(account_id) + incommon_participants.map(&:Id).include?(account_id) + end + + def self.intl_partner?(account_id) + intl_partners.map(&:Id).include?(account_id) + end +end diff --git a/lib/sf/base.rb b/lib/sf/base.rb new file mode 100644 index 0000000..c65ebe0 --- /dev/null +++ b/lib/sf/base.rb @@ -0,0 +1,15 @@ +module Sf + module Base + def self.included(base) + base.extend(ClassMathods) + end + + module ClassMathods + def build_collection(models) + models.map { |model| new(model) } + end + end + + end + +end diff --git a/lib/sf/contact.rb b/lib/sf/contact.rb new file mode 100644 index 0000000..78a9df0 --- /dev/null +++ b/lib/sf/contact.rb @@ -0,0 +1,173 @@ +class Sf::Contact + include Sf::Base + attr_accessor :diffs, :person + + FIELDS = ['Id', 'AccountId', 'Salutation', 'FirstName', 'LastName', + 'Title', 'Email', 'HasOptedOutOfEmail', 'Phone', 'MailingStreet', + 'MailingCity', 'MailingState', 'MailingPostalCode', 'MailingCountry', + 'Status__c', 'Functional_Title__c', 'Informal_Name__c', + 'Meeting_Reg_Pre_Population_Opt_Out__c', 'Meeting_Reg_Hold__c'] + FIELDS_SELECT_STR = FIELDS.join(', ') + + def initialize(contact=nil) + @contact = contact.nil? ? Restforce::SObject.new(sobject_type: 'Contact') : contact + @diffs = {} + end + + def account + Sf::Account.find "#{self.AccountId}" + end + + def account_name + account.Name + end + + def method_missing(method_name, *args, &block) + @contact.send(method_name, *args, &block) + end + + def respond_to_missing?(method_name, include_private = false) + @contact.respond_to?(method_name, include_private) || super + end + + def update(args={}) + params = {'Id' => self.Id} + args.map {|k,v| params[k] = v} + Sf.client.update("Contact", params) + end + + def set_person_by_email(email) + self.person = Sf::Contact.find_by(Email: email) + end + + def get_diffs + unless self.Id.nil? + sf_person_mapping.each do |p, sf| + diffs[p.to_sym] = true unless self.send(sf).eql? person.send(p) + end + else + sf_person_mapping.each do |p, sf| + diffs[p.to_sym] = true + end + end + diffs + end + + def attrs_from_person + sf_attrs = {} + sf_person_mapping.each do |p, sf| + compare_attr(sf_attrs, p, sf) + end + sf_attrs + end + + def create_from_person! + Sf.client.create("Contact", self.attrs_from_person) + end + + def update_from_person! + update(self.attrs_from_person) if self.Id.present? + end + + def sf_person_mapping + { + 'prefix' => 'Salutation', + 'first_name' => 'FirstName', + 'last_name' => 'LastName', + 'informal_name' => 'Informal_Name__c', + 'title' => 'Title', + 'functional_title' => 'Functional_Title__c', + 'email' => 'Email', + 'phone_number' => 'Phone', + 'website' => 'Website', + 'street_address' => 'MailingStreet', + 'city' => 'MailingCity', + 'state' => 'MailingState', + 'zip' => 'MailingPostalCode', + 'country' => 'MailingCountry', + 'exclude_directory' => 'Meeting_Reg_Pre_Population_Opt_Out__c', + 'sf_id' => 'Id', + 'sf_account_id' => 'AccountId' + } + end + + def compare_attr(sf_attrs, person_attr, sf_attr) + person_value = person.send(person_attr) + sf_value = self.send(sf_attr) + if person_value.present? and !person_value.eql?(sf_value) + sf_attrs[sf_attr] = person_value + end + end + def self.find(id) + new Sf.client.find("Contact", id) + end + + def self.where(args = {}) + where = args.map {|k,v| "#{k} = \'#{v}\'"}.join(" and ") + sobjects = Sf.client.query("select #{FIELDS_SELECT_STR} from Contact where #{where}") + build_collection sobjects unless sobjects.blank? + end + + def self.find_by(args = {}) + where(args).first + end + + def self.internet2_staffs + from = 'Contact_Affiliation__c' + i2 = Sf::Account.internet2 + staff_filter = { + Account__c: i2.Id, Role__c: "Staff", + Program__c: "Internet2 Administration", Service__c: "Human Resources", + Status__c: "Current" + } + where = staff_filter.map {|k,v| "#{k} = \'#{v}\'"}.join(" and ") + build_contacts(from, where) + end + + def self.internet2_staff?(contact_id) + Sf::Contact.internet2_staffs.map(&:Id).include?(contact_id) + end + + def self.board_of_trustees_as_of(date=nil) + from = 'Seat__c' + date ||= Date.today + date = date.strftime('%Y-%m-%d') + where = "Committee__r.Name = 'BOT (Board of Trustees)' and + Start_Date__c <= #{date} and Term_End_Date__c >= #{date} and + Contact__c <> null" + build_contacts(from, where) + end + + def self.board_member_as_of?(contact_id, date=nil) + Sf::Contact.board_of_trustees_as_of(date).map(&:Id).include?(contact_id) + end + + def self.advisory_group_members + from = 'Seat__c' + where = "Committee__r.Category__c = 'Advisory Group' and + Term_End_Date__c = null and Actual_End_Date__c = null + and Contact__c <> null" + build_contacts(from, where) + end + + def self.advisory_group_member?(contact_id) + Sf::Contact.advisory_group_members.map(&:Id).include?(contact_id) + end + + def self.active_functional_title_pick_list + func_titles = Sf.client.picklist_values("Contact", "Functional_Title__c").map{|f| f if f.active} + func_titles.compact.sort{|a,b| a.label <=> b.label} + func_titles + end + + private + + def self.build_contacts(from, where) + build_collection Sf.client.query( + "SELECT #{Sf::Contact::FIELDS_SELECT_STR} + FROM Contact + WHERE Id + IN (SELECT Contact__c FROM #{from} where #{where})" + ) + end +end diff --git a/lib/sf/contact_affiliation.rb b/lib/sf/contact_affiliation.rb new file mode 100644 index 0000000..86ef16f --- /dev/null +++ b/lib/sf/contact_affiliation.rb @@ -0,0 +1,38 @@ +class Sf::ContactAffiliation + include Sf::Base + API_NAME = 'Contact_Affiliation__c' + #FIELDS = [ + # 'Account__c', 'Contact__c', 'Contact_Email__c', 'Contact_Name__c', + # 'End_Date__c', 'Primary__c', 'Program__c', 'Role__c', 'Service__c', + # 'Status__c', 'Title__c' + #] + FIELDS = [ + 'Account__c', 'Contact__c', 'Contact_Email__c', 'Contact_Name__c', + 'Program__c', 'Role__c', 'Service__c', + 'Status__c', 'Title__c' + ] + FIELDS_SELECT_STR = FIELDS.join(', ') + + def initialize(contact_affiliation = nil) + @contact_affiliation = contact_affiliation + end + + def account + new Sf::Account.find("Account", Account__c) + end + + def method_missing(method_name, *args, &block) + @contact_affiliation.send(method_name, *args, &block) + end + + def respond_to_missing?(method_name, include_private = false) + @contact_affiliation.respond_to?(method_name, include_private) || super + end + + def self.where(args = {}) + where = args.map {|k,v| "#{k} = \'#{v}\'"}.join(" and ") + sobjects = Sf.client.query("select #{FIELDS_SELECT_STR} from #{API_NAME} where #{where}") + build_collection sobjects unless sobjects.blank? + end + +end diff --git a/lib/sf/reg_event.rb b/lib/sf/reg_event.rb new file mode 100644 index 0000000..99ed57b --- /dev/null +++ b/lib/sf/reg_event.rb @@ -0,0 +1,69 @@ +class Sf::RegEvent + include Sf::Base + attr_accessor :reg_event + + FIELDS = ['Id', 'code__c', 'start_date__c', 'end_date__c', 'max_attendees__c', + 'early_rate_deadline__c', 'regular_rate_deadline__c', 'cancellation_deadline__c'] + FIELDS_SELECT_STR = FIELDS.join(', ') + def initialize(reg_event=nil) + @reg_event = reg_event.nil? ? Restforce::SObject.new(sobject_type: 'RegEvent__c') : reg_event + end + + def method_missing(method_name, *args, &block) + @reg_event.send(method_name, *args, &block) + end + + def respond_to_missing?(method_name, include_private = false) + @reg_event.respond_to?(method_name, include_private) || super + end + + def create_from_event + attrs = {} + self.event_mapping.each do |event_attr, sf_event_attr| + reg_event_value = self.reg_event.send(event_attr) + reg_event_value = reg_event_value.iso8601 if reg_event_value.class.eql?(ActiveSupport::TimeWithZone) + attrs[sf_event_attr] = reg_event_value unless reg_event_value.nil? + end + Sf.client.create("RegEvent__c", attrs) + end + + def update(attrs = {}) + attrs['Id'] = self.Id + Sf.client.update("RegEvent__c", attrs) + end + + def event_mapping + { + 'code' => 'code__c', + 'start_date' => 'start_date__c', + 'end_date' => 'end_date__c', + 'early_rate_deadline' => 'early_rate_deadline__c', + 'regular_rate_deadline' => 'regular_rate_deadline__c', + 'cancellation_deadline' => 'cancellation_deadline__c', + 'registration_closes_at' => 'registration_closes_at__c', + 'registration_opens_at' => 'registration_opens_at__c', + 'title' => 'title__c', + 'description' => 'description__c', + 'max_attendees' => 'max_attendees__c', + 'event_home_url' => 'event_home_url__c', + 'status' => 'status__c', + 'updated_at' => 'updated_at__c', + 'id' => 'reg_event_id__c' + } + end + + def self.find(id) + new Sf.client.find("RegEvent__c", id) + end + + def self.find_by_code(code) + where({code__c: code}).first + end + + def self.where(args = {}) + where = args.map {|k,v| "#{k} = \'#{v}\'"}.join(" and ") + sobjects = Sf.client.query("select #{FIELDS_SELECT_STR} from RegEvent__c where #{where}") + build_collection sobjects unless sobjects.blank? + end +end + diff --git a/lib/sf/reg_item.rb b/lib/sf/reg_item.rb new file mode 100644 index 0000000..78b7956 --- /dev/null +++ b/lib/sf/reg_item.rb @@ -0,0 +1,42 @@ +class Sf::RegItem + include Sf::Base + attr_accessor :reg_item + + def initialize(reg_item=nil) + @reg_item = reg_item.nil? ? Restforce::SObject.new(sobject_type: 'RegItem__c') : reg_item + end + + def method_missing(method_name, *args, &block) + @reg_item.send(method_name, *args, &block) + end + + def respond_to_missing?(method_name, include_private = false) + @reg_item.respond_to?(method_name, include_private) || super + end + + def create_from_reg_item_for(sf_reg_id) + attrs = {} + self.reg_item_mapping.each do |reg_item_attr, sf_reg_item_attr| + attrs[sf_reg_item_attr] = self.reg_item.send(reg_item_attr) + end + attrs['reg_registration_id__c'] = sf_reg_id + Sf.client.create("RegItem__c", attrs) + end + def reg_item_mapping + { + 'code' => 'code__c', + 'comp_code' => 'comp_code__c', + 'charged_amount' => 'charged_amount__c', + 'item_type' => 'item_type__c', + 'note' => 'note__c', + 'reg_class_used' => 'reg_class_used__c', + 'id' => 'reg_item_id__c', + 'reg_rate_code' => 'reg_rate_code__c', + 'reg_rate_description' => 'reg_rate_description__c', + 'reg_rate_id' => 'reg_rate_id__c', + 'seqn' => 'seqn__c', + 'created_at' => 'created_at__c' + } + end + +end diff --git a/lib/sf/reg_registration.rb b/lib/sf/reg_registration.rb new file mode 100644 index 0000000..2a0ccde --- /dev/null +++ b/lib/sf/reg_registration.rb @@ -0,0 +1,151 @@ +class Sf::RegRegistration + include Sf::Base + attr_accessor :registration, :zip + + FIELDS = ['account_id', 'cc_email', 'city', 'contact_id', 'country', + 'created_at', 'designation', 'email', 'event_code', + 'exclude_directory', 'first_name', 'full_name', 'functional_title', + 'informal_name', 'last_name', 'processed_by', 'processed_at', + 'mobile_number', 'user_entered_organization_name', + 'organization_display_name', 'phone_number', 'prefix', + 'reg_classes', 'reg_event_id', 'reg_registration_id', 'rne_private', + 'roster_private', 'sponsor_private', 'state', 'status', + 'street_address', 'submitted_at', 'title', 'updated_at', 'version', + 'website', 'zip' ] + + FIELDS_SELECT_STR = FIELDS.map{|fd| "#{fd}__c"}.join(', ') + + def initialize(reg_registration=nil) + @reg_registration = reg_registration.nil? ? Restforce::SObject.new(sobject_type: 'RegRegistration__c') : reg_registration + end + + def method_missing(method_name, *args, &block) + @reg_registration.send(method_name, *args, &block) + end + + def respond_to_missing?(method_name, include_private = false) + @reg_registration.respond_to?(method_name, include_private) || super + end + + def create_from_registration + attrs = {} + self.registration_mapping.each do |reg_attr, sf_reg_attr| + reg_value = self.registration.send(reg_attr) + reg_value = reg_value.iso8601 if reg_value.class.eql?(ActiveSupport::TimeWithZone) + attrs[sf_reg_attr] = reg_value unless reg_value.nil? + end + attrs['event_code__c'] = self.registration.event.code + attrs['reg_event_id__c'] = self.registration.event.sf_event_id + attrs['processed_at__c'] = Time.now.iso8601 + if self.registration.payment.present? and payment = self.registration.payment + attrs['payment_method__c'] = payment.payment_method + attrs['credit_card_type__c'] = payment.card_type + attrs['charged_amount__c'] = payment.charge_amount + attrs['paid_amount__c'] = payment.paid_amount + attrs['credit_card_number__c'] = payment.card_last4 + attrs['payment_reference_id__c'] = payment.pnref + attrs['bank_authorization_code__c'] = payment.authcode + end + if sf_reg_id = Sf.client.create("RegRegistration__c", attrs) + self.registration.reg_items.each do |reg_item| + #TODO: rescue and rollback + if reg_item.sf_reg_item_id = Sf::RegItem.create_for(reg_item, sf_reg_id) + reg_item.save + end + end + end + sf_reg_id + end + + def update(attrs = {}) + attrs['Id'] = self.Id + Sf.client.update("RegRegistration__c", attrs) + end + + def registration_mapping + { + 'sf_account_id' => 'account_id__c', + 'cc_email' => 'cc_email__c', + 'city' => 'city__c', + 'sf_id' => 'contact_id__c', + 'country' => 'country__c', + 'created_at' => 'created_at__c', + 'designation' => 'designation__c', + 'created_at' => 'created_at__c', + 'email' => 'email__c', + 'first_name' => 'first_name__c', + 'full_name' => 'full_name__c', + 'functional_title' => 'functional_title__c', + 'informal_name' => 'informal_name__c', + 'last_name' => 'last_name__c', + 'mobile_number' => 'mobile_number__c', + 'organization_name' => 'user_entered_organization_name__c', + 'phone_number' => 'phone_number__c', + 'prefix' => 'prefix__c', + 'reg_classes' => 'reg_classes__c', + 'id' => 'reg_registration_id__c', + 'rne_private' => 'rne_private__c', + 'roster_private' => 'roster_private__c', + 'sponsor_private' => 'sponsor_private__c', + 'state' => 'state__c', + 'status' => 'status__c', + 'street_address' => 'street_address__c', + 'submitted_at' => 'submitted_at__c', + 'title' => 'title__c', + 'updated_at' => 'updated_at__c', + 'version' => 'version__c', + 'website' => 'website__c', + 'zip' => 'zip__c', + 'total' => 'charged_amount__c', + 'processed_by' => 'processed_by__c', + 'processed_at' => 'processed_at__c', + 'receipt_id' => 'receipt_id__c', + 'reference_id' => 'reference_id__c', + 'balance' => 'balance__c', + 'from_ip_address' => 'ip_address__c' + } + end + + def self.all + build_collection Sf.client.query( + "SELECT #{Sf::RegRegistration::FIELDS_SELECT_STR} + FROM RegRegistration__c" + ) + end + + def self.find(id) + new Sf.client.find("RegRegistration__c", id) + end + + def self.find_all_by_event_code(code) + self.where(event_code__c: code) + end + + def self.where(args = {}) + where = args.map {|k,v| "#{k} = \'#{v}\'"}.join(" and ") + sobjects = Sf.client.query("select #{FIELDS_SELECT_STR} from RegRegistration__c where #{where}") + build_collection sobjects unless sobjects.blank? + end + + def self.create_for(reg_item, sf_reg_id) + sf_reg_item = self.new(reg_item) + sf_reg_item.create_from_reg_item_for(sf_reg_id) + end + + def self.attendees_for(event_code, reg_item_code) + return if event_code.nil? or reg_item_code.nil? + rr = 'reg_registration_id__r' + selects = "#{rr}.Id, #{rr}.email__c, #{rr}.last_name__c, #{rr}.first_name__c, #{rr}.account_id__r.Name, #{rr}.roster_private__c" + where = "code__c = \'#{reg_item_code}\' and #{rr}.event_code__c = \'#{event_code}\' and #{rr}.status__c = 'processed'" + sobjects = Sf.client.query("select #{selects} from RegItem__c where #{where}") + build_collection sobjects.map(&:reg_registration_id__r) unless sobjects.blank? + end + + def self.roster_for(event_code, reg_item_code) + all_attendees = self.attendees_for(event_code, reg_item_code) + all_attendees.map!{|a| a unless a.roster_private__c} unless all_attendees.nil? + all_attendees.compact unless all_attendees.nil? + end + +end +