[FIX] Correction by apr
[odoo/odoo.git] / addons / import_sugarcrm / import_sugarcrm.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21 from osv import fields, osv
22 import sugar
23 from tools.translate import _
24 from import_base.import_framework import *
25 from import_base.mapper import *
26 from datetime import datetime
27 import base64
28 import pprint
29 pp = pprint.PrettyPrinter(indent=4)
30 #copy old import here
31
32 import htmllib
33
34 def unescape_htmlentities(s):
35     p = htmllib.HTMLParser(None)
36     p.save_bgn()
37     p.feed(s)
38     return p.save_end()
39
40 class related_ref(dbmapper):
41     def __init__(self, type):
42         self.type = type
43         
44     def __call__(self, external_val):
45         if external_val.get('parent_type') in self.type and external_val.get('parent_id'):
46             return self.parent.xml_id_exist(external_val['parent_type'], external_val['parent_id'])
47         return ''
48
49 class sugar_import(import_framework):
50     URL = False   
51     TABLE_CONTACT = 'Contacts'
52     TABLE_ACCOUNT = 'Accounts'
53     TABLE_USER = 'Users'
54     TABLE_EMPLOYEE = 'Employees'
55     TABLE_RESSOURCE = "resource"
56     TABLE_OPPORTUNITY = 'Opportunities'
57     TABLE_LEAD = 'Leads'
58     TABLE_STAGE = 'crm_stage'
59     TABLE_ATTENDEE = 'calendar_attendee'
60     TABLE_CALL = 'Calls'
61     TABLE_MEETING = 'Meetings'
62     TABLE_TASK = 'Tasks'
63     TABLE_PROJECT = 'Project'
64     TABLE_PROJECT_TASK = 'ProjectTask'
65     TABLE_BUG = 'Bugs'
66     TABLE_CASE = 'Cases'
67     TABLE_NOTE = 'Notes'
68     TABLE_EMAIL = 'Emails'
69     TABLE_COMPAIGN = 'Campaigns'
70     TABLE_DOCUMENT = 'Documents'
71     TABLE_HISTORY_ATTACHMNET = 'history_attachment'
72     
73     MAX_RESULT_PER_PAGE = 200
74     
75     def initialize(self):
76         #login
77         PortType,sessionid = sugar.login(self.context.get('username',''), self.context.get('password',''), self.context.get('url',''))
78         if sessionid == '-1':
79             raise osv.except_osv(_('Error !'), _('Authentication error !\nBad Username or Password or bad SugarSoap Api url !'))
80         self.context['port'] = PortType
81         self.context['session_id'] = sessionid
82         
83     def get_data(self, table):
84         offset = 0
85         res = []
86         while True:
87             r = sugar.search(self.context.get('port'), self.context.get('session_id'), table, offset, self.MAX_RESULT_PER_PAGE)
88             res.extend(r)
89             if len(r) < self.MAX_RESULT_PER_PAGE:
90                 break;
91             offset += self.MAX_RESULT_PER_PAGE
92         return res
93     
94     #def get_link(self, from_table, ids, to_table):
95         #return sugar.relation_search(self.context.get('port'), self.context.get('session_id'), from_table, module_id=ids, related_module=to_table)
96
97     """
98     Common import method
99     """
100     def get_category(self, val, model, name):
101         fields = ['name', 'object_id']
102         data = [name, model]
103         return self.import_object(fields, data, 'crm.case.categ', 'crm_categ', name, [('object_id.model','=',model), ('name', 'ilike', name)])
104
105     def get_job_title(self, dict, salutation):
106         fields = ['shortcut', 'name', 'domain']
107         if salutation:
108             data = [salutation, salutation, 'Contact']
109             return self.import_object(fields, data, 'res.partner.title', 'contact_title', salutation, [('shortcut', '=', salutation)])
110
111     def get_channel_id(self, dict, val):
112         if not val:
113             return False
114         fields = ['name']
115         data = [val]
116         return self.import_object(fields, data, 'res.partner.canal', 'crm_channel', val)
117     
118     def get_all_states(self, external_val, country_id):
119         """Get states or create new state unless country_id is False"""
120         state_code = external_val[0:3] #take the tree first char
121         fields = ['country_id/id', 'name', 'code']
122         data = [country_id, external_val, state_code]
123         if country_id:
124             return self.import_object(fields, data, 'res.country.state', 'country_state', external_val) 
125         return False
126
127     def get_all_countries(self, val):
128         """Get Country, if no country match do not create anything, to avoid duplicate country code"""
129         return self.mapped_id_if_exist('res.country', [('name', 'ilike', val)], 'country', val)
130     
131     def get_float_time(self, dict, hour, min):
132         min = int(min) * 100 / 60
133         return "%s.%i" % (hour, min)
134     
135     """
136     import Documents
137     """
138     
139     def import_related_document(self, val):
140         res_model = False
141         res_id = False
142         sugar_document_account = sugar.relation_search(self.context.get('port'), self.context.get('session_id'), 'Documents', module_id=val.get('id'), related_module='Accounts', query=None, deleted=None)
143         sugar_document_contact = sugar.relation_search(self.context.get('port'), self.context.get('session_id'), 'Documents', module_id=val.get('id'), related_module=self.TABLE_CONTACT, query=None, deleted=None)
144         sugar_document_opportunity = sugar.relation_search(self.context.get('port'), self.context.get('session_id'), 'Documents', module_id=val.get('id'), related_module=self.TABLE_OPPORTUNITY, query=None, deleted=None)
145         sugar_document_case = sugar.relation_search(self.context.get('port'), self.context.get('session_id'), 'Documents', module_id=val.get('id'), related_module=self.TABLE_CASE, query=None, deleted=None)
146         sugar_document_bug = sugar.relation_search(self.context.get('port'), self.context.get('session_id'), 'Documents', module_id=val.get('id'), related_module=self.TABLE_BUG, query=None, deleted=None)
147         if sugar_document_account:
148             res_id = self.get_mapped_id(self.TABLE_ACCOUNT,sugar_document_account[0])
149             res_model = 'res.partner'
150         elif sugar_document_contact:
151             res_id = self.get_mapped_id(self.TABLE_CONTACT, sugar_document_contact[0])
152             res_model = 'res.partner.address'
153         elif sugar_document_opportunity:
154             res_id = self.get_mapped_id(self.TABLE_OPPORTUNITY, sugar_document_opportunity[0])
155             res_model = 'crm.lead'
156         elif sugar_document_case:
157             res_id = self.get_mapped_id(self.TABLE_CASE, sugar_document_case[0])
158             res_model = 'crm.claim'
159         elif sugar_document_bug:
160             res_id = self.get_mapped_id(self.TABLE_BUG, sugar_document_bug[0])
161             res_model = 'project.issue'
162         return res_id,res_model
163     
164     def import_document(self, val):
165         File,Filename = sugar.get_document_revision_search(self.context.get('port'), self.context.get('session_id'), val.get('document_revision_id'))
166         #File = base64.encodestring(File)
167         res_id, res_model  = self.import_related_document(val)
168         val['res_id'] = res_id
169         val['res_model'] = res_model
170         if File:
171             val['datas'] = File
172             val['datas_fname'] = Filename
173         return val   
174         
175     def get_document_mapping(self): 
176         return { 
177                 'model' : 'ir.attachment',
178                 'dependencies' : [self.TABLE_USER],
179                 'hook' : self.import_document,
180                 'map' : {
181                          'name':'document_name',
182                          'description': ppconcat('description'),
183                          'datas': 'datas',
184                          'datas_fname': 'datas_fname',
185                          'res_model': 'res_model',
186                          'res_id': 'res_id',
187                 }
188             }     
189         
190     
191     """
192     import Emails
193     """
194
195       
196     def import_email(self, val):
197         vals = sugar.email_search(self.context.get('port'), self.context.get('session_id'), self.TABLE_EMAIL, val.get('id'))
198         model_obj =  self.obj.pool.get('ir.model.data')
199         for val in vals:
200             xml_id = self.xml_id_exist(val.get('parent_type'), val.get('parent_id'))
201             model_ids = model_obj.search(self.cr, self.uid, [('name', 'like', xml_id)])
202             if model_ids:
203                 model = model_obj.browse(self.cr, self.uid, model_ids)[0]
204                 if model.model == 'res.partner':
205                     val['partner_id/.id'] = model.res_id
206                 else:    
207                     val['res_id'] = model.res_id
208                     val['model'] = model.model
209         return val   
210         
211     def get_email_mapping(self): 
212         return { 
213                 'model' : 'mailgate.message',
214                 'dependencies' : [self.TABLE_USER, self.TABLE_ACCOUNT, self.TABLE_CONTACT, self.TABLE_LEAD, self.TABLE_OPPORTUNITY, self.TABLE_MEETING, self.TABLE_CALL],
215                 'hook' : self.import_email,
216                 'map' : {
217                         'name':'name',
218                         'history' : const("1"),
219                         'date':'date_sent',
220                         'email_from': 'from_addr_name',
221                         'email_to': 'to_addrs_names',
222                         'email_cc': 'cc_addrs_names',
223                         'email_bcc': 'bcc_addrs_names',
224                         'message_id': 'message_id',
225                         'res_id': 'res_id',
226                         'model': 'model',
227                         'partner_id/.id': 'partner_id/.id',                         
228                         'user_id/id': ref(self.TABLE_USER, 'assigned_user_id'),
229                         'description': ppconcat('description', 'description_html'),
230                 }
231             } 
232     
233     """
234     import History(Notes)
235     """
236     
237
238     def import_history(self, val):
239         model_obj =  self.obj.pool.get('ir.model.data')
240         xml_id = self.xml_id_exist(val.get('parent_type'), val.get('parent_id'))
241         model_ids = model_obj.search(self.cr, self.uid, [('name', 'like', xml_id)])
242         if model_ids:
243             model = model_obj.browse(self.cr, self.uid, model_ids)[0]
244             if model.model == 'res.partner':
245                 val['partner_id/.id'] = model.res_id
246             val['res_id'] = model.res_id
247             val['model'] = model.model
248         File, Filename = sugar.attachment_search(self.context.get('port'), self.context.get('session_id'), self.TABLE_NOTE, val.get('id')) 
249         if File:
250             val['datas'] = File
251             val['datas_fname'] = Filename
252         return val    
253     
254     def get_history_mapping(self): 
255         return { 
256                 'model' : 'ir.attachment',
257                 'dependencies' : [self.TABLE_USER, self.TABLE_ACCOUNT, self.TABLE_CONTACT, self.TABLE_LEAD, self.TABLE_OPPORTUNITY, self.TABLE_MEETING, self.TABLE_CALL, self.TABLE_EMAIL],
258                 'hook' : self.import_history,
259                 'map' : {
260                       'name':'name',
261                       'user_id/id': ref(self.TABLE_USER, 'created_by'),
262                       'description': ppconcat('description', 'description_html'),
263                       'res_id': 'res_id',
264                       'res_model': 'model',
265                       'partner_id/.id' : 'partner_id/.id',
266                       'datas' : 'datas',
267                       'datas_fname' : 'datas_fname'
268                 }
269             }     
270     
271     """
272     import Claims(Cases)
273     """
274     def get_claim_priority(self, val):
275         priority_dict = {            
276                 'P1': '2',
277                 'P2': '3',
278                 'P3': '4'
279         }
280         return priority_dict.get(val.get('priority'), '')
281         
282     def get_contact_info_from_account(self, val):
283         partner_id = self.get_mapped_id(self.TABLE_ACCOUNT, val.get('account_id'))
284         partner_address_id = False
285         partner_phone = False
286         partner_email = False
287         partner = self.obj.pool.get('res.partner').browse(self.cr, self.uid, [partner_id])[0]
288         if partner.address and partner.address[0]:
289             address = partner.address[0]
290             partner_address_id = address.id
291             partner_phone = address.phone
292             partner_email = address.email
293         return partner_address_id, partner_phone,partner_email
294     
295     def import_crm_claim(self, val):
296         partner_address_id, partner_phone,partner_email =  self.get_contact_info_from_account(val)
297         val['partner_address_id/.id'] = partner_address_id
298         val['partner_phone'] = partner_phone
299         val['email_from'] = partner_email
300         return val
301     
302     def get_crm_claim_mapping(self): 
303         return { 
304                 'model' : 'crm.claim',
305                 'dependencies' : [self.TABLE_USER, self.TABLE_ACCOUNT, self.TABLE_CONTACT, self.TABLE_LEAD],
306                 'hook' : self.import_crm_claim,
307                 'map' : {
308                     'name': concat('case_number','name', delimiter='-'),
309                     'date': 'date_entered',
310                     'user_id/id': ref(self.TABLE_USER, 'assigned_user_id'),
311                     'description': ppconcat('description', 'resolution', 'work_log'),
312                     'partner_id/id': ref(self.TABLE_ACCOUNT, 'account_id'),
313                     'partner_address_id/.id': 'partner_address_id/.id',
314                     'categ_id/id': call(self.get_category, 'crm.claim', value('type')),
315                     'partner_phone': 'partner_phone',
316                     'email_from': 'email_from',                                        
317                     'priority': self.get_claim_priority,
318                     'state': map_val('status', self.project_issue_state)
319                 }
320             }    
321     """
322     Import Project Issue(Bugs)
323     """
324     project_issue_state = {
325             'New' : 'draft',
326             'Assigned':'open',
327             'Closed': 'done',
328             'Pending': 'pending',
329             'Rejected': 'cancel',
330     }
331      
332     def get_project_issue_priority(self, val):
333         priority_dict = {
334                 'Urgent': '1',
335                 'High': '2',
336                 'Medium': '3',
337                 'Low': '4'
338          }
339         return priority_dict.get(val.get('priority'), '')     
340       
341     def get_bug_project_id(self, dict, val):
342         fields = ['name']
343         data = [val]
344         return self.import_object(fields, data, 'project.project', 'project_issue', val)    
345     
346     def get_project_issue_mapping(self):
347         return { 
348                 'model' : 'project.issue',
349                 'dependencies' : [self.TABLE_USER],
350                 'map' : {
351                     'name': concat('bug_number', 'name', delimiter='-'),
352                     'project_id/id': call(self.get_bug_project_id, 'sugarcrm_bugs'),
353                     'categ_id/id': call(self.get_category, 'project.issue', value('type')),
354                     'description': ppconcat('description', 'source', 'resolution', 'work_log', 'found_in_release', 'release_name', 'fixed_in_release_name', 'fixed_in_release'),
355                     'priority': self.get_project_issue_priority,
356                     'state': map_val('status', self.project_issue_state),
357                     'assigned_to/id' : ref(self.TABLE_USER, 'assigned_user_id'),
358                 }
359             }
360     
361     """
362     import Project Tasks
363     """
364     project_task_state = {
365             'Not Started': 'draft',
366             'In Progress': 'open',
367             'Completed': 'done',
368             'Pending Input': 'pending',
369             'Deferred': 'cancelled',
370      }
371     
372     def get_project_task_priority(self, val):
373         priority_dict = {
374             'High': '0',
375             'Medium': '2',
376             'Low': '3'
377         }
378         return priority_dict.get(val.get('priority'), '')
379     
380     def get_project_task_mapping(self):
381         return { 
382                 'model' : 'project.task',
383                 'dependencies' : [self.TABLE_USER, self.TABLE_PROJECT],
384                 'map' : {
385                     'name': 'name',
386                     'date_start': 'date_start',
387                     'date_end': 'date_finish',
388                     'project_id/id': ref(self.TABLE_PROJECT, 'project_id'),
389                     'planned_hours': 'estimated_effort',
390                     'priority': self.get_project_task_priority,
391                     'description': ppconcat('description','milestone_flag', 'project_task_id', 'task_number', 'percent_complete'),
392                     'user_id/id': ref(self.TABLE_USER, 'assigned_user_id'),
393                     'partner_id/id': 'partner_id/id',
394                     'contact_id/id': 'contact_id/id',
395                     'state': map_val('status', self.project_task_state)
396                 }
397             }
398
399     """
400     import Projects
401     """
402     project_state = {
403             'Draft' : 'draft',
404             'In Review': 'open',
405             'Published': 'close'
406      }
407     
408     def import_project_account(self, val):
409         partner_id = False
410         partner_invoice_id = False        
411         sugar_project_account = sugar.relation_search(self.context.get('port'), self.context.get('session_id'), 'Project', module_id=val.get('id'), related_module=self.TABLE_ACCOUNT, query=None, deleted=None)
412         sugar_project_contact = sugar.relation_search(self.context.get('port'), self.context.get('session_id'), 'Project', module_id=val.get('id'), related_module=self.TABLE_CONTACT, query=None, deleted=None)
413         for contact_id in sugar_project_contact:
414             partner_invoice_id = self.get_mapped_id(self.TABLE_CONTACT, contact_id)
415         for account_id in sugar_project_account:
416             partner_id = self.get_mapped_id(self.TABLE_ACCOUNT, account_id)
417         return partner_id, partner_invoice_id      
418            
419     def import_project(self, val):
420         partner_id, partner_invoice_id  = self.import_project_account(val)    
421         val['partner_id/.id'] = partner_id
422         val['contact_id/.id'] = partner_invoice_id
423         return val
424     
425     def get_project_mapping(self):
426         return { 
427                 'model' : 'project.project',
428                 'dependencies' : [self.TABLE_CONTACT, self.TABLE_ACCOUNT, self.TABLE_USER],
429                 'hook' : self.import_project,
430                 'map' : {
431                     'name': 'name',
432                     'date_start': 'estimated_start_date',
433                     'date': 'estimated_end_date',
434                     'user_id/id': ref(self.TABLE_USER, 'assigned_user_id'),
435                     'partner_id/.id': 'partner_id/.id',
436                     'contact_id/.id': 'contact_id/.id',
437                     'state': map_val('status', self.project_state)
438                 }
439             }
440     
441     """
442     import Tasks
443     """
444     task_state = {
445             'Completed' : 'done',
446             'Not Started':'draft',
447             'In Progress': 'open',
448             'Pending Input': 'draft',
449             'deferred': 'cancel'
450         }
451
452     def import_task(self, val):
453         date =  val.get('date_start') or datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
454         val['date'] = ''.join(date)
455         date_deadline = val.get('date_due') or datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
456         val['date_deadline'] = ''.join(date_deadline)
457         return val
458
459     def get_task_mapping(self):
460         return { 
461                 'model' : 'crm.meeting',
462                 'dependencies' : [self.TABLE_CONTACT, self.TABLE_ACCOUNT, self.TABLE_USER],
463                 'hook' : self.import_task,
464                 'map' : {
465                     'name': 'name',
466                     'date': 'date',
467                     'date_deadline': 'date_deadline',
468                     'user_id/id': ref(self.TABLE_USER, 'assigned_user_id'),
469                     'categ_id/id': call(self.get_category, 'crm.meeting', const('Tasks')),
470                     'partner_id/id': related_ref(self.TABLE_ACCOUNT),
471                     'partner_address_id/id': ref(self.TABLE_CONTACT,'contact_id'),
472                     'state': map_val('status', self.task_state)
473                 }
474             }
475        
476     """
477     import Calls
478     """  
479     #TODO adapt with project trunk-crm-imp   
480     call_state = {   
481             'Planned' : 'open',
482             'Held':'done',
483             'Not Held': 'pending',
484         }
485
486     def get_calls_mapping(self):
487         return { 
488                 'model' : 'crm.phonecall',
489                 'dependencies' : [self.TABLE_ACCOUNT, self.TABLE_CONTACT, self.TABLE_OPPORTUNITY, self.TABLE_LEAD],
490                 'map' : {
491                     'name': 'name',
492                     'date': 'date_start',
493                     'duration': call(self.get_float_time, value('duration_hours'), value('duration_minutes')),
494                     'user_id/id':  ref(self.TABLE_USER, 'assigned_user_id'),
495                     'partner_id/id': related_ref(self.TABLE_ACCOUNT),
496                     'partner_address_id/id': related_ref(self.TABLE_CONTACT),
497                     'categ_id/id': call(self.get_category, 'crm.phonecall', value('direction')),
498                     'opportunity_id/id': related_ref(self.TABLE_OPPORTUNITY),
499                     'description': ppconcat('description'),   
500                     'state': map_val('status', self.call_state)                      
501                 }
502             }       
503          
504     """
505         import meeting
506     """
507     meeting_state = {
508             'Planned' : 'draft',
509             'Held': 'open',
510             'Not Held': 'draft', 
511         }
512 #TODO    
513     def get_attendee_id(self, cr, uid, module_name, module_id):
514         contact_id = False
515         user_id = False
516         attendee_id= []
517         attendee_dict = sugar.user_get_attendee_list(self.context.get('port'), self.context.get('session_id'), module_name, module_id)
518         for attendee in attendee_dict:
519             user_id = self.xml_id_exist(self.TABLE_USER, attendee.get('id', False))
520             contact_id = False
521             if not user_id:
522                 contact_id = self.xml_id_exist(self.TABLE_CONTACT, attendee.get('id', False))
523             fields = ['user_id/id', 'email', 'partner_address_id/id']
524             data = [user_id, attendee.get('email1'), contact_id]
525             attendee_xml_id = self.import_object(fields, data, 'calendar.attendee', self.TABLE_ATTENDEE, user_id or contact_id or attendee.get('email1'), ['|',('user_id', '=', attendee.get('id')),('partner_address_id','=',attendee.get('id')),('email', '=', attendee.get('email1'))])
526             attendee_id.append(attendee_xml_id)
527         return ','.join(attendee_id) 
528     
529     def get_alarm_id(self, dict_val, val):
530         alarm_dict = {
531             '60': '1 minute before',
532             '300': '5 minutes before',
533             '600': '10 minutes before',
534             '900': '15 minutes before',
535             '1800':'30 minutes before',
536             '3600': '1 hour before',
537         }
538         return self.mapped_id_if_exist('res.alarm', [('name', 'like', alarm_dict.get(val))], 'alarm', val)
539     
540     #TODO attendees
541
542     def import_meeting(self, val):
543         attendee_id = self.get_attendee_id(self.cr, self.uid, 'Meetings', val.get('id')) #TODO
544         val['attendee_ids/id'] = attendee_id
545         return val
546
547     def get_meeting_mapping(self):
548         return { 
549                 'model' : 'crm.meeting',
550                 'dependencies' : [self.TABLE_CONTACT, self.TABLE_OPPORTUNITY, self.TABLE_LEAD, self.TABLE_TASK],
551                 'hook': self.import_meeting,
552                 'map' : {
553                     'name': 'name',
554                     'date': 'date_start',
555                     'duration': call(self.get_float_time, value('duration_hours'), value('duration_minutes')),
556                     'location': 'location',
557                     'attendee_ids/id':'attendee_ids/id',
558                     'alarm_id/id': call(self.get_alarm_id, value('reminder_time')),
559                     'user_id/id': ref(self.TABLE_USER, 'assigned_user_id'),
560                     'partner_id/id': related_ref(self.TABLE_ACCOUNT),
561                     'partner_address_id/id': related_ref(self.TABLE_CONTACT),
562                     'state': map_val('status', self.meeting_state)
563                 }
564             }
565     
566     """
567         import Opportunity
568     """
569     opp_state = {
570             'Need Analysis' : 'New',
571             'Closed Lost': 'Lost',
572             'Closed Won': 'Won', 
573             'Value Proposition': 'Proposition',
574             'Negotiation/Review': 'Negotiation'
575         }
576         
577     def get_opportunity_status(self, sugar_val):
578         fields = ['name', 'type']
579         name = 'Opportunity_' + sugar_val['sales_stage']
580         data = [sugar_val['sales_stage'], 'Opportunity']
581         return self.import_object(fields, data, 'crm.case.stage', self.TABLE_STAGE, name, [('type', '=', 'opportunity'), ('name', 'ilike', sugar_val['sales_stage'])])
582     
583     def import_opportunity_contact(self, val):
584         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))
585             
586         partner_contact_id = False 
587         partner_contact_email = False       
588         partner_address_obj = self.obj.pool.get('res.partner.address')
589         partner_xml_id = self.name_exist(self.TABLE_ACCOUNT, val['account_name'], 'res.partner')
590         
591         for contact in sugar_opportunities_contact:
592             address_id = self.get_mapped_id(self.TABLE_CONTACT, contact)
593             if address_id:                    
594                 address = partner_address_obj.browse(self.cr, self.uid, address_id)
595                 partner_name = address.partner_id and address.partner_id.name or False
596                 if not partner_name: #link with partner id 
597                     fields = ['partner_id/id']
598                     data = [partner_xml_id]
599                     self.import_object(fields, data, 'res.partner.address', self.TABLE_CONTACT, contact, self.DO_NOT_FIND_DOMAIN)
600                 if not partner_name or partner_name == val.get('account_name'):
601                     partner_contact_id = self.xml_id_exist(self.TABLE_CONTACT, contact)
602                     partner_contact_email = address.email
603         return partner_contact_id, partner_contact_email
604
605     def import_opp(self, val):    
606         partner_contact_id, partner_contact_email = self.import_opportunity_contact(val)
607         val['partner_address_id/id'] = partner_contact_id
608         val['email_from'] = partner_contact_email
609         return val
610     
611     def get_opp_mapping(self):
612         return {
613             'model' : 'crm.lead',
614             'dependencies' : [self.TABLE_USER, self.TABLE_ACCOUNT, self.TABLE_CONTACT,self.TABLE_COMPAIGN],
615             'hook' : self.import_opp,
616             'map' :  {
617                 'name': 'name',
618                 'probability': 'probability',
619                 'partner_id/id': refbyname(self.TABLE_ACCOUNT, 'account_name', 'res.partner'),
620                 'title_action': 'next_step',
621                 'partner_address_id/id': 'partner_address_id/id',
622                 'planned_revenue': 'amount',
623                 'date_deadline': 'date_closed',
624                 'user_id/id' : ref(self.TABLE_USER, 'assigned_user_id'),
625                 'stage_id/id' : self.get_opportunity_status,
626                 'type' : const('opportunity'),
627                 'categ_id/id': call(self.get_category, 'crm.lead', value('opportunity_type')),
628                 'email_from': 'email_from',
629                 'state': map_val('status', self.opp_state),
630                 'description' : 'description',
631             }
632         }
633         
634     """
635     import campaign
636     """
637     
638     def get_compaign_mapping(self):
639         return {
640             'model' : 'crm.case.resource.type',
641             'map' : {
642                 'name': 'name',
643                 } 
644         }    
645         
646     """
647         import lead
648     """
649     def get_lead_status(self, sugar_val):
650         fields = ['name', 'type']
651         name = 'lead_' + sugar_val.get('status', '')
652         data = [sugar_val.get('status', ''), 'lead']
653         return self.import_object(fields, data, 'crm.case.stage', self.TABLE_STAGE, name, [('type', '=', 'lead'), ('name', 'ilike', sugar_val.get('status', ''))])
654
655     lead_state = {
656         'New' : 'draft',
657         'Assigned':'open',
658         'In Progress': 'open',
659         'Recycled': 'cancel',
660         'Dead': 'done',
661         'Converted': 'done',
662     }
663     
664     def import_lead(self, val):
665         if val.get('opportunity_id'): #if lead is converted into opp, don't import as lead
666             return False
667         if val.get('primary_address_country'):
668             country_id = self.get_all_countries(val.get('primary_address_country'))
669             val['country_id/id'] =  country_id
670             val['state_id/id'] =  self.get_all_states(val.get('primary_address_state'), country_id)
671         return val
672     
673     def get_lead_mapping(self):
674         return {
675             'model' : 'crm.lead',
676             'dependencies' : [self.TABLE_COMPAIGN, self.TABLE_USER],
677             'hook' : self.import_lead,
678             'map' : {
679                 'name': concat('first_name', 'last_name'),
680                 'contact_name': concat('first_name', 'last_name'),
681                 'description': ppconcat('description', 'refered_by', 'lead_source', 'lead_source_description', 'website', 'email2', 'status_description', 'lead_source_description', 'do_not_call'),
682                 'partner_name': 'account_name',
683                 'email_from': 'email1',
684                 'phone': 'phone_work',
685                 'mobile': 'phone_mobile',
686                 'title/id': call(self.get_job_title, value('salutation')),
687                 'function':'title',
688                 'street': 'primary_address_street',
689                 'street2': 'alt_address_street',
690                 'zip': 'primary_address_postalcode',
691                 'city':'primary_address_city',
692                 'user_id/id' : ref(self.TABLE_USER, 'assigned_user_id'),
693                 'stage_id/id' : self.get_lead_status,
694                 'type' : const('lead'),
695                 'state': map_val('status', self.lead_state) ,
696                 'fax': 'phone_fax',
697                 'referred': 'refered_by',
698                 'optout': 'do_not_call',
699                 'channel_id/id': call(self.get_channel_id, value('lead_source')),
700                 'type_id/id': ref(self.TABLE_COMPAIGN, 'campaign_id'),
701                 'country_id/id': 'country_id/id',
702                 'state_id/id': 'state_id/id',
703                 } 
704         }
705     
706     """
707         import contact
708     """
709     
710     def get_email(self, val):
711         email_address = sugar.get_contact_by_email(self.context.get('port'), self.context.get('username'), self.context.get('password'), val.get('email1'))
712         if email_address:
713             return ','.join(email_address)     
714     
715     def import_contact(self, val):
716         if val.get('primary_address_country'):
717             country_id = self.get_all_countries(val.get('primary_address_country'))
718             state = self.get_all_states(val.get('primary_address_state'), country_id)
719             val['country_id/id'] =  country_id
720             val['state_id/id'] =  state
721         return val    
722         
723     def get_contact_mapping(self):
724         return { 
725             'model' : 'res.partner.address',
726             'dependencies' : [self.TABLE_ACCOUNT],
727             'hook' : self.import_contact,
728             'map' :  {
729                 'name': concat('first_name', 'last_name'),
730                 'partner_id/id': ref(self.TABLE_ACCOUNT,'account_id'),
731                 'phone': 'phone_work',
732                 'mobile': 'phone_mobile',
733                 'fax': 'phone_fax',
734                 'function': 'title',
735                 'street': 'primary_address_street',
736                 'zip': 'primary_address_postalcode',
737                 'city': 'primary_address_city',
738                 'country_id/id': 'country_id/id',
739                 'state_id/id': 'state_id/id',
740                 'email': self.get_email,
741                 'type': const('contact')
742             }
743         }
744     
745     """ 
746         import Account
747     """
748     
749     def get_address_type(self, val, type):
750         if type == 'invoice':
751             type_address = 'billing'
752         else:
753             type_address = 'shipping' 
754             
755         if type == 'default':
756             map_partner_address = {
757                 'name': 'name',
758                 'type': const('default'),
759                 'email': 'email1' 
760             }
761         else:        
762             map_partner_address = {
763                 'name': 'name',
764                 'phone': 'phone_office',
765                 'mobile': 'phone_mobile',
766                 'fax': 'phone_fax',
767                 'type': 'type',
768                 'street': type_address + '_address_street',
769                 'zip': type_address +'_address_postalcode',
770                 'city': type_address +'_address_city',
771                  'country_id/id': 'country_id/id',
772                  'type': 'type',
773                 }
774             
775         if val.get(type_address +'_address_country'):
776             country_id = self.get_all_countries(val.get(type_address +'_address_country'))
777             state = self.get_all_states(val.get(type_address +'_address_state'), country_id)
778             val['country_id/id'] =  country_id
779             val['state_id/id'] =  state
780             
781         val['type'] = type
782         val['id_new'] = val['id'] + '_address_' + type
783         return self.import_object_mapping(map_partner_address, val, 'res.partner.address', self.TABLE_CONTACT, val['id_new'], self.DO_NOT_FIND_DOMAIN) 
784         
785     def get_partner_address(self, val):
786         address_id=[]
787         type_dict = {'billing_address_street' : 'invoice', 'shipping_address_street' : 'delivery', 'type': 'default'}
788         for key, type_value in type_dict.items():
789             if val.get(key):
790                 id = self.get_address_type(val, type_value)
791                 address_id.append(id)
792           
793         return ','.join(address_id)
794     
795     def get_partner_mapping(self):
796         return {
797                 'model' : 'res.partner',
798                 'dependencies' : [self.TABLE_USER],
799                 'map' : {
800                     'name': 'name',
801                     'website': 'website',
802                     'user_id/id': ref(self.TABLE_USER,'assigned_user_id'),
803                     'ref': 'sic_code',
804                     'comment': ppconcat('description', 'employees', 'ownership', 'annual_revenue', 'rating', 'industry', 'ticker_symbol'),
805                     'customer': const('1'),
806                     'supplier': const('0'),
807                     'address/id':'address/id', 
808                     'parent_id/id_parent' : 'parent_id',
809                     'address/id' : self.get_partner_address,
810                 }
811         }
812
813     """
814         import Employee
815     """
816     def get_ressource(self, val):
817         map_resource = { 
818             'name': concat('first_name', 'last_name'),
819         }        
820         return self.import_object_mapping(map_resource, val, 'resource.resource', self.TABLE_RESSOURCE, val['id'], self.DO_NOT_FIND_DOMAIN)
821     
822     def get_job_id(self, val):
823         fields = ['name']
824         data = [val.get('title')]
825         return self.import_object(fields, data, 'hr.job', 'hr_job', val.get('title'))
826
827     def get_user_address(self, val):
828         map_user_address = {
829             'name': concat('first_name', 'last_name'),
830             'city': 'address_city',
831             'country_id/id': 'country_id/id',
832             'state_id/id': 'state_id/id',
833             'street': 'address_street',
834             'zip': 'address_postalcode',
835             'fax': 'phone_fax',
836             'phone': 'phone_work',
837             'mobile':'phone_mobile',
838             'email': 'email1'
839         }
840         
841         if val.get('address_country'):
842             country_id = self.get_all_countries(val.get('address_country'))
843             state_id = self.get_all_states(val.get('address_state'), country_id)
844             val['country_id/id'] =  country_id
845             val['state_id/id'] =  state_id
846             
847         return self.import_object_mapping(map_user_address, val, 'res.partner.address', self.TABLE_CONTACT, val['id'], self.DO_NOT_FIND_DOMAIN)
848
849     def get_employee_mapping(self):
850         return {
851             'model' : 'hr.employee',
852             'dependencies' : [self.TABLE_USER],
853             'map' : {
854                 'resource_id/id': self.get_ressource, 
855                 'name': concat('first_name', 'last_name'),
856                 'work_phone': 'phone_work',
857                 'mobile_phone':  'phone_mobile',
858                 'user_id/id': ref(self.TABLE_USER, 'id'), 
859                 'address_home_id/id': self.get_user_address,
860                 'notes': ppconcat('messenger_type', 'messenger_id', 'description'),
861                 'job_id/id': self.get_job_id,
862                 'work_email' : 'email1',
863                 'coach_id/id_parent' : 'reports_to_id',
864             }
865      }
866     
867     """
868         import user
869     """  
870     def import_user(self, val):
871         user_obj = self.obj.pool.get('res.users')
872         user_ids = user_obj.search(self.cr, self.uid, [('login', '=', val.get('user_name'))])
873         if user_ids: 
874             val['.id'] = str(user_ids[0])
875         else:
876             val['password'] = 'sugarcrm' #default password for all user #TODO needed in documentation
877             
878         val['context_lang'] = self.context.get('lang','en_US')
879         return val
880     
881     def get_users_department(self, val):
882         dep = val.get('department')
883         fields = ['name']
884         data = [dep]
885         if not dep:
886             return False
887         return self.import_object(fields, data, 'hr.department', 'hr_department_user', dep)
888
889     def get_user_mapping(self):
890         return {
891             'model' : 'res.users',
892             'hook' : self.import_user,
893             'map' : { 
894                 'name': concat('first_name', 'last_name'),
895                 'login': value('user_name', fallback='last_name'),
896                 'context_lang' : 'context_lang',
897                 'password' : 'password',
898                 '.id' : '.id',
899                 'context_department_id/id': self.get_users_department,
900                 'user_email' : 'email1',
901             }
902         }
903
904
905     def get_mapping(self):
906         return {
907             self.TABLE_USER : self.get_user_mapping(),
908             self.TABLE_EMPLOYEE : self.get_employee_mapping(),
909             self.TABLE_ACCOUNT : self.get_partner_mapping(),
910             self.TABLE_CONTACT : self.get_contact_mapping(),
911             self.TABLE_LEAD : self.get_lead_mapping(),
912             self.TABLE_OPPORTUNITY : self.get_opp_mapping(),
913             self.TABLE_MEETING : self.get_meeting_mapping(),
914             self.TABLE_CALL : self.get_calls_mapping(),
915             self.TABLE_TASK : self.get_task_mapping(),
916             self.TABLE_PROJECT : self.get_project_mapping(),
917             self.TABLE_PROJECT_TASK: self.get_project_task_mapping(),
918             self.TABLE_BUG: self.get_project_issue_mapping(),
919             self.TABLE_CASE: self.get_crm_claim_mapping(),
920             self.TABLE_NOTE: self.get_history_mapping(),
921             self.TABLE_EMAIL: self.get_email_mapping(),
922             self.TABLE_DOCUMENT: self.get_document_mapping(),
923             self.TABLE_COMPAIGN: self.get_compaign_mapping()
924         }
925         
926     """
927         Email notification
928     """   
929     def get_email_subject(self, result, error=False):
930         if error:
931             return "Sugarcrm data import failed at %s due to an unexpected error" % self.date_ended
932         return "your sugarcrm data were successfully imported at %s" % self.date_ended 
933     
934     def get_body_header(self, result):
935         return "Sugarcrm import : report of last import" 
936
937
938 class import_sugarcrm(osv.osv):
939     """Import SugarCRM DATA"""
940     
941     _name = "import.sugarcrm"
942     _description = __doc__
943     _columns = {
944         'username': fields.char('User Name', size=64, required=True),
945         'password': fields.char('Password', size=24,required=True),
946         'url' : fields.char('SugarSoap Api url:', size=264, required=True, help="Webservice's url where to get the data.\
947                       example : 'http://example.com/sugarcrm/soap.php', or copy the address of your sugarcrm application http://trial.sugarcrm.com/qbquyj4802/index.php?module=Home&action=index"),
948         'user' : fields.boolean('User', help="Check this box to import sugarCRM Users into OpenERP users, warning if a user with the same login exist in OpenERP, user information will be erase by sugarCRM user information", readonly=True),
949         'opportunity': fields.boolean('Leads & Opp', help="Check this box to import sugarCRM Leads and Opportunities into OpenERP Leads and Opportunities"),
950         'contact': fields.boolean('Contacts', help="Check this box to import sugarCRM Contacts into OpenERP addresses"),
951         'account': fields.boolean('Accounts', help="Check this box to import sugarCRM Accounts into OpenERP partners"),
952         'employee': fields.boolean('Employee', help="Check this box to import sugarCRM Employees into OpenERP employees"),
953         'meeting': fields.boolean('Meetings', help="Check this box to import sugarCRM Meetings and Tasks into OpenERP meetings"),
954         'call': fields.boolean('Calls', help="Check this box to import sugarCRM Calls into OpenERP calls"),
955         'claim': fields.boolean('Cases', help="Check this box to import sugarCRM Cases into OpenERP claims"),
956         'email_history': fields.boolean('Email and Note',help="Check this box to import sugarCRM Emails, Notes and Attachments into OpenERP Messages and Attachments"),
957         'project': fields.boolean('Projects', help="Check this box to import sugarCRM Projects into OpenERP projects"),
958         'project_task': fields.boolean('Project Tasks', help="Check this box to import sugarCRM Project Tasks into OpenERP tasks"),
959         'bug': fields.boolean('Bugs', help="Check this box to import sugarCRM Bugs into OpenERP project issues"),
960         'document': fields.boolean('Documents', help="Check this box to import sugarCRM Documents into OpenERP documents"),
961         'email_from': fields.char('Notify End Of Import To:', size=128),
962         'instance_name': fields.char("Instance's Name", size=64, help="Prefix of SugarCRM id to differentiate xml_id of SugarCRM models datas come from different server."),
963     }
964     
965     def _get_email_id(self, cr, uid, context=None):
966         return self.pool.get('res.users').browse(cr, uid, uid, context=context).user_email
967     
968     def _module_installed(self, cr, uid, model, context=None):
969         module_id = self.pool.get('ir.module.module').search(cr, uid, [('name', '=', model), ('state', "=", "installed")], context=context)
970         return bool(module_id)
971                 
972     def _project_installed(self, cr, uid, context=None):
973         return self._module_installed(cr,uid,'project',context=context)
974                         
975     def _crm_claim_installed(self, cr, uid, context=None):
976         return self._module_installed(cr,uid,'crm_claim',context=context)
977                     
978     def _project_issue_installed(self, cr, uid, context=None):
979         return self._module_installed(cr,uid,'project_issue',context=context)
980     
981     def _hr_installed(self, cr, uid, context=None):
982         return self._module_installed(cr,uid,'hr',context=context)
983     
984     _defaults = {#to be set to true, but easier for debugging
985        'user' : True,
986        'opportunity': True,
987        'contact' : True,
988        'account' : True,
989         'employee' : _hr_installed,
990         'meeting' : True,
991         'call' : True,
992         'claim' : _crm_claim_installed,    
993         'email_history' : True, 
994         'project' : _project_installed,   
995         'project_task': _project_installed,     
996         'bug': _project_issue_installed,
997         'document': True,
998         'instance_name': 'sugarcrm',
999         'email_from': _get_email_id,
1000         'username' : 'admin',
1001         'password' : '',
1002         'url':  "http://sugarcrm.example.com/soap.php"
1003     }
1004     
1005     def check_url(self, url, context):
1006         if not context:
1007             context = {}
1008         user = context.get('username')
1009         password = context.get('password')
1010         
1011         try :
1012             sugar.login(user, password, url)
1013             return True
1014         except Exception:
1015             return False
1016                 
1017         
1018     def parse_valid_url(self, context=None):
1019         if not context:
1020             context = {}
1021         url = context.get('url')
1022         url_split = str(url).split('/')
1023         while len(url_split) >= 3:
1024             #3 case, soap.php is already at the end of url should be valid
1025             #url end by / or not
1026             if url_split[-1] == 'soap.php':
1027                 url = "/".join(url_split)
1028             elif url_split[-1] == '':
1029                 url = "/".join(url_split) + "soap.php"
1030             else:
1031                 url = "/".join(url_split) + "/soap.php"
1032             
1033             if self.check_url(url, context):
1034                 return url
1035             else: 
1036                 url_split = url_split[:-1] #take parent folder
1037         return url
1038             
1039     def get_key(self, cr, uid, ids, context=None):
1040         """Select Key as For which Module data we want import data."""
1041         if not context:
1042             context = {}
1043         key_list = []
1044         module = {}
1045         for current in self.browse(cr, uid, ids, context):
1046             context.update({'username': current.username, 'password': current.password, 'url': current.url, 'email_user': current.email_from or False, 'instance_name': current.instance_name or False})
1047             if current.user:
1048                 key_list.append('Users')
1049             if current.contact:
1050                 key_list.append('Contacts')
1051             if current.account:
1052                 key_list.append('Accounts')  
1053             if current.opportunity:
1054                 key_list.append('Leads')
1055                 key_list.append('Opportunities')
1056             if current.employee:
1057                 key_list.append('Employees')
1058                 module.update({'Employees':'hr'}) 
1059             if current.meeting:
1060                 key_list.append('Meetings')
1061             if current.call:
1062                 key_list.append('Calls')
1063             if current.claim:
1064                 key_list.append('Cases')  
1065                 module.update({'Cases':'crm_claim'})
1066             if current.email_history:
1067                 key_list.append('Emails') 
1068                 key_list.append('Notes') 
1069             if current.project:
1070                 key_list.append('Project')
1071                 module.update({'Project':'project'})
1072             if current.project_task:
1073                 key_list.append('ProjectTask')
1074                 module.update({'ProjectTask':'project'})
1075             if current.bug:
1076                 key_list.append('Bugs')
1077                 module.update({'Bugs':'project_issue'})
1078             if current.document:
1079                 key_list.append('Documents')
1080         return key_list,module
1081
1082
1083     def do_import_all(self, cr, uid, *args):
1084         """
1085         scheduler Method
1086         """
1087         context = {'username': args[4], 'password': args[5], 'url': args[3], 'instance_name': args[3]}
1088         imp = sugar_import(self, cr, uid, args[2], "import_sugarcrm", [args[1]], context)
1089         imp.set_table_list(args[0])
1090         imp.start()
1091         return True 
1092
1093     def import_from_scheduler_all(self, cr, uid, ids, context=None):
1094         keys, module_list = self.get_key(cr, uid, ids, context)
1095         if not keys:
1096             raise osv.except_osv(_('Warning !'), _('Select Module to Import.'))
1097         key_list = module_list.keys()
1098         for module in key_list :
1099             module = module_list[module]
1100             state = self.get_all(cr,uid,module,context=context)
1101             if state == False:
1102                 keys =  ', '.join(key_list)
1103                 raise osv.except_osv(_('Error !!'), _("%s data required %s Module to be installed, Please install %s module") %(keys,module,module))
1104         cron_obj = self.pool.get('ir.cron')
1105         url = self.parse_valid_url(context)
1106         args = (keys,context.get('email_user'), context.get('instance_name'), url, context.get('username'), context.get('password') )
1107         new_create_id = cron_obj.create(cr, uid, {'name': 'Import SugarCRM datas','interval_type': 'hours','interval_number': 1, 'numbercall': -1,'model': 'import.sugarcrm','function': 'do_import_all', 'args': args, 'active': False})
1108         return {
1109             'name': 'SugarCRM Scheduler',
1110             'view_type': 'form',
1111             'view_mode': 'form,tree',
1112             'res_model': 'ir.cron',
1113             'res_id': new_create_id,
1114             'type': 'ir.actions.act_window',
1115         }
1116         
1117     def import_all(self, cr, uid, ids, context=None):
1118         
1119 #        """Import all sugarcrm data into openerp module"""
1120         keys, module_list = self.get_key(cr, uid, ids, context)
1121         if not keys:
1122             raise osv.except_osv(_('Warning !'), _('Select Module to Import.'))
1123         key_list = module_list.keys()
1124         for module in key_list :
1125             module = module_list[module]
1126             state = self._module_installed(cr,uid,module,context=context)
1127             if state == False:
1128                 keys =  ', '.join(key_list)
1129                 raise osv.except_osv(_('Error !!'), _("%s data required %s Module to be installed, Please install %s module") %(keys,module,module))
1130         url = self.parse_valid_url(context)
1131         context.update({'url': url})
1132         imp = sugar_import(self, cr, uid, context.get('instance_name'), "import_sugarcrm", [context.get('email_user')], context)
1133         imp.set_table_list(keys)
1134         imp.start()
1135         obj_model = self.pool.get('ir.model.data')
1136         model_data_ids = obj_model.search(cr,uid,[('model','=','ir.ui.view'),('name','=','import.message.form')])
1137         resource_id = obj_model.read(cr, uid, model_data_ids, fields=['res_id'])
1138         return {
1139                 'view_type': 'form',
1140                 'view_mode': 'form',
1141                 'res_model': 'import.message',
1142                 'views': [(resource_id,'form')],
1143                 'type': 'ir.actions.act_window',
1144                 'target': 'new',
1145             }
1146         
1147 import_sugarcrm()