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