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