Skip to content

Commit

Permalink
Merge branch 'develop' into gf-changes
Browse files Browse the repository at this point in the history
  • Loading branch information
pavlo-mk authored Oct 17, 2024
2 parents 52f73a3 + b741d44 commit 62192df
Show file tree
Hide file tree
Showing 44 changed files with 623 additions and 132 deletions.
21 changes: 12 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,18 @@ Following the Principles for Digital Development, the “reuse and improve” an

### Why

* Put cash recipients at the center with secure personal data processing
* Unified consolidated reporting for beneficiary data
* Increase accountability beneficiaries and donors
* Financial Inclusion of Beneficiaries
* To Enhance Traceability
* Simplify current process in EMOPS
* Ensuring due diligence in-cash transfer
* Grow the use of effective cash program for children in a risk informed manner
* Eliminate redundancies and dupes in how we do things
|#| HOPE Functions | Added Value |
|---|--------------------------|----------------------------------------------|
|1|Registration of payees data | Quality Assurance for data collection, verification of eligibility |
|2|Deduplication of personal records|Mitigate the risk of duplicate records|
|3|Target payments|Ensure inclusion of relevant payees in payment plan|
|4|Entitlment Calculations|Ensure cash benefit entitlement rules are uphold|
|5|Payment Management|Approval, Authorization and Financial Release tracking|
|6|Reconciliation of Individual payment|Support liquidation or advance or reimbursement to Financial Service Provider|
|7|Payment Verification|Mitigate the risk of inaccurate FSP reports and fraud|
|8|Grievances Redressals and Payees Communications|Ensure payment quality and direct communication with payees|
|9|Roles base access|Uphold segragation of duties and data protection principles|
|10|Reporting|Access process and output indicators|

## Legal
Humanitarian cash Operations and Programme Ecosystem
Expand Down
10 changes: 10 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,13 @@ coverage:
- "hct_mis_api/conftest.py"
- "hct_mis_api/config/settings.py"
- "hct_mis_api/apps/core/management/commands/*"
- "pragma: no cover"
- "pragma: no-cover"
- "def __repr__"
- "raise AssertionError"
- "raise NotImplementedError"
- "except ImportError"
- "if __name__ == .__main__."
- "if TYPE_CHECKING"
- "^\\s*(import\\s.+|from\\s+.+import\\s+.+)"
- "logger.exception(e)"
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ export const VolumeByDeliveryMechanismSection: React.FC<
color={getDeliveryMechanismColor(vdm.deliveryMechanism.name)}
>
<LabelizedField
label={`${vdm.deliveryMechanism.name} (${vdm.deliveryMechanism.fsp?.name})`}
value={`${vdm.volume} ${paymentPlan.currency} (${vdm.volumeUsd} USD)`}
label={`${vdm.deliveryMechanism.name} (${vdm.deliveryMechanism.fsp?.name ?? '-'})`}
value={`${vdm.volume ?? '0.00'} ${paymentPlan.currency} (${vdm.volumeUsd ?? '0.00'} USD)`}
/>
</FieldBorder>
</Grid>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ export const VolumeByDeliveryMechanismSection: React.FC<
color={getDeliveryMechanismColor(vdm.deliveryMechanism.name)}
>
<LabelizedField
label={`${vdm.deliveryMechanism.name} (${vdm.deliveryMechanism.fsp?.name})`}
value={`${vdm.volume} ${paymentPlan.currency} (${vdm.volumeUsd} USD)`}
label={`${vdm.deliveryMechanism.name} (${vdm.deliveryMechanism.fsp?.name ?? '-'})`}
value={`${vdm.volume ?? '0.00'} ${paymentPlan.currency} (${vdm.volumeUsd ?? '0.00'} USD)`}
/>
</FieldBorder>
</Grid>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function EditFollowUpSetUpFspPage(): React.ReactElement {

const mappedInitialDeliveryMechanisms =
paymentPlanData.paymentPlan.deliveryMechanisms.map((el) => ({
deliveryMechanism: el.name,
deliveryMechanism: el.code,
fsp: el.fsp?.id || '',
chosenConfiguration: el.chosenConfiguration || '',
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const EditPeopleFollowUpSetUpFspPage = (): React.ReactElement => {

const mappedInitialDeliveryMechanisms =
paymentPlanData.paymentPlan.deliveryMechanisms.map((el) => ({
deliveryMechanism: el.name,
deliveryMechanism: el.code,
fsp: el.fsp?.id || '',
chosenConfiguration: el.chosenConfiguration || '',
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const EditPeopleSetUpFspPage = (): React.ReactElement => {

const mappedInitialDeliveryMechanisms =
paymentPlanData.paymentPlan.deliveryMechanisms.map((el) => ({
deliveryMechanism: el.name,
deliveryMechanism: el.code,
fsp: el.fsp?.id || '',
chosenConfiguration: el.chosenConfiguration || '',
}));
Expand Down
8 changes: 8 additions & 0 deletions src/hct_mis_api/api/endpoints/core/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from hct_mis_api.apps.core.api.filters import UpdatedAtFilter
from hct_mis_api.apps.core.models import BusinessArea


class BusinessAreaFilter(UpdatedAtFilter):
class Meta:
model = BusinessArea
fields = ("active",)
18 changes: 18 additions & 0 deletions src/hct_mis_api/api/endpoints/core/views.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
from typing import TYPE_CHECKING, Any

from rest_framework.generics import ListAPIView
from rest_framework.response import Response

from hct_mis_api.api.endpoints.base import HOPEAPIView
from hct_mis_api.api.endpoints.core.filters import BusinessAreaFilter
from hct_mis_api.api.endpoints.core.serializers import BusinessAreaSerializer
from hct_mis_api.apps.core.models import BusinessArea

if TYPE_CHECKING:
from rest_framework.request import Request


class BusinessAreaListView(HOPEAPIView, ListAPIView):
serializer_class = BusinessAreaSerializer
queryset = BusinessArea.objects.all()

def list(self, request: "Request", *args: Any, **kwargs: Any) -> Response:
queryset = self.queryset

filterset = BusinessAreaFilter(request.GET, queryset=queryset)
if filterset.is_valid():
queryset = filterset.qs

page = self.paginate_queryset(queryset)
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
3 changes: 2 additions & 1 deletion src/hct_mis_api/api/endpoints/lookups/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from hct_mis_api.api.endpoints.lookups.area import AreaList, AreaTypeList # noqa: F401
from hct_mis_api.api.endpoints.lookups.base import ( # noqa: F401
Country,
CountryAPIView,
DataCollectingPolicy,
DocumentType,
MaritalStatus,
ObservedDisability,
ProgramStatuses,
Relationship,
ResidenceStatus,
Roles,
Expand Down
16 changes: 12 additions & 4 deletions src/hct_mis_api/api/endpoints/lookups/base.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from typing import TYPE_CHECKING, Any, Optional

from django_countries import Countries
from rest_framework.generics import ListAPIView
from rest_framework.response import Response

from hct_mis_api.api.endpoints.base import HOPEAPIView
from hct_mis_api.api.endpoints.serializers import CountrySerializer
from hct_mis_api.apps.geo.models import Country
from hct_mis_api.apps.household.models import (
COLLECT_TYPES,
IDENTIFICATION_TYPE_CHOICE,
Expand All @@ -25,9 +27,10 @@ def get(self, request: "Request", format: Optional[Any] = None) -> Response:
return Response(dict(IDENTIFICATION_TYPE_CHOICE))


class Country(HOPEAPIView):
def get(self, request: "Request", format: Optional[Any] = None) -> Response:
return Response(dict(Countries()))
class CountryAPIView(HOPEAPIView, ListAPIView):
queryset = Country.objects.all()
serializer_class = CountrySerializer
pagination_class = None


class ResidenceStatus(HOPEAPIView):
Expand Down Expand Up @@ -78,3 +81,8 @@ def get(self, request: "Request", format: Optional[Any] = None) -> Response:
class ProgramScope(HOPEAPIView):
def get(self, request: "Request", format: Optional[Any] = None) -> Response:
return Response(dict(Program.SCOPE_CHOICE))


class ProgramStatuses(HOPEAPIView):
def get(self, request: "Request", format: Optional[Any] = None) -> Response:
return Response(dict(Program.STATUS_CHOICE))
24 changes: 24 additions & 0 deletions src/hct_mis_api/api/endpoints/program/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from django.db.models.query import QuerySet

from django_filters import BooleanFilter, CharFilter

from hct_mis_api.apps.core.api.filters import UpdatedAtFilter
from hct_mis_api.apps.program.models import Program


class ProgramFilter(UpdatedAtFilter):
business_area = CharFilter(field_name="business_area__slug")
active = BooleanFilter(method="is_active_filter")

class Meta:
model = Program
fields = (
"business_area",
"active",
"status",
)

def is_active_filter(self, queryset: "QuerySet[Program]", name: str, value: bool) -> "QuerySet[Program]":
if value is True:
return queryset.filter(status=Program.ACTIVE)
return queryset.exclude(status=Program.ACTIVE)
5 changes: 5 additions & 0 deletions src/hct_mis_api/api/endpoints/program/views.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter
from rest_framework.generics import ListAPIView

from hct_mis_api.api.endpoints.base import HOPEAPIView
from hct_mis_api.api.endpoints.program.filters import ProgramFilter
from hct_mis_api.api.endpoints.program.serializers import ProgramGlobalSerializer
from hct_mis_api.apps.program.models import Program


class ProgramGlobalListView(HOPEAPIView, ListAPIView):
serializer_class = ProgramGlobalSerializer
queryset = Program.objects.all()
filter_backends = (OrderingFilter, DjangoFilterBackend)
filterset_class = ProgramFilter
18 changes: 12 additions & 6 deletions src/hct_mis_api/api/endpoints/rdi/program.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from hct_mis_api.api.endpoints.base import HOPEAPIBusinessAreaViewSet
from hct_mis_api.api.models import Grant
from hct_mis_api.apps.core.api.filters import UpdatedAtFilter
from hct_mis_api.apps.program.models import Program

if TYPE_CHECKING:
Expand All @@ -33,17 +34,17 @@ class Meta:


class ProgramViewSet(CreateModelMixin, HOPEAPIBusinessAreaViewSet):
serializer = ProgramSerializer
serializer_class = ProgramSerializer
model = Program
permission = Grant.API_READ_ONLY

def perform_create(self, serializer: "BaseSerializer") -> None:
serializer.save(business_area=self.selected_business_area)
def perform_create(self, serializer_class: "BaseSerializer") -> None:
serializer_class.save(business_area=self.selected_business_area)

@extend_schema(request=ProgramSerializer)
def create(self, request: "Request", *args: Any, **kwargs: Any) -> Response:
self.selected_business_area # TODO does it work? It should be called
serializer = ProgramSerializer(data=request.data)
self.selected_business_area
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
if Grant.API_PROGRAM_CREATE.name not in request.auth.grants:
raise PermissionDenied()
Expand All @@ -53,5 +54,10 @@ def create(self, request: "Request", *args: Any, **kwargs: Any) -> Response:

def list(self, request: "Request", *args: Any, **kwargs: Any) -> Response:
queryset = self.model.objects.filter(business_area=self.selected_business_area)
serializer = self.serializer(queryset, many=True)

filterset = UpdatedAtFilter(request.GET, queryset=queryset)
if filterset.is_valid():
queryset = filterset.qs

serializer = self.serializer_class(queryset, many=True)
return Response(serializer.data)
10 changes: 10 additions & 0 deletions src/hct_mis_api/api/endpoints/serializers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
from django.db import models
from django.utils.translation import gettext_lazy as _

from rest_framework import serializers

from hct_mis_api.apps.geo.models import Country


class RejectPolicy(models.TextChoices):
STRICT = "STRICT", _("STRICT")
LAX = "LAX", _("Lax")


class CountrySerializer(serializers.ModelSerializer):
class Meta:
model = Country
fields = ("name", "short_name", "iso_code2", "iso_code3", "iso_num")
3 changes: 2 additions & 1 deletion src/hct_mis_api/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
path("areatypes/", endpoints.lookups.AreaTypeList().as_view(), name="areatype-list"),
path("constance/", ConstanceSettingsAPIView().as_view(), name="constance-list"),
path("lookups/document/", endpoints.lookups.DocumentType().as_view(), name="document-list"),
path("lookups/country/", endpoints.lookups.Country().as_view(), name="country-list"),
path("lookups/country/", endpoints.lookups.CountryAPIView().as_view(), name="country-list"),
path("lookups/residencestatus/", endpoints.lookups.ResidenceStatus().as_view(), name="residencestatus-list"),
path("lookups/maritalstatus/", endpoints.lookups.MaritalStatus().as_view(), name="maritalstatus-list"),
path(
Expand All @@ -36,6 +36,7 @@
),
path("lookups/role/", endpoints.lookups.Roles().as_view(), name="role-list"),
path("lookups/sex/", endpoints.lookups.Sex().as_view(), name="sex-list"),
path("lookups/program-statuses/", endpoints.lookups.ProgramStatuses().as_view(), name="program-statuses-list"),
path("business_areas/", endpoints.core.BusinessAreaListView.as_view(), name="business-area-list"),
path("programs/", ProgramGlobalListView.as_view(), name="program-global-list"),
path(
Expand Down
Empty file.
6 changes: 6 additions & 0 deletions src/hct_mis_api/apps/core/api/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django_filters import DateFromToRangeFilter
from django_filters.rest_framework import FilterSet


class UpdatedAtFilter(FilterSet):
updated_at = DateFromToRangeFilter()
3 changes: 1 addition & 2 deletions src/hct_mis_api/apps/core/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,8 @@ def program(self) -> Any:
@classmethod
def _create(cls, target_class: Any, *args: Any, **kwargs: Any) -> FlexibleAttribute:
label = kwargs.pop("label", None)
kwargs["label"] = {"English(EN)": label}
obj = super()._create(target_class, *args, **kwargs)
obj.label = {"English(EN)": label}
obj.save()
return obj

class Meta:
Expand Down
19 changes: 19 additions & 0 deletions src/hct_mis_api/apps/core/migrations/0088_migration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 3.2.25 on 2024-10-15 17:18

from django.db import migrations, models
import hct_mis_api.apps.core.models


class Migration(migrations.Migration):

dependencies = [
('core', '0087_migration'),
]

operations = [
migrations.AlterField(
model_name='flexibleattribute',
name='label',
field=models.JSONField(default=dict, validators=[hct_mis_api.apps.core.models.label_contains_english_en_validator]),
),
]
9 changes: 8 additions & 1 deletion src/hct_mis_api/apps/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ def get_sys_option(self, key: str, default: None = None) -> Any:
return default


def label_contains_english_en_validator(data: dict) -> None:
if "English(EN)" not in data:
raise ValidationError('The "English(EN)" key is required in the label.')


class FlexibleAttribute(SoftDeletableModel, NaturalKeyModel, TimeStampedUUIDModel):
STRING = "STRING"
IMAGE = "IMAGE"
Expand Down Expand Up @@ -237,7 +242,7 @@ class FlexibleAttribute(SoftDeletableModel, NaturalKeyModel, TimeStampedUUIDMode
null=True,
related_name="flex_field",
)
label = JSONField(default=dict)
label = JSONField(default=dict, validators=[label_contains_english_en_validator])
hint = JSONField(default=dict)
group = models.ForeignKey(
"core.FlexibleAttributeGroup", on_delete=models.CASCADE, related_name="flex_attributes", null=True, blank=True
Expand All @@ -264,6 +269,8 @@ def clean(self) -> None:
):
raise ValidationError(f'Flex field with name "{self.name}" already exists inside a program.')

label_contains_english_en_validator(self.label)

def save(self, *args: Any, **kwargs: Any) -> None:
self.clean()
super().save(*args, **kwargs)
Expand Down
3 changes: 2 additions & 1 deletion src/hct_mis_api/apps/geo/api/filters.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from django_filters import rest_framework as filters

from hct_mis_api.apps.core.api.filters import UpdatedAtFilter
from hct_mis_api.apps.geo.models import Area


class AreaFilter(filters.FilterSet):
class AreaFilter(UpdatedAtFilter):
level = filters.NumberFilter(
field_name="area_type__area_level",
)
Expand Down
Loading

0 comments on commit 62192df

Please sign in to comment.