Skip to content

Commit

Permalink
feat: visualise instalments and alterations (hl-1496) (#3547)
Browse files Browse the repository at this point in the history
* fix: unused property used wrong field

* fix: certain overlooks in application fixture

* feat: add gui for viewing recoveries and instalment amounts

* feat: add recovery amounts after alteration
  • Loading branch information
sirtawast authored Nov 19, 2024
1 parent 7f4e4aa commit f56e581
Show file tree
Hide file tree
Showing 21 changed files with 495 additions and 147 deletions.
12 changes: 3 additions & 9 deletions backend/benefit/applications/api/v1/serializers/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@
PaySubsidySerializer,
TrainingCompensationSerializer,
)
from calculator.enums import InstalmentStatus
from calculator.models import Calculation
from common.delay_call import call_now_or_later, do_delayed_calls_at_end
from common.exceptions import BenefitAPIException
Expand All @@ -92,18 +91,13 @@
from users.utils import get_company_from_request, get_request_user_from_context


def _get_pending_instalment(application, excluded_status=[]):
def _get_pending_instalment(application):
"""Get the latest pending instalment for the application"""
try:
instalments = application.calculation.instalments.filter(
instalment_number__gt=1
)
instalment = (
instalments.exclude(status__in=excluded_status)
.order_by("-due_date")
.first()
or None
)
instalment = instalments.filter(instalment_number=2).first() or None
if instalment is not None:
return InstalmentSerializer(instalment).data
except AttributeError:
Expand Down Expand Up @@ -1980,7 +1974,7 @@ class Meta:
pending_instalment = serializers.SerializerMethodField("get_pending_instalment")

def get_pending_instalment(self, application):
return _get_pending_instalment(application, [InstalmentStatus.COMPLETED])
return _get_pending_instalment(application)

ahjo_error = serializers.SerializerMethodField("get_latest_ahjo_error")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
class SimpleApplicationAlterationSerializer(DynamicFieldsModelSerializer):
class Meta:
model = ApplicationAlteration
fields = ["state"]
read_only_fields = ["state"]
fields = ["state", "recovery_amount"]
read_only_fields = ["state", "recovery_amount"]


class BaseApplicationAlterationSerializer(DynamicFieldsModelSerializer):
Expand Down
6 changes: 3 additions & 3 deletions backend/benefit/applications/fixtures/test_applications.json
Original file line number Diff line number Diff line change
Expand Up @@ -1471,8 +1471,8 @@
"paper_application_date": null,
"de_minimis_aid": false,
"batch": "fb9e81a4-cf6d-4f7f-abee-366c8a5bfbfc",
"ahjo_case_id": null,
"ahjo_case_guid": null,
"ahjo_case_id": "HEL 2024-234",
"ahjo_case_guid": "9c66ead0-25c4-4eba-952e-b44774c23056",
"handled_by_ahjo_automation": true,
"handler": "47ecedfa-351b-4815-bfac-96bdbc640178",
"bases": []
Expand Down Expand Up @@ -1542,7 +1542,7 @@
"fields": {
"created_at": "2024-11-04T10:10:12.358Z",
"modified_at": "2024-11-04T10:10:12.358Z",
"status": "submitted_but_not_sent_to_ahjo",
"status": "details_received",
"application": "10c25d67-f783-4625-9ff4-2418b629f20a",
"error_from_ahjo": null,
"ahjo_request_id": null,
Expand Down
2 changes: 1 addition & 1 deletion backend/benefit/applications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ def calculated_effective_benefit_amount(self):
if original_benefit is not None and self.alteration_set is not None:
return original_benefit - sum(
[
alteration.collection_amount
alteration.recovery_amount or 0
for alteration in self.alteration_set.all()
]
)
Expand Down
9 changes: 9 additions & 0 deletions backend/benefit/calculator/api/v1/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class Meta:
"amount",
"created_at",
"modified_at",
"amount_after_recoveries",
]
read_only_fields = [
"id",
Expand All @@ -69,6 +70,7 @@ class Meta:
"amount",
"created_at",
"modified_at",
"amount_after_recoveries",
]

def validate_status(self, status):
Expand All @@ -79,6 +81,13 @@ def validate_status(self, status):

return status

amount_after_recoveries = serializers.SerializerMethodField(
"get_amount_after_recoveries",
)

def amount_after_recoveries(self, obj):
return getattr(obj, "amount_after_recoveries", None)

status = serializers.ChoiceField(
validators=[InstalmentStatusValidator()],
choices=InstalmentStatus.choices,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 4.2.11 on 2024-11-15 08:11

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("calculator", "0017_instalment"),
]

operations = [
migrations.AddField(
model_name="instalment",
name="amount_paid",
field=models.DecimalField(
blank=True,
decimal_places=2,
editable=False,
max_digits=7,
null=True,
verbose_name="To be set only ONCE when final amount is sent to Talpa. The set value should be defined by 'amount' field that is reduced by handled ApplicationAlteration recoveries at the time of Talpa robot visit.",
),
),
]
32 changes: 32 additions & 0 deletions backend/benefit/calculator/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from encrypted_fields.fields import EncryptedCharField, SearchField
from simple_history.models import HistoricalRecords

from applications.enums import ApplicationAlterationState
from applications.models import Application, PAY_SUBSIDY_PERCENT_CHOICES
from calculator.enums import DescriptionType, InstalmentStatus, RowType
from common.exceptions import BenefitAPIException
Expand Down Expand Up @@ -854,6 +855,18 @@ class Instalment(UUIDModel, TimeStampedModel):
blank=True,
)

amount_paid = models.DecimalField(
max_digits=7,
decimal_places=2,
editable=False,
verbose_name=_(
"To be set only ONCE when final amount is sent to Talpa. The set value should be defined by 'amount' "
"field that is reduced by handled ApplicationAlteration recoveries at the time of Talpa robot visit."
),
blank=True,
null=True,
)

due_date = models.DateField(blank=True, null=True, verbose_name=_("Due date"))

status = models.CharField(
Expand All @@ -864,6 +877,25 @@ class Instalment(UUIDModel, TimeStampedModel):
blank=True,
)

@property
def amount_after_recoveries(self):
if self.amount_paid:
return max(self.amount_paid, 0)
if self.instalment_number == 1:
return max(self.amount, 0)

alteration_set = self.calculation.application.alteration_set.filter(
state=ApplicationAlterationState.HANDLED,
)
if alteration_set.count() == 0:
return max(self.amount, 0)

return max(
self.amount
- sum([alteration.recovery_amount or 0 for alteration in alteration_set]),
0,
)

def __str__(self):
return f"Instalment of {self.amount}€, \
number {self.instalment_number}/{self.calculation.instalments.count()} \
Expand Down
16 changes: 13 additions & 3 deletions frontend/benefit/handler/public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,8 @@
"error_in_talpa": "Virhe maksussa"
},
"calculationEndDate": "Viim. tukipäivä",
"calculatedBenefitAmount": "Tukisumma"
"calculatedBenefitAmount": "Tukisumma",
"instalmentAmount": "Maksuerä"
},
"messages": {
"empty": {
Expand Down Expand Up @@ -1388,8 +1389,17 @@
"header3": "Maksuerät",
"acceptedBenefit": "Myönnettävä Helsinki-lisä",
"firstInstalment": "Ensimmäinen maksuerä",
"secondInstalment": "Toinen maksuerä",
"total": "Yhteensä"
"secondInstalment": "Toinen maksuerä, eräpäivä",
"total": "Yhteensä",
"alterationRow": "Muutosilmoitus {{startDate}} - {{endDate}}",
"totalRecoveries": "Takaisinlaskutettavan tuen määrä",
"unpaidRecoveries": {
"title": "Takaisinlaskutettava osuus",
"tooltip": "Huomaa, että takaisinlaskutettava osuus muuttuu, jos uusia muutosilmoituksia hyväksytään tai niitä peruutaan. Jos olet epävarma, tarkasta laskutuksen tilanne Talpalta."
},
"totalAfterRecoveries": "Tuki takaisinlaskutuksen jälkeen",
"totalPaidSum": "Toteutuneet maksuerät yhteensä",
"totalPlannedSum": "Maksuerät yhteensä"
},
"errors": {
"trainingCompensation": {
Expand Down
16 changes: 13 additions & 3 deletions frontend/benefit/handler/public/locales/fi/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,8 @@
"error_in_talpa": "Virhe maksussa"
},
"calculationEndDate": "Viim. tukipäivä",
"calculatedBenefitAmount": "Tukisumma"
"calculatedBenefitAmount": "Tukisumma",
"instalmentAmount": "Maksuerä"
},
"messages": {
"empty": {
Expand Down Expand Up @@ -832,7 +833,7 @@
"reportAlteration": "Tee uusi muutosilmoitus"
},
"calculation": "Laskelma",
"instalments": "Maksuerät"
"instalments": "Maksetut tuet"
},
"alterations": {
"new": {
Expand Down Expand Up @@ -1389,7 +1390,16 @@
"acceptedBenefit": "Myönnettävä Helsinki-lisä",
"firstInstalment": "Ensimmäinen maksuerä",
"secondInstalment": "Toinen maksuerä",
"total": "Yhteensä"
"total": "Yhteensä",
"alterationRow": "Muutosilmoitus {{startDate}} - {{endDate}}",
"totalRecoveries": "Takaisinlaskutettavan tuen määrä",
"unpaidRecoveries": {
"title": "Takaisinlaskutettava osuus",
"tooltip": "Huomaa, että takaisinlaskutettava osuus muuttuu, jos uusia muutosilmoituksia hyväksytään tai niitä peruutaan. Jos olet epävarma, tarkasta laskutuksen tilanne Talpalta."
},
"totalAfterRecoveries": "Tuki takaisinlaskutuksen jälkeen",
"totalPaidSum": "Toteutuneet maksuerät yhteensä",
"totalPlannedSum": "Maksuerät yhteensä"
},
"errors": {
"trainingCompensation": {
Expand Down
16 changes: 13 additions & 3 deletions frontend/benefit/handler/public/locales/sv/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,8 @@
"error_in_talpa": "Virhe maksussa"
},
"calculationEndDate": "Viim. tukipäivä",
"calculatedBenefitAmount": "Tukisumma"
"calculatedBenefitAmount": "Tukisumma",
"instalmentAmount": "Maksuerä"
},
"messages": {
"empty": {
Expand Down Expand Up @@ -832,7 +833,7 @@
"reportAlteration": "Tee uusi muutosilmoitus"
},
"calculation": "Laskelma",
"instalments": "Maksuerät"
"instalments": "Maksetut tuet"
},
"alterations": {
"new": {
Expand Down Expand Up @@ -1389,7 +1390,16 @@
"acceptedBenefit": "Myönnettävä Helsinki-lisä",
"firstInstalment": "Ensimmäinen maksuerä",
"secondInstalment": "Toinen maksuerä",
"total": "Yhteensä"
"total": "Yhteensä",
"alterationRow": "Muutosilmoitus {{startDate}} - {{endDate}}",
"totalRecoveries": "Takaisinlaskutettavan tuen määrä",
"unpaidRecoveries": {
"title": "Takaisinlaskutettava osuus",
"tooltip": "Huomaa, että takaisinlaskutettava osuus muuttuu, jos uusia muutosilmoituksia hyväksytään tai niitä peruutaan. Jos olet epävarma, tarkasta laskutuksen tilanne Talpalta."
},
"totalAfterRecoveries": "Tuki takaisinlaskutuksen jälkeen",
"totalPaidSum": "Toteutuneet maksuerät yhteensä",
"totalPlannedSum": "Maksuerät yhteensä"
},
"errors": {
"trainingCompensation": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,10 @@ const AlterationHandlingForm = ({
theme="coat"
iconLeft={<IconCheck />}
disabled={
isSubmitting || (isSubmitted && hasErrors) || !isCSVDownloadDone
formik.values.isRecoverable &&
(isSubmitting ||
(isSubmitted && hasErrors) ||
!isCSVDownloadDone)
}
isLoading={isSubmitting}
loadingText={t('common:utility.submitting')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getTagStyleForStatus } from 'benefit/handler/utils/applications';
import { APPLICATION_STATUSES } from 'benefit-shared/constants';
import {
AhjoError,
ApplicationAlteration,
ApplicationListItemData,
Instalment,
} from 'benefit-shared/types/application';
Expand Down Expand Up @@ -60,16 +61,33 @@ const buildApplicationUrl = (

const getFirstInstalmentTotalAmount = (
calculatedBenefitAmount: string,
pendingInstalment?: Instalment
pendingInstalment?: Instalment,
alterations?: ApplicationAlteration[]
): string | JSX.Element => {
let firstInstalment = parseInt(calculatedBenefitAmount, 10);
let recoveryAmount = 0;
if (pendingInstalment) {
firstInstalment -= parseInt(String(pendingInstalment?.amount), 10);
firstInstalment -= parseInt(
String(pendingInstalment?.amountAfterRecoveries),
10
);
recoveryAmount = alterations
? alterations?.reduce(
(prev: number, cur: ApplicationAlteration) =>
prev + parseInt(cur.recoveryAmount, 10),
0
)
: 0;
}
return pendingInstalment ? (
<>
{formatFloatToCurrency(firstInstalment, null, 'fi-FI', 0)} /{' '}
{formatFloatToCurrency(calculatedBenefitAmount, 'EUR', 'fi-FI', 0)}
{formatFloatToCurrency(
parseInt(calculatedBenefitAmount, 10) - recoveryAmount,
'EUR',
'fi-FI',
0
)}
</>
) : (
formatFloatToCurrency(firstInstalment, 'EUR', 'fi-FI', 0)
Expand Down
Loading

0 comments on commit f56e581

Please sign in to comment.