diff --git a/stock_secondary_unit/__manifest__.py b/stock_secondary_unit/__manifest__.py index 0c1be32fc..8754a85b8 100644 --- a/stock_secondary_unit/__manifest__.py +++ b/stock_secondary_unit/__manifest__.py @@ -1,24 +1,21 @@ # Copyright 2018 Tecnativa - Sergio Teruel # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { - 'name': 'Stock Secondary Unit', - 'summary': 'Get product quantities in a secondary unit', - 'version': '12.0.1.0.2', - 'development_status': 'Beta', - 'category': 'stock', - 'website': 'https://github.com/OCA/stock-logistics-warehouse', - 'author': 'Tecnativa, Odoo Community Association (OCA)', - 'license': 'AGPL-3', - 'application': False, - 'installable': True, - 'depends': [ - 'stock', - 'product_secondary_unit', - ], - 'data': [ - 'views/product_views.xml', - 'views/stock_move_views.xml', - 'views/stock_picking_views.xml', - 'report/report_deliveryslip.xml', + "name": "Stock Secondary Unit", + "summary": "Get product quantities in a secondary unit", + "version": "13.0.1.0.2", + "development_status": "Beta", + "category": "stock", + "website": "https://github.com/OCA/stock-logistics-warehouse", + "author": "Tecnativa, Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": ["stock", "product_secondary_unit"], + "data": [ + "views/product_views.xml", + "views/stock_move_views.xml", + "views/stock_picking_views.xml", + "report/report_deliveryslip.xml", ], } diff --git a/stock_secondary_unit/models/product.py b/stock_secondary_unit/models/product.py index 46fa07283..a75fc54e6 100644 --- a/stock_secondary_unit/models/product.py +++ b/stock_secondary_unit/models/product.py @@ -1,35 +1,35 @@ # Copyright 2018 Tecnativa - Sergio Teruel # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from odoo import fields, models -from odoo.addons import decimal_precision as dp from odoo.tools.float_utils import float_round +from odoo.addons import decimal_precision as dp + class StockProductSecondaryUnit(models.AbstractModel): - _name = 'stock.product.secondary.unit' - _description = 'Stock Product Secondary Unit' + _name = "stock.product.secondary.unit" + _description = "Stock Product Secondary Unit" secondary_unit_qty_available = fields.Float( - string='Quantity On Hand (2Unit)', - compute='_compute_secondary_unit_qty_available', - digits=dp.get_precision('Product Unit of Measure'), + string="Quantity On Hand (2Unit)", + compute="_compute_secondary_unit_qty_available", + digits=dp.get_precision("Product Unit of Measure"), ) def _compute_secondary_unit_qty_available(self): - for product in self.filtered('stock_secondary_uom_id'): - qty = product.qty_available / ( - product.stock_secondary_uom_id.factor or 1.0) + for product in self.filtered("stock_secondary_uom_id"): + qty = product.qty_available / (product.stock_secondary_uom_id.factor or 1.0) product.secondary_unit_qty_available = float_round( - qty, precision_rounding=product.uom_id.rounding) + qty, precision_rounding=product.uom_id.rounding + ) class ProductTemplate(models.Model): - _inherit = ['product.template', 'stock.product.secondary.unit'] - _name = 'product.template' + _inherit = ["product.template", "stock.product.secondary.unit"] + _name = "product.template" stock_secondary_uom_id = fields.Many2one( - comodel_name='product.secondary.unit', - string='Second unit for inventory', + comodel_name="product.secondary.unit", string="Second unit for inventory" ) def _compute_quantities(self): @@ -38,8 +38,8 @@ class ProductTemplate(models.Model): class ProductProduct(models.Model): - _inherit = ['product.product', 'stock.product.secondary.unit'] - _name = 'product.product' + _inherit = ["product.product", "stock.product.secondary.unit"] + _name = "product.product" def _compute_quantities(self): super()._compute_quantities() diff --git a/stock_secondary_unit/models/stock_move.py b/stock_secondary_unit/models/stock_move.py index 342cac6bb..3cf670d63 100644 --- a/stock_secondary_unit/models/stock_move.py +++ b/stock_secondary_unit/models/stock_move.py @@ -1,52 +1,49 @@ # Copyright 2018 Tecnativa - Sergio Teruel # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from odoo import api, fields, models -from odoo.addons import decimal_precision as dp from odoo.tools.float_utils import float_round +from odoo.addons import decimal_precision as dp + class StockSecondaryUnitMixin(models.AbstractModel): - _name = 'stock.secondary.unit.mixin' - _description = 'Stock Secondary Unit Mixin' + _name = "stock.secondary.unit.mixin" + _description = "Stock Secondary Unit Mixin" secondary_uom_id = fields.Many2one( - comodel_name='product.secondary.unit', - string='Second unit', + comodel_name="product.secondary.unit", string="Second unit" ) secondary_uom_qty = fields.Float( - string='Secondary Qty', - digits=dp.get_precision('Product Unit of Measure'), + string="Secondary Qty", digits=dp.get_precision("Product Unit of Measure") ) class StockMove(models.Model): - _inherit = ['stock.move', 'stock.secondary.unit.mixin'] - _name = 'stock.move' + _inherit = ["stock.move", "stock.secondary.unit.mixin"] + _name = "stock.move" def _merge_moves_fields(self): res = super(StockMove, self)._merge_moves_fields() - res['secondary_uom_qty'] = self[-1:].secondary_uom_qty + res["secondary_uom_qty"] = self[-1:].secondary_uom_qty return res class StockMoveLine(models.Model): - _inherit = ['stock.move.line', 'stock.secondary.unit.mixin'] - _name = 'stock.move.line' + _inherit = ["stock.move.line", "stock.secondary.unit.mixin"] + _name = "stock.move.line" @api.model def create(self, vals): - move = self.env['stock.move'].browse(vals.get('move_id', False)) + move = self.env["stock.move"].browse(vals.get("move_id", False)) if move.secondary_uom_id: - uom = self.env['uom.uom'].browse(vals['product_uom_id']) + uom = self.env["uom.uom"].browse(vals["product_uom_id"]) factor = move.secondary_uom_id.factor * uom.factor - move_line_qty = vals.get( - 'product_uom_qty', vals.get('qty_done', 0.0)) + move_line_qty = vals.get("product_uom_qty", vals.get("qty_done", 0.0)) qty = float_round( move_line_qty / (factor or 1.0), - precision_rounding=move.secondary_uom_id.uom_id.rounding + precision_rounding=move.secondary_uom_id.uom_id.rounding, + ) + vals.update( + {"secondary_uom_qty": qty, "secondary_uom_id": move.secondary_uom_id.id} ) - vals.update({ - 'secondary_uom_qty': qty, - 'secondary_uom_id': move.secondary_uom_id.id, - }) return super().create(vals) diff --git a/stock_secondary_unit/tests/test_stock_secondary_unit.py b/stock_secondary_unit/tests/test_stock_secondary_unit.py index d62b553f9..b5b0467e1 100644 --- a/stock_secondary_unit/tests/test_stock_secondary_unit.py +++ b/stock_secondary_unit/tests/test_stock_secondary_unit.py @@ -10,94 +10,118 @@ class TestProductSecondaryUnit(SavepointCase): @classmethod def setUpClass(cls): super().setUpClass() - cls.warehouse = cls.env.ref('stock.warehouse0') - cls.product_uom_kg = cls.env.ref('uom.product_uom_kgm') - cls.product_uom_unit = cls.env.ref('uom.product_uom_unit') - ProductAttribute = cls.env['product.attribute'] - ProductAttributeValue = cls.env['product.attribute.value'] - cls.attribute_color = ProductAttribute.create({'name': 'test_color'}) - cls.attribute_value_white = ProductAttributeValue.create({ - 'name': 'test_white', - 'attribute_id': cls.attribute_color.id, - }) - cls.attribute_value_black = ProductAttributeValue.create({ - 'name': 'test_black', - 'attribute_id': cls.attribute_color.id, - }) - cls.product_template = cls.env['product.template'].create({ - 'name': 'test', - 'uom_id': cls.product_uom_kg.id, - 'uom_po_id': cls.product_uom_kg.id, - 'type': 'product', - 'secondary_uom_ids': [ - (0, 0, { - 'code': 'A', - 'name': 'unit-700', - 'uom_id': cls.product_uom_unit.id, - 'factor': 0.5, - }), - (0, 0, { - 'code': 'B', - 'name': 'unit-900', - 'uom_id': cls.product_uom_unit.id, - 'factor': 0.9, - }), - ], - 'attribute_line_ids': [(0, 0, { - 'attribute_id': cls.attribute_color.id, - 'value_ids': [(4, cls.attribute_value_white.id), - (4, cls.attribute_value_black.id)], - })], - }) - secondary_unit = cls.env['product.secondary.unit'].search([ - ('product_tmpl_id', '=', cls.product_template.id), - ], limit=1) - cls.product_template.write({ - 'sale_secondary_uom_id': secondary_unit.id, - 'stock_secondary_uom_id': secondary_unit.id, - }) - StockQuant = cls.env['stock.quant'] - cls.quant_white = StockQuant.create({ - 'product_id': cls.product_template.product_variant_ids[0].id, - 'location_id': cls.warehouse.lot_stock_id.id, - 'quantity': 10.0, - }) - cls.quant_black = StockQuant.create({ - 'product_id': cls.product_template.product_variant_ids[1].id, - 'location_id': cls.warehouse.lot_stock_id.id, - 'quantity': 10.0, - }) + cls.warehouse = cls.env.ref("stock.warehouse0") + cls.product_uom_kg = cls.env.ref("uom.product_uom_kgm") + cls.product_uom_unit = cls.env.ref("uom.product_uom_unit") + ProductAttribute = cls.env["product.attribute"] + ProductAttributeValue = cls.env["product.attribute.value"] + cls.attribute_color = ProductAttribute.create({"name": "test_color"}) + cls.attribute_value_white = ProductAttributeValue.create( + {"name": "test_white", "attribute_id": cls.attribute_color.id} + ) + cls.attribute_value_black = ProductAttributeValue.create( + {"name": "test_black", "attribute_id": cls.attribute_color.id} + ) + cls.product_template = cls.env["product.template"].create( + { + "name": "test", + "uom_id": cls.product_uom_kg.id, + "uom_po_id": cls.product_uom_kg.id, + "type": "product", + "secondary_uom_ids": [ + ( + 0, + 0, + { + "code": "A", + "name": "unit-700", + "uom_id": cls.product_uom_unit.id, + "factor": 0.5, + }, + ), + ( + 0, + 0, + { + "code": "B", + "name": "unit-900", + "uom_id": cls.product_uom_unit.id, + "factor": 0.9, + }, + ), + ], + "attribute_line_ids": [ + ( + 0, + 0, + { + "attribute_id": cls.attribute_color.id, + "value_ids": [ + (4, cls.attribute_value_white.id), + (4, cls.attribute_value_black.id), + ], + }, + ) + ], + } + ) + secondary_unit = cls.env["product.secondary.unit"].search( + [("product_tmpl_id", "=", cls.product_template.id)], limit=1 + ) + cls.product_template.write( + { + "sale_secondary_uom_id": secondary_unit.id, + "stock_secondary_uom_id": secondary_unit.id, + } + ) + StockQuant = cls.env["stock.quant"] + cls.quant_white = StockQuant.create( + { + "product_id": cls.product_template.product_variant_ids[0].id, + "location_id": cls.warehouse.lot_stock_id.id, + "quantity": 10.0, + } + ) + cls.quant_black = StockQuant.create( + { + "product_id": cls.product_template.product_variant_ids[1].id, + "location_id": cls.warehouse.lot_stock_id.id, + "quantity": 10.0, + } + ) def test_01_stock_secondary_unit_template(self): - self.assertEqual( - self.product_template.secondary_unit_qty_available, 40.0) + self.assertEqual(self.product_template.secondary_unit_qty_available, 40.0) def test_02_stock_secondary_unit_variant(self): for variant in self.product_template.product_variant_ids.filtered( - 'attribute_value_ids'): + "attribute_value_ids" + ): self.assertEqual(variant.secondary_unit_qty_available, 20) def test_03_stock_picking_secondary_unit(self): - StockPicking = self.env['stock.picking'] + StockPicking = self.env["stock.picking"] product1 = self.product_template.product_variant_ids[0] - location = self.env.ref('stock.stock_location_suppliers') - location_dest = self.env.ref('stock.stock_location_stock') - picking_type = self.env.ref('stock.picking_type_in') + location = self.env.ref("stock.stock_location_suppliers") + location_dest = self.env.ref("stock.stock_location_stock") + picking_type = self.env.ref("stock.picking_type_in") move_vals = { - 'product_id': product1.id, - 'name': product1.display_name, - 'secondary_uom_id': product1.secondary_uom_ids[0].id, - 'product_uom': product1.uom_id.id, - 'product_uom_qty': 10.0, - 'location_id': location.id, - 'location_dest_id': location_dest.id, + "product_id": product1.id, + "name": product1.display_name, + "secondary_uom_id": product1.secondary_uom_ids[0].id, + "product_uom": product1.uom_id.id, + "product_uom_qty": 10.0, + "location_id": location.id, + "location_dest_id": location_dest.id, } do_vals = { - 'location_id': location.id, - 'location_dest_id': location_dest.id, - 'picking_type_id': picking_type.id, - 'move_ids_without_package': [(0, None, move_vals), - (0, None, move_vals)], # 2 moves + "location_id": location.id, + "location_dest_id": location_dest.id, + "picking_type_id": picking_type.id, + "move_ids_without_package": [ + (0, None, move_vals), + (0, None, move_vals), + ], # 2 moves } delivery_order = StockPicking.create(do_vals) delivery_order.action_confirm() @@ -105,8 +129,9 @@ class TestProductSecondaryUnit(SavepointCase): self.assertEquals(len(delivery_order.move_lines), 1) self.assertEquals(len(delivery_order.move_line_ids), 1) # Qty merged to 20, and secondary unit qty is 40line - uom_qty = sum(delivery_order.move_lines.mapped('product_uom_qty')) - secondary_uom_qty = \ - sum(delivery_order.move_line_ids.mapped('secondary_uom_qty')) + uom_qty = sum(delivery_order.move_lines.mapped("product_uom_qty")) + secondary_uom_qty = sum( + delivery_order.move_line_ids.mapped("secondary_uom_qty") + ) self.assertEquals(uom_qty, 20.0) self.assertEquals(secondary_uom_qty, 40.0)