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
27 from tools.translate import _
28 from crm import crm_case
33 CRM_LEAD_PENDING_STATES = (
34 crm.AVAILABLE_STATES[2][0], # Cancelled
35 crm.AVAILABLE_STATES[3][0], # Done
36 crm.AVAILABLE_STATES[4][0], # Pending
39 class crm_lead(crm_case, osv.osv):
43 _order = "date_action, priority, id desc"
44 _inherit = ['mailgate.thread','res.partner.address']
45 def _compute_day(self, cr, uid, ids, fields, args, context={}):
47 @param cr: the current row, from the database cursor,
48 @param uid: the current user’s ID for security checks,
49 @param ids: List of Openday’s IDs
50 @return: difference between current date and log date
51 @param context: A standard dictionary for contextual values
53 cal_obj = self.pool.get('resource.calendar')
54 res_obj = self.pool.get('resource.resource')
57 for lead in self.browse(cr, uid, ids , context):
62 if field == 'day_open':
64 date_create = datetime.strptime(lead.create_date, "%Y-%m-%d %H:%M:%S")
65 date_open = datetime.strptime(lead.date_open, "%Y-%m-%d %H:%M:%S")
66 ans = date_open - date_create
67 date_until = lead.date_open
68 elif field == 'day_close':
70 date_create = datetime.strptime(lead.create_date, "%Y-%m-%d %H:%M:%S")
71 date_close = datetime.strptime(lead.date_closed, "%Y-%m-%d %H:%M:%S")
72 date_until = lead.date_closed
73 ans = date_close - date_create
77 resource_ids = res_obj.search(cr, uid, [('user_id','=',lead.user_id.id)])
79 resource_id = resource_ids[0]
81 duration = float(ans.days)
82 if lead.section_id and lead.section_id.resource_calendar_id:
83 duration = float(ans.days) * 24
84 new_dates = cal_obj.interval_get(cr,
86 lead.section_id.resource_calendar_id and lead.section_id.resource_calendar_id.id or False,
87 mx.DateTime.strptime(lead.create_date, '%Y-%m-%d %H:%M:%S'),
92 date_until = mx.DateTime.strptime(date_until, '%Y-%m-%d %H:%M:%S')
93 for in_time, out_time in new_dates:
94 if in_time.date not in no_days:
95 no_days.append(in_time.date)
96 if out_time > date_until:
98 duration = len(no_days)
99 res[lead.id][field] = abs(int(duration))
103 # Overridden from res.partner.address:
104 'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null',
105 select=True, help="Optional linked partner, usually after conversion of the lead"),
108 'id': fields.integer('ID'),
109 'name': fields.char('Name', size=64),
110 'active': fields.boolean('Active', required=False),
111 'date_action_last': fields.datetime('Last Action', readonly=1),
112 'date_action_next': fields.datetime('Next Action', readonly=1),
113 'email_from': fields.char('Email', size=128, help="E-mail address of the contact"),
114 'section_id': fields.many2one('crm.case.section', 'Sales Team', \
115 select=True, help='Sales team to which this case belongs to. Defines responsible user and e-mail address for the mail gateway.'),
116 'create_date': fields.datetime('Creation Date' , readonly=True),
117 'email_cc': fields.text('Watchers Emails', 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"),
118 'description': fields.text('Notes'),
119 'write_date': fields.datetime('Update Date' , readonly=True),
122 'categ_id': fields.many2one('crm.case.categ', 'Category', \
123 domain="[('section_id','=',section_id),\
124 ('object_id.model', '=', 'crm.lead')]"),
125 'type_id': fields.many2one('crm.case.resource.type', 'Lead Type', \
126 domain="[('section_id','=',section_id),\
127 ('object_id.model', '=', 'crm.lead')]"),
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 res = super(crm_lead, self).case_open(cr, uid, ids, *args)
189 if old_state == 'draft':
190 stage_id = super(crm_lead, self).stage_next(cr, uid, ids, *args)
192 value = self.onchange_stage_id(cr, uid, ids, stage_id, context={})['value']
195 value.update({'date_open': time.strftime('%Y-%m-%d %H:%M:%S'), 'stage_id': stage_id})
196 self.write(cr, uid, ids, value)
198 for (id, name) in self.name_get(cr, uid, ids):
199 type = self.browse(cr, uid, id).type or 'Lead'
200 message = (_('The ') + type.title()) + " '" + name + "' "+ _("has been Opened.")
201 self.log(cr, uid, id, message)
204 def case_close(self, cr, uid, ids, *args):
205 """Overrides close for crm_case for setting close date
206 @param self: The object pointer
207 @param cr: the current row, from the database cursor,
208 @param uid: the current user’s ID for security checks,
209 @param ids: List of case Ids
210 @param *args: Tuple Value for additional Params
212 res = super(crm_lead, self).case_close(cr, uid, ids, args)
213 self.write(cr, uid, ids, {'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')})
214 for (id, name) in self.name_get(cr, uid, ids):
215 lead = self.browse(cr, uid, id)
216 if lead.type == 'lead':
217 message = _('The Lead') + " '" + name + "' "+ _("has been Closed.")
218 self.log(cr, uid, id, message)
221 def convert_opportunity(self, cr, uid, ids, context=None):
222 """ Precomputation for converting lead to opportunity
223 @param cr: the current row, from the database cursor,
224 @param uid: the current user’s ID for security checks,
225 @param ids: List of closeday’s IDs
226 @param context: A standard dictionary for contextual values
227 @return: Value of action in dict
231 context.update({'active_ids': ids})
233 data_obj = self.pool.get('ir.model.data')
234 data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_action')
239 view_id = data_obj.browse(cr, uid, data_id, context=context).res_id
241 for case in self.browse(cr, uid, ids):
242 context.update({'active_id': case.id})
243 if not case.partner_id:
244 data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_partner')
247 view_id1 = data_obj.browse(cr, uid, data_id, context=context).res_id
249 'name': _('Create Partner'),
251 'view_mode': 'form,tree',
252 'res_model': 'crm.lead2opportunity.partner',
255 'views': [(view_id1, 'form')],
256 'type': 'ir.actions.act_window',
263 'name': _('Create Opportunity'),
265 'view_mode': 'form,tree',
266 'res_model': 'crm.lead2opportunity.action',
269 'views': [(view_id, 'form')],
270 'type': 'ir.actions.act_window',
276 def stage_next(self, cr, uid, ids, context=None):
277 stage = super(crm_lead, self).stage_next(cr, uid, ids, context)
279 stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, stage, context=context)
280 if stage_obj.on_change:
281 data = {'probability': stage_obj.probability}
282 self.write(cr, uid, ids, data)
285 def message_new(self, cr, uid, msg, context):
287 Automatically calls when new email message arrives
289 @param self: The object pointer
290 @param cr: the current row, from the database cursor,
291 @param uid: the current user’s ID for security checks
294 mailgate_pool = self.pool.get('email.server.tools')
296 subject = msg.get('subject')
297 body = msg.get('body')
298 msg_from = msg.get('from')
299 priority = msg.get('priority')
303 'email_from': msg_from,
304 'email_cc': msg.get('cc'),
308 if msg.get('priority', False):
309 vals['priority'] = priority
311 res = mailgate_pool.get_partner(cr, uid, msg.get('from') or msg.get_unixfrom())
315 res = self.create(cr, uid, vals, context)
317 message = _('A Lead created') + " '" + subject + "' " + _("from Mailgate.")
318 self.log(cr, uid, res, message)
320 attachents = msg.get('attachments', [])
321 for attactment in attachents or []:
324 'datas':binascii.b2a_base64(str(attachents.get(attactment))),
325 'datas_fname': attactment,
326 'description': 'Mail attachment',
327 'res_model': self._name,
330 self.pool.get('ir.attachment').create(cr, uid, data_attach)
334 def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context={}):
336 @param self: The object pointer
337 @param cr: the current row, from the database cursor,
338 @param uid: the current user’s ID for security checks,
339 @param ids: List of update mail’s IDs
342 if isinstance(ids, (str, int, long)):
345 if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
346 vals['priority'] = msg.get('priority')
349 'cost':'planned_cost',
350 'revenue': 'planned_revenue',
351 'probability':'probability'
354 for line in msg['body'].split('\n'):
356 res = tools.misc.command_re.match(line)
357 if res and maps.get(res.group(1).lower()):
358 key = maps.get(res.group(1).lower())
359 vls[key] = res.group(2).lower()
362 # Unfortunately the API is based on lists
363 # but we want to update the state based on the
364 # previous state, so we have to loop:
365 for case in self.browse(cr, uid, ids, context=context):
367 if case.state in CRM_LEAD_PENDING_STATES:
368 values.update(state=crm.AVAILABLE_STATES[1][0]) #re-open
369 res = self.write(cr, uid, [case.id], values, context=context)
373 def msg_send(self, cr, uid, id, *args, **argv):
376 @param self: The object pointer
377 @param cr: the current row, from the database cursor,
378 @param uid: the current user’s ID for security checks,
379 @param ids: List of email’s IDs
380 @param *args: Return Tuple Value
381 @param **args: Return Dictionary of Keyword Value
386 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: