From 1c50d6655143dc176cd5d3d2feeed7b437440d75 Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Fri, 25 Aug 2023 09:13:46 +0200
Subject: [PATCH] # 342 Fix

* fixes bug where rounding error on aggregated db SUM() would occur
* simplifies code base
---
 compensation/models/compensation.py           |  4 +++-
 compensation/models/eco_account.py            | 19 +++++--------------
 compensation/utils/quality.py                 |  2 +-
 .../views/compensation/compensation.py        |  6 ++----
 compensation/views/eco_account/eco_account.py |  4 ++--
 ema/views/ema.py                              |  4 ++--
 konova/tests/test_views.py                    |  2 +-
 7 files changed, 16 insertions(+), 25 deletions(-)

diff --git a/compensation/models/compensation.py b/compensation/models/compensation.py
index 3a238164..33c2008b 100644
--- a/compensation/models/compensation.py
+++ b/compensation/models/compensation.py
@@ -199,7 +199,9 @@ class AbstractCompensation(BaseObject,
         Returns:
 
         """
-        return qs.aggregate(Sum("surface"))["surface__sum"] or 0
+        val = qs.aggregate(Sum("surface"))["surface__sum"] or 0
+        val = float('{:0.2f}'.format(val))
+        return val
 
     def quality_check(self) -> CompensationQualityChecker:
         """ Performs data quality check
diff --git a/compensation/models/eco_account.py b/compensation/models/eco_account.py
index 35e4c02b..913937dc 100644
--- a/compensation/models/eco_account.py
+++ b/compensation/models/eco_account.py
@@ -57,7 +57,7 @@ class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMix
 
     def clean(self):
         # Deductable surface can not be larger than added states after surface
-        after_state_sum = self.get_state_after_surface_sum()
+        after_state_sum = self.get_surface_after_states()
         if self.deductable_surface > after_state_sum:
             raise ValidationError(_("Deductable surface can not be larger than existing surfaces in after states"))
 
@@ -96,15 +96,9 @@ class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMix
         Returns:
             sum_surface (float)
         """
-        return self.deductions.all().aggregate(Sum("surface"))["surface__sum"] or 0
-
-    def get_state_after_surface_sum(self) -> float:
-        """ Calculates the account's after state surface sum
-
-        Returns:
-            sum_surface (float)
-        """
-        return self.after_states.all().aggregate(Sum("surface"))["surface__sum"] or 0
+        val = self.deductions.all().aggregate(Sum("surface"))["surface__sum"] or 0
+        val = float('{:0.2f}'.format(val))
+        return val
 
     def __calculate_deductable_rest(self):
         """ Calculates available rest surface of the eco account
@@ -114,10 +108,7 @@ class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMix
         Returns:
             ret_val_total (float): Total amount
         """
-        deductions = self.deductions.filter(
-            intervention__deleted=None,
-        )
-        deductions_surfaces = deductions.aggregate(Sum("surface"))["surface__sum"] or 0
+        deductions_surfaces = self.get_deductions_surface()
 
         available_surface = self.deductable_surface
         if available_surface is None:
diff --git a/compensation/utils/quality.py b/compensation/utils/quality.py
index 6edd691d..6aa5b11b 100644
--- a/compensation/utils/quality.py
+++ b/compensation/utils/quality.py
@@ -95,7 +95,7 @@ class EcoAccountQualityChecker(CompensationQualityChecker):
         is_surface_invalid = surface == 0
         if is_surface_invalid:
             self._add_missing_attr_name(_("Available Surface"))
-        after_state_surface = self.obj.get_state_after_surface_sum()
+        after_state_surface = self.obj.get_surface_after_states()
         if surface > after_state_surface:
             self.messages.append(
                 _("Deductable surface can not be larger than state surface")
diff --git a/compensation/views/compensation/compensation.py b/compensation/views/compensation/compensation.py
index 32787f84..0a198bb1 100644
--- a/compensation/views/compensation/compensation.py
+++ b/compensation/views/compensation/compensation.py
@@ -228,8 +228,6 @@ def detail_view(request: HttpRequest, id: str):
     _user = request.user
     is_data_shared = comp.intervention.is_shared_with(_user)
 
-
-
     # Order states according to surface
     before_states = comp.before_states.all().prefetch_related("biotope_type").order_by("-surface")
     after_states = comp.after_states.all().prefetch_related("biotope_type").order_by("-surface")
@@ -237,8 +235,8 @@ def detail_view(request: HttpRequest, id: str):
 
     # Precalculate logical errors between before- and after-states
     # Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
-    sum_before_states = before_states.aggregate(Sum("surface"))["surface__sum"] or 0
-    sum_after_states = after_states.aggregate(Sum("surface"))["surface__sum"] or 0
+    sum_before_states = comp.get_surface_before_states()
+    sum_after_states = comp.get_surface_after_states()
     diff_states = abs(sum_before_states - sum_after_states)
 
     request = comp.set_status_messages(request)
diff --git a/compensation/views/eco_account/eco_account.py b/compensation/views/eco_account/eco_account.py
index 71cfda51..685ddd2e 100644
--- a/compensation/views/eco_account/eco_account.py
+++ b/compensation/views/eco_account/eco_account.py
@@ -208,8 +208,8 @@ def detail_view(request: HttpRequest, id: str):
 
     # Precalculate logical errors between before- and after-states
     # Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
-    sum_before_states = before_states.aggregate(Sum("surface"))["surface__sum"] or 0
-    sum_after_states = after_states.aggregate(Sum("surface"))["surface__sum"] or 0
+    sum_before_states = acc.get_surface_before_states()
+    sum_after_states = acc.get_surface_after_states()
     diff_states = abs(sum_before_states - sum_after_states)
     # Calculate rest of available surface for deductions
     available_total = acc.deductable_rest
diff --git a/ema/views/ema.py b/ema/views/ema.py
index 53d525a6..0296b149 100644
--- a/ema/views/ema.py
+++ b/ema/views/ema.py
@@ -149,8 +149,8 @@ def detail_view(request: HttpRequest, id: str):
 
     # Precalculate logical errors between before- and after-states
     # Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
-    sum_before_states = before_states.aggregate(Sum("surface"))["surface__sum"] or 0
-    sum_after_states = after_states.aggregate(Sum("surface"))["surface__sum"] or 0
+    sum_before_states = ema.get_surface_before_states()
+    sum_after_states = ema.get_surface_after_states()
     diff_states = abs(sum_before_states - sum_after_states)
 
     ema.set_status_messages(request)
diff --git a/konova/tests/test_views.py b/konova/tests/test_views.py
index 437f114a..a78116fc 100644
--- a/konova/tests/test_views.py
+++ b/konova/tests/test_views.py
@@ -441,7 +441,7 @@ class BaseTestCase(TestCase):
         eco_account.actions.add(self.comp_action)
         eco_account.geometry.geom = self.create_dummy_geometry()
         eco_account.geometry.save()
-        eco_account.deductable_surface = eco_account.get_state_after_surface_sum()
+        eco_account.deductable_surface = eco_account.get_surface_after_states()
         eco_account.deadlines.add(self.finished_deadline)
         eco_account.save()
         return eco_account