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):
66 return sugar.search(self.context.get('port'), self.context.get('session_id'), table)
70 def get_category(self, val, model, name):
71 fields = ['name', 'object_id']
73 return self.import_object(fields, data, 'crm.case.categ', 'crm_categ', name, [('object_id.model','=',model), ('name', 'ilike', name)])
75 def get_job_title(self, dict, salutation):
76 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)
95 def get_all_countries(self, val):
96 """Get Country, if no country match do not create anything, to avoid duplicate country code"""
97 return self.mapped_id_if_exist('res.country', [('name', 'ilike', val)], 'country', val)
99 def get_float_time(self, dict, hour, min):
100 min = int(min) * 100 / 60
101 return "%s.%i" % (hour, min)
110 def import_project_account(self, val):
112 partner_invoice_id = False
113 model_obj = self.obj.pool.get('ir.model.data')
114 partner_obj = self.obj.pool.get('res.partner')
115 partner_address_obj = self.obj.pool.get('res.partner.address')
116 sugar_project_account = sugar.relation_search(self.context.get('port'), self.context.get('session_id'), 'Project', module_id=val.get('id'), related_module='Accounts', query=None, deleted=None)
117 for account_id in sugar_project_account:
118 partner_id = self.get_mapped_id(self.TABLE_ACCOUNT, account_id)
119 partner_invoice_ids= partner_address_obj.search(self.cr, self.uid, [('partner_id', '=', partner_id), ('type', '=', 'invoice')])
120 if partner_invoice_ids:
121 partner_invoice_id = partner_invoice_ids[0]
122 return partner_id, partner_invoice_id
124 def import_project(self, val):
125 partner_id, partner_invoice_id = self.import_project_account(val)
126 val['partner_id/.id'] = partner_id
127 val['contact_id/.id'] = partner_invoice_id
130 def get_project_mapping(self):
132 'model' : 'project.project',
133 'dependencies' : [self.TABLE_CONTACT, self.TABLE_ACCOUNT, self.TABLE_USER],
134 'hook' : self.import_project,
137 'date_start': 'estimated_start_date',
138 'date': 'estimated_end_date',
139 'user_id/id': ref(self.TABLE_USER, 'assigned_user_id'),
140 'partner_id/.id': 'partner_id/.id',
141 'contact_id/.id': 'contact_id/.id',
142 'state': map_val('status', self.project_state)
150 'Completed' : 'done',
151 'Not Started':'draft',
152 'In Progress': 'open',
153 'Pending Input': 'draft',
157 def get_task_mapping(self):
159 'model' : 'crm.meeting',
160 'dependencies' : [self.TABLE_CONTACT, self.TABLE_ACCOUNT, self.TABLE_USER],
163 'date': 'date_start' or datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
164 'date_deadline': 'date_due' or datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
165 'user_id/id': ref(self.TABLE_USER, 'assigned_user_id'),
166 'categ_id/id': call(self.get_category, 'crm.meeting', 'Tasks'),
167 'partner_id/id': related_ref(self.TABLE_ACCOUNT),
168 'partner_address_id/id': related_ref(self.TABLE_CONTACT),
169 'state': map_val('status', self.task_state)
179 'Not Held': 'pending',
182 def get_calls_mapping(self):
184 'model' : 'crm.phonecall',
185 'dependencies' : [self.TABLE_ACCOUNT, self.TABLE_CONTACT, self.TABLE_OPPORTUNITY, self.TABLE_LEAD],
188 'date': 'date_start',
189 'duration': call(self.get_float_time, value('duration_hours'), value('duration_minutes')),
190 'user_id/id': ref(self.TABLE_USER, 'assigned_user_id'),
191 'partner_id/id': related_ref(self.TABLE_ACCOUNT),
192 'partner_address_id/id': related_ref(self.TABLE_CONTACT),
193 'categ_id/id': call(self.get_category, 'crm.phonecall', value('direction')),
194 'opportunity_id/id': related_ref(self.TABLE_OPPORTUNITY),
195 'description': 'description',
196 'state': map_val('status', self.call_state)
209 def get_alarm_id(self, dict_val, val):
211 '60': '1 minute before',
212 '300': '5 minutes before',
213 '600': '10 minutes before',
214 '900': '15 minutes before',
215 '1800':'30 minutes before',
216 '3600': '1 hour before',
218 return self.mapped_id_if_exist('res.alarm', [('name', 'like', alarm_dict.get(val))], 'alarm', val)
221 def get_meeting_mapping(self):
223 'model' : 'crm.meeting',
224 'dependencies' : [self.TABLE_CONTACT, self.TABLE_OPPORTUNITY, self.TABLE_LEAD],
227 'date': 'date_start',
228 'duration': call(self.get_float_time, value('duration_hours'), value('duration_minutes')),
229 'location': 'location',
230 'alarm_id/id': call(self.get_alarm_id, value('reminder_time')),
231 'user_id/id': ref(self.TABLE_USER, 'assigned_user_id'),
232 'partner_id/id': related_ref(self.TABLE_ACCOUNT),
233 'partner_address_id/id': related_ref(self.TABLE_CONTACT),
234 'state': map_val('status', self.meeting_state)
241 def get_opportunity_status(self, sugar_val):
242 fields = ['name', 'type']
243 name = 'Opportunity_' + sugar_val['sales_stage']
244 data = [sugar_val['sales_stage'], 'Opportunity']
245 return self.import_object(fields, data, 'crm.case.stage', self.TABLE_STAGE, name, [('type', '=', 'opportunity'), ('name', 'ilike', sugar_val['sales_stage'])])
247 def import_opportunity_contact(self, val):
248 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))
250 partner_contact_id = False
251 partner_contact_email = False
252 partner_address_obj = self.obj.pool.get('res.partner.address')
253 partner_xml_id = self.name_exist(self.TABLE_ACCOUNT, val['account_name'], 'res.partner')
255 for contact in sugar_opportunities_contact:
256 address_id = self.get_mapped_id(self.TABLE_CONTACT, contact)
258 address = partner_address_obj.browse(self.cr, self.uid, address_id)
259 partner_name = address.partner_id and address.partner_id.name or False
260 if not partner_name: #link with partner id
261 fields = ['partner_id/id']
262 data = [partner_xml_id]
263 self.import_object(fields, data, 'res.partner.address', self.TABLE_CONTACT, contact, self.DO_NOT_FIND_DOMAIN)
264 if not partner_name or partner_name == val.get('account_name'):
265 partner_contact_id = self.xml_id_exist(self.TABLE_CONTACT, contact)
266 partner_contact_email = address.email
267 return partner_contact_id, partner_contact_email
269 def import_opp(self, val):
270 partner_contact_name, partner_contact_email = self.import_opportunity_contact(val)
271 val['partner_address_id/id'] = partner_contact_name
272 val['email_from'] = partner_contact_email
275 def get_opp_mapping(self):
277 'model' : 'crm.lead',
278 'dependencies' : [self.TABLE_USER, self.TABLE_ACCOUNT, self.TABLE_CONTACT],
279 'hook' : self.import_opp,
282 'probability': 'probability',
283 'partner_id/id': refbyname(self.TABLE_ACCOUNT, 'account_name', 'res.partner'),
284 'title_action': 'next_step',
285 'partner_address_id/id': 'partner_address_id/id',
286 'planned_revenue': 'amount',
287 'date_deadline': 'date_closed',
288 'user_id/id' : ref(self.TABLE_USER, 'assigned_user_id'),
289 'stage_id/id' : self.get_opportunity_status,
290 'type' : const('opportunity'),
291 'categ_id/id': call(self.get_category, 'crm.lead', value('opportunity_type')),
292 'email_from': 'email_from',
293 'state' : const('open'), #TODO
300 def get_lead_status(self, sugar_val):
301 fields = ['name', 'type']
302 name = 'lead_' + sugar_val.get('status', '')
303 data = [sugar_val.get('status', ''), 'lead']
304 return self.import_object(fields, data, 'crm.case.stage', self.TABLE_STAGE, name, [('type', '=', 'lead'), ('name', 'ilike', sugar_val.get('status', ''))])
309 'In Progress': 'open',
310 'Recycled': 'cancel',
316 def import_lead(self, val):
317 if val.get('opportunity_id'): #if lead is converted into opp, don't import as lead
319 if val.get('primary_address_country'):
320 country_id = self.get_all_countries(val.get('primary_address_country'))
321 val['country_id/id'] = country_id
322 val['state_id/id'] = self.get_all_states(val.get('primary_address_state'), country_id)
325 def get_lead_mapping(self):
327 'model' : 'crm.lead',
328 'hook' : self.import_lead,
330 'name': concat('first_name', 'last_name'),
331 'contact_name': concat('first_name', 'last_name'),
332 'description': ppconcat('description', 'refered_by', 'lead_source', 'lead_source_description', 'website', 'email2', 'status_description', 'lead_source_description', 'do_not_call'),
333 'partner_name': 'account_name',
334 'email_from': 'email1',
335 'phone': 'phone_work',
336 'mobile': 'phone_mobile',
337 'title/id': call(self.get_job_title, value('salutation')),
339 'street': 'primary_address_street',
340 'street2': 'alt_address_street',
341 'zip': 'primary_address_postalcode',
342 'city':'primary_address_city',
343 'user_id/id' : ref(self.TABLE_USER, 'assigned_user_id'),
344 'stage_id/id' : self.get_lead_status,
345 'type' : const('lead'),
346 'state': map_val('status', self.lead_state) ,
348 'referred': 'refered_by',
349 'optout': 'do_not_call',
350 'type_id/id': call(self.get_campaign_id, value('lead_source')),
351 'country_id/id': 'country_id/id',
352 'state_id/id': 'state_id/id'
359 def get_email(self, val):
360 return val.get('email1') + ','+ val.get('email2')
362 def import_contact(self, val):
363 if val.get('primary_address_country'):
364 country_id = self.get_all_countries(val.get('primary_address_country'))
365 state = self.get_all_states(val.get('primary_address_state'), country_id)
366 val['country_id/id'] = country_id
367 val['state_id/id'] = state
370 def get_contact_mapping(self):
372 'model' : 'res.partner.address',
373 'dependencies' : [self.TABLE_ACCOUNT],
374 'hook' : self.import_contact,
376 'name': concat('first_name', 'last_name'),
377 'partner_id/id': ref(self.TABLE_ACCOUNT,'account_id'),
378 'phone': 'phone_work',
379 'mobile': 'phone_mobile',
382 'street': 'primary_address_street',
383 'zip': 'primary_address_postalcode',
384 'city': 'primary_address_city',
385 'country_id/id': 'country_id/id',
386 'state_id/id': 'state_id/id',
387 'email': self.get_email,
388 'type': const('contact')
395 def get_address_type(self, val, type):
396 if type == 'invoice':
397 type_address = 'billing'
399 type_address = 'shipping'
401 map_partner_address = {
403 'phone': 'phone_office',
404 'mobile': 'phone_mobile',
407 'street': type_address + '_address_street',
408 'zip': type_address +'_address_postalcode',
409 'city': type_address +'_address_city',
410 'country_id/id': 'country_id/id',
414 if val.get(type_address +'_address_country'):
415 country_id = self.get_all_countries(val.get(type_address +'_address_country'))
416 state = self.get_all_states(val.get(type_address +'_address_state'), country_id)
417 val['country_id/id'] = country_id
418 val['state_id/id'] = state
420 val['id_new'] = val['id'] + '_address_' + type
421 return self.import_object_mapping(map_partner_address, val, 'res.partner.address', self.TABLE_CONTACT, val['id_new'], self.DO_NOT_FIND_DOMAIN)
423 def get_partner_address(self, val):
425 type_dict = {'billing_address_street' : 'invoice', 'shipping_address_street' : 'delivery'}
426 for key, type_value in type_dict.items():
428 id = self.get_address_type(val, type_value)
429 address_id.append(id)
431 return ','.join(address_id)
433 def get_partner_mapping(self):
435 'model' : 'res.partner',
436 'dependencies' : [self.TABLE_USER],
439 'website': 'website',
440 'user_id/id': ref(self.TABLE_USER,'assigned_user_id'),
442 'comment': ppconcat('description', 'employees', 'ownership', 'annual_revenue', 'rating', 'industry', 'ticker_symbol'),
443 'customer': const('1'),
444 'supplier': const('0'),
445 'address/id':'address/id',
446 'parent_id/id_parent' : 'parent_id',
447 'address/id' : self.get_partner_address,
454 def get_ressource(self, val):
456 'name': concat('first_name', 'last_name'),
458 return self.import_object_mapping(map_resource, val, 'resource.resource', self.TABLE_RESSOURCE, val['id'], self.DO_NOT_FIND_DOMAIN)
460 def get_job_id(self, val):
462 data = [val.get('title')]
463 return self.import_object(fields, data, 'hr.job', 'hr_job', val.get('title'))
465 def get_user_address(self, val):
467 'name': concat('first_name', 'last_name'),
468 'city': 'address_city',
469 'country_id/id': 'country_id/id',
470 'state_id/id': 'state_id/id',
471 'street': 'address_street',
472 'zip': 'address_postalcode',
476 if val.get('address_country'):
477 country_id = self.get_all_countries(val.get('address_country'))
478 state_id = self.get_all_states(val.get('address_state'), country_id)
479 val['country_id/id'] = country_id
480 val['state_id/id'] = state_id
482 return self.import_object_mapping(map_user_address, val, 'res.partner.address', self.TABLE_CONTACT, val['id'], self.DO_NOT_FIND_DOMAIN)
484 def get_employee_mapping(self):
486 'model' : 'hr.employee',
487 'dependencies' : [self.TABLE_USER],
489 'resource_id/id': self.get_ressource,
490 'name': concat('first_name', 'last_name'),
491 'work_phone': 'phone_work',
492 'mobile_phone': 'phone_mobile',
493 'user_id/id': ref(self.TABLE_USER, 'id'),
494 'address_home_id/id': self.get_user_address,
495 'notes': 'description',
496 'job_id/id': self.get_job_id,
503 def import_user(self, val):
504 user_obj = self.obj.pool.get('res.users')
505 user_ids = user_obj.search(self.cr, self.uid, [('login', '=', val.get('user_name'))])
507 val['.id'] = str(user_ids[0])
509 val['password'] = 'sugarcrm' #default password for all user #TODO needed in documentation
511 val['context_lang'] = self.context.get('lang','en_US')
514 def get_users_department(self, val):
515 dep = val.get('department')
520 return self.import_object(fields, data, 'hr.department', 'hr_department_user', dep)
522 def get_user_mapping(self):
524 'model' : 'res.users',
525 'hook' : self.import_user,
527 'name': concat('first_name', 'last_name'),
528 'login': 'user_name',
529 'context_lang' : 'context_lang',
530 'password' : 'password',
532 'context_department_id/id': self.get_users_department,
536 def get_mapping(self):
538 self.TABLE_USER : self.get_user_mapping(),
539 self.TABLE_EMPLOYEE : self.get_employee_mapping(),
540 self.TABLE_ACCOUNT : self.get_partner_mapping(),
541 self.TABLE_CONTACT : self.get_contact_mapping(),
542 self.TABLE_LEAD : self.get_lead_mapping(),
543 self.TABLE_OPPORTUNITY : self.get_opp_mapping(),
544 self.TABLE_MEETING : self.get_meeting_mapping(),
545 self.TABLE_CALL : self.get_calls_mapping(),
546 self.TABLE_TASK : self.get_task_mapping(),
547 self.TABLE_PROJECT : self.get_project_mapping(),
554 class import_sugarcrm(osv.osv):
555 """Import SugarCRM DATA"""
557 _name = "import.sugarcrm"
558 _description = __doc__
560 'opportunity': fields.boolean('Leads and Opportunities', help="If Opportunities are checked, SugarCRM opportunities data imported in OpenERP crm-Opportunity form"),
561 'user': fields.boolean('Users', help="If Users are checked, SugarCRM Users data imported in OpenERP Users form"),
562 'contact': fields.boolean('Contacts', help="If Contacts are checked, SugarCRM Contacts data imported in OpenERP partner address form"),
563 'account': fields.boolean('Accounts', help="If Accounts are checked, SugarCRM Accounts data imported in OpenERP partners form"),
564 'employee': fields.boolean('Employee', help="If Employees is checked, SugarCRM Employees data imported in OpenERP employees form"),
565 'meeting': fields.boolean('Meetings', help="If Meetings is checked, SugarCRM Meetings data imported in OpenERP meetings form"),
566 'call': fields.boolean('Calls', help="If Calls is checked, SugarCRM Calls data imported in OpenERP phonecalls form"),
567 'claim': fields.boolean('Claims', help="If Claims is checked, SugarCRM Claims data imported in OpenERP Claims form"),
568 'email': fields.boolean('Emails', help="If Emails is checked, SugarCRM Emails data imported in OpenERP Emails form"),
569 'project': fields.boolean('Projects', help="If Projects is checked, SugarCRM Projects data imported in OpenERP Projects form"),
570 'project_task': fields.boolean('Project Tasks', help="If Project Tasks is checked, SugarCRM Project Tasks data imported in OpenERP Project Tasks form"),
571 'task': fields.boolean('Tasks', help="If Tasks is checked, SugarCRM Tasks data imported in OpenERP Meetings form"),
572 'bug': fields.boolean('Bugs', help="If Bugs is checked, SugarCRM Bugs data imported in OpenERP Project Issues form"),
573 'attachment': fields.boolean('Attachments', help="If Attachments is checked, SugarCRM Notes data imported in OpenERP's Related module's History with attachment"),
574 'document': fields.boolean('Documents', help="If Documents is checked, SugarCRM Documents data imported in OpenERP Document Form"),
575 'username': fields.char('User Name', size=64),
576 'password': fields.char('Password', size=24),
578 _defaults = {#to be set to true, but easier for debugging
579 'opportunity': False,
590 'project_task': False,
595 def get_key(self, cr, uid, ids, context=None):
596 """Select Key as For which Module data we want import data."""
600 for current in self.browse(cr, uid, ids, context):
601 if current.opportunity:
602 key_list.append('Leads')
603 key_list.append('Opportunities')
605 key_list.append('Users')
607 key_list.append('Contacts')
609 key_list.append('Accounts')
611 key_list.append('Employees')
613 key_list.append('Meetings')
615 key_list.append('Tasks')
617 key_list.append('Calls')
619 key_list.append('Claims')
621 key_list.append('Emails')
623 key_list.append('Project')
624 if current.project_task:
625 key_list.append('Project Tasks')
627 key_list.append('Bugs')
628 if current.attachment:
629 key_list.append('Notes')
631 key_list.append('Documents')
634 def import_all(self, cr, uid, ids, context=None):
636 # """Import all sugarcrm data into openerp module"""
637 keys = self.get_key(cr, uid, ids, context)
638 imp = sugar_import(self, cr, uid, "sugarcrm", "import_sugarcrm", context)
641 obj_model = self.pool.get('ir.model.data')
642 model_data_ids = obj_model.search(cr,uid,[('model','=','ir.ui.view'),('name','=','import.message.form')])
643 resource_id = obj_model.read(cr, uid, model_data_ids, fields=['res_id'])
647 'res_model': 'import.message',
648 'views': [(resource_id,'form')],
649 'type': 'ir.actions.act_window',