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