fixed bug:308803
[odoo/odoo.git] / addons / purchase / purchase.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution   
5 #    Copyright (C) 2004-2008 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 from osv import fields
24 from osv import osv
25 import time
26 import netsvc
27
28 import ir
29 from mx import DateTime
30 import pooler
31 from tools import config
32 from tools.translate import _
33
34 #
35 # Model definition
36 #
37 class purchase_order(osv.osv):
38     def _calc_amount(self, cr, uid, ids, prop, unknow_none, unknow_dict):
39         res = {}
40         for order in self.browse(cr, uid, ids):
41             res[order.id] = 0
42             for oline in order.order_line:
43                 res[order.id] += oline.price_unit * oline.product_qty
44         return res
45
46     def _amount_untaxed(self, cr, uid, ids, field_name, arg, context):
47         res = {}
48         cur_obj=self.pool.get('res.currency')
49         for purchase in self.browse(cr, uid, ids):
50             res[purchase.id] = 0.0
51             for line in purchase.order_line:
52                 res[purchase.id] += line.price_subtotal
53             cur = purchase.pricelist_id.currency_id
54             res[purchase.id] = cur_obj.round(cr, uid, cur, res[purchase.id])
55
56         return res
57
58     def _amount_tax(self, cr, uid, ids, field_name, arg, context):
59         res = {}
60         cur_obj=self.pool.get('res.currency')
61         for order in self.browse(cr, uid, ids):
62             val = 0.0
63             cur=order.pricelist_id.currency_id
64             for line in order.order_line:
65                 for c in self.pool.get('account.tax').compute(cr, uid, line.taxes_id, line.price_unit, line.product_qty, order.partner_address_id.id, line.product_id, order.partner_id):
66                     val+= c['amount']
67             res[order.id]=cur_obj.round(cr, uid, cur, val)
68         return res
69
70     def _amount_total(self, cr, uid, ids, field_name, arg, context):
71         res = {}
72         untax = self._amount_untaxed(cr, uid, ids, field_name, arg, context)
73         tax = self._amount_tax(cr, uid, ids, field_name, arg, context)
74         cur_obj=self.pool.get('res.currency')
75         for id in ids:
76             order=self.browse(cr, uid, [id])[0]
77             cur=order.pricelist_id.currency_id
78             res[id] = cur_obj.round(cr, uid, cur, untax.get(id, 0.0) + tax.get(id, 0.0))
79         return res
80
81     def _set_minimum_planned_date(self, cr, uid, ids, name, value, arg, context):
82         if not value: return False
83         if type(ids)!=type([]):
84             ids=[ids]
85         for po in self.browse(cr, uid, ids, context):
86             cr.execute("""update purchase_order_line set
87                     date_planned=%s
88                 where
89                     order_id=%s and
90                     (date_planned=%s or date_planned<%s)""", (value,po.id,po.minimum_planned_date,value))
91         return True
92
93     def _minimum_planned_date(self, cr, uid, ids, field_name, arg, context):
94         res={}
95         purchase_obj=self.browse(cr, uid, ids, context=context)
96         for purchase in purchase_obj:
97             res[purchase.id] = False
98             if purchase.order_line:
99                 min_date=purchase.order_line[0].date_planned
100                 for line in purchase.order_line:
101                     if line.date_planned < min_date:
102                         min_date=line.date_planned
103                 res[purchase.id]=min_date
104         return res
105
106     def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
107         res = {}
108         for purchase in self.browse(cursor, user, ids, context=context):
109             tot = 0.0
110             if purchase.invoice_id and purchase.invoice_id.state not in ('draft','cancel'):
111                 tot += purchase.invoice_id.amount_untaxed
112             if purchase.amount_untaxed:
113                 res[purchase.id] = tot * 100.0 / purchase.amount_untaxed
114             else:
115                 res[purchase.id] = 0.0
116         return res
117
118     def _shipped_rate(self, cr, uid, ids, name, arg, context=None):
119         if not ids: return {}
120         res = {}
121         for id in ids:
122             res[id] = [0.0,0.0]
123         cr.execute('''SELECT
124                 p.purchase_id,sum(m.product_qty), m.state
125             FROM
126                 stock_move m
127             LEFT JOIN
128                 stock_picking p on (p.id=m.picking_id)
129             WHERE
130                 p.purchase_id in ('''+','.join(map(str,ids))+''')
131             GROUP BY m.state, p.purchase_id''')
132         for oid,nbr,state in cr.fetchall():
133             if state=='cancel':
134                 continue
135             if state=='done':
136                 res[oid][0] += nbr or 0.0
137                 res[oid][1] += nbr or 0.0
138             else:
139                 res[oid][1] += nbr or 0.0
140         for r in res:
141             if not res[r][1]:
142                 res[r] = 0.0
143             else:
144                 res[r] = 100.0 * res[r][0] / res[r][1]
145         return res
146
147     _columns = {
148         'name': fields.char('Order Reference', size=64, required=True, select=True),
149         'origin': fields.char('Origin', size=64,
150             help="Reference of the document that generated this purchase order request."
151         ),
152         'partner_ref': fields.char('Partner Ref.', size=64),
153         'date_order':fields.date('Date Ordered', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}),
154         'date_approve':fields.date('Date Approved', readonly=1),
155         'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}, change_default=True),
156         'partner_address_id':fields.many2one('res.partner.address', 'Address', required=True, states={'posted':[('readonly',True)]}),
157
158         'dest_address_id':fields.many2one('res.partner.address', 'Destination Address', states={'posted':[('readonly',True)]},
159             help="Put an address if you want to deliver directly from the supplier to the customer." \
160                 "In this case, it will remove the warehouse link and set the customer location."
161         ),
162         'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', states={'posted':[('readonly',True)]}),
163         'location_id': fields.many2one('stock.location', 'Destination', required=True),
164
165         'pricelist_id':fields.many2one('product.pricelist', 'Pricelist', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}, help="The pricelist sets the currency used for this purchase order. It also computes the supplier price for the selected products/quantities."),
166
167         'state': fields.selection([('draft', 'Request for Quotation'), ('wait', 'Waiting'), ('confirmed', 'Confirmed'), ('approved', 'Approved'),('except_picking', 'Shipping Exception'), ('except_invoice', 'Invoice Exception'), ('done', 'Done'), ('cancel', 'Cancelled')], 'Order Status', readonly=True, help="The state of the purchase order or the quotation request. A quotation is a purchase order in a 'Draft' state. Then the order has to be confirmed by the user, the state switch to 'Confirmed'. Then the supplier must confirm the order to change the state to 'Approved'. When the purchase order is paid and received, the state becomes 'Done'. If a cancel action occurs in the invoice or in the reception of goods, the state becomes in exception.", select=True),
168         'order_line': fields.one2many('purchase.order.line', 'order_id', 'Order Lines', states={'approved':[('readonly',True)]}),
169         'validator' : fields.many2one('res.users', 'Validated by', readonly=True),
170         'notes': fields.text('Notes'),
171         'invoice_id': fields.many2one('account.invoice', 'Invoice', readonly=True),
172         'picking_ids': fields.one2many('stock.picking', 'purchase_id', 'Picking List', readonly=True, help="This is the list of picking list that have been generated for this purchase"),
173         'shipped':fields.boolean('Received', readonly=True, select=True),
174         'shipped_rate': fields.function(_shipped_rate, method=True, string='Received', type='float'),
175         'invoiced':fields.boolean('Invoiced & Paid', readonly=True, select=True),
176         'invoiced_rate': fields.function(_invoiced_rate, method=True, string='Invoiced', type='float'),
177         'invoice_method': fields.selection([('manual','Manual'),('order','From Order'),('picking','From Picking')], 'Invoicing Control', required=True,
178             help="From Order: a draft invoice will be pre-generated based on the purchase order. The accountant " \
179                 "will just have to validate this invoice for control.\n" \
180                 "From Picking: a draft invoice will be pre-genearted based on validated receptions.\n" \
181                 "Manual: no invoice will be pre-generated. The accountant will have to encode manually."
182         ),
183         'minimum_planned_date':fields.function(_minimum_planned_date, fnct_inv=_set_minimum_planned_date, method=True,store=True, string='Planned Date', type='datetime', help="This is computed as the minimum scheduled date of all purchase order lines' products."),
184         'amount_untaxed': fields.function(_amount_untaxed, method=True, string='Untaxed Amount'),
185         'amount_tax': fields.function(_amount_tax, method=True, string='Taxes'),
186         'amount_total': fields.function(_amount_total, method=True, string='Total'),
187     }
188     _defaults = {
189         'date_order': lambda *a: time.strftime('%Y-%m-%d'),
190         'state': lambda *a: 'draft',
191         'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
192         'shipped': lambda *a: 0,
193         'invoice_method': lambda *a: 'order',
194         'invoiced': lambda *a: 0,
195         'partner_address_id': lambda self, cr, uid, context: context.get('partner_id', False) and self.pool.get('res.partner').address_get(cr, uid, [context['partner_id']], ['default'])['default'],
196         'pricelist_id': lambda self, cr, uid, context: context.get('partner_id', False) and self.pool.get('res.partner').browse(cr, uid, context['partner_id']).property_product_pricelist_purchase.id,
197     }
198     _name = "purchase.order"
199     _description = "Purchase order"
200     _order = "name desc"
201     
202     def unlink(self, cr, uid, ids):
203         purchase_orders = self.read(cr, uid, ids, ['state'])
204         unlink_ids = []
205         for s in purchase_orders:
206             if s['state'] in ['draft','cancel']:
207                 unlink_ids.append(s['id'])
208             else:
209                 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Purchase Order(s) which are in %s State!' % s['state']))
210         return osv.osv.unlink(self, cr, uid, unlink_ids)        
211     
212     def button_dummy(self, cr, uid, ids, context={}):
213         return True
214
215     def onchange_dest_address_id(self, cr, uid, ids, adr_id):
216         if not adr_id:
217             return {}
218         part_id = self.pool.get('res.partner.address').read(cr, uid, [adr_id], ['partner_id'])[0]['partner_id'][0]
219         loc_id = self.pool.get('res.partner').browse(cr, uid, part_id).property_stock_customer.id
220         return {'value':{'location_id': loc_id, 'warehouse_id': False}}
221
222     def onchange_warehouse_id(self, cr, uid, ids, warehouse_id):
223         if not warehouse_id:
224             return {}
225         res = self.pool.get('stock.warehouse').read(cr, uid, [warehouse_id], ['lot_input_id'])[0]['lot_input_id'][0]
226         return {'value':{'location_id': res, 'dest_address_id': False}}
227
228     def onchange_partner_id(self, cr, uid, ids, part):
229         if not part:
230             return {'value':{'partner_address_id': False}}
231         addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['default'])
232         part = self.pool.get('res.partner').browse(cr, uid, part)
233         pricelist = part.property_product_pricelist_purchase.id
234         return {'value':{'partner_address_id': addr['default'], 'pricelist_id': pricelist}}
235
236     def wkf_approve_order(self, cr, uid, ids, context={}):
237         self.write(cr, uid, ids, {'state': 'approved', 'date_approve': time.strftime('%Y-%m-%d')})
238         return True
239
240     def wkf_confirm_order(self, cr, uid, ids, context={}):
241         for po in self.browse(cr, uid, ids):
242             if self.pool.get('res.partner.event.type').check(cr, uid, 'purchase_open'):
243                 self.pool.get('res.partner.event').create(cr, uid, {'name':'Purchase Order: '+po.name, 'partner_id':po.partner_id.id, 'date':time.strftime('%Y-%m-%d %H:%M:%S'), 'user_id':uid, 'partner_type':'retailer', 'probability': 1.0, 'planned_cost':po.amount_untaxed})
244         current_name = self.name_get(cr, uid, ids)[0][1]
245         for id in ids:
246             self.write(cr, uid, [id], {'state' : 'confirmed', 'validator' : uid})
247         return True
248
249     def wkf_warn_buyer(self, cr, uid, ids):
250         self.write(cr, uid, ids, {'state' : 'wait', 'validator' : uid})
251         request = pooler.get_pool(cr.dbname).get('res.request')
252         for po in self.browse(cr, uid, ids):
253             managers = []
254             for oline in po.order_line:
255                 manager = oline.product_id.product_manager
256                 if manager and not (manager.id in managers):
257                     managers.append(manager.id)
258             for manager_id in managers:
259                 request.create(cr, uid,
260                       {'name' : "Purchase amount over the limit",
261                        'act_from' : uid,
262                        'act_to' : manager_id,
263                        'body': 'Somebody has just confirmed a purchase with an amount over the defined limit',
264                        'ref_partner_id': po.partner_id.id,
265                        'ref_doc1': 'purchase.order,%d' % (po.id,),
266                        })
267     def inv_line_create(self,a,ol):
268         return (0, False, {
269             'name': ol.name,
270             'account_id': a,
271             'price_unit': ol.price_unit or 0.0,
272             'quantity': ol.product_qty,
273             'product_id': ol.product_id.id or False,
274             'uos_id': ol.product_uom.id or False,
275             'invoice_line_tax_id': [(6, 0, [x.id for x in ol.taxes_id])],
276             'account_analytic_id': ol.account_analytic_id.id,
277         })
278
279     def action_cancel_draft(self, cr, uid, ids, *args):
280         if not len(ids):
281             return False
282         self.write(cr, uid, ids, {'state':'draft','shipped':0})
283         wf_service = netsvc.LocalService("workflow")
284         for p_id in ids:
285             wf_service.trg_create(uid, 'purchase.order', p_id, cr)
286         return True
287
288     def action_invoice_create(self, cr, uid, ids, *args):
289         res = False
290         for o in self.browse(cr, uid, ids):
291             il = []
292             for ol in o.order_line:
293
294                 if ol.product_id:
295                     a = ol.product_id.product_tmpl_id.property_account_expense.id
296                     if not a:
297                         a = ol.product_id.categ_id.property_account_expense_categ.id
298                     if not a:
299                         raise osv.except_osv(_('Error !'), _('There is no expense account defined for this product: "%s" (id:%d)') % (ol.product_id.name, ol.product_id.id,))
300                 else:
301                     a = self.pool.get('ir.property').get(cr, uid, 'property_account_expense_categ', 'product.category')
302                 a = self.pool.get('account.fiscal.position').map_account(cr, uid, o.partner_id, a)
303                 il.append(self.inv_line_create(a,ol))
304
305             a = o.partner_id.property_account_payable.id
306             inv = {
307                 'name': o.partner_ref or o.name,
308                 'reference': "P%dPO%d" % (o.partner_id.id, o.id),
309                 'account_id': a,
310                 'type': 'in_invoice',
311                 'partner_id': o.partner_id.id,
312                 'currency_id': o.pricelist_id.currency_id.id,
313                 'address_invoice_id': o.partner_address_id.id,
314                 'address_contact_id': o.partner_address_id.id,
315                 'origin': o.name,
316                 'invoice_line': il,
317             }
318             inv_id = self.pool.get('account.invoice').create(cr, uid, inv, {'type':'in_invoice'})
319             self.pool.get('account.invoice').button_compute(cr, uid, [inv_id], {'type':'in_invoice'}, set_total=True)
320
321             self.write(cr, uid, [o.id], {'invoice_id': inv_id})
322             res = inv_id
323         return res
324
325     def has_stockable_product(self,cr, uid, ids, *args):
326         for order in self.browse(cr, uid, ids):
327             for order_line in order.order_line:
328                 if order_line.product_id and order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
329                     return True
330         return False
331
332     def action_cancel(self, cr, uid, ids, context={}):
333         ok = True
334         purchase_order_line_obj = self.pool.get('purchase.order.line')
335         for purchase in self.browse(cr, uid, ids):
336             for pick in purchase.picking_ids:
337                 if pick.state not in ('draft','cancel'):
338                     raise osv.except_osv(
339                         _('Could not cancel purchase order !'),
340                         _('You must first cancel all packings attached to this purchase order.'))
341             for pick in purchase.picking_ids:
342                 wf_service = netsvc.LocalService("workflow")
343                 wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_cancel', cr)
344             inv = purchase.invoice_id
345             if inv and inv.state not in ('cancel','draft'):
346                 raise osv.except_osv(
347                     _('Could not cancel this purchase order !'),
348                     _('You must first cancel all invoices attached to this purchase order.'))
349             if inv:
350                 wf_service = netsvc.LocalService("workflow")
351                 wf_service.trg_validate(uid, 'account.invoice', inv.id, 'invoice_cancel', cr)
352         self.write(cr,uid,ids,{'state':'cancel'})
353         return True
354
355     def action_picking_create(self,cr, uid, ids, *args):
356         picking_id = False
357         for order in self.browse(cr, uid, ids):
358             loc_id = order.partner_id.property_stock_supplier.id
359             istate = 'none'
360             if order.invoice_method=='picking':
361                 istate = '2binvoiced'
362             picking_id = self.pool.get('stock.picking').create(cr, uid, {
363                 'origin': order.name+((order.origin and (':'+order.origin)) or ''),
364                 'type': 'in',
365                 'address_id': order.dest_address_id.id or order.partner_address_id.id,
366                 'invoice_state': istate,
367                 'purchase_id': order.id,
368             })
369             for order_line in order.order_line:
370                 if not order_line.product_id:
371                     continue
372                 if order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
373                     dest = order.location_id.id
374                     self.pool.get('stock.move').create(cr, uid, {
375                         'name': 'PO:'+order_line.name,
376                         'product_id': order_line.product_id.id,
377                         'product_qty': order_line.product_qty,
378                         'product_uos_qty': order_line.product_qty,
379                         'product_uom': order_line.product_uom.id,
380                         'product_uos': order_line.product_uom.id,
381                         'date_planned': order_line.date_planned,
382                         'location_id': loc_id,
383                         'location_dest_id': dest,
384                         'picking_id': picking_id,
385                         'move_dest_id': order_line.move_dest_id.id,
386                         'state': 'assigned',
387                         'purchase_line_id': order_line.id,
388                     })
389                     if order_line.move_dest_id:
390                         self.pool.get('stock.move').write(cr, uid, [order_line.move_dest_id.id], {'location_id':order.location_id.id})
391             wf_service = netsvc.LocalService("workflow")
392             wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
393         return picking_id
394     def copy(self, cr, uid, id, default=None,context={}):
395         if not default:
396             default = {}
397         default.update({
398             'state':'draft',
399             'shipped':False,
400             'invoiced':False,
401             'invoice_id':False,
402             'picking_ids':[],
403             'name': self.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
404         })
405         return super(purchase_order, self).copy(cr, uid, id, default, context)
406
407 purchase_order()
408
409 class purchase_order_line(osv.osv):
410     def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
411         res = {}
412         cur_obj=self.pool.get('res.currency')
413         for line in self.browse(cr, uid, ids):
414             cur = line.order_id.pricelist_id.currency_id
415             res[line.id] = cur_obj.round(cr, uid, cur, line.price_unit * line.product_qty)
416         return res
417
418     _columns = {
419         'name': fields.char('Description', size=64, required=True),
420         'product_qty': fields.float('Quantity', required=True, digits=(16,2)),
421         'date_planned': fields.datetime('Scheduled date', required=True),
422         'taxes_id': fields.many2many('account.tax', 'purchase_order_taxe', 'ord_id', 'tax_id', 'Taxes'),
423         'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
424         'product_id': fields.many2one('product.product', 'Product', domain=[('purchase_ok','=',True)], change_default=True),
425         'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
426         'move_dest_id': fields.many2one('stock.move', 'Reservation Destination', ondelete='set null'),
427         'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
428         'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal'),
429         'notes': fields.text('Notes'),
430         'order_id': fields.many2one('purchase.order', 'Order Ref', select=True, required=True, ondelete='cascade'),
431         'account_analytic_id':fields.many2one('account.analytic.account', 'Analytic Account',),
432     }
433     _defaults = {
434         'product_qty': lambda *a: 1.0
435     }
436     _table = 'purchase_order_line'
437     _name = 'purchase.order.line'
438     _description = 'Purchase Order lines'
439     def copy(self, cr, uid, id, default=None,context={}):
440         if not default:
441             default = {}
442         default.update({'state':'draft', 'move_id':False})
443         return super(purchase_order_line, self).copy(cr, uid, id, default, context)
444
445     def product_id_change(self, cr, uid, ids, pricelist, product, qty, uom,
446             partner_id, date_order=False):
447         if not pricelist:
448             raise osv.except_osv(_('No Pricelist !'), _('You have to select a pricelist in the purchase form !\nPlease set one before choosing a product.'))
449         if not product:
450             return {'value': {'price_unit': 0.0, 'name':'','notes':'', 'product_uom' : False}, 'domain':{'product_uom':[]}}
451         prod= self.pool.get('product.product').browse(cr, uid,product)
452         lang=False
453         if partner_id:
454             lang=self.pool.get('res.partner').read(cr, uid, partner_id)['lang']
455         context={'lang':lang}
456         context['partner_id'] = partner_id
457
458         prod = self.pool.get('product.product').browse(cr, uid, product, context=context)
459         prod_uom_po = prod.uom_po_id.id
460         if not uom:
461             uom = prod_uom_po
462         if not date_order:
463             date_order = time.strftime('%Y-%m-%d')
464         price = self.pool.get('product.pricelist').price_get(cr,uid,[pricelist],
465                 product, qty or 1.0, partner_id, {
466                     'uom': uom,
467                     'date': date_order,
468                     })[pricelist]
469
470         qty = 1
471         seller_delay = 0
472         for s in prod.seller_ids:
473             seller_delay = s.delay
474             if s.name.id == partner_id:
475                 seller_delay = s.delay
476                 qty = s.qty
477         dt = (DateTime.now() + DateTime.RelativeDateTime(days=seller_delay or 0.0)).strftime('%Y-%m-%d %H:%M:%S')
478         prod_name = prod.partner_ref
479
480
481         res = {'value': {'price_unit': price, 'name':prod_name, 'taxes_id':map(lambda x: x.id, prod.supplier_taxes_id),
482             'date_planned': dt,'notes':prod.description_purchase,
483             'product_qty': qty,
484             'product_uom': uom}}
485         domain = {}
486
487         partner = self.pool.get('res.partner').browse(cr, uid, partner_id)
488         taxes = self.pool.get('account.tax').browse(cr, uid,map(lambda x: x.id, prod.supplier_taxes_id))
489         res['value']['taxes_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, partner, taxes)
490
491         res2 = self.pool.get('product.uom').read(cr, uid, [uom], ['category_id'])
492         res3 = prod.uom_id.category_id.id
493         domain = {'product_uom':[('category_id','=',res2[0]['category_id'][0])]}
494         if res2[0]['category_id'][0] != res3:
495             raise osv.except_osv(_('Wrong Product UOM !'), _('You have to select a product UOM in the same category than the purchase UOM of the product'))
496
497         res['domain'] = domain
498         return res
499
500     def product_uom_change(self, cr, uid, ids, pricelist, product, qty, uom,
501             partner_id, date_order=False):
502         res = self.product_id_change(cr, uid, ids, pricelist, product, qty, uom,
503                 partner_id, date_order=date_order)
504         if 'product_uom' in res['value']:
505             del res['value']['product_uom']
506         if not uom:
507             res['value']['price_unit'] = 0.0
508         return res
509 purchase_order_line()
510
511 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
512