Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[16.0][IMP] rma: add dashboard #429

Open
wants to merge 1 commit into
base: 16.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rma/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
109 changes: 108 additions & 1 deletion rma/models/rma_operation.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -10,7 +17,107 @@

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)])

Check warning on line 123 in rma/models/rma_operation.py

View check run for this annotation

Codecov / codecov/patch

rma/models/rma_operation.py#L121-L123

Added lines #L121 - L123 were not covered by tests
1 change: 1 addition & 0 deletions rma/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -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
73 changes: 73 additions & 0 deletions rma/tests/test_rma_dashboard.py
Original file line number Diff line number Diff line change
@@ -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"),
)
188 changes: 188 additions & 0 deletions rma/views/dashboard.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2024 ACSONE SA/NV
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo>

<record id="rma_operation_kanban" model="ir.ui.view">
<field name="model">rma.operation</field>
<field name="arch" type="xml">
<kanban
class="oe_background_grey o_kanban_dashboard o_emphasize_colors"
create="0"
group_create="false"
>
<field name="color" />
<field name="count_rma_draft" />
<field name="count_rma_awaiting_action" />
<field name="count_rma_processed" />
<templates>
<t t-name="kanban-box">
<div
t-attf-class="#{!selection_mode ? kanban_color(record.color.raw_value) : ''}"
name="rma_operation"
>
<div t-attf-class="o_kanban_card_header">
<div class="o_kanban_card_header_title">
<div class="o_primary" t-if="!selection_mode">
<a type="object" name="get_action_all_rma">
<field name="name" />
</a>
</div>
<div class="o_primary" t-if="selection_mode">
<field name="name" />
</div>
</div>
<div
class="o_kanban_manage_button_section"
t-if="!selection_mode"
>
<a class="o_kanban_manage_toggle_button" href="#"><i
class="fa fa-ellipsis-v"
role="img"
aria-label="Manage"
title="Manage"
/></a>
</div>
</div>
<div
class="container o_kanban_card_content"
t-if="!selection_mode"
>
<div class="row">
<div class="col-9 o_kanban_primary_left">
<button
class="btn btn-link"
name="get_action_rma_tree_draft"
type="object"
>
<span>Draft</span>
</button>
</div>
<div class="col-3 o_kanban_primary_right">
<button
class="btn btn-link"
name="get_action_rma_tree_draft"
type="object"
>
<span
t-esc="record.count_rma_draft.value"
/>
</button>
</div>
<div class="col-9 o_kanban_primary_left">
<button
class="btn btn-link"
name="get_action_rma_tree_awaiting_action"
type="object"
>
<span>Awaiting action</span>
</button>
</div>
<div class="col-3 o_kanban_primary_right">
<button
class="btn btn-link"
name="get_action_rma_tree_awaiting_action"
type="object"
>
<span
t-esc="record.count_rma_awaiting_action.value"
/>
</button>
</div>
<div class="col-9 o_kanban_primary_left">
<button
class="btn btn-link"
name="get_action_rma_tree_processed"
type="object"
>
<span>Processed</span>
</button>
</div>
<div class="col-3 o_kanban_primary_right">
<button
class="btn btn-link"
name="get_action_rma_tree_processed"
type="object"
>
<span
t-esc="record.count_rma_processed.value"
/>
</button>
</div>
</div>
</div>
<div
class="container o_kanban_card_manage_pane dropdown-menu"
role="menu"
>
<div class="row">
<div
class="col-6 o_kanban_card_manage_section o_kanban_manage_new"
>
<div
role="menuitem"
class="o_kanban_card_manage_title"
>
<span>New</span>
</div>
<div role="menuitem">
<a
name="%(action_rma_form)d"
type="action"
>RMA</a>
</div>
</div>
</div>

<div
t-if="widget.editable"
class="o_kanban_card_manage_settings row"
>
<div
class="col-8"
role="menuitem"
aria-haspopup="true"
>
<ul
class="oe_kanban_colorpicker"
data-field="color"
role="menu"
/>
</div>
<div role="menuitem" class="col-4">
<a
class="dropdown-item"
role="menuitem"
type="edit"
>Configuration</a>
</div>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>

<record id="rma_dashboard_action" model="ir.actions.act_window">
<field name="name">RMA Overview</field>
<field name="res_model">rma.operation</field>
<field name="type">ir.actions.act_window</field>
<field name="view_mode">kanban,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face"> Create a new RMA operation </p>
<p>The RMA operation system allows you to configure each return operation
with specific settings that will adjust its behavior.</p>
</field>
</record>

<menuitem
action="rma_dashboard_action"
id="rma_dashboard_menu"
parent="rma_menu"
sequence="0"
name="Overview"
/>

</odoo>
Loading
Loading