diff --git a/mrp_packaging_default/README.rst b/mrp_packaging_default/README.rst new file mode 100644 index 000000000..8ee9fd772 --- /dev/null +++ b/mrp_packaging_default/README.rst @@ -0,0 +1,176 @@ +===================== +MRP Default Packaging +===================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:430d1de03606fea9f58a23276a8423eafb1a11f45a74b0bdb94cd84662d0ccf3 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fmanufacture-lightgray.png?logo=github + :target: https://github.com/OCA/manufacture/tree/16.0/mrp_packaging_default + :alt: OCA/manufacture +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/manufacture-16-0/manufacture-16-0-mrp_packaging_default + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/manufacture&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows and encourages the use of packaging within MRP, both +to manufacture products or to create kits. + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +In certain businesses, it is important to know the packaging you will +use to manufacture a product. Example: food wholesale companies. + +For example, imagine you want to create a food basket that includes, +among other things, 1 kg of cheese. It is not the same to put it in +small sliced and vacuum-packed 100g packages as it is to put a whole +cheese ball. Even when the product and the total weight are the same: +1kg of cheese. + +If you are interested in this module, you may also be interested in +``sale_packaging_default``. + +Configuration +============= + +To see the effects of this module, you need to: + +1. Go to *Settings*. +2. Activate *Inventory > Products > Product Packagings*. +3. Optionally, activate also *Units of Measure*. This is not required, + but if you are interested in this module, it's probably because you + use this. +4. Save. + +Usage +===== + +Some component products must exist. Those components will be later +included in the manufactured or kit product. Then, you'll notice the +module effects. + +To create the component products: + +1. Go to *Inventory > Products > Products*. +2. Create a product. +3. Configure its unit of measure (if you enabled that option). +4. Add some line(s) in *Inventory > Packaging*. + +To use this module with **a kit of products**, you need to: + +1. Go to *Inventory > Products > Products*. +2. Create a product that will be the kit. +3. Set *Product Type* "Consumable". +4. Configure its unit of measure (if you enabled that option). +5. Enable *Inventory > Operations > Routes > Manufacture*. +6. Click on *Bill of Materials* button and create a new one. +7. Set *BoM Type* "Kit". +8. Configure the rest of the BoM. When you configure the component + lines, use the new *Packaging* and *Packaging Qty* fields. +9. Go to *Inventory > Delivery Orders (three dots) > New > Planned + Transfer*. +10. Fill the *Delivery Address*. +11. Add one *Operations* line with the kit product you just created. +12. Click on *Mark as TODO*. +13. You will notice that the kit has been replaced by its components, + and each component line includes the packaging and its qty, just + like you configured them in the BoM. + +To use it with **a manufactured product**, instead: + +1. Go to *Inventory > Products > Products*. +2. Create a product; the one that will be manufactured. +3. Set *Product Type* "Storable Product". +4. Configure its unit of measure (if you enabled that option). +5. Enable *Inventory > Operations > Routes > Manufacture*. +6. Click on *Bill of Materials* button and create a new one. +7. Set *BoM Type* "Manufacture this product". +8. Configure the rest of the BoM. When you configure the component + lines, use the new *Packaging* and *Packaging Qty* fields. +9. Go back to the product form. +10. Click on *Reordering Rules* button and create a new one. +11. Set some minimal and maximal quantities. +12. Click on *Order Once*. If you don't see this button, you can also go + to *Inventory > Operations > Run Scheduler > Run Scheduler*. +13. Go to *Manufacturing > Operations > Manufacturing Orders*. You will + see a new MO created from the reordering rule. Open it. +14. See how the *Components* lines contain packaging information, just + like you defined it in the BoM. The same would happen if you created + the MO manually. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Moduon + +Contributors +------------ + +- Jairo Llopis (`Moduon `__) + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +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. + +.. |maintainer-rafaelbn| image:: https://github.com/rafaelbn.png?size=40px + :target: https://github.com/rafaelbn + :alt: rafaelbn +.. |maintainer-yajo| image:: https://github.com/yajo.png?size=40px + :target: https://github.com/yajo + :alt: yajo + +Current `maintainers `__: + +|maintainer-rafaelbn| |maintainer-yajo| + +This module is part of the `OCA/manufacture `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/mrp_packaging_default/__init__.py b/mrp_packaging_default/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/mrp_packaging_default/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/mrp_packaging_default/__manifest__.py b/mrp_packaging_default/__manifest__.py new file mode 100644 index 000000000..cf38a82a3 --- /dev/null +++ b/mrp_packaging_default/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2023 Moduon Team S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) + +{ + "name": "MRP Default Packaging", + "summary": "Include packaging info in MRP by default", + "version": "16.0.1.0.0", + "development_status": "Alpha", + "category": "Manufacturing/Manufacturing", + "website": "https://github.com/OCA/manufacture", + "author": "Moduon, Odoo Community Association (OCA)", + "maintainers": ["rafaelbn", "yajo"], + "license": "LGPL-3", + "depends": ["mrp", "stock_move_packaging_qty"], + "data": [ + "views/mrp_bom_view.xml", + "views/mrp_production_view.xml", + ], +} diff --git a/mrp_packaging_default/i18n/.empty b/mrp_packaging_default/i18n/.empty new file mode 100644 index 000000000..e69de29bb diff --git a/mrp_packaging_default/models/__init__.py b/mrp_packaging_default/models/__init__.py new file mode 100644 index 000000000..cbf159186 --- /dev/null +++ b/mrp_packaging_default/models/__init__.py @@ -0,0 +1,2 @@ +from . import mrp_bom_line +from . import stock_move diff --git a/mrp_packaging_default/models/mrp_bom_line.py b/mrp_packaging_default/models/mrp_bom_line.py new file mode 100644 index 000000000..b2b6c531a --- /dev/null +++ b/mrp_packaging_default/models/mrp_bom_line.py @@ -0,0 +1,67 @@ +# Copyright 2023 Moduon Team S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) +from odoo import api, fields, models + + +class MrpBomLine(models.Model): + _inherit = "mrp.bom.line" + + product_packaging_id = fields.Many2one( + comodel_name="product.packaging", + string="Packaging", + compute="_compute_product_packaging", + store=True, + readonly=False, + domain="[('product_id', '=', product_id)]", + check_company=True, + ) + product_packaging_qty = fields.Float( + string="Packaging Qty.", + compute="_compute_product_packaging", + digits="Product Unit of Measure", + store=True, + readonly=False, + ) + + @api.depends("product_id", "product_qty", "product_uom_id") + def _compute_product_packaging(self): + """Set the appropriate packaging for the product qty.""" + for one in self: + one.product_packaging_id = ( + one.product_id.packaging_ids._find_suitable_product_packaging( + one.product_qty, one.product_uom_id + ) + ) + if not one.product_packaging_id: + one.product_packaging_qty = 0 + continue + uom_qty_per_package = ( + one.product_packaging_id.product_uom_id._compute_quantity( + one.product_packaging_id.qty, one.product_uom_id + ) + ) + one.product_packaging_qty = ( + one.product_packaging_id._check_qty(one.product_qty, one.product_uom_id) + / uom_qty_per_package + ) + + @api.onchange("product_packaging_id", "product_packaging_qty") + def _onchange_product_packaging_set_qty(self): + """When interactively setting a new packaging, set default qty values.""" + if not self.product_packaging_id: + return + self.product_qty = ( + self.product_packaging_qty + * self.product_uom_id._compute_quantity( + self.product_packaging_id.qty, + self.product_packaging_id.product_uom_id, + ) + ) + + @api.onchange("product_id") + def _onchange_product_set_qty_from_packaging(self): + """When interactively setting a new product, set default packaging values.""" + default_packaging = self.product_id.packaging_ids[:1] + if default_packaging: + self.product_uom_id = default_packaging.product_uom_id + self.product_qty = default_packaging.qty diff --git a/mrp_packaging_default/models/stock_move.py b/mrp_packaging_default/models/stock_move.py new file mode 100644 index 000000000..85ea4eaee --- /dev/null +++ b/mrp_packaging_default/models/stock_move.py @@ -0,0 +1,36 @@ +# Copyright 2023 Moduon Team S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) + + +from odoo import api, models + + +class StockMove(models.Model): + _inherit = "stock.move" + + @api.model + def _packaging_vals_from_bom_line(self, vals): + """Fill vals with packaging info from BoM line.""" + try: + bom_line = self.env["mrp.bom.line"].browse(vals["bom_line_id"]) + except KeyError: + # No BoM line, nothing to do + return + vals.update( + { + "product_packaging_id": bom_line.product_packaging_id.id, + "product_packaging_qty": bom_line.product_packaging_qty, + } + ) + + @api.model_create_multi + def create(self, vals_list): + """Inherit packaging from BoM line.""" + for vals in vals_list: + self._packaging_vals_from_bom_line(vals) + return super().create(vals_list) + + def write(self, vals): + """Inherit packaging from BoM line.""" + self._packaging_vals_from_bom_line(vals) + return super().write(vals) diff --git a/mrp_packaging_default/readme/CONFIGURE.md b/mrp_packaging_default/readme/CONFIGURE.md new file mode 100644 index 000000000..df5f4dd0f --- /dev/null +++ b/mrp_packaging_default/readme/CONFIGURE.md @@ -0,0 +1,7 @@ +To see the effects of this module, you need to: + +1. Go to *Settings*. +2. Activate *Inventory > Products > Product Packagings*. +3. Optionally, activate also *Units of Measure*. This is not required, but if + you are interested in this module, it's probably because you use this. +4. Save. diff --git a/mrp_packaging_default/readme/CONTEXT.md b/mrp_packaging_default/readme/CONTEXT.md new file mode 100644 index 000000000..880d87e79 --- /dev/null +++ b/mrp_packaging_default/readme/CONTEXT.md @@ -0,0 +1,10 @@ +In certain businesses, it is important to know the packaging you will use to +manufacture a product. Example: food wholesale companies. + +For example, imagine you want to create a food basket that includes, among +other things, 1 kg of cheese. It is not the same to put it in small sliced and +vacuum-packed 100g packages as it is to put a whole cheese ball. Even when the +product and the total weight are the same: 1kg of cheese. + +If you are interested in this module, you may also be interested in +`sale_packaging_default`. diff --git a/mrp_packaging_default/readme/CONTRIBUTORS.md b/mrp_packaging_default/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..76f3b916a --- /dev/null +++ b/mrp_packaging_default/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Jairo Llopis ([Moduon](https://www.moduon.team/)) diff --git a/mrp_packaging_default/readme/DESCRIPTION.md b/mrp_packaging_default/readme/DESCRIPTION.md new file mode 100644 index 000000000..d4d8764f5 --- /dev/null +++ b/mrp_packaging_default/readme/DESCRIPTION.md @@ -0,0 +1,2 @@ +This module allows and encourages the use of packaging within MRP, both to +manufacture products or to create kits. diff --git a/mrp_packaging_default/readme/USAGE.md b/mrp_packaging_default/readme/USAGE.md new file mode 100644 index 000000000..9e164a305 --- /dev/null +++ b/mrp_packaging_default/readme/USAGE.md @@ -0,0 +1,49 @@ +Some component products must exist. Those components will be later included in the manufactured or kit product. Then, you'll notice the module effects. + +To create the component products: + +1. Go to *Inventory > Products > Products*. +2. Create a product. +3. Configure its unit of measure (if you enabled that option). +4. Add some line(s) in *Inventory > Packaging*. + +To use this module with **a kit of products**, you need to: + +1. Go to *Inventory > Products > Products*. +2. Create a product that will be the kit. +3. Set *Product Type* "Consumable". +4. Configure its unit of measure (if you enabled that option). +5. Enable *Inventory > Operations > Routes > Manufacture*. +7. Click on *Bill of Materials* button and create a new one. +8. Set *BoM Type* "Kit". +9. Configure the rest of the BoM. When you configure the component lines, use + the new *Packaging* and *Packaging Qty* fields. +10. Go to *Inventory > Delivery Orders (three dots) > New > Planned Transfer*. +11. Fill the *Delivery Address*. +12. Add one *Operations* line with the kit product you just created. +13. Click on *Mark as TODO*. +14. You will notice that the kit has been replaced by its components, and each + component line includes the packaging and its qty, just like you configured + them in the BoM. + +To use it with **a manufactured product**, instead: + +1. Go to *Inventory > Products > Products*. +2. Create a product; the one that will be manufactured. +3. Set *Product Type* "Storable Product". +4. Configure its unit of measure (if you enabled that option). +5. Enable *Inventory > Operations > Routes > Manufacture*. +7. Click on *Bill of Materials* button and create a new one. +8. Set *BoM Type* "Manufacture this product". +9. Configure the rest of the BoM. When you configure the component lines, use + the new *Packaging* and *Packaging Qty* fields. +10. Go back to the product form. +11. Click on *Reordering Rules* button and create a new one. +12. Set some minimal and maximal quantities. +13. Click on *Order Once*. If you don't see this button, you can also go to + *Inventory > Operations > Run Scheduler > Run Scheduler*. +14. Go to *Manufacturing > Operations > Manufacturing Orders*. You will see a + new MO created from the reordering rule. Open it. +15. See how the *Components* lines contain packaging information, just like you + defined it in the BoM. The same would happen if you created the MO + manually. diff --git a/mrp_packaging_default/static/description/icon.png b/mrp_packaging_default/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/mrp_packaging_default/static/description/icon.png differ diff --git a/mrp_packaging_default/static/description/index.html b/mrp_packaging_default/static/description/index.html new file mode 100644 index 000000000..488fd6d76 --- /dev/null +++ b/mrp_packaging_default/static/description/index.html @@ -0,0 +1,512 @@ + + + + + + +MRP Default Packaging + + + +
+

MRP Default Packaging

+ + +

Alpha License: LGPL-3 OCA/manufacture Translate me on Weblate Try me on Runboat

+

This module allows and encourages the use of packaging within MRP, both +to manufacture products or to create kits.

+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

Table of contents

+ +
+

Use Cases / Context

+

In certain businesses, it is important to know the packaging you will +use to manufacture a product. Example: food wholesale companies.

+

For example, imagine you want to create a food basket that includes, +among other things, 1 kg of cheese. It is not the same to put it in +small sliced and vacuum-packed 100g packages as it is to put a whole +cheese ball. Even when the product and the total weight are the same: +1kg of cheese.

+

If you are interested in this module, you may also be interested in +sale_packaging_default.

+
+
+

Configuration

+

To see the effects of this module, you need to:

+
    +
  1. Go to Settings.
  2. +
  3. Activate Inventory > Products > Product Packagings.
  4. +
  5. Optionally, activate also Units of Measure. This is not required, +but if you are interested in this module, it’s probably because you +use this.
  6. +
  7. Save.
  8. +
+
+
+

Usage

+

Some component products must exist. Those components will be later +included in the manufactured or kit product. Then, you’ll notice the +module effects.

+

To create the component products:

+
    +
  1. Go to Inventory > Products > Products.
  2. +
  3. Create a product.
  4. +
  5. Configure its unit of measure (if you enabled that option).
  6. +
  7. Add some line(s) in Inventory > Packaging.
  8. +
+

To use this module with a kit of products, you need to:

+
    +
  1. Go to Inventory > Products > Products.
  2. +
  3. Create a product that will be the kit.
  4. +
  5. Set Product Type “Consumable”.
  6. +
  7. Configure its unit of measure (if you enabled that option).
  8. +
  9. Enable Inventory > Operations > Routes > Manufacture.
  10. +
  11. Click on Bill of Materials button and create a new one.
  12. +
  13. Set BoM Type “Kit”.
  14. +
  15. Configure the rest of the BoM. When you configure the component +lines, use the new Packaging and Packaging Qty fields.
  16. +
  17. Go to Inventory > Delivery Orders (three dots) > New > Planned +Transfer.
  18. +
  19. Fill the Delivery Address.
  20. +
  21. Add one Operations line with the kit product you just created.
  22. +
  23. Click on Mark as TODO.
  24. +
  25. You will notice that the kit has been replaced by its components, +and each component line includes the packaging and its qty, just +like you configured them in the BoM.
  26. +
+

To use it with a manufactured product, instead:

+
    +
  1. Go to Inventory > Products > Products.
  2. +
  3. Create a product; the one that will be manufactured.
  4. +
  5. Set Product Type “Storable Product”.
  6. +
  7. Configure its unit of measure (if you enabled that option).
  8. +
  9. Enable Inventory > Operations > Routes > Manufacture.
  10. +
  11. Click on Bill of Materials button and create a new one.
  12. +
  13. Set BoM Type “Manufacture this product”.
  14. +
  15. Configure the rest of the BoM. When you configure the component +lines, use the new Packaging and Packaging Qty fields.
  16. +
  17. Go back to the product form.
  18. +
  19. Click on Reordering Rules button and create a new one.
  20. +
  21. Set some minimal and maximal quantities.
  22. +
  23. Click on Order Once. If you don’t see this button, you can also go +to Inventory > Operations > Run Scheduler > Run Scheduler.
  24. +
  25. Go to Manufacturing > Operations > Manufacturing Orders. You will +see a new MO created from the reordering rule. Open it.
  26. +
  27. See how the Components lines contain packaging information, just +like you defined it in the BoM. The same would happen if you created +the MO manually.
  28. +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Moduon
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

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.

+

Current maintainers:

+

rafaelbn yajo

+

This module is part of the OCA/manufacture project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/mrp_packaging_default/tests/__init__.py b/mrp_packaging_default/tests/__init__.py new file mode 100644 index 000000000..dd85c6bc8 --- /dev/null +++ b/mrp_packaging_default/tests/__init__.py @@ -0,0 +1 @@ +from . import test_mrp_packaging_default diff --git a/mrp_packaging_default/tests/test_mrp_packaging_default.py b/mrp_packaging_default/tests/test_mrp_packaging_default.py new file mode 100644 index 000000000..654abd5d2 --- /dev/null +++ b/mrp_packaging_default/tests/test_mrp_packaging_default.py @@ -0,0 +1,262 @@ +# Copyright 2023 Moduon Team S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) +from odoo import Command +from odoo.tests.common import Form + +from odoo.addons.mrp.tests.common import TestMrpCommon + + +class MrpPackagingDefaultCase(TestMrpCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + # Enable product packaging + cls.env.ref("base.group_user")._apply_group( + cls.env.ref("product.group_stock_packaging") + ) + # Sandwich ingredients + cls.tomato_product = cls.env["product.product"].create( + { + "name": "Tomato", + "type": "product", + "list_price": 5.0, + "categ_id": cls.product_category.id, + "uom_id": cls.env.ref("uom.product_uom_kgm").id, + "uom_po_id": cls.env.ref("uom.product_uom_kgm").id, + "packaging_ids": [ + Command.create({"name": "Box", "qty": 3}), + Command.create({"name": "Unit", "qty": 0.2}), + Command.create({"name": "Slice", "qty": 0.05}), + ], + } + ) + cls.lettuce_product = cls.env["product.product"].create( + { + "name": "Lettuce", + "type": "product", + "list_price": 1.0, + "categ_id": cls.product_category.id, + "uom_id": cls.env.ref("uom.product_uom_kgm").id, + "uom_po_id": cls.env.ref("uom.product_uom_kgm").id, + "packaging_ids": [ + Command.create({"name": "Leaf", "qty": 0.05}), + ], + } + ) + cls.bread_product = cls.env["product.product"].create( + { + "name": "Bread", + "type": "product", + "list_price": 3.0, + "categ_id": cls.product_category.id, + "uom_id": cls.env.ref("uom.product_uom_kgm").id, + "uom_po_id": cls.env.ref("uom.product_uom_kgm").id, + "packaging_ids": [ + Command.create({"name": "Slice", "qty": 0.1}), + ], + } + ) + + def create_sandwich(self, cooked): + """Create a sandwich product and its BoMs. + + If the sandwich is cooked, then we sell it as a manufactured product. + + Otherwise, it's a DIY sandwich; we sell the ingredients and you cook it + yourself. + """ + sandwich = self.env["product.product"].create( + { + "name": "Sandwich", + "type": "product" if cooked else "consu", + "list_price": 10.0 if cooked else 7.0, + "categ_id": self.product_category.id, + "uom_id": self.env.ref("uom.product_uom_unit").id, + "uom_po_id": self.env.ref("uom.product_uom_unit").id, + "route_ids": [ + Command.link(self.warehouse_1.manufacture_pull_id.route_id.id) + ], + } + ) + bom_f = Form(self.env["mrp.bom"]) + bom_f.product_tmpl_id = sandwich.product_tmpl_id + bom_f.type = "normal" if cooked else "phantom" + with bom_f.bom_line_ids.new() as line_f: + line_f.product_id = self.tomato_product + self.assertEqual(line_f.product_packaging_id.name, "Box") + self.assertEqual(line_f.product_packaging_qty, 1) + self.assertEqual(line_f.product_qty, 3) + line_f.product_packaging_id = self.tomato_product.packaging_ids[2] + line_f.product_packaging_qty = 2 + self.assertEqual(line_f.product_packaging_id.name, "Slice") + self.assertEqual(line_f.product_qty, 0.1) + with bom_f.bom_line_ids.new() as line_f: + line_f.product_id = self.lettuce_product + self.assertEqual(line_f.product_packaging_id.name, "Leaf") + self.assertEqual(line_f.product_packaging_qty, 1) + self.assertEqual(line_f.product_qty, 0.05) + line_f.product_packaging_qty = 4 + self.assertEqual(line_f.product_qty, 0.2) + with bom_f.bom_line_ids.new() as line_f: + line_f.product_id = self.bread_product + self.assertEqual(line_f.product_packaging_id.name, "Slice") + self.assertEqual(line_f.product_packaging_qty, 1) + self.assertEqual(line_f.product_qty, 0.1) + line_f.product_packaging_qty = 2 + self.assertEqual(line_f.product_qty, 0.2) + bom_f.save() + return sandwich + + def test_deliver_diy_sandwich(self): + """Deliver a DIY sandwich. + + To deliver a sandwich, we would usually create a sale order. However, + `sale` is not among this module's dependencies, so we create the + picking directly. + + In product kits, the standard behavior es that when you confirm the + picking, the system replaces the kit with its components, linked to + each BoM line. + + Here we just exercise that behavior and assert that the packaging data + matches the one that comes from the BoM. + """ + sandwich = self.create_sandwich(cooked=False) + picking_f = Form(self.env["stock.picking"].with_user(self.user_stock_user)) + picking_f.partner_id = self.partner_1 + picking_f.picking_type_id = self.warehouse_1.out_type_id + with picking_f.move_ids_without_package.new() as move_f: + move_f.product_id = sandwich + move_f.product_uom_qty = 2 + picking = picking_f.save() + picking.action_confirm() + self.assertRecordValues( + picking.move_ids_without_package, + [ + { + "bom_line_id": sandwich.bom_ids[0].bom_line_ids[0].id, + "description_bom_line": "Sandwich - 1/3", + "product_id": self.tomato_product.id, + "product_packaging_id": self.tomato_product.packaging_ids[2].id, + "product_packaging_qty": 2.0, + "product_uom": self.env.ref("uom.product_uom_kgm").id, + "product_uom_qty": 0.1, + }, + { + "bom_line_id": sandwich.bom_ids[0].bom_line_ids[1].id, + "description_bom_line": "Sandwich - 2/3", + "product_id": self.lettuce_product.id, + "product_packaging_id": self.lettuce_product.packaging_ids[0].id, + "product_packaging_qty": 4.0, + "product_uom": self.env.ref("uom.product_uom_kgm").id, + "product_uom_qty": 0.2, + }, + { + "bom_line_id": sandwich.bom_ids[0].bom_line_ids[2].id, + "description_bom_line": "Sandwich - 3/3", + "product_id": self.bread_product.id, + "product_packaging_id": self.bread_product.packaging_ids[0].id, + "product_packaging_qty": 2, + "product_uom": self.env.ref("uom.product_uom_kgm").id, + "product_uom_qty": 0.2, + }, + ], + ) + + def test_auto_procure_cooked_sandwich(self): + """Procure a cooked sandwich. + + This sandwich is a manufactured product. It has reordering rules, so + the procurement scheduler will create a manufacturing order. + + We will just exercise that scenario and make sure the packaging data in + the manufacturing order matches the one in the BoM. + """ + sandwich = self.create_sandwich(cooked=True) + # Define a reordering rule for the cooked sandwich + rule_f = Form(self.env["stock.warehouse.orderpoint"]) + rule_f.product_id = sandwich + rule_f.product_min_qty = 4 + rule_f.product_max_qty = 10 + rule_f.save() + # Run the stock scheduler + self.env["procurement.group"].run_scheduler() + # Check the created manufacturing order + mo = self.env["mrp.production"].search([("product_id", "=", sandwich.id)]) + self.assertEqual(mo.state, "confirmed") + self.assertEqual(mo.qty_producing, 0) + self.assertEqual(mo.product_qty, 10) + self.assertEqual(mo.bom_id, sandwich.bom_ids) + self.assertRecordValues( + mo.move_raw_ids, + [ + { + "bom_line_id": sandwich.bom_ids[0].bom_line_ids[0].id, + "product_id": self.tomato_product.id, + "product_packaging_id": self.tomato_product.packaging_ids[2].id, + "product_packaging_qty": 2.0, + "product_uom": self.env.ref("uom.product_uom_kgm").id, + "product_uom_qty": 0.1, + }, + { + "bom_line_id": sandwich.bom_ids[0].bom_line_ids[1].id, + "product_id": self.lettuce_product.id, + "product_packaging_id": self.lettuce_product.packaging_ids[0].id, + "product_packaging_qty": 4.0, + "product_uom": self.env.ref("uom.product_uom_kgm").id, + "product_uom_qty": 0.2, + }, + { + "bom_line_id": sandwich.bom_ids[0].bom_line_ids[2].id, + "product_id": self.bread_product.id, + "product_packaging_id": self.bread_product.packaging_ids[0].id, + "product_packaging_qty": 2, + "product_uom": self.env.ref("uom.product_uom_kgm").id, + "product_uom_qty": 0.2, + }, + ], + ) + + def test_manual_mo_cooked_sandwich(self): + """Create a manufacturing order for a cooked sandwich, interactively.""" + sandwich = self.create_sandwich(cooked=True) + # Create a manufacturing order + mo_f = Form(self.env["mrp.production"]) + mo_f.product_id = sandwich + self.assertEqual(mo_f.bom_id, sandwich.bom_ids) + mo_f.product_qty = 10 + mo = mo_f.save() + # Check the created manufacturing order + self.assertEqual(mo.state, "draft") + self.assertEqual(mo.qty_producing, 0) + self.assertEqual(mo.product_qty, 10) + self.assertEqual(mo.bom_id, sandwich.bom_ids) + self.assertRecordValues( + mo.move_raw_ids, + [ + { + "bom_line_id": sandwich.bom_ids[0].bom_line_ids[0].id, + "product_id": self.tomato_product.id, + "product_packaging_id": self.tomato_product.packaging_ids[2].id, + "product_packaging_qty": 2.0, + "product_uom": self.env.ref("uom.product_uom_kgm").id, + "product_uom_qty": 0.1, + }, + { + "bom_line_id": sandwich.bom_ids[0].bom_line_ids[1].id, + "product_id": self.lettuce_product.id, + "product_packaging_id": self.lettuce_product.packaging_ids[0].id, + "product_packaging_qty": 4.0, + "product_uom": self.env.ref("uom.product_uom_kgm").id, + "product_uom_qty": 0.2, + }, + { + "bom_line_id": sandwich.bom_ids[0].bom_line_ids[2].id, + "product_id": self.bread_product.id, + "product_packaging_id": self.bread_product.packaging_ids[0].id, + "product_packaging_qty": 2, + "product_uom": self.env.ref("uom.product_uom_kgm").id, + "product_uom_qty": 0.2, + }, + ], + ) diff --git a/mrp_packaging_default/views/mrp_bom_view.xml b/mrp_packaging_default/views/mrp_bom_view.xml new file mode 100644 index 000000000..94458974e --- /dev/null +++ b/mrp_packaging_default/views/mrp_bom_view.xml @@ -0,0 +1,28 @@ + + + + + Product packaging + mrp.bom + + + + + + + + + diff --git a/mrp_packaging_default/views/mrp_production_view.xml b/mrp_packaging_default/views/mrp_production_view.xml new file mode 100644 index 000000000..5cab7031d --- /dev/null +++ b/mrp_packaging_default/views/mrp_production_view.xml @@ -0,0 +1,44 @@ + + + + + Product packaging + mrp.production + + + + + + + + + + + + + diff --git a/setup/mrp_packaging_default/odoo/addons/mrp_packaging_default b/setup/mrp_packaging_default/odoo/addons/mrp_packaging_default new file mode 120000 index 000000000..91b2441b4 --- /dev/null +++ b/setup/mrp_packaging_default/odoo/addons/mrp_packaging_default @@ -0,0 +1 @@ +../../../../mrp_packaging_default \ No newline at end of file diff --git a/setup/mrp_packaging_default/setup.py b/setup/mrp_packaging_default/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/mrp_packaging_default/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)