1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
22 from osv import fields, osv
23 from datetime import datetime
26 from tools.translate import _
27 from crm import crm_case
32 CRM_LEAD_PENDING_STATES = (
33 crm.AVAILABLE_STATES[2][0], # Cancelled
34 crm.AVAILABLE_STATES[3][0], # Done
35 crm.AVAILABLE_STATES[4][0], # Pending
38 class crm_lead(crm_case, osv.osv):
42 _order = "date_action, priority, id desc"
43 _inherit = ['mailgate.thread','res.partner.address']
44 def _compute_day(self, cr, uid, ids, fields, args, context={}):
46 @param cr: the current row, from the database cursor,
47 @param uid: the current user’s ID for security checks,
48 @param ids: List of Openday’s IDs
49 @return: difference between current date and log date
50 @param context: A standard dictionary for contextual values
52 cal_obj = self.pool.get('resource.calendar')
53 res_obj = self.pool.get('resource.resource')
56 for lead in self.browse(cr, uid, ids , context):
61 if field == 'day_open':
63 date_create = datetime.strptime(lead.create_date, "%Y-%m-%d %H:%M:%S")
64 date_open = datetime.strptime(lead.date_open, "%Y-%m-%d %H:%M:%S")
65 ans = date_open - date_create
66 date_until = lead.date_open
67 elif field == 'day_close':
69 date_create = datetime.strptime(lead.create_date, "%Y-%m-%d %H:%M:%S")
70 date_close = datetime.strptime(lead.date_closed, "%Y-%m-%d %H:%M:%S")
71 date_until = lead.date_closed
72 ans = date_close - date_create
76 resource_ids = res_obj.search(cr, uid, [('user_id','=',lead.user_id.id)])
78 resource_id = resource_ids[0]
80 duration = float(ans.days)
81 if lead.section_id and lead.section_id.resource_calendar_id:
82 duration = float(ans.days) * 24
83 new_dates = cal_obj.interval_get(cr,
85 lead.section_id.resource_calendar_id and lead.section_id.resource_calendar_id.id or False,
86 datetime.strptime(lead.create_date, '%Y-%m-%d %H:%M:%S'),
91 date_until = datetime.strptime(date_until, '%Y-%m-%d %H:%M:%S')
92 for in_time, out_time in new_dates:
93 if in_time.date not in no_days:
94 no_days.append(in_time.date)
95 if out_time > date_until:
97 duration = len(no_days)
98 res[lead.id][field] = abs(int(duration))
102 # Overridden from res.partner.address:
103 'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null',
104 select=True, help="Optional linked partner, usually after conversion of the lead"),
107 'id': fields.integer('ID'),
108 'name': fields.char('Name', size=64),
109 'active': fields.boolean('Active', required=False),
110 'date_action_last': fields.datetime('Last Action', readonly=1),
111 'date_action_next': fields.datetime('Next Action', readonly=1),
112 'email_from': fields.char('Email', size=128, help="E-mail address of the contact"),
113 'section_id': fields.many2one('crm.case.section', 'Sales Team', \
114 select=True, help='Sales team to which this case belongs to. Defines responsible user and e-mail address for the mail gateway.'),
115 'create_date': fields.datetime('Creation Date' , readonly=True),
116 'email_cc': fields.text('Global CC', size=252 , help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
117 'description': fields.text('Notes'),
118 'write_date': fields.datetime('Update Date' , readonly=True),
121 'categ_id': fields.many2one('crm.case.categ', 'Category', \
122 domain="['|',('section_id','=',section_id),('section_id','=',False), ('object_id.model', '=', 'crm.project.bug')]"),
123 'type_id': fields.many2one('crm.case.resource.type', 'Campaign', \
124 domain="['|',('section_id','=',section_id),('section_id','=',False)]"),
125 'channel_id': fields.many2one('res.partner.canal', 'Channel'),
127 'contact_name': fields.char('Contact Name', size=64),
128 'partner_name': fields.char("Partner Name", size=64),
129 'optin': fields.boolean('Opt-In'),
130 'optout': fields.boolean('Opt-Out'),
131 'type':fields.selection([
133 ('opportunity','Opportunity'),
135 ],'Type', help="Type is used to separate Leads and Opportunities"),
136 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
137 'date_closed': fields.datetime('Closed', readonly=True),
138 'stage_id': fields.many2one('crm.case.stage', 'Stage'),
139 'user_id': fields.many2one('res.users', 'Salesman',help='By Default Salesman is Administrator when create New User'),
140 'referred': fields.char('Referred By', size=64),
141 'date_open': fields.datetime('Opened', readonly=True),
142 'day_open': fields.function(_compute_day, string='Days to Open', \
143 method=True, multi='day_open', type="float", store=True),
144 'day_close': fields.function(_compute_day, string='Days to Close', \
145 method=True, multi='day_close', type="float", store=True),
146 'state': fields.selection(crm.AVAILABLE_STATES, 'State', size=16, readonly=True,
147 help='The state is set to \'Draft\', when a case is created.\
148 \nIf the case is in progress the state is set to \'Open\'.\
149 \nWhen the case is over, the state is set to \'Done\'.\
150 \nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
151 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
155 'active': lambda *a: 1,
156 'user_id': crm_case._get_default_user,
157 'email_from': crm_case._get_default_email,
158 'state': lambda *a: 'draft',
159 'type': lambda *a: 'lead',
160 'section_id': crm_case._get_section,
161 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c),
162 'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
165 def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
166 """This function returns value of partner email based on Partner Address
167 @param self: The object pointer
168 @param cr: the current row, from the database cursor,
169 @param uid: the current user’s ID for security checks,
170 @param ids: List of case IDs
171 @param add: Id of Partner's address
172 @email: Partner's email ID
175 return {'value': {'email_from': False, 'country_id': False}}
176 address = self.pool.get('res.partner.address').browse(cr, uid, add)
177 return {'value': {'email_from': address.email, 'phone': address.phone, 'country_id': address.country_id.id}}
179 def case_open(self, cr, uid, ids, *args):
180 """Overrides cancel for crm_case for setting Open Date
181 @param self: The object pointer
182 @param cr: the current row, from the database cursor,
183 @param uid: the current user’s ID for security checks,
184 @param ids: List of case's Ids
185 @param *args: Give Tuple Value
187 old_state = self.read(cr, uid, ids, ['state'])[0]['state']
188 old_stage_id = self.read(cr, uid, ids, ['stage_id'])[0]['stage_id']
189 res = super(crm_lead, self).case_open(cr, uid, ids, *args)
190 if old_state == 'draft':
193 stage_id = super(crm_lead, self).stage_next(cr, uid, ids, *args)
195 value.update({'stage_id': stage_id})
196 value.update(self.onchange_stage_id(cr, uid, ids, stage_id, context={})['value'])
197 value.update({'date_open': time.strftime('%Y-%m-%d %H:%M:%S')})
198 self.write(cr, uid, ids, value)
200 for (id, name) in self.name_get(cr, uid, ids):
201 type = self.browse(cr, uid, id).type or 'Lead'
202 message = (_('The ') + type.title()) + " '" + name + "' "+ _("has been Opened.")
203 self.log(cr, uid, id, message)
206 def case_close(self, cr, uid, ids, *args):
207 """Overrides close for crm_case for setting close date
208 @param self: The object pointer
209 @param cr: the current row, from the database cursor,
210 @param uid: the current user’s ID for security checks,
211 @param ids: List of case Ids
212 @param *args: Tuple Value for additional Params
214 res = super(crm_lead, self).case_close(cr, uid, ids, args)
215 self.write(cr, uid, ids, {'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')})
216 for (id, name) in self.name_get(cr, uid, ids):
217 lead = self.browse(cr, uid, id)
218 if lead.type == 'lead':
219 message = _('The Lead') + " '" + name + "' "+ _("has been Closed.")
220 self.log(cr, uid, id, message)
223 def convert_opportunity(self, cr, uid, ids, context=None):
224 """ Precomputation for converting lead to opportunity
225 @param cr: the current row, from the database cursor,
226 @param uid: the current user’s ID for security checks,
227 @param ids: List of closeday’s IDs
228 @param context: A standard dictionary for contextual values
229 @return: Value of action in dict
233 context.update({'active_ids': ids})
235 data_obj = self.pool.get('ir.model.data')
236 data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_action')
241 view_id = data_obj.browse(cr, uid, data_id, context=context).res_id
243 for case in self.browse(cr, uid, ids):
244 context.update({'active_id': case.id})
245 if not case.partner_id:
246 data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_partner')
249 view_id1 = data_obj.browse(cr, uid, data_id, context=context).res_id
251 'name': _('Create Partner'),
253 'view_mode': 'form,tree',
254 'res_model': 'crm.lead2opportunity.partner',
257 'views': [(view_id1, 'form')],
258 'type': 'ir.actions.act_window',
265 'name': _('Create Opportunity'),
267 'view_mode': 'form,tree',
268 'res_model': 'crm.lead2opportunity.action',
271 'views': [(view_id, 'form')],
272 'type': 'ir.actions.act_window',
278 def stage_next(self, cr, uid, ids, context=None):
279 stage = super(crm_lead, self).stage_next(cr, uid, ids, context)
281 stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, stage, context=context)
282 if stage_obj.on_change:
283 data = {'probability': stage_obj.probability}
284 self.write(cr, uid, ids, data)
287 def message_new(self, cr, uid, msg, context):
289 Automatically calls when new email message arrives
291 @param self: The object pointer
292 @param cr: the current row, from the database cursor,
293 @param uid: the current user’s ID for security checks
296 mailgate_pool = self.pool.get('email.server.tools')
298 subject = msg.get('subject')
299 body = msg.get('body')
300 msg_from = msg.get('from')
301 priority = msg.get('priority')
305 'email_from': msg_from,
306 'email_cc': msg.get('cc'),
310 if msg.get('priority', False):
311 vals['priority'] = priority
313 res = mailgate_pool.get_partner(cr, uid, msg.get('from') or msg.get_unixfrom())
317 res = self.create(cr, uid, vals, context)
319 message = _('A Lead created') + " '" + subject + "' " + _("from Mailgate.")
320 self.log(cr, uid, res, message)
322 attachents = msg.get('attachments', [])
323 for attactment in attachents or []:
326 'datas':binascii.b2a_base64(str(attachents.get(attactment))),
327 'datas_fname': attactment,
328 'description': 'Mail attachment',
329 'res_model': self._name,
332 self.pool.get('ir.attachment').create(cr, uid, data_attach)
336 def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context={}):
338 @param self: The object pointer
339 @param cr: the current row, from the database cursor,
340 @param uid: the current user’s ID for security checks,
341 @param ids: List of update mail’s IDs
344 if isinstance(ids, (str, int, long)):
347 if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
348 vals['priority'] = msg.get('priority')
351 'cost':'planned_cost',
352 'revenue': 'planned_revenue',
353 'probability':'probability'
356 for line in msg['body'].split('\n'):
358 res = tools.misc.command_re.match(line)
359 if res and maps.get(res.group(1).lower()):
360 key = maps.get(res.group(1).lower())
361 vls[key] = res.group(2).lower()
364 # Unfortunately the API is based on lists
365 # but we want to update the state based on the
366 # previous state, so we have to loop:
367 for case in self.browse(cr, uid, ids, context=context):
369 if case.state in CRM_LEAD_PENDING_STATES:
370 values.update(state=crm.AVAILABLE_STATES[1][0]) #re-open
371 res = self.write(cr, uid, [case.id], values, context=context)
375 def msg_send(self, cr, uid, id, *args, **argv):
378 @param self: The object pointer
379 @param cr: the current row, from the database cursor,
380 @param uid: the current user’s ID for security checks,
381 @param ids: List of email’s IDs
382 @param *args: Return Tuple Value
383 @param **args: Return Dictionary of Keyword Value
388 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: