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