diff --git a/rma/__manifest__.py b/rma/__manifest__.py index c9e6d57a2..a3003f4fe 100644 --- a/rma/__manifest__.py +++ b/rma/__manifest__.py @@ -34,6 +34,7 @@ "views/rma_tag_views.xml", "views/stock_picking_views.xml", "views/stock_warehouse_views.xml", + "views/dashboard.xml", "views/res_config_settings_views.xml", ], "post_init_hook": "post_init_hook", diff --git a/rma/models/rma_operation.py b/rma/models/rma_operation.py index cb4c08bb0..36936a0e0 100644 --- a/rma/models/rma_operation.py +++ b/rma/models/rma_operation.py @@ -1,7 +1,14 @@ # Copyright 2020 Tecnativa - Ernesto Tejeda # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import fields, models +from ast import literal_eval +from collections import defaultdict + +from odoo import _, api, fields, models +from odoo.osv.expression import AND + +PROCESSED_STATES = ["received", "refunded", "replaced", "finished"] +AWAITING_ACTION_STATES = ["waiting_return", "waiting_replacement", "confirmed"] class RmaOperation(models.Model): @@ -10,7 +17,107 @@ class RmaOperation(models.Model): active = fields.Boolean(default=True) name = fields.Char(required=True, translate=True) + color = fields.Integer() + count_rma_draft = fields.Integer(compute="_compute_count_rma") + count_rma_awaiting_action = fields.Integer(compute="_compute_count_rma") + count_rma_processed = fields.Integer(compute="_compute_count_rma") _sql_constraints = [ ("name_uniq", "unique (name)", "That operation name already exists !"), ] + + @api.model + def _get_rma_draft_domain(self): + return [("state", "=", "draft")] + + @api.model + def _get_rma_awaiting_action_domain(self): + return [("state", "in", AWAITING_ACTION_STATES)] + + @api.model + def _get_rma_processed_domain(self): + return [("state", "in", PROCESSED_STATES)] + + def _compute_count_rma(self): + self.update( + { + "count_rma_draft": 0, + "count_rma_processed": 0, + "count_rma_awaiting_action": 0, + } + ) + state_by_op = defaultdict(int) + for group in self.env["rma"].read_group( + AND([[("operation_id", "!=", False)]]), + groupby=["operation_id", "state"], + fields=["id"], + lazy=False, + ): + operation_id = group.get("operation_id")[0] + state = group.get("state") + count = group.get("__count") + if state == "draft": + state_by_op[(operation_id, "count_rma_draft")] += count + if state in PROCESSED_STATES: + state_by_op[(operation_id, "count_rma_processed")] += count + if state in AWAITING_ACTION_STATES: + state_by_op[(operation_id, "count_rma_awaiting_action")] += count + for (operation_id, field), count in state_by_op.items(): + self.browse(operation_id).update({field: count}) + + def _get_action(self, name, domain): + action = self.env["ir.actions.actions"]._for_xml_id("rma.rma_action") + action["display_name"] = name + context = { + "search_default_operation_id": [self.id], + "default_operation_id": self.id, + } + action_context = literal_eval(action["context"]) + context = {**action_context, **context} + action["context"] = context + action["domain"] = domain + return action + + def get_action_rma_tree_draft(self): + self.ensure_one() + name = self.display_name + ": " + _("Draft") + return self._get_action( + name, + domain=AND( + [ + [("operation_id", "=", self.id)], + self._get_rma_draft_domain(), + ] + ), + ) + + def get_action_rma_tree_awaiting_action(self): + self.ensure_one() + name = self.display_name + ": " + _("Awaiting Action") + return self._get_action( + name, + domain=AND( + [ + [("operation_id", "=", self.id)], + self._get_rma_awaiting_action_domain(), + ] + ), + ) + + def get_action_rma_tree_processed(self): + self.ensure_one() + name = self.display_name + ": " + _("Processed") + return self._get_action( + name, + domain=AND( + [ + [("operation_id", "=", self.id)], + self._get_rma_processed_domain(), + ] + ), + ) + + def get_action_all_rma(self): + self.ensure_one() + name = self.display_name + return self._get_action(name, domain=[("operation_id", "=", self.id)]) diff --git a/rma/tests/__init__.py b/rma/tests/__init__.py index 5f9ab818b..360833ad7 100644 --- a/rma/tests/__init__.py +++ b/rma/tests/__init__.py @@ -1,3 +1,4 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from . import test_rma +from . import test_rma_dashboard diff --git a/rma/tests/test_rma_dashboard.py b/rma/tests/test_rma_dashboard.py new file mode 100644 index 000000000..16415b0fe --- /dev/null +++ b/rma/tests/test_rma_dashboard.py @@ -0,0 +1,73 @@ +# Copyright 2024 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from .test_rma import TestRma + +PROCESSED_STATES = ["received", "refunded", "replaced", "finished"] +AWAITING_ACTION_STATES = ["waiting_return", "waiting_replacement", "confirmed"] + + +class TestRmaDashboard(TestRma): + def test_0(self): + operation_replace = self.env.ref("rma.rma_operation_replace") + operation_return = self.env.ref("rma.rma_operation_return") + operation_refund = self.env.ref("rma.rma_operation_refund") + replace_draft_1 = self._create_rma( + self.partner, self.product, 1, self.rma_loc, operation=operation_replace + ) + self._create_rma( + self.partner, self.product, 1, self.rma_loc, operation=operation_replace + ) # replace_draft_2 + replace_draft_1.copy({"state": "confirmed"}) # replace_confirmed + replace_draft_1.copy({"state": "received"}) # replace_received + replace_draft_1.copy({"state": "waiting_return"}) # replace_waiting_return + replace_draft_1.copy( # replace_waiting_replacement + {"state": "waiting_replacement"} + ) + return_draft = self._create_rma( + self.partner, self.product, 1, self.rma_loc, operation=operation_return + ) + return_draft.copy({"state": "confirmed"}) # return_confirmed + return_draft.copy({"state": "waiting_return"}) # return_waiting_return + return_draft.copy({"state": "returned"}) # return_returned + return_draft.copy({"state": "finished"}) # return_finished + refund_draft = self._create_rma( + self.partner, self.product, 1, self.rma_loc, operation=operation_refund + ) + refund_draft.copy({"state": "finished"}) # refund_refunded + + self.assertEqual(operation_replace.count_rma_draft, 2) + self.assertEqual(operation_replace.count_rma_awaiting_action, 3) + self.assertEqual(operation_replace.count_rma_processed, 1) + + self.assertEqual(operation_return.count_rma_draft, 1) + self.assertEqual(operation_return.count_rma_awaiting_action, 2) + self.assertEqual(operation_return.count_rma_processed, 1) + + self.assertEqual(operation_refund.count_rma_draft, 1) + self.assertEqual(operation_refund.count_rma_awaiting_action, 0) + self.assertEqual(operation_refund.count_rma_processed, 1) + + action = operation_replace.get_action_rma_tree_draft() + self.assertListEqual( + ["&", ("operation_id", "=", operation_replace.id), ("state", "=", "draft")], + action.get("domain"), + ) + action = operation_replace.get_action_rma_tree_awaiting_action() + self.assertListEqual( + [ + "&", + ("operation_id", "=", operation_replace.id), + ("state", "in", AWAITING_ACTION_STATES), + ], + action.get("domain"), + ) + action = operation_replace.get_action_rma_tree_processed() + self.assertListEqual( + [ + "&", + ("operation_id", "=", operation_replace.id), + ("state", "in", PROCESSED_STATES), + ], + action.get("domain"), + ) diff --git a/rma/views/dashboard.xml b/rma/views/dashboard.xml new file mode 100644 index 000000000..53d5e41d3 --- /dev/null +++ b/rma/views/dashboard.xml @@ -0,0 +1,188 @@ + + + + + + rma.operation + + + + + + + + +
+
+
+
+ + + +
+
+ +
+
+
+ +
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+
+
+
+
+ + + RMA Overview + rma.operation + ir.actions.act_window + kanban,form + +

Create a new RMA operation

+

The RMA operation system allows you to configure each return operation + with specific settings that will adjust its behavior.

+
+
+ + + +
diff --git a/rma/views/rma_views.xml b/rma/views/rma_views.xml index 8ad666162..6c57d98d9 100644 --- a/rma/views/rma_views.xml +++ b/rma/views/rma_views.xml @@ -421,4 +421,16 @@ + + + New RMA + rma + ir.actions.act_window + form + { + 'search_operation_id': [active_id], + 'default_operation_id': active_id, + } + +