diff --git a/purchase_transport_mode/README.rst b/purchase_transport_mode/README.rst new file mode 100644 index 00000000000..051c88bfea6 --- /dev/null +++ b/purchase_transport_mode/README.rst @@ -0,0 +1,92 @@ +======================= +Purchase Transport Mode +======================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:de72b94c90dd1d08449772428e9d13b2683f202b7831109f787ab7c77da09d45 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fpurchase--workflow-lightgray.png?logo=github + :target: https://github.com/OCA/purchase-workflow/tree/16.0/purchase_transport_mode + :alt: OCA/purchase-workflow +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/purchase-workflow-16-0/purchase-workflow-16-0-purchase_transport_mode + :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/purchase-workflow&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Purchases can be received by different modes of transport. For example, the transport can be done in a truck with a loading capacity of X tonnes or Y tonnes. +With this module it is possible to validate that the purchase complies with the requirements of a particular mode of transport. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +To use this module, you need to: + +#. In Purchase settings enable purchase transport mode validation +#. Create a transport modes with contraints applied on PO +#. Select transport mode on PO + +If the transport requirements are not met, a message in yellow will be displayed at the top of the form. + +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 +~~~~~~~ + +* Camptocamp +* BCIM + +Contributors +~~~~~~~~~~~~ + +* Telmo Santos +* Cyril Jeanneret +* Jacques-Etienne Baudoux (BCIM) +* Simone Orsi + +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. + +This module is part of the `OCA/purchase-workflow `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/purchase_transport_mode/__init__.py b/purchase_transport_mode/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/purchase_transport_mode/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/purchase_transport_mode/__manifest__.py b/purchase_transport_mode/__manifest__.py new file mode 100644 index 00000000000..1be2b377b53 --- /dev/null +++ b/purchase_transport_mode/__manifest__.py @@ -0,0 +1,27 @@ +# Copyright 2023 Camptocamp (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +{ + "name": "Purchase Transport Mode", + "version": "16.0.1.0.0", + "development_status": "Beta", + "summary": "Purchase expection based on constraints", + "author": "Camptocamp, BCIM, Odoo Community Association (OCA)", + "license": "AGPL-3", + "category": "Purchase", + "depends": [ + "purchase", + "purchase_exception", + ], + "website": "https://github.com/OCA/purchase-workflow", + "data": [ + "security/ir.model.access.csv", + "templates/purchase_order_transport_mode_status.xml", + "views/res_partner_views.xml", + "views/purchase_transport_mode_views.xml", + "views/purchase_transport_mode_constraint_views.xml", + "views/purchase_order_view.xml", + "views/res_config_settings_views.xml", + "data/purchase_exception_data.xml", + ], + "installable": True, +} diff --git a/purchase_transport_mode/data/purchase_exception_data.xml b/purchase_transport_mode/data/purchase_exception_data.xml new file mode 100644 index 00000000000..96e30f65d0a --- /dev/null +++ b/purchase_transport_mode/data/purchase_exception_data.xml @@ -0,0 +1,11 @@ + + + + Transport mode + Transport mode requirements not satisfied + 50 + purchase.order + if not self.transport_mode_status_ok: + failed=True + + diff --git a/purchase_transport_mode/models/__init__.py b/purchase_transport_mode/models/__init__.py new file mode 100644 index 00000000000..77c2f828a3e --- /dev/null +++ b/purchase_transport_mode/models/__init__.py @@ -0,0 +1,6 @@ +from . import purchase_transport_mode +from . import purchase_transport_mode_constraint +from . import res_partner +from . import purchase_order +from . import res_company +from . import res_config_settings diff --git a/purchase_transport_mode/models/purchase_order.py b/purchase_transport_mode/models/purchase_order.py new file mode 100644 index 00000000000..c7a71e38dce --- /dev/null +++ b/purchase_transport_mode/models/purchase_order.py @@ -0,0 +1,70 @@ +# Copyright 2023 Camptocamp (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + + +from odoo import api, fields, models + +from odoo.addons.base_sparse_field.models.fields import Serialized + + +class PurchaseOrder(models.Model): + _inherit = "purchase.order" + _name = "purchase.order" + + transport_mode_id = fields.Many2one("purchase.transport.mode") + transport_mode_status = Serialized( + compute="_compute_transport_mode_validation_status", + help="Collect and validate order details to satisfy transport mode requirements", + default={}, + ) + transport_mode_status_display = fields.Html( + compute="_compute_transport_mode_validation_status", + help="Render transport_mode_status in the UI", + ) + transport_mode_status_ok = fields.Boolean( + compute="_compute_transport_mode_validation_status", + help="All transport mode requirements are satisfied", + ) + + @api.depends("transport_mode_id") + def _compute_transport_mode_validation_status(self): + for rec in self: + rec.transport_mode_status = rec._get_transport_mode_validation_status() + rec.transport_mode_status_display = ( + rec._get_transport_mode_validation_status_display() + ) + rec.transport_mode_status_ok = False if rec.transport_mode_status else True + + @api.onchange("partner_id") + def onchange_partner_id(self): + if self.partner_id: + self.transport_mode_id = ( + self.partner_id.commercial_partner_id.purchase_transport_mode_id + ) + + def _get_transport_mode_validation_status(self): + self.ensure_one() + errors = [] + if ( + not self.company_id.purchase_transport_mode_contraint_enabled + or not isinstance(self.id, int) # Record is not saved yet + ): + return {} + for constraint in self.transport_mode_id.constraint_ids: + if not constraint.filter_valid_purchase(self): + error_message = "{}: {}".format( + constraint.name, constraint.description or "" + ) + errors.append(error_message) + if errors: + return {"errors": errors} + return {} + + def _get_transport_mode_validation_status_display(self): + errors = self.transport_mode_status.get("errors", []) + if errors: + return self.env["ir.qweb"]._render( + "purchase_transport_mode.purchase_order_transport_mode_status_display", + {"order": self, "errors": errors}, + ) + return "" diff --git a/purchase_transport_mode/models/purchase_transport_mode.py b/purchase_transport_mode/models/purchase_transport_mode.py new file mode 100644 index 00000000000..10c6ea8c8e8 --- /dev/null +++ b/purchase_transport_mode/models/purchase_transport_mode.py @@ -0,0 +1,18 @@ +# Copyright 2023 Camptocamp (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + + +from odoo import fields, models + + +class PurchaseTransportMode(models.Model): + _name = "purchase.transport.mode" + _description = "Transport Mode" + + name = fields.Char(required=True) + + constraint_ids = fields.One2many( + comodel_name="purchase.transport.mode.constraint", + inverse_name="purchase_transport_mode_id", + string="Purchase Transport mode Constraints", + ) diff --git a/purchase_transport_mode/models/purchase_transport_mode_constraint.py b/purchase_transport_mode/models/purchase_transport_mode_constraint.py new file mode 100644 index 00000000000..28451f770d3 --- /dev/null +++ b/purchase_transport_mode/models/purchase_transport_mode_constraint.py @@ -0,0 +1,30 @@ +# Copyright 2023 Camptocamp (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + + +from odoo import fields, models +from odoo.tools.safe_eval import safe_eval + + +class PurchaseTransportModeConstraints(models.Model): + _name = "purchase.transport.mode.constraint" + _description = "Transport Mode Constraint" + + name = fields.Char(required=True) + description = fields.Char() + purchase_domain = fields.Char( + string="Source Purchase Domain", + default=[], + copy=False, + help="Domain based on purchase", + ) + + purchase_transport_mode_id = fields.Many2one("purchase.transport.mode") + + def filter_valid_purchase(self, purchase): + if not self.purchase_domain: + return purchase + domain = safe_eval(self.purchase_domain or "[]") + if not domain: + return purchase + return purchase.filtered_domain(domain) diff --git a/purchase_transport_mode/models/res_company.py b/purchase_transport_mode/models/res_company.py new file mode 100644 index 00000000000..92aebffb4eb --- /dev/null +++ b/purchase_transport_mode/models/res_company.py @@ -0,0 +1,10 @@ +# Copyright 2023 Camptocamp (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + purchase_transport_mode_contraint_enabled = fields.Boolean() diff --git a/purchase_transport_mode/models/res_config_settings.py b/purchase_transport_mode/models/res_config_settings.py new file mode 100644 index 00000000000..069405a153c --- /dev/null +++ b/purchase_transport_mode/models/res_config_settings.py @@ -0,0 +1,13 @@ +# Copyright 2023 Camptocamp (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + purchase_transport_mode_contraint_enabled = fields.Boolean( + related="company_id.purchase_transport_mode_contraint_enabled", + readonly=False, + string="Validate purchase transport mode", + ) diff --git a/purchase_transport_mode/models/res_partner.py b/purchase_transport_mode/models/res_partner.py new file mode 100644 index 00000000000..552104f828a --- /dev/null +++ b/purchase_transport_mode/models/res_partner.py @@ -0,0 +1,10 @@ +# Copyright 2023 Camptocamp (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class ResPartner(models.Model): + _inherit = "res.partner" + + purchase_transport_mode_id = fields.Many2one("purchase.transport.mode") diff --git a/purchase_transport_mode/readme/CONTRIBUTORS.rst b/purchase_transport_mode/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..4cbf0015588 --- /dev/null +++ b/purchase_transport_mode/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ +* Telmo Santos +* Cyril Jeanneret +* Jacques-Etienne Baudoux (BCIM) +* Simone Orsi diff --git a/purchase_transport_mode/readme/DESCRIPTION.rst b/purchase_transport_mode/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..64ed18256d7 --- /dev/null +++ b/purchase_transport_mode/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +Purchases can be received by different modes of transport. For example, the transport can be done in a truck with a loading capacity of X tonnes or Y tonnes. +With this module it is possible to validate that the purchase complies with the requirements of a particular mode of transport. diff --git a/purchase_transport_mode/readme/USAGE.rst b/purchase_transport_mode/readme/USAGE.rst new file mode 100644 index 00000000000..7ceda94010b --- /dev/null +++ b/purchase_transport_mode/readme/USAGE.rst @@ -0,0 +1,7 @@ +To use this module, you need to: + +#. In Purchase settings enable purchase transport mode validation +#. Create a transport modes with contraints applied on PO +#. Select transport mode on PO + +If the transport requirements are not met, a message in yellow will be displayed at the top of the form. diff --git a/purchase_transport_mode/security/ir.model.access.csv b/purchase_transport_mode/security/ir.model.access.csv new file mode 100644 index 00000000000..15c578219d9 --- /dev/null +++ b/purchase_transport_mode/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_purchase_transport_mode,access_purchase_transport_mode,model_purchase_transport_mode,purchase.group_purchase_user,1,1,1,1 +access_purchase_transport_mode_constraint,access_purchase_transport_mode_constraint,model_purchase_transport_mode_constraint,purchase.group_purchase_user,1,1,1,1 diff --git a/purchase_transport_mode/static/description/index.html b/purchase_transport_mode/static/description/index.html new file mode 100644 index 00000000000..2e9a7c8c397 --- /dev/null +++ b/purchase_transport_mode/static/description/index.html @@ -0,0 +1,437 @@ + + + + + + +Purchase Transport Mode + + + +
+

Purchase Transport Mode

+ + +

Beta License: AGPL-3 OCA/purchase-workflow Translate me on Weblate Try me on Runboat

+

Purchases can be received by different modes of transport. For example, the transport can be done in a truck with a loading capacity of X tonnes or Y tonnes. +With this module it is possible to validate that the purchase complies with the requirements of a particular mode of transport.

+

Table of contents

+ +
+

Usage

+

To use this module, you need to:

+
    +
  1. In Purchase settings enable purchase transport mode validation
  2. +
  3. Create a transport modes with contraints applied on PO
  4. +
  5. Select transport mode on PO
  6. +
+

If the transport requirements are not met, a message in yellow will be displayed at the top of the form.

+
+
+

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

+
    +
  • Camptocamp
  • +
  • BCIM
  • +
+
+
+

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.

+

This module is part of the OCA/purchase-workflow project on GitHub.

+

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

+
+
+
+ + diff --git a/purchase_transport_mode/templates/purchase_order_transport_mode_status.xml b/purchase_transport_mode/templates/purchase_order_transport_mode_status.xml new file mode 100644 index 00000000000..b2fbb2b7758 --- /dev/null +++ b/purchase_transport_mode/templates/purchase_order_transport_mode_status.xml @@ -0,0 +1,18 @@ + + + + diff --git a/purchase_transport_mode/tests/__init__.py b/purchase_transport_mode/tests/__init__.py new file mode 100644 index 00000000000..effa0ee9c26 --- /dev/null +++ b/purchase_transport_mode/tests/__init__.py @@ -0,0 +1 @@ +from . import test_purchase_transport_mode diff --git a/purchase_transport_mode/tests/test_purchase_transport_mode.py b/purchase_transport_mode/tests/test_purchase_transport_mode.py new file mode 100644 index 00000000000..05f808e38d9 --- /dev/null +++ b/purchase_transport_mode/tests/test_purchase_transport_mode.py @@ -0,0 +1,69 @@ +# Copyright 2023 Camptocamp (). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + + +from odoo.tests import TransactionCase + +from odoo.addons.base.tests.common import DISABLED_MAIL_CONTEXT + + +class TestPurchaseTransportMode(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env["base"].with_context(**DISABLED_MAIL_CONTEXT).env + cls.product_a = cls.env.ref("product.consu_delivery_01") + cls.purchase_order = cls.env["purchase.order"].create( + { + "company_id": cls.env.company.id, + "partner_id": cls.env.ref("base.res_partner_12").id, + } + ) + cls.env["purchase.order.line"].create( + { + "order_id": cls.purchase_order.id, + "name": cls.product_a.name, + "product_id": cls.product_a.id, + "product_qty": 5, + } + ) + + cls.transport_mode_a = cls.env["purchase.transport.mode"].create( + {"name": "Transport mode A"} + ) + cls.constraint_a = cls.env["purchase.transport.mode.constraint"].create( + { + "name": "Constraint A", + "description": "The total amount of the order must be higher than 5000", + "purchase_transport_mode_id": cls.transport_mode_a.id, + "purchase_domain": [("amount_total", ">", 5000)], + } + ) + + def enable_purchase_transport_mode_validation(self): + self.settings = self.env["res.config.settings"].create({}) + self.settings.purchase_transport_mode_contraint_enabled = True + self.settings.set_values() + + def test_disabled_purchase_transport_mode_validation(self): + self.purchase_order.button_confirm() + self.assertEqual(self.purchase_order.state, "purchase") + + def test_purchase_transport_mode_validation(self): + self.enable_purchase_transport_mode_validation() + self.purchase_order.transport_mode_id = self.transport_mode_a + self.purchase_order.button_confirm() + self.assertFalse(self.purchase_order.transport_mode_status_ok) + self.assertEqual(self.purchase_order.state, "draft") + self.assertEqual( + self.purchase_order.transport_mode_status, + { + "errors": [ + "Constraint A: The total amount of the order must be higher than 5000" + ] + }, + ) + self.purchase_order.order_line.product_qty = 500 + self.purchase_order.invalidate_cache() + self.purchase_order.button_confirm() + self.assertEqual(self.purchase_order.state, "purchase") diff --git a/purchase_transport_mode/views/purchase_order_view.xml b/purchase_transport_mode/views/purchase_order_view.xml new file mode 100644 index 00000000000..3dedee01bbd --- /dev/null +++ b/purchase_transport_mode/views/purchase_order_view.xml @@ -0,0 +1,27 @@ + + + + purchase.order.form.inherit + purchase.order + + + + + + + + + + + + + + diff --git a/purchase_transport_mode/views/purchase_transport_mode_constraint_views.xml b/purchase_transport_mode/views/purchase_transport_mode_constraint_views.xml new file mode 100644 index 00000000000..c834da60423 --- /dev/null +++ b/purchase_transport_mode/views/purchase_transport_mode_constraint_views.xml @@ -0,0 +1,44 @@ + + + + purchase.transport.mode.constraint + +
+ +
+
+ + + + + +
+
+
+
+ + purchase.transport.mode.constraint + + + + + + + + + + + purchase.transport.mode.constraint + + + + + + +
diff --git a/purchase_transport_mode/views/purchase_transport_mode_views.xml b/purchase_transport_mode/views/purchase_transport_mode_views.xml new file mode 100644 index 00000000000..0123b63e86d --- /dev/null +++ b/purchase_transport_mode/views/purchase_transport_mode_views.xml @@ -0,0 +1,53 @@ + + + + purchase.transport.mode + +
+ +
+
+ + + + + + + + +
+
+
+
+ + + purchase.transport.mode + + + + + + + + + purchase.transport.mode + + + + + + + + + Purchase Transport Mode + ir.actions.act_window + purchase.transport.mode + tree,form + + +
diff --git a/purchase_transport_mode/views/res_config_settings_views.xml b/purchase_transport_mode/views/res_config_settings_views.xml new file mode 100644 index 00000000000..0189b5bc56e --- /dev/null +++ b/purchase_transport_mode/views/res_config_settings_views.xml @@ -0,0 +1,36 @@ + + + + + res.config.settings.view.form.inherit + res.config.settings + + + + +
+
+ +
+
+
+
+
+
+
+ +
diff --git a/purchase_transport_mode/views/res_partner_views.xml b/purchase_transport_mode/views/res_partner_views.xml new file mode 100644 index 00000000000..54b33dd8b4b --- /dev/null +++ b/purchase_transport_mode/views/res_partner_views.xml @@ -0,0 +1,13 @@ + + + + res.partner.form.inherit + res.partner + + + + + + + + diff --git a/setup/purchase_transport_mode/odoo/addons/purchase_transport_mode b/setup/purchase_transport_mode/odoo/addons/purchase_transport_mode new file mode 120000 index 00000000000..1f6fc8cba5a --- /dev/null +++ b/setup/purchase_transport_mode/odoo/addons/purchase_transport_mode @@ -0,0 +1 @@ +../../../../purchase_transport_mode \ No newline at end of file diff --git a/setup/purchase_transport_mode/setup.py b/setup/purchase_transport_mode/setup.py new file mode 100644 index 00000000000..28c57bb6403 --- /dev/null +++ b/setup/purchase_transport_mode/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)