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