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