[MERGE]: Merge with lp:openobject-trunk-dev-addons2
[odoo/odoo.git] / addons / purchase / purchase.py
index 3316b2f..6c8d86d 100644 (file)
@@ -2,7 +2,7 @@
 ##############################################################################
 #
 #    OpenERP, Open Source Management Solution
-#    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
+#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
 #
 #    This program is free software: you can redistribute it and/or modify
 #    it under the terms of the GNU Affero General Public License as
 #
 ##############################################################################
 
-from osv import fields
-from osv import osv
+from mx import DateTime
 import time
-import netsvc
+from datetime import datetime
+from dateutil.relativedelta import relativedelta
 
+from osv import osv, fields
+import netsvc
 import ir
-from mx import DateTime
 import pooler
 from tools import config
 from tools.translate import _
+import decimal_precision as dp
+from osv.orm import browse_record, browse_null
 
 #
 # Model definition
@@ -42,7 +45,7 @@ class purchase_order(osv.osv):
                 res[order.id] += oline.price_unit * oline.product_qty
         return res
 
-    def _amount_all(self, cr, uid, ids, field_name, arg, context):
+    def _amount_all(self, cr, uid, ids, field_name, arg, context=None):
         res = {}
         cur_obj=self.pool.get('res.currency')
         for order in self.browse(cr, uid, ids):
@@ -52,17 +55,17 @@ class purchase_order(osv.osv):
                 'amount_total': 0.0,
             }
             val = val1 = 0.0
-            cur=order.pricelist_id.currency_id
+            cur = order.pricelist_id.currency_id
             for line in order.order_line:
-                for c in self.pool.get('account.tax').compute(cr, uid, line.taxes_id, line.price_unit, line.product_qty, order.partner_address_id.id, line.product_id, order.partner_id):
-                    val+= c['amount']
-                val1 += line.price_subtotal
+               val1 += line.price_subtotal
+               for c in self.pool.get('account.tax').compute_all(cr, uid, line.taxes_id, line.price_unit, line.product_qty, order.partner_address_id.id, line.product_id.id, order.partner_id)['taxes']:
+                    val += c.get('amount', 0.0)
             res[order.id]['amount_tax']=cur_obj.round(cr, uid, cur, val)
             res[order.id]['amount_untaxed']=cur_obj.round(cr, uid, cur, val1)
             res[order.id]['amount_total']=res[order.id]['amount_untaxed'] + res[order.id]['amount_tax']
         return res
 
-    def _set_minimum_planned_date(self, cr, uid, ids, name, value, arg, context):
+    def _set_minimum_planned_date(self, cr, uid, ids, name, value, arg, context=None):
         if not value: return False
         if type(ids)!=type([]):
             ids=[ids]
@@ -74,11 +77,11 @@ class purchase_order(osv.osv):
                     (date_planned=%s or date_planned<%s)""", (value,po.id,po.minimum_planned_date,value))
         return True
 
-    def _minimum_planned_date(self, cr, uid, ids, field_name, arg, context):
+    def _minimum_planned_date(self, cr, uid, ids, field_name, arg, context=None):
         res={}
         purchase_obj=self.browse(cr, uid, ids, context=context)
         for purchase in purchase_obj:
-            res[purchase.id] = False
+            res[purchase.id] = time.strftime('%Y-%m-%d %H:%M:%S')
             if purchase.order_line:
                 min_date=purchase.order_line[0].date_planned
                 for line in purchase.order_line:
@@ -111,8 +114,7 @@ class purchase_order(osv.osv):
             LEFT JOIN
                 stock_picking p on (p.id=m.picking_id)
             WHERE
-                p.purchase_id in ('''+','.join(map(str, ids))+''')
-            GROUP BY m.state, p.purchase_id''')
+                p.purchase_id IN %s GROUP BY m.state, p.purchase_id''',(tuple(ids),))
         for oid,nbr,state in cr.fetchall():
             if state=='cancel':
                 continue
@@ -128,7 +130,7 @@ class purchase_order(osv.osv):
                 res[r] = 100.0 * res[r][0] / res[r][1]
         return res
 
-    def _get_order(self, cr, uid, ids, context={}):
+    def _get_order(self, cr, uid, ids, context=None):
         result = {}
         for line in self.pool.get('purchase.order.line').browse(cr, uid, ids, context=context):
             result[line.order_id.id] = True
@@ -143,73 +145,83 @@ class purchase_order(osv.osv):
                 res[purchase.id] = False
         return res
 
+    STATE_SELECTION = [
+        ('draft', 'Request for Quotation'),
+        ('wait', 'Waiting'),
+        ('confirmed', 'Waiting Supplier Ack'),
+        ('approved', 'Approved'),
+        ('except_picking', 'Shipping Exception'),
+        ('except_invoice', 'Invoice Exception'),
+        ('done', 'Done'),
+        ('cancel', 'Cancelled')
+    ]
+
     _columns = {
-        'name': fields.char('Order Reference', size=64, required=True, select=True),
-        'origin': fields.char('Origin', size=64,
+        '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"),
+        'origin': fields.char('Source Document', size=64,
             help="Reference of the document that generated this purchase order request."
         ),
-        'partner_ref': fields.char('Supplier Ref.', size=64),
+        'partner_ref': fields.char('Supplier Reference', states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, size=64),
         'date_order':fields.date('Date Ordered', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}, help="Date on which this document has been created."),
-        'date_approve':fields.date('Date Approved', readonly=1),
-        'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}, change_default=True),
-        'partner_address_id':fields.many2one('res.partner.address', 'Supplier Address', required=True, states={'posted':[('readonly',True)]}),
-
-        'dest_address_id':fields.many2one('res.partner.address', 'Destination Address', states={'posted':[('readonly',True)]},
+        'date_approve':fields.date('Date Approved', readonly=1, help="Date on which purchase order has been approved"),
+        'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, change_default=True),
+        'partner_address_id':fields.many2one('res.partner.address', 'Address', required=True,
+            states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},domain="[('partner_id', '=', partner_id)]"),
+        'dest_address_id':fields.many2one('res.partner.address', 'Destination Address',
+            states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},
             help="Put an address if you want to deliver directly from the supplier to the customer." \
                 "In this case, it will remove the warehouse link and set the customer location."
         ),
-        'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', states={'posted':[('readonly',True)]}),
-        'location_id': fields.many2one('stock.location', 'Destination', required=True),
-
-        'pricelist_id':fields.many2one('product.pricelist', 'Pricelist', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}, help="The pricelist sets the currency used for this purchase order. It also computes the supplier price for the selected products/quantities."),
-
-        'state': fields.selection([('draft', 'Request for Quotation'), ('wait', 'Waiting'), ('confirmed', 'Waiting Supplier Ack'), ('approved', 'Approved'),('except_picking', 'Shipping Exception'), ('except_invoice', 'Invoice Exception'), ('done', 'Done'), ('cancel', 'Cancelled')], 'Order State', 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),
-        'order_line': fields.one2many('purchase.order.line', 'order_id', 'Order Lines', states={'approved':[('readonly',True)]}),
+        'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}),
+        'location_id': fields.many2one('stock.location', 'Destination', required=True, domain=[('usage','<>','view')]),
+        '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."),
+        'state': fields.selection(STATE_SELECTION, 'State', 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),
+        'order_line': fields.one2many('purchase.order.line', 'order_id', 'Order Lines', states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
         'validator' : fields.many2one('res.users', 'Validated by', readonly=True),
         'notes': fields.text('Notes'),
-        'invoice_id': fields.many2one('account.invoice', 'Invoice', readonly=True),
+        'invoice_id': fields.many2one('account.invoice', 'Invoice', readonly=True, help="An invoice generated for a purchase order"),
         'picking_ids': fields.one2many('stock.picking', 'purchase_id', 'Picking List', readonly=True, help="This is the list of picking list that have been generated for this purchase"),
-        'shipped':fields.boolean('Received', readonly=True, select=True),
+        'shipped':fields.boolean('Received', readonly=True, select=True, help="It indicates that a picking has been done"),
         'shipped_rate': fields.function(_shipped_rate, method=True, string='Received', type='float'),
-        'invoiced': fields.function(_invoiced, method=True, string='Invoiced & Paid', type='boolean'),
+        'invoiced': fields.function(_invoiced, method=True, string='Invoiced & Paid', type='boolean', help="It indicates that an invoice has been paid"),
         'invoiced_rate': fields.function(_invoiced_rate, method=True, string='Invoiced', type='float'),
         'invoice_method': fields.selection([('manual','Manual'),('order','From Order'),('picking','From Picking')], 'Invoicing Control', required=True,
             help="From Order: a draft invoice will be pre-generated based on the purchase order. The accountant " \
                 "will just have to validate this invoice for control.\n" \
-                "From Picking: a draft invoice will be pre-genearted based on validated receptions.\n" \
+                "From Picking: a draft invoice will be pre-generated based on validated receptions.\n" \
                 "Manual: no invoice will be pre-generated. The accountant will have to encode manually."
         ),
-        'minimum_planned_date':fields.function(_minimum_planned_date, fnct_inv=_set_minimum_planned_date, method=True,store=True, string='Planned Date', type='datetime', help="This is computed as the minimum scheduled date of all purchase order lines' products."),
-        'amount_untaxed': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Untaxed Amount',
+        'minimum_planned_date':fields.function(_minimum_planned_date, fnct_inv=_set_minimum_planned_date, method=True,store=True, string='Expected Date', type='date', help="This is computed as the minimum scheduled date of all purchase order lines' products."),
+        'amount_untaxed': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Purchase Price'), string='Untaxed Amount',
             store={
                 'purchase.order.line': (_get_order, None, 10),
-            }, multi="sums"),
-        'amount_tax': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Taxes',
+            }, multi="sums", help="The amount without tax"),
+        'amount_tax': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Purchase Price'), string='Taxes',
             store={
                 'purchase.order.line': (_get_order, None, 10),
-            }, multi="sums"),
-        'amount_total': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Total',
+            }, multi="sums", help="The tax amount"),
+        'amount_total': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Purchase Price'), string='Total',
             store={
                 'purchase.order.line': (_get_order, None, 10),
-            }, multi="sums"),
+            }, multi="sums",help="The total amount"),
         'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position'),
         'product_id': fields.related('order_line','product_id', type='many2one', relation='product.product', string='Product'),
         'create_uid':  fields.many2one('res.users', 'Responsible'),
         'company_id': fields.many2one('res.company','Company',required=True,select=1),
     }
     _defaults = {
-        'date_order': lambda *a: time.strftime('%Y-%m-%d'),
-        'state': lambda *a: 'draft',
+        'date_order': time.strftime('%Y-%m-%d'),
+        'state': 'draft',
         'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
-        'shipped': lambda *a: 0,
-        'invoice_method': lambda *a: 'order',
-        'invoiced': lambda *a: 0,
+        'shipped': 0,
+        'invoice_method': 'order',
+        'invoiced': 0,
         'partner_address_id': lambda self, cr, uid, context: context.get('partner_id', False) and self.pool.get('res.partner').address_get(cr, uid, [context['partner_id']], ['default'])['default'],
         '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,
-        'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'purchase.order', c),
+        'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'purchase.order', context=c),
     }
     _name = "purchase.order"
-    _description = "Purchase order"
+    _description = "Purchase Order"
     _order = "name desc"
 
     def unlink(self, cr, uid, ids, context=None):
@@ -219,7 +231,14 @@ class purchase_order(osv.osv):
             if s['state'] in ['draft','cancel']:
                 unlink_ids.append(s['id'])
             else:
-                raise osv.except_osv(_('Invalid action !'), _('Cannot delete Purchase Order(s) which are in %s State!' % s['state']))
+                raise osv.except_osv(_('Invalid action !'), _('Cannot delete Purchase Order(s) which are in %s State!')  % _(dict(purchase_order.STATE_SELECTION).get(s['state'])))
+
+        # TODO: temporary fix in 5.0, to remove in 5.2 when subflows support
+        # automatically sending subflow.delete upon deletion
+        wf_service = netsvc.LocalService("workflow")
+        for id in unlink_ids:
+            wf_service.trg_validate(uid, 'purchase.order', id, 'purchase_cancel', cr)
+
         return super(purchase_order, self).unlink(cr, uid, unlink_ids, context=context)
 
     def button_dummy(self, cr, uid, ids, context={}):
@@ -239,6 +258,7 @@ class purchase_order(osv.osv):
         return {'value':{'location_id': res, 'dest_address_id': False}}
 
     def onchange_partner_id(self, cr, uid, ids, part):
+
         if not part:
             return {'value':{'partner_address_id': False, 'fiscal_position': False}}
         addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['default'])
@@ -249,15 +269,32 @@ class purchase_order(osv.osv):
 
     def wkf_approve_order(self, cr, uid, ids, context={}):
         self.write(cr, uid, ids, {'state': 'approved', 'date_approve': time.strftime('%Y-%m-%d')})
+        for (id,name) in self.name_get(cr, uid, ids):
+                message = _('Purchase order ') + " '" + name + "' "+_("is approved by the supplier")
+                self.log(cr, uid, id, message)
         return True
 
+    #TODO: implement messages system
     def wkf_confirm_order(self, cr, uid, ids, context={}):
+        product = []
+        todo = []
         for po in self.browse(cr, uid, ids):
-            if self.pool.get('res.partner.event.type').check(cr, uid, 'purchase_open'):
-                self.pool.get('res.partner.event').create(cr, uid, {'name':'Purchase Order: '+po.name, 'partner_id':po.partner_id.id, 'date':time.strftime('%Y-%m-%d %H:%M:%S'), 'user_id':uid, 'partner_type':'retailer', 'probability': 1.0, 'planned_cost':po.amount_untaxed})
+            if not po.order_line:
+                raise osv.except_osv(_('Error !'),_('You can not confirm purchase order without Purchase Order Lines.'))
+            for line in po.order_line:
+                if line.state=='draft':
+                    todo.append(line.id)
         current_name = self.name_get(cr, uid, ids)[0][1]
+        self.pool.get('purchase.order.line').action_confirm(cr, uid, todo, context)
+        message = _('confirm Order') + " ' " + po.name
+        self.log(cr,uid,po,message)
         for id in ids:
             self.write(cr, uid, [id], {'state' : 'confirmed', 'validator' : uid})
+            for line in po.order_line:
+                product.append(line.product_id.default_code or '')
+                params = ', '.join(map(lambda x : str(x), product))
+            message = _('Purchase order ') + " '" + po.name + "' "+_('placed on')+ " '" + po.date_order + "' "+_('for')+" '" + params + "' "+ _("is confirmed")
+            self.log(cr, uid, id, message)
         return True
 
     def wkf_warn_buyer(self, cr, uid, ids):
@@ -270,14 +307,14 @@ class purchase_order(osv.osv):
                 if manager and not (manager.id in managers):
                     managers.append(manager.id)
             for manager_id in managers:
-                request.create(cr, uid,
-                      {'name' : "Purchase amount over the limit",
+                request.create(cr, uid,{
+                       'name' : "Purchase amount over the limit",
                        'act_from' : uid,
                        'act_to' : manager_id,
                        'body': 'Somebody has just confirmed a purchase with an amount over the defined limit',
                        'ref_partner_id': po.partner_id.id,
                        'ref_doc1': 'purchase.order,%d' % (po.id,),
-                       })
+                })
     def inv_line_create(self, cr, uid, a, ol):
         return (0, False, {
             'name': ol.name,
@@ -287,7 +324,7 @@ class purchase_order(osv.osv):
             'product_id': ol.product_id.id or False,
             'uos_id': ol.product_uom.id or False,
             'invoice_line_tax_id': [(6, 0, [x.id for x in ol.taxes_id])],
-            'account_analytic_id': ol.account_analytic_id.id,
+            'account_analytic_id': ol.account_analytic_id.id or False,
         })
 
     def action_cancel_draft(self, cr, uid, ids, *args):
@@ -296,16 +333,23 @@ class purchase_order(osv.osv):
         self.write(cr, uid, ids, {'state':'draft','shipped':0})
         wf_service = netsvc.LocalService("workflow")
         for p_id in ids:
+            # Deleting the existing instance of workflow for PO
+            wf_service.trg_delete(uid, 'purchase.order', p_id, cr)
             wf_service.trg_create(uid, 'purchase.order', p_id, cr)
+        for (id,name) in self.name_get(cr, uid, ids):
+                message = _('Purchase order') + " '" + name + "' "+ _("is in the draft state")
+                self.log(cr, uid, id, message)
         return True
 
     def action_invoice_create(self, cr, uid, ids, *args):
         res = False
+
         journal_obj = self.pool.get('account.journal')
         for o in self.browse(cr, uid, ids):
             il = []
+            todo = []
             for ol in o.order_line:
-
+                todo.append(ol.id)
                 if ol.product_id:
                     a = ol.product_id.product_tmpl_id.property_account_expense.id
                     if not a:
@@ -313,7 +357,7 @@ class purchase_order(osv.osv):
                     if not a:
                         raise osv.except_osv(_('Error !'), _('There is no expense account defined for this product: "%s" (id:%d)') % (ol.product_id.name, ol.product_id.id,))
                 else:
-                    a = self.pool.get('ir.property').get(cr, uid, 'property_account_expense_categ', 'product.category')
+                    a = self.pool.get('ir.property').get(cr, uid, 'property_account_expense_categ', 'product.category').id
                 fpos = o.fiscal_position or False
                 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
                 il.append(self.inv_line_create(cr, uid, a, ol))
@@ -341,7 +385,7 @@ class purchase_order(osv.osv):
             }
             inv_id = self.pool.get('account.invoice').create(cr, uid, inv, {'type':'in_invoice'})
             self.pool.get('account.invoice').button_compute(cr, uid, [inv_id], {'type':'in_invoice'}, set_total=True)
-
+            self.pool.get('purchase.order.line').write(cr, uid, todo, {'invoiced':True})
             self.write(cr, uid, [o.id], {'invoice_id': inv_id})
             res = inv_id
         return res
@@ -374,6 +418,8 @@ class purchase_order(osv.osv):
                 wf_service = netsvc.LocalService("workflow")
                 wf_service.trg_validate(uid, 'account.invoice', inv.id, 'invoice_cancel', cr)
         self.write(cr,uid,ids,{'state':'cancel'})
+        message = _('Purchase order ') + " '" + purchase.name + "' "+ _("is cancelled")
+        self.log(cr, uid, id, message)
         return True
 
     def action_picking_create(self,cr, uid, ids, *args):
@@ -383,20 +429,24 @@ class purchase_order(osv.osv):
             istate = 'none'
             if order.invoice_method=='picking':
                 istate = '2binvoiced'
+            pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.in')
             picking_id = self.pool.get('stock.picking').create(cr, uid, {
+                'name': pick_name,
                 'origin': order.name+((order.origin and (':'+order.origin)) or ''),
                 'type': 'in',
                 'address_id': order.dest_address_id.id or order.partner_address_id.id,
                 'invoice_state': istate,
                 'purchase_id': order.id,
                 'company_id': order.company_id.id,
+                'move_lines' : [],
             })
+            todo_moves = []
             for order_line in order.order_line:
                 if not order_line.product_id:
                     continue
                 if order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
                     dest = order.location_id.id
-                    self.pool.get('stock.move').create(cr, uid, {
+                    move = self.pool.get('stock.move').create(cr, uid, {
                         'name': 'PO:'+order_line.name,
                         'product_id': order_line.product_id.id,
                         'product_qty': order_line.product_qty,
@@ -404,16 +454,20 @@ class purchase_order(osv.osv):
                         'product_uom': order_line.product_uom.id,
                         'product_uos': order_line.product_uom.id,
                         'date_planned': order_line.date_planned,
+                        'date_expected': order_line.date_planned,
                         'location_id': loc_id,
                         'location_dest_id': dest,
                         'picking_id': picking_id,
                         'move_dest_id': order_line.move_dest_id.id,
-                        'state': 'assigned',
+                        'state': 'draft',
                         'purchase_line_id': order_line.id,
                         'company_id': order.company_id.id,
                     })
                     if order_line.move_dest_id:
                         self.pool.get('stock.move').write(cr, uid, [order_line.move_dest_id.id], {'location_id':order.location_id.id})
+                    todo_moves.append(move)
+            self.pool.get('stock.move').action_confirm(cr, uid, todo_moves)
+            self.pool.get('stock.move').force_assign(cr, uid, todo_moves)
             wf_service = netsvc.LocalService("workflow")
             wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
         return picking_id
@@ -431,59 +485,182 @@ class purchase_order(osv.osv):
         })
         return super(purchase_order, self).copy(cr, uid, id, default, context)
 
+
+    def do_merge(self, cr, uid, ids, context):
+        """
+        To merge similar type of purchase orders.
+        Orders will only be merged if:
+        * Purchase Orders are in draft
+        * Purchase Orders belong to the same partner
+        * Purchase Orders are have same stock location, same pricelist
+        Lines will only be merged if:
+        * Order lines are exactly the same except for the quantity and unit
+
+         @param self: The object pointer.
+         @param cr: A database cursor
+         @param uid: ID of the user currently logged in
+         @param ids: the ID or list of IDs
+         @param context: A standard dictionary
+
+         @return: new purchase order id
+
+        """
+        wf_service = netsvc.LocalService("workflow")
+        def make_key(br, fields):
+            list_key = []
+            for field in fields:
+                field_val = getattr(br, field)
+                if field in ('product_id', 'move_dest_id', 'account_analytic_id'):
+                    if not field_val:
+                        field_val = False
+                if isinstance(field_val, browse_record):
+                    field_val = field_val.id
+                elif isinstance(field_val, browse_null):
+                    field_val = False
+                elif isinstance(field_val, list):
+                    field_val = ((6, 0, tuple([v.id for v in field_val])),)
+                list_key.append((field, field_val))
+            list_key.sort()
+            return tuple(list_key)
+
+    # compute what the new orders should contain
+
+        new_orders = {}
+
+        for porder in [order for order in self.browse(cr, uid, ids) if order.state == 'draft']:
+            order_key = make_key(porder, ('partner_id', 'location_id', 'pricelist_id'))
+            new_order = new_orders.setdefault(order_key, ({}, []))
+            new_order[1].append(porder.id)
+            order_infos = new_order[0]
+            if not order_infos:
+                order_infos.update({
+                    'origin': porder.origin,
+                    'date_order': time.strftime('%Y-%m-%d'),
+                    'partner_id': porder.partner_id.id,
+                    'partner_address_id': porder.partner_address_id.id,
+                    'dest_address_id': porder.dest_address_id.id,
+                    'warehouse_id': porder.warehouse_id.id,
+                    'location_id': porder.location_id.id,
+                    'pricelist_id': porder.pricelist_id.id,
+                    'state': 'draft',
+                    'order_line': {},
+                    'notes': '%s' % (porder.notes or '',),
+                    'fiscal_position': porder.fiscal_position and porder.fiscal_position.id or False,
+                })
+            else:
+                if porder.notes:
+                    order_infos['notes'] = (order_infos['notes'] or '') + ('\n%s' % (porder.notes,))
+                if porder.origin:
+                    order_infos['origin'] = (order_infos['origin'] or '') + ' ' + porder.origin
+
+            for order_line in porder.order_line:
+                line_key = make_key(order_line, ('name', 'date_planned', 'taxes_id', 'price_unit', 'notes', 'product_id', 'move_dest_id', 'account_analytic_id'))
+                o_line = order_infos['order_line'].setdefault(line_key, {})
+                if o_line:
+                    # merge the line with an existing line
+                    o_line['product_qty'] += order_line.product_qty * order_line.product_uom.factor / o_line['uom_factor']
+                else:
+                    # append a new "standalone" line
+                    for field in ('product_qty', 'product_uom'):
+                        field_val = getattr(order_line, field)
+                        if isinstance(field_val, browse_record):
+                            field_val = field_val.id
+                        o_line[field] = field_val
+                    o_line['uom_factor'] = order_line.product_uom and order_line.product_uom.factor or 1.0
+
+
+
+        allorders = []
+        for order_key, (order_data, old_ids) in new_orders.iteritems():
+            # skip merges with only one order
+            if len(old_ids) < 2:
+                allorders += (old_ids or [])
+                continue
+
+            # cleanup order line data
+            for key, value in order_data['order_line'].iteritems():
+                del value['uom_factor']
+                value.update(dict(key))
+            order_data['order_line'] = [(0, 0, value) for value in order_data['order_line'].itervalues()]
+
+            # create the new order
+            neworder_id = self.create(cr, uid, order_data)
+            allorders.append(neworder_id)
+
+            # make triggers pointing to the old orders point to the new order
+            for old_id in old_ids:
+                wf_service.trg_redirect(uid, 'purchase.order', old_id, neworder_id, cr)
+                wf_service.trg_validate(uid, 'purchase.order', old_id, 'purchase_cancel', cr)
+        return allorders
+
 purchase_order()
 
 class purchase_order_line(osv.osv):
-    def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
+    def _amount_line(self, cr, uid, ids, prop, arg,context):
         res = {}
         cur_obj=self.pool.get('res.currency')
-        for line in self.browse(cr, uid, ids):
+        tax_obj = self.pool.get('account.tax')
+        for line in self.browse(cr, uid, ids, context=context):
+            taxes = tax_obj.compute_all(cr, uid, line.taxes_id, line.price_unit, line.product_qty)
             cur = line.order_id.pricelist_id.currency_id
-            res[line.id] = cur_obj.round(cr, uid, cur, line.price_unit * line.product_qty)
+            res[line.id] = cur_obj.round(cr, uid, cur, taxes['total'])
         return res
 
     _columns = {
         'name': fields.char('Description', size=256, required=True),
         'product_qty': fields.float('Quantity', required=True, digits=(16,2)),
-        'date_planned': fields.datetime('Scheduled date', required=True),
+        'date_planned': fields.date('Scheduled date', required=True),
         'taxes_id': fields.many2many('account.tax', 'purchase_order_taxe', 'ord_id', 'tax_id', 'Taxes'),
         'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
         'product_id': fields.many2one('product.product', 'Product', domain=[('purchase_ok','=',True)], change_default=True),
         'move_ids': fields.one2many('stock.move', 'purchase_line_id', 'Reservation', readonly=True, ondelete='set null'),
         'move_dest_id': fields.many2one('stock.move', 'Reservation Destination', ondelete='set null'),
-        'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
-        'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal', digits=(16, int(config['price_accuracy']))),
+        'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Purchase Price')),
+        'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal', digits_compute= dp.get_precision('Purchase Price')),
         'notes': fields.text('Notes'),
-        'order_id': fields.many2one('purchase.order', 'Order Ref', select=True, required=True, ondelete='cascade'),
+        'order_id': fields.many2one('purchase.order', 'Order Reference', select=True, required=True, ondelete='cascade'),
         'account_analytic_id':fields.many2one('account.analytic.account', 'Analytic Account',),
-        'company_id': fields.related('order_id','company_id',type='many2one',relation='res.company',string='Company')
+        'company_id': fields.related('order_id','company_id',type='many2one',relation='res.company',string='Company'),
+        'state': fields.selection([('draft', 'Draft'), ('confirmed', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')], 'State', required=True, readonly=True,
+                                  help=' * The \'Draft\' state is set automatically when purchase order in draft state. \
+                                       \n* The \'Confirmed\' state is set automatically as confirm when purchase order in confirm state. \
+                                       \n* The \'Done\' state is set automatically when purchase order is set as done. \
+                                       \n* The \'Cancelled\' state is set automatically when user cancel purchase order.'),
+        'invoice_lines': fields.many2many('account.invoice.line', 'purchase_order_line_invoice_rel', 'order_line_id', 'invoice_id', 'Invoice Lines', readonly=True),
+        'invoiced': fields.boolean('Invoiced', readonly=True),
+        'partner_id': fields.related('order_id','partner_id',string='Partner',readonly=True,type="many2one", relation="res.partner"),
+        'date_order': fields.related('order_id','date_order',string='Order Date',readonly=True,type="date")
+
     }
     _defaults = {
-        'product_qty': lambda *a: 1.0
+        'product_qty': lambda *a: 1.0,
+        'state': lambda *args: 'draft',
+        'invoiced': lambda *a: 0,
     }
     _table = 'purchase_order_line'
     _name = 'purchase.order.line'
-    _description = 'Purchase Order lines'
+    _description = 'Purchase Order Line'
+
     def copy_data(self, cr, uid, id, default=None,context={}):
         if not default:
             default = {}
-        default.update({'state':'draft', 'move_ids':[]})
+        default.update({'state':'draft', 'move_ids':[],'invoiced':0,'invoice_lines':[]})
         return super(purchase_order_line, self).copy_data(cr, uid, id, default, context)
 
     def product_id_change(self, cr, uid, ids, pricelist, product, qty, uom,
-            partner_id, date_order=False, fiscal_position=False, date_planned=False, 
+            partner_id, date_order=False, fiscal_position=False, date_planned=False,
             name=False, price_unit=False, notes=False):
         if not pricelist:
-            raise osv.except_osv(_('No Pricelist !'), _('You have to select a pricelist in the purchase form !\nPlease set one before choosing a product.'))
+            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.'))
         if not  partner_id:
             raise osv.except_osv(_('No Partner!'), _('You have to select a partner in the purchase form !\nPlease set one partner before choosing a product.'))
         if not product:
             return {'value': {'price_unit': price_unit or 0.0, 'name': name or '',
                 'notes': notes or'', 'product_uom' : uom or False}, 'domain':{'product_uom':[]}}
-        prod= self.pool.get('product.product').browse(cr, uid,product)
+        prod= self.pool.get('product.product').browse(cr, uid, product)
         lang=False
         if partner_id:
-            lang=self.pool.get('res.partner').read(cr, uid, partner_id)['lang']
+            lang=self.pool.get('res.partner').read(cr, uid, partner_id, ['lang'])['lang']
         context={'lang':lang}
         context['partner_id'] = partner_id
 
@@ -493,15 +670,6 @@ class purchase_order_line(osv.osv):
             uom = prod_uom_po
         if not date_order:
             date_order = time.strftime('%Y-%m-%d')
-        if price_unit:
-            price = price_unit
-        else:
-            price = self.pool.get('product.pricelist').price_get(cr,uid,[pricelist],
-                product, qty or 1.0, partner_id, {
-                    'uom': uom,
-                    'date': date_order,
-                    })[pricelist]
-
         qty = qty or 1.0
         seller_delay = 0
         for s in prod.seller_ids:
@@ -510,12 +678,19 @@ class purchase_order_line(osv.osv):
                 temp_qty = s.qty # supplier _qty assigned to temp
                 if qty < temp_qty: # If the supplier quantity is greater than entered from user, set minimal.
                     qty = temp_qty
-
-        dt = (DateTime.now() + DateTime.RelativeDateTime(days=seller_delay or 0.0)).strftime('%Y-%m-%d %H:%M:%S')
+        if price_unit:
+            price = price_unit
+        else:
+            price = self.pool.get('product.pricelist').price_get(cr,uid,[pricelist],
+                    product, qty or 1.0, partner_id, {
+                        'uom': uom,
+                        'date': date_order,
+                        })[pricelist]
+        dt = (datetime.now() + relativedelta(days=int(seller_delay) or 0.0)).strftime('%Y-%m-%d %H:%M:%S')
         prod_name = self.pool.get('product.product').name_get(cr, uid, [prod.id])[0][1]
 
 
-        res = {'value': {'price_unit': price, 'name': name or prod_name, 
+        res = {'value': {'price_unit': price, 'name': name or prod_name,
             'taxes_id':map(lambda x: x.id, prod.supplier_taxes_id),
             'date_planned': date_planned or dt,'notes': notes or prod.description_purchase,
             'product_qty': qty,
@@ -537,15 +712,106 @@ class purchase_order_line(osv.osv):
         return res
 
     def product_uom_change(self, cr, uid, ids, pricelist, product, qty, uom,
-            partner_id, date_order=False):
+            partner_id, date_order=False,fiscal_position=False):
         res = self.product_id_change(cr, uid, ids, pricelist, product, qty, uom,
-                partner_id, date_order=date_order)
+                partner_id, date_order=date_order,fiscal_position=fiscal_position)
         if 'product_uom' in res['value']:
             del res['value']['product_uom']
         if not uom:
             res['value']['price_unit'] = 0.0
         return res
+
+    def action_confirm(self, cr, uid, ids, context={}):
+        self.write(cr, uid, ids, {'state': 'confirmed'}, context)
+        for (id,name) in self.name_get(cr, uid, ids):
+            message = _('Purchase order line') + " '" + name + "' "+ _("is confirmed")
+            self.log(cr, uid, id, message)
+        return True
+
 purchase_order_line()
 
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+class procurement_order(osv.osv):
+    _inherit = 'procurement.order'
+    _columns = {
+        'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
+    }
+
+    def action_po_assign(self, cr, uid, ids, context={}):
+        """ This is action which call from workflow to assign purchase order to procurements
+        @return: True
+        """
+        res = self.make_po(cr, uid, ids, context=context)
+        res = res.values()
+        return len(res) and res[0] or 0 #TO CHECK: why workflow is generated error if return not integer value
+
+    def make_po(self, cr, uid, ids, context={}):
+        """ Make purchase order from procurement
+        @return: New created Purchase Orders procurement wise
+        """
+        res = {}
+        company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
+        partner_obj = self.pool.get('res.partner')
+        uom_obj = self.pool.get('product.uom')
+        pricelist_obj = self.pool.get('product.pricelist')
+        prod_obj = self.pool.get('product.product')
+        acc_pos_obj = self.pool.get('account.fiscal.position')
+        po_obj = self.pool.get('purchase.order')
+        for procurement in self.browse(cr, uid, ids):
+            res_id = procurement.move_id.id
+            partner = procurement.product_id.seller_id # Taken Main Supplier of Product of Procurement.
+            seller_qty = procurement.product_id.seller_qty
+            seller_delay = int(procurement.product_id.seller_delay)
+            partner_id = partner.id
+            address_id = partner_obj.address_get(cr, uid, [partner_id], ['delivery'])['delivery']
+            pricelist_id = partner.property_product_pricelist_purchase.id
+
+            uom_id = procurement.product_id.uom_po_id.id
+
+            qty = uom_obj._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
+            if seller_qty:
+                qty = max(qty,seller_qty)
+
+            price = pricelist_obj.price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, False, {'uom': uom_id})[pricelist_id]
+
+            newdate = DateTime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S')
+            newdate = newdate - DateTime.RelativeDateTime(days=company.po_lead)
+            newdate = newdate - seller_delay
+
+            #Passing partner_id to context for purchase order line integrity of Line name
+            context.update({'lang': partner.lang, 'partner_id': partner_id})
+
+            product = prod_obj.browse(cr, uid, procurement.product_id.id, context=context)
+
+            line = {
+                'name': product.partner_ref,
+                'product_qty': qty,
+                'product_id': procurement.product_id.id,
+                'product_uom': uom_id,
+                'price_unit': price,
+                'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
+                'move_dest_id': res_id,
+                'notes': product.description_purchase,
+            }
 
+            taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
+            taxes = acc_pos_obj.map_tax(cr, uid, partner.property_account_position, taxes_ids)
+            line.update({
+                'taxes_id': [(6,0,taxes)]
+            })
+            purchase_id = po_obj.create(cr, uid, {
+                'origin': procurement.origin,
+                'partner_id': partner_id,
+                'partner_address_id': address_id,
+                'location_id': procurement.location_id.id,
+                'pricelist_id': pricelist_id,
+                'order_line': [(0,0,line)],
+                'company_id': procurement.company_id.id,
+                'fiscal_position': partner.property_account_position and partner.property_account_position.id or False
+            })
+            res[procurement.id] = purchase_id
+            self.write(cr, uid, [procurement.id], {'state': 'running', 'purchase_id': purchase_id})
+        return res
+
+procurement_order()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: