1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
21 from osv import fields, osv
23 from tools.translate import _
24 from import_base.import_framework import *
25 from import_base.mapper import *
29 pp = pprint.PrettyPrinter(indent=4)
32 class related_ref(dbmapper):
33 def __init__(self, type):
36 def __call__(self, external_val):
37 if external_val.get('parent_type') in self.type and external_val.get('parent_id'):
38 return self.parent.xml_id_exist(external_val['parent_type'], external_val['parent_id'])
42 class sugar_import(import_framework):
43 TABLE_CONTACT = 'Contacts'
44 TABLE_ACCOUNT = 'Accounts'
46 TABLE_EMPLOYEE = 'Employees'
47 TABLE_RESSOURCE = "resource"
48 TABLE_OPPORTUNITY = 'Opportunities'
50 TABLE_STAGE = 'crm_stage'
52 TABLE_MEETING = 'Meetings'
54 TABLE_PROJECT = 'Project'
55 TABLE_PROJECT_TASK = 'ProjectTask'
61 PortType,sessionid = sugar.login(self.context.get('username',''), self.context.get('password',''), self.context.get('url',''))
62 self.context['port'] = PortType
63 self.context['session_id'] = sessionid
65 def get_data(self, table):
67 return sugar.search(self.context.get('port'), self.context.get('session_id'), table)
71 def get_category(self, val, model, name):
72 fields = ['name', 'object_id']
74 return self.import_object(fields, data, 'crm.case.categ', 'crm_categ', name, [('object_id.model','=',model), ('name', 'ilike', name)])
76 def get_job_title(self, dict, salutation):
77 fields = ['shortcut', 'name', 'domain']
78 data = [salutation, salutation, 'Contact']
79 return self.import_object(fields, data, 'res.partner.title', 'contact_title', salutation, [('shortcut', '=', salutation)])
81 def get_campaign_id(self, dict, val):
84 return self.import_object(fields, data, 'crm.case.resource.type', 'crm_campaign', val)
86 def get_all_states(self, external_val, country_id):
87 """Get states or create new state unless country_id is False"""
88 state_code = external_val[0:3] #take the tree first char
89 fields = ['country_id/id', 'name', 'code']
90 data = [country_id, external_val, state_code]
92 return self.import_object(fields, data, 'res.country.state', 'country_state', external_val)
96 def get_all_countries(self, val):
97 """Get Country, if no country match do not create anything, to avoid duplicate country code"""
98 return self.mapped_id_if_exist('res.country', [('name', 'ilike', val)], 'country', val)
100 def get_float_time(self, dict, hour, min):
101 min = int(min) * 100 / 60
102 return "%s.%i" % (hour, min)
113 def get_alarm_id(self, dict_val, val):
115 '60': '1 minute before',
116 '300': '5 minutes before',
117 '600': '10 minutes before',
118 '900': '15 minutes before',
119 '1800':'30 minutes before',
120 '3600': '1 hour before',
122 return self.mapped_id_if_exist('res.alarm', [('name', 'like', alarm_dict.get(val))], 'alarm', val)
126 def get_meeting_mapping(self):
128 'model' : 'crm.meeting',
129 'dependencies' : [self.TABLE_CONTACT, self.TABLE_OPPORTUNITY, self.TABLE_LEAD],
132 'date': 'date_start',
133 'duration': call(self.get_float_time, value('duration_hours'), value('duration_minutes')),
134 'location': 'location',
135 'alarm_id/id': call(self.get_alarm_id, value('reminder_time')),
136 'user_id/id': ref(self.TABLE_USER, 'assigned_user_id'),
137 'partner_id/id': related_ref(self.TABLE_ACCOUNT),
138 'partner_address_id/id': related_ref(self.TABLE_CONTACT),
139 'state': map_val('status', self.meeting_state)
146 def get_opportunity_status(self, sugar_val):
147 fields = ['name', 'type']
148 name = 'Opportunity_' + sugar_val['sales_stage']
149 data = [sugar_val['sales_stage'], 'Opportunity']
150 return self.import_object(fields, data, 'crm.case.stage', self.TABLE_STAGE, name, [('type', '=', 'opportunity'), ('name', 'ilike', sugar_val['sales_stage'])])
152 def import_opportunity_contact(self, val):
153 sugar_opportunities_contact = set(sugar.relation_search(self.context.get('port'), self.context.get('session_id'), 'Opportunities', module_id=val.get('id'), related_module='Contacts', query=None, deleted=None))
155 partner_contact_id = False
156 partner_contact_email = False
157 partner_address_obj = self.obj.pool.get('res.partner.address')
158 partner_xml_id = self.name_exist(self.TABLE_ACCOUNT, val['account_name'], 'res.partner')
160 for contact in sugar_opportunities_contact:
161 address_id = self.get_mapped_id(self.TABLE_CONTACT, contact)
163 address = partner_address_obj.browse(self.cr, self.uid, address_id)
164 partner_name = address.partner_id and address.partner_id.name or False
165 if not partner_name: #link with partner id
166 fields = ['partner_id/id']
167 data = [partner_xml_id]
168 self.import_object(fields, data, 'res.partner.address', self.TABLE_CONTACT, contact, self.DO_NOT_FIND_DOMAIN)
169 if not partner_name or partner_name == val.get('account_name'):
170 partner_contact_id = self.xml_id_exist(self.TABLE_CONTACT, contact)
171 partner_contact_email = address.email
172 return partner_contact_id, partner_contact_email
174 def import_opp(self, val):
175 partner_contact_name, partner_contact_email = self.import_opportunity_contact(val)
176 val['partner_address_id/id'] = partner_contact_name
177 val['email_from'] = partner_contact_email
180 def get_opp_mapping(self):
182 'model' : 'crm.lead',
183 'dependencies' : [self.TABLE_USER, self.TABLE_ACCOUNT, self.TABLE_CONTACT],
184 'hook' : self.import_opp,
187 'probability': 'probability',
188 'partner_id/id': refbyname(self.TABLE_ACCOUNT, 'account_name', 'res.partner'),
189 'title_action': 'next_step',
190 'partner_address_id/id': 'partner_address_id/id',
191 'planned_revenue': 'amount',
192 'date_deadline': 'date_closed',
193 'user_id/id' : ref(self.TABLE_USER, 'assigned_user_id'),
194 'stage_id/id' : self.get_opportunity_status,
195 'type' : const('opportunity'),
196 'categ_id/id': call(self.get_category, 'crm.lead', value('opportunity_type')),
197 'email_from': 'email_from',
198 'state' : const('open'), #TODO
205 def get_lead_status(self, sugar_val):
206 fields = ['name', 'type']
207 name = 'lead_' + sugar_val.get('status', '')
208 data = [sugar_val.get('status', ''), 'lead']
209 return self.import_object(fields, data, 'crm.case.stage', self.TABLE_STAGE, name, [('type', '=', 'lead'), ('name', 'ilike', sugar_val.get('status', ''))])
214 'In Progress': 'open',
215 'Recycled': 'cancel',
221 def import_lead(self, val):
222 if val.get('opportunity_id'): #if lead is converted into opp, don't import as lead
225 if val.get('primary_address_country'):
226 country_id = self.get_all_countries(val.get('primary_address_country'))
227 val['country_id/id'] = country_id
228 val['state_id/id'] = self.get_all_states(val.get('primary_address_state'), country_id)
231 def get_lead_mapping(self):
233 'model' : 'crm.lead',
234 'hook' : self.import_lead,
236 'name': concat('first_name', 'last_name'),
237 'contact_name': concat('first_name', 'last_name'),
238 'description': ppconcat('description', 'refered_by', 'lead_source', 'lead_source_description', 'website', 'email2', 'status_description', 'lead_source_description', 'do_not_call'),
239 'partner_name': 'account_name',
240 'email_from': 'email1',
241 'phone': 'phone_work',
242 'mobile': 'phone_mobile',
243 'title/id': call(self.get_job_title, value('salutation')),
245 'street': 'primary_address_street',
246 'street2': 'alt_address_street',
247 'zip': 'primary_address_postalcode',
248 'city':'primary_address_city',
249 'user_id/id' : ref(self.TABLE_USER, 'assigned_user_id'),
250 'stage_id/id' : self.get_lead_status,
251 'type' : const('lead'),
252 'state': map_val('status', self.lead_state) ,
254 'referred': 'refered_by',
255 'optout': 'do_not_call',
256 'type_id/id': call(self.get_campaign_id, value('lead_source')),
257 'country_id/id': 'country_id/id',
258 'state_id/id': 'state_id/id'
265 def get_email(self, val):
266 return val.get('email1') + ','+ val.get('email2')
268 def import_contact(self, val):
269 if val.get('primary_address_country'):
270 country_id = self.get_all_countries(val.get('primary_address_country'))
271 state = self.get_all_states(val.get('primary_address_state'), country_id)
272 val['country_id/id'] = country_id
273 val['state_id/id'] = state
276 def get_contact_mapping(self):
278 'model' : 'res.partner.address',
279 'dependencies' : [self.TABLE_ACCOUNT],
280 'hook' : self.import_contact,
282 'name': concat('first_name', 'last_name'),
283 'partner_id/id': ref(self.TABLE_ACCOUNT,'account_id'),
284 'phone': 'phone_work',
285 'mobile': 'phone_mobile',
288 'street': 'primary_address_street',
289 'zip': 'primary_address_postalcode',
290 'city': 'primary_address_city',
291 'country_id/id': 'country_id/id',
292 'state_id/id': 'state_id/id',
293 'email': self.get_email,
294 'type': const('contact')
301 def get_address_type(self, val, type):
302 if type == 'invoice':
303 type_address = 'billing'
305 type_address = 'shipping'
307 map_partner_address = {
309 'phone': 'phone_office',
310 'mobile': 'phone_mobile',
313 'street': type_address + '_address_street',
314 'zip': type_address +'_address_postalcode',
315 'city': type_address +'_address_city',
316 'country_id/id': 'country_id/id',
321 if val.get(type_address +'_address_country'):
322 country_id = self.get_all_countries(val.get(type_address +'_address_country'))
323 state = self.get_all_states(val.get(type_address +'_address_state'), country_id)
324 val['country_id/id'] = country_id
325 val['state_id/id'] = state
327 val['id_new'] = val['id'] + '_address_' + type
328 return self.import_object_mapping(map_partner_address, val, 'res.partner.address', self.TABLE_CONTACT, val['id_new'], self.DO_NOT_FIND_DOMAIN)
330 def get_partner_address(self, val):
332 type_dict = {'billing_address_street' : 'invoice', 'shipping_address_street' : 'delivery'}
333 for key, type_value in type_dict.items():
335 id = self.get_address_type(val, type_value)
336 address_id.append(id)
338 return ','.join(address_id)
340 def get_partner_mapping(self):
342 'model' : 'res.partner',
343 'dependencies' : [self.TABLE_USER],
346 'website': 'website',
347 'user_id/id': ref(self.TABLE_USER,'assigned_user_id'),
349 'comment': ppconcat('description', 'employees', 'ownership', 'annual_revenue', 'rating', 'industry', 'ticker_symbol'),
350 'customer': const('1'),
351 'supplier': const('0'),
352 'address/id':'address/id',
353 'parent_id/id_parent' : 'parent_id',
354 'address/id' : self.get_partner_address,
361 def get_ressource(self, val):
363 'name': concat('first_name', 'last_name'),
365 return self.import_object_mapping(map_resource, val, 'resource.resource', self.TABLE_RESSOURCE, val['id'], self.DO_NOT_FIND_DOMAIN)
367 def get_job_id(self, val):
369 data = [val.get('title')]
370 return self.import_object(fields, data, 'hr.job', 'hr_job', val.get('title'))
372 def get_user_address(self, val):
374 'name': concat('first_name', 'last_name'),
375 'city': 'address_city',
376 'country_id/id': 'country_id/id',
377 'state_id/id': 'state_id/id',
378 'street': 'address_street',
379 'zip': 'address_postalcode',
383 if val.get('address_country'):
384 country_id = self.get_all_countries(val.get('address_country'))
385 state_id = self.get_all_states(val.get('address_state'), country_id)
386 val['country_id/id'] = country_id
387 val['state_id/id'] = state_id
389 return self.import_object_mapping(map_user_address, val, 'res.partner.address', self.TABLE_CONTACT, val['id'], self.DO_NOT_FIND_DOMAIN)
391 def get_employee_mapping(self):
393 'model' : 'hr.employee',
394 'dependencies' : [self.TABLE_USER],
396 'resource_id/id': self.get_ressource,
397 'name': concat('first_name', 'last_name'),
398 'work_phone': 'phone_work',
399 'mobile_phone': 'phone_mobile',
400 'user_id/id': ref(self.TABLE_USER, 'id'),
401 'address_home_id/id': self.get_user_address,
402 'notes': 'description',
403 'job_id/id': self.get_job_id,
410 def import_user(self, val):
411 user_obj = self.obj.pool.get('res.users')
412 user_ids = user_obj.search(self.cr, self.uid, [('login', '=', val.get('user_name'))])
414 val['.id'] = str(user_ids[0])
416 val['password'] = 'sugarcrm' #default password for all user #TODO needed in documentation
418 val['context_lang'] = self.context.get('lang','en_US')
421 def get_users_department(self, val):
422 dep = val.get('department')
427 return self.import_object(fields, data, 'hr.department', 'hr_department_user', dep)
429 def get_user_mapping(self):
431 'model' : 'res.users',
432 'hook' : self.import_user,
434 'name': concat('first_name', 'last_name'),
435 'login': 'user_name',
436 'context_lang' : 'context_lang',
437 'password' : 'password',
439 'context_department_id/id': self.get_users_department,
443 def get_mapping(self):
445 self.TABLE_USER : self.get_user_mapping(),
446 self.TABLE_EMPLOYEE : self.get_employee_mapping(),
447 self.TABLE_ACCOUNT : self.get_partner_mapping(),
448 self.TABLE_CONTACT : self.get_contact_mapping(),
449 self.TABLE_LEAD : self.get_lead_mapping(),
450 self.TABLE_OPPORTUNITY : self.get_opp_mapping(),
451 self.TABLE_MEETING : self.get_meeting_mapping(),
458 class import_sugarcrm(osv.osv):
459 """Import SugarCRM DATA"""
461 _name = "import.sugarcrm"
462 _description = __doc__
464 'opportunity': fields.boolean('Leads and Opportunities', help="If Opportunities are checked, SugarCRM opportunities data imported in OpenERP crm-Opportunity form"),
465 'user': fields.boolean('Users', help="If Users are checked, SugarCRM Users data imported in OpenERP Users form"),
466 'contact': fields.boolean('Contacts', help="If Contacts are checked, SugarCRM Contacts data imported in OpenERP partner address form"),
467 'account': fields.boolean('Accounts', help="If Accounts are checked, SugarCRM Accounts data imported in OpenERP partners form"),
468 'employee': fields.boolean('Employee', help="If Employees is checked, SugarCRM Employees data imported in OpenERP employees form"),
469 'meeting': fields.boolean('Meetings', help="If Meetings is checked, SugarCRM Meetings data imported in OpenERP meetings form"),
470 'call': fields.boolean('Calls', help="If Calls is checked, SugarCRM Calls data imported in OpenERP phonecalls form"),
471 'claim': fields.boolean('Claims', help="If Claims is checked, SugarCRM Claims data imported in OpenERP Claims form"),
472 'email': fields.boolean('Emails', help="If Emails is checked, SugarCRM Emails data imported in OpenERP Emails form"),
473 'project': fields.boolean('Projects', help="If Projects is checked, SugarCRM Projects data imported in OpenERP Projects form"),
474 'project_task': fields.boolean('Project Tasks', help="If Project Tasks is checked, SugarCRM Project Tasks data imported in OpenERP Project Tasks form"),
475 'task': fields.boolean('Tasks', help="If Tasks is checked, SugarCRM Tasks data imported in OpenERP Meetings form"),
476 'bug': fields.boolean('Bugs', help="If Bugs is checked, SugarCRM Bugs data imported in OpenERP Project Issues form"),
477 'attachment': fields.boolean('Attachments', help="If Attachments is checked, SugarCRM Notes data imported in OpenERP's Related module's History with attachment"),
478 'document': fields.boolean('Documents', help="If Documents is checked, SugarCRM Documents data imported in OpenERP Document Form"),
479 'username': fields.char('User Name', size=64),
480 'password': fields.char('Password', size=24),
482 _defaults = {#to be set to true, but easier for debugging
483 'opportunity': False,
494 'project_task': False,
499 def get_key(self, cr, uid, ids, context=None):
500 """Select Key as For which Module data we want import data."""
504 for current in self.browse(cr, uid, ids, context):
505 if current.opportunity:
506 key_list.append('Leads')
507 key_list.append('Opportunities')
509 key_list.append('Users')
511 key_list.append('Contacts')
513 key_list.append('Accounts')
515 key_list.append('Employees')
517 key_list.append('Meetings')
519 key_list.append('Tasks')
521 key_list.append('Calls')
523 key_list.append('Claims')
525 key_list.append('Emails')
527 key_list.append('Projects')
528 if current.project_task:
529 key_list.append('Project Tasks')
531 key_list.append('Bugs')
532 if current.attachment:
533 key_list.append('Notes')
535 key_list.append('Documents')
538 def import_all(self, cr, uid, ids, context=None):
540 # """Import all sugarcrm data into openerp module"""
541 keys = self.get_key(cr, uid, ids, context)
543 imp = sugar_import(self, cr, uid, "sugarcrm", "import_sugarcrm", context)
546 obj_model = self.pool.get('ir.model.data')
547 model_data_ids = obj_model.search(cr,uid,[('model','=','ir.ui.view'),('name','=','import.message.form')])
548 resource_id = obj_model.read(cr, uid, model_data_ids, fields=['res_id'])
552 'res_model': 'import.message',
553 'views': [(resource_id,'form')],
554 'type': 'ir.actions.act_window',