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, orm
23 from datetime import datetime, timedelta
28 from tools.translate import _
29 from crm import crm_case
35 CRM_LEAD_PENDING_STATES = (
36 crm.AVAILABLE_STATES[2][0], # Cancelled
37 crm.AVAILABLE_STATES[3][0], # Done
38 crm.AVAILABLE_STATES[4][0], # Pending
41 class crm_lead(osv.osv, crm_case):
45 _order = "priority, id desc"
46 _inherit = ['mailgate.thread','res.partner.address']
47 def _compute_day(self, cr, uid, ids, fields, args, context={}):
49 @param cr: the current row, from the database cursor,
50 @param uid: the current user’s ID for security checks,
51 @param ids: List of Openday’s IDs
52 @return: difference between current date and log date
53 @param context: A standard dictionary for contextual values
55 cal_obj = self.pool.get('resource.calendar')
56 res_obj = self.pool.get('resource.resource')
59 for lead in self.browse(cr, uid, ids , context):
64 if field == 'day_open':
66 date_create = datetime.strptime(lead.create_date, "%Y-%m-%d %H:%M:%S")
67 date_open = datetime.strptime(lead.date_open, "%Y-%m-%d %H:%M:%S")
68 ans = date_open - date_create
69 date_until = lead.date_open
70 elif field == 'day_close':
72 date_create = datetime.strptime(lead.create_date, "%Y-%m-%d %H:%M:%S")
73 date_close = datetime.strptime(lead.date_closed, "%Y-%m-%d %H:%M:%S")
74 date_until = lead.date_closed
75 ans = date_close - date_create
79 resource_ids = res_obj.search(cr, uid, [('user_id','=',lead.user_id.id)])
81 resource_id = resource_ids[0]
83 duration = float(ans.days)
84 if lead.section_id and lead.section_id.resource_calendar_id:
85 duration = float(ans.days) * 24
86 new_dates = cal_obj.interval_get(cr,
88 lead.section_id.resource_calendar_id and lead.section_id.resource_calendar_id.id or False,
89 mx.DateTime.strptime(lead.create_date, '%Y-%m-%d %H:%M:%S'),
94 date_until = mx.DateTime.strptime(date_until, '%Y-%m-%d %H:%M:%S')
95 for in_time, out_time in new_dates:
96 if in_time.date not in no_days:
97 no_days.append(in_time.date)
98 if out_time > date_until:
100 duration = len(no_days)
101 res[lead.id][field] = abs(int(duration))
105 # Overridden from res.partner.address:
106 'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null',
107 select=True, help="Optional linked partner, usually after conversion of the lead"),
110 'name': fields.char('Name', size=64),
111 'active': fields.boolean('Active', required=False),
112 'date_action_last': fields.datetime('Last Action', readonly=1),
113 'date_action_next': fields.datetime('Next Action', readonly=1),
114 'email_from': fields.char('Email', size=128, help="E-mail address of the contact"),
115 'section_id': fields.many2one('crm.case.section', 'Sales Team', \
116 select=True, help='Sales team to which this case belongs to. Defines responsible user and e-mail address for the mail gateway.'),
117 'create_date': fields.datetime('Creation Date' , readonly=True),
118 'email_cc': fields.text('Watchers Emails', size=252 , help="These \
119 addresses(Comma-separated) will receive a copy of the future e-mail communication between partner \
121 'description': fields.text('Notes'),
122 'write_date': fields.datetime('Update Date' , readonly=True),
125 'categ_id': fields.many2one('crm.case.categ', 'Lead Source', \
126 domain="[('section_id','=',section_id),\
127 ('object_id.model', '=', 'crm.lead')]"),
128 'type_id': fields.many2one('crm.case.resource.type', 'Lead Type', \
129 domain="[('section_id','=',section_id),\
130 ('object_id.model', '=', 'crm.lead')]"),
131 'partner_name': fields.char("Partner Name", size=64),
132 'optin': fields.boolean('Opt-In'),
133 'optout': fields.boolean('Opt-Out'),
134 'type':fields.selection([
136 ('opportunity','Opportunity'),
138 ],'Type', help="Type is used to separate Leads and Opportunities"),
139 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
140 'date_closed': fields.datetime('Closed', readonly=True),
141 'stage_id': fields.many2one('crm.case.stage', 'Stage'),
142 'user_id': fields.many2one('res.users', 'Salesman',help='By Default Salesman is Administrator when create New User'),
143 'referred': fields.char('Referred By', size=64),
144 'date_open': fields.datetime('Opened', readonly=True),
145 'day_open': fields.function(_compute_day, string='Days to Open', \
146 method=True, multi='day_open', type="float", store=True),
147 'day_close': fields.function(_compute_day, string='Days to Close', \
148 method=True, multi='day_close', type="float", store=True),
149 'state': fields.selection(crm.AVAILABLE_STATES, 'State', size=16, readonly=True,
150 help='The state is set to \'Draft\', when a case is created.\
151 \nIf the case is in progress the state is set to \'Open\'.\
152 \nWhen the case is over, the state is set to \'Done\'.\
153 \nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
154 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)], readonly=True),
155 'partner_assigned_id': fields.many2one('res.partner', 'Assigned Partner', help="Partner this case has been forwarded/assigned to.", select=True),
156 'date_assign': fields.date('Assignation Date', help="Last date this case was forwarded/assigned to a partner"),
160 'active': lambda *a: 1,
161 'user_id': crm_case._get_default_user,
162 'email_from': crm_case._get_default_email,
163 'state': lambda *a: 'draft',
164 'type': lambda *a: 'lead',
165 'section_id': crm_case._get_section,
166 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c),
167 'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
170 def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
171 """This function returns value of partner email based on Partner Address
172 @param self: The object pointer
173 @param cr: the current row, from the database cursor,
174 @param uid: the current user’s ID for security checks,
175 @param ids: List of case IDs
176 @param add: Id of Partner's address
177 @email: Partner's email ID
180 return {'value': {'email_from': False, 'country_id': False}}
181 address = self.pool.get('res.partner.address').browse(cr, uid, add)
182 return {'value': {'email_from': address.email, 'phone': address.phone, 'country_id': address.country_id.id}}
184 def case_open(self, cr, uid, ids, *args):
185 """Overrides cancel for crm_case for setting Open Date
186 @param self: The object pointer
187 @param cr: the current row, from the database cursor,
188 @param uid: the current user’s ID for security checks,
189 @param ids: List of case's Ids
190 @param *args: Give Tuple Value
192 old_state = self.read(cr, uid, ids, ['state'])[0]['state']
193 res = super(crm_lead, self).case_open(cr, uid, ids, *args)
194 if old_state == 'draft':
195 stage_id = super(crm_lead, self).stage_next(cr, uid, ids, *args)
197 raise osv.except_osv(_('Warning !'), _('There is no stage defined for this Sale Team.'))
198 value = self.onchange_stage_id(cr, uid, ids, stage_id, context={})['value']
199 value.update({'date_open': time.strftime('%Y-%m-%d %H:%M:%S'), 'stage_id': stage_id})
200 self.write(cr, uid, ids, value)
202 for (id, name) in self.name_get(cr, uid, ids):
203 message = _('The Lead') + " '" + name + "' "+ _("has been written as Open.")
204 self.log(cr, uid, id, message)
207 def case_close(self, cr, uid, ids, *args):
208 """Overrides close for crm_case for setting close date
209 @param self: The object pointer
210 @param cr: the current row, from the database cursor,
211 @param uid: the current user’s ID for security checks,
212 @param ids: List of case Ids
213 @param *args: Tuple Value for additional Params
215 res = super(crm_lead, self).case_close(cr, uid, ids, args)
216 self.write(cr, uid, ids, {'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')})
217 for (id, name) in self.name_get(cr, uid, ids):
218 message = _('The Lead') + " '" + name + "' "+ _("has been written as Closed.")
219 self.log(cr, uid, id, message)
222 def convert_opportunity(self, cr, uid, ids, context=None):
223 """ Precomputation for converting lead to opportunity
224 @param cr: the current row, from the database cursor,
225 @param uid: the current user’s ID for security checks,
226 @param ids: List of closeday’s IDs
227 @param context: A standard dictionary for contextual values
228 @return: Value of action in dict
232 context.update({'active_ids': ids})
234 data_obj = self.pool.get('ir.model.data')
235 data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_create')
240 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',
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 msg_from = msg['from']
346 if msg.get('priority'):
347 vals['priority'] = msg.get('priority')
350 'cost':'planned_cost',
351 'revenue': 'planned_revenue',
352 'probability':'probability'
355 for line in msg['body'].split('\n'):
357 res = tools.misc.command_re.match(line)
358 if res and maps.get(res.group(1).lower()):
359 key = maps.get(res.group(1).lower())
360 vls[key] = res.group(2).lower()
363 # Unfortunately the API is based on lists
364 # but we want to update the state based on the
365 # previous state, so we have to loop:
366 for case in self.browse(cr, uid, ids, context=context):
368 if case.state in CRM_LEAD_PENDING_STATES:
369 values.update(state=crm.AVAILABLE_STATES[1][0]) #re-open
370 res = self.write(cr, uid, [case.id], values, context=context)
374 def msg_send(self, cr, uid, id, *args, **argv):
377 @param self: The object pointer
378 @param cr: the current row, from the database cursor,
379 @param uid: the current user’s ID for security checks,
380 @param ids: List of email’s IDs
381 @param *args: Return Tuple Value
382 @param **args: Return Dictionary of Keyword Value
387 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: