Skip to content

Commit 884408b

Browse files
committed
feat(eap): add new fields and remove status activated
- add new fields lead_timeframe_unit - change pending pfa to approved(pending pfa)
1 parent 4d3a293 commit 884408b

5 files changed

Lines changed: 65 additions & 57 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Generated by Django 4.2.29 on 2026-03-16 09:00
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('eap', '0006_fulleap_meal_source_of_information_and_more'),
10+
]
11+
12+
operations = [
13+
migrations.RemoveField(
14+
model_name='eapregistration',
15+
name='activated_at',
16+
),
17+
migrations.AddField(
18+
model_name='fulleap',
19+
name='lead_timeframe_unit',
20+
field=models.IntegerField(blank=True, choices=[(10, 'Years'), (20, 'Months'), (30, 'Days'), (40, 'Hours')], null=True, verbose_name='Lead Timeframe Unit'),
21+
),
22+
migrations.AlterField(
23+
model_name='eapregistration',
24+
name='status',
25+
field=models.IntegerField(choices=[(10, 'Under Development'), (20, 'Under Review'), (30, 'NS Addressing Comments'), (40, 'Technically Validated'), (50, 'Approved(Pending PFA)'), (60, 'Approved')], default=10, help_text='Select the current status of the EAP development process.', verbose_name='EAP Status'),
26+
),
27+
migrations.AlterField(
28+
model_name='enablingapproach',
29+
name='ap_code',
30+
field=models.IntegerField(default=1, verbose_name='AP Code'),
31+
preserve_default=False,
32+
),
33+
]

eap/models.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ class Approach(models.IntegerChoices):
441441

442442
approach = models.IntegerField(choices=Approach.choices, verbose_name=_("Approach"))
443443
budget_per_approach = models.IntegerField(verbose_name=_("Budget per approach (CHF)"))
444-
ap_code = models.IntegerField(verbose_name=_("AP Code"), null=True, blank=True)
444+
ap_code = models.IntegerField(verbose_name=_("AP Code"))
445445
previous_id = models.PositiveIntegerField(verbose_name=_("Previous ID"), null=True, blank=True)
446446

447447
indicators = models.ManyToManyField(
@@ -553,7 +553,7 @@ class EAPStatus(models.IntegerChoices):
553553
IFRC can change status to NS_ADDRESSING_COMMENTS or PENDING_PFA.
554554
"""
555555

556-
PENDING_PFA = 50, _("Pending PFA")
556+
PENDING_PFA = 50, _("Approved(Pending PFA)")
557557
"""EAP is in the process of signing the PFA between IFRC and NS.
558558
"""
559559

@@ -562,9 +562,6 @@ class EAPStatus(models.IntegerChoices):
562562
Cannot be changed back to previous statuses.
563563
"""
564564

565-
ACTIVATED = 70, _("Activated")
566-
"""EAP has been activated"""
567-
568565

569566
# BASE MODEL FOR EAP
570567
class EAPRegistration(EAPBaseModel):
@@ -722,12 +719,6 @@ class EAPRegistration(EAPBaseModel):
722719
verbose_name=_("pending pfa at"),
723720
help_text=_("Timestamp when the EAP was marked as pending PFA."),
724721
)
725-
activated_at = models.DateTimeField(
726-
null=True,
727-
blank=True,
728-
verbose_name=_("activated at"),
729-
help_text=_("Timestamp when the EAP was activated."),
730-
)
731722

732723
# EAP submission deadline
733724
deadline = models.DateField(
@@ -1449,13 +1440,19 @@ class FullEAP(EAPBaseModel, CommonEAPFields):
14491440
)
14501441

14511442
# NOTE: In days
1452-
# TODO(susilnem): add unit for lead time
14531443
lead_time = models.IntegerField(
14541444
verbose_name=_("Lead Time"),
14551445
null=True,
14561446
blank=True,
14571447
)
14581448

1449+
lead_timeframe_unit = models.IntegerField(
1450+
choices=TimeFrame.choices,
1451+
verbose_name=_("Lead Timeframe Unit"),
1452+
null=True,
1453+
blank=True,
1454+
)
1455+
14591456
trigger_statement_source_of_information = models.ManyToManyField(
14601457
SourceInformation,
14611458
verbose_name=_("Trigger Statement Source of Forecast"),

eap/serializers.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,6 @@ class Meta:
255255
"status",
256256
"status_display",
257257
"requirement_cost",
258-
"activated_at",
259258
"approved_at",
260259
"created_at",
261260
"modified_at",
@@ -784,6 +783,23 @@ class Meta:
784783
)
785784
exclude = ("cover_image",)
786785

786+
def _validate_timeframe(self, data: dict[str, typing.Any]) -> None:
787+
lead_unit = data.get("lead_timeframe_unit")
788+
lead_time_value = data.get("lead_time")
789+
790+
if lead_time_value is not None and lead_unit is None:
791+
raise serializers.ValidationError(
792+
{
793+
"lead_timeframe_unit": gettext("lead timeframe and unit must both be provided."),
794+
}
795+
)
796+
797+
if lead_unit is not None and lead_time_value is not None:
798+
if lead_unit != TimeFrame.DAYS:
799+
raise serializers.ValidationError(
800+
{"lead_timeframe_unit": gettext("lead timeframe unit must be Days for Full EAP.")}
801+
)
802+
787803
def validate(self, data: dict[str, typing.Any]) -> dict[str, typing.Any]:
788804
original_eap_registration = getattr(self.instance, "eap_registration", None) if self.instance else None
789805
eap_registration: EAPRegistration | None = data.get("eap_registration", original_eap_registration)
@@ -812,6 +828,9 @@ def validate(self, data: dict[str, typing.Any]) -> dict[str, typing.Any]:
812828
if eap_type and eap_type != EAPType.FULL_EAP:
813829
raise serializers.ValidationError("Cannot create Full EAP for non-full EAP registration.")
814830

831+
# Validate timeframe fields
832+
self._validate_timeframe(data)
833+
815834
# Validate all image fields in one place
816835
for field in self.IMAGE_FIELDS:
817836
if field in data:
@@ -844,7 +863,6 @@ def create(self, validated_data: dict[str, typing.Any]):
844863
(EAPRegistration.Status.TECHNICALLY_VALIDATED, EAPRegistration.Status.NS_ADDRESSING_COMMENTS),
845864
(EAPRegistration.Status.TECHNICALLY_VALIDATED, EAPRegistration.Status.PENDING_PFA),
846865
(EAPRegistration.Status.PENDING_PFA, EAPRegistration.Status.APPROVED),
847-
(EAPRegistration.Status.APPROVED, EAPRegistration.Status.ACTIVATED),
848866
]
849867
)
850868

@@ -1065,17 +1083,6 @@ def _validate_status(self, validated_data: dict[str, typing.Any]) -> dict[str, t
10651083
]
10661084
)
10671085

1068-
elif (current_status, new_status) == (
1069-
EAPRegistration.Status.APPROVED,
1070-
EAPRegistration.Status.ACTIVATED,
1071-
):
1072-
# Update timestamp
1073-
self.instance.activated_at = timezone.now()
1074-
self.instance.save(
1075-
update_fields=[
1076-
"activated_at",
1077-
]
1078-
)
10791086
return validated_data
10801087

10811088
def validate(self, validated_data: dict[str, typing.Any]) -> dict[str, typing.Any]:

eap/test_views.py

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ def test_active_eaps(self):
260260
partners=[self.partner2.id],
261261
created_by=self.country_admin,
262262
modified_by=self.country_admin,
263-
status=EAPStatus.ACTIVATED,
263+
status=EAPStatus.APPROVED,
264264
eap_type=EAPType.SIMPLIFIED_EAP,
265265
)
266266
EAPRegistrationFactory.create(
@@ -1719,36 +1719,6 @@ def test_status_transition(self):
17191719
response = self.client.patch(url, update_data, format="json")
17201720
self.assertEqual(response.status_code, 400)
17211721

1722-
# NOTE: Transition to ACTIVATED
1723-
# APPROVED -> ACTIVATED
1724-
data = {
1725-
"status": EAPStatus.ACTIVATED,
1726-
}
1727-
1728-
# LOGIN as country admin user
1729-
# FAILS: As only ifrc admins or superuser can
1730-
self.authenticate(self.country_admin)
1731-
response = self.client.post(self.url, data, format="json")
1732-
self.assertEqual(response.status_code, 400)
1733-
1734-
# LOGIN as IFRC admin user
1735-
# SUCCESS: As only ifrc admins or superuser can
1736-
self.assertIsNone(self.eap_registration.activated_at)
1737-
self.authenticate(self.ifrc_admin_user)
1738-
response = self.client.post(self.url, data, format="json")
1739-
self.assertEqual(response.status_code, 200)
1740-
self.assertEqual(response.data["status"], EAPStatus.ACTIVATED)
1741-
# Check is the activated timeline is added
1742-
self.eap_registration.refresh_from_db()
1743-
self.assertIsNotNone(self.eap_registration.activated_at)
1744-
1745-
# Check as if NS user cannot update after ACTIVATED
1746-
# FAILS As simplified EAP is in ACTIVATED, cannot updated
1747-
self.authenticate(self.country_admin)
1748-
url = f"/api/v2/simplified-eap/{simplified_eap.id}/"
1749-
response = self.client.patch(url, update_data, format="json")
1750-
self.assertEqual(response.status_code, 400)
1751-
17521722
@mock.patch("eap.serializers.generate_export_eap_pdf")
17531723
@mock.patch("eap.serializers.group")
17541724
@mock.patch("eap.serializers.send_new_eap_submission_email")
@@ -2427,6 +2397,7 @@ def test_create_full_eap(self):
24272397
"total_budget": 10000,
24282398
"objective": "FUll eap objective",
24292399
"lead_time": 5,
2400+
"lead_timeframe_unit": TimeFrame.DAYS,
24302401
"expected_submission_time": "2024-12-31",
24312402
"readiness_budget": 3000,
24322403
"pre_positioning_budget": 4000,

eap/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def get_queryset(self) -> QuerySet[EAPRegistration]:
6464
return (
6565
super()
6666
.get_queryset()
67-
.filter(status__in=[EAPStatus.APPROVED, EAPStatus.ACTIVATED])
67+
.filter(status=EAPStatus.APPROVED)
6868
.select_related("disaster_type", "country")
6969
.annotate(
7070
requirement_cost=Case(

0 commit comments

Comments
 (0)