diff --git a/stock_picking_product_kit_helper/__manifest__.py b/stock_picking_product_kit_helper/__manifest__.py index b14f3ff39..b8ebefed3 100644 --- a/stock_picking_product_kit_helper/__manifest__.py +++ b/stock_picking_product_kit_helper/__manifest__.py @@ -1,20 +1,15 @@ # Copyright 2019 Kitti U. - Ecosoft # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { - 'name': 'Stock Picking Product Kit Helper', - 'summary': 'Set quanity in picking line based on product kit quantity', - 'version': '12.0.1.0.0', - 'category': 'Stock', - 'website': 'https://github.com/OCA/manufacture', - 'author': 'Ecosoft, Odoo Community Association (OCA)', - 'license': 'AGPL-3', - 'installable': True, - 'depends': [ - 'sale_mrp', - ], - 'data': [ - 'security/ir.model.access.csv', - 'views/stock_view.xml', - ], - 'maintainers': ['kittiu'] + "name": "Stock Picking Product Kit Helper", + "summary": "Set quanity in picking line based on product kit quantity", + "version": "13.0.1.0.0", + "category": "Stock", + "website": "https://github.com/OCA/manufacture", + "author": "Ecosoft, Odoo Community Association (OCA)", + "license": "AGPL-3", + "installable": True, + "depends": ["sale_mrp"], + "data": ["security/ir.model.access.csv", "views/stock_view.xml"], + "maintainers": ["kittiu"], } diff --git a/stock_picking_product_kit_helper/models/stock_picking.py b/stock_picking_product_kit_helper/models/stock_picking.py index 1ad27466e..9da66163b 100644 --- a/stock_picking_product_kit_helper/models/stock_picking.py +++ b/stock_picking_product_kit_helper/models/stock_picking.py @@ -1,100 +1,103 @@ # Copyright 2019 Kitti U. - Ecosoft # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import models, fields, api, _ +from odoo import _, api, fields, models from odoo.exceptions import ValidationError class StockPicking(models.Model): - _inherit = 'stock.picking' + _inherit = "stock.picking" product_kit_helper_ids = fields.One2many( - comodel_name='stock.picking.product.kit.helper', - string='Product Kit Helper Lines', - inverse_name='picking_id', + comodel_name="stock.picking.product.kit.helper", + string="Product Kit Helper Lines", + inverse_name="picking_id", readonly=False, - states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, + states={"done": [("readonly", True)], "cancel": [("readonly", True)]}, ) has_product_kit = fields.Boolean( - string='Has Product Kit', - compute='_compute_has_product_kit', + string="Has Product Kit", + compute="_compute_has_product_kit", help="True if there is at least 1 product kit in the sales order", ) @api.model def _is_product_kit(self, product, company): - BOM = self.env['mrp.bom'].sudo() - bom = BOM._bom_find(product=product, - company_id=company.id) - return bom and bom.type == 'phantom' + BOM = self.env["mrp.bom"].sudo() + bom = BOM._bom_find(product=product, company_id=company.id) + return bom and bom.type == "phantom" @api.multi def _compute_has_product_kit(self): for picking in self: - if any(self._is_product_kit(line.product_id, line.company_id) - for line in picking.move_lines.mapped('sale_line_id')): + if any( + self._is_product_kit(line.product_id, line.company_id) + for line in picking.move_lines.mapped("sale_line_id") + ): picking.has_product_kit = True @api.multi def show_product_kit(self): """Find move_lines with product kit to create helper line.""" self.ensure_one() - BOM = self.env['mrp.bom'].sudo() + BOM = self.env["mrp.bom"].sudo() helpers = [] - for sale_line in self.move_lines.mapped('sale_line_id'): - bom = BOM._bom_find(product=sale_line.product_id, - company_id=sale_line.company_id.id) - if bom and bom.type == 'phantom': # Create product kit line - helpers.append((0, 0, {'sale_line_id': sale_line.id, - 'product_id': sale_line.product_id.id, - 'product_uom_qty': 0.0, - })) + for sale_line in self.move_lines.mapped("sale_line_id"): + bom = BOM._bom_find( + product=sale_line.product_id, company_id=sale_line.company_id.id + ) + if bom and bom.type == "phantom": # Create product kit line + helpers.append( + ( + 0, + 0, + { + "sale_line_id": sale_line.id, + "product_id": sale_line.product_id.id, + "product_uom_qty": 0.0, + }, + ) + ) self.product_kit_helper_ids.unlink() - self.write({'product_kit_helper_ids': helpers}) + self.write({"product_kit_helper_ids": helpers}) @api.multi def action_product_kit_helper(self): """Assign product kit's quantity to stock move.""" self.ensure_one() - if self.state in ('done', 'cancel'): + if self.state in ("done", "cancel"): raise ValidationError( - _('Product Kit Helper is not allowed on current state')) + _("Product Kit Helper is not allowed on current state") + ) for helper in self.product_kit_helper_ids: helper.action_explode_helper() class StockPickingProductKitHelper(models.Model): - _name = 'stock.picking.product.kit.helper' + _name = "stock.picking.product.kit.helper" _description = """ Product Kit Helper, allow user to specify quantity of product kit, to explode as product quantity in operations tab """ picking_id = fields.Many2one( - comodel_name='stock.picking', - string='Picking', + comodel_name="stock.picking", + string="Picking", required=True, index=True, - ondelete='cascade', + ondelete="cascade", ) sale_line_id = fields.Many2one( - comodel_name='sale.order.line', - string='Sales Order Line', - required=True, + comodel_name="sale.order.line", string="Sales Order Line", required=True ) product_id = fields.Many2one( - comodel_name='product.product', - string='Product', - required=True, - readonly=True, - ) - product_uom_qty = fields.Float( - string='Quantity', + comodel_name="product.product", string="Product", required=True, readonly=True ) + product_uom_qty = fields.Float(string="Quantity") product_uom = fields.Many2one( - comodel_name='uom.uom', - string='Unit of Measure', - related='sale_line_id.product_uom', + comodel_name="uom.uom", + string="Unit of Measure", + related="sale_line_id.product_uom", readonly=True, ) @@ -103,31 +106,36 @@ class StockPickingProductKitHelper(models.Model): """Explodes product kit quantity to detailed product in stock move.""" self.ensure_one() # Mock stock.move, in order to resue stock.move's action_explode - StockMove = self.env['stock.move'] - mock_loc = self.env['stock.location'].sudo().search([], limit=1) - mock_pt = self.env['stock.picking.type'].sudo().search([], limit=1) - mock_stock_move = StockMove.sudo().create({ - 'name': '/', - 'product_id': self.product_id.id, - 'product_uom': self.product_uom.id, - 'product_uom_qty': self.product_uom_qty, - 'picking_type_id': mock_pt.id, - 'location_id': mock_loc.id, - 'location_dest_id': mock_loc.id, - }) + StockMove = self.env["stock.move"] + mock_loc = self.env["stock.location"].sudo().search([], limit=1) + mock_pt = self.env["stock.picking.type"].sudo().search([], limit=1) + mock_stock_move = StockMove.sudo().create( + { + "name": "/", + "product_id": self.product_id.id, + "product_uom": self.product_uom.id, + "product_uom_qty": self.product_uom_qty, + "picking_type_id": mock_pt.id, + "location_id": mock_loc.id, + "location_dest_id": mock_loc.id, + } + ) # Reuse explode function and assign quantity_done in stock.move mock_processed_moves = mock_stock_move.action_explode() for mock_move in mock_processed_moves: - stock_move = StockMove.search([ - ('picking_id', '=', self.picking_id.id), - ('sale_line_id', '=', self.sale_line_id.id), - ('product_id', '=', mock_move.product_id.id)]) + stock_move = StockMove.search( + [ + ("picking_id", "=", self.picking_id.id), + ("sale_line_id", "=", self.sale_line_id.id), + ("product_id", "=", mock_move.product_id.id), + ] + ) if not stock_move: continue if len(stock_move) != 1: raise ValidationError( - _('No matching detailed product %s for product kit %s') % - (mock_move.product_id.display_name, - self.product_id.display_name)) - stock_move.write({'quantity_done': mock_move.product_uom_qty}) + _("No matching detailed product %s for product kit %s") + % (mock_move.product_id.display_name, self.product_id.display_name) + ) + stock_move.write({"quantity_done": mock_move.product_uom_qty}) mock_processed_moves.sudo().unlink() diff --git a/stock_picking_product_kit_helper/tests/test_stock_picking_product_kit_helper.py b/stock_picking_product_kit_helper/tests/test_stock_picking_product_kit_helper.py index cfa50a48d..8ea8e12c8 100644 --- a/stock_picking_product_kit_helper/tests/test_stock_picking_product_kit_helper.py +++ b/stock_picking_product_kit_helper/tests/test_stock_picking_product_kit_helper.py @@ -1,16 +1,15 @@ # Copyright 2019 Kitti U. - Ecosoft # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo.tests import common, Form from odoo.exceptions import ValidationError +from odoo.tests import Form, common class TestStockPickingProductKitHelper(common.TransactionCase): - def setUp(self): super(TestStockPickingProductKitHelper, self).setUp() - self.partner = self.env.ref('base.res_partner_2') - self.table_kit = self.env.ref('mrp.product_product_table_kit') + self.partner = self.env.ref("base.res_partner_2") + self.table_kit = self.env.ref("mrp.product_product_table_kit") def test_00_sale_product_kit_helper(self): """Test sale order with product kit, I expect, @@ -19,7 +18,7 @@ class TestStockPickingProductKitHelper(common.TransactionCase): - After picking is done, do not allow to use helper """ # Create sales order of 10 table kit - order_form = Form(self.env['sale.order']) + order_form = Form(self.env["sale.order"]) order_form.partner_id = self.partner with order_form.order_line.new() as line: line.product_id = self.table_kit @@ -27,38 +26,45 @@ class TestStockPickingProductKitHelper(common.TransactionCase): order = order_form.save() order.action_confirm() # In the picking, product line is exploded. - picking = order.mapped('picking_ids') + picking = order.mapped("picking_ids") self.assertEqual(len(picking), 1) stock_moves = picking.move_lines # 1 SO line exploded to 2 moves - moves = [{'product': x.product_id.name, 'qty': x.product_uom_qty} - for x in stock_moves] - self.assertEqual(moves, - [{'product': 'Wood Panel', 'qty': 10.0}, - {'product': 'Bolt', 'qty': 40.0}]) + moves = [ + {"product": x.product_id.name, "qty": x.product_uom_qty} + for x in stock_moves + ] + self.assertEqual( + moves, + [{"product": "Wood Panel", "qty": 10.0}, {"product": "Bolt", "qty": 40.0}], + ) self.assertTrue(picking.has_product_kit) self.assertFalse(picking.product_kit_helper_ids) # Not show yet picking.show_product_kit() self.assertEqual(len(picking.product_kit_helper_ids), 1) # Assign product set 4 qty and test that it apply to stock.move - picking.product_kit_helper_ids[0].write({'product_uom_qty': 4.0}) + picking.product_kit_helper_ids[0].write({"product_uom_qty": 4.0}) picking.action_product_kit_helper() - moves = [{'product': x.product_id.name, 'qty': x.quantity_done} - for x in stock_moves] - self.assertEqual(moves, - [{'product': 'Wood Panel', 'qty': 4.0}, - {'product': 'Bolt', 'qty': 16.0}]) + moves = [ + {"product": x.product_id.name, "qty": x.quantity_done} for x in stock_moves + ] + self.assertEqual( + moves, + [{"product": "Wood Panel", "qty": 4.0}, {"product": "Bolt", "qty": 16.0}], + ) # Assign again to 10 qty - picking.product_kit_helper_ids[0].write({'product_uom_qty': 10.0}) + picking.product_kit_helper_ids[0].write({"product_uom_qty": 10.0}) picking.action_product_kit_helper() - moves = [{'product': x.product_id.name, 'qty': x.quantity_done} - for x in stock_moves] - self.assertEqual(moves, - [{'product': 'Wood Panel', 'qty': 10.0}, - {'product': 'Bolt', 'qty': 40.0}]) + moves = [ + {"product": x.product_id.name, "qty": x.quantity_done} for x in stock_moves + ] + self.assertEqual( + moves, + [{"product": "Wood Panel", "qty": 10.0}, {"product": "Bolt", "qty": 40.0}], + ) # Validate Picking picking.button_validate() - self.assertEqual(picking.state, 'done') + self.assertEqual(picking.state, "done") # After done state, block the helper with self.assertRaises(ValidationError): picking.action_product_kit_helper()