[FIX] tools.convert: use tools.ustr() instead of str() on exceptions.
[odoo/odoo.git] / addons / procurement / schedulers.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
23 from dateutil.relativedelta import relativedelta
24 from openerp import netsvc
25 from openerp import pooler
26 from openerp.osv import osv
27 from openerp.osv import fields
28 from openerp.tools.translate import _
29 from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
30 from openerp import tools
31
32 class procurement_order(osv.osv):
33     _inherit = 'procurement.order'
34
35     def run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
36         ''' Runs through scheduler.
37         @param use_new_cursor: False or the dbname
38         '''
39         self._procure_confirm(cr, uid, use_new_cursor=use_new_cursor, context=context)
40         self._procure_orderpoint_confirm(cr, uid, automatic=automatic,\
41                 use_new_cursor=use_new_cursor, context=context)
42
43     def _procure_confirm(self, cr, uid, ids=None, use_new_cursor=False, context=None):
44         '''
45         Call the scheduler to check the procurement order
46
47         @param self: The object pointer
48         @param cr: The current row, from the database cursor,
49         @param uid: The current user ID for security checks
50         @param ids: List of selected IDs
51         @param use_new_cursor: False or the dbname
52         @param context: A standard dictionary for contextual values
53         @return:  Dictionary of values
54         '''
55         if context is None:
56             context = {}
57         try:
58             if use_new_cursor:
59                 cr = pooler.get_db(use_new_cursor).cursor()
60
61             procurement_obj = self.pool.get('procurement.order')
62             if not ids:
63                 ids = procurement_obj.search(cr, uid, [('state', '=', 'exception')], order="date_planned")
64             self.signal_button_restart(cr, uid, ids)
65             if use_new_cursor:
66                 cr.commit()
67             company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id
68             maxdate = (datetime.today() + relativedelta(days=company.schedule_range)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
69             start_date = fields.datetime.now()
70             offset = 0
71             report = []
72             report_total = 0
73             report_except = 0
74             report_later = 0
75             while True:
76                 ids = procurement_obj.search(cr, uid, [('state', '=', 'confirmed'), ('procure_method', '=', 'make_to_order')], offset=offset, limit=500, order='priority, date_planned', context=context)
77                 for proc in procurement_obj.browse(cr, uid, ids, context=context):
78                     if maxdate >= proc.date_planned:
79                         self.signal_button_check(cr, uid, [proc.id])
80                     else:
81                         offset += 1
82                         report_later += 1
83
84                     if proc.state == 'exception':
85                         report.append(_('PROC %d: on order - %3.2f %-5s - %s') % \
86                                 (proc.id, proc.product_qty, proc.product_uom.name,
87                                     proc.product_id.name))
88                         report_except += 1
89                     report_total += 1
90                 if use_new_cursor:
91                     cr.commit()
92                 if not ids:
93                     break
94             offset = 0
95             ids = []
96             while True:
97                 report_ids = []
98                 ids = procurement_obj.search(cr, uid, [('state', '=', 'confirmed'), ('procure_method', '=', 'make_to_stock')], offset=offset)
99                 for proc in procurement_obj.browse(cr, uid, ids):
100                     if maxdate >= proc.date_planned:
101                         self.signal_button_check(cr, uid, [proc.id])
102                         report_ids.append(proc.id)
103                     else:
104                         report_later += 1
105                     report_total += 1
106
107                     if proc.state == 'exception':
108                         report.append(_('PROC %d: from stock - %3.2f %-5s - %s') % \
109                                 (proc.id, proc.product_qty, proc.product_uom.name,
110                                     proc.product_id.name,))
111                         report_except += 1
112
113
114                 if use_new_cursor:
115                     cr.commit()
116                 offset += len(ids)
117                 if not ids: break
118             end_date = fields.datetime.now()
119
120             if use_new_cursor:
121                 cr.commit()
122         finally:
123             if use_new_cursor:
124                 try:
125                     cr.close()
126                 except Exception:
127                     pass
128         return {}
129
130     def _prepare_automatic_op_procurement(self, cr, uid, product, warehouse, location_id, context=None):
131         return {'name': _('Automatic OP: %s') % (product.name,),
132                 'origin': _('SCHEDULER'),
133                 'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
134                 'product_id': product.id,
135                 'product_qty': -product.virtual_available,
136                 'product_uom': product.uom_id.id,
137                 'location_id': location_id,
138                 'company_id': warehouse.company_id.id,
139                 'procure_method': 'make_to_order',}
140
141     def create_automatic_op(self, cr, uid, context=None):
142         """
143         Create procurement of  virtual stock < 0
144
145         @param self: The object pointer
146         @param cr: The current row, from the database cursor,
147         @param uid: The current user ID for security checks
148         @param context: A standard dictionary for contextual values
149         @return:  Dictionary of values
150         """
151         if context is None:
152             context = {}
153         product_obj = self.pool.get('product.product')
154         proc_obj = self.pool.get('procurement.order')
155         warehouse_obj = self.pool.get('stock.warehouse')
156
157         warehouse_ids = warehouse_obj.search(cr, uid, [], context=context)
158         products_ids = product_obj.search(cr, uid, [('purchase_ok', '=', True)], order='id', context=context)
159
160         for warehouse in warehouse_obj.browse(cr, uid, warehouse_ids, context=context):
161             context['warehouse'] = warehouse
162             # Here we check products availability.
163             # We use the method 'read' for performance reasons, because using the method 'browse' may crash the server.
164             for product_read in product_obj.read(cr, uid, products_ids, ['virtual_available'], context=context):
165                 if product_read['virtual_available'] >= 0.0:
166                     continue
167
168                 product = product_obj.browse(cr, uid, [product_read['id']], context=context)[0]
169                 if product.supply_method == 'buy':
170                     location_id = warehouse.lot_input_id.id
171                 elif product.supply_method == 'produce':
172                     location_id = warehouse.lot_stock_id.id
173                 else:
174                     continue
175                 proc_id = proc_obj.create(cr, uid,
176                             self._prepare_automatic_op_procurement(cr, uid, product, warehouse, location_id, context=context),
177                             context=context)
178                 self.signal_button_confirm(cr, uid, [proc_id])
179                 self.signal_button_check(cr, uid, [proc_id])
180         return True
181
182     def _get_orderpoint_date_planned(self, cr, uid, orderpoint, start_date, context=None):
183         date_planned = start_date + \
184                        relativedelta(days=orderpoint.product_id.seller_delay or 0.0)
185         return date_planned.strftime(DEFAULT_SERVER_DATE_FORMAT)
186
187     def _prepare_orderpoint_procurement(self, cr, uid, orderpoint, product_qty, context=None):
188         return {'name': orderpoint.name,
189                 'date_planned': self._get_orderpoint_date_planned(cr, uid, orderpoint, datetime.today(), context=context),
190                 'product_id': orderpoint.product_id.id,
191                 'product_qty': product_qty,
192                 'company_id': orderpoint.company_id.id,
193                 'product_uom': orderpoint.product_uom.id,
194                 'location_id': orderpoint.location_id.id,
195                 'procure_method': 'make_to_order',
196                 'origin': orderpoint.name}
197         
198     def _product_virtual_get(self, cr, uid, order_point):
199         location_obj = self.pool.get('stock.location')
200         return location_obj._product_virtual_get(cr, uid,
201                 order_point.location_id.id, [order_point.product_id.id],
202                 {'uom': order_point.product_uom.id})[order_point.product_id.id]
203
204     def _procure_orderpoint_confirm(self, cr, uid, automatic=False,\
205             use_new_cursor=False, context=None, user_id=False):
206         '''
207         Create procurement based on Orderpoint
208         use_new_cursor: False or the dbname
209
210         @param self: The object pointer
211         @param cr: The current row, from the database cursor,
212         @param user_id: The current user ID for security checks
213         @param context: A standard dictionary for contextual values
214         @param param: False or the dbname
215         @return:  Dictionary of values
216         """
217         '''
218         if context is None:
219             context = {}
220         if use_new_cursor:
221             cr = pooler.get_db(use_new_cursor).cursor()
222         orderpoint_obj = self.pool.get('stock.warehouse.orderpoint')
223         
224         procurement_obj = self.pool.get('procurement.order')
225         offset = 0
226         ids = [1]
227         if automatic:
228             self.create_automatic_op(cr, uid, context=context)
229         while ids:
230             ids = orderpoint_obj.search(cr, uid, [], offset=offset, limit=100)
231             for op in orderpoint_obj.browse(cr, uid, ids, context=context):
232                 prods = self._product_virtual_get(cr, uid, op)
233                 if prods is None:
234                     continue
235                 if prods < op.product_min_qty:
236                     qty = max(op.product_min_qty, op.product_max_qty)-prods
237
238                     reste = qty % op.qty_multiple
239                     if reste > 0:
240                         qty += op.qty_multiple - reste
241
242                     if qty <= 0:
243                         continue
244                     if op.product_id.type not in ('consu'):
245                         if op.procurement_draft_ids:
246                         # Check draft procurement related to this order point
247                             pro_ids = [x.id for x in op.procurement_draft_ids]
248                             procure_datas = procurement_obj.read(
249                                 cr, uid, pro_ids, ['id', 'product_qty'], context=context)
250                             to_generate = qty
251                             for proc_data in procure_datas:
252                                 if to_generate >= proc_data['product_qty']:
253                                     self.signal_button_confirm(cr, uid, [proc_data['id']])
254                                     procurement_obj.write(cr, uid, [proc_data['id']],  {'origin': op.name}, context=context)
255                                     to_generate -= proc_data['product_qty']
256                                 if not to_generate:
257                                     break
258                             qty = to_generate
259
260                     if qty:
261                         proc_id = procurement_obj.create(cr, uid,
262                                                          self._prepare_orderpoint_procurement(cr, uid, op, qty, context=context),
263                                                          context=context)
264                         self.signal_button_confirm(cr, uid, [proc_id])
265                         self.signal_button_check(cr, uid, [proc_id])
266                         orderpoint_obj.write(cr, uid, [op.id],
267                                 {'procurement_id': proc_id}, context=context)
268             offset += len(ids)
269             if use_new_cursor:
270                 cr.commit()
271         if use_new_cursor:
272             cr.commit()
273             cr.close()
274         return {}
275
276 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: