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