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