From e57ac16476bf127c9bd989092e4247e7c12bd5be Mon Sep 17 00:00:00 2001
From: Nicolas Martinelli <nim@odoo.com>
Date: Thu, 27 Aug 2015 12:34:25 +0200
Subject: [PATCH] [IMP] sale_stock: adaptation due to the new Sale module

Major changes:
- No invoicing anymore
- Migration to new api

Reason: complete rewrite of the Sale module.

Responsible: fp, dbo, nim
---
 addons/sale_stock/__openerp__.py              |  11 -
 addons/sale_stock/account_invoice_view.xml    |   4 +-
 addons/sale_stock/company.py                  |  21 +-
 addons/sale_stock/company_view.xml            |   2 +-
 addons/sale_stock/report/sale_report.py       |  29 +-
 addons/sale_stock/report/sale_report_view.xml |  21 -
 addons/sale_stock/res_config.py               |  82 +--
 addons/sale_stock/res_config_view.xml         |   3 +-
 addons/sale_stock/sale_stock.py               | 672 +++++-------------
 addons/sale_stock/sale_stock_demo.xml         |  13 +-
 addons/sale_stock/sale_stock_demo.yml         |   1 -
 addons/sale_stock/sale_stock_view.xml         | 137 +---
 addons/sale_stock/sale_stock_workflow.xml     |   6 -
 addons/sale_stock/stock_view.xml              |   6 +-
 .../test/cancel_order_sale_stock.yml          |  94 ---
 .../sale_stock/test/picking_order_policy.yml  | 217 ------
 .../sale_stock/test/prepaid_order_policy.yml  |  30 -
 .../test/sale_order_canceled_line.yml         |  45 --
 .../sale_stock/test/sale_order_onchange.yml   |  33 -
 addons/sale_stock/test/sale_stock_users.yml   |  57 --
 addons/sale_stock/tests/__init__.py           |   3 +
 addons/sale_stock/tests/test_sale_stock.py    | 174 +++++
 22 files changed, 441 insertions(+), 1220 deletions(-)
 delete mode 100644 addons/sale_stock/report/sale_report_view.xml
 delete mode 100644 addons/sale_stock/sale_stock_workflow.xml
 delete mode 100644 addons/sale_stock/test/cancel_order_sale_stock.yml
 delete mode 100644 addons/sale_stock/test/picking_order_policy.yml
 delete mode 100644 addons/sale_stock/test/prepaid_order_policy.yml
 delete mode 100644 addons/sale_stock/test/sale_order_canceled_line.yml
 delete mode 100644 addons/sale_stock/test/sale_order_onchange.yml
 delete mode 100644 addons/sale_stock/test/sale_stock_users.yml
 create mode 100644 addons/sale_stock/tests/__init__.py
 create mode 100644 addons/sale_stock/tests/test_sale_stock.py

diff --git a/addons/sale_stock/__openerp__.py b/addons/sale_stock/__openerp__.py
index 7944e8a8e064..a2672e107f9b 100644
--- a/addons/sale_stock/__openerp__.py
+++ b/addons/sale_stock/__openerp__.py
@@ -31,22 +31,11 @@ You can choose flexible invoicing methods:
         'security/ir.model.access.csv',
         'company_view.xml',
         'sale_stock_view.xml',
-        'sale_stock_workflow.xml',
         'stock_view.xml',
         'res_config_view.xml',
-        'report/sale_report_view.xml',
         'account_invoice_view.xml',
     ],
     'demo': ['sale_stock_demo.xml'],
-    'test': [
-        '../account/test/account_minimal_test.xml',
-        'test/sale_stock_users.yml',
-        'test/cancel_order_sale_stock.yml',
-        'test/picking_order_policy.yml',
-        'test/prepaid_order_policy.yml',
-        'test/sale_order_onchange.yml',
-        'test/sale_order_canceled_line.yml',
-    ],
     'installable': True,
     'auto_install': True,
 }
diff --git a/addons/sale_stock/account_invoice_view.xml b/addons/sale_stock/account_invoice_view.xml
index e9b8c44cba32..be326a46270c 100644
--- a/addons/sale_stock/account_invoice_view.xml
+++ b/addons/sale_stock/account_invoice_view.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <odoo>
-    <record id="view_invoice_form_inherit" model="ir.ui.view">
+    <record id="invoice_form_inherit_sale_stock" model="ir.ui.view">
         <field name="name">account.invoice.form.sale.stock</field>
         <field name="model">account.invoice</field>
         <field name="inherit_id" ref="account.invoice_form"/>
@@ -13,7 +13,7 @@
         </field>
     </record>
 
-    <template id="report_invoice_incoterm" inherit_id="account.report_invoice_document">
+    <template id="report_invoice_document_inherit_sale_stock" inherit_id="account.report_invoice_document">
         <xpath expr="//div[@name='reference']" position="after">
             <div class="col-xs-2" t-if="o.incoterms_id" groups="sale.group_display_incoterm">
                 <strong>Incoterms:</strong>
diff --git a/addons/sale_stock/company.py b/addons/sale_stock/company.py
index a991f14e1cc0..7b1e382f1aac 100644
--- a/addons/sale_stock/company.py
+++ b/addons/sale_stock/company.py
@@ -1,18 +1,13 @@
 # -*- coding: utf-8 -*-
 # Part of Odoo. See LICENSE file for full copyright and licensing details.
 
-from openerp.osv import fields, osv
+from openerp import fields, models
 
-class company(osv.osv):
+class company(models.Model):
     _inherit = 'res.company'
-    _columns = {
-        'security_lead': fields.float(
-            'Sales Safety Days', required=True,
-            help="Margin of error for dates promised to customers. "\
-                 "Products will be scheduled for procurement and delivery "\
-                 "that many days earlier than the actual promised date, to "\
-                 "cope with unexpected delays in the supply chain."),
-    }
-    _defaults = {
-        'security_lead': 0.0,
-    }
+
+    security_lead = fields.Float('Sales Safety Days', required=True, default = 0.0,
+        help="Margin of error for dates promised to customers. "\
+             "Products will be scheduled for procurement and delivery "\
+             "that many days earlier than the actual promised date, to "\
+             "cope with unexpected delays in the supply chain.")
diff --git a/addons/sale_stock/company_view.xml b/addons/sale_stock/company_view.xml
index 032c264ebd6b..4866d30d6d6c 100644
--- a/addons/sale_stock/company_view.xml
+++ b/addons/sale_stock/company_view.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" ?>
 <openerp>
     <data>
-        <record id="mrp_company" model="ir.ui.view">
+        <record id="view_company_form_inherit_sale_stock" model="ir.ui.view">
             <field name="name">res.company.mrp.config</field>
             <field name="model">res.company</field>
             <field name="priority">24</field>
diff --git a/addons/sale_stock/report/sale_report.py b/addons/sale_stock/report/sale_report.py
index 47d8551019fd..3475684df444 100644
--- a/addons/sale_stock/report/sale_report.py
+++ b/addons/sale_stock/report/sale_report.py
@@ -1,30 +1,15 @@
  # -*- coding: utf-8 -*-
 # Part of Odoo. See LICENSE file for full copyright and licensing details.
- 
-from openerp.osv import fields, osv
-from openerp import tools
 
-class sale_report(osv.osv):
+from openerp import fields, models
+
+class SaleReport(models.Model):
     _inherit = "sale.report"
-    _columns = {
-        'shipped': fields.boolean('Shipped', readonly=True),
-        'shipped_qty_1': fields.integer('# of Shipped Lines', readonly=True),
-        'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse',readonly=True),
-        'state': fields.selection([
-            ('draft', 'Draft Quotation'),
-            ('sent', 'Quotation Sent'),
-            ('waiting_date', 'Waiting Schedule'),
-            ('manual', 'Sale to Invoice'),
-            ('progress', 'Sale Order'),
-            ('shipping_except', 'Shipping Exception'),
-            ('invoice_except', 'Invoice Exception'),
-            ('done', 'Done'),
-            ('cancel', 'Cancelled')
-            ], 'Order Status', readonly=True),
-    }
+
+    warehouse_id = fields.Many2one('stock.warehouse', 'Warehouse', readonly=True)
 
     def _select(self):
-        return  super(sale_report, self)._select() + ", s.warehouse_id as warehouse_id, s.shipped, s.shipped::integer as shipped_qty_1"
+        return super(SaleReport, self)._select() + ", s.warehouse_id as warehouse_id"
 
     def _group_by(self):
-        return super(sale_report, self)._group_by() + ", s.warehouse_id, s.shipped"
+        return super(SaleReport, self)._group_by() + ", s.warehouse_id"
diff --git a/addons/sale_stock/report/sale_report_view.xml b/addons/sale_stock/report/sale_report_view.xml
deleted file mode 100644
index 0ef9e9df3a69..000000000000
--- a/addons/sale_stock/report/sale_report_view.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<openerp>
-     <data>
-       <!-- Filters too specific to propose directly to the user
-        <record id="view_order_product_search_sale_stock_inherit" model="ir.ui.view">
-            <field name="name">sale.report.search.sale.stock</field>
-            <field name="model">sale.report</field>
-            <field name="inherit_id" ref="sale.view_order_product_search"/>
-            <field name="arch" type="xml">
-                <filter name="Sales" position="after">
-                    <separator/>
-                    <filter string="Picked" domain="[('shipped','=',True)]"/>
-                </filter>
-                <xpath expr="//group/filter[@name='status']" position="after">
-                    <filter string="Warehouse" context="{'group_by':'warehouse_id'}"/>
-                </xpath>
-            </field>
-        </record>
-      -->
-     </data>
-</openerp>
diff --git a/addons/sale_stock/res_config.py b/addons/sale_stock/res_config.py
index be0689a4308e..6852a73d3de5 100644
--- a/addons/sale_stock/res_config.py
+++ b/addons/sale_stock/res_config.py
@@ -1,59 +1,47 @@
 # -*- coding: utf-8 -*-
 # Part of Odoo. See LICENSE file for full copyright and licensing details.
 
-import openerp
 from openerp import SUPERUSER_ID
-from openerp.osv import fields, osv
-from openerp.tools.translate import _
+from openerp import api, fields, models, _
+from openerp.exceptions import AccessError
 
-class sale_configuration(osv.osv_memory):
+class SaleConfiguration(models.TransientModel):
     _inherit = 'sale.config.settings'
 
-    _columns = {
-        'default_order_policy': fields.selection([
-                ('manual', 'Invoice based on sales order'),
-                ('picking', 'Invoice based on delivery orders')],
-            'Invoicing Method', default_model='sale.order'),
-        'module_delivery': fields.selection([
-            (0, 'No shipping costs on sales orders'),
-            (1, 'Allow adding shipping costs')
-            ], "Shipping"),
-        'default_picking_policy' : fields.selection([
-            (0, 'Ship products when some are available, and allow back orders'),
-            (1, 'Ship all products at once, without back orders')
-            ], "Default Shipping Policy"),
-        'group_mrp_properties': fields.selection([
-            (0, "Don't use manufacturing properties (recommended as its easier)"),
-            (1, 'Allow setting manufacturing order properties per order line (avanced)')
-            ], "Properties on SO Lines",
-            implied_group='sale.group_mrp_properties',
-            help="Allows you to tag sales order lines with properties."),
-        'group_route_so_lines': fields.selection([
-            (0, 'No order specific routes like MTO or drop shipping'),
-            (1, 'Choose specific routes on sales order lines (advanced)')
-            ], "Order Routing",
-            implied_group='sale_stock.group_route_so_lines'),
-    }
-
-    _defaults = {
-        'default_order_policy': 'manual',
-    }
-
-    def get_default_sale_config(self, cr, uid, ids, context=None):
-        ir_values = self.pool.get('ir.values')
-        default_picking_policy = ir_values.get_default(cr, uid, 'sale.order', 'picking_policy')
+    module_delivery = fields.Selection([
+        (0, 'No shipping costs on sales orders'),
+        (1, 'Allow adding shipping costs')
+        ], "Shipping")
+    default_picking_policy = fields.Selection([
+        (0, 'Ship products when some are available, and allow back orders'),
+        (1, 'Ship all products at once, without back orders')
+        ], "Default Shipping Policy")
+    group_mrp_properties = fields.Selection([
+        (0, "Don't use manufacturing properties (recommended as its easier)"),
+        (1, 'Allow setting manufacturing order properties per order line (avanced)')
+        ], "Properties on SO Lines",
+        implied_group='sale.group_mrp_properties',
+        help="Allows you to tag sales order lines with properties.")
+    group_route_so_lines = fields.Selection([
+        (0, 'No order specific routes like MTO or drop shipping'),
+        (1, 'Choose specific routes on sales order lines (advanced)')
+        ], "Order Routing",
+        implied_group='sale_stock.group_route_so_lines')
+
+    @api.multi
+    def get_default_sale_config(self):
+        default_picking_policy = self.env['ir.values'].get_default('sale.order', 'picking_policy')
         return {
-            'default_picking_policy': (default_picking_policy == 'one') and 1 or 0,
+            'default_picking_policy': 1 if default_picking_policy == 'one' else 0,
         }
 
-    def set_sale_defaults(self, cr, uid, ids, context=None):
-        if not self.pool['res.users']._is_admin(cr, uid, [uid]):
-            raise openerp.exceptions.AccessError(_("Only administrators can change the settings"))
-        ir_values = self.pool.get('ir.values')
-        wizard = self.browse(cr, uid, ids)[0]
+    @api.multi
+    def set_sale_defaults(self):
+        self.ensure_one()
+        if not self.env.user._is_admin():
+            raise AccessError(_("Only administrators can change the settings"))
 
-        default_picking_policy = 'one' if wizard.default_picking_policy else 'direct'
-        ir_values.set_default(cr, SUPERUSER_ID, 'sale.order', 'picking_policy', default_picking_policy)
-        res = super(sale_configuration, self).set_sale_defaults(cr, uid, ids, context)
+        default_picking_policy = 'one' if self.default_picking_policy else 'direct'
+        self.env['ir.values'].sudo().set_default('sale.order', 'picking_policy', default_picking_policy)
+        res = super(SaleConfiguration, self).set_sale_defaults()
         return res
-
diff --git a/addons/sale_stock/res_config_view.xml b/addons/sale_stock/res_config_view.xml
index f00e142b2aa8..1babd1df6445 100644
--- a/addons/sale_stock/res_config_view.xml
+++ b/addons/sale_stock/res_config_view.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <odoo>
-    <record id="view_sales_config_sale_stock" model="ir.ui.view">
+    <record id="view_sales_config_inherit_sale_stock" model="ir.ui.view">
         <field name="name">sale settings</field>
         <field name="model">sale.config.settings</field>
         <field name="inherit_id" ref="sale.view_sales_config"/>
@@ -12,7 +12,6 @@
                 </group>
             </xpath>
             <xpath expr="//group[@id='sale']" position="inside">
-                <field name="default_order_policy" widget="radio"/>
                 <field name="group_route_so_lines" widget="radio"/>
                 <field name="group_mrp_properties" widget="radio" groups="base.group_no_one"/>
             </xpath>
diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py
index f49adcb3c172..9b9c4900e0da 100644
--- a/addons/sale_stock/sale_stock.py
+++ b/addons/sale_stock/sale_stock.py
@@ -1,516 +1,230 @@
 # -*- coding: utf-8 -*-
 # Part of Odoo. See LICENSE file for full copyright and licensing details.
+
 from datetime import datetime, timedelta
-from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, DATETIME_FORMATS_MAP, float_compare
-from openerp.osv import fields, osv
-from openerp.tools.safe_eval import safe_eval as eval
-from openerp.tools.translate import _
-import pytz
-from openerp import SUPERUSER_ID
-from openerp.exceptions import UserError
-
-class sale_order(osv.osv):
-    _inherit = "sale.order"
+from openerp import api, fields, models, _
+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, float_compare
 
-    def _get_default_warehouse(self, cr, uid, context=None):
-        company_id = self.pool.get('res.users')._get_company(cr, uid, context=context)
-        warehouse_ids = self.pool.get('stock.warehouse').search(cr, uid, [('company_id', '=', company_id)], context=context)
-        if not warehouse_ids:
-            return False
-        return warehouse_ids[0]
-
-    def _get_shipped(self, cr, uid, ids, name, args, context=None):
-        res = {}
-        for sale in self.browse(cr, uid, ids, context=context):
-            group = sale.procurement_group_id
-            if group:
-                res[sale.id] = all([proc.state in ['cancel', 'done'] for proc in group.procurement_ids])
-            else:
-                res[sale.id] = False
-        return res
 
-    def _get_orders(self, cr, uid, ids, context=None):
-        res = set()
-        for move in self.browse(cr, uid, ids, context=context):
-            if move.procurement_id and move.procurement_id.sale_line_id:
-                res.add(move.procurement_id.sale_line_id.order_id.id)
-        return list(res)
-
-    def _get_orders_procurements(self, cr, uid, ids, context=None):
-        res = set()
-        for proc in self.pool.get('procurement.order').browse(cr, uid, ids, context=context):
-            if proc.state =='done' and proc.sale_line_id:
-                res.add(proc.sale_line_id.order_id.id)
-        return list(res)
-
-    def _get_picking_ids(self, cr, uid, ids, name, args, context=None):
-        res = {}
-        StockPicking = self.pool.get('stock.picking')
-        for sale in self.browse(cr, uid, ids, context=context):
-            picking_ids = []
-            if sale.procurement_group_id:
-                picking_ids = StockPicking.search(cr, uid, [('group_id', '=', sale.procurement_group_id.id)], context=context)
-            res[sale.id] = {
-                'picking_ids': picking_ids,
-                'delivery_count': len(picking_ids)
-            }
-        return res
+class SaleOrder(models.Model):
+    _inherit = "sale.order"
 
-    def _prepare_order_line_procurement(self, cr, uid, order, line, group_id=False, context=None):
-        vals = super(sale_order, self)._prepare_order_line_procurement(cr, uid, order, line, group_id=group_id, context=context)
-        location_id = order.partner_shipping_id.property_stock_customer.id
-        vals['location_id'] = location_id
-        routes = line.route_id and [(4, line.route_id.id)] or []
-        vals['route_ids'] = routes
-        vals['warehouse_id'] = order.warehouse_id and order.warehouse_id.id or False
-        vals['partner_dest_id'] = order.partner_shipping_id.id
-        vals['invoice_state'] = (order.order_policy == 'picking') and '2binvoiced' or 'none'
-        return vals
+    @api.model
+    def _default_warehouse_id(self):
+        company = self.env.user.company_id.id
+        warehouse_ids = self.env['stock.warehouse'].search([('company_id', '=', company)], limit=1)
+        return warehouse_ids
+
+    incoterm = fields.Many2one('stock.incoterms', 'Incoterms', help="International Commercial Terms are a series of predefined commercial terms used in international transactions.")
+    picking_policy = fields.Selection([
+        ('direct', 'Deliver each product when available'),
+        ('one', 'Deliver all products at once')],
+        string='Shipping Policy', required=True, readonly=True, default='direct',
+        states={'draft': [('readonly', False)], 'sent': [('readonly', False)]})
+    warehouse_id = fields.Many2one('stock.warehouse', string='Warehouse',
+        required=True, readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
+        default=_default_warehouse_id)
+    picking_ids = fields.One2many('stock.picking', compute='_compute_picking_ids', string='Picking associated to this sale')
+    delivery_count = fields.Integer(string='Delivery Orders', compute='_compute_picking_ids')
+
+    @api.multi
+    @api.depends('procurement_group_id')
+    def _compute_picking_ids(self):
+        for order in self:
+            if not order.procurement_group_id:
+                order.picking_ids = []
+                order.delivery_count = 0
+            else:
+                order.picking_ids = self.env['stock.picking'].search([('group_id', '=', order.procurement_group_id.id)]).ids
+                order.delivery_count = len(order.picking_ids.ids)
 
-    def _prepare_invoice(self, cr, uid, order, lines, context=None):
-        if context is None:
-            context = {}
-        invoice_vals = super(sale_order, self)._prepare_invoice(cr, uid, order, lines, context=context)
-        invoice_vals['incoterms_id'] = order.incoterm.id or False
-        return invoice_vals
+    @api.onchange('warehouse_id')
+    def _onchange_warehouse_id(self):
+        if self.warehouse_id.company_id:
+            self.company_id = self.warehouse_id.company_id.id
 
-    _columns = {
-        'incoterm': fields.many2one('stock.incoterms', 'Incoterms', help="International Commercial Terms are a series of predefined commercial terms used in international transactions."),
-        'picking_policy': fields.selection([('direct', 'Deliver each product when available'), ('one', 'Deliver all products at once')],
-            'Shipping Policy', required=True, readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
-            help="""Pick 'Deliver each product when available' if you allow partial delivery."""),
-        'order_policy': fields.selection([
-                ('manual', 'On Demand'),
-                ('picking', 'On Delivery Order'),
-                ('prepaid', 'Before Delivery'),
-            ], 'Create Invoice', required=True, readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
-            help="""On demand: A draft invoice can be created from the sales order when needed. \nOn delivery order: A draft invoice can be created from the delivery order when the products have been delivered. \nBefore delivery: A draft invoice is created from the sales order and must be paid before the products can be delivered."""),
-        'shipped': fields.function(_get_shipped, string='Delivered', type='boolean', store={
-                'procurement.order': (_get_orders_procurements, ['state'], 10)
-            }),
-        'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True, readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}),
-        'picking_ids': fields.function(_get_picking_ids, method=True, type='one2many', relation='stock.picking', string='Picking associated to this sale', multi='_get_picking_ids'),
-        'delivery_count': fields.function(_get_picking_ids, type='integer', string='Delivery Orders', multi='_get_picking_ids'),
-    }
-    _defaults = {
-        'warehouse_id': _get_default_warehouse,
-        'picking_policy': 'direct',
-        'order_policy': 'manual',
-    }
-    def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context=None):
-        val = {}
-        if warehouse_id:
-            warehouse = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context)
-            if warehouse.company_id:
-                val['company_id'] = warehouse.company_id.id
-        return {'value': val}
-
-    def action_view_delivery(self, cr, uid, ids, context=None):
+    @api.multi
+    def action_view_delivery(self):
         '''
         This function returns an action that display existing delivery orders
         of given sales order ids. It can either be a in a list or in a form
         view, if there is only one delivery order to show.
         '''
-        
-        mod_obj = self.pool.get('ir.model.data')
-        act_obj = self.pool.get('ir.actions.act_window')
-
-        result = mod_obj.get_object_reference(cr, uid, 'stock', 'action_picking_tree_all')
-        id = result and result[1] or False
-        result = act_obj.read(cr, uid, [id], context=context)[0]
+        action = self.env.ref('stock.action_picking_tree_all')
+
+        result = {
+            'name': action.name,
+            'help': action.help,
+            'type': action.type,
+            'view_type': action.view_type,
+            'view_mode': action.view_mode,
+            'target': action.target,
+            'context': action.context,
+            'res_model': action.res_model,
+        }
 
-        #compute the number of delivery orders to display
-        pick_ids = []
-        for so in self.browse(cr, uid, ids, context=context):
-            pick_ids += [picking.id for picking in so.picking_ids]
+        pick_ids = sum([order.picking_ids.ids for order in self], [])
 
-        #choose the view_mode accordingly
         if len(pick_ids) > 1:
-            result['domain'] = "[('id','in',[" + ','.join(map(str, pick_ids)) + "])]"
-        else:
-            res = mod_obj.get_object_reference(cr, uid, 'stock', 'view_picking_form')
-            result['views'] = [(res and res[1] or False, 'form')]
-            result['res_id'] = pick_ids and pick_ids[0] or False
+            result['domain'] = "[('id','in',["+','.join(map(str, pick_ids))+"])]"
+        elif len(pick_ids) == 1:
+            form = self.env.ref('stock.view_picking_form', False)
+            form_id = form.id if form else False
+            result['views'] = [(form_id, 'form')]
+            result['res_id'] = pick_ids[0]
         return result
 
-    def action_invoice_create(self, cr, uid, ids, grouped=False, states=['confirmed', 'done', 'exception'], date_invoice = False, context=None):
-        move_obj = self.pool.get("stock.move")
-        res = super(sale_order,self).action_invoice_create(cr, uid, ids, grouped=grouped, states=states, date_invoice = date_invoice, context=context)
-        for order in self.browse(cr, uid, ids, context=context):
-            if order.order_policy == 'picking':
-                for picking in order.picking_ids:
-                    move_obj.write(cr, uid, [x.id for x in picking.move_lines], {'invoice_state': 'invoiced'}, context=context)
-        return res
+    @api.multi
+    def _prepare_invoice(self):
+        invoice_vals = super(SaleOrder, self)._prepare_invoice()
+        invoice_vals['incoterms_id'] = self.incoterm.id or False
+        return invoice_vals
 
-    def action_wait(self, cr, uid, ids, context=None):
-        res = super(sale_order, self).action_wait(cr, uid, ids, context=context)
-        for o in self.browse(cr, uid, ids):
-            noprod = self.test_no_product(cr, uid, o, context)
-            if noprod and o.order_policy=='picking':
-                self.write(cr, uid, [o.id], {'order_policy': 'manual'}, context=context)
+    @api.model
+    def _prepare_procurement_group(self):
+        res = super(SaleOrder, self)._prepare_procurement_group()
+        res.update({'move_type': self.picking_policy, 'partner_id': self.partner_shipping_id.id})
         return res
 
-    def _get_date_planned(self, cr, uid, order, line, start_date, context=None):
-        date_planned = super(sale_order, self)._get_date_planned(cr, uid, order, line, start_date, context=context)
-        date_planned = (date_planned - timedelta(days=order.company_id.security_lead)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
-        return date_planned
 
-    def _prepare_procurement_group(self, cr, uid, order, context=None):
-        res = super(sale_order, self)._prepare_procurement_group(cr, uid, order, context=None)
-        res.update({'move_type': order.picking_policy})
-        return res
-
-    def action_ship_end(self, cr, uid, ids, context=None):
-        super(sale_order, self).action_ship_end(cr, uid, ids, context=context)
-        for order in self.browse(cr, uid, ids, context=context):
-            val = {'shipped': True}
-            if order.state == 'shipping_except':
-                val['state'] = 'progress'
-                if (order.order_policy == 'manual'):
-                    for line in order.order_line:
-                        if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
-                            val['state'] = 'manual'
-                            break
-            res = self.write(cr, uid, [order.id], val)
-        return True
-
-    def has_stockable_products(self, cr, uid, ids, *args):
-        for order in self.browse(cr, uid, ids):
-            for order_line in order.order_line:
-                if order_line.state == 'cancel':
-                    continue
-                if order_line.product_id and order_line.product_id.type in ('product', 'consu'):
-                    return True
-        return False
-
-
-class product_product(osv.osv):
-    _inherit = 'product.product'
-    
-    def need_procurement(self, cr, uid, ids, context=None):
-        #when sale/product is installed alone, there is no need to create procurements, but with sale_stock
-        #we must create a procurement for each product that is not a service.
-        for product in self.browse(cr, uid, ids, context=context):
-            if product.id and product.type in ['product', 'consu']:
-                return True
-        return super(product_product, self).need_procurement(cr, uid, ids, context=context)
-
-class sale_order_line(osv.osv):
+class SaleOrderLine(models.Model):
     _inherit = 'sale.order.line'
 
-
-    def _number_packages(self, cr, uid, ids, field_name, arg, context=None):
-        res = {}
-        for line in self.browse(cr, uid, ids, context=context):
-            try:
-                res[line.id] = int((line.product_uom_qty+line.product_packaging.qty-0.0001) / line.product_packaging.qty)
-            except:
-                res[line.id] = 1
-        return res
-
-    _columns = {
-        'product_packaging': fields.many2one('product.packaging', 'Packaging'),
-        'number_packages': fields.function(_number_packages, type='integer', string='Number Packages'),
-        'route_id': fields.many2one('stock.location.route', 'Route', domain=[('sale_selectable', '=', True)]),
-        'product_tmpl_id': fields.related('product_id', 'product_tmpl_id', type='many2one', relation='product.template', string='Product Template'),
-    }
-
-    _defaults = {
-        'product_packaging': False,
-    }
-
-    def product_packaging_change(self, cr, uid, ids, pricelist, product, qty=0, uom=False,
-                                   partner_id=False, packaging=False, flag=False, context=None):
-        if not product:
-            return {'value': {'product_packaging': False}}
-        product_obj = self.pool.get('product.product')
-        product_uom_obj = self.pool.get('product.uom')
-        pack_obj = self.pool.get('product.packaging')
-        warning = {}
-        result = {}
-        warning_msgs = ''
-        if flag:
-            res = self.product_id_change(cr, uid, ids, pricelist=pricelist,
-                    product=product, qty=qty, uom=uom, partner_id=partner_id,
-                    packaging=packaging, flag=False, context=context)
-            warning_msgs = res.get('warning') and res['warning'].get('message', '') or ''
-
-        products = product_obj.browse(cr, uid, product, context=context)
-        if not products.packaging_ids:
-            packaging = result['product_packaging'] = False
-
-        if packaging:
-            default_uom = products.uom_id and products.uom_id.id
-            pack = pack_obj.browse(cr, uid, packaging, context=context)
-            q = product_uom_obj._compute_qty(cr, uid, uom, pack.qty, default_uom)
-#            qty = qty - qty % q + q
-            if qty and (q and not (qty % q) == 0):
-                barcode = pack.barcode or _('(n/a)')
-                qty_pack = pack.qty
-                type_ul = pack.ul
-                if not warning_msgs:
-                    warn_msg = _("You selected a quantity of %d Units.\n"
-                                "But it's not compatible with the selected packaging.\n"
-                                "Here is a proposition of quantities according to the packaging:\n"
-                                "Barcode: %s Quantity: %s Type of ul: %s") % \
-                                    (qty, barcode, qty_pack, type_ul.name)
-                    warning_msgs += _("Picking Information ! : ") + warn_msg + "\n\n"
-                warning = {
-                       'title': _('Configuration Error!'),
-                       'message': warning_msgs
-                }
-            result['product_uom_qty'] = qty
-
-        return {'value': result, 'warning': warning}
-
-    def _check_routing(self, cr, uid, ids, product, warehouse_id, context=None):
-        """ Verify the route of the product based on the warehouse
-            return True if the product availibility in stock does not need to be verified
-        """
-        is_available = False
-        if warehouse_id:
-            warehouse = self.pool['stock.warehouse'].browse(cr, uid, warehouse_id, context=context)
-            for product_route in product.route_ids:
-                if warehouse.mto_pull_id and warehouse.mto_pull_id.route_id and warehouse.mto_pull_id.route_id.id == product_route.id:
-                    is_available = True
-                    break
-        else:
-            try:
-                mto_route_id = self.pool['stock.warehouse']._get_mto_route(cr, uid, context=context)
-            except osv.except_osv:
-                # if route MTO not found in ir_model_data, we treat the product as in MTS
-                mto_route_id = False
-            if mto_route_id:
-                for product_route in product.route_ids:
-                    if product_route.id == mto_route_id:
-                        is_available = True
-                        break
-        return is_available
-
-    def product_id_change_with_wh(self, cr, uid, ids, pricelist, product, qty=0,
-            uom=False, qty_uos=0, uos=False, name='', partner_id=False,
-            lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position_id=False, flag=False, warehouse_id=False, context=None):
-        context = context or {}
-        product_uom_obj = self.pool.get('product.uom')
-        product_obj = self.pool.get('product.product')
-        warning = {}
-        #UoM False due to hack which makes sure uom changes price, ... in product_id_change
-        res = self.product_id_change(cr, uid, ids, pricelist, product, qty=qty,
-            uom=False, qty_uos=qty_uos, uos=uos, name=name, partner_id=partner_id,
-            lang=lang, update_tax=update_tax, date_order=date_order, packaging=packaging, fiscal_position_id=fiscal_position_id, flag=flag, context=context)
-
-        if not product:
-            res['value'].update({'product_packaging': False})
-            return res
-
-        # set product uom in context to get virtual stock in current uom
-        if 'product_uom' in res.get('value', {}):
-            # use the uom changed by super call
-            context = dict(context, uom=res['value']['product_uom'])
-        elif uom:
-            # fallback on selected
-            context = dict(context, uom=uom)
-
-        #update of result obtained in super function
-        product_obj = product_obj.browse(cr, uid, product, context=context)
-        res['value'].update({'product_tmpl_id': product_obj.product_tmpl_id.id, 'delay': (product_obj.sale_delay or 0.0)})
-
-        # Calling product_packaging_change function after updating UoM
-        res_packing = self.product_packaging_change(cr, uid, ids, pricelist, product, qty, uom, partner_id, packaging, context=context)
-        res['value'].update(res_packing.get('value', {}))
-        warning_msgs = res_packing.get('warning') and res_packing['warning']['message'] or ''
-
-        if product_obj.type == 'product':
-            #determine if the product needs further check for stock availibility
-            is_available = self._check_routing(cr, uid, ids, product_obj, warehouse_id, context=context)
-
-            #check if product is available, and if not: raise a warning, but do this only for products that aren't processed in MTO
-            if not is_available:
-                uom_record = False
-                if uom:
-                    uom_record = product_uom_obj.browse(cr, uid, uom, context=context)
-                    if product_obj.uom_id.category_id.id != uom_record.category_id.id:
-                        uom_record = False
-                if not uom_record:
-                    uom_record = product_obj.uom_id
-                compare_qty = float_compare(product_obj.virtual_available, qty, precision_rounding=uom_record.rounding)
-                if compare_qty == -1:
-                    warn_msg = _('You plan to sell %.2f %s but you only have %.2f %s available !\nThe real stock is %.2f %s. (without reservations)') % \
-                        (qty, uom_record.name,
-                         max(0,product_obj.virtual_available), uom_record.name,
-                         max(0,product_obj.qty_available), uom_record.name)
-                    warning_msgs += _("Not enough stock ! : ") + warn_msg + "\n\n"
-
-        #update of warning messages
-        if warning_msgs:
-            warning = {
-                       'title': _('Configuration Error!'),
-                       'message' : warning_msgs
+    product_packaging = fields.Many2one('product.packaging', string='Packaging', default=False)
+    route_id = fields.Many2one('stock.location.route', string='Route', domain=[('sale_selectable', '=', True)])
+    product_tmpl_id = fields.Many2one('product.template', related='product_id.product_tmpl_id', string='Product Template')
+    procurement_ids = fields.One2many('procurement.order', 'so_line_id', string='Procurements')
+
+    @api.multi
+    @api.depends('product_id')
+    def _compute_qty_delivered_updateable(self):
+        for line in self:
+            if line.product_id.type not in ('consu', 'product'):
+                return super(SaleOrderLine, self)._compute_qty_delivered_updateable()
+            line.qty_delivered_updateable = False
+
+    @api.onchange('product_id')
+    def _onchange_product_id_set_customer_lead(self):
+        self.customer_lead = self.product_id.sale_delay
+        return {}
+
+    @api.onchange('product_packaging')
+    def _onchange_product_packaging(self):
+        if self.product_packaging:
+            return self._check_package()
+        return {}
+
+    @api.onchange('product_id', 'product_uom_qty')
+    def _onchange_product_id_check_availability(self):
+        if not self.product_id:
+            self.product_packaging = False
+            return {}
+        self.product_tmpl_id = self.product_id.product_tmpl_id
+        if self.product_id.type == 'product':
+            product = self.product_id.with_context(
+                lang=self.order_id.partner_id.lang,
+                partner_id=self.order_id.partner_id.id,
+                date_order=self.order_id.date_order,
+                pricelist_id=self.order_id.pricelist_id.id,
+                uom=self.product_uom.id,
+                warehouse_id=self.order_id.warehouse_id.id
+            )
+            if float_compare(product.virtual_available, self.product_uom_qty, precision_rounding=self.product_uom.rounding) == -1:
+                # Check if MTO, Cross-Dock or Drop-Shipping
+                is_available = False
+                for route in self.route_id+self.product_id.route_ids:
+                    for pull in route.pull_ids:
+                        if pull.location_id.id == self.order_id.warehouse_id.lot_stock_id.id:
+                            is_available = True
+                if not is_available:
+                    return {
+                        'title': _('Not enough inventory!'),
+                        'message' : _('You plan to sell %.2f %s but you only have %.2f %s available!\nThe stock on hand is %.2f %s.') % \
+                            (self.product_uom_qty, self.product_uom.name, product.virtual_available, self.product_uom.name, product.qty_available, self.product_uom.name)
                     }
-        res.update({'warning': warning})
-        return res
+        return {}
+
+    @api.multi
+    def _prepare_order_line_procurement(self, group_id=False):
+        vals = super(SaleOrderLine, self)._prepare_order_line_procurement(group_id=group_id)
+        date_planned = datetime.strptime(self.order_id.date_order, DEFAULT_SERVER_DATETIME_FORMAT)\
+            + timedelta(days=self.customer_lead or 0.0) - timedelta(days=self.order_id.company_id.security_lead)
+        vals.update({
+            'date_planned': date_planned.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
+            'location_id': self.order_id.partner_shipping_id.property_stock_customer.id,
+            'route_ids': self.route_id and [(4, self.route_id.id)] or [],
+            'warehouse_id': self.order_id.warehouse_id and self.order_id.warehouse_id.id or False,
+            'partner_dest_id': self.order_id.partner_shipping_id.id,
+            'so_line_id': self.id,
+        })
+        return vals
 
-    def button_cancel(self, cr, uid, ids, context=None):
-        lines = self.browse(cr, uid, ids, context=context)
-        for procurement in lines.mapped('procurement_ids'):
-            for move in procurement.move_ids:
-                if move.state == 'done' and not move.scrapped:
-                    raise osv.except_osv(_('Invalid Action!'), _('You cannot cancel a sale order line which is linked to a stock move already done.'))
-        return super(sale_order_line, self).button_cancel(cr, uid, ids, context=context)
-
-class stock_move(osv.osv):
-    _inherit = 'stock.move'
-
-    def _get_master_data(self, cr, uid, move, inv_type, context=None):
-        if inv_type in ('out_invoice', 'out_refund') and (move.procurement_id.sale_line_id or move.origin_returned_move_id.procurement_id.sale_line_id):
-            sale_order = move.procurement_id.sale_line_id.order_id or move.origin_returned_move_id.procurement_id.sale_line_id.order_id
-            return sale_order.partner_invoice_id, sale_order.user_id.id, sale_order.pricelist_id.currency_id.id
-        elif move.picking_id.sale_id or move.origin_returned_move_id.picking_id.sale_id and inv_type in ('out_invoice', 'out_refund'):
-            # In case of extra move, it is better to use the same data as the original moves
-            sale_order = move.picking_id.sale_id or move.origin_returned_move_id.picking_id.sale_id
-            return sale_order.partner_invoice_id, sale_order.user_id.id, sale_order.pricelist_id.currency_id.id
-        return super(stock_move, self)._get_master_data(cr, uid, move, inv_type, context=context)
-
-    def _get_invoice_line_vals(self, cr, uid, move, partner, inv_type, context=None):
-        res = super(stock_move, self)._get_invoice_line_vals(cr, uid, move, partner, inv_type, context=context)
-        if inv_type in ('out_invoice', 'out_refund') and (move.procurement_id.sale_line_id or move.origin_returned_move_id.procurement_id.sale_line_id):
-            sale_line = move.procurement_id.sale_line_id or move.origin_returned_move_id.procurement_id.sale_line_id
-            res['invoice_line_tax_ids'] = [(6, 0, [x.id for x in sale_line.tax_id])]
-            res['account_analytic_id'] = sale_line.order_id.project_id and sale_line.order_id.project_id.id or False
-            res['discount'] = sale_line.discount
-            if move.product_id.id != sale_line.product_id.id:
-                res['price_unit'] = self.pool['product.pricelist'].price_get(
-                    cr, uid, [sale_line.order_id.pricelist_id.id],
-                    move.product_id.id, move.product_uom_qty or 1.0,
-                    sale_line.order_id.partner_id, context=context)[sale_line.order_id.pricelist_id.id]
-            else:
-                res['price_unit'] = sale_line.price_unit
-            uos_coeff = move.product_uom_qty and move.product_uos_qty / move.product_uom_qty or 1.0
-            res['price_unit'] = res['price_unit'] / uos_coeff
-            res['sale_line_ids'] = [(4, sale_line.id)]
-        return res
+    @api.multi
+    def _get_delivered_qty(self):
+        self.ensure_one()
+        super(SaleOrderLine, self)._get_delivered_qty()
+        qty = 0.0
+        for move in self.procurement_ids.mapped('move_ids').filtered(lambda r: r.state == 'done' and not r.scrapped):
+            if move.location_dest_id.usage == "customer":
+                qty += self.env['product.uom']._compute_qty_obj(move.product_uom, move.product_uom_qty, self.product_uom)
+            elif move.location_id.usage == "customer":
+                qty -= self.env['product.uom']._compute_qty_obj(move.product_uom, move.product_uom_qty, self.product_uom)
+        return qty
+
+    @api.multi
+    def _check_package(self):
+        default_uom = self.product_id.product_uom
+        pack = self.product_packaging
+        qty = self.product_uom_qty
+        q = self.product_id.product_uom._compute_qty(pack.qty, default_uom)
+        if qty and q and (qty % q):
+            newqty = qty - (qty % q) + q
+            return {
+               'title': _('Warning!'),
+               'message': _("This product is packaged by %d %s. You should sell %d %s.") % (pack.qty, default_uom, newqty, default_uom)
+            }
+        return {}
 
-    def _get_moves_taxes(self, cr, uid, moves, inv_type, context=None):
-        is_extra_move, extra_move_tax = super(stock_move, self)._get_moves_taxes(cr, uid, moves, inv_type, context=context)
-        if inv_type == 'out_invoice':
-            for move in moves:
-                if move.procurement_id and move.procurement_id.sale_line_id:
-                    is_extra_move[move.id] = False
-                    extra_move_tax[move.picking_id, move.product_id] = [(6, 0, [x.id for x in move.procurement_id.sale_line_id.tax_id])]
-                elif move.picking_id.sale_id and move.product_id.product_tmpl_id.taxes_id:
-                    fp = move.picking_id.sale_id.fiscal_position
-                    res = self.pool.get("account.invoice.line").product_id_change(cr, uid, [], move.product_id.id, None, partner_id=move.picking_id.partner_id.id, fposition_id=(fp and fp.id), context=context)
-                    extra_move_tax[0, move.product_id] = [(6, 0, res['value']['invoice_line_tax_ids'])]
-                else:
-                    extra_move_tax[0, move.product_id] = [(6, 0, [x.id for x in move.product_id.product_tmpl_id.taxes_id])]
-        return (is_extra_move, extra_move_tax)
-
-    def _get_taxes(self, cr, uid, move, context=None):
-        if move.procurement_id.sale_line_id.tax_id:
-            return [tax.id for tax in move.procurement_id.sale_line_id.tax_id]
-        return super(stock_move, self)._get_taxes(cr, uid, move, context=context)
-
-class stock_location_route(osv.osv):
-    _inherit = "stock.location.route"
-    _columns = {
-        'sale_selectable': fields.boolean("Selectable on Sales Order Line")
-        }
 
+class StockLocationRoute(models.Model):
+    _inherit = "stock.location.route"
 
-class stock_picking(osv.osv):
-    _inherit = "stock.picking"
-
-    def _get_partner_to_invoice(self, cr, uid, picking, context=None):
-        """ Inherit the original function of the 'stock' module
-            We select the partner of the sales order as the partner of the customer invoice
-        """
-        if picking.sale_id:
-            saleorder_ids = self.pool['sale.order'].search(cr, uid, [('procurement_group_id' ,'=', picking.group_id.id)], context=context)
-            saleorders = self.pool['sale.order'].browse(cr, uid, saleorder_ids, context=context)
-            if saleorders and saleorders[0] and saleorders[0].order_policy == 'picking':
-                saleorder = saleorders[0]
-                return saleorder.partner_invoice_id.id
-        return super(stock_picking, self)._get_partner_to_invoice(cr, uid, picking, context=context)
-    
-    def _get_sale_id(self, cr, uid, ids, name, args, context=None):
-        sale_obj = self.pool.get("sale.order")
-        res = {}
-        for picking in self.browse(cr, uid, ids, context=context):
-            res[picking.id] = False
-            if picking.group_id:
-                sale_ids = sale_obj.search(cr, uid, [('procurement_group_id', '=', picking.group_id.id)], context=context)
-                if sale_ids:
-                    res[picking.id] = sale_ids[0]
-        return res
-    
-    _columns = {
-        'sale_id': fields.function(_get_sale_id, type="many2one", relation="sale.order", string="Sale Order"),
-    }
-
-    def _get_invoice_vals(self, cr, uid, key, inv_type, journal_id, moves, context=None):
-        inv_vals = super(stock_picking, self)._get_invoice_vals(cr, uid, key, inv_type, journal_id, moves, context=context)
-        if inv_type in ('out_invoice', 'out_refund'):
-            sales = [x.picking_id.sale_id or x.origin_returned_move_id.picking_id.sale_id for x in moves if x.picking_id.sale_id or x.origin_returned_move_id.picking_id.sale_id]
-            if sales:
-                sale = sales[0]
-                inv_vals.update({
-                    'fiscal_position_id': sale.fiscal_position_id.id,
-                    'payment_term_id': sale.payment_term_id.id,
-                    'user_id': sale.user_id.id,
-                    'team_id': sale.team_id.id,
-                    'name': sale.client_order_ref or '',
-                    'sale_ids': [(6, 0, list(set([x.id for x in sales])))],
-                    })
-        return inv_vals
-
-    def get_service_line_vals(self, cr, uid, moves, partner, inv_type, context=None):
-        res = super(stock_picking, self).get_service_line_vals(cr, uid, moves, partner, inv_type, context=context)
-        if inv_type == 'out_invoice':
-            sale_line_obj = self.pool.get('sale.order.line')
-            orders = list(set([x.procurement_id.sale_line_id.order_id.id for x in moves if x.procurement_id.sale_line_id]))
-            sale_line_ids = sale_line_obj.search(cr, uid, [('order_id', 'in', orders), ('invoiced', '=', False), '|', ('product_id', '=', False),
-                                                           ('product_id.type', '=', 'service')], context=context)
-            if sale_line_ids:
-                created_lines = sale_line_obj.invoice_line_create(cr, uid, sale_line_ids, context=context)
-                res += [(4, x) for x in created_lines]
-        return res
+    sale_selectable = fields.Boolean(string="Selectable on Sales Order Line")
 
 
-class account_invoice(osv.Model):
+class AccountInvoice(models.Model):
     _inherit = 'account.invoice'
-    _columns = {
-        'incoterms_id': fields.many2one(
-            'stock.incoterms',
-            "Incoterms",
-            help="Incoterms are series of sales terms. They are used to divide transaction costs and responsibilities between buyer and seller and reflect state-of-the-art transportation practices.",
-            readonly=True, 
-            states={'draft': [('readonly', False)]}),
-    }
-
-class sale_advance_payment_inv(osv.TransientModel):
-    _inherit = 'sale.advance.payment.inv'
-
-    def _prepare_advance_invoice_vals(self, cr, uid, ids, context=None):
-        result = super(sale_advance_payment_inv,self)._prepare_advance_invoice_vals(cr, uid, ids, context=context)
-        if context is None:
-            context = {}
-
-        sale_obj = self.pool.get('sale.order')
-        sale_ids = context.get('active_ids', [])
-        res = []
-        for sale in sale_obj.browse(cr, uid, sale_ids, context=context):
-            elem = filter(lambda t: t[0] == sale.id, result)[0]
-            elem[1]['incoterms_id'] = sale.incoterm.id or False
-            res.append(elem)
-        return res
+
+    incoterms_id = fields.Many2one('stock.incoterms', string="Incoterms",
+        help="Incoterms are series of sales terms. They are used to divide transaction costs and responsibilities between buyer and seller and reflect state-of-the-art transportation practices.",
+        readonly=True, states={'draft': [('readonly', False)]})
 
 
-class procurement_order(osv.osv):
+class ProcurementOrder(models.Model):
     _inherit = "procurement.order"
 
-    def _run_move_create(self, cr, uid, procurement, context=None):
-        vals = super(procurement_order, self)._run_move_create(cr, uid, procurement, context=context)
-        #copy the sequence from the sale order line on the stock move
-        if procurement.sale_line_id:
-            vals.update({'sequence': procurement.sale_line_id.sequence})
+    so_line_id = fields.Many2one('sale.order.line', string='Sale Order Line')
+
+    @api.model
+    def _run_move_create(self, procurement):
+        vals = super(ProcurementOrder, self)._run_move_create(procurement)
+        if self.sale_line_id:
+            vals.update({'sequence': self.sale_line_id.sequence})
         return vals
+
+
+class StockMove(models.Model):
+    _inherit = "stock.move"
+
+    @api.multi
+    def action_done(self):
+        result = super(StockMove, self).action_done()
+
+        # Update delivered quantities on sale order lines
+        todo = self.env['sale.order.line']
+        for move in self:
+            if (move.procurement_id.so_line_id) and (move.product_id.invoice_policy in ('order', 'delivery')):
+                todo |= move.procurement_id.so_line_id
+        for line in todo:
+            line.qty_delivered = line._get_delivered_qty()
+        return result
diff --git a/addons/sale_stock/sale_stock_demo.xml b/addons/sale_stock/sale_stock_demo.xml
index a8b9d984c450..06010a5a6b6a 100644
--- a/addons/sale_stock/sale_stock_demo.xml
+++ b/addons/sale_stock/sale_stock_demo.xml
@@ -4,7 +4,6 @@
     
         <record id="sale.sale_order_1" model="sale.order">
             <field name="warehouse_id" ref="stock.warehouse0"/>
-            <field name="order_policy">prepaid</field>
         </record>
         
         <record id="sale.sale_order_2" model="sale.order">
@@ -17,23 +16,15 @@
 
         <record id="sale.sale_order_5" model="sale.order">
             <field name="warehouse_id" ref="stock.warehouse0"/>
-            <field name="order_policy">picking</field>
         </record>
 
         <record id="sale.sale_order_6" model="sale.order">
             <field name="warehouse_id" ref="stock.warehouse0"/>
-            <field name="order_policy">picking</field>
         </record>
-        
+
         <record id="sale.sale_order_8" model="sale.order">
             <field name="warehouse_id" ref="stock.warehouse0"/>
         </record>
-        
-         <!-- Confirm some Sale Orders-->
-        <workflow action="order_confirm" model="sale.order" ref="sale.sale_order_5"/>
-        
-        <!-- Run all schedulers -->
-        <function model="procurement.order" name="run_scheduler"/>
-        
+
     </data>
 </openerp>
diff --git a/addons/sale_stock/sale_stock_demo.yml b/addons/sale_stock/sale_stock_demo.yml
index 5ffa75f0f780..66df6e9643aa 100644
--- a/addons/sale_stock/sale_stock_demo.yml
+++ b/addons/sale_stock/sale_stock_demo.yml
@@ -6,7 +6,6 @@
         if account_id:
             vals = {
               'warehouse_id': ref('stock.warehouse0'),
-              'order_policy': 'prepaid',
             }
             self._update(cr, uid, 'sale.order', 'sale', vals, 'sale_order_4')
 
diff --git a/addons/sale_stock/sale_stock_view.xml b/addons/sale_stock/sale_stock_view.xml
index 3f72d3f1c102..f67aa533dbc9 100644
--- a/addons/sale_stock/sale_stock_view.xml
+++ b/addons/sale_stock/sale_stock_view.xml
@@ -2,16 +2,12 @@
 <openerp>
     <data>
 
-        <record id="view_order_form_inherit" model="ir.ui.view">
+        <record id="view_order_form_inherit_sale_stock" model="ir.ui.view">
             <field name="name">sale.order.form.sale.stock</field>
             <field name="model">sale.order</field>
             <field name="inherit_id" ref="sale.view_order_form"/>
             <field name="arch" type="xml">
                 <data>
-                   <xpath expr="//button[@name='invoice_corrected']" position="after">
-                       <button name="ship_recreate" states="shipping_except" string="Recreate Delivery Order"/>
-                       <button name="ship_corrected" states="shipping_except" string="Ignore Exception"/>
-                   </xpath>
                    <xpath expr="//button[@name='action_view_invoice']" position="before">
                        <field name="picking_ids" invisible="1"/>
                        <button type="object"
@@ -19,140 +15,31 @@
                            class="oe_stat_button"
                            icon="fa-truck"
                            attrs="{'invisible': [('delivery_count', '=', 0)]}" groups="base.group_user">
-                           <field name="delivery_count" widget="statinfo" string="Transfers" help="Delivery Orders"/>
+                           <field name="delivery_count" widget="statinfo" string="Delivery"/>
                        </button>
                     </xpath>
-                    <xpath expr="//button[@name='action_cancel']" position="after">
-                        <button name="ship_cancel" states="shipping_except" string="Cancel Order"/>
-                    </xpath>
-                    <field name="state" position="attributes">
-                        <attribute name="statusbar_colors" t-translate="off">{"shipping_except":"red","invoice_except":"red","waiting_date":"blue"}</attribute>
-                    </field>
-                    <field name="company_id" position="replace">
-                        <field name="company_id" readonly="True"/>
-                    </field>
                     <xpath expr="//group[@name='sales_person']" position="before">
                         <group string="Shipping Information" name="sale_shipping">
-                            <field name="warehouse_id" on_change="onchange_warehouse_id(warehouse_id)" options="{'no_create': True}" groups="stock.group_locations"/>
+                            <field name="warehouse_id" options="{'no_create': True}" groups="stock.group_locations"/>
                             <field name="incoterm" widget="selection" groups="base.group_user"/>
                             <field name="picking_policy" required="True"/>
                         </group>
                     </xpath>
-                    <xpath expr="//field[@name='order_line']/form//field[@name='product_id']" position="attributes">
-                      <!-- no product_uos to force reset of product_uom, product_uos and product_uos_qty in porduct_id_change -->
-                      <attribute name="on_change">product_id_change_with_wh(
-                        parent.pricelist_id, product_id, product_uom_qty, False, product_uos_qty, False, name,
-                        parent.partner_id, False, True, parent.date_order, product_packaging, parent.fiscal_position_id,
-                        False, parent.warehouse_id, context)
-                      </attribute>
-                      <attribute name="context">{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':False}</attribute>
-                    </xpath>
-                    <xpath expr="//field[@name='order_line']/tree//field[@name='product_id']" position="attributes">
-                       <attribute name="on_change">product_id_change_with_wh(
-                        parent.pricelist_id, product_id, product_uom_qty, False, product_uos_qty, False, name,
-                        parent.partner_id, False, True, parent.date_order, product_packaging, parent.fiscal_position_id,
-                        False, parent.warehouse_id, context)
-                      </attribute>
-                   </xpath>
-                    <xpath expr="//field[@name='order_line']/form//field[@name='product_uos_qty']" position="attributes">
-                      <!-- keep product_uos to force update of product_uom and product_uom_qty in porduct_id_change -->
-                      <attribute name="on_change">product_id_change_with_wh(
-                        parent.pricelist_id, product_id, product_uom_qty, False, product_uos_qty, product_uos, name,
-                        parent.partner_id, False, True, parent.date_order, product_packaging, parent.fiscal_position_id,
-                        False, parent.warehouse_id, context)
-                      </attribute>
+                    <xpath expr="//page/field[@name='order_line']/form/group/group/field[@name='tax_id']" position="before">
+                        <field name="product_tmpl_id" invisible="1"/>
+                        <field name="product_packaging" context="{'default_product_tmpl_id': product_tmpl_id, 'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom}" domain="[('product_tmpl_id','=',product_tmpl_id)]" groups="product.group_stock_packaging" />
                     </xpath>
-                    <xpath expr="//field[@name='order_line']/form//field[@name='product_uom_qty']" position="attributes">
-                      <attribute name="on_change">product_id_change_with_wh(
-                        parent.pricelist_id, product_id, product_uom_qty, product_uom, product_uos_qty, False, name, 
-                        parent.partner_id, False, False, parent.date_order, product_packaging, parent.fiscal_position_id,
-                        True, parent.warehouse_id, context)
-                      </attribute> 
+                    <xpath expr="//field[@name='order_line']/form/group/group/field[@name='price_unit']" position="before">
+                         <field name="route_id" groups="sale_stock.group_route_so_lines"/>
                     </xpath>
-                   <xpath expr="//group[@name='technical']" position="inside">
-                       <field name="shipped" groups="base.group_no_one"/>
-                   </xpath>
-                   <xpath expr="//page/field[@name='order_line']/form/group/group/field[@name='tax_id']" position="after">
-                       <label for="delay"/>
-                       <div>
-                           <field name="delay" class="oe_inline"/> days
-                       </div>
-                   </xpath>
-                   <xpath expr="//page/field[@name='order_line']/form/group/group/field[@name='tax_id']" position="before">
-                       <field name="product_tmpl_id" invisible="1"/>
-                       <field name="product_packaging" context="{'default_product_tmpl_id': product_tmpl_id, 'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom}" on_change="product_packaging_change(parent.pricelist_id, product_id, product_uom_qty, product_uom, parent.partner_id, product_packaging, True, context)" domain="[('product_tmpl_id','=',product_tmpl_id)]" groups="product.group_stock_packaging" />
-                   </xpath>
-                   <xpath expr="//page/field[@name='order_line']/tree/field[@name='sequence']" position="after">
-                       <field name="delay" invisible="1"/>
-                   </xpath>
-                   <xpath expr="//page/field[@name='order_line']/tree/field[@name='th_weight']" position="after">
-                       <field name="product_packaging" invisible="1"/>
-                   </xpath>
-                   <xpath expr="//group[@name='sale_pay']" position="inside">
-                            <field name="order_policy"/>
-                   </xpath>                            
-                   <xpath expr="//field[@name='order_line']/form/group/group/field[@name='price_unit']" position="before">
+                    <xpath expr="//field[@name='order_line']/tree/field[@name='price_unit']" position="before">
                         <field name="route_id" groups="sale_stock.group_route_so_lines"/>
-                   </xpath>
-                   <xpath expr="//field[@name='order_line']/tree/field[@name='price_unit']" position="before">
-                       <field name="route_id" groups="sale_stock.group_route_so_lines"/>
-                   </xpath>
+                    </xpath>
                 </data>
            </field>
         </record>
 
-        <record id="view_res_partner_tree_type" model="ir.ui.view">
-            <field name="name">res.partner.tree.inherit.type</field>
-            <field name="model">res.partner</field>
-            <field name="inherit_id" ref="base.view_partner_tree"/>
-            <field name="arch" type="xml">
-                <field name="parent_id" position="after">
-                    <field name="type" invisible="context.get('hide_type', 1)"/>
-                </field>
-            </field>
-        </record>
-
-        <!-- On the customer/supplier form if "Allow a different address for
-        delivery and invoicing" is set add "Contact Details" in the more menu
-        showing the list of contact with their types -->
-        <act_window
-            id="res_partner_rule_children"
-            name="Contact Details"
-            context="{'default_parent_id': active_id, 'hide_type': 0}"
-            domain="[('parent_id','=',active_id)]"
-            res_model="res.partner"
-            src_model="res.partner"
-            view_mode="tree,form,kanban"
-            view_type="form"
-            groups="sale.group_delivery_invoice_address"
-            />
-
-        <record id="view_picking_internal_search_inherit" model="ir.ui.view">
-            <field name="name">stock.picking.search.inherit</field>
-            <field name="model">stock.picking</field>
-            <field name="inherit_id" ref="stock.view_picking_internal_search"/>
-            <field name="arch" type="xml">
-                <xpath expr="//field[@name='partner_id']" position="before">
-                    <filter string="To Invoice" name="to_invoice" domain="[('invoice_state', '=', '2binvoiced')]"/>
-                </xpath>
-            </field>
-        </record>
-
-
-        <record id="view_order_form_inherit2" model="ir.ui.view">
-            <field name="name">sale.order.line.form.sale.stock.location</field>
-            <field name="model">sale.order.line</field>
-            <field name="inherit_id" ref="sale.view_order_line_form2"/>
-            <field name="arch" type="xml">
-                <data>
-                   <xpath expr="//field[@name='price_unit']" position="before">
-                       <field name="route_id" groups="sale_stock.group_route_so_lines" />
-                   </xpath>
-                </data>
-           </field>
-        </record>
-        
-        <record id="view_order_line_tree_inherit" model="ir.ui.view">
+        <record id="view_order_line_tree_inherit_sale_stock" model="ir.ui.view">
             <field name="name">sale.order.line.tree.sale.stock.location</field>
             <field name="inherit_id" ref="sale.view_order_line_tree"/>
             <field name="model">sale.order.line</field>
@@ -163,7 +50,7 @@
             </field>
         </record>
 
-        <template id="report_sale_order_incoterm" inherit_id="sale.report_saleorder_document">
+        <template id="report_saleorder_document_inherit_sale_stock" inherit_id="sale.report_saleorder_document">
           <xpath expr="//div[@name='payment_term']" position="after">
             <div class="col-xs-3" t-if="doc.incoterm" groups="sale.group_display_incoterm">
               <strong>Incoterms:</strong>
diff --git a/addons/sale_stock/sale_stock_workflow.xml b/addons/sale_stock/sale_stock_workflow.xml
deleted file mode 100644
index 308167500350..000000000000
--- a/addons/sale_stock/sale_stock_workflow.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<openerp>
-    <data>
-
-    </data>
-</openerp>
diff --git a/addons/sale_stock/stock_view.xml b/addons/sale_stock/stock_view.xml
index 24c6d46caabe..95f72bd6f986 100644
--- a/addons/sale_stock/stock_view.xml
+++ b/addons/sale_stock/stock_view.xml
@@ -3,7 +3,7 @@
     <data>
 
         <!--  Add menu: Billing - Deliveries to invoice -->
-        <record id="outgoing_picking_list_to_invoice" model="ir.actions.act_window">
+        <record id="stock_picking_action_outgoing_picking_list_to_invoice" model="ir.actions.act_window">
             <field name="name">Deliveries to Invoice</field>
             <field name="res_model">stock.picking</field>
             <field name="type">ir.actions.act_window</field>
@@ -18,9 +18,9 @@
             groups="base.group_sale_salesman"
             parent="base.menu_base_partner" sequence="5" />
         <menuitem id="base.menu_invoiced" name="Invoicing" parent="base.menu_aftersale" sequence="1"/>
-        <menuitem action="outgoing_picking_list_to_invoice" id="menu_action_picking_list_to_invoice" parent="base.menu_invoiced" sequence="20"/>
+        <menuitem action="stock_picking_action_outgoing_picking_list_to_invoice" id="menu_action_picking_list_to_invoice" parent="base.menu_invoiced" sequence="20"/>
         
-        <record id="stock_location_route_form_view_inherit" model="ir.ui.view">
+        <record id="stock_location_route_form_view_inherit_sale_stock" model="ir.ui.view">
             <field name="name">stock.location.route.form</field>
             <field name="inherit_id" ref="stock.stock_location_route_form_view"/>
             <field name="model">stock.location.route</field>
diff --git a/addons/sale_stock/test/cancel_order_sale_stock.yml b/addons/sale_stock/test/cancel_order_sale_stock.yml
deleted file mode 100644
index 49835b064c92..000000000000
--- a/addons/sale_stock/test/cancel_order_sale_stock.yml
+++ /dev/null
@@ -1,94 +0,0 @@
--
-  In order to test the cancel sale order with that user which have salesman rights.
-  First I confirm order.
--
-  !context
-    uid: 'res_sale_stock_salesman'
--
-  !workflow {model: sale.order, action: order_confirm, ref: sale.sale_order_8}
--
-  I do a partial delivery order as a stock user.
--
-  !context
-    uid: 'res_stock_user'
--
-  !python {model: stock.picking}: |
-    domain = [('origin','=','Test/001')]
-    picks = self.search(cr, uid, domain, context=context)
-    pick = self.browse(cr, uid, picks[-1], context=context)
-    self.pool.get('stock.pack.operation').create(cr, uid, {
-        'picking_id': pick.id,
-        'product_id': ref('product.product_product_27'),
-        'product_uom_id': ref('product.product_uom_unit'),
-        'product_qty': 1,
-        'location_id': pick.location_id.id,
-        'location_dest_id': pick.location_dest_id.id,
-    })
-    pick.do_transfer()
--
-  I test that I have two pickings, one done and one backorder to do
--
-  !python {model: stock.picking}: |
-    picks = self.search(cr, uid, [('origin','=','Test/001')])
-    assert len(picks)>1, 'Only one picking, partial picking may have failed!'
-    picks = self.search(cr, uid, [('origin','=','Test/001'), ('state','=','done')])
-    assert len(picks)==1, 'You should have one delivery order which is done!'
-    picks = self.search(cr, uid, [('origin','=','Test/001'), ('backorder_id','=',picks[0])])
-    assert len(picks)==1, 'You should have one backorder to process!'
--
-  I cancel the backorder
--
-  !python {model: stock.picking}: |
-    picks = self.search(cr, uid, [('origin','=','Test/001'),('backorder_id','<>',False)])
-    self.action_cancel(cr, uid, picks)
--
-  I run the scheduler.
--
-  !python {model: procurement.order}: |
-
-     self.run_scheduler(cr, uid)
--
-  Salesman can also check order therefore test with that user which have salesman rights,
--
-  !context
-    uid: 'res_sale_stock_salesman'
--
-  I check order status in "Ship Exception".
--
-  !assert {model: sale.order, id: sale.sale_order_8, string: Sale order should be in shipping exception}:
-    - state == "shipping_except"
--
-  Now I regenerate shipment.
--
-  !workflow {model: sale.order, action: ship_recreate, ref: sale.sale_order_8}
--
-  I check state of order in 'To Invoice'.
--
-  !assert {model: sale.order, id: sale.sale_order_8, string: Sale order should be In Progress state}:
-    - state == 'manual'
--
-  I make invoice for order.
--
-  !workflow {model: sale.order, action: manual_invoice, ref: sale.sale_order_8}
--
-  To cancel the sale order from Invoice Exception, I have to cancel the invoice of sale order.
--
-  !python {model: sale.order}: |
-    invoice_ids = self.browse(cr, uid, ref("sale.sale_order_8")).invoice_ids
-    first_invoice_id = invoice_ids[0]
-    self.pool.get('account.invoice').signal_workflow(cr, uid, [first_invoice_id.id], 'invoice_cancel')
--
-  I check order status in "Invoice Exception" and related invoice is in cancel state.
--
-  !assert {model: sale.order, id: sale.sale_order_8, string: Sale order should be in Invoice Exception state}:
-    - state == "invoice_except", "Order should be in Invoice Exception state after cancel Invoice"
--
-  Then I click on the Ignore Exception button.
--
-  !workflow {model: sale.order, action: invoice_corrected, ref: sale.sale_order_8}
-
--
-  I check state of order in 'In Progress'.
--
-  !assert {model: sale.order, id: sale.sale_order_8, string: Sale order should be In progress state}:
-    - state == 'progress'
diff --git a/addons/sale_stock/test/picking_order_policy.yml b/addons/sale_stock/test/picking_order_policy.yml
deleted file mode 100644
index 097de63919ef..000000000000
--- a/addons/sale_stock/test/picking_order_policy.yml
+++ /dev/null
@@ -1,217 +0,0 @@
--
-  In order to test process of the Sale Order with access rights of saleman,
--
-  !context
-    uid: 'res_sale_stock_salesman'
--
-  Create a new SO to be sure we don't have one with product that can explode in mrp
--
-  !record {model: sale.order, id: sale_order_service}:
-    partner_id: base.res_partner_18
-    partner_invoice_id: base.res_partner_18
-    partner_shipping_id: base.res_partner_18
-    user_id: base.user_root
-    pricelist_id: product.list0
-    warehouse_id: stock.warehouse0
-    order_policy: picking
--
-  Add SO line with service type product in SO to check flow which contain service type product in SO(BUG#1167330).
--
-  !record {model: sale.order.line, id: sale_order_1}:
-    name: 'On Site Assistance'
-    product_id: product.product_product_2
-    product_uom_qty: 1.0
-    product_uom: 1
-    price_unit: 150.0
-    order_id: sale_order_service
--
-  Add a second SO line with a normal product
--
-  !record {model: sale.order.line, id: sale_order_2}:
-    name: 'Mouse Optical'
-    product_id: product.product_product_10
-    product_uom_qty: 1.0
-    product_uom: 1
-    price_unit: 150.0
-    order_id: sale_order_service
--
-  First I check the total amount of the Quotation before Approved.
--
-  !python {model: sale.order}: |
-    from openerp.tools import float_compare
-    so = self.browse(cr, uid, ref('sale_order_service'))
-    float_compare(sum([l.price_subtotal for l in so.order_line]), so.amount_untaxed, precision_digits=2) == 0, "The amount of the Quotation is not correctly computed"
--
-  I set an explicit invoicing partner that is different from the main SO Customer
--
-  !python {model: sale.order}: |
-    order = self.browse(cr, uid, ref("sale_order_service"))
-    order.write({'partner_invoice_id': ref('base.res_partner_address_30')})
--
-  I confirm the quotation with Invoice based on deliveries policy.
--
-  !workflow {model: sale.order, action: order_confirm, ref: sale_order_service}
--
-  I check that invoice should not created before dispatch delivery.
--
-  !python {model: sale.order}: |
-    order = self.pool.get('sale.order').browse(cr, uid, ref("sale_order_service"))
-    assert order.state == 'progress', 'Order should be in inprogress.'
-    assert len(order.invoice_ids) == False, "Invoice should not created."
--
-  I check the details of procurement after confirmed quotation.
--
-  !python {model: sale.order}: |
-    from datetime import datetime, timedelta
-    from dateutil.relativedelta import relativedelta
-    from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
-    order = self.browse(cr, uid, ref("sale_order_service"))
-    for order_line in order.order_line:
-        if order_line.product_id.type == 'product':
-            procurement = order_line.procurement_ids[0]
-            date_planned = datetime.strptime(order.date_order, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=order_line.delay or 0.0)
-            date_planned = (date_planned - timedelta(days=order.company_id.security_lead)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
-            assert procurement.date_planned == date_planned, "Scheduled date is not correspond."
-            assert procurement.product_id.id == order_line.product_id.id, "Product is not correspond."
-            assert procurement.product_qty == order_line.product_uom_qty, "Qty is not correspond."
-            assert procurement.product_uom.id == order_line.product_uom.id, "UOM is not correspond."
--
-  Only stock user can change data related warehouse therefore test with that user which have stock user rights,
--
-  !context
-    uid: 'res_stock_user'
--
-  I run the scheduler.
--
-  !python {model: procurement.order}: |
-    self.run_scheduler(cr, uid)
--
-  Salesman can also check order therefore test with that user which have salesman rights,
--
-  !context
-    uid: 'res_sale_stock_salesman'
--
-  I check the details of delivery order after confirmed quotation.
--
-  !python {model: sale.order}: |
-    from datetime import datetime, timedelta
-    from dateutil.relativedelta import relativedelta
-    from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
-    sale_order = self.browse(cr, uid, ref("sale_order_service"))
-    assert sale_order.picking_ids, "Delivery order is not created."
-    for picking in sale_order.picking_ids:
-      assert picking.state == "auto" or "confirmed", "Delivery order should be in 'Waitting Availability' state."
-      assert picking.origin == sale_order.name,"Origin of Delivery order is not correspond with sequence number of sale order."
-      assert picking.picking_type_id == self.pool.get('ir.model.data').get_object(cr, uid, 'stock', 'picking_type_out', context=context),"Shipment should be Outgoing."
-      assert picking.move_type == sale_order.picking_policy,"Delivery Method is not corresponding with delivery method of sale order."
-      assert picking.partner_id.id == sale_order.partner_shipping_id.id,"Shipping Address is not correspond with sale order."
-      assert picking.note == sale_order.note,"Note is not correspond with sale order."
-      assert picking.invoice_state == (sale_order.order_policy=='picking' and '2binvoiced') or 'none',"Invoice policy is not correspond with sale order."
-      assert len(picking.move_lines) == len(sale_order.order_line) - 1, "Total move of delivery order are not corresposning with total sale order lines."
-      location_id = sale_order.warehouse_id.lot_stock_id.id
-      for move in picking.move_lines:
-         order_line = move.procurement_id.sale_line_id
-         date_planned = datetime.strptime(sale_order.date_order, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=order_line.delay or 0.0)
-         date_planned = (date_planned - timedelta(days=sale_order.company_id.security_lead)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
-         assert datetime.strptime(move.date_expected, DEFAULT_SERVER_DATETIME_FORMAT) == datetime.strptime(date_planned, DEFAULT_SERVER_DATETIME_FORMAT), "Excepted Date is not correspond with Planned Date."
-         assert move.product_id.id == order_line.product_id.id,"Product is not correspond."
-         assert move.product_qty == order_line.product_uom_qty,"Product Quantity is not correspond."
-         assert move.product_uom.id == order_line.product_uom.id,"Product UOM is not correspond."
-         assert move.product_uos_qty == (order_line.product_uos and order_line.product_uos_qty or order_line.product_uom_qty), "Product UOS Quantity is not correspond."
-         assert move.product_uos.id == (order_line.product_uos and order_line.product_uos.id or order_line.product_uom.id), "Product UOS is not correspond"
-         assert move.product_packaging.id == order_line.product_packaging.id,"Product packaging is not correspond."
-         #assert move.location_id.id == location_id,"Source Location is not correspond."
--
-  Now, I dispatch delivery order.
--
-  !python {model: stock.picking}: |
-    order = self.pool.get('sale.order').browse(cr, uid, ref("sale_order_service"), context=context)
-    for pick in order.picking_ids:
-        data = pick.force_assign()
-        if data == True:
-          pick.do_transfer()
--
-  I run the scheduler.
--
-  !python {model: procurement.order}: |
-    self.run_scheduler(cr, uid)
--
-  I check sale order to verify shipment.
--
-  !python {model: sale.order}: |
-    order = self.pool.get('sale.order').browse(cr, uid, ref("sale_order_service"))
-    assert order.shipped == True, "Sale order is not Delivered."
-    #assert order.state == 'progress', 'Order should be in inprogress.'
-    assert len(order.invoice_ids) == False, "Invoice should not created on dispatch delivery order."
--
-  I create Invoice from Delivery Order.
--
-  !python {model: stock.invoice.onshipping}: |
-    sale = self.pool.get('sale.order')
-    sale_order = sale.browse(cr, uid, ref("sale_order_service"))
-    ship_ids = [x.id for x in sale_order.picking_ids]
-    wiz_id = self.create(cr, uid, {'journal_id': ref('sales_journal')},
-      {'active_ids': ship_ids, 'active_model': 'stock.picking'})
-    self.create_invoice(cr, uid, [wiz_id], {"active_ids": ship_ids, "active_id": ship_ids[0]})
--
-  I check the invoice details after dispatched delivery.
--
-  !python {model: sale.order}: |
-    from openerp.tools import float_compare
-    order = self.browse(cr, uid, ref("sale_order_service"))
-    assert order.invoice_ids, "Invoice is not created."
-    ac = order.partner_invoice_id.property_account_receivable_id.id
-    journal_ids = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'sale'), ('company_id', '=', order.company_id.id)])
-    for invoice in order.invoice_ids:
-        assert invoice.type == 'out_invoice',"Invoice should be Customer Invoice."
-        assert invoice.account_id.id == ac,"Invoice account is not correspond."
-        assert invoice.reference == order.client_order_ref or order.name,"Reference is not correspond."
-        assert invoice.partner_id.id == order.partner_invoice_id.id,"Customer does not correspond."
-        assert invoice.currency_id.id == order.pricelist_id.currency_id.id, "Currency is not correspond."
-        assert (invoice.comment or '') == (order.note or ''),"Note is not correspond."
-        assert invoice.journal_id.id in journal_ids,"Sales Journal is not link on Invoice."
-        assert invoice.payment_term_id.id == order.payment_term_id.id, "Payment term is not correspond."
-    for so_line in order.order_line:
-        inv_line = so_line.invoice_lines[0]
-        ac = so_line.product_id.property_account_income_id.id or so_line.product_id.categ_id.property_account_income_categ_id.id
-        assert inv_line.product_id.id == so_line.product_id.id or False,"Product is not correspond"
-        assert inv_line.account_id.id == ac,"Account of Invoice line is not corresponding."
-        assert inv_line.uos_id.id == (so_line.product_uos and so_line.product_uos.id or so_line.product_uom.id), "Product UOS is not correspond."
-        assert float_compare(inv_line.price_unit, so_line.price_unit , precision_digits=2) == 0, "Price Unit is not correspond."
-        assert inv_line.quantity == (so_line.product_uos and so_line.product_uos_qty or so_line.product_uom_qty), "Product qty is not correspond."
-        assert inv_line.price_subtotal == so_line.price_subtotal, "Price sub total is not correspond."
--
-  Only Stock manager can open the Invoice therefore test with that user which have stock manager rights,
--
-  !context
-    uid: 'res_stock_manager'
--
-  I open the Invoice.
--
-  !python {model: sale.order}: |
-    so = self.browse(cr, uid, ref("sale_order_service"))
-    account_invoice_obj = self.pool.get('account.invoice')
-    for invoice in so.invoice_ids:
-      account_invoice_obj.signal_workflow(cr, uid, [invoice.id], 'invoice_open')
--
-  I pay the invoice
--
-  !python {model: account.invoice}: |
-    sale_order = self.pool.get('sale.order')
-    order = sale_order.browse(cr, uid, ref("sale_order_service"))
-    journal_ids = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'cash'), ('company_id', '=', order.company_id.id)], limit=1)
-    import time
-    for invoice in order.invoice_ids:
-        self.pay_and_reconcile(cr, uid, [invoice.id], journal_ids[0])
--
-  To test process of the Sale Order with access rights of saleman,
--
-  !context
-    uid: 'res_sale_stock_salesman'
--
-  I check the order after paid invoice.
--
-  !python {model: sale.order}: |
-    order = self.browse(cr, uid, ref("sale_order_service"))
-    assert order.invoiced == True, "Sale order is not invoiced."
-    assert order.state == 'done', 'Order should be in closed.'
diff --git a/addons/sale_stock/test/prepaid_order_policy.yml b/addons/sale_stock/test/prepaid_order_policy.yml
deleted file mode 100644
index 74449371adaf..000000000000
--- a/addons/sale_stock/test/prepaid_order_policy.yml
+++ /dev/null
@@ -1,30 +0,0 @@
--
-  In order to test the Prepaid Order Policy, I create a product
--
-  !record {model: product.product, id: product_prepaid1}:
-    name: 'OpenERP Documentation Book'
-    list_price: 60.60
--
-  Now i create a sale order that uses my new product
--
-  !record {model: sale.order, id: sale_order_prepaid1}:
-    partner_id: base.res_partner_2
-    order_policy: prepaid
-    order_line:
-      - product_id: sale_stock.product_prepaid1
-        product_uom_qty: 10
--
-  Now I confirm the Quotation with "Pay before delivery" policy with access rights of salesman.
--
-  !context
-    uid: 'res_sale_stock_salesman'
--
-  !workflow {model: sale.order, action: order_confirm, ref: sale_order_prepaid1}
--
-  I check that delivery order should not created before invoice is paid.
--
-  !python {model: sale.order}: |
-    sale_order = self.browse(cr, uid, ref("sale_order_prepaid1"))
-    assert len(sale_order.picking_ids) == False, "Delivery order should not created before invoice."
-    assert sale_order.invoice_ids, "Invoice should be created."
-
diff --git a/addons/sale_stock/test/sale_order_canceled_line.yml b/addons/sale_stock/test/sale_order_canceled_line.yml
deleted file mode 100644
index d78c88a09911..000000000000
--- a/addons/sale_stock/test/sale_order_canceled_line.yml
+++ /dev/null
@@ -1,45 +0,0 @@
--
-  I create a draft Sale Order with 2 lines but 1 canceled in order to check if the canceled lines are not considered in the logic
--
-  !record {model: sale.order, id: sale_order_cl_3}:
-    partner_id: base.res_partner_3
-    partner_invoice_id: base.res_partner_address_25
-    partner_shipping_id: base.res_partner_address_25
-    pricelist_id: product.list0
-    order_policy: manual
--
-  !record {model: sale.order.line, id: sale_order_cl_3_line_1}:
-    order_id: sale_order_cl_3
-    product_id: product.product_product_27
-    product_uom_qty: 1
-    product_uom: 1
-    price_unit: 3645
-    name: 'Laptop Customized'
--
-  !record {model: sale.order.line, id: sale_order_cl_3_line_2}:
-    order_id: sale_order_cl_3
-    product_id: product.product_product_12
-    product_uom_qty: 1
-    product_uom: 1
-    price_unit: 12.50
-    name: 'Mouse, Wireless'
--
-  I cancel the first line
--
-   !python {model: sale.order.line, id: sale_order_cl_3_line_1}: |
-     self.button_cancel()
--
-  I confirm the sale order
--
-  !workflow {model: sale.order, action: order_confirm, ref: sale_order_cl_3}
--
-  I check that no procurement has been generated for the canceled line
--
-  !assert {model: sale.order.line, id: sale_order_cl_3_line_1, string: The canceled line should not have a procurement}:
-    - not procurement_ids
--
-  I check that we have only 1 stock move, for the not canceled line
--
-  !python {model: sale.order, id: sale_order_cl_3}: |
-    moves = self.picking_ids.mapped('move_lines')
-    assert len(moves) == 1, "We should have 1 move, got %s" % len(moves)
diff --git a/addons/sale_stock/test/sale_order_onchange.yml b/addons/sale_stock/test/sale_order_onchange.yml
deleted file mode 100644
index ef5c30015546..000000000000
--- a/addons/sale_stock/test/sale_order_onchange.yml
+++ /dev/null
@@ -1,33 +0,0 @@
--
-  Only sales manager Creates product so let's check with access rights of salemanager.
--
-  !context
-    uid: 'res_sale_stock_salesmanager'
--
-  In order to test the onchange of the Sale Order, I create a product
--
-  !record {model: product.product, id: product_onchange1}:
-    name: 'Devil Worship Book'
-    lst_price: 66.6
-    default_code: 'DWB00001'
--
-  In sale order to test process of onchange of Sale Order with access rights of saleman.
--
-  !context
-    uid: 'res_sale_stock_salesman'
--
-  Now i create a sale order that uses my new product
--
-  !record {model: sale.order, id: sale_order_onchange1}:
-    partner_id: base.res_partner_2
-    order_line:
-      - product_id: sale_stock.product_onchange1
-        product_uom_qty: 10
--
-  I verify that the onchange of product on sale order line was correctly triggered
--
-  !python {model: sale.order}: |
-    from openerp.tools import float_compare
-    order_line = self.browse(cr, uid, ref('sale_order_onchange1')).order_line
-    assert order_line[0].name == u'[DWB00001] Devil Worship Book', "The onchange function of product was not correctly triggered"
-    assert float_compare(order_line[0].price_unit, 66.6, precision_digits=2) == 0, "The onchange function of product was not correctly triggered"
diff --git a/addons/sale_stock/test/sale_stock_users.yml b/addons/sale_stock/test/sale_stock_users.yml
deleted file mode 100644
index 559929632c9b..000000000000
--- a/addons/sale_stock/test/sale_stock_users.yml
+++ /dev/null
@@ -1,57 +0,0 @@
--
-   Create a user as 'Stock Salesmanager'
--
-  !record {model: res.users, id: res_sale_stock_salesmanager, view: False}:
-    company_id: base.main_company
-    name: Stock Sales manager
-    login: ssm
-    email: ss_salesmanager@yourcompany.com
--
-  I added groups for Salesmanager.
--
-  !record {model: res.users, id: res_sale_stock_salesmanager}:
-    groups_id:
-      - base.group_sale_manager
--
-   Create a user as 'Stock Salesman'
--
-  !record {model: res.users, id: res_sale_stock_salesman, view: False}:
-    company_id: base.main_company
-    name: Stock Salesman
-    login: ssu
-    email: ss_salesman@yourcompany.com
--
-  I added groups for Stock Salesman.
--
-  !record {model: res.users, id: res_sale_stock_salesman}:
-    groups_id:
-      - base.group_sale_salesman_all_leads
-      - stock.group_stock_user
--
-   Create a user as 'Stock User'
--
-  !record {model: res.users, id: res_stock_user, view: False}:
-    company_id: base.main_company
-    name: Stock User
-    login: sau
-    email: stock_user@yourcompany.com
--
-  I added groups for Stock User.
--
-  !record {model: res.users, id: res_stock_user}:
-    groups_id:
-      - stock.group_stock_user
--
-   Create a user as 'Stock Manager'
--
-  !record {model: res.users, id: res_stock_manager, view: False}:
-    company_id: base.main_company
-    name: Stock Manager
-    login: sam
-    email: stock_manager@yourcompany.com
--
-  I added groups for Stock Manager.
--
-  !record {model: res.users, id: res_stock_manager}:
-    groups_id:
-      - stock.group_stock_manager
diff --git a/addons/sale_stock/tests/__init__.py b/addons/sale_stock/tests/__init__.py
new file mode 100644
index 000000000000..ebc608a933e1
--- /dev/null
+++ b/addons/sale_stock/tests/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import test_sale_stock
diff --git a/addons/sale_stock/tests/test_sale_stock.py b/addons/sale_stock/tests/test_sale_stock.py
new file mode 100644
index 000000000000..87dd85c6df37
--- /dev/null
+++ b/addons/sale_stock/tests/test_sale_stock.py
@@ -0,0 +1,174 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from openerp.addons.sale.tests.test_sale_common import TestSale
+
+
+class TestSaleStock(TestSale):
+    def test_00_sale_stock_invoice(self):
+        """
+        Test SO's changes when playing around with stock moves, quants, pack operations, pickings
+        and whatever other model there is in stock with "invoice on delivery" products
+        """
+        inv_obj = self.env['account.invoice']
+        self.so = self.env['sale.order'].create({
+            'partner_id': self.partner.id,
+            'partner_invoice_id': self.partner.id,
+            'partner_shipping_id': self.partner.id,
+            'order_line': [(0, 0, {'name': p.name, 'product_id': p.id, 'product_uom_qty': 2, 'product_uom': p.uom_id.id, 'price_unit': p.list_price}) for (_, p) in self.products.iteritems()],
+            'pricelist_id': self.env.ref('product.list0').id,
+            'picking_policy': 'direct',
+        })
+
+        # confirm our standard so, check the picking
+        self.so.action_confirm()
+        self.assertTrue(self.so.picking_ids, 'Sale Stock: no picking created for "invoice on delivery" stockable products')
+        # invoice on order
+        self.so.action_invoice_create()
+
+        # deliver partially, check the so's invoice_status and delivered quantities
+        self.assertEqual(self.so.invoice_status, 'no', 'Sale Stock: so invoice_status should be "nothing to invoice" after invoicing')
+        pick = self.so.picking_ids
+        pick.force_assign()
+        pick.pack_operation_product_ids.write({'qty_done': 1})
+        wiz_act = pick.do_new_transfer()
+        wiz = self.env[wiz_act['res_model']].browse(wiz_act['res_id'])
+        wiz.process()
+        self.assertEqual(self.so.invoice_status, 'to invoice', 'Sale Stock: so invoice_status should be "to invoice" after partial delivery')
+        del_qties = [sol.qty_delivered for sol in self.so.order_line]
+        del_qties_truth = [1.0 if sol.product_id.type in ['product', 'consu'] else 0.0 for sol in self.so.order_line]
+        self.assertEqual(del_qties, del_qties_truth, 'Sale Stock: delivered quantities are wrong after partial delivery')
+        # invoice on delivery: only stockable products
+        inv_id = self.so.action_invoice_create()
+        inv_1 = inv_obj.browse(inv_id)
+        self.assertTrue(all([il.product_id.invoice_policy == 'delivery' for il in inv_1.invoice_line_ids]),
+                        'Sale Stock: invoice should only contain "invoice on delivery" products')
+
+        # complete the delivery and check invoice_status again
+        self.assertEqual(self.so.invoice_status, 'no',
+                         'Sale Stock: so invoice_status should be "nothing to invoice" after partial delivery and invoicing')
+        self.assertEqual(len(self.so.picking_ids), 2, 'Sale Stock: number of pickings should be 2')
+        pick_2 = self.so.picking_ids[0]
+        pick_2.force_assign()
+        pick_2.pack_operation_product_ids.write({'qty_done': 1})
+        self.assertIsNone(pick_2.do_new_transfer(), 'Sale Stock: second picking should be final without need for a backorder')
+        self.assertEqual(self.so.invoice_status, 'to invoice', 'Sale Stock: so invoice_status should be "to invoice" after complete delivery')
+        del_qties = [sol.qty_delivered for sol in self.so.order_line]
+        del_qties_truth = [2.0 if sol.product_id.type in ['product', 'consu'] else 0.0 for sol in self.so.order_line]
+        self.assertEqual(del_qties, del_qties_truth, 'Sale Stock: delivered quantities are wrong after complete delivery')
+        # invoice on delivery
+        inv_id = self.so.action_invoice_create()
+        self.assertEqual(self.so.invoice_status, 'invoiced',
+                         'Sale Stock: so invoice_status should be "fully invoiced" after complete delivery and invoicing')
+
+    def test_01_sale_stock_order(self):
+        """
+        Test SO's changes when playing around with stock moves, quants, pack operations, pickings
+        and whatever other model there is in stock with "invoice on order" products
+        """
+        # let's cheat and put all our products to "invoice on order"
+        self.so = self.env['sale.order'].create({
+            'partner_id': self.partner.id,
+            'partner_invoice_id': self.partner.id,
+            'partner_shipping_id': self.partner.id,
+            'order_line': [(0, 0, {'name': p.name, 'product_id': p.id, 'product_uom_qty': 2, 'product_uom': p.uom_id.id, 'price_unit': p.list_price}) for (_, p) in self.products.iteritems()],
+            'pricelist_id': self.env.ref('product.list0').id,
+            'picking_policy': 'direct',
+        })
+        for sol in self.so.order_line:
+            sol.product_id.invoice_policy = 'order'
+        # confirm our standard so, check the picking
+        self.so.action_confirm()
+        self.assertTrue(self.so.picking_ids, 'Sale Stock: no picking created for "invoice on order" stockable products')
+        # let's do an invoice for a deposit of 5%
+        adv_wiz = self.env['sale.advance.payment.inv'].with_context(active_ids=[self.so.id]).create({
+            'advance_payment_method': 'percentage',
+            'amount': 5.0
+        })
+        act = adv_wiz.with_context(open_invoices=True).create_invoices()
+        inv = self.env['account.invoice'].browse(act['res_id'])
+        self.assertEqual(inv.amount_untaxed, self.so.amount_untaxed * 5.0 / 100.0, 'Sale Stock: deposit invoice is wrong')
+        self.assertEqual(self.so.invoice_status, 'to invoice', 'Sale Stock: so should be to invoice after invoicing deposit')
+        # invoice on order: everything should be invoiced
+        self.so.action_invoice_create()
+        self.assertEqual(self.so.invoice_status, 'invoiced', 'Sale Stock: so should be fully invoiced after second invoice')
+
+        # deliver, check the delivered quantities
+        pick = self.so.picking_ids
+        pick.force_assign()
+        pick.pack_operation_product_ids.write({'qty_done': 2})
+        self.assertIsNone(pick.do_new_transfer(), 'Sale Stock: complete delivery should not need a backorder')
+        del_qties = [sol.qty_delivered for sol in self.so.order_line]
+        del_qties_truth = [2.0 if sol.product_id.type in ['product', 'consu'] else 0.0 for sol in self.so.order_line]
+        self.assertEqual(del_qties, del_qties_truth, 'Sale Stock: delivered quantities are wrong after partial delivery')
+        # invoice on delivery: nothing to invoice
+        self.assertFalse(self.so.action_invoice_create(), 'Sale Stock: there should be nothing to invoice')
+
+    def test_02_sale_stock_return(self):
+        """
+        Test a SO with a product invoiced on delivery. Deliver and invoice the SO, then do a return
+        of the picking. Check that a refund invoice is well generated.
+        """
+        # intial so
+        self.partner = self.env.ref('base.res_partner_1')
+        self.product = self.env.ref('product.product_product_47')
+        so_vals = {
+            'partner_id': self.partner.id,
+            'partner_invoice_id': self.partner.id,
+            'partner_shipping_id': self.partner.id,
+            'order_line': [(0, 0, {
+                'name': self.product.name,
+                'product_id': self.product.id,
+                'product_uom_qty': 5.0,
+                'product_uom': self.product.uom_id.id,
+                'price_unit': self.product.list_price})],
+            'pricelist_id': self.env.ref('product.list0').id,
+        }
+        self.so = self.env['sale.order'].create(so_vals)
+
+        # confirm our standard so, check the picking
+        self.so.action_confirm()
+        self.assertTrue(self.so.picking_ids, 'Sale Stock: no picking created for "invoice on delivery" stockable products')
+
+        # invoice in on delivery, nothing should be invoiced
+        self.assertEqual(self.so.invoice_status, 'no', 'Sale Stock: so invoice_status should be "nothing to invoice"')
+
+        # deliver completely
+        pick = self.so.picking_ids
+        pick.force_assign()
+        pick.pack_operation_product_ids.write({'qty_done': 5})
+        pick.do_new_transfer()
+
+        # Check quantity delivered
+        del_qty = sum(sol.qty_delivered for sol in self.so.order_line)
+        self.assertEqual(del_qty, 5.0, 'Sale Stock: delivered quantity should be 5.0 after complete delivery')
+
+        # Check invoice
+        self.assertEqual(self.so.invoice_status, 'to invoice', 'Sale Stock: so invoice_status should be "to invoice" before invoicing')
+        inv_1_id = self.so.action_invoice_create()
+        self.assertEqual(self.so.invoice_status, 'invoiced', 'Sale Stock: so invoice_status should be "invoiced" after invoicing')
+        self.assertEqual(len(inv_1_id), 1, 'Sale Stock: only one invoice should be created')
+        self.inv_1 = self.env['account.invoice'].browse(inv_1_id)
+        self.assertEqual(self.inv_1.amount_untaxed, self.inv_1.amount_untaxed, 'Sale Stock: amount in SO and invoice should be the same')
+
+        # Create return picking
+        StockReturnPicking = self.env['stock.return.picking']
+        default_data = StockReturnPicking.with_context(active_ids=pick.ids, active_id=pick.ids[0]).default_get(['move_dest_exists', 'original_location_id', 'product_return_moves', 'parent_location_id', 'location_id'])
+        return_wiz = StockReturnPicking.with_context(active_ids=pick.ids, active_id=pick.ids[0]).create(default_data)
+        res = return_wiz.create_returns()
+        return_pick = self.env['stock.picking'].browse(res['res_id'])
+
+        # Validate picking
+        return_pick.force_assign()
+        return_pick.pack_operation_product_ids.write({'qty_done': 5})
+        return_pick.do_new_transfer()
+
+        # Check invoice
+        self.assertEqual(self.so.invoice_status, 'to invoice', 'Sale Stock: so invoice_status should be "to invoice" before invoicing')
+        # let's do an invoice with refunds
+        adv_wiz = self.env['sale.advance.payment.inv'].with_context(active_ids=[self.so.id]).create({
+            'advance_payment_method': 'all',
+        })
+        adv_wiz.with_context(open_invoices=True).create_invoices()
+        self.inv_2 = self.so.invoice_ids[1]
+        self.assertEqual(self.so.invoice_status, 'no', 'Sale Stock: so invoice_status should be "no" after invoicing the return')
+        self.assertEqual(self.inv_2.amount_untaxed, self.inv_2.amount_untaxed, 'Sale Stock: amount in SO and invoice should be the same')
-- 
GitLab