Skip to content

Commit 70872c5

Browse files
committed
[IMP] rma: add dashboard
1 parent 98ec8b5 commit 70872c5

File tree

6 files changed

+383
-1
lines changed

6 files changed

+383
-1
lines changed

rma/__manifest__.py

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"views/rma_tag_views.xml",
3535
"views/stock_picking_views.xml",
3636
"views/stock_warehouse_views.xml",
37+
"views/dashboard.xml",
3738
"views/res_config_settings_views.xml",
3839
],
3940
"post_init_hook": "post_init_hook",

rma/models/rma_operation.py

+108-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
# Copyright 2020 Tecnativa - Ernesto Tejeda
22
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
33

4-
from odoo import fields, models
4+
from ast import literal_eval
5+
from collections import defaultdict
6+
7+
from odoo import _, api, fields, models
8+
from odoo.osv.expression import AND
9+
10+
PROCESSED_STATES = ["received", "refunded", "replaced", "finished"]
11+
AWAITING_ACTION_STATES = ["waiting_return", "waiting_replacement", "confirmed"]
512

613

714
class RmaOperation(models.Model):
@@ -10,7 +17,107 @@ class RmaOperation(models.Model):
1017

1118
active = fields.Boolean(default=True)
1219
name = fields.Char(required=True, translate=True)
20+
color = fields.Integer()
21+
count_rma_draft = fields.Integer(compute="_compute_count_rma")
22+
count_rma_awaiting_action = fields.Integer(compute="_compute_count_rma")
23+
count_rma_processed = fields.Integer(compute="_compute_count_rma")
1324

1425
_sql_constraints = [
1526
("name_uniq", "unique (name)", "That operation name already exists !"),
1627
]
28+
29+
@api.model
30+
def _get_rma_draft_domain(self):
31+
return [("state", "=", "draft")]
32+
33+
@api.model
34+
def _get_rma_awaiting_action_domain(self):
35+
return [("state", "in", AWAITING_ACTION_STATES)]
36+
37+
@api.model
38+
def _get_rma_processed_domain(self):
39+
return [("state", "in", PROCESSED_STATES)]
40+
41+
def _compute_count_rma(self):
42+
self.update(
43+
{
44+
"count_rma_draft": 0,
45+
"count_rma_processed": 0,
46+
"count_rma_awaiting_action": 0,
47+
}
48+
)
49+
state_by_op = defaultdict(int)
50+
for group in self.env["rma"].read_group(
51+
AND([[("operation_id", "!=", False)]]),
52+
groupby=["operation_id", "state"],
53+
fields=["id"],
54+
lazy=False,
55+
):
56+
operation_id = group.get("operation_id")[0]
57+
state = group.get("state")
58+
count = group.get("__count")
59+
if state == "draft":
60+
state_by_op[(operation_id, "count_rma_draft")] += count
61+
if state in PROCESSED_STATES:
62+
state_by_op[(operation_id, "count_rma_processed")] += count
63+
if state in AWAITING_ACTION_STATES:
64+
state_by_op[(operation_id, "count_rma_awaiting_action")] += count
65+
for (operation_id, field), count in state_by_op.items():
66+
self.browse(operation_id).update({field: count})
67+
68+
def _get_action(self, name, domain):
69+
action = self.env["ir.actions.actions"]._for_xml_id("rma.rma_action")
70+
action["display_name"] = name
71+
context = {
72+
"search_default_operation_id": [self.id],
73+
"default_operation_id": self.id,
74+
}
75+
action_context = literal_eval(action["context"])
76+
context = {**action_context, **context}
77+
action["context"] = context
78+
action["domain"] = domain
79+
return action
80+
81+
def get_action_rma_tree_draft(self):
82+
self.ensure_one()
83+
name = self.display_name + ": " + _("Draft")
84+
return self._get_action(
85+
name,
86+
domain=AND(
87+
[
88+
[("operation_id", "=", self.id)],
89+
self._get_rma_draft_domain(),
90+
]
91+
),
92+
)
93+
94+
def get_action_rma_tree_awaiting_action(self):
95+
self.ensure_one()
96+
name = self.display_name + ": " + _("Awaiting Action")
97+
return self._get_action(
98+
name,
99+
domain=AND(
100+
[
101+
[("operation_id", "=", self.id)],
102+
self._get_rma_awaiting_action_domain(),
103+
]
104+
),
105+
)
106+
107+
def get_action_rma_tree_processed(self):
108+
self.ensure_one()
109+
name = self.display_name + ": " + _("Processed")
110+
return self._get_action(
111+
name,
112+
domain=AND(
113+
[
114+
[("operation_id", "=", self.id)],
115+
self._get_rma_processed_domain(),
116+
]
117+
),
118+
)
119+
120+
def get_action_all_rma(self):
121+
self.ensure_one()
122+
name = self.display_name
123+
return self._get_action(name, domain=[("operation_id", "=", self.id)])

rma/tests/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
22

33
from . import test_rma
4+
from . import test_rma_dashboard

rma/tests/test_rma_dashboard.py

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Copyright 2024 ACSONE SA/NV
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3+
4+
from .test_rma import TestRma
5+
6+
PROCESSED_STATES = ["received", "refunded", "replaced", "finished"]
7+
AWAITING_ACTION_STATES = ["waiting_return", "waiting_replacement", "confirmed"]
8+
9+
10+
class TestRmaDashboard(TestRma):
11+
def test_0(self):
12+
operation_replace = self.env.ref("rma.rma_operation_replace")
13+
operation_return = self.env.ref("rma.rma_operation_return")
14+
operation_refund = self.env.ref("rma.rma_operation_refund")
15+
replace_draft_1 = self._create_rma(
16+
self.partner, self.product, 1, self.rma_loc, operation=operation_replace
17+
)
18+
self._create_rma(
19+
self.partner, self.product, 1, self.rma_loc, operation=operation_replace
20+
) # replace_draft_2
21+
replace_draft_1.copy({"state": "confirmed"}) # replace_confirmed
22+
replace_draft_1.copy({"state": "received"}) # replace_received
23+
replace_draft_1.copy({"state": "waiting_return"}) # replace_waiting_return
24+
replace_draft_1.copy( # replace_waiting_replacement
25+
{"state": "waiting_replacement"}
26+
)
27+
return_draft = self._create_rma(
28+
self.partner, self.product, 1, self.rma_loc, operation=operation_return
29+
)
30+
return_draft.copy({"state": "confirmed"}) # return_confirmed
31+
return_draft.copy({"state": "waiting_return"}) # return_waiting_return
32+
return_draft.copy({"state": "returned"}) # return_returned
33+
return_draft.copy({"state": "finished"}) # return_finished
34+
refund_draft = self._create_rma(
35+
self.partner, self.product, 1, self.rma_loc, operation=operation_refund
36+
)
37+
refund_draft.copy({"state": "finished"}) # refund_refunded
38+
39+
self.assertEqual(operation_replace.count_rma_draft, 2)
40+
self.assertEqual(operation_replace.count_rma_awaiting_action, 3)
41+
self.assertEqual(operation_replace.count_rma_processed, 1)
42+
43+
self.assertEqual(operation_return.count_rma_draft, 1)
44+
self.assertEqual(operation_return.count_rma_awaiting_action, 2)
45+
self.assertEqual(operation_return.count_rma_processed, 1)
46+
47+
self.assertEqual(operation_refund.count_rma_draft, 1)
48+
self.assertEqual(operation_refund.count_rma_awaiting_action, 0)
49+
self.assertEqual(operation_refund.count_rma_processed, 1)
50+
51+
action = operation_replace.get_action_rma_tree_draft()
52+
self.assertListEqual(
53+
["&", ("operation_id", "=", operation_replace.id), ("state", "=", "draft")],
54+
action.get("domain"),
55+
)
56+
action = operation_replace.get_action_rma_tree_awaiting_action()
57+
self.assertListEqual(
58+
[
59+
"&",
60+
("operation_id", "=", operation_replace.id),
61+
("state", "in", AWAITING_ACTION_STATES),
62+
],
63+
action.get("domain"),
64+
)
65+
action = operation_replace.get_action_rma_tree_processed()
66+
self.assertListEqual(
67+
[
68+
"&",
69+
("operation_id", "=", operation_replace.id),
70+
("state", "in", PROCESSED_STATES),
71+
],
72+
action.get("domain"),
73+
)

rma/views/dashboard.xml

+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<!-- Copyright 2024 ACSONE SA/NV
3+
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
4+
<odoo>
5+
6+
<record id="rma_operation_kanban" model="ir.ui.view">
7+
<field name="model">rma.operation</field>
8+
<field name="arch" type="xml">
9+
<kanban
10+
class="oe_background_grey o_kanban_dashboard o_emphasize_colors"
11+
create="0"
12+
group_create="false"
13+
>
14+
<field name="color" />
15+
<field name="count_rma_draft" />
16+
<field name="count_rma_awaiting_action" />
17+
<field name="count_rma_processed" />
18+
<templates>
19+
<t t-name="kanban-box">
20+
<div
21+
t-attf-class="#{!selection_mode ? kanban_color(record.color.raw_value) : ''}"
22+
name="rma_operation"
23+
>
24+
<div t-attf-class="o_kanban_card_header">
25+
<div class="o_kanban_card_header_title">
26+
<div class="o_primary" t-if="!selection_mode">
27+
<a type="object" name="get_action_all_rma">
28+
<field name="name" />
29+
</a>
30+
</div>
31+
<div class="o_primary" t-if="selection_mode">
32+
<field name="name" />
33+
</div>
34+
</div>
35+
<div
36+
class="o_kanban_manage_button_section"
37+
t-if="!selection_mode"
38+
>
39+
<a class="o_kanban_manage_toggle_button" href="#"><i
40+
class="fa fa-ellipsis-v"
41+
role="img"
42+
aria-label="Manage"
43+
title="Manage"
44+
/></a>
45+
</div>
46+
</div>
47+
<div
48+
class="container o_kanban_card_content"
49+
t-if="!selection_mode"
50+
>
51+
<div class="row">
52+
<div class="col-9 o_kanban_primary_left">
53+
<button
54+
class="btn btn-link"
55+
name="get_action_rma_tree_draft"
56+
type="object"
57+
>
58+
<span>Draft</span>
59+
</button>
60+
</div>
61+
<div class="col-3 o_kanban_primary_right">
62+
<button
63+
class="btn btn-link"
64+
name="get_action_rma_tree_draft"
65+
type="object"
66+
>
67+
<span
68+
t-esc="record.count_rma_draft.value"
69+
/>
70+
</button>
71+
</div>
72+
<div class="col-9 o_kanban_primary_left">
73+
<button
74+
class="btn btn-link"
75+
name="get_action_rma_tree_awaiting_action"
76+
type="object"
77+
>
78+
<span>Awaiting action</span>
79+
</button>
80+
</div>
81+
<div class="col-3 o_kanban_primary_right">
82+
<button
83+
class="btn btn-link"
84+
name="get_action_rma_tree_awaiting_action"
85+
type="object"
86+
>
87+
<span
88+
t-esc="record.count_rma_awaiting_action.value"
89+
/>
90+
</button>
91+
</div>
92+
<div class="col-9 o_kanban_primary_left">
93+
<button
94+
class="btn btn-link"
95+
name="get_action_rma_tree_processed"
96+
type="object"
97+
>
98+
<span>Processed</span>
99+
</button>
100+
</div>
101+
<div class="col-3 o_kanban_primary_right">
102+
<button
103+
class="btn btn-link"
104+
name="get_action_rma_tree_processed"
105+
type="object"
106+
>
107+
<span
108+
t-esc="record.count_rma_processed.value"
109+
/>
110+
</button>
111+
</div>
112+
</div>
113+
</div>
114+
<div
115+
class="container o_kanban_card_manage_pane dropdown-menu"
116+
role="menu"
117+
>
118+
<div class="row">
119+
<div
120+
class="col-6 o_kanban_card_manage_section o_kanban_manage_new"
121+
>
122+
<div
123+
role="menuitem"
124+
class="o_kanban_card_manage_title"
125+
>
126+
<span>New</span>
127+
</div>
128+
<div role="menuitem">
129+
<a
130+
name="%(action_rma_form)d"
131+
type="action"
132+
>RMA</a>
133+
</div>
134+
</div>
135+
</div>
136+
137+
<div
138+
t-if="widget.editable"
139+
class="o_kanban_card_manage_settings row"
140+
>
141+
<div
142+
class="col-8"
143+
role="menuitem"
144+
aria-haspopup="true"
145+
>
146+
<ul
147+
class="oe_kanban_colorpicker"
148+
data-field="color"
149+
role="menu"
150+
/>
151+
</div>
152+
<div role="menuitem" class="col-4">
153+
<a
154+
class="dropdown-item"
155+
role="menuitem"
156+
type="edit"
157+
>Configuration</a>
158+
</div>
159+
</div>
160+
</div>
161+
</div>
162+
</t>
163+
</templates>
164+
</kanban>
165+
</field>
166+
</record>
167+
168+
<record id="rma_dashboard_action" model="ir.actions.act_window">
169+
<field name="name">RMA Overview</field>
170+
<field name="res_model">rma.operation</field>
171+
<field name="type">ir.actions.act_window</field>
172+
<field name="view_mode">kanban,form</field>
173+
<field name="help" type="html">
174+
<p class="o_view_nocontent_smiling_face"> Create a new RMA operation </p>
175+
<p>The RMA operation system allows you to configure each return operation
176+
with specific settings that will adjust its behavior.</p>
177+
</field>
178+
</record>
179+
180+
<menuitem
181+
action="rma_dashboard_action"
182+
id="rma_dashboard_menu"
183+
parent="rma_menu"
184+
sequence="0"
185+
name="Overview"
186+
/>
187+
188+
</odoo>

0 commit comments

Comments
 (0)