diff --git a/purchase_reception_status_line/README.rst b/purchase_reception_status_line/README.rst new file mode 100644 index 00000000000..090788acccd --- /dev/null +++ b/purchase_reception_status_line/README.rst @@ -0,0 +1,100 @@ +============================== +Purchase Reception Status Line +============================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:f4b1b4db3d01699868997506525d3b3e05eaf22dac85ccd43586bbb2ad9f758e + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/15.0/purchase_reception_status_line + :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-15-0/purchase-workflow-15-0-purchase_reception_status_line + :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=15.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adds a field *Reception Status* on purchase orders line. +On a confirmed purchase order line, it can have 3 different values: + +* Nothing Received +* Partially Received +* Fully Received + +Also takes this into account when computing the reception status for the +purchase order. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +If you are part of the *Purchase Manager* group, you can force a confirmed +purchase order to **Full Received** status: you have to check the field +**Force Received** located in the hidden fields on the +tree view (Order Lines tab). + +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 +~~~~~~~ + +* ForgeFlow + +Contributors +~~~~~~~~~~~~ + +* David Jiménez + +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-DavidJForgeFlow| image:: https://github.com/DavidJForgeFlow.png?size=40px + :target: https://github.com/DavidJForgeFlow + :alt: DavidJForgeFlow + +Current `maintainer `__: + +|maintainer-DavidJForgeFlow| + +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_reception_status_line/__init__.py b/purchase_reception_status_line/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/purchase_reception_status_line/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/purchase_reception_status_line/__manifest__.py b/purchase_reception_status_line/__manifest__.py new file mode 100644 index 00000000000..47438313fb0 --- /dev/null +++ b/purchase_reception_status_line/__manifest__.py @@ -0,0 +1,16 @@ +# Copyright 2024 ForgeFlow (http://www.akretion.com/) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Purchase Reception Status Line", + "version": "15.0.1.0.0", + "category": "Purchases", + "license": "AGPL-3", + "summary": "Add reception status on purchase order lines", + "author": "ForgeFlow,Odoo Community Association (OCA)", + "maintainers": ["DavidJForgeFlow"], + "website": "https://github.com/OCA/purchase-workflow", + "depends": ["purchase_reception_status"], + "data": ["views/purchase_order.xml"], + "installable": True, +} diff --git a/purchase_reception_status_line/models/__init__.py b/purchase_reception_status_line/models/__init__.py new file mode 100644 index 00000000000..677dbb3b247 --- /dev/null +++ b/purchase_reception_status_line/models/__init__.py @@ -0,0 +1,2 @@ +from . import purchase_order +from . import purchase_order_line diff --git a/purchase_reception_status_line/models/purchase_order.py b/purchase_reception_status_line/models/purchase_order.py new file mode 100644 index 00000000000..8924ee142c7 --- /dev/null +++ b/purchase_reception_status_line/models/purchase_order.py @@ -0,0 +1,35 @@ +# Copyright 2024 ForgeFlow (http://www.akretion.com/) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, models + + +class PurchaseOrder(models.Model): + _inherit = "purchase.order" + + @api.depends( + "state", + "force_received", + "order_line.qty_received", + "order_line.product_qty", + "order_line.force_received", + "order_line.reception_status", + ) + def _compute_reception_status(self): + result = super()._compute_reception_status() + for order in self.filtered(lambda po: po.reception_status != "received"): + status = order.reception_status + if order.state in ("purchase", "done"): + if all( + [line.reception_status == "received" for line in order.order_line] + ): + status = "received" + elif any( + [ + line.reception_status in ["received", "partial"] + for line in order.order_line + ] + ): + status = "partial" + order.reception_status = status + return result diff --git a/purchase_reception_status_line/models/purchase_order_line.py b/purchase_reception_status_line/models/purchase_order_line.py new file mode 100644 index 00000000000..7268ab47f3a --- /dev/null +++ b/purchase_reception_status_line/models/purchase_order_line.py @@ -0,0 +1,68 @@ +# Copyright 2024 ForgeFlow (http://www.akretion.com/) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models +from odoo.tools import float_compare + + +class PurchaseOrderLine(models.Model): + _inherit = "purchase.order.line" + + reception_status = fields.Selection( + [ + ("no", "Nothing Received"), + ("partial", "Partially Received"), + ("received", "Fully Received"), + ], + compute="_compute_reception_status", + store=True, + ) + force_received = fields.Boolean( + readonly=False, + states={"draft": [("readonly", True)]}, + compute="_compute_force_received", + store=True, + copy=False, + help="If true, the reception status will be forced to Fully Received, " + "even if some quantities are not fully received. ", + ) + + @api.depends("order_id.force_received") + def _compute_force_received(self): + prec = self.env["decimal.precision"].precision_get("Product Unit of Measure") + for rec in self: + if ( + rec.order_id.force_received + and not float_compare( + rec.qty_received, rec.product_qty, precision_digits=prec + ) + >= 0 + ): + rec.force_received = True + + @api.depends( + "state", + "force_received", + "qty_received", + "product_qty", + "order_id.force_received", + ) + def _compute_reception_status(self): + prec = self.env["decimal.precision"].precision_get("Product Unit of Measure") + for line in self: + status = "no" + if line.order_id.state in ("purchase", "done"): + if line.force_received: + status = "received" + elif ( + float_compare( + line.qty_received, line.product_qty, precision_digits=prec + ) + >= 0 + ): + status = "received" + elif float_compare(line.qty_received, 0, precision_digits=prec) > 0: + status = "partial" + line.reception_status = ( + status if not line.order_id.force_received else "received" + ) diff --git a/purchase_reception_status_line/readme/CONTRIBUTORS.rst b/purchase_reception_status_line/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..fcd08101846 --- /dev/null +++ b/purchase_reception_status_line/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* David Jiménez diff --git a/purchase_reception_status_line/readme/DESCRIPTION.rst b/purchase_reception_status_line/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..09467e18a4d --- /dev/null +++ b/purchase_reception_status_line/readme/DESCRIPTION.rst @@ -0,0 +1,9 @@ +This module adds a field *Reception Status* on purchase orders line. +On a confirmed purchase order line, it can have 3 different values: + +* Nothing Received +* Partially Received +* Fully Received + +Also takes this into account when computing the reception status for the +purchase order. diff --git a/purchase_reception_status_line/readme/USAGE.rst b/purchase_reception_status_line/readme/USAGE.rst new file mode 100644 index 00000000000..bf662081706 --- /dev/null +++ b/purchase_reception_status_line/readme/USAGE.rst @@ -0,0 +1,4 @@ +If you are part of the *Purchase Manager* group, you can force a confirmed +purchase order to **Full Received** status: you have to check the field +**Force Received** located in the hidden fields on the +tree view (Order Lines tab). diff --git a/purchase_reception_status_line/static/description/icon.png b/purchase_reception_status_line/static/description/icon.png new file mode 100644 index 00000000000..3a0328b516c Binary files /dev/null and b/purchase_reception_status_line/static/description/icon.png differ diff --git a/purchase_reception_status_line/static/description/index.html b/purchase_reception_status_line/static/description/index.html new file mode 100644 index 00000000000..b7fc27be527 --- /dev/null +++ b/purchase_reception_status_line/static/description/index.html @@ -0,0 +1,438 @@ + + + + + +Purchase Reception Status Line + + + +
+

Purchase Reception Status Line

+ + +

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

+

This module adds a field Reception Status on purchase orders line. +On a confirmed purchase order line, it can have 3 different values:

+
    +
  • Nothing Received
  • +
  • Partially Received
  • +
  • Fully Received
  • +
+

Also takes this into account when computing the reception status for the +purchase order.

+

Table of contents

+ +
+

Usage

+

If you are part of the Purchase Manager group, you can force a confirmed +purchase order to Full Received status: you have to check the field +Force Received located in the hidden fields on the +tree view (Order Lines tab).

+
+
+

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

+
    +
  • 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:

+

DavidJForgeFlow

+

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_reception_status_line/tests/__init__.py b/purchase_reception_status_line/tests/__init__.py new file mode 100644 index 00000000000..fbaa84a130e --- /dev/null +++ b/purchase_reception_status_line/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2024 ForgeFlow (http://www.akretion.com/) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_purchase_receipt_status_line diff --git a/purchase_reception_status_line/tests/test_purchase_receipt_status_line.py b/purchase_reception_status_line/tests/test_purchase_receipt_status_line.py new file mode 100644 index 00000000000..1c30b73c9d1 --- /dev/null +++ b/purchase_reception_status_line/tests/test_purchase_receipt_status_line.py @@ -0,0 +1,76 @@ +# Copyright 2024 ForgeFlow (http://www.akretion.com/) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests import TransactionCase + + +class TestPurchaseReceiptionStatusLine(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.partner = cls.env["res.partner"].create({"name": "Purchase Partner"}) + cls.product = cls.env["product.product"].create( + # Use "service" type product to be able to set receipt qty manually + {"name": "Purchase Product", "type": "service"} + ) + cls.order = cls.env["purchase.order"].create( + { + "partner_id": cls.partner.id, + "company_id": cls.env.user.company_id.id, + } + ) + + def test_01_order_empty_with_lines(self): + """Test empty order w/ lines: receipt status should be received""" + line1 = self.env["purchase.order.line"].create( + { + "order_id": self.order.id, + "name": self.product.name, + "product_id": self.product.id, + "product_qty": 0.0, + } + ) + self.order.button_confirm() + self.assertEqual(line1.reception_status, "received") + + def test_02_order_with_lines(self): + """Test order w/ lines + + Add 2 lines: + 1) 20u ordered, 0u received + 2) 5u ordered, 5u received + """ + self.env["purchase.order.line"].create( + [ + { + "order_id": self.order.id, + "name": self.product.name, + "product_id": self.product.id, + "product_qty": 20.0, + "qty_received_manual": 0.0, + }, + { + "order_id": self.order.id, + "name": self.product.name, + "product_id": self.product.id, + "product_qty": 5.0, + "qty_received_manual": 3.0, + }, + ] + ) + self.order.button_confirm() + self.assertEqual(self.order.order_line[0].reception_status, "no") + self.assertEqual(self.order.order_line[1].reception_status, "partial") + self.order.order_line[0].force_received = True + self.assertEqual(self.order.order_line[0].reception_status, "received") + self.assertEqual(self.order.order_line[1].reception_status, "partial") + self.order.order_line[1].force_received = True + self.assertEqual(self.order.order_line[0].reception_status, "received") + self.assertEqual(self.order.order_line[1].reception_status, "received") + self.order.order_line[1].force_received = False + self.assertEqual(self.order.order_line[0].reception_status, "received") + self.assertEqual(self.order.order_line[1].reception_status, "partial") + self.order.force_received = True + self.assertEqual(self.order.order_line[0].reception_status, "received") + self.assertEqual(self.order.order_line[1].reception_status, "received") + self.assertEqual(self.order.order_line[1].force_received, True) diff --git a/purchase_reception_status_line/views/purchase_order.xml b/purchase_reception_status_line/views/purchase_order.xml new file mode 100644 index 00000000000..b0e3325c5dc --- /dev/null +++ b/purchase_reception_status_line/views/purchase_order.xml @@ -0,0 +1,23 @@ + + + + + received_status.purchase.order.form + purchase.order + + + + + + + + + diff --git a/setup/purchase_reception_status_line/odoo/addons/purchase_reception_status_line b/setup/purchase_reception_status_line/odoo/addons/purchase_reception_status_line new file mode 120000 index 00000000000..e4299859527 --- /dev/null +++ b/setup/purchase_reception_status_line/odoo/addons/purchase_reception_status_line @@ -0,0 +1 @@ +../../../../purchase_reception_status_line \ No newline at end of file diff --git a/setup/purchase_reception_status_line/setup.py b/setup/purchase_reception_status_line/setup.py new file mode 100644 index 00000000000..28c57bb6403 --- /dev/null +++ b/setup/purchase_reception_status_line/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)