a7e516853e7163f05ed00b3f181789e8dee01fe0
[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 import time
23 from datetime import datetime
24 from dateutil.relativedelta import relativedelta
25
26 from osv import osv, fields
27 import netsvc
28 import pooler
29 from tools.translate import _
30 import decimal_precision as dp
31 from osv.orm import browse_record, browse_null
32 from tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
33
34 #
35 # Model definition
36 #
37 class purchase_order(osv.osv):
38
39     def _amount_all(self, cr, uid, ids, field_name, arg, context=None):
40         res = {}
41         cur_obj=self.pool.get('res.currency')
42         for order in self.browse(cr, uid, ids, context=context):
43             res[order.id] = {
44                 'amount_untaxed': 0.0,
45                 'amount_tax': 0.0,
46                 'amount_total': 0.0,
47             }
48             val = val1 = 0.0
49             cur = order.pricelist_id.currency_id
50             for line in order.order_line:
51                val1 += line.price_subtotal
52                for c in self.pool.get('account.tax').compute_all(cr, uid, line.taxes_id, line.price_unit, line.product_qty, line.product_id.id, order.partner_id)['taxes']:
53                     val += c.get('amount', 0.0)
54             res[order.id]['amount_tax']=cur_obj.round(cr, uid, cur, val)
55             res[order.id]['amount_untaxed']=cur_obj.round(cr, uid, cur, val1)
56             res[order.id]['amount_total']=res[order.id]['amount_untaxed'] + res[order.id]['amount_tax']
57         return res
58
59     def _set_minimum_planned_date(self, cr, uid, ids, name, value, arg, context=None):
60         if not value: return False
61         if type(ids)!=type([]):
62             ids=[ids]
63         for po in self.browse(cr, uid, ids, context=context):
64             if po.order_line:
65                 cr.execute("""update purchase_order_line set
66                         date_planned=%s
67                     where
68                         order_id=%s and
69                         (date_planned=%s or date_planned<%s)""", (value,po.id,po.minimum_planned_date,value))
70             cr.execute("""update purchase_order set
71                     minimum_planned_date=%s where id=%s""", (value, po.id))
72         return True
73
74     def _minimum_planned_date(self, cr, uid, ids, field_name, arg, context=None):
75         res={}
76         purchase_obj=self.browse(cr, uid, ids, context=context)
77         for purchase in purchase_obj:
78             res[purchase.id] = False
79             if purchase.order_line:
80                 min_date=purchase.order_line[0].date_planned
81                 for line in purchase.order_line:
82                     if line.date_planned < min_date:
83                         min_date=line.date_planned
84                 res[purchase.id]=min_date
85         return res
86
87
88     def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
89         res = {}
90         for purchase in self.browse(cursor, user, ids, context=context):
91             tot = 0.0
92             for invoice in purchase.invoice_ids:
93                 if invoice.state not in ('draft','cancel'):
94                     tot += invoice.amount_untaxed
95             if purchase.amount_untaxed:
96                 res[purchase.id] = tot * 100.0 / purchase.amount_untaxed
97             else:
98                 res[purchase.id] = 0.0
99         return res
100
101     def _shipped_rate(self, cr, uid, ids, name, arg, context=None):
102         if not ids: return {}
103         res = {}
104         for id in ids:
105             res[id] = [0.0,0.0]
106         cr.execute('''SELECT
107                 p.purchase_id,sum(m.product_qty), m.state
108             FROM
109                 stock_move m
110             LEFT JOIN
111                 stock_picking p on (p.id=m.picking_id)
112             WHERE
113                 p.purchase_id IN %s GROUP BY m.state, p.purchase_id''',(tuple(ids),))
114         for oid,nbr,state in cr.fetchall():
115             if state=='cancel':
116                 continue
117             if state=='done':
118                 res[oid][0] += nbr or 0.0
119                 res[oid][1] += nbr or 0.0
120             else:
121                 res[oid][1] += nbr or 0.0
122         for r in res:
123             if not res[r][1]:
124                 res[r] = 0.0
125             else:
126                 res[r] = 100.0 * res[r][0] / res[r][1]
127         return res
128
129     def _get_order(self, cr, uid, ids, context=None):
130         result = {}
131         for line in self.pool.get('purchase.order.line').browse(cr, uid, ids, context=context):
132             result[line.order_id.id] = True
133         return result.keys()
134
135     def _invoiced(self, cursor, user, ids, name, arg, context=None):
136         res = {}
137         for purchase in self.browse(cursor, user, ids, context=context):
138             invoiced = False
139             if purchase.invoiced_rate == 100.00:
140                 invoiced = True
141             res[purchase.id] = invoiced
142         return res
143
144     STATE_SELECTION = [
145         ('draft', 'Request for Quotation'),
146         ('wait', 'Waiting'),
147         ('confirmed', 'Waiting Approval'),
148         ('approved', 'Approved'),
149         ('except_picking', 'Shipping Exception'),
150         ('except_invoice', 'Invoice Exception'),
151         ('done', 'Done'),
152         ('cancel', 'Cancelled')
153     ]
154     _columns = {
155         'name': fields.char('Order Reference', size=64, required=True, select=True, help="unique number of the purchase order,computed automatically when the purchase order is created"),
156         'origin': fields.char('Source Document', size=64,
157             help="Reference of the document that generated this purchase order request."
158         ),
159         'partner_ref': fields.char('Supplier Reference', states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, size=64),
160         'date_order':fields.date('Order Date', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}, select=True, help="Date on which this document has been created."),
161         'date_approve':fields.date('Date Approved', readonly=1, select=True, help="Date on which purchase order has been approved"),
162         'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, change_default=True),
163         'dest_address_id':fields.many2one('res.partner', 'Customer Address (Direct Delivery)',
164             states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},
165             help="Put an address if you want to deliver directly from the supplier to the customer." \
166                 "In this case, it will remove the warehouse link and set the customer location."
167         ),
168         'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}),
169         'location_id': fields.many2one('stock.location', 'Destination', required=True, domain=[('usage','<>','view')]),
170         '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."),
171         'state': fields.selection(STATE_SELECTION, '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),
172         'order_line': fields.one2many('purchase.order.line', 'order_id', 'Order Lines', states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
173         'validator' : fields.many2one('res.users', 'Validated by', readonly=True),
174         'notes': fields.text('Notes'),
175         'invoice_ids': fields.many2many('account.invoice', 'purchase_invoice_rel', 'purchase_id', 'invoice_id', 'Invoices', help="Invoices generated for a purchase order"),
176         '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"),
177         'shipped':fields.boolean('Received', readonly=True, select=True, help="It indicates that a picking has been done"),
178         'shipped_rate': fields.function(_shipped_rate, string='Received', type='float'),
179         'invoiced': fields.function(_invoiced, string='Invoiced & Paid', type='boolean', help="It indicates that an invoice has been paid"),
180         'invoiced_rate': fields.function(_invoiced_rate, string='Invoiced', type='float'),
181         'invoice_method': fields.selection([('manual','Based on Purchase Order lines'),('order','Based on generated draft invoice'),('picking','Based on receptions')], 'Invoicing Control', required=True,
182             help="Based on Purchase Order lines: place individual lines in 'Invoice Control > Based on P.O. lines' from where you can selectively create an invoice.\n" \
183                 "Based on generated invoice: create a draft invoice you can validate later.\n" \
184                 "Based on receptions: let you create an invoice when receptions are validated."
185         ),
186         'minimum_planned_date':fields.function(_minimum_planned_date, fnct_inv=_set_minimum_planned_date, string='Expected Date', type='date', select=True, help="This is computed as the minimum scheduled date of all purchase order lines' products.",
187             store = {
188                 'purchase.order.line': (_get_order, ['date_planned'], 10),
189             }
190         ),
191         'amount_untaxed': fields.function(_amount_all, digits_compute= dp.get_precision('Purchase Price'), string='Untaxed Amount',
192             store={
193                 'purchase.order.line': (_get_order, None, 10),
194             }, multi="sums", help="The amount without tax"),
195         'amount_tax': fields.function(_amount_all, digits_compute= dp.get_precision('Purchase Price'), string='Taxes',
196             store={
197                 'purchase.order.line': (_get_order, None, 10),
198             }, multi="sums", help="The tax amount"),
199         'amount_total': fields.function(_amount_all, digits_compute= dp.get_precision('Purchase Price'), string='Total',
200             store={
201                 'purchase.order.line': (_get_order, None, 10),
202             }, multi="sums",help="The total amount"),
203         'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position'),
204         'product_id': fields.related('order_line','product_id', type='many2one', relation='product.product', string='Product'),
205         'create_uid':  fields.many2one('res.users', 'Responsible'),
206         'company_id': fields.many2one('res.company','Company',required=True,select=1),
207     }
208     _defaults = {
209         'date_order': fields.date.context_today,
210         'state': 'draft',
211         'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
212         'shipped': 0,
213         'invoice_method': 'order',
214         'invoiced': 0,
215         '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,
216         'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'purchase.order', context=c),
217     }
218     _sql_constraints = [
219         ('name_uniq', 'unique(name, company_id)', 'Order Reference must be unique per Company!'),
220     ]
221     _name = "purchase.order"
222     _inherit = ['mail.thread']
223     _description = "Purchase Order"
224     _order = "name desc"
225     
226     def create(self, cr, uid, vals, context=None):
227         order =  super(purchase_order, self).create(cr, uid, vals, context=context)
228         if order:
229             self.create_send_note(cr, uid, [order], context=context)
230         return order
231     
232     def unlink(self, cr, uid, ids, context=None):
233         purchase_orders = self.read(cr, uid, ids, ['state'], context=context)
234         unlink_ids = []
235         for s in purchase_orders:
236             if s['state'] in ['draft','cancel']:
237                 unlink_ids.append(s['id'])
238             else:
239                 raise osv.except_osv(_('Invalid action !'), _('In order to delete a purchase order, it must be cancelled first!'))
240
241         # TODO: temporary fix in 5.0, to remove in 5.2 when subflows support
242         # automatically sending subflow.delete upon deletion
243         wf_service = netsvc.LocalService("workflow")
244         for id in unlink_ids:
245             wf_service.trg_validate(uid, 'purchase.order', id, 'purchase_cancel', cr)
246
247         return super(purchase_order, self).unlink(cr, uid, unlink_ids, context=context)
248
249     def button_dummy(self, cr, uid, ids, context=None):
250         return True
251
252     def onchange_dest_address_id(self, cr, uid, ids, address_id):
253         if not address_id:
254             return {}
255         address = self.pool.get('res.partner')
256         values = {'warehouse_id': False}
257         supplier = address.browse(cr, uid, address_id)
258         if supplier:
259             location_id = supplier.property_stock_customer.id
260             values.update({'location_id': location_id})
261         return {'value':values}
262
263     def onchange_warehouse_id(self, cr, uid, ids, warehouse_id):
264         if not warehouse_id:
265             return {}
266         warehouse = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id)
267         return {'value':{'location_id': warehouse.lot_input_id.id, 'dest_address_id': False}}
268
269     def onchange_partner_id(self, cr, uid, ids, partner_id):
270         partner = self.pool.get('res.partner')
271         if not partner_id:
272             return {'value':{'fiscal_position': False}}
273         supplier_address = partner.address_get(cr, uid, [partner_id], ['default'])
274         supplier = partner.browse(cr, uid, partner_id)
275         pricelist = supplier.property_product_pricelist_purchase.id
276         fiscal_position = supplier.property_account_position and supplier.property_account_position.id or False
277         return {'value':{'pricelist_id': pricelist, 'fiscal_position': fiscal_position}}
278
279     def wkf_approve_order(self, cr, uid, ids, context=None):
280         self.write(cr, uid, ids, {'state': 'approved', 'date_approve': fields.date.context_today(self,cr,uid,context=context)})
281         return True
282
283     #TODO: implement messages system
284     def wkf_confirm_order(self, cr, uid, ids, context=None):
285         todo = []
286         for po in self.browse(cr, uid, ids, context=context):
287             if not po.order_line:
288                 raise osv.except_osv(_('Error !'),_('You cannot confirm a purchase order without any lines.'))
289             for line in po.order_line:
290                 if line.state=='draft':
291                     todo.append(line.id)
292             message = _("Purchase order '%s' is confirmed.") % (po.name,)
293             self.log(cr, uid, po.id, message)
294 #        current_name = self.name_get(cr, uid, ids)[0][1]
295         self.pool.get('purchase.order.line').action_confirm(cr, uid, todo, context)
296         for id in ids:
297             self.write(cr, uid, [id], {'state' : 'confirmed', 'validator' : uid})
298         self.confirm_send_note(cr, uid, ids, context)
299         return True
300
301     def _prepare_inv_line(self, cr, uid, account_id, order_line, context=None):
302         """Collects require data from purchase order line that is used to create invoice line
303         for that purchase order line
304         :param account_id: Expense account of the product of PO line if any.
305         :param browse_record order_line: Purchase order line browse record
306         :return: Value for fields of invoice lines.
307         :rtype: dict
308         """
309         return {
310             'name': order_line.name,
311             'account_id': account_id,
312             'price_unit': order_line.price_unit or 0.0,
313             'quantity': order_line.product_qty,
314             'product_id': order_line.product_id.id or False,
315             'uos_id': order_line.product_uom.id or False,
316             'invoice_line_tax_id': [(6, 0, [x.id for x in order_line.taxes_id])],
317             'account_analytic_id': order_line.account_analytic_id.id or False,
318         }
319
320     def action_cancel_draft(self, cr, uid, ids, *args):
321         if not len(ids):
322             return False
323         self.write(cr, uid, ids, {'state':'draft','shipped':0})
324         wf_service = netsvc.LocalService("workflow")
325         for p_id in ids:
326             # Deleting the existing instance of workflow for PO
327             wf_service.trg_delete(uid, 'purchase.order', p_id, cr)
328             wf_service.trg_create(uid, 'purchase.order', p_id, cr)
329         for (id,name) in self.name_get(cr, uid, ids):
330             message = _("Purchase order '%s' has been set in draft state.") % name
331             self.log(cr, uid, id, message)
332         return True
333
334     def action_invoice_create(self, cr, uid, ids, context=None):
335         """Generates invoice for given ids of purchase orders and links that invoice ID to purchase order.
336         :param ids: list of ids of purchase orders.
337         :return: ID of created invoice.
338         :rtype: int
339         """
340         res = False
341
342         journal_obj = self.pool.get('account.journal')
343         inv_obj = self.pool.get('account.invoice')
344         inv_line_obj = self.pool.get('account.invoice.line')
345         fiscal_obj = self.pool.get('account.fiscal.position')
346         property_obj = self.pool.get('ir.property')
347
348         for order in self.browse(cr, uid, ids, context=context):
349             pay_acc_id = order.partner_id.property_account_payable.id
350             journal_ids = journal_obj.search(cr, uid, [('type', '=','purchase'),('company_id', '=', order.company_id.id)], limit=1)
351             if not journal_ids:
352                 raise osv.except_osv(_('Error !'),
353                     _('There is no purchase journal defined for this company: "%s" (id:%d)') % (order.company_id.name, order.company_id.id))
354
355             # generate invoice line correspond to PO line and link that to created invoice (inv_id) and PO line
356             inv_lines = []
357             for po_line in order.order_line:
358                 if po_line.product_id:
359                     acc_id = po_line.product_id.product_tmpl_id.property_account_expense.id
360                     if not acc_id:
361                         acc_id = po_line.product_id.categ_id.property_account_expense_categ.id
362                     if not acc_id:
363                         raise osv.except_osv(_('Error !'), _('There is no expense account defined for this product: "%s" (id:%d)') % (po_line.product_id.name, po_line.product_id.id,))
364                 else:
365                     acc_id = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category').id
366                 fpos = order.fiscal_position or False
367                 acc_id = fiscal_obj.map_account(cr, uid, fpos, acc_id)
368
369                 inv_line_data = self._prepare_inv_line(cr, uid, acc_id, po_line, context=context)
370                 inv_line_id = inv_line_obj.create(cr, uid, inv_line_data, context=context)
371                 inv_lines.append(inv_line_id)
372
373                 po_line.write({'invoiced':True, 'invoice_lines': [(4, inv_line_id)]}, context=context)
374
375             # get invoice data and create invoice
376             inv_data = {
377                 'name': order.partner_ref or order.name,
378                 'reference': order.partner_ref or order.name,
379                 'account_id': pay_acc_id,
380                 'type': 'in_invoice',
381                 'partner_id': order.partner_id.id,
382                 'currency_id': order.pricelist_id.currency_id.id,
383                 'journal_id': len(journal_ids) and journal_ids[0] or False,
384                 'invoice_line': [(6, 0, inv_lines)],
385                 'origin': order.name,
386                 'fiscal_position': order.fiscal_position.id or order.partner_id.property_account_position.id,
387                 'payment_term': order.partner_id.property_payment_term and order.partner_id.property_payment_term.id or False,
388                 'company_id': order.company_id.id,
389             }
390             inv_id = inv_obj.create(cr, uid, inv_data, context=context)
391
392             # compute the invoice
393             inv_obj.button_compute(cr, uid, [inv_id], context=context, set_total=True)
394
395             # Link this new invoice to related purchase order
396             order.write({'invoice_ids': [(4, inv_id)]}, context=context)
397             res = inv_id
398         if res:
399             self.invoice_send_note(cr, uid, ids, res, context)
400         return res
401     
402     def invoice_done(self, cr, uid, ids, context=None):
403         self.write(cr, uid, ids, {'state':'approved'}, context=context)
404         self.invoice_done_send_note(cr, uid, ids, context=context)
405         return True
406         
407     def has_stockable_product(self,cr, uid, ids, *args):
408         for order in self.browse(cr, uid, ids):
409             for order_line in order.order_line:
410                 if order_line.product_id and order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
411                     return True
412         return False
413
414     def action_cancel(self, cr, uid, ids, context=None):
415         wf_service = netsvc.LocalService("workflow")
416         for purchase in self.browse(cr, uid, ids, context=context):
417             for pick in purchase.picking_ids:
418                 if pick.state not in ('draft','cancel'):
419                     raise osv.except_osv(
420                         _('Unable to cancel this purchase order!'),
421                         _('You must first cancel all receptions related to this purchase order.'))
422             for pick in purchase.picking_ids:
423                 wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_cancel', cr)
424             for inv in purchase.invoice_ids:
425                 if inv and inv.state not in ('cancel','draft'):
426                     raise osv.except_osv(
427                         _('Unable to cancel this purchase order!'),
428                         _('You must first cancel all invoices related to this purchase order.'))
429                 if inv:
430                     wf_service.trg_validate(uid, 'account.invoice', inv.id, 'invoice_cancel', cr)
431         self.write(cr,uid,ids,{'state':'cancel'})
432
433         for (id, name) in self.name_get(cr, uid, ids):
434             wf_service.trg_validate(uid, 'purchase.order', id, 'purchase_cancel', cr)
435             message = _("Purchase order '%s' is cancelled.") % name
436             self.log(cr, uid, id, message)
437         return True
438
439     def _prepare_order_picking(self, cr, uid, order, context=None):
440         return {
441             'name': self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.in'),
442             'origin': order.name + ((order.origin and (':' + order.origin)) or ''),
443             'date': order.date_order,
444             'type': 'in',
445             'partner_id': order.dest_address_id.id or order.partner_id.id,
446             'invoice_state': '2binvoiced' if order.invoice_method == 'picking' else 'none',
447             'purchase_id': order.id,
448             'company_id': order.company_id.id,
449             'move_lines' : [],
450         }
451
452     def _prepare_order_line_move(self, cr, uid, order, order_line, picking_id, context=None):
453         return {
454             'name': order.name + ': ' + (order_line.name or ''),
455             'product_id': order_line.product_id.id,
456             'product_qty': order_line.product_qty,
457             'product_uos_qty': order_line.product_qty,
458             'product_uom': order_line.product_uom.id,
459             'product_uos': order_line.product_uom.id,
460             'date': order_line.date_planned,
461             'date_expected': order_line.date_planned,
462             'location_id': order.partner_id.property_stock_supplier.id,
463             'location_dest_id': order.location_id.id,
464             'picking_id': picking_id,
465             'partner_id': order.dest_address_id.id or order.partner_id.id,
466             'move_dest_id': order_line.move_dest_id.id,
467             'state': 'draft',
468             'purchase_line_id': order_line.id,
469             'company_id': order.company_id.id,
470             'price_unit': order_line.price_unit
471         }
472
473     def _create_pickings(self, cr, uid, order, order_lines, picking_id=False, context=None):
474         """Creates pickings and appropriate stock moves for given order lines, then
475         confirms the moves, makes them available, and confirms the picking.
476
477         If ``picking_id`` is provided, the stock moves will be added to it, otherwise
478         a standard outgoing picking will be created to wrap the stock moves, as returned
479         by :meth:`~._prepare_order_picking`.
480
481         Modules that wish to customize the procurements or partition the stock moves over
482         multiple stock pickings may override this method and call ``super()`` with
483         different subsets of ``order_lines`` and/or preset ``picking_id`` values.
484
485         :param browse_record order: purchase order to which the order lines belong
486         :param list(browse_record) order_lines: purchase order line records for which picking
487                                                 and moves should be created.
488         :param int picking_id: optional ID of a stock picking to which the created stock moves
489                                will be added. A new picking will be created if omitted.
490         :return: list of IDs of pickings used/created for the given order lines (usually just one)
491         """
492         if not picking_id:
493             picking_id = self.pool.get('stock.picking').create(cr, uid, self._prepare_order_picking(cr, uid, order, context=context))
494         todo_moves = []
495         stock_move = self.pool.get('stock.move')
496         wf_service = netsvc.LocalService("workflow")
497         for order_line in order_lines:
498             if not order_line.product_id:
499                 continue
500             if order_line.product_id.type in ('product', 'consu'):
501                 move = stock_move.create(cr, uid, self._prepare_order_line_move(cr, uid, order, order_line, picking_id, context=context))
502                 if order_line.move_dest_id:
503                     order_line.move_dest_id.write({'location_id': order.location_id.id})
504                 todo_moves.append(move)
505         stock_move.action_confirm(cr, uid, todo_moves)
506         stock_move.force_assign(cr, uid, todo_moves)
507         wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
508         return [picking_id]
509     
510     def action_picking_create(self,cr, uid, ids, context=None):
511         picking_ids = []
512         for order in self.browse(cr, uid, ids):
513             picking_ids.extend(self._create_pickings(cr, uid, order, order.order_line, None, context=context))
514
515         # Must return one unique picking ID: the one to connect in the subflow of the purchase order.
516         # In case of multiple (split) pickings, we should return the ID of the critical one, i.e. the
517         # one that should trigger the advancement of the purchase workflow.
518         # By default we will consider the first one as most important, but this behavior can be overridden.
519         if picking_ids:
520             self.shipment_send_note(cr, uid, ids, picking_ids[0], context=context)
521         return picking_ids[0] if picking_ids else False
522
523     def picking_done(self, cr, uid, ids, context=None):
524         self.write(cr, uid, ids, {'shipped':1,'state':'approved'}, context=context)
525         self.shipment_done_send_note(cr, uid, ids, context=context)
526         return True
527
528     def copy(self, cr, uid, id, default=None, context=None):
529         if not default:
530             default = {}
531         default.update({
532             'state':'draft',
533             'shipped':False,
534             'invoiced':False,
535             'invoice_ids': [],
536             'picking_ids': [],
537             'name': self.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
538         })
539         return super(purchase_order, self).copy(cr, uid, id, default, context)
540
541     def do_merge(self, cr, uid, ids, context=None):
542         """
543         To merge similar type of purchase orders.
544         Orders will only be merged if:
545         * Purchase Orders are in draft
546         * Purchase Orders belong to the same partner
547         * Purchase Orders are have same stock location, same pricelist
548         Lines will only be merged if:
549         * Order lines are exactly the same except for the quantity and unit
550
551          @param self: The object pointer.
552          @param cr: A database cursor
553          @param uid: ID of the user currently logged in
554          @param ids: the ID or list of IDs
555          @param context: A standard dictionary
556
557          @return: new purchase order id
558
559         """
560         #TOFIX: merged order line should be unlink
561         wf_service = netsvc.LocalService("workflow")
562         def make_key(br, fields):
563             list_key = []
564             for field in fields:
565                 field_val = getattr(br, field)
566                 if field in ('product_id', 'move_dest_id', 'account_analytic_id'):
567                     if not field_val:
568                         field_val = False
569                 if isinstance(field_val, browse_record):
570                     field_val = field_val.id
571                 elif isinstance(field_val, browse_null):
572                     field_val = False
573                 elif isinstance(field_val, list):
574                     field_val = ((6, 0, tuple([v.id for v in field_val])),)
575                 list_key.append((field, field_val))
576             list_key.sort()
577             return tuple(list_key)
578
579     # compute what the new orders should contain
580
581         new_orders = {}
582
583         for porder in [order for order in self.browse(cr, uid, ids, context=context) if order.state == 'draft']:
584             order_key = make_key(porder, ('partner_id', 'location_id', 'pricelist_id'))
585             new_order = new_orders.setdefault(order_key, ({}, []))
586             new_order[1].append(porder.id)
587             order_infos = new_order[0]
588             if not order_infos:
589                 order_infos.update({
590                     'origin': porder.origin,
591                     'date_order': porder.date_order,
592                     'partner_id': porder.partner_id.id,
593                     'dest_address_id': porder.dest_address_id.id,
594                     'warehouse_id': porder.warehouse_id.id,
595                     'location_id': porder.location_id.id,
596                     'pricelist_id': porder.pricelist_id.id,
597                     'state': 'draft',
598                     'order_line': {},
599                     'notes': '%s' % (porder.notes or '',),
600                     'fiscal_position': porder.fiscal_position and porder.fiscal_position.id or False,
601                 })
602             else:
603                 if porder.date_order < order_infos['date_order']:
604                     order_infos['date_order'] = porder.date_order
605                 if porder.notes:
606                     order_infos['notes'] = (order_infos['notes'] or '') + ('\n%s' % (porder.notes,))
607                 if porder.origin:
608                     order_infos['origin'] = (order_infos['origin'] or '') + ' ' + porder.origin
609
610             for order_line in porder.order_line:
611                 line_key = make_key(order_line, ('name', 'date_planned', 'taxes_id', 'price_unit', 'notes', 'product_id', 'move_dest_id', 'account_analytic_id'))
612                 o_line = order_infos['order_line'].setdefault(line_key, {})
613                 if o_line:
614                     # merge the line with an existing line
615                     o_line['product_qty'] += order_line.product_qty * order_line.product_uom.factor / o_line['uom_factor']
616                 else:
617                     # append a new "standalone" line
618                     for field in ('product_qty', 'product_uom'):
619                         field_val = getattr(order_line, field)
620                         if isinstance(field_val, browse_record):
621                             field_val = field_val.id
622                         o_line[field] = field_val
623                     o_line['uom_factor'] = order_line.product_uom and order_line.product_uom.factor or 1.0
624
625
626
627         allorders = []
628         orders_info = {}
629         for order_key, (order_data, old_ids) in new_orders.iteritems():
630             # skip merges with only one order
631             if len(old_ids) < 2:
632                 allorders += (old_ids or [])
633                 continue
634
635             # cleanup order line data
636             for key, value in order_data['order_line'].iteritems():
637                 del value['uom_factor']
638                 value.update(dict(key))
639             order_data['order_line'] = [(0, 0, value) for value in order_data['order_line'].itervalues()]
640
641             # create the new order
642             neworder_id = self.create(cr, uid, order_data)
643             orders_info.update({neworder_id: old_ids})
644             allorders.append(neworder_id)
645
646             # make triggers pointing to the old orders point to the new order
647             for old_id in old_ids:
648                 wf_service.trg_redirect(uid, 'purchase.order', old_id, neworder_id, cr)
649                 wf_service.trg_validate(uid, 'purchase.order', old_id, 'purchase_cancel', cr)
650         return orders_info
651         
652     # -----------------------------
653     # OpenChatter and notifications
654     # -----------------------------
655     def get_needaction_user_ids(self, cr, uid, ids, context=None):
656         result = dict.fromkeys(ids, [])
657         for obj in self.browse(cr, uid, ids, context=context):
658             if obj.state == 'approved':
659                 result[obj.id] = [obj.validator.id]
660         return result
661     
662     def create_send_note(self, cr, uid, ids, context=None):
663         return self.message_append_note(cr, uid, ids, body=_("Request for quotation <b>created</b>."), context=context)
664
665     def confirm_send_note(self, cr, uid, ids, context=None):
666         for obj in self.browse(cr, uid, ids, context=context):
667             self.message_subscribe(cr, uid, [obj.id], [obj.validator.id], context=context)
668             self.message_append_note(cr, uid, [obj.id], body=_("Quotation for <em>%s</em> <b>converted</b> to a Purchase Order of %s %s.") % (obj.partner_id.name, obj.amount_total, obj.pricelist_id.currency_id.symbol), context=context)
669         
670     def shipment_send_note(self, cr, uid, ids, picking_id, context=None):
671         for order in self.browse(cr, uid, ids, context=context):
672             for picking in (pck for pck in order.picking_ids if pck.id == picking_id):
673                 pck_date =  datetime.strptime(picking.min_date, '%Y-%m-%d %H:%M:%S').strftime('%m/%d/%Y')
674                 self.message_append_note(cr, uid, [order.id], body=_("Shipment <em>%s</em> <b>scheduled</b> for %s.") % (picking.name, pck_date), context=context)
675     
676     def invoice_send_note(self, cr, uid, ids, invoice_id, context=None):
677         for order in self.browse(cr, uid, ids, context=context):
678             for invoice in (inv for inv in order.invoice_ids if inv.id == invoice_id):
679                 self.message_append_note(cr, uid, [order.id], body=_("Draft Invoice of %s %s is <b>waiting for validation</b>.") % (invoice.amount_total, invoice.currency_id.symbol), context=context)
680     
681     def shipment_done_send_note(self, cr, uid, ids, context=None):
682         self.message_append_note(cr, uid, ids, body=_("""Shipment <b>received</b>."""), context=context)
683      
684     def invoice_done_send_note(self, cr, uid, ids, context=None):
685         self.message_append_note(cr, uid, ids, body=_("Invoice <b>paid</b>."), context=context)
686     
687     def cancel_send_note(self, cr, uid, ids, context=None):
688         for obj in self.browse(cr, uid, ids, context=context):
689             self.message_append_note(cr, uid, [obj.id], body=_("Purchase Order for <em>%s</em> <b>cancelled</b>.") % (obj.partner_id.name), context=context)
690
691 purchase_order()
692
693 class purchase_order_line(osv.osv):
694     def _amount_line(self, cr, uid, ids, prop, arg, context=None):
695         res = {}
696         cur_obj=self.pool.get('res.currency')
697         tax_obj = self.pool.get('account.tax')
698         for line in self.browse(cr, uid, ids, context=context):
699             taxes = tax_obj.compute_all(cr, uid, line.taxes_id, line.price_unit, line.product_qty)
700             cur = line.order_id.pricelist_id.currency_id
701             res[line.id] = cur_obj.round(cr, uid, cur, taxes['total'])
702         return res
703
704     def _get_uom_id(self, cr, uid, context=None):
705         try:
706             proxy = self.pool.get('ir.model.data')
707             result = proxy.get_object_reference(cr, uid, 'product', 'product_uom_unit')
708             return result[1]
709         except Exception, ex:
710             return False
711
712     _columns = {
713         'name': fields.char('Description', size=256, required=True),
714         'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product UoM'), required=True),
715         'date_planned': fields.date('Scheduled Date', required=True, select=True),
716         'taxes_id': fields.many2many('account.tax', 'purchase_order_taxe', 'ord_id', 'tax_id', 'Taxes'),
717         'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
718         'product_id': fields.many2one('product.product', 'Product', domain=[('purchase_ok','=',True)], change_default=True),
719         'move_ids': fields.one2many('stock.move', 'purchase_line_id', 'Reservation', readonly=True, ondelete='set null'),
720         'move_dest_id': fields.many2one('stock.move', 'Reservation Destination', ondelete='set null'),
721         'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Purchase Price')),
722         'price_subtotal': fields.function(_amount_line, string='Subtotal', digits_compute= dp.get_precision('Purchase Price')),
723         'notes': fields.text('Notes'),
724         'order_id': fields.many2one('purchase.order', 'Order Reference', select=True, required=True, ondelete='cascade'),
725         'account_analytic_id':fields.many2one('account.analytic.account', 'Analytic Account',),
726         'company_id': fields.related('order_id','company_id',type='many2one',relation='res.company',string='Company', store=True, readonly=True),
727         'state': fields.selection([('draft', 'Draft'), ('confirmed', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')], 'State', required=True, readonly=True,
728                                   help=' * The \'Draft\' state is set automatically when purchase order in draft state. \
729                                        \n* The \'Confirmed\' state is set automatically as confirm when purchase order in confirm state. \
730                                        \n* The \'Done\' state is set automatically when purchase order is set as done. \
731                                        \n* The \'Cancelled\' state is set automatically when user cancel purchase order.'),
732         'invoice_lines': fields.many2many('account.invoice.line', 'purchase_order_line_invoice_rel', 'order_line_id', 'invoice_id', 'Invoice Lines', readonly=True),
733         'invoiced': fields.boolean('Invoiced', readonly=True),
734         'partner_id': fields.related('order_id','partner_id',string='Partner',readonly=True,type="many2one", relation="res.partner", store=True),
735         'date_order': fields.related('order_id','date_order',string='Order Date',readonly=True,type="date")
736
737     }
738     _defaults = {
739         'product_uom' : _get_uom_id,
740         'product_qty': lambda *a: 1.0,
741         'state': lambda *args: 'draft',
742         'invoiced': lambda *a: 0,
743     }
744     _table = 'purchase_order_line'
745     _name = 'purchase.order.line'
746     _description = 'Purchase Order Line'
747
748     def copy_data(self, cr, uid, id, default=None, context=None):
749         if not default:
750             default = {}
751         default.update({'state':'draft', 'move_ids':[],'invoiced':0,'invoice_lines':[]})
752         return super(purchase_order_line, self).copy_data(cr, uid, id, default, context)
753
754     def onchange_product_uom(self, cr, uid, ids, pricelist_id, product_id, qty, uom_id,
755             partner_id, date_order=False, fiscal_position_id=False, date_planned=False,
756             name=False, price_unit=False, notes=False, context=None):
757         """
758         onchange handler of product_uom.
759         """
760         if not uom_id:
761             return {'value': {'price_unit': price_unit or 0.0, 'name': name or '', 'notes': notes or'', 'product_uom' : uom_id or False}}
762         return self.onchange_product_id(cr, uid, ids, pricelist_id, product_id, qty, uom_id,
763             partner_id, date_order=date_order, fiscal_position_id=fiscal_position_id, date_planned=date_planned,
764             name=name, price_unit=price_unit, notes=notes, context=context)
765
766     def _get_date_planned(self, cr, uid, supplier_info, date_order_str, context=None):
767         """Return the datetime value to use as Schedule Date (``date_planned``) for
768            PO Lines that correspond to the given product.supplierinfo,
769            when ordered at `date_order_str`.
770
771            :param browse_record | False supplier_info: product.supplierinfo, used to
772                determine delivery delay (if False, default delay = 0)
773            :param str date_order_str: date of order, as a string in
774                DEFAULT_SERVER_DATE_FORMAT
775            :rtype: datetime
776            :return: desired Schedule Date for the PO line
777         """
778         supplier_delay = int(supplier_info.delay) if supplier_info else 0
779         return datetime.strptime(date_order_str, DEFAULT_SERVER_DATE_FORMAT) + relativedelta(days=supplier_delay)
780
781     def onchange_product_id(self, cr, uid, ids, pricelist_id, product_id, qty, uom_id,
782             partner_id, date_order=False, fiscal_position_id=False, date_planned=False,
783             name=False, price_unit=False, notes=False, context=None):
784         """
785         onchange handler of product_id.
786         """
787         if context is None:
788             context = {}
789
790         res = {'value': {'price_unit': price_unit or 0.0, 'name': name or '', 'notes': notes or '', 'product_uom' : uom_id or False}}
791         if not product_id:
792             return res
793
794         product_product = self.pool.get('product.product')
795         product_uom = self.pool.get('product.uom')
796         res_partner = self.pool.get('res.partner')
797         product_supplierinfo = self.pool.get('product.supplierinfo')
798         product_pricelist = self.pool.get('product.pricelist')
799         account_fiscal_position = self.pool.get('account.fiscal.position')
800         account_tax = self.pool.get('account.tax')
801
802         # - check for the presence of partner_id and pricelist_id
803         if not pricelist_id:
804             raise osv.except_osv(_('No Pricelist !'), _('You have to select a pricelist or a supplier in the purchase form !\nPlease set one before choosing a product.'))
805         if not partner_id:
806             raise osv.except_osv(_('No Partner!'), _('You have to select a partner in the purchase form !\nPlease set one partner before choosing a product.'))
807
808         # - determine name and notes based on product in partner lang.
809         lang = res_partner.browse(cr, uid, partner_id).lang
810         context_partner = {'lang': lang, 'partner_id': partner_id}
811         product = product_product.browse(cr, uid, product_id, context=context_partner)
812         res['value'].update({'name': product.name, 'notes': notes or product.description_purchase})
813
814         # - set a domain on product_uom
815         res['domain'] = {'product_uom': [('category_id','=',product.uom_id.category_id.id)]}
816
817         # - check that uom and product uom belong to the same category
818         product_uom_po_id = product.uom_po_id.id
819         if not uom_id:
820             uom_id = product_uom_po_id
821
822         if product.uom_id.category_id.id != product_uom.browse(cr, uid, uom_id, context=context).category_id.id:
823             res['warning'] = {'title': _('Warning'), 'message': _('Selected UOM does not belong to the same category as the product UOM')}
824             uom_id = product_uom_po_id
825
826         res['value'].update({'product_uom': uom_id})
827
828         # - determine product_qty and date_planned based on seller info
829         if not date_order:
830             date_order = fields.date.context_today(cr,uid,context=context)
831
832         qty = qty or 1.0
833         supplierinfo = False
834         supplierinfo_ids = product_supplierinfo.search(cr, uid, [('name','=',partner_id),('product_id','=',product.id)])
835         if supplierinfo_ids:
836             supplierinfo = product_supplierinfo.browse(cr, uid, supplierinfo_ids[0], context=context)
837             if supplierinfo.product_uom.id != uom_id:
838                 res['warning'] = {'title': _('Warning'), 'message': _('The selected supplier only sells this product by %s') % supplierinfo.product_uom.name }
839             min_qty = product_uom._compute_qty(cr, uid, supplierinfo.product_uom.id, supplierinfo.min_qty, to_uom_id=uom_id)
840             if qty < min_qty: # If the supplier quantity is greater than entered from user, set minimal.
841                 res['warning'] = {'title': _('Warning'), 'message': _('The selected supplier has a minimal quantity set to %s %s, you should not purchase less.') % (supplierinfo.min_qty, supplierinfo.product_uom.name)}
842                 qty = min_qty
843
844         dt = self._get_date_planned(cr, uid, supplierinfo, date_order, context=context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
845
846         res['value'].update({'date_planned': date_planned or dt, 'product_qty': qty})
847
848         # - determine price_unit and taxes_id
849         price = product_pricelist.price_get(cr, uid, [pricelist_id],
850                     product.id, qty or 1.0, partner_id, {'uom': uom_id, 'date': date_order})[pricelist_id]
851
852         taxes = account_tax.browse(cr, uid, map(lambda x: x.id, product.supplier_taxes_id))
853         fpos = fiscal_position_id and account_fiscal_position.browse(cr, uid, fiscal_position_id, context=context) or False
854         taxes_ids = account_fiscal_position.map_tax(cr, uid, fpos, taxes)
855         res['value'].update({'price_unit': price, 'taxes_id': taxes_ids})
856
857         return res
858
859     product_id_change = onchange_product_id
860     product_uom_change = onchange_product_uom
861
862     def action_confirm(self, cr, uid, ids, context=None):
863         self.write(cr, uid, ids, {'state': 'confirmed'}, context=context)
864         return True
865
866 purchase_order_line()
867
868 class procurement_order(osv.osv):
869     _inherit = 'procurement.order'
870     _columns = {
871         'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
872     }
873
874     def action_po_assign(self, cr, uid, ids, context=None):
875         """ This is action which call from workflow to assign purchase order to procurements
876         @return: True
877         """
878         res = self.make_po(cr, uid, ids, context=context)
879         res = res.values()
880         return len(res) and res[0] or 0 #TO CHECK: why workflow is generated error if return not integer value
881
882     def create_procurement_purchase_order(self, cr, uid, procurement, po_vals, line_vals, context=None):
883         """Create the purchase order from the procurement, using
884            the provided field values, after adding the given purchase
885            order line in the purchase order.
886
887            :params procurement: the procurement object generating the purchase order
888            :params dict po_vals: field values for the new purchase order (the
889                                  ``order_line`` field will be overwritten with one
890                                  single line, as passed in ``line_vals``).
891            :params dict line_vals: field values of the single purchase order line that
892                                    the purchase order will contain.
893            :return: id of the newly created purchase order
894            :rtype: int
895         """
896         po_vals.update({'order_line': [(0,0,line_vals)]})
897         return self.pool.get('purchase.order').create(cr, uid, po_vals, context=context)
898
899     def _get_purchase_schedule_date(self, cr, uid, procurement, company, context=None):
900         """Return the datetime value to use as Schedule Date (``date_planned``) for the
901            Purchase Order Lines created to satisfy the given procurement.
902
903            :param browse_record procurement: the procurement for which a PO will be created.
904            :param browse_report company: the company to which the new PO will belong to.
905            :rtype: datetime
906            :return: the desired Schedule Date for the PO lines
907         """
908         procurement_date_planned = datetime.strptime(procurement.date_planned, DEFAULT_SERVER_DATETIME_FORMAT)
909         schedule_date = (procurement_date_planned - relativedelta(days=company.po_lead))
910         return schedule_date
911
912     def _get_purchase_order_date(self, cr, uid, procurement, company, schedule_date, context=None):
913         """Return the datetime value to use as Order Date (``date_order``) for the
914            Purchase Order created to satisfy the given procurement.
915
916            :param browse_record procurement: the procurement for which a PO will be created.
917            :param browse_report company: the company to which the new PO will belong to.
918            :param datetime schedule_date: desired Scheduled Date for the Purchase Order lines.
919            :rtype: datetime
920            :return: the desired Order Date for the PO
921         """
922         seller_delay = int(procurement.product_id.seller_delay)
923         return schedule_date - relativedelta(days=seller_delay)
924
925     def make_po(self, cr, uid, ids, context=None):
926         """ Make purchase order from procurement
927         @return: New created Purchase Orders procurement wise
928         """
929         res = {}
930         if context is None:
931             context = {}
932         company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id
933         partner_obj = self.pool.get('res.partner')
934         uom_obj = self.pool.get('product.uom')
935         pricelist_obj = self.pool.get('product.pricelist')
936         prod_obj = self.pool.get('product.product')
937         acc_pos_obj = self.pool.get('account.fiscal.position')
938         seq_obj = self.pool.get('ir.sequence')
939         warehouse_obj = self.pool.get('stock.warehouse')
940         for procurement in self.browse(cr, uid, ids, context=context):
941             res_id = procurement.move_id.id
942             partner = procurement.product_id.seller_id # Taken Main Supplier of Product of Procurement.
943             seller_qty = procurement.product_id.seller_qty
944             partner_id = partner.id
945             address_id = partner_obj.address_get(cr, uid, [partner_id], ['delivery'])['delivery']
946             pricelist_id = partner.property_product_pricelist_purchase.id
947             warehouse_id = warehouse_obj.search(cr, uid, [('company_id', '=', procurement.company_id.id or company.id)], context=context)
948             uom_id = procurement.product_id.uom_po_id.id
949
950             qty = uom_obj._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
951             if seller_qty:
952                 qty = max(qty,seller_qty)
953
954             price = pricelist_obj.price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, partner_id, {'uom': uom_id})[pricelist_id]
955
956             schedule_date = self._get_purchase_schedule_date(cr, uid, procurement, company, context=context)
957             purchase_date = self._get_purchase_order_date(cr, uid, procurement, company, schedule_date, context=context)
958
959             #Passing partner_id to context for purchase order line integrity of Line name
960             context.update({'lang': partner.lang, 'partner_id': partner_id})
961
962             product = prod_obj.browse(cr, uid, procurement.product_id.id, context=context)
963             taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
964             taxes = acc_pos_obj.map_tax(cr, uid, partner.property_account_position, taxes_ids)
965
966             line_vals = {
967                 'name': product.partner_ref,
968                 'product_qty': qty,
969                 'product_id': procurement.product_id.id,
970                 'product_uom': uom_id,
971                 'price_unit': price or 0.0,
972                 'date_planned': schedule_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
973                 'move_dest_id': res_id,
974                 'notes': product.description_purchase,
975                 'taxes_id': [(6,0,taxes)],
976             }
977             name = seq_obj.get(cr, uid, 'purchase.order') or _('PO: %s') % procurement.name
978             po_vals = {
979                 'name': name,
980                 'origin': procurement.origin,
981                 'partner_id': partner_id,
982                 'location_id': procurement.location_id.id,
983                 'warehouse_id': warehouse_id and warehouse_id[0] or False,
984                 'pricelist_id': pricelist_id,
985                 'date_order': purchase_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
986                 'company_id': procurement.company_id.id,
987                 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False
988             }
989             res[procurement.id] = self.create_procurement_purchase_order(cr, uid, procurement, po_vals, line_vals, context=context)
990             self.write(cr, uid, [procurement.id], {'state': 'running', 'purchase_id': res[procurement.id]})
991         return res
992
993 procurement_order()
994 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: