[FIX] purchase: security file update: added rights for manager. Fixed rights for...
[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     def _invoiced(self, cursor, user, ids, name, arg, context=None):
139         res = {}
140         for purchase in self.browse(cursor, user, ids, context=context):
141             if purchase.invoice_id.reconciled:
142                 res[purchase.id] = purchase.invoice_id.reconciled
143             else:
144                 res[purchase.id] = False
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', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, help="Date on which this document has been created."),
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)],'done':[('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)],'done':[('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)],'done':[('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.function(_invoiced, method=True, string='Invoiced & Paid', type='boolean'),
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_all, method=True, digits=(16, int(config['price_accuracy'])), string='Untaxed Amount',
185             store={
186                 'purchase.order.line': (_get_order, None, 10),
187             }, multi="sums"),
188         'amount_tax': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Taxes',
189             store={
190                 'purchase.order.line': (_get_order, None, 10),
191             }, multi="sums"),
192         'amount_total': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Total',
193             store={
194                 'purchase.order.line': (_get_order, None, 10),
195             }, multi="sums"),
196         'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position')
197     }
198     _defaults = {
199         'date_order': lambda *a: time.strftime('%Y-%m-%d'),
200         'state': lambda *a: 'draft',
201         'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
202         'shipped': lambda *a: 0,
203         'invoice_method': lambda *a: 'order',
204         'invoiced': lambda *a: 0,
205         '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'],
206         '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,
207     }
208     _name = "purchase.order"
209     _description = "Purchase order"
210     _order = "name desc"
211
212     def unlink(self, cr, uid, ids, context=None):
213         purchase_orders = self.read(cr, uid, ids, ['state'])
214         unlink_ids = []
215         for s in purchase_orders:
216             if s['state'] in ['draft','cancel']:
217                 unlink_ids.append(s['id'])
218             else:
219                 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Purchase Order(s) which are in %s State!' % s['state']))
220
221         # TODO: temporary fix in 5.0, to remove in 5.2 when subflows support 
222         # automatically sending subflow.delete upon deletion
223         wf_service = netsvc.LocalService("workflow")
224         for id in unlink_ids:
225             wf_service.trg_validate(uid, 'purchase.order', id, 'purchase_cancel', cr)
226
227         return super(purchase_order, self).unlink(cr, uid, unlink_ids, context=context)
228
229     def button_dummy(self, cr, uid, ids, context={}):
230         return True
231
232     def onchange_dest_address_id(self, cr, uid, ids, adr_id):
233         if not adr_id:
234             return {}
235         part_id = self.pool.get('res.partner.address').read(cr, uid, [adr_id], ['partner_id'])[0]['partner_id'][0]
236         loc_id = self.pool.get('res.partner').browse(cr, uid, part_id).property_stock_customer.id
237         return {'value':{'location_id': loc_id, 'warehouse_id': False}}
238
239     def onchange_warehouse_id(self, cr, uid, ids, warehouse_id):
240         if not warehouse_id:
241             return {}
242         res = self.pool.get('stock.warehouse').read(cr, uid, [warehouse_id], ['lot_input_id'])[0]['lot_input_id'][0]
243         return {'value':{'location_id': res, 'dest_address_id': False}}
244
245     def onchange_partner_id(self, cr, uid, ids, part):
246         if not part:
247             return {'value':{'partner_address_id': False, 'fiscal_position': False}}
248         addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['default'])
249         part = self.pool.get('res.partner').browse(cr, uid, part)
250         pricelist = part.property_product_pricelist_purchase.id
251         fiscal_position = part.property_account_position and part.property_account_position.id or False
252         return {'value':{'partner_address_id': addr['default'], 'pricelist_id': pricelist, 'fiscal_position': fiscal_position}}
253
254     def wkf_approve_order(self, cr, uid, ids, context={}):
255         self.write(cr, uid, ids, {'state': 'approved', 'date_approve': time.strftime('%Y-%m-%d')})
256         return True
257
258     def wkf_confirm_order(self, cr, uid, ids, context={}):
259         for po in self.browse(cr, uid, ids):
260             if self.pool.get('res.partner.event.type').check(cr, uid, 'purchase_open'):
261                 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})
262         current_name = self.name_get(cr, uid, ids)[0][1]
263         for id in ids:
264             self.write(cr, uid, [id], {'state' : 'confirmed', 'validator' : uid})
265         return True
266
267     def wkf_warn_buyer(self, cr, uid, ids):
268         self.write(cr, uid, ids, {'state' : 'wait', 'validator' : uid})
269         request = pooler.get_pool(cr.dbname).get('res.request')
270         for po in self.browse(cr, uid, ids):
271             managers = []
272             for oline in po.order_line:
273                 manager = oline.product_id.product_manager
274                 if manager and not (manager.id in managers):
275                     managers.append(manager.id)
276             for manager_id in managers:
277                 request.create(cr, uid,
278                       {'name' : "Purchase amount over the limit",
279                        'act_from' : uid,
280                        'act_to' : manager_id,
281                        'body': 'Somebody has just confirmed a purchase with an amount over the defined limit',
282                        'ref_partner_id': po.partner_id.id,
283                        'ref_doc1': 'purchase.order,%d' % (po.id,),
284                        })
285     def inv_line_create(self, cr, uid, a, ol):
286         return (0, False, {
287             'name': ol.name,
288             'account_id': a,
289             'price_unit': ol.price_unit or 0.0,
290             'quantity': ol.product_qty,
291             'product_id': ol.product_id.id or False,
292             'uos_id': ol.product_uom.id or False,
293             'invoice_line_tax_id': [(6, 0, [x.id for x in ol.taxes_id])],
294             'account_analytic_id': ol.account_analytic_id.id,
295         })
296
297     def action_cancel_draft(self, cr, uid, ids, *args):
298         if not len(ids):
299             return False
300         self.write(cr, uid, ids, {'state':'draft','shipped':0})
301         wf_service = netsvc.LocalService("workflow")
302         for p_id in ids:
303             # Deleting the existing instance of workflow for PO
304             wf_service.trg_delete(uid, 'purchase.order', p_id, cr)            
305             wf_service.trg_create(uid, 'purchase.order', p_id, cr)
306         return True
307
308     def action_invoice_create(self, cr, uid, ids, *args):
309         res = False
310         journal_obj = self.pool.get('account.journal')
311         for o in self.browse(cr, uid, ids):
312             il = []
313             for ol in o.order_line:
314
315                 if ol.product_id:
316                     a = ol.product_id.product_tmpl_id.property_account_expense.id
317                     if not a:
318                         a = ol.product_id.categ_id.property_account_expense_categ.id
319                     if not a:
320                         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,))
321                 else:
322                     a = self.pool.get('ir.property').get(cr, uid, 'property_account_expense_categ', 'product.category')
323                 fpos = o.fiscal_position or False
324                 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
325                 il.append(self.inv_line_create(cr, uid, a, ol))
326
327             a = o.partner_id.property_account_payable.id
328             journal_ids = journal_obj.search(cr, uid, [('type', '=','purchase')], limit=1)
329             inv = {
330                 'name': o.partner_ref or o.name,
331                 'reference': "P%dPO%d" % (o.partner_id.id, o.id),
332                 'account_id': a,
333                 'type': 'in_invoice',
334                 'partner_id': o.partner_id.id,
335                 'currency_id': o.pricelist_id.currency_id.id,
336                 'address_invoice_id': o.partner_address_id.id,
337                 'address_contact_id': o.partner_address_id.id,
338                 'journal_id': len(journal_ids) and journal_ids[0] or False,
339                 'origin': o.name,
340                 'invoice_line': il,
341                 'fiscal_position': o.partner_id.property_account_position.id,
342                 'payment_term':o.partner_id.property_payment_term and o.partner_id.property_payment_term.id or False,
343             }
344             inv_id = self.pool.get('account.invoice').create(cr, uid, inv, {'type':'in_invoice'})
345             self.pool.get('account.invoice').button_compute(cr, uid, [inv_id], {'type':'in_invoice'}, set_total=True)
346
347             self.write(cr, uid, [o.id], {'invoice_id': inv_id})
348             res = inv_id
349         return res
350
351     def has_stockable_product(self,cr, uid, ids, *args):
352         for order in self.browse(cr, uid, ids):
353             for order_line in order.order_line:
354                 if order_line.product_id and order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
355                     return True
356         return False
357
358     def action_cancel(self, cr, uid, ids, context={}):
359         ok = True
360         purchase_order_line_obj = self.pool.get('purchase.order.line')
361         for purchase in self.browse(cr, uid, ids):
362             for pick in purchase.picking_ids:
363                 if pick.state not in ('draft','cancel'):
364                     raise osv.except_osv(
365                         _('Could not cancel purchase order !'),
366                         _('You must first cancel all packing attached to this purchase order.'))
367             for pick in purchase.picking_ids:
368                 wf_service = netsvc.LocalService("workflow")
369                 wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_cancel', cr)
370             inv = purchase.invoice_id
371             if inv and inv.state not in ('cancel','draft'):
372                 raise osv.except_osv(
373                     _('Could not cancel this purchase order !'),
374                     _('You must first cancel all invoices attached to this purchase order.'))
375             if inv:
376                 wf_service = netsvc.LocalService("workflow")
377                 wf_service.trg_validate(uid, 'account.invoice', inv.id, 'invoice_cancel', cr)
378         self.write(cr,uid,ids,{'state':'cancel'})
379         return True
380
381     def action_picking_create(self,cr, uid, ids, *args):
382         picking_id = False
383         for order in self.browse(cr, uid, ids):
384             loc_id = order.partner_id.property_stock_supplier.id
385             istate = 'none'
386             if order.invoice_method=='picking':
387                 istate = '2binvoiced'
388             picking_id = self.pool.get('stock.picking').create(cr, uid, {
389                 'origin': order.name+((order.origin and (':'+order.origin)) or ''),
390                 'type': 'in',
391                 'address_id': order.dest_address_id.id or order.partner_address_id.id,
392                 'invoice_state': istate,
393                 'purchase_id': order.id,
394             })
395             for order_line in order.order_line:
396                 if not order_line.product_id:
397                     continue
398                 if order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
399                     dest = order.location_id.id
400                     move = self.pool.get('stock.move').create(cr, uid, {
401                         'name': 'PO:'+order_line.name,
402                         'product_id': order_line.product_id.id,
403                         'product_qty': order_line.product_qty,
404                         'product_uos_qty': order_line.product_qty,
405                         'product_uom': order_line.product_uom.id,
406                         'product_uos': order_line.product_uom.id,
407                         'date_planned': order_line.date_planned,
408                         'location_id': loc_id,
409                         'location_dest_id': dest,
410                         'picking_id': picking_id,
411                         'move_dest_id': order_line.move_dest_id.id,
412                         'state': 'draft',
413                         'purchase_line_id': order_line.id,
414                     })
415                     if order_line.move_dest_id:
416                         self.pool.get('stock.move').write(cr, uid, [order_line.move_dest_id.id], {'location_id':order.location_id.id})
417                     self.pool.get('stock.move').action_confirm(cr, uid, [move])
418                     self.pool.get('stock.move').force_assign(cr,uid, [move])
419             wf_service = netsvc.LocalService("workflow")
420             wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
421         return picking_id
422     def copy(self, cr, uid, id, default=None,context={}):
423         if not default:
424             default = {}
425         default.update({
426             'state':'draft',
427             'shipped':False,
428             'invoiced':False,
429             'invoice_id':False,
430             'picking_ids':[],
431             'name': self.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
432         })
433         return super(purchase_order, self).copy(cr, uid, id, default, context)
434
435 purchase_order()
436
437 class purchase_order_line(osv.osv):
438     def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
439         res = {}
440         cur_obj=self.pool.get('res.currency')
441         for line in self.browse(cr, uid, ids):
442             cur = line.order_id.pricelist_id.currency_id
443             res[line.id] = cur_obj.round(cr, uid, cur, line.price_unit * line.product_qty)
444         return res
445
446     _columns = {
447         'name': fields.char('Description', size=256, required=True),
448         'product_qty': fields.float('Quantity', required=True, digits=(16,2)),
449         'date_planned': fields.datetime('Scheduled date', required=True),
450         'taxes_id': fields.many2many('account.tax', 'purchase_order_taxe', 'ord_id', 'tax_id', 'Taxes'),
451         'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
452         'product_id': fields.many2one('product.product', 'Product', domain=[('purchase_ok','=',True)], change_default=True),
453         'move_ids': fields.one2many('stock.move', 'purchase_line_id', 'Reservation', readonly=True, ondelete='set null'),
454         'move_dest_id': fields.many2one('stock.move', 'Reservation Destination', ondelete='set null'),
455         'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
456         'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal', digits=(16, int(config['price_accuracy']))),
457         'notes': fields.text('Notes'),
458         'order_id': fields.many2one('purchase.order', 'Order Ref', select=True, required=True, ondelete='cascade'),
459         'account_analytic_id':fields.many2one('account.analytic.account', 'Analytic Account',),
460     }
461     _defaults = {
462         'product_qty': lambda *a: 1.0
463     }
464     _table = 'purchase_order_line'
465     _name = 'purchase.order.line'
466     _description = 'Purchase Order lines'
467     def copy_data(self, cr, uid, id, default=None,context={}):
468         if not default:
469             default = {}
470         default.update({'state':'draft', 'move_ids':[]})
471         return super(purchase_order_line, self).copy_data(cr, uid, id, default, context)
472
473     def product_id_change(self, cr, uid, ids, pricelist, product, qty, uom,
474             partner_id, date_order=False, fiscal_position=False):
475         if not pricelist:
476             raise osv.except_osv(_('No Pricelist !'), _('You have to select a pricelist in the purchase form !\nPlease set one before choosing a product.'))
477         if not  partner_id:
478             raise osv.except_osv(_('No Partner!'), _('You have to select a partner in the purchase form !\nPlease set one partner before choosing a product.'))
479         if not product:
480             return {'value': {'price_unit': 0.0, 'name':'','notes':'', 'product_uom' : False}, 'domain':{'product_uom':[]}}
481         prod= self.pool.get('product.product').browse(cr, uid,product)
482         lang=False
483         if partner_id:
484             lang=self.pool.get('res.partner').read(cr, uid, partner_id)['lang']
485         context={'lang':lang}
486         context['partner_id'] = partner_id
487
488         prod = self.pool.get('product.product').browse(cr, uid, product, context=context)
489         prod_uom_po = prod.uom_po_id.id
490         if not uom:
491             uom = prod_uom_po
492         if not date_order:
493             date_order = time.strftime('%Y-%m-%d')
494         
495         qty = qty or 1.0
496         seller_delay = 0
497         for s in prod.seller_ids:
498             if s.name.id == partner_id:
499                 seller_delay = s.delay
500                 temp_qty = s.qty # supplier _qty assigned to temp
501                 if qty < temp_qty: # If the supplier quantity is greater than entered from user, set minimal.
502                     qty = temp_qty
503
504         price = self.pool.get('product.pricelist').price_get(cr,uid,[pricelist],
505                 product, qty or 1.0, partner_id, {
506                     'uom': uom,
507                     'date': date_order,
508                     })[pricelist]
509         dt = (DateTime.now() + DateTime.RelativeDateTime(days=seller_delay or 0.0)).strftime('%Y-%m-%d %H:%M:%S')
510         prod_name = prod.partner_ref
511
512
513         res = {'value': {'price_unit': price, 'name':prod_name, 'taxes_id':map(lambda x: x.id, prod.supplier_taxes_id),
514             'date_planned': dt,'notes':prod.description_purchase,
515             'product_qty': qty,
516             'product_uom': uom}}
517         domain = {}
518
519         partner = self.pool.get('res.partner').browse(cr, uid, partner_id)
520         taxes = self.pool.get('account.tax').browse(cr, uid,map(lambda x: x.id, prod.supplier_taxes_id))
521         fpos = fiscal_position and self.pool.get('account.fiscal.position').browse(cr, uid, fiscal_position) or False
522         res['value']['taxes_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
523
524         res2 = self.pool.get('product.uom').read(cr, uid, [uom], ['category_id'])
525         res3 = prod.uom_id.category_id.id
526         domain = {'product_uom':[('category_id','=',res2[0]['category_id'][0])]}
527         if res2[0]['category_id'][0] != res3:
528             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'))
529
530         res['domain'] = domain
531         return res
532
533     def product_uom_change(self, cr, uid, ids, pricelist, product, qty, uom,
534             partner_id, date_order=False):
535         res = self.product_id_change(cr, uid, ids, pricelist, product, qty, uom,
536                 partner_id, date_order=date_order)
537         if 'product_uom' in res['value']:
538             del res['value']['product_uom']
539         if not uom:
540             res['value']['price_unit'] = 0.0
541         return res
542 purchase_order_line()
543
544 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
545