[IMP] improve code
[odoo/odoo.git] / addons / purchase / purchase.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 import time
23 from datetime import datetime
24 from dateutil.relativedelta import relativedelta
25
26 from osv import osv, fields
27 import netsvc
28 import pooler
29 from tools.translate import _
30 import decimal_precision as dp
31 from osv.orm import browse_record, browse_null
32 from tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, DATETIME_FORMATS_MAP
33
34 #
35 # Model definition
36 #
37 class purchase_order(osv.osv):
38
39     def _amount_all(self, cr, uid, ids, field_name, arg, context=None):
40         res = {}
41         cur_obj=self.pool.get('res.currency')
42         for order in self.browse(cr, uid, ids, context=context):
43             res[order.id] = {
44                 'amount_untaxed': 0.0,
45                 'amount_tax': 0.0,
46                 'amount_total': 0.0,
47             }
48             val = val1 = 0.0
49             cur = order.pricelist_id.currency_id
50             for line in order.order_line:
51                val1 += line.price_subtotal
52                for c in self.pool.get('account.tax').compute_all(cr, uid, line.taxes_id, line.price_unit, line.product_qty, line.product_id.id, order.partner_id)['taxes']:
53                     val += c.get('amount', 0.0)
54             res[order.id]['amount_tax']=cur_obj.round(cr, uid, cur, val)
55             res[order.id]['amount_untaxed']=cur_obj.round(cr, uid, cur, val1)
56             res[order.id]['amount_total']=res[order.id]['amount_untaxed'] + res[order.id]['amount_tax']
57         return res
58
59     def _set_minimum_planned_date(self, cr, uid, ids, name, value, arg, context=None):
60         if not value: return False
61         if type(ids)!=type([]):
62             ids=[ids]
63         for po in self.browse(cr, uid, ids, context=context):
64             if po.order_line:
65                 cr.execute("""update purchase_order_line set
66                         date_planned=%s
67                     where
68                         order_id=%s and
69                         (date_planned=%s or date_planned<%s)""", (value,po.id,po.minimum_planned_date,value))
70             cr.execute("""update purchase_order set
71                     minimum_planned_date=%s where id=%s""", (value, po.id))
72         return True
73
74     def _minimum_planned_date(self, cr, uid, ids, field_name, arg, context=None):
75         res={}
76         purchase_obj=self.browse(cr, uid, ids, context=context)
77         for purchase in purchase_obj:
78             res[purchase.id] = False
79             if purchase.order_line:
80                 min_date=purchase.order_line[0].date_planned
81                 for line in purchase.order_line:
82                     if line.date_planned < min_date:
83                         min_date=line.date_planned
84                 res[purchase.id]=min_date
85         return res
86
87
88     def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
89         res = {}
90         for purchase in self.browse(cursor, user, ids, context=context):
91             tot = 0.0
92             for invoice in purchase.invoice_ids:
93                 if invoice.state not in ('draft','cancel'):
94                     tot += invoice.amount_untaxed
95             if purchase.amount_untaxed:
96                 res[purchase.id] = tot * 100.0 / purchase.amount_untaxed
97             else:
98                 res[purchase.id] = 0.0
99         return res
100
101     def _shipped_rate(self, cr, uid, ids, name, arg, context=None):
102         if not ids: return {}
103         res = {}
104         for id in ids:
105             res[id] = [0.0,0.0]
106         cr.execute('''SELECT
107                 p.purchase_id,sum(m.product_qty), m.state
108             FROM
109                 stock_move m
110             LEFT JOIN
111                 stock_picking p on (p.id=m.picking_id)
112             WHERE
113                 p.purchase_id IN %s GROUP BY m.state, p.purchase_id''',(tuple(ids),))
114         for oid,nbr,state in cr.fetchall():
115             if state=='cancel':
116                 continue
117             if state=='done':
118                 res[oid][0] += nbr or 0.0
119                 res[oid][1] += nbr or 0.0
120             else:
121                 res[oid][1] += nbr or 0.0
122         for r in res:
123             if not res[r][1]:
124                 res[r] = 0.0
125             else:
126                 res[r] = 100.0 * res[r][0] / res[r][1]
127         return res
128
129     def _get_order(self, cr, uid, ids, context=None):
130         result = {}
131         for line in self.pool.get('purchase.order.line').browse(cr, uid, ids, context=context):
132             result[line.order_id.id] = True
133         return result.keys()
134
135     def _invoiced(self, cursor, user, ids, name, arg, context=None):
136         res = {}
137         for purchase in self.browse(cursor, user, ids, context=context):
138             invoiced = False
139             if purchase.invoiced_rate == 100.00:
140                 invoiced = True
141             res[purchase.id] = invoiced
142         return res
143
144     STATE_SELECTION = [
145         ('draft', 'Draft PO'),
146         ('sent', 'RFQ Sent'),
147         ('confirmed', 'Waiting Approval'),
148         ('approved', 'Purchase Order'),
149         ('except_picking', 'Shipping Exception'),
150         ('except_invoice', 'Invoice Exception'),
151         ('done', 'Done'),
152         ('cancel', 'Cancelled')
153     ]
154
155     _columns = {
156         'name': fields.char('Order Reference', size=64, required=True, select=True, help="Unique number of the purchase order, computed automatically when the purchase order is created."),
157         'origin': fields.char('Source Document', size=64,
158             help="Reference of the document that generated this purchase order request."
159         ),
160         'partner_ref': fields.char('Supplier Reference', states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, size=64),
161         'date_order':fields.date('Order Date', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}, select=True, help="Date on which this document has been created."),
162         'date_approve':fields.date('Date Approved', readonly=1, select=True, help="Date on which purchase order has been approved"),
163         'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, change_default=True),
164         'dest_address_id':fields.many2one('res.partner', 'Customer Address (Direct Delivery)',
165             states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},
166             help="Put an address if you want to deliver directly from the supplier to the customer. " \
167                 "Otherwise, keep empty to deliver to your own company."
168         ),
169         'warehouse_id': fields.many2one('stock.warehouse', 'Destination Warehouse', states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}),
170         'location_id': fields.many2one('stock.location', 'Destination', required=True, domain=[('usage','<>','view')]),
171         'pricelist_id':fields.many2one('product.pricelist', 'Pricelist', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, help="The pricelist sets the currency used for this purchase order. It also computes the supplier price for the selected products/quantities."),
172         'state': fields.selection(STATE_SELECTION, 'Status', readonly=True, help="The state of the purchase order or the quotation request. A quotation is a purchase order in a 'Draft' state. Then the order has to be confirmed by the user, the state switch to 'Confirmed'. Then the supplier must confirm the order to change the state to 'Approved'. When the purchase order is paid and received, the state becomes 'Done'. If a cancel action occurs in the invoice or in the reception of goods, the state becomes in exception.", select=True),
173         'order_line': fields.one2many('purchase.order.line', 'order_id', 'Order Lines', states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
174         'validator' : fields.many2one('res.users', 'Validated by', readonly=True),
175         'notes': fields.text('Terms and Conditions'),
176         'invoice_ids': fields.many2many('account.invoice', 'purchase_invoice_rel', 'purchase_id', 'invoice_id', 'Invoices', help="Invoices generated for a purchase order"),
177         'picking_ids': fields.one2many('stock.picking.in', 'purchase_id', 'Picking List', readonly=True, help="This is the list of incoming shipments that have been generated for this purchase order."),
178         'shipped':fields.boolean('Received', readonly=True, select=True, help="It indicates that a picking has been done"),
179         'shipped_rate': fields.function(_shipped_rate, string='Received', type='float'),
180         'invoiced': fields.function(_invoiced, string='Invoice Received', type='boolean', help="It indicates that an invoice has been paid"),
181         'invoiced_rate': fields.function(_invoiced_rate, string='Invoiced', type='float'),
182         'invoice_method': fields.selection([('manual','Based on Purchase Order lines'),('order','Based on generated draft invoice'),('picking','Bases on incoming shipments')], 'Invoicing Control', required=True,
183             help="Based on Purchase Order lines: place individual lines in 'Invoice Control > Based on P.O. lines' from where you can selectively create an invoice.\n" \
184                 "Based on generated invoice: create a draft invoice you can validate later.\n" \
185                 "Bases on incoming shipments: let you create an invoice when receptions are validated."
186         ),
187         'minimum_planned_date':fields.function(_minimum_planned_date, fnct_inv=_set_minimum_planned_date, string='Expected Date', type='date', select=True, help="This is computed as the minimum scheduled date of all purchase order lines' products.",
188             store = {
189                 'purchase.order.line': (_get_order, ['date_planned'], 10),
190             }
191         ),
192         'amount_untaxed': fields.function(_amount_all, digits_compute= dp.get_precision('Account'), string='Untaxed Amount',
193             store={
194                 'purchase.order.line': (_get_order, None, 10),
195             }, multi="sums", help="The amount without tax"),
196         'amount_tax': fields.function(_amount_all, digits_compute= dp.get_precision('Account'), string='Taxes',
197             store={
198                 'purchase.order.line': (_get_order, None, 10),
199             }, multi="sums", help="The tax amount"),
200         'amount_total': fields.function(_amount_all, digits_compute= dp.get_precision('Account'), string='Total',
201             store={
202                 'purchase.order.line': (_get_order, None, 10),
203             }, multi="sums",help="The total amount"),
204         'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position'),
205         'product_id': fields.related('order_line','product_id', type='many2one', relation='product.product', string='Product'),
206         'create_uid':  fields.many2one('res.users', 'Responsible'),
207         'company_id': fields.many2one('res.company','Company',required=True,select=1),
208     }
209     _defaults = {
210         'date_order': fields.date.context_today,
211         'state': 'draft',
212         'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
213         'shipped': 0,
214         'invoice_method': 'order',
215         'invoiced': 0,
216         'pricelist_id': lambda self, cr, uid, context: context.get('partner_id', False) and self.pool.get('res.partner').browse(cr, uid, context['partner_id']).property_product_pricelist_purchase.id,
217         'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'purchase.order', context=c),
218     }
219     _sql_constraints = [
220         ('name_uniq', 'unique(name, company_id)', 'Order Reference must be unique per Company!'),
221     ]
222     _name = "purchase.order"
223     _inherit = ['mail.thread', 'ir.needaction_mixin']
224     _description = "Purchase Order"
225     _order = "name desc"
226
227     def create(self, cr, uid, vals, context=None):
228         order =  super(purchase_order, self).create(cr, uid, vals, context=context)
229         if order:
230             self.create_send_note(cr, uid, [order], context=context)
231         return order
232
233     def unlink(self, cr, uid, ids, context=None):
234         purchase_orders = self.read(cr, uid, ids, ['state'], context=context)
235         unlink_ids = []
236         for s in purchase_orders:
237             if s['state'] in ['draft','cancel']:
238                 unlink_ids.append(s['id'])
239             else:
240                 raise osv.except_osv(_('Invalid Action!'), _('In order to delete a purchase order, you must cancel it first.'))
241
242         # TODO: temporary fix in 5.0, to remove in 5.2 when subflows support
243         # automatically sending subflow.delete upon deletion
244         wf_service = netsvc.LocalService("workflow")
245         for id in unlink_ids:
246             wf_service.trg_validate(uid, 'purchase.order', id, 'purchase_cancel', cr)
247
248         return super(purchase_order, self).unlink(cr, uid, unlink_ids, context=context)
249
250     def button_dummy(self, cr, uid, ids, context=None):
251         return True
252
253     def onchange_dest_address_id(self, cr, uid, ids, address_id):
254         if not address_id:
255             return {}
256         address = self.pool.get('res.partner')
257         values = {'warehouse_id': False}
258         supplier = address.browse(cr, uid, address_id)
259         if supplier:
260             location_id = supplier.property_stock_customer.id
261             values.update({'location_id': location_id})
262         return {'value':values}
263
264     def onchange_warehouse_id(self, cr, uid, ids, warehouse_id):
265         if not warehouse_id:
266             return {}
267         warehouse = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id)
268         return {'value':{'location_id': warehouse.lot_input_id.id, 'dest_address_id': False}}
269
270     def onchange_partner_id(self, cr, uid, ids, partner_id):
271         partner = self.pool.get('res.partner')
272         if not partner_id:
273             return {'value':{'fiscal_position': False}}
274         supplier_address = partner.address_get(cr, uid, [partner_id], ['default'])
275         supplier = partner.browse(cr, uid, partner_id)
276         pricelist = supplier.property_product_pricelist_purchase.id
277         fiscal_position = supplier.property_account_position and supplier.property_account_position.id or False
278         return {'value':{'pricelist_id': pricelist, 'fiscal_position': fiscal_position}}
279
280     def view_invoice(self, cr, uid, ids, context=None):
281         '''
282         This function returns an action that display existing invoices of given sale order ids. It can either be a in a list or in a form view, if there is only one invoice to show.
283         '''
284         mod_obj = self.pool.get('ir.model.data')
285         wizard_obj = self.pool.get('purchase.order.line_invoice')
286         #compute the number of invoices to display
287         inv_ids = []
288         for po in self.browse(cr, uid, ids, context=context):
289             if po.invoice_method == 'manual':
290                 if not po.invoice_ids:
291                     context.update({'active_ids' :  [line.id for line in po.order_line]})
292                     wizard_obj.makeInvoices(cr, uid, [], context=context)
293
294         for po in self.browse(cr, uid, ids, context=context):
295             inv_ids+= [invoice.id for invoice in po.invoice_ids]
296         res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_supplier_form')
297         res_id = res and res[1] or False
298
299         return {
300             'name': _('Supplier Invoices'),
301             'view_type': 'form',
302             'view_mode': 'form',
303             'view_id': [res_id],
304             'res_model': 'account.invoice',
305             'context': "{'type':'in_invoice', 'journal_type': 'purchase'}",
306             'type': 'ir.actions.act_window',
307             'nodestroy': True,
308             'target': 'current',
309             'res_id': inv_ids and inv_ids[0] or False,
310         }
311
312     def view_picking(self, cr, uid, ids, context=None):
313         '''
314         This function returns an action that display existing pîcking orders of given purchase order ids.
315         '''
316         mod_obj = self.pool.get('ir.model.data')
317         pick_ids = []
318         for po in self.browse(cr, uid, ids, context=context):
319             pick_ids += [picking.id for picking in po.picking_ids]
320
321         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 needaction_domain_get(self, cr, uid, ids, context=None):
735         return [('state','=','draft')]
736
737     def create_send_note(self, cr, uid, ids, context=None):
738         return self.message_post(cr, uid, ids, body=_("Request for quotation <b>created</b>."), subtype="new", context=context)
739
740     def confirm_send_note(self, cr, uid, ids, context=None):
741         for obj in self.browse(cr, uid, ids, context=context):
742             self.message_post(cr, uid, [obj.id], body=_("Quotation for <em>%s</em> <b>converted</b> to a Purchase Order of %s %s.") % (obj.partner_id.name, obj.amount_total, obj.pricelist_id.currency_id.symbol), context=context)
743
744     def shipment_send_note(self, cr, uid, ids, picking_id, context=None):
745         for order in self.browse(cr, uid, ids, context=context):
746             for picking in (pck for pck in order.picking_ids if pck.id == picking_id):
747                 # convert datetime field to a datetime, using server format, then
748                 # convert it to the user TZ and re-render it with %Z to add the timezone
749                 picking_datetime = fields.DT.datetime.strptime(picking.min_date, DEFAULT_SERVER_DATETIME_FORMAT)
750                 picking_date_str = fields.datetime.context_timestamp(cr, uid, picking_datetime, context=context).strftime(DATETIME_FORMATS_MAP['%+'] + " (%Z)")
751                 self.message_post(cr, uid, [order.id], body=_("Shipment <em>%s</em> <b>scheduled</b> for %s.") % (picking.name, picking_date_str), context=context)
752
753     def invoice_send_note(self, cr, uid, ids, invoice_id, context=None):
754         for order in self.browse(cr, uid, ids, context=context):
755             for invoice in (inv for inv in order.invoice_ids if inv.id == invoice_id):
756                 self.message_post(cr, uid, [order.id], body=_("Draft Invoice of %s %s is <b>waiting for validation</b>.") % (invoice.amount_total, invoice.currency_id.symbol), subtype="pending", context=context)
757
758     def shipment_done_send_note(self, cr, uid, ids, context=None):
759         self.message_post(cr, uid, ids, body=_("""Shipment <b>received</b>."""), subtype="received", context=context)
760
761     def invoice_done_send_note(self, cr, uid, ids, context=None):
762         self.message_post(cr, uid, ids, body=_("Invoice <b>paid</b>."), subtype="paid", context=context)
763
764     def draft_send_note(self, cr, uid, ids, context=None):
765         return self.message_post(cr, uid, ids, body=_("Purchase Order has been set to <b>draft</b>."), context=context)
766
767     def cancel_send_note(self, cr, uid, ids, context=None):
768         for obj in self.browse(cr, uid, ids, context=context):
769             self.message_post(cr, uid, [obj.id], body=_("Purchase Order for <em>%s</em> <b>cancelled</b>.") % (obj.partner_id.name), subtype="cancelled", context=context)
770
771 purchase_order()
772
773 class purchase_order_line(osv.osv):
774     def _amount_line(self, cr, uid, ids, prop, arg, context=None):
775         res = {}
776         cur_obj=self.pool.get('res.currency')
777         tax_obj = self.pool.get('account.tax')
778         for line in self.browse(cr, uid, ids, context=context):
779             taxes = tax_obj.compute_all(cr, uid, line.taxes_id, line.price_unit, line.product_qty)
780             cur = line.order_id.pricelist_id.currency_id
781             res[line.id] = cur_obj.round(cr, uid, cur, taxes['total'])
782         return res
783
784     def _get_uom_id(self, cr, uid, context=None):
785         try:
786             proxy = self.pool.get('ir.model.data')
787             result = proxy.get_object_reference(cr, uid, 'product', 'product_uom_unit')
788             return result[1]
789         except Exception, ex:
790             return False
791
792     _columns = {
793         'name': fields.text('Description', required=True),
794         'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
795         'date_planned': fields.date('Scheduled Date', required=True, select=True),
796         'taxes_id': fields.many2many('account.tax', 'purchase_order_taxe', 'ord_id', 'tax_id', 'Taxes'),
797         'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True),
798         'product_id': fields.many2one('product.product', 'Product', domain=[('purchase_ok','=',True)], change_default=True),
799         'move_ids': fields.one2many('stock.move', 'purchase_line_id', 'Reservation', readonly=True, ondelete='set null'),
800         'move_dest_id': fields.many2one('stock.move', 'Reservation Destination', ondelete='set null'),
801         'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Product Price')),
802         'price_subtotal': fields.function(_amount_line, string='Subtotal', digits_compute= dp.get_precision('Account')),
803         'order_id': fields.many2one('purchase.order', 'Order Reference', select=True, required=True, ondelete='cascade'),
804         'account_analytic_id':fields.many2one('account.analytic.account', 'Analytic Account',),
805         'company_id': fields.related('order_id','company_id',type='many2one',relation='res.company',string='Company', store=True, readonly=True),
806         'state': fields.selection([('draft', 'Draft'), ('confirmed', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')], 'Status', required=True, readonly=True,
807                                   help=' * The \'Draft\' state is set automatically when purchase order in draft state. \
808                                        \n* The \'Confirmed\' state is set automatically as confirm when purchase order in confirm state. \
809                                        \n* The \'Done\' state is set automatically when purchase order is set as done. \
810                                        \n* The \'Cancelled\' state is set automatically when user cancel purchase order.'),
811         'invoice_lines': fields.many2many('account.invoice.line', 'purchase_order_line_invoice_rel', 'order_line_id', 'invoice_id', 'Invoice Lines', readonly=True),
812         'invoiced': fields.boolean('Invoiced', readonly=True),
813         'partner_id': fields.related('order_id','partner_id',string='Partner',readonly=True,type="many2one", relation="res.partner", store=True),
814         'date_order': fields.related('order_id','date_order',string='Order Date',readonly=True,type="date")
815
816     }
817     _defaults = {
818         'product_uom' : _get_uom_id,
819         'product_qty': lambda *a: 1.0,
820         'state': lambda *args: 'draft',
821         'invoiced': lambda *a: 0,
822     }
823     _table = 'purchase_order_line'
824     _name = 'purchase.order.line'
825     _description = 'Purchase Order Line'
826
827     def copy_data(self, cr, uid, id, default=None, context=None):
828         if not default:
829             default = {}
830         default.update({'state':'draft', 'move_ids':[],'invoiced':0,'invoice_lines':[]})
831         return super(purchase_order_line, self).copy_data(cr, uid, id, default, context)
832
833     def onchange_product_uom(self, cr, uid, ids, pricelist_id, product_id, qty, uom_id,
834             partner_id, date_order=False, fiscal_position_id=False, date_planned=False,
835             name=False, price_unit=False, context=None):
836         """
837         onchange handler of product_uom.
838         """
839         if not uom_id:
840             return {'value': {'price_unit': price_unit or 0.0, 'name': name or '', 'product_uom' : uom_id or False}}
841         return self.onchange_product_id(cr, uid, ids, pricelist_id, product_id, qty, uom_id,
842             partner_id, date_order=date_order, fiscal_position_id=fiscal_position_id, date_planned=date_planned,
843             name=name, price_unit=price_unit, context=context)
844
845     def _get_date_planned(self, cr, uid, supplier_info, date_order_str, context=None):
846         """Return the datetime value to use as Schedule Date (``date_planned``) for
847            PO Lines that correspond to the given product.supplierinfo,
848            when ordered at `date_order_str`.
849
850            :param browse_record | False supplier_info: product.supplierinfo, used to
851                determine delivery delay (if False, default delay = 0)
852            :param str date_order_str: date of order, as a string in
853                DEFAULT_SERVER_DATE_FORMAT
854            :rtype: datetime
855            :return: desired Schedule Date for the PO line
856         """
857         supplier_delay = int(supplier_info.delay) if supplier_info else 0
858         return datetime.strptime(date_order_str, DEFAULT_SERVER_DATE_FORMAT) + relativedelta(days=supplier_delay)
859
860     def onchange_product_id(self, cr, uid, ids, pricelist_id, product_id, qty, uom_id,
861             partner_id, date_order=False, fiscal_position_id=False, date_planned=False,
862             name=False, price_unit=False, context=None):
863         """
864         onchange handler of product_id.
865         """
866         if context is None:
867             context = {}
868
869         res = {'value': {'price_unit': price_unit or 0.0, 'name': name or '', 'product_uom' : uom_id or False}}
870         if not product_id:
871             return res
872
873         product_product = self.pool.get('product.product')
874         product_uom = self.pool.get('product.uom')
875         res_partner = self.pool.get('res.partner')
876         product_supplierinfo = self.pool.get('product.supplierinfo')
877         product_pricelist = self.pool.get('product.pricelist')
878         account_fiscal_position = self.pool.get('account.fiscal.position')
879         account_tax = self.pool.get('account.tax')
880
881         # - check for the presence of partner_id and pricelist_id
882         if not pricelist_id:
883             raise osv.except_osv(_('No Pricelist !'), _('Select a price list for a supplier in the purchase form to choose a product.'))
884         if not partner_id:
885             raise osv.except_osv(_('No Partner!'), _('Select a partner in purchase order to choose a product.'))
886
887         # - determine name and notes based on product in partner lang.
888         lang = res_partner.browse(cr, uid, partner_id).lang
889         context_partner = {'lang': lang, 'partner_id': partner_id}
890         product = product_product.browse(cr, uid, product_id, context=context_partner)
891         name = product.name
892         if product.description_purchase:
893             name += '\n' + product.description_purchase
894         res['value'].update({'name': name})
895
896         # - set a domain on product_uom
897         res['domain'] = {'product_uom': [('category_id','=',product.uom_id.category_id.id)]}
898
899         # - check that uom and product uom belong to the same category
900         product_uom_po_id = product.uom_po_id.id
901         if not uom_id:
902             uom_id = product_uom_po_id
903
904         if product.uom_id.category_id.id != product_uom.browse(cr, uid, uom_id, context=context).category_id.id:
905             res['warning'] = {'title': _('Warning!'), 'message': _('Selected Unit of Measure does not belong to the same category as the product Unit of Measure.')}
906             uom_id = product_uom_po_id
907
908         res['value'].update({'product_uom': uom_id})
909
910         # - determine product_qty and date_planned based on seller info
911         if not date_order:
912             date_order = fields.date.context_today(self,cr,uid,context=context)
913
914         qty = qty or 1.0
915         supplierinfo = False
916         for supplier in product.seller_ids:
917             if supplier.name.id == partner_id:
918                 supplierinfo = supplier
919                 if supplierinfo.product_uom.id != uom_id:
920                     res['warning'] = {'title': _('Warning!'), 'message': _('The selected supplier only sells this product by %s') % supplierinfo.product_uom.name }
921                 min_qty = product_uom._compute_qty(cr, uid, supplierinfo.product_uom.id, supplierinfo.min_qty, to_uom_id=uom_id)
922                 if qty < min_qty: # If the supplier quantity is greater than entered from user, set minimal.
923                     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)}
924                     qty = min_qty
925
926         dt = self._get_date_planned(cr, uid, supplierinfo, date_order, context=context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
927
928         res['value'].update({'date_planned': date_planned or dt, 'product_qty': qty})
929
930         # - determine price_unit and taxes_id
931         price = product_pricelist.price_get(cr, uid, [pricelist_id],
932                     product.id, qty or 1.0, partner_id, {'uom': uom_id, 'date': date_order})[pricelist_id]
933
934         taxes = account_tax.browse(cr, uid, map(lambda x: x.id, product.supplier_taxes_id))
935         fpos = fiscal_position_id and account_fiscal_position.browse(cr, uid, fiscal_position_id, context=context) or False
936         taxes_ids = account_fiscal_position.map_tax(cr, uid, fpos, taxes)
937         res['value'].update({'price_unit': price, 'taxes_id': taxes_ids})
938
939         return res
940
941     product_id_change = onchange_product_id
942     product_uom_change = onchange_product_uom
943
944     def action_confirm(self, cr, uid, ids, context=None):
945         self.write(cr, uid, ids, {'state': 'confirmed'}, context=context)
946         return True
947
948 purchase_order_line()
949
950 class procurement_order(osv.osv):
951     _inherit = 'procurement.order'
952     _columns = {
953         'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
954     }
955
956     def action_po_assign(self, cr, uid, ids, context=None):
957         """ This is action which call from workflow to assign purchase order to procurements
958         @return: True
959         """
960         res = self.make_po(cr, uid, ids, context=context)
961         res = res.values()
962         return len(res) and res[0] or 0 #TO CHECK: why workflow is generated error if return not integer value
963
964     def create_procurement_purchase_order(self, cr, uid, procurement, po_vals, line_vals, context=None):
965         """Create the purchase order from the procurement, using
966            the provided field values, after adding the given purchase
967            order line in the purchase order.
968
969            :params procurement: the procurement object generating the purchase order
970            :params dict po_vals: field values for the new purchase order (the
971                                  ``order_line`` field will be overwritten with one
972                                  single line, as passed in ``line_vals``).
973            :params dict line_vals: field values of the single purchase order line that
974                                    the purchase order will contain.
975            :return: id of the newly created purchase order
976            :rtype: int
977         """
978         po_vals.update({'order_line': [(0,0,line_vals)]})
979         return self.pool.get('purchase.order').create(cr, uid, po_vals, context=context)
980
981     def _get_purchase_schedule_date(self, cr, uid, procurement, company, context=None):
982         """Return the datetime value to use as Schedule Date (``date_planned``) for the
983            Purchase Order Lines created to satisfy the given procurement.
984
985            :param browse_record procurement: the procurement for which a PO will be created.
986            :param browse_report company: the company to which the new PO will belong to.
987            :rtype: datetime
988            :return: the desired Schedule Date for the PO lines
989         """
990         procurement_date_planned = datetime.strptime(procurement.date_planned, DEFAULT_SERVER_DATETIME_FORMAT)
991         schedule_date = (procurement_date_planned - relativedelta(days=company.po_lead))
992         return schedule_date
993
994     def _get_purchase_order_date(self, cr, uid, procurement, company, schedule_date, context=None):
995         """Return the datetime value to use as Order Date (``date_order``) for the
996            Purchase Order created to satisfy the given procurement.
997
998            :param browse_record procurement: the procurement for which a PO will be created.
999            :param browse_report company: the company to which the new PO will belong to.
1000            :param datetime schedule_date: desired Scheduled Date for the Purchase Order lines.
1001            :rtype: datetime
1002            :return: the desired Order Date for the PO
1003         """
1004         seller_delay = int(procurement.product_id.seller_delay)
1005         return schedule_date - relativedelta(days=seller_delay)
1006
1007     def make_po(self, cr, uid, ids, context=None):
1008         """ Make purchase order from procurement
1009         @return: New created Purchase Orders procurement wise
1010         """
1011         res = {}
1012         if context is None:
1013             context = {}
1014         company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id
1015         partner_obj = self.pool.get('res.partner')
1016         uom_obj = self.pool.get('product.uom')
1017         pricelist_obj = self.pool.get('product.pricelist')
1018         prod_obj = self.pool.get('product.product')
1019         acc_pos_obj = self.pool.get('account.fiscal.position')
1020         seq_obj = self.pool.get('ir.sequence')
1021         warehouse_obj = self.pool.get('stock.warehouse')
1022         for procurement in self.browse(cr, uid, ids, context=context):
1023             res_id = procurement.move_id.id
1024             partner = procurement.product_id.seller_id # Taken Main Supplier of Product of Procurement.
1025             seller_qty = procurement.product_id.seller_qty
1026             partner_id = partner.id
1027             address_id = partner_obj.address_get(cr, uid, [partner_id], ['delivery'])['delivery']
1028             pricelist_id = partner.property_product_pricelist_purchase.id
1029             warehouse_id = warehouse_obj.search(cr, uid, [('company_id', '=', procurement.company_id.id or company.id)], context=context)
1030             uom_id = procurement.product_id.uom_po_id.id
1031
1032             qty = uom_obj._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
1033             if seller_qty:
1034                 qty = max(qty,seller_qty)
1035
1036             price = pricelist_obj.price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, partner_id, {'uom': uom_id})[pricelist_id]
1037
1038             schedule_date = self._get_purchase_schedule_date(cr, uid, procurement, company, context=context)
1039             purchase_date = self._get_purchase_order_date(cr, uid, procurement, company, schedule_date, context=context)
1040
1041             #Passing partner_id to context for purchase order line integrity of Line name
1042             context.update({'lang': partner.lang, 'partner_id': partner_id})
1043
1044             product = prod_obj.browse(cr, uid, procurement.product_id.id, context=context)
1045             taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
1046             taxes = acc_pos_obj.map_tax(cr, uid, partner.property_account_position, taxes_ids)
1047
1048             name = product.partner_ref
1049             if product.description_purchase:
1050                 name += '\n'+ product.description_purchase
1051             line_vals = {
1052                 'name': name,
1053                 'product_qty': qty,
1054                 'product_id': procurement.product_id.id,
1055                 'product_uom': uom_id,
1056                 'price_unit': price or 0.0,
1057                 'date_planned': schedule_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
1058                 'move_dest_id': res_id,
1059                 'taxes_id': [(6,0,taxes)],
1060             }
1061             name = seq_obj.get(cr, uid, 'purchase.order') or _('PO: %s') % procurement.name
1062             po_vals = {
1063                 'name': name,
1064                 'origin': procurement.origin,
1065                 'partner_id': partner_id,
1066                 'location_id': procurement.location_id.id,
1067                 'warehouse_id': warehouse_id and warehouse_id[0] or False,
1068                 'pricelist_id': pricelist_id,
1069                 'date_order': purchase_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
1070                 'company_id': procurement.company_id.id,
1071                 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False
1072             }
1073             res[procurement.id] = self.create_procurement_purchase_order(cr, uid, procurement, po_vals, line_vals, context=context)
1074             self.write(cr, uid, [procurement.id], {'state': 'running', 'purchase_id': res[procurement.id]})
1075             self.running_send_note(cr, uid, [procurement.id], context=context)
1076         return res
1077
1078 procurement_order()
1079
1080 class mail_message(osv.osv):
1081     _name = 'mail.message'
1082     _inherit = 'mail.message'
1083
1084     def _postprocess_sent_message(self, cr, uid, message, context=None):
1085         if message.model == 'purchase.order':
1086             wf_service = netsvc.LocalService("workflow")
1087             wf_service.trg_validate(uid, 'purchase.order', message.res_id, 'send_rfq', cr)
1088         return super(mail_message, self)._postprocess_sent_message(cr, uid, message=message, context=context)
1089
1090 mail_message()
1091 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: