[IMP] purchase : Supplier check box should not be there if purchase is not installed.
[odoo/odoo.git] / addons / purchase / purchase.py
index 6dd6db4..775aebf 100644 (file)
@@ -29,7 +29,7 @@ import pooler
 from tools.translate import _
 import decimal_precision as dp
 from osv.orm import browse_record, browse_null
-from tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
+from tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, DATETIME_FORMATS_MAP
 
 #
 # Model definition
@@ -49,7 +49,7 @@ class purchase_order(osv.osv):
             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']:
+               for c in self.pool.get('account.tax').compute_all(cr, uid, line.taxes_id, line.price_unit, line.product_qty, line.product_id.id, order.partner_id)['taxes']:
                     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)
@@ -142,10 +142,11 @@ class purchase_order(osv.osv):
         return res
 
     STATE_SELECTION = [
-        ('draft', 'Request for Quotation'),
+        ('draft', 'Draft PO'),
         ('wait', 'Waiting'),
+        ('sent', 'RFQ Sent'),
         ('confirmed', 'Waiting Approval'),
-        ('approved', 'Approved'),
+        ('approved', 'Purchase Order'),
         ('except_picking', 'Shipping Exception'),
         ('except_invoice', 'Invoice Exception'),
         ('done', 'Done'),
@@ -161,9 +162,7 @@ class purchase_order(osv.osv):
         '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."),
         '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)]"),
-        'dest_address_id':fields.many2one('res.partner.address', 'Destination Address', domain="[('partner_id', '!=', False)]",
+        'dest_address_id':fields.many2one('res.partner', 'Customer Address (Direct Delivery)',
             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."
@@ -171,20 +170,20 @@ 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(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),
+        'state': fields.selection(STATE_SELECTION, 'Status', readonly=True, help="The state of the purchase order or the quotation request. A quotation is a purchase order in a 'Draft' state. Then the order has to be confirmed by the user, the state switch to 'Confirmed'. Then the supplier must confirm the order to change the state to 'Approved'. When the purchase order is paid and received, the state becomes 'Done'. If a cancel action occurs in the invoice or in the reception of goods, the state becomes in exception.", select=True),
         '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'),
+        'notes': fields.text('Terms and Conditions'),
         '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"),
+        'picking_ids': fields.one2many('stock.picking.in', 'purchase_id', 'Picking List', readonly=True, help="This is the list of incomming shipments that have been generated for this purchase order."),
         'shipped':fields.boolean('Received', readonly=True, select=True, help="It indicates that a picking has been done"),
         'shipped_rate': fields.function(_shipped_rate, string='Received', type='float'),
-        'invoiced': fields.function(_invoiced, string='Invoiced & Paid', type='boolean', help="It indicates that an invoice has been paid"),
+        'invoiced': fields.function(_invoiced, string='Invoice Received', type='boolean', help="It indicates that an invoice has been paid"),
         'invoiced_rate': fields.function(_invoiced_rate, string='Invoiced', type='float'),
-        'invoice_method': fields.selection([('manual','Based on Purchase Order lines'),('order','Based on generated draft invoice'),('picking','Based on receptions')], 'Invoicing Control', required=True,
+        'invoice_method': fields.selection([('manual','Based on Purchase Order lines'),('order','Based on generated draft invoice'),('picking','Bases on incoming shipments')], 'Invoicing Control', required=True,
             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" \
                 "Based on generated invoice: create a draft invoice you can validate later.\n" \
-                "Based on receptions: let you create an invoice when receptions are validated."
+                "Bases on incoming shipments: let you create an invoice when receptions are validated."
         ),
         '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.",
             store = {
@@ -209,13 +208,12 @@ class purchase_order(osv.osv):
         'company_id': fields.many2one('res.company','Company',required=True,select=1),
     }
     _defaults = {
-        'date_order': lambda *a: time.strftime('%Y-%m-%d'),
+        'date_order': fields.date.context_today,
         'state': 'draft',
         'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
         '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', context=c),
     }
@@ -223,9 +221,16 @@ class purchase_order(osv.osv):
         ('name_uniq', 'unique(name, company_id)', 'Order Reference must be unique per Company!'),
     ]
     _name = "purchase.order"
+    _inherit = ['ir.needaction_mixin', 'mail.thread']
     _description = "Purchase Order"
     _order = "name desc"
-
+    
+    def create(self, cr, uid, vals, context=None):
+        order =  super(purchase_order, self).create(cr, uid, vals, context=context)
+        if order:
+            self.create_send_note(cr, uid, [order], context=context)
+        return order
+    
     def unlink(self, cr, uid, ids, context=None):
         purchase_orders = self.read(cr, uid, ids, ['state'], context=context)
         unlink_ids = []
@@ -249,9 +254,9 @@ class purchase_order(osv.osv):
     def onchange_dest_address_id(self, cr, uid, ids, address_id):
         if not address_id:
             return {}
-        address = self.pool.get('res.partner.address')
+        address = self.pool.get('res.partner')
         values = {'warehouse_id': False}
-        supplier = address.browse(cr, uid, address_id).partner_id
+        supplier = address.browse(cr, uid, address_id)
         if supplier:
             location_id = supplier.property_stock_customer.id
             values.update({'location_id': location_id})
@@ -266,17 +271,97 @@ class purchase_order(osv.osv):
     def onchange_partner_id(self, cr, uid, ids, partner_id):
         partner = self.pool.get('res.partner')
         if not partner_id:
-            return {'value':{'partner_address_id': False, 'fiscal_position': False}}
+            return {'value':{'fiscal_position': False}}
         supplier_address = partner.address_get(cr, uid, [partner_id], ['default'])
         supplier = partner.browse(cr, uid, partner_id)
         pricelist = supplier.property_product_pricelist_purchase.id
         fiscal_position = supplier.property_account_position and supplier.property_account_position.id or False
-        return {'value':{'partner_address_id': supplier_address['default'], 'pricelist_id': pricelist, 'fiscal_position': fiscal_position}}
+        return {'value':{'pricelist_id': pricelist, 'fiscal_position': fiscal_position}}
+
+    def view_invoice(self, cr, uid, ids, context=None):
+        '''
+        This function returns an action that display existing invoices of given sale order ids. It can either be a in a list or in a form view, if there is only one invoice to show.
+        '''
+        mod_obj = self.pool.get('ir.model.data')
+        wizard_obj = self.pool.get('purchase.order.line_invoice')
+        #compute the number of invoices to display
+        inv_ids = []
+        for po in self.browse(cr, uid, ids, context=context):
+            if po.invoice_method == 'manual':
+                if not po.invoice_ids:
+                    context.update({'active_ids' :  [line.id for line in po.order_line]})
+                    wizard_obj.makeInvoices(cr, uid, [], context=context)
+            
+        for po in self.browse(cr, uid, ids, context=context):
+            inv_ids+= [invoice.id for invoice in po.invoice_ids]
+        res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_supplier_form')
+        res_id = res and res[1] or False
+
+        return {
+            'name': _('Supplier Invoices'),
+            'view_type': 'form',
+            'view_mode': 'form',
+            'view_id': [res_id],
+            'res_model': 'account.invoice',
+            'context': "{'type':'in_invoice', 'journal_type': 'purchase'}",
+            'type': 'ir.actions.act_window',
+            'nodestroy': True,
+            'target': 'current',
+            'res_id': inv_ids and inv_ids[0] or False,
+        }
+
+    def view_picking(self, cr, uid, ids, context=None):
+        '''
+        This function returns an action that display existing pĂ®cking orders of given purchase order ids.
+        '''
+        mod_obj = self.pool.get('ir.model.data')
+        pick_ids = []
+        for po in self.browse(cr, uid, ids, context=context):
+            pick_ids += [picking.id for picking in po.picking_ids]
+
+        res = mod_obj.get_object_reference(cr, uid, 'stock', 'view_picking_in_form')
+        res_id = res and res[1] or False
+
+        return {
+            'name': _('Receptions'),
+            'view_type': 'form',
+            'view_mode': 'form',
+            'view_id': [res_id],
+            'res_model': 'stock.picking',
+            'context': "{'contact_display': 'partner'}",
+            'type': 'ir.actions.act_window',
+            'nodestroy': True,
+            'target': 'current',
+            'res_id': pick_ids and pick_ids[0] or False,
+        }
 
     def wkf_approve_order(self, cr, uid, ids, context=None):
-        self.write(cr, uid, ids, {'state': 'approved', 'date_approve': time.strftime('%Y-%m-%d')})
+        self.write(cr, uid, ids, {'state': 'approved', 'date_approve': fields.date.context_today(self,cr,uid,context=context)})
         return True
 
+    def wkf_send_rfq(self, cr, uid, ids, context=None):
+        '''
+        This function opens a window to compose an email, with the edi purchase template message loaded by default
+        '''
+        mod_obj = self.pool.get('ir.model.data')
+        template = mod_obj.get_object_reference(cr, uid, 'purchase', 'email_template_edi_purchase')
+        template_id = template and template[1] or False
+        res = mod_obj.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')
+        res_id = res and res[1] or False
+        ctx = dict(context, active_model='purchase.order', active_id=ids[0])
+        ctx.update({'mail.compose.template_id': template_id})
+        return {
+            'view_type': 'form',
+            'view_mode': 'form',
+            'res_model': 'mail.compose.message',
+            'views': [(res_id,'form')],
+            'view_id': res_id,
+            'type': 'ir.actions.act_window',
+            'target': 'new',
+            'context': ctx,
+            'nodestroy': True,
+        }
+
     #TODO: implement messages system
     def wkf_confirm_order(self, cr, uid, ids, context=None):
         todo = []
@@ -286,16 +371,15 @@ class purchase_order(osv.osv):
             for line in po.order_line:
                 if line.state=='draft':
                     todo.append(line.id)
-            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})
+        self.confirm_send_note(cr, uid, ids, context)
         return True
 
     def _prepare_inv_line(self, cr, uid, account_id, order_line, context=None):
-        """Collects require data from purchase order line that is used to create invoice line 
+        """Collects require data from purchase order line that is used to create invoice line
         for that purchase order line
         :param account_id: Expense account of the product of PO line if any.
         :param browse_record order_line: Purchase order line browse record
@@ -313,7 +397,7 @@ class purchase_order(osv.osv):
             'account_analytic_id': order_line.account_analytic_id.id or False,
         }
 
-    def action_cancel_draft(self, cr, uid, ids, *args):
+    def action_cancel_draft(self, cr, uid, ids, context=None):
         if not len(ids):
             return False
         self.write(cr, uid, ids, {'state':'draft','shipped':0})
@@ -322,9 +406,7 @@ class purchase_order(osv.osv):
             # 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 '%s' has been set in draft state.") % name
-            self.log(cr, uid, id, message)
+        self.draft_send_note(cr, uid, ids, context=context)
         return True
 
     def action_invoice_create(self, cr, uid, ids, context=None):
@@ -376,10 +458,8 @@ class purchase_order(osv.osv):
                 'type': 'in_invoice',
                 'partner_id': order.partner_id.id,
                 'currency_id': order.pricelist_id.currency_id.id,
-                'address_invoice_id': order.partner_address_id.id,
-                'address_contact_id': order.partner_address_id.id,
                 'journal_id': len(journal_ids) and journal_ids[0] or False,
-                'invoice_line': [(6, 0, inv_lines)], 
+                'invoice_line': [(6, 0, inv_lines)],
                 'origin': order.name,
                 'fiscal_position': order.fiscal_position.id or order.partner_id.property_account_position.id,
                 'payment_term': order.partner_id.property_payment_term and order.partner_id.property_payment_term.id or False,
@@ -393,8 +473,15 @@ class purchase_order(osv.osv):
             # Link this new invoice to related purchase order
             order.write({'invoice_ids': [(4, inv_id)]}, context=context)
             res = inv_id
+        if res:
+            self.invoice_send_note(cr, uid, ids, res, context)
         return res
-
+    
+    def invoice_done(self, cr, uid, ids, context=None):
+        self.write(cr, uid, ids, {'state':'approved'}, context=context)
+        self.invoice_done_send_note(cr, uid, ids, context=context)
+        return True
+        
     def has_stockable_product(self,cr, uid, ids, *args):
         for order in self.browse(cr, uid, ids):
             for order_line in order.order_line:
@@ -420,11 +507,10 @@ class purchase_order(osv.osv):
                 if inv:
                     wf_service.trg_validate(uid, 'account.invoice', inv.id, 'invoice_cancel', cr)
         self.write(cr,uid,ids,{'state':'cancel'})
-        
+
         for (id, name) in self.name_get(cr, uid, ids):
             wf_service.trg_validate(uid, 'purchase.order', id, 'purchase_cancel', cr)
-            message = _("Purchase order '%s' is cancelled.") % name
-            self.log(cr, uid, id, message)
+        self.cancel_send_note(cr, uid, ids, context)
         return True
 
     def _prepare_order_picking(self, cr, uid, order, context=None):
@@ -432,14 +518,16 @@ class purchase_order(osv.osv):
             'name': self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.in'),
             'origin': order.name + ((order.origin and (':' + order.origin)) or ''),
             'date': order.date_order,
+            'partner_id': order.dest_address_id.id or order.partner_id.id,
+            'invoice_state': '2binvoiced' if order.invoice_method == 'picking' else 'none',
             'type': 'in',
-            'address_id': order.dest_address_id.id or order.partner_address_id.id,
+            'partner_id': order.dest_address_id.id or order.partner_id.id,
             'invoice_state': '2binvoiced' if order.invoice_method == 'picking' else 'none',
             'purchase_id': order.id,
             'company_id': order.company_id.id,
             'move_lines' : [],
         }
-         
+
     def _prepare_order_line_move(self, cr, uid, order, order_line, picking_id, context=None):
         return {
             'name': order.name + ': ' + (order_line.name or ''),
@@ -453,7 +541,7 @@ class purchase_order(osv.osv):
             'location_id': order.partner_id.property_stock_supplier.id,
             'location_dest_id': order.location_id.id,
             'picking_id': picking_id,
-            'address_id': order.dest_address_id.id or order.partner_address_id.id,
+            'partner_id': order.dest_address_id.id or order.partner_id.id,
             'move_dest_id': order_line.move_dest_id.id,
             'state': 'draft',
             'purchase_line_id': order_line.id,
@@ -480,7 +568,7 @@ class purchase_order(osv.osv):
                                will be added. A new picking will be created if omitted.
         :return: list of IDs of pickings used/created for the given order lines (usually just one)
         """
-        if not picking_id: 
+        if not picking_id:
             picking_id = self.pool.get('stock.picking').create(cr, uid, self._prepare_order_picking(cr, uid, order, context=context))
         todo_moves = []
         stock_move = self.pool.get('stock.move')
@@ -507,8 +595,15 @@ class purchase_order(osv.osv):
         # In case of multiple (split) pickings, we should return the ID of the critical one, i.e. the
         # one that should trigger the advancement of the purchase workflow.
         # By default we will consider the first one as most important, but this behavior can be overridden.
+        if picking_ids:
+            self.shipment_send_note(cr, uid, ids, picking_ids[0], context=context)
         return picking_ids[0] if picking_ids else False
 
+    def picking_done(self, cr, uid, ids, context=None):
+        self.write(cr, uid, ids, {'shipped':1,'state':'approved'}, context=context)
+        self.shipment_done_send_note(cr, uid, ids, context=context)
+        return True
+
     def copy(self, cr, uid, id, default=None, context=None):
         if not default:
             default = {}
@@ -574,7 +669,6 @@ class purchase_order(osv.osv):
                     'origin': porder.origin,
                     'date_order': porder.date_order,
                     '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,
@@ -633,6 +727,52 @@ class purchase_order(osv.osv):
                 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 orders_info
+        
+    # --------------------------------------
+    # OpenChatter methods and notifications
+    # --------------------------------------
+    
+    def get_needaction_user_ids(self, cr, uid, ids, context=None):
+        result = dict.fromkeys(ids, [])
+        for obj in self.browse(cr, uid, ids, context=context):
+            if obj.state == 'approved':
+                result[obj.id] = [obj.validator.id]
+        return result
+    
+    def create_send_note(self, cr, uid, ids, context=None):
+        return self.message_append_note(cr, uid, ids, body=_("Request for quotation <b>created</b>."), context=context)
+
+    def confirm_send_note(self, cr, uid, ids, context=None):
+        for obj in self.browse(cr, uid, ids, context=context):
+            self.message_subscribe(cr, uid, [obj.id], [obj.validator.id], context=context)
+            self.message_append_note(cr, uid, [obj.id], body=_("Quotation for <em>%s</em> <b>converted</b> to a Purchase Order of %s %s.") % (obj.partner_id.name, obj.amount_total, obj.pricelist_id.currency_id.symbol), context=context)
+        
+    def shipment_send_note(self, cr, uid, ids, picking_id, context=None):
+        for order in self.browse(cr, uid, ids, context=context):
+            for picking in (pck for pck in order.picking_ids if pck.id == picking_id):
+                # convert datetime field to a datetime, using server format, then
+                # convert it to the user TZ and re-render it with %Z to add the timezone
+                picking_datetime = fields.DT.datetime.strptime(picking.min_date, DEFAULT_SERVER_DATETIME_FORMAT)
+                picking_date_str = fields.datetime.context_timestamp(cr, uid, picking_datetime, context=context).strftime(DATETIME_FORMATS_MAP['%+'] + " (%Z)")
+                self.message_append_note(cr, uid, [order.id], body=_("Shipment <em>%s</em> <b>scheduled</b> for %s.") % (picking.name, picking_date_str), context=context)
+    
+    def invoice_send_note(self, cr, uid, ids, invoice_id, context=None):
+        for order in self.browse(cr, uid, ids, context=context):
+            for invoice in (inv for inv in order.invoice_ids if inv.id == invoice_id):
+                self.message_append_note(cr, uid, [order.id], body=_("Draft Invoice of %s %s is <b>waiting for validation</b>.") % (invoice.amount_total, invoice.currency_id.symbol), context=context)
+    
+    def shipment_done_send_note(self, cr, uid, ids, context=None):
+        self.message_append_note(cr, uid, ids, body=_("""Shipment <b>received</b>."""), context=context)
+     
+    def invoice_done_send_note(self, cr, uid, ids, context=None):
+        self.message_append_note(cr, uid, ids, body=_("Invoice <b>paid</b>."), context=context)
+        
+    def draft_send_note(self, cr, uid, ids, context=None):
+        return self.message_append_note(cr, uid, ids, body=_("Purchase Order has been set to <b>draft</b>."), context=context)
+    
+    def cancel_send_note(self, cr, uid, ids, context=None):
+        for obj in self.browse(cr, uid, ids, context=context):
+            self.message_append_note(cr, uid, [obj.id], body=_("Purchase Order for <em>%s</em> <b>cancelled</b>.") % (obj.partner_id.name), context=context)
 
 purchase_order()
 
@@ -657,10 +797,10 @@ class purchase_order_line(osv.osv):
 
     _columns = {
         'name': fields.char('Description', size=256, required=True),
-        'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product UoM'), required=True),
+        'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), 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_uom': fields.many2one('product.uom', 'Product Unit of Measure', 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'),
@@ -670,7 +810,7 @@ class purchase_order_line(osv.osv):
         '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', store=True, readonly=True),
-        'state': fields.selection([('draft', 'Draft'), ('confirmed', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')], 'State', required=True, readonly=True,
+        'state': fields.selection([('draft', 'Draft'), ('confirmed', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')], 'Status', 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. \
@@ -697,101 +837,114 @@ class purchase_order_line(osv.osv):
         default.update({'state':'draft', 'move_ids':[],'invoiced':0,'invoice_lines':[]})
         return super(purchase_order_line, self).copy_data(cr, uid, id, default, context)
 
-    #TOFIX:
-    # - name of method should "onchange_product_id"
-    # - docstring
-    # - merge 'product_uom_change' method
-    # - split into small internal methods for clearity 
-    def product_id_change(self, cr, uid, ids, pricelist, product, qty, uom,
-            partner_id, date_order=False, fiscal_position=False, date_planned=False,
-            name=False, price_unit=False, notes=False, context={}):
-        if not pricelist:
+    def onchange_product_uom(self, cr, uid, ids, pricelist_id, product_id, qty, uom_id,
+            partner_id, date_order=False, fiscal_position_id=False, date_planned=False,
+            name=False, price_unit=False, notes=False, context=None):
+        """
+        onchange handler of product_uom.
+        """
+        if not uom_id:
+            return {'value': {'price_unit': price_unit or 0.0, 'name': name or '', 'notes': notes or'', 'product_uom' : uom_id or False}}
+        return self.onchange_product_id(cr, uid, ids, pricelist_id, product_id, qty, uom_id,
+            partner_id, date_order=date_order, fiscal_position_id=fiscal_position_id, date_planned=date_planned,
+            name=name, price_unit=price_unit, notes=notes, context=context)
+
+    def _get_date_planned(self, cr, uid, supplier_info, date_order_str, context=None):
+        """Return the datetime value to use as Schedule Date (``date_planned``) for
+           PO Lines that correspond to the given product.supplierinfo,
+           when ordered at `date_order_str`.
+
+           :param browse_record | False supplier_info: product.supplierinfo, used to
+               determine delivery delay (if False, default delay = 0)
+           :param str date_order_str: date of order, as a string in
+               DEFAULT_SERVER_DATE_FORMAT
+           :rtype: datetime
+           :return: desired Schedule Date for the PO line
+        """
+        supplier_delay = int(supplier_info.delay) if supplier_info else 0
+        return datetime.strptime(date_order_str, DEFAULT_SERVER_DATE_FORMAT) + relativedelta(days=supplier_delay)
+
+    def onchange_product_id(self, cr, uid, ids, pricelist_id, product_id, qty, uom_id,
+            partner_id, date_order=False, fiscal_position_id=False, date_planned=False,
+            name=False, price_unit=False, notes=False, context=None):
+        """
+        onchange handler of product_id.
+        """
+        if context is None:
+            context = {}
+
+        res = {'value': {'price_unit': price_unit or 0.0, 'name': name or '', 'notes': notes or '', 'product_uom' : uom_id or False}}
+        if not product_id:
+            return res
+
+        product_product = self.pool.get('product.product')
+        product_uom = self.pool.get('product.uom')
+        res_partner = self.pool.get('res.partner')
+        product_supplierinfo = self.pool.get('product.supplierinfo')
+        product_pricelist = self.pool.get('product.pricelist')
+        account_fiscal_position = self.pool.get('account.fiscal.position')
+        account_tax = self.pool.get('account.tax')
+
+        # - check for the presence of partner_id and pricelist_id
+        if not pricelist_id:
             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:
+        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':[]}}
-        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']
+        # - determine name and notes based on product in partner lang.
+        lang = res_partner.browse(cr, uid, partner_id).lang
         context_partner = {'lang': lang, 'partner_id': partner_id}
+        product = product_product.browse(cr, uid, product_id, context=context_partner)
+        res['value'].update({'name': product.name, 'notes': notes or product.description_purchase})
+
+        # - set a domain on product_uom
+        res['domain'] = {'product_uom': [('category_id','=',product.uom_id.category_id.id)]}
+
+        # - check that uom and product uom belong to the same category
+        product_uom_po_id = product.uom_po_id.id
+        if not uom_id:
+            uom_id = product_uom_po_id
+
+        if product.uom_id.category_id.id != product_uom.browse(cr, uid, uom_id, context=context).category_id.id:
+            res['warning'] = {'title': _('Warning'), 'message': _('Selected Unit of Measure does not belong to the same category as the product Unit of Measure')}
+            uom_id = product_uom_po_id
 
-        prod = self.pool.get('product.product').browse(cr, uid, product, context=context)
-        prod_uom_po = prod.uom_po_id.id
-        if not uom:
-            uom = prod_uom_po
+        res['value'].update({'product_uom': uom_id})
+
+        # - determine product_qty and date_planned based on seller info
         if not date_order:
-            date_order = time.strftime('%Y-%m-%d')
+            date_order = fields.date.context_today(self,cr,uid,context=context)
+
         qty = qty or 1.0
-        seller_delay = 0
-        if uom:
-            uom1_cat = prod.uom_id.category_id.id
-            uom2_cat = product_uom_pool.browse(cr, uid, uom).category_id.id
-            if uom1_cat != uom2_cat:
-                uom = False
-
-        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
-                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
-                    res.update({'warning': {'title': _('Warning'), 'message': _('The selected supplier has a minimal quantity set to %s, you should not 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]
-        dt = (datetime.now() + relativedelta(days=int(seller_delay) or 0.0)).strftime('%Y-%m-%d %H:%M:%S')
-
-
-        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': prod.uom_id.id}})
-        domain = {}
-
-        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)
-        res2 = self.pool.get('product.uom').read(cr, uid, [prod.uom_id.id], ['category_id'])
-        res3 = prod.uom_id.category_id.id
-        domain = {'product_uom':[('category_id','=',res2[0]['category_id'][0])]}
-        if res2[0]['category_id'][0] != res3:
-            raise osv.except_osv(_('Wrong Product UOM !'), _('You have to select a product UOM in the same category than the purchase UOM of the product'))
-
-        res['domain'] = domain
-        return res
+        supplierinfo = False
+        supplierinfo_ids = product_supplierinfo.search(cr, uid, [('name','=',partner_id),('product_id','=',product.id)])
+        if supplierinfo_ids:
+            supplierinfo = product_supplierinfo.browse(cr, uid, supplierinfo_ids[0], context=context)
+            if supplierinfo.product_uom.id != uom_id:
+                res['warning'] = {'title': _('Warning'), 'message': _('The selected supplier only sells this product by %s') % supplierinfo.product_uom.name }
+            min_qty = product_uom._compute_qty(cr, uid, supplierinfo.product_uom.id, supplierinfo.min_qty, to_uom_id=uom_id)
+            if qty < min_qty: # If the supplier quantity is greater than entered from user, set minimal.
+                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)}
+                qty = min_qty
+
+        dt = self._get_date_planned(cr, uid, supplierinfo, date_order, context=context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
+
+        res['value'].update({'date_planned': date_planned or dt, 'product_qty': qty})
+
+        # - determine price_unit and taxes_id
+        price = product_pricelist.price_get(cr, uid, [pricelist_id],
+                    product.id, qty or 1.0, partner_id, {'uom': uom_id, 'date': date_order})[pricelist_id]
+
+        taxes = account_tax.browse(cr, uid, map(lambda x: x.id, product.supplier_taxes_id))
+        fpos = fiscal_position_id and account_fiscal_position.browse(cr, uid, fiscal_position_id, context=context) or False
+        taxes_ids = account_fiscal_position.map_tax(cr, uid, fpos, taxes)
+        res['value'].update({'price_unit': price, 'taxes_id': taxes_ids})
 
-    #TOFIX:
-    # - merge into 'product_id_change' method
-    def product_uom_change(self, cr, uid, ids, pricelist, product, qty, uom,
-            partner_id, date_order=False, fiscal_position=False, date_planned=False,
-            name=False, price_unit=False, notes=False, context={}):
-        res = self.product_id_change(cr, uid, ids, pricelist, product, qty, uom,
-                partner_id, date_order=date_order, fiscal_position=fiscal_position, date_planned=date_planned,
-            name=name, price_unit=price_unit, notes=notes, context=context)
-        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
 
+    product_id_change = onchange_product_id
+    product_uom_change = onchange_product_uom
+
     def action_confirm(self, cr, uid, ids, context=None):
         self.write(cr, uid, ids, {'state': 'confirmed'}, context=context)
         return True
@@ -912,7 +1065,6 @@ class procurement_order(osv.osv):
                 'name': name,
                 'origin': procurement.origin,
                 'partner_id': partner_id,
-                'partner_address_id': address_id,
                 'location_id': procurement.location_id.id,
                 'warehouse_id': warehouse_id and warehouse_id[0] or False,
                 'pricelist_id': pricelist_id,
@@ -922,7 +1074,20 @@ class procurement_order(osv.osv):
             }
             res[procurement.id] = self.create_procurement_purchase_order(cr, uid, procurement, po_vals, line_vals, context=context)
             self.write(cr, uid, [procurement.id], {'state': 'running', 'purchase_id': res[procurement.id]})
+            self.running_send_note(cr, uid, [procurement.id], context=context)
         return res
 
 procurement_order()
+
+class mail_message(osv.osv):
+    _name = 'mail.message'
+    _inherit = 'mail.message'
+    
+    def _postprocess_sent_message(self, cr, uid, message, context=None):
+        if message.model == 'purchase.order':
+            wf_service = netsvc.LocalService("workflow")
+            wf_service.trg_validate(uid, 'purchase.order', message.res_id, 'send_rfq', cr)
+        return super(mail_message, self)._postprocess_sent_message(cr, uid, message=message, context=context)
+
+mail_message()
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: