[MERGE]upstream
[odoo/odoo.git] / addons / base_status / base_stage.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-today OpenERP SA (<http://www.openerp.com>)
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21
22 from openerp.osv import fields, osv
23 from openerp.tools.translate import _
24
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)
36     """
37
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
41         """
42         if context is None:
43             context = {}
44         if context.get('portal'):
45             user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
46             return user.partner_id.id
47         return False
48
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
52         """
53         if context is None:
54             context = {}
55         if context.get('portal'):
56             user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
57             return user.email
58         return False        
59
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
63         """
64         if context is None:
65             context = {}
66         if not context or context.get('portal'):
67             return False
68         return uid
69
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
74         """
75         data = {'value': {'email_from': False, 'phone':False}}
76         if add:
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,
87                              }
88         fields = self.fields_get(cr, uid, context=context or {})
89         for key in data['value'].keys():
90             if key not in fields:
91                 del data['value'][key]
92         return data
93
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
98         """
99         data={}
100         if  part:
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}
104
105     def _get_default_section_id(self, cr, uid, context=None):
106         """ Gives default section """
107         return False
108
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)
112
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
120             from base_stage.
121
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
129         """
130         return False
131
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
135         """
136         if isinstance(cases, (int, long)):
137             cases = self.browse(cr, uid, cases, context=context)
138         for case in cases:
139             stage_id = self.stage_find(cr, uid, [case], None, [('state', '=', state_name)], context=context)
140             if stage_id:
141                 self.stage_set(cr, uid, [case.id], stage_id, context=context)
142         return True
143
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.
148         """
149         value = {}
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)
154
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')].
163         """
164         for case in self.browse(cr, uid, ids, context=context):
165             seq = 0
166             if case.stage_id:
167                 seq = case.stage_id.sequence or 0
168             section_id = None
169             next_stage_id = self.stage_find(cr, uid, [case], None, [('sequence', op, seq)],order, context=context)
170             if next_stage_id:
171                 return self.stage_set(cr, uid, [case.id], next_stage_id, context=context)
172         return False
173
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
177         """
178         return self.stage_change(cr, uid, ids, '>','sequence', context)
179
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
183         """
184         return self.stage_change(cr, uid, ids, '<', 'sequence desc', context)
185
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
189             exist."""
190         if default is None:
191             default = {}
192
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)
199
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
209             else:
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)
212         return True
213
214     def case_open(self, cr, uid, ids, context=None):
215         """ Opens case """
216         cases = self.browse(cr, uid, ids, context=context)
217         for case in cases:
218             data = {'active': True}
219             if not case.user_id:
220                 data['user_id'] = uid
221             self.case_set(cr, uid, [case.id], 'open', data, context=context)
222         return True
223
224     def case_close(self, cr, uid, ids, context=None):
225         """ Closes case """
226         return self.case_set(cr, uid, ids, 'done', {'active': True, 'date_closed': fields.datetime.now()}, context=context)
227
228     def case_cancel(self, cr, uid, ids, context=None):
229         """ Cancels case """
230         return self.case_set(cr, uid, ids, 'cancel', {'active': True}, context=context)
231
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)
235
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)
239
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
242             of the record.
243
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
251                      'draft' or 'close'.
252             :params update_values: values that will be added with the state
253                      update when writing values to the record.
254         """
255         cases = self.browse(cr, uid, ids, context=context)
256         # 1. update the stage
257         if new_state_name:
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)
261         # 2. update values
262         if values_to_update:
263             self.write(cr, uid, ids, values_to_update, context=context)
264         return True