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

feat: visualise instalments and alterations (hl-1496) #3547

Merged
merged 4 commits into from
Nov 19, 2024
Merged
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
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
Loading