diff --git a/mrp_unbuild_tracked_raw_material/README.rst b/mrp_unbuild_tracked_raw_material/README.rst new file mode 100644 index 000000000..86af614f3 --- /dev/null +++ b/mrp_unbuild_tracked_raw_material/README.rst @@ -0,0 +1,122 @@ +================================ +Mrp Unbuild Tracked Raw Material +================================ + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fmanufacture-lightgray.png?logo=github + :target: https://github.com/OCA/manufacture/tree/13.0/mrp_unbuild_tracked_raw_material + :alt: OCA/manufacture +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/manufacture-13-0/manufacture-13-0-mrp_unbuild_tracked_raw_material + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/129/13.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Odoo has a limitation on tracked product's components +which are not manufactured in the ERP. + +When you try to do it, you get this warning: + +Some of your components are tracked, you have to specify a manufacturing order in order to retrieve the correct components. + +Unfortunately, it doesn't cover all the use cases. + +Example: +You receive eggs and you want to unbuild them in 2 parts: + + - yellow part + - white part + +Each of the parts are tracked and not linked to a previous manufacturing order +because, you don't build the eggs yourself, you subcontract it to a chicken. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +Customize method `_prepare_lots_for_purchased_unbuild()` to define your own data lot + +Usage +===== + +# Check 'Allow Unbuild Purchased' field in Inventory tab on product form +for any product with a bom you didn't manufactured but you want unbuild. + +# Go to Manufacturing > Operations > Unbuild Orders + +# Encode an unbuild order with the product used in first step + +Known issues / Roadmap +====================== + +This module doesn't take account product `allow_unbuild_purchased` checked +which use `serial` tracking + +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 smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Akretion + +Contributors +~~~~~~~~~~~~ + +Akretion: + + * David Béal + +* Pimolnat Suntian + +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-bealdav| image:: https://github.com/bealdav.png?size=40px + :target: https://github.com/bealdav + :alt: bealdav + +Current `maintainer `__: + +|maintainer-bealdav| + +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_unbuild_tracked_raw_material/__init__.py b/mrp_unbuild_tracked_raw_material/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/mrp_unbuild_tracked_raw_material/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/mrp_unbuild_tracked_raw_material/__manifest__.py b/mrp_unbuild_tracked_raw_material/__manifest__.py new file mode 100644 index 000000000..10895f517 --- /dev/null +++ b/mrp_unbuild_tracked_raw_material/__manifest__.py @@ -0,0 +1,17 @@ +# Copyright (C) 2019 Akretion (http://www.akretion.com). All Rights Reserved +# @author David BEAL +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Mrp Unbuild Tracked Raw Material", + "summary": "Allow to unbuild tracked purchased products", + "author": "Akretion, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/manufacture", + "category": "Manufacturing", + "version": "13.0.1.0.0", + "license": "AGPL-3", + "depends": ["mrp"], + "maintainers": ["bealdav"], + "data": ["views/product_view.xml"], + "installable": True, +} diff --git a/mrp_unbuild_tracked_raw_material/i18n/mrp_unbuild_tracked_raw_material.pot b/mrp_unbuild_tracked_raw_material/i18n/mrp_unbuild_tracked_raw_material.pot new file mode 100644 index 000000000..6a3cda1ff --- /dev/null +++ b/mrp_unbuild_tracked_raw_material/i18n/mrp_unbuild_tracked_raw_material.pot @@ -0,0 +1,64 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mrp_unbuild_tracked_raw_material +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: mrp_unbuild_tracked_raw_material +#: code:addons/mrp_unbuild_tracked_raw_material/models/unbuild.py:45 +#, python-format +msgid "%s \n" +"\n" +"%s" +msgstr "" + +#. module: mrp_unbuild_tracked_raw_material +#: model:ir.model.fields,field_description:mrp_unbuild_tracked_raw_material.field_product_product__allow_unbuild_purchased +#: model:ir.model.fields,field_description:mrp_unbuild_tracked_raw_material.field_product_template__allow_unbuild_purchased +msgid "Allow Unbuild Purchased" +msgstr "" + +#. module: mrp_unbuild_tracked_raw_material +#: model:ir.model.fields,help:mrp_unbuild_tracked_raw_material.field_product_product__allow_unbuild_purchased +#: model:ir.model.fields,help:mrp_unbuild_tracked_raw_material.field_product_template__allow_unbuild_purchased +msgid "If checked, unbuild orders doesn't assume a previous manufacturing order have built this product.\n" +"In this case it's a purchased product and you want unbuild it" +msgstr "" + +#. module: mrp_unbuild_tracked_raw_material +#: model:ir.model,name:mrp_unbuild_tracked_raw_material.model_product_template +msgid "Product Template" +msgstr "" + +#. module: mrp_unbuild_tracked_raw_material +#: code:addons/mrp_unbuild_tracked_raw_material/models/unbuild.py:108 +#, python-format +msgid "Product has been unbuilt without previous manufacturing order" +msgstr "" + +#. module: mrp_unbuild_tracked_raw_material +#: model:ir.model,name:mrp_unbuild_tracked_raw_material.model_mrp_unbuild +msgid "Unbuild Order" +msgstr "" + +#. module: mrp_unbuild_tracked_raw_material +#: code:addons/mrp_unbuild_tracked_raw_material/models/unbuild.py:43 +#, python-format +msgid "Unbuild Purchased" +msgstr "" + +#. module: mrp_unbuild_tracked_raw_material +#: code:addons/mrp_unbuild_tracked_raw_material/models/unbuild.py:82 +#, python-format +msgid "Unbuild of component of tracked as serial is not supported for now: contact maintainers of 'mrp_unbuild_tracked_raw_material' module if you want this feature" +msgstr "" + diff --git a/mrp_unbuild_tracked_raw_material/models/__init__.py b/mrp_unbuild_tracked_raw_material/models/__init__.py new file mode 100644 index 000000000..7adb5e1e2 --- /dev/null +++ b/mrp_unbuild_tracked_raw_material/models/__init__.py @@ -0,0 +1,2 @@ +from . import product +from . import unbuild diff --git a/mrp_unbuild_tracked_raw_material/models/product.py b/mrp_unbuild_tracked_raw_material/models/product.py new file mode 100644 index 000000000..f6238f996 --- /dev/null +++ b/mrp_unbuild_tracked_raw_material/models/product.py @@ -0,0 +1,15 @@ +# Copyright (C) 2019 Akretion (http://www.akretion.com). All Rights Reserved +# @author David BEAL +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + allow_unbuild_purchased = fields.Boolean( + help="If checked, unbuild orders doesn't assume a previous " + "manufacturing order have built this product.\n" + "In this case it's a purchased product and you want unbuild it" + ) diff --git a/mrp_unbuild_tracked_raw_material/models/unbuild.py b/mrp_unbuild_tracked_raw_material/models/unbuild.py new file mode 100644 index 000000000..78665ce54 --- /dev/null +++ b/mrp_unbuild_tracked_raw_material/models/unbuild.py @@ -0,0 +1,134 @@ +# Copyright (C) 2019 Akretion (http://www.akretion.com). All Rights Reserved +# @author David BEAL +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from datetime import datetime + +from odoo import _, models +from odoo.exceptions import UserError +from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT as DT_FORMAT + +logger = logging.getLogger(__name__) + +MESSAGE = ( + "Some of your components are tracked, you have to specify " + "a manufacturing order in order to retrieve the correct components." +) +ALTER_MESSAGE = ( + "Alternatively, you may unbuild '%s' tracked product " + "if you set it as '%s'.\n" + "In this case lots'll be automatically created for you." +) + + +class MrpUnbuild(models.Model): + _inherit = "mrp.unbuild" + + def action_unbuild(self): + """ We need to catch raise behavior when tracked products + are unbuild without an original manufacturing order and + go on with another workflow with + _bypass_tracked_product_without_mo() + """ + try: + res = super().action_unbuild() + except UserError as err: + # Unbuild is impossible because of MESSAGE + # original condition of this raise is : + # any(produce_move.has_tracking != 'none' + # and not self.mo_id for produce_move in produce_moves) + if hasattr(err, "name") and err.name == _(MESSAGE): + if self.product_id.allow_unbuild_purchased: + # In this case it becomes possible to unbuild + return self._bypass_tracked_product_without_mo() + # Here other option to resolve unbuild conditions + new_message = _(ALTER_MESSAGE) % ( + self.product_id.name, + _("Unbuild Purchased"), + ) + # We teach user the 2 conditions that make unbuild possible + raise UserError(_("%s \n\n%s") % (err.name, new_message)) + # Here is the Odoo native raise + raise err + return res + + def _bypass_tracked_product_without_mo(self): + # These moves are already generated with call to + # super().action_unbuild(). We catch them + consume_move = self.env["stock.move"].search( + [("consume_unbuild_id", "=", self.id)] + ) + produce_moves = self.env["stock.move"].search([("unbuild_id", "=", self.id)]) + + # Comes from + # https://github.com/OCA/ocb/blob/12.0/addons/mrp/models/... + # mrp_unbuild.py#L117 + if consume_move: + if consume_move.has_tracking != "none": + self.env["stock.move.line"].create( + { + "move_id": consume_move.id, + "lot_id": self.lot_id.id, + "qty_done": consume_move.product_uom_qty, + "product_id": consume_move.product_id.id, + "product_uom_id": consume_move.product_uom.id, + "location_id": consume_move.location_id.id, + "location_dest_id": consume_move.location_dest_id.id, + } + ) + else: + consume_move.quantity_done = consume_move.product_uom_qty + consume_move._action_done() + + # Comment from odoo original module: + # TODO: Will fail if user do more than one unbuild with lot + # on the same MO. Need to check what other unbuild has aready took + for produce_move in produce_moves: + if produce_move.has_tracking != "none": + if produce_move.product_id.tracking == "serial": + # TODO + raise UserError( + _( + "Unbuild of component of tracked as serial " + "is not supported for now: contact maintainers of " + "'mrp_unbuild_tracked_raw_material' module " + "if you want this feature" + ) + ) + lot = self.env["stock.production.lot"].create( + self._prepare_lots_for_purchased_unbuild(produce_move.product_id) + ) + self.env["stock.move.line"].create( + { + "move_id": produce_move.id, + "lot_id": lot.id, + "qty_done": produce_move.product_uom_qty, + "product_id": produce_move.product_id.id, + "product_uom_id": produce_move.product_uom.id, + "location_id": produce_move.location_id.id, + "location_dest_id": produce_move.location_dest_id.id, + } + ) + else: + produce_move.quantity_done = produce_move.product_uom_qty + # comes from native code + produce_moves._action_done() + produced_move_line_ids = produce_moves.mapped("move_line_ids").filtered( + lambda ml: ml.qty_done > 0 + ) + consume_move.move_line_ids.write( + {"produce_line_ids": [(6, 0, produced_move_line_ids.ids)]} + ) + self.message_post( + body=_("Product has been unbuilt without previous manufacturing order") + ) + return self.write({"state": "done"}) + + def _prepare_lots_for_purchased_unbuild(self, product): + # Customize your data lot with your own code + return { + "name": datetime.now().strftime(DT_FORMAT), + "product_id": product.id, + "company_id": self.env.company.id, + } diff --git a/mrp_unbuild_tracked_raw_material/readme/CONFIGURE.rst b/mrp_unbuild_tracked_raw_material/readme/CONFIGURE.rst new file mode 100644 index 000000000..573718713 --- /dev/null +++ b/mrp_unbuild_tracked_raw_material/readme/CONFIGURE.rst @@ -0,0 +1 @@ +Customize method `_prepare_lots_for_purchased_unbuild()` to define your own data lot diff --git a/mrp_unbuild_tracked_raw_material/readme/CONTRIBUTORS.rst b/mrp_unbuild_tracked_raw_material/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..e8d004e6d --- /dev/null +++ b/mrp_unbuild_tracked_raw_material/readme/CONTRIBUTORS.rst @@ -0,0 +1,5 @@ +Akretion: + + * David Béal + +* Pimolnat Suntian diff --git a/mrp_unbuild_tracked_raw_material/readme/DESCRIPTION.rst b/mrp_unbuild_tracked_raw_material/readme/DESCRIPTION.rst new file mode 100644 index 000000000..f340cd97a --- /dev/null +++ b/mrp_unbuild_tracked_raw_material/readme/DESCRIPTION.rst @@ -0,0 +1,17 @@ +Odoo has a limitation on tracked product's components +which are not manufactured in the ERP. + +When you try to do it, you get this warning: + +Some of your components are tracked, you have to specify a manufacturing order in order to retrieve the correct components. + +Unfortunately, it doesn't cover all the use cases. + +Example: +You receive eggs and you want to unbuild them in 2 parts: + + - yellow part + - white part + +Each of the parts are tracked and not linked to a previous manufacturing order +because, you don't build the eggs yourself, you subcontract it to a chicken. diff --git a/mrp_unbuild_tracked_raw_material/readme/ROADMAP.rst b/mrp_unbuild_tracked_raw_material/readme/ROADMAP.rst new file mode 100644 index 000000000..00031023f --- /dev/null +++ b/mrp_unbuild_tracked_raw_material/readme/ROADMAP.rst @@ -0,0 +1,2 @@ +This module doesn't take account product `allow_unbuild_purchased` checked +which use `serial` tracking diff --git a/mrp_unbuild_tracked_raw_material/readme/USAGE.rst b/mrp_unbuild_tracked_raw_material/readme/USAGE.rst new file mode 100644 index 000000000..015ace3d8 --- /dev/null +++ b/mrp_unbuild_tracked_raw_material/readme/USAGE.rst @@ -0,0 +1,6 @@ +# Check 'Allow Unbuild Purchased' field in Inventory tab on product form +for any product with a bom you didn't manufactured but you want unbuild. + +# Go to Manufacturing > Operations > Unbuild Orders + +# Encode an unbuild order with the product used in first step diff --git a/mrp_unbuild_tracked_raw_material/static/description/icon.png b/mrp_unbuild_tracked_raw_material/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/mrp_unbuild_tracked_raw_material/static/description/icon.png differ diff --git a/mrp_unbuild_tracked_raw_material/static/description/index.html b/mrp_unbuild_tracked_raw_material/static/description/index.html new file mode 100644 index 000000000..eed2a8197 --- /dev/null +++ b/mrp_unbuild_tracked_raw_material/static/description/index.html @@ -0,0 +1,460 @@ + + + + + + +Mrp Unbuild Tracked Raw Material + + + +
+

Mrp Unbuild Tracked Raw Material

+ + +

Beta License: AGPL-3 OCA/manufacture Translate me on Weblate Try me on Runbot

+

Odoo has a limitation on tracked product’s components +which are not manufactured in the ERP.

+

When you try to do it, you get this warning:

+

Some of your components are tracked, you have to specify a manufacturing order in order to retrieve the correct components.

+

Unfortunately, it doesn’t cover all the use cases.

+

Example: +You receive eggs and you want to unbuild them in 2 parts:

+
+
    +
  • yellow part
  • +
  • white part
  • +
+
+

Each of the parts are tracked and not linked to a previous manufacturing order +because, you don’t build the eggs yourself, you subcontract it to a chicken.

+

Table of contents

+ +
+

Configuration

+

Customize method _prepare_lots_for_purchased_unbuild() to define your own data lot

+
+
+

Usage

+

# Check ‘Allow Unbuild Purchased’ field in Inventory tab on product form +for any product with a bom you didn’t manufactured but you want unbuild.

+

# Go to Manufacturing > Operations > Unbuild Orders

+

# Encode an unbuild order with the product used in first step

+
+
+

Known issues / Roadmap

+

This module doesn’t take account product allow_unbuild_purchased checked +which use serial tracking

+
+
+

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 smashing it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Akretion
  • +
+
+
+

Contributors

+

Akretion:

+
+ +
+ +
+
+

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 maintainer:

+

bealdav

+

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_unbuild_tracked_raw_material/tests/__init__.py b/mrp_unbuild_tracked_raw_material/tests/__init__.py new file mode 100644 index 000000000..96a4196aa --- /dev/null +++ b/mrp_unbuild_tracked_raw_material/tests/__init__.py @@ -0,0 +1 @@ +from . import test_unbuild diff --git a/mrp_unbuild_tracked_raw_material/tests/test_unbuild.py b/mrp_unbuild_tracked_raw_material/tests/test_unbuild.py new file mode 100644 index 000000000..6edf404f0 --- /dev/null +++ b/mrp_unbuild_tracked_raw_material/tests/test_unbuild.py @@ -0,0 +1,82 @@ +# Copyright (C) 2019 Akretion (http://www.akretion.com). All Rights Reserved +# @author David BEAL +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from datetime import datetime + +# from odoo.tests.common import TransactionCase +from odoo.addons.mrp.tests.common import TestMrpCommon + + +class TestUnbuildUnmanufacturedProduct(TestMrpCommon): + def setUp(self, *args, **kwargs): + self.company = self.env.ref("base.main_company") + self.loc = self.env.ref("stock.stock_location_stock") + super().setUp(*args, **kwargs) + + def create_data(self): + prd_to_build = self.env["product.product"].create( + { + "name": "To unbuild", + "type": "product", + "allow_unbuild_purchased": True, + "tracking": "lot", + } + ) + prd_to_use1 = self.env["product.product"].create( + {"name": "component 1", "type": "product", "tracking": "lot"} + ) + prd_to_use2 = self.env["product.product"].create( + {"name": "component 2", "type": "product", "tracking": "none"} + ) + bom = self.env["mrp.bom"].create( + { + "product_id": prd_to_build.id, + "product_tmpl_id": prd_to_build.product_tmpl_id.id, + "product_uom_id": self.uom_unit.id, + "product_qty": 1.0, + "type": "normal", + "bom_line_ids": [ + (0, 0, {"product_id": prd_to_use2.id, "product_qty": 1}), + (0, 0, {"product_id": prd_to_use1.id, "product_qty": 2}), + ], + } + ) + return (bom, prd_to_build, prd_to_use1, prd_to_use2) + + def test_unbuild(self): + bom, prd_to_build, prd_to_use1, prd_to_use2 = self.create_data() + lot = self.env["stock.production.lot"].create( + { + "name": "%s" % datetime.now(), + "product_id": prd_to_build.id, + "company_id": self.company.id, + } + ) + self.env["stock.quant"]._update_available_quantity( + prd_to_build, self.loc, 10, lot_id=lot + ) + unbuild = self.env["mrp.unbuild"].create( + { + "product_id": prd_to_build.id, + "bom_id": bom.id, + "product_qty": 1.0, + "lot_id": lot.id, + "product_uom_id": self.uom_unit.id, + "location_id": self.loc.id, + "location_dest_id": self.loc.id, + } + ) + unbuild.action_validate() + self._check_qty(9, prd_to_build) + self._check_qty(2, prd_to_use1) + self._check_qty(1, prd_to_use2) + + def _check_qty(self, qty, product): + self.assertEqual( + self.env["stock.quant"]._get_available_quantity( + product, self.loc, allow_negative=True + ), + qty, + "You should have the {} product '{}' in stock".format(qty, product.name), + ) diff --git a/mrp_unbuild_tracked_raw_material/views/product_view.xml b/mrp_unbuild_tracked_raw_material/views/product_view.xml new file mode 100644 index 000000000..c8a554806 --- /dev/null +++ b/mrp_unbuild_tracked_raw_material/views/product_view.xml @@ -0,0 +1,15 @@ + + + + product.template + + + + + + + + diff --git a/setup/mrp_unbuild_tracked_raw_material/odoo/addons/mrp_unbuild_tracked_raw_material b/setup/mrp_unbuild_tracked_raw_material/odoo/addons/mrp_unbuild_tracked_raw_material new file mode 120000 index 000000000..9bf782814 --- /dev/null +++ b/setup/mrp_unbuild_tracked_raw_material/odoo/addons/mrp_unbuild_tracked_raw_material @@ -0,0 +1 @@ +../../../../mrp_unbuild_tracked_raw_material \ No newline at end of file diff --git a/setup/mrp_unbuild_tracked_raw_material/setup.py b/setup/mrp_unbuild_tracked_raw_material/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/mrp_unbuild_tracked_raw_material/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)