[FIX] MRP/Purchase : Seller delay has to be converted into integer for date calculations.
[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             todo_moves = []
396             for order_line in order.order_line:
397                 if not order_line.product_id:
398                     continue
399                 if order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
400                     dest = order.location_id.id
401                     move = self.pool.get('stock.move').create(cr, uid, {
402                         'name': 'PO:'+order_line.name,
403                         'product_id': order_line.product_id.id,
404                         'product_qty': order_line.product_qty,
405                         'product_uos_qty': order_line.product_qty,
406                         'product_uom': order_line.product_uom.id,
407                         'product_uos': order_line.product_uom.id,
408                         'date_planned': order_line.date_planned,
409                         'location_id': loc_id,
410                         'location_dest_id': dest,
411                         'picking_id': picking_id,
412                         'move_dest_id': order_line.move_dest_id.id,
413                         'state': 'draft',
414                         'purchase_line_id': order_line.id,
415                     })
416                     if order_line.move_dest_id:
417                         self.pool.get('stock.move').write(cr, uid, [order_line.move_dest_id.id], {'location_id':order.location_id.id})
418                     todo_moves.append(move)    
419             self.pool.get('stock.move').action_confirm(cr, uid, todo_moves)
420             self.pool.get('stock.move').force_assign(cr, uid, todo_moves)
421             wf_service = netsvc.LocalService("workflow")
422             wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
423         return picking_id
424     def copy(self, cr, uid, id, default=None,context={}):
425         if not default:
426             default = {}
427         default.update({
428             'state':'draft',
429             'shipped':False,
430             'invoiced':False,
431             'invoice_id':False,
432             'picking_ids':[],
433             'name': self.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
434         })
435         return super(purchase_order, self).copy(cr, uid, id, default, context)
436
437 purchase_order()
438
439 class purchase_order_line(osv.osv):
440     def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
441         res = {}
442         cur_obj=self.pool.get('res.currency')
443         for line in self.browse(cr, uid, ids):
444             cur = line.order_id.pricelist_id.currency_id
445             res[line.id] = cur_obj.round(cr, uid, cur, line.price_unit * line.product_qty)
446         return res
447
448     _columns = {
449         'name': fields.char('Description', size=256, required=True),
450         'product_qty': fields.float('Quantity', required=True, digits=(16,2)),
451         'date_planned': fields.datetime('Scheduled date', required=True),
452         'taxes_id': fields.many2many('account.tax', 'purchase_order_taxe', 'ord_id', 'tax_id', 'Taxes'),
453         'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
454         'product_id': fields.many2one('product.product', 'Product', domain=[('purchase_ok','=',True)], change_default=True),
455         'move_ids': fields.one2many('stock.move', 'purchase_line_id', 'Reservation', readonly=True, ondelete='set null'),
456         'move_dest_id': fields.many2one('stock.move', 'Reservation Destination', ondelete='set null'),
457         'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
458         'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal', digits=(16, int(config['price_accuracy']))),
459         'notes': fields.text('Notes'),
460         'order_id': fields.many2one('purchase.order', 'Order Ref', select=True, required=True, ondelete='cascade'),
461         'account_analytic_id':fields.many2one('account.analytic.account', 'Analytic Account',),
462     }
463     _defaults = {
464         'product_qty': lambda *a: 1.0
465     }
466     _table = 'purchase_order_line'
467     _name = 'purchase.order.line'
468     _description = 'Purchase Order lines'
469     def copy_data(self, cr, uid, id, default=None,context={}):
470         if not default:
471             default = {}
472         default.update({'state':'draft', 'move_ids':[]})
473         return super(purchase_order_line, self).copy_data(cr, uid, id, default, context)
474
475     def product_id_change(self, cr, uid, ids, pricelist, product, qty, uom,
476             partner_id, date_order=False, fiscal_position=False):
477         if not pricelist:
478             raise osv.except_osv(_('No Pricelist !'), _('You have to select a pricelist in the purchase form !\nPlease set one before choosing a product.'))
479         if not  partner_id:
480             raise osv.except_osv(_('No Partner!'), _('You have to select a partner in the purchase form !\nPlease set one partner before choosing a product.'))
481         if not product:
482             return {'value': {'price_unit': 0.0, 'name':'','notes':'', 'product_uom' : False}, 'domain':{'product_uom':[]}}
483         prod= self.pool.get('product.product').browse(cr, uid,product)
484         lang=False
485         if partner_id:
486             lang=self.pool.get('res.partner').read(cr, uid, partner_id)['lang']
487         context={'lang':lang}
488         context['partner_id'] = partner_id
489
490         prod = self.pool.get('product.product').browse(cr, uid, product, context=context)
491         prod_uom_po = prod.uom_po_id.id
492         if not uom:
493             uom = prod_uom_po
494         if not date_order:
495             date_order = time.strftime('%Y-%m-%d')
496         
497         qty = qty or 1.0
498         seller_delay = 0
499         for s in prod.seller_ids:
500             if s.name.id == partner_id:
501                 seller_delay = s.delay
502                 temp_qty = s.qty # supplier _qty assigned to temp
503                 if qty < temp_qty: # If the supplier quantity is greater than entered from user, set minimal.
504                     qty = temp_qty
505
506         price = self.pool.get('product.pricelist').price_get(cr,uid,[pricelist],
507                 product, qty or 1.0, partner_id, {
508                     'uom': uom,
509                     'date': date_order,
510                     })[pricelist]
511         dt = (DateTime.now() + DateTime.RelativeDateTime(days=int(seller_delay) or 0.0)).strftime('%Y-%m-%d %H:%M:%S')
512         prod_name = prod.partner_ref
513
514
515         res = {'value': {'price_unit': price, 'name':prod_name, 'taxes_id':map(lambda x: x.id, prod.supplier_taxes_id),
516             'date_planned': dt,'notes':prod.description_purchase,
517             'product_qty': qty,
518             'product_uom': uom}}
519         domain = {}
520
521         partner = self.pool.get('res.partner').browse(cr, uid, partner_id)
522         taxes = self.pool.get('account.tax').browse(cr, uid,map(lambda x: x.id, prod.supplier_taxes_id))
523         fpos = fiscal_position and self.pool.get('account.fiscal.position').browse(cr, uid, fiscal_position) or False
524         res['value']['taxes_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
525
526         res2 = self.pool.get('product.uom').read(cr, uid, [uom], ['category_id'])
527         res3 = prod.uom_id.category_id.id
528         domain = {'product_uom':[('category_id','=',res2[0]['category_id'][0])]}
529         if res2[0]['category_id'][0] != res3:
530             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'))
531
532         res['domain'] = domain
533         return res
534
535     def product_uom_change(self, cr, uid, ids, pricelist, product, qty, uom,
536             partner_id, date_order=False):
537         res = self.product_id_change(cr, uid, ids, pricelist, product, qty, uom,
538                 partner_id, date_order=date_order)
539         if 'product_uom' in res['value']:
540             del res['value']['product_uom']
541         if not uom:
542             res['value']['price_unit'] = 0.0
543         return res
544 purchase_order_line()
545
546 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
547