From 01fdd869b88d5314e5baaf275e7f7a11897e185b Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Thu, 20 Apr 2017 14:27:53 +0200 Subject: [PATCH] [IMP] Adds the glue which fill in the product packaging in stock When a sale order is confirmed and product packaging is filled in sale order line(s), the information is propagated through procurement orders and stock moves. This is conditioned by a parameter on procurement rule. --- packaging_uom/models/product_packaging.py | 46 ++--- packaging_uom/tests/test_product_packaging.py | 66 +++++-- .../models/procurement_order.py | 4 +- .../models/purchase_order_line.py | 61 ++++--- .../tests/test_purchase_order_line.py | 56 ++++++ .../views/product_supplier_info_view.xml | 9 +- sale_packaging/__manifest__.py | 3 +- sale_packaging/models/__init__.py | 3 + sale_packaging/models/procurement_order.py | 21 +++ sale_packaging/models/procurement_rule.py | 14 ++ sale_packaging/models/sale_order_line.py | 9 + sale_packaging/models/stock_move.py | 18 ++ sale_packaging/tests/__init__.py | 3 +- sale_packaging/tests/test_stock_move.py | 172 ++++++++++++++++++ sale_packaging/views/procurement_rule.xml | 13 ++ 15 files changed, 430 insertions(+), 68 deletions(-) create mode 100644 sale_packaging/models/procurement_order.py create mode 100644 sale_packaging/models/procurement_rule.py create mode 100644 sale_packaging/models/stock_move.py create mode 100644 sale_packaging/tests/test_stock_move.py create mode 100644 sale_packaging/views/procurement_rule.xml diff --git a/packaging_uom/models/product_packaging.py b/packaging_uom/models/product_packaging.py index 20586ee4176f..ab8422cb6a44 100644 --- a/packaging_uom/models/product_packaging.py +++ b/packaging_uom/models/product_packaging.py @@ -34,42 +34,44 @@ def _default_uom_categ_domain_id(self): readonly=True ) - @api.one + @api.multi @api.depends('uom_id', 'product_tmpl_id.uom_id') def _compute_qty(self): """ Compute the quantity by package based on uom """ - if self.uom_id and self.product_tmpl_id: - self.qty = self.uom_id._compute_quantity( - 1, to_unit=self.product_tmpl_id.uom_id) - else: - self.qty = 0 + for packaging in self: + if packaging.uom_id and packaging.product_tmpl_id: + packaging.qty = packaging.uom_id._compute_quantity( + 1, to_unit=packaging.product_tmpl_id.uom_id) + else: + packaging.qty = 0 - @api.one + @api.multi def _inverse_qty(self): """ The inverse method is defined to make the code compatible with existing modules and to not break tests... :return: """ - category_id = self.product_tmpl_id.uom_id.category_id - uom_id = self.uom_id.search([ - ("factor", "=", 1.0 / self.qty), - ('category_id', '=', category_id.id)]) - if not uom_id: - uom_id = self.uom_id .create({ - 'name': "%s %s" % (category_id.name, self.qty), - 'category_id': category_id.id, - 'rounding': self.product_tmpl_id.uom_id.rounding, - 'uom_type': 'bigger', - 'factor_inv': self.qty, - 'active': True - }) - self.uom_id = uom_id + for packaging in self: + category_id = packaging.product_tmpl_id.uom_id.category_id + uom_id = packaging.uom_id.search([ + ("factor", "=", 1.0 / self.qty), + ('category_id', '=', category_id.id)]) + if not uom_id: + uom_id = packaging.uom_id.create({ + 'name': "%s %s" % (category_id.name, packaging.qty), + 'category_id': category_id.id, + 'rounding': packaging.product_tmpl_id.uom_id.rounding, + 'uom_type': 'bigger', + 'factor_inv': packaging.qty, + 'active': True + }) + packaging.uom_id = uom_id @api.multi - @api.constrains + @api.constrains('uom_id') def _check_uom_id(self): """ Check uom_id is not null diff --git a/packaging_uom/tests/test_product_packaging.py b/packaging_uom/tests/test_product_packaging.py index 00b7d628b0ef..ad1af7c835c6 100644 --- a/packaging_uom/tests/test_product_packaging.py +++ b/packaging_uom/tests/test_product_packaging.py @@ -2,6 +2,7 @@ # Copyright 2015-2017 ACSONE SA/NV () # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import odoo.tests.common as common +from odoo.exceptions import ValidationError class TestProductPackaging(common.TransactionCase): @@ -10,12 +11,13 @@ def setUp(self): super(TestProductPackaging, self).setUp() self.uom_unit = self.env.ref('product.product_uom_unit') self.uom_dozen = self.env.ref('product.product_uom_dozen') - self.product_tmpl_dozen = self.env[ - 'product.template'].new( - {'uom_id': self.uom_dozen}) - self.product_tmpl_unit = self.env[ - 'product.template'].new( - {'uom_id': self.uom_unit}) + self.product_tmpl_dozen = self.env['product.template'].create( + {'name': 'PRODUCT DOZEN', + 'uom_id': self.uom_dozen.id}) + self.product_tmpl_unit = self.env['product.template'].create( + {'name': 'PRODUCT UNIT', + 'uom_id': self.uom_unit.id} + ) def test_compute_quantity_by_package(self): """ Create a packagings with uom product_uom_dozen on @@ -41,14 +43,22 @@ def test_compute_quantity_by_package(self): """ packaging_obj = self.env['product.packaging'] - product_packaging_dozen = packaging_obj.new( - {'product_tmpl_id': self.product_tmpl_dozen, - 'uom_id': self.uom_dozen}) + product_packaging_dozen = packaging_obj.create( + {'name': 'PACKAGING 1', + 'product_tmpl_id': self.product_tmpl_dozen.id, + 'uom_id': self.uom_dozen.id}) self.assertAlmostEqual(product_packaging_dozen.qty, 1) - product_packaging_unit = packaging_obj.new( - {'product_tmpl_id': self.product_tmpl_unit, - 'uom_id': self.uom_dozen}) + product_packaging_unit = packaging_obj.with_context( + get_uom_categ_from_uom=self.uom_dozen.category_id.id).create( + {'name': 'PACKAGING 2', + 'product_tmpl_id': self.product_tmpl_unit.id, + 'uom_id': self.uom_dozen.id}) self.assertAlmostEqual(product_packaging_unit.qty, 12) + self.assertEqual( + self.uom_dozen.category_id, + product_packaging_unit.uom_categ_domain_id, + 'The UOM domain is not well set' + ) product_uom_24 = self.env['product.uom'].create( {'category_id': self.env.ref('product.product_uom_categ_unit').id, 'name': 'Double Dozens', @@ -69,3 +79,35 @@ def test_compute_quantity_by_package(self): self.assertAlmostEqual(product_packaging_dozen.qty, 2) product_packaging_unit.uom_id = product_uom_6 self.assertAlmostEqual(product_packaging_unit.qty, 6) + # Set Packaging Quantity + product_packaging_dozen.qty = 1 + self.assertEquals( + self.uom_unit, + product_packaging_dozen.uom_id + ) + # Try to set null on uom + with self.assertRaises(ValidationError): + product_packaging_dozen.uom_id = None + + # Define a new packaging unit + uom_524 = self.env['product.uom'].search([ + ('category_id', '=', + product_packaging_dozen.product_tmpl_id.uom_id.category_id.id), + ('name', + '=', + '%s %s' % + (product_packaging_dozen.product_tmpl_id.uom_id.category_id.name, + float(524))) + ]) + self.assertEqual(0, len(uom_524)) + product_packaging_dozen.qty = 524 + uom_524 = self.env['product.uom'].search([ + ('category_id', '=', + product_packaging_dozen.product_tmpl_id.uom_id.category_id.id), + ('name', + '=', + '%s %s' % + (product_packaging_dozen.product_tmpl_id.uom_id.category_id.name, + float(524))) + ]) + self.assertEqual(1, len(uom_524)) diff --git a/purchase_packaging/models/procurement_order.py b/purchase_packaging/models/procurement_order.py index 48a1b233a8c9..589aed429450 100644 --- a/purchase_packaging/models/procurement_order.py +++ b/purchase_packaging/models/procurement_order.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Copyright 2015-2017 ACSONE SA/NV () # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, models +from odoo import api, fields, models class ProcurementOrder(models.Model): @@ -17,7 +17,7 @@ def _prepare_purchase_order_line(self, po, supplier): seller = self.product_id._select_seller( partner_id=supplier.name, quantity=res['product_qty'], - date=po.date_order and po.date_order[:10], + date=po.date_order and fields.Date.from_string(po.date_order), uom_id=self.product_id.uom_po_id) if seller.packaging_id: res['packaging_id'] = seller.packaging_id.id diff --git a/purchase_packaging/models/purchase_order_line.py b/purchase_packaging/models/purchase_order_line.py index 991aaeb1ff69..c91227e53876 100644 --- a/purchase_packaging/models/purchase_order_line.py +++ b/purchase_packaging/models/purchase_order_line.py @@ -43,44 +43,49 @@ def _get_product_seller(self): return self.product_id._select_seller( partner_id=self.order_id.partner_id, quantity=self.product_qty, - date=self.order_id.date_order and self.order_id.date_order[:10], + date=self.order_id.date_order and + fields.Date.from_string(self.order_id.date_order), uom_id=self.product_uom) - @api.one + @api.multi @api.depends('product_purchase_uom_id', 'product_purchase_qty') def _compute_product_qty(self): """ Compute the total quantity """ - uom_obj = self.env['product.uom'] - to_uom = uom_obj.search( - [('category_id', '=', self.product_purchase_uom_id.category_id.id), - ('uom_type', '=', 'reference')], limit=1) - if not self.product_purchase_uom_id: - return - self.product_qty = self.product_purchase_uom_id._compute_quantity( - self.product_purchase_qty, - to_uom) + for line in self: + uom_obj = self.env['product.uom'] + to_uom = uom_obj.search( + [('category_id', + '=', + line.product_purchase_uom_id.category_id.id), + ('uom_type', '=', 'reference')], limit=1) + if not line.product_purchase_uom_id: + return + line.product_qty = line.product_purchase_uom_id._compute_quantity( + line.product_purchase_qty, + to_uom) - @api.one + @api.multi def _inverse_product_qty(self): """ If product_quantity is set compute the purchase_qty """ - if self.product_id: - supplier = self._get_product_seller() - if supplier: - product_purchase_uom = supplier.min_qty_uom_id - uom_obj = self.env['product.uom'] - from_uom = uom_obj.search( - [('category_id', '=', - product_purchase_uom.category_id.id), - ('uom_type', '=', 'reference')], limit=1) - self.product_purchase_qty = from_uom._compute_quantity( - self.product_qty, - product_purchase_uom) - self.product_purchase_uom_id = product_purchase_uom.id - else: - self.product_purchase_qty = self.product_qty + for line in self: + if line.product_id: + supplier = line._get_product_seller() + if supplier: + product_purchase_uom = supplier.min_qty_uom_id + uom_obj = self.env['product.uom'] + from_uom = uom_obj.search( + [('category_id', '=', + product_purchase_uom.category_id.id), + ('uom_type', '=', 'reference')], limit=1) + line.product_purchase_qty = from_uom._compute_quantity( + line.product_qty, + product_purchase_uom) + line.product_purchase_uom_id = product_purchase_uom.id + else: + line.product_purchase_qty = line.product_qty @api.onchange("packaging_id") def _onchange_packaging_id(self): @@ -136,7 +141,7 @@ def onchange_product_id(self): if res.get('domain'): res['domain'].update(domain) else: - res['domain'] = domain + res['domain'] = domain # pragma: no cover not aware of super return res @api.multi diff --git a/purchase_packaging/tests/test_purchase_order_line.py b/purchase_packaging/tests/test_purchase_order_line.py index 4f3468f0c0f3..ce1745998dd2 100644 --- a/purchase_packaging/tests/test_purchase_order_line.py +++ b/purchase_packaging/tests/test_purchase_order_line.py @@ -22,6 +22,11 @@ def setUp(self): 'uom_id': self.env.ref('product.product_uom_dozen').id, 'name': 'Packaging Dozen'} ) + self.product_packaging_unit = self.env['product.packaging'].create( + {'product_tmpl_id': self.product_tmpl_id.id, + 'uom_id': self.env.ref('product.product_uom_unit').id, + 'name': 'Packaging Unit'} + ) self.product_uom_8 = self.env['product.uom'].create( {'category_id': self.env.ref('product.product_uom_categ_unit').id, 'name': 'COL8', @@ -79,3 +84,54 @@ def test_po_line(self): self.assertEqual(sm.product_uom.id, self.env.ref('product.product_uom_dozen').id) self.assertAlmostEqual(sm.product_uom_qty, 16) + + def test_po_line_no_product(self): + self.product_supplier_info.min_qty_uom_id = self.product_uom_8 + self.product_supplier_info.min_qty = 2 + self.product_supplier_info.packaging_id = self.product_packaging_dozen + + po = self.env['purchase.order'].create( + {'partner_id': self.product_supplier_info.name.id}) + po_line = po.order_line.new({ + 'product_id': self.product_tmpl_id.product_variant_id, + 'product_purchase_qty': 1.0, + 'product_purchase_uom_id': + po.order_line._default_product_purchase_uom_id(), + 'order_id': po + }) + po_line.onchange_product_id() + po_line.product_id = None + po_line.product_qty = 2.0 + self.assertEqual( + 2.0, + po_line.product_purchase_qty, + 'The purchase quantity is not well set' + ) + + def test_po_line_change_packaging(self): + self.product_supplier_info.min_qty_uom_id = self.product_uom_8 + self.product_supplier_info.min_qty = 2 + self.product_supplier_info.packaging_id = self.product_packaging_dozen + + po = self.env['purchase.order'].create( + {'partner_id': self.product_supplier_info.name.id}) + po_line = po.order_line.new({ + 'product_id': self.product_tmpl_id.product_variant_id, + 'product_purchase_qty': 1.0, + 'product_purchase_uom_id': + po.order_line._default_product_purchase_uom_id(), + 'order_id': po + }) + po_line.onchange_product_id() + self.assertEquals( + self.product_packaging_dozen.uom_id, + po_line.product_uom, + 'The UOM Unit is not well set' + ) + po_line.packaging_id = self.product_packaging_unit + po_line._onchange_packaging_id() + self.assertEquals( + self.product_packaging_unit.uom_id, + po_line.product_uom, + 'The product uom is not well set' + ) diff --git a/purchase_packaging/views/product_supplier_info_view.xml b/purchase_packaging/views/product_supplier_info_view.xml index bce0de3ff706..1edf741f02c6 100644 --- a/purchase_packaging/views/product_supplier_info_view.xml +++ b/purchase_packaging/views/product_supplier_info_view.xml @@ -22,10 +22,15 @@ - + x + + + of - +
+ using package +
diff --git a/sale_packaging/__manifest__.py b/sale_packaging/__manifest__.py index 9ca911c4d2cc..5969cee34c14 100755 --- a/sale_packaging/__manifest__.py +++ b/sale_packaging/__manifest__.py @@ -13,7 +13,8 @@ "depends": ["sale_stock", "packaging_uom", ], - "data": ["views/sale_order_line_views.xml", + "data": ["views/procurement_rule.xml", + "views/sale_order_line_views.xml", ], "license": "AGPL-3", "installable": True, diff --git a/sale_packaging/models/__init__.py b/sale_packaging/models/__init__.py index 843441d34b86..b78d67cae0da 100755 --- a/sale_packaging/models/__init__.py +++ b/sale_packaging/models/__init__.py @@ -3,3 +3,6 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import sale_order_line +from . import procurement_order +from . import procurement_rule +from . import stock_move diff --git a/sale_packaging/models/procurement_order.py b/sale_packaging/models/procurement_order.py new file mode 100644 index 000000000000..66e4af32c297 --- /dev/null +++ b/sale_packaging/models/procurement_order.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ProcurementOrder(models.Model): + + _inherit = 'procurement.order' + + product_packaging = fields.Many2one( + 'product.packaging', + string='Packaging', + default=False) + + def _get_stock_move_values(self): + vals = super(ProcurementOrder, self)._get_stock_move_values() + if self.product_packaging: + vals.update({'product_packaging': self.product_packaging.id}) + return vals diff --git a/sale_packaging/models/procurement_rule.py b/sale_packaging/models/procurement_rule.py new file mode 100644 index 000000000000..ad7ea2ecec4c --- /dev/null +++ b/sale_packaging/models/procurement_rule.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ProcurementRule(models.Model): + + _inherit = 'procurement.rule' + + propagate_product_packaging = fields.Boolean( + string='Propagate Product Packaging', + default=True) diff --git a/sale_packaging/models/sale_order_line.py b/sale_packaging/models/sale_order_line.py index cfd8d7e9fb12..5f636aac112c 100644 --- a/sale_packaging/models/sale_order_line.py +++ b/sale_packaging/models/sale_order_line.py @@ -36,3 +36,12 @@ def create(self, vals): @api.multi def write(self, vals): return super(SaleOrderLine, self).write(self.update_vals(vals)) + + @api.multi + def _prepare_order_line_procurement(self, group_id=False): + vals = super(SaleOrderLine, self)._prepare_order_line_procurement( + group_id=group_id) + vals.update({ + 'product_packaging': self.product_packaging.id, + }) + return vals diff --git a/sale_packaging/models/stock_move.py b/sale_packaging/models/stock_move.py new file mode 100644 index 000000000000..62b5a2472636 --- /dev/null +++ b/sale_packaging/models/stock_move.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class StockMove(models.Model): + + _inherit = 'stock.move' + + def _prepare_procurement_from_move(self): + vals = super(StockMove, self)._prepare_procurement_from_move() + if self.product_packaging and\ + self.rule_id and\ + self.rule_id.propagate_product_packaging: + vals.update({'product_packaging': self.product_packaging.id}) + return vals diff --git a/sale_packaging/tests/__init__.py b/sale_packaging/tests/__init__.py index 58bdc4aba20e..7cb68b83d86a 100644 --- a/sale_packaging/tests/__init__.py +++ b/sale_packaging/tests/__init__.py @@ -2,4 +2,5 @@ # Copyright 2015-2017 ACSONE SA/NV () # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from . import test_sale_order_line \ No newline at end of file +from . import test_sale_order_line +from . import test_stock_move diff --git a/sale_packaging/tests/test_stock_move.py b/sale_packaging/tests/test_stock_move.py new file mode 100644 index 000000000000..1bf6bb43ad88 --- /dev/null +++ b/sale_packaging/tests/test_stock_move.py @@ -0,0 +1,172 @@ +# -*- coding: utf-8 -*- +# Copyright 2015-2017 ACSONE SA/NV () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import odoo.tests.common as common + + +class TestStockMove(common.TransactionCase): + + def setUp(self): + """ Create a packagings with uom product_uom_dozen on + * product_product_3 (uom is product_uom_unit) + """ + super(TestStockMove, self).setUp() + self.product_packaging_dozen = self.env['product.packaging'].create( + {'product_tmpl_id': self.env.ref('product.product_product_3' + ).product_tmpl_id.id, + 'uom_id': self.env.ref('product.product_uom_dozen').id, + 'name': 'dozen'}) + self.product_packaging_dozen.product_tmpl_id.lst_price = 45 + + vals = {'name': 'ROUTE 1', + 'sequence': 1, + 'product_selectable': True, + } + self.route = self.env['stock.location.route'].create(vals) + + vals = {'name': 'OUT => Customer', + 'action': 'move', + 'location_id': self.ref('stock.stock_location_customers'), + 'location_src_id': self.ref('stock.stock_location_output'), + 'procure_method': 'make_to_order', + 'route_id': self.route.id, + 'picking_type_id': self.ref('stock.picking_type_out'), + 'propagate_product_packaging': True} + + self.rule = self.env['procurement.rule'].create(vals) + + vals = {'name': 'Stock => OUT', + 'action': 'move', + 'location_id': self.ref('stock.stock_location_output'), + 'location_src_id': self.ref('stock.stock_location_stock'), + 'procure_method': 'make_to_stock', + 'route_id': self.route.id, + 'picking_type_id': self.ref('stock.picking_type_internal')} + + self.rule_pick = self.env['procurement.rule'].create(vals) + + self.env.ref('product.product_product_3').route_ids |= self.route + + def test_stock_move(self): + """ Create a sale order line with product product_3 + Set product_packaging product_packaging_dozen + Confirm sale order + Check Procurement contains product packaging + Run Procurement + Check Stock Moves contain product packaging + """ + so_line = self.env['sale.order.line'].create( + {'order_id': self.env['sale.order'].create( + {'partner_id': self.env.ref('base.res_partner_2').id}).id, + 'product_id': self.ref('product.product_product_3'), + 'product_uom_qty': 1.0}) + so_line.product_id_change() + so_line.product_packaging = self.product_packaging_dozen + so_line._onchange_product_packaging() + # Confirm Sale Order + so_line.order_id.action_confirm() + procurement = self.env['procurement.order'].search( + [('sale_line_id', '=', so_line.id)]) + self.assertEqual( + 1, + len(procurement), + "There is no procurement") + self.assertEqual( + self.product_packaging_dozen, + procurement.product_packaging, + "The Customer procurement does not contain the product packaging") + # Run Procurement + procurement.run() + # Check Move OUT => Customer + picking_out = so_line.order_id.picking_ids.filtered( + lambda p: p.location_dest_id == + self.env.ref('stock.stock_location_customers')) + self.assertEqual( + 1, + len(picking_out), + "There is no Picking OUT") + move = picking_out.move_lines.filtered( + lambda m: m.product_id == + self.env.ref('product.product_product_3')) + self.assertEqual( + self.product_packaging_dozen, + move.product_packaging, + "Stock Move OUT does not contains product packaging") + # Check Move STOCK => OUT + picking_stock = so_line.order_id.picking_ids.filtered( + lambda p: p.location_dest_id == + self.env.ref('stock.stock_location_output')) + self.assertEqual( + 1, + len(picking_stock), + "There is no Picking Stock") + move = picking_stock.move_lines.filtered( + lambda m: m.product_id == + self.env.ref('product.product_product_3')) + self.assertEqual( + self.product_packaging_dozen, + move.product_packaging, + "Stock Move STOCK does not contains product packaging") + + def test_stock_move_no_propagate(self): + """ Change Procurement Rule to no propagate product packaging + Create a sale order line with product product_3 + Set product_packaging product_packaging_dozen + Confirm sale order + Check Procurement contains product packaging + Run Procurement + Check Stock Moves contain product packaging + """ + self.rule.propagate_product_packaging = False + so_line = self.env['sale.order.line'].create( + {'order_id': self.env['sale.order'].create( + {'partner_id': self.env.ref('base.res_partner_2').id}).id, + 'product_id': self.ref('product.product_product_3'), + 'product_uom_qty': 1.0}) + so_line.product_id_change() + so_line.product_packaging = self.product_packaging_dozen + so_line._onchange_product_packaging() + # Confirm Sale Order + so_line.order_id.action_confirm() + procurement = self.env['procurement.order'].search( + [('sale_line_id', '=', so_line.id)]) + self.assertEqual( + 1, + len(procurement), + "There is no procurement") + self.assertEqual( + self.product_packaging_dozen, + procurement.product_packaging, + "The Customer procurement does not contain the product packaging") + # Run Procurement + procurement.run() + # Check Move OUT => Customer + picking_out = so_line.order_id.picking_ids.filtered( + lambda p: p.location_dest_id == + self.env.ref('stock.stock_location_customers')) + self.assertEqual( + 1, + len(picking_out), + "There is no Picking OUT") + move = picking_out.move_lines.filtered( + lambda m: m.product_id == + self.env.ref('product.product_product_3')) + self.assertEqual( + self.product_packaging_dozen, + move.product_packaging, + "Stock Move OUT does not contains product packaging") + # Check Move STOCK => OUT + picking_stock = so_line.order_id.picking_ids.filtered( + lambda p: p.location_dest_id == + self.env.ref('stock.stock_location_output')) + self.assertEqual( + 1, + len(picking_stock), + "There is no Picking Stock") + move = picking_stock.move_lines.filtered( + lambda m: m.product_id == + self.env.ref('product.product_product_3')) + self.assertEqual( + self.env['product.packaging'], + move.product_packaging, + "Stock Move STOCK does contain product packaging") diff --git a/sale_packaging/views/procurement_rule.xml b/sale_packaging/views/procurement_rule.xml new file mode 100644 index 000000000000..8934bcb0aa54 --- /dev/null +++ b/sale_packaging/views/procurement_rule.xml @@ -0,0 +1,13 @@ + + + + procurement.rule.form.stock.inherit + procurement.rule + + + + + + + + \ No newline at end of file