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('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"),
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),('section_id','=',False), ('object_id.model', '=', 'crm.project.bug')]"),
124 'type_id': fields.many2one('crm.case.resource.type', 'Campaign', \
125 domain="['|',('section_id','=',section_id),('section_id','=',False)]"),
126 'channel_id': fields.many2one('res.partner.canal', 'Channel'),
128 'contact_name': fields.char('Contact Name', size=64),
129 'partner_name': fields.char("Partner Name", size=64),
130 'optin': fields.boolean('Opt-In', help="If opt-in is checked, this contact has accepted to receive emails."),
131 'optout': fields.boolean('Opt-Out', help="If opt-out is checked, this contact has refused to receive emails or unsubscribed to a campaign."),
132 'type':fields.selection([
134 ('opportunity','Opportunity'),
136 ],'Type', help="Type is used to separate Leads and Opportunities"),
137 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
138 'date_closed': fields.datetime('Closed', readonly=True),
139 'stage_id': fields.many2one('crm.case.stage', 'Stage'),
140 'user_id': fields.many2one('res.users', 'Salesman',help='By Default Salesman is Administrator when create New User'),
141 'referred': fields.char('Referred By', size=64),
142 'date_open': fields.datetime('Opened', readonly=True),
143 'day_open': fields.function(_compute_day, string='Days to Open', \
144 method=True, multi='day_open', type="float", store=True),
145 'day_close': fields.function(_compute_day, string='Days to Close', \
146 method=True, multi='day_close', type="float", store=True),
147 'state': fields.selection(crm.AVAILABLE_STATES, 'State', size=16, readonly=True,
148 help='The state is set to \'Draft\', when a case is created.\
149 \nIf the case is in progress the state is set to \'Open\'.\
150 \nWhen the case is over, the state is set to \'Done\'.\
151 \nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
152 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
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 onchange_partner_address_id(self, cr, uid, ids, add, email=False):
167 """This function returns value of partner email based on Partner Address
168 @param self: The object pointer
169 @param cr: the current row, from the database cursor,
170 @param uid: the current user’s ID for security checks,
171 @param ids: List of case IDs
172 @param add: Id of Partner's address
173 @email: Partner's email ID
176 return {'value': {'email_from': False, 'country_id': False}}
177 address = self.pool.get('res.partner.address').browse(cr, uid, add)
178 return {'value': {'email_from': address.email, 'phone': address.phone, 'country_id': address.country_id.id}}
180 def case_open(self, cr, uid, ids, *args):
181 """Overrides cancel for crm_case for setting Open Date
182 @param self: The object pointer
183 @param cr: the current row, from the database cursor,
184 @param uid: the current user’s ID for security checks,
185 @param ids: List of case's Ids
186 @param *args: Give Tuple Value
188 old_state = self.read(cr, uid, ids, ['state'])[0]['state']
189 res = super(crm_lead, self).case_open(cr, uid, ids, *args)
190 if old_state == 'draft':
191 stage_id = super(crm_lead, self).stage_next(cr, uid, ids, *args)
193 value = self.onchange_stage_id(cr, uid, ids, stage_id, context={})['value']
196 value.update({'date_open': time.strftime('%Y-%m-%d %H:%M:%S'), 'stage_id': stage_id})
197 self.write(cr, uid, ids, value)
199 for (id, name) in self.name_get(cr, uid, ids):
200 type = self.browse(cr, uid, id).type or 'Lead'
201 message = (_('The ') + type.title()) + " '" + name + "' "+ _("has been Opened.")
202 self.log(cr, uid, id, message)
205 def case_close(self, cr, uid, ids, *args):
206 """Overrides close for crm_case for setting close date
207 @param self: The object pointer
208 @param cr: the current row, from the database cursor,
209 @param uid: the current user’s ID for security checks,
210 @param ids: List of case Ids
211 @param *args: Tuple Value for additional Params
213 res = super(crm_lead, self).case_close(cr, uid, ids, args)
214 self.write(cr, uid, ids, {'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')})
215 for (id, name) in self.name_get(cr, uid, ids):
216 lead = self.browse(cr, uid, id)
217 if lead.type == 'lead':
218 message = _('The Lead') + " '" + name + "' "+ _("has been 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_action')
240 view_id = data_obj.browse(cr, uid, data_id, context=context).res_id
242 for case in self.browse(cr, uid, ids):
243 context.update({'active_id': case.id})
244 if not case.partner_id:
245 data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_partner')
248 view_id1 = data_obj.browse(cr, uid, data_id, context=context).res_id
250 'name': _('Create Partner'),
252 'view_mode': 'form,tree',
253 'res_model': 'crm.lead2opportunity.partner',
256 'views': [(view_id1, 'form')],
257 'type': 'ir.actions.act_window',
264 'name': _('Create Opportunity'),
266 'view_mode': 'form,tree',
267 'res_model': 'crm.lead2opportunity.action',
270 'views': [(view_id, 'form')],
271 'type': 'ir.actions.act_window',
277 def stage_next(self, cr, uid, ids, context=None):
278 stage = super(crm_lead, self).stage_next(cr, uid, ids, context)
280 stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, stage, context=context)
281 if stage_obj.on_change:
282 data = {'probability': stage_obj.probability}
283 self.write(cr, uid, ids, data)
286 def message_new(self, cr, uid, msg, context):
288 Automatically calls when new email message arrives
290 @param self: The object pointer
291 @param cr: the current row, from the database cursor,
292 @param uid: the current user’s ID for security checks
295 mailgate_pool = self.pool.get('email.server.tools')
297 subject = msg.get('subject')
298 body = msg.get('body')
299 msg_from = msg.get('from')
300 priority = msg.get('priority')
304 'email_from': msg_from,
305 'email_cc': msg.get('cc'),
309 if msg.get('priority', False):
310 vals['priority'] = priority
312 res = mailgate_pool.get_partner(cr, uid, msg.get('from') or msg.get_unixfrom())
316 res = self.create(cr, uid, vals, context)
318 message = _('A Lead created') + " '" + subject + "' " + _("from Mailgate.")
319 self.log(cr, uid, res, message)
321 attachents = msg.get('attachments', [])
322 for attactment in attachents or []:
325 'datas':binascii.b2a_base64(str(attachents.get(attactment))),
326 'datas_fname': attactment,
327 'description': 'Mail attachment',
328 'res_model': self._name,
331 self.pool.get('ir.attachment').create(cr, uid, data_attach)
335 def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context={}):
337 @param self: The object pointer
338 @param cr: the current row, from the database cursor,
339 @param uid: the current user’s ID for security checks,
340 @param ids: List of update mail’s IDs
343 if isinstance(ids, (str, int, long)):
346 if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
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: