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(osv.osv, crm_case):
43 _order = "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 '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('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"),
117 'description': fields.text('Notes'),
118 'write_date': fields.datetime('Update Date' , readonly=True),
121 'categ_id': fields.many2one('crm.case.categ', 'Lead Source', \
122 domain="[('section_id','=',section_id),\
123 ('object_id.model', '=', 'crm.lead')]"),
124 'type_id': fields.many2one('crm.case.resource.type', 'Lead Type', \
125 domain="[('section_id','=',section_id),\
126 ('object_id.model', '=', 'crm.lead')]"),
127 'partner_name': fields.char("Partner Name", size=64),
128 'optin': fields.boolean('Opt-In'),
129 'optout': fields.boolean('Opt-Out'),
130 'type':fields.selection([
132 ('opportunity','Opportunity'),
134 ],'Type', help="Type is used to separate Leads and Opportunities"),
135 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
136 'date_closed': fields.datetime('Closed', readonly=True),
137 'stage_id': fields.many2one('crm.case.stage', 'Stage'),
138 'user_id': fields.many2one('res.users', 'Salesman',help='By Default Salesman is Administrator when create New User'),
139 'referred': fields.char('Referred By', size=64),
140 'date_open': fields.datetime('Opened', readonly=True),
141 'day_open': fields.function(_compute_day, string='Days to Open', \
142 method=True, multi='day_open', type="float", store=True),
143 'day_close': fields.function(_compute_day, string='Days to Close', \
144 method=True, multi='day_close', type="float", store=True),
145 'state': fields.selection(crm.AVAILABLE_STATES, 'State', size=16, readonly=True,
146 help='The state is set to \'Draft\', when a case is created.\
147 \nIf the case is in progress the state is set to \'Open\'.\
148 \nWhen the case is over, the state is set to \'Done\'.\
149 \nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
150 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)], readonly=True),
151 'partner_assigned_id': fields.many2one('res.partner', 'Assigned Partner', help="Partner this case has been forwarded/assigned to.", select=True),
152 'date_assign': fields.date('Assignation Date', help="Last date this case was forwarded/assigned to a partner"),
156 'active': lambda *a: 1,
157 'user_id': crm_case._get_default_user,
158 'email_from': crm_case._get_default_email,
159 'state': lambda *a: 'draft',
160 'type': lambda *a: 'lead',
161 'section_id': crm_case._get_section,
162 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c),
163 'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
166 def create(self, cr, uid, vals, context=None):
167 # FIXME: Do we really need this? if yes, this is not a correct way to check for stage
168 # if not vals.get('stage_id',False):
169 # raise osv.except_osv('Error', _('There is no stage defined for this Sales Team'))
170 return super(crm_lead, self).create(cr, uid, vals, context=context)
172 def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
173 """This function returns value of partner email based on Partner Address
174 @param self: The object pointer
175 @param cr: the current row, from the database cursor,
176 @param uid: the current user’s ID for security checks,
177 @param ids: List of case IDs
178 @param add: Id of Partner's address
179 @email: Partner's email ID
182 return {'value': {'email_from': False, 'country_id': False}}
183 address = self.pool.get('res.partner.address').browse(cr, uid, add)
184 return {'value': {'email_from': address.email, 'phone': address.phone, 'country_id': address.country_id.id}}
186 def case_open(self, cr, uid, ids, *args):
187 """Overrides cancel for crm_case for setting Open Date
188 @param self: The object pointer
189 @param cr: the current row, from the database cursor,
190 @param uid: the current user’s ID for security checks,
191 @param ids: List of case's Ids
192 @param *args: Give Tuple Value
194 old_state = self.read(cr, uid, ids, ['state'])[0]['state']
195 res = super(crm_lead, self).case_open(cr, uid, ids, *args)
196 if old_state == 'draft':
197 stage_id = super(crm_lead, self).stage_next(cr, uid, ids, *args)
199 raise osv.except_osv(_('Warning !'), _('There is no stage defined for this Sale Team.'))
200 value = self.onchange_stage_id(cr, uid, ids, stage_id, context={})['value']
201 value.update({'date_open': time.strftime('%Y-%m-%d %H:%M:%S'), 'stage_id': stage_id})
202 self.write(cr, uid, ids, value)
204 for (id, name) in self.name_get(cr, uid, ids):
205 type = self.browse(cr, uid, id).type or 'Lead'
206 message = (_('The ') + type.title()) + " '" + name + "' "+ _("has been Opened.")
207 self.log(cr, uid, id, message)
210 def case_close(self, cr, uid, ids, *args):
211 """Overrides close for crm_case for setting close date
212 @param self: The object pointer
213 @param cr: the current row, from the database cursor,
214 @param uid: the current user’s ID for security checks,
215 @param ids: List of case Ids
216 @param *args: Tuple Value for additional Params
218 res = super(crm_lead, self).case_close(cr, uid, ids, args)
219 self.write(cr, uid, ids, {'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')})
220 for (id, name) in self.name_get(cr, uid, ids):
221 lead = self.browse(cr, uid, id)
222 if lead.type == 'lead':
223 message = _('The Lead') + " '" + name + "' "+ _("has been Closed.")
224 self.log(cr, uid, id, message)
227 def convert_opportunity(self, cr, uid, ids, context=None):
228 """ Precomputation for converting lead to opportunity
229 @param cr: the current row, from the database cursor,
230 @param uid: the current user’s ID for security checks,
231 @param ids: List of closeday’s IDs
232 @param context: A standard dictionary for contextual values
233 @return: Value of action in dict
237 context.update({'active_ids': ids})
239 data_obj = self.pool.get('ir.model.data')
240 data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_action')
245 view_id = data_obj.browse(cr, uid, data_id, context=context).res_id
247 for case in self.browse(cr, uid, ids):
248 context.update({'active_id': case.id})
249 if not case.partner_id:
250 data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_partner')
253 view_id1 = data_obj.browse(cr, uid, data_id, context=context).res_id
255 'name': _('Create Partner'),
257 'view_mode': 'form,tree',
258 'res_model': 'crm.lead2opportunity.partner',
261 'views': [(view_id1, 'form')],
262 'type': 'ir.actions.act_window',
269 'name': _('Create Opportunity'),
271 'view_mode': 'form,tree',
272 'res_model': 'crm.lead2opportunity.action',
275 'views': [(view_id, 'form')],
276 'type': 'ir.actions.act_window',
282 def stage_next(self, cr, uid, ids, context=None):
283 stage = super(crm_lead, self).stage_next(cr, uid, ids, context)
285 stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, stage, context=context)
286 if stage_obj.on_change:
287 data = {'probability': stage_obj.probability}
288 self.write(cr, uid, ids, data)
291 def message_new(self, cr, uid, msg, context):
293 Automatically calls when new email message arrives
295 @param self: The object pointer
296 @param cr: the current row, from the database cursor,
297 @param uid: the current user’s ID for security checks
300 mailgate_pool = self.pool.get('email.server.tools')
302 subject = msg.get('subject')
303 body = msg.get('body')
304 msg_from = msg.get('from')
305 priority = msg.get('priority')
309 'email_from': msg_from,
310 'email_cc': msg.get('cc'),
314 if msg.get('priority', False):
315 vals['priority'] = priority
317 res = mailgate_pool.get_partner(cr, uid, msg.get('from') or msg.get_unixfrom())
321 res = self.create(cr, uid, vals, context)
323 message = _('A Lead created') + " '" + subject + "' " + _("from Mailgate.")
324 self.log(cr, uid, res, message)
326 attachents = msg.get('attachments', [])
327 for attactment in attachents or []:
330 'datas':binascii.b2a_base64(str(attachents.get(attactment))),
331 'datas_fname': attactment,
332 'description': 'Mail attachment',
333 'res_model': self._name,
336 self.pool.get('ir.attachment').create(cr, uid, data_attach)
340 def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context={}):
342 @param self: The object pointer
343 @param cr: the current row, from the database cursor,
344 @param uid: the current user’s ID for security checks,
345 @param ids: List of update mail’s IDs
348 if isinstance(ids, (str, int, long)):
351 if msg.get('priority'):
352 vals['priority'] = msg.get('priority')
355 'cost':'planned_cost',
356 'revenue': 'planned_revenue',
357 'probability':'probability'
360 for line in msg['body'].split('\n'):
362 res = tools.misc.command_re.match(line)
363 if res and maps.get(res.group(1).lower()):
364 key = maps.get(res.group(1).lower())
365 vls[key] = res.group(2).lower()
368 # Unfortunately the API is based on lists
369 # but we want to update the state based on the
370 # previous state, so we have to loop:
371 for case in self.browse(cr, uid, ids, context=context):
373 if case.state in CRM_LEAD_PENDING_STATES:
374 values.update(state=crm.AVAILABLE_STATES[1][0]) #re-open
375 res = self.write(cr, uid, [case.id], values, context=context)
379 def msg_send(self, cr, uid, id, *args, **argv):
382 @param self: The object pointer
383 @param cr: the current row, from the database cursor,
384 @param uid: the current user’s ID for security checks,
385 @param ids: List of email’s IDs
386 @param *args: Return Tuple Value
387 @param **args: Return Dictionary of Keyword Value
392 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: