From eb0bede9e8653b83fe471f5d7585cff46ff51d82 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Fri, 12 Jul 2024 16:32:19 +0200 Subject: [PATCH] [IMP] rma_sale: Display the allowed quantity to return in the RMA wizard - Display the allowed quantity to return - Simplify data encoding by adding a button to select/deselect all lines - Ensure that the quantity to return is always less than or equal to the allowed quantity --- rma_sale/README.rst | 2 + rma_sale/models/sale.py | 1 + rma_sale/readme/CONTRIBUTORS.rst | 2 + rma_sale/static/description/index.html | 2 + rma_sale/tests/__init__.py | 1 + rma_sale/tests/test_rma_sale.py | 1 + rma_sale/tests/test_rma_sale_allowed_qty.py | 110 ++++++++++++++++++ rma_sale/wizard/sale_order_rma_wizard.py | 36 ++++++ .../wizard/sale_order_rma_wizard_views.xml | 2 + rma_sale_mrp/tests/test_rma_sale_mrp.py | 3 +- 10 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 rma_sale/tests/test_rma_sale_allowed_qty.py diff --git a/rma_sale/README.rst b/rma_sale/README.rst index 058ae37c2..082831c98 100644 --- a/rma_sale/README.rst +++ b/rma_sale/README.rst @@ -106,6 +106,8 @@ Contributors * Chafique Delli * Giovanni Serra - Ooops +* Souheil Bejaoui - ACSONE SA/NV +* Jacques-Etienne Baudoux - BCIM Maintainers ~~~~~~~~~~~ diff --git a/rma_sale/models/sale.py b/rma_sale/models/sale.py index 97b6209e9..4900bb600 100644 --- a/rma_sale/models/sale.py +++ b/rma_sale/models/sale.py @@ -31,6 +31,7 @@ def _prepare_rma_wizard_line_vals(self, data): return { "product_id": data["product"].id, "quantity": data["quantity"], + "allowed_quantity": data["quantity"], "sale_line_id": data["sale_line_id"].id, "uom_id": data["uom"].id, "picking_id": data["picking"] and data["picking"].id, diff --git a/rma_sale/readme/CONTRIBUTORS.rst b/rma_sale/readme/CONTRIBUTORS.rst index 75646b6d3..5caeb7de7 100644 --- a/rma_sale/readme/CONTRIBUTORS.rst +++ b/rma_sale/readme/CONTRIBUTORS.rst @@ -7,3 +7,5 @@ * Chafique Delli * Giovanni Serra - Ooops +* Souheil Bejaoui - ACSONE SA/NV +* Jacques-Etienne Baudoux - BCIM \ No newline at end of file diff --git a/rma_sale/static/description/index.html b/rma_sale/static/description/index.html index 34b7ada7b..2f1276967 100644 --- a/rma_sale/static/description/index.html +++ b/rma_sale/static/description/index.html @@ -454,6 +454,8 @@

Contributors

  • Chafique Delli <chafique.delli@akretion.com>
  • Giovanni Serra - Ooops <giovanni@ooops404.com>
  • +
  • Souheil Bejaoui - ACSONE SA/NV <souheil.bejaoui@acsone.eu.com>
  • +
  • Jacques-Etienne Baudoux - BCIM <je@bcim.be>
  • diff --git a/rma_sale/tests/__init__.py b/rma_sale/tests/__init__.py index 5318a7053..77bce454e 100644 --- a/rma_sale/tests/__init__.py +++ b/rma_sale/tests/__init__.py @@ -2,3 +2,4 @@ from . import test_rma_sale from . import test_rma_sale_portal +from . import test_rma_sale_allowed_qty diff --git a/rma_sale/tests/test_rma_sale.py b/rma_sale/tests/test_rma_sale.py index f295d3ac8..b8628b60a 100644 --- a/rma_sale/tests/test_rma_sale.py +++ b/rma_sale/tests/test_rma_sale.py @@ -159,6 +159,7 @@ def test_create_rma_from_so_portal_user(self): "product_id": order.order_line.product_id.id, "sale_line_id": order.order_line.id, "quantity": order.order_line.product_uom_qty, + "allowed_quantity": order.order_line.qty_delivered, "uom_id": order.order_line.product_uom.id, "picking_id": order.picking_ids[0].id, "operation_id": operation.id, diff --git a/rma_sale/tests/test_rma_sale_allowed_qty.py b/rma_sale/tests/test_rma_sale_allowed_qty.py new file mode 100644 index 000000000..d926760ab --- /dev/null +++ b/rma_sale/tests/test_rma_sale_allowed_qty.py @@ -0,0 +1,110 @@ +# Copyright 2024 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import Command +from odoo.exceptions import ValidationError +from odoo.tests.common import TransactionCase + + +class TestRmaSaleQuantityAllowed(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.warehouse = cls.env.ref("stock.warehouse0") + cls.loc_stock = cls.warehouse.lot_stock_id + cls.partner1 = cls.env["res.partner"].create({"name": "Partner"}) + cls.p1 = cls.env["product.product"].create( + {"name": "Unittest P1", "type": "product"} + ) + cls.so = cls.env["sale.order"].create( + { + "partner_id": cls.partner1.id, + "order_line": [ + Command.create( + { + "name": cls.p1.name, + "product_id": cls.p1.id, + "product_uom_qty": 5, + "price_unit": 50, + }, + ) + ], + } + ) + cls.env["stock.quant"].with_context(inventory_mode=True).create( + { + "product_id": cls.p1.id, + "inventory_quantity": 10, + "location_id": cls.loc_stock.id, + } + )._apply_inventory() + cls.so.action_confirm() + cls.picking = cls.so.picking_ids[0] + + def _get_rma_wizard(self): + action = self.so.action_create_rma() + return self.env[action.get("res_model")].browse(action.get("res_id")) + + def _deliver(self, qty): + self.picking.move_line_ids.qty_done = qty + self.picking._action_done() + self.assertEqual(self.picking.state, "done") + self.assertEqual(self.so.order_line.qty_delivered, qty) + + def test_1(self): + """ + Test rma wizard: + + - fully deliver the so + - open rma wizard + expected: + - qty proposed: 5 + - allowed qty 5 + - qty 0 if is_return_all = False + """ + self._deliver(5) + wizard = self._get_rma_wizard() + self.assertEqual(len(wizard.line_ids), 1) + self.assertEqual(wizard.line_ids.quantity, 5) + self.assertEqual(wizard.line_ids.allowed_quantity, 5) + wizard.is_return_all = False + self.assertEqual(wizard.line_ids.quantity, 0) + wizard.is_return_all = True + self.assertEqual(wizard.line_ids.quantity, 5) + + def test_2(self): + """ + Test rma wizard: + + - partially deliver the so + - open rma wizard + expected: + - qty proposed: 3 + - allowed qty 3 + - qty 0 if is_return_all = False + """ + self._deliver(3) + wizard = self._get_rma_wizard() + self.assertEqual(len(wizard.line_ids), 1) + self.assertEqual(wizard.line_ids.quantity, 3) + self.assertEqual(wizard.line_ids.allowed_quantity, 3) + wizard.is_return_all = False + self.assertEqual(wizard.line_ids.quantity, 0) + wizard.is_return_all = True + self.assertEqual(wizard.line_ids.quantity, 3) + + def test_3(self): + """ + Test rma wizard: + Try to return more than the allowed qty + """ + self._deliver(3) + wizard = self._get_rma_wizard() + self.assertEqual(len(wizard.line_ids), 1) + self.assertEqual(wizard.line_ids.quantity, 3) + self.assertEqual(wizard.line_ids.allowed_quantity, 3) + with self.assertRaises( + ValidationError, msg="You can't exceed the allowed quantity" + ): + wizard.line_ids.quantity = 5 + wizard.line_ids.quantity = 1 diff --git a/rma_sale/wizard/sale_order_rma_wizard.py b/rma_sale/wizard/sale_order_rma_wizard.py index b976f4450..54d69c416 100644 --- a/rma_sale/wizard/sale_order_rma_wizard.py +++ b/rma_sale/wizard/sale_order_rma_wizard.py @@ -1,8 +1,11 @@ # Copyright 2020 Tecnativa - Ernesto Tejeda # Copyright 2022 Tecnativa - Víctor Martínez +# Copyright 2024 ACSONE SA/NV # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from odoo import SUPERUSER_ID, _, api, fields, models +from odoo.exceptions import ValidationError +from odoo.tools.float_utils import float_compare class SaleOrderRmaWizard(models.TransientModel): @@ -46,6 +49,7 @@ def _domain_location_id(self): custom_description = fields.Text( help="Values coming from portal RMA request form custom fields", ) + is_return_all = fields.Boolean(string="Return All?", default=True) def create_rma(self, from_portal=False): self.ensure_one() @@ -126,7 +130,11 @@ class SaleOrderLineRmaWizard(models.TransientModel): quantity = fields.Float( digits="Product Unit of Measure", required=True, + compute="_compute_quantity", + store=True, + readonly=False, ) + allowed_quantity = fields.Float(digits="Product Unit of Measure", readonly=True) uom_id = fields.Many2one( comodel_name="uom.uom", string="Unit of Measure", @@ -151,6 +159,14 @@ class SaleOrderLineRmaWizard(models.TransientModel): ) description = fields.Text() + @api.depends("wizard_id.is_return_all", "allowed_quantity") + def _compute_quantity(self): + for rec in self: + if not rec.wizard_id.is_return_all: + rec.quantity = 0 + else: + rec.quantity = rec.allowed_quantity + @api.onchange("product_id") def onchange_product_id(self): self.picking_id = False @@ -187,6 +203,26 @@ def _compute_allowed_picking_ids(self): lambda x: x.state == "done" ) + @api.constrains("quantity", "allowed_quantity") + def _check_quantity(self): + precision = self.env["decimal.precision"].precision_get( + "Product Unit of Measure" + ) + for rec in self: + if ( + float_compare( + rec.quantity, rec.allowed_quantity, precision_digits=precision + ) + == 1 + ): + raise ValidationError( + _( + "You can't exceed the allowed quantity for returning product " + "%(product)s.", + product=rec.product_id.display_name, + ) + ) + def _prepare_rma_values(self): self.ensure_one() partner_shipping = ( diff --git a/rma_sale/wizard/sale_order_rma_wizard_views.xml b/rma_sale/wizard/sale_order_rma_wizard_views.xml index 078799419..f24482d50 100644 --- a/rma_sale/wizard/sale_order_rma_wizard_views.xml +++ b/rma_sale/wizard/sale_order_rma_wizard_views.xml @@ -12,12 +12,14 @@
    + +