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