sf_migrate 1.0.2

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