diff --git a/mrp_workcenter_cost/README.rst b/mrp_workcenter_cost/README.rst new file mode 100644 index 000000000..76d50f119 --- /dev/null +++ b/mrp_workcenter_cost/README.rst @@ -0,0 +1,99 @@ +======================================== +Manufacturing - Workcenter Cost Duration +======================================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! 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/16.0/mrp_workcenter_cost + :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_workcenter_cost + :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/16.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows you to configure how to compute the cost of a manufactured product: + +* *Effective*: standard Odoo behavior, the cost is computed based on the real duration of the workcenter. +* *Theoretical*: the cost is computed based on the theoretical duration of the workcenter. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +On a product form, you can configure the *Workcenter Cost Duration* method. + +By default it is set to *Effective* (standard Odoo behavior). By setting it to +*Theoretical*, the cost will be computed based on the theoretical duration of +the workcenter (ignoring the real duration fill by the user). + +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 +~~~~~~~ + +* Camptocamp + +Contributors +~~~~~~~~~~~~ + +* `Camptocamp `_ + + * Ivàn Todorovich + * Sébastien Alix + +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-ivantodorovich| image:: https://github.com/ivantodorovich.png?size=40px + :target: https://github.com/ivantodorovich + :alt: ivantodorovich +.. |maintainer-sebalix| image:: https://github.com/sebalix.png?size=40px + :target: https://github.com/sebalix + :alt: sebalix + +Current `maintainers `__: + +|maintainer-ivantodorovich| |maintainer-sebalix| + +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_workcenter_cost/__init__.py b/mrp_workcenter_cost/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/mrp_workcenter_cost/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/mrp_workcenter_cost/__manifest__.py b/mrp_workcenter_cost/__manifest__.py new file mode 100644 index 000000000..034b42404 --- /dev/null +++ b/mrp_workcenter_cost/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2023 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) +{ + "name": "Manufacturing - Workcenter Cost Duration", + "summary": "Controls how to compute the workcenter cost (effective vs theoretical)", + "version": "16.0.1.0.0", + "author": "Camptocamp, Odoo Community Association (OCA)", + "license": "AGPL-3", + "category": "Manufacturing/Manufacturing", + "website": "https://github.com/OCA/manufacture", + "depends": [ + "mrp_account", + ], + "data": [ + "views/product_template.xml", + ], + "installable": True, + "auto_install": False, + "development_status": "Beta", + "maintainers": ["ivantodorovich", "sebalix"], +} diff --git a/mrp_workcenter_cost/models/__init__.py b/mrp_workcenter_cost/models/__init__.py new file mode 100644 index 000000000..0629d5b0a --- /dev/null +++ b/mrp_workcenter_cost/models/__init__.py @@ -0,0 +1,2 @@ +from . import product_template +from . import mrp_production diff --git a/mrp_workcenter_cost/models/mrp_production.py b/mrp_workcenter_cost/models/mrp_production.py new file mode 100644 index 000000000..0effe4ec7 --- /dev/null +++ b/mrp_workcenter_cost/models/mrp_production.py @@ -0,0 +1,28 @@ +# Copyright 2023 Camptocamp SA (https://www.camptocamp.com). +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import models + + +class MrpProduction(models.Model): + _inherit = "mrp.production" + + def _cal_price(self, consumed_moves): + # OVERRIDE to use the theoretical duration of the workcenter + # This will overwrite the real duration of the workcenter, but that's ok for now + should_overwrite_duration = ( + self.product_id.mrp_workcenter_cost == "theoretical" + and not any(t.cost_already_recorded for t in self.workorder_ids.time_ids) + ) + duration_by_workorder = {} + if should_overwrite_duration: + workorders = self.workorder_ids.filtered("duration_expected") + for workorder in workorders: + duration_by_workorder[workorder.id] = workorder.duration + workorder.duration = workorder.duration_expected + res = super()._cal_price(consumed_moves) + # Restore the durations set by users + if should_overwrite_duration: + for workorder in workorders: + workorder.duration = duration_by_workorder[workorder.id] + return res diff --git a/mrp_workcenter_cost/models/product_template.py b/mrp_workcenter_cost/models/product_template.py new file mode 100644 index 000000000..7c1345596 --- /dev/null +++ b/mrp_workcenter_cost/models/product_template.py @@ -0,0 +1,19 @@ +# Copyright 2023 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import fields, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + mrp_workcenter_cost = fields.Selection( + selection=[("theoretical", "Theoretical"), ("effective", "Effective")], + string="Workcenter Cost Duration", + help="Controls how to compute the workcenter cost for manufactured products.\n" + "* Theoretical: The cost is computed based on the theoretical duration of the " + "workcenter.\n" + "* Effective: The cost is computed based on the real duration of the workcenter.", + default="effective", + required=True, + ) diff --git a/mrp_workcenter_cost/readme/CONTRIBUTORS.rst b/mrp_workcenter_cost/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..3ed9a412d --- /dev/null +++ b/mrp_workcenter_cost/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ +* `Camptocamp `_ + + * Ivàn Todorovich + * Sébastien Alix diff --git a/mrp_workcenter_cost/readme/DESCRIPTION.rst b/mrp_workcenter_cost/readme/DESCRIPTION.rst new file mode 100644 index 000000000..18fd342f1 --- /dev/null +++ b/mrp_workcenter_cost/readme/DESCRIPTION.rst @@ -0,0 +1,4 @@ +This module allows you to configure how to compute the cost of a manufactured product: + +* *Effective*: standard Odoo behavior, the cost is computed based on the real duration of the workcenter. +* *Theoretical*: the cost is computed based on the theoretical duration of the workcenter. diff --git a/mrp_workcenter_cost/readme/USAGE.rst b/mrp_workcenter_cost/readme/USAGE.rst new file mode 100644 index 000000000..96d5dc2bc --- /dev/null +++ b/mrp_workcenter_cost/readme/USAGE.rst @@ -0,0 +1,5 @@ +On a product form, you can configure the *Workcenter Cost Duration* method. + +By default it is set to *Effective* (standard Odoo behavior). By setting it to +*Theoretical*, the cost will be computed based on the theoretical duration of +the workcenter (ignoring the real duration fill by the user). diff --git a/mrp_workcenter_cost/static/description/icon.png b/mrp_workcenter_cost/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/mrp_workcenter_cost/static/description/icon.png differ diff --git a/mrp_workcenter_cost/static/description/index.html b/mrp_workcenter_cost/static/description/index.html new file mode 100644 index 000000000..2eaf955fa --- /dev/null +++ b/mrp_workcenter_cost/static/description/index.html @@ -0,0 +1,440 @@ + + + + + + +Manufacturing - Workcenter Cost Duration + + + +
+

Manufacturing - Workcenter Cost Duration

+ + +

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

+

This module allows you to configure how to compute the cost of a manufactured product:

+
    +
  • Effective: standard Odoo behavior, the cost is computed based on the real duration of the workcenter.
  • +
  • Theoretical: the cost is computed based on the theoretical duration of the workcenter.
  • +
+

Table of contents

+ +
+

Usage

+

On a product form, you can configure the Workcenter Cost Duration method.

+

By default it is set to Effective (standard Odoo behavior). By setting it to +Theoretical, the cost will be computed based on the theoretical duration of +the workcenter (ignoring the real duration fill by the user).

+
+
+

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

+
    +
  • Camptocamp
  • +
+
+
+

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:

+

ivantodorovich sebalix

+

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_workcenter_cost/tests/__init__.py b/mrp_workcenter_cost/tests/__init__.py new file mode 100644 index 000000000..3777fcd1c --- /dev/null +++ b/mrp_workcenter_cost/tests/__init__.py @@ -0,0 +1 @@ +from . import test_mrp_workcenter_cost diff --git a/mrp_workcenter_cost/tests/test_mrp_workcenter_cost.py b/mrp_workcenter_cost/tests/test_mrp_workcenter_cost.py new file mode 100644 index 000000000..81a86ffaf --- /dev/null +++ b/mrp_workcenter_cost/tests/test_mrp_workcenter_cost.py @@ -0,0 +1,76 @@ +# Copyright 2023 Camptocamp SA (https://www.camptocamp.com). +# @author Iván Todorovich +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo.tests import Form, TransactionCase + + +class TestMrp(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + cls.bom = cls.env.ref("mrp.mrp_bom_table_top") + cls.bom.product_tmpl_id.categ_id.property_cost_method = "fifo" + cls.location_stock = cls.env.ref("stock.stock_location_stock") + for i, bom_line in enumerate(cls.bom.bom_line_ids): + # Configure some component costs + bom_line.product_id.standard_price = 10 + # Some initial stocks + lot = cls.env["stock.lot"].create( + { + "name": f"LOT_{i}", + "product_id": bom_line.product_id.id, + "company_id": cls.env.company.id, + } + ) + cls.env["stock.quant"].create( + { + "product_id": bom_line.product_id.id, + "lot_id": lot.id, + "product_uom_id": bom_line.product_id.uom_id.id, + "quantity": 10.00, + "location_id": cls.location_stock.id, + } + ) + # Configure workcenter costs + cls.bom_routing = cls.env.ref("mrp.mrp_routing_workcenter_0") + cls.bom_routing.active = True + cls.bom_routing.workcenter_id.costs_hour = 100 + # Create a MO + with Form(cls.env["mrp.production"]) as mo_form: + mo_form.product_id = cls.bom.product_id + mo_form.bom_id = cls.bom + mo_form.product_qty = 1 + cls.production = mo_form.save() + cls.production.action_confirm() + cls.production.action_generate_serial() + + def _mrp_production_done(self, production): + for raw_move in production.move_raw_ids: + raw_move.quantity_done = raw_move.product_uom_qty + res = production.button_mark_done() + self.assertEqual(production.state, "done", str(res)) + + def test_workcenter_cost_effective(self): + self.assertEqual( + self.bom.product_tmpl_id.mrp_workcenter_cost, + "effective", + "The default value should be 'Effective' (odoo core behaviour)", + ) + self.production.workorder_ids.duration = 30 + self._mrp_production_done(self.production) + finished_move = self.production.move_finished_ids + self.assertAlmostEqual(finished_move.price_unit, 70) + + def test_workcenter_cost_theoretical(self): + self.bom.product_tmpl_id.mrp_workcenter_cost = "theoretical" + self.production.workorder_ids.duration = 30 + self._mrp_production_done(self.production) + self.assertEqual(self.production.workorder_ids.duration, 30) + self.assertNotEqual( + self.production.workorder_ids.duration, + self.production.workorder_ids.duration_expected, + ) + finished_move = self.production.move_finished_ids + self.assertAlmostEqual(finished_move.price_unit, 120) diff --git a/mrp_workcenter_cost/views/product_template.xml b/mrp_workcenter_cost/views/product_template.xml new file mode 100644 index 000000000..188f96151 --- /dev/null +++ b/mrp_workcenter_cost/views/product_template.xml @@ -0,0 +1,18 @@ + + + + + product.template + + + + + + + + diff --git a/setup/mrp_workcenter_cost/odoo/addons/mrp_workcenter_cost b/setup/mrp_workcenter_cost/odoo/addons/mrp_workcenter_cost new file mode 120000 index 000000000..b67e9821b --- /dev/null +++ b/setup/mrp_workcenter_cost/odoo/addons/mrp_workcenter_cost @@ -0,0 +1 @@ +../../../../mrp_workcenter_cost \ No newline at end of file diff --git a/setup/mrp_workcenter_cost/setup.py b/setup/mrp_workcenter_cost/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/mrp_workcenter_cost/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)