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