[FIX] htmlentities
[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         print  val.get('date_start'), val.get('date_due')
454         val['date'] = val.get('date_start') or datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
455         val['date_deadline'] = val.get('date_due') or datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
456         return val
457
458     def get_task_mapping(self):
459         return { 
460                 'model' : 'crm.meeting',
461                 'dependencies' : [self.TABLE_CONTACT, self.TABLE_ACCOUNT, self.TABLE_USER],
462                 'hook' : self.import_task,
463                 'map' : {
464                     'name': 'name',
465                     'date': 'date',
466                     'date_deadline': 'date_deadline',
467                     'user_id/id': ref(self.TABLE_USER, 'assigned_user_id'),
468                     'categ_id/id': call(self.get_category, 'crm.meeting', const('Tasks')),
469                     'partner_id/id': related_ref(self.TABLE_ACCOUNT),
470                     'partner_address_id/id': ref(self.TABLE_CONTACT,'contact_id'),
471                     'state': map_val('status', self.task_state)
472                 }
473             }
474        
475     """
476     import Calls
477     """  
478     #TODO adapt with project trunk-crm-imp   
479     call_state = {   
480             'Planned' : 'open',
481             'Held':'done',
482             'Not Held': 'pending',
483         }
484
485     def get_calls_mapping(self):
486         return { 
487                 'model' : 'crm.phonecall',
488                 'dependencies' : [self.TABLE_ACCOUNT, self.TABLE_CONTACT, self.TABLE_OPPORTUNITY, self.TABLE_LEAD],
489                 'map' : {
490                     'name': 'name',
491                     'date': 'date_start',
492                     'duration': call(self.get_float_time, value('duration_hours'), value('duration_minutes')),
493                     'user_id/id':  ref(self.TABLE_USER, 'assigned_user_id'),
494                     'partner_id/id': related_ref(self.TABLE_ACCOUNT),
495                     'partner_address_id/id': related_ref(self.TABLE_CONTACT),
496                     'categ_id/id': call(self.get_category, 'crm.phonecall', value('direction')),
497                     'opportunity_id/id': related_ref(self.TABLE_OPPORTUNITY),
498                     'description': ppconcat('description'),   
499                     'state': map_val('status', self.call_state)                      
500                 }
501             }       
502          
503     """
504         import meeting
505     """
506     meeting_state = {
507             'Planned' : 'draft',
508             'Held': 'open',
509             'Not Held': 'draft', 
510         }
511 #TODO    
512     def get_attendee_id(self, cr, uid, module_name, module_id):
513         contact_id = False
514         user_id = False
515         attendee_id= []
516         attendee_dict = sugar.user_get_attendee_list(self.context.get('port'), self.context.get('session_id'), module_name, module_id)
517         for attendee in attendee_dict:
518             user_id = self.xml_id_exist(self.TABLE_USER, attendee.get('id', False))
519             contact_id = False
520             if not user_id:
521                 contact_id = self.xml_id_exist(self.TABLE_CONTACT, attendee.get('id', False))
522             fields = ['user_id/id', 'email', 'partner_address_id/id']
523             data = [user_id, attendee.get('email1'), contact_id]
524             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'))])
525             attendee_id.append(attendee_xml_id)
526         return ','.join(attendee_id) 
527     
528     def get_alarm_id(self, dict_val, val):
529         alarm_dict = {
530             '60': '1 minute before',
531             '300': '5 minutes before',
532             '600': '10 minutes before',
533             '900': '15 minutes before',
534             '1800':'30 minutes before',
535             '3600': '1 hour before',
536         }
537         return self.mapped_id_if_exist('res.alarm', [('name', 'like', alarm_dict.get(val))], 'alarm', val)
538     
539     #TODO attendees
540
541     def import_meeting(self, val):
542         attendee_id = self.get_attendee_id(self.cr, self.uid, 'Meetings', val.get('id')) #TODO
543         val['attendee_ids/id'] = attendee_id
544         return val
545
546     def get_meeting_mapping(self):
547         return { 
548                 'model' : 'crm.meeting',
549                 'dependencies' : [self.TABLE_CONTACT, self.TABLE_OPPORTUNITY, self.TABLE_LEAD, self.TABLE_TASK],
550                 'hook': self.import_meeting,
551                 'map' : {
552                     'name': 'name',
553                     'date': 'date_start',
554                     'duration': call(self.get_float_time, value('duration_hours'), value('duration_minutes')),
555                     'location': 'location',
556                     'attendee_ids/id':'attendee_ids/id',
557                     'alarm_id/id': call(self.get_alarm_id, value('reminder_time')),
558                     'user_id/id': ref(self.TABLE_USER, 'assigned_user_id'),
559                     'partner_id/id': related_ref(self.TABLE_ACCOUNT),
560                     'partner_address_id/id': related_ref(self.TABLE_CONTACT),
561                     'state': map_val('status', self.meeting_state)
562                 }
563             }
564     
565     """
566         import Opportunity
567     """
568     opp_state = {
569             'Need Analysis' : 'New',
570             'Closed Lost': 'Lost',
571             'Closed Won': 'Won', 
572             'Value Proposition': 'Proposition',
573             'Negotiation/Review': 'Negotiation'
574         }
575         
576     def get_opportunity_status(self, sugar_val):
577         fields = ['name', 'type']
578         name = 'Opportunity_' + sugar_val['sales_stage']
579         data = [sugar_val['sales_stage'], 'Opportunity']
580         return self.import_object(fields, data, 'crm.case.stage', self.TABLE_STAGE, name, [('type', '=', 'opportunity'), ('name', 'ilike', sugar_val['sales_stage'])])
581     
582     def import_opportunity_contact(self, val):
583         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))
584             
585         partner_contact_id = False 
586         partner_contact_email = False       
587         partner_address_obj = self.obj.pool.get('res.partner.address')
588         partner_xml_id = self.name_exist(self.TABLE_ACCOUNT, val['account_name'], 'res.partner')
589         
590         for contact in sugar_opportunities_contact:
591             address_id = self.get_mapped_id(self.TABLE_CONTACT, contact)
592             if address_id:                    
593                 address = partner_address_obj.browse(self.cr, self.uid, address_id)
594                 partner_name = address.partner_id and address.partner_id.name or False
595                 if not partner_name: #link with partner id 
596                     fields = ['partner_id/id']
597                     data = [partner_xml_id]
598                     self.import_object(fields, data, 'res.partner.address', self.TABLE_CONTACT, contact, self.DO_NOT_FIND_DOMAIN)
599                 if not partner_name or partner_name == val.get('account_name'):
600                     partner_contact_id = self.xml_id_exist(self.TABLE_CONTACT, contact)
601                     partner_contact_email = address.email
602         return partner_contact_id, partner_contact_email
603
604     def import_opp(self, val):    
605         partner_contact_id, partner_contact_email = self.import_opportunity_contact(val)
606         val['partner_address_id/id'] = partner_contact_id
607         val['email_from'] = partner_contact_email
608         return val
609     
610     def get_opp_mapping(self):
611         return {
612             'model' : 'crm.lead',
613             'dependencies' : [self.TABLE_USER, self.TABLE_ACCOUNT, self.TABLE_CONTACT,self.TABLE_COMPAIGN],
614             'hook' : self.import_opp,
615             'map' :  {
616                 'name': 'name',
617                 'probability': 'probability',
618                 'partner_id/id': refbyname(self.TABLE_ACCOUNT, 'account_name', 'res.partner'),
619                 'title_action': 'next_step',
620                 'partner_address_id/id': 'partner_address_id/id',
621                 'planned_revenue': 'amount',
622                 'date_deadline': 'date_closed',
623                 'user_id/id' : ref(self.TABLE_USER, 'assigned_user_id'),
624                 'stage_id/id' : self.get_opportunity_status,
625                 'type' : const('opportunity'),
626                 'categ_id/id': call(self.get_category, 'crm.lead', value('opportunity_type')),
627                 'email_from': 'email_from',
628                 'state': map_val('status', self.opp_state)  , #TODO
629             }
630         }
631         
632     """
633     import campaign
634     """
635     
636     def get_compaign_mapping(self):
637         return {
638             'model' : 'crm.case.resource.type',
639             'map' : {
640                 'name': 'name',
641                 } 
642         }    
643         
644     """
645         import lead
646     """
647     def get_lead_status(self, sugar_val):
648         fields = ['name', 'type']
649         name = 'lead_' + sugar_val.get('status', '')
650         data = [sugar_val.get('status', ''), 'lead']
651         return self.import_object(fields, data, 'crm.case.stage', self.TABLE_STAGE, name, [('type', '=', 'lead'), ('name', 'ilike', sugar_val.get('status', ''))])
652
653     lead_state = {
654         'New' : 'draft',
655         'Assigned':'open',
656         'In Progress': 'open',
657         'Recycled': 'cancel',
658         'Dead': 'done',
659         'Converted': 'done',
660     }
661     
662     def import_lead(self, val):
663         if val.get('opportunity_id'): #if lead is converted into opp, don't import as lead
664             return False
665         if val.get('primary_address_country'):
666             country_id = self.get_all_countries(val.get('primary_address_country'))
667             val['country_id/id'] =  country_id
668             val['state_id/id'] =  self.get_all_states(val.get('primary_address_state'), country_id)
669         return val
670     
671     def get_lead_mapping(self):
672         return {
673             'model' : 'crm.lead',
674             'dependencies' : [self.TABLE_COMPAIGN, self.TABLE_USER],
675             'hook' : self.import_lead,
676             'map' : {
677                 'name': concat('first_name', 'last_name'),
678                 'contact_name': concat('first_name', 'last_name'),
679                 'description': ppconcat('description', 'refered_by', 'lead_source', 'lead_source_description', 'website', 'email2', 'status_description', 'lead_source_description', 'do_not_call'),
680                 'partner_name': 'account_name',
681                 'email_from': 'email1',
682                 'phone': 'phone_work',
683                 'mobile': 'phone_mobile',
684                 'title/id': call(self.get_job_title, value('salutation')),
685                 'function':'title',
686                 'street': 'primary_address_street',
687                 'street2': 'alt_address_street',
688                 'zip': 'primary_address_postalcode',
689                 'city':'primary_address_city',
690                 'user_id/id' : ref(self.TABLE_USER, 'assigned_user_id'),
691                 'stage_id/id' : self.get_lead_status,
692                 'type' : const('lead'),
693                 'state': map_val('status', self.lead_state) ,
694                 'fax': 'phone_fax',
695                 'referred': 'refered_by',
696                 'optout': 'do_not_call',
697                 'channel_id/id': call(self.get_channel_id, value('lead_source')),
698                 'type_id/id': ref(self.TABLE_COMPAIGN, 'campaign_id'),
699                 'country_id/id': 'country_id/id',
700                 'state_id/id': 'state_id/id'
701                 } 
702         }
703     
704     """
705         import contact
706     """
707     
708     def get_email(self, val):
709         email_address = sugar.get_contact_by_email(self.context.get('port'), self.context.get('username'), self.context.get('password'), val.get('email1'))
710         if email_address:
711             return ','.join(email_address)     
712     
713     def import_contact(self, val):
714         if val.get('primary_address_country'):
715             country_id = self.get_all_countries(val.get('primary_address_country'))
716             state = self.get_all_states(val.get('primary_address_state'), country_id)
717             val['country_id/id'] =  country_id
718             val['state_id/id'] =  state
719         return val    
720         
721     def get_contact_mapping(self):
722         return { 
723             'model' : 'res.partner.address',
724             'dependencies' : [self.TABLE_ACCOUNT],
725             'hook' : self.import_contact,
726             'map' :  {
727                 'name': concat('first_name', 'last_name'),
728                 'partner_id/id': ref(self.TABLE_ACCOUNT,'account_id'),
729                 'phone': 'phone_work',
730                 'mobile': 'phone_mobile',
731                 'fax': 'phone_fax',
732                 'function': 'title',
733                 'street': 'primary_address_street',
734                 'zip': 'primary_address_postalcode',
735                 'city': 'primary_address_city',
736                 'country_id/id': 'country_id/id',
737                 'state_id/id': 'state_id/id',
738                 'email': self.get_email,
739                 'type': const('contact')
740             }
741         }
742     
743     """ 
744         import Account
745     """
746     
747     def get_address_type(self, val, type):
748         if type == 'invoice':
749             type_address = 'billing'
750         else:
751             type_address = 'shipping' 
752             
753         if type == 'default':
754             map_partner_address = {
755                 'name': 'name',
756                 'type': const('default'),
757                 'email': 'email1' 
758             }
759         else:        
760             map_partner_address = {
761                 'name': 'name',
762                 'phone': 'phone_office',
763                 'mobile': 'phone_mobile',
764                 'fax': 'phone_fax',
765                 'type': 'type',
766                 'street': type_address + '_address_street',
767                 'zip': type_address +'_address_postalcode',
768                 'city': type_address +'_address_city',
769                  'country_id/id': 'country_id/id',
770                  'type': 'type',
771                 }
772             
773         if val.get(type_address +'_address_country'):
774             country_id = self.get_all_countries(val.get(type_address +'_address_country'))
775             state = self.get_all_states(val.get(type_address +'_address_state'), country_id)
776             val['country_id/id'] =  country_id
777             val['state_id/id'] =  state
778             
779         val['type'] = type
780         val['id_new'] = val['id'] + '_address_' + type
781         return self.import_object_mapping(map_partner_address, val, 'res.partner.address', self.TABLE_CONTACT, val['id_new'], self.DO_NOT_FIND_DOMAIN) 
782         
783     def get_partner_address(self, val):
784         address_id=[]
785         type_dict = {'billing_address_street' : 'invoice', 'shipping_address_street' : 'delivery', 'type': 'default'}
786         for key, type_value in type_dict.items():
787             if val.get(key):
788                 id = self.get_address_type(val, type_value)
789                 address_id.append(id)
790           
791         return ','.join(address_id)
792     
793     def get_partner_mapping(self):
794         return {
795                 'model' : 'res.partner',
796                 'dependencies' : [self.TABLE_USER],
797                 'map' : {
798                     'name': 'name',
799                     'website': 'website',
800                     'user_id/id': ref(self.TABLE_USER,'assigned_user_id'),
801                     'ref': 'sic_code',
802                     'comment': ppconcat('description', 'employees', 'ownership', 'annual_revenue', 'rating', 'industry', 'ticker_symbol'),
803                     'customer': const('1'),
804                     'supplier': const('0'),
805                     'address/id':'address/id', 
806                     'parent_id/id_parent' : 'parent_id',
807                     'address/id' : self.get_partner_address,
808                 }
809         }
810
811     """
812         import Employee
813     """
814     def get_ressource(self, val):
815         map_resource = { 
816             'name': concat('first_name', 'last_name'),
817         }        
818         return self.import_object_mapping(map_resource, val, 'resource.resource', self.TABLE_RESSOURCE, val['id'], self.DO_NOT_FIND_DOMAIN)
819     
820     def get_job_id(self, val):
821         fields = ['name']
822         data = [val.get('title')]
823         return self.import_object(fields, data, 'hr.job', 'hr_job', val.get('title'))
824
825     def get_user_address(self, val):
826         map_user_address = {
827             'name': concat('first_name', 'last_name'),
828             'city': 'address_city',
829             'country_id/id': 'country_id/id',
830             'state_id/id': 'state_id/id',
831             'street': 'address_street',
832             'zip': 'address_postalcode',
833             'fax': 'phone_fax',
834             'phone': 'phone_work',
835             'mobile':'phone_mobile',
836             'email': 'email1'
837         }
838         
839         if val.get('address_country'):
840             country_id = self.get_all_countries(val.get('address_country'))
841             state_id = self.get_all_states(val.get('address_state'), country_id)
842             val['country_id/id'] =  country_id
843             val['state_id/id'] =  state_id
844             
845         return self.import_object_mapping(map_user_address, val, 'res.partner.address', self.TABLE_CONTACT, val['id'], self.DO_NOT_FIND_DOMAIN)
846
847     def get_employee_mapping(self):
848         return {
849             'model' : 'hr.employee',
850             'dependencies' : [self.TABLE_USER],
851             'map' : {
852                 'resource_id/id': self.get_ressource, 
853                 'name': concat('first_name', 'last_name'),
854                 'work_phone': 'phone_work',
855                 'mobile_phone':  'phone_mobile',
856                 'user_id/id': ref(self.TABLE_USER, 'id'), 
857                 'address_home_id/id': self.get_user_address,
858                 'notes': ppconcat('messenger_type', 'messenger_id', 'description'),
859                 'job_id/id': self.get_job_id,
860                 'work_email' : 'email1',
861                 'coach_id/id_parent' : 'reports_to_id',
862             }
863      }
864     
865     """
866         import user
867     """  
868     def import_user(self, val):
869         user_obj = self.obj.pool.get('res.users')
870         user_ids = user_obj.search(self.cr, self.uid, [('login', '=', val.get('user_name'))])
871         if user_ids: 
872             val['.id'] = str(user_ids[0])
873         else:
874             val['password'] = 'sugarcrm' #default password for all user #TODO needed in documentation
875             
876         val['context_lang'] = self.context.get('lang','en_US')
877         return val
878     
879     def get_users_department(self, val):
880         dep = val.get('department')
881         fields = ['name']
882         data = [dep]
883         if not dep:
884             return False
885         return self.import_object(fields, data, 'hr.department', 'hr_department_user', dep)
886
887     def get_user_mapping(self):
888         return {
889             'model' : 'res.users',
890             'hook' : self.import_user,
891             'map' : { 
892                 'name': concat('first_name', 'last_name'),
893                 'login': value('user_name', fallback='last_name'),
894                 'context_lang' : 'context_lang',
895                 'password' : 'password',
896                 '.id' : '.id',
897                 'context_department_id/id': self.get_users_department,
898                 'user_email' : 'email1',
899             }
900         }
901
902
903     def get_mapping(self):
904         return {
905             self.TABLE_USER : self.get_user_mapping(),
906             self.TABLE_EMPLOYEE : self.get_employee_mapping(),
907             self.TABLE_ACCOUNT : self.get_partner_mapping(),
908             self.TABLE_CONTACT : self.get_contact_mapping(),
909             self.TABLE_LEAD : self.get_lead_mapping(),
910             self.TABLE_OPPORTUNITY : self.get_opp_mapping(),
911             self.TABLE_MEETING : self.get_meeting_mapping(),
912             self.TABLE_CALL : self.get_calls_mapping(),
913             self.TABLE_TASK : self.get_task_mapping(),
914             self.TABLE_PROJECT : self.get_project_mapping(),
915             self.TABLE_PROJECT_TASK: self.get_project_task_mapping(),
916             self.TABLE_BUG: self.get_project_issue_mapping(),
917             self.TABLE_CASE: self.get_crm_claim_mapping(),
918             self.TABLE_NOTE: self.get_history_mapping(),
919             self.TABLE_EMAIL: self.get_email_mapping(),
920             self.TABLE_DOCUMENT: self.get_document_mapping(),
921             self.TABLE_COMPAIGN: self.get_compaign_mapping()
922         }
923         
924     """
925         Email notification
926     """   
927     def get_email_subject(self, result, error=False):
928         if error:
929             return "Sugarcrm data import failed at %s due to an unexpected error" % self.date_ended
930         return "your sugarcrm data were successfully imported at %s" % self.date_ended 
931     
932     def get_body_header(self, result):
933         return "Sugarcrm import : report of last import" 
934
935
936 class import_sugarcrm(osv.osv):
937     """Import SugarCRM DATA"""
938     
939     _name = "import.sugarcrm"
940     _description = __doc__
941     _columns = {
942         'username': fields.char('User Name', size=64, required=True),
943         'password': fields.char('Password', size=24,required=True),
944         'url' : fields.char('SugarSoap Api url:', size=264, required=True, help="Webservice's url where to get the data.\
945                       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"),
946         '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),
947         'opportunity': fields.boolean('Leads & Opp', help="Check this box to import sugarCRM Leads and Opportunities into OpenERP Leads and Opportunities"),
948         'contact': fields.boolean('Contacts', help="Check this box to import sugarCRM Contacts into OpenERP addresses"),
949         'account': fields.boolean('Accounts', help="Check this box to import sugarCRM Accounts into OpenERP partners"),
950         'employee': fields.boolean('Employee', help="Check this box to import sugarCRM Employees into OpenERP employees"),
951         'meeting': fields.boolean('Meetings', help="Check this box to import sugarCRM Meetings and Tasks into OpenERP meetings"),
952         'call': fields.boolean('Calls', help="Check this box to import sugarCRM Calls into OpenERP calls"),
953         'claim': fields.boolean('Cases', help="Check this box to import sugarCRM Cases into OpenERP claims"),
954         'email_history': fields.boolean('Email and Note',help="Check this box to import sugarCRM Emails, Notes and Attachments into OpenERP Messages and Attachments"),
955         'project': fields.boolean('Projects', help="Check this box to import sugarCRM Projects into OpenERP projects"),
956         'project_task': fields.boolean('Project Tasks', help="Check this box to import sugarCRM Project Tasks into OpenERP tasks"),
957         'bug': fields.boolean('Bugs', help="Check this box to import sugarCRM Bugs into OpenERP project issues"),
958         'document': fields.boolean('Documents', help="Check this box to import sugarCRM Documents into OpenERP documents"),
959         'email_from': fields.char('Notify End Of Import To:', size=128),
960         '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."),
961     }
962     
963     def _get_email_id(self, cr, uid, context=None):
964         return self.pool.get('res.users').browse(cr, uid, uid, context=context).user_email
965     
966     def _module_installed(self, cr, uid, model, context=None):
967         module_id = self.pool.get('ir.module.module').search(cr, uid, [('name', '=', model), ('state', "=", "installed")], context=context)
968         return bool(module_id)
969                 
970     def _project_installed(self, cr, uid, context=None):
971         return self._module_installed(cr,uid,'project',context=context)
972                         
973     def _crm_claim_installed(self, cr, uid, context=None):
974         return self._module_installed(cr,uid,'crm_claim',context=context)
975                     
976     def _project_issue_installed(self, cr, uid, context=None):
977         return self._module_installed(cr,uid,'project_issue',context=context)
978     
979     def _hr_installed(self, cr, uid, context=None):
980         return self._module_installed(cr,uid,'hr',context=context)
981     
982     _defaults = {#to be set to true, but easier for debugging
983        'user' : True,
984        'opportunity': True,
985        'contact' : True,
986        'account' : True,
987         'employee' : _hr_installed,
988         'meeting' : True,
989         'call' : True,
990         'claim' : _crm_claim_installed,    
991         'email_history' : True, 
992         'project' : _project_installed,   
993         'project_task': _project_installed,     
994         'bug': _project_issue_installed,
995         'document': True,
996         'instance_name': 'sugarcrm',
997         'email_from': _get_email_id,
998         #'username' : 'admin',
999         #'password' : '',
1000         #'url':  "http://sugarcrm.example.com/soap.php"
1001         'username' : 'tfr',
1002         'password' : 'a',
1003         'url':  "http://localhost/sugarcrm/soap.php"        
1004     }
1005     
1006     def check_url(self, url, context):
1007         if not context:
1008             context = {}
1009         user = context.get('username')
1010         password = context.get('password')
1011         
1012         try :
1013             sugar.login(user, password, url)
1014             return True
1015         except Exception:
1016             return False
1017                 
1018         
1019     def parse_valid_url(self, context=None):
1020         if not context:
1021             context = {}
1022         url = context.get('url')
1023         url_split = str(url).split('/')
1024         while len(url_split) >= 3:
1025             #3 case, soap.php is already at the end of url should be valid
1026             #url end by / or not
1027             if url_split[-1] == 'soap.php':
1028                 url = "/".join(url_split)
1029             elif url_split[-1] == '':
1030                 url = "/".join(url_split) + "soap.php"
1031             else:
1032                 url = "/".join(url_split) + "/soap.php"
1033             
1034             if self.check_url(url, context):
1035                 return url
1036             else: 
1037                 url_split = url_split[:-1] #take parent folder
1038         return url
1039             
1040     def get_key(self, cr, uid, ids, context=None):
1041         """Select Key as For which Module data we want import data."""
1042         if not context:
1043             context = {}
1044         key_list = []
1045         module = {}
1046         for current in self.browse(cr, uid, ids, context):
1047             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})
1048             if current.user:
1049                 key_list.append('Users')
1050             if current.contact:
1051                 key_list.append('Contacts')
1052             if current.account:
1053                 key_list.append('Accounts')  
1054             if current.opportunity:
1055                 key_list.append('Leads')
1056                 key_list.append('Opportunities')
1057             if current.employee:
1058                 key_list.append('Employees')
1059                 module.update({'Employees':'hr'}) 
1060             if current.meeting:
1061                 key_list.append('Meetings')
1062             if current.call:
1063                 key_list.append('Calls')
1064             if current.claim:
1065                 key_list.append('Cases')  
1066                 module.update({'Cases':'crm_claim'})
1067             if current.email_history:
1068                 key_list.append('Emails') 
1069                 key_list.append('Notes') 
1070             if current.project:
1071                 key_list.append('Project')
1072                 module.update({'Project':'project'})
1073             if current.project_task:
1074                 key_list.append('ProjectTask')
1075                 module.update({'ProjectTask':'project'})
1076             if current.bug:
1077                 key_list.append('Bugs')
1078                 module.update({'Bugs':'project_issue'})
1079             if current.document:
1080                 key_list.append('Documents')
1081         return key_list,module
1082
1083
1084     def do_import_all(self, cr, uid, *args):
1085         """
1086         scheduler Method
1087         """
1088         context = {'username': args[4], 'password': args[5], 'url': args[3], 'instance_name': args[3]}
1089         imp = sugar_import(self, cr, uid, args[2], "import_sugarcrm", [args[1]], context)
1090         imp.set_table_list(args[0])
1091         imp.start()
1092         return True 
1093
1094     def import_from_scheduler_all(self, cr, uid, ids, context=None):
1095         keys, module_list = self.get_key(cr, uid, ids, context)
1096         if not keys:
1097             raise osv.except_osv(_('Warning !'), _('Select Module to Import.'))
1098         key_list = module_list.keys()
1099         for module in key_list :
1100             module = module_list[module]
1101             state = self.get_all(cr,uid,module,context=context)
1102             if state == False:
1103                 keys =  ', '.join(key_list)
1104                 raise osv.except_osv(_('Error !!'), _("%s data required %s Module to be installed, Please install %s module") %(keys,module,module))
1105         cron_obj = self.pool.get('ir.cron')
1106         url = self.parse_valid_url(context)
1107         args = (keys,context.get('email_user'), context.get('instance_name'), url, context.get('username'), context.get('password') )
1108         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})
1109         return {
1110             'name': 'SugarCRM Scheduler',
1111             'view_type': 'form',
1112             'view_mode': 'form,tree',
1113             'res_model': 'ir.cron',
1114             'res_id': new_create_id,
1115             'type': 'ir.actions.act_window',
1116         }
1117         
1118     def import_all(self, cr, uid, ids, context=None):
1119         
1120 #        """Import all sugarcrm data into openerp module"""
1121         keys, module_list = self.get_key(cr, uid, ids, context)
1122         if not keys:
1123             raise osv.except_osv(_('Warning !'), _('Select Module to Import.'))
1124         key_list = module_list.keys()
1125         for module in key_list :
1126             module = module_list[module]
1127             state = self._module_installed(cr,uid,module,context=context)
1128             if state == False:
1129                 keys =  ', '.join(key_list)
1130                 raise osv.except_osv(_('Error !!'), _("%s data required %s Module to be installed, Please install %s module") %(keys,module,module))
1131         url = self.parse_valid_url(context)
1132         context.update({'url': url})
1133         imp = sugar_import(self, cr, uid, context.get('instance_name'), "import_sugarcrm", [context.get('email_user')], context)
1134         imp.set_table_list(keys)
1135         imp.start()
1136         obj_model = self.pool.get('ir.model.data')
1137         model_data_ids = obj_model.search(cr,uid,[('model','=','ir.ui.view'),('name','=','import.message.form')])
1138         resource_id = obj_model.read(cr, uid, model_data_ids, fields=['res_id'])
1139         return {
1140                 'view_type': 'form',
1141                 'view_mode': 'form',
1142                 'res_model': 'import.message',
1143                 'views': [(resource_id,'form')],
1144                 'type': 'ir.actions.act_window',
1145                 'target': 'new',
1146             }
1147         
1148 import_sugarcrm()