Skip to content
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 .ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ exclude = ["setup/*"]
[lint.per-file-ignores]
"__init__.py" = ["F401", "I001"] # ignore unused and unsorted imports in __init__.py
"__manifest__.py" = ["B018"] # useless expression
"mis_builder/models/mis_report_style.py" = ["C901"] # ignore complexity for compare_and_render method

[lint.isort]
section-order = ["future", "standard-library", "third-party", "odoo", "odoo-addons", "first-party", "local-folder"]
Expand Down
11 changes: 10 additions & 1 deletion mis_builder/models/mis_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,15 @@
from .expression_evaluator import ExpressionEvaluator
from .kpimatrix import KpiMatrix
from .mis_kpi_data import ACC_AVG, ACC_NONE, ACC_SUM
from .mis_report_style import CMP_DIFF, CMP_NONE, CMP_PCT, TYPE_NUM, TYPE_PCT, TYPE_STR
from .mis_report_style import (
CMP_DIFF,
CMP_NONE,
CMP_PCT,
CMP_PCT_NEG,
TYPE_NUM,
TYPE_PCT,
TYPE_STR,
)
from .mis_safe_eval import DataError
from .simple_array import SimpleArray, named_simple_array

Expand Down Expand Up @@ -123,6 +131,7 @@ class MisReportKpi(models.Model):
[
(CMP_DIFF, "Difference"),
(CMP_PCT, "Percentage"),
(CMP_PCT_NEG, "Percentage (negative growth)"),
(CMP_NONE, "None"),
],
required=True,
Expand Down
20 changes: 19 additions & 1 deletion mis_builder/models/mis_report_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def copy(self): # pylint: disable=copy-wo-api-one,method-required-super

CMP_DIFF = "diff"
CMP_PCT = "pct"
CMP_PCT_NEG = "pct_neg" # Percentage (negative growth)
CMP_NONE = "none"


Expand Down Expand Up @@ -241,7 +242,7 @@ def render_str(self, lang, value):
return unicode(value)

@api.model
def compare_and_render(
def compare_and_render( # pylint: disable=too-many-locals,too-complex
self,
lang,
style_props,
Expand Down Expand Up @@ -304,6 +305,23 @@ def compare_and_render(
delta_type = TYPE_PCT
else:
delta = AccountingNone
elif compare_method == CMP_PCT_NEG:
if base_value and round(base_value, style_props.dp or 0) != 0:
# Calculate the percentage change
delta = (value - base_value) / abs(base_value)
if delta and round(delta, 3) != 0:
# For negative values, invert the growth logic
if base_value < 0:
# If the new value is more negative than base,
# it's negative growth
if value < base_value:
delta = -abs(delta) # Negative growth
else:
delta = abs(delta) # Positive growth
delta_style.update(dp=1)
delta_type = TYPE_PCT
else:
delta = AccountingNone
if delta is not AccountingNone:
delta_r = self.render(lang, delta_style, delta_type, delta, sign="+")
return delta, delta_r, delta_style, delta_type
Expand Down
45 changes: 44 additions & 1 deletion mis_builder/tests/test_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@

from ..models.accounting_none import AccountingNone
from ..models.data_error import DataError
from ..models.mis_report_style import CMP_DIFF, CMP_PCT, TYPE_NUM, TYPE_PCT, TYPE_STR
from ..models.mis_report_style import (
CMP_DIFF,
CMP_PCT,
CMP_PCT_NEG,
TYPE_NUM,
TYPE_PCT,
TYPE_STR,
)


class TestRendering(common.TransactionCase):
Expand Down Expand Up @@ -184,6 +191,42 @@ def test_compare_pct_result_type(self):
)
self.assertEqual(result[3], TYPE_NUM)

def test_compare_num_pct_neg(self):
"""Test percentage (negative growth) comparison method."""
# Case 1: Cost increases (more negative) = negative growth
# -100 to -114.6 should give -14.6%
result = self._compare_and_render(-114.6, -100, TYPE_NUM, CMP_PCT_NEG)
self.assertEqual((-0.146, "\u201114.6\xa0%"), result)

# Case 2: Cost decreases (less negative) = positive growth
# -100 to -80 should give +20%
result = self._compare_and_render(-80, -100, TYPE_NUM, CMP_PCT_NEG)
self.assertEqual((0.2, "+20.0\xa0%"), result)

# Case 3: Positive values (should work same as CMP_PCT)
result = self._compare_and_render(120, 100, TYPE_NUM, CMP_PCT_NEG)
self.assertEqual((0.2, "+20.0\xa0%"), result)

# Case 4: From positive to negative
result = self._compare_and_render(-50, 100, TYPE_NUM, CMP_PCT_NEG)
self.assertEqual((-1.5, "\u2011150.0\xa0%"), result)

# Case 5: Edge case - zero base value
result = self._compare_and_render(50, 0, TYPE_NUM, CMP_PCT_NEG)
self.assertEqual((AccountingNone, ""), result)

# Case 6: Edge case - both zero
result = self._compare_and_render(0, 0, TYPE_NUM, CMP_PCT_NEG)
self.assertEqual((AccountingNone, ""), result)

# Case 7: Small change detection
result = self._compare_and_render(-100.01, -100, TYPE_NUM, CMP_PCT_NEG)
self.assertEqual((AccountingNone, ""), result)

# Case 8: Large negative growth
result = self._compare_and_render(-200, -100, TYPE_NUM, CMP_PCT_NEG)
self.assertEqual((-1.0, "\u2011100.0\xa0%"), result)

def test_merge(self):
self.style.color = "#FF0000"
self.style.color_inherit = False
Expand Down
Loading