[MERGE] merge with latest stable
[odoo/odoo.git] / addons / purchase / purchase.py
index e7f5473..0767e20 100644 (file)
 #
 ##############################################################################
 
-from mx import DateTime
 import time
 from datetime import datetime
 from dateutil.relativedelta import relativedelta
 
 from osv import osv, fields
 import netsvc
-import ir
 import pooler
-from tools import config
 from tools.translate import _
 import decimal_precision as dp
 from osv.orm import browse_record, browse_null
@@ -37,6 +34,7 @@ from osv.orm import browse_record, browse_null
 # Model definition
 #
 class purchase_order(osv.osv):
+
     def _calc_amount(self, cr, uid, ids, prop, unknow_none, unknow_dict):
         res = {}
         for order in self.browse(cr, uid, ids):
@@ -48,18 +46,18 @@ class purchase_order(osv.osv):
     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):
+        for order in self.browse(cr, uid, ids, context=context):
             res[order.id] = {
                 'amount_untaxed': 0.0,
                 'amount_tax': 0.0,
                 'amount_total': 0.0,
-                            }
+            }
             val = val1 = 0.0
             cur = order.pricelist_id.currency_id
             for line in order.order_line:
                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['amount']
+                    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']
@@ -69,19 +67,22 @@ class purchase_order(osv.osv):
         if not value: return False
         if type(ids)!=type([]):
             ids=[ids]
-        for po in self.browse(cr, uid, ids, context):
-            cr.execute("""update purchase_order_line set
-                    date_planned=%s
-                where
-                    order_id=%s and
-                    (date_planned=%s or date_planned<%s)""", (value,po.id,po.minimum_planned_date,value))
+        for po in self.browse(cr, uid, ids, context=context):
+            if po.order_line:
+                cr.execute("""update purchase_order_line set
+                        date_planned=%s
+                    where
+                        order_id=%s and
+                        (date_planned=%s or date_planned<%s)""", (value,po.id,po.minimum_planned_date,value))
+            cr.execute("""update purchase_order set
+                    minimum_planned_date=%s where id=%s""", (value, po.id))
         return True
 
     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] = time.strftime('%Y-%m-%d %H:%M:%S')
+            res[purchase.id] = False
             if purchase.order_line:
                 min_date=purchase.order_line[0].date_planned
                 for line in purchase.order_line:
@@ -90,14 +91,17 @@ class purchase_order(osv.osv):
                 res[purchase.id]=min_date
         return res
 
+
     def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
         res = {}
         for purchase in self.browse(cursor, user, ids, context=context):
             tot = 0.0
-            if purchase.invoice_id and purchase.invoice_id.state not in ('draft','cancel'):
-                tot += purchase.invoice_id.amount_untaxed
+            for invoice in purchase.invoice_ids:
+                if invoice.state not in ('draft','cancel'):
+                    tot += invoice.amount_untaxed
+            
             if purchase.amount_untaxed:
-                res[purchase.id] = tot * 100.0 / purchase.amount_untaxed
+                res[purchase.id] = min(100.0, tot * 100.0 / (purchase.amount_untaxed))
             else:
                 res[purchase.id] = 0.0
         return res
@@ -139,20 +143,31 @@ class purchase_order(osv.osv):
     def _invoiced(self, cursor, user, ids, name, arg, context=None):
         res = {}
         for purchase in self.browse(cursor, user, ids, context=context):
-            if purchase.invoice_id.reconciled:
-                res[purchase.id] = purchase.invoice_id.reconciled
-            else:
-                res[purchase.id] = False
+            invoiced = False
+            if purchase.invoiced_rate == 100.00:
+                invoiced = True
+            res[purchase.id] = invoiced
         return res
 
+    STATE_SELECTION = [
+        ('draft', 'Request for Quotation'),
+        ('wait', 'Waiting'),
+        ('confirmed', 'Waiting Approval'),
+        ('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, 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 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, help="Date on which purchase order has been approved"),
+        'date_order':fields.date('Date Ordered', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}, select=True, help="Date on which this document has been created."),
+        'date_approve':fields.date('Date Approved', readonly=1, select=True, 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)]"),
@@ -164,11 +179,11 @@ class purchase_order(osv.osv):
         '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([('draft', 'Request for Quotation'), ('wait', 'Waiting'), ('confirmed', 'Waiting Supplier Ack'), ('approved', 'Approved'),('except_picking', 'Shipping Exception'), ('except_invoice', 'Invoice Exception'), ('done', 'Done'), ('cancel', 'Cancelled')], '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),
+        '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, help="An invoice generated for a purchase order"),
+        'invoice_ids': fields.many2many('account.invoice', 'purchase_invoice_rel', 'purchase_id', 'invoice_id', 'Invoices', help="Invoices 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, help="It indicates that a picking has been done"),
         'shipped_rate': fields.function(_shipped_rate, method=True, string='Received', type='float'),
@@ -178,9 +193,13 @@ class purchase_order(osv.osv):
             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-generated based on validated receptions.\n" \
-                "Manual: no invoice will be pre-generated. The accountant will have to encode manually."
+                "Manual: allows you to generate suppliers invoices by chosing in the uninvoiced lines of all manual purchase orders."
+        ),
+        'minimum_planned_date':fields.function(_minimum_planned_date, fnct_inv=_set_minimum_planned_date, method=True, string='Expected Date', type='date', select=True, help="This is computed as the minimum scheduled date of all purchase order lines' products.",
+            store = {
+                'purchase.order.line': (_get_order, ['date_planned'], 10),
+            }
         ),
-        '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),
@@ -199,7 +218,7 @@ class purchase_order(osv.osv):
         'company_id': fields.many2one('res.company','Company',required=True,select=1),
     }
     _defaults = {
-        'date_order': time.strftime('%Y-%m-%d'),
+        'date_order': lambda *a: time.strftime('%Y-%m-%d'),
         'state': 'draft',
         'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
         'shipped': 0,
@@ -209,18 +228,21 @@ class purchase_order(osv.osv):
         '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', context=c),
     }
+    _sql_constraints = [
+        ('name_uniq', 'unique(name)', 'Order Reference must be unique !'),
+    ]
     _name = "purchase.order"
     _description = "Purchase Order"
     _order = "name desc"
 
     def unlink(self, cr, uid, ids, context=None):
-        purchase_orders = self.read(cr, uid, ids, ['state'])
+        purchase_orders = self.read(cr, uid, ids, ['state'], context=context)
         unlink_ids = []
         for s in purchase_orders:
             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
@@ -230,15 +252,18 @@ class purchase_order(osv.osv):
 
         return super(purchase_order, self).unlink(cr, uid, unlink_ids, context=context)
 
-    def button_dummy(self, cr, uid, ids, context={}):
+    def button_dummy(self, cr, uid, ids, context=None):
         return True
 
     def onchange_dest_address_id(self, cr, uid, ids, adr_id):
         if not adr_id:
             return {}
-        part_id = self.pool.get('res.partner.address').read(cr, uid, [adr_id], ['partner_id'])[0]['partner_id'][0]
-        loc_id = self.pool.get('res.partner').browse(cr, uid, part_id).property_stock_customer.id
-        return {'value':{'location_id': loc_id, 'warehouse_id': False}}
+        values = {'warehouse_id': False}
+        part_id = self.pool.get('res.partner.address').browse(cr, uid, adr_id).partner_id
+        if part_id:
+            loc_id = part_id.property_stock_customer.id
+            values.update({'location_id': loc_id})
+        return {'value':values}
 
     def onchange_warehouse_id(self, cr, uid, ids, warehouse_id):
         if not warehouse_id:
@@ -256,32 +281,25 @@ class purchase_order(osv.osv):
         fiscal_position = part.property_account_position and part.property_account_position.id or False
         return {'value':{'partner_address_id': addr['default'], 'pricelist_id': pricelist, 'fiscal_position': fiscal_position}}
 
-    def wkf_approve_order(self, cr, uid, ids, context={}):
+    def wkf_approve_order(self, cr, uid, ids, context=None):
         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 = []
+    def wkf_confirm_order(self, cr, uid, ids, context=None):
         todo = []
-        for po in self.browse(cr, uid, ids):
+        for po in self.browse(cr, uid, ids, context=context):
             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]
+            message = _("Purchase order '%s' is confirmed.") % (po.name,)
+            self.log(cr, uid, po.id, message)
+#        current_name = self.name_get(cr, uid, ids)[0][1]
         self.pool.get('purchase.order.line').action_confirm(cr, uid, todo, context)
         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):
@@ -294,14 +312,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',
+                       '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,
@@ -324,13 +342,14 @@ class purchase_order(osv.osv):
             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)
+            message = _("Purchase order '%s' has been set in draft state.") % name
+            self.log(cr, uid, id, message)
         return True
 
     def action_invoice_create(self, cr, uid, ids, *args):
         res = False
-
+        property_obj = self.pool.get('ir.property')
+        fp_obj =  self.pool.get('account.fiscal.position')
         journal_obj = self.pool.get('account.journal')
         for o in self.browse(cr, uid, ids):
             il = []
@@ -338,26 +357,30 @@ class purchase_order(osv.osv):
             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:
-                        a = ol.product_id.categ_id.property_account_expense_categ.id
-                    if not a:
+                    acc_id = ol.product_id.product_tmpl_id.property_account_expense.id
+                    if not acc_id:
+                        acc_id = ol.product_id.categ_id.property_account_expense_categ.id
+                    if not acc_id:
                         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').id
+                    prop = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category')
+                    acc_id = prop and prop.id or False
                 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))
+                acc_id = fp_obj.map_account(cr, uid, fpos, acc_id)
+                if not acc_id:
+                    raise osv.except_osv(_('Error !'),
+                        _('There is no expense account defined in default Properties for Product Category or Fiscal Position is not defined !'))
+                il.append(self.inv_line_create(cr, uid, acc_id, ol))
 
-            a = o.partner_id.property_account_payable.id
+            acc_id = o.partner_id.property_account_payable.id
             journal_ids = journal_obj.search(cr, uid, [('type', '=','purchase'),('company_id', '=', o.company_id.id)], limit=1)
             if not journal_ids:
                 raise osv.except_osv(_('Error !'),
                     _('There is no purchase journal defined for this company: "%s" (id:%d)') % (o.company_id.name, o.company_id.id))
             inv = {
                 'name': o.partner_ref or o.name,
-                'reference': "P%dPO%d" % (o.partner_id.id, o.id),
-                'account_id': a,
+                'reference': o.partner_ref or o.name,
+                'account_id': acc_id,
                 'type': 'in_invoice',
                 'partner_id': o.partner_id.id,
                 'currency_id': o.pricelist_id.currency_id.id,
@@ -366,14 +389,14 @@ class purchase_order(osv.osv):
                 'journal_id': len(journal_ids) and journal_ids[0] or False,
                 'origin': o.name,
                 'invoice_line': il,
-                'fiscal_position': o.partner_id.property_account_position.id,
+                'fiscal_position': o.fiscal_position.id or o.partner_id.property_account_position.id,
                 'payment_term': o.partner_id.property_payment_term and o.partner_id.property_payment_term.id or False,
                 'company_id': o.company_id.id,
             }
-            inv_id = self.pool.get('account.invoice').create(cr, uid, inv, {'type':'in_invoice'})
+            inv_id = self.pool.get('account.invoice').create(cr, uid, inv, {'type':'in_invoice', 'journal_type': 'purchase'})
             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})
+            self.write(cr, uid, [o.id], {'invoice_ids': [(4, inv_id)]})
             res = inv_id
         return res
 
@@ -384,10 +407,8 @@ class purchase_order(osv.osv):
                     return True
         return False
 
-    def action_cancel(self, cr, uid, ids, context={}):
-        ok = True
-        purchase_order_line_obj = self.pool.get('purchase.order.line')
-        for purchase in self.browse(cr, uid, ids):
+    def action_cancel(self, cr, uid, ids, context=None):
+        for purchase in self.browse(cr, uid, ids, context=context):
             for pick in purchase.picking_ids:
                 if pick.state not in ('draft','cancel'):
                     raise osv.except_osv(
@@ -396,22 +417,33 @@ class purchase_order(osv.osv):
             for pick in purchase.picking_ids:
                 wf_service = netsvc.LocalService("workflow")
                 wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_cancel', cr)
-            inv = purchase.invoice_id
-            if inv and inv.state not in ('cancel','draft'):
-                raise osv.except_osv(
-                    _('Could not cancel this purchase order !'),
-                    _('You must first cancel all invoices attached to this purchase order.'))
-            if inv:
-                wf_service = netsvc.LocalService("workflow")
-                wf_service.trg_validate(uid, 'account.invoice', inv.id, 'invoice_cancel', cr)
+            for inv in purchase.invoice_ids:
+                if inv and inv.state not in ('cancel','draft'):
+                    raise osv.except_osv(
+                        _('Could not cancel this purchase order !'),
+                        _('You must first cancel all invoices attached to this purchase order.'))
+                if inv:
+                    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)
+        for (id,name) in self.name_get(cr, uid, ids):
+            message = _("Purchase order '%s' is cancelled.") % name
+            self.log(cr, uid, id, message)
         return True
 
     def action_picking_create(self,cr, uid, ids, *args):
         picking_id = False
         for order in self.browse(cr, uid, ids):
+            reception_address_id = False
+            if order.dest_address_id:
+                reception_address_id = order.dest_address_id.id
+            elif order.warehouse_id and order.warehouse_id.partner_address_id:
+                reception_address_id = order.warehouse_id.partner_address_id.id
+            else:
+                if order.company_id.partner_id.address:
+                    addresses_default = [address.id for address in order.company_id.partner_id.address if address.type == 'default']
+                    addresses_delivery = [address.id for address in order.company_id.partner_id.address if address.type == 'delivery']
+                    reception_address_id = (addresses_delivery and addresses_delivery[0]) or (addresses_default and addresses_default[0]) or False
             loc_id = order.partner_id.property_stock_supplier.id
             istate = 'none'
             if order.invoice_method=='picking':
@@ -421,7 +453,7 @@ class purchase_order(osv.osv):
                 '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,
+                'address_id': reception_address_id,
                 'invoice_state': istate,
                 'purchase_id': order.id,
                 'company_id': order.company_id.id,
@@ -434,13 +466,13 @@ class purchase_order(osv.osv):
                 if order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
                     dest = order.location_id.id
                     move = self.pool.get('stock.move').create(cr, uid, {
-                        'name': 'PO:'+order_line.name,
+                        'name': order.name + ': ' +(order_line.name or ''),
                         'product_id': order_line.product_id.id,
                         'product_qty': order_line.product_qty,
                         'product_uos_qty': order_line.product_qty,
                         'product_uom': order_line.product_uom.id,
                         'product_uos': order_line.product_uom.id,
-                        'date_planned': order_line.date_planned,
+                        'date': order_line.date_planned,
                         'date_expected': order_line.date_planned,
                         'location_id': loc_id,
                         'location_dest_id': dest,
@@ -449,6 +481,7 @@ class purchase_order(osv.osv):
                         'state': 'draft',
                         'purchase_line_id': order_line.id,
                         'company_id': order.company_id.id,
+                        'price_unit': order_line.price_unit
                     })
                     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})
@@ -459,21 +492,21 @@ class purchase_order(osv.osv):
             wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
         return picking_id
 
-    def copy(self, cr, uid, id, default=None,context={}):
+    def copy(self, cr, uid, id, default=None, context=None):
         if not default:
             default = {}
         default.update({
             'state':'draft',
             'shipped':False,
             'invoiced':False,
-            'invoice_id':False,
-            'picking_ids':[],
+            'invoice_ids': [],
+            'picking_ids': [],
             'name': self.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
         })
         return super(purchase_order, self).copy(cr, uid, id, default, context)
 
 
-    def do_merge(self, cr, uid, ids, context):
+    def do_merge(self, cr, uid, ids, context=None):
         """
         To merge similar type of purchase orders.
         Orders will only be merged if:
@@ -514,28 +547,27 @@ class purchase_order(osv.osv):
 
         new_orders = {}
 
-        for porder in [order for order in self.browse(cr, uid, ids) if order.state == 'draft']:
+        for porder in [order for order in self.browse(cr, uid, ids, context=context) 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,
+                    '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:
-                #order_infos['name'] += ', %s' % porder.name
                 if porder.notes:
                     order_infos['notes'] = (order_infos['notes'] or '') + ('\n%s' % (porder.notes,))
                 if porder.origin:
@@ -559,6 +591,7 @@ class purchase_order(osv.osv):
 
 
         allorders = []
+        orders_info = {}
         for order_key, (order_data, old_ids) in new_orders.iteritems():
             # skip merges with only one order
             if len(old_ids) < 2:
@@ -573,18 +606,19 @@ class purchase_order(osv.osv):
 
             # create the new order
             neworder_id = self.create(cr, uid, order_data)
+            orders_info.update({neworder_id: old_ids})
             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
+        return orders_info
 
 purchase_order()
 
 class purchase_order_line(osv.osv):
-    def _amount_line(self, cr, uid, ids, prop, arg,context):
+    def _amount_line(self, cr, uid, ids, prop, arg, context=None):
         res = {}
         cur_obj=self.pool.get('res.currency')
         tax_obj = self.pool.get('account.tax')
@@ -597,7 +631,7 @@ class purchase_order_line(osv.osv):
     _columns = {
         'name': fields.char('Description', size=256, required=True),
         'product_qty': fields.float('Quantity', required=True, digits=(16,2)),
-        'date_planned': fields.date('Scheduled date', required=True),
+        'date_planned': fields.date('Scheduled Date', required=True, select=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),
@@ -608,7 +642,7 @@ class purchase_order_line(osv.osv):
         'notes': fields.text('Notes'),
         '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', store=True, readonly=True),
         '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. \
@@ -616,7 +650,7 @@ class purchase_order_line(osv.osv):
                                        \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"),
+        'partner_id': fields.related('order_id','partner_id',string='Partner',readonly=True,type="many2one", relation="res.partner", store=True),
         'date_order': fields.related('order_id','date_order',string='Order Date',readonly=True,type="date")
 
     }
@@ -629,10 +663,10 @@ class purchase_order_line(osv.osv):
     _name = 'purchase.order.line'
     _description = 'Purchase Order Line'
 
-    def copy_data(self, cr, uid, id, default=None,context={}):
+    def copy_data(self, cr, uid, id, default=None, context=None):
         if not default:
             default = {}
-        default.update({'state':'draft', 'move_ids':[],'invoiced':0,'invoice_lines':[]})
+        default.update({'state':'draft', 'move_ids':[], 'move_dest_id':False, '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,
@@ -645,13 +679,15 @@ class purchase_order_line(osv.osv):
         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':[]}}
+        res = {}
         prod= self.pool.get('product.product').browse(cr, uid, product)
+
+        product_uom_pool = self.pool.get('product.uom')
         lang=False
         if partner_id:
             lang=self.pool.get('res.partner').read(cr, uid, partner_id, ['lang'])['lang']
-        context={'lang':lang}
-        context['partner_id'] = partner_id
-
+        context = self.pool.get('res.users').context_get(cr, uid)
+        context_partner = {'lang':lang, 'partner_id': partner_id}
         prod = self.pool.get('product.product').browse(cr, uid, product, context=context)
         prod_uom_po = prod.uom_po_id.id
         if not uom:
@@ -660,32 +696,43 @@ class purchase_order_line(osv.osv):
             date_order = time.strftime('%Y-%m-%d')
         qty = qty or 1.0
         seller_delay = 0
+
+        prod_name = self.pool.get('product.product').name_get(cr, uid, [prod.id], context=context_partner)[0][1]
+        res = {}
         for s in prod.seller_ids:
             if s.name.id == partner_id:
                 seller_delay = s.delay
-                temp_qty = s.qty # supplier _qty assigned to temp
+                if s.product_uom:
+                    temp_qty = product_uom_pool._compute_qty(cr, uid, s.product_uom.id, s.min_qty, to_uom_id=prod.uom_id.id)
+                    uom = s.product_uom.id #prod_uom_po
+                temp_qty = s.min_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
-        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, {
+                    res.update({'warning': {'title': _('Warning'), 'message': _('The selected supplier has a minimal quantity set to %s, you cannot purchase less.') % qty}})
+        qty_in_product_uom = product_uom_pool._compute_qty(cr, uid, uom, qty, to_uom_id=prod.uom_id.id)
+        price = self.pool.get('product.pricelist').price_get(cr,uid,[pricelist],
+                    product, qty_in_product_uom or 1.0, partner_id, {
                         'uom': uom,
                         'date': date_order,
                         })[pricelist]
+        if price is False:
+            warning = {
+                'title': 'No valid pricelist line found !',
+                'message':
+                    "Couldn't find a pricelist line matching this product and quantity.\n"
+                    "You have to change either the product, the quantity or the pricelist."
+                }
+            res.update({'warning': warning})
         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.update({'value': {'price_unit': price, 'name': 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,
-            'product_uom': uom}}
+            'product_uom': uom}})
         domain = {}
 
-        partner = self.pool.get('res.partner').browse(cr, uid, partner_id)
         taxes = self.pool.get('account.tax').browse(cr, uid,map(lambda x: x.id, prod.supplier_taxes_id))
         fpos = fiscal_position and self.pool.get('account.fiscal.position').browse(cr, uid, fiscal_position) or False
         res['value']['taxes_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
@@ -700,20 +747,22 @@ 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,fiscal_position=False):
+            partner_id, date_order=False, fiscal_position=False, date_planned=False,
+            name=False, price_unit=False, notes=False):
         res = self.product_id_change(cr, uid, ids, pricelist, product, qty, uom,
-                partner_id, date_order=date_order,fiscal_position=fiscal_position)
+                partner_id, date_order=date_order, fiscal_position=fiscal_position, date_planned=date_planned,
+            name=name, price_unit=price_unit, notes=notes)
         if 'product_uom' in res['value']:
+            if uom and (uom != res['value']['product_uom']) and res['value']['product_uom']:
+                seller_uom_name = self.pool.get('product.uom').read(cr, uid, [res['value']['product_uom']], ['name'])[0]['name']
+                res.update({'warning': {'title': _('Warning'), 'message': _('The selected supplier only sells this product by %s') % seller_uom_name }})
             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)
+    def action_confirm(self, cr, uid, ids, context=None):
+        self.write(cr, uid, ids, {'state': 'confirmed'}, context=context)
         return True
 
 purchase_order_line()
@@ -724,7 +773,7 @@ class procurement_order(osv.osv):
         'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
     }
 
-    def action_po_assign(self, cr, uid, ids, context={}):
+    def action_po_assign(self, cr, uid, ids, context=None):
         """ This is action which call from workflow to assign purchase order to procurements
         @return: True
         """
@@ -732,19 +781,22 @@ class procurement_order(osv.osv):
         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={}):
+    def make_po(self, cr, uid, ids, context=None):
         """ 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
+        if context is None:
+            context = {}
+        company = self.pool.get('res.users').browse(cr, uid, uid, context=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):
+        po_line_obj = self.pool.get('purchase.order.line')
+        for procurement in self.browse(cr, uid, ids, context=context):
             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
@@ -752,6 +804,7 @@ class procurement_order(osv.osv):
             partner_id = partner.id
             address_id = partner_obj.address_get(cr, uid, [partner_id], ['delivery'])['delivery']
             pricelist_id = partner.property_product_pricelist_purchase.id
+            fiscal_position = partner.property_account_position and partner.property_account_position.id or False
 
             uom_id = procurement.product_id.uom_po_id.id
 
@@ -759,11 +812,14 @@ class procurement_order(osv.osv):
             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]
+            price = pricelist_obj.price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, partner_id, {'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
+            newdate = datetime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S')
+            newdate = (newdate - relativedelta(days=company.po_lead)) - relativedelta(days=seller_delay)
+
+            res_onchange = po_line_obj.product_id_change(cr, uid, ids, pricelist_id, procurement.product_id.id, qty, uom_id,
+                partner_id, time.strftime('%Y-%m-%d'), fiscal_position=fiscal_position, date_planned=datetime.now() + relativedelta(days=seller_delay or 0.0),
+            name=procurement.name, price_unit=procurement.product_id.list_price, notes=procurement.product_id.description_purchase)
 
             #Passing partner_id to context for purchase order line integrity of Line name
             context.update({'lang': partner.lang, 'partner_id': partner_id})
@@ -772,10 +828,10 @@ class procurement_order(osv.osv):
 
             line = {
                 'name': product.partner_ref,
-                'product_qty': qty,
+                'product_qty': res_onchange['value']['product_qty'],
                 'product_id': procurement.product_id.id,
-                'product_uom': uom_id,
-                'price_unit': price,
+                'product_uom': res_onchange['value']['product_uom'],
+                'price_unit': res_onchange['value']['price_unit'],
                 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
                 'move_dest_id': res_id,
                 'notes': product.description_purchase,
@@ -802,4 +858,22 @@ class procurement_order(osv.osv):
 
 procurement_order()
 
+class stock_invoice_onshipping(osv.osv_memory):
+    _inherit = "stock.invoice.onshipping"
+
+    def create_invoice(self, cr, uid, ids, context=None):
+        if context is None:
+            context = {}
+        res = super(stock_invoice_onshipping,self).create_invoice(cr, uid, ids, context=context)
+        purchase_obj = self.pool.get('purchase.order')
+        picking_obj = self.pool.get('stock.picking')
+        for pick_id in res:
+            pick = picking_obj.browse(cr, uid, pick_id, context=context)
+            if pick.purchase_id:
+                purchase_obj.write(cr, uid, [pick.purchase_id.id], {
+                    'invoice_ids': [(4, res[pick_id])]}, context=context)
+        return res
+
+stock_invoice_onshipping()
+
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: