diff --git a/stock_picking_variant_mgmt/README.rst b/stock_picking_variant_mgmt/README.rst new file mode 100644 index 000000000..53ae43d7f --- /dev/null +++ b/stock_picking_variant_mgmt/README.rst @@ -0,0 +1,90 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +================================================= +Handle easily multiple variants on Stock Pickings +================================================= + +This module allows to add/modify of all the variants of a product in a direct +screen without the need of handling them one by one. + +It also adds a convenient way of handling the transfer of the products in a +2D matrix with all the values of the first attribute in columns, and the +rest of the combinations in rows. + +Configuration +============= + +#. Configure your user to have any permission from "Inventory" group. +#. Create a product with 2 attributes and several values. + +Usage +===== + +#. Go to Inventory > Dashboard. +#. Create a new picking from one of the existing picking types. +#. Press "Add variants" button located in the upper right corner of the + "Initial Demand" tab. +#. A new screen will appear allowing you to select the products that have + variants. +#. Once you select the product, a 2D matrix will appear with the first + attribute values as columns and the second one (if any) as rows. +#. If there are already order lines for the product variants, the current + quantity will be pre-filled in the matrix. +#. Change the quantities for the variant you want and click on "Transfer to + picking" +#. Move lines for the variants will be created/removed to comply with the + input you have done. + +As extra feature for saving steps, there's also a button on each existing line +that corresponds to a variant that opens the dialog directly with the product +selected. + +You are also able to manage variants on 1 dimension in the transfer: + +#. Go to the "Operations" page. +#. Press on "Manage Variants Transfer" button in the upper right corner of the + tab. +#. Change the quantities to transfer. +#. Click on "Transfer to picking" button. + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/137/9.0 + +Known issues / Roadmap +====================== + +* The inline button for modifying quantities for an existing line won't + work correctly until these 2 PRs are merged in Odoo: + + * https://github.com/odoo/odoo/pull/13557 + * https://github.com/odoo/odoo/pull/13635 + + The patches are already integrated on OCB. + +* Make this work with product with more than 1 attribute. + +Credits +======= + +Contributors +------------ + +* Pedro M. Baeza + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/stock_picking_variant_mgmt/__init__.py b/stock_picking_variant_mgmt/__init__.py new file mode 100644 index 000000000..2eb723313 --- /dev/null +++ b/stock_picking_variant_mgmt/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models +from . import wizard diff --git a/stock_picking_variant_mgmt/__openerp__.py b/stock_picking_variant_mgmt/__openerp__.py new file mode 100644 index 000000000..9d0c0f1e3 --- /dev/null +++ b/stock_picking_variant_mgmt/__openerp__.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Pedro M. Baeza +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + 'name': 'Handle easily multiple variants on Stock Pickings', + 'summary': 'Handle the addition/removal of multiple variants and the ' + 'quantities transferred in the Pickings.', + 'version': '9.0.1.0.0', + 'author': 'Tecnativa,' + 'Odoo Community Association (OCA)', + 'category': 'Inventory, Logistics, Warehousing', + 'license': 'AGPL-3', + 'website': 'https://www.tecnativa.com', + 'depends': [ + 'stock', + 'web_widget_x2many_2d_matrix', + ], + 'demo': [], + 'data': [ + 'wizard/stock_manage_variant_view.xml', + 'wizard/stock_transfer_manage_variant_view.xml', + 'views/stock_picking_view.xml', + ], + 'installable': True, +} diff --git a/stock_picking_variant_mgmt/models/__init__.py b/stock_picking_variant_mgmt/models/__init__.py new file mode 100644 index 000000000..e8798b9ce --- /dev/null +++ b/stock_picking_variant_mgmt/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import stock_move diff --git a/stock_picking_variant_mgmt/models/stock_move.py b/stock_picking_variant_mgmt/models/stock_move.py new file mode 100644 index 000000000..943651b44 --- /dev/null +++ b/stock_picking_variant_mgmt/models/stock_move.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Pedro M. Baeza +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import models, fields + + +class StockMove(models.Model): + _inherit = 'stock.move' + + product_attribute_value_ids = fields.Many2many( + comodel_name='product.attribute.value', + related="product_id.attribute_value_ids", + readonly=True, + ) diff --git a/stock_picking_variant_mgmt/static/description/icon.png b/stock_picking_variant_mgmt/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/stock_picking_variant_mgmt/static/description/icon.png differ diff --git a/stock_picking_variant_mgmt/tests/__init__.py b/stock_picking_variant_mgmt/tests/__init__.py new file mode 100644 index 000000000..c8b913281 --- /dev/null +++ b/stock_picking_variant_mgmt/tests/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_stock_picking_variant_mgmt diff --git a/stock_picking_variant_mgmt/tests/test_stock_picking_variant_mgmt.py b/stock_picking_variant_mgmt/tests/test_stock_picking_variant_mgmt.py new file mode 100644 index 000000000..a489082eb --- /dev/null +++ b/stock_picking_variant_mgmt/tests/test_stock_picking_variant_mgmt.py @@ -0,0 +1,161 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Pedro M. Baeza +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp.tests import common + + +@common.at_install(False) +@common.post_install(True) +class TestStockPickingVariantMgmt(common.SavepointCase): + @classmethod + def setUpClass(cls): + super(TestStockPickingVariantMgmt, cls).setUpClass() + cls.partner = cls.env['res.partner'].create({'name': 'Test partner'}) + cls.attribute1 = cls.env['product.attribute'].create({ + 'name': 'Test Attribute 1', + 'value_ids': [ + (0, 0, {'name': 'Value 1'}), + (0, 0, {'name': 'Value 2'}), + (0, 0, {'name': 'Value 3'}), + (0, 0, {'name': 'Value 4'}), + ], + }) + cls.product_tmpl = cls.env['product.template'].create({ + 'name': 'Test template', + 'attribute_line_ids': [ + (0, 0, { + 'attribute_id': cls.attribute1.id, + 'value_ids': [(6, 0, cls.attribute1.value_ids.ids)], + }), + ], + }) + assert len(cls.product_tmpl.product_variant_ids) == 4 + cls.warehouse = cls.env['stock.warehouse'].search([], limit=1) + cls.picking_type = cls.env['stock.picking.type'].search([ + ('warehouse_id', '=', cls.warehouse.id), + ('code', '=', 'incoming'), + ], limit=1) + cls.picking = cls.env['stock.picking'].create({ + 'partner_id': cls.partner.id, + 'picking_type_id': cls.picking_type.id, + 'location_id': cls.partner.property_stock_supplier.id, + 'location_dest_id': cls.picking_type.default_location_dest_id.id, + }) + cls.Move = cls.env['stock.move'] + cls.product1 = cls.product_tmpl.product_variant_ids[0] + move_vals = cls.Move.onchange_product_id( + prod_id=cls.product1.id, + loc_id=cls.picking.location_id.id, + loc_dest_id=cls.picking.location_dest_id.id, + partner_id=cls.picking.partner_id.id, + ).get('value', {}) + move_vals.update({ + 'product_id': cls.product1.id, + 'picking_id': cls.picking.id, + 'product_uom_qty': 1, + }) + cls.move1 = cls.Move.create(move_vals) + cls.product2 = cls.product_tmpl.product_variant_ids[1] + move_vals = cls.Move.onchange_product_id( + prod_id=cls.product2.id, + loc_id=cls.picking.location_id.id, + loc_dest_id=cls.picking.location_dest_id.id, + partner_id=cls.picking.partner_id.id, + ).get('value', {}) + move_vals.update({ + 'product_id': cls.product2.id, + 'picking_id': cls.picking.id, + 'product_uom_qty': 2, + }) + cls.move2 = cls.Move.create(move_vals) + cls.Wizard = cls.env['stock.manage.variant'].with_context( + active_ids=cls.picking.ids, active_id=cls.picking.id, + active_model=cls.picking._name, + ) + cls.product_single = cls.env['product.product'].create({ + 'name': 'Product without variants', + }) + move_vals = cls.Move.onchange_product_id( + prod_id=cls.product_single.id, + loc_id=cls.picking.location_id.id, + loc_dest_id=cls.picking.location_dest_id.id, + partner_id=cls.picking.partner_id.id, + ).get('value', {}) + move_vals.update({ + 'product_id': cls.product_single.id, + 'picking_id': cls.picking.id, + 'product_uom_qty': 2, + }) + cls.move3 = cls.Move.create(move_vals) + + def test_add_variants(self): + self.move1.unlink() + self.move2.unlink() + self.move3.unlink() + wizard = self.Wizard.new({'product_tmpl_id': self.product_tmpl.id}) + wizard._onchange_product_tmpl_id() + wizard = wizard.create(wizard._convert_to_write(wizard._cache)) + self.assertEqual(len(wizard.variant_line_ids), 4) + wizard.variant_line_ids[0].product_uom_qty = 1 + wizard.variant_line_ids[1].product_uom_qty = 2 + wizard.variant_line_ids[2].product_uom_qty = 3 + wizard.variant_line_ids[3].product_uom_qty = 4 + wizard.button_transfer_to_picking() + self.assertEqual(len(self.picking.move_lines), 4, + "There should be 4 lines in the picking") + self.assertEqual(self.picking.move_lines[0].product_uom_qty, 1) + self.assertEqual(self.picking.move_lines[1].product_uom_qty, 2) + self.assertEqual(self.picking.move_lines[2].product_uom_qty, 3) + self.assertEqual(self.picking.move_lines[3].product_uom_qty, 4) + + def test_modify_variants(self): + Wizard2 = self.Wizard.with_context( + default_product_tmpl_id=self.product_tmpl.id, + active_model='stock.move', + active_id=self.move1.id, active_ids=self.move1.ids + ) + wizard = Wizard2.create({}) + wizard._onchange_product_tmpl_id() + self.assertEqual( + len(wizard.variant_line_ids.filtered('product_uom_qty')), 2, + "There should be two fields with any quantity in the wizard." + ) + wizard.variant_line_ids.filtered( + lambda x: x.product_id == self.product1).product_uom_qty = 0 + wizard.variant_line_ids.filtered( + lambda x: x.product_id == self.product2).product_uom_qty = 10 + wizard.button_transfer_to_picking() + self.assertFalse(self.move1.exists(), "Move not removed.") + self.assertEqual( + self.move2.product_uom_qty, 10, "Move not changed quantity.", + ) + + def test_variant_transfer(self): + self.picking.action_confirm() + self.picking.action_assign() + self.assertEqual(len(self.picking.pack_operation_ids), 3) + wizard = self.env['stock.transfer.manage.variant'].with_context( + active_model='stock.picking', active_ids=self.picking.ids, + active_id=self.picking.id, + ).create({}) + self.assertEqual(len(wizard.variant_line_ids), 6) + wizard.variant_line_ids.filtered( + lambda x: x.product_id == self.product1 + ).qty_done = 1 + wizard.variant_line_ids.filtered( + lambda x: x.product_id == self.product2 + ).qty_done = 2 + wizard.variant_line_ids.filtered( + lambda x: x.product_id == self.product_single + ).qty_done = 3 + wizard.button_transfer_to_picking() + self.assertEqual(self.picking.pack_operation_ids.filtered( + lambda x: x.product_id == self.product1 + ).qty_done, 1) + self.assertEqual(self.picking.pack_operation_ids.filtered( + lambda x: x.product_id == self.product2 + ).qty_done, 2) + self.assertEqual(self.picking.pack_operation_ids.filtered( + lambda x: x.product_id == self.product_single + ).qty_done, 3) diff --git a/stock_picking_variant_mgmt/views/stock_picking_view.xml b/stock_picking_variant_mgmt/views/stock_picking_view.xml new file mode 100644 index 000000000..b9cdd4335 --- /dev/null +++ b/stock_picking_variant_mgmt/views/stock_picking_view.xml @@ -0,0 +1,51 @@ + + + + + + stock.picking + + + +
+
+
+ +
+
+
+
+
+ + + stock.move + + + + + + + +