From b10c0a44c14138b38416c02b277622f0fef0bd3b Mon Sep 17 00:00:00 2001 From: Pavlo Mokiichuk Date: Tue, 5 Mar 2024 15:15:19 +0100 Subject: [PATCH] DCT limit_to > available_for --- backend/hct_mis_api/apps/core/admin.py | 4 ++-- backend/hct_mis_api/apps/core/fixtures.py | 2 +- .../hct_mis_api/apps/core/fixtures/data.json | 14 +++++++------- .../apps/core/migrations/0078_migration.py | 18 ++++++++++++++++++ backend/hct_mis_api/apps/core/models.py | 2 +- backend/hct_mis_api/apps/core/schema.py | 6 +++++- backend/hct_mis_api/apps/core/validators.py | 6 +++--- .../tests/test_payment_plan_reconciliation.py | 2 +- ...st_recalculating_household_cash_received.py | 2 +- backend/hct_mis_api/apps/program/schema.py | 7 ++++++- .../tests/test_change_program_status.py | 2 +- .../apps/program/tests/test_create_program.py | 4 +--- .../apps/program/tests/test_update_program.py | 8 ++++---- backend/hct_mis_api/apps/program/validators.py | 6 +++++- ...test_czech_republic_registration_service.py | 2 +- .../tests/test_generic_registration_service.py | 2 +- .../test_sri_lanka_registration_service.py | 2 +- .../test_ukrainian_registration_service.py | 2 +- .../services/base_flex_registration_service.py | 5 ++++- backend/selenium_tests/conftest.py | 2 +- backend/specific.json | 10 +++++----- 21 files changed, 70 insertions(+), 38 deletions(-) create mode 100644 backend/hct_mis_api/apps/core/migrations/0078_migration.py diff --git a/backend/hct_mis_api/apps/core/admin.py b/backend/hct_mis_api/apps/core/admin.py index d3f2465877..bc542da2e8 100644 --- a/backend/hct_mis_api/apps/core/admin.py +++ b/backend/hct_mis_api/apps/core/admin.py @@ -730,10 +730,10 @@ class DataCollectingTypeAdmin(AdminFiltersMixin, admin.ModelAdmin): "recalculate_composition", ) list_filter = ( - ("limit_to", AutoCompleteFilter), + ("available_for", AutoCompleteFilter), "active", "individual_filters_available", "household_filters_available", "recalculate_composition", ) - filter_horizontal = ("compatible_types", "limit_to") + filter_horizontal = ("compatible_types", "available_for") diff --git a/backend/hct_mis_api/apps/core/fixtures.py b/backend/hct_mis_api/apps/core/fixtures.py index e69de097cd..52fae6022c 100644 --- a/backend/hct_mis_api/apps/core/fixtures.py +++ b/backend/hct_mis_api/apps/core/fixtures.py @@ -97,4 +97,4 @@ def business_areas(self, create: Any, extracted: List[Any], **kwargs: Any) -> No if extracted: for business_area in extracted: - self.limit_to.add(business_area) + self.available_for.add(business_area) diff --git a/backend/hct_mis_api/apps/core/fixtures/data.json b/backend/hct_mis_api/apps/core/fixtures/data.json index 1706d61ed9..26d7348754 100644 --- a/backend/hct_mis_api/apps/core/fixtures/data.json +++ b/backend/hct_mis_api/apps/core/fixtures/data.json @@ -6667,7 +6667,7 @@ "individual_filters_available": true, "household_filters_available": true, "compatible_types": [1, 2, 3], - "limit_to": ["c259b1a0-ae3a-494e-b343-f7c8eb060c68", "3e269a73-123b-43e1-86af-ced1b30d8e80"] + "available_for": ["c259b1a0-ae3a-494e-b343-f7c8eb060c68", "3e269a73-123b-43e1-86af-ced1b30d8e80"] } }, { @@ -6684,7 +6684,7 @@ "individual_filters_available": false, "household_filters_available": true, "compatible_types": [2, 3], - "limit_to": ["c259b1a0-ae3a-494e-b343-f7c8eb060c68", "3e269a73-123b-43e1-86af-ced1b30d8e80"] + "available_for": ["c259b1a0-ae3a-494e-b343-f7c8eb060c68", "3e269a73-123b-43e1-86af-ced1b30d8e80"] } }, { @@ -6701,7 +6701,7 @@ "individual_filters_available": true, "household_filters_available": true, "compatible_types": [3], - "limit_to": ["c259b1a0-ae3a-494e-b343-f7c8eb060c68", "3e269a73-123b-43e1-86af-ced1b30d8e80"] + "available_for": ["c259b1a0-ae3a-494e-b343-f7c8eb060c68", "3e269a73-123b-43e1-86af-ced1b30d8e80"] } }, { @@ -6717,7 +6717,7 @@ "individual_filters_available": false, "household_filters_available": true, "compatible_types": [4], - "limit_to": ["c259b1a0-ae3a-494e-b343-f7c8eb060c68", "3e269a73-123b-43e1-86af-ced1b30d8e80"] + "available_for": ["c259b1a0-ae3a-494e-b343-f7c8eb060c68", "3e269a73-123b-43e1-86af-ced1b30d8e80"] } }, { @@ -6733,7 +6733,7 @@ "individual_filters_available": false, "household_filters_available": false, "compatible_types": [5], - "limit_to": ["c259b1a0-ae3a-494e-b343-f7c8eb060c68", "3e269a73-123b-43e1-86af-ced1b30d8e80"] + "available_for": ["c259b1a0-ae3a-494e-b343-f7c8eb060c68", "3e269a73-123b-43e1-86af-ced1b30d8e80"] } }, { @@ -6748,7 +6748,7 @@ "active": false, "individual_filters_available": true, "compatible_types": [], - "limit_to": ["c259b1a0-ae3a-494e-b343-f7c8eb060c68", "3e269a73-123b-43e1-86af-ced1b30d8e80"] + "available_for": ["c259b1a0-ae3a-494e-b343-f7c8eb060c68", "3e269a73-123b-43e1-86af-ced1b30d8e80"] } }, { @@ -6764,7 +6764,7 @@ "deprecated": true, "individual_filters_available": true, "compatible_types": [], - "limit_to": ["c259b1a0-ae3a-494e-b343-f7c8eb060c68", "3e269a73-123b-43e1-86af-ced1b30d8e80"] + "available_for": ["c259b1a0-ae3a-494e-b343-f7c8eb060c68", "3e269a73-123b-43e1-86af-ced1b30d8e80"] } }, { diff --git a/backend/hct_mis_api/apps/core/migrations/0078_migration.py b/backend/hct_mis_api/apps/core/migrations/0078_migration.py new file mode 100644 index 0000000000..21b4b2f05d --- /dev/null +++ b/backend/hct_mis_api/apps/core/migrations/0078_migration.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.24 on 2024-03-05 14:11 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0077_migration'), + ] + + operations = [ + migrations.RenameField( + model_name='datacollectingtype', + old_name='limit_to', + new_name='available_for', + ), + ] diff --git a/backend/hct_mis_api/apps/core/models.py b/backend/hct_mis_api/apps/core/models.py index 6a69092098..13b0e8cae9 100644 --- a/backend/hct_mis_api/apps/core/models.py +++ b/backend/hct_mis_api/apps/core/models.py @@ -428,7 +428,7 @@ class Type(models.TextChoices): type = models.CharField(choices=Type.choices, null=True, blank=True, max_length=32) description = models.TextField(blank=True) compatible_types = models.ManyToManyField("self", blank=True, symmetrical=False) - limit_to = models.ManyToManyField(to="BusinessArea", related_name="data_collecting_types", blank=True) + available_for = models.ManyToManyField(to="BusinessArea", related_name="data_collecting_types", blank=True) active = models.BooleanField(default=True) deprecated = models.BooleanField( default=False, help_text="Cannot be used in new programs, totally hidden in UI, only admin have access" diff --git a/backend/hct_mis_api/apps/core/schema.py b/backend/hct_mis_api/apps/core/schema.py index a69f504bc7..c8667f928b 100644 --- a/backend/hct_mis_api/apps/core/schema.py +++ b/backend/hct_mis_api/apps/core/schema.py @@ -14,6 +14,7 @@ from django.core.exceptions import ObjectDoesNotExist from django.db import models +from django.db.models import Q import graphene from constance import config @@ -407,9 +408,12 @@ def resolve_all_languages(self, info: Any, code: str, **kwargs: Any) -> List[Lan def resolve_data_collection_type_choices(self, info: Any, **kwargs: Any) -> List[Dict[str, Any]]: data_collecting_types = ( DataCollectingType.objects.filter( + Q( + Q(available_for__slug=info.context.headers.get("Business-Area").lower()) + | Q(available_for__isnull=True) + ), active=True, deprecated=False, - limit_to__slug=info.context.headers.get("Business-Area").lower(), ) .exclude(code__iexact="unknown") .only("code", "label", "description") diff --git a/backend/hct_mis_api/apps/core/validators.py b/backend/hct_mis_api/apps/core/validators.py index 9e4c2a19dc..420696a0c6 100644 --- a/backend/hct_mis_api/apps/core/validators.py +++ b/backend/hct_mis_api/apps/core/validators.py @@ -329,9 +329,9 @@ def validate_data_collecting_type(cls, *args: Any, **kwargs: Any) -> Optional[No program = kwargs.get("program") business_area = kwargs.get("business_area") or getattr(program, "business_area", None) - # validate program BA and DCT.limit_to - if data_collecting_type and business_area: - if business_area not in data_collecting_type.limit_to.all(): + # validate program BA and DCT.available_for + if data_collecting_type and business_area and data_collecting_type.available_for.all().count() > 0: + if business_area not in data_collecting_type.available_for.all(): raise ValidationError("This Data Collection Type is not assigned to the Program's Business Area") # user can update the program and don't update data collecting type diff --git a/backend/hct_mis_api/apps/payment/tests/test_payment_plan_reconciliation.py b/backend/hct_mis_api/apps/payment/tests/test_payment_plan_reconciliation.py index 2a82832bff..c3f276221c 100644 --- a/backend/hct_mis_api/apps/payment/tests/test_payment_plan_reconciliation.py +++ b/backend/hct_mis_api/apps/payment/tests/test_payment_plan_reconciliation.py @@ -298,7 +298,7 @@ def setUpTestData(cls) -> None: cls.data_collecting_type = DataCollectingType.objects.create( code="full", description="Full individual collected", active=True ) - cls.data_collecting_type.limit_to.add(cls.business_area) + cls.data_collecting_type.available_for.add(cls.business_area) @patch("hct_mis_api.apps.payment.models.PaymentPlan.get_exchange_rate", return_value=2.0) def test_receiving_reconciliations_from_fsp(self, mock_get_exchange_rate: Any) -> None: diff --git a/backend/hct_mis_api/apps/payment/tests/test_recalculating_household_cash_received.py b/backend/hct_mis_api/apps/payment/tests/test_recalculating_household_cash_received.py index de369073cf..9792b64c21 100644 --- a/backend/hct_mis_api/apps/payment/tests/test_recalculating_household_cash_received.py +++ b/backend/hct_mis_api/apps/payment/tests/test_recalculating_household_cash_received.py @@ -98,7 +98,7 @@ def setUpTestData(cls) -> None: cls.data_collecting_type = DataCollectingType.objects.create( code="full", description="Full individual collected", active=True ) - cls.data_collecting_type.limit_to.add(cls.business_area) + cls.data_collecting_type.available_for.add(cls.business_area) cls.create_program_mutation_variables = { "programData": { diff --git a/backend/hct_mis_api/apps/program/schema.py b/backend/hct_mis_api/apps/program/schema.py index ce32d7c075..5382bd1a3d 100644 --- a/backend/hct_mis_api/apps/program/schema.py +++ b/backend/hct_mis_api/apps/program/schema.py @@ -236,9 +236,14 @@ def resolve_cash_plan_status_choices(self, info: Any, **kwargs: Any) -> List[Dic def resolve_data_collecting_type_choices(self, info: Any, **kwargs: Any) -> List[Dict[str, Any]]: return list( DataCollectingType.objects.filter( + Q( + Q( + available_for__slug=info.context.headers.get("Business-Area").lower(), + ) + | Q(available_for__isnull=True) + ), active=True, deprecated=False, - limit_to__slug=info.context.headers.get("Business-Area").lower(), ) .exclude(code__iexact="unknown") .annotate(name=F("label")) diff --git a/backend/hct_mis_api/apps/program/tests/test_change_program_status.py b/backend/hct_mis_api/apps/program/tests/test_change_program_status.py index c54bfef3fa..20643995c6 100644 --- a/backend/hct_mis_api/apps/program/tests/test_change_program_status.py +++ b/backend/hct_mis_api/apps/program/tests/test_change_program_status.py @@ -78,7 +78,7 @@ def test_status_change( data_collecting_type, _ = DataCollectingType.objects.update_or_create( **{"label": "Full", "code": "full_collection", "description": "Full"} ) - data_collecting_type.limit_to.add(self.business_area) + data_collecting_type.available_for.add(self.business_area) program = ProgramFactory.create( status=initial_status, business_area=self.business_area, diff --git a/backend/hct_mis_api/apps/program/tests/test_create_program.py b/backend/hct_mis_api/apps/program/tests/test_create_program.py index 8a07246559..b2afaae677 100644 --- a/backend/hct_mis_api/apps/program/tests/test_create_program.py +++ b/backend/hct_mis_api/apps/program/tests/test_create_program.py @@ -57,7 +57,7 @@ def setUpTestData(cls) -> None: active=True, individual_filters_available=True, ) - cls.data_collecting_type.limit_to.add(cls.business_area) + cls.data_collecting_type.available_for.add(cls.business_area) cls.program_data = { "programData": { "name": "Test", @@ -112,7 +112,6 @@ def test_create_program_with_deprecated_dct(self) -> None: dct, _ = DataCollectingType.objects.update_or_create( **{"label": "Deprecated", "code": "deprecated", "description": "Deprecated", "deprecated": True} ) - dct.limit_to.add(self.business_area) self.create_user_role_with_permissions(self.user, [Permissions.PROGRAMME_CREATE], self.business_area) program_data = self.program_data @@ -126,7 +125,6 @@ def test_create_program_with_inactive_dct(self) -> None: dct, _ = DataCollectingType.objects.update_or_create( **{"label": "Inactive", "code": "inactive", "description": "Inactive", "active": False} ) - dct.limit_to.add(self.business_area) self.create_user_role_with_permissions(self.user, [Permissions.PROGRAMME_CREATE], self.business_area) program_data = self.program_data diff --git a/backend/hct_mis_api/apps/program/tests/test_update_program.py b/backend/hct_mis_api/apps/program/tests/test_update_program.py index 40ca79ac46..d0be31079c 100644 --- a/backend/hct_mis_api/apps/program/tests/test_update_program.py +++ b/backend/hct_mis_api/apps/program/tests/test_update_program.py @@ -111,7 +111,7 @@ def test_update_program_authenticated( def test_update_active_program_with_dct(self) -> None: self.create_user_role_with_permissions(self.user, [Permissions.PROGRAMME_UPDATE], self.business_area) data_collecting_type = DataCollectingType.objects.get(code="full_collection") - data_collecting_type.limit_to.add(self.business_area) + data_collecting_type.available_for.add(self.business_area) Program.objects.filter(id=self.program.id).update( status=Program.ACTIVE, data_collecting_type=data_collecting_type ) @@ -137,7 +137,7 @@ def test_update_active_program_with_dct(self) -> None: def test_update_draft_not_empty_program_with_dct(self) -> None: self.create_user_role_with_permissions(self.user, [Permissions.PROGRAMME_UPDATE], self.business_area) data_collecting_type = DataCollectingType.objects.get(code="full_collection") - data_collecting_type.limit_to.add(self.business_area) + data_collecting_type.available_for.add(self.business_area) create_household(household_args={"program": self.program}) self.snapshot_graphql_request( @@ -158,7 +158,7 @@ def test_update_program_with_deprecated_dct(self) -> None: dct, _ = DataCollectingType.objects.update_or_create( **{"label": "Deprecated", "code": "deprecated", "description": "Deprecated", "deprecated": True} ) - dct.limit_to.add(self.business_area) + dct.available_for.add(self.business_area) self.create_user_role_with_permissions(self.user, [Permissions.PROGRAMME_UPDATE], self.business_area) @@ -179,7 +179,7 @@ def test_update_program_with_inactive_dct(self) -> None: dct, _ = DataCollectingType.objects.update_or_create( **{"label": "Inactive", "code": "inactive", "description": "Inactive", "active": False} ) - dct.limit_to.add(self.business_area) + dct.available_for.add(self.business_area) self.create_user_role_with_permissions(self.user, [Permissions.PROGRAMME_UPDATE], self.business_area) diff --git a/backend/hct_mis_api/apps/program/validators.py b/backend/hct_mis_api/apps/program/validators.py index f959e644d6..04bfa9d502 100644 --- a/backend/hct_mis_api/apps/program/validators.py +++ b/backend/hct_mis_api/apps/program/validators.py @@ -75,8 +75,12 @@ def validate_data_collecting_type( original_program_data_collecting_type: Optional["DataCollectingType"] = None, data_collecting_type: Optional["DataCollectingType"] = None, ) -> None: - if business_area not in data_collecting_type.limit_to.all(): + if ( + data_collecting_type.available_for.all().count() > 0 + and business_area not in data_collecting_type.available_for.all() + ): raise ValidationError("This Data Collection Type is not assigned to the Program's Business Area") + if not original_program_data_collecting_type: raise ValidationError("The original Programme must have a Data Collection Type.") elif ( diff --git a/backend/hct_mis_api/apps/registration_datahub/tests/test_czech_republic_registration_service.py b/backend/hct_mis_api/apps/registration_datahub/tests/test_czech_republic_registration_service.py index 05340d7c65..9fcbb7d7fa 100644 --- a/backend/hct_mis_api/apps/registration_datahub/tests/test_czech_republic_registration_service.py +++ b/backend/hct_mis_api/apps/registration_datahub/tests/test_czech_republic_registration_service.py @@ -67,7 +67,7 @@ def setUp(cls) -> None: cls.business_area = BusinessAreaFactory(slug="some-czech-slug") cls.data_collecting_type = DataCollectingType.objects.create(label="someLabel", code="some_label") - cls.data_collecting_type.limit_to.add(cls.business_area) + cls.data_collecting_type.available_for.add(cls.business_area) cls.program = ProgramFactory(status="ACTIVE", data_collecting_type=cls.data_collecting_type) cls.organization = OrganizationFactory(business_area=cls.business_area, slug=cls.business_area.slug) diff --git a/backend/hct_mis_api/apps/registration_datahub/tests/test_generic_registration_service.py b/backend/hct_mis_api/apps/registration_datahub/tests/test_generic_registration_service.py index d721f32af2..865937e207 100644 --- a/backend/hct_mis_api/apps/registration_datahub/tests/test_generic_registration_service.py +++ b/backend/hct_mis_api/apps/registration_datahub/tests/test_generic_registration_service.py @@ -47,7 +47,7 @@ def setUp(cls) -> None: cls.business_area = BusinessAreaFactory(slug="generic-slug") cls.data_collecting_type = DataCollectingType.objects.create(label="SomeFull", code="some_full") - cls.data_collecting_type.limit_to.add(cls.business_area) + cls.data_collecting_type.available_for.add(cls.business_area) cls.program = ProgramFactory(status="ACTIVE", data_collecting_type=cls.data_collecting_type) cls.organization = OrganizationFactory(business_area=cls.business_area, slug=cls.business_area.slug) diff --git a/backend/hct_mis_api/apps/registration_datahub/tests/test_sri_lanka_registration_service.py b/backend/hct_mis_api/apps/registration_datahub/tests/test_sri_lanka_registration_service.py index 34aa7ba40f..982d59bde8 100644 --- a/backend/hct_mis_api/apps/registration_datahub/tests/test_sri_lanka_registration_service.py +++ b/backend/hct_mis_api/apps/registration_datahub/tests/test_sri_lanka_registration_service.py @@ -48,7 +48,7 @@ def setUp(cls) -> None: cls.business_area = BusinessAreaFactory(slug="sri-lanka2") cls.data_collecting_type = DataCollectingType.objects.create(label="SizeOnlyXYZ", code="size_onlyXYZ") - cls.data_collecting_type.limit_to.add(cls.business_area) + cls.data_collecting_type.available_for.add(cls.business_area) cls.program = ProgramFactory(status="ACTIVE", data_collecting_type=cls.data_collecting_type) cls.organization = OrganizationFactory(business_area=cls.business_area, slug=cls.business_area.slug) diff --git a/backend/hct_mis_api/apps/registration_datahub/tests/test_ukrainian_registration_service.py b/backend/hct_mis_api/apps/registration_datahub/tests/test_ukrainian_registration_service.py index 022e42d249..d6ba357ec4 100644 --- a/backend/hct_mis_api/apps/registration_datahub/tests/test_ukrainian_registration_service.py +++ b/backend/hct_mis_api/apps/registration_datahub/tests/test_ukrainian_registration_service.py @@ -66,7 +66,7 @@ def setUpTestData(cls) -> None: cls.business_area = BusinessAreaFactory(slug="some-ukraine-slug") cls.data_collecting_type = DataCollectingType.objects.create(label="SomeFull", code="some_full") - cls.data_collecting_type.limit_to.add(cls.business_area) + cls.data_collecting_type.available_for.add(cls.business_area) cls.program = ProgramFactory(status="ACTIVE", data_collecting_type=cls.data_collecting_type) cls.organization = OrganizationFactory(business_area=cls.business_area, slug=cls.business_area.slug) diff --git a/backend/hct_mis_api/aurora/services/base_flex_registration_service.py b/backend/hct_mis_api/aurora/services/base_flex_registration_service.py index 15efb73045..37f32e4f6f 100644 --- a/backend/hct_mis_api/aurora/services/base_flex_registration_service.py +++ b/backend/hct_mis_api/aurora/services/base_flex_registration_service.py @@ -97,7 +97,10 @@ def validate_data_collection_type(self) -> None: if data_collecting_type.deprecated: raise ValidationError("Data Collecting Type of program is deprecated") - if business_area not in data_collecting_type.limit_to.all(): + if ( + data_collecting_type.available_for.all().count() > 0 + and business_area not in data_collecting_type.available_for.all() + ): raise ValidationError( f"{business_area.slug.capitalize()} is not limited for DataCollectingType: {data_collecting_type.code}" ) diff --git a/backend/selenium_tests/conftest.py b/backend/selenium_tests/conftest.py index 1ac2094ee8..558e3648cd 100644 --- a/backend/selenium_tests/conftest.py +++ b/backend/selenium_tests/conftest.py @@ -158,7 +158,7 @@ def create_super_user() -> User: data_collecting_type = DataCollectingType.objects.create( label=dct["label"], code=dct["code"], description=dct["description"], active=dct["active"] ) - data_collecting_type.limit_to.add(business_area) + data_collecting_type.available_for.add(business_area) data_collecting_type.save() partner.permissions = { diff --git a/backend/specific.json b/backend/specific.json index 27cf0063fc..972bf65201 100644 --- a/backend/specific.json +++ b/backend/specific.json @@ -10,7 +10,7 @@ "active": true, "individual_filters_available": false, "compatible_types": [], - "limit_to": [] + "available_for": [] } }, { @@ -24,7 +24,7 @@ "active": true, "individual_filters_available": false, "compatible_types": [], - "limit_to": [] + "available_for": [] } }, { @@ -38,7 +38,7 @@ "active": true, "individual_filters_available": false, "compatible_types": [], - "limit_to": [] + "available_for": [] } }, { @@ -52,7 +52,7 @@ "active": true, "individual_filters_available": false, "compatible_types": [], - "limit_to": [] + "available_for": [] } }, { @@ -66,7 +66,7 @@ "active": true, "individual_filters_available": false, "compatible_types": [], - "limit_to": [] + "available_for": [] } } ]