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