diff --git a/stock_move_location/__manifest__.py b/stock_move_location/__manifest__.py index 531a97ae9..29729a248 100644 --- a/stock_move_location/__manifest__.py +++ b/stock_move_location/__manifest__.py @@ -17,6 +17,7 @@ "category": "Stock", "data": [ 'data/stock_quant_view.xml', + 'views/stock_picking_type_views.xml', 'wizard/stock_move_location.xml', ], } diff --git a/stock_move_location/models/__init__.py b/stock_move_location/models/__init__.py index d8a43735d..58ff9b56b 100644 --- a/stock_move_location/models/__init__.py +++ b/stock_move_location/models/__init__.py @@ -2,3 +2,4 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from . import stock_move +from . import stock_picking_type diff --git a/stock_move_location/models/stock_picking_type.py b/stock_move_location/models/stock_picking_type.py new file mode 100644 index 000000000..9b77f490a --- /dev/null +++ b/stock_move_location/models/stock_picking_type.py @@ -0,0 +1,25 @@ +# Copyright 2019 Sergio Teruel +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import fields, models + + +class StockPickingType(models.Model): + _inherit = "stock.picking.type" + + show_move_onhand = fields.Boolean( + string='Show Move On hand stock', + help="Show a button 'Move On Hand' in the Inventory Dashboard " + "to initiate the process to move the products in stock " + "at the origin location.") + + def action_move_location(self): + action = self.env.ref( + 'stock_move_location.wiz_stock_move_location_action').read()[0] + action['context'] = { + 'default_origin_location_id': self.default_location_src_id.id, + 'default_destination_location_id': + self.default_location_dest_id.id, + 'default_picking_type_id': self.id, + 'default_edit_locations': False, + } + return action diff --git a/stock_move_location/readme/CONTRIBUTORS.rst b/stock_move_location/readme/CONTRIBUTORS.rst index b0de51f61..0e307c893 100644 --- a/stock_move_location/readme/CONTRIBUTORS.rst +++ b/stock_move_location/readme/CONTRIBUTORS.rst @@ -1,4 +1,6 @@ * Mathieu Vatel * Mykhailo Panarin * Sergio Teruel -* Joan Sisquella \ No newline at end of file +* Joan Sisquella +* Jordi Ballester Alomar +* Lois Rilo diff --git a/stock_move_location/readme/USAGE.rst b/stock_move_location/readme/USAGE.rst index 680d2db92..55d8c0b71 100644 --- a/stock_move_location/readme/USAGE.rst +++ b/stock_move_location/readme/USAGE.rst @@ -1,5 +1,5 @@ -* A new menuitem Stock > Move from location... opens a wizard - where 2 location ca be specified. +* A new menu item Stock > Move from location... opens a wizard + where 2 location can 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 @@ -16,3 +16,7 @@ If you want to transfer a full quant: opened. * Select the quants which you want move to another location + +If you go to the Inventory Dashboard you can see the button "Move from location" +in each of the picking types (only applicable to internal transfers). Press it +and you will be directed to the wizard. \ No newline at end of file diff --git a/stock_move_location/tests/test_common.py b/stock_move_location/tests/test_common.py index 04769281c..ee70c0e85 100644 --- a/stock_move_location/tests/test_common.py +++ b/stock_move_location/tests/test_common.py @@ -16,6 +16,12 @@ class TestsCommon(common.SavepointCase): cls.wizard_obj = cls.env["wiz.stock.move.location"] cls.quant_obj = cls.env["stock.quant"] + # Enable multi-locations: + wizard = cls.env['res.config.settings'].create({ + 'group_stock_multi_locations': True, + }) + wizard.execute() + cls.internal_loc_1 = cls.location_obj.create({ "name": "INT_1", "usage": "internal", diff --git a/stock_move_location/tests/test_move_location.py b/stock_move_location/tests/test_move_location.py index de1a04559..4c7f2bb60 100644 --- a/stock_move_location/tests/test_move_location.py +++ b/stock_move_location/tests/test_move_location.py @@ -100,13 +100,14 @@ class TestMoveLocation(TestsCommon): """Test planned transfer.""" wizard = self._create_wizard(self.internal_loc_1, self.internal_loc_2) wizard.onchange_origin_location() - wizard.with_context({'planned': True}).action_move_location() + wizard = wizard.with_context({'planned': True}) + wizard.action_move_location() picking = wizard.picking_id - self.assertEqual(picking.state, 'draft') + self.assertEqual(picking.state, 'assigned') self.assertEqual(len(picking.move_line_ids), 4) self.assertEqual( - sorted(picking.move_line_ids.mapped("qty_done")), - [0.0, 0.0, 0.0, 0.0], + sorted(picking.move_line_ids.mapped("product_uom_qty")), + [1, 1, 1, 123], ) def test_quant_transfer(self): @@ -129,3 +130,20 @@ class TestMoveLocation(TestsCommon): wizard.origin_location_id = self.internal_loc_2 wizard._onchange_destination_location_id() self.assertEqual(len(lines), 3) + + def test_readonly_location_computation(self): + """Test that origin_location_disable and destination_location_disable + are computed correctly.""" + wizard = self._create_wizard(self.internal_loc_1, self.internal_loc_2) + # locations are editable. + self.assertFalse(wizard.origin_location_disable) + self.assertFalse(wizard.destination_location_disable) + # Disable edit mode: + wizard.edit_locations = False + self.assertTrue(wizard.origin_location_disable) + self.assertTrue(wizard.destination_location_disable) + + def test_picking_type_action_dummy(self): + """Test that no error is raised from actions.""" + pick_type = self.env.ref("stock.picking_type_internal") + pick_type.action_move_location() diff --git a/stock_move_location/views/stock_picking_type_views.xml b/stock_move_location/views/stock_picking_type_views.xml new file mode 100644 index 000000000..a974b9d75 --- /dev/null +++ b/stock_move_location/views/stock_picking_type_views.xml @@ -0,0 +1,32 @@ + + + + + Operation Types + stock.picking.type + + + + + + + + + + stock.picking.type + + + + + + +
+ +
+
+
+
+ +
diff --git a/stock_move_location/wizard/stock_move_location.py b/stock_move_location/wizard/stock_move_location.py index 0bbea79e7..d7049fb63 100644 --- a/stock_move_location/wizard/stock_move_location.py +++ b/stock_move_location/wizard/stock_move_location.py @@ -11,12 +11,28 @@ class StockMoveLocationWizard(models.TransientModel): _name = "wiz.stock.move.location" _description = 'Wizard move location' + @api.multi + def _get_default_picking_type_id(self): + company_id = self.env.context.get('company_id') or \ + self.env.user.company_id.id + return self.env['stock.picking.type'].search( + [('code', '=', 'internal'), + ('warehouse_id.company_id', '=', company_id)], limit=1).id + + origin_location_disable = fields.Boolean( + compute="_compute_readonly_locations", + help="technical field to disable the edition of origin location." + ) origin_location_id = fields.Many2one( string='Origin Location', comodel_name='stock.location', required=True, domain=lambda self: self._get_locations_domain(), ) + destination_location_disable = fields.Boolean( + compute="_compute_readonly_locations", + help="technical field to disable the edition of destination location." + ) destination_location_id = fields.Many2one( string='Destination Location', comodel_name='stock.location', @@ -27,10 +43,25 @@ class StockMoveLocationWizard(models.TransientModel): string="Move Location lines", comodel_name="wiz.stock.move.location.line", ) + picking_type_id = fields.Many2one( + comodel_name='stock.picking.type', + default=_get_default_picking_type_id, + ) picking_id = fields.Many2one( string="Connected Picking", comodel_name="stock.picking", ) + edit_locations = fields.Boolean(string='Edit Locations', + default=True) + + @api.depends('edit_locations') + def _compute_readonly_locations(self): + for rec in self: + rec.origin_location_disable = self.env.context.get( + 'origin_location_disable', False) + if not rec.edit_locations: + rec.origin_location_disable = True + rec.destination_location_disable = True @api.model def default_get(self, fields): @@ -70,7 +101,7 @@ class StockMoveLocationWizard(models.TransientModel): def _create_picking(self): return self.env['stock.picking'].create({ - 'picking_type_id': self.env.ref('stock.picking_type_internal').id, + 'picking_type_id': self.picking_type_id.id, 'location_id': self.origin_location_id.id, 'location_dest_id': self.destination_location_id.id, }) @@ -120,8 +151,9 @@ class StockMoveLocationWizard(models.TransientModel): move = self.env["stock.move"].create( self._get_move_values(picking, lines), ) - for line in lines: - line.create_move_lines(picking, move) + if not self.env.context.get("planned"): + for line in lines: + line.create_move_lines(picking, move) return move @api.multi @@ -131,6 +163,9 @@ class StockMoveLocationWizard(models.TransientModel): self._create_moves(picking) if not self.env.context.get("planned"): picking.button_validate() + else: + picking.action_confirm() + picking.action_assign() self.picking_id = picking return self._get_picking_action(picking.id) @@ -166,12 +201,16 @@ class StockMoveLocationWizard(models.TransientModel): product_data = [] for group in self._get_group_quants(): product = product_obj.browse(group.get("product_id")).exists() + # Apply the putaway strategy + location_dest_id = \ + self.destination_location_id.get_putaway_strategy( + product).id or self.destination_location_id.id 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, + 'destination_location_id': location_dest_id, # cursor returns None instead of False 'lot_id': group.get("lot_id") or False, 'product_uom_id': product.uom_id.id, diff --git a/stock_move_location/wizard/stock_move_location.xml b/stock_move_location/wizard/stock_move_location.xml index 895ccb0db..1463380db 100755 --- a/stock_move_location/wizard/stock_move_location.xml +++ b/stock_move_location/wizard/stock_move_location.xml @@ -7,12 +7,24 @@
+
+
+ + +
+ + + - - + + + + - + @@ -23,6 +35,26 @@ + + + +
+
+ + + + + +
+
+
+ +
+
+
+
+
+