[IMP]account_analytic_analysis: set ids intes of id for write bcoz in project there...
[odoo/odoo.git] / addons / base_action_rule / base_action_rule.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
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 datetime import datetime, timedelta
23 import time
24 import logging
25
26 from openerp import SUPERUSER_ID
27 from openerp.osv import fields, osv
28 from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
29
30 _logger = logging.getLogger(__name__)
31
32 DATE_RANGE_FUNCTION = {
33     'minutes': lambda interval: timedelta(minutes=interval),
34     'hour': lambda interval: timedelta(hours=interval),
35     'day': lambda interval: timedelta(days=interval),
36     'month': lambda interval: timedelta(months=interval),
37     False: lambda interval: timedelta(0),
38 }
39
40 def get_datetime(date_str):
41     '''Return a datetime from a date string or a datetime string'''
42     # complete date time if date_str contains only a date
43     if ' ' not in date_str:
44         date_str = date_str + " 00:00:00"
45     return datetime.strptime(date_str, DEFAULT_SERVER_DATETIME_FORMAT)
46
47
48 class base_action_rule(osv.osv):
49     """ Base Action Rules """
50
51     _name = 'base.action.rule'
52     _description = 'Action Rules'
53
54     _columns = {
55         'name':  fields.char('Rule Name', size=64, required=True),
56         'model_id': fields.many2one('ir.model', 'Related Document Model',
57             required=True, domain=[('osv_memory', '=', False)]),
58         'model': fields.related('model_id', 'model', type="char", size=256, string='Model'),
59         'create_date': fields.datetime('Create Date', readonly=1),
60         'active': fields.boolean('Active',
61             help="When unchecked, the rule is hidden and will not be executed."),
62         'sequence': fields.integer('Sequence',
63             help="Gives the sequence order when displaying a list of rules."),
64         'trg_date_id': fields.many2one('ir.model.fields', string='Trigger Date',
65             domain="[('model_id', '=', model_id), ('ttype', 'in', ('date', 'datetime'))]"),
66         'trg_date_range': fields.integer('Delay after trigger date',
67             help="Delay after the trigger date." \
68             "You can put a negative number if you need a delay before the" \
69             "trigger date, like sending a reminder 15 minutes before a meeting."),
70         'trg_date_range_type': fields.selection([('minutes', 'Minutes'), ('hour', 'Hours'),
71                                 ('day', 'Days'), ('month', 'Months')], 'Delay type'),
72         'act_user_id': fields.many2one('res.users', 'Set Responsible'),
73         'act_followers': fields.many2many("res.partner", string="Add Followers"),
74         'server_action_ids': fields.many2many('ir.actions.server', string='Server Actions',
75             domain="[('model_id', '=', model_id)]",
76             help="Examples: email reminders, call object service, etc."),
77         'filter_pre_id': fields.many2one('ir.filters', string='Before Update Filter',
78             ondelete='restrict',
79             domain="[('model_id', '=', model_id.model)]",
80             help="If present, this condition must be satisfied before the update of the record."),
81         'filter_id': fields.many2one('ir.filters', string='After Update Filter',
82             ondelete='restrict',
83             domain="[('model_id', '=', model_id.model)]",
84             help="If present, this condition must be satisfied after the update of the record."),
85         'last_run': fields.datetime('Last Run', readonly=1),
86     }
87
88     _defaults = {
89         'active': True,
90         'trg_date_range_type': 'day',
91     }
92
93     _order = 'sequence'
94
95     def _filter(self, cr, uid, action, action_filter, record_ids, context=None):
96         """ filter the list record_ids that satisfy the action filter """
97         if record_ids and action_filter:
98             assert action.model == action_filter.model_id, "Filter model different from action rule model"
99             model = self.pool.get(action_filter.model_id)
100             domain = [('id', 'in', record_ids)] + eval(action_filter.domain)
101             ctx = dict(context or {})
102             ctx.update(eval(action_filter.context))
103             record_ids = model.search(cr, uid, domain, context=ctx)
104         return record_ids
105
106     def _process(self, cr, uid, action, record_ids, context=None):
107         """ process the given action on the records """
108         # execute server actions
109         model = self.pool.get(action.model_id.model)
110         if action.server_action_ids:
111             server_action_ids = map(int, action.server_action_ids)
112             for record in model.browse(cr, uid, record_ids, context):
113                 action_server_obj = self.pool.get('ir.actions.server')
114                 ctx = dict(context, active_model=model._name, active_ids=[record.id], active_id=record.id)
115                 action_server_obj.run(cr, uid, server_action_ids, context=ctx)
116
117         # modify records
118         values = {}
119         if 'date_action_last' in model._all_columns:
120             values['date_action_last'] = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
121         if action.act_user_id and 'user_id' in model._all_columns:
122             values['user_id'] = action.act_user_id.id
123         if values:
124             model.write(cr, uid, record_ids, values, context=context)
125
126         if action.act_followers and hasattr(model, 'message_subscribe'):
127             follower_ids = map(int, action.act_followers)
128             model.message_subscribe(cr, uid, record_ids, follower_ids, context=context)
129
130         return True
131
132     def _wrap_create(self, old_create, model):
133         """ Return a wrapper around `old_create` calling both `old_create` and
134             `_process`, in that order.
135         """
136         def wrapper(cr, uid, vals, context=None):
137             # avoid loops or cascading actions
138             if context and context.get('action'):
139                 return old_create(cr, uid, vals, context=context)
140
141             context = dict(context or {}, action=True)
142             new_id = old_create(cr, uid, vals, context=context)
143
144             # as it is a new record, we do not consider the actions that have a prefilter
145             action_dom = [('model', '=', model), ('trg_date_id', '=', False), ('filter_pre_id', '=', False)]
146             action_ids = self.search(cr, uid, action_dom, context=context)
147
148             # check postconditions, and execute actions on the records that satisfy them
149             for action in self.browse(cr, uid, action_ids, context=context):
150                 if self._filter(cr, uid, action, action.filter_id, [new_id], context=context):
151                     self._process(cr, uid, action, [new_id], context=context)
152             return new_id
153
154         return wrapper
155
156     def _wrap_write(self, old_write, model):
157         """ Return a wrapper around `old_write` calling both `old_write` and
158             `_process`, in that order.
159         """
160         def wrapper(cr, uid, ids, vals, context=None):
161             # avoid loops or cascading actions
162             if context and context.get('action'):
163                 return old_write(cr, uid, ids, vals, context=context)
164
165             context = dict(context or {}, action=True)
166             ids = [ids] if isinstance(ids, (int, long, str)) else ids
167
168             # retrieve the action rules to possibly execute
169             action_dom = [('model', '=', model), ('trg_date_id', '=', False)]
170             action_ids = self.search(cr, uid, action_dom, context=context)
171             actions = self.browse(cr, uid, action_ids, context=context)
172
173             # check preconditions
174             pre_ids = {}
175             for action in actions:
176                 pre_ids[action] = self._filter(cr, uid, action, action.filter_pre_id, ids, context=context)
177
178             # execute write
179             old_write(cr, uid, ids, vals, context=context)
180
181             # check postconditions, and execute actions on the records that satisfy them
182             for action in actions:
183                 post_ids = self._filter(cr, uid, action, action.filter_id, pre_ids[action], context=context)
184                 if post_ids:
185                     self._process(cr, uid, action, post_ids, context=context)
186             return True
187
188         return wrapper
189
190     def _register_hook(self, cr, ids=None):
191         """ Wrap the methods `create` and `write` of the models specified by
192             the rules given by `ids` (or all existing rules if `ids` is `None`.)
193         """
194         if ids is None:
195             ids = self.search(cr, SUPERUSER_ID, [])
196         for action_rule in self.browse(cr, SUPERUSER_ID, ids):
197             model = action_rule.model_id.model
198             model_obj = self.pool.get(model)
199             if not hasattr(model_obj, 'base_action_ruled'):
200                 model_obj.create = self._wrap_create(model_obj.create, model)
201                 model_obj.write = self._wrap_write(model_obj.write, model)
202                 model_obj.base_action_ruled = True
203         return True
204
205     def create(self, cr, uid, vals, context=None):
206         res_id = super(base_action_rule, self).create(cr, uid, vals, context=context)
207         self._register_hook(cr, [res_id])
208         return res_id
209
210     def write(self, cr, uid, ids, vals, context=None):
211         if isinstance(ids, (int, long)):
212             ids = [ids]
213         super(base_action_rule, self).write(cr, uid, ids, vals, context=context)
214         self._register_hook(cr, ids)
215         return True
216
217     def _check(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
218         """ This Function is called by scheduler. """
219         context = context or {}
220         # retrieve all the action rules that have a trg_date_id and no precondition
221         action_dom = [('trg_date_id', '!=', False), ('filter_pre_id', '=', False)]
222         action_ids = self.search(cr, uid, action_dom, context=context)
223         for action in self.browse(cr, uid, action_ids, context=context):
224             now = datetime.now()
225             last_run = get_datetime(action.last_run) if action.last_run else False
226
227             # retrieve all the records that satisfy the action's condition
228             model = self.pool.get(action.model_id.model)
229             domain = []
230             ctx = dict(context)
231             if action.filter_id:
232                 domain = eval(action.filter_id.domain)
233                 ctx.update(eval(action.filter_id.context))
234             record_ids = model.search(cr, uid, domain, context=ctx)
235
236             # determine when action should occur for the records
237             date_field = action.trg_date_id.name
238             if date_field == 'date_action_last' and 'create_date' in model._all_columns:
239                 get_record_dt = lambda record: record[date_field] or record.create_date
240             else:
241                 get_record_dt = lambda record: record[date_field]
242
243             delay = DATE_RANGE_FUNCTION[action.trg_date_range_type](action.trg_date_range)
244
245             # process action on the records that should be executed
246             for record in model.browse(cr, uid, record_ids, context=context):
247                 record_dt = get_record_dt(record)
248                 if not record_dt:
249                     continue
250                 action_dt = get_datetime(record_dt) + delay
251                 if last_run and (last_run <= action_dt < now) or (action_dt < now):
252                     try:
253                         self._process(cr, uid, action, [record.id], context=context)
254                     except Exception:
255                         import traceback
256                         _logger.error(traceback.format_exc())
257
258             action.write({'last_run': now.strftime(DEFAULT_SERVER_DATETIME_FORMAT)})