1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-today OpenERP SA (<http://www.openerp.com>)
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 openerp.osv import fields, osv
23 from openerp.tools.translate import _
25 class base_stage(object):
26 """ Base utility mixin class for objects willing to manage their stages.
27 Object that inherit from this class should inherit from mailgate.thread
28 to have access to the mail gateway, as well as Chatter. Objects
29 subclassing this class should define the following colums:
30 - ``date_open`` (datetime field)
31 - ``date_closed`` (datetime field)
32 - ``user_id`` (many2one to res.users)
33 - ``partner_id`` (many2one to res.partner)
34 - ``stage_id`` (many2one to a stage definition model)
35 - ``state`` (selection field, related to the stage_id.state)
38 def _get_default_partner(self, cr, uid, context=None):
39 """ Gives id of partner for current user
40 :param context: if portal not in context returns False
44 if context.get('portal'):
45 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
46 return user.partner_id.id
49 def _get_default_email(self, cr, uid, context=None):
50 """ Gives default email address for current user
51 :param context: if portal not in context returns False
55 if context.get('portal'):
56 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
60 def _get_default_user(self, cr, uid, context=None):
61 """ Gives current user id
62 :param context: if portal not in context returns False
66 if not context or context.get('portal'):
70 def onchange_partner_address_id(self, cr, uid, ids, add, email=False, context=None):
71 """ This function returns value of partner email based on Partner Address
72 :param add: Id of Partner's address
73 :param email: Partner's email ID
75 data = {'value': {'email_from': False, 'phone':False}}
77 address = self.pool.get('res.partner').browse(cr, uid, add)
78 data['value'] = {'partner_name': address and address.name or False,
79 'email_from': address and address.email or False,
80 'phone': address and address.phone or False,
81 'street': address and address.street or False,
82 'street2': address and address.street2 or False,
83 'city': address and address.city or False,
84 'state_id': address.state_id and address.state_id.id or False,
85 'zip': address and address.zip or False,
86 'country_id': address.country_id and address.country_id.id or False,
88 fields = self.fields_get(cr, uid, context=context or {})
89 for key in data['value'].keys():
91 del data['value'][key]
94 def onchange_partner_id(self, cr, uid, ids, part, email=False):
95 """ This function returns value of partner address based on partner
96 :param part: Partner's id
97 :param email: Partner's email ID
101 addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['contact'])
102 data.update(self.onchange_partner_address_id(cr, uid, ids, addr['contact'])['value'])
103 return {'value': data}
105 def _get_default_section_id(self, cr, uid, context=None):
106 """ Gives default section """
109 def _get_default_stage_id(self, cr, uid, context=None):
110 """ Gives default stage_id """
111 return self.stage_find(cr, uid, [], None, [('state', '=', 'draft')], context=context)
113 def stage_find(self, cr, uid, cases, section_id, domain=[], order='sequence', context=None):
114 """ Find stage, with a given (optional) domain on the search,
115 ordered by the order parameter. If several stages match the
116 search criterions, the first one will be returned, according
117 to the requested search order.
118 This method is meant to be overriden by subclasses. That way
119 specific behaviors can be achieved for every class inheriting
122 :param cases: browse_record of cases
123 :param section_id: section limitating the search, given for
124 a generic search (for example default search).
125 A section models concepts such as Sales team
126 (for CRM), ou departments (for HR).
127 :param domain: a domain on the search of stages
128 :param order: order of the search
132 def stage_set_with_state_name(self, cr, uid, cases, state_name, context=None):
133 """ Set a new stage, with a state_name instead of a stage_id
134 :param cases: browse_record of cases
136 if isinstance(cases, (int, long)):
137 cases = self.browse(cr, uid, cases, context=context)
139 stage_id = self.stage_find(cr, uid, [case], None, [('state', '=', state_name)], context=context)
141 self.stage_set(cr, uid, [case.id], stage_id, context=context)
144 def stage_set(self, cr, uid, ids, stage_id, context=None):
145 """ Set the new stage. This methods is the right method to call
146 when changing states. It also checks whether an onchange is
147 defined, and execute it.
150 if hasattr(self, 'onchange_stage_id'):
151 value = self.onchange_stage_id(cr, uid, ids, stage_id, context=context)['value']
152 value['stage_id'] = stage_id
153 return self.write(cr, uid, ids, value, context=context)
155 def stage_change(self, cr, uid, ids, op, order, context=None):
156 """ Change the stage and take the next one, based on a condition
157 writen for the 'sequence' field and an operator. This methods
158 checks whether the case has a current stage, and takes its
159 sequence. Otherwise, a default 0 sequence is chosen and this
160 method will therefore choose the first available stage.
161 For example if op is '>' and current stage has a sequence of
162 10, this will call stage_find, with [('sequence', '>', '10')].
164 for case in self.browse(cr, uid, ids, context=context):
167 seq = case.stage_id.sequence or 0
169 next_stage_id = self.stage_find(cr, uid, [case], None, [('sequence', op, seq)],order, context=context)
171 return self.stage_set(cr, uid, [case.id], next_stage_id, context=context)
174 def stage_next(self, cr, uid, ids, context=None):
175 """ This function computes next stage for case from its current stage
176 using available stage for that case type
178 return self.stage_change(cr, uid, ids, '>','sequence', context)
180 def stage_previous(self, cr, uid, ids, context=None):
181 """ This function computes previous stage for case from its current
182 stage using available stage for that case type
184 return self.stage_change(cr, uid, ids, '<', 'sequence desc', context)
186 def copy(self, cr, uid, id, default=None, context=None):
187 """ Overrides orm copy method to avoid copying messages,
188 as well as date_closed and date_open columns if they
193 if hasattr(self, '_columns'):
194 if self._columns.get('date_closed'):
195 default.update({ 'date_closed': False, })
196 if self._columns.get('date_open'):
197 default.update({ 'date_open': False })
198 return super(base_stage, self).copy(cr, uid, id, default, context=context)
200 def case_escalate(self, cr, uid, ids, context=None):
201 """ Escalates case to parent level """
202 for case in self.browse(cr, uid, ids, context=context):
203 data = {'active': True}
204 if case.section_id.parent_id:
205 data['section_id'] = case.section_id.parent_id.id
206 if case.section_id.parent_id.change_responsible:
207 if case.section_id.parent_id.user_id:
208 data['user_id'] = case.section_id.parent_id.user_id.id
210 raise osv.except_osv(_('Error!'), _("You are already at the top level of your sales-team category.\nTherefore you cannot escalate furthermore."))
211 self.write(cr, uid, [case.id], data, context=context)
214 def case_open(self, cr, uid, ids, context=None):
216 cases = self.browse(cr, uid, ids, context=context)
218 data = {'active': True}
220 data['user_id'] = uid
221 self.case_set(cr, uid, [case.id], 'open', data, context=context)
224 def case_close(self, cr, uid, ids, context=None):
226 return self.case_set(cr, uid, ids, 'done', {'active': True, 'date_closed': fields.datetime.now()}, context=context)
228 def case_cancel(self, cr, uid, ids, context=None):
230 return self.case_set(cr, uid, ids, 'cancel', {'active': True}, context=context)
232 def case_pending(self, cr, uid, ids, context=None):
233 """ Set case as pending """
234 return self.case_set(cr, uid, ids, 'pending', {'active': True}, context=context)
236 def case_reset(self, cr, uid, ids, context=None):
237 """ Resets case as draft """
238 return self.case_set(cr, uid, ids, 'draft', {'active': True}, context=context)
240 def case_set(self, cr, uid, ids, new_state_name=None, values_to_update=None, new_stage_id=None, context=None):
241 """ Generic method for setting case. This methods wraps the update
244 :params new_state_name: the new state of the record; this method
245 will call ``stage_set_with_state_name``
246 that will find the stage matching the
247 new state, using the ``stage_find`` method.
248 :params new_stage_id: alternatively, you may directly give the
249 new stage of the record
250 :params state_name: the new value of the state, such as
252 :params update_values: values that will be added with the state
253 update when writing values to the record.
255 cases = self.browse(cr, uid, ids, context=context)
256 # 1. update the stage
258 self.stage_set_with_state_name(cr, uid, cases, new_state_name, context=context)
259 elif not (new_stage_id is None):
260 self.stage_set(cr, uid, ids, new_stage_id, context=context)
263 self.write(cr, uid, ids, values_to_update, context=context)