[IMP] Improved code.
[odoo/odoo.git] / addons / procurement / procurement.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 osv import osv, fields
23 from tools.translate import _
24 import netsvc
25 import time
26 import decimal_precision as dp
27
28 # Procurement
29 # ------------------------------------------------------------------
30 #
31 # Produce, Buy or Find products and place a move
32 #     then wizard for picking lists & move
33 #
34
35 class mrp_property_group(osv.osv):
36     """
37     Group of mrp properties.
38     """
39     _name = 'mrp.property.group'
40     _description = 'Property Group'
41     _columns = {
42         'name': fields.char('Property Group', size=64, required=True),
43         'description': fields.text('Description'),
44     }
45 mrp_property_group()
46
47 class mrp_property(osv.osv):
48     """
49     Properties of mrp.
50     """
51     _name = 'mrp.property'
52     _description = 'Property'
53     _columns = {
54         'name': fields.char('Name', size=64, required=True),
55         'composition': fields.selection([('min','min'),('max','max'),('plus','plus')], 'Properties composition', required=True, help="Not used in computations, for information purpose only."),
56         'group_id': fields.many2one('mrp.property.group', 'Property Group', required=True),
57         'description': fields.text('Description'),
58     }
59     _defaults = {
60         'composition': lambda *a: 'min',
61     }
62 mrp_property()
63
64 class StockMove(osv.osv):
65     _inherit = 'stock.move'
66     _columns= {
67         'procurements': fields.one2many('procurement.order', 'move_id', 'Procurements'),
68     }
69
70     def copy(self, cr, uid, id, default=None, context=None):
71         default = default or {}
72         default['procurements'] = []
73         return super(StockMove, self).copy(cr, uid, id, default, context=context)
74
75 StockMove()
76
77 class procurement_order(osv.osv):
78     """
79     Procurement Orders
80     """
81     _name = "procurement.order"
82     _description = "Procurement"
83     _order = 'priority,date_planned desc'
84     _inherit = ['ir.needaction_mixin', 'mail.thread']
85     _log_create = False
86     _columns = {
87         'name': fields.char('Reason', size=64, required=True, help='Procurement name.'),
88         'origin': fields.char('Source Document', size=64,
89             help="Reference of the document that created this Procurement.\n"
90             "This is automatically completed by OpenERP."),
91         'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority', required=True, select=True),
92         'date_planned': fields.datetime('Scheduled date', required=True, select=True),
93         'date_close': fields.datetime('Date Closed'),
94         'product_id': fields.many2one('product.product', 'Product', required=True, states={'draft':[('readonly',False)]}, readonly=True),
95         'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product UoM'), required=True, states={'draft':[('readonly',False)]}, readonly=True),
96         'product_uom': fields.many2one('product.uom', 'Product UoM', required=True, states={'draft':[('readonly',False)]}, readonly=True),
97         'product_uos_qty': fields.float('UoS Quantity', states={'draft':[('readonly',False)]}, readonly=True),
98         'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
99         'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
100         'close_move': fields.boolean('Close Move at end', required=True),
101         'location_id': fields.many2one('stock.location', 'Location', required=True, states={'draft':[('readonly',False)]}, readonly=True),
102         'procure_method': fields.selection([('make_to_stock','from stock'),('make_to_order','on order')], 'Procurement Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]},
103             readonly=True, required=True, help="If you encode manually a Procurement, you probably want to use" \
104             " a make to order method."),
105
106         'note': fields.text('Note'),
107         'message': fields.char('Latest error', size=64, help="Exception occurred while computing procurement orders."),
108         'state': fields.selection([
109             ('draft','Draft'),
110             ('confirmed','Confirmed'),
111             ('exception','Exception'),
112             ('running','Running'),
113             ('cancel','Cancel'),
114             ('ready','Ready'),
115             ('done','Done'),
116             ('waiting','Waiting')], 'State', required=True,
117             help='When a procurement is created the state is set to \'Draft\'.\n If the procurement is confirmed, the state is set to \'Confirmed\'.\
118             \nAfter confirming the state is set to \'Running\'.\n If any exception arises in the order then the state is set to \'Exception\'.\n Once the exception is removed the state becomes \'Ready\'.\n It is in \'Waiting\'. state when the procurement is waiting for another one to finish.'),
119         'note': fields.text('Note'),
120         'company_id': fields.many2one('res.company','Company',required=True),
121         'user_id': fields.many2one('res.users', 'Salesman'),
122     }
123     _defaults = {
124         'state': 'draft',
125         'priority': '1',
126         'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
127         'close_move': 0,
128         'procure_method': 'make_to_order',
129         'user_id': lambda obj, cr, uid, context: uid,
130         'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'procurement.order', context=c)
131     }
132
133     def unlink(self, cr, uid, ids, context=None):
134         procurements = self.read(cr, uid, ids, ['state'], context=context)
135         unlink_ids = []
136         for s in procurements:
137             if s['state'] in ['draft','cancel']:
138                 unlink_ids.append(s['id'])
139             else:
140                 raise osv.except_osv(_('Invalid action !'),
141                         _('Cannot delete Procurement Order(s) which are in %s state!') % \
142                         s['state'])
143         return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
144
145     def onchange_product_id(self, cr, uid, ids, product_id, context=None):
146         """ Finds UoM and UoS of changed product.
147         @param product_id: Changed id of product.
148         @return: Dictionary of values.
149         """
150         if product_id:
151             w = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
152             v = {
153                 'product_uom': w.uom_id.id,
154                 'product_uos': w.uos_id and w.uos_id.id or w.uom_id.id
155             }
156             return {'value': v}
157         return {}
158
159     def check_product(self, cr, uid, ids, context=None):
160         """ Checks product type.
161         @return: True or False
162         """
163         return all(proc.product_id.type in ('product', 'consu') for proc in self.browse(cr, uid, ids, context=context))
164
165     def check_move_cancel(self, cr, uid, ids, context=None):
166         """ Checks if move is cancelled or not.
167         @return: True or False.
168         """
169         return all(procurement.move_id.state == 'cancel' for procurement in self.browse(cr, uid, ids, context=context))
170
171     #This Function is create to avoid  a server side Error Like 'ERROR:tests.mrp:name 'check_move' is not defined' 
172     def check_move(self, cr, uid, ids, context=None):
173         pass
174
175     def check_move_done(self, cr, uid, ids, context=None):
176         """ Checks if move is done or not.
177         @return: True or False.
178         """
179         return all(proc.product_id.type == 'service' or (proc.move_id and proc.move_id.state == 'done') \
180                     for proc in self.browse(cr, uid, ids, context=context))
181
182     #
183     # This method may be overrided by objects that override procurement.order
184     # for computing their own purpose
185     #
186     def _quantity_compute_get(self, cr, uid, proc, context=None):
187         """ Finds sold quantity of product.
188         @param proc: Current procurement.
189         @return: Quantity or False.
190         """
191         if proc.product_id.type == 'product' and proc.move_id:
192             if proc.move_id.product_uos:
193                 return proc.move_id.product_uos_qty
194         return False
195
196     def _uom_compute_get(self, cr, uid, proc, context=None):
197         """ Finds UoS if product is Stockable Product.
198         @param proc: Current procurement.
199         @return: UoS or False.
200         """
201         if proc.product_id.type == 'product' and proc.move_id:
202             if proc.move_id.product_uos:
203                 return proc.move_id.product_uos.id
204         return False
205
206     #
207     # Return the quantity of product shipped/produced/served, which may be
208     # different from the planned quantity
209     #
210     def quantity_get(self, cr, uid, id, context=None):
211         """ Finds quantity of product used in procurement.
212         @return: Quantity of product.
213         """
214         proc = self.browse(cr, uid, id, context=context)
215         result = self._quantity_compute_get(cr, uid, proc, context=context)
216         if not result:
217             result = proc.product_qty
218         return result
219
220     def uom_get(self, cr, uid, id, context=None):
221         """ Finds UoM of product used in procurement.
222         @return: UoM of product.
223         """
224         proc = self.browse(cr, uid, id, context=context)
225         result = self._uom_compute_get(cr, uid, proc, context=context)
226         if not result:
227             result = proc.product_uom.id
228         return result
229
230     def check_waiting(self, cr, uid, ids, context=None):
231         """ Checks state of move.
232         @return: True or False
233         """
234         for procurement in self.browse(cr, uid, ids, context=context):
235             if procurement.move_id and procurement.move_id.state == 'auto':
236                 return True
237         return False
238
239     def check_produce_service(self, cr, uid, procurement, context=None):
240         return False
241
242     def check_produce_product(self, cr, uid, procurement, context=None):
243         """ Finds BoM of a product if not found writes exception message.
244         @param procurement: Current procurement.
245         @return: True or False.
246         """
247         return True
248
249     def check_make_to_stock(self, cr, uid, ids, context=None):
250         """ Checks product type.
251         @return: True or False
252         """
253         ok = True
254         for procurement in self.browse(cr, uid, ids, context=context):
255             if procurement.product_id.type == 'service':
256                 ok = ok and self._check_make_to_stock_service(cr, uid, procurement, context)
257             else:
258                 ok = ok and self._check_make_to_stock_product(cr, uid, procurement, context)
259         return ok
260
261     def check_produce(self, cr, uid, ids, context=None):
262         """ Checks product type.
263         @return: True or False
264         """
265         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
266         for procurement in self.browse(cr, uid, ids, context=context):
267             product = procurement.product_id
268             #TOFIX: if product type is 'service' but supply_method is 'buy'.
269             if product.supply_method <> 'produce':
270                 supplier = product.seller_id
271                 if supplier and user.company_id and user.company_id.partner_id:
272                     if supplier.id == user.company_id.partner_id.id:
273                         continue
274                 return False
275             if product.type=='service':
276                 res = self.check_produce_service(cr, uid, procurement, context)
277             else:
278                 res = self.check_produce_product(cr, uid, procurement, context)
279             if not res:
280                 return False
281         return True
282
283     def check_buy(self, cr, uid, ids):
284         """ Checks product type.
285         @return: True or Product Id.
286         """
287         user = self.pool.get('res.users').browse(cr, uid, uid)
288         partner_obj = self.pool.get('res.partner')
289         for procurement in self.browse(cr, uid, ids):
290             if procurement.product_id.product_tmpl_id.supply_method <> 'buy':
291                 return False
292             if not procurement.product_id.seller_ids:
293                 cr.execute('update procurement_order set message=%s where id=%s',
294                         (_('No supplier defined for this product !'), procurement.id))
295                 return False
296             partner = procurement.product_id.seller_id #Taken Main Supplier of Product of Procurement.
297
298             if not partner:
299                 cr.execute('update procurement_order set message=%s where id=%s',
300                            (_('No default supplier defined for this product'), procurement.id))
301                 return False
302
303             if user.company_id and user.company_id.partner_id:
304                 if partner.id == user.company_id.partner_id.id:
305                     return False
306
307             address_id = partner_obj.address_get(cr, uid, [partner.id], ['delivery'])['delivery']
308             if not address_id:
309                 cr.execute('update procurement_order set message=%s where id=%s',
310                         (_('No address defined for the supplier'), procurement.id))
311                 return False
312         return True
313
314     def test_cancel(self, cr, uid, ids):
315         """ Tests whether state of move is cancelled or not.
316         @return: True or False
317         """
318         for record in self.browse(cr, uid, ids):
319             if record.move_id and record.move_id.state == 'cancel':
320                 return True
321         return False
322
323     def action_confirm(self, cr, uid, ids, context=None):
324         """ Confirms procurement and writes exception message if any.
325         @return: True
326         """
327         move_obj = self.pool.get('stock.move')
328         for procurement in self.browse(cr, uid, ids, context=context):
329             if procurement.product_qty <= 0.00:
330                 raise osv.except_osv(_('Data Insufficient !'),
331                     _('Please check the quantity in procurement order(s), it should not be 0 or less!'))
332             if procurement.product_id.type in ('product', 'consu'):
333                 if not procurement.move_id:
334                     source = procurement.location_id.id
335                     if procurement.procure_method == 'make_to_order':
336                         source = procurement.product_id.product_tmpl_id.property_stock_procurement.id
337                     id = move_obj.create(cr, uid, {
338                         'name': procurement.name,
339                         'location_id': source,
340                         'location_dest_id': procurement.location_id.id,
341                         'product_id': procurement.product_id.id,
342                         'product_qty': procurement.product_qty,
343                         'product_uom': procurement.product_uom.id,
344                         'date_expected': procurement.date_planned,
345                         'state': 'draft',
346                         'company_id': procurement.company_id.id,
347                         'auto_validate': True,
348                     })
349                     move_obj.action_confirm(cr, uid, [id], context=context)
350                     self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move': 1})
351         self.write(cr, uid, ids, {'state': 'confirmed', 'message': ''})
352         self.state_change_send_note(cr, uid, ids ,'confirmed', context)
353         return True
354
355     def action_move_assigned(self, cr, uid, ids, context=None):
356         """ Changes procurement state to Running and writes message.
357         @return: True
358         """
359         self.write(cr, uid, ids, {'state': 'running',
360                 'message': _('from stock: products assigned.')})
361         self.change_state_send_note(cr, uid, ids, 'running', context)
362         return True
363
364     def _check_make_to_stock_service(self, cr, uid, procurement, context=None):
365         """
366            This method may be overrided by objects that override procurement.order
367            for computing their own purpose
368         @return: True"""
369         return True
370
371     def _check_make_to_stock_product(self, cr, uid, procurement, context=None):
372         """ Checks procurement move state.
373         @param procurement: Current procurement.
374         @return: True or move id.
375         """
376         ok = True
377         if procurement.move_id:
378             message = False
379             id = procurement.move_id.id
380             if not (procurement.move_id.state in ('done','assigned','cancel')):
381                 ok = ok and self.pool.get('stock.move').action_assign(cr, uid, [id])
382                 order_point_id = self.pool.get('stock.warehouse.orderpoint').search(cr, uid, [('product_id', '=', procurement.product_id.id)], context=context)
383                 if not order_point_id and not ok:
384                      message = _("Not enough stock and no minimum orderpoint rule defined.")
385                 elif not order_point_id:
386                     message = _("No minimum orderpoint rule defined.")
387                 elif not ok:
388                     message = _("Not enough stock.")
389
390                 if message:
391                     self.log(cr, uid, procurement.id, _("Procurement '%s' is in exception: ") % (procurement.name) + message)
392                     cr.execute('update procurement_order set message=%s where id=%s', (message, procurement.id))
393         return ok
394
395     def action_produce_assign_service(self, cr, uid, ids, context=None):
396         """ Changes procurement state to Running.
397         @return: True
398         """
399         for procurement in self.browse(cr, uid, ids, context=context):
400             self.write(cr, uid, [procurement.id], {'state': 'running'})
401         self.change_state_send_note(cr, uid, ids, context=None)
402         return True
403
404     def action_produce_assign_product(self, cr, uid, ids, context=None):
405         """ This is action which call from workflow to assign production order to procurements
406         @return: True
407         """
408         return 0
409
410
411     def action_po_assign(self, cr, uid, ids, context=None):
412         """ This is action which call from workflow to assign purchase order to procurements
413         @return: True
414         """
415         return 0
416
417     def action_cancel(self, cr, uid, ids):
418         """ Cancels procurement and writes move state to Assigned.
419         @return: True
420         """
421         todo = []
422         todo2 = []
423         move_obj = self.pool.get('stock.move')
424         for proc in self.browse(cr, uid, ids):
425             if proc.close_move and proc.move_id:
426                 if proc.move_id.state not in ('done', 'cancel'):
427                     todo2.append(proc.move_id.id)
428             else:
429                 if proc.move_id and proc.move_id.state == 'waiting':
430                     todo.append(proc.move_id.id)
431         if len(todo2):
432             move_obj.action_cancel(cr, uid, todo2)
433         if len(todo):
434             move_obj.write(cr, uid, todo, {'state': 'assigned'})
435         self.write(cr, uid, ids, {'state': 'cancel'})
436         self.state_change_send_note(cr, uid, ids, 'cancelled', context=None)
437         wf_service = netsvc.LocalService("workflow")
438         for id in ids:
439             wf_service.trg_trigger(uid, 'procurement.order', id, cr)
440         return True
441
442     def action_check_finished(self, cr, uid, ids):
443         return self.check_move_done(cr, uid, ids)
444
445     def action_check(self, cr, uid, ids):
446         """ Checks procurement move state whether assigned or done.
447         @return: True
448         """
449         ok = False
450         for procurement in self.browse(cr, uid, ids):
451             if procurement.move_id and procurement.move_id.state == 'assigned' or procurement.move_id.state == 'done':
452                 self.action_done(cr, uid, [procurement.id])
453                 ok = True
454         return ok
455
456     def action_ready(self, cr, uid, ids):
457         """ Changes procurement state to Ready.
458         @return: True
459         """
460         res = self.write(cr, uid, ids, {'state': 'ready'})
461         self.change_state_send_note(cr, uid, ids, 'ready', context=None)
462         return res
463
464     def action_done(self, cr, uid, ids):
465         """ Changes procurement state to Done and writes Closed date.
466         @return: True
467         """
468         move_obj = self.pool.get('stock.move')
469         for procurement in self.browse(cr, uid, ids):
470             if procurement.move_id:
471                 if procurement.close_move and (procurement.move_id.state <> 'done'):
472                     move_obj.action_done(cr, uid, [procurement.move_id.id])
473         res = self.write(cr, uid, ids, {'state': 'done', 'date_close': time.strftime('%Y-%m-%d')})
474         self.state_change_send_note(cr, uid, ids, 'done', context)
475         wf_service = netsvc.LocalService("workflow")
476         for id in ids:
477             wf_service.trg_trigger(uid, 'procurement.order', id, cr)
478         return res
479
480     # ----------------------------------------
481     # OpenChatter methods and notifications
482     # ----------------------------------------
483
484     def get_needaction_user_ids(self, cr, uid, ids, context=None):
485         result = dict.fromkeys(ids, [])
486         for obj in self.browse(cr, uid, ids, context=context):
487             if (obj.state == 'draft' or obj.state == 'waiting'):
488                 result[obj.id] = [obj.user_id.id]
489         return result
490
491     def create(self, cr, uid, vals, context=None):
492         obj_id = super(procurement_order, self).create(cr, uid, vals, context)
493         self.state_change_send_note(cr, uid, [obj_id], 'created', context=context)
494         return obj_id
495
496     def state_change_send_note(self, cr, uid, ids, state, context=None):
497         for obj in self.browse(cr, uid, ids, context=context):
498             self.message_subscribe(cr, uid, [obj.id], [obj.user_id.id], context=context)
499             self.message_append_note(cr, uid, [obj.id], body=_("Procurement has been <b>%s</b>.") % (state), context=context)
500
501     def change_state_send_note(self, cr, uid, ids, state, context=None):
502         for obj in self.browse(cr, uid, ids, context=context):
503             self.message_subscribe(cr, uid, [obj.id], [obj.user_id.id], context=context)
504             self.message_append_note(cr, uid, [obj.id], body=_("Procurement has been set to <b>%s</b> state.") % (state), context=context)
505
506 procurement_order()
507
508 class StockPicking(osv.osv):
509     _inherit = 'stock.picking'
510
511     def test_finished(self, cursor, user, ids):
512         wf_service = netsvc.LocalService("workflow")
513         res = super(StockPicking, self).test_finished(cursor, user, ids)
514         for picking in self.browse(cursor, user, ids):
515             for move in picking.move_lines:
516                 if move.state == 'done' and move.procurements:
517                     for procurement in move.procurements:
518                         wf_service.trg_validate(user, 'procurement.order',
519                             procurement.id, 'button_check', cursor)
520         return res
521
522 StockPicking()
523
524 class stock_warehouse_orderpoint(osv.osv):
525     """
526     Defines Minimum stock rules.
527     """
528     _name = "stock.warehouse.orderpoint"
529     _description = "Minimum Inventory Rule"
530
531     def _get_draft_procurements(self, cr, uid, ids, field_name, arg, context=None):
532         if context is None:
533             context = {}
534         result = {}
535         procurement_obj = self.pool.get('procurement.order')
536         for orderpoint in self.browse(cr, uid, ids, context=context):
537             procurement_ids = procurement_obj.search(cr, uid , [('state', '=', 'draft'), ('product_id', '=', orderpoint.product_id.id), ('location_id', '=', orderpoint.location_id.id)])
538             result[orderpoint.id] = procurement_ids
539         return result
540
541     _columns = {
542         'name': fields.char('Name', size=32, required=True),
543         'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the orderpoint without removing it."),
544         'logic': fields.selection([('max','Order to Max'),('price','Best price (not yet active!)')], 'Reordering Mode', required=True),
545         'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True, ondelete="cascade"),
546         'location_id': fields.many2one('stock.location', 'Location', required=True, ondelete="cascade"),
547         'product_id': fields.many2one('product.product', 'Product', required=True, ondelete='cascade', domain=[('type','=','product')]),
548         'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
549         'product_min_qty': fields.float('Min Quantity', required=True,
550             help="When the virtual stock goes below the Min Quantity specified for this field, OpenERP generates "\
551             "a procurement to bring the virtual stock to the Max Quantity."),
552         'product_max_qty': fields.float('Max Quantity', required=True,
553             help="When the virtual stock goes below the Min Quantity, OpenERP generates "\
554             "a procurement to bring the virtual stock to the Quantity specified as Max Quantity."),
555         'qty_multiple': fields.integer('Qty Multiple', required=True,
556             help="The procurement quantity will be rounded up to this multiple."),
557         'procurement_id': fields.many2one('procurement.order', 'Latest procurement', ondelete="set null"),
558         'company_id': fields.many2one('res.company','Company',required=True),
559         'procurement_draft_ids': fields.function(_get_draft_procurements, type='many2many', relation="procurement.order", \
560                                 string="Related Procurement Orders",help="Draft procurement of the product and location of that orderpoint"),
561     }
562     _defaults = {
563         'active': lambda *a: 1,
564         'logic': lambda *a: 'max',
565         'qty_multiple': lambda *a: 1,
566         'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'stock.orderpoint') or '',
567         'product_uom': lambda sel, cr, uid, context: context.get('product_uom', False),
568         'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.warehouse.orderpoint', context=c)
569     }
570     _sql_constraints = [
571         ('qty_multiple_check', 'CHECK( qty_multiple > 0 )', 'Qty Multiple must be greater than zero.'),
572     ]
573
574     def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context=None):
575         """ Finds location id for changed warehouse.
576         @param warehouse_id: Changed id of warehouse.
577         @return: Dictionary of values.
578         """
579         if warehouse_id:
580             w = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context)
581             v = {'location_id': w.lot_stock_id.id}
582             return {'value': v}
583         return {}
584
585     def onchange_product_id(self, cr, uid, ids, product_id, context=None):
586         """ Finds UoM for changed product.
587         @param product_id: Changed id of product.
588         @return: Dictionary of values.
589         """
590         if product_id:
591             prod = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
592             v = {'product_uom': prod.uom_id.id}
593             return {'value': v}
594         return {}
595     
596     def copy(self, cr, uid, id, default=None, context=None):
597         if not default:
598             default = {}
599         default.update({
600             'name': self.pool.get('ir.sequence').get(cr, uid, 'stock.orderpoint') or '',
601         })
602         return super(stock_warehouse_orderpoint, self).copy(cr, uid, id, default, context=context)
603     
604 stock_warehouse_orderpoint()
605
606 class product_product(osv.osv):
607     _inherit="product.product"
608     _columns = {
609         'orderpoint_ids': fields.one2many('stock.warehouse.orderpoint', 'product_id', 'Minimum Stock Rule')
610     }
611 product_product()
612
613 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: