From 73f3887941c05616273e0a5a53743b53ca806faf Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Tue, 11 Oct 2022 13:31:00 +0200
Subject: [PATCH 1/2] Hotfix for ordering of availability

* fixes error 500 in case of ordering by availability
---
 compensation/tables/eco_account.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/compensation/tables/eco_account.py b/compensation/tables/eco_account.py
index 17b8ecaa..39a74d76 100644
--- a/compensation/tables/eco_account.py
+++ b/compensation/tables/eco_account.py
@@ -36,7 +36,7 @@ class EcoAccountTable(BaseTable, TableRenderMixin):
     )
     av = tables.Column(
         verbose_name=_("Available"),
-        orderable=True,
+        orderable=False,
         empty_values=[],
         attrs={
             "th": {

From 68dfef18c589b69c28fcd0432cc9cad85c5f93b7 Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Tue, 11 Oct 2022 15:20:11 +0200
Subject: [PATCH 2/2] Fixes EcoAccount availability ordering

* adds db based table ordering for EcoAccountTable
---
 .../0011_ecoaccount_deductable_rest.py        | 36 +++++++++++++++
 compensation/models/eco_account.py            | 46 +++++++++++++++----
 compensation/tables/eco_account.py            | 11 +++--
 .../tests/ecoaccount/test_workflow.py         |  2 +-
 compensation/views/eco_account/eco_account.py |  3 +-
 5 files changed, 83 insertions(+), 15 deletions(-)
 create mode 100644 compensation/migrations/0011_ecoaccount_deductable_rest.py

diff --git a/compensation/migrations/0011_ecoaccount_deductable_rest.py b/compensation/migrations/0011_ecoaccount_deductable_rest.py
new file mode 100644
index 00000000..96ab2d3b
--- /dev/null
+++ b/compensation/migrations/0011_ecoaccount_deductable_rest.py
@@ -0,0 +1,36 @@
+# Generated by Django 3.1.3 on 2022-10-11 11:39
+
+from django.db import migrations, models
+from django.db.models import Sum
+
+
+def fill_deductable_rest(apps, schema_editor):
+    EcoAccount = apps.get_model("compensation", "EcoAccount")
+    accs = EcoAccount.objects.all()
+    for acc in accs:
+
+        deductions = acc.deductions.filter(
+            intervention__deleted=None,
+        )
+        deductions_surfaces = deductions.aggregate(Sum("surface"))["surface__sum"] or 0
+        available_surfaces = acc.deductable_surface or deductions_surfaces
+        rest = available_surfaces - deductions_surfaces
+
+        acc.deductable_rest = rest
+        acc.save()
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('compensation', '0010_auto_20220815_1030'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='ecoaccount',
+            name='deductable_rest',
+            field=models.FloatField(blank=True, default=0, help_text='Amount of deductable rest', null=True),
+        ),
+        migrations.RunPython(fill_deductable_rest)
+    ]
diff --git a/compensation/models/eco_account.py b/compensation/models/eco_account.py
index dc9e336f..48414cca 100644
--- a/compensation/models/eco_account.py
+++ b/compensation/models/eco_account.py
@@ -35,6 +35,12 @@ class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMix
         help_text="Amount of deductable surface - can be lower than the total surface due to deduction limitations",
         default=0,
     )
+    deductable_rest = models.FloatField(
+        blank=True,
+        null=True,
+        help_text="Amount of deductable rest",
+        default=0,
+    )
 
     legal = models.OneToOneField(
         "intervention.Legal",
@@ -100,28 +106,22 @@ class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMix
         """
         return self.after_states.all().aggregate(Sum("surface"))["surface__sum"] or 0
 
-    def get_available_rest(self) -> (float, float):
+    def __calculate_deductable_rest(self):
         """ Calculates available rest surface of the eco account
 
         Args:
 
         Returns:
             ret_val_total (float): Total amount
-            ret_val_relative (float): Amount as percentage (0-100)
         """
         deductions = self.deductions.filter(
             intervention__deleted=None,
         )
         deductions_surfaces = deductions.aggregate(Sum("surface"))["surface__sum"] or 0
         available_surfaces = self.deductable_surface or deductions_surfaces ## no division by zero
-        ret_val_total = available_surfaces - deductions_surfaces
+        ret_val = available_surfaces - deductions_surfaces
 
-        if available_surfaces > 0:
-            ret_val_relative = int((ret_val_total / available_surfaces) * 100)
-        else:
-            ret_val_relative = 0
-
-        return ret_val_total, ret_val_relative
+        return ret_val
 
     def quality_check(self) -> EcoAccountQualityChecker:
         """ Quality check
@@ -181,6 +181,29 @@ class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMix
         for team_id in shared_teams:
             celery_send_mail_deduction_changed_team.delay(self.identifier, self.title, team_id, data_change)
 
+    def update_deductable_rest(self):
+        """
+        Updates deductable_rest, which holds the amount of rest surface for this account.
+
+        Returns:
+
+        """
+        self.deductable_rest = self.__calculate_deductable_rest()
+        self.save()
+
+    def get_deductable_rest_relative(self):
+        """
+        Returns deductable_rest relative to deductable_surface mapped to [0,100]
+
+        Returns:
+
+        """
+        try:
+            ret_val = int((self.deductable_rest / (self.deductable_surface or 0)) * 100)
+        except ZeroDivisionError:
+            ret_val = 0
+        return ret_val
+
 
 class EcoAccountDocument(AbstractDocument):
     """
@@ -272,3 +295,8 @@ class EcoAccountDeduction(BaseResource):
             self.intervention.mark_as_edited(user, edit_comment=DEDUCTION_REMOVED)
             self.account.mark_as_edited(user, edit_comment=DEDUCTION_REMOVED)
         super().delete(*args, **kwargs)
+        self.account.update_deductable_rest()
+
+    def save(self, *args, **kwargs):
+        super().save(*args, **kwargs)
+        self.account.update_deductable_rest()
diff --git a/compensation/tables/eco_account.py b/compensation/tables/eco_account.py
index 39a74d76..7cd121f1 100644
--- a/compensation/tables/eco_account.py
+++ b/compensation/tables/eco_account.py
@@ -36,8 +36,8 @@ class EcoAccountTable(BaseTable, TableRenderMixin):
     )
     av = tables.Column(
         verbose_name=_("Available"),
-        orderable=False,
-        empty_values=[],
+        orderable=True,
+        accessor="deductable_rest",
         attrs={
             "th": {
                 "class": "w-20",
@@ -100,13 +100,16 @@ class EcoAccountTable(BaseTable, TableRenderMixin):
         """ Renders the available column for an eco account
 
         Args:
-            value (str): The identifier value
+            value (float): The deductable_rest
             record (EcoAccount): The eco account record
 
         Returns:
 
         """
-        value_total, value_relative = record.get_available_rest()
+        try:
+            value_relative = record.get_deductable_rest_relative()
+        except ZeroDivisionError:
+            value_relative = 0
         html = render_to_string("konova/widgets/progressbar.html", {"value": value_relative})
         return format_html(html)
 
diff --git a/compensation/tests/ecoaccount/test_workflow.py b/compensation/tests/ecoaccount/test_workflow.py
index d1a3cf09..c7efb05a 100644
--- a/compensation/tests/ecoaccount/test_workflow.py
+++ b/compensation/tests/ecoaccount/test_workflow.py
@@ -230,7 +230,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
         self.assertTrue(self.intervention.log.first().action == UserAction.EDITED)
 
     def test_edit_deduction(self):
-        test_surface = self.eco_account.get_available_rest()[0]
+        test_surface = self.eco_account.deductable_rest
         self.eco_account.set_recorded(self.superuser)
         self.intervention.share_with_user(self.superuser)
         self.eco_account.refresh_from_db()
diff --git a/compensation/views/eco_account/eco_account.py b/compensation/views/eco_account/eco_account.py
index f75fcc18..1d259b95 100644
--- a/compensation/views/eco_account/eco_account.py
+++ b/compensation/views/eco_account/eco_account.py
@@ -200,7 +200,8 @@ def detail_view(request: HttpRequest, id: str):
     sum_after_states = after_states.aggregate(Sum("surface"))["surface__sum"] or 0
     diff_states = abs(sum_before_states - sum_after_states)
     # Calculate rest of available surface for deductions
-    available_total, available_relative = acc.get_available_rest()
+    available_total = acc.deductable_rest
+    available_relative = acc.get_deductable_rest_relative()
 
     # Prefetch related data to decrease the amount of db connections
     deductions = acc.deductions.filter(