diff --git a/stock_move_location/README.rst b/stock_move_location/README.rst
new file mode 100644
index 000000000000..21cd7854d5e2
--- /dev/null
+++ b/stock_move_location/README.rst
@@ -0,0 +1,21 @@
+**This file is going to be generated by oca-gen-addon-readme.**
+
+*Manual changes will be overwritten.*
+
+Please provide content in the ``readme`` directory:
+
+* **DESCRIPTION.rst** (required)
+* INSTALL.rst (optional)
+* CONFIGURE.rst (optional)
+* **USAGE.rst** (optional, highly recommended)
+* DEVELOP.rst (optional)
+* ROADMAP.rst (optional)
+* HISTORY.rst (optional, recommended)
+* **CONTRIBUTORS.rst** (optional, highly recommended)
+* CREDITS.rst (optional)
+
+Content of this README will also be drawn from the addon manifest,
+from keys such as name, authors, maintainers, development_status,
+and license.
+
+A good, one sentence summary in the manifest is also highly recommended.
diff --git a/stock_move_location/__init__.py b/stock_move_location/__init__.py
new file mode 100644
index 000000000000..91ba63a47213
--- /dev/null
+++ b/stock_move_location/__init__.py
@@ -0,0 +1,6 @@
+# Copyright (C) 2011 Julius Network Solutions SARL <contact@julius.fr>
+# Copyright 2018 Camptocamp SA
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
+
+from . import wizard
+from . import models
diff --git a/stock_move_location/__manifest__.py b/stock_move_location/__manifest__.py
new file mode 100644
index 000000000000..d9b725fc5ba2
--- /dev/null
+++ b/stock_move_location/__manifest__.py
@@ -0,0 +1,21 @@
+# Copyright (C) 2011 Julius Network Solutions SARL <contact@julius.fr>
+# Copyright 2018 Camptocamp SA
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
+
+{
+    "name": "Move Stock Location",
+    "version": "11.0.1.0.0",
+    "author": "Julius Network Solutions, "
+              "Odoo Community Association (OCA)",
+    "summary": "This module allows to move all stock "
+               "in a stock location to an other one.",
+    "website": "https://github.com/OCA/stock-logistics-warehouse",
+    'license': 'AGPL-3',
+    "depends": [
+        "stock",
+    ],
+    "category": "Stock",
+    "data": [
+        'wizard/stock_move_location.xml',
+    ],
+}
diff --git a/stock_move_location/i18n/stock_move_location.pot b/stock_move_location/i18n/stock_move_location.pot
new file mode 100644
index 000000000000..ebbc50d73e90
--- /dev/null
+++ b/stock_move_location/i18n/stock_move_location.pot
@@ -0,0 +1,168 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+#	* stock_move_location
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 11.0+e\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2019-01-08 23:43+0000\n"
+"PO-Revision-Date: 2019-01-08 23:43+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: stock_move_location
+#: model:ir.ui.view,arch_db:stock_move_location.view_wiz_stock_move_location_form_stock_move_location
+msgid "Add all"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.ui.view,arch_db:stock_move_location.view_wiz_stock_move_location_form_stock_move_location
+msgid "Cancel"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.ui.view,arch_db:stock_move_location.view_wiz_stock_move_location_form_stock_move_location
+msgid "Clear all"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_create_uid
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_line_create_uid
+msgid "Created by"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_create_date
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_line_create_date
+msgid "Created on"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_destination_location_id
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_line_destination_location_id
+msgid "Destination Location"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_display_name
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_line_display_name
+msgid "Display Name"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_id
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_line_id
+msgid "ID"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.model,name:stock_move_location.model_stock_inventory
+msgid "Inventory"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.ui.view,arch_db:stock_move_location.view_wiz_stock_move_location_form_stock_move_location
+msgid "Inventory Details"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.model,name:stock_move_location.model_stock_inventory_line
+msgid "Inventory Line"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location___last_update
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_line___last_update
+msgid "Last Modified on"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_line_write_uid
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_write_uid
+msgid "Last Updated by"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_line_write_date
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_write_date
+msgid "Last Updated on"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_line_lot_id
+msgid "Lot/Serial Number"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_line_max_quantity
+msgid "Maximum available quantity"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.ui.view,arch_db:stock_move_location.view_wiz_stock_move_location_form_stock_move_location
+msgid "Move Location"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_stock_move_location_line_ids
+msgid "Move Location lines"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.actions.act_window,name:stock_move_location.wiz_stock_move_location_action
+#: model:ir.ui.menu,name:stock_move_location.menuitem_move_location
+msgid "Move from location..."
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_line_move_location_wizard_id
+msgid "Move location Wizard"
+msgstr ""
+
+#. module: stock_move_location
+#: code:addons/stock_move_location/wizard/stock_move_location_line.py:56
+#, python-format
+msgid "Move quantity can not exceed max quantity or be negative"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_line_origin_location_id
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_origin_location_id
+msgid "Origin Location"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_line_product_id
+msgid "Product"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_line_product_uom_id
+msgid "Product Unit of Measure"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.model.fields,field_description:stock_move_location.field_wiz_stock_move_location_line_move_quantity
+msgid "Quantity to move"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.ui.view,arch_db:stock_move_location.view_wiz_stock_move_location_form_stock_move_location
+msgid "UoM"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.model,name:stock_move_location.model_wiz_stock_move_location
+msgid "wiz.stock.move.location"
+msgstr ""
+
+#. module: stock_move_location
+#: model:ir.model,name:stock_move_location.model_wiz_stock_move_location_line
+msgid "wiz.stock.move.location.line"
+msgstr ""
+
diff --git a/stock_move_location/models/__init__.py b/stock_move_location/models/__init__.py
new file mode 100644
index 000000000000..d8a43735d3a3
--- /dev/null
+++ b/stock_move_location/models/__init__.py
@@ -0,0 +1,4 @@
+# Copyright 2019 Camptocamp SA
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
+
+from . import stock_move
diff --git a/stock_move_location/models/stock_move.py b/stock_move_location/models/stock_move.py
new file mode 100644
index 000000000000..b9c4883ea3f8
--- /dev/null
+++ b/stock_move_location/models/stock_move.py
@@ -0,0 +1,20 @@
+# Copyright 2019 Camptocamp SA
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
+
+from odoo import api, fields, models
+
+
+class StockMove(models.Model):
+    _inherit = "stock.move"
+
+    location_move = fields.Boolean(
+        string="Part of move location",
+        help="Wether this move is a part of stock_location moves",
+    )
+
+    @api.depends("location_move")
+    def _compute_show_details_visible(self):
+        super()._compute_show_details_visible()
+        for move in self:
+            if move.location_move:
+                move.show_details_visible = True
diff --git a/stock_move_location/readme/CONTRIBUTORS.rst b/stock_move_location/readme/CONTRIBUTORS.rst
new file mode 100644
index 000000000000..012a547fecf5
--- /dev/null
+++ b/stock_move_location/readme/CONTRIBUTORS.rst
@@ -0,0 +1,2 @@
+* Mathieu Vatel <mathieu@julius.fr>
+* Mykhailo Panarin <m.panarin@mobilunity.com>
diff --git a/stock_move_location/readme/DESCRIPTION.rst b/stock_move_location/readme/DESCRIPTION.rst
new file mode 100644
index 000000000000..7b0eeb0eedb5
--- /dev/null
+++ b/stock_move_location/readme/DESCRIPTION.rst
@@ -0,0 +1 @@
+This module allows to move entire location of products from one place to another
diff --git a/stock_move_location/readme/USAGE.rst b/stock_move_location/readme/USAGE.rst
new file mode 100644
index 000000000000..60efc93b1404
--- /dev/null
+++ b/stock_move_location/readme/USAGE.rst
@@ -0,0 +1,10 @@
+* A new menuitem Stock > Move from location... opens a wizard
+  where 2 location ca be specified.
+* Select origin and destination locations and press "IMMEDIATE TRANSFER" or "PLANNED TRANSFER"
+* Press `ADD ALL` button to add all products available
+* Those lines can be edited. Move quantity can't be more than a max available quantity
+* Move doesn't care about the reservations and will move stuff anyway
+* If during you operation with the wizard the real quantity will change
+  it will move only the available quantity at the button press
+* Products will be moved and a form view of picking that did that will show up
+* If "PLANNED TRANSFER" is used - the picking won't be validated automatically
diff --git a/stock_move_location/tests/__init__.py b/stock_move_location/tests/__init__.py
new file mode 100644
index 000000000000..00527a8b0772
--- /dev/null
+++ b/stock_move_location/tests/__init__.py
@@ -0,0 +1,6 @@
+# Copyright (C) 2011 Julius Network Solutions SARL <contact@julius.fr>
+# Copyright 2018 Camptocamp SA
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
+
+from . import test_common
+from . import test_move_location
diff --git a/stock_move_location/tests/test_common.py b/stock_move_location/tests/test_common.py
new file mode 100644
index 000000000000..bb158ce15f5c
--- /dev/null
+++ b/stock_move_location/tests/test_common.py
@@ -0,0 +1,93 @@
+# Copyright (C) 2011 Julius Network Solutions SARL <contact@julius.fr>
+# Copyright 2018 Camptocamp SA
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
+
+from odoo.tests import common
+
+
+class TestsCommon(common.SavepointCase):
+
+    @classmethod
+    def setUpClass(cls):
+        super().setUpClass()
+        cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
+        cls.location_obj = cls.env["stock.location"]
+        product_obj = cls.env["product.product"]
+        cls.wizard_obj = cls.env["wiz.stock.move.location"]
+        cls.quant_obj = cls.env["stock.quant"]
+
+        cls.internal_loc_1 = cls.location_obj.create({
+            "name": "INT_1",
+            "usage": "internal",
+            "active": True,
+        })
+        cls.internal_loc_2 = cls.location_obj.create({
+            "name": "INT_2",
+            "usage": "internal",
+            "active": True,
+        })
+        cls.uom_unit = cls.env.ref('product.product_uom_unit')
+        cls.product_no_lots = product_obj.create({
+            "name": "Pineapple",
+            "type": "product",
+            "tracking": "none",
+            'categ_id': cls.env.ref('product.product_category_all').id,
+        })
+        cls.product_lots = product_obj.create({
+            "name": "Pineapple",
+            "type": "product",
+            "tracking": "lot",
+            'categ_id': cls.env.ref('product.product_category_all').id,
+        })
+        cls.lot1 = cls.env['stock.production.lot'].create({
+            'product_id': cls.product_lots.id,
+        })
+        cls.lot2 = cls.env['stock.production.lot'].create({
+            'product_id': cls.product_lots.id,
+        })
+        cls.lot3 = cls.env['stock.production.lot'].create({
+            'product_id': cls.product_lots.id,
+        })
+
+    def setup_product_amounts(self):
+        self.set_product_amount(
+            self.product_no_lots,
+            self.internal_loc_1,
+            123,
+        )
+        self.set_product_amount(
+            self.product_lots,
+            self.internal_loc_1,
+            1,
+            lot_id=self.lot1,
+        )
+        self.set_product_amount(
+            self.product_lots,
+            self.internal_loc_1,
+            1,
+            lot_id=self.lot2,
+        )
+        self.set_product_amount(
+            self.product_lots,
+            self.internal_loc_1,
+            1,
+            lot_id=self.lot3,
+        )
+
+    def set_product_amount(self, product, location, amount, lot_id=None):
+        self.env['stock.quant']._update_available_quantity(
+            product,
+            location,
+            amount,
+            lot_id=lot_id,
+        )
+
+    def check_product_amount(self, product, location, amount, lot_id=None):
+        self.assertEqual(
+            self.env['stock.quant']._get_available_quantity(
+                product,
+                location,
+                lot_id=lot_id,
+            ),
+            amount,
+        )
diff --git a/stock_move_location/tests/test_move_location.py b/stock_move_location/tests/test_move_location.py
new file mode 100644
index 000000000000..2dec616e10d3
--- /dev/null
+++ b/stock_move_location/tests/test_move_location.py
@@ -0,0 +1,111 @@
+# Copyright (C) 2011 Julius Network Solutions SARL <contact@julius.fr>
+# Copyright 2018 Camptocamp SA
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
+
+from .test_common import TestsCommon
+from odoo.exceptions import ValidationError
+
+
+class TestMoveLocation(TestsCommon):
+
+    def _create_wizard(self, origin_location, destination_location):
+        return self.wizard_obj.create({
+            "origin_location_id": origin_location.id,
+            "destination_location_id": destination_location.id,
+        })
+
+    def test_move_location_wizard(self):
+        """Test a simple move.
+        """
+        self.setup_product_amounts()
+        wizard = self._create_wizard(self.internal_loc_1, self.internal_loc_2)
+        wizard.add_lines()
+        wizard.action_move_location()
+        self.check_product_amount(
+            self.product_no_lots, self.internal_loc_1, 0,
+        )
+        self.check_product_amount(
+            self.product_lots, self.internal_loc_1, 0, self.lot1,
+        )
+        self.check_product_amount(
+            self.product_lots, self.internal_loc_1, 0, self.lot1,
+        )
+        self.check_product_amount(
+            self.product_lots, self.internal_loc_1, 0, self.lot1,
+        )
+        self.check_product_amount(
+            self.product_no_lots, self.internal_loc_2, 123,
+        )
+        self.check_product_amount(
+            self.product_lots, self.internal_loc_2, 1, self.lot1,
+        )
+        self.check_product_amount(
+            self.product_lots, self.internal_loc_2, 1, self.lot1,
+        )
+        self.check_product_amount(
+            self.product_lots, self.internal_loc_2, 1, self.lot1,
+        )
+
+    def test_move_location_wizard_amount(self):
+        """Can't move more than exists
+        """
+        self.setup_product_amounts()
+        wizard = self._create_wizard(self.internal_loc_1, self.internal_loc_2)
+        wizard.add_lines()
+        with self.assertRaises(ValidationError):
+            wizard.stock_move_location_line_ids[0].move_quantity += 1
+
+    def test_move_location_wizard_ignore_reserved(self):
+        """Can't move more than exists
+        """
+        self.setup_product_amounts()
+        wizard = self._create_wizard(self.internal_loc_1, self.internal_loc_2)
+        wizard.add_lines()
+        # reserve some quants
+        self.quant_obj._update_reserved_quantity(
+            self.product_no_lots,
+            self.internal_loc_1,
+            50,
+        )
+        self.quant_obj._update_reserved_quantity(
+            self.product_lots,
+            self.internal_loc_1,
+            1,
+            lot_id=self.lot1,
+        )
+        # doesn't care about reservations, everything is moved
+        wizard.action_move_location()
+        self.check_product_amount(
+            self.product_no_lots, self.internal_loc_1, 0,
+        )
+        self.check_product_amount(
+            self.product_no_lots, self.internal_loc_2, 123,
+        )
+        self.check_product_amount(
+            self.product_lots, self.internal_loc_2, 1, self.lot1,
+        )
+
+    def test_wizard_clear_lines(self):
+        """Test lines getting cleared properly
+        """
+        self.setup_product_amounts()
+        wizard = self._create_wizard(self.internal_loc_1, self.internal_loc_2)
+        wizard.add_lines()
+        self.assertEqual(len(wizard.stock_move_location_line_ids), 4)
+        wizard._onchange_locations()
+        self.assertEqual(len(wizard.stock_move_location_line_ids), 0)
+
+    def test_planned_transfer(self):
+        """Test planned transfer
+        """
+        self.setup_product_amounts()
+        wizard = self._create_wizard(self.internal_loc_1, self.internal_loc_2)
+        wizard.add_lines()
+        wizard.with_context({'planned': True}).action_move_location()
+        picking = wizard.picking_id
+        self.assertEqual(picking.state, 'draft')
+        self.assertEqual(len(picking.move_line_ids), 4)
+        self.assertEqual(
+            sorted(picking.move_line_ids.mapped("qty_done")),
+            [1, 1, 1, 123],
+        )
diff --git a/stock_move_location/wizard/__init__.py b/stock_move_location/wizard/__init__.py
new file mode 100644
index 000000000000..d9fdbf21cacb
--- /dev/null
+++ b/stock_move_location/wizard/__init__.py
@@ -0,0 +1,6 @@
+# Copyright (C) 2011 Julius Network Solutions SARL <contact@julius.fr>
+# Copyright 2018 Camptocamp SA
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
+
+from . import stock_move_location
+from . import stock_move_location_line
diff --git a/stock_move_location/wizard/stock_move_location.py b/stock_move_location/wizard/stock_move_location.py
new file mode 100644
index 000000000000..65c39134bc45
--- /dev/null
+++ b/stock_move_location/wizard/stock_move_location.py
@@ -0,0 +1,197 @@
+# Copyright (C) 2011 Julius Network Solutions SARL <contact@julius.fr>
+# Copyright 2018 Camptocamp SA
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
+
+from itertools import groupby
+
+from odoo import api, fields, models
+
+
+class StockMoveLocationWizard(models.TransientModel):
+    _name = "wiz.stock.move.location"
+
+    origin_location_id = fields.Many2one(
+        string='Origin Location',
+        comodel_name='stock.location',
+        required=True,
+        domain=lambda self: self._get_locations_domain(),
+    )
+    destination_location_id = fields.Many2one(
+        string='Destination Location',
+        comodel_name='stock.location',
+        required=True,
+        domain=lambda self: self._get_locations_domain(),
+    )
+    stock_move_location_line_ids = fields.One2many(
+        string="Move Location lines",
+        comodel_name="wiz.stock.move.location.line",
+        inverse_name="move_location_wizard_id",
+    )
+    picking_id = fields.Many2one(
+        string="Connected Picking",
+        comodel_name="stock.picking",
+    )
+
+    @api.onchange('origin_location_id', 'destination_location_id')
+    def _onchange_locations(self):
+        self._clear_lines()
+
+    @api.onchange("stock_move_location_line_ids")
+    def _onchange_stock_move_location_line_ids(self):
+        lines_to_update = self.stock_move_location_line_ids.filtered(
+            lambda x: x.custom is True and
+            not all([x.origin_location_id, x.destination_location_id])
+        )
+        lines_to_update.update({
+            "origin_location_id": self.origin_location_id,
+            "destination_location_id": self.destination_location_id,
+        })
+        # for an easier extension of this function
+        return lines_to_update
+
+    def _clear_lines(self):
+        origin = self.origin_location_id
+        destination = self.destination_location_id
+        # there is `invalidate_cache` call inside the unlink
+        # which will clear the wizard - not cool.
+        # we have to keep the values somehow
+        self.stock_move_location_line_ids.unlink()
+        self.origin_location_id = origin
+        self.destination_location_id = destination
+
+    def _get_locations_domain(self):
+        return [('usage', '=', 'internal')]
+
+    def _create_picking(self):
+        return self.env['stock.picking'].create({
+            'picking_type_id': self.env.ref('stock.picking_type_internal').id,
+            'location_id': self.origin_location_id.id,
+            'location_dest_id': self.destination_location_id.id,
+        })
+
+    @api.multi
+    def group_lines(self):
+        sorted_lines = sorted(
+            self.stock_move_location_line_ids,
+            key=lambda x: x.product_id,
+        )
+        groups = groupby(sorted_lines, key=lambda x: x.product_id)
+        groups_dict = {}
+        for prod, lines in groups:
+            groups_dict[prod.id] = list(lines)
+        return groups_dict
+
+    @api.multi
+    def _create_moves(self, picking):
+        self.ensure_one()
+        groups = self.group_lines()
+        moves = self.env["stock.move"]
+        for group, lines in groups.items():
+            move = self._create_move(picking, lines)
+            moves |= move
+        return moves
+
+    def _get_move_values(self, picking, lines):
+        # locations are same for the products
+        location_from_id = lines[0].origin_location_id.id
+        location_to_id = lines[0].destination_location_id.id
+        product_id = lines[0].product_id.id
+        product_uom_id = lines[0].product_uom_id.id
+        qty = sum([x.move_quantity for x in lines])
+        return {
+            "name": "test",
+            "location_id": location_from_id,
+            "location_dest_id": location_to_id,
+            "product_id": product_id,
+            "product_uom": product_uom_id,
+            "product_uom_qty": qty,
+            "picking_id": picking.id,
+            "location_move": True,
+        }
+
+    @api.multi
+    def _create_move(self, picking, lines):
+        self.ensure_one()
+        move = self.env["stock.move"].create(
+            self._get_move_values(picking, lines),
+        )
+        for line in lines:
+            line.create_move_lines(picking, move)
+        return move
+
+    @api.multi
+    def action_move_location(self):
+        self.ensure_one()
+        picking = self._create_picking()
+        self._create_moves(picking)
+        if not self.env.context.get("planned"):
+            picking.action_confirm()
+            picking.action_assign()
+            picking.button_validate()
+        self.picking_id = picking
+        return self._get_picking_action(picking.id)
+
+    def _get_picking_action(self, pickinig_id):
+        action = self.env.ref("stock.action_picking_tree_all").read()[0]
+        form_view = self.env.ref("stock.view_picking_form").id
+        action.update({
+            "view_mode": "form",
+            "views": [(form_view, "form")],
+            "res_id": pickinig_id,
+        })
+        return action
+
+    def _get_group_quants_sql(self):
+        location_id = self.origin_location_id.id
+        company = self.env['res.company']._company_default_get(
+            'stock.inventory',
+        )
+        return """
+        SELECT product_id, lot_id, SUM(quantity)
+        FROM stock_quant
+        WHERE location_id = {location_id} AND company_id = {company_id}
+        GROUP BY product_id, lot_id
+        """.format(
+            location_id=location_id,
+            company_id=company.id,
+        )
+
+    def _get_stock_move_location_lines_values(self):
+        product_obj = self.env['product.product']
+
+        # Using sql as search_group doesn't support aggregation functions
+        # leading to overhead in queries to DB
+        self.env.cr.execute(self._get_group_quants_sql())
+        product_data = []
+        for group in self.env.cr.dictfetchall():
+            product = product_obj.browse(group.get("product_id")).exists()
+            product_data.append({
+                'product_id': product.id,
+                'move_quantity': group.get("sum"),
+                'max_quantity': group.get("sum"),
+                'origin_location_id': self.origin_location_id.id,
+                'destination_location_id': self.destination_location_id.id,
+                # cursor returns None instead of False
+                'lot_id': group.get("lot_id") or False,
+                'product_uom_id': product.uom_id.id,
+                'move_location_wizard_id': self.id,
+                'custom': False,
+            })
+        return product_data
+
+    def add_lines(self):
+        self.ensure_one()
+        if not self.stock_move_location_line_ids:
+            for line_val in self._get_stock_move_location_lines_values():
+                if line_val.get('max_quantity') <= 0:
+                    continue
+                self.env["wiz.stock.move.location.line"].create(line_val)
+        return {
+            "type": "ir.actions.do_nothing",
+        }
+
+    def clear_lines(self):
+        self._clear_lines()
+        return {
+            "type": "ir.action.do_nothing",
+        }
diff --git a/stock_move_location/wizard/stock_move_location.xml b/stock_move_location/wizard/stock_move_location.xml
new file mode 100755
index 000000000000..6ae9831ed44b
--- /dev/null
+++ b/stock_move_location/wizard/stock_move_location.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+
+  <record id="view_wiz_stock_move_location_form_stock_move_location" model="ir.ui.view">
+    <field name="name">wiz.stock.move.location.form.stock_move_location</field>
+    <field name="model">wiz.stock.move.location</field>
+    <field name="arch" type="xml">
+      <form>
+        <sheet>
+          <group name="main">
+            <field name="origin_location_id"/>
+            <field name="destination_location_id"/>
+          </group>
+          <group name="button">
+            <button name="add_lines" string="Add all" type="object" class="btn-primary"/>
+            <button name="clear_lines" string="Clear all" type="object" class="btn-primary"/>
+          </group>
+          <group name="lines">
+            <field name="stock_move_location_line_ids" nolabel="1" >
+              <tree string="Inventory Details" editable="bottom" decoration-info="move_quantity != max_quantity" decoration-danger="(move_quantity &lt; 0) or (move_quantity > max_quantity)">
+                  <field name="product_id"  domain="[('type','=','product')]"/>
+                  <field name="product_uom_id" string="UoM" groups="product.group_uom"/>
+                  <field name="origin_location_id" readonly="1" />
+                  <field name="destination_location_id" readonly="1" />
+                  <field name="lot_id" domain="[('product_id', '=', product_id)]" context="{'default_product_id': product_id}"  groups="stock.group_production_lot" options="{'no_create': True}"/>
+                  <field name="move_quantity"/>
+                  <field name="custom" invisible="1" />
+                  <field name="max_quantity" attrs="{'readonly': [('custom', '!=', True)]}" />
+              </tree>
+            </field>
+          </group>
+          <footer>
+            <button name="action_move_location" string="Immediate Transfer" type="object" class="btn-primary"/>
+            <button name="action_move_location" string="Planned Transfer" type="object" class="btn-primary" context="{'planned': True}"/>
+            <button special="cancel" string="Cancel" class="btn-default"/>
+          </footer>
+        </sheet>
+      </form>
+    </field>
+  </record>
+
+  <record id="wiz_stock_move_location_action" model="ir.actions.act_window">
+    <field name="name">Move from location...</field>
+    <field name="res_model">wiz.stock.move.location</field>
+    <field name="view_mode">form</field>
+    <field name="target">new</field>
+  </record>
+
+  <menuitem
+      id="menuitem_move_location"
+      string="Move from location..."
+      parent="stock.menu_stock_warehouse_mgmt"
+      action="wiz_stock_move_location_action"
+      sequence="99"/>
+
+</odoo>
diff --git a/stock_move_location/wizard/stock_move_location_line.py b/stock_move_location/wizard/stock_move_location_line.py
new file mode 100644
index 000000000000..02abadacced3
--- /dev/null
+++ b/stock_move_location/wizard/stock_move_location_line.py
@@ -0,0 +1,150 @@
+# Copyright (C) 2011 Julius Network Solutions SARL <contact@julius.fr>
+# Copyright 2018 Camptocamp SA
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
+
+from odoo import _, api, fields, models
+from odoo.addons import decimal_precision as dp
+from odoo.exceptions import ValidationError
+from odoo.tools import float_compare
+
+
+class StockMoveLocationWizardLine(models.TransientModel):
+    _name = "wiz.stock.move.location.line"
+
+    move_location_wizard_id = fields.Many2one(
+        string="Move location Wizard",
+        comodel_name="wiz.stock.move.location",
+        ondelete="cascade",
+        required=True,
+    )
+    product_id = fields.Many2one(
+        string="Product",
+        comodel_name="product.product",
+        required=True,
+    )
+    origin_location_id = fields.Many2one(
+        string='Origin Location',
+        comodel_name='stock.location',
+    )
+    destination_location_id = fields.Many2one(
+        string='Destination Location',
+        comodel_name='stock.location',
+    )
+    product_uom_id = fields.Many2one(
+        string='Product Unit of Measure',
+        comodel_name='product.uom',
+    )
+    lot_id = fields.Many2one(
+        string='Lot/Serial Number',
+        comodel_name='stock.production.lot',
+        domain="[('product_id','=',product_id)]"
+    )
+    move_quantity = fields.Float(
+        string="Quantity to move",
+        digits=dp.get_precision('Product Unit of Measure'),
+    )
+    max_quantity = fields.Float(
+        string="Maximum available quantity",
+        digits=dp.get_precision('Product Unit of Measure'),
+    )
+    custom = fields.Boolean(
+        string="Custom line",
+        default=True,
+    )
+
+    @api.model
+    def get_rounding(self):
+        return self.env.ref("product.decimal_product_uom").digits or 3
+
+    @api.constrains("max_quantity", "move_quantity")
+    def _constraint_max_move_quantity(self):
+        for record in self:
+            if (float_compare(
+                    record.move_quantity,
+                    record.max_quantity, self.get_rounding()) == 1 or
+                    float_compare(record.move_quantity, 0.0,
+                                  self.get_rounding()) == -1):
+                raise ValidationError(_(
+                    "Move quantity can not exceed max quantity or be negative"
+                ))
+
+    def create_move_lines(self, picking, move):
+        for line in self:
+            values = line._get_move_line_values(picking, move)
+            if values.get("qty_done") <= 0:
+                continue
+            self.env["stock.move.line"].create(
+                values
+            )
+        return True
+
+    @api.multi
+    def _get_move_line_values(self, picking, move):
+        self.ensure_one()
+        return {
+            "product_id": self.product_id.id,
+            "lot_id": self.lot_id.id,
+            "location_id": self.origin_location_id.id,
+            "location_dest_id": self.destination_location_id.id,
+            "qty_done": self._get_available_quantity(),
+            "product_uom_id": self.product_uom_id.id,
+            "picking_id": picking.id,
+            "move_id": move.id,
+        }
+
+    def _get_available_quantity(self):
+        """We check here if the actual amount changed in the stock.
+
+        We don't care about the reservations but we do care about not moving
+        more than exists."""
+        self.ensure_one()
+        if not self.product_id:
+            return 0
+        if self.env.context.get("planned"):
+            # for planned transfer we don't care about the amounts at all
+            return self.move_quantity
+        # switched to sql here to improve performance and lower db queries
+        self.env.cr.execute(self._get_specific_quants_sql())
+        available_qty = self.env.cr.fetchone()
+        if not available_qty:
+            # if it is immediate transfer and product doesn't exist in that
+            # location -> make the transfer of 0.
+            return 0
+        available_qty = available_qty[0]
+        if float_compare(
+                available_qty,
+                self.move_quantity, self.get_rounding()) == -1:
+            return available_qty
+        return self.move_quantity
+
+    def _get_specific_quants_sql(self):
+        self.ensure_one()
+        lot = "AND lot_id = {}".format(self.lot_id.id)
+        if not self.lot_id:
+            lot = "AND lot_id is null"
+        return """
+        SELECT sum(quantity)
+        FROM stock_quant
+        WHERE location_id = {location}
+        {lot}
+        AND product_id = {product}
+        GROUP BY location_id, product_id, lot_id
+        """.format(
+            location=self.origin_location_id.id,
+            product=self.product_id.id,
+            lot=lot,
+        )
+
+    @api.model
+    def create(self, vals):
+        res = super().create(vals)
+        # update of wizard lines is extremely buggy
+        # so i have to handle this additionally in create
+        if not all([res.origin_location_id, res.destination_location_id]):
+            or_loc_id = res.move_location_wizard_id.origin_location_id.id
+            des_loc_id = res.move_location_wizard_id.destination_location_id.id
+            res.write({
+                "origin_location_id": or_loc_id,
+                "destination_location_id": des_loc_id,
+            })
+        return res