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