sf_migrate 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e862896e87413a3c8447dcb85efa73fea0f1c761
4
+ data.tar.gz: 4ee2a6e915659d3e257cb9f0dc13222835839439
5
+ SHA512:
6
+ metadata.gz: 6bc6c9a71e565aad931468ab34c143d700a3113b084d57128dc0f608db0567a0f3915c35ab57e27db14cf25a2587486b630de28a5d500d9e2ce084119f77c187
7
+ data.tar.gz: 90b2079df5066ae955500f2ab3b2ce04effc6a84362decf2e96d60d021e847800a5c9a8db82a84145fdf46ee970144a11a604e60fd004a1995e87e467e390b0d
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://www.rubygems.org"
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,2 @@
1
+ Some documentation
2
+ ==================
data/bin/sf_migrate ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
4
+ require "pathname"
5
+ bin_file = Pathname.new(__FILE__).realpath
6
+
7
+ $LOAD_PATH.unshift File.expand_path("../../lib", bin_file)
8
+ require 'sf_migrate'
9
+
10
+ SalesforceMigration::Runner.start
@@ -0,0 +1,13 @@
1
+ sugar_url: <url>
2
+ sugar_username: <username>
3
+ sugar_password: <password>
4
+
5
+ salesforce_consumer_key: <SF_consumer_key>
6
+ salesforce_consumer_secret: <SF_consumer_secret>
7
+ salesforce_username: <SF_username>
8
+ salesforce_password: <SF_password>
9
+
10
+ db_type: mysql2
11
+ db_user: <db_user>
12
+ db_password: <db_password>
13
+ db_database: <schema_name>
data/lib/export.rb ADDED
@@ -0,0 +1,146 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__)
2
+
3
+ module SalesforceMigration
4
+ class Export
5
+ # Initilize migration export object, authenticates to SalesForce and start the export
6
+ #
7
+ # @param [Hash] options hash representing passed command line options
8
+ def initialize(options)
9
+ credentials = YAML.load_file(options[:config_file])
10
+ consumer_key = credentials['salesforce_consumer_key']
11
+ consumer_secret = credentials['salesforce_consumer_secret']
12
+ username = credentials['salesforce_username']
13
+ password = credentials['salesforce_password']
14
+ @csv_dir = options[:csv_dir]
15
+ @action = options[:action]
16
+ @logger = SalesforceMigration::Runner::create_logger
17
+ @logger.info("Export action started")
18
+ @client = Databasedotcom::Client.new :client_id => consumer_key, :client_secret => consumer_secret
19
+ @client.authenticate :username => username, :password => password
20
+
21
+ @logger.info("Authentication to SalesForce successfull")
22
+ start
23
+ @logger.info("Export action ended successfully")
24
+ end
25
+
26
+ private
27
+ def start
28
+ %w(ISOs__c Agent__c Account Payment_Methods__c Banks__c MerchantToAPM__c ccrmbasic__Email__c Email_Association__c).each do |type|
29
+ @logger.info "Writing CSV for #{type}"
30
+ write_to_csv type
31
+ end
32
+ end
33
+
34
+ # Generate and save CSV file for given type
35
+ #
36
+ # @param [String] type export type name
37
+ def write_to_csv(type)
38
+ fields = return_fields(type)
39
+ records = get_records(type)
40
+ file_name = generate_name(type)
41
+
42
+ CSV.open(file_name, "wb:UTF-8") do |file|
43
+ @logger.info "Opened #{file_name} for export"
44
+ file << fields
45
+ records.each do |record|
46
+ arr = []
47
+ fields.each do |field|
48
+ arr << record.send(field)
49
+ arr.map!(&method(:remove_quotes))
50
+ end
51
+ file << arr
52
+ end
53
+ end
54
+ end
55
+
56
+ # In order to fix the Invalid Session Id in SugarCRM we need to remove all
57
+ # quotes from text fields, because they mess up the JSON object which comunicate with
58
+ # our SugarCRM instance.
59
+ # Note that on Mac we don`t need this method.
60
+ def remove_quotes(field)
61
+ field.gsub(/'/, "").strip if field.is_a? String
62
+ end
63
+
64
+
65
+ # Return the header fields
66
+ def return_fields(type)
67
+ return constantize "SalesforceMigration::Fields::#{type.slice(0..-4).upcase}_FIELDS" if type.end_with?("__c")
68
+ return constantize "SalesforceMigration::Fields::#{type.upcase}_FIELDS"
69
+ end
70
+
71
+ # Get all records for specific type of object
72
+ #
73
+ # @param [String] type export type name
74
+ #
75
+ # @return [Databasedotcom::Collection] the requested records
76
+ def get_records(type)
77
+ if @action == 'initial_run'
78
+ @logger.info "Getting all records from SalesForce for #{type}"
79
+ records = get_all_sobjects(type)
80
+ else
81
+ @logger.info "Getting records for yesterday from SalesForce for #{type}"
82
+ records = @client.materialize(type)
83
+
84
+ datetime = DateTime.now
85
+ datetime = datetime -= 1
86
+ records.query("lastmodifieddate >= #{datetime}")
87
+ end
88
+ end
89
+
90
+ # Generates a name for CSV file depending on the action
91
+ #
92
+ # @param [String] type export type name
93
+ #
94
+ # @return [String] generated filename
95
+ #
96
+ # @example
97
+ # @action = 'update'
98
+ # @csv_dir = '/tmp'
99
+ # generate_name('Account') #=> '/tmp/update/2012-07-07/Account_export.csv'
100
+ def generate_name(type)
101
+ if @action == 'initial_run'
102
+ FileUtils.mkdir_p("#{@csv_dir}/initial/") unless Dir.exists? "#{@csv_dir}/initial/"
103
+ "#{@csv_dir}/initial/#{type}_export.csv"
104
+ else
105
+ today = lambda { Date.today.to_s }
106
+ dir = "#{@csv_dir}/update/#{today.call}"
107
+ FileUtils.mkdir_p(dir) unless Dir.exists? dir
108
+ "#{dir}/#{type}_export.csv"
109
+ end
110
+ end
111
+
112
+ # Borrowed from activesupport/lib/active_support/inflector/methods.rb, line 212
113
+ def constantize(camel_cased_word)
114
+ names = camel_cased_word.split('::')
115
+ names.shift if names.empty? || names.first.empty?
116
+ constant = Object
117
+ names.each do |name|
118
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
119
+ end
120
+ constant
121
+ end
122
+
123
+ # Get all objects of given type. Databasedotcom pulls only 250 records by query
124
+ #
125
+ # @param [String] type type of sobject
126
+ #
127
+ # @return [Array<Databasedotcom::Sobject::Sobject>] All available sobjects
128
+ # Some objects, like Merchants and Emails are exported VIA a certain criteria
129
+ def get_all_sobjects(type)
130
+ case type
131
+ when 'Account'
132
+ records = @client.materialize(type).query("Agents__c != ''")
133
+ when 'ccrmbasic__Email__c'
134
+ records = @client.materialize(type).query("ccrmbasic__Contact__c != ''")
135
+ else
136
+ records = @client.materialize(type).all
137
+ end
138
+ sobjects = records.dup.to_a
139
+ while records.next_page?
140
+ sobjects += records.next_page
141
+ records = records.next_page
142
+ end
143
+ sobjects
144
+ end
145
+ end
146
+ end
data/lib/fields.rb ADDED
@@ -0,0 +1,14 @@
1
+ module SalesforceMigration
2
+ class Fields
3
+ # Salesforce objects and the fields for import
4
+ ISOS_FIELDS = %w{Active__c Address__c Adult_Chargeback_Fee__c Adult_Commission__c Adult_Transaction_Fee__c Agreement_Received__c Background_Check_Complete__c Business_Phone_Number__c Cell_Phone_Number__c Comments__c CreatedBy CreatedById CreatedDate Curren__c CurrencyIsoCode Email_Address__c Fax_Number__c Gambling_Chargeback_Fee__c Gambling_Commission_Fee__c Gambling_Transaction_Fee__c General_Conventional_Chargeback_Fee__c General_Conventional_Commission_Fee__c General_Conventional_Transaction_Fee__c General_Non_Conventional_Chargeback_Fee__c General_Non_Conventional_Commission_Fee__c General_Non_Conventional_Transaction_Fee__c ISO_ID_Assigned__c ISO_ID_Number__c ISO_Owner_Name__c Id KYC_Gathered__c LastActivityDate LastModifiedBy LastModifiedById LastModifiedDate Name New_Hire_Package_Received__c Owner OwnerId Pharmacy_Chargeback_Fee__c Pharmacy_Commission_Fee__c Pharmacy_Transaction_Fee__c Projected_Deals_Count__c Recruiter__c Region__c System_Access_Granted__c Training_Completed__c Welcome_Call_Completed__c Welcome_Package_Sent__c eMerchantPay_ISO_ID__c}
5
+ AGENT_FIELDS = %w{Id Name LastActivityDate CreatedBy CreatedDate CreatedById LastModifiedDate LastModifiedById LastModifiedBy Owner OwnerId Agent_Billing_Address__c Agent_Cell_Phone__c Agent_Id__c Agent_Phone__c Agent_Skype__c Agreement_Received__c Agreement_Sent__c Background_Check_Complete__c Business_Phone_Number__c Comments__c Commission_Fee__c Email_Address__c eMerchantPay_Agent_ID__c Fee_Comments__c ISO_Company__c New_Hire_Package_Received__c One_Time_Commission_Currency__c Recruiter__c Region__c Split_percentage__c System_Access_Granted__c Title__c Transaction_Fee__c}
6
+ ACCOUNT_FIELDS = %w{Additional_Url__c Agents__c AnnualRevenue Approval_Citeria__c Approve_1__c Approve_2__c Approve_3__c BillingCity BillingCountry BillingPostalCode BillingState BillingStreet City__c Comment__c CreatedBy CreatedById CreatedDate Credit_Card__c Curren__c CurrencyIsoCode Current_Volume__c Description Descriptor__c FD_Merchant_Number__c Fax ISO_Name__c Id Industry Info_Capture__c Integration__c Iovation__c IsPartner Jigsaw LastModifiedBy LastModifiedById LastModifiedDate Merchant_Email__c Merchant_Type__c Name NumberOfEmployees Overlying_Company__c Owner OwnerId Phone Projected_Monthly_Volume__c Recruiter__c Secondary_FD_Merchant_Number__c ShippingCity ShippingCountry ShippingPostalCode ShippingState ShippingStreet Status_Comments__c Status__c Threat_Metrix__c Type__c Url__c VBV_MC3D__c Vacation1__c Website}
7
+ PAYMENT_METHODS_FIELDS = %w{Accounts__c Business_Contact__c Business_Type_Coments__c Business_type__c Buy_Rates__c Chargeback_Comments__c Chargeback__c Client_Integration__c Countries2__c Countries__c CreatedBy CreatedById CreatedDate CurrencyIsoCode Id Integration__c LastModifiedBy LastModifiedById LastModifiedDate Name Our_Buy_Rates__c Owner OwnerId Proccessing_Currency__c Processing_Currency__c Rebilling_Comments__c Rebilling__c Refund_Comments__c Refund__c Release_Date__c Settlement_Currency__c Settlement_Cycle__c Settlement__c Tech_Contact__c Type__c V_terminal_Comments__c V_terminal__c}
8
+ BANKS_FIELDS = %w{Account_Number__c BIC_Code__c Merchant_2__c Bank_Address__c Bank_Name__c Contract__c CreatedBy CreatedById CreatedDate CurrencyIsoCode Holder_Address__c IBAN__c Id LastActivityDate LastModifiedBy LastModifiedById LastModifiedDate Name Owner OwnerId Processing_Currency__c Routing_Number__c SWIFT__c Settlement_Currency__c}
9
+ MERCHANTTOAPM_FIELDS = %w{Merchant__c Payment_Methods__c}
10
+ CCRMBASIC__EMAIL_FIELDS = %w{ccrmbasic__To__c ccrmbasic__Cc__c ccrmbasic__Body__c Id Name}
11
+ EMAIL_ASSOCIATION_FIELDS= %w{Merchant_Name__c Email__c}
12
+ SYSTEM_FIELDS = %w{id owner ownerid createddate createdby createdbyid lastactivitydate lastmodifieddate lastmodifiedby lastmodifiedbyid recruiter region}
13
+ end
14
+ end
data/lib/import.rb ADDED
@@ -0,0 +1,553 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__)
2
+
3
+ module SalesforceMigration
4
+ # Workflow for import of specific object
5
+ #
6
+ # import_agents -> load_file_for_import -> transform_csv_file -> prefix_sf_attribute_names
7
+ # -> transform_agents
8
+ # -> populate_sugar -> get_sugarcrm_module_type
9
+ # -> convert_string_to_datetime
10
+ # -> create_association -> find_sugarcrm_object
11
+ # -> create_user
12
+ class Import
13
+
14
+ # Initialize migration import object and start the import
15
+ #
16
+ # @param [Hash] options hash representing passed command line options
17
+ def initialize(options)
18
+ credentials = YAML.load_file(options[:config_file])
19
+ url = credentials['sugar_url']
20
+ username = credentials['sugar_username']
21
+ password = credentials['sugar_password']
22
+ @csv_dir = options[:csv_dir]
23
+ SugarCRM.connect(url, username, password)
24
+ @action = options[:action]
25
+ @isos, @agents, @merchants, @bank_accounts, @agent_users, @iso_users, @emails = [], [], [], [], [], [], []
26
+ @payment_to_merchants, @emails_to_merchants = [], []
27
+ @logger = SalesforceMigration::Runner::create_logger
28
+ @logger.info("Import action started")
29
+ start
30
+ @logger.info("Import action ended successfully")
31
+ end
32
+
33
+ private
34
+
35
+ def start
36
+ import_emails
37
+ import_payment_methods
38
+ import_isos
39
+ import_agents
40
+ import_merchants
41
+ import_settlement_bank_accounts
42
+
43
+ associate_records_to_groups
44
+ end
45
+
46
+ # Import methods. They load the csv file, for the given type
47
+ # get rid of unnecessary information, transforms other fields, so
48
+ # they can match the one in SugarCRM and calls for the populate_sugar method
49
+ # which start the actual import. Keep in mind, that the order of the imports is important
50
+ def import_isos
51
+ records = load_file_for_import 'ISOs__c'
52
+ records = transform_isos(records)
53
+ populate_sugar(records, "iso")
54
+ end
55
+
56
+ def import_agents
57
+ records = load_file_for_import 'Agent__c'
58
+ records = transform_agents(records)
59
+ populate_sugar(records, "agent")
60
+ end
61
+
62
+ def import_merchants
63
+ records = load_file_for_import 'Account'
64
+ records = transform_merchants(records)
65
+ populate_sugar(records, "merchant")
66
+ end
67
+
68
+ def import_settlement_bank_accounts
69
+ records = load_file_for_import 'Banks__c'
70
+ records = transform_settlement_bank_accounts(records)
71
+ populate_sugar(records, "settlement_bank_account")
72
+ end
73
+
74
+ def import_payment_methods
75
+ records = load_file_for_import 'Payment_Methods__c'
76
+ junction_records = load_file_for_import 'MerchantToAPM__c'
77
+ # We don`t need db table to save the many-to-many associations between Merchant and Payment Method,
78
+ # so we just store them in an array and use it later on
79
+ junction_records.each do |jrecord|
80
+ @payment_to_merchants << jrecord
81
+ end
82
+ populate_sugar(records, "payment_method")
83
+ end
84
+
85
+ def import_emails
86
+ records = load_file_for_import 'ccrmbasic__Email__c'
87
+ records = transform_emails(records)
88
+ junction_records = load_file_for_import 'Email_Association__c'
89
+ # We don`t need db table to save the many-to-many associations between Merchant and Payment Method,
90
+ # so we just store them in an array and use it later on
91
+ junction_records.each do |jrecord|
92
+ @emails_to_merchants << jrecord
93
+ end
94
+ populate_sugar(records, "email")
95
+ end
96
+
97
+ # Load CSV for import
98
+ #
99
+ # @param [String] module_name name of the module
100
+ #
101
+ # @return [Array] parsed CSV file
102
+ def load_file_for_import(module_name)
103
+ if @action == 'initial_run'
104
+ filename = "#{@csv_dir}/initial/#{module_name}_export.csv"
105
+ else
106
+ today = lambda { Date.today.to_s }
107
+ dir = "#{@csv_dir}/update/#{today.call}"
108
+ filename = "#{dir}/#{module_name}_export.csv"
109
+ end
110
+ @logger.error("Could not create or find a csv filename for module #{module_name}") unless defined? filename
111
+ @logger.info("Loading CSV file #{filename} for import")
112
+ csv_file = ::CSV.read(filename, :encoding => 'utf-8')
113
+ transform_csv_file(csv_file)
114
+ end
115
+
116
+ # Load CSV file, removes headers and create a hash with the headers as keys
117
+ #
118
+ # @param [Array] file parsed CSV file
119
+ #
120
+ # @return [Array] transformed CSV file
121
+ def transform_csv_file(csv_file)
122
+ @logger.info("Transforming CSV file")
123
+ transformed = []
124
+ headers = csv_file.shift
125
+ headers.map!(&method(:prepare_custom_headers))
126
+
127
+ csv_file.each do |row|
128
+ transformed << Hash[headers.zip row]
129
+ end
130
+ prefix_sf_attribute_names transformed
131
+ end
132
+
133
+ #Remove the __c and lowercase the custom header fields, exported from Salesforce.
134
+ #
135
+ # @param [String] header
136
+ def prepare_custom_headers(header)
137
+ header.end_with?("__c") ? header.slice(0..-4).downcase : header.downcase
138
+ end
139
+
140
+ # Add sf_ prefix to all auto-generated attribute names from Salesforce
141
+ #
142
+ # @param [Array<SugarCRM::Namespace::Object>] records records to be prefixed
143
+ #
144
+ # @return [Array<SugarCRM::Namespace::Object>] returns the records
145
+ def prefix_sf_attribute_names(records)
146
+ sf_attribute_names = SalesforceMigration::Fields::SYSTEM_FIELDS
147
+ records.each do |r|
148
+ sf_attribute_names.each do |field|
149
+ r["sf_#{field.downcase}"] = r[field]
150
+ r.delete(field)
151
+ end
152
+ end
153
+ records
154
+ end
155
+
156
+ def transform_isos(records)
157
+ @logger.info("Transforming ISO fields")
158
+ records.each do |record|
159
+ record['general_c_chargeback_fee'] = record['general_conventional_chargeback_fee']
160
+ record['general_c_commission_fee'] = record['general_conventional_commission_fee']
161
+ record['general_c_transaction_fee'] = record['general_conventional_transaction_fee']
162
+ record['general_nc_chargeback_fee'] = record['general_non_conventional_chargeback_fee']
163
+ record['general_nc_commission_fee'] = record['general_non_conventional_commission_fee']
164
+ record['general_nc_transaction_fee'] = record['general_non_conventional_transaction_fee']
165
+ record.delete 'general_conventional_chargeback_fee'
166
+ record.delete 'general_conventional_commission_fee'
167
+ record.delete 'general_conventional_transaction_fee'
168
+ record.delete 'general_non_conventional_chargeback_fee'
169
+ record.delete 'general_non_conventional_commission_fee'
170
+ record.delete 'general_non_conventional_transaction_fee'
171
+ end
172
+ records
173
+ end
174
+
175
+ def transform_agents(records)
176
+ @logger.info("Transforming fields")
177
+ records.each do |record|
178
+ record['sf_iso'] = record['iso_company']
179
+ record.delete 'iso_company'
180
+ end
181
+ records
182
+ end
183
+
184
+ def transform_merchants(records)
185
+ @logger.info("Transforming Merchant fields")
186
+ records.each do |record|
187
+ record['sf_agent'] = record['agents']
188
+ record['url'] = record['url'].to_s.gsub(/<[^>]*>|^[\n\r]*/, '')
189
+ record['additional_url'] = record['additional_url'].to_s.gsub(/(<[^>]*>|^[\n\r]*)/, '')
190
+ record.delete 'agents'
191
+ record.delete 'sf_lastactivitydate'
192
+ record.delete 'sf_lastmodifieddate'
193
+ record.delete 'sf_region'
194
+ end
195
+ records
196
+ end
197
+
198
+ def transform_settlement_bank_accounts(records)
199
+ @logger.info("Transforming Settlement Bank Account fields")
200
+ records.each do |record|
201
+ record['sf_iso'] = record['iso']
202
+ record['sf_contract'] = record['contract']
203
+ record['sf_merchant'] = record['merchant_2']
204
+ record.delete 'merchant_2'
205
+ record.delete 'iso'
206
+ record.delete 'contract'
207
+ record.delete 'sf_recruiter'
208
+ record.delete 'sf_region'
209
+ end
210
+ records
211
+ end
212
+
213
+ def transform_emails(records)
214
+ @logger.info("Transforming Email fields")
215
+ records.each do |record|
216
+ record['receiver'] = record['ccrmbasic__to']
217
+ record['cc'] = record['ccrmbasic__cc']
218
+ record['body'] = convert_characters(record['ccrmbasic__body']) unless record['ccrmbasic__body'].nil?
219
+
220
+ record.delete 'ccrmbasic__to'
221
+ record.delete 'ccrmbasic__cc'
222
+ record.delete 'ccrmbasic__body'
223
+ end
224
+ records
225
+ end
226
+
227
+ # Convert all string datetime attributes to DateTime objects
228
+ #
229
+ # @param [SugarCRM::Namespace::Object] record the record which attributes must be converted
230
+ #
231
+ # @return [SugarCRM::Namespace::Object] the record
232
+ def convert_string_to_datetime(record)
233
+ record['sf_lastmodifieddate'] = record['sf_lastmodifieddate'].to_datetime if record['sf_lastmodifieddate']
234
+ record['sf_createddate'] = record['sf_createddate'].to_datetime if record['sf_createddate']
235
+ record['sf_lastactivitydate'] = record['sf_lastactivitydate'].to_date if record['sf_lastactivitydate']
236
+ record
237
+ end
238
+
239
+ def convert_characters(string)
240
+ string.gsub!(/\'/, '&apos;')
241
+ string.gsub!(/'/, '')
242
+ string.gsub!(/"/, '')
243
+ string.gsub!(/\"/, '&quot;')
244
+ string
245
+ end
246
+
247
+ # Populate SugarCRM with records
248
+ # If it's initial run, it will create all records from the CSV file
249
+ # If it's update, it will update existing records and create new if necessary
250
+ #
251
+ # @param [Array] records records to be inserted in SugarCRM
252
+ # @param [String] type type of the object
253
+ def populate_sugar(records, type)
254
+ module_type = get_sugarcrm_module_type(type)
255
+ case @action
256
+ when 'initial_run'
257
+ @logger.info("Creating new records for #{type} type")
258
+ records.each do |record|
259
+ create_sugar_record(module_type, record, type)
260
+ end
261
+ when 'update'
262
+ records.each do |record|
263
+ record = convert_string_to_datetime(record)
264
+ existing_record = find_sugarcrm_object(type, 'sf_id', record['sf_id'])
265
+ existing_record = existing_record.first if existing_record.is_a?(Array)
266
+ if existing_record
267
+ #TODO Nil values are not handled properly by SugarCRM in update_attributes, so we must transform them into blank values
268
+ #remove this loop and use the main one!!
269
+ record.each do |key, val|
270
+ record[key] = "" if val.nil?
271
+ end
272
+ @logger.info("Updating record for #{type} #{record['name']}")
273
+ existing_record.update_attributes!(record)
274
+ else
275
+ @logger.info("Creating new record for #{type} #{record['name']}")
276
+ create_sugar_record(module_type, record, type)
277
+ end
278
+ end
279
+ end
280
+ end
281
+
282
+ # Create the actual records and users in SugarCRM. Populates the var_pool
283
+ def create_sugar_record(module_type, record, type)
284
+ record = convert_string_to_datetime(record)
285
+
286
+ obj = module_type.new(record)
287
+ obj.save!
288
+ obj = create_association(obj, type) unless %(email payment_method).include? type
289
+ create_security_group_iso obj if type == 'iso'
290
+
291
+ create_user(obj, type) if %(iso agent).include? type
292
+ populate_var_pool(obj, type)
293
+ end
294
+
295
+ # Populates variables with SugarCRM objects.
296
+ # We use them later on, when associating objects with Security Groups
297
+ # Actually we don`t use @bank_accounts & @emails for now, but it`s probably a good idea to store the objects
298
+ # @param [SugarCRM::Namespace::Object] obj object for which a user will be created
299
+ # @param [String] type type of the object
300
+ def populate_var_pool(obj, type)
301
+ case type
302
+ when 'merchant'
303
+ @merchants << obj
304
+ when 'settlement_bank_account'
305
+ @bank_accounts << obj
306
+ when 'iso'
307
+ @isos << obj
308
+ when 'agent'
309
+ @agents << obj
310
+ when 'email'
311
+ @emails << obj
312
+ end
313
+ end
314
+
315
+ # Create association for agent, merchant, settlement bank Account, Email, Payment Method
316
+ # If it is agent, it will find the ISO by id and create the association
317
+ # If it is merchant, it will find the Agent, Payment Method, User and create the associations
318
+ # If it is Settlement Bank Account it will find the Merchant and create the associations
319
+ # @param [SugarCRM::Namespace::Object] obj the object for which an association will be created
320
+ # @param [String] type type of the object
321
+ #
322
+ # @return [SugarCRM::Namespace::Object] the object
323
+ def create_association(obj, type)
324
+ @logger.info("Creating association for #{type} #{obj.name}")
325
+ case type
326
+ when "agent"
327
+ iso = find_sugarcrm_object('iso', 'sf_id', obj.sf_iso)
328
+ obj.associate! iso if iso
329
+ when "merchant"
330
+
331
+ payment_method_id = find_payment_method_id(obj.sf_id)
332
+ if payment_method_id
333
+ payment_method = find_sugarcrm_object('payment_method', 'sf_id', payment_method_id)
334
+ obj.associate! payment_method
335
+ end
336
+
337
+ email_id = find_email_id(obj.sf_id)
338
+ if email_id
339
+ email = find_sugarcrm_object('email', 'sf_id', email_id)
340
+ obj.associate! email
341
+ end
342
+
343
+ agent = find_sugarcrm_object('agent', 'sf_id', obj.sf_agent)
344
+ if agent
345
+ obj.associate! agent
346
+ obj.assigned_user_id = agent.assigned_user_id
347
+ end
348
+
349
+ when "settlement_bank_account"
350
+ merchant = find_sugarcrm_object('merchant', 'sf_id', obj.sf_merchant)
351
+ obj.associate! merchant if merchant
352
+ end
353
+ obj
354
+ end
355
+
356
+ # Create user associated with SugarCRM object
357
+ # Default email: mail@example.com, default password: 123456
358
+ #
359
+ # @param [SugarCRM::Namespace::Object] obj object for which a user will be created
360
+ # @param [String] type type of the object
361
+ def create_user(obj, type)
362
+ @logger.info("Creating user for #{type} #{obj.name}")
363
+ user = SugarCRM::User.new
364
+ user.user_name = (type == 'agent') ? obj.emerchantpay_agent_id : obj.emerchantpay_iso_id
365
+ user.user_name ||= "EMP"
366
+ user.last_name = obj.name
367
+ user.type_c = type
368
+ #user.email1 = obj.email_address || "mail@example.com"
369
+ user.email1 = 'stefan@emerchantpay.com'
370
+ user.status = 'Inactive'
371
+ user.system_generated_password = false
372
+ user.save!
373
+ obj.assigned_user_id = user.id
374
+ obj.save!
375
+
376
+ populate_user_pool(user, type)
377
+ end
378
+
379
+ # Populates Users as SugarCRM objects.
380
+ # We use them later on, when associating objects with Security Groups
381
+ # @param [SugarCRM::Namespace::Object] Already created user object
382
+ # @param [String] type type of the object
383
+ def populate_user_pool(user, type)
384
+ case type
385
+ when 'iso'
386
+ @iso_users << user
387
+ when 'agent'
388
+ @agent_users << user
389
+ end
390
+ end
391
+
392
+ # Find records(payment methods, emails) in the junction object arrays, given the merchant_id
393
+ # Both payment methods and emails have a many-to-many relationship with the merchant in Salesforce
394
+ def find_payment_method_id(merchant_id)
395
+ @payment_to_merchants.each do |record|
396
+ return record['payment_methods'] if record['merchant'] == merchant_id.to_s
397
+ end
398
+ false
399
+ end
400
+
401
+ def find_email_id(merchant_id)
402
+ @emails_to_merchants.each do |record|
403
+ return record['email'] if record['merchant_name'] == merchant_id.to_s
404
+ end
405
+ false
406
+ end
407
+
408
+ #Creates the Security Group object in SugarCRM
409
+ # @param [SugarCRM::Namespace::EmpIso] Iso object
410
+ def create_security_group_iso(iso)
411
+ @logger.info("Creating SecurityGroup #{iso.name}")
412
+ sg = SugarCRM::SecurityGroup.new(:name => iso.name) unless find_sugarcrm_object('security_group','name', iso.name)
413
+ sg.save! if sg
414
+ end
415
+
416
+ # Assign all records, that need to be in a Security Group, to the Group
417
+ def associate_records_to_groups
418
+ put_isos_into_iso_group
419
+ put_agents_into_iso_group
420
+ put_merchants_into_iso_group
421
+ end
422
+
423
+ def put_isos_into_iso_group
424
+ if @isos
425
+ @logger.info("Puting ISOs into ISO groups")
426
+ role = SugarCRM::ACLRole.find_by_name("isos")
427
+ @isos.each do |iso|
428
+ sg = find_sugarcrm_object('security_group','name', iso.name)
429
+ user = SugarCRM::User.find_by_last_name(iso.name)
430
+ iso.associate! sg
431
+ user.associate! sg
432
+ role.associate! sg
433
+ end
434
+ end
435
+ end
436
+
437
+ def put_agents_into_iso_group
438
+ if @agents
439
+ @logger.info("Putting agent records, agent users and agent role into ISO groups")
440
+ @agents.each do |agent|
441
+ sg = find_sugarcrm_object('security_group','name', agent.emp_iso.first.name) unless agent.emp_iso.empty?
442
+ role = SugarCRM::ACLRole.find_by_name('agents')
443
+ if sg
444
+ user = SugarCRM::User.find_by_last_name(agent.name)
445
+ @logger.info("Puting Agent #{agent.name} in Security Group #{sg.name}")
446
+ agent.associate! sg
447
+ user.associate! sg
448
+ role.associate! sg
449
+ end
450
+ end
451
+ end
452
+ end
453
+
454
+ # Bank accounts, Emails and Payment Methods must be inserted from this method, not separately,
455
+ # because we need to use the merchant object loop, which provides us with the ready merchant id, with which
456
+ # we can check if a merchant has one or more bank acounts, emails, pm, etc.. Otherwise we have to
457
+ # make a separate merchant select in every other method.
458
+ def put_merchants_into_iso_group
459
+ if @merchants
460
+ @logger.info("Puting merchants into ISO groups")
461
+ @merchants.each do |merchant|
462
+ unless (merchant.emp_agent.empty? || merchant.emp_agent.first.emp_iso.empty?)
463
+ sg = find_sugarcrm_object('security_group','name', merchant.emp_agent.first.emp_iso.first.name)
464
+ @logger.info("Puting merchant #{merchant.name} into ISO group")
465
+
466
+ if sg
467
+ merchant.associate! sg
468
+ put_email_objects_into_iso_group(sg, merchant.sf_id)
469
+ put_payment_methods_objects_into_iso_group(sg, merchant.sf_id)
470
+ end
471
+
472
+ bank = find_sugarcrm_object('settlement_bank_account', 'sf_merchant', merchant.sf_id)
473
+ if (bank)
474
+ @logger.info("Puting Bank Account for #{merchant.name} into ISO group")
475
+ put_bank_accounts_into_iso_group(bank, sg)
476
+ end
477
+ end
478
+ end
479
+ end
480
+ end
481
+
482
+ def put_bank_accounts_into_iso_group(banks, sg)
483
+ if banks.is_a?(Array)
484
+ banks.each do |bank|
485
+ bank.associate! sg
486
+ end
487
+ else
488
+ banks.associate! sg
489
+ end
490
+ end
491
+
492
+ def put_email_objects_into_iso_group(sg, merchant_id)
493
+ @logger.info("Puting Emails into ISO group")
494
+ email_id = find_email_id(merchant_id)
495
+ if email_id
496
+ email = find_sugarcrm_object('email', 'sf_id', email_id)
497
+ if email
498
+ if email.is_a?(Array)
499
+ email.each do |e|
500
+ e.associate! sg
501
+ end
502
+ else
503
+ email.associate! sg
504
+ end
505
+ end
506
+ end
507
+ end
508
+
509
+ def put_payment_methods_objects_into_iso_group(sg, merchant_id)
510
+ @logger.info("Puting Payment Methods into ISO group")
511
+ payment_method_id = find_payment_method_id(merchant_id)
512
+ if payment_method_id
513
+ payment_method = find_sugarcrm_object('payment_method', 'sf_id', payment_method_id)
514
+ if payment_method
515
+ if payment_method.is_a?(Array)
516
+ payment_method.each do |pm|
517
+ pm.associate! sg
518
+ end
519
+ else
520
+ payment_method.associate! sg
521
+ end
522
+ end
523
+ end
524
+ end
525
+
526
+ # Returns the SugarCRM module namespace
527
+ # @param [String] type type of the module object
528
+ def get_sugarcrm_module_type(type)
529
+ modules = {
530
+ "iso" => SugarCRM::EmpIso,
531
+ "agent" => SugarCRM::EmpAgent,
532
+ "merchant" => SugarCRM::EmpMerchant,
533
+ "payment_method" => SugarCRM::EmpPaymentmethod,
534
+ "settlement_bank_account" => SugarCRM::EmpSettlementBankAccount,
535
+ "security_group" => SugarCRM::SecurityGroup,
536
+ "email" => SugarCRM::EmpEmail
537
+ }
538
+ modules[type]
539
+ end
540
+
541
+ # Find a SugarCRM object
542
+ #
543
+ # @param [String] type type of the module object
544
+ # @param [String] attribute name of the attribute to search by
545
+ # @param [String] search_string string used in WHERE clause
546
+ # find_sugarcrm_object('emp_ISO', 'sf_id', 'a073000000SSTEYAA5') # => #<SugarCRM::Namespace0::EmpIso ... >
547
+ def find_sugarcrm_object(type, attribute, search_string)
548
+ module_type = get_sugarcrm_module_type(type)._module.name
549
+ SugarCRM.connection.get_entry_list(module_type, "#{module_type.downcase}.#{attribute} = '#{search_string}'")
550
+ end
551
+
552
+ end
553
+ end
data/lib/mailer.rb ADDED
@@ -0,0 +1,117 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__)
2
+
3
+ module SalesforceMigration
4
+
5
+ class Mailer
6
+
7
+ def initialize(options)
8
+ @@config = YAML.load_file(options[:config_file])
9
+ @logger = SalesforceMigration::Runner::create_logger
10
+
11
+ # Ugly way to fix the bug with user_hash update
12
+ # We set up a connection and update it manually.update_attributes does not work.
13
+ # The issue is forwarded to sugarcrm gem crew
14
+ ActiveRecord::Base.establish_connection(
15
+ :adapter => @@config['db_type'],
16
+ :host => "localhost",
17
+ :username => @@config['db_user'],
18
+ :password => @@config['db_password'],
19
+ :database => @@config['db_database']
20
+ )
21
+
22
+ @logger.info("Starting the mailer")
23
+ start
24
+ @logger.info("Mailer finished")
25
+ end
26
+
27
+ private
28
+
29
+ #Search the agent portal database for all users, who are inactive
30
+ #This is the script, that activates them
31
+ def start
32
+ inactive_users = SugarCRM::User.find_all_by_status('Inactive')
33
+
34
+ if inactive_users
35
+ inactive_users.each do |user|
36
+ @logger.info("Sending email to #{user.last_name}")
37
+
38
+ plain_password = generate_password
39
+ hash_password = Digest::MD5.hexdigest plain_password
40
+
41
+ sm = send_mail(user.email1, user.user_name, plain_password)
42
+ if sm
43
+ query = "UPDATE users SET user_hash='#{hash_password}', status='Active' WHERE id='#{user.id}'"
44
+ ActiveRecord::Base.connection.execute(query);
45
+
46
+ @logger.info("Updated user #{user.last_name} status to active")
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ #Send the welcoming email to the user
53
+ #We need the welcoming text
54
+ #@param [String] the email address
55
+ def send_mail(email, username, password)
56
+ mail = Mail.new do
57
+ from 'agentportal@emerchantpay.com'
58
+ to email
59
+ subject 'Welcome to Emerchantpay Agent Portal'
60
+ html_part do
61
+ body Mailer::create_template(username, password)
62
+ end
63
+ end
64
+ mail.delivery_method :smtp, {:address => "emp-ldn-exch01.emp.internal.com",
65
+ :port => 25,
66
+ :domain => "emp.internal.com",
67
+ :authentication => nil,
68
+ :enable_starttls_auto => true}
69
+
70
+ if mail.deliver!
71
+ true
72
+ else
73
+ @logger.error("Email for user #{last_name} failed!")
74
+ end
75
+ end
76
+
77
+ # Generates the plain text password that is going to be
78
+ # send to the user in the email
79
+ # ActiveSupport::SecureRandom is deprecated in Rails > 3.1
80
+ def generate_password
81
+ str = SecureRandom.hex(6)
82
+ str
83
+ end
84
+
85
+ def self.create_template(username, password)
86
+ email_template = <<-TEMPLATE
87
+ Dear valued partner,<br/><br/>
88
+
89
+ We have created an account for you to access the newly created eMerchantPay <a href="#{@@config["sugar_url"]}">Agent Portal</a><br/><br/>
90
+
91
+ Your account data is:<br/><br/>
92
+
93
+ Login: <b>#{username}</b><br/>
94
+ Password: <b>#{password}</b><br/><br/>
95
+
96
+ Features:<br/>
97
+
98
+ <ul>
99
+ <li>Based on the acclaimed SugarCRM, check details <a href="http://www.sugarcrm.com">here</a></li>
100
+ <li>Exports of merchants and various data items in different formats for further processing</li>
101
+ <li>Viewing information for your Merchants and the associated resources - bank accounts, emails, payment methods, etc.</li>
102
+ <li>Homepage customization for personalization and access</li>
103
+ <li>Can be accessed by mobile phone</li>
104
+ <li>And many others...</li>
105
+ </ul>
106
+
107
+ <br/>
108
+ If you have any questions, send an email to support@emerchantpay.com (also available by clicking the Support link from the main menu in the <a href="#{@@config["sugar_url"]}">Agent Portal</a>)
109
+ <br/><br/>
110
+ ---<br/><br/>
111
+
112
+ Your <a href="http://www.emerchantpay.com">eMerchantPay</a> team<br/><br/>
113
+ TEMPLATE
114
+ email_template
115
+ end
116
+ end
117
+ end
data/lib/sf_migrate.rb ADDED
@@ -0,0 +1,77 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__)
2
+
3
+ require 'optparse'
4
+ require 'csv'
5
+ require 'yaml'
6
+ require 'logger'
7
+ require 'fileutils'
8
+ require 'databasedotcom'
9
+ require 'sugarcrm'
10
+ require 'mail'
11
+ require 'active_support/core_ext'
12
+ require 'active_record'
13
+ require 'fields'
14
+ require 'export'
15
+ require 'import'
16
+ require 'mailer'
17
+
18
+ module SalesforceMigration
19
+ module Runner
20
+ extend self
21
+
22
+ # Start the migration dependening on command line argument: `initial_run` or `update`
23
+ def start
24
+ options = {}
25
+ # Where do we need to store the csvs?
26
+ options[:csv_dir] = "/var/sugarcrm/csv"
27
+ options[:log_dir] = "/var/log/sugarcrm"
28
+ options[:config_file] = File.join(File.dirname(__FILE__), "../config/credentials.yaml")
29
+ @log_dir = options[:log_dir]
30
+ create_logger
31
+ optparse = OptionParser.new do |opts|
32
+ opts.banner = "Usage: sf_migrate [options]"
33
+ opts.on("-a", "--action NAME", "Start the initial_run or the daily update") do |action|
34
+ options[:action] = action
35
+ end
36
+ opts.on("-f", "--config_file CONFIG_FILE", "Supply an optional config file") do |f|
37
+ options[:config_file] = File.expand_path(f) if File.exists? f
38
+ end
39
+ opts.on("-c", "--csv_dir CSV_DIR", "Set the directory in which exported csv from Salesforce will be kept") do |c|
40
+ options[:csv_dir] = c
41
+ end
42
+ opts.on("-l", "--log_dir LOG_DIR", "Set the directory in which the logger will reside") do |l|
43
+ options[:log_dir] = l
44
+ end
45
+ opts.on("-m", "--send_mail", "Send activation email to every new user") do |m|
46
+ options[:send_mail] = m
47
+ end
48
+ opts.on( '-h', '--help', 'Display this screen' ) do
49
+ puts opts
50
+ exit
51
+ end
52
+ end
53
+ optparse.parse!
54
+ begin
55
+ SalesforceMigration::Export.new(options)
56
+ SalesforceMigration::Import.new(options)
57
+ SalesforceMigration::Mailer.new(options) if options[:send_mail]
58
+ rescue => e
59
+ @logger.error(e)
60
+ end
61
+ end
62
+
63
+ def create_logger
64
+ original_formatter = Logger::Formatter.new
65
+ today = lambda { Date.today.to_s }
66
+ dir = "#{@log_dir}/#{today.call}"
67
+ file = "#{dir}/migration.log"
68
+ FileUtils.mkdir_p(dir) unless Dir.exists? dir
69
+ @logger = Logger.new(file)
70
+ @logger.formatter = proc { |severity, datetime, progname, msg|
71
+ original_formatter.call("IMPORT", datetime, progname, msg)
72
+ }
73
+ @logger
74
+ end
75
+
76
+ end
77
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sf_migrate
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Dimitar KostovStefan Slaveykov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2013-05-30 00:00:00 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sugarcrm_emp
16
+ prerelease: false
17
+ requirement: &id001 !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.10.1
22
+ type: :runtime
23
+ version_requirements: *id001
24
+ - !ruby/object:Gem::Dependency
25
+ name: databasedotcom_emp
26
+ prerelease: false
27
+ requirement: &id002 !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ~>
30
+ - !ruby/object:Gem::Version
31
+ version: 1.3.1
32
+ type: :runtime
33
+ version_requirements: *id002
34
+ - !ruby/object:Gem::Dependency
35
+ name: activesupport
36
+ prerelease: false
37
+ requirement: &id003 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ version: 3.2.6
42
+ type: :runtime
43
+ version_requirements: *id003
44
+ - !ruby/object:Gem::Dependency
45
+ name: activerecord
46
+ prerelease: false
47
+ requirement: &id004 !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ~>
50
+ - !ruby/object:Gem::Version
51
+ version: 3.2.13
52
+ type: :runtime
53
+ version_requirements: *id004
54
+ - !ruby/object:Gem::Dependency
55
+ name: mail
56
+ prerelease: false
57
+ requirement: &id005 !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 2.5.3
62
+ type: :runtime
63
+ version_requirements: *id005
64
+ - !ruby/object:Gem::Dependency
65
+ name: roo
66
+ prerelease: false
67
+ requirement: &id006 !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ~>
70
+ - !ruby/object:Gem::Version
71
+ version: 1.10.2
72
+ type: :runtime
73
+ version_requirements: *id006
74
+ description: SalesForce to SugarCRM migration tool
75
+ email:
76
+ - stefan@emerchantpay.com
77
+ executables:
78
+ - sf_migrate
79
+ extensions: []
80
+
81
+ extra_rdoc_files: []
82
+
83
+ files:
84
+ - README.md
85
+ - Gemfile
86
+ - bin/sf_migrate
87
+ - lib/fields.rb
88
+ - lib/export.rb
89
+ - lib/import.rb
90
+ - lib/mailer.rb
91
+ - lib/sf_migrate.rb
92
+ - config/credentials.yaml.example
93
+ homepage:
94
+ licenses: []
95
+
96
+ metadata: {}
97
+
98
+ post_install_message:
99
+ rdoc_options: []
100
+
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - &id007
106
+ - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: "0"
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - *id007
112
+ requirements: []
113
+
114
+ rubyforge_project:
115
+ rubygems_version: 2.0.3
116
+ signing_key:
117
+ specification_version: 4
118
+ summary: Makes a bridge between Salesforce and SugarCRM
119
+ test_files: []
120
+