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