diff --git a/mrp_default_workorder_time/README.rst b/mrp_default_workorder_time/README.rst new file mode 100644 index 000000000..583192ded --- /dev/null +++ b/mrp_default_workorder_time/README.rst @@ -0,0 +1,86 @@ +========================== +MRP Default Workorder Time +========================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! 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-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/14.0/mrp_default_workorder_time + :alt: OCA/manufacture +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/manufacture-14-0/manufacture-14-0-mrp_default_workorder_time + :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/14.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +There are occasions when the registration of work orders is not completed with the correct times, this module allows you to configure a threshold percentage that assigns the projected time as fully productive time if a work time less than this percentage is detected. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +In the production module configurations, the "Record minimum order time" feature must be activated, by default the minimum percentage is ten but it can be changed as needed. + +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 +~~~~~~~ + +* ForgeFlow + +Contributors +~~~~~~~~~~~~ + +* Christopher Ormaza + +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-ChrisOForgeFlow| image:: https://github.com/ChrisOForgeFlow.png?size=40px + :target: https://github.com/ChrisOForgeFlow + :alt: ChrisOForgeFlow + +Current `maintainer `__: + +|maintainer-ChrisOForgeFlow| + +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_default_workorder_time/__init__.py b/mrp_default_workorder_time/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/mrp_default_workorder_time/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/mrp_default_workorder_time/__manifest__.py b/mrp_default_workorder_time/__manifest__.py new file mode 100644 index 000000000..6fe60363a --- /dev/null +++ b/mrp_default_workorder_time/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2022 ForgeFlow S.L. (https://www.forgeflow.com) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +{ + "name": "MRP Default Workorder Time", + "version": "14.0.1.0.0", + "development_status": "Beta", + "license": "LGPL-3", + "author": "ForgeFlow, Odoo Community Association (OCA)", + "maintainers": ["ChrisOForgeFlow"], + "summary": "Adds an MRP default workorder time", + "website": "https://github.com/OCA/manufacture", + "category": "Manufacturing", + "depends": ["mrp", "stock"], + "data": [ + "views/res_config_settings_view.xml", + ], + "demo": [], + "installable": True, + "application": True, +} diff --git a/mrp_default_workorder_time/models/__init__.py b/mrp_default_workorder_time/models/__init__.py new file mode 100644 index 000000000..ff59718f2 --- /dev/null +++ b/mrp_default_workorder_time/models/__init__.py @@ -0,0 +1,3 @@ +from . import company +from . import res_config_settings +from . import workorder diff --git a/mrp_default_workorder_time/models/company.py b/mrp_default_workorder_time/models/company.py new file mode 100644 index 000000000..ccc220249 --- /dev/null +++ b/mrp_default_workorder_time/models/company.py @@ -0,0 +1,24 @@ +# Copyright 2022 ForgeFlow S.L. (https://www.forgeflow.com) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models + + +class ResCompany(models.Model): + + _inherit = "res.company" + + use_projected_time_work_orders = fields.Boolean( + string="Use projected time work orders?", + default=True, + ) + minimum_order_time_threshold = fields.Float( + string="Minimum order time threshold(%)", + required=False, + default=10, + ) + maximum_order_time_threshold = fields.Float( + string="Maximum order time threshold(%)", + required=False, + default=150, + ) diff --git a/mrp_default_workorder_time/models/res_config_settings.py b/mrp_default_workorder_time/models/res_config_settings.py new file mode 100644 index 000000000..7117ae733 --- /dev/null +++ b/mrp_default_workorder_time/models/res_config_settings.py @@ -0,0 +1,19 @@ +# Copyright 2022 ForgeFlow S.L. (https://www.forgeflow.com) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + + _inherit = "res.config.settings" + + use_projected_time_work_orders = fields.Boolean( + related="company_id.use_projected_time_work_orders", readonly=False + ) + minimum_order_time_threshold = fields.Float( + related="company_id.minimum_order_time_threshold", readonly=False + ) + maximum_order_time_threshold = fields.Float( + related="company_id.maximum_order_time_threshold", readonly=False + ) diff --git a/mrp_default_workorder_time/models/workorder.py b/mrp_default_workorder_time/models/workorder.py new file mode 100644 index 000000000..fd5d0a8fa --- /dev/null +++ b/mrp_default_workorder_time/models/workorder.py @@ -0,0 +1,92 @@ +# Copyright 2022 ForgeFlow S.L. (https://www.forgeflow.com) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from dateutil.relativedelta import relativedelta + +from odoo import _, models +from odoo.exceptions import UserError +from odoo.tools import float_compare + + +class MrpWorkOrder(models.Model): + + _inherit = "mrp.workorder" + + def add_time_to_work_order(self, fully_productive_time): + self.ensure_one() + # self._compute_duration() + date_start = self.date_planned_start + if not date_start: + raise UserError( + _( + "You should plan orders to set default " + "production time on work orders, please check" + ) + ) + date_end = self.date_planned_start + relativedelta( + minutes=self.duration_expected + ) + if self.time_ids: + minutes_to_add = self.duration_expected - self.duration + if float_compare(minutes_to_add, 0, precision_digits=6) == -1: + return + date_start = max(self.time_ids.mapped("date_end")) + date_end = date_start + relativedelta(minutes=minutes_to_add) + self.write( + { + "time_ids": [ + ( + 0, + 0, + { + "user_id": self.env.user.id, + "date_start": date_start, + "date_end": date_end, + "loss_id": fully_productive_time.id, + "workcenter_id": self.workcenter_id.id, + "description": fully_productive_time.name, + }, + ) + ] + } + ) + + def reduce_time_to_workorder(self, fully_productive_time): + self.ensure_one() + self.time_ids.filtered( + lambda x: x.loss_id.id == fully_productive_time.id + ).sudo().unlink() + self.add_time_to_work_order(fully_productive_time) + + def button_finish(self): + res = super().button_finish() + fully_productive_time = self.env["mrp.workcenter.productivity.loss"].search( + [("loss_type", "=", "performance")], limit=1 + ) + if not fully_productive_time: + raise UserError( + _( + "Fully Productive Time is not configured on system, " + "please contact with your administrator" + ) + ) + for workorder in self.filtered( + lambda x: x.state == "done" + and ( + not x.time_ids + or ( + (100 - x.duration_percent) + <= x.production_id.company_id.minimum_order_time_threshold + ) + ) + and x.production_id.company_id.use_projected_time_work_orders + ): + # FIX ME: this is because duration expected use this field to compute + workorder.qty_production = workorder.qty_produced + workorder.duration_expected = workorder._get_duration_expected() + workorder.add_time_to_work_order(fully_productive_time) + if ( + 100 - workorder.duration_percent + ) > workorder.production_id.company_id.maximum_order_time_threshold: + workorder.reduce_time_to_workorder(fully_productive_time) + return res diff --git a/mrp_default_workorder_time/readme/CONTRIBUTORS.rst b/mrp_default_workorder_time/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..b647a292e --- /dev/null +++ b/mrp_default_workorder_time/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Christopher Ormaza diff --git a/mrp_default_workorder_time/readme/DESCRIPTION.rst b/mrp_default_workorder_time/readme/DESCRIPTION.rst new file mode 100644 index 000000000..01279f8c8 --- /dev/null +++ b/mrp_default_workorder_time/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +There are occasions when the registration of work orders is not completed with the correct times, this module allows you to configure a threshold percentage that assigns the projected time as fully productive time if a work time less than this percentage is detected. diff --git a/mrp_default_workorder_time/readme/USAGE.rst b/mrp_default_workorder_time/readme/USAGE.rst new file mode 100644 index 000000000..3414ae73d --- /dev/null +++ b/mrp_default_workorder_time/readme/USAGE.rst @@ -0,0 +1 @@ +In the production module configurations, the "Record minimum order time" feature must be activated, by default the minimum percentage is ten but it can be changed as needed. diff --git a/mrp_default_workorder_time/static/description/index.html b/mrp_default_workorder_time/static/description/index.html new file mode 100644 index 000000000..cb2301e04 --- /dev/null +++ b/mrp_default_workorder_time/static/description/index.html @@ -0,0 +1,426 @@ + + + + + + +MRP Default Workorder Time + + + +
+

MRP Default Workorder Time

+ + +

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

+

There are occasions when the registration of work orders is not completed with the correct times, this module allows you to configure a threshold percentage that assigns the projected time as fully productive time if a work time less than this percentage is detected.

+

Table of contents

+ +
+

Usage

+

In the production module configurations, the “Record minimum order time” feature must be activated, by default the minimum percentage is ten but it can be changed as needed.

+
+
+

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

+
    +
  • ForgeFlow
  • +
+
+
+

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

+

ChrisOForgeFlow

+

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_default_workorder_time/tests/__init__.py b/mrp_default_workorder_time/tests/__init__.py new file mode 100644 index 000000000..1260322ff --- /dev/null +++ b/mrp_default_workorder_time/tests/__init__.py @@ -0,0 +1 @@ +from . import test_workorder_times diff --git a/mrp_default_workorder_time/tests/test_workorder_times.py b/mrp_default_workorder_time/tests/test_workorder_times.py new file mode 100644 index 000000000..2a2ad26b7 --- /dev/null +++ b/mrp_default_workorder_time/tests/test_workorder_times.py @@ -0,0 +1,131 @@ +# Copyright 2022 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from dateutil.relativedelta import relativedelta + +from odoo.fields import Datetime as Dt +from odoo.tests import Form + +from odoo.addons.mrp.tests import common + + +class TestMrpDefaultWorkorderTime(common.TestMrpCommon): + def _crete_production_with_workorders( + self, product_qty=100, set_time=False, percent_time=False + ): + fully_productive_time = self.env["mrp.workcenter.productivity.loss"].search( + [("loss_type", "=", "performance")], limit=1 + ) + mo_form = Form(self.env["mrp.production"]) + mo_form.product_id = self.bom_2.product_id + mo_form.bom_id = self.bom_2 + mo_form.product_qty = product_qty + mo = mo_form.save() + self.env["stock.quant"]._update_available_quantity( + self.product_4, self.stock_location, 200 + ) + self.env["stock.quant"]._update_available_quantity( + self.product_3, self.stock_location, 200 + ) + mo.date_planned_start = Dt.now() + mo.action_confirm() + mo.action_assign() + mo.button_plan() + if set_time: + date_start = Dt.now() + date_end = date_start + relativedelta( + minutes=mo.workorder_ids[0].duration_expected * (percent_time / 100.0) + ) + mo.workorder_ids.write( + { + "time_ids": [ + ( + 0, + 0, + { + "user_id": self.env.user.id, + "date_start": date_start, + "date_end": date_end, + "loss_id": fully_productive_time.id, + "workcenter_id": mo.workorder_ids[0].workcenter_id.id, + "description": fully_productive_time.name, + }, + ) + ] + } + ) + return mo + + def finish_production(self, mo): + res = mo.button_mark_done() + if res is not True: + ctx = { + "active_id": mo.id, + "active_ids": mo.ids, + "active_model": "mrp.production", + } + ctx.update(res.get("context", {})) + wizard_form = Form(self.env["mrp.immediate.production"].with_context(**ctx)) + wizard = wizard_form.save() + wizard.process() + + def test_mrp_default_workorder_time(self): + self.stock_location = self.env.ref("stock.stock_location_stock") + mo = self._crete_production_with_workorders(1) + self.assertEqual(len(mo), 1, "MO should have been created") + self.finish_production(mo) + self.assertEqual( + mo.workorder_ids[0].duration, mo.workorder_ids[0].duration_expected + ) + + mo2 = self._crete_production_with_workorders(1) + mo2.company_id.use_projected_time_work_orders = False + self.assertEqual(len(mo2), 1, "MO should have been created") + self.finish_production(mo2) + self.assertNotEqual( + mo2.workorder_ids[0].duration, mo2.workorder_ids[0].duration_expected + ) + + mo3 = self._crete_production_with_workorders( + 1, True, mo.company_id.minimum_order_time_threshold + ) + mo3.company_id.use_projected_time_work_orders = True + mo3.company_id.minimum_order_time_threshold = 20 + self.assertEqual(len(mo3), 1, "MO should have been created") + self.finish_production(mo3) + self.assertEqual( + mo3.workorder_ids[0].duration, mo3.workorder_ids[0].duration_expected + ) + + mo4 = self._crete_production_with_workorders( + 1, True, mo.company_id.minimum_order_time_threshold + 1 + ) + mo4.company_id.use_projected_time_work_orders = True + mo4.company_id.minimum_order_time_threshold = 20 + self.assertEqual(len(mo4), 1, "MO should have been created") + self.finish_production(mo4) + self.assertNotEqual( + mo4.workorder_ids[0].duration, mo4.workorder_ids[0].duration_expected + ) + + mo5 = self._crete_production_with_workorders( + 1, True, mo.company_id.maximum_order_time_threshold + ) + mo5.company_id.use_projected_time_work_orders = True + mo5.company_id.minimum_order_time_threshold = 20 + self.assertEqual(len(mo5), 1, "MO should have been created") + self.finish_production(mo5) + self.assertNotEqual( + mo5.workorder_ids[0].duration, mo5.workorder_ids[0].duration_expected + ) + + mo6 = self._crete_production_with_workorders( + 1, True, mo.company_id.maximum_order_time_threshold + 1 + ) + mo6.company_id.use_projected_time_work_orders = True + mo6.company_id.minimum_order_time_threshold = 20 + self.assertEqual(len(mo6), 1, "MO should have been created") + self.finish_production(mo6) + self.assertNotEqual( + mo6.workorder_ids[0].duration, mo6.workorder_ids[0].duration_expected + ) diff --git a/mrp_default_workorder_time/views/res_config_settings_view.xml b/mrp_default_workorder_time/views/res_config_settings_view.xml new file mode 100644 index 000000000..b579806b9 --- /dev/null +++ b/mrp_default_workorder_time/views/res_config_settings_view.xml @@ -0,0 +1,56 @@ + + + + + + res.config.settings.view.form.inherit.mrp + res.config.settings + + + +
+
+ +
+
+
+
+
+
+
+ +
+