diff --git a/backend/benefit/applications/api/v1/serializers/application.py b/backend/benefit/applications/api/v1/serializers/application.py index 72f0ac6aa2..ebaf892296 100755 --- a/backend/benefit/applications/api/v1/serializers/application.py +++ b/backend/benefit/applications/api/v1/serializers/application.py @@ -1,5 +1,5 @@ from datetime import date, timedelta -from typing import Dict, List +from typing import Dict, List, Union from dateutil.relativedelta import relativedelta from django.conf import settings @@ -32,6 +32,7 @@ PaySubsidyGranted, ) from applications.models import ( + AhjoStatus, Application, ApplicationBasis, ApplicationLogEntry, @@ -177,6 +178,7 @@ class Meta: "warnings", "duration_in_months_rounded", "total_deminimis_amount", + "ahjo_status", ] read_only_fields = [ "submitted_at", @@ -348,6 +350,8 @@ class Meta: ahjo_decision = serializers.ReadOnlyField() + ahjo_status = serializers.SerializerMethodField("get_latest_ahjo_status") + submitted_at = serializers.SerializerMethodField("get_submitted_at") modified_at = serializers.SerializerMethodField( @@ -909,6 +913,14 @@ def _validate_apprenticeship_program( } ) + def get_latest_ahjo_status(self, obj) -> Union[str, None]: + """Get the latest Ahjo status text for the application""" + try: + status = obj.ahjo_status.latest() + except AhjoStatus.DoesNotExist: + return None + return status.status + @staticmethod def _get_available_benefit_types( company, @@ -1071,6 +1083,10 @@ def handle_status_transition( instance.start_date, instance.end_date ) + # We are submitting the application here as a handler or a user + # so we can create the initial Ahjo status + AhjoStatus.objects.create(application=instance) + call_now_or_later( instance.calculation.calculate, duplicate_check=("calculation.calculate", instance.pk), diff --git a/backend/benefit/applications/enums.py b/backend/benefit/applications/enums.py index ce3f3f42d6..9da50ac93a 100644 --- a/backend/benefit/applications/enums.py +++ b/backend/benefit/applications/enums.py @@ -139,3 +139,25 @@ class PaySubsidyGranted(models.TextChoices): GRANTED = "granted", _("Pay subsidy granted (default)") GRANTED_AGED = "granted_aged", _("Pay subsidy granted (aged)") NOT_GRANTED = "not_granted", _("No granted pay subsidy") + + +class AhjoStatus(models.TextChoices): + # The possible statuses for Ahjo processing + SUBMITTED_BUT_NOT_SENT_TO_AHJO = "submitted_but_not_sent_to_ahjo", _( + "Submitted but not sent to AHJO" + ) + REQUEST_TO_OPEN_CASE_SENT = "request_to_open_case_sent", _( + "Request to open the case sent to AHJO" + ) + CASE_OPENED = "case_opened", _("Case opened in AHJO") + UPDATE_REQUEST_SENT = "update_request_sent", _("Update request sent") + UPDATE_REQUEST_RECEIVED = "update_request_received", _("Update request received") + DECISION_PROPOSAL_SENT = "decision_proposal_sent", _("Decision proposal sent") + DECISION_PROPOSAL_ACCEPTED = "decision_proposal_accepted", _( + "Decision proposal accepted" + ) + DECISION_PROPOSAL_REJECTED = "decision_proposal_rejected", _( + "Decision proposal rejected" + ) + DELETE_REQUEST_SENT = "delete_request_sent", _("Delete request sent") + DELETE_REQUEST_RECEIVED = "delete_request_received", _("Delete request received") diff --git a/backend/benefit/applications/migrations/0044_ahjostatus.py b/backend/benefit/applications/migrations/0044_ahjostatus.py new file mode 100644 index 0000000000..04e68a30cc --- /dev/null +++ b/backend/benefit/applications/migrations/0044_ahjostatus.py @@ -0,0 +1,31 @@ +# Generated by Django 3.2.18 on 2023-10-26 11:01 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('applications', '0043_ahjosetting'), + ] + + operations = [ + migrations.CreateModel( + name='AhjoStatus', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='time created')), + ('modified_at', models.DateTimeField(auto_now=True, verbose_name='time modified')), + ('status', models.CharField(choices=[('submitted_but_not_sent_to_ahjo', 'Submitted but not sent to AHJO'), ('request_to_open_case_sent', 'Request to open the case sent to AHJO'), ('diary_number_received', 'Diary number received'), ('case_opened', 'Case opened in AHJO'), ('update_request_sent', 'Update request sent'), ('update_request_received', 'Update request received'), ('decision_proposal_sent', 'Decision proposal sent'), ('decision_proposal_accepted', 'Decision proposal accepted'), ('decision_proposal_rejected', 'Decision proposal rejected'), ('delete_request_sent', 'Delete request sent'), ('delete_request_received', 'Delete request received')], default='submitted_but_not_sent_to_ahjo', max_length=64, verbose_name='status')), + ('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ahjo_status', to='applications.application', verbose_name='application')), + ], + options={ + 'verbose_name': 'ahjo status', + 'verbose_name_plural': 'ahjo statuses', + 'db_table': 'bf_applications_ahjo_status', + 'ordering': ['application__created_at', 'created_at'], + 'get_latest_by': 'created_at', + }, + ), + ] diff --git a/backend/benefit/applications/models.py b/backend/benefit/applications/models.py index daff62da7c..7bb105d1f2 100755 --- a/backend/benefit/applications/models.py +++ b/backend/benefit/applications/models.py @@ -5,6 +5,7 @@ from django.core.validators import MaxLengthValidator, MinLengthValidator from django.db import connection, models from django.db.models import JSONField, OuterRef, Subquery +from django.db.models.constraints import UniqueConstraint from django.utils.translation import gettext_lazy as _ from encrypted_fields.fields import EncryptedCharField, SearchField from phonenumber_field.modelfields import PhoneNumberField @@ -12,6 +13,7 @@ from applications.enums import ( AhjoDecision, + AhjoStatus, ApplicationBatchStatus, ApplicationOrigin, ApplicationStatus, @@ -881,3 +883,33 @@ class ReviewState(models.Model): class AhjoSetting(TimeStampedModel): name = models.CharField(max_length=255, unique=True) data = JSONField() + + +class AhjoStatus(TimeStampedModel): + """ + Ahjo status of the application + """ + + status = models.CharField( + max_length=64, + verbose_name=_("status"), + choices=AhjoStatus.choices, + default=AhjoStatus.SUBMITTED_BUT_NOT_SENT_TO_AHJO, + ) + application = models.ForeignKey( + Application, + verbose_name=_("application"), + related_name="ahjo_status", + on_delete=models.CASCADE, + ) + + def __str__(self): + return self.status + + class Meta: + db_table = "bf_applications_ahjo_status" + verbose_name = _("ahjo status") + verbose_name_plural = _("ahjo statuses") + ordering = ["application__created_at", "created_at"] + get_latest_by = "created_at" + UniqueConstraint(fields=["application_id", "status"], name="unique_status") diff --git a/backend/benefit/applications/tests/test_applications_api.py b/backend/benefit/applications/tests/test_applications_api.py index 5740c6fb2b..e8d4ce3291 100755 --- a/backend/benefit/applications/tests/test_applications_api.py +++ b/backend/benefit/applications/tests/test_applications_api.py @@ -25,6 +25,7 @@ ) from applications.api.v1.views import BaseApplicationViewSet from applications.enums import ( + AhjoStatus, ApplicationStatus, ApplicationStep, AttachmentType, @@ -1006,6 +1007,24 @@ def test_application_date_range_on_submit( assert submit_response.status_code == status_code +def test_application_has_default_ahjo_status_after_submit( + request, api_client, application +): + add_attachments_to_application(request, application) + + data = ApplicantApplicationSerializer(application).data + + api_client.put( + get_detail_url(application), + data, + ) + application.refresh_from_db() + submit_response = _submit_application(api_client, application) + assert ( + submit_response.data["ahjo_status"] == AhjoStatus.SUBMITTED_BUT_NOT_SENT_TO_AHJO + ) + + @pytest.mark.parametrize( "company_form_code,de_minimis_aid,de_minimis_aid_set,association_has_business_activities,expected_result", [