From d377fdfd7c630f8f24890a088162c043e8a52421 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Tue, 6 Aug 2024 02:14:13 +0200 Subject: [PATCH 001/118] add new field flex_field_classification instead of is_flex_field, update validations, take flex fields based on program --- backend/hct_mis_api/apps/targeting/choices.py | 8 +++ .../apps/targeting/graphql_types.py | 37 ++++++++---- .../targeting/migrations/0045_migration.py | 42 ++++++++++++++ backend/hct_mis_api/apps/targeting/models.py | 57 +++++++++++-------- .../targeting/services/targeting_service.py | 24 +++++--- .../hct_mis_api/apps/targeting/validators.py | 35 ++++++++---- 6 files changed, 150 insertions(+), 53 deletions(-) create mode 100644 backend/hct_mis_api/apps/targeting/choices.py create mode 100644 backend/hct_mis_api/apps/targeting/migrations/0045_migration.py diff --git a/backend/hct_mis_api/apps/targeting/choices.py b/backend/hct_mis_api/apps/targeting/choices.py new file mode 100644 index 0000000000..407714affb --- /dev/null +++ b/backend/hct_mis_api/apps/targeting/choices.py @@ -0,0 +1,8 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + + +class FlexFieldClassification(models.TextChoices): + NOT_FLEX_FIELD = "NOT_FLEX_FIELD", _("Not Flex Field") + FLEX_FIELD_NOT_PDU = "FLEX_FIELD_NOT_PDU", _("Flex Field Not PDU") + FLEX_FIELD_PDU = "FLEX_FIELD_PDU", _("Flex Field PDU") \ No newline at end of file diff --git a/backend/hct_mis_api/apps/targeting/graphql_types.py b/backend/hct_mis_api/apps/targeting/graphql_types.py index a1839d4b89..77ab799885 100644 --- a/backend/hct_mis_api/apps/targeting/graphql_types.py +++ b/backend/hct_mis_api/apps/targeting/graphql_types.py @@ -18,7 +18,9 @@ from hct_mis_api.apps.core.field_attributes.fields_types import Scope from hct_mis_api.apps.core.models import FlexibleAttribute from hct_mis_api.apps.core.schema import ExtendedConnection, FieldAttributeNode +from hct_mis_api.apps.core.utils import decode_id_string_required, decode_id_string from hct_mis_api.apps.household.schema import HouseholdNode +from hct_mis_api.apps.program.models import Program from hct_mis_api.apps.targeting.filters import HouseholdFilter, TargetPopulationFilter from hct_mis_api.apps.utils.schema import Arg @@ -53,6 +55,12 @@ def filter_choices(field: Optional[Dict], args: List) -> Optional[Dict]: return field +class FlexFieldClassification(graphene.Enum): + NOT_FLEX_FIELD = "NOT_FLEX_FIELD" + FLEX_FIELD_NOT_PDU = "FLEX_FIELD_NOT_PDU" + FLEX_FIELD_PDU = "FLEX_FIELD_PDU" + + class TargetingCriteriaRuleFilterNode(DjangoObjectType): arguments = graphene.List(Arg) field_attribute = graphene.Field(FieldAttributeNode) @@ -61,9 +69,7 @@ def resolve_arguments(self, info: Any) -> "GrapheneList": return self.arguments def resolve_field_attribute(parent, info: Any) -> Optional[Dict]: - if parent.is_flex_field: - return FlexibleAttribute.objects.get(name=parent.field_name) - else: + if parent.flex_field_classification == FlexFieldClassification.NOT_FLEX_FIELD: field_attribute = get_field_by_name( parent.field_name, parent.targeting_criteria_rule.targeting_criteria.target_population ) @@ -72,6 +78,12 @@ def resolve_field_attribute(parent, info: Any) -> Optional[Dict]: field_attribute, parent.arguments # type: ignore # can't convert graphene list to list ) + program = None + if parent.flex_field_classification == "FLEX_FIELD_PDU": + encoded_program_id = input.get("program") or info.context.headers.get("Program") + program = Program.objects.get(id=decode_id_string(encoded_program_id)) + return FlexibleAttribute.objects.get(name=parent.field_name, program=program) + class Meta: model = target_models.TargetingCriteriaRuleFilter @@ -84,14 +96,18 @@ def resolve_arguments(self, info: Any) -> "GrapheneList": return self.arguments def resolve_field_attribute(parent, info: Any) -> Any: - if parent.is_flex_field: - return FlexibleAttribute.objects.get(name=parent.field_name) + if parent.flex_field_classification == "NOT_FLEX_FIELD": + field_attribute = get_field_by_name( + parent.field_name, + parent.individuals_filters_block.targeting_criteria_rule.targeting_criteria.target_population, + ) + return filter_choices(field_attribute, parent.arguments) # type: ignore # can't convert graphene list to list - field_attribute = get_field_by_name( - parent.field_name, - parent.individuals_filters_block.targeting_criteria_rule.targeting_criteria.target_population, - ) - return filter_choices(field_attribute, parent.arguments) # type: ignore # can't convert graphene list to list + program = None + if parent.flex_field_classification == "FLEX_FIELD_PDU": + encoded_program_id = input.get("program") or info.context.headers.get("Program") + program = Program.objects.get(id=decode_id_string(encoded_program_id)) + return FlexibleAttribute.objects.get(name=parent.field_name, program=program) class Meta: model = target_models.TargetingIndividualBlockRuleFilter @@ -164,6 +180,7 @@ class Meta: class TargetingCriteriaRuleFilterObjectType(graphene.InputObjectType): comparison_method = graphene.String(required=True) is_flex_field = graphene.Boolean(required=True) + flex_field_classification = graphene.Field(FlexFieldClassification, required=True) field_name = graphene.String(required=True) arguments = graphene.List(Arg, required=True) diff --git a/backend/hct_mis_api/apps/targeting/migrations/0045_migration.py b/backend/hct_mis_api/apps/targeting/migrations/0045_migration.py new file mode 100644 index 0000000000..91b1a93f22 --- /dev/null +++ b/backend/hct_mis_api/apps/targeting/migrations/0045_migration.py @@ -0,0 +1,42 @@ +# Generated by Django 3.2.25 on 2024-08-05 23:07 + +from django.db import migrations, models + + +def convert_is_flex_field_to_classification(apps, schema_editor): + TargetingCriteriaRuleFilter = apps.get_model('targeting', 'TargetingCriteriaRuleFilter') + TargetingCriteriaRuleFilter.objects.filter(is_flex_field=True).update(flex_field_classification='FLEX_FIELD_NOT_PDU') + TargetingCriteriaRuleFilter.objects.filter(is_flex_field=False).update(flex_field_classification='NOT_FLEX_FIELD') + + TargetingIndividualBlockRuleFilter = apps.get_model('targeting', 'TargetingIndividualBlockRuleFilter') + TargetingIndividualBlockRuleFilter.objects.filter(is_flex_field=True).update(flex_field_classification='FLEX_FIELD_NOT_PDU') + TargetingIndividualBlockRuleFilter.objects.filter(is_flex_field=False).update(flex_field_classification='NOT_FLEX_FIELD') + + +class Migration(migrations.Migration): + + dependencies = [ + ('targeting', '0044_migration'), + ] + + operations = [ + migrations.AddField( + model_name='targetingcriteriarulefilter', + name='flex_field_classification', + field=models.CharField(choices=[('NOT_FLEX_FIELD', 'Not Flex Field'), ('FLEX_FIELD_NOT_PDU', 'Flex Field Not PDU'), ('FLEX_FIELD_PDU', 'Flex Field PDU')], default='NOT_FLEX_FIELD', max_length=20), + ), + migrations.AddField( + model_name='targetingindividualblockrulefilter', + name='flex_field_classification', + field=models.CharField(choices=[('NOT_FLEX_FIELD', 'Not Flex Field'), ('FLEX_FIELD_NOT_PDU', 'Flex Field Not PDU'), ('FLEX_FIELD_PDU', 'Flex Field PDU')], default='NOT_FLEX_FIELD', max_length=20), + ), + migrations.RunPython(convert_is_flex_field_to_classification), + migrations.RemoveField( + model_name='targetingcriteriarulefilter', + name='is_flex_field', + ), + migrations.RemoveField( + model_name='targetingindividualblockrulefilter', + name='is_flex_field', + ), + ] diff --git a/backend/hct_mis_api/apps/targeting/models.py b/backend/hct_mis_api/apps/targeting/models.py index 1f1c0ea5b4..109edb80a2 100644 --- a/backend/hct_mis_api/apps/targeting/models.py +++ b/backend/hct_mis_api/apps/targeting/models.py @@ -23,6 +23,7 @@ from hct_mis_api.apps.core.utils import map_unicef_ids_to_households_unicef_ids from hct_mis_api.apps.household.models import Household from hct_mis_api.apps.steficon.models import Rule, RuleCommit +from hct_mis_api.apps.targeting.choices import FlexFieldClassification from hct_mis_api.apps.targeting.services.targeting_service import ( TargetingCriteriaFilterBase, TargetingCriteriaQueryingBase, @@ -445,21 +446,6 @@ class TargetingCriteriaRuleFilter(TimeStampedUUIDModel, TargetingCriteriaFilterB :Residential Status != Refugee """ - @property - def is_social_worker_program(self) -> bool: - try: - return self.targeting_criteria_rule.targeting_criteria.target_population.program.is_social_worker_program - except ( - AttributeError, - TargetingCriteriaRuleFilter.targeting_criteria_rule.RelatedObjectDoesNotExist, - ): - return False - - def get_core_fields(self) -> List: - if self.is_social_worker_program: - return FieldFactory.from_only_scopes([Scope.TARGETING, Scope.XLSX_PEOPLE]) - return FieldFactory.from_scope(Scope.TARGETING).associated_with_household() - comparison_method = models.CharField( max_length=20, choices=TargetingCriteriaFilterBase.COMPARISON_CHOICES, @@ -469,7 +455,11 @@ def get_core_fields(self) -> List: related_name="filters", on_delete=models.CASCADE, ) - is_flex_field = models.BooleanField(default=False) + flex_field_classification = models.CharField( + max_length=20, + choices=FlexFieldClassification.choices, + default=FlexFieldClassification.NOT_FLEX_FIELD, + ) field_name = models.CharField(max_length=50) arguments = JSONField( help_text=""" @@ -477,6 +467,21 @@ def get_core_fields(self) -> List: """ ) + @property + def is_social_worker_program(self) -> bool: + try: + return self.targeting_criteria_rule.targeting_criteria.target_population.program.is_social_worker_program + except ( + AttributeError, + TargetingCriteriaRuleFilter.targeting_criteria_rule.RelatedObjectDoesNotExist, + ): + return False + + def get_core_fields(self) -> List: + if self.is_social_worker_program: + return FieldFactory.from_only_scopes([Scope.TARGETING, Scope.XLSX_PEOPLE]) + return FieldFactory.from_scope(Scope.TARGETING).associated_with_household() + class TargetingIndividualBlockRuleFilter(TimeStampedUUIDModel, TargetingCriteriaFilterBase): """ @@ -486,13 +491,6 @@ class TargetingIndividualBlockRuleFilter(TimeStampedUUIDModel, TargetingCriteria :Residential Status != Refugee """ - @property - def is_social_worker_program(self) -> bool: - return False - - def get_core_fields(self) -> List: - return FieldFactory.from_scope(Scope.TARGETING).associated_with_individual() - comparison_method = models.CharField( max_length=20, choices=TargetingCriteriaFilterBase.COMPARISON_CHOICES, @@ -502,7 +500,11 @@ def get_core_fields(self) -> List: related_name="individual_block_filters", on_delete=models.CASCADE, ) - is_flex_field = models.BooleanField(default=False) + flex_field_classification = models.CharField( + max_length=20, + choices=FlexFieldClassification.choices, + default=FlexFieldClassification.NOT_FLEX_FIELD, + ) field_name = models.CharField(max_length=50) arguments = JSONField( help_text=""" @@ -510,5 +512,12 @@ def get_core_fields(self) -> List: """ ) + @property + def is_social_worker_program(self) -> bool: + return False + + def get_core_fields(self) -> List: + return FieldFactory.from_scope(Scope.TARGETING).associated_with_individual() + def get_lookup_prefix(self, associated_with: Any) -> str: return "" diff --git a/backend/hct_mis_api/apps/targeting/services/targeting_service.py b/backend/hct_mis_api/apps/targeting/services/targeting_service.py index 3c46288270..2cf8b1282a 100644 --- a/backend/hct_mis_api/apps/targeting/services/targeting_service.py +++ b/backend/hct_mis_api/apps/targeting/services/targeting_service.py @@ -19,6 +19,7 @@ from hct_mis_api.apps.core.utils import get_attr_value from hct_mis_api.apps.grievance.models import GrievanceTicket from hct_mis_api.apps.household.models import Household, Individual +from hct_mis_api.apps.targeting.choices import FlexFieldClassification logger = logging.getLogger(__name__) @@ -346,18 +347,27 @@ def get_query_for_core_field(self) -> Q: return self.get_query_for_lookup(f"{lookup_prefix}{lookup}", core_field_attr) def get_query_for_flex_field(self) -> Q: - flex_field_attr = FlexibleAttribute.objects.get(name=self.field_name) - if not flex_field_attr: - logger.error(f"There are no Flex Field Attributes associated with this fieldName {self.field_name}") - raise ValidationError( - f"There are no Flex Field Attributes associated with this fieldName {self.field_name}" - ) + if self.flex_field_classification == FlexFieldClassification.FLEX_FIELD_PDU: + program = self.targeting_criteria_rule.targeting_criteria.target_population.program + flex_field_attr = FlexibleAttribute.objects.get(name=self.field_name, program=program) + if not flex_field_attr: + logger.error(f"There is no PDU Flex Field Attribute associated with this fieldName {self.field_name} in program {program.name}") + raise ValidationError( + f"There is no PDU Flex Field Attribute associated with this fieldName {self.field_name} in program {program.name}" + ) + else: + flex_field_attr = FlexibleAttribute.objects.get(name=self.field_name, program=None) + if not flex_field_attr: + logger.error(f"There is no Flex Field Attributes associated with this fieldName {self.field_name}") + raise ValidationError( + f"There is no Flex Field Attributes associated with this fieldName {self.field_name}" + ) lookup_prefix = self.get_lookup_prefix(_INDIVIDUAL if flex_field_attr.associated_with == 1 else _HOUSEHOLD) lookup = f"{lookup_prefix}flex_fields__{flex_field_attr.name}" return self.get_query_for_lookup(lookup, flex_field_attr) def get_query(self) -> Q: - if not self.is_flex_field: + if self.flex_field_classification == FlexFieldClassification.NOT_FLEX_FIELD: return self.get_query_for_core_field() return self.get_query_for_flex_field() diff --git a/backend/hct_mis_api/apps/targeting/validators.py b/backend/hct_mis_api/apps/targeting/validators.py index c1f56eda09..d1a823b743 100644 --- a/backend/hct_mis_api/apps/targeting/validators.py +++ b/backend/hct_mis_api/apps/targeting/validators.py @@ -10,6 +10,7 @@ from hct_mis_api.apps.core.validators import BaseValidator from hct_mis_api.apps.household.models import Household, Individual from hct_mis_api.apps.program.models import Program +from hct_mis_api.apps.targeting.choices import FlexFieldClassification from hct_mis_api.apps.targeting.models import ( TargetingCriteriaRuleFilter, TargetPopulation, @@ -74,9 +75,9 @@ def validate(target_population: TargetPopulation) -> None: class TargetingCriteriaRuleFilterInputValidator: @staticmethod - def validate(rule_filter: Any) -> None: - is_flex_field = rule_filter.is_flex_field - if not is_flex_field: + def validate(rule_filter: Any, program: Program) -> None: + flex_field_classification = rule_filter.flex_field_classification + if flex_field_classification == FlexFieldClassification.NOT_FLEX_FIELD: attributes = FieldFactory.from_scope(Scope.TARGETING).to_dict_by("name") attribute = attributes.get(rule_filter.field_name) if attribute is None: @@ -84,9 +85,9 @@ def validate(rule_filter: Any) -> None: raise ValidationError( f"Can't find any core field attribute associated with {rule_filter.field_name} field name" ) - else: + elif flex_field_classification == FlexFieldClassification.FLEX_FIELD_NOT_PDU: try: - attribute = FlexibleAttribute.objects.get(name=rule_filter.field_name) + attribute = FlexibleAttribute.objects.get(name=rule_filter.field_name, program=None) except FlexibleAttribute.DoesNotExist: logger.exception( f"Can't find any flex field attribute associated with {rule_filter.field_name} field name", @@ -94,6 +95,16 @@ def validate(rule_filter: Any) -> None: raise ValidationError( f"Can't find any flex field attribute associated with {rule_filter.field_name} field name" ) + else: + try: + attribute = FlexibleAttribute.objects.get(name=rule_filter.field_name, program=program) + except FlexibleAttribute.DoesNotExist: + logger.exception( + f"Can't find PDU flex field attribute associated with {rule_filter.field_name} field name in program {program.name}", + ) + raise ValidationError( + f"Can't find PDU flex field attribute associated with {rule_filter.field_name} field name in program {program.name}", + ) comparison_attribute = TargetingCriteriaRuleFilter.COMPARISON_ATTRIBUTES.get(rule_filter.comparison_method) if comparison_attribute is None: logger.error(f"Unknown comparison method - {rule_filter.comparison_method}") @@ -118,7 +129,7 @@ def validate(rule_filter: Any) -> None: class TargetingCriteriaRuleInputValidator: @staticmethod - def validate(rule: "Rule") -> None: + def validate(rule: "Rule", program: "Program") -> None: total_len = 0 filters = rule.get("filters") individuals_filters_blocks = rule.get("individuals_filters_blocks") @@ -131,7 +142,7 @@ def validate(rule: "Rule") -> None: logger.error("There should be at least 1 filter or block in rules") raise ValidationError("There should be at least 1 filter or block in rules") for rule_filter in filters: - TargetingCriteriaRuleFilterInputValidator.validate(rule_filter) + TargetingCriteriaRuleFilterInputValidator.validate(rule_filter=rule_filter, program=program) class TargetingCriteriaInputValidator: @@ -148,11 +159,11 @@ def validate(targeting_criteria: Dict, program: Program) -> None: if household_ids and not ( program_dct.household_filters_available or program_dct.type == DataCollectingType.Type.SOCIAL ): - logger.error("Target criteria can has only individual ids") - raise ValidationError("Target criteria can has only individual ids") + logger.error("Target criteria can only have individual ids") + raise ValidationError("Target criteria can only have individual ids") if individual_ids and not program_dct.individual_filters_available: - logger.error("Target criteria can has only household ids") - raise ValidationError("Target criteria can has only household ids") + logger.error("Target criteria can only have household ids") + raise ValidationError("Target criteria can only have household ids") if household_ids: ids_list = household_ids.split(",") @@ -176,4 +187,4 @@ def validate(targeting_criteria: Dict, program: Program) -> None: if not household_ids and not individual_ids: for rule in rules: - TargetingCriteriaRuleInputValidator.validate(rule) + TargetingCriteriaRuleInputValidator.validate(rule=rule, program=program) From 07cae13d02d62d6e1e869f90dedd64d50d21d91a Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Thu, 8 Aug 2024 12:34:54 +0200 Subject: [PATCH 002/118] fix issue happening lately on dev --- backend/hct_mis_api/apps/program/mutations.py | 5 ++++- backend/hct_mis_api/apps/program/utils.py | 10 +++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/backend/hct_mis_api/apps/program/mutations.py b/backend/hct_mis_api/apps/program/mutations.py index 94e6af6a20..c5fb78f6ad 100644 --- a/backend/hct_mis_api/apps/program/mutations.py +++ b/backend/hct_mis_api/apps/program/mutations.py @@ -266,7 +266,10 @@ def processed_mutate(cls, root: Any, info: Any, program_data: Dict) -> "CopyProg # create partner access only for SELECTED_PARTNERS_ACCESS type, since NONE and ALL are handled through signal if partner_access == Program.SELECTED_PARTNERS_ACCESS: create_program_partner_access(partners_data, program, partner_access) - copy_program_task.delay(copy_from_program_id=program_id, new_program_id=program.id) + + transaction.on_commit( + lambda: copy_program_task.delay(copy_from_program_id=program_id, new_program_id=program.id) + ) if pdu_fields is not None: FlexibleAttributeForPDUService(program, pdu_fields).create_pdu_flex_attributes() diff --git a/backend/hct_mis_api/apps/program/utils.py b/backend/hct_mis_api/apps/program/utils.py index 9a43762cac..7bc7f73975 100644 --- a/backend/hct_mis_api/apps/program/utils.py +++ b/backend/hct_mis_api/apps/program/utils.py @@ -87,6 +87,7 @@ def copy_program_population(self) -> None: else: individuals = self.copy_individuals_without_collections() households = self.copy_households_without_collections(individuals) + self.copy_household_related_data(households, individuals) self.copy_individual_related_data(individuals) @@ -173,13 +174,8 @@ def copy_roles_per_household( role.pk = None role.household = new_household role.rdi_merge_status = self.rdi_merge_status - role.individual = ( - getattr(Individual, self.manager) - .filter(id__in=[ind.pk for ind in new_individuals]) - .get( - program=self.program, - copied_from=role.individual, - ) + role.individual = next( + filter(lambda ind: ind.program == self.program and ind.copied_from == role.individual, new_individuals) ) roles_in_household.append(role) return roles_in_household From 99ae6e229b2ae924e3fbd68dc601444eedc05350 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Thu, 8 Aug 2024 15:11:14 +0200 Subject: [PATCH 003/118] adjust test --- .../apps/program/tests/test_copy_program.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/backend/hct_mis_api/apps/program/tests/test_copy_program.py b/backend/hct_mis_api/apps/program/tests/test_copy_program.py index 76e14f6cb5..e9e03bc219 100644 --- a/backend/hct_mis_api/apps/program/tests/test_copy_program.py +++ b/backend/hct_mis_api/apps/program/tests/test_copy_program.py @@ -229,11 +229,12 @@ def test_copy_with_permissions(self) -> None: self.create_user_role_with_permissions(self.user, [Permissions.PROGRAMME_DUPLICATE], self.business_area) self.assertIsNone(self.household1.household_collection) self.assertIsNone(self.individuals1[0].individual_collection) - self.snapshot_graphql_request( - request_string=self.COPY_PROGRAM_MUTATION, - context={"user": self.user}, - variables=self.copy_data, - ) + with self.captureOnCommitCallbacks(execute=True): + self.snapshot_graphql_request( + request_string=self.COPY_PROGRAM_MUTATION, + context={"user": self.user}, + variables=self.copy_data, + ) copied_program = Program.objects.exclude(id=self.program.id).order_by("created_at").last() self.assertEqual(copied_program.status, Program.DRAFT) self.assertEqual(copied_program.name, "copied name") From f9ac91f9c97a5593909a27c26dee5472dbcb70ef Mon Sep 17 00:00:00 2001 From: szymon-kellton <130459593+szymon-kellton@users.noreply.github.com> Date: Mon, 12 Aug 2024 09:10:55 +0200 Subject: [PATCH 004/118] Selenium: Grievance tickets - module tests (#4059) * Init * Test * Parametrization * Girevance in progress * Grievance in progress * Grievance in progress * Grievance in progress * Grievance in progress * Grievance in progress * Grievance in progress * Grievance in progress * Grievance in progress * Grievance in progress. * Grievance in progress * Grievance in progress * Grievance in progress * Grievance in progress * test_grievance_tickets_create_new_ticket[Data Change Individual Data Update] * Small fix * Added test_grievance_tickets_process_tickets * Added test_grievance_tickets_go_to_admin_panel_button * Added test_grievance_tickets_activity_log * In progress: test_grievance_tickets_add_note * Grievance in progress * yarn test -u * Added test_grievance_tickets_create_new_tickets_Grievance_Complaint_Payment_Related_Complaint * In progress test_grievance_tickets_look_up_linked_ticket * In progress test_grievance_tickets_look_up_linked_ticket * Added test_grievance_tickets_check_identity_verification * Instability fixes part 1 * Instability fixes part 2 * Instability fixes part 3 * Instability fixes part 4 * Instability fixes part 5 * Fix * Fix for select_listbox_element * Black * Fix for select_option_by_name * Fix for test_grievance_tickets_create_new_ticket * black * Fix for test_grievance_tickets_create_new_ticket[Data Change Individual Data Update] * Fix for test_grievance_tickets_create_new_ticket[Data Change Individual Data Update] * Fix * Test * Test * Test * Test * Test * Test --- .../accountability/test_communication.py | 4 +- .../accountability/test_surveys.py | 4 +- backend/selenium_tests/drawer/test_drawer.py | 10 +- .../selenium_tests/filters/test_filters.py | 21 +- .../grievance/feedback/test_feedback.py | 12 +- .../test_grievance_tickets.py | 792 +++++++++++++++++- .../helpers/document_example.png | Bin 0 -> 91392 bytes backend/selenium_tests/helpers/helper.py | 15 +- .../test_managerial_console.py | 4 +- .../page_object/admin_panel/admin_panel.py | 4 + .../page_object/base_components.py | 4 +- .../grievance/details_grievance_page.py | 78 ++ .../grievance/grievance_tickets.py | 21 +- .../page_object/grievance/new_feedback.py | 10 +- .../page_object/grievance/new_ticket.py | 342 +++++++- .../households_details.py | 4 + .../payment_module/test_payment_module.py | 18 +- .../test_payment_verification.py | 6 +- backend/selenium_tests/people/test_people.py | 10 +- .../program_details/test_program_details.py | 6 +- .../program_log/test_program_log.py | 6 +- .../test_programme_management.py | 12 +- .../programme_population/test_households.py | 4 +- .../programme_population/test_individuals.py | 8 +- .../test_periodic_data_templates.py | 10 +- .../test_periodic_data_update_upload.py | 8 +- .../programme_user/test_programme_user.py | 2 +- .../test_registration_data_import.py | 16 +- .../targeting/test_targeting.py | 50 +- .../selenium_tests/tools/tag_name_finder.py | 4 +- .../ActivityLogTable/ActivityLogTable.tsx | 2 +- .../core/ActivityLogTable/LogRow.tsx | 2 +- frontend/src/components/core/PageHeader.tsx | 2 +- .../NewDocumentationFieldArray.tsx | 1 + .../GrievancesTable/bulk/BulkBaseModal.tsx | 7 +- ...okUpHouseholdIndividualSelectionDetail.tsx | 5 +- .../LookUpLinkedTicketsDisplay.tsx | 2 +- .../LookUpPaymentRecordDisplay.tsx | 2 +- .../src/components/grievances/Notes/Notes.tsx | 4 +- .../individualDataRow.tsx | 4 +- .../CreatePaymentPlanHeader.test.tsx.snap | 1 + .../CreateSetUpFspHeader.test.tsx.snap | 1 + .../__snapshots__/EditFspHeader.test.tsx.snap | 1 + .../EditPaymentPlanHeader.test.tsx.snap | 1 + .../EditSetUpFspHeader.test.tsx.snap | 1 + .../__snapshots__/FspHeader.test.tsx.snap | 1 + .../CreatePaymentPlanHeader.test.tsx.snap | 1 + .../CreateSetUpFspHeader.test.tsx.snap | 1 + .../__snapshots__/EditFspHeader.test.tsx.snap | 1 + .../EditPaymentPlanHeader.test.tsx.snap | 1 + .../EditSetUpFspHeader.test.tsx.snap | 1 + .../__snapshots__/FspHeader.test.tsx.snap | 1 + .../HouseholdCompositionTable.tsx | 2 +- .../HouseholdCompositionTable.test.tsx.snap | 1 + 54 files changed, 1385 insertions(+), 146 deletions(-) create mode 100644 backend/selenium_tests/helpers/document_example.png diff --git a/backend/selenium_tests/accountability/test_communication.py b/backend/selenium_tests/accountability/test_communication.py index 08c505c6c3..179c34bf69 100644 --- a/backend/selenium_tests/accountability/test_communication.py +++ b/backend/selenium_tests/accountability/test_communication.py @@ -51,7 +51,7 @@ def test_smoke_accountability_communication( add_accountability_communication_message: Message, pageAccountabilityCommunication: AccountabilityCommunication, ) -> None: - pageAccountabilityCommunication.selectGlobalProgramFilter("Test Program").click() + pageAccountabilityCommunication.selectGlobalProgramFilter("Test Program") pageAccountabilityCommunication.getNavAccountability().click() assert "Communication" in pageAccountabilityCommunication.getPageHeaderTitle().text assert "NEW MESSAGE" in pageAccountabilityCommunication.getButtonCommunicationCreateNew().text @@ -81,7 +81,7 @@ def test_smoke_accountability_communication_details( pageAccountabilityCommunication: AccountabilityCommunication, pageAccountabilityCommunicationDetails: AccountabilityCommunicationDetails, ) -> None: - pageAccountabilityCommunication.selectGlobalProgramFilter("Test Program").click() + pageAccountabilityCommunication.selectGlobalProgramFilter("Test Program") pageAccountabilityCommunication.getNavAccountability().click() pageAccountabilityCommunication.getRows()[0].click() assert "MSG-24-0666" in pageAccountabilityCommunicationDetails.getPageHeaderTitle().text diff --git a/backend/selenium_tests/accountability/test_surveys.py b/backend/selenium_tests/accountability/test_surveys.py index 7723c1b6f7..568197f729 100644 --- a/backend/selenium_tests/accountability/test_surveys.py +++ b/backend/selenium_tests/accountability/test_surveys.py @@ -64,7 +64,7 @@ def test_smoke_accountability_surveys( add_accountability_surveys_message: Survey, pageAccountabilitySurveys: AccountabilitySurveys, ) -> None: - pageAccountabilitySurveys.selectGlobalProgramFilter("Test Program").click() + pageAccountabilitySurveys.selectGlobalProgramFilter("Test Program") pageAccountabilitySurveys.getNavAccountability().click() pageAccountabilitySurveys.getNavSurveys().click() @@ -99,7 +99,7 @@ def test_smoke_accountability_surveys_details( pageAccountabilitySurveysDetails: AccountabilitySurveysDetails, ) -> None: add_accountability_surveys_message.recipients.set([Household.objects.first()]) - pageAccountabilitySurveys.selectGlobalProgramFilter("Test Program").click() + pageAccountabilitySurveys.selectGlobalProgramFilter("Test Program") pageAccountabilitySurveys.getNavAccountability().click() pageAccountabilitySurveys.getNavSurveys().click() pageAccountabilitySurveys.getRows()[0].click() diff --git a/backend/selenium_tests/drawer/test_drawer.py b/backend/selenium_tests/drawer/test_drawer.py index 665baa124b..1fe60dfe4d 100644 --- a/backend/selenium_tests/drawer/test_drawer.py +++ b/backend/selenium_tests/drawer/test_drawer.py @@ -62,7 +62,7 @@ def test_social_worker_program_drawer_order( pageProgrammeManagement: ProgrammeManagement, pageProgrammeDetails: ProgrammeDetails, ) -> None: - pageProgrammeManagement.selectGlobalProgramFilter("Worker Program").click() + pageProgrammeManagement.selectGlobalProgramFilter("Worker Program") assert "Worker Program" in pageProgrammeDetails.getHeaderTitle().text expected_menu_items = [ "Country Dashboard", @@ -86,7 +86,7 @@ def test_normal_program_drawer_order( pageProgrammeManagement: ProgrammeManagement, pageProgrammeDetails: ProgrammeDetails, ) -> None: - pageProgrammeManagement.selectGlobalProgramFilter("Normal Program").click() + pageProgrammeManagement.selectGlobalProgramFilter("Normal Program") assert "Normal Program" in pageProgrammeDetails.getHeaderTitle().text expected_menu_items = [ "Country Dashboard", @@ -133,16 +133,16 @@ def test_inactive_draft_subheader( active_program_name = active_program.name finished_program_name = finished_program.name - pageProgrammeManagement.selectGlobalProgramFilter(draft_program_name).click() + pageProgrammeManagement.selectGlobalProgramFilter(draft_program_name) assert draft_program_name in pageProgrammeDetails.getHeaderTitle().text assert pageProgrammeDetails.getDrawerInactiveSubheader().text == "program inactive" - pageProgrammeManagement.selectGlobalProgramFilter(active_program_name).click() + pageProgrammeManagement.selectGlobalProgramFilter(active_program_name) assert active_program_name in pageProgrammeDetails.getHeaderTitle().text with pytest.raises(Exception): pageProgrammeDetails.getDrawerInactiveSubheader(timeout=0.05) # first have to search Finished program because of default filtering - pageProgrammeManagement.selectGlobalProgramFilter(finished_program_name).click() + pageProgrammeManagement.selectGlobalProgramFilter(finished_program_name) assert finished_program_name in pageProgrammeDetails.getHeaderTitle().text assert pageProgrammeDetails.getDrawerInactiveSubheader().text == "program inactive" diff --git a/backend/selenium_tests/filters/test_filters.py b/backend/selenium_tests/filters/test_filters.py index 4ed65ae449..1ca4adc3b8 100644 --- a/backend/selenium_tests/filters/test_filters.py +++ b/backend/selenium_tests/filters/test_filters.py @@ -32,6 +32,11 @@ TargetPopulationFactory, ) from hct_mis_api.apps.targeting.models import TargetPopulation +from selenium_tests.page_object.grievance.details_grievance_page import ( + GrievanceDetailsPage, +) +from selenium_tests.page_object.grievance.grievance_tickets import GrievanceTickets +from selenium_tests.page_object.grievance.new_ticket import NewTicket from selenium_tests.page_object.programme_details.programme_details import ( ProgrammeDetails, ) @@ -283,7 +288,7 @@ def create_programs() -> None: @pytest.mark.usefixtures("login") class TestSmokeFilters: def test_filters_selected_program(self, create_programs: None, filters: Filters) -> None: - filters.selectGlobalProgramFilter("Test Programm").click() + filters.selectGlobalProgramFilter("Test Programm") programs = { "Registration Data Import": [ @@ -524,7 +529,7 @@ def test_filters_happy_path_search_filter( filters: Filters, pageProgrammeDetails: ProgrammeDetails, ) -> None: - filters.selectGlobalProgramFilter("Test Programm").click() + filters.selectGlobalProgramFilter("Test Programm") assert "Test Programm" in pageProgrammeDetails.getHeaderTitle().text filters.wait_for(f'[data-cy="nav-{module[0]}').click() assert filters.waitForNumberOfRows(2) @@ -536,3 +541,15 @@ def test_filters_happy_path_search_filter( filters.getFilterByLocator(module[1]).send_keys(module[2]) filters.getButtonFiltersApply().click() assert filters.waitForNumberOfRows(1) + + @pytest.mark.skip("ToDo") + def test_grievance_tickets_filters_of_households_and_individuals( + self, + pageGrievanceTickets: GrievanceTickets, + pageGrievanceNewTicket: NewTicket, + pageGrievanceDetailsPage: GrievanceDetailsPage, + filters: Filters, + ) -> None: + pageGrievanceTickets.getNavGrievance().click() + assert "Grievance Tickets" in pageGrievanceTickets.getGrievanceTitle().text + pageGrievanceTickets.getButtonNewTicket().click() diff --git a/backend/selenium_tests/grievance/feedback/test_feedback.py b/backend/selenium_tests/grievance/feedback/test_feedback.py index 2cb3856cf5..c6453246e3 100644 --- a/backend/selenium_tests/grievance/feedback/test_feedback.py +++ b/backend/selenium_tests/grievance/feedback/test_feedback.py @@ -208,12 +208,12 @@ def test_check_feedback_filtering_by_chosen_programme( pageFeedback.getRow(0).click() assert "-" in pageFeedbackDetails.getProgramme().text pageFeedbackDetails.getButtonEdit().click() - pageNewFeedback.selectProgramme("Test Programm").click() + pageNewFeedback.selectProgramme("Test Programm") pageNewFeedback.getButtonNext().click() # Check Feedback filtering by chosen Programme assert "Test Programm" in pageFeedbackDetails.getProgramme().text assert pageFeedback.globalProgramFilterText in pageFeedback.getGlobalProgramFilter().text - pageFeedback.selectGlobalProgramFilter("Test Programm").click() + pageFeedback.selectGlobalProgramFilter("Test Programm") assert "Test Programm" in pageProgrammeDetails.getHeaderTitle().text pageFeedback.wait_for_disappear(pageFeedback.navGrievanceDashboard) pageFeedback.getNavGrievance().click() @@ -222,14 +222,14 @@ def test_check_feedback_filtering_by_chosen_programme( assert 1 == len(pageFeedback.getRows()) assert "Negative Feedback" in pageFeedback.getRow(0).find_elements("tag name", "td")[1].text - pageFeedback.selectGlobalProgramFilter("Draft Program").click() + pageFeedback.selectGlobalProgramFilter("Draft Program") assert "Draft Program" in pageProgrammeDetails.getHeaderTitle().text pageFeedback.wait_for_disappear(pageFeedback.navGrievanceDashboard) pageFeedback.getNavGrievance().click() pageFeedback.getNavFeedback().click() assert 0 == len(pageFeedback.getRows()) - pageFeedback.selectGlobalProgramFilter("All Programmes").click() + pageFeedback.selectGlobalProgramFilter("All Programmes") assert "Programme Management" in pageProgrammeDetails.getHeaderTitle().text pageFeedback.wait_for_disappear(pageFeedback.navGrievanceDashboard) pageFeedback.getNavGrievance().click() @@ -340,7 +340,7 @@ def test_edit_feedback( pageFeedback.getRow(0).click() assert "-" in pageFeedbackDetails.getProgramme().text pageFeedbackDetails.getButtonEdit().click() - pageNewFeedback.selectProgramme("Draft Program").click() + pageNewFeedback.selectProgramme("Draft Program") pageNewFeedback.getDescription().click() pageNewFeedback.getDescription().send_keys(Keys.CONTROL, "a") pageNewFeedback.getDescription().send_keys("New description") @@ -348,7 +348,7 @@ def test_edit_feedback( pageNewFeedback.getInputArea().send_keys("Abkamari") pageNewFeedback.getInputLanguage().send_keys("English") # ToDo: Enable after Fix bug - # pageNewFeedback.selectArea("Abband").click() + # pageNewFeedback.selectArea("Abband") pageNewFeedback.getButtonNext().click() # Check edited Feedback assert "Draft Program" in pageFeedbackDetails.getProgramme().text diff --git a/backend/selenium_tests/grievance/grievance_tickets/test_grievance_tickets.py b/backend/selenium_tests/grievance/grievance_tickets/test_grievance_tickets.py index 8172048682..996a7315dc 100644 --- a/backend/selenium_tests/grievance/grievance_tickets/test_grievance_tickets.py +++ b/backend/selenium_tests/grievance/grievance_tickets/test_grievance_tickets.py @@ -1,4 +1,5 @@ import base64 +from datetime import datetime from time import sleep from typing import Generator @@ -6,14 +7,17 @@ from django.core.management import call_command import pytest +from dateutil.relativedelta import relativedelta from page_object.grievance.details_grievance_page import GrievanceDetailsPage from page_object.grievance.grievance_tickets import GrievanceTickets from page_object.grievance.new_ticket import NewTicket from pytest_django import DjangoDbBlocker from selenium.webdriver.common.by import By +from selenium.webdriver.remote.webelement import WebElement from hct_mis_api.apps.account.models import User -from hct_mis_api.apps.core.models import BusinessArea +from hct_mis_api.apps.core.fixtures import DataCollectingTypeFactory +from hct_mis_api.apps.core.models import BusinessArea, DataCollectingType from hct_mis_api.apps.grievance.models import ( GrievanceTicket, TicketNeedsAdjudicationDetails, @@ -23,7 +27,21 @@ create_household_and_individuals, ) from hct_mis_api.apps.household.models import HOST, Household, Individual +from hct_mis_api.apps.payment.fixtures import CashPlanFactory, PaymentRecordFactory +from hct_mis_api.apps.payment.models import PaymentRecord +from hct_mis_api.apps.program.fixtures import ProgramFactory +from hct_mis_api.apps.program.models import Program +from hct_mis_api.apps.targeting.fixtures import ( + TargetingCriteriaFactory, + TargetPopulationFactory, +) from selenium_tests.drawer.test_drawer import get_program_with_dct_type_and_name +from selenium_tests.helpers.date_time_format import FormatTime +from selenium_tests.page_object.admin_panel.admin_panel import AdminPanel +from selenium_tests.page_object.programme_population.households import Households +from selenium_tests.page_object.programme_population.households_details import ( + HouseholdsDetails, +) from selenium_tests.page_object.programme_population.individuals import Individuals pytestmark = pytest.mark.django_db(transaction=True) @@ -56,6 +74,55 @@ def create_programs(django_db_setup: Generator[None, None, None], django_db_bloc yield +@pytest.fixture +def household_without_disabilities() -> Household: + yield create_custom_household(observed_disability=[]) + + +@pytest.fixture +def hh_with_payment_record(household_without_disabilities: Household) -> PaymentRecord: + targeting_criteria = TargetingCriteriaFactory() + + target_population = TargetPopulationFactory( + created_by=User.objects.first(), + targeting_criteria=targeting_criteria, + business_area=household_without_disabilities.business_area, + ) + cash_plan = CashPlanFactory( + program=household_without_disabilities.program, + business_area=household_without_disabilities.business_area, + ) + cash_plan.save() + payment_record = PaymentRecordFactory( + parent=cash_plan, + household=household_without_disabilities, + target_population=target_population, + delivered_quantity_usd=None, + business_area=household_without_disabilities.business_area, + ) + payment_record.save() + return payment_record + + +def find_text_of_label(element: WebElement) -> str: + return element.find_element(By.XPATH, "..").find_element(By.XPATH, "..").text + + +def create_program( + name: str, dct_type: str = DataCollectingType.Type.STANDARD, status: str = Program.ACTIVE +) -> Program: + BusinessArea.objects.filter(slug="afghanistan").update(is_payment_plan_applicable=True) + dct = DataCollectingTypeFactory(type=dct_type) + program = ProgramFactory( + name=name, + start_date=datetime.now() - relativedelta(months=1), + end_date=datetime.now() + relativedelta(months=1), + data_collecting_type=dct, + status=status, + ) + return program + + def create_custom_household(observed_disability: list[str], residence_status: str = HOST) -> Household: program = get_program_with_dct_type_and_name("Test Program", "1234") household, _ = create_household_and_individuals( @@ -137,7 +204,11 @@ def generate_grievance( hh = create_custom_household(observed_disability=[]) - individual_qs = Individual.objects.filter(household=hh) + individual_qs = [ + Individual.objects.get(unicef_id="IND-00-0000.0011"), + Individual.objects.get(unicef_id="IND-00-0000.0022"), + Individual.objects.get(unicef_id="IND-00-0000.0033"), + ] # list of possible duplicates in the ticket possible_duplicates = [individual_qs[1], individual_qs[2]] @@ -179,6 +250,76 @@ def generate_grievance( return grievance_ticket +@pytest.fixture +def add_grievance_tickets() -> GrievanceTicket: + GrievanceTicket._meta.get_field("created_at").auto_now_add = False + GrievanceTicket._meta.get_field("updated_at").auto_now = False + grievance = create_grievance_referral() + GrievanceTicket._meta.get_field("created_at").auto_now_add = True + GrievanceTicket._meta.get_field("updated_at").auto_now = True + yield grievance + + +@pytest.fixture +def create_four_grievance_tickets() -> [GrievanceTicket]: + GrievanceTicket._meta.get_field("created_at").auto_now_add = False + GrievanceTicket._meta.get_field("updated_at").auto_now = False + grievance = list() + for _ in range(4): + grievance.append(create_grievance_referral(assigned_to="")) + GrievanceTicket._meta.get_field("created_at").auto_now_add = True + GrievanceTicket._meta.get_field("updated_at").auto_now = True + yield grievance + + +def create_grievance_referral( + unicef_id: str = "GRV-0000001", + status: int = GrievanceTicket.STATUS_NEW, + category: int = GrievanceTicket.CATEGORY_REFERRAL, + created_by: User | None = None, + assigned_to: User | None | str = None, + business_area: BusinessArea | None = None, + priority: int = 1, + urgency: int = 1, + household_unicef_id: str = "HH-20-0000.0001", + updated_at: str = "2023-09-27T11:26:33.846Z", + created_at: str = "2022-04-30T09:54:07.827000", +) -> GrievanceTicket: + created_by = User.objects.first() if created_by is None else created_by + business_area = BusinessArea.objects.filter(slug="afghanistan").first() if business_area is None else business_area + + ticket_data = { + "business_area": business_area, + "unicef_id": unicef_id, + "language": "Polish", + "consent": True, + "description": "grievance_ticket_1", + "category": category, + "status": status, + "created_by": created_by, + "created_at": created_at, + "updated_at": updated_at, + "household_unicef_id": household_unicef_id, + "priority": priority, + "urgency": urgency, + } + if assigned_to is None: + assigned_to = User.objects.first() + ticket_data["assigned_to"] = assigned_to + elif isinstance(assigned_to, User): + ticket_data["assigned_to"] = assigned_to + + grievance_ticket = GrievanceTicket.objects.create(**ticket_data) + + from hct_mis_api.apps.grievance.models import TicketReferralDetails + + TicketReferralDetails.objects.create( + ticket=grievance_ticket, + ) + + return grievance_ticket + + @pytest.mark.usefixtures("login") class TestSmokeGrievanceTickets: def test_check_grievance_tickets_user_generated_page( @@ -274,7 +415,7 @@ def test_check_grievance_tickets_details_page( assert "-" in pageGrievanceDetailsPage.getTicketPaymentLabel().text assert "-" in pageGrievanceDetailsPage.getLabelPaymentPlan().text assert "-" in pageGrievanceDetailsPage.getLabelPaymentPlanVerification().text - assert "Test Programm" in pageGrievanceDetailsPage.getLabelProgramme().text + assert "Test Program" in pageGrievanceDetailsPage.getLabelProgramme().text assert "Andarab" in pageGrievanceDetailsPage.getAdministrativeLevel().text assert "-" in pageGrievanceDetailsPage.getAreaVillage().text assert "English | English" in pageGrievanceDetailsPage.getLanguagesSpoken().text @@ -287,20 +428,70 @@ def test_check_grievance_tickets_details_page( @pytest.mark.usefixtures("login") class TestGrievanceTicketsHappyPath: + def test_grievance_tickets_create_new_ticket_referral( + self, + pageGrievanceTickets: GrievanceTickets, + pageGrievanceNewTicket: NewTicket, + pageGrievanceDetailsPage: GrievanceDetailsPage, + ) -> None: + pageGrievanceTickets.getNavGrievance().click() + assert "Grievance Tickets" in pageGrievanceTickets.getGrievanceTitle().text + pageGrievanceTickets.getButtonNewTicket().click() + pageGrievanceNewTicket.getSelectCategory().click() + pageGrievanceNewTicket.select_option_by_name("Referral") + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getHouseholdTab() + assert pageGrievanceNewTicket.waitForNoResults() + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getReceivedConsent().click() + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getDescription().send_keys("Happy path test 1234!") + pageGrievanceNewTicket.getButtonNext().click() + assert "Happy path test 1234!" in pageGrievanceDetailsPage.getTicketDescription().text + assert "Referral" in pageGrievanceDetailsPage.getTicketCategory().text + assert "New" in pageGrievanceDetailsPage.getTicketStatus().text + assert "Not set" in pageGrievanceDetailsPage.getTicketPriority().text + assert "Not set" in pageGrievanceDetailsPage.getTicketUrgency().text + + +@pytest.mark.usefixtures("login") +class TestGrievanceTickets: @pytest.mark.parametrize( "test_data", [ - pytest.param({"category": "Sensitive Grievance", "type": "Miscellaneous"}, id="Sensitive Grievance"), - pytest.param({"category": "Grievance Complaint", "type": "Other Complaint"}, id="Grievance Complaint"), + pytest.param( + {"category": "Sensitive Grievance", "type": "Miscellaneous"}, id="Sensitive Grievance Miscellaneous" + ), + pytest.param( + {"category": "Sensitive Grievance", "type": "Personal disputes"}, + id="Sensitive Grievance Personal disputes", + ), + pytest.param( + {"category": "Grievance Complaint", "type": "Other Complaint"}, id="Grievance Complaint Other Complaint" + ), + pytest.param( + {"category": "Grievance Complaint", "type": "Registration Related Complaint"}, + id="Grievance Complaint Registration Related Complaint", + ), + pytest.param( + {"category": "Grievance Complaint", "type": "FSP Related Complaint"}, + id="Grievance Complaint FSP Related Complaint", + ), + pytest.param( + {"category": "Data Change", "type": "Withdraw Individual"}, id="Data Change Withdraw Individual" + ), + pytest.param( + {"category": "Data Change", "type": "Withdraw Household"}, id="Data Change Withdraw Household" + ), ], ) - @pytest.mark.skip(reason="ToDo") - def test_grievance_tickets_create_new_ticket( + def test_grievance_tickets_create_new_tickets( self, pageGrievanceTickets: GrievanceTickets, pageGrievanceNewTicket: NewTicket, pageGrievanceDetailsPage: GrievanceDetailsPage, test_data: dict, + household_without_disabilities: Household, ) -> None: pageGrievanceTickets.getNavGrievance().click() assert "Grievance Tickets" in pageGrievanceTickets.getGrievanceTitle().text @@ -308,46 +499,601 @@ def test_grievance_tickets_create_new_ticket( pageGrievanceNewTicket.getSelectCategory().click() pageGrievanceNewTicket.select_option_by_name(test_data["category"]) pageGrievanceNewTicket.getIssueType().click() - pageGrievanceNewTicket.select_option_by_name(test_data["type"]) + pageGrievanceNewTicket.select_listbox_element(test_data["type"]) + assert test_data["category"] in pageGrievanceNewTicket.getSelectCategory().text + assert test_data["type"] in pageGrievanceNewTicket.getIssueType().text pageGrievanceNewTicket.getButtonNext().click() pageGrievanceNewTicket.getHouseholdTab() - assert pageGrievanceNewTicket.waitForNoResults() + pageGrievanceNewTicket.getHouseholdTableRows(0).click() + if test_data["type"] not in ["Withdraw Household", "Household Data Update", "Add Individual"]: + pageGrievanceNewTicket.getIndividualTab().click() + pageGrievanceNewTicket.getIndividualTableRows(0).click() pageGrievanceNewTicket.getButtonNext().click() pageGrievanceNewTicket.getReceivedConsent().click() pageGrievanceNewTicket.getButtonNext().click() pageGrievanceNewTicket.getDescription().send_keys("Happy path test 1234!") pageGrievanceNewTicket.getButtonNext().click() assert "Happy path test 1234!" in pageGrievanceDetailsPage.getTicketDescription().text + assert "Test Program" in pageGrievanceDetailsPage.getLabelProgramme().text + user = User.objects.get(email="test@example.com") + assert f"{user.first_name} {user.last_name}" in pageGrievanceDetailsPage.getLabelCreatedBy().text assert test_data["category"] in pageGrievanceDetailsPage.getTicketCategory().text assert test_data["type"] in pageGrievanceDetailsPage.getLabelIssueType().text assert "New" in pageGrievanceDetailsPage.getTicketStatus().text assert "Not set" in pageGrievanceDetailsPage.getTicketPriority().text assert "Not set" in pageGrievanceDetailsPage.getTicketUrgency().text - def test_grievance_tickets_create_new_ticket_referral( + def test_grievance_tickets_create_new_ticket_Data_Change_Add_Individual_All_Fields( self, pageGrievanceTickets: GrievanceTickets, pageGrievanceNewTicket: NewTicket, pageGrievanceDetailsPage: GrievanceDetailsPage, + household_without_disabilities: Household, ) -> None: pageGrievanceTickets.getNavGrievance().click() assert "Grievance Tickets" in pageGrievanceTickets.getGrievanceTitle().text pageGrievanceTickets.getButtonNewTicket().click() pageGrievanceNewTicket.getSelectCategory().click() - pageGrievanceNewTicket.select_option_by_name("Referral") + pageGrievanceNewTicket.select_option_by_name("Data Change") + pageGrievanceNewTicket.getIssueType().click() + pageGrievanceNewTicket.select_listbox_element("Add Individual") + assert "Data Change" in pageGrievanceNewTicket.getSelectCategory().text + assert "Add Individual" in pageGrievanceNewTicket.getIssueType().text pageGrievanceNewTicket.getButtonNext().click() pageGrievanceNewTicket.getHouseholdTab() - assert pageGrievanceNewTicket.waitForNoResults() + pageGrievanceNewTicket.getHouseholdTableRows(0).click() pageGrievanceNewTicket.getButtonNext().click() pageGrievanceNewTicket.getReceivedConsent().click() pageGrievanceNewTicket.getButtonNext().click() - pageGrievanceNewTicket.getDescription().send_keys("Happy path test 1234!") + pageGrievanceNewTicket.getDescription().send_keys("Add Individual - TEST") + pageGrievanceNewTicket.getPhoneNoAlternative().send_keys("999 999 999") + pageGrievanceNewTicket.getDatePickerFilter().click() + pageGrievanceNewTicket.getDatePickerFilter().send_keys(FormatTime(1, 5, 1986).numerically_formatted_date) + pageGrievanceNewTicket.getInputIndividualdataBlockchainname().send_keys("TEST") + pageGrievanceNewTicket.getInputIndividualdataFamilyname().send_keys("Teria") + pageGrievanceNewTicket.getInputIndividualdataFullname().send_keys("Krido") + pageGrievanceNewTicket.getEstimatedBirthDate().click() + pageGrievanceNewTicket.select_listbox_element("Yes") + pageGrievanceNewTicket.getSelectIndividualdataSex().click() + pageGrievanceNewTicket.select_listbox_element("Male") + pageGrievanceNewTicket.getInputIndividualdataGivenname().send_keys("Krato") + pageGrievanceNewTicket.getSelectIndividualdataCommsdisability().click() + pageGrievanceNewTicket.select_listbox_element("A lot of difficulty") + pageGrievanceNewTicket.getSelectIndividualdataHearingdisability().click() + pageGrievanceNewTicket.select_listbox_element("A lot of difficulty") + pageGrievanceNewTicket.getSelectIndividualdataMemorydisability().click() + pageGrievanceNewTicket.select_listbox_element("Cannot do at all") + pageGrievanceNewTicket.getSelectIndividualdataSeeingdisability().click() + pageGrievanceNewTicket.select_listbox_element("Some difficulty") + pageGrievanceNewTicket.getSelectIndividualdataPhysicaldisability().click() + pageGrievanceNewTicket.select_listbox_element("None") + pageGrievanceNewTicket.getInputIndividualdataEmail().send_keys("kridoteria@bukare.cz") + pageGrievanceNewTicket.getSelectIndividualdataDisability().click() + pageGrievanceNewTicket.select_listbox_element("disabled") + pageGrievanceNewTicket.getSelectIndividualdataPregnant().click() + pageGrievanceNewTicket.select_listbox_element("No") + pageGrievanceNewTicket.getSelectIndividualdataMaritalstatus().click() + pageGrievanceNewTicket.select_listbox_element("Married") + pageGrievanceNewTicket.getInputIndividualdataMiddlename().send_keys("Batu") + pageGrievanceNewTicket.getInputIndividualdataPaymentdeliveryphoneno().send_keys("123 456 789") + pageGrievanceNewTicket.getInputIndividualdataPhoneno().send_keys("098 765 432") + pageGrievanceNewTicket.getSelectIndividualdataPreferredlanguage().click() + pageGrievanceNewTicket.select_listbox_element("English") + pageGrievanceNewTicket.getSelectIndividualdataRelationship().click() + pageGrievanceNewTicket.select_listbox_element("Wife / Husband") + pageGrievanceNewTicket.getSelectIndividualdataRole().click() + pageGrievanceNewTicket.select_listbox_element("Alternate collector") + pageGrievanceNewTicket.getInputIndividualdataWalletaddress().send_keys("Wordoki") + pageGrievanceNewTicket.getInputIndividualdataWalletname().send_keys("123") + pageGrievanceNewTicket.getInputIndividualdataWhoanswersaltphone().send_keys("000 000 000") + pageGrievanceNewTicket.getInputIndividualdataWhoanswersphone().send_keys("111 11 11") + pageGrievanceNewTicket.getButtonNext().click() - assert "Happy path test 1234!" in pageGrievanceDetailsPage.getTicketDescription().text - assert "Referral" in pageGrievanceDetailsPage.getTicketCategory().text + assert "Add Individual - TEST" in pageGrievanceDetailsPage.getTicketDescription().text + assert "Data Change" in pageGrievanceDetailsPage.getTicketCategory().text + assert "Add Individual" in pageGrievanceDetailsPage.getLabelIssueType().text + assert "New" in pageGrievanceDetailsPage.getTicketStatus().text + assert "Not set" in pageGrievanceDetailsPage.getTicketPriority().text + assert "Not set" in pageGrievanceDetailsPage.getTicketUrgency().text + + def test_grievance_tickets_create_new_ticket_Data_Change_Add_Individual_Mandatory_Fields( + self, + pageGrievanceTickets: GrievanceTickets, + pageGrievanceNewTicket: NewTicket, + pageGrievanceDetailsPage: GrievanceDetailsPage, + household_without_disabilities: Household, + ) -> None: + pageGrievanceTickets.getNavGrievance().click() + assert "Grievance Tickets" in pageGrievanceTickets.getGrievanceTitle().text + pageGrievanceTickets.getButtonNewTicket().click() + pageGrievanceNewTicket.getSelectCategory().click() + pageGrievanceNewTicket.select_option_by_name("Data Change") + pageGrievanceNewTicket.getIssueType().click() + pageGrievanceNewTicket.select_listbox_element("Add Individual") + assert "Data Change" in pageGrievanceNewTicket.getSelectCategory().text + assert "Add Individual" in pageGrievanceNewTicket.getIssueType().text + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getHouseholdTab() + pageGrievanceNewTicket.getHouseholdTableRows(0).click() + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getReceivedConsent().click() + pageGrievanceNewTicket.getButtonNext().click() + + pageGrievanceNewTicket.getDescription().send_keys("Add Individual - TEST") + pageGrievanceNewTicket.getDatePickerFilter().click() + pageGrievanceNewTicket.getDatePickerFilter().send_keys(FormatTime(1, 5, 1986).numerically_formatted_date) + + pageGrievanceNewTicket.getInputIndividualdataFullname().send_keys("Krido") + pageGrievanceNewTicket.getSelectIndividualdataSex().click() + pageGrievanceNewTicket.select_listbox_element("Male") + + pageGrievanceNewTicket.getEstimatedBirthDate().click() + pageGrievanceNewTicket.select_listbox_element("Yes") + + pageGrievanceNewTicket.getSelectIndividualdataRelationship().click() + pageGrievanceNewTicket.select_listbox_element("Wife / Husband") + pageGrievanceNewTicket.getSelectIndividualdataRole().click() + pageGrievanceNewTicket.select_listbox_element("Alternate collector") + pageGrievanceNewTicket.getButtonNext().click() + assert "ASSIGN TO ME" in pageGrievanceDetailsPage.getButtonAssignToMe().text assert "New" in pageGrievanceDetailsPage.getTicketStatus().text assert "Not set" in pageGrievanceDetailsPage.getTicketPriority().text assert "Not set" in pageGrievanceDetailsPage.getTicketUrgency().text + assert "-" in pageGrievanceDetailsPage.getTicketAssigment().text + assert "Data Change" in pageGrievanceDetailsPage.getTicketCategory().text + assert "Add Individual" in pageGrievanceDetailsPage.getLabelIssueType().text + assert household_without_disabilities.unicef_id in pageGrievanceDetailsPage.getTicketHouseholdID().text + assert "Test Program" in pageGrievanceDetailsPage.getLabelProgramme().text + assert datetime.now().strftime("%-d %b %Y") in pageGrievanceDetailsPage.getLabelDateCreation().text + assert datetime.now().strftime("%-d %b %Y") in pageGrievanceDetailsPage.getLabelLastModifiedDate().text + assert "-" in pageGrievanceDetailsPage.getLabelAdministrativeLevel2().text + assert "-" in pageGrievanceDetailsPage.getLabelLanguagesSpoken().text + assert "-" in pageGrievanceDetailsPage.getLabelDocumentation().text + assert "Add Individual - TEST" in pageGrievanceDetailsPage.getLabelDescription().text + assert "-" in pageGrievanceDetailsPage.getLabelComments().text + assert "Male" in pageGrievanceDetailsPage.getLabelGender().text + assert "Alternate collector" in pageGrievanceDetailsPage.getLabelRole().text + assert "Krido" in pageGrievanceDetailsPage.getLabelFullName().text + assert "1986-05-01" in pageGrievanceDetailsPage.getLabelBirthDate().text + assert "Wife / Husband" in pageGrievanceDetailsPage.getLabelRelationship().text + assert "Yes" in pageGrievanceDetailsPage.getLabelEstimatedBirthDate().text + + @pytest.mark.parametrize( + "test_data", + [ + pytest.param( + {"category": "Data Change", "type": "Household Data Update"}, id="Data Change Household Data Update" + ), + ], + ) + def test_hh_grievance_tickets_create_new_ticket( + self, + pageGrievanceTickets: GrievanceTickets, + pageGrievanceNewTicket: NewTicket, + pageGrievanceDetailsPage: GrievanceDetailsPage, + household_without_disabilities: Household, + test_data: dict, + ) -> None: + pageGrievanceTickets.getNavGrievance().click() + assert "Grievance Tickets" in pageGrievanceTickets.getGrievanceTitle().text + pageGrievanceTickets.getButtonNewTicket().click() + pageGrievanceNewTicket.getSelectCategory().click() + pageGrievanceNewTicket.select_option_by_name(str(test_data["category"])) + pageGrievanceNewTicket.getIssueType().click() + pageGrievanceNewTicket.select_listbox_element(str(test_data["type"])) + assert test_data["category"] in pageGrievanceNewTicket.getSelectCategory().text + assert test_data["type"] in pageGrievanceNewTicket.getIssueType().text + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getHouseholdTab() + pageGrievanceNewTicket.getHouseholdTableRows(0).click() + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getReceivedConsent().click() + pageGrievanceNewTicket.getButtonNext().click() + + pageGrievanceNewTicket.getDescription().send_keys("Add Individual - TEST") + pageGrievanceNewTicket.getButtonAddNewField() + pageGrievanceNewTicket.getSelectFieldName().click() + pageGrievanceNewTicket.select_option_by_name("Females age 12 - 17 with disability") + pageGrievanceNewTicket.getInputValue().send_keys("1") + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceDetailsPage.getCheckboxHouseholdData() + assert "Female Age Group 12 17" in pageGrievanceDetailsPage.getRows()[0].text + assert "- 1" in pageGrievanceDetailsPage.getRows()[0].text + + @pytest.mark.parametrize( + "test_data", + [ + pytest.param( + {"category": "Data Change", "type": "Individual Data Update"}, + id="Data Change Individual Data Update", + ) + ], + ) + def test_grievance_tickets_create_new_ticket( + self, + pageGrievanceTickets: GrievanceTickets, + pageGrievanceNewTicket: NewTicket, + pageGrievanceDetailsPage: GrievanceDetailsPage, + household_without_disabilities: Household, + test_data: dict, + ) -> None: + pageGrievanceTickets.getNavGrievance().click() + assert "Grievance Tickets" in pageGrievanceTickets.getGrievanceTitle().text + pageGrievanceTickets.getButtonNewTicket().click() + pageGrievanceNewTicket.getSelectCategory().click() + pageGrievanceNewTicket.select_option_by_name(str(test_data["category"])) + pageGrievanceNewTicket.getIssueType().click() + pageGrievanceNewTicket.element_clickable(f'li[data-cy="select-option-{test_data["type"]}"]') + pageGrievanceNewTicket.select_listbox_element(str(test_data["type"])) + assert test_data["category"] in pageGrievanceNewTicket.getSelectCategory().text + assert test_data["type"] in pageGrievanceNewTicket.getIssueType().text + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getHouseholdTab() + pageGrievanceNewTicket.getHouseholdTableRows(0).click() + pageGrievanceNewTicket.getIndividualTab().click() + pageGrievanceNewTicket.getIndividualTableRows(0).click() + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getReceivedConsent().click() + pageGrievanceNewTicket.getButtonNext().click() + + pageGrievanceNewTicket.getDescription().send_keys("Add Individual - TEST") + pageGrievanceNewTicket.getButtonAddNewField().click() + pageGrievanceNewTicket.getIndividualFieldName(0).click() + pageGrievanceNewTicket.select_option_by_name("Gender") + pageGrievanceNewTicket.getInputIndividualData("Gender").click() + pageGrievanceNewTicket.select_listbox_element("Female") + pageGrievanceNewTicket.getIndividualFieldName(1).click() + pageGrievanceNewTicket.select_option_by_name("Preferred language") + pageGrievanceNewTicket.getInputIndividualData("Preferred language").click() + pageGrievanceNewTicket.select_listbox_element("English | English") + + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceDetailsPage.getCheckboxIndividualData() + row0 = pageGrievanceDetailsPage.getRows()[0].text.split(" ") + assert "Gender" in row0[0] + assert "Female" in row0[-1] + + row1 = pageGrievanceDetailsPage.getRows()[1].text.split(" ") + assert "Preferred Language" in f"{row1[0]} {row1[1]}" + assert "English" in row1[-1] + + def test_grievance_tickets_create_new_tickets_Grievance_Complaint_Partner_Related_Complaint( + self, + pageGrievanceTickets: GrievanceTickets, + pageGrievanceNewTicket: NewTicket, + pageGrievanceDetailsPage: GrievanceDetailsPage, + household_without_disabilities: Household, + ) -> None: + pageGrievanceTickets.getNavGrievance().click() + assert "Grievance Tickets" in pageGrievanceTickets.getGrievanceTitle().text + pageGrievanceTickets.getButtonNewTicket().click() + pageGrievanceNewTicket.getSelectCategory().click() + pageGrievanceNewTicket.select_option_by_name("Grievance Complaint") + pageGrievanceNewTicket.getIssueType().click() + pageGrievanceNewTicket.select_listbox_element("Partner Related Complaint") + assert "Grievance Complaint" in pageGrievanceNewTicket.getSelectCategory().text + assert "Partner Related Complaint" in pageGrievanceNewTicket.getIssueType().text + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getHouseholdTab() + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getReceivedConsent().click() + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getPartner().click() + pageGrievanceNewTicket.select_option_by_name("UNICEF") + pageGrievanceNewTicket.getDescription().send_keys("Test !@#$ OK") + pageGrievanceNewTicket.getButtonNext().click() + assert "UNICEF" in pageGrievanceDetailsPage.getLabelPartner().text + + def test_grievance_tickets_create_new_tickets_Grievance_Complaint_Payment_Related_Complaint( + self, + pageGrievanceTickets: GrievanceTickets, + pageGrievanceNewTicket: NewTicket, + pageGrievanceDetailsPage: GrievanceDetailsPage, + hh_with_payment_record: PaymentRecord, + ) -> None: + pageGrievanceTickets.getNavGrievance().click() + assert "Grievance Tickets" in pageGrievanceTickets.getGrievanceTitle().text + pageGrievanceTickets.getButtonNewTicket().click() + pageGrievanceNewTicket.getSelectCategory().click() + pageGrievanceNewTicket.select_option_by_name("Grievance Complaint") + pageGrievanceNewTicket.getIssueType().click() + pageGrievanceNewTicket.select_listbox_element("Payment Related Complaint") + assert "Grievance Complaint" in pageGrievanceNewTicket.getSelectCategory().text + assert "Payment Related Complaint" in pageGrievanceNewTicket.getIssueType().text + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getHouseholdTab() + pageGrievanceNewTicket.getHouseholdTableRows(0).click() + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getReceivedConsent().click() + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getDescription().send_keys("TEST Payment Related Complaint") + pageGrievanceNewTicket.getLookUpPaymentRecord().click() + pageGrievanceNewTicket.getCheckboxSelectAll().click() + pageGrievanceNewTicket.getButtonSubmit().click() + assert hh_with_payment_record.unicef_id in pageGrievanceDetailsPage.getPaymentRecord().text + + def test_grievance_tickets_look_up_linked_ticket( + self, + pageGrievanceTickets: GrievanceTickets, + pageGrievanceNewTicket: NewTicket, + pageGrievanceDetailsPage: GrievanceDetailsPage, + household_without_disabilities: Household, + add_grievance_tickets: GrievanceTicket, + ) -> None: + linked_ticket = GrievanceTicket.objects.first().unicef_id + pageGrievanceTickets.getNavGrievance().click() + assert "Grievance Tickets" in pageGrievanceTickets.getGrievanceTitle().text + pageGrievanceTickets.getButtonNewTicket().click() + pageGrievanceNewTicket.getSelectCategory().click() + pageGrievanceNewTicket.select_option_by_name("Referral") + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getHouseholdTab() + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getReceivedConsent().click() + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getDescription().send_keys("TEST Linked Ticket") + pageGrievanceNewTicket.getLookUpButton().click() + pageGrievanceNewTicket.getCheckboxSelectAll().click() + pageGrievanceNewTicket.getButtonSubmit().click() + assert linked_ticket in pageGrievanceNewTicket.getLinkedTicketId().text + pageGrievanceNewTicket.getButtonEdit().click() + pageGrievanceNewTicket.getButtonSubmit().click() + pageGrievanceNewTicket.getButtonDelete().click() + with pytest.raises(Exception): + pageGrievanceNewTicket.getLinkedTicketId() + pageGrievanceNewTicket.getLookUpButton().click() + pageGrievanceNewTicket.getCheckboxSelectAll().click() + pageGrievanceNewTicket.getButtonSubmit().click() + assert linked_ticket in pageGrievanceNewTicket.getLinkedTicketId().text + pageGrievanceNewTicket.getButtonNext().click() + assert linked_ticket in pageGrievanceDetailsPage.getLabelTickets().text + + def test_grievance_tickets_add_documentation( + self, + pageGrievanceTickets: GrievanceTickets, + pageGrievanceNewTicket: NewTicket, + pageGrievanceDetailsPage: GrievanceDetailsPage, + household_without_disabilities: Household, + ) -> None: + pageGrievanceTickets.getNavGrievance().click() + assert "Grievance Tickets" in pageGrievanceTickets.getGrievanceTitle().text + pageGrievanceTickets.getButtonNewTicket().click() + pageGrievanceNewTicket.getSelectCategory().click() + pageGrievanceNewTicket.select_option_by_name("Referral") + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getHouseholdTab() + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getReceivedConsent().click() + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getDescription().send_keys("Happy path test 1234!") + pageGrievanceNewTicket.getAddDocumentation().click() + pageGrievanceNewTicket.getInputDocumentationName(0).send_keys("example") + pageGrievanceNewTicket.upload_file(f"{pytest.SELENIUM_PATH}/helpers/document_example.png") + pageGrievanceNewTicket.getButtonNext().click() + assert "example" in pageGrievanceDetailsPage.getLinkShowPhoto().text + pageGrievanceDetailsPage.getLinkShowPhoto().click() + pageGrievanceDetailsPage.getButtonRotateImage().click() + pageGrievanceDetailsPage.getButtonCancel().click() + assert "example" in pageGrievanceDetailsPage.getLinkShowPhoto().text + + def test_grievance_tickets_check_identity_verification( + self, + pageGrievanceTickets: GrievanceTickets, + pageGrievanceNewTicket: NewTicket, + pageGrievanceDetailsPage: GrievanceDetailsPage, + household_without_disabilities: Household, + ) -> None: + pageGrievanceTickets.getNavGrievance().click() + assert "Grievance Tickets" in pageGrievanceTickets.getGrievanceTitle().text + pageGrievanceTickets.getButtonNewTicket().click() + pageGrievanceNewTicket.getSelectCategory().click() + pageGrievanceNewTicket.select_option_by_name("Data Change") + pageGrievanceNewTicket.getIssueType().click() + pageGrievanceNewTicket.select_listbox_element("Individual Data Update") + assert "Data Change" in pageGrievanceNewTicket.getSelectCategory().text + assert "Individual Data Update" in pageGrievanceNewTicket.getIssueType().text + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getHouseholdTab() + pageGrievanceNewTicket.getIndividualTab().click() + individual_unicef_id = pageGrievanceNewTicket.getIndividualTableRows(0).text.split(" ")[0] + pageGrievanceNewTicket.getIndividualTableRows(0).click() + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getInputQuestionnaire_size().click() + assert "3" in pageGrievanceNewTicket.getLabelHouseholdSize().text + pageGrievanceNewTicket.getInputQuestionnaire_malechildrencount().click() + assert "-" in pageGrievanceNewTicket.getLabelNumberOfMaleChildren().text + pageGrievanceNewTicket.getInputQuestionnaire_femalechildrencount().click() + assert "-" in pageGrievanceNewTicket.getLabelNumberOfFemaleChildren().text + pageGrievanceNewTicket.getInputQuestionnaire_childrendisabledcount().click() + assert "-" in pageGrievanceNewTicket.getLabelNumberOfDisabledChildren().text + pageGrievanceNewTicket.getInputQuestionnaire_headofhousehold().click() + individual = Individual.objects.get(unicef_id=individual_unicef_id) + household = individual.household + assert individual.full_name in pageGrievanceNewTicket.getLabelHeadOfHousehold().text + pageGrievanceNewTicket.getInputQuestionnaire_countryorigin().click() + assert str(household.country_origin) in pageGrievanceNewTicket.getLabelCountryOfOrigin().text + pageGrievanceNewTicket.getInputQuestionnaire_address().click() + assert household.address.replace("\n", " ") in pageGrievanceNewTicket.getLabelAddress().text + pageGrievanceNewTicket.getInputQuestionnaire_village().click() + assert household.village in pageGrievanceNewTicket.getLabelVillage().text + pageGrievanceNewTicket.getInputQuestionnaire_admin1().click() + assert "-" in pageGrievanceNewTicket.getLabelAdministrativeLevel1().text + pageGrievanceNewTicket.getInputQuestionnaire_admin2().click() + assert "-" in pageGrievanceNewTicket.getLabelAdministrativeLevel2().text + pageGrievanceNewTicket.getInputQuestionnaire_admin3().click() + assert "-" in pageGrievanceNewTicket.getLabelAdministrativeLevel3().text + pageGrievanceNewTicket.getInputQuestionnaire_admin4().click() + assert "-" in pageGrievanceNewTicket.getLabelAdministrativeLevel4().text + pageGrievanceNewTicket.getInputQuestionnaire_months_displaced_h_f().click() + assert "-" in pageGrievanceNewTicket.getLabelLengthOfTimeSinceArrival().text + pageGrievanceNewTicket.getInputQuestionnaire_fullname().click() + assert individual.full_name in pageGrievanceNewTicket.getLabelIndividualFullName().text + pageGrievanceNewTicket.getInputQuestionnaire_birthdate().click() + assert "-" in pageGrievanceNewTicket.getLabelBirthDate().text + pageGrievanceNewTicket.getInputQuestionnaire_sex().click() + assert individual.sex in pageGrievanceNewTicket.getLabelGender().text + pageGrievanceNewTicket.getInputQuestionnaire_phoneno().click() + assert "-" in pageGrievanceNewTicket.getLabelPhoneNumber().text + pageGrievanceNewTicket.getInputQuestionnaire_relationship().click() + assert "HEAD" in pageGrievanceNewTicket.getLabelRelationshipToHoh().text + pageGrievanceNewTicket.getReceivedConsent().click() + pageGrievanceNewTicket.getButtonNext().click() + + def test_grievance_tickets_edit_tickets_from_main_grievance_page( + self, + pageGrievanceTickets: GrievanceTickets, + pageHouseholds: Households, + create_four_grievance_tickets: [GrievanceTicket], + ) -> None: + pageGrievanceTickets.getNavGrievance().click() + assert "Grievance Tickets" in pageGrievanceTickets.getGrievanceTitle().text + pageGrievanceTickets.getSelectAll().click() + pageGrievanceTickets.getButtonAssign().click() + pageGrievanceTickets.getDropdown().click() + pageGrievanceTickets.select_listbox_element("test@example.com") + for str_row in pageGrievanceTickets.getRows(): + list_row = str_row.text.replace("\n", " ").split(" ") + assert list_row[0] in pageGrievanceTickets.getSelectedTickets().text + pageGrievanceTickets.getButtonSave().click() + pageGrievanceTickets.getStatusContainer() + pageGrievanceTickets.waitForRows() + for _ in range(50): + if "Assigned" in pageGrievanceTickets.getStatusContainer()[0].text: + break + sleep(0.1) + else: + assert "Assigned" in pageGrievanceTickets.getStatusContainer()[0].text + for str_row in pageGrievanceTickets.getRows(): + list_row = str_row.text.replace("\n", " ").split(" ") + assert list_row[1] in "Assigned" + + pageGrievanceTickets.getSelectAll().click() + pageGrievanceTickets.getButtonSetPriority().click() + pageGrievanceTickets.getDropdown().click() + pageGrievanceTickets.select_listbox_element("Medium") + pageGrievanceTickets.getButtonSave().click() + pageGrievanceTickets.getStatusContainer() + pageGrievanceTickets.waitForRows() + for _ in range(50): + if "Medium" in pageGrievanceTickets.getRows()[0].text: + break + sleep(0.1) + else: + assert "Medium" in pageGrievanceTickets.getRows()[0].text + for str_row in pageGrievanceTickets.getRows(): + assert "Medium" in str_row.text.replace("\n", " ").split(" ") + pageGrievanceTickets.getSelectAll().click() + pageGrievanceTickets.getButtonSetUrgency().click() + pageGrievanceTickets.getDropdown().click() + pageGrievanceTickets.select_listbox_element("Urgent") + pageGrievanceTickets.getButtonSave().click() + pageGrievanceTickets.getStatusContainer() + pageGrievanceTickets.waitForRows() + for _ in range(0): + if "Urgent" in pageGrievanceTickets.getRows()[0].text: + break + sleep(0.1) + else: + assert "Urgent" in pageGrievanceTickets.getRows()[0].text + for str_row in pageGrievanceTickets.getRows(): + assert "Urgent" in str_row.text.replace("\n", " ").split(" ") + + def test_grievance_tickets_process_tickets( + self, + pageGrievanceTickets: GrievanceTickets, + pageGrievanceNewTicket: NewTicket, + pageGrievanceDetailsPage: GrievanceDetailsPage, + household_without_disabilities: Household, + pageHouseholdsDetails: HouseholdsDetails, + pageHouseholds: Households, + ) -> None: + pageGrievanceTickets.getNavGrievance().click() + assert "Grievance Tickets" in pageGrievanceTickets.getGrievanceTitle().text + pageGrievanceTickets.getButtonNewTicket().click() + pageGrievanceNewTicket.getSelectCategory().click() + pageGrievanceNewTicket.select_option_by_name("Data Change") + pageGrievanceNewTicket.getIssueType().click() + pageGrievanceNewTicket.select_listbox_element("Household Data Update") + assert "Data Change" in pageGrievanceNewTicket.getSelectCategory().text + assert "Household Data Update" in pageGrievanceNewTicket.getIssueType().text + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getHouseholdTab() + pageGrievanceNewTicket.getHouseholdTableRows(0).click() + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getReceivedConsent().click() + pageGrievanceNewTicket.getButtonNext().click() + + pageGrievanceNewTicket.getDescription().send_keys("Add Individual - TEST") + pageGrievanceNewTicket.getButtonAddNewField() + pageGrievanceNewTicket.getSelectFieldName().click() + pageGrievanceNewTicket.select_option_by_name("Males Age 0 - 5") + pageGrievanceNewTicket.getInputValue().send_keys("5") + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceDetailsPage.getCheckboxHouseholdData() + pageGrievanceDetailsPage.getButtonAssignToMe().click() + pageGrievanceDetailsPage.getButtonSetInProgress().click() + pageGrievanceDetailsPage.getButtonSendForApproval().click() + pageGrievanceDetailsPage.getCheckboxHouseholdData().click() + pageGrievanceDetailsPage.getButtonApproval().click() + pageGrievanceDetailsPage.getButtonCloseTicket().click() + pageGrievanceDetailsPage.getButtonConfirm().click() + assert "Ticket ID" in pageGrievanceDetailsPage.getTitle().text + pageGrievanceNewTicket.selectGlobalProgramFilter("Test Program") + pageGrievanceNewTicket.getNavProgrammePopulation().click() + pageHouseholds.getHouseholdsRows()[0].click() + assert "5" in pageHouseholdsDetails.getRow05().text + + def test_grievance_tickets_add_note( + self, + pageGrievanceTickets: GrievanceTickets, + pageGrievanceNewTicket: NewTicket, + pageGrievanceDetailsPage: GrievanceDetailsPage, + household_without_disabilities: Household, + add_grievance_tickets: GrievanceTicket, + ) -> None: + pageGrievanceTickets.getNavGrievance().click() + assert "Grievance Tickets" in pageGrievanceTickets.getGrievanceTitle().text + pageGrievanceTickets.getTicketListRow()[0].click() + pageGrievanceDetailsPage.getInputNewnote().send_keys("Test adding new note.") + pageGrievanceDetailsPage.getButtonNewNote().click() + user = pageGrievanceDetailsPage.getNoteName().text + assert 1 == len(pageGrievanceDetailsPage.getNoteRows()) + assert user in pageGrievanceDetailsPage.getNoteRows()[0].text + assert datetime.now().strftime("%-d %b %Y") in pageGrievanceDetailsPage.getNoteRows()[0].text + assert "Test adding new note." in pageGrievanceDetailsPage.getNoteRows()[0].text + + def test_grievance_tickets_activity_log( + self, + pageGrievanceTickets: GrievanceTickets, + pageGrievanceDetailsPage: GrievanceDetailsPage, + household_without_disabilities: Household, + add_grievance_tickets: GrievanceTicket, + ) -> None: + pageGrievanceTickets.getNavGrievance().click() + assert "Grievance Tickets" in pageGrievanceTickets.getGrievanceTitle().text + pageGrievanceTickets.getTicketListRow()[0].click() + pageGrievanceDetailsPage.getButtonAssignToMe().click() + pageGrievanceDetailsPage.getButtonSetInProgress().click() + pageGrievanceDetailsPage.driver.refresh() + pageGrievanceDetailsPage.getExpandCollapseButton().click() + assert "Assigned" in pageGrievanceDetailsPage.getLogRow()[0].text + assert "In Progress" in pageGrievanceDetailsPage.getLogRow()[0].text + + def test_grievance_tickets_go_to_admin_panel_button( + self, + pageGrievanceTickets: GrievanceTickets, + pageGrievanceNewTicket: NewTicket, + pageGrievanceDetailsPage: GrievanceDetailsPage, + household_without_disabilities: Household, + add_grievance_tickets: GrievanceTicket, + pageAdminPanel: AdminPanel, + ) -> None: + pageGrievanceTickets.getNavGrievance().click() + assert "Grievance Tickets" in pageGrievanceTickets.getGrievanceTitle().text + pageGrievanceTickets.getTicketListRow()[0].click() + pageGrievanceDetailsPage.getButtonAdmin().click() + assert "grievance_ticket_1" in pageAdminPanel.getUnicefID().text + assert GrievanceTicket.objects.first().unicef_id in pageAdminPanel.getUnicefID().text def test_grievance_tickets_needs_adjudication( self, @@ -443,7 +1189,7 @@ def test_grievance_tickets_needs_adjudication( pageGrievanceDetailsPage.getButtonConfirm().click() pageGrievanceDetailsPage.disappearButtonConfirm() - pageGrievanceDetailsPage.selectGlobalProgramFilter("Test Program").click() + pageGrievanceDetailsPage.selectGlobalProgramFilter("Test Program") pageGrievanceDetailsPage.getNavProgrammePopulation().click() pageIndividuals.getNavIndividuals().click() pageIndividuals.getIndividualTableRow() @@ -457,3 +1203,17 @@ def test_grievance_tickets_needs_adjudication( if duplicated_individual_unicef_id in individual_row.text: for icon in individual_row.find_elements(By.TAG_NAME, "svg"): assert "Confirmed Duplicate" in icon.get_attribute("aria-label") + + @pytest.mark.xfail(reason="Unskip after fix bug: 209087") + def test_grievance_tickets_create_new_error( + self, + pageGrievanceTickets: GrievanceTickets, + pageGrievanceNewTicket: NewTicket, + pageGrievanceDetailsPage: GrievanceDetailsPage, + ) -> None: + pageGrievanceTickets.getNavGrievance().click() + assert "Grievance Tickets" in pageGrievanceTickets.getGrievanceTitle().text + pageGrievanceTickets.getButtonNewTicket().click() + pageGrievanceNewTicket.getButtonNext().click() + with pytest.raises(Exception): + pageGrievanceNewTicket.getHouseholdTab() diff --git a/backend/selenium_tests/helpers/document_example.png b/backend/selenium_tests/helpers/document_example.png new file mode 100644 index 0000000000000000000000000000000000000000..e8ccab06075c6d00302da0f407245c2509858fa1 GIT binary patch literal 91392 zcmb6Bby$?m`v(kT5F%0%(%mWDs4QL5y-0U=D@d0JEYcDSu5?S6nUfjQ1 z@9+Ek^*nPdaO_-jO`LPinV%v2t&;RJbYgTQB&27uG9XnXB-HmvNRI{39s)HMWtw=v zp9e0g(r=K82S~SoFOOW_$f~2Eq0KHS|3X5dM3M!)R`*QXo!@dH*+}O=7*%PC8v) zWH9k24)4Ox@HO@oCQsc#WZtW=$E^l+h>bdcjDK z#267s`7vkMGi|Zme2q&9s_M)dxZXAbH-!wN-i=j2k#8|^(mMEpa%DRE|Kb|EJR1KfDX zYO;j4*G;OC{W52gLfg5HeuIE!c#4~phLkK+>^(tPZHk+dt^wnXWwc;`@!i-bG}7H~ zNaqo9Fp*6vB->ay*jxb;=A<~OkdNohqx)s-q9&@UsOZ1_?64wWA6ft6OehDe1y<+e ztr6hk4t+sfy)%S51i~{*q*7uNjVWc0JLI(!5E38=o>}@d-5yDB-`5Yzd&>T*tqR%{ z#P4!i>p-SMu7=fi#BEJ$vo>wee9S?=Q+()IoVCw(Kk)D}B2)sEknMhWkxXp}jGc{{ zgLz5^kw)7wRAtt|8d=j+sv&KJ!gJr^pOOpx1Xm&}sCn_sXZ)S)>kM$d-Ohg3%@ZRQ z&PT+58vU{~CNg&zwFvL96qr`;T4s4)iVU#KpT{wIo!iHv%9(UJ^d=^5m;RL)(is24 zvozmwTQpTa)nx8%B;rk-m-q<~$JjozRLSMc2dl4pno1c^e<9Q3JA4p>t2eEMos33v z!AVR}fD=X)LZ>tU*Km?5R(@Q*%>L*gDRTCk({qzmaB?dfKeAwvE?Vjex!PO-wz1w_ zO5iJn1PyjL8+k3$K(|5z2h0A*(Sw+eMOgpr#{FEu*mA)QS$nHnpA%N4sOh`v&&c!oKMlM}shWR&%-I<>Zy6 zJGkP+*Y-q~{A|}7cRO($1C0#0P9#irO#Ge?7K@HQ`_Sr=5nB5)RG|+qQd)a0o9jXz z9xQ3bE=s0tb2-Ga?{n5lWkCHtLii5_KdGx}_BmaFuivly=>6Qb5Ep*4kA_)7^WxQD z!SVi=!2y=ZfxB3zh-MuQEE{Ims4c+`^@ir}F73zupon?zrYq>&oM_Xy?VAXSEr=Bfb$yF*V z%dzjv*B0ueIRcA(LcEc(L#1&!1g}udQj{y2D^%9yF`}*E^UkA-9Jf}A>%z1wzbls2 z)3ky43YHUKv*biI?_?JqhpesXXxt7G+*Q(FN0&z!5?&kY*yC_(PRnrW*O!^;8z2G( zuBEDrMY7`6{c{cX1O7hy5t&SiN23=UAb>mlpw*Ff&l#@RP?w4b4 zf3>}+A3b%tlnK0AX^?Gq@e?Lf#p~uRKJP|w!MOPpC2@4!{8n>SvlOJ~$9P<82m?&A;l>39DFwEdzgiRq&Z~_14x)p8FMd^c$WL8uBqSY%c1YMAC#yjlL=a@B1pG zkg*a`7lVu#4=>dZlh!eNn^$Y)?^pip_`>Q>bn7ja07bA)jrJ0P5sQa(nY^qZXj z5>@>Th^lirO5`VuVkeb+C-w754q;*~S;+dyzRyVD<1!Bgi}pgH>9CjkK5#EKmh};a zhlPwE1*#15>W#$2VN9~?{Xmlwnn=N;HBr$d>1ggZ7;%zSmypnbqt3cE-`S+egP+UF zeup;j>In@kOWy6cBUo0M0$uV#-C^(U^_#_^P4bxUxty6Bm$e;GQQPOQMn<%?R6k|9 zk4O7pHHzJV-CVyznZ)^}y;;MI8S%ibtXR>}C)W^r+kX@gl3q=aU_ER7fmsX|S-NJ? zs69>BVk9}QP>~R}f*O?I7aGbCI@S5b(}400%||7y3kH>(S69~;U1Mgd)~uYnNfVI= zv*J|Qf)fqhUo^yqkgh zbbI^rgBoU>8`urU<#31TBRMhi^+xfkyEP8MMKW*PLScM}lN-~+O$>b;XQ}WoS7%j6 zRM8aCjxv3)Jtn;LvO29Nxpvi=s4Lyp-u1_vUl2`}%<42&A0(bEx`PXKh%We&?U`FZ zy*oGgM&@+OFSHj#nQ2~J{Y%jJt_^}cwF;KAo1oUA&MRn*Gnu~&_dtFvX3&zOg|ax# zOORAhQ(g9$^~=S&#Smkcf6nKic%tesup0>$YD|eu?;Jf{jo-P>eI331oz9Oc)kW^= zDxW&8rIltcc;27x!+jD7IyGFlqq?G{L=S0q?{fC>FyzD=Xov_qcBx1uv1X%JqmKJx zQ0j4d8r)WJN1?pW6844@FC!)WV4EVU>+&f_rmS%LdPPZzutCNid3k?I)uj~3zj^V% zFu9&4zNb)=1*cZOSevzao*b$q9aLeZWhPcPcEV(Ju)jk@s&w_!ZYJeaJ4cguOdWr1 znupj8_z2AVwbNj3s8!UCo|9mN8j89&yE-EV+TE>|W(mPP{R4H*d*>tNYPzZk4n8Z_ zwQ=FFdONZm65pxmdGT`HXj-_j_r(Y@HR3uo6*HgZq~iow;?nZxmF@m|8%(<#waQys zy!F|bam`f4PHk2`2M^cta|Vv)BK_oK)aIsoHX-W=JTws{_4|2wgDh|&@4?%xJGIm1 zD>EUho{x1P$`HCT1I;E~m=2W2o%f5#{(=9vn}?su)7tLlr8?gH-ivOG{D$H>djvB`!*d;$I6O{;s`j}Iah82s3+J=tY;v@?J)KGO8SfZ<^Qr_n zx`Z4&cGY74;hR#e1<$p#4$EB<@@FCK!F;pH6Xr1{HL6wZD<>CNvDk6o{Gx*IrMi8Y z;6{2seCHgEV6R%j!OW+RC2rbtj zIz~Z(Zq$PMJ1ZMi_DFVMXu83Xn_+YjBb!pcBa3{*r0u7rx?ajXCf%IMZ@;Qs^5@7M zJnlZJQkB41Ys{0y@3v7gr$!QM6I&1ZRGxmtf78Il)71K1+wa}xhpmsNIjZoH>Z8^?C^`Fp|Z1)TJUI_t^L;5xLHS+^HHXc!d9GkI3a(*?Xa@uB%#D4 z=OBjh=kJ|j;T|MiN_UVN7&jRwY-@y{mOmaR^Ia{w!HQIJa?y@R z&kZtNU|SWhorZFzQ6(>{&60e)?Qd;uB9#iBJeB~d8ycu+Xv%>&(rxLets-8wbGm7) zbp&6M!l%jLS&lzA2O-zC(PqXRvke_(^ zIT(6sz&<@67ItXh<8B@r9?h&>GfPShbJb;wTPb?Fu|b$rNgO?HT<2BNPk}}-B(gP>3mfBEObiD* zouU1P#X3QvoEbPKG0c2}&?i*Nngt&D^!NqDiM77&D%9jCy-)KW#`jzRWe>LwJLxG^ zv&{+#2q}T864xSQ);j2d0w7EMf9`AM9E(g7B;YA6|XmilURD?s&QY1 zwk<6kF>N{V5Q|UGeaTFv+50j4eUz=sxNK^}a51dY7-VYb-sFC+E}>{A%#G_V>8{FZWn6AZuljzZ|SU<0ofpC-P zVeP`kdj2!crzb8Jgal7dYyl$K;O@!rq?qyPC`cuB$%9DY5PU+Z?BN7B@2Fei>zS-# zr|-2d@z4Wq`-?!-uR%?(enI1l=p-*q;<@!|xoWt6rwdv?sFXtM`FXmyY$3~YSU~q$ zG9>&ZI>a+WRSwyyfZN2y7(N5aQ%;V#@_7+Z^Cn7~sDmG(rN-jW{rI-+ zwLy>Sr*UCmSXh><8x9WL3;QZcUYx;hu^ul56Yc_#@xwIC2dBh6r-I6gh@3BCEcg+& z1yGB3Q%kO0UQL{X^f&DyTrc-reFmzlMYgxyd>ThWtu)L!63ysxo{z%WwICh(8oIdQ z#kVQ!Osrp$^(+ted5NC6CE|+(Pzq+&8as0Ezb~ZSnqPz*;pOeFc-4jyR#_vN$PWL4%<1hu$rC@ zSWO4wK0dVh(ba&AK=RV7D|K6nK9BU#K%npJ*2Mbk#jJ=}fQw&IhwV1d<4VuY;X=O; z7O*F0Rw_k!;UJ*z=9 zCLnbUO;sID5kA2^$GRc{7fBr_R%po>z~Q|BkNcp6_%sb!V^$3)U+mGZ1Rro|4S7q(4)At@7>5~E# zIRRq7IsU0Pw8%pk68FbKKBmhr2r3!Ol%<__N=FcR^hAtoNV$uxbBI|oaFTlEBeRFK2U~^?e~)dhy1IGE`drYf#WXa% zS{&*wt4pZ?3zv*q?pJw$vI{DFd*#suAwIuRyh`H?4jW!%Av0;^cNnvR|*=J?CUqGtd+g z<0Xqd!G}t6yF`wBhnnTf!MX=@{m$q}sIt>+<&QNEr#1#_*4A|^K0i3^yIww=dW^#+ zXdpPjTC1=yG0viq!?*3G8EK}hygJMv*|){vlRJ1Xu~FagMhx5V0;)u^E_vZ{lm(EO ztSn_~raP6}mGy;$kta12m6_sx%RWgQKN=Upx2ChodN%oNNP*`wz0Pqz&(7G?w5-O7 zsgF^$7^WhqktL{#==*PwbRcDBdQWX@m1-CW7U8GKVF|~NWL|Ogy=c zb*`}S1x+*L%sTbx_cju!tYw)tsUo7BU$!Qrp9ggG7&^PRc`npBB^?4J@<=N~w zlG$Zr0V4ME0SUfoGPK4{V5%esvo6~FE0r1ZIpbBE!Zh>SrAL!<{@p3L7iTvTsi9GXE zQ!2HJ6Ttm3vy#!GteJ=|=_YAqU#$UEcivUHCED5CFJR^a3YB6YfQ-zw*j>LSOpjF(e^i5EIx_C_gFLyq^X>R0G=4WnIvC+cLE2 zIkV#!{Z3$RdQ=${$QaVTG*)6LI$omx8WfknUp}&NB3G-=ltdRW*Y(Lz?-LKco*_1Q=PN4lW@?)nX%H{yo7 zofD)i={#b$2#TW~{x-Vz1lmU|ThEM#{ydJyOjtI5{IP^`|EI)rjRN&9k{!Z5Oov`A z{|h-Df_QkQ+h)zY*BY#CsXIlNfJD?OGr)ygLRcu8o;aEgH++}0AS!;EOo>{xKz)P< z9AY-nr>?(Pl3P^a(uO*(dQ*)CxA4wZtgjqITgrVIG*|LnCL>n;C0hLo}*5IVYc ze#tB!>82aaI5IV+py*{~y+Tx6V3(d6PiyAXdv8J0p-egW#)Sy!(vZ070RlfTv_O) zcI1PRIqb|hrl6*qQK$Ej*I$e zDwJ#THAMDT5ckBcjSJ*JGs)oe#z!Nh<0IL=&^Kl4;+j2v6-7F{iDp42E8si@Z@$Te}n;-=6!WzR!_~=Cd@U|npf6U!>m5YAx?DrT=*Q?hqVq3ZU*CN# zwgy#rFTOFOPN6WUU0kEvh#qquzbiTPV>=Kcq&VEv5v^eX=qM_S@=DHoT*9y+~3!>qU6{ZcEd zr-idCiQ6c)9`@x*1%d&HkEhD@;@G?7FU-SUzHqHX9C1}JkhOw|NXf)=0bAz*Ahh+Z#~rpr)XOuWx%uWoZ~!H7qDjZhQ$Y?Qt(q7y(KLSdSvdi1VaD9#+V{t+eZHKeV)M?0 zRj{VK?9=Nl&79Zb+|85`%fB`~V|aJQD;R8;;RDrtS_ydlvq|ncN7Z{yHE*(hFOZb* z^N(fNTL}dqOeyiUNyruJk8+B^)a&XlYBN>!WvM;gs3S1lqVKFMO1C{+B>djXTs6z70t%!#U5-%5%}lRcR)4Hh}thiWGX;x>&eJlkY9A0 zl>^pt*f-9>JWXclPKMB~{9N(aD-=nULRK~~cT^dSD1<5qoa8ldtf>^dF1z03{|)52 z1m$LXRyBe$!b6`fb#fl8Mf{ybH+5UNhV%q+4Fm)#(^*{jgBH6Z#U7=EG2_a|e*Ba= zj*Amc9o&%1E1z7^QkuEi;4*UcoA)B!)M{N)@|i{FY-XZotfmTHYy~M7UKIobiLcV<>;|EIC!P?^7X4dof%* zXFS5d?Q42S0vq*z*R4yB$VhD0$VTZn8& z+_KE7ppJRGW)V3lxqN!+*0Eb;Mle3Qmy5)F%b{h|M&z+=Afrq>rZhE3UH_e) zyO-~63%l?`O6u3xfO|t?UGP{%-&p{NI7le20g;xw!*BXj4p!etUJylhEK4GLS(QkX z3^wavZOJ+q#g0$@GtnYQHu0wzGrN;b1DR#9V^I{w9wT4F zXxd%ecN_5Z?)Bi6eETRdRr%L$gSTs)svE+QW7scBfATk5tw;8Kr&ghj5v0EN1lMPc zkAA11S7$Qzv5}6fnQMl zF4`+DG2ZqQUr8NVGb$)dKBzsqTIbDll-q&NFs|PA`<{=$s76y`A>?G5#^Hc5P&o}vmG6Bmf zS&xL2%)zeEP}{jubl=Knj)pBQCEc+3O6mqPMk)99_t~D$NYFI$6(YJszdpUX)y9K@ zmO^}=L}4O)!1u_u(miJ_)7Q(c|1*%K0X~%^4mZ7XmWRTf*V{kWNkj)98~Z0+csx%@ ztlbwJUc({~uNjzFFmm-LYA_k7pOK-$eu&@t8MN9d( z+M$%2aG=uOhB{J#zCyPMwCY@IJj;=XgBDUOzB#oIzWQJ?9l0@R&6`qu`#N+qSq6(l(=I@OZV7-hv1-!@3RUDcNZ(V0Dl0;E#>XS|6*(eA$1Day3R&J`6$+yB51WP-eg zhIt(=9b`_f9e>QtqJQ>nOMU!)?-@!iW4|UK0V=e zOGtxlhf}6@W;*gF=?d&mA0Pg^SQBr1u{dl1gCpJgd)L>=f1#o_U4fb^YA_e(gev$2_o2TXwdJy(>hK1ys=VWi<=cQScNFFeaPBl|^V6PF z@?;>eF!ttW%T;v4V9QnQvyc=yN_xuCOH%|=!4drQ;Y(twLgmM24T~*TYt+qFPfPJR zWS(HYw?Lv3_$3*FOL{LG4%5CB046w-Hwsy_Qa?tNqG(XeuTMhg++UxCBhgh?Lgm8p z6r{ox0GNd}g+SiDib69O~qqU>A|Iy5iv{QnANYV7-)w_o+?BUtn zoPDojAHE~`*?9Gs^#2d`MD-fGKD4vp!#g#%4*T0ox(MToq0Q3Kymf+fdk0CU z*I8_#W;I%budzQ)cR@a9m-(A9QhP5y5z@jLKqCHC zOAveV;jK)OdO2&P?&Cv|=aMSeA02s!V-Kc79ghSk&`>m(>AIYM+%9?1M;O160a2@{ zx89TPy#y6$_&G-G1x9RYG~$}Hpyz86+|)T)fjtRFMP#7MOKjl8zQn<(XY2eD%e%qx z2LRjiTdqg3pOxj--JHLOYpDN|QcOKm10$(y= zVT=Cdg54KDqf=EA%hjQOhQ9wL{2oC3xpZg#?a3YK0naOXh2F)#O-kABM|-LKdEIos zkMk#20sL>{cRQvuyc9oIjzH8@Xn~Yy*^K0;pO7#!7$X$WsS#Pzg>M->Gm^lp04C6o z)pNXMsqNQue5Ch;C=1a0?prDnFh;c@YXVC2@g2r5lY2~;y)rf~P4hx7V25qSFX+8) z7$k%sX3(T)eRc}~{r6*4nim%Eh?L;|^ngh{ll}o&4u&M~(cYQsK+JTK6*^BE6ne+B zAqMdF(N^2QnWU89i>6CU4 zYa~6Te1Msu9w31y3B>8CLyX#%N)_nkZ@!t|fCt^bAPIjY0w~CV*&u#TiT5`UJ_{M8 zKzi!r1Q;}L&@&VLm2$5Wt$0lw&@&4%_aC4S5|S#EVENPc8Qi$26#=IAQoMTa)px`j z&~As=BLG@$+ewm)u6FumY`Yx%&d;|EA@<(AWQ zUy0TdJot+OeD(e8s61*yF+!>krF31g?AB>J*0ZN&DG-bJf61mg)Azbs%vFl-!6#760v`@f8e^xh9Rr2jQNNB&Q=At6l> z{=a`Q*tW}Oq27Z78?8|1+vLt*hG_kk?tdK%9J`A>^7d;|0N^44EH;0680lBH#1qW_ z+T9PCDXdg;wdjAd)IP9(R-jLQ(0qev_lXs{a6>X&_;KUVeESyrqu>7=%1WbI8t+Y7DL~d$rEZHkz-OErh2ofTXFg3SPJjOns`nC_g0? zaDIG*MgC6UeY+J0uv zg&_WD`LY6Vse#0)c!@q)YKqt<&VNfIjk-yB;I}_l+i3R|uVWT##!a_hdC^agJ{~8o z$MT2!#_i@$bps7aTH;2xW;)OZ(qsLSwDH&-eURC9(*gFtsjOVVZ9X<@f}lN3!C^EK+sYX`Td zR(@_Bf%8UO+c{-F=F6@J#qVHm*lUC#cZXYvE75KxX@bc@N<~uh+G>)ZBE@HSD}$K^ z^)Ahor2u%cgRUz*lI)D35%yK8Zsp zt=PeS(Z#C~a3cd5_xD`>g0PV|@+8zA&1{`N4$J~P#ru3+@5u7xKsp!kX*Q$(`S@8` zPqJFSB0z@r=kbQ~2=`HxdIT!qNh|a51Kk1$p^-Ld*T+cLKt{}-f=^r_Xq|9z9k0hkh7yW(xh zWTvQ(yTvmU2_xzeD=ikWf`s06gf4YT4F}s4*@jYen!v;>&=axYiSZp*A1?l>k+&Yz zRmov_8w3zY`Un7?v4y@F;OvvzTYvi{Y?~R*(R|K@gZw5-4{L@#e}h>rB7c3oinxl6 zL0E!NDkvt**FOLpbC%8z<|aBrNiS~rvFWtfvYXvlRz0(V^>g`XIj~_QyX|_8w4#Sm zQI9?wU>pnG)No5{doe)~SrfbQ+UIN_yyy-gA0RO4AV93>gL0~?IO7j>|J=pP-Tdq5 zGG492@AefJ>VG>tRhEuAB$Uo>_mfpryE!84+W#)5{<&3q9X6buA7{<1#I)6`nshrG zNT7`y4-9?1?oqG~)+dj(V5Uu1Xmlf11S<`!?&{ML8|dqbX#%=OMGdVWNq{%10j(g|aIy=drlG1sk12@7m}>nzJk zK$4kJ^(Bh53kOHECs6V=EwxhsI*}Fxf3fcFTVuU4UPnavvmFN+5ikYL)|n7+TDHb* z0N8-|RjUWD^?1P5&E03dcRK4`2MvNkFL_^b&o0bgd@HyJbud(AtVZODgg19Y541?# z#DjvPkYHuClhZrjOH3ZWk63^gCOhH%k(Ro6=@?(1lj|4wor_ zG$VD`lb)-HG;#R}3|OSBrzLo)x|r!a!4cgjd0U`ySzAj~9o4bYC_w{&_!$SQH?@^$ zKO(l};$IJPDsYiz8k8=2O@ZIR{jScm#C{)LEQ^Tq@HK6r#iLRuu5)gmk)(f!Mi{i4 z^3dw#f^l3x9F4cFmYMO3FH?&yba@4~z7cHgJmfu)eRJzvy zfSFyaPi|CIPJT%XB-Nx}4lPi(Xq$p))S3q~-^SlbHtUnaOq^y>Ovf^rsGsUYLx@sN z54;~-D$E@5blO!dBq+=A?$DC*1u-cZ)?R|~gQVq|Z66H)tO10x73TNCSdZ)u@*KD)8v97JqiApvf zNQojrh4-J@d@ovdLNw$9Uh`6O)4*S>{T)Gxb}>tV6{7?s?`{CR5HzqGkRDY4?rdpg z|9pP|z^)yDy`#efAdT1C^3KV-v*!caBATjSniIh>{Nt$@2-NiX+gF@>C;(&whyth3 zXV6n8bzK3DY`Whgsi51g1AU09Z9#|c5_Hr*;MaMOG>~S_0dJgNhA}e0kLTq}kOQ*@ zn`>Wka=EYnE(=Ndt|;wUSZwa>z7NPRAZXpMWe6e26TKHkDD9m0Q||c|Ff_e(?771X zuW`=)!%;{`Tqgc!1%`XJgC$J}=DlNh5{EH6Zi4b;@!<+_~y;U;^3h2N&0)!hgZsXr^>oT=Q^gn@n?i73mKJ~*0pm0C&b2Fnl3u*3|yFb zFCl^bv8kyEgXHx5T&$w?&uT^Ffzv!Xus*tTrl;d0lZa1vJ9)PkfUf)q$yOyG zpqVkkO6y2OoRjk~s_PG5AKLCVJnpJ>*>GF2@HM-?1wz#@#s-rWFsLIL7iIV}9 zOdjkg>i@9@FdtjLdepL>J#49^HQC)AHo0S_!zV0UpOuxBmltC6WtDG!etxpVaBy%i z;O^!^?V{0rPghx4*~R5#r=&SUUqDD`W_*0jQNS}}a?2G8g)%5-=%4h5YqL&_kMr{I zte$zVIoI}^3p*`?UcYWdECK4Oy1F_pF76fOS5;VVZ!g$;i>^wyab9Aodt_uJ2o-C6 zW5X+>Mw>M{I+|;m&);ieY^<`T=HmQ3@7Y%T$gui;^3Wc=Z{)K;WdLqI@)j*jks7(^js)79BY zF7BU($juaTLx*<$OT`#Iz^y8M65Dms!;tAILJl!wBamk$NKOBzeEH(JQ&i*bC^0Zw z1Au&8vEy3b4Q;uS$L1Fl*m!#WW)Q!W)zpMkMlT-Rt;Q>-Ddj8Ba}!6mzH>A-HgIbB#zt6Jn1O-e#{Vuw4y2UtVsEdM^;TU|vl#1ZjX$mk`Jgm~AFWvz zD;#liJ20RW+OA??;CF*?3o)wHWg9$UPukg^uLo$>7W@=eANnRMRt&*NifL1&3sv1Z z^PZiV0mvM19G~e(n&icq#BT7tq(s?{4IAzHC^jGpL=D1=?8pr!5#2gyyvXPefO`*#PSp+Ne;%*@QBVSoWlJtBK*e%^1T zGn$0o9uFU%2qVNaNGx;EmJc}3cYaDrm^jf%!Cy?^=;$rcKlgUnKp*k}_erajxD+FR7Lo~HcF6CBVO|3%g@#2K6WBt5_ zhDJ&AZJkt(tUZ13F7SI>`ayr;^RY6cEBZ*wtR+lu1vzhdP2oeDBYH9 zz{R|WyqsJZBS7b~l7On&y~$#O_zc&7MTo#>cmL;akOUgUZhx=bv83NiKAUP%A|f4K z-4W}m#hZPP_&BJ#$%il@VPT1^q3SxYzCLYsS{muSbK3~ML+q5mO}XmlGh}GPEPZ`_ z{rwlLtH5=xvM_aAoOaIs{{Hdtago7`i;H}|Z*Lj>Pje^$Y#9!)XnaD#=fi*y-p4X} z_l^<|ed`S2vEU`0GmMP`F9%L_zEb)Of{Fn*JTH%)dV6Q*`D3cuw2jNw*4Nb5*4Cdf zMaHX@0yPW#PWt(wIC80g*v5%d%J%`j0@+6%XBbyFCE7^Er(AXB=DV8%d0@~)2t@9+ zmxo6U&#%>Y?1oLO05siocdD#1C=R|RetQ(l!pxl8GmvncE5B92Q_8C>^e>f2WdTnT z3_=NNO&dV;;?mI2fYkN$Sj@i4CMG7*$A@oR0wTrEjg9UzaBQrsm}-a2EAHj`^%_S2 z6Ta^!p&}Seb|T=DOwV!3;g}^)G0et@FEw0-`9T?6;!AQeRrdDwHt+bS8jZl9jmzw8 z>WXPnw8!)e4DoP)xoP6U`pw}317M0a`yJX%*E(LBgotE30)jkt3mr{jF9A}e`KfoD zg-prwLqjc`3XFKpwfe9i9d0!r#R-m=$qF{TUh=g8>kMT7E+>W-xCH8n(|9(a2fK)o z*9C*Y+)s3SDO_A!CT)8-6#y~Qwc;d{KD%94SXfwE>geu%o)arpK5g6aPvJ{tmd8~(VR^Vzd!9{Y1!dwXmg97z!Q zGydG3;hjgD1{sf;#tky}2E1GxF&tZ0e_x;sH@7!eLh1WI!=D5E9hUV+zH_)bWVCwPl!F7KnBsfC!3B_gk zXE*|ZNU!QFx0U~=Y{&bbtSi>cvWfmNvK?u&fD4|c#%0=JjCOJ8_47)~=ENzxN zDGfKjq(~0-WT$|8GV2#tTRpqxR-7>Ww3?2C3m*~kj18-m|KYQHg2+lrBIBH$pUa&X z#haU(&)L@i;V?~}2}>Rjp!O$ChwN*F&$^m#_8jhie7wC?2_X;&7jP9Sn`iPzWo1Gb z;_B+rhnAG1!rIG)1@boU5yf*96qMXU*PNi4?CWdaf^lLDlxDe`wUDPcH>T*Q+SKLOZle*3vE>FJ7grQg3t zgaKM*eSJNbE*z9yRHTR}S*6R?Ut?!!$@v1~$n&}+;HJQp&!GBGjf=^~k)jsK*>Lwo~h{?9PP>T0fQ zMXO0yjO_E-ot>RYEC8+Le{(iT;diQHXlS^84i%aNnE&MDWCZs$HgK5!JwN|)Lz~mw zMbky2_}%5;^XJc-FZ&%>3FJk{lRozXVn{7tiLOSyV4RJOZEtT6=moIs(1WR8V4w%S z;~|cZqr<7HtnBOWS5#2ASKK@=Fs!R)cFv@zaUfTv!azFp#_35ZGU@g(p)1P|#36RDBdVhLam?cI< zPw!s3c#0JNW2SN2*48jkaaT<5o?MDuA4(KyvY-X3a|nxz->vq<_q6)nT`k_h4R1fY zb0%Yb4Aa%s1#Arldir~<#;c6zh$Mvm-Qp+dYRZ4G05P=xm}C{x*R7}&fcY338(Xi> zUaJq*)}ET0(l#*Y=Tz8+a5P_+YZd_1NW|R$6cz+RP(OeBt2JnqkF=}tYQO%`KLt?v zA6~u686E=+(dz2;)m2)4AXiD@d-z&fy6gAvA6+X;0_-d-1$A|GwY6TTUTYIrMvd9o z*_pSe`S)TdHT5Y<5J4ogtn8(*@D*0T?{@dl*C07)Iq*VBNl8`Xu%)9FiGBUDzrfCHxLMrTTJK9>xdUbOU(A?Y{&>!y) z2n|3!i;7_X%M+dpoXv>w5=?Ilq|lb#U0WlgB_Nse??a?tLjY?_KKuJm2mZfBRsSF1 z*el#us1HBfSQ7l{9SI3D&hN?BgT-cX5s?N8wm<#}5ZyN_;{dRH5x^P&7QGr4`0o`` z^zue)M%-{#c6N3S4u&0vsF)bw&Q)MwVB_}RdEfUeiUuTB5BB!l1W0$ZUSd>So&RxX zfM_%4jNOwM5cTlH#JZz^HiV6xT`b`CdJUcg1xyo-%gf7p@}-66kB_$dh1*x+rSoY1 zPT>8SFO)2~U#RSVt;<9KcGa zTiI!Lug%&qP|>dMVKyoyc~yuTUiLQaUMCq|+v-KJE;B=#TM<0C3JdM!cT1r{X|H==>cckBHc1 zrdF~4#&Q8ZU5C&3fAnt2nAM;b5f+vQAtNKB1W~zpSpxva$jHd{_VzfL+`K$<{&}1@ z%^dqe7-*X^i_fXP= zbeL|E5#MZNHV-<@1zcam{$HrCd~A0v`v$l{nLnGOQJ_b>okC)Lx6&SKTqb~XGg~6= zdrYe45;Z;o|@@E2&c=)W_vujWCxN3xp@Wi_iIlG=TcK@H^yOgHP)X~h1D41lNEMp zW4iQgS<2aZ%1znu_~VwFIFQ2qs9pt9RyT3z22x=^rede23lP_keV}}Ca#})?%j$R4 zI?9W0W*3_BB1CpGv7k3b{OvotJzeR^(}wV{Ui7Ckv$`GBaie>;d#IV@h2rn3gxTEd znf1J?Z+~*0^b#~@k1ix8YS`R^N_fFXZcmuwC_>!xQvxdcNEK~U^Njg(az%DVnw(S#q z`xA@*&ewUWUD1O*SE2CkcbvHkiog{)zyzKdD4T#M$k7a2GLq6~pCpwi4W`au@&VUf zpG8G4?lwt)SmEH`$0_|=)>eh^NvjZsyw5R!)h2$qjGoyge&Onqq1lc+T_WNk6<>gh zj_lCzTi<@5QNYvFR}PXKS2Bt+F(HQ7b>_tLy*=##f=c_htyyJHsO4rN+sk(u2ul?D ziSsg+dQ`W1udOBsq+T6)SR+{C*_DmSD z$^@%hsn!K#?1S~0`iH0!N~B`61mVRC+xq8M&C_DL)?(>j3|e)Q zJ#V@SKBe?S`77OjB3*Y4oB3de3BrK?Q zeLzOOQGEiyBJaEJ1`e)j-XrIgH>D8 zckdWXg#7l;-<_LAgdo`UKk+rkElS46xNxQ|$ZhBE10UtyigV+VE#bC~35R5pz2pwP(*h(-3xdAJ$`Q!vT$Jw4Fp-!5jso6c(mWp+?1_ z!SzO1W#hBmk;LE4XKEXf&HByS%F4TCcKiDTfM0NA&CE9J0ve}D8|+I;6I#BoGjVO~ z&|%9;6@B$qR6KL?SIw2pe8bt6MF;Sp_OB0u8W*NcK0R$+Jd0`{SdHa9=33vCjyCqn=BUlu zz=qJ)EFeGA{hhbK^iSZF>q&q@rpKrAC~|WXQfT<-J3_=h*2-+w$T+^16$mE=C%u7j zA3#dLJ#QG|KOtQ_>-@T5Mj1a=*z~6fumqNz729i3X{PqiWyF}-N2ONGXp)e|!!))7 zrKA{hk2E=4zjiIAs=W3c@5#5+;$?cl5Go4kZuxlxt&+-Y|0*BGvm9w1StWL3x!|%om?qT^;WY<^U8j+?j`ymS2FW5Yj=2 zzaRlp3eJ`fGV*5<@_cg5Y<)fJAx1bx(XE(H{c5z}?tQwm11v#d{ zSWGRH3l4~nKWBe1zFx|$EGxG+!<|q~^0nG-gAU*CpD$jN&ZNoD z$vxQ5{;fBytBA}wjakw%`|lT>x?6L$m&!k$o(Fm~fIRpttqm8@($az+0_^qOh6)KX zg@uJ%U~-ig>o;z8Ra77BiNvet50UU6cSi?SeSY>8S+I9-N>mBYH~NgBd-Kl))+J@S}L^&gj0J-I4rcJdki2aM}CY2b2~dT0-58_ ztF_EBvL}LH{5?gKDo;DQcLACs0w5YdH-Bsl7Ay5EK<)N%H+R3?k^W&U_}XAnVzTy; zat@VvHU9fXm&~;R9Aa)(${N1+6#tesq`s}XBdwv&#l*zW(9sW#5r0)vpv+aWgWtU` z1z;+DpTD~W3JUi68GOFIy`6;Y!M}$m{XV-B(2I3-b_zp+M9F8YN(nx- z0?fJlJ~u4^+WE|2_3lX+(igpV4aYwpKX=?4nv?;b&lx!I*mkN^QBnDygKl--%>Gs( zXz1O`L1nC}8kOkE?RJrCUO}>x+xHqChe|tW^-SmDrP=nTzX_Oe+a=7m#vHMWr z_=Cv`2(Rw&zsonuB+cl(Zx0%F23SVP6a(bC6_f;_;u@NlKhyd*&UTeFanS6K=CpoL%`7>~8f_2JY8)|4wfUfIov~PVhB=QN!t(ejf zZ%8Ckw_m`=FL>x%Jb8KaQZI7YScztClJnH&f3GL8fj3b+yt>NBzLb^bO~4Bi3;2%t z#f-C@Ps6;57(wmZ0?o}e1Q#(*UI{Rgugg5BXBso}NAxokH@~%}%GU*zBIf7M{?c7& zo^|yd_xAfkmvLc=3K4|@W@kVo8QnW?QN~)D0qRX$6|3^z{nNlsG7}>+qpYWi{BCZ; zpsebRd_Ycr8b9gui~s|>yUT5VfQXSVJhSa$ak4^QUc>32&k`c0>hpi9dygJe=!!^7 zcdd^W`-oEEl5*J?<|0cMdy>V$j;uz$cy_!Cs+jcDxUZt*V*@6GIRGsKZBCf8^TnCt z23w#El>$xZ!-o$!IXPFGm)F(`hv5J>{)(I*1H@84ApZ;u3;_1VGwn@uuOvU;3Sj7< z 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

k)J{p_FtxdMYAZ1-k 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 literal 0 HcmV?d00001 diff --git a/backend/selenium_tests/helpers/helper.py b/backend/selenium_tests/helpers/helper.py index 7ec2f5ade9..e8dd9186ca 100644 --- a/backend/selenium_tests/helpers/helper.py +++ b/backend/selenium_tests/helpers/helper.py @@ -77,16 +77,19 @@ def element_clickable( ) -> bool: return self._wait(timeout).until(EC.element_to_be_clickable((element_type, locator))) - def select_listbox_element( - self, name: str, listbox: str = 'ul[role="listbox"]', tag_name: str = "li" - ) -> WebElement: + def select_listbox_element(self, name: str, listbox: str = 'ul[role="listbox"]', tag_name: str = "li") -> None: + sleep(2) select_element = self.wait_for(listbox) items = select_element.find_elements("tag name", tag_name) for item in items: + sleep(0.3) if name in item.text: self._wait().until(EC.element_to_be_clickable((By.XPATH, f"//*[contains(text(), '{name}')]"))) - return item - raise AssertionError(f"Element: {name} is not in the list.") + item.click() + self.wait_for_disappear('ul[role="listbox"]') + break + else: + raise AssertionError(f"Element: {name} is not in the list: {[item.text for item in items]}") def check_page_after_click(self, button: WebElement, url_fragment: str) -> None: programme_creation_url = self.driver.current_url @@ -104,8 +107,8 @@ def upload_file( def select_option_by_name(self, optionName: str) -> None: selectOption = f'li[data-cy="select-option-{optionName}"]' - self.wait_for(selectOption).click() try: + self.wait_for(selectOption).click() self.wait_for_disappear(selectOption) except BaseException: sleep(1) diff --git a/backend/selenium_tests/managerial_console/test_managerial_console.py b/backend/selenium_tests/managerial_console/test_managerial_console.py index e5fd871fd4..d3616db97f 100644 --- a/backend/selenium_tests/managerial_console/test_managerial_console.py +++ b/backend/selenium_tests/managerial_console/test_managerial_console.py @@ -168,7 +168,7 @@ def test_managerial_console_happy_path( pageManagerialConsole.getNavManagerialConsole().click() # Approve Payment Plan pageManagerialConsole.getProgramSelectApproval().click() - pageManagerialConsole.select_listbox_element("Test Programm").click() + pageManagerialConsole.select_listbox_element("Test Programm") pageManagerialConsole.getColumnField() pageManagerialConsole.getSelectApproval().click() @@ -177,7 +177,7 @@ def test_managerial_console_happy_path( pageManagerialConsole.getButtonSave().click() # Authorize Payment Plan pageManagerialConsole.getProgramSelectAuthorization().click() - pageManagerialConsole.select_listbox_element("Test Programm").click() + pageManagerialConsole.select_listbox_element("Test Programm") pageManagerialConsole.getColumnFieldAuthorization() pageManagerialConsole.getSelectAllAuthorization().click() pageManagerialConsole.getAuthorizeButton().click() diff --git a/backend/selenium_tests/page_object/admin_panel/admin_panel.py b/backend/selenium_tests/page_object/admin_panel/admin_panel.py index 2763752f72..5971c90d6d 100644 --- a/backend/selenium_tests/page_object/admin_panel/admin_panel.py +++ b/backend/selenium_tests/page_object/admin_panel/admin_panel.py @@ -12,6 +12,10 @@ class AdminPanel(BaseComponents): buttonLogout = '//*[@id="user-tools"]/a[3]' loggedOut = '//*[@id="content"]' errorNote = '//*[@class="errornote"]' + unicefID = '//*[@id="content"]/h2' + + def getUnicefID(self) -> WebElement: + return self.wait_for(self.unicefID, By.XPATH) def getErrorLogin(self) -> WebElement: return self.wait_for(self.errorNote, By.XPATH) diff --git a/backend/selenium_tests/page_object/base_components.py b/backend/selenium_tests/page_object/base_components.py index 1c40bbdfd9..de742d0bac 100644 --- a/backend/selenium_tests/page_object/base_components.py +++ b/backend/selenium_tests/page_object/base_components.py @@ -152,7 +152,7 @@ def getNavResourcesReleaseNote(self) -> WebElement: def getDrawerItems(self) -> WebElement: return self.wait_for(self.drawerItems) - def selectGlobalProgramFilter(self, name: str) -> WebElement: + def selectGlobalProgramFilter(self, name: str) -> None: # TODO: remove this one after fix bug with cache self.getMenuUserProfile().click() self.getMenuItemClearCache().click() @@ -163,7 +163,7 @@ def selectGlobalProgramFilter(self, name: str) -> WebElement: self.getGlobalProgramFilterSearchButton().click() self.wait_for_text_disappear("All Programmes", '[data-cy="select-option-name"]') - return self.select_listbox_element(name) + self.select_listbox_element(name) def getDrawerInactiveSubheader(self, timeout: int = Common.DEFAULT_TIMEOUT) -> WebElement: return self.wait_for(self.drawerInactiveSubheader, timeout=timeout) diff --git a/backend/selenium_tests/page_object/grievance/details_grievance_page.py b/backend/selenium_tests/page_object/grievance/details_grievance_page.py index f14e8ad6c3..3ccab0cbd5 100644 --- a/backend/selenium_tests/page_object/grievance/details_grievance_page.py +++ b/backend/selenium_tests/page_object/grievance/details_grievance_page.py @@ -14,6 +14,8 @@ class GrievanceDetailsPage(BaseComponents): buttonCloseTicket = 'button[data-cy="button-close-ticket"]' buttonConfirm = 'button[data-cy="button-confirm"]' buttonAssignToMe = 'button[data-cy="button-assign-to-me"]' + buttonSendForApproval = 'button[data-cy="button-send-for-approval"]' + buttonApproval = 'button[data-cy="button-approve"]' ticketStatus = 'div[data-cy="label-Status"]' ticketPriority = 'div[data-cy="label-Priority"]' ticketUrgency = 'div[data-cy="label-Urgency"]' @@ -34,6 +36,7 @@ class GrievanceDetailsPage(BaseComponents): languagesSpoken = 'div[data-cy="label-Languages Spoken"]' documentation = 'div[data-cy="label-Documentation"]' ticketDescription = 'div[data-cy="label-Description"]' + labelCreatedBy = 'div[data-cy="label-Created By"]' comments = 'div[data-cy="label-Comments"]' createLinkedTicket = 'button[data-cy="button-create-linked-ticket"]' markDuplicate = 'button[data-cy="button-mark-duplicate"]' @@ -50,6 +53,11 @@ class GrievanceDetailsPage(BaseComponents): cellVillage = 'th[data-cy="table-cell-village"]' newNoteField = 'textarea[data-cy="input-newNote"]' buttonNewNote = 'button[data-cy="button-add-note"]' + labelLanguagesSpoken = 'div[data-cy="label-Languages Spoken"]' + labelDocumentation = 'div[data-cy="label-Documentation"]' + labelDescription = 'div[data-cy="label-Description"]' + noteRow = '[data-cy="note-row"]' + noteName = '[data-cy="note-name"]' labelGENDER = 'div[data-cy="label-GENDER"]' labelRole = 'div[data-cy="label-role"]' labelPhoneNo = 'div[data-cy="label-phone no"]' @@ -77,6 +85,9 @@ class GrievanceDetailsPage(BaseComponents): labelTickets = 'div[data-cy="label-Tickets"]' checkbox = 'tr[role="checkbox"]' labelPartner = 'div[data-cy="label-Partner"]' + labelAdministrativeLevel2 = 'div[data-cy="label-Administrative Level 2"]' + checkboxHouseholdData = 'span[data-cy="checkbox-household-data"]' + checkboxIndividualData = 'span[data-cy="checkbox-requested-data-change"]' approveBoxNeedsAdjudicationTitle = 'h6[data-cy="approve-box-needs-adjudication-title"]' buttonCreateLinkedTicket = 'button[data-cy="button-create-linked-ticket"]' buttonMarkDistinct = 'button[data-cy="button-mark-distinct"]' @@ -133,6 +144,11 @@ class GrievanceDetailsPage(BaseComponents): headingCellChange_from = 'div[data-cy="heading-cell-change_from"]' headingCellChange_to = 'div[data-cy="heading-cell-change_to"]' pagination = 'div[data-cy="pagination"]' + buttonAdmin = 'div[data-cy="button-admin"]' + logRow = 'div[data-cy="log-row"]' + paymentRecord = 'span[data-cy="payment-record"]' + labelGender = 'div[data-cy="label-GENDER"]' + # Texts textTitle = "Ticket ID: " textStatusNew = "New" @@ -151,10 +167,19 @@ class GrievanceDetailsPage(BaseComponents): possibleDuplicateGoldenRow = 'tr[data-cy="possible-duplicate-golden-row"]' peopleIcon = 'svg[data-cy="people-icon"]' personIcon = 'svg[data-cy="person-icon"]' + buttonRotateImage = 'button[data-cy="button-rotate-image"]' + buttonCancel = 'button[data-cy="button-cancel"]' + linkShowPhoto = 'a[data-cy="link-show-photo"]' + + def getLabelGender(self) -> WebElement: + return self.wait_for(self.labelGender) def getPersonIcon(self) -> WebElement: return self.wait_for(self.personIcon) + def getLabelAdministrativeLevel2(self) -> WebElement: + return self.wait_for(self.labelAdministrativeLevel2) + def getPeopleIcon(self) -> WebElement: return self.wait_for(self.peopleIcon) @@ -184,6 +209,12 @@ def getButtonCloseTicket(self) -> WebElement: def getButtonAssignToMe(self) -> WebElement: return self.wait_for(self.buttonAssignToMe) + def getButtonSendForApproval(self) -> WebElement: + return self.wait_for(self.buttonSendForApproval) + + def getButtonApproval(self) -> WebElement: + return self.wait_for(self.buttonApproval) + def getButtonSetInProgress(self) -> WebElement: return self.wait_for(self.buttonSetInProgress) @@ -247,6 +278,9 @@ def getLastModifiedDate(self) -> WebElement: def getAdministrativeLevel(self) -> WebElement: return self.wait_for(self.administrativeLevel) + def getLabelLastModifiedDate(self) -> WebElement: + return self.wait_for(self.lastModifiedDate) + def getAreaVillage(self) -> WebElement: return self.wait_for(self.areaVillage) @@ -259,6 +293,12 @@ def getDocumentation(self) -> WebElement: def getTicketDescription(self) -> WebElement: return self.wait_for(self.ticketDescription) + def getLabelCreatedBy(self) -> WebElement: + return self.wait_for(self.labelCreatedBy) + + def getLabelDateCreation(self) -> WebElement: + return self.wait_for(self.dateCreation) + def getLabelComments(self) -> WebElement: return self.wait_for(self.comments) @@ -310,6 +350,22 @@ def getNewNoteField(self) -> WebElement: def getButtonNewNote(self) -> WebElement: return self.wait_for(self.buttonNewNote) + def getNoteRows(self) -> [WebElement]: + self.wait_for(self.noteRow) + return self.get_elements(self.noteRow) + + def getLabelLanguagesSpoken(self) -> WebElement: + return self.wait_for(self.labelLanguagesSpoken) + + def getLabelDocumentation(self) -> WebElement: + return self.wait_for(self.labelDocumentation) + + def getLabelDescription(self) -> WebElement: + return self.wait_for(self.labelDescription) + + def getNoteName(self) -> WebElement: + return self.wait_for(self.noteName) + def getLabelGENDER(self) -> WebElement: return self.wait_for(self.labelGENDER) @@ -391,6 +447,12 @@ def getCheckbox(self) -> WebElement: def getApproveBoxNeedsAdjudicationTitle(self) -> WebElement: return self.wait_for(self.approveBoxNeedsAdjudicationTitle) + def getCheckboxHouseholdData(self) -> WebElement: + return self.wait_for(self.checkboxHouseholdData) + + def getCheckboxIndividualData(self) -> WebElement: + return self.wait_for(self.checkboxIndividualData) + def getButtonCreateLinkedTicket(self) -> WebElement: return self.wait_for(self.buttonCreateLinkedTicket) @@ -569,3 +631,19 @@ def getPagination(self) -> WebElement: def getButtonCancel(self) -> WebElement: return self.wait_for(self.buttonCancel) + + def getButtonAdmin(self) -> WebElement: + return self.wait_for(self.buttonAdmin) + + def getLogRow(self) -> [WebElement]: + self.wait_for(self.logRow) + return self.get_elements(self.logRow) + + def getPaymentRecord(self) -> WebElement: + return self.wait_for(self.paymentRecord) + + def getButtonRotateImage(self) -> WebElement: + return self.wait_for(self.buttonRotateImage) + + def getLinkShowPhoto(self) -> WebElement: + return self.wait_for(self.linkShowPhoto) diff --git a/backend/selenium_tests/page_object/grievance/grievance_tickets.py b/backend/selenium_tests/page_object/grievance/grievance_tickets.py index 674796a0fc..bb530baf68 100644 --- a/backend/selenium_tests/page_object/grievance/grievance_tickets.py +++ b/backend/selenium_tests/page_object/grievance/grievance_tickets.py @@ -53,7 +53,11 @@ class GrievanceTickets(BaseComponents): buttonSetPriority = 'button[data-cy="button-Set priority"]' buttonSetUrgency = 'button[data-cy="button-Set Urgency"]' buttonAddNote = 'button[data-cy="button-add note"]' - + selectedTickets = 'span[data-cy="selected-tickets"]' + buttonCancel = 'button[data-cy="button-cancel"]' + buttonSave = 'button[data-cy="button-save"]' + dropdown = 'tbody[data-cy="dropdown"]' + statusContainer = '[data-cy="status-container"]' dateTitleFilterPopup = 'div[class="MuiPaper-root MuiPopover-paper MuiPaper-elevation8 MuiPaper-rounded"]' daysFilterPopup = ( 'div[class="MuiPickersSlideTransition-transitionContainer MuiPickersCalendar-transitionContainer"]' @@ -64,6 +68,21 @@ class GrievanceTickets(BaseComponents): textTabTitle = "Grievance Tickets List" # Elements + def getDropdown(self) -> WebElement: + return self.wait_for(self.dropdown) + + def getStatusContainer(self) -> [WebElement]: + self.wait_for(self.statusContainer) + return self.get_elements(self.statusContainer) + + def getButtonCancel(self) -> WebElement: + return self.wait_for(self.buttonCancel) + + def getButtonSave(self) -> WebElement: + return self.wait_for(self.buttonSave) + + def getSelectedTickets(self) -> WebElement: + return self.wait_for(self.selectedTickets) def getGrievanceTitle(self) -> WebElement: return self.wait_for(self.titlePage) diff --git a/backend/selenium_tests/page_object/grievance/new_feedback.py b/backend/selenium_tests/page_object/grievance/new_feedback.py index a99b06e656..e09bc6c77c 100644 --- a/backend/selenium_tests/page_object/grievance/new_feedback.py +++ b/backend/selenium_tests/page_object/grievance/new_feedback.py @@ -110,9 +110,9 @@ def getInputArea(self) -> WebElement: def getAdminAreaAutocomplete(self) -> WebElement: return self.wait_for(self.adminAreaAutocomplete) - def selectArea(self, name: str) -> WebElement: + def selectArea(self, name: str) -> None: self.getAdminAreaAutocomplete().click() - return self.select_listbox_element(name) + self.select_listbox_element(name) def getIssueType(self) -> WebElement: return self.wait_for(self.issueType) @@ -123,9 +123,9 @@ def getInputIssueType(self) -> WebElement: def getProgrammeSelect(self) -> WebElement: return self.wait_for(self.programmeSelect) - def selectProgramme(self, name: str) -> WebElement: + def selectProgramme(self, name: str) -> None: self.getProgrammeSelect().click() - return self.select_listbox_element(name) + self.select_listbox_element(name) def checkElementsOnPage(self) -> None: assert self.textTitle in self.getTitlePage().text @@ -137,4 +137,4 @@ def checkElementsOnPage(self) -> None: def chooseOptionByName(self, name: str) -> None: self.getSelectIssueType().click() - self.select_listbox_element(name).click() + self.select_listbox_element(name) diff --git a/backend/selenium_tests/page_object/grievance/new_ticket.py b/backend/selenium_tests/page_object/grievance/new_ticket.py index e81bb12697..db383a33cf 100644 --- a/backend/selenium_tests/page_object/grievance/new_ticket.py +++ b/backend/selenium_tests/page_object/grievance/new_ticket.py @@ -1,6 +1,7 @@ from time import sleep from page_object.base_components import BaseComponents +from selenium.webdriver.common.by import By from selenium.webdriver.remote.webelement import WebElement @@ -11,7 +12,8 @@ class NewTicket(BaseComponents): issueType = 'div[data-cy="select-issueType"]' buttonNext = 'button[data-cy="button-submit"]' statusOptions = 'li[role="option"]' - lookUpTabs = 'button[role="tab"]' + lookUpIndividualTab = 'button[data-cy="look-up-individual"]' + lookUpHouseholdTab = 'button[data-cy="look-up-household"]' householdTableRow = 'tr[data-cy="household-table-row"]' individualTableRow = 'tr[data-cy="individual-table-row"]' tableRow = '[data-cy="table-row"]' @@ -61,10 +63,91 @@ class NewTicket(BaseComponents): labelCategoryDescription = 'div[data-cy="label-Category Description"]' labelIssueTypeDescription = 'div[data-cy="label-Issue Type Description"]' selectFieldName = 'div[data-cy="select-householdDataUpdateFields[0].fieldName"]' - individualFieldName = 'div[data-cy="select-individualDataUpdateFields[0].fieldName"]' + individualFieldName = 'div[data-cy="select-individualDataUpdateFields[{}].fieldName"]' inputValue = 'input[data-cy="input-householdDataUpdateFields[0].fieldValue"]' partner = 'div[data-cy="select-partner"]' tablePagination = '[data-cy="table-pagination"]' + inputDescription = 'textarea[data-cy="input-description"]' + inputComments = 'textarea[data-cy="input-comments"]' + selectProgram = 'div[data-cy="select-program"]' + inputIndividualdataPhonenoalternative = 'input[data-cy="input-individualDataPhoneNoAlternative"]' + datePickerFilter = 'div[data-cy="date-picker-filter"]' + inputIndividualdataBlockchainname = 'input[data-cy="input-individualData.blockchainName"]' + selectIndividualdataSelfcaredisability = 'div[data-cy="select-individualData.selfcareDisability"]' + selectIndividualdataObserveddisability = 'div[data-cy="select-individualData.observedDisability"]' + selectIndividualdataWorkstatus = 'div[data-cy="select-individualData.workStatus"]' + selectIndividualdataEstimatedbirthdate = 'div[data-cy="select-individualData.estimatedBirthDate"]' + inputIndividualdataFamilyname = 'input[data-cy="input-individualData.familyName"]' + inputIndividualdataFullname = 'input[data-cy="input-individualData.fullName"]' + selectIndividualdataSex = 'div[data-cy="select-individualData.sex"]' + inputIndividualdataGivenname = 'input[data-cy="input-individualData.givenName"]' + selectIndividualdataCommsdisability = 'div[data-cy="select-individualData.commsDisability"]' + selectIndividualdataHearingdisability = 'div[data-cy="select-individualData.hearingDisability"]' + selectIndividualdataMemorydisability = 'div[data-cy="select-individualData.memoryDisability"]' + selectIndividualdataSeeingdisability = 'div[data-cy="select-individualData.seeingDisability"]' + selectIndividualdataPhysicaldisability = 'div[data-cy="select-individualData.physicalDisability"]' + inputIndividualdataEmail = 'input[data-cy="input-individualData.email"]' + selectIndividualdataDisability = 'div[data-cy="select-individualData.disability"]' + selectIndividualdataPregnant = 'div[data-cy="select-individualData.pregnant"]' + selectIndividualdataMaritalstatus = 'div[data-cy="select-individualData.maritalStatus"]' + inputIndividualdataMiddlename = 'input[data-cy="input-individualData.middleName"]' + inputIndividualdataPaymentdeliveryphoneno = 'input[data-cy="input-individualData.paymentDeliveryPhoneNo"]' + inputIndividualdataPhoneno = 'input[data-cy="input-individualData.phoneNo"]' + selectIndividualdataPreferredlanguage = 'div[data-cy="select-individualData.preferredLanguage"]' + selectIndividualdataRelationship = 'div[data-cy="select-individualData.relationship"]' + selectIndividualdataRole = 'div[data-cy="select-individualData.role"]' + inputIndividualdataWalletaddress = 'input[data-cy="input-individualData.walletAddress"]' + inputIndividualdataWalletname = 'input[data-cy="input-individualData.walletName"]' + inputIndividualdataWhoanswersaltphone = 'input[data-cy="input-individualData.whoAnswersAltPhone"]' + inputIndividualdataWhoanswersphone = 'input[data-cy="input-individualData.whoAnswersPhone"]' + selectHouseholddataupdatefieldsFieldname = 'div[data-cy="select-householdDataUpdateFields[{}].fieldName"]' + buttonAddNewField = 'button[data-cy="button-add-new-field"]' + inputIndividualData = 'div[data-cy="input-individual-data-{}"]' # Gender + checkboxSelectAll = 'span[data-cy="checkbox-select-all"]' + buttonSubmit = 'button[data-cy="button-submit"]' + linkedTicketId = 'span[data-cy="linked-ticket-id"]' + linkedTicket = '[data-cy="linked-ticket"]' + buttonEdit = 'svg[data-cy="button-edit"]' + buttonDelete = 'svg[data-cy="button-delete"]' + addDocumentation = 'button[data-cy="add-documentation"]' + inputDocumentationName = 'input[data-cy="input-documentation[{}].name"]' + inputFile = 'input[type="file"]' + inputQuestionnaire_size = 'span[data-cy="input-questionnaire_size"]' + labelHouseholdSize = 'div[data-cy="label-Household Size"]' + inputQuestionnaire_malechildrencount = 'span[data-cy="input-questionnaire_maleChildrenCount"]' + labelNumberOfMaleChildren = 'div[data-cy="label-Number of Male Children"]' + inputQuestionnaire_femalechildrencount = 'span[data-cy="input-questionnaire_femaleChildrenCount"]' + labelNumberOfFemaleChildren = 'div[data-cy="label-Number of Female Children"]' + inputQuestionnaire_childrendisabledcount = 'span[data-cy="input-questionnaire_childrenDisabledCount"]' + labelNumberOfDisabledChildren = 'div[data-cy="label-Number of Disabled Children"]' + inputQuestionnaire_headofhousehold = 'span[data-cy="input-questionnaire_headOfHousehold"]' + labelHeadOfHousehold = 'div[data-cy="label-Head of Household"]' + inputQuestionnaire_countryorigin = 'span[data-cy="input-questionnaire_countryOrigin"]' + labelCountryOfOrigin = 'div[data-cy="label-Country of Origin"]' + inputQuestionnaire_address = 'span[data-cy="input-questionnaire_address"]' + labelAddress = 'div[data-cy="label-Address"]' + inputQuestionnaire_village = 'span[data-cy="input-questionnaire_village"]' + labelVillage = 'div[data-cy="label-Village"]' + inputQuestionnaire_admin1 = 'span[data-cy="input-questionnaire_admin1"]' + labelAdministrativeLevel1 = 'div[data-cy="label-Administrative Level 1"]' + inputQuestionnaire_admin2 = 'span[data-cy="input-questionnaire_admin2"]' + labelAdministrativeLevel2 = 'div[data-cy="label-Administrative Level 2"]' + inputQuestionnaire_admin3 = 'span[data-cy="input-questionnaire_admin3"]' + labelAdministrativeLevel3 = 'div[data-cy="label-Administrative Level 3"]' + inputQuestionnaire_admin4 = 'span[data-cy="input-questionnaire_admin4"]' + labelAdministrativeLevel4 = 'div[data-cy="label-Administrative Level 4"]' + inputQuestionnaire_months_displaced_h_f = 'span[data-cy="input-questionnaire_months_displaced_h_f"]' + labelLengthOfTimeSinceArrival = 'div[data-cy="label-LENGTH OF TIME SINCE ARRIVAL"]' + inputQuestionnaire_fullname = 'span[data-cy="input-questionnaire_fullName"]' + labelIndividualFullName = 'div[data-cy="label-Individual Full Name"]' + inputQuestionnaire_birthdate = 'span[data-cy="input-questionnaire_birthDate"]' + labelBirthDate = 'div[data-cy="label-Birth Date"]' + inputQuestionnaire_sex = 'span[data-cy="input-questionnaire_sex"]' + labelGender = 'div[data-cy="label-Gender"]' + inputQuestionnaire_phoneno = 'span[data-cy="input-questionnaire_phoneNo"]' + labelPhoneNumber = 'div[data-cy="label-Phone Number"]' + inputQuestionnaire_relationship = 'span[data-cy="input-questionnaire_relationship"]' + labelRelationshipToHoh = 'div[data-cy="label-Relationship to HOH"]' # Texts textLookUpHousehold = "LOOK UP HOUSEHOLD" @@ -120,16 +203,18 @@ def getOption(self) -> WebElement: return self.wait_for(self.statusOptions) def getHouseholdTab(self) -> WebElement: - return self.wait_for(self.lookUpTabs) + return self.wait_for(self.lookUpHouseholdTab) def getIndividualTab(self) -> WebElement: - return self.wait_for(self.lookUpTabs) + return self.wait_for(self.lookUpIndividualTab) def getHouseholdTableRows(self, number: int) -> WebElement: - return self.wait_for(self.householdTableRow)[number] + self.wait_for(self.householdTableRow) + return self.get_elements(self.householdTableRow)[number] def getIndividualTableRows(self, number: int) -> WebElement: - return self.wait_for(self.individualTableRow)[number] + self.wait_for(self.individualTableRow) + return self.get_elements(self.individualTableRow)[number] def getReceivedConsent(self) -> WebElement: return self.wait_for(self.receivedConsent) @@ -233,6 +318,9 @@ def getAddDocument(self) -> WebElement: def getLookUpButton(self) -> WebElement: return self.wait_for(self.lookUpButton) + def getLookUpPaymentRecord(self) -> [WebElement]: + return self.get_elements(self.lookUpButton)[1] + def getCheckbox(self) -> WebElement: return self.wait_for(self.checkbox) @@ -272,8 +360,8 @@ def getSelectFieldName(self) -> WebElement: def getInputValue(self) -> WebElement: return self.wait_for(self.inputValue) - def getIndividualFieldName(self) -> WebElement: - return self.wait_for(self.individualFieldName) + def getIndividualFieldName(self, index: int) -> WebElement: + return self.wait_for(self.individualFieldName.format(str(index))) def getPartner(self) -> WebElement: return self.wait_for(self.partner) @@ -290,3 +378,241 @@ def waitForNoResults(self) -> bool: return True sleep(1) return False + + def getSelectProgram(self) -> WebElement: + return self.wait_for(self.selectProgram) + + def getInputIndividualdataPhonenoalternative(self) -> WebElement: + return self.wait_for(self.inputIndividualdataPhonenoalternative) + + def getDatePickerFilter(self) -> WebElement: + return self.wait_for(self.datePickerFilter).find_element("tag name", "input") + + def getInputIndividualdataBlockchainname(self) -> WebElement: + return self.wait_for(self.inputIndividualdataBlockchainname) + + def getSelectIndividualdataSelfcaredisability(self) -> WebElement: + return self.wait_for(self.selectIndividualdataSelfcaredisability) + + def getSelectIndividualdataObserveddisability(self) -> WebElement: + return self.wait_for(self.selectIndividualdataObserveddisability) + + def getSelectIndividualdataWorkstatus(self) -> WebElement: + return self.wait_for(self.selectIndividualdataWorkstatus) + + def getSelectIndividualdataEstimatedbirthdate(self) -> WebElement: + return self.wait_for(self.selectIndividualdataEstimatedbirthdate) + + def getInputIndividualdataFamilyname(self) -> WebElement: + return self.wait_for(self.inputIndividualdataFamilyname) + + def getInputIndividualdataFullname(self) -> WebElement: + return self.wait_for(self.inputIndividualdataFullname) + + def getSelectIndividualdataSex(self) -> WebElement: + return self.wait_for(self.selectIndividualdataSex) + + def getInputIndividualdataGivenname(self) -> WebElement: + return self.wait_for(self.inputIndividualdataGivenname) + + def getSelectIndividualdataCommsdisability(self) -> WebElement: + return self.wait_for(self.selectIndividualdataCommsdisability) + + def getSelectIndividualdataHearingdisability(self) -> WebElement: + return self.wait_for(self.selectIndividualdataHearingdisability) + + def getSelectIndividualdataMemorydisability(self) -> WebElement: + return self.wait_for(self.selectIndividualdataMemorydisability) + + def getSelectIndividualdataSeeingdisability(self) -> WebElement: + return self.wait_for(self.selectIndividualdataSeeingdisability) + + def getSelectIndividualdataPhysicaldisability(self) -> WebElement: + return self.wait_for(self.selectIndividualdataPhysicaldisability) + + def getInputIndividualdataEmail(self) -> WebElement: + return self.wait_for(self.inputIndividualdataEmail) + + def getSelectIndividualdataDisability(self) -> WebElement: + return self.wait_for(self.selectIndividualdataDisability) + + def getSelectIndividualdataPregnant(self) -> WebElement: + return self.wait_for(self.selectIndividualdataPregnant) + + def getSelectIndividualdataMaritalstatus(self) -> WebElement: + return self.wait_for(self.selectIndividualdataMaritalstatus) + + def getInputIndividualdataMiddlename(self) -> WebElement: + return self.wait_for(self.inputIndividualdataMiddlename) + + def getInputIndividualdataPaymentdeliveryphoneno(self) -> WebElement: + return self.wait_for(self.inputIndividualdataPaymentdeliveryphoneno) + + def getInputIndividualdataPhoneno(self) -> WebElement: + return self.wait_for(self.inputIndividualdataPhoneno) + + def getSelectIndividualdataPreferredlanguage(self) -> WebElement: + return self.wait_for(self.selectIndividualdataPreferredlanguage) + + def getSelectIndividualdataRelationship(self) -> WebElement: + return self.wait_for(self.selectIndividualdataRelationship) + + def getSelectIndividualdataRole(self) -> WebElement: + return self.wait_for(self.selectIndividualdataRole) + + def getInputIndividualdataWalletaddress(self) -> WebElement: + return self.wait_for(self.inputIndividualdataWalletaddress) + + def getInputIndividualdataWalletname(self) -> WebElement: + return self.wait_for(self.inputIndividualdataWalletname) + + def getInputIndividualdataWhoanswersaltphone(self) -> WebElement: + return self.wait_for(self.inputIndividualdataWhoanswersaltphone) + + def getInputIndividualdataWhoanswersphone(self) -> WebElement: + return self.wait_for(self.inputIndividualdataWhoanswersphone) + + def getButtonAddNewField(self) -> WebElement: + return self.wait_for(self.buttonAddNewField) + + def getSelectHouseholddataupdatefieldsFieldname(self, index: int) -> WebElement: + field = self.wait_for(self.selectHouseholddataupdatefieldsFieldname.format(index)) + return field.find_element(By.XPATH, "./..") + + def getInputIndividualData(self, type: str) -> WebElement: + return self.wait_for(self.inputIndividualData.format(type)) + + def getCheckboxSelectAll(self) -> WebElement: + return self.wait_for(self.checkboxSelectAll) + + def getButtonSubmit(self) -> WebElement: + return self.get_elements(self.buttonSubmit)[1] + + def getLinkedTicketId(self) -> WebElement: + return self.wait_for(self.linkedTicketId) + + def getLinkedTicket(self) -> WebElement: + return self.wait_for(self.linkedTicket) + + def getButtonEdit(self) -> WebElement: + return self.wait_for(self.buttonEdit) + + def getButtonDelete(self) -> WebElement: + return self.wait_for(self.buttonDelete) + + def getAddDocumentation(self) -> WebElement: + return self.wait_for(self.addDocumentation) + + def getInputDocumentationName(self, index: int) -> WebElement: + return self.wait_for(self.inputDocumentationName.format(index)) + + def getInputFile(self) -> WebElement: + return self.wait_for(self.inputFile) + + def getInputQuestionnaire_size(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_size) + + def getLabelHouseholdSize(self) -> WebElement: + return self.wait_for(self.labelHouseholdSize) + + def getInputQuestionnaire_malechildrencount(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_malechildrencount) + + def getLabelNumberOfMaleChildren(self) -> WebElement: + return self.wait_for(self.labelNumberOfMaleChildren) + + def getInputQuestionnaire_femalechildrencount(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_femalechildrencount) + + def getLabelNumberOfFemaleChildren(self) -> WebElement: + return self.wait_for(self.labelNumberOfFemaleChildren) + + def getInputQuestionnaire_childrendisabledcount(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_childrendisabledcount) + + def getLabelNumberOfDisabledChildren(self) -> WebElement: + return self.wait_for(self.labelNumberOfDisabledChildren) + + def getInputQuestionnaire_headofhousehold(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_headofhousehold) + + def getLabelHeadOfHousehold(self) -> WebElement: + return self.wait_for(self.labelHeadOfHousehold) + + def getInputQuestionnaire_countryorigin(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_countryorigin) + + def getLabelCountryOfOrigin(self) -> WebElement: + return self.wait_for(self.labelCountryOfOrigin) + + def getInputQuestionnaire_address(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_address) + + def getLabelAddress(self) -> WebElement: + return self.wait_for(self.labelAddress) + + def getInputQuestionnaire_village(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_village) + + def getLabelVillage(self) -> WebElement: + return self.wait_for(self.labelVillage) + + def getInputQuestionnaire_admin1(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_admin1) + + def getLabelAdministrativeLevel1(self) -> WebElement: + return self.wait_for(self.labelAdministrativeLevel1) + + def getInputQuestionnaire_admin2(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_admin2) + + def getLabelAdministrativeLevel2(self) -> WebElement: + return self.wait_for(self.labelAdministrativeLevel2) + + def getInputQuestionnaire_admin3(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_admin3) + + def getLabelAdministrativeLevel3(self) -> WebElement: + return self.wait_for(self.labelAdministrativeLevel3) + + def getInputQuestionnaire_admin4(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_admin4) + + def getLabelAdministrativeLevel4(self) -> WebElement: + return self.wait_for(self.labelAdministrativeLevel4) + + def getInputQuestionnaire_months_displaced_h_f(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_months_displaced_h_f) + + def getLabelLengthOfTimeSinceArrival(self) -> WebElement: + return self.wait_for(self.labelLengthOfTimeSinceArrival) + + def getInputQuestionnaire_fullname(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_fullname) + + def getLabelIndividualFullName(self) -> WebElement: + return self.wait_for(self.labelIndividualFullName) + + def getInputQuestionnaire_birthdate(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_birthdate) + + def getLabelBirthDate(self) -> WebElement: + return self.wait_for(self.labelBirthDate) + + def getInputQuestionnaire_sex(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_sex) + + def getLabelGender(self) -> WebElement: + return self.wait_for(self.labelGender) + + def getInputQuestionnaire_phoneno(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_phoneno) + + def getLabelPhoneNumber(self) -> WebElement: + return self.wait_for(self.labelPhoneNumber) + + def getInputQuestionnaire_relationship(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_relationship) + + def getLabelRelationshipToHoh(self) -> WebElement: + return self.wait_for(self.labelRelationshipToHoh) diff --git a/backend/selenium_tests/page_object/programme_population/households_details.py b/backend/selenium_tests/page_object/programme_population/households_details.py index 899a91f72f..b26c6cfee1 100644 --- a/backend/selenium_tests/page_object/programme_population/households_details.py +++ b/backend/selenium_tests/page_object/programme_population/households_details.py @@ -37,6 +37,7 @@ class HouseholdsDetails(BaseComponents): labelImportName = 'div[data-cy="label-Import name"]' labelRegistrationDate = 'div[data-cy="label-Registration Date"]' labelUserName = 'div[data-cy="label-User name"]' + row05 = '[data-cy="row05"]' def getPageHeaderContainer(self) -> WebElement: return self.wait_for(self.pageHeaderContainer) @@ -139,3 +140,6 @@ def getLabelRegistrationDate(self) -> WebElement: def getLabelUserName(self) -> WebElement: return self.wait_for(self.labelUserName) + + def getRow05(self) -> WebElement: + return self.wait_for(self.row05) diff --git a/backend/selenium_tests/payment_module/test_payment_module.py b/backend/selenium_tests/payment_module/test_payment_module.py index 91d9fd2b0f..8578100035 100644 --- a/backend/selenium_tests/payment_module/test_payment_module.py +++ b/backend/selenium_tests/payment_module/test_payment_module.py @@ -145,7 +145,7 @@ def create_payment_plan(create_targeting: None) -> PaymentPlan: @pytest.mark.usefixtures("login") class TestSmokePaymentModule: def test_smoke_payment_plan(self, create_payment_plan: PaymentPlan, pagePaymentModule: PaymentModule) -> None: - pagePaymentModule.selectGlobalProgramFilter("Test Program").click() + pagePaymentModule.selectGlobalProgramFilter("Test Program") pagePaymentModule.getNavPaymentModule().click() assert "Payment Module" in pagePaymentModule.getPageHeaderTitle().text assert "NEW PAYMENT PLAN" in pagePaymentModule.getButtonNewPaymentPlan().text @@ -174,7 +174,7 @@ def test_smoke_payment_plan(self, create_payment_plan: PaymentPlan, pagePaymentM def test_smoke_new_payment_plan( self, create_test_program: Program, pagePaymentModule: PaymentModule, pageNewPaymentPlan: NewPaymentPlan ) -> None: - pagePaymentModule.selectGlobalProgramFilter("Test Program").click() + pagePaymentModule.selectGlobalProgramFilter("Test Program") pagePaymentModule.getNavPaymentModule().click() pagePaymentModule.getButtonNewPaymentPlan().click() @@ -193,7 +193,7 @@ def test_smoke_details_payment_plan( pagePaymentModule: PaymentModule, pagePaymentModuleDetails: PaymentModuleDetails, ) -> None: - pagePaymentModule.selectGlobalProgramFilter("Test Program").click() + pagePaymentModule.selectGlobalProgramFilter("Test Program") pagePaymentModule.getNavPaymentModule().click() assert "NEW PAYMENT PLAN" in pagePaymentModule.getButtonNewPaymentPlan().text pagePaymentModule.getRow(0).click() @@ -246,11 +246,11 @@ def test_payment_plan_happy_path( ) -> None: targeting = TargetPopulation.objects.first() program = Program.objects.get(name="Test Program") - pagePaymentModule.selectGlobalProgramFilter("Test Program").click() + pagePaymentModule.selectGlobalProgramFilter("Test Program") pagePaymentModule.getNavPaymentModule().click() pagePaymentModule.getButtonNewPaymentPlan().click() pageNewPaymentPlan.getInputTargetPopulation().click() - pageNewPaymentPlan.select_listbox_element(targeting.name).click() + pageNewPaymentPlan.select_listbox_element(targeting.name) pageNewPaymentPlan.getInputStartDate().click() pageNewPaymentPlan.getInputStartDate().send_keys( FormatTime(time=program.start_date + timedelta(days=12)).numerically_formatted_date @@ -258,7 +258,7 @@ def test_payment_plan_happy_path( pageNewPaymentPlan.getInputEndDate().click() pageNewPaymentPlan.getInputEndDate().send_keys(FormatTime(time=program.end_date).numerically_formatted_date) pageNewPaymentPlan.getInputCurrency().click() - pageNewPaymentPlan.select_listbox_element("Czech koruna").click() + pageNewPaymentPlan.select_listbox_element("Czech koruna") pageNewPaymentPlan.getInputDispersionStartDate().click() pageNewPaymentPlan.getInputDispersionStartDate().send_keys(FormatTime(22, 1, 2024).numerically_formatted_date) pageNewPaymentPlan.getInputDispersionEndDate().click() @@ -280,7 +280,7 @@ def test_payment_plan_happy_path( pagePaymentModuleDetails.getButtonLockPlan().click() pagePaymentModuleDetails.getButtonSubmit().click() pagePaymentModuleDetails.getInputEntitlementFormula().click() - pagePaymentModuleDetails.select_listbox_element("Test Rule").click() + pagePaymentModuleDetails.select_listbox_element("Test Rule") pagePaymentModuleDetails.getButtonApplySteficon().click() for _ in range(10): @@ -293,10 +293,10 @@ def test_payment_plan_happy_path( pagePaymentModuleDetails.getButtonSetUpFsp().click() pagePaymentModuleDetails.getSelectDeliveryMechanism().click() - pagePaymentModuleDetails.select_listbox_element("Cash").click() + pagePaymentModuleDetails.select_listbox_element("Cash") pagePaymentModuleDetails.getButtonNextSave().click() pagePaymentModuleDetails.getSelectDeliveryMechanismFSP().click() - pagePaymentModuleDetails.select_listbox_element("FSP_1").click() + pagePaymentModuleDetails.select_listbox_element("FSP_1") pagePaymentModuleDetails.getButtonNextSave().click() pagePaymentModuleDetails.checkStatus("LOCKED") pagePaymentModuleDetails.getButtonLockPlan().click() diff --git a/backend/selenium_tests/payment_verification/test_payment_verification.py b/backend/selenium_tests/payment_verification/test_payment_verification.py index de279cdc72..37d7467dc7 100644 --- a/backend/selenium_tests/payment_verification/test_payment_verification.py +++ b/backend/selenium_tests/payment_verification/test_payment_verification.py @@ -119,7 +119,7 @@ class TestSmokePaymentVerification: def test_smoke_payment_verification( self, active_program: Program, add_payment_verification: PV, pagePaymentVerification: PaymentVerification ) -> None: - pagePaymentVerification.selectGlobalProgramFilter("Active Program").click() + pagePaymentVerification.selectGlobalProgramFilter("Active Program") pagePaymentVerification.getNavPaymentVerification().click() assert "Payment Verification" in pagePaymentVerification.getPageHeaderTitle().text assert "List of Payment Plans" in pagePaymentVerification.getTableTitle().text @@ -139,7 +139,7 @@ def test_smoke_payment_verification_details( pagePaymentVerification: PaymentVerification, pagePaymentVerificationDetails: PaymentVerificationDetails, ) -> None: - pagePaymentVerification.selectGlobalProgramFilter("Active Program").click() + pagePaymentVerification.selectGlobalProgramFilter("Active Program") pagePaymentVerification.getNavPaymentVerification().click() pagePaymentVerification.getCashPlanTableRow().click() assert "Payment Plan PP-0000-00-1122334" in pagePaymentVerificationDetails.getPageHeaderTitle().text @@ -190,7 +190,7 @@ def test_happy_path_payment_verification( pagePaymentVerificationDetails: PaymentVerificationDetails, pagePaymentRecord: PaymentRecord, ) -> None: - pagePaymentVerification.selectGlobalProgramFilter("Active Program").click() + pagePaymentVerification.selectGlobalProgramFilter("Active Program") pagePaymentVerification.getNavPaymentVerification().click() pagePaymentVerification.getCashPlanTableRow().click() assert "1" in pagePaymentVerificationDetails.getLabelPaymentRecords().text diff --git a/backend/selenium_tests/people/test_people.py b/backend/selenium_tests/people/test_people.py index 03b8f53691..24b6b4ee3a 100644 --- a/backend/selenium_tests/people/test_people.py +++ b/backend/selenium_tests/people/test_people.py @@ -106,7 +106,7 @@ def get_program_with_dct_type_and_name( @pytest.mark.usefixtures("login") class TestSmokePeople: def test_smoke_page_people(self, social_worker_program: Program, pagePeople: People) -> None: - pagePeople.selectGlobalProgramFilter("Worker Program").click() + pagePeople.selectGlobalProgramFilter("Worker Program") pagePeople.getNavPeople().click() assert "People" in pagePeople.getTableTitle().text assert "Individual ID" in pagePeople.getIndividualId().text @@ -123,7 +123,7 @@ def test_smoke_page_details_people( pagePeopleDetails: PeopleDetails, filters: Filters, ) -> None: - pagePeople.selectGlobalProgramFilter("Worker Program").click() + pagePeople.selectGlobalProgramFilter("Worker Program") pagePeople.getNavPeople().click() assert "People" in pagePeople.getTableTitle().text unicef_id = pagePeople.getIndividualTableRow(0).text.split(" ")[0] @@ -227,7 +227,7 @@ def test_people_happy_path( pagePeople: People, pagePeopleDetails: PeopleDetails, ) -> None: - pagePeople.selectGlobalProgramFilter("Worker Program").click() + pagePeople.selectGlobalProgramFilter("Worker Program") pagePeople.getNavPeople().click() pagePeople.getIndividualTableRow(0).click() assert "21.36" in pagePeopleDetails.getLabelTotalCashReceived().text @@ -236,3 +236,7 @@ def test_people_happy_path( assert "21.36" in pagePeopleDetails.getRows()[0].text assert "DELIVERED FULLY" in pagePeopleDetails.getRows()[0].text assert add_people_with_payment_record.unicef_id in pagePeopleDetails.getRows()[0].text + + @pytest.mark.skip(reason="ToDo") + def test_check_data_after_grievance_ticket_processed(self) -> None: + pass diff --git a/backend/selenium_tests/program_details/test_program_details.py b/backend/selenium_tests/program_details/test_program_details.py index 15768693c2..b8acfc6643 100644 --- a/backend/selenium_tests/program_details/test_program_details.py +++ b/backend/selenium_tests/program_details/test_program_details.py @@ -115,7 +115,7 @@ class TestProgrammeDetails: def test_program_details(self, standard_program: Program, pageProgrammeDetails: ProgrammeDetails) -> None: program = Program.objects.get(name="Test For Edit") # Go to Programme Details - pageProgrammeDetails.selectGlobalProgramFilter("Test For Edit").click() + pageProgrammeDetails.selectGlobalProgramFilter("Test For Edit") # Check Details page assert "Test For Edit" in pageProgrammeDetails.getHeaderTitle().text assert "DRAFT" in pageProgrammeDetails.getProgramStatus().text @@ -151,7 +151,7 @@ def test_edit_programme_from_details( pageProgrammeDetails: ProgrammeDetails, pageProgrammeManagement: ProgrammeManagement, ) -> None: - pageProgrammeDetails.selectGlobalProgramFilter("Test Programm").click() + pageProgrammeDetails.selectGlobalProgramFilter("Test Programm") pageProgrammeDetails.getButtonEditProgram().click() pageProgrammeManagement.getInputProgrammeName().send_keys(Keys.CONTROL + "a") pageProgrammeManagement.getInputProgrammeName().send_keys("New name after Edit") @@ -177,7 +177,7 @@ def test_edit_programme_from_details( def test_program_details_happy_path( self, create_payment_plan: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: - pageProgrammeDetails.selectGlobalProgramFilter("Test For Edit").click() + pageProgrammeDetails.selectGlobalProgramFilter("Test For Edit") assert "DRAFT" in pageProgrammeDetails.getProgramStatus().text assert "0" in pageProgrammeDetails.getLabelProgramSize().text pageProgrammeDetails.getButtonActivateProgram().click() diff --git a/backend/selenium_tests/program_log/test_program_log.py b/backend/selenium_tests/program_log/test_program_log.py index 8ca67173fc..a2d2672a9f 100644 --- a/backend/selenium_tests/program_log/test_program_log.py +++ b/backend/selenium_tests/program_log/test_program_log.py @@ -21,7 +21,7 @@ class TestProgrammeLog: def test_smoke_program_log( self, standard_program: Program, pageProgramLog: ProgramLog, pageProgrammeDetails: ProgrammeDetails ) -> None: - pageProgrammeDetails.selectGlobalProgramFilter("Test Program").click() + pageProgrammeDetails.selectGlobalProgramFilter("Test Program") pageProgrammeDetails.getButtonFinishProgram().click() pageProgrammeDetails.clickButtonFinishProgramPopup() pageProgramLog.getNavProgramLog().click() @@ -39,10 +39,10 @@ def test_smoke_program_log( def test_smoke_activity_log( self, standard_program: Program, pageProgramLog: ProgramLog, pageProgrammeDetails: ProgrammeDetails ) -> None: - pageProgrammeDetails.selectGlobalProgramFilter("Test Program").click() + pageProgrammeDetails.selectGlobalProgramFilter("Test Program") pageProgrammeDetails.getButtonFinishProgram().click() pageProgrammeDetails.clickButtonFinishProgramPopup() - pageProgrammeDetails.selectGlobalProgramFilter("All Programmes").click() + pageProgrammeDetails.selectGlobalProgramFilter("All Programmes") pageProgramLog.getNavActivityLog().click() assert "Activity Log" in pageProgramLog.getPageHeaderTitle().text assert "Update" in pageProgramLog.getActionCell().text diff --git a/backend/selenium_tests/programme_management/test_programme_management.py b/backend/selenium_tests/programme_management/test_programme_management.py index 548334d3e4..2da79d47fc 100644 --- a/backend/selenium_tests/programme_management/test_programme_management.py +++ b/backend/selenium_tests/programme_management/test_programme_management.py @@ -69,9 +69,9 @@ def test_create_programme( pageProgrammeManagement.getButtonAddTimeSeriesField().click() pageProgrammeManagement.getInputPduFieldsObjectLabel(0).send_keys("test series field name") pageProgrammeManagement.getSelectPduFieldsObjectPduDataSubtype(0).click() - pageProgrammeManagement.select_listbox_element("Text").click() + pageProgrammeManagement.select_listbox_element("Text") pageProgrammeManagement.getSelectPduFieldsObjectPduDataNumberOfRounds(0).click() - pageProgrammeManagement.select_listbox_element("1").click() + pageProgrammeManagement.select_listbox_element("1") pageProgrammeManagement.getInputPduFieldsRoundsNames(0, 0).send_keys("Round 1") pageProgrammeManagement.getButtonNext().click() # 3rd step (Partners) @@ -480,9 +480,9 @@ def test_copy_programme( pageProgrammeManagement.getButtonAddTimeSeriesField().click() pageProgrammeManagement.getInputPduFieldsObjectLabel(0).send_keys("Time Series Field Name 1") pageProgrammeManagement.getSelectPduFieldsObjectPduDataSubtype(0).click() - pageProgrammeManagement.select_listbox_element("Text").click() + pageProgrammeManagement.select_listbox_element("Text") pageProgrammeManagement.getSelectPduFieldsObjectPduDataNumberOfRounds(0).click() - pageProgrammeManagement.select_listbox_element("1").click() + pageProgrammeManagement.select_listbox_element("1") pageProgrammeManagement.getInputPduFieldsRoundsNames(0, 0).send_keys("Round 1") pageProgrammeManagement.getButtonNext().click() # 3rd step (Partners) @@ -502,9 +502,9 @@ def test_copy_programme( pageProgrammeManagement.getButtonAddTimeSeriesField().click() pageProgrammeManagement.getInputPduFieldsObjectLabel(0).send_keys("Any name") pageProgrammeManagement.getSelectPduFieldsObjectPduDataSubtype(0).click() - pageProgrammeManagement.select_listbox_element("Number").click() + pageProgrammeManagement.select_listbox_element("Number") pageProgrammeManagement.getSelectPduFieldsObjectPduDataNumberOfRounds(0).click() - pageProgrammeManagement.select_listbox_element("1").click() + pageProgrammeManagement.select_listbox_element("1") pageProgrammeManagement.getInputPduFieldsRoundsNames(0, 0).send_keys("Round 1") pageProgrammeManagement.getButtonNext().click() # 3rd step (Partners) diff --git a/backend/selenium_tests/programme_population/test_households.py b/backend/selenium_tests/programme_population/test_households.py index 5f2b37ddf4..52f42c87d8 100644 --- a/backend/selenium_tests/programme_population/test_households.py +++ b/backend/selenium_tests/programme_population/test_households.py @@ -27,7 +27,7 @@ class TestSmokeHouseholds: def test_smoke_page_households( self, create_programs: None, add_households: None, pageHouseholds: Households ) -> None: - pageHouseholds.selectGlobalProgramFilter("Test Programm").click() + pageHouseholds.selectGlobalProgramFilter("Test Programm") pageHouseholds.getNavProgrammePopulation().click() pageHouseholds.getNavHouseholds().click() assert 2 == len(pageHouseholds.getHouseholdsRows()) @@ -48,7 +48,7 @@ def test_smoke_page_households_details( pageHouseholds: Households, pageHouseholdsDetails: HouseholdsDetails, ) -> None: - pageHouseholds.selectGlobalProgramFilter("Test Programm").click() + pageHouseholds.selectGlobalProgramFilter("Test Programm") pageHouseholds.getNavProgrammePopulation().click() pageHouseholds.getNavHouseholds().click() pageHouseholds.getHouseholdsRowByNumber(0).click() diff --git a/backend/selenium_tests/programme_population/test_individuals.py b/backend/selenium_tests/programme_population/test_individuals.py index f66a42b427..526944d5ba 100644 --- a/backend/selenium_tests/programme_population/test_individuals.py +++ b/backend/selenium_tests/programme_population/test_individuals.py @@ -27,7 +27,7 @@ class TestSmokeIndividuals: def test_smoke_page_individuals( self, create_programs: None, add_households: None, pageIndividuals: Individuals ) -> None: - pageIndividuals.selectGlobalProgramFilter("Test Programm").click() + pageIndividuals.selectGlobalProgramFilter("Test Programm") pageIndividuals.getNavProgrammePopulation().click() pageIndividuals.getNavIndividuals().click() assert "Individuals" in pageIndividuals.getTableTitle().text @@ -47,7 +47,7 @@ def test_smoke_page_individuals_details( pageIndividuals: Individuals, pageIndividualsDetails: IndividualsDetails, ) -> None: - pageIndividuals.selectGlobalProgramFilter("Test Programm").click() + pageIndividuals.selectGlobalProgramFilter("Test Programm") pageIndividuals.getNavProgrammePopulation().click() pageIndividuals.getNavIndividuals().click() pageIndividuals.getIndividualTableRow()[0].click() @@ -82,3 +82,7 @@ def test_smoke_page_individuals_details( assert "0048503123555" in pageIndividualsDetails.getLabelPhoneNumber().text assert "-" in pageIndividualsDetails.getLabelAlternativePhoneNumber().text assert "-" in pageIndividualsDetails.getLabelDateOfLastScreeningAgainstSanctionsList().text + + @pytest.mark.skip(reason="ToDo") + def test_check_data_after_grievance_ticket_processed(self) -> None: + pass diff --git a/backend/selenium_tests/programme_population/test_periodic_data_templates.py b/backend/selenium_tests/programme_population/test_periodic_data_templates.py index 9d7e3fdbf2..a5912f0c0c 100644 --- a/backend/selenium_tests/programme_population/test_periodic_data_templates.py +++ b/backend/selenium_tests/programme_population/test_periodic_data_templates.py @@ -124,7 +124,7 @@ def test_periodic_data_template_export_and_download( } ], ) - pageIndividuals.selectGlobalProgramFilter(program.name).click() + pageIndividuals.selectGlobalProgramFilter(program.name) pageIndividuals.getNavProgrammePopulation().click() pageIndividuals.getNavIndividuals().click() pageIndividuals.getTabPeriodicDataUpdates().click() @@ -170,7 +170,7 @@ def test_periodic_data_template_list( periodic_data_update_template.refresh_from_db() index = periodic_data_update_template.id - pageIndividuals.selectGlobalProgramFilter(program.name).click() + pageIndividuals.selectGlobalProgramFilter(program.name) pageIndividuals.getNavProgrammePopulation().click() pageIndividuals.getNavIndividuals().click() pageIndividuals.getTabPeriodicDataUpdates().click() @@ -218,7 +218,7 @@ def test_periodic_data_template_details( periodic_data_update_template.refresh_from_db() index = periodic_data_update_template.id - pageIndividuals.selectGlobalProgramFilter(program.name).click() + pageIndividuals.selectGlobalProgramFilter(program.name) pageIndividuals.getNavProgrammePopulation().click() pageIndividuals.getNavIndividuals().click() pageIndividuals.getTabPeriodicDataUpdates().click() @@ -246,7 +246,7 @@ def test_periodic_data_template_create_and_download( pagePeriodicDataUpdateTemplatesDetails: PeriodicDatUpdateTemplatesDetails, individual: Individual, ) -> None: - pageIndividuals.selectGlobalProgramFilter(program.name).click() + pageIndividuals.selectGlobalProgramFilter(program.name) pageIndividuals.getNavProgrammePopulation().click() pageIndividuals.getNavIndividuals().click() pageIndividuals.getTabPeriodicDataUpdates().click() @@ -254,7 +254,7 @@ def test_periodic_data_template_create_and_download( pagePeriodicDataUpdateTemplates.getNewTemplateButton().click() pagePeriodicDataUpdateTemplatesDetails.getFiltersRegistrationDataImport().click() - pagePeriodicDataUpdateTemplatesDetails.select_listbox_element(individual.registration_data_import.name).click() + pagePeriodicDataUpdateTemplatesDetails.select_listbox_element(individual.registration_data_import.name) pagePeriodicDataUpdateTemplatesDetails.getSubmitButton().click() pagePeriodicDataUpdateTemplatesDetails.getCheckbox(string_attribute.name).click() pagePeriodicDataUpdateTemplatesDetails.getSubmitButton().click() diff --git a/backend/selenium_tests/programme_population/test_periodic_data_update_upload.py b/backend/selenium_tests/programme_population/test_periodic_data_update_upload.py index 01d961ace1..24ce2008c5 100644 --- a/backend/selenium_tests/programme_population/test_periodic_data_update_upload.py +++ b/backend/selenium_tests/programme_population/test_periodic_data_update_upload.py @@ -162,7 +162,7 @@ def test_periodic_data_update_upload_success( [["Test Value", "2021-05-02"]], program, ) - pageIndividuals.selectGlobalProgramFilter(program.name).click() + pageIndividuals.selectGlobalProgramFilter(program.name) pageIndividuals.getNavProgrammePopulation().click() pageIndividuals.getNavIndividuals().click() pageIndividuals.getTabPeriodicDataUpdates().click() @@ -202,7 +202,7 @@ def test_periodic_data_update_upload_form_error( [["Test Value", "2021-05-02"]], program, ) - pageIndividuals.selectGlobalProgramFilter(program.name).click() + pageIndividuals.selectGlobalProgramFilter(program.name) pageIndividuals.getNavProgrammePopulation().click() pageIndividuals.getNavIndividuals().click() pageIndividuals.getTabPeriodicDataUpdates().click() @@ -251,7 +251,7 @@ def test_periodic_data_update_upload_error( with NamedTemporaryFile(delete=False, suffix=".xlsx") as tmp_file: wb.save(tmp_file.name) tmp_file.seek(0) - pageIndividuals.selectGlobalProgramFilter(program.name).click() + pageIndividuals.selectGlobalProgramFilter(program.name) pageIndividuals.getNavProgrammePopulation().click() pageIndividuals.getNavIndividuals().click() pageIndividuals.getTabPeriodicDataUpdates().click() @@ -289,7 +289,7 @@ def test_periodic_data_uploads_list( template=periodic_data_update_template, status=PeriodicDataUpdateUpload.Status.SUCCESSFUL, ) - pageIndividuals.selectGlobalProgramFilter(program.name).click() + pageIndividuals.selectGlobalProgramFilter(program.name) pageIndividuals.getNavProgrammePopulation().click() pageIndividuals.getNavIndividuals().click() pageIndividuals.getTabPeriodicDataUpdates().click() diff --git a/backend/selenium_tests/programme_user/test_programme_user.py b/backend/selenium_tests/programme_user/test_programme_user.py index 95ec1917d9..e8e93ef5e8 100644 --- a/backend/selenium_tests/programme_user/test_programme_user.py +++ b/backend/selenium_tests/programme_user/test_programme_user.py @@ -20,7 +20,7 @@ def test_smoke_programme_users( test_program: Program, pageProgrammeUsers: ProgrammeUsers, ) -> None: - pageProgrammeUsers.selectGlobalProgramFilter("Test Program").click() + pageProgrammeUsers.selectGlobalProgramFilter("Test Program") pageProgrammeUsers.getNavProgrammeUsers().click() assert "Users List" in pageProgrammeUsers.getTableTitle().text assert "Name" in pageProgrammeUsers.getTableLabel()[1].text diff --git a/backend/selenium_tests/registration_data_import/test_registration_data_import.py b/backend/selenium_tests/registration_data_import/test_registration_data_import.py index 0e3b911943..dbd5424b75 100644 --- a/backend/selenium_tests/registration_data_import/test_registration_data_import.py +++ b/backend/selenium_tests/registration_data_import/test_registration_data_import.py @@ -85,7 +85,7 @@ def test_smoke_registration_data_import( self, create_programs: None, add_rdi: None, pageRegistrationDataImport: RegistrationDataImport ) -> None: # Go to Registration Data Import - pageRegistrationDataImport.selectGlobalProgramFilter("Test Programm").click() + pageRegistrationDataImport.selectGlobalProgramFilter("Test Programm") pageRegistrationDataImport.getNavRegistrationDataImport().click() # Check Elements on Page assert pageRegistrationDataImport.titleText in pageRegistrationDataImport.getPageHeaderTitle().text @@ -105,7 +105,7 @@ def test_smoke_registration_data_import_select_file( self, create_programs: None, pageRegistrationDataImport: RegistrationDataImport ) -> None: # Go to Registration Data Import - pageRegistrationDataImport.selectGlobalProgramFilter("Test Programm").click() + pageRegistrationDataImport.selectGlobalProgramFilter("Test Programm") pageRegistrationDataImport.getNavRegistrationDataImport().click() assert pageRegistrationDataImport.titleText in pageRegistrationDataImport.getPageHeaderTitle().text pageRegistrationDataImport.getButtonImport().click() @@ -128,7 +128,7 @@ def test_smoke_registration_data_details_page( pageDetailsRegistrationDataImport: RDIDetailsPage, ) -> None: # Go to Registration Data Import - pageRegistrationDataImport.selectGlobalProgramFilter("Test Programm").click() + pageRegistrationDataImport.selectGlobalProgramFilter("Test Programm") pageRegistrationDataImport.getNavRegistrationDataImport().click() assert pageRegistrationDataImport.expectedRows(2) assert "2" in pageRegistrationDataImport.getTableTitle().text @@ -173,7 +173,7 @@ def test_registration_data_import_happy_path( pageHouseholdsDetails: HouseholdsDetails, ) -> None: # Go to Registration Data Import - pageRegistrationDataImport.selectGlobalProgramFilter("Test Programm").click() + pageRegistrationDataImport.selectGlobalProgramFilter("Test Programm") pageRegistrationDataImport.getNavRegistrationDataImport().click() assert pageRegistrationDataImport.titleText in pageRegistrationDataImport.getPageHeaderTitle().text pageRegistrationDataImport.getButtonImport().click() @@ -208,7 +208,7 @@ def test_import_empty_kobo_form( self, login: None, create_programs: None, pageRegistrationDataImport: RegistrationDataImport, kobo_setup: None ) -> None: # Go to Registration Data Import - pageRegistrationDataImport.selectGlobalProgramFilter("Test Programm").click() + pageRegistrationDataImport.selectGlobalProgramFilter("Test Programm") pageRegistrationDataImport.getNavRegistrationDataImport().click() assert pageRegistrationDataImport.titleText in pageRegistrationDataImport.getPageHeaderTitle().text pageRegistrationDataImport.getButtonImport().click() @@ -220,7 +220,7 @@ def test_import_empty_kobo_form( pageRegistrationDataImport.getInputName().send_keys("Test 1234 !") pageRegistrationDataImport.getKoboProjectSelect().click() - pageRegistrationDataImport.select_listbox_element("Education new programme").click() + pageRegistrationDataImport.select_listbox_element("Education new programme") assert pageRegistrationDataImport.buttonImportFileIsEnabled(timeout=300) assert "0" in pageRegistrationDataImport.getNumberOfHouseholds().text @@ -240,7 +240,7 @@ def test_import_kobo_form( areas: None, ) -> None: # Go to Registration Data Import - pageRegistrationDataImport.selectGlobalProgramFilter("Test Programm").click() + pageRegistrationDataImport.selectGlobalProgramFilter("Test Programm") pageRegistrationDataImport.getNavRegistrationDataImport().click() assert pageRegistrationDataImport.titleText in pageRegistrationDataImport.getPageHeaderTitle().text pageRegistrationDataImport.getButtonImport().click() @@ -252,7 +252,7 @@ def test_import_kobo_form( pageRegistrationDataImport.getInputName().send_keys("Test 1234 !") pageRegistrationDataImport.getKoboProjectSelect().click() - pageRegistrationDataImport.select_listbox_element("UNICEF NGA Education").click() + pageRegistrationDataImport.select_listbox_element("UNICEF NGA Education") assert pageRegistrationDataImport.buttonImportFileIsEnabled(timeout=300) assert "1" in pageRegistrationDataImport.getNumberOfHouseholds().text diff --git a/backend/selenium_tests/targeting/test_targeting.py b/backend/selenium_tests/targeting/test_targeting.py index ac0649f7df..7485dcc1bb 100644 --- a/backend/selenium_tests/targeting/test_targeting.py +++ b/backend/selenium_tests/targeting/test_targeting.py @@ -138,7 +138,7 @@ def add_targeting() -> None: @pytest.mark.usefixtures("login") class TestSmokeTargeting: def test_smoke_targeting_page(self, create_programs: None, add_targeting: None, pageTargeting: Targeting) -> None: - pageTargeting.selectGlobalProgramFilter("Test Programm").click() + pageTargeting.selectGlobalProgramFilter("Test Programm") pageTargeting.getNavTargeting().click() assert "Targeting" in pageTargeting.getTitlePage().text assert "CREATE NEW" in pageTargeting.getButtonCreateNew().text @@ -152,7 +152,7 @@ def test_smoke_targeting_page(self, create_programs: None, add_targeting: None, def test_smoke_targeting_create_use_filters( self, create_programs: None, add_targeting: None, pageTargeting: Targeting, pageTargetingCreate: TargetingCreate ) -> None: - pageTargeting.selectGlobalProgramFilter("Test Programm").click() + pageTargeting.selectGlobalProgramFilter("Test Programm") pageTargeting.getNavTargeting().click() pageTargeting.getButtonCreateNew().click() pageTargeting.getCreateUseFilters().click() @@ -167,7 +167,7 @@ def test_smoke_targeting_create_use_filters( def test_smoke_targeting_create_use_ids( self, create_programs: None, add_targeting: None, pageTargeting: Targeting, pageTargetingCreate: TargetingCreate ) -> None: - pageTargeting.selectGlobalProgramFilter("Test Programm").click() + pageTargeting.selectGlobalProgramFilter("Test Programm") pageTargeting.getNavTargeting().click() pageTargeting.getButtonCreateNew().click() pageTargeting.getCreateUseIDs().click() @@ -186,7 +186,7 @@ def test_smoke_targeting_details_page( pageTargeting: Targeting, pageTargetingDetails: TargetingDetails, ) -> None: - pageTargeting.selectGlobalProgramFilter("Test Programm").click() + pageTargeting.selectGlobalProgramFilter("Test Programm") pageTargeting.getNavTargeting().click() pageTargeting.chooseTargetPopulations(0).click() assert "Copy TP" in pageTargetingDetails.getPageHeaderTitle().text @@ -315,7 +315,7 @@ def test_targeting_create_use_ids_hh( pageTargetingDetails: TargetingDetails, pageTargetingCreate: TargetingCreate, ) -> None: - pageTargeting.selectGlobalProgramFilter("Test Programm").click() + pageTargeting.selectGlobalProgramFilter("Test Programm") pageTargeting.getNavTargeting().click() pageTargeting.getButtonCreateNew().click() pageTargeting.getCreateUseIDs().click() @@ -347,7 +347,7 @@ def test_targeting_create_use_ids_individual( pageTargetingDetails: TargetingDetails, pageTargetingCreate: TargetingCreate, ) -> None: - pageTargeting.selectGlobalProgramFilter("Test Programm").click() + pageTargeting.selectGlobalProgramFilter("Test Programm") pageTargeting.getNavTargeting().click() pageTargeting.getButtonCreateNew().click() pageTargeting.getCreateUseIDs().click() @@ -377,7 +377,7 @@ def test_targeting_rebuild( pageTargetingDetails: TargetingDetails, pageTargetingCreate: TargetingCreate, ) -> None: - pageTargeting.selectGlobalProgramFilter("Test Programm").click() + pageTargeting.selectGlobalProgramFilter("Test Programm") pageTargeting.getNavTargeting().click() pageTargeting.chooseTargetPopulations(0).click() pageTargetingDetails.getLabelStatus() @@ -395,7 +395,7 @@ def test_targeting_mark_ready( pageTargetingDetails: TargetingDetails, pageTargetingCreate: TargetingCreate, ) -> None: - pageTargeting.selectGlobalProgramFilter("Test Programm").click() + pageTargeting.selectGlobalProgramFilter("Test Programm") pageTargeting.getNavTargeting().click() filters.selectFiltersSatus("OPEN") pageTargeting.chooseTargetPopulations(0).click() @@ -417,7 +417,7 @@ def test_copy_targeting( pageTargetingCreate: TargetingCreate, ) -> None: program = Program.objects.get(name="Test Programm") - pageTargeting.selectGlobalProgramFilter(program.name).click() + pageTargeting.selectGlobalProgramFilter(program.name) pageTargeting.getNavTargeting().click() pageTargeting.chooseTargetPopulations(0).click() pageTargetingDetails.getButtonTargetPopulationDuplicate().click() @@ -440,7 +440,7 @@ def test_edit_targeting( pageTargetingDetails: TargetingDetails, pageTargetingCreate: TargetingCreate, ) -> None: - pageTargeting.selectGlobalProgramFilter("Test Programm").click() + pageTargeting.selectGlobalProgramFilter("Test Programm") pageTargeting.getNavTargeting().click() pageTargeting.chooseTargetPopulations(0).click() pageTargetingDetails.getButtonEdit().click() @@ -465,7 +465,7 @@ def test_delete_targeting( pageTargeting: Targeting, pageTargetingDetails: TargetingDetails, ) -> None: - pageTargeting.selectGlobalProgramFilter("Test Programm").click() + pageTargeting.selectGlobalProgramFilter("Test Programm") pageTargeting.getNavTargeting().click() pageTargeting.disappearLoadingRows() old_list = pageTargeting.getTargetPopulationsRows() @@ -492,7 +492,7 @@ def test_targeting_different_program_statuses( program = Program.objects.get(name="Test Programm") program.status = Program.DRAFT program.save() - pageTargeting.selectGlobalProgramFilter("Test Programm").click() + pageTargeting.selectGlobalProgramFilter("Test Programm") pageTargeting.getNavTargeting().click() pageTargeting.mouse_on_element(pageTargeting.getButtonInactiveCreateNew()) assert "Program has to be active to create a new Target Population" in pageTargeting.geTooltip().text @@ -538,7 +538,7 @@ def test_exclude_households_with_active_adjudication_ticket( program = Program.objects.get(name="Test Programm") program.data_collecting_type.type = test_data["type"] program.data_collecting_type.save() - pageTargeting.selectGlobalProgramFilter("Test Programm").click() + pageTargeting.selectGlobalProgramFilter("Test Programm") pageTargeting.getNavTargeting().click() pageTargeting.getButtonCreateNew().click() pageTargeting.getCreateUseIDs().click() @@ -606,7 +606,7 @@ def test_exclude_households_with_sanction_screen_flag( program = Program.objects.get(name="Test Programm") program.data_collecting_type.type = test_data["type"] program.data_collecting_type.save() - pageTargeting.selectGlobalProgramFilter("Test Programm").click() + pageTargeting.selectGlobalProgramFilter("Test Programm") pageTargeting.getNavTargeting().click() pageTargeting.getButtonCreateNew().click() pageTargeting.getCreateUseIDs().click() @@ -633,7 +633,7 @@ def test_targeting_info_button( create_programs: None, pageTargeting: Targeting, ) -> None: - pageTargeting.selectGlobalProgramFilter("Test Programm").click() + pageTargeting.selectGlobalProgramFilter("Test Programm") pageTargeting.getNavTargeting().click() pageTargeting.getButtonTargetPopulation().click() pageTargeting.getTabFieldList() @@ -647,7 +647,7 @@ def test_targeting_filters( pageTargeting: Targeting, filters: Filters, ) -> None: - pageTargeting.selectGlobalProgramFilter("Test Programm").click() + pageTargeting.selectGlobalProgramFilter("Test Programm") pageTargeting.getNavTargeting().click() filters.getFiltersSearch().send_keys("Copy") filters.getButtonFiltersApply().click() @@ -655,7 +655,7 @@ def test_targeting_filters( assert "OPEN" in pageTargeting.getStatusContainer().text filters.getButtonFiltersClear().click() filters.getFiltersStatus().click() - filters.select_listbox_element("Open").click() + filters.select_listbox_element("Open") filters.getButtonFiltersApply().click() pageTargeting.countTargetPopulations(1) assert "OPEN" in pageTargeting.getStatusContainer().text @@ -678,7 +678,7 @@ def test_targeting_and_labels( add_targeting: None, pageTargeting: Targeting, ) -> None: - pageTargeting.selectGlobalProgramFilter("Test Programm").click() + pageTargeting.selectGlobalProgramFilter("Test Programm") pageTargeting.getNavTargeting().click() pageTargeting.getColumnName().click() pageTargeting.disappearLoadingRows() @@ -720,7 +720,7 @@ def test_targeting_parametrized_rules_filters( pageTargetingDetails: TargetingDetails, pageTargetingCreate: TargetingCreate, ) -> None: - pageTargeting.selectGlobalProgramFilter("Test Programm").click() + pageTargeting.selectGlobalProgramFilter("Test Programm") pageTargeting.getNavTargeting().click() pageTargeting.getButtonCreateNew().click() pageTargeting.getButtonCreateNewByFilters().click() @@ -728,7 +728,7 @@ def test_targeting_parametrized_rules_filters( pageTargetingCreate.getAddCriteriaButton().click() pageTargetingCreate.getAddPeopleRuleButton().click() pageTargetingCreate.getTargetingCriteriaAutoComplete().click() - pageTargetingCreate.select_listbox_element("Females Age 0 - 5").click() + pageTargetingCreate.select_listbox_element("Females Age 0 - 5") pageTargetingCreate.getInputFiltersValueFrom(0).send_keys("0") pageTargetingCreate.getInputFiltersValueTo(0).send_keys("1") pageTargetingCreate.getInputFiltersValueTo(0).send_keys("1") @@ -748,7 +748,7 @@ def test_targeting_parametrized_rules_filters_and_or( pageTargetingDetails: TargetingDetails, pageTargetingCreate: TargetingCreate, ) -> None: - pageTargeting.selectGlobalProgramFilter("Test Programm").click() + pageTargeting.selectGlobalProgramFilter("Test Programm") pageTargeting.getNavTargeting().click() pageTargeting.getButtonCreateNew().click() pageTargeting.getButtonCreateNewByFilters().click() @@ -756,16 +756,16 @@ def test_targeting_parametrized_rules_filters_and_or( pageTargetingCreate.getAddCriteriaButton().click() pageTargetingCreate.getAddPeopleRuleButton().click() pageTargetingCreate.getTargetingCriteriaAutoComplete().click() - pageTargetingCreate.select_listbox_element("Females Age 0 - 5").click() + pageTargetingCreate.select_listbox_element("Females Age 0 - 5") pageTargetingCreate.getInputFiltersValueFrom(0).send_keys("0") pageTargetingCreate.getInputFiltersValueTo(0).send_keys("1") pageTargetingCreate.getButtonHouseholdRule().click() pageTargetingCreate.getTargetingCriteriaAutoComplete(1).click() - pageTargetingCreate.select_listbox_element("Village").click() + pageTargetingCreate.select_listbox_element("Village") pageTargetingCreate.getInputFiltersValue(1).send_keys("Testtown") pageTargetingCreate.getButtonIndividualRule().click() pageTargetingCreate.getTargetingCriteriaAutoCompleteIndividual().click() - pageTargetingCreate.select_listbox_element("Does the Individual have disability?").click() + pageTargetingCreate.select_listbox_element("Does the Individual have disability?") pageTargetingCreate.getSelectMany().click() pageTargetingCreate.select_multiple_option_by_name(HEARING, SEEING) pageTargetingCreate.getTargetingCriteriaAddDialogSaveButton().click() @@ -783,7 +783,7 @@ def test_targeting_parametrized_rules_filters_and_or( pageTargetingCreate.getTargetingCriteriaAddDialogSaveButton().click() pageTargetingCreate.getAddHouseholdRuleButton().click() pageTargetingCreate.getTargetingCriteriaAutoComplete().click() - pageTargetingCreate.select_listbox_element("Males age 0 - 5 with disability").click() + pageTargetingCreate.select_listbox_element("Males age 0 - 5 with disability") pageTargetingCreate.getInputFiltersValueFrom(0).send_keys("1") pageTargetingCreate.getInputFiltersValueTo(0).send_keys("10") pageTargetingCreate.get_elements(pageTargetingCreate.targetingCriteriaAddDialogSaveButton)[1].click() diff --git a/backend/selenium_tests/tools/tag_name_finder.py b/backend/selenium_tests/tools/tag_name_finder.py index d105d21d8b..8e89adb8ed 100644 --- a/backend/selenium_tests/tools/tag_name_finder.py +++ b/backend/selenium_tests/tools/tag_name_finder.py @@ -20,7 +20,7 @@ def printing(what: str, web_driver: WebDriver, label: str = "data-cy", page_obje ids = web_driver.find_elements(By.XPATH, f"//*[@{label}]") for ii in ids: data_cy_attribute = ii.get_attribute(label) # type: ignore - var_name = [i.capitalize() for i in data_cy_attribute.lower().replace("-", " ").split(" ")] + var_name = [i.capitalize() for i in data_cy_attribute.lower().replace(".", " ").replace("-", " ").split(" ")] method_name = "get" + "".join(var_name) var_name[0] = var_name[0].lower() var_name = "".join(var_name) # type: ignore @@ -32,6 +32,8 @@ def printing(what: str, web_driver: WebDriver, label: str = "data-cy", page_obje print(f"{ii.text}") if what == "Assert": print(f'assert "{ii.text}" in {page_object_str}.{method_name}().text') + if what == "Input": + print(f'{page_object_str}.{method_name}().send_keys("")') if __name__ == "__main__": diff --git a/frontend/src/components/core/ActivityLogTable/ActivityLogTable.tsx b/frontend/src/components/core/ActivityLogTable/ActivityLogTable.tsx index 3bc92da605..cfdf0e519e 100644 --- a/frontend/src/components/core/ActivityLogTable/ActivityLogTable.tsx +++ b/frontend/src/components/core/ActivityLogTable/ActivityLogTable.tsx @@ -97,7 +97,7 @@ export function ActivityLogTable({ {logEntries.map((value) => ( diff --git a/frontend/src/components/core/ActivityLogTable/LogRow.tsx b/frontend/src/components/core/ActivityLogTable/LogRow.tsx index cf00c215eb..c57e590d7c 100644 --- a/frontend/src/components/core/ActivityLogTable/LogRow.tsx +++ b/frontend/src/components/core/ActivityLogTable/LogRow.tsx @@ -49,7 +49,7 @@ export const LogRow = ({ logEntry }: LogRowProps): ReactElement => { const { length } = keys; if (length === 1) { return ( - + {moment(logEntry.timestamp).format('DD MMM YYYY HH:mm')} diff --git a/frontend/src/components/core/PageHeader.tsx b/frontend/src/components/core/PageHeader.tsx index c148e0abf0..c955059b1b 100644 --- a/frontend/src/components/core/PageHeader.tsx +++ b/frontend/src/components/core/PageHeader.tsx @@ -103,7 +103,7 @@ export function PageHeader({ {title} - + {flags} diff --git a/frontend/src/components/grievances/Documentation/NewDocumentationFieldArray.tsx b/frontend/src/components/grievances/Documentation/NewDocumentationFieldArray.tsx index c3099356a2..bcdc770dde 100644 --- a/frontend/src/components/grievances/Documentation/NewDocumentationFieldArray.tsx +++ b/frontend/src/components/grievances/Documentation/NewDocumentationFieldArray.tsx @@ -34,6 +34,7 @@ export function NewDocumentationFieldArray({ ))} diff --git a/frontend/src/components/paymentmodule/CreatePaymentPlan/CreatePaymentPlanHeader/CreatePaymentPlanHeader.test.tsx b/frontend/src/components/paymentmodule/CreatePaymentPlan/CreatePaymentPlanHeader/CreatePaymentPlanHeader.test.tsx deleted file mode 100644 index 3fd618a996..0000000000 --- a/frontend/src/components/paymentmodule/CreatePaymentPlan/CreatePaymentPlanHeader/CreatePaymentPlanHeader.test.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import * as React from 'react'; -import { PERMISSIONS } from '../../../../config/permissions'; -import { render } from '../../../../testUtils/testUtils'; -import { CreatePaymentPlanHeader } from './CreatePaymentPlanHeader'; -import { fakeBaseUrl } from '../../../../../fixtures/core/fakeBaseUrl'; - -describe('components/paymentmodule/CreatePaymentPlanHeader', () => { - it('should render', () => { - const { container } = render( - Promise.resolve()} - baseUrl={fakeBaseUrl} - permissions={[PERMISSIONS.PM_VIEW_LIST]} - loadingCreate={false} - />, - ); - expect(container).toMatchSnapshot(); - }); -}); diff --git a/frontend/src/components/paymentmodule/CreatePaymentPlan/CreatePaymentPlanHeader/CreatePaymentPlanHeader.tsx b/frontend/src/components/paymentmodule/CreatePaymentPlan/CreatePaymentPlanHeader/CreatePaymentPlanHeader.tsx index ee3b47ddc6..dd3c5d58de 100644 --- a/frontend/src/components/paymentmodule/CreatePaymentPlan/CreatePaymentPlanHeader/CreatePaymentPlanHeader.tsx +++ b/frontend/src/components/paymentmodule/CreatePaymentPlan/CreatePaymentPlanHeader/CreatePaymentPlanHeader.tsx @@ -1,31 +1,63 @@ import { Box, Button } from '@mui/material'; -import { Link } from 'react-router-dom'; +import { Link, useParams } from 'react-router-dom'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { hasPermissions, PERMISSIONS } from '../../../../config/permissions'; import { BreadCrumbsItem } from '@core/BreadCrumbs'; import { PageHeader } from '@core/PageHeader'; import { LoadingButton } from '@core/LoadingButton'; +import { decodeIdString } from '@utils/utils'; +import { useQuery } from '@tanstack/react-query'; +import { fetchProgramCycle } from '@api/programCycleApi'; +import { useBaseUrl } from '@hooks/useBaseUrl'; interface CreatePaymentPlanHeaderProps { handleSubmit: () => Promise; - baseUrl: string; permissions: string[]; loadingCreate: boolean; } export function CreatePaymentPlanHeader({ handleSubmit, - baseUrl, permissions, loadingCreate, }: CreatePaymentPlanHeaderProps): React.ReactElement { const { t } = useTranslation(); + const { businessArea, programId } = useBaseUrl(); + const { programCycleId } = useParams(); + + const decodedProgramCycleId = decodeIdString(programCycleId); + + const { data: programCycleData, isLoading: isLoadingProgramCycle } = useQuery( + { + queryKey: [ + 'programCyclesDetails', + businessArea, + programId, + decodedProgramCycleId, + ], + queryFn: async () => { + return fetchProgramCycle( + businessArea, + programId, + decodedProgramCycleId, + ); + }, + }, + ); + + if (isLoadingProgramCycle) { + return null; + } const breadCrumbsItems: BreadCrumbsItem[] = [ { title: t('Payment Module'), - to: `/${baseUrl}/payment-module/`, + to: '../../..', + }, + { + title: `${programCycleData.title} (ID: ${programCycleData.unicef_id})`, + to: '../..', }, ]; @@ -40,7 +72,7 @@ export function CreatePaymentPlanHeader({ > - diff --git a/frontend/src/components/paymentmodule/CreatePaymentPlan/CreatePaymentPlanHeader/__snapshots__/CreatePaymentPlanHeader.test.tsx.snap b/frontend/src/components/paymentmodule/CreatePaymentPlan/CreatePaymentPlanHeader/__snapshots__/CreatePaymentPlanHeader.test.tsx.snap deleted file mode 100644 index 361bf54f8e..0000000000 --- a/frontend/src/components/paymentmodule/CreatePaymentPlan/CreatePaymentPlanHeader/__snapshots__/CreatePaymentPlanHeader.test.tsx.snap +++ /dev/null @@ -1,108 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`components/paymentmodule/CreatePaymentPlanHeader should render 1`] = ` -

-
-
-
- -
-
-
- -
-
-
- New Payment Plan -
-
-
-
-
-
-
- - -
-
-
-
-
-
-
-`; diff --git a/frontend/src/components/paymentmodule/CreatePaymentPlan/PaymentPlanParameters/PaymentPlanParameters.tsx b/frontend/src/components/paymentmodule/CreatePaymentPlan/PaymentPlanParameters/PaymentPlanParameters.tsx index feaa339e7c..b61488708e 100644 --- a/frontend/src/components/paymentmodule/CreatePaymentPlan/PaymentPlanParameters/PaymentPlanParameters.tsx +++ b/frontend/src/components/paymentmodule/CreatePaymentPlan/PaymentPlanParameters/PaymentPlanParameters.tsx @@ -42,51 +42,6 @@ export const PaymentPlanParameters = ({ - - } - dataCy="input-start-date" - tooltip={t( - 'The first day of the period intended to be covered by the payment plan. Note that individuals/households cannot be paid twice from the same programme within this period.', - )} - /> - - - } - dataCy="input-end-date" - tooltip={t( - 'The last day of the period intended to be covered by the payment plan. Note that individuals/households cannot be paid twice from the same programme within this period.', - )} - /> - - - - + + + diff --git a/frontend/src/components/paymentmodule/CreatePaymentPlan/PaymentPlanTargeting/PaymentPlanTargeting.tsx b/frontend/src/components/paymentmodule/CreatePaymentPlan/PaymentPlanTargeting/PaymentPlanTargeting.tsx index 6e4d16f8f7..bfa9c4152c 100644 --- a/frontend/src/components/paymentmodule/CreatePaymentPlan/PaymentPlanTargeting/PaymentPlanTargeting.tsx +++ b/frontend/src/components/paymentmodule/CreatePaymentPlan/PaymentPlanTargeting/PaymentPlanTargeting.tsx @@ -6,7 +6,6 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { FormikSelectField } from '@shared/Formik/FormikSelectField'; import { AllTargetPopulationsQuery } from '@generated/graphql'; -import { GreyText } from '@core/GreyText'; import { LoadingComponent } from '@core/LoadingComponent'; import { OverviewContainer } from '@core/OverviewContainer'; import { Title } from '@core/Title'; @@ -41,11 +40,10 @@ export function PaymentPlanTargeting({ return ( - <Typography variant="h6">{t('Targeting')}</Typography> + <Typography variant="h6">{t('Target Population')}</Typography> - {t('Select Target Population')}
- Targeting + Target Population
-
- Select Target Population -
diff --git a/frontend/src/components/paymentmodule/CreateSetUpFsp/CreateSetUpFspHeader/CreateSetUpFspHeader.tsx b/frontend/src/components/paymentmodule/CreateSetUpFsp/CreateSetUpFspHeader/CreateSetUpFspHeader.tsx index 288e2b1491..8ec718ae8d 100644 --- a/frontend/src/components/paymentmodule/CreateSetUpFsp/CreateSetUpFspHeader/CreateSetUpFspHeader.tsx +++ b/frontend/src/components/paymentmodule/CreateSetUpFsp/CreateSetUpFspHeader/CreateSetUpFspHeader.tsx @@ -16,7 +16,7 @@ export function CreateSetUpFspHeader({ }: CreateSetUpFspHeaderProps): React.ReactElement { const location = useLocation(); const { t } = useTranslation(); - const { id } = useParams(); + const { paymentPlanId } = useParams(); const isFollowUp = location.pathname.indexOf('followup') !== -1; const breadCrumbsItems: BreadCrumbsItem[] = [ @@ -24,7 +24,7 @@ export function CreateSetUpFspHeader({ title: t('Payment Module'), to: `/${baseUrl}/payment-module/${ isFollowUp ? 'followup-payment-plans' : 'payment-plans' - }/${id}`, + }/${paymentPlanId}`, }, ]; diff --git a/frontend/src/components/paymentmodule/CreateSetUpFsp/SetUpFspCore/SetUpFspCore.tsx b/frontend/src/components/paymentmodule/CreateSetUpFsp/SetUpFspCore/SetUpFspCore.tsx index 39605ae1b5..f2b1dd1fbe 100644 --- a/frontend/src/components/paymentmodule/CreateSetUpFsp/SetUpFspCore/SetUpFspCore.tsx +++ b/frontend/src/components/paymentmodule/CreateSetUpFsp/SetUpFspCore/SetUpFspCore.tsx @@ -43,7 +43,7 @@ export const SetUpFspCore = ({ const { baseUrl } = useBaseUrl(); const navigate = useNavigate(); const { t } = useTranslation(); - const { id } = useParams(); + const { paymentPlanId } = useParams(); const location = useLocation(); const { data: deliveryMechanismsData, loading: deliveryMechanismLoading } = @@ -54,7 +54,7 @@ export const SetUpFspCore = ({ const { data: fspsData } = useAvailableFspsForDeliveryMechanismsQuery({ variables: { input: { - paymentPlanId: id, + paymentPlanId, }, }, fetchPolicy: 'network-only', @@ -100,7 +100,7 @@ export const SetUpFspCore = ({ await chooseDeliveryMechanisms({ variables: { input: { - paymentPlanId: id, + paymentPlanId, deliveryMechanisms: mappedDeliveryMechanisms, }, }, @@ -109,14 +109,14 @@ export const SetUpFspCore = ({ query: AvailableFspsForDeliveryMechanismsDocument, variables: { input: { - paymentPlanId: id, + paymentPlanId, }, }, }, { query: PaymentPlanDocument, variables: { - id, + id: paymentPlanId, }, }, ], @@ -148,7 +148,7 @@ export const SetUpFspCore = ({ await assignFspToDeliveryMechanism({ variables: { input: { - paymentPlanId: id, + paymentPlanId, mappings, }, }, @@ -157,7 +157,7 @@ export const SetUpFspCore = ({ navigate( `/${baseUrl}/payment-module/${ isFollowUp ? 'followup-payment-plans' : 'payment-plans' - }/${id}`, + }/${paymentPlanId}`, ); } catch (e) { e.graphQLErrors.map((x) => showMessage(x.message)); @@ -260,7 +260,7 @@ export const SetUpFspCore = ({ step={activeStep} submitForm={submitForm} baseUrl={baseUrl} - paymentPlanId={id} + paymentPlanId={paymentPlanId} handleBackStep={handleBackStep} /> diff --git a/frontend/src/components/paymentmodule/EditFsp/EditFspHeader/EditFspHeader.tsx b/frontend/src/components/paymentmodule/EditFsp/EditFspHeader/EditFspHeader.tsx index b172074a59..bb268d5bbf 100644 --- a/frontend/src/components/paymentmodule/EditFsp/EditFspHeader/EditFspHeader.tsx +++ b/frontend/src/components/paymentmodule/EditFsp/EditFspHeader/EditFspHeader.tsx @@ -22,7 +22,7 @@ export function EditFspHeader({ const breadCrumbsItems: BreadCrumbsItem[] = [ { title: t('Payment Module'), - to: `/${baseUrl}/payment-module/`, + to: `/${baseUrl}/payment-module/payment-plans`, }, ]; @@ -37,7 +37,10 @@ export function EditFspHeader({ > - diff --git a/frontend/src/components/paymentmodule/EditFsp/EditFspHeader/__snapshots__/EditFspHeader.test.tsx.snap b/frontend/src/components/paymentmodule/EditFsp/EditFspHeader/__snapshots__/EditFspHeader.test.tsx.snap index 24ce097a8c..ceb08ba362 100644 --- a/frontend/src/components/paymentmodule/EditFsp/EditFspHeader/__snapshots__/EditFspHeader.test.tsx.snap +++ b/frontend/src/components/paymentmodule/EditFsp/EditFspHeader/__snapshots__/EditFspHeader.test.tsx.snap @@ -40,7 +40,7 @@ exports[`components/paymentmodule/EditFspHeader should render 1`] = ` Payment Module @@ -76,7 +76,7 @@ exports[`components/paymentmodule/EditFspHeader should render 1`] = ` > Cancel diff --git a/frontend/src/components/paymentmodule/EditSetUpFsp/EditSetUpFspHeader/EditSetUpFspHeader.tsx b/frontend/src/components/paymentmodule/EditSetUpFsp/EditSetUpFspHeader/EditSetUpFspHeader.tsx index b9d4988812..748a01e489 100644 --- a/frontend/src/components/paymentmodule/EditSetUpFsp/EditSetUpFspHeader/EditSetUpFspHeader.tsx +++ b/frontend/src/components/paymentmodule/EditSetUpFsp/EditSetUpFspHeader/EditSetUpFspHeader.tsx @@ -16,7 +16,7 @@ export function EditSetUpFspHeader({ const { baseUrl } = useBaseUrl(); const location = useLocation(); const { t } = useTranslation(); - const { id } = useParams(); + const { paymentPlanId } = useParams(); const isFollowUp = location.pathname.indexOf('followup') !== -1; const breadCrumbsItems: BreadCrumbsItem[] = [ @@ -24,7 +24,7 @@ export function EditSetUpFspHeader({ title: t('Payment Module'), to: `/${baseUrl}/payment-module/${ isFollowUp ? 'followup-payment-plans' : 'payment-plans' - }/${id}`, + }/${paymentPlanId}`, }, ]; return ( diff --git a/frontend/src/components/paymentmodule/FollowUpPaymentPlanDetails/FollowUpPaymentPlanDetailsHeader/FollowUpPaymentPlanDetailsHeader.tsx b/frontend/src/components/paymentmodule/FollowUpPaymentPlanDetails/FollowUpPaymentPlanDetailsHeader/FollowUpPaymentPlanDetailsHeader.tsx index f1be0d16b0..9a55422533 100644 --- a/frontend/src/components/paymentmodule/FollowUpPaymentPlanDetails/FollowUpPaymentPlanDetailsHeader/FollowUpPaymentPlanDetailsHeader.tsx +++ b/frontend/src/components/paymentmodule/FollowUpPaymentPlanDetails/FollowUpPaymentPlanDetailsHeader/FollowUpPaymentPlanDetailsHeader.tsx @@ -39,7 +39,7 @@ export function FollowUpPaymentPlanDetailsHeader({ const breadCrumbsItems: BreadCrumbsItem[] = [ { title: t('Payment Module'), - to: `/${baseUrl}/payment-module/`, + to: `/${baseUrl}/payment-module/payment-plans`, }, ]; @@ -74,8 +74,8 @@ export function FollowUpPaymentPlanDetailsHeader({ const canSendToPaymentGateway = hasPermissions(PERMISSIONS.PM_SEND_TO_PAYMENT_GATEWAY, permissions) && paymentPlan.canSendToPaymentGateway; - const canSplit = hasPermissions(PERMISSIONS.PM_SPLIT, permissions) && - paymentPlan.canSplit; + const canSplit = + hasPermissions(PERMISSIONS.PM_SPLIT, permissions) && paymentPlan.canSplit; let buttons: React.ReactElement | null = null; switch (paymentPlan.status) { diff --git a/frontend/src/components/paymentmodule/FspPlanDetails/FspHeader/FspHeader.tsx b/frontend/src/components/paymentmodule/FspPlanDetails/FspHeader/FspHeader.tsx index 2e23c74d5e..8cd6d28137 100644 --- a/frontend/src/components/paymentmodule/FspPlanDetails/FspHeader/FspHeader.tsx +++ b/frontend/src/components/paymentmodule/FspPlanDetails/FspHeader/FspHeader.tsx @@ -21,7 +21,7 @@ export function FspHeader({ const breadCrumbsItems: BreadCrumbsItem[] = [ { title: t('Payment Module'), - to: `/${baseUrl}/payment-module/`, + to: `/${baseUrl}/payment-module/payment-plans`, }, ]; diff --git a/frontend/src/components/paymentmodule/FspPlanDetails/FspHeader/__snapshots__/FspHeader.test.tsx.snap b/frontend/src/components/paymentmodule/FspPlanDetails/FspHeader/__snapshots__/FspHeader.test.tsx.snap index 7eb375351f..e514eeaeeb 100644 --- a/frontend/src/components/paymentmodule/FspPlanDetails/FspHeader/__snapshots__/FspHeader.test.tsx.snap +++ b/frontend/src/components/paymentmodule/FspPlanDetails/FspHeader/__snapshots__/FspHeader.test.tsx.snap @@ -40,7 +40,7 @@ exports[`components/paymentmodule/FspPlanDetails/FspHeader should render 1`] = ` Payment Module diff --git a/frontend/src/components/paymentmodule/PaymentPlanDetails/FspSection/FspSection.tsx b/frontend/src/components/paymentmodule/PaymentPlanDetails/FspSection/FspSection.tsx index f860d4bccc..8609347efe 100644 --- a/frontend/src/components/paymentmodule/PaymentPlanDetails/FspSection/FspSection.tsx +++ b/frontend/src/components/paymentmodule/PaymentPlanDetails/FspSection/FspSection.tsx @@ -19,7 +19,7 @@ export const FspSection = ({ paymentPlan, }: FspSectionProps): React.ReactElement => { const { t } = useTranslation(); - const { id } = useParams(); + const { paymentPlanId } = useParams(); const { isActiveProgram } = useProgramContext(); const { deliveryMechanisms, isFollowUp } = paymentPlan; @@ -54,7 +54,7 @@ export const FspSection = ({ component={Link} to={`/${baseUrl}/payment-module/${ isFollowUp ? 'followup-payment-plans' : 'payment-plans' - }/${id}/setup-fsp/edit`} + }/${paymentPlanId}/setup-fsp/edit`} disabled={!isActiveProgram} > {t('Edit FSP')} @@ -100,7 +100,7 @@ export const FspSection = ({ component={Link} to={`/${baseUrl}/payment-module/${ isFollowUp ? 'followup-payment-plans' : 'payment-plans' - }/${id}/setup-fsp/create`} + }/${paymentPlanId}/setup-fsp/create`} > {t('Set up FSP')} diff --git a/frontend/src/components/paymentmodule/PaymentPlanDetails/PaymentPlanDetailsHeader/DeletePaymentPlan.tsx b/frontend/src/components/paymentmodule/PaymentPlanDetails/PaymentPlanDetailsHeader/DeletePaymentPlan.tsx index 5a2f47d07a..ea3f7cfc4a 100644 --- a/frontend/src/components/paymentmodule/PaymentPlanDetails/PaymentPlanDetailsHeader/DeletePaymentPlan.tsx +++ b/frontend/src/components/paymentmodule/PaymentPlanDetails/PaymentPlanDetailsHeader/DeletePaymentPlan.tsx @@ -45,7 +45,7 @@ export function DeletePaymentPlan({ }, }); showMessage(t('Payment Plan Deleted')); - navigate(`/${baseUrl}/payment-module`); + navigate(`/${baseUrl}/payment-module/payment-plans`); } catch (e) { e.graphQLErrors.map((x) => showMessage(x.message)); } diff --git a/frontend/src/components/paymentmodule/PaymentPlanDetails/PaymentPlanDetailsHeader/HeaderButtons/OpenPaymentPlanHeaderButtons.tsx b/frontend/src/components/paymentmodule/PaymentPlanDetails/PaymentPlanDetailsHeader/HeaderButtons/OpenPaymentPlanHeaderButtons.tsx index 7927f5e2d1..ff28239f38 100644 --- a/frontend/src/components/paymentmodule/PaymentPlanDetails/PaymentPlanDetailsHeader/HeaderButtons/OpenPaymentPlanHeaderButtons.tsx +++ b/frontend/src/components/paymentmodule/PaymentPlanDetails/PaymentPlanDetailsHeader/HeaderButtons/OpenPaymentPlanHeaderButtons.tsx @@ -6,7 +6,6 @@ import { Link } from 'react-router-dom'; import { PaymentPlanQuery } from '@generated/graphql'; import { DeletePaymentPlan } from '../DeletePaymentPlan'; import { LockPaymentPlan } from '../LockPaymentPlan'; -import { useBaseUrl } from '@hooks/useBaseUrl'; import { useProgramContext } from '../../../../../programContext'; export interface OpenPaymentPlanHeaderButtonsProps { @@ -23,9 +22,7 @@ export function OpenPaymentPlanHeaderButtons({ canLock, }: OpenPaymentPlanHeaderButtonsProps): React.ReactElement { const { t } = useTranslation(); - const { baseUrl } = useBaseUrl(); const { isActiveProgram } = useProgramContext(); - const { id, isFollowUp } = paymentPlan; return ( @@ -37,9 +34,7 @@ export function OpenPaymentPlanHeaderButtons({ color="primary" startIcon={} component={Link} - to={`/${baseUrl}/payment-module/${ - isFollowUp ? 'followup-payment-plans' : 'payment-plans' - }/${id}/edit`} + to={'./edit'} disabled={!isActiveProgram} > {t('Edit')} diff --git a/frontend/src/components/paymentmodule/PaymentPlanDetails/PaymentPlanDetailsHeader/HeaderButtons/__snapshots__/OpenPaymentPlanHeaderButtons.test.tsx.snap b/frontend/src/components/paymentmodule/PaymentPlanDetails/PaymentPlanDetailsHeader/HeaderButtons/__snapshots__/OpenPaymentPlanHeaderButtons.test.tsx.snap index 2653fdf16b..f170db72af 100644 --- a/frontend/src/components/paymentmodule/PaymentPlanDetails/PaymentPlanDetailsHeader/HeaderButtons/__snapshots__/OpenPaymentPlanHeaderButtons.test.tsx.snap +++ b/frontend/src/components/paymentmodule/PaymentPlanDetails/PaymentPlanDetailsHeader/HeaderButtons/__snapshots__/OpenPaymentPlanHeaderButtons.test.tsx.snap @@ -34,7 +34,7 @@ exports[`components/paymentmodule/PaymentPlanDetails/PaymentPlanDetailsHeader/He > - diff --git a/frontend/src/components/paymentmodulepeople/CreatePaymentPlan/CreatePaymentPlanHeader/__snapshots__/CreatePaymentPlanHeader.test.tsx.snap b/frontend/src/components/paymentmodulepeople/CreatePaymentPlan/CreatePaymentPlanHeader/__snapshots__/CreatePaymentPlanHeader.test.tsx.snap index 361bf54f8e..047f5ed431 100644 --- a/frontend/src/components/paymentmodulepeople/CreatePaymentPlan/CreatePaymentPlanHeader/__snapshots__/CreatePaymentPlanHeader.test.tsx.snap +++ b/frontend/src/components/paymentmodulepeople/CreatePaymentPlan/CreatePaymentPlanHeader/__snapshots__/CreatePaymentPlanHeader.test.tsx.snap @@ -40,7 +40,7 @@ exports[`components/paymentmodule/CreatePaymentPlanHeader should render 1`] = ` Payment Module @@ -76,7 +76,7 @@ exports[`components/paymentmodule/CreatePaymentPlanHeader should render 1`] = ` > Cancel diff --git a/frontend/src/components/paymentmodulepeople/CreatePaymentPlan/PaymentPlanTargeting/PaymentPlanTargeting.tsx b/frontend/src/components/paymentmodulepeople/CreatePaymentPlan/PaymentPlanTargeting/PaymentPlanTargeting.tsx index 6e4d16f8f7..bfa9c4152c 100644 --- a/frontend/src/components/paymentmodulepeople/CreatePaymentPlan/PaymentPlanTargeting/PaymentPlanTargeting.tsx +++ b/frontend/src/components/paymentmodulepeople/CreatePaymentPlan/PaymentPlanTargeting/PaymentPlanTargeting.tsx @@ -6,7 +6,6 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { FormikSelectField } from '@shared/Formik/FormikSelectField'; import { AllTargetPopulationsQuery } from '@generated/graphql'; -import { GreyText } from '@core/GreyText'; import { LoadingComponent } from '@core/LoadingComponent'; import { OverviewContainer } from '@core/OverviewContainer'; import { Title } from '@core/Title'; @@ -41,11 +40,10 @@ export function PaymentPlanTargeting({ return ( - <Typography variant="h6">{t('Targeting')}</Typography> + <Typography variant="h6">{t('Target Population')}</Typography> - {t('Select Target Population')}
- Targeting + Target Population
-
- Select Target Population -
diff --git a/frontend/src/components/paymentmodulepeople/CreateSetUpFsp/CreateSetUpFspHeader/CreateSetUpFspHeader.tsx b/frontend/src/components/paymentmodulepeople/CreateSetUpFsp/CreateSetUpFspHeader/CreateSetUpFspHeader.tsx index 288e2b1491..8ec718ae8d 100644 --- a/frontend/src/components/paymentmodulepeople/CreateSetUpFsp/CreateSetUpFspHeader/CreateSetUpFspHeader.tsx +++ b/frontend/src/components/paymentmodulepeople/CreateSetUpFsp/CreateSetUpFspHeader/CreateSetUpFspHeader.tsx @@ -16,7 +16,7 @@ export function CreateSetUpFspHeader({ }: CreateSetUpFspHeaderProps): React.ReactElement { const location = useLocation(); const { t } = useTranslation(); - const { id } = useParams(); + const { paymentPlanId } = useParams(); const isFollowUp = location.pathname.indexOf('followup') !== -1; const breadCrumbsItems: BreadCrumbsItem[] = [ @@ -24,7 +24,7 @@ export function CreateSetUpFspHeader({ title: t('Payment Module'), to: `/${baseUrl}/payment-module/${ isFollowUp ? 'followup-payment-plans' : 'payment-plans' - }/${id}`, + }/${paymentPlanId}`, }, ]; diff --git a/frontend/src/components/paymentmodulepeople/CreateSetUpFsp/SetUpFspCore/SetUpFspCore.tsx b/frontend/src/components/paymentmodulepeople/CreateSetUpFsp/SetUpFspCore/SetUpFspCore.tsx index 39605ae1b5..f2b1dd1fbe 100644 --- a/frontend/src/components/paymentmodulepeople/CreateSetUpFsp/SetUpFspCore/SetUpFspCore.tsx +++ b/frontend/src/components/paymentmodulepeople/CreateSetUpFsp/SetUpFspCore/SetUpFspCore.tsx @@ -43,7 +43,7 @@ export const SetUpFspCore = ({ const { baseUrl } = useBaseUrl(); const navigate = useNavigate(); const { t } = useTranslation(); - const { id } = useParams(); + const { paymentPlanId } = useParams(); const location = useLocation(); const { data: deliveryMechanismsData, loading: deliveryMechanismLoading } = @@ -54,7 +54,7 @@ export const SetUpFspCore = ({ const { data: fspsData } = useAvailableFspsForDeliveryMechanismsQuery({ variables: { input: { - paymentPlanId: id, + paymentPlanId, }, }, fetchPolicy: 'network-only', @@ -100,7 +100,7 @@ export const SetUpFspCore = ({ await chooseDeliveryMechanisms({ variables: { input: { - paymentPlanId: id, + paymentPlanId, deliveryMechanisms: mappedDeliveryMechanisms, }, }, @@ -109,14 +109,14 @@ export const SetUpFspCore = ({ query: AvailableFspsForDeliveryMechanismsDocument, variables: { input: { - paymentPlanId: id, + paymentPlanId, }, }, }, { query: PaymentPlanDocument, variables: { - id, + id: paymentPlanId, }, }, ], @@ -148,7 +148,7 @@ export const SetUpFspCore = ({ await assignFspToDeliveryMechanism({ variables: { input: { - paymentPlanId: id, + paymentPlanId, mappings, }, }, @@ -157,7 +157,7 @@ export const SetUpFspCore = ({ navigate( `/${baseUrl}/payment-module/${ isFollowUp ? 'followup-payment-plans' : 'payment-plans' - }/${id}`, + }/${paymentPlanId}`, ); } catch (e) { e.graphQLErrors.map((x) => showMessage(x.message)); @@ -260,7 +260,7 @@ export const SetUpFspCore = ({ step={activeStep} submitForm={submitForm} baseUrl={baseUrl} - paymentPlanId={id} + paymentPlanId={paymentPlanId} handleBackStep={handleBackStep} /> diff --git a/frontend/src/components/paymentmodulepeople/EditFsp/EditFspHeader/EditFspHeader.tsx b/frontend/src/components/paymentmodulepeople/EditFsp/EditFspHeader/EditFspHeader.tsx index b172074a59..bb268d5bbf 100644 --- a/frontend/src/components/paymentmodulepeople/EditFsp/EditFspHeader/EditFspHeader.tsx +++ b/frontend/src/components/paymentmodulepeople/EditFsp/EditFspHeader/EditFspHeader.tsx @@ -22,7 +22,7 @@ export function EditFspHeader({ const breadCrumbsItems: BreadCrumbsItem[] = [ { title: t('Payment Module'), - to: `/${baseUrl}/payment-module/`, + to: `/${baseUrl}/payment-module/payment-plans`, }, ]; @@ -37,7 +37,10 @@ export function EditFspHeader({ > - diff --git a/frontend/src/components/paymentmodulepeople/EditFsp/EditFspHeader/__snapshots__/EditFspHeader.test.tsx.snap b/frontend/src/components/paymentmodulepeople/EditFsp/EditFspHeader/__snapshots__/EditFspHeader.test.tsx.snap index 24ce097a8c..ceb08ba362 100644 --- a/frontend/src/components/paymentmodulepeople/EditFsp/EditFspHeader/__snapshots__/EditFspHeader.test.tsx.snap +++ b/frontend/src/components/paymentmodulepeople/EditFsp/EditFspHeader/__snapshots__/EditFspHeader.test.tsx.snap @@ -40,7 +40,7 @@ exports[`components/paymentmodule/EditFspHeader should render 1`] = ` Payment Module @@ -76,7 +76,7 @@ exports[`components/paymentmodule/EditFspHeader should render 1`] = ` > Cancel diff --git a/frontend/src/components/paymentmodulepeople/EditSetUpFsp/EditSetUpFspHeader/EditSetUpFspHeader.tsx b/frontend/src/components/paymentmodulepeople/EditSetUpFsp/EditSetUpFspHeader/EditSetUpFspHeader.tsx index b9d4988812..748a01e489 100644 --- a/frontend/src/components/paymentmodulepeople/EditSetUpFsp/EditSetUpFspHeader/EditSetUpFspHeader.tsx +++ b/frontend/src/components/paymentmodulepeople/EditSetUpFsp/EditSetUpFspHeader/EditSetUpFspHeader.tsx @@ -16,7 +16,7 @@ export function EditSetUpFspHeader({ const { baseUrl } = useBaseUrl(); const location = useLocation(); const { t } = useTranslation(); - const { id } = useParams(); + const { paymentPlanId } = useParams(); const isFollowUp = location.pathname.indexOf('followup') !== -1; const breadCrumbsItems: BreadCrumbsItem[] = [ @@ -24,7 +24,7 @@ export function EditSetUpFspHeader({ title: t('Payment Module'), to: `/${baseUrl}/payment-module/${ isFollowUp ? 'followup-payment-plans' : 'payment-plans' - }/${id}`, + }/${paymentPlanId}`, }, ]; return ( diff --git a/frontend/src/components/paymentmodulepeople/FollowUpPaymentPlanDetails/FollowUpPaymentPlanDetailsHeader/FollowUpPaymentPlanDetailsHeader.tsx b/frontend/src/components/paymentmodulepeople/FollowUpPaymentPlanDetails/FollowUpPaymentPlanDetailsHeader/FollowUpPaymentPlanDetailsHeader.tsx index 1d9ec7d830..9a55422533 100644 --- a/frontend/src/components/paymentmodulepeople/FollowUpPaymentPlanDetails/FollowUpPaymentPlanDetailsHeader/FollowUpPaymentPlanDetailsHeader.tsx +++ b/frontend/src/components/paymentmodulepeople/FollowUpPaymentPlanDetails/FollowUpPaymentPlanDetailsHeader/FollowUpPaymentPlanDetailsHeader.tsx @@ -39,7 +39,7 @@ export function FollowUpPaymentPlanDetailsHeader({ const breadCrumbsItems: BreadCrumbsItem[] = [ { title: t('Payment Module'), - to: `/${baseUrl}/payment-module/`, + to: `/${baseUrl}/payment-module/payment-plans`, }, ]; diff --git a/frontend/src/components/paymentmodulepeople/FspPlanDetails/FspHeader/FspHeader.tsx b/frontend/src/components/paymentmodulepeople/FspPlanDetails/FspHeader/FspHeader.tsx index 2e23c74d5e..8cd6d28137 100644 --- a/frontend/src/components/paymentmodulepeople/FspPlanDetails/FspHeader/FspHeader.tsx +++ b/frontend/src/components/paymentmodulepeople/FspPlanDetails/FspHeader/FspHeader.tsx @@ -21,7 +21,7 @@ export function FspHeader({ const breadCrumbsItems: BreadCrumbsItem[] = [ { title: t('Payment Module'), - to: `/${baseUrl}/payment-module/`, + to: `/${baseUrl}/payment-module/payment-plans`, }, ]; diff --git a/frontend/src/components/paymentmodulepeople/FspPlanDetails/FspHeader/__snapshots__/FspHeader.test.tsx.snap b/frontend/src/components/paymentmodulepeople/FspPlanDetails/FspHeader/__snapshots__/FspHeader.test.tsx.snap index 7eb375351f..e514eeaeeb 100644 --- a/frontend/src/components/paymentmodulepeople/FspPlanDetails/FspHeader/__snapshots__/FspHeader.test.tsx.snap +++ b/frontend/src/components/paymentmodulepeople/FspPlanDetails/FspHeader/__snapshots__/FspHeader.test.tsx.snap @@ -40,7 +40,7 @@ exports[`components/paymentmodule/FspPlanDetails/FspHeader should render 1`] = ` Payment Module diff --git a/frontend/src/components/paymentmodulepeople/PaymentPlanDetails/FspSection/FspSection.tsx b/frontend/src/components/paymentmodulepeople/PaymentPlanDetails/FspSection/FspSection.tsx index 6c90ee58d9..1714d4b422 100644 --- a/frontend/src/components/paymentmodulepeople/PaymentPlanDetails/FspSection/FspSection.tsx +++ b/frontend/src/components/paymentmodulepeople/PaymentPlanDetails/FspSection/FspSection.tsx @@ -19,7 +19,7 @@ export function FspSection({ paymentPlan, }: FspSectionProps): React.ReactElement { const { t } = useTranslation(); - const { id } = useParams(); + const { paymentPlanId } = useParams(); const { isActiveProgram } = useProgramContext(); const { deliveryMechanisms, isFollowUp } = paymentPlan; @@ -54,7 +54,7 @@ export function FspSection({ component={Link} to={`/${baseUrl}/payment-module/${ isFollowUp ? 'followup-payment-plans' : 'payment-plans' - }/${id}/setup-fsp/edit`} + }/${paymentPlanId}/setup-fsp/edit`} disabled={!isActiveProgram} > {t('Edit FSP')} @@ -100,7 +100,7 @@ export function FspSection({ component={Link} to={`/${baseUrl}/payment-module/${ isFollowUp ? 'followup-payment-plans' : 'payment-plans' - }/${id}/setup-fsp/create`} + }/${paymentPlanId}/setup-fsp/create`} > {t('Set up FSP')} diff --git a/frontend/src/components/paymentmodulepeople/PaymentPlanDetails/PaymentPlanDetailsHeader/DeletePaymentPlan.tsx b/frontend/src/components/paymentmodulepeople/PaymentPlanDetails/PaymentPlanDetailsHeader/DeletePaymentPlan.tsx index 5a2f47d07a..ea3f7cfc4a 100644 --- a/frontend/src/components/paymentmodulepeople/PaymentPlanDetails/PaymentPlanDetailsHeader/DeletePaymentPlan.tsx +++ b/frontend/src/components/paymentmodulepeople/PaymentPlanDetails/PaymentPlanDetailsHeader/DeletePaymentPlan.tsx @@ -45,7 +45,7 @@ export function DeletePaymentPlan({ }, }); showMessage(t('Payment Plan Deleted')); - navigate(`/${baseUrl}/payment-module`); + navigate(`/${baseUrl}/payment-module/payment-plans`); } catch (e) { e.graphQLErrors.map((x) => showMessage(x.message)); } diff --git a/frontend/src/components/paymentmodulepeople/PaymentPlanDetails/PaymentPlanDetailsHeader/PaymentPlanDetailsHeader.tsx b/frontend/src/components/paymentmodulepeople/PaymentPlanDetails/PaymentPlanDetailsHeader/PaymentPlanDetailsHeader.tsx index 6778ffd540..d07825c466 100644 --- a/frontend/src/components/paymentmodulepeople/PaymentPlanDetails/PaymentPlanDetailsHeader/PaymentPlanDetailsHeader.tsx +++ b/frontend/src/components/paymentmodulepeople/PaymentPlanDetails/PaymentPlanDetailsHeader/PaymentPlanDetailsHeader.tsx @@ -34,7 +34,7 @@ export function PaymentPlanDetailsHeader({ const breadCrumbsItems: BreadCrumbsItem[] = [ { title: t('Payment Module'), - to: `/${baseUrl}/payment-module/`, + to: `/${baseUrl}/payment-module/payment-plans`, }, ]; diff --git a/frontend/src/components/targeting/EditTargetPopulation/EditTargetPopulation.tsx b/frontend/src/components/targeting/EditTargetPopulation/EditTargetPopulation.tsx index 6f8dc5dfee..d861f5d8cc 100644 --- a/frontend/src/components/targeting/EditTargetPopulation/EditTargetPopulation.tsx +++ b/frontend/src/components/targeting/EditTargetPopulation/EditTargetPopulation.tsx @@ -21,6 +21,7 @@ import { Exclusions } from '../CreateTargetPopulation/Exclusions'; import { PaperContainer } from '../PaperContainer'; import { TargetingCriteria } from '../TargetingCriteria'; import { EditTargetPopulationHeader } from './EditTargetPopulationHeader'; +import { ProgramCycleAutocompleteRest } from '@shared/autocompletes/rest/ProgramCycleAutocompleteRest'; interface EditTargetPopulationProps { targetPopulation: TargetPopulationQuery['targetPopulation']; @@ -47,6 +48,10 @@ export const EditTargetPopulation = ({ targetPopulation.targetingCriteria.flagExcludeIfOnSanctionList || false, householdIds: targetPopulation.targetingCriteria.householdIds, individualIds: targetPopulation.targetingCriteria.individualIds, + programCycleId: { + value: targetPopulation.programCycle.id, + name: targetPopulation.programCycle.title, + }, }; const [mutate, { loading }] = useUpdateTpMutation(); const { showMessage } = useSnackbar(); @@ -95,6 +100,9 @@ export const EditTargetPopulation = ({ householdIds: idValidation, individualIds: idValidation, exclusionReason: Yup.string().max(500, t('Too long')), + programCycleId: Yup.object().shape({ + value: Yup.string().required('Program Cycle is required'), + }), }); const handleSubmit = async (values): Promise => { @@ -106,6 +114,7 @@ export const EditTargetPopulation = ({ programId: values.program, excludedIds: values.excludedIds, exclusionReason: values.exclusionReason, + programCycleId: values.programCycleId.value, ...(targetPopulation.status === TargetPopulationStatus.Open && { name: values.name, }), @@ -134,7 +143,7 @@ export const EditTargetPopulation = ({ validationSchema={validationSchema} onSubmit={handleSubmit} > - {({ values, submitForm }) => ( + {({ values, submitForm, errors, setFieldValue }) => (
{t('Targeting Criteria')} + + + { + await setFieldValue('programCycleId', e); + }} + required + // @ts-ignore + error={errors.programCycleId?.value} + /> + + {changeDate} @@ -71,6 +77,13 @@ export function TargetPopulationDetails({ value={programName} /> + + + {t('Are you sure you want to activate this Programme?')} +
+ {t( + 'Upon activation of this Programme, default Programme Cycles will be created.', + )}
diff --git a/frontend/src/containers/dialogs/targetPopulation/DuplicateTargetPopulation.tsx b/frontend/src/containers/dialogs/targetPopulation/DuplicateTargetPopulation.tsx index f0f15c1683..4b6cb0e37a 100644 --- a/frontend/src/containers/dialogs/targetPopulation/DuplicateTargetPopulation.tsx +++ b/frontend/src/containers/dialogs/targetPopulation/DuplicateTargetPopulation.tsx @@ -1,4 +1,4 @@ -import { Button, DialogContent, DialogTitle } from '@mui/material'; +import { Button, DialogContent, DialogTitle, Grid } from '@mui/material'; import { Field, Formik } from 'formik'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; @@ -15,9 +15,13 @@ import { DialogFooter } from '../DialogFooter'; import { DialogTitleWrapper } from '../DialogTitleWrapper'; import { useBaseUrl } from '@hooks/useBaseUrl'; import { useNavigate } from 'react-router-dom'; +import { ProgramCycleAutocompleteRest } from '@shared/autocompletes/rest/ProgramCycleAutocompleteRest'; const validationSchema = Yup.object().shape({ name: Yup.string().required('Name is required'), + programCycleId: Yup.object().shape({ + value: Yup.string().required('Program Cycle is required'), + }), }); interface DuplicateTargetPopulationProps { @@ -39,6 +43,10 @@ export const DuplicateTargetPopulation = ({ const initialValues = { name: '', id: targetPopulationId, + programCycleId: { + value: '', + name: '', + }, }; return ( @@ -53,8 +61,11 @@ export const DuplicateTargetPopulation = ({ initialValues={initialValues} onSubmit={async (values) => { try { + const programCycleId = values.programCycleId.value; const res = await mutate({ - variables: { input: { targetPopulationData: { ...values } } }, + variables: { + input: { targetPopulationData: { ...values, programCycleId } }, + }, }); setOpen(false); showMessage(t('Target Population Duplicated')); @@ -66,7 +77,7 @@ export const DuplicateTargetPopulation = ({ } }} > - {({ submitForm }) => ( + {({ submitForm, setFieldValue, values, errors }) => ( <> {open && } @@ -82,14 +93,28 @@ export const DuplicateTargetPopulation = ({ 'This duplicate will copy the Target Criteria of the Programme Population and update to the latest results from the system.', )} - + + + + + + { + await setFieldValue('programCycleId', e); + }} + required + error={errors.programCycleId?.value} + /> + + diff --git a/frontend/src/containers/pages/paymentmodule/EditFollowUpPaymentPlanPage.tsx b/frontend/src/containers/pages/paymentmodule/EditFollowUpPaymentPlanPage.tsx index d085433ef3..536f86ba9d 100644 --- a/frontend/src/containers/pages/paymentmodule/EditFollowUpPaymentPlanPage.tsx +++ b/frontend/src/containers/pages/paymentmodule/EditFollowUpPaymentPlanPage.tsx @@ -23,12 +23,12 @@ import { today } from '@utils/utils'; export const EditFollowUpPaymentPlanPage = (): React.ReactElement => { const navigate = useNavigate(); - const { id } = useParams(); + const { paymentPlanId } = useParams(); const { t } = useTranslation(); const { data: paymentPlanData, loading: loadingPaymentPlan } = usePaymentPlanQuery({ variables: { - id, + id: paymentPlanId, }, fetchPolicy: 'cache-and-network', }); @@ -57,8 +57,6 @@ export const EditFollowUpPaymentPlanPage = (): React.ReactElement => { const initialValues = { targetingId: paymentPlan.targetPopulation.id, - startDate: paymentPlan.startDate, - endDate: paymentPlan.endDate, currency: { name: paymentPlan.currencyName, value: paymentPlan.currency, @@ -70,19 +68,6 @@ export const EditFollowUpPaymentPlanPage = (): React.ReactElement => { const validationSchema = Yup.object().shape({ targetingId: Yup.string().required(t('Target Population is required')), currency: Yup.string().nullable().required(t('Currency is required')), - startDate: Yup.date().required(t('Start Date is required')), - endDate: Yup.date() - .required(t('End Date is required')) - .when('startDate', (startDate: any, schema: Yup.DateSchema) => - startDate - ? schema.min( - startDate, - `${t('End date has to be greater than')} ${moment( - startDate, - ).format('YYYY-MM-DD')}`, - ) - : schema, - ), dispersionStartDate: Yup.date().required( t('Dispersion Start Date is required'), ), @@ -108,10 +93,8 @@ export const EditFollowUpPaymentPlanPage = (): React.ReactElement => { const res = await mutate({ variables: { input: { - paymentPlanId: id, + paymentPlanId, targetingId: values.targetingId, - startDate: values.startDate, - endDate: values.endDate, dispersionStartDate: values.dispersionStartDate, dispersionEndDate: values.dispersionEndDate, currency: values.currency?.value diff --git a/frontend/src/containers/pages/paymentmodule/EditFollowUpSetUpFspPage.tsx b/frontend/src/containers/pages/paymentmodule/EditFollowUpSetUpFspPage.tsx index 1b6c193584..1392fe4259 100644 --- a/frontend/src/containers/pages/paymentmodule/EditFollowUpSetUpFspPage.tsx +++ b/frontend/src/containers/pages/paymentmodule/EditFollowUpSetUpFspPage.tsx @@ -9,12 +9,12 @@ import { usePermissions } from '@hooks/usePermissions'; import { usePaymentPlanQuery } from '@generated/graphql'; export function EditFollowUpSetUpFspPage(): React.ReactElement { - const { id } = useParams(); + const { paymentPlanId } = useParams(); const { data: paymentPlanData, loading: paymentPlanLoading } = usePaymentPlanQuery({ variables: { - id, + id: paymentPlanId, }, fetchPolicy: 'cache-and-network', }); @@ -41,10 +41,7 @@ export function EditFollowUpSetUpFspPage(): React.ReactElement { return ( <> - + ); } diff --git a/frontend/src/containers/pages/paymentmodule/EditPaymentPlanPage.tsx b/frontend/src/containers/pages/paymentmodule/EditPaymentPlanPage.tsx index 2f85fbe837..a6df81cb43 100644 --- a/frontend/src/containers/pages/paymentmodule/EditPaymentPlanPage.tsx +++ b/frontend/src/containers/pages/paymentmodule/EditPaymentPlanPage.tsx @@ -23,12 +23,12 @@ import { useBaseUrl } from '@hooks/useBaseUrl'; export const EditPaymentPlanPage = (): React.ReactElement => { const navigate = useNavigate(); - const { id } = useParams(); + const { paymentPlanId } = useParams(); const { t } = useTranslation(); const { data: paymentPlanData, loading: loadingPaymentPlan } = usePaymentPlanQuery({ variables: { - id, + id: paymentPlanId, }, fetchPolicy: 'cache-and-network', }); @@ -56,8 +56,6 @@ export const EditPaymentPlanPage = (): React.ReactElement => { const initialValues = { targetingId: paymentPlan.targetPopulation.id, - startDate: paymentPlan.startDate, - endDate: paymentPlan.endDate, currency: { name: paymentPlan.currencyName, value: paymentPlan.currency, @@ -68,19 +66,6 @@ export const EditPaymentPlanPage = (): React.ReactElement => { const validationSchema = Yup.object().shape({ targetingId: Yup.string().required(t('Target Population is required')), - startDate: Yup.date(), - endDate: Yup.date() - .required(t('End Date is required')) - .when('startDate', (startDate: any, schema: Yup.DateSchema) => - startDate - ? schema.min( - startDate as Date, - `${t('End date has to be greater than')} ${moment( - startDate, - ).format('YYYY-MM-DD')}`, - ) - : schema, - ), dispersionStartDate: Yup.date().required( t('Dispersion Start Date is required'), ), @@ -106,10 +91,8 @@ export const EditPaymentPlanPage = (): React.ReactElement => { const res = await mutate({ variables: { input: { - paymentPlanId: id, + paymentPlanId: paymentPlanId, targetingId: values.targetingId, - startDate: values.startDate, - endDate: values.endDate, dispersionStartDate: values.dispersionStartDate, dispersionEndDate: values.dispersionEndDate, currency: values.currency?.value diff --git a/frontend/src/containers/pages/paymentmodule/EditSetUpFspPage.tsx b/frontend/src/containers/pages/paymentmodule/EditSetUpFspPage.tsx index ac7a95b91a..380b58f48b 100644 --- a/frontend/src/containers/pages/paymentmodule/EditSetUpFspPage.tsx +++ b/frontend/src/containers/pages/paymentmodule/EditSetUpFspPage.tsx @@ -9,12 +9,12 @@ import { usePermissions } from '@hooks/usePermissions'; import { usePaymentPlanQuery } from '@generated/graphql'; export function EditSetUpFspPage(): React.ReactElement { - const { id } = useParams(); + const { paymentPlanId } = useParams(); const { data: paymentPlanData, loading: paymentPlanLoading } = usePaymentPlanQuery({ variables: { - id, + id: paymentPlanId, }, fetchPolicy: 'cache-and-network', }); diff --git a/frontend/src/containers/pages/paymentmodule/FollowUpPaymentPlanDetailsPage.tsx b/frontend/src/containers/pages/paymentmodule/FollowUpPaymentPlanDetailsPage.tsx index 73e30953eb..e5148971d2 100644 --- a/frontend/src/containers/pages/paymentmodule/FollowUpPaymentPlanDetailsPage.tsx +++ b/frontend/src/containers/pages/paymentmodule/FollowUpPaymentPlanDetailsPage.tsx @@ -20,13 +20,13 @@ import { ExcludeSection } from '@components/paymentmodule/PaymentPlanDetails/Exc import { useBaseUrl } from '@hooks/useBaseUrl'; export function FollowUpPaymentPlanDetailsPage(): React.ReactElement { - const { id } = useParams(); + const { paymentPlanId } = useParams(); const permissions = usePermissions(); const { baseUrl, businessArea } = useBaseUrl(); const { data, loading, startPolling, stopPolling, error } = usePaymentPlanQuery({ variables: { - id, + id: paymentPlanId, }, fetchPolicy: 'cache-and-network', }); diff --git a/frontend/src/containers/pages/paymentmodule/PaymentDetailsPage.tsx b/frontend/src/containers/pages/paymentmodule/PaymentDetailsPage.tsx index 3789c84f0d..44260ce93d 100644 --- a/frontend/src/containers/pages/paymentmodule/PaymentDetailsPage.tsx +++ b/frontend/src/containers/pages/paymentmodule/PaymentDetailsPage.tsx @@ -22,12 +22,12 @@ import { AdminButton } from '@core/AdminButton'; export function PaymentDetailsPage(): React.ReactElement { const { t } = useTranslation(); - const { id } = useParams(); + const { paymentPlanId } = useParams(); const { data: caData, loading: caLoading } = useCashAssistUrlPrefixQuery({ fetchPolicy: 'cache-first', }); const { data, loading } = usePaymentQuery({ - variables: { id }, + variables: { id: paymentPlanId }, fetchPolicy: 'cache-and-network', }); const paymentPlanStatus = data?.payment?.parent?.status; @@ -44,7 +44,7 @@ export function PaymentDetailsPage(): React.ReactElement { const breadCrumbsItems: BreadCrumbsItem[] = [ { title: t('Payment Module'), - to: `/${baseUrl}/payment-module/`, + to: `/${baseUrl}/payment-module/payment-plans`, }, { title: ` ${paymentPlanIsFollowUp ? 'Follow-up ' : ''} Payment Plan ${ diff --git a/frontend/src/containers/pages/paymentmodule/PaymentModulePage.tsx b/frontend/src/containers/pages/paymentmodule/PaymentModulePage.tsx index 5264cf9d15..71cb4e186a 100644 --- a/frontend/src/containers/pages/paymentmodule/PaymentModulePage.tsx +++ b/frontend/src/containers/pages/paymentmodule/PaymentModulePage.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { useState } from 'react'; -import { Link, useLocation } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { PageHeader } from '@components/core/PageHeader'; import { PermissionDenied } from '@components/core/PermissionDenied'; @@ -10,9 +10,6 @@ import { usePermissions } from '@hooks/usePermissions'; import { getFilterFromQueryParams } from '@utils/utils'; import { PaymentPlansTable } from '../../tables/paymentmodule/PaymentPlansTable'; import { PaymentPlansFilters } from '../../tables/paymentmodule/PaymentPlansTable/PaymentPlansFilters'; -import { useBaseUrl } from '@hooks/useBaseUrl'; -import { ButtonTooltip } from '@components/core/ButtonTooltip'; -import { useProgramContext } from '../../../programContext'; const initialFilter = { search: '', @@ -26,10 +23,8 @@ const initialFilter = { export function PaymentModulePage(): React.ReactElement { const { t } = useTranslation(); - const { baseUrl } = useBaseUrl(); const permissions = usePermissions(); const location = useLocation(); - const { isActiveProgram } = useProgramContext(); const [filter, setFilter] = useState( getFilterFromQueryParams(location, initialFilter), @@ -45,21 +40,7 @@ export function PaymentModulePage(): React.ReactElement { return ( <> - - {hasPermissions(PERMISSIONS.PM_CREATE, permissions) && ( - - {t('NEW PAYMENT PLAN')} - - )} - + { const navigate = useNavigate(); const { t } = useTranslation(); const [mutate, { loading: loadingCreate }] = useCreatePpMutation(); const { showMessage } = useSnackbar(); - const { baseUrl, businessArea, programId } = useBaseUrl(); + const { businessArea, programId } = useBaseUrl(); const permissions = usePermissions(); + const { programCycleId } = useParams(); const { data: allTargetPopulationsData, loading: loadingTargetPopulations } = useAllTargetPopulationsQuery({ @@ -33,6 +34,7 @@ export const CreatePaymentPlanPage = (): React.ReactElement => { businessArea, paymentPlanApplicable: true, program: [programId], + programCycle: programCycleId, }, fetchPolicy: 'network-only', }); @@ -45,20 +47,7 @@ export const CreatePaymentPlanPage = (): React.ReactElement => { const validationSchema = Yup.object().shape({ targetingId: Yup.string().required(t('Target Population is required')), - startDate: Yup.date().required(t('Start Date is required')), - endDate: Yup.date() - .required(t('End Date is required')) - .when('startDate', (startDate: any, schema: Yup.DateSchema) => - startDate && typeof startDate === 'string' - ? schema.min( - parseISO(startDate), - `${t('End date has to be greater than')} ${format(parseISO(startDate), 'yyyy-MM-dd')}`, - ) - : schema, - ), - currency: Yup.string() - .nullable() - .required(t('Currency is required')), + currency: Yup.string().nullable().required(t('Currency is required')), dispersionStartDate: Yup.date().required( t('Dispersion Start Date is required'), ), @@ -80,8 +69,6 @@ export const CreatePaymentPlanPage = (): React.ReactElement => { type FormValues = Yup.InferType; const initialValues: FormValues = { targetingId: '', - startDate: null, - endDate: null, currency: null, dispersionStartDate: null, dispersionEndDate: null, @@ -89,36 +76,27 @@ export const CreatePaymentPlanPage = (): React.ReactElement => { const handleSubmit = async (values: FormValues): Promise => { try { - const startDate = values.startDate - ? format(new Date(values.startDate), 'yyyy-MM-dd') - : null; - const endDate = values.endDate - ? format(new Date(values.endDate), 'yyyy-MM-dd') - : null; const dispersionStartDate = values.dispersionStartDate ? format(new Date(values.dispersionStartDate), 'yyyy-MM-dd') : null; const dispersionEndDate = values.dispersionEndDate ? format(new Date(values.dispersionEndDate), 'yyyy-MM-dd') : null; + const { currency, targetingId } = values; const res = await mutate({ variables: { - //@ts-ignore input: { businessAreaSlug: businessArea, - ...values, - startDate, - endDate, + currency, + targetingId, dispersionStartDate, dispersionEndDate, }, }, }); showMessage(t('Payment Plan Created')); - navigate( - `/${baseUrl}/payment-module/payment-plans/${res.data.createPaymentPlan.paymentPlan.id}`, - ); + navigate(`../${res.data.createPaymentPlan.paymentPlan.id}`); } catch (e) { e.graphQLErrors.map((x) => showMessage(x.message)); } @@ -137,7 +115,6 @@ export const CreatePaymentPlanPage = (): React.ReactElement => { diff --git a/frontend/src/containers/pages/paymentmodule/ProgramCycle/PaymentPlanDetails/PaymentPlanDetails.tsx b/frontend/src/containers/pages/paymentmodule/ProgramCycle/PaymentPlanDetails/PaymentPlanDetails.tsx new file mode 100644 index 0000000000..5647cefe47 --- /dev/null +++ b/frontend/src/containers/pages/paymentmodule/ProgramCycle/PaymentPlanDetails/PaymentPlanDetails.tsx @@ -0,0 +1,89 @@ +import { Grid, Typography } from '@mui/material'; +import * as React from 'react'; +import { useTranslation } from 'react-i18next'; +import { renderUserName } from '@utils/utils'; +import { PaymentPlanQuery } from '@generated/graphql'; +import { ContainerColumnWithBorder } from '@core/ContainerColumnWithBorder'; +import { LabelizedField } from '@core/LabelizedField'; +import { OverviewContainer } from '@core/OverviewContainer'; +import { Title } from '@core/Title'; +import { UniversalMoment } from '@core/UniversalMoment'; +import { FieldBorder } from '@core/FieldBorder'; +import { RelatedFollowUpPaymentPlans } from '@components/paymentmodule/PaymentPlanDetails/PaymentPlanDetails/RelatedFollowUpPaymentPlans'; + +interface PaymentPlanDetailsProps { + baseUrl: string; + paymentPlan: PaymentPlanQuery['paymentPlan']; +} + +export const PaymentPlanDetails = ({ + baseUrl, + paymentPlan, +}: PaymentPlanDetailsProps): React.ReactElement => { + const { t } = useTranslation(); + const { + createdBy, + currency, + startDate, + endDate, + dispersionStartDate, + dispersionEndDate, + followUps, + } = paymentPlan; + + return ( + + + + <Typography variant="h6">{t('Details')}</Typography> + + + + + + + {renderUserName(createdBy)} + + + + + {startDate} + + + + + {endDate} + + + + + {currency} + + + + + {dispersionStartDate} + + + + + {dispersionEndDate} + + + + + + + + + + + + + + + ); +}; diff --git a/frontend/src/containers/pages/paymentmodule/ProgramCycle/PaymentPlanDetails/PaymentPlanDetailsHeader.tsx b/frontend/src/containers/pages/paymentmodule/ProgramCycle/PaymentPlanDetails/PaymentPlanDetailsHeader.tsx new file mode 100644 index 0000000000..834c4a4bf2 --- /dev/null +++ b/frontend/src/containers/pages/paymentmodule/ProgramCycle/PaymentPlanDetails/PaymentPlanDetailsHeader.tsx @@ -0,0 +1,234 @@ +import styled from 'styled-components'; +import { Box } from '@mui/material'; +import { PaymentPlanQuery } from '@generated/graphql'; +import { useTranslation } from 'react-i18next'; +import { BreadCrumbsItem } from '@core/BreadCrumbs'; +import { hasPermissions, PERMISSIONS } from '../../../../../config/permissions'; +import React from 'react'; +import { OpenPaymentPlanHeaderButtons } from '@components/paymentmodule/PaymentPlanDetails/PaymentPlanDetailsHeader/HeaderButtons/OpenPaymentPlanHeaderButtons'; +import { LockedPaymentPlanHeaderButtons } from '@components/paymentmodule/PaymentPlanDetails/PaymentPlanDetailsHeader/HeaderButtons/LockedPaymentPlanHeaderButtons'; +import { LockedFspPaymentPlanHeaderButtons } from '@components/paymentmodule/PaymentPlanDetails/PaymentPlanDetailsHeader/HeaderButtons/LockedFspPaymentPlanHeaderButtons'; +import { InApprovalPaymentPlanHeaderButtons } from '@components/paymentmodule/PaymentPlanDetails/PaymentPlanDetailsHeader/HeaderButtons/InApprovalPaymentPlanHeaderButtons'; +import { InAuthorizationPaymentPlanHeaderButtons } from '@components/paymentmodule/PaymentPlanDetails/PaymentPlanDetailsHeader/HeaderButtons/InAuthorizationPaymentPlanHeaderButtons'; +import { InReviewPaymentPlanHeaderButtons } from '@components/paymentmodule/PaymentPlanDetails/PaymentPlanDetailsHeader/HeaderButtons/InReviewPaymentPlanHeaderButtons'; +import { AcceptedPaymentPlanHeaderButtons } from '@components/paymentmodule/PaymentPlanDetails/PaymentPlanDetailsHeader/HeaderButtons/AcceptedPaymentPlanHeaderButtons'; +import { PageHeader } from '@core/PageHeader'; +import { StatusBox } from '@core/StatusBox'; +import { + decodeIdString, + paymentPlanBackgroundActionStatusToColor, + paymentPlanStatusToColor, +} from '@utils/utils'; +import { useQuery } from '@tanstack/react-query'; +import { fetchProgramCycle } from '@api/programCycleApi'; +import { useBaseUrl } from '@hooks/useBaseUrl'; +import { useParams } from 'react-router-dom'; + +const StatusWrapper = styled(Box)` + width: 150px; +`; + +interface PaymentPlanDetailsHeaderProps { + permissions: string[]; + paymentPlan: PaymentPlanQuery['paymentPlan']; +} + +export const PaymentPlanDetailsHeader = ({ + permissions, + paymentPlan, +}: PaymentPlanDetailsHeaderProps): React.ReactElement => { + const { t } = useTranslation(); + const { businessArea, programId } = useBaseUrl(); + const { programCycleId } = useParams(); + + const { data: programCycleData, isLoading: isLoadingProgramCycle } = useQuery( + { + queryKey: [ + 'programCyclesDetails', + businessArea, + programId, + decodeIdString(programCycleId), + ], + queryFn: async () => { + return fetchProgramCycle( + businessArea, + programId, + decodeIdString(programCycleId), + ); + }, + enabled: !!programCycleId, + }, + ); + + if (isLoadingProgramCycle) { + return null; + } + + const breadCrumbsItems: BreadCrumbsItem[] = []; + + if (programCycleId) { + breadCrumbsItems.push({ + title: t('Payment Module'), + to: '../../..', + }); + breadCrumbsItems.push({ + title: `${programCycleData.title} (ID: ${programCycleData.unicef_id})`, + to: '../..', + }); + } else { + breadCrumbsItems.push({ + title: t('Payment Module'), + to: '..', + }); + } + + const canRemove = hasPermissions(PERMISSIONS.PM_CREATE, permissions); + const canEdit = hasPermissions(PERMISSIONS.PM_CREATE, permissions); + const canLock = hasPermissions(PERMISSIONS.PM_LOCK_AND_UNLOCK, permissions); + const canUnlock = hasPermissions(PERMISSIONS.PM_LOCK_AND_UNLOCK, permissions); + const canSendForApproval = hasPermissions( + PERMISSIONS.PM_SEND_FOR_APPROVAL, + permissions, + ); + const canApprove = hasPermissions( + PERMISSIONS.PM_ACCEPTANCE_PROCESS_APPROVE, + permissions, + ); + const canAuthorize = hasPermissions( + PERMISSIONS.PM_ACCEPTANCE_PROCESS_AUTHORIZE, + permissions, + ); + const canMarkAsReleased = hasPermissions( + PERMISSIONS.PM_ACCEPTANCE_PROCESS_FINANCIAL_REVIEW, + permissions, + ); + const canDownloadXlsx = hasPermissions( + PERMISSIONS.PM_DOWNLOAD_XLSX_FOR_FSP, + permissions, + ); + const canExportXlsx = hasPermissions( + PERMISSIONS.PM_EXPORT_XLSX_FOR_FSP, + permissions, + ); + const canSplit = + hasPermissions(PERMISSIONS.PM_SPLIT, permissions) && paymentPlan.canSplit; + const canSendToPaymentGateway = + hasPermissions(PERMISSIONS.PM_SEND_TO_PAYMENT_GATEWAY, permissions) && + paymentPlan.canSendToPaymentGateway; + + let buttons: React.ReactElement | null = null; + switch (paymentPlan.status) { + case 'OPEN': + buttons = ( + + ); + break; + case 'LOCKED': + buttons = ( + + ); + break; + case 'LOCKED_FSP': + buttons = ( + + ); + break; + case 'IN_APPROVAL': + buttons = ( + + ); + break; + case 'IN_AUTHORIZATION': + buttons = ( + + ); + break; + case 'IN_REVIEW': + buttons = ( + + ); + break; + case 'FINISHED': + case 'ACCEPTED': + buttons = ( + + ); + break; + default: + break; + } + + return ( + + {t('Payment Plan')} ID:{' '} + + {paymentPlan.unicefId} + + + + + {paymentPlan.backgroundActionStatus && ( + + + + )} + + } + breadCrumbs={ + hasPermissions(PERMISSIONS.PM_VIEW_DETAILS, permissions) + ? breadCrumbsItems + : null + } + > + {buttons} + + ); +}; diff --git a/frontend/src/containers/pages/paymentmodule/PaymentPlanDetailsPage.tsx b/frontend/src/containers/pages/paymentmodule/ProgramCycle/PaymentPlanDetails/PaymentPlanDetailsPage.tsx similarity index 76% rename from frontend/src/containers/pages/paymentmodule/PaymentPlanDetailsPage.tsx rename to frontend/src/containers/pages/paymentmodule/ProgramCycle/PaymentPlanDetails/PaymentPlanDetailsPage.tsx index cc062b5bd7..73e4a5b081 100644 --- a/frontend/src/containers/pages/paymentmodule/PaymentPlanDetailsPage.tsx +++ b/frontend/src/containers/pages/paymentmodule/ProgramCycle/PaymentPlanDetails/PaymentPlanDetailsPage.tsx @@ -1,37 +1,36 @@ -import { Box } from '@mui/material'; -import * as React from 'react'; -import { useEffect } from 'react'; +import React, { useEffect } from 'react'; import { useParams } from 'react-router-dom'; +import { usePermissions } from '@hooks/usePermissions'; +import { useBaseUrl } from '@hooks/useBaseUrl'; import { PaymentPlanBackgroundActionStatus, PaymentPlanStatus, usePaymentPlanQuery, } from '@generated/graphql'; -import { LoadingComponent } from '@components/core/LoadingComponent'; -import { PermissionDenied } from '@components/core/PermissionDenied'; -import { AcceptanceProcess } from '@components/paymentmodule/PaymentPlanDetails/AcceptanceProcess/AcceptanceProcess'; -import { Entitlement } from '@components/paymentmodule/PaymentPlanDetails/Entitlement/Entitlement'; -import { ExcludeSection } from '@components/paymentmodule/PaymentPlanDetails/ExcludeSection'; +import { LoadingComponent } from '@core/LoadingComponent'; +import { hasPermissions, PERMISSIONS } from '../../../../../config/permissions'; +import { isPermissionDeniedError } from '@utils/utils'; +import { PermissionDenied } from '@core/PermissionDenied'; +import { Box } from '@mui/material'; +import { PaymentPlanDetailsHeader } from '@containers/pages/paymentmodule/ProgramCycle/PaymentPlanDetails/PaymentPlanDetailsHeader'; +import { PaymentPlanDetails } from '@containers/pages/paymentmodule/ProgramCycle/PaymentPlanDetails/PaymentPlanDetails'; +import { AcceptanceProcess } from '@components/paymentmodule/PaymentPlanDetails/AcceptanceProcess'; +import { Entitlement } from '@components/paymentmodule/PaymentPlanDetails/Entitlement'; import { FspSection } from '@components/paymentmodule/PaymentPlanDetails/FspSection'; -import { PaymentPlanDetails } from '@components/paymentmodule/PaymentPlanDetails/PaymentPlanDetails'; -import { PaymentPlanDetailsHeader } from '@components/paymentmodule/PaymentPlanDetails/PaymentPlanDetailsHeader'; +import { ExcludeSection } from '@components/paymentmodule/PaymentPlanDetails/ExcludeSection'; import { PaymentPlanDetailsResults } from '@components/paymentmodule/PaymentPlanDetails/PaymentPlanDetailsResults'; +import { PaymentsTable } from '@containers/tables/paymentmodule/PaymentsTable'; import { ReconciliationSummary } from '@components/paymentmodule/PaymentPlanDetails/ReconciliationSummary'; -import { PERMISSIONS, hasPermissions } from '../../../config/permissions'; -import { useBaseUrl } from '@hooks/useBaseUrl'; -import { usePermissions } from '@hooks/usePermissions'; -import { isPermissionDeniedError } from '@utils/utils'; -import { UniversalActivityLogTable } from '../../tables/UniversalActivityLogTable'; -import { PaymentsTable } from '../../tables/paymentmodule/PaymentsTable'; +import { UniversalActivityLogTable } from '@containers/tables/UniversalActivityLogTable'; -export function PaymentPlanDetailsPage(): React.ReactElement { - const { id } = useParams(); +export const PaymentPlanDetailsPage = (): React.ReactElement => { + const { paymentPlanId } = useParams(); const permissions = usePermissions(); const { baseUrl, businessArea } = useBaseUrl(); const { data, loading, startPolling, stopPolling, error } = usePaymentPlanQuery({ variables: { - id, + id: paymentPlanId, }, fetchPolicy: 'cache-and-network', }); @@ -76,7 +75,6 @@ export function PaymentPlanDetailsPage(): React.ReactElement { @@ -100,11 +98,11 @@ export function PaymentPlanDetailsPage(): React.ReactElement { {shouldDisplayReconciliationSummary && ( )} - {hasPermissions(PERMISSIONS.ACTIVITY_LOG_VIEW, permissions) && ( - - )} )} + {hasPermissions(PERMISSIONS.ACTIVITY_LOG_VIEW, permissions) && ( + + )} ); -} +}; diff --git a/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/FollowUpPaymentPlansModal.tsx b/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/FollowUpPaymentPlansModal.tsx new file mode 100644 index 0000000000..3f17fd8dfb --- /dev/null +++ b/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/FollowUpPaymentPlansModal.tsx @@ -0,0 +1,128 @@ +import React, { useState } from 'react'; +import { AllPaymentPlansForTableQuery } from '@generated/graphql'; +import styled from 'styled-components'; +import VisibilityIcon from '@mui/icons-material/Visibility'; +import { useTranslation } from 'react-i18next'; +import { useBaseUrl } from '@hooks/useBaseUrl'; +import TableRow from '@mui/material/TableRow'; +import TableCell from '@mui/material/TableCell'; +import { BlackLink } from '@core/BlackLink'; +import { UniversalMoment } from '@core/UniversalMoment'; +import { + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + IconButton, +} from '@mui/material'; +import { DialogTitleWrapper } from '@containers/dialogs/DialogTitleWrapper'; +import { DialogDescription } from '@containers/dialogs/DialogDescription'; +import { LabelizedField } from '@core/LabelizedField'; +import { StyledTable } from '@components/grievances/GrievancesApproveSection/ApproveSectionStyles'; +import TableHead from '@mui/material/TableHead'; +import TableBody from '@mui/material/TableBody'; +import { DialogFooter } from '@containers/dialogs/DialogFooter'; + +interface FollowUpPaymentPlansModalProps { + paymentPlan: AllPaymentPlansForTableQuery['allPaymentPlans']['edges'][0]['node']; + canViewDetails: boolean; +} + +const BlackEyeIcon = styled(VisibilityIcon)` + color: #000; +`; + +export const FollowUpPaymentPlansModal = ({ + paymentPlan, + canViewDetails, +}: FollowUpPaymentPlansModalProps): React.ReactElement => { + const { t } = useTranslation(); + const [open, setOpen] = useState(false); + const { baseUrl } = useBaseUrl(); + + const followUps = + paymentPlan.followUps?.edges?.map((edge) => edge?.node) || []; + + if (!followUps.length) return null; + + const mappedRows = followUps.map((row) => ( + + + + {row.unicefId} + + + + {row.dispersionStartDate} + + + {row.dispersionEndDate} + + + )); + + return ( + <> + setOpen(true)} + data-cy="button-eye-follow-ups" + > + + + setOpen(false)} scroll="paper"> + + {t('Follow-up Payment Plans')} + + + + + + {canViewDetails ? ( + + {paymentPlan.unicefId} + + ) : ( + paymentPlan.unicefId + )} + + + + + + + + {t('Follow-up Payment Plan ID')} + + + {t('Dispersion Start Date')} + + + {t('Dispersion End Date')} + + + + {mappedRows} + + + + + + + + + + ); +}; diff --git a/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/PaymentPlanTableRow.tsx b/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/PaymentPlanTableRow.tsx new file mode 100644 index 0000000000..a8fd87b942 --- /dev/null +++ b/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/PaymentPlanTableRow.tsx @@ -0,0 +1,76 @@ +import { + AllPaymentPlansForTableQuery, + useCashPlanVerificationStatusChoicesQuery, +} from '@generated/graphql'; +import React from 'react'; +import { ClickableTableRow } from '@components/core/Table/ClickableTableRow'; +import TableCell from '@mui/material/TableCell'; +import { BlackLink } from '@core/BlackLink'; +import { StatusBox } from '@core/StatusBox'; +import { + formatCurrencyWithSymbol, + paymentPlanStatusToColor, +} from '@utils/utils'; +import { UniversalMoment } from '@core/UniversalMoment'; +import { FollowUpPaymentPlansModal } from '@containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/FollowUpPaymentPlansModal'; + +interface PaymentPlanTableRowProps { + paymentPlan: AllPaymentPlansForTableQuery['allPaymentPlans']['edges'][0]['node']; + canViewDetails: boolean; +} + +export const PaymentPlanTableRow = ({ + paymentPlan, + canViewDetails, +}: PaymentPlanTableRowProps): React.ReactElement => { + const { data: statusChoicesData } = + useCashPlanVerificationStatusChoicesQuery(); + + const paymentPlanPath = `./payment-plans/${paymentPlan.id}`; + + if (!statusChoicesData) return null; + + return ( + + + {paymentPlan.isFollowUp ? 'Follow-up: ' : ''} + {canViewDetails ? ( + {paymentPlan.unicefId} + ) : ( + paymentPlan.unicefId + )} + + + + + + {paymentPlan.totalHouseholdsCount || '-'} + + + {`${formatCurrencyWithSymbol(paymentPlan.totalEntitledQuantity)}`} + + + {`${formatCurrencyWithSymbol(paymentPlan.totalUndeliveredQuantity)}`} + + + {`${formatCurrencyWithSymbol(paymentPlan.totalDeliveredQuantity)}`} + + + {paymentPlan.dispersionStartDate} + + + {paymentPlan.dispersionEndDate} + + + + + + + ); +}; diff --git a/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/PaymentPlansFilters.tsx b/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/PaymentPlansFilters.tsx new file mode 100644 index 0000000000..a62218f4ad --- /dev/null +++ b/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/PaymentPlansFilters.tsx @@ -0,0 +1,192 @@ +import React from 'react'; +import { + AllPaymentPlansForTableQueryVariables, + usePaymentPlanStatusChoicesQueryQuery, +} from '@generated/graphql'; +import styled from 'styled-components'; +import { useTranslation } from 'react-i18next'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { createHandleApplyFilterChange } from '@utils/utils'; +import { ContainerWithBorder } from '@core/ContainerWithBorder'; +import { Title } from '@core/Title'; +import { MenuItem, Typography } from '@mui/material'; +import Grid from '@mui/material/Grid'; +import { SearchTextField } from '@core/SearchTextField'; +import { SelectFilter } from '@core/SelectFilter'; +import { NumberTextField } from '@core/NumberTextField'; +import { Box } from '@mui/system'; +import { DatePickerFilter } from '@core/DatePickerFilter'; +import moment from 'moment'; +import { ClearApplyButtons } from '@core/ClearApplyButtons'; + +export type FilterProps = Pick< + AllPaymentPlansForTableQueryVariables, + | 'search' + | 'status' + | 'totalEntitledQuantityFrom' + | 'totalEntitledQuantityTo' + | 'dispersionStartDate' + | 'dispersionEndDate' + | 'isFollowUp' +>; + +const FilterSectionWrapper = styled.div` + padding: 20 20 0 20; +`; + +interface PaymentPlansFilterProps { + filter; + setFilter: (filter) => void; + initialFilter; + appliedFilter; + setAppliedFilter: (filter) => void; +} + +export const PaymentPlansFilters = ({ + filter, + setFilter, + initialFilter, + appliedFilter, + setAppliedFilter, +}: PaymentPlansFilterProps): React.ReactElement => { + const { t } = useTranslation(); + const navigate = useNavigate(); + const location = useLocation(); + + const { handleFilterChange, applyFilterChanges, clearFilter } = + createHandleApplyFilterChange( + initialFilter, + navigate, + location, + filter, + setFilter, + appliedFilter, + setAppliedFilter, + ); + + const handleApplyFilter = (): void => { + applyFilterChanges(); + }; + + const handleClearFilter = (): void => { + clearFilter(); + }; + + const { data: statusChoicesData } = usePaymentPlanStatusChoicesQueryQuery(); + + if (!statusChoicesData) { + return null; + } + + return ( + + + + <Typography variant="h6">{t('Payment Plans Filters')}</Typography> + + + + handleFilterChange('search', e.target.value)} + /> + + + handleFilterChange('status', e.target.value)} + variant="outlined" + label={t('Status')} + multiple + value={filter.status} + fullWidth + > + {statusChoicesData.paymentPlanStatusChoices.map((item) => { + return ( + + {item.name} + + ); + })} + + + + + + handleFilterChange( + 'totalEntitledQuantityFrom', + e.target.value, + ) + } + /> + + + + + handleFilterChange('totalEntitledQuantityTo', e.target.value) + } + error={ + filter.totalEntitledQuantityFrom && + filter.totalEntitledQuantityTo && + filter.totalEntitledQuantityFrom > + filter.totalEntitledQuantityTo + } + /> + + + { + if ( + filter.dispersionEndDate && + moment(date).isAfter(filter.dispersionEndDate) + ) { + handleFilterChange( + 'dispersionStartDate', + moment(date).format('YYYY-MM-DD'), + ); + handleFilterChange('dispersionEndDate', undefined); + } else { + handleFilterChange( + 'dispersionStartDate', + moment(date).format('YYYY-MM-DD'), + ); + } + }} + value={filter.dispersionStartDate} + /> + + + + handleFilterChange( + 'dispersionEndDate', + moment(date).format('YYYY-MM-DD'), + ) + } + value={filter.dispersionEndDate} + minDate={filter.dispersionStartDate} + minDateMessage={} + /> + + + + + + ); +}; diff --git a/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/PaymentPlansHeadCells.ts b/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/PaymentPlansHeadCells.ts new file mode 100644 index 0000000000..dca69f159f --- /dev/null +++ b/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/PaymentPlansHeadCells.ts @@ -0,0 +1,57 @@ +export const headCells = [ + { + disablePadding: false, + label: 'Payment Plan ID', + id: 'id', + numeric: false, + }, + { + disablePadding: false, + label: 'Status', + id: 'status', + numeric: false, + }, + + { + disablePadding: false, + label: 'Num. of Households', + id: 'totalHouseholdsCount', + numeric: false, + }, + { + disablePadding: false, + label: 'Total Entitled Quantity (USD)', + id: 'totalEntitledQuantity', + numeric: true, + }, + { + disablePadding: false, + label: 'Total Undelivered Quantity (USD)', + id: 'totalUndeliveredQuantity', + numeric: true, + }, + { + disablePadding: false, + label: 'Total Delivered Quantity (USD)', + id: 'totalDeliveredQuantity', + numeric: true, + }, + { + disablePadding: false, + label: 'Dispersion Start Date', + id: 'dispersionStartDate', + numeric: false, + }, + { + disablePadding: false, + label: 'Dispersion End Date', + id: 'dispersionEndDate', + numeric: false, + }, + { + disablePadding: false, + label: 'Follow-up Payment Plans', + id: 'followup-id', + numeric: false, + }, +]; diff --git a/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/PaymentPlansTable.tsx b/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/PaymentPlansTable.tsx new file mode 100644 index 0000000000..b1229b5414 --- /dev/null +++ b/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/PaymentPlansTable.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { ProgramCycle } from '@api/programCycleApi'; +import { useBaseUrl } from '@hooks/useBaseUrl'; +import { + AllPaymentPlansForTableQuery, + AllPaymentPlansForTableQueryVariables, + useAllPaymentPlansForTableQuery, +} from '@generated/graphql'; +import { UniversalTable } from '@containers/tables/UniversalTable'; +import { headCells } from '@containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/PaymentPlansHeadCells'; +import { PaymentPlanTableRow } from '@containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/PaymentPlanTableRow'; + +interface PaymentPlansTableProps { + programCycle: ProgramCycle; + filter; + canViewDetails: boolean; + title?: string; +} + +export const PaymentPlansTable = ({ + programCycle, + filter, + canViewDetails, + title, +}: PaymentPlansTableProps): React.ReactElement => { + const { programId, businessArea } = useBaseUrl(); + const initialVariables: AllPaymentPlansForTableQueryVariables = { + businessArea, + search: filter.search, + status: filter.status, + totalEntitledQuantityFrom: filter.totalEntitledQuantityFrom, + totalEntitledQuantityTo: filter.totalEntitledQuantityTo, + dispersionStartDate: filter.dispersionStartDate, + dispersionEndDate: filter.dispersionEndDate, + isFollowUp: null, + program: programId, + programCycle: programCycle.id, + }; + + return ( + + defaultOrderBy="-createdAt" + title={title} + headCells={headCells} + query={useAllPaymentPlansForTableQuery} + queriedObjectName="allPaymentPlans" + initialVariables={initialVariables} + renderRow={(row) => ( + + )} + /> + ); +}; diff --git a/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/ProgramCycleDetailsHeader.tsx b/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/ProgramCycleDetailsHeader.tsx new file mode 100644 index 0000000000..6009262f14 --- /dev/null +++ b/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/ProgramCycleDetailsHeader.tsx @@ -0,0 +1,166 @@ +import React from 'react'; +import { Box, Button } from '@mui/material'; +import { PageHeader } from '@core/PageHeader'; +import { + finishProgramCycle, + ProgramCycle, + reactivateProgramCycle, +} from '@api/programCycleApi'; +import { useTranslation } from 'react-i18next'; +import { BreadCrumbsItem } from '@core/BreadCrumbs'; +import { Link } from 'react-router-dom'; +import AddIcon from '@mui/icons-material/Add'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useBaseUrl } from '@hooks/useBaseUrl'; +import { useSnackbar } from '@hooks/useSnackBar'; +import { decodeIdString } from '@utils/utils'; +import { hasPermissions, PERMISSIONS } from '../../../../../config/permissions'; +import { usePermissions } from '@hooks/usePermissions'; + +interface ProgramCycleDetailsHeaderProps { + programCycle: ProgramCycle; +} + +export const ProgramCycleDetailsHeader = ({ + programCycle, +}: ProgramCycleDetailsHeaderProps): React.ReactElement => { + const permissions = usePermissions(); + const { showMessage } = useSnackbar(); + const { t } = useTranslation(); + const queryClient = useQueryClient(); + const { businessArea, programId } = useBaseUrl(); + + const decodedProgramCycleId = decodeIdString(programCycle.id); + + const { mutateAsync: finishMutation, isPending: isPendingFinishing } = + useMutation({ + mutationFn: async () => { + return finishProgramCycle( + businessArea, + programId, + decodedProgramCycleId, + ); + }, + onSuccess: async () => { + await queryClient.invalidateQueries({ + queryKey: [ + 'programCyclesDetails', + businessArea, + programId, + decodedProgramCycleId, + ], + }); + }, + }); + + const { mutateAsync: reactivateMutation, isPending: isPendingReactivation } = + useMutation({ + mutationFn: async () => { + return reactivateProgramCycle( + businessArea, + programId, + decodedProgramCycleId, + ); + }, + onSuccess: async () => { + await queryClient.invalidateQueries({ + queryKey: [ + 'programCyclesDetails', + businessArea, + programId, + decodedProgramCycleId, + ], + }); + }, + }); + + const breadCrumbsItems: BreadCrumbsItem[] = [ + { + title: t('Payment Module'), + to: '..', + }, + ]; + + const finishAction = async () => { + try { + await finishMutation(); + showMessage(t('Programme Cycle Finished')); + } catch (e) { + e.data?.forEach((message: string) => showMessage(message)); + } + }; + + const reactivateAction = async () => { + try { + await reactivateMutation(); + showMessage(t('Programme Cycle Reactivated')); + } catch (e) { + e.data?.forEach((message: string) => showMessage(message)); + } + }; + + const buttons = ( + <> + + {programCycle.status !== 'Finished' && + hasPermissions(PERMISSIONS.PM_CREATE, permissions) && ( + + + + )} + {programCycle.status === 'Active' && ( + + + + )} + {programCycle.status === 'Finished' && ( + + + + )} + + + ); + + return ( + + + + {programCycle.title} (ID: {programCycle.unicef_id}) + + + + } + breadCrumbs={breadCrumbsItems} + > + {buttons} + + ); +}; diff --git a/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/ProgramCycleDetailsPage.tsx b/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/ProgramCycleDetailsPage.tsx new file mode 100644 index 0000000000..13ca3cd620 --- /dev/null +++ b/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/ProgramCycleDetailsPage.tsx @@ -0,0 +1,78 @@ +import React, { useState } from 'react'; +import { decodeIdString, getFilterFromQueryParams } from '@utils/utils'; +import { useQuery } from '@tanstack/react-query'; +import { fetchProgramCycle } from '@api/programCycleApi'; +import { useBaseUrl } from '@hooks/useBaseUrl'; +import { useLocation, useParams } from 'react-router-dom'; +import { ProgramCycleDetailsHeader } from '@containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/ProgramCycleDetailsHeader'; +import { ProgramCycleDetailsSection } from '@containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/ProgramCycleDetailsSection'; +import { TableWrapper } from '@core/TableWrapper'; +import { hasPermissions, PERMISSIONS } from '../../../../../config/permissions'; +import { usePermissions } from '@hooks/usePermissions'; +import { PaymentPlansTable } from '@containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/PaymentPlansTable'; +import { PaymentPlansFilters } from '@containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/PaymentPlansFilters'; + +const initialFilter = { + search: '', + dispersionStartDate: undefined, + dispersionEndDate: undefined, + status: [], + totalEntitledQuantityFrom: null, + totalEntitledQuantityTo: null, + isFollowUp: false, +}; + +export const ProgramCycleDetailsPage = (): React.ReactElement => { + const { businessArea, programId } = useBaseUrl(); + const { programCycleId } = useParams(); + const location = useLocation(); + const permissions = usePermissions(); + + const decodedProgramCycleId = decodeIdString(programCycleId); + + const { data, isLoading } = useQuery({ + queryKey: [ + 'programCyclesDetails', + businessArea, + programId, + decodedProgramCycleId, + ], + queryFn: async () => { + return fetchProgramCycle(businessArea, programId, decodedProgramCycleId); + }, + }); + const [filter, setFilter] = useState( + getFilterFromQueryParams(location, initialFilter), + ); + const [appliedFilter, setAppliedFilter] = useState( + getFilterFromQueryParams(location, initialFilter), + ); + + if (isLoading) return null; + + return ( + <> + + + + + + + + + + ); +}; diff --git a/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/ProgramCycleDetailsSection.tsx b/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/ProgramCycleDetailsSection.tsx new file mode 100644 index 0000000000..2043816f09 --- /dev/null +++ b/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/ProgramCycleDetailsSection.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { ContainerColumnWithBorder } from '@core/ContainerColumnWithBorder'; +import { Title } from '@core/Title'; +import { Typography } from '@mui/material'; +import { OverviewContainer } from '@core/OverviewContainer'; +import Grid from '@mui/material/Grid'; +import { StatusBox } from '@core/StatusBox'; +import { programCycleStatusToColor } from '@utils/utils'; +import { LabelizedField } from '@core/LabelizedField'; +import { UniversalMoment } from '@core/UniversalMoment'; +import { ProgramCycle } from '@api/programCycleApi'; +import { useTranslation } from 'react-i18next'; + +interface ProgramCycleDetailsSectionProps { + programCycle: ProgramCycle; +} + +export const ProgramCycleDetailsSection = ({ + programCycle, +}: ProgramCycleDetailsSectionProps): React.ReactElement => { + const { t } = useTranslation(); + return ( + + + + <Typography variant="h6">{t('Details')}</Typography> + + + + + + + + + {programCycle.created_by} + + + + + {programCycle.start_date} + + + + + {programCycle.end_date} + + + + + + {programCycle.program_start_date} + + + + + + + {programCycle.program_end_date} + + + + + + {programCycle.frequency_of_payments} + + + + + + + ); +}; diff --git a/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCyclePage.tsx b/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCyclePage.tsx new file mode 100644 index 0000000000..a6181f759d --- /dev/null +++ b/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCyclePage.tsx @@ -0,0 +1,56 @@ +import React, { useState } from 'react'; +import { PageHeader } from '@core/PageHeader'; +import { useTranslation } from 'react-i18next'; +import { ProgramCyclesFilters } from '@containers/tables/ProgramCyclesTable/ProgramCyclesFilters'; +import { getFilterFromQueryParams } from '@utils/utils'; +import { useLocation } from 'react-router-dom'; +import { usePermissions } from '@hooks/usePermissions'; +import { hasPermissions, PERMISSIONS } from '../../../../config/permissions'; +import { PermissionDenied } from '@core/PermissionDenied'; +import { ProgramCyclesTable } from '@containers/tables/ProgramCyclesTable/ProgramCyclesTable'; +import { useProgramContext } from '../../../../programContext'; +import { TableWrapper } from '@core/TableWrapper'; + +const initialFilter = { + search: '', + status: '', + total_entitled_quantity_usd_from: '', + total_entitled_quantity_usd_to: '', + start_date: '', + end_date: '', +}; + +export const ProgramCyclePage = (): React.ReactElement => { + const { t } = useTranslation(); + const permissions = usePermissions(); + const location = useLocation(); + const { selectedProgram } = useProgramContext(); + + const [filter, setFilter] = useState( + getFilterFromQueryParams(location, initialFilter), + ); + const [appliedFilter, setAppliedFilter] = useState( + getFilterFromQueryParams(location, initialFilter), + ); + + if (permissions === null) return null; + if (!selectedProgram) return null; + if (!hasPermissions(PERMISSIONS.PM_VIEW_LIST, permissions)) + return ; + + return ( + <> + + + + + + + ); +}; diff --git a/frontend/src/containers/pages/paymentmodulepeople/CreatePeoplePaymentPlanPage.tsx b/frontend/src/containers/pages/paymentmodulepeople/CreatePeoplePaymentPlanPage.tsx index 10b95d608d..e97ae6091c 100644 --- a/frontend/src/containers/pages/paymentmodulepeople/CreatePeoplePaymentPlanPage.tsx +++ b/frontend/src/containers/pages/paymentmodulepeople/CreatePeoplePaymentPlanPage.tsx @@ -17,7 +17,7 @@ import { } from '@generated/graphql'; import { AutoSubmitFormOnEnter } from '@components/core/AutoSubmitFormOnEnter'; import { useBaseUrl } from '@hooks/useBaseUrl'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; export const CreatePeoplePaymentPlanPage = (): React.ReactElement => { const navigate = useNavigate(); @@ -26,6 +26,7 @@ export const CreatePeoplePaymentPlanPage = (): React.ReactElement => { const { showMessage } = useSnackbar(); const { baseUrl, businessArea, programId } = useBaseUrl(); const permissions = usePermissions(); + const { programCycleId } = useParams(); const { data: allTargetPopulationsData, loading: loadingTargetPopulations } = useAllTargetPopulationsQuery({ @@ -33,6 +34,7 @@ export const CreatePeoplePaymentPlanPage = (): React.ReactElement => { businessArea, paymentPlanApplicable: true, program: [programId], + programCycle: programCycleId, }, fetchPolicy: 'network-only', }); @@ -45,18 +47,7 @@ export const CreatePeoplePaymentPlanPage = (): React.ReactElement => { const validationSchema = Yup.object().shape({ targetingId: Yup.string().required(t('Target Population is required')), - startDate: Yup.date().required(t('Start Date is required')), - endDate: Yup.date() - .required(t('End Date is required')) - .when('startDate', (startDate: any, schema: Yup.DateSchema) => - startDate && typeof startDate === 'string' - ? schema.min( - parseISO(startDate), - `${t('End date has to be greater than')} ${format(parseISO(startDate), 'yyyy-MM-dd')}`, - ) - : schema, - ), - currency: Yup.string().nullable().required(t('Currency is required')), + currency: Yup.string().required(t('Currency is required')), dispersionStartDate: Yup.date().required( t('Dispersion Start Date is required'), ), @@ -78,8 +69,6 @@ export const CreatePeoplePaymentPlanPage = (): React.ReactElement => { type FormValues = Yup.InferType; const initialValues: FormValues = { targetingId: '', - startDate: null, - endDate: null, currency: null, dispersionStartDate: null, dispersionEndDate: null, @@ -87,27 +76,20 @@ export const CreatePeoplePaymentPlanPage = (): React.ReactElement => { const handleSubmit = async (values: FormValues): Promise => { try { - const startDate = values.startDate - ? format(new Date(values.startDate), 'yyyy-MM-dd') - : null; - const endDate = values.endDate - ? format(new Date(values.endDate), 'yyyy-MM-dd') - : null; const dispersionStartDate = values.dispersionStartDate ? format(new Date(values.dispersionStartDate), 'yyyy-MM-dd') : null; const dispersionEndDate = values.dispersionEndDate ? format(new Date(values.dispersionEndDate), 'yyyy-MM-dd') : null; + const { currency, targetingId } = values; const res = await mutate({ variables: { - //@ts-ignore input: { businessAreaSlug: businessArea, - ...values, - startDate, - endDate, + currency, + targetingId, dispersionStartDate, dispersionEndDate, }, @@ -135,7 +117,6 @@ export const CreatePeoplePaymentPlanPage = (): React.ReactElement => { diff --git a/frontend/src/containers/pages/paymentmodulepeople/EditPeopleFollowUpPaymentPlanPage.tsx b/frontend/src/containers/pages/paymentmodulepeople/EditPeopleFollowUpPaymentPlanPage.tsx index 4fa43891d3..4438cba037 100644 --- a/frontend/src/containers/pages/paymentmodulepeople/EditPeopleFollowUpPaymentPlanPage.tsx +++ b/frontend/src/containers/pages/paymentmodulepeople/EditPeopleFollowUpPaymentPlanPage.tsx @@ -23,12 +23,12 @@ import { today } from '@utils/utils'; export const EditPeopleFollowUpPaymentPlanPage = (): React.ReactElement => { const navigate = useNavigate(); - const { id } = useParams(); + const { paymentPlanId } = useParams(); const { t } = useTranslation(); const { data: paymentPlanData, loading: loadingPaymentPlan } = usePaymentPlanQuery({ variables: { - id, + id: paymentPlanId, }, fetchPolicy: 'cache-and-network', }); @@ -57,8 +57,6 @@ export const EditPeopleFollowUpPaymentPlanPage = (): React.ReactElement => { const initialValues = { targetingId: paymentPlan.targetPopulation.id, - startDate: paymentPlan.startDate, - endDate: paymentPlan.endDate, currency: { name: paymentPlan.currencyName, value: paymentPlan.currency, @@ -70,19 +68,6 @@ export const EditPeopleFollowUpPaymentPlanPage = (): React.ReactElement => { const validationSchema = Yup.object().shape({ targetingId: Yup.string().required(t('Target Population is required')), currency: Yup.string().nullable().required(t('Currency is required')), - startDate: Yup.date().required(t('Start Date is required')), - endDate: Yup.date() - .required(t('End Date is required')) - .when('startDate', (startDate: any, schema: Yup.DateSchema) => - startDate - ? schema.min( - startDate, - `${t('End date has to be greater than')} ${moment( - startDate, - ).format('YYYY-MM-DD')}`, - ) - : schema, - ), dispersionStartDate: Yup.date().required( t('Dispersion Start Date is required'), ), @@ -108,10 +93,8 @@ export const EditPeopleFollowUpPaymentPlanPage = (): React.ReactElement => { const res = await mutate({ variables: { input: { - paymentPlanId: id, + paymentPlanId, targetingId: values.targetingId, - startDate: values.startDate, - endDate: values.endDate, dispersionStartDate: values.dispersionStartDate, dispersionEndDate: values.dispersionEndDate, currency: values.currency?.value diff --git a/frontend/src/containers/pages/paymentmodulepeople/EditPeopleFollowUpSetUpFspPage.tsx b/frontend/src/containers/pages/paymentmodulepeople/EditPeopleFollowUpSetUpFspPage.tsx index c235845f8a..e179c16557 100644 --- a/frontend/src/containers/pages/paymentmodulepeople/EditPeopleFollowUpSetUpFspPage.tsx +++ b/frontend/src/containers/pages/paymentmodulepeople/EditPeopleFollowUpSetUpFspPage.tsx @@ -9,12 +9,12 @@ import { usePermissions } from '@hooks/usePermissions'; import { usePaymentPlanQuery } from '@generated/graphql'; export const EditPeopleFollowUpSetUpFspPage = (): React.ReactElement => { - const { id } = useParams(); + const { paymentPlanId } = useParams(); const { data: paymentPlanData, loading: paymentPlanLoading } = usePaymentPlanQuery({ variables: { - id, + id: paymentPlanId, }, fetchPolicy: 'cache-and-network', }); diff --git a/frontend/src/containers/pages/paymentmodulepeople/EditPeoplePaymentPlanPage.tsx b/frontend/src/containers/pages/paymentmodulepeople/EditPeoplePaymentPlanPage.tsx index c7cda427bc..f54a69bb5b 100644 --- a/frontend/src/containers/pages/paymentmodulepeople/EditPeoplePaymentPlanPage.tsx +++ b/frontend/src/containers/pages/paymentmodulepeople/EditPeoplePaymentPlanPage.tsx @@ -23,12 +23,12 @@ import { useBaseUrl } from '@hooks/useBaseUrl'; export const EditPeoplePaymentPlanPage = (): React.ReactElement => { const navigate = useNavigate(); - const { id } = useParams(); + const { paymentPlanId } = useParams(); const { t } = useTranslation(); const { data: paymentPlanData, loading: loadingPaymentPlan } = usePaymentPlanQuery({ variables: { - id, + id: paymentPlanId, }, fetchPolicy: 'cache-and-network', }); @@ -56,8 +56,6 @@ export const EditPeoplePaymentPlanPage = (): React.ReactElement => { const initialValues = { targetingId: paymentPlan.targetPopulation.id, - startDate: paymentPlan.startDate, - endDate: paymentPlan.endDate, currency: { name: paymentPlan.currencyName, value: paymentPlan.currency, @@ -68,19 +66,6 @@ export const EditPeoplePaymentPlanPage = (): React.ReactElement => { const validationSchema = Yup.object().shape({ targetingId: Yup.string().required(t('Target Population is required')), - startDate: Yup.date(), - endDate: Yup.date() - .required(t('End Date is required')) - .when('startDate', (startDate: any, schema: Yup.DateSchema) => - startDate - ? schema.min( - startDate as Date, - `${t('End date has to be greater than')} ${moment( - startDate, - ).format('YYYY-MM-DD')}`, - ) - : schema, - ), dispersionStartDate: Yup.date().required( t('Dispersion Start Date is required'), ), @@ -106,10 +91,8 @@ export const EditPeoplePaymentPlanPage = (): React.ReactElement => { const res = await mutate({ variables: { input: { - paymentPlanId: id, + paymentPlanId, targetingId: values.targetingId, - startDate: values.startDate, - endDate: values.endDate, dispersionStartDate: values.dispersionStartDate, dispersionEndDate: values.dispersionEndDate, currency: values.currency?.value diff --git a/frontend/src/containers/pages/paymentmodulepeople/EditPeopleSetUpFspPage.tsx b/frontend/src/containers/pages/paymentmodulepeople/EditPeopleSetUpFspPage.tsx index 2e7d505514..59e380fcf2 100644 --- a/frontend/src/containers/pages/paymentmodulepeople/EditPeopleSetUpFspPage.tsx +++ b/frontend/src/containers/pages/paymentmodulepeople/EditPeopleSetUpFspPage.tsx @@ -9,12 +9,12 @@ import { usePermissions } from '@hooks/usePermissions'; import { usePaymentPlanQuery } from '@generated/graphql'; export const EditPeopleSetUpFspPage = (): React.ReactElement => { - const { id } = useParams(); + const { paymentPlanId } = useParams(); const { data: paymentPlanData, loading: paymentPlanLoading } = usePaymentPlanQuery({ variables: { - id, + id: paymentPlanId, }, fetchPolicy: 'cache-and-network', }); diff --git a/frontend/src/containers/pages/paymentmodulepeople/PeopleFollowUpPaymentPlanDetailsPage.tsx b/frontend/src/containers/pages/paymentmodulepeople/PeopleFollowUpPaymentPlanDetailsPage.tsx index e926bf6b35..8d0b46f78c 100644 --- a/frontend/src/containers/pages/paymentmodulepeople/PeopleFollowUpPaymentPlanDetailsPage.tsx +++ b/frontend/src/containers/pages/paymentmodulepeople/PeopleFollowUpPaymentPlanDetailsPage.tsx @@ -20,13 +20,13 @@ import { ExcludeSection } from '@components/paymentmodule/PaymentPlanDetails/Exc import { useBaseUrl } from '@hooks/useBaseUrl'; export const PeopleFollowUpPaymentPlanDetailsPage = (): React.ReactElement => { - const { id } = useParams(); + const { paymentPlanId } = useParams(); const permissions = usePermissions(); const { baseUrl, businessArea } = useBaseUrl(); const { data, loading, startPolling, stopPolling, error } = usePaymentPlanQuery({ variables: { - id, + id: paymentPlanId, }, fetchPolicy: 'cache-and-network', }); diff --git a/frontend/src/containers/pages/paymentmodulepeople/PeoplePaymentDetailsPage.tsx b/frontend/src/containers/pages/paymentmodulepeople/PeoplePaymentDetailsPage.tsx index cb818aa86a..d448ff6d1e 100644 --- a/frontend/src/containers/pages/paymentmodulepeople/PeoplePaymentDetailsPage.tsx +++ b/frontend/src/containers/pages/paymentmodulepeople/PeoplePaymentDetailsPage.tsx @@ -22,12 +22,12 @@ import { AdminButton } from '@core/AdminButton'; export const PeoplePaymentDetailsPage = (): React.ReactElement => { const { t } = useTranslation(); - const { id } = useParams(); + const { paymentPlanId } = useParams(); const { data: caData, loading: caLoading } = useCashAssistUrlPrefixQuery({ fetchPolicy: 'cache-first', }); const { data, loading } = usePaymentQuery({ - variables: { id }, + variables: { id: paymentPlanId }, fetchPolicy: 'cache-and-network', }); const paymentPlanStatus = data?.payment?.parent?.status; @@ -44,7 +44,7 @@ export const PeoplePaymentDetailsPage = (): React.ReactElement => { const breadCrumbsItems: BreadCrumbsItem[] = [ { title: t('Payment Module'), - to: `/${baseUrl}/payment-module/`, + to: `/${baseUrl}/payment-module/payment-plans`, }, { title: ` ${paymentPlanIsFollowUp ? 'Follow-up ' : ''} Payment Plan ${ diff --git a/frontend/src/containers/pages/paymentmodulepeople/PeoplePaymentModulePage.tsx b/frontend/src/containers/pages/paymentmodulepeople/PeoplePaymentModulePage.tsx index 82cc700cbd..dc6fccca4c 100644 --- a/frontend/src/containers/pages/paymentmodulepeople/PeoplePaymentModulePage.tsx +++ b/frontend/src/containers/pages/paymentmodulepeople/PeoplePaymentModulePage.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { useState } from 'react'; -import { Link, useLocation } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { PageHeader } from '@components/core/PageHeader'; import { PermissionDenied } from '@components/core/PermissionDenied'; @@ -8,9 +8,6 @@ import { TableWrapper } from '@components/core/TableWrapper'; import { hasPermissions, PERMISSIONS } from '../../../config/permissions'; import { usePermissions } from '@hooks/usePermissions'; import { getFilterFromQueryParams } from '@utils/utils'; -import { useBaseUrl } from '@hooks/useBaseUrl'; -import { ButtonTooltip } from '@components/core/ButtonTooltip'; -import { useProgramContext } from '../../../programContext'; import { PeoplePaymentPlansTable } from '@containers/tables/paymentmodulePeople/PeoplePaymentPlansTable'; import { PeoplePaymentPlansFilters } from '@containers/tables/paymentmodulePeople/PeoplePaymentPlansTable/PeoplePaymentPlansFilters'; @@ -26,10 +23,8 @@ const initialFilter = { export const PeoplePaymentModulePage = (): React.ReactElement => { const { t } = useTranslation(); - const { baseUrl } = useBaseUrl(); const permissions = usePermissions(); const location = useLocation(); - const { isActiveProgram } = useProgramContext(); const [filter, setFilter] = useState( getFilterFromQueryParams(location, initialFilter), @@ -45,21 +40,7 @@ export const PeoplePaymentModulePage = (): React.ReactElement => { return ( <> - - {hasPermissions(PERMISSIONS.PM_CREATE, permissions) && ( - - {t('NEW PAYMENT PLAN')} - - )} - + { - const { id } = useParams(); + const { paymentPlanId } = useParams(); const permissions = usePermissions(); const { baseUrl, businessArea } = useBaseUrl(); const { data, loading, startPolling, stopPolling, error } = usePaymentPlanQuery({ variables: { - id, + id: paymentPlanId, }, fetchPolicy: 'cache-and-network', }); diff --git a/frontend/src/containers/pages/payments/PaymentPlanVerificationDetailsPage.tsx b/frontend/src/containers/pages/payments/PaymentPlanVerificationDetailsPage.tsx index 4e60332b1f..7ed92b2f77 100644 --- a/frontend/src/containers/pages/payments/PaymentPlanVerificationDetailsPage.tsx +++ b/frontend/src/containers/pages/payments/PaymentPlanVerificationDetailsPage.tsx @@ -71,9 +71,9 @@ export function PaymentPlanVerificationDetailsPage(): React.ReactElement { const [appliedFilter, setAppliedFilter] = useState( getFilterFromQueryParams(location, initialFilter), ); - const { id } = useParams(); + const { paymentPlanId } = useParams(); const { data, loading, error } = usePaymentPlanQuery({ - variables: { id }, + variables: { id: paymentPlanId }, fetchPolicy: 'cache-and-network', }); const { data: choicesData, loading: choicesLoading } = diff --git a/frontend/src/containers/pages/program/ProgramDetailsPage.tsx b/frontend/src/containers/pages/program/ProgramDetailsPage.tsx index 1970d80ff5..ba5157b23d 100644 --- a/frontend/src/containers/pages/program/ProgramDetailsPage.tsx +++ b/frontend/src/containers/pages/program/ProgramDetailsPage.tsx @@ -15,9 +15,9 @@ import { hasPermissions, PERMISSIONS } from '../../../config/permissions'; import { useBaseUrl } from '@hooks/useBaseUrl'; import { usePermissions } from '@hooks/usePermissions'; import { isPermissionDeniedError } from '@utils/utils'; -import { CashPlanTable } from '../../tables/payments/CashPlanTable'; import { UniversalActivityLogTable } from '../../tables/UniversalActivityLogTable'; import { ProgramDetailsPageHeader } from '../headers/ProgramDetailsPageHeader'; +import { ProgramCycleTable } from '@containers/tables/ProgramCycle/ProgramCycleTable'; const Container = styled.div` && { @@ -31,8 +31,7 @@ const TableWrapper = styled.div` display: flex; flex-direction: row; flex-wrap: wrap; - padding: 20px; - padding-bottom: 0; + padding: 20px 20px 0; `; const NoCashPlansContainer = styled.div` @@ -44,12 +43,6 @@ const NoCashPlansTitle = styled.div` line-height: 28px; text-align: center; `; -const NoCashPlansSubTitle = styled.div` - color: rgba(0, 0, 0, 0.38); - font-size: 16px; - line-height: 19px; - text-align: center; -`; export function ProgramDetailsPage(): React.ReactElement { const { t } = useTranslation(); @@ -100,17 +93,12 @@ export function ProgramDetailsPage(): React.ReactElement { {program.status === ProgramStatus.Draft ? ( - {t('To see more details please Activate your Programme')} + {t('Activate the Programme to create a Cycle')} - - {t( - 'All data will be pushed to CashAssist. You can edit this plan even if it is active.', - )} - ) : ( - + )} {hasPermissions(PERMISSIONS.ACTIVITY_LOG_VIEW, permissions) && ( diff --git a/frontend/src/containers/pages/targeting/CreateTargetPopulationPage.tsx b/frontend/src/containers/pages/targeting/CreateTargetPopulationPage.tsx index f6c4e07b2f..08a3b5fda4 100644 --- a/frontend/src/containers/pages/targeting/CreateTargetPopulationPage.tsx +++ b/frontend/src/containers/pages/targeting/CreateTargetPopulationPage.tsx @@ -23,6 +23,7 @@ import { FormikTextField } from '@shared/Formik/FormikTextField'; import { useProgramContext } from 'src/programContext'; import { AndDivider, AndDividerLabel } from '@components/targeting/AndDivider'; import { FormikCheckboxField } from '@shared/Formik/FormikCheckboxField'; +import { ProgramCycleAutocompleteRest } from '@shared/autocompletes/rest/ProgramCycleAutocompleteRest'; export const CreateTargetPopulationPage = (): React.ReactElement => { const { t } = useTranslation(); @@ -33,6 +34,10 @@ export const CreateTargetPopulationPage = (): React.ReactElement => { name: '', criterias: [], program: programId, + programCycleId: { + value: '', + name: '', + }, excludedIds: '', exclusionReason: '', flagExcludeIfActiveAdjudicationTicket: false, @@ -79,12 +84,16 @@ export const CreateTargetPopulationPage = (): React.ReactElement => { const validationSchema = Yup.object().shape({ name: Yup.string() - .min(3, t('Targeting name should have at least 3 characters.')) - .max(255, t('Targeting name should have at most 255 characters.')), + .required(t('Targeting Name is required')) + .min(3, t('Targeting Name should have at least 3 characters.')) + .max(255, t('Targeting Name should have at most 255 characters.')), excludedIds: idValidation, householdIds: idValidation, individualIds: idValidation, exclusionReason: Yup.string().max(500, t('Too long')), + programCycleId: Yup.object().shape({ + value: Yup.string().required('Program Cycle is required'), + }), }); const handleSubmit = async (values): Promise => { @@ -93,6 +102,7 @@ export const CreateTargetPopulationPage = (): React.ReactElement => { variables: { input: { programId: values.program, + programCycleId: values.programCycleId.value, name: values.name, excludedIds: values.excludedIds, exclusionReason: values.exclusionReason, @@ -116,7 +126,7 @@ export const CreateTargetPopulationPage = (): React.ReactElement => { validationSchema={validationSchema} onSubmit={handleSubmit} > - {({ submitForm, values }) => ( + {({ submitForm, values, setFieldValue, errors }) => ( { {t('Targeting Criteria')} + + + { + await setFieldValue('programCycleId', e); + }} + required + // @ts-ignore + error={errors.programCycleId?.value} + /> + + { const { isSocialDctType } = useProgramContext(); @@ -31,15 +29,37 @@ export const PaymentModuleRoutes = (): React.ReactElement => { if (isSocialDctType) { children = [ { - path: '', - element: , - }, - { - path: 'new-plan', - element: , + path: 'payment-plans', + children: [ + { + path: '', + element: , + }, + { + path: ':paymentPlanId', + children: [ + { + path: '', + element: , + }, + { + path: 'edit', + element: , + }, + { + path: 'setup-fsp/edit', + element: , + }, + { + path: 'setup-fsp/create', + element: , + }, + ], + }, + ], }, { - path: 'followup-payment-plans/:id', + path: 'followup-payment-plans/:paymentPlanId', children: [ { path: '', @@ -60,43 +80,84 @@ export const PaymentModuleRoutes = (): React.ReactElement => { ], }, { - path: 'payment-plans/:id', + path: 'payments/:id', + element: , + }, + { + path: 'program-cycles', children: [ { path: '', - element: , - }, - { - path: 'edit', - element: , - }, - { - path: 'setup-fsp/edit', - element: , - }, - { - path: 'setup-fsp/create', - element: , + element: , + }, + { + path: ':programCycleId', + children: [ + { + path: '', + element: , + }, + { + path: 'payment-plans', + children: [ + { + path: 'new-plan', + element: , + }, + { + path: ':paymentPlanId', + children: [ + { + path: '', + element: , + }, + { + path: 'edit', + element: , + }, + ], + }, + ], + }, + ], }, ], }, - { - path: 'payments/:id', - element: , - }, ]; } else { children = [ { - path: '', - element: , - }, - { - path: 'new-plan', - element: , + path: 'payment-plans', + children: [ + { + path: '', + element: , + }, + { + path: ':paymentPlanId', + children: [ + { + path: '', + element: , + }, + { + path: 'edit', + element: , + }, + { + path: 'setup-fsp/edit', + element: , + }, + { + path: 'setup-fsp/create', + element: , + }, + ], + }, + ], }, { - path: 'followup-payment-plans/:id', + path: 'followup-payment-plans/:paymentPlanId', children: [ { path: '', @@ -117,30 +178,49 @@ export const PaymentModuleRoutes = (): React.ReactElement => { ], }, { - path: 'payment-plans/:id', + path: 'payments/:id', + element: , + }, + { + path: 'program-cycles', children: [ { path: '', - element: , - }, - { - path: 'edit', - element: , - }, - { - path: 'setup-fsp/edit', - element: , - }, - { - path: 'setup-fsp/create', - element: , + element: , + }, + { + path: ':programCycleId', + children: [ + { + path: '', + element: , + }, + { + path: 'payment-plans', + children: [ + { + path: 'new-plan', + element: , + }, + { + path: ':paymentPlanId', + children: [ + { + path: '', + element: , + }, + { + path: 'edit', + element: , + }, + ], + }, + ], + }, + ], }, ], }, - { - path: 'payments/:id', - element: , - }, ]; } diff --git a/frontend/src/containers/tables/ProgramCycle/DeleteProgramCycle.tsx b/frontend/src/containers/tables/ProgramCycle/DeleteProgramCycle.tsx new file mode 100644 index 0000000000..8b9c6d22a8 --- /dev/null +++ b/frontend/src/containers/tables/ProgramCycle/DeleteProgramCycle.tsx @@ -0,0 +1,105 @@ +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import DeleteIcon from '@mui/icons-material/Delete'; +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + IconButton, +} from '@mui/material'; +import { DialogTitleWrapper } from '@containers/dialogs/DialogTitleWrapper'; +import { DialogDescription } from '@containers/dialogs/DialogDescription'; +import { GreyText } from '@core/GreyText'; +import { DialogFooter } from '@containers/dialogs/DialogFooter'; +import { LoadingButton } from '@core/LoadingButton'; +import { ProgramQuery } from '@generated/graphql'; +import { deleteProgramCycle, ProgramCycle } from '@api/programCycleApi'; +import { useSnackbar } from '@hooks/useSnackBar'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { decodeIdString } from '@utils/utils'; +import { useBaseUrl } from '@hooks/useBaseUrl'; + +const WhiteDeleteIcon = styled(DeleteIcon)` + color: #fff; +`; + +interface DeleteProgramCycleProps { + program: ProgramQuery['program']; + programCycle: ProgramCycle; +} + +export const DeleteProgramCycle = ({ + program, + programCycle, +}: DeleteProgramCycleProps): React.ReactElement => { + const { t } = useTranslation(); + const [open, setOpen] = useState(false); + const { businessArea } = useBaseUrl(); + const { showMessage } = useSnackbar(); + const queryClient = useQueryClient(); + + const { mutateAsync, isPending } = useMutation({ + mutationFn: async () => { + return deleteProgramCycle( + businessArea, + program.id, + decodeIdString(programCycle.id), + ); + }, + onSuccess: async () => { + await queryClient.invalidateQueries({ + queryKey: ['programCycles', businessArea, program.id], + }); + setOpen(false); + }, + }); + + const handleDelete = async (): Promise => { + try { + await mutateAsync(); + showMessage(t('Programme Cycle Deleted')); + } catch (e) { + e.data?.forEach((message: string) => showMessage(message)); + } + }; + + return ( + <> + setOpen(true)}> + + + setOpen(false)} scroll="paper"> + + + { + 'Are you sure you want to delete the Program Cycle from the system?' + } + + + + + {t('This action cannot be undone.')} + + + + + + } + > + {t('Delete')} + + + + + + ); +}; diff --git a/frontend/src/containers/tables/ProgramCycle/EditProgramCycle.tsx b/frontend/src/containers/tables/ProgramCycle/EditProgramCycle.tsx new file mode 100644 index 0000000000..25214c8078 --- /dev/null +++ b/frontend/src/containers/tables/ProgramCycle/EditProgramCycle.tsx @@ -0,0 +1,223 @@ +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import * as Yup from 'yup'; +import { decodeIdString, today } from '@utils/utils'; +import moment from 'moment'; +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + IconButton, +} from '@mui/material'; +import EditIcon from '@mui/icons-material/EditRounded'; +import { Field, Formik } from 'formik'; +import { AutoSubmitFormOnEnter } from '@core/AutoSubmitFormOnEnter'; +import { DialogTitleWrapper } from '@containers/dialogs/DialogTitleWrapper'; +import { DialogDescription } from '@containers/dialogs/DialogDescription'; +import { GreyText } from '@core/GreyText'; +import Grid from '@mui/material/Grid'; +import { FormikTextField } from '@shared/Formik/FormikTextField'; +import { FormikDateField } from '@shared/Formik/FormikDateField'; +import CalendarTodayRoundedIcon from '@mui/icons-material/CalendarTodayRounded'; +import { DialogFooter } from '@containers/dialogs/DialogFooter'; +import { LoadingButton } from '@core/LoadingButton'; +import { + ProgramCycle, + ProgramCycleUpdate, + ProgramCycleUpdateResponse, + updateProgramCycle, +} from '@api/programCycleApi'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useBaseUrl } from '@hooks/useBaseUrl'; +import { ProgramQuery } from '@generated/graphql'; +import type { DefaultError } from '@tanstack/query-core'; +import { useSnackbar } from '@hooks/useSnackBar'; + +interface EditProgramCycleProps { + programCycle: ProgramCycle; + program: ProgramQuery['program']; +} + +export const EditProgramCycle = ({ + programCycle, + program, +}: EditProgramCycleProps): React.ReactElement => { + const { t } = useTranslation(); + const [open, setOpen] = useState(false); + const { businessArea } = useBaseUrl(); + const { showMessage } = useSnackbar(); + const queryClient = useQueryClient(); + + const { mutateAsync, isPending } = useMutation< + ProgramCycleUpdateResponse, + DefaultError, + ProgramCycleUpdate + >({ + mutationFn: async (body) => { + return updateProgramCycle( + businessArea, + program.id, + decodeIdString(programCycle.id), + body, + ); + }, + onSuccess: async () => { + await queryClient.invalidateQueries({ + queryKey: ['programCycles', businessArea, program.id], + }); + setOpen(false); + }, + }); + + const isEndDateRequired = !!programCycle.end_date; + + const handleUpdate = async (values: any): Promise => { + try { + await mutateAsync(values); + showMessage(t('Programme Cycle Updated')); + } catch (e) { + e.data?.forEach((message: string) => showMessage(message)); + } + }; + + const initialValues: { + [key: string]: string | boolean | number; + } = { + title: programCycle.title, + start_date: programCycle.start_date, + end_date: programCycle.end_date ?? undefined, + }; + + const endDateValidationSchema = () => { + const validation = Yup.date() + .min(today, t('End Date cannot be in the past')) + .max(program.endDate, t('End Date cannot be after Programme End Date')) + .when( + 'start_date', + ([start_date], schema) => + start_date && + schema.min( + start_date, + `${t('End date have to be greater than')} ${moment( + start_date, + ).format('YYYY-MM-DD')}`, + ), + ); + + if (isEndDateRequired) { + return validation.required(t('End Date is required')); + } + + return validation; + }; + + const validationSchema = Yup.object().shape({ + title: Yup.string() + .required(t('Programme Cycle title is required')) + .min(2, t('Too short')) + .max(150, t('Too long')), + start_date: Yup.date() + .required(t('Start Date is required')) + .min( + program.startDate, + t('Start Date cannot be before Programme Start Date'), + ), + end_date: endDateValidationSchema(), + }); + + return ( + <> + { + setOpen(true); + }} + color="primary" + data-cy="button-edit-program-cycle" + > + + + setOpen(false)} scroll="paper"> + + {({ submitForm }) => ( + <> + {open && } + + {t('Edit Programme Cycle')} + + + + + {t('Change details of the Programme Cycle')} + + + + + + + + + } + /> + + + + } + /> + + + + + + + + {t('Save')} + + + + + )} + + + + ); +}; diff --git a/frontend/src/containers/tables/ProgramCycle/HeadCells.ts b/frontend/src/containers/tables/ProgramCycle/HeadCells.ts new file mode 100644 index 0000000000..af9673edfa --- /dev/null +++ b/frontend/src/containers/tables/ProgramCycle/HeadCells.ts @@ -0,0 +1,74 @@ +import { HeadCell } from '@core/Table/EnhancedTableHead'; +import { ProgramCycle } from '@api/programCycleApi'; + +const headCells: HeadCell[] = [ + { + id: 'unicef_id', + numeric: false, + disablePadding: false, + label: 'Programme Cycle ID', + dataCy: 'head-cell-id', + }, + { + id: 'title', + numeric: false, + disablePadding: false, + label: 'Programme Cycle Title', + dataCy: 'head-cell-programme-cycle-title', + }, + { + id: 'status', + numeric: false, + disablePadding: false, + label: 'Status', + dataCy: 'head-cell-status', + }, + { + id: 'total_entitled_quantity', + numeric: true, + disablePadding: false, + label: 'Total Entitled Quantity', + disableSort: true, + dataCy: 'head-cell-total-entitled-quantity', + }, + { + id: 'total_undelivered_quantity', + numeric: true, + disablePadding: false, + label: 'Total Undelivered Quantity', + disableSort: true, + dataCy: 'head-cell-total-undelivered-quantity', + }, + { + id: 'total_delivered_quantity', + numeric: true, + disablePadding: false, + label: 'Total Delivered Quantity', + disableSort: true, + dataCy: 'head-cell-total-delivered-quantity', + }, + { + id: 'start_date', + numeric: false, + disablePadding: false, + label: 'Start Date', + dataCy: 'head-cell-start-date', + }, + { + id: 'end_date', + numeric: false, + disablePadding: false, + label: 'End Date', + dataCy: 'head-cell-end-date', + }, + { + id: 'empty', + numeric: false, + disablePadding: false, + label: '', + disableSort: true, + dataCy: 'head-cell-empty', + }, +]; + +export default headCells; diff --git a/frontend/src/containers/tables/ProgramCycle/NewProgramCycle/AddNewProgramCycle.tsx b/frontend/src/containers/tables/ProgramCycle/NewProgramCycle/AddNewProgramCycle.tsx new file mode 100644 index 0000000000..fbfa158886 --- /dev/null +++ b/frontend/src/containers/tables/ProgramCycle/NewProgramCycle/AddNewProgramCycle.tsx @@ -0,0 +1,102 @@ +import { Button, Dialog } from '@mui/material'; +import * as React from 'react'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { ProgramQuery } from '@generated/graphql'; +import AddIcon from '@mui/icons-material/Add'; +import { CreateProgramCycle } from '@containers/tables/ProgramCycle/NewProgramCycle/CreateProgramCycle'; +import { UpdateProgramCycle } from '@containers/tables/ProgramCycle/NewProgramCycle/UpdateProgramCycle'; +import { ProgramCycle } from '@api/programCycleApi'; +import { useQueryClient } from '@tanstack/react-query'; +import { useBaseUrl } from '@hooks/useBaseUrl'; + +interface AddNewProgramCycleProps { + program: ProgramQuery['program']; + programCycles?: ProgramCycle[]; +} + +export const AddNewProgramCycle = ({ + program, + programCycles, +}: AddNewProgramCycleProps): React.ReactElement => { + const { t } = useTranslation(); + const [open, setOpen] = useState(false); + const [step, setStep] = useState(0); + const queryClient = useQueryClient(); + const { businessArea } = useBaseUrl(); + + const handleClose = async () => { + await queryClient.invalidateQueries({ + queryKey: ['programCycles', businessArea, program.id], + }); + setOpen(false); + }; + + const handleNext = (): void => { + setStep(step + 1); + }; + + const handleSubmit = (): void => { + setOpen(false); + }; + + const lastProgramCycle = programCycles[programCycles.length - 1]; + + const stepsToRender = []; + if (lastProgramCycle.end_date) { + stepsToRender.push( + , + ); + } else { + stepsToRender.push( + , + ); + stepsToRender.push( + , + ); + } + + return ( + <> + + setOpen(false)} + scroll="paper" + aria-labelledby="form-dialog-title" + > + {stepsToRender.map((stepComponent, index) => { + if (index === step) { + return stepComponent; + } + })} + + + ); +}; diff --git a/frontend/src/containers/tables/ProgramCycle/NewProgramCycle/CreateProgramCycle.tsx b/frontend/src/containers/tables/ProgramCycle/NewProgramCycle/CreateProgramCycle.tsx new file mode 100644 index 0000000000..68eaafb557 --- /dev/null +++ b/frontend/src/containers/tables/ProgramCycle/NewProgramCycle/CreateProgramCycle.tsx @@ -0,0 +1,208 @@ +import * as Yup from 'yup'; +import { today } from '@utils/utils'; +import moment from 'moment/moment'; +import { DialogTitleWrapper } from '@containers/dialogs/DialogTitleWrapper'; +import { + Box, + Button, + DialogContent, + DialogTitle, + FormHelperText, +} from '@mui/material'; +import { DialogDescription } from '@containers/dialogs/DialogDescription'; +import { DialogFooter } from '@containers/dialogs/DialogFooter'; +import { DialogActions } from '@containers/dialogs/DialogActions'; +import { LoadingButton } from '@core/LoadingButton'; +import * as React from 'react'; +import { useTranslation } from 'react-i18next'; +import { ProgramQuery } from '@generated/graphql'; +import { Field, Form, Formik, FormikValues } from 'formik'; +import { GreyText } from '@core/GreyText'; +import Grid from '@mui/material/Grid'; +import { FormikTextField } from '@shared/Formik/FormikTextField'; +import { FormikDateField } from '@shared/Formik/FormikDateField'; +import CalendarTodayRoundedIcon from '@mui/icons-material/CalendarTodayRounded'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { + createProgramCycle, + ProgramCycleCreate, + ProgramCycleCreateResponse, +} from '@api/programCycleApi'; +import type { DefaultError } from '@tanstack/query-core'; +import { useBaseUrl } from '@hooks/useBaseUrl'; +import { useSnackbar } from '@hooks/useSnackBar'; + +interface CreateProgramCycleProps { + program: ProgramQuery['program']; + onClose: () => void; + onSubmit: () => void; + step?: string; +} + +interface MutationError extends DefaultError { + data: any; +} + +export const CreateProgramCycle = ({ + program, + onClose, + onSubmit, + step, +}: CreateProgramCycleProps) => { + const { t } = useTranslation(); + const { businessArea } = useBaseUrl(); + const queryClient = useQueryClient(); + const { showMessage } = useSnackbar(); + + const validationSchema = Yup.object().shape({ + title: Yup.string() + .required(t('Programme Cycle Title is required')) + .min(2, t('Too short')) + .max(150, t('Too long')), + start_date: Yup.date() + .required(t('Start Date is required')) + .min( + program.startDate, + t('Start Date cannot be before Programme Start Date'), + ), + end_date: Yup.date() + .min(today, t('End Date cannot be in the past')) + .max(program.endDate, t('End Date cannot be after Programme End Date')) + .when( + 'start_date', + ([start_date], schema) => + start_date && + schema.min( + start_date, + `${t('End date have to be greater than')} ${moment( + start_date, + ).format('YYYY-MM-DD')}`, + ), + ), + }); + + const initialValues: { + [key: string]: string | boolean | number; + } = { + title: '', + start_date: undefined, + end_date: undefined, + }; + + const { mutateAsync, isPending, error } = useMutation< + ProgramCycleCreateResponse, + MutationError, + ProgramCycleCreate + >({ + mutationFn: async (body) => { + return createProgramCycle(businessArea, program.id, body); + }, + onSuccess: async () => { + await queryClient.invalidateQueries({ + queryKey: ['programCycles', businessArea, program.id], + }); + onSubmit(); + }, + }); + + const handleSubmit = async (values: FormikValues) => { + try { + await mutateAsync({ + title: values.title, + start_date: values.start_date, + end_date: values.end_date, + }); + showMessage(t('Programme Cycle Created')); + } catch (e) { + /* empty */ + } + }; + + return ( + + {({ submitForm }) => ( + + <> + + + + {t('Add New Programme Cycle')} + {step && {step}} + + + + + + + {t('Enter data for the new Programme Cycle')} + + + + + + {error?.data?.title && ( + {error.data.title} + )} + + + } + /> + {error?.data?.start_date && ( + + {error.data.start_date} + + )} + + + } + /> + {error?.data?.end_date && ( + {error.data.end_date} + )} + + + + + + + + {t('CREATE')} + + + + + + )} + + ); +}; diff --git a/frontend/src/containers/tables/ProgramCycle/NewProgramCycle/UpdateProgramCycle.tsx b/frontend/src/containers/tables/ProgramCycle/NewProgramCycle/UpdateProgramCycle.tsx new file mode 100644 index 0000000000..ccb66db2e5 --- /dev/null +++ b/frontend/src/containers/tables/ProgramCycle/NewProgramCycle/UpdateProgramCycle.tsx @@ -0,0 +1,194 @@ +import { ProgramQuery } from '@generated/graphql'; +import { DialogTitleWrapper } from '@containers/dialogs/DialogTitleWrapper'; +import { + Box, + Button, + DialogContent, + DialogTitle, + FormHelperText, +} from '@mui/material'; +import { DialogDescription } from '@containers/dialogs/DialogDescription'; +import { DialogFooter } from '@containers/dialogs/DialogFooter'; +import { DialogActions } from '@containers/dialogs/DialogActions'; +import { LoadingButton } from '@core/LoadingButton'; +import * as React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Field, Form, Formik, FormikValues } from 'formik'; +import { decodeIdString, today } from '@utils/utils'; +import moment from 'moment'; +import * as Yup from 'yup'; +import { GreyText } from '@core/GreyText'; +import Grid from '@mui/material/Grid'; +import { LabelizedField } from '@core/LabelizedField'; +import { FormikDateField } from '@shared/Formik/FormikDateField'; +import CalendarTodayRoundedIcon from '@mui/icons-material/CalendarTodayRounded'; +import { + ProgramCycle, + ProgramCycleUpdate, + ProgramCycleUpdateResponse, + updateProgramCycle, +} from '@api/programCycleApi'; +import { useMutation } from '@tanstack/react-query'; +import type { DefaultError } from '@tanstack/query-core'; +import { useBaseUrl } from '@hooks/useBaseUrl'; +import { useSnackbar } from '@hooks/useSnackBar'; + +interface UpdateProgramCycleProps { + program: ProgramQuery['program']; + programCycle?: ProgramCycle; + onClose: () => void; + onSubmit: () => void; + step?: string; +} + +interface MutationError extends DefaultError { + data: any; +} + +export const UpdateProgramCycle = ({ + program, + programCycle, + onClose, + onSubmit, + step, +}: UpdateProgramCycleProps) => { + const { t } = useTranslation(); + const { businessArea } = useBaseUrl(); + const { showMessage } = useSnackbar(); + + const validationSchema = Yup.object().shape({ + end_date: Yup.date() + .required(t('End Date is required')) + .min(today, t('End Date cannot be in the past')) + .max(program.endDate, t('End Date cannot be after Programme End Date')) + .when( + 'start_date', + (start_date, schema) => + start_date && + schema.min( + start_date, + `${t('End date have to be greater than')} ${moment( + start_date, + ).format('YYYY-MM-DD')}`, + ), + ), + }); + + const initialValues: { + [key: string]: string | boolean | number | null; + } = { + id: programCycle.id, + title: programCycle.title, + start_date: programCycle.start_date, + end_date: undefined, + }; + + const { mutateAsync, isPending, error } = useMutation< + ProgramCycleUpdateResponse, + MutationError, + ProgramCycleUpdate + >({ + mutationFn: async (body) => { + return updateProgramCycle( + businessArea, + program.id, + decodeIdString(programCycle.id), + body, + ); + }, + onSuccess: () => { + onSubmit(); + }, + }); + + const handleSubmit = async (values: FormikValues) => { + try { + await mutateAsync({ + title: programCycle.title, + start_date: programCycle.start_date, + end_date: values.end_date, + }); + showMessage(t('Programme Cycle Updated')); + } catch (e) { + /* empty */ + } + }; + + return ( + + {({ submitForm, values }) => ( +
+ + + + {t('Add New Programme Cycle')} + {step && {step}} + + + + + + + {t( + 'Before you create a new Cycle, it is necessary to specify the end date of the existing Cycle', + )} + + + + + + {values.title} + + + + + {values.start_date} + + + + } + data-cy="input-previous-program-cycle-end-date" + /> + {error?.data?.end_date && ( + {error.data.end_date} + )} + + + + + + + + {t('NEXT')} + + + +
+ )} +
+ ); +}; diff --git a/frontend/src/containers/tables/ProgramCycle/ProgramCycleTable.tsx b/frontend/src/containers/tables/ProgramCycle/ProgramCycleTable.tsx new file mode 100644 index 0000000000..14f5da0171 --- /dev/null +++ b/frontend/src/containers/tables/ProgramCycle/ProgramCycleTable.tsx @@ -0,0 +1,128 @@ +import { ProgramQuery } from '@generated/graphql'; +import { UniversalRestTable } from '@components/rest/UniversalRestTable/UniversalRestTable'; +import React, { ReactElement, useState } from 'react'; +import { ClickableTableRow } from '@core/Table/ClickableTableRow'; +import TableCell from '@mui/material/TableCell'; +import { UniversalMoment } from '@core/UniversalMoment'; +import { StatusBox } from '@core/StatusBox'; +import { programCycleStatusToColor } from '@utils/utils'; +import headCells from '@containers/tables/ProgramCycle/HeadCells'; +import { AddNewProgramCycle } from '@containers/tables/ProgramCycle/NewProgramCycle/AddNewProgramCycle'; +import { DeleteProgramCycle } from '@containers/tables/ProgramCycle/DeleteProgramCycle'; +import { EditProgramCycle } from '@containers/tables/ProgramCycle/EditProgramCycle'; +import { useQuery } from '@tanstack/react-query'; +import { useBaseUrl } from '@hooks/useBaseUrl'; +import { fetchProgramCycles, ProgramCycle } from '@api/programCycleApi'; +import { BlackLink } from '@core/BlackLink'; +import { usePermissions } from '@hooks/usePermissions'; +import { hasPermissions, PERMISSIONS } from '../../../config/permissions'; + +interface ProgramCycleTableProps { + program: ProgramQuery['program']; +} + +export const ProgramCycleTable = ({ program }: ProgramCycleTableProps) => { + const [queryVariables, setQueryVariables] = useState({ + offset: 0, + limit: 5, + ordering: 'created_at', + }); + const { businessArea, baseUrl } = useBaseUrl(); + const permissions = usePermissions(); + const canCreateProgramCycle = + program.status !== 'DRAFT' && + hasPermissions(PERMISSIONS.PM_PROGRAMME_CYCLE_CREATE, permissions); + + const { data, error, isLoading } = useQuery({ + queryKey: ['programCycles', businessArea, program.id, queryVariables], + queryFn: async () => { + return fetchProgramCycles(businessArea, program.id, queryVariables); + }, + }); + + const renderRow = (row: ProgramCycle): ReactElement => { + const detailsUrl = `/${baseUrl}/payment-module/program-cycles/${row.id}`; + const canEditProgramCycle = + (row.status === 'Draft' || row.status === 'Active') && + hasPermissions(PERMISSIONS.PM_PROGRAMME_CYCLE_UPDATE, permissions); + const canDeleteProgramCycle = + row.status === 'Draft' && + data.results.length > 1 && + hasPermissions(PERMISSIONS.PM_PROGRAMME_CYCLE_DELETE, permissions); + return ( + + + {row.unicef_id} + + {row.title} + + + + + {row.total_entitled_quantity_usd || '-'} + + + {row.total_undelivered_quantity_usd || '-'} + + + {row.total_delivered_quantity_usd || '-'} + + + {row.start_date} + + + {row.end_date} + + + + {program.status === 'ACTIVE' && ( + <> + {canEditProgramCycle && ( + + )} + + {canDeleteProgramCycle && ( + + )} + + )} + + + ); + }; + + if (isLoading) { + return null; + } + + const actions = []; + + if (canCreateProgramCycle) { + actions.push( + , + ); + } + + return ( + + ); +}; diff --git a/frontend/src/containers/tables/ProgramCyclesTable/HeadCells.ts b/frontend/src/containers/tables/ProgramCyclesTable/HeadCells.ts new file mode 100644 index 0000000000..e2969b4dab --- /dev/null +++ b/frontend/src/containers/tables/ProgramCyclesTable/HeadCells.ts @@ -0,0 +1,60 @@ +import { HeadCell } from '@core/Table/EnhancedTableHead'; +import { ProgramCycle } from '@api/programCycleApi'; + +export const headCells: HeadCell[] = [ + { + id: 'unicef_id', + numeric: false, + disablePadding: false, + label: 'Programme Cycle ID', + disableSort: true, + dataCy: 'head-cell-id', + }, + { + id: 'title', + numeric: false, + disablePadding: false, + label: 'Programme Cycle Title', + disableSort: true, + dataCy: 'head-cell-programme-cycles-title', + }, + { + id: 'status', + numeric: false, + disablePadding: false, + label: 'Status', + disableSort: true, + dataCy: 'head-cell-status', + }, + { + id: 'total_entitled_quantity', + numeric: true, + disablePadding: false, + label: 'Total Entitled Quantity', + disableSort: true, + dataCy: 'head-cell-total-entitled-quantity', + }, + { + id: 'start_date', + numeric: false, + disablePadding: false, + label: 'Start Date', + dataCy: 'head-cell-start-date', + }, + { + id: 'end_date', + numeric: false, + disablePadding: false, + label: 'End Date', + disableSort: true, + dataCy: 'head-cell-end-date', + }, + { + id: 'empty', + numeric: false, + disablePadding: false, + label: '', + disableSort: true, + dataCy: 'head-cell-empty', + }, +]; diff --git a/frontend/src/containers/tables/ProgramCyclesTable/ProgramCyclesFilters.tsx b/frontend/src/containers/tables/ProgramCyclesTable/ProgramCyclesFilters.tsx new file mode 100644 index 0000000000..a6726e1c85 --- /dev/null +++ b/frontend/src/containers/tables/ProgramCyclesTable/ProgramCyclesFilters.tsx @@ -0,0 +1,150 @@ +import { ClearApplyButtons } from '@core/ClearApplyButtons'; +import { useTranslation } from 'react-i18next'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { createHandleApplyFilterChange } from '@utils/utils'; +import React from 'react'; +import Grid from '@mui/material/Grid'; +import { ContainerWithBorder } from '@core/ContainerWithBorder'; +import { SearchTextField } from '@core/SearchTextField'; +import { SelectFilter } from '@core/SelectFilter'; +import { MenuItem } from '@mui/material'; +import { NumberTextField } from '@core/NumberTextField'; +import { DatePickerFilter } from '@core/DatePickerFilter'; +import moment from 'moment/moment'; + +interface ProgramCyclesFiltersProps { + filter; + setFilter: (filter) => void; + initialFilter; + appliedFilter; + setAppliedFilter: (filter) => void; +} + +const programCycleStatuses = [ + { value: 'ACTIVE', name: 'Active' }, + { value: 'DRAFT', name: 'Draft' }, + { value: 'FINISHED', name: 'Finished' }, +]; + +export const ProgramCyclesFilters = ({ + filter, + setFilter, + initialFilter, + appliedFilter, + setAppliedFilter, +}: ProgramCyclesFiltersProps) => { + const { t } = useTranslation(); + const navigate = useNavigate(); + const location = useLocation(); + + const { handleFilterChange, applyFilterChanges, clearFilter } = + createHandleApplyFilterChange( + initialFilter, + navigate, + location, + filter, + setFilter, + appliedFilter, + setAppliedFilter, + ); + + const handleApplyFilter = (): void => { + applyFilterChanges(); + }; + + const handleClearFilter = (): void => { + clearFilter(); + }; + + return ( + + + + handleFilterChange('search', e.target.value)} + /> + + + handleFilterChange('status', e.target.value)} + variant="outlined" + label={t('Status')} + value={filter.status} + fullWidth + > + {programCycleStatuses.map((item) => { + return ( + + {item.name} + + ); + })} + + + + + handleFilterChange( + 'total_entitled_quantity_usd_from', + e.target.value, + ) + } + /> + + + + handleFilterChange( + 'total_entitled_quantity_usd_to', + e.target.value, + ) + } + error={ + filter.total_entitled_quantity_usd_from && + filter.total_entitled_quantity_usd_to && + filter.total_entitled_quantity_usd_from > + filter.total_entitled_quantity_usd_to + } + /> + + + + handleFilterChange( + 'start_date', + date ? moment(date).format('YYYY-MM-DD') : '', + ) + } + value={filter.startDate} + /> + + + + handleFilterChange( + 'end_date', + date ? moment(date).format('YYYY-MM-DD') : '', + ) + } + value={filter.end_date} + /> + + + + + ); +}; diff --git a/frontend/src/containers/tables/ProgramCyclesTable/ProgramCyclesTable.tsx b/frontend/src/containers/tables/ProgramCyclesTable/ProgramCyclesTable.tsx new file mode 100644 index 0000000000..d02e9755da --- /dev/null +++ b/frontend/src/containers/tables/ProgramCyclesTable/ProgramCyclesTable.tsx @@ -0,0 +1,159 @@ +import React, { ReactElement, useEffect, useState } from 'react'; +import { ClickableTableRow } from '@core/Table/ClickableTableRow'; +import TableCell from '@mui/material/TableCell'; +import { StatusBox } from '@core/StatusBox'; +import { decodeIdString, programCycleStatusToColor } from '@utils/utils'; +import { UniversalMoment } from '@core/UniversalMoment'; +import { UniversalRestTable } from '@components/rest/UniversalRestTable/UniversalRestTable'; +import { headCells } from '@containers/tables/ProgramCyclesTable/HeadCells'; +import { useBaseUrl } from '@hooks/useBaseUrl'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { + fetchProgramCycles, + finishProgramCycle, + ProgramCycle, + ProgramCyclesQuery, + reactivateProgramCycle, +} from '@api/programCycleApi'; +import { BlackLink } from '@core/BlackLink'; +import { useTranslation } from 'react-i18next'; +import { Button } from '@mui/material'; +import { useSnackbar } from '@hooks/useSnackBar'; + +interface ProgramCyclesTableProps { + program; + filters; +} + +export const ProgramCyclesTable = ({ + program, + filters, +}: ProgramCyclesTableProps) => { + const { showMessage } = useSnackbar(); + const [queryVariables, setQueryVariables] = useState({ + offset: 0, + limit: 5, + ordering: 'created_at', + ...filters, + }); + + const { businessArea } = useBaseUrl(); + const { t } = useTranslation(); + const queryClient = useQueryClient(); + + const { data, refetch, error, isLoading } = useQuery({ + queryKey: ['programCycles', businessArea, program.id, queryVariables], + queryFn: async () => { + return fetchProgramCycles(businessArea, program.id, queryVariables); + }, + }); + + const { mutateAsync: finishMutation, isPending: isPendingFinishing } = + useMutation({ + mutationFn: async ({ programCycleId }: { programCycleId: string }) => { + return finishProgramCycle(businessArea, program.id, programCycleId); + }, + onSuccess: async () => { + await queryClient.invalidateQueries({ + queryKey: ['programCycles', businessArea, program.id], + }); + }, + }); + + const { mutateAsync: reactivateMutation, isPending: isPendingReactivation } = + useMutation({ + mutationFn: async ({ programCycleId }: { programCycleId: string }) => { + return reactivateProgramCycle(businessArea, program.id, programCycleId); + }, + onSuccess: async () => { + await queryClient.invalidateQueries({ + queryKey: ['programCycles', businessArea, program.id], + }); + }, + }); + + useEffect(() => { + setQueryVariables((oldVariables) => ({ ...oldVariables, ...filters })); + }, [filters]); + + useEffect(() => { + void refetch(); + }, [queryVariables, refetch]); + + const finishAction = async (programCycle: ProgramCycle) => { + try { + const decodedProgramCycleId = decodeIdString(programCycle.id); + await finishMutation({ programCycleId: decodedProgramCycleId }); + showMessage(t('Programme Cycle Finished')); + } catch (e) { + e.data?.forEach((message: string) => showMessage(message)); + } + }; + + const reactivateAction = async (programCycle: ProgramCycle) => { + try { + const decodedProgramCycleId = decodeIdString(programCycle.id); + await reactivateMutation({ programCycleId: decodedProgramCycleId }); + showMessage(t('Programme Cycle Reactivated')); + } catch (e) { + e.data?.forEach((message: string) => showMessage(message)); + } + }; + + const renderRow = (row: ProgramCycle): ReactElement => ( + + + {row.unicef_id} + + {row.title} + + + + + {row.total_entitled_quantity_usd || '-'} + + + {row.start_date} + + + {row.end_date} + + + {row.status === 'Finished' && ( + + )} + {row.status === 'Active' && ( + + )} + + + ); + + return ( + + ); +}; diff --git a/frontend/src/shared/autocompletes/rest/BaseAutocompleteRest.tsx b/frontend/src/shared/autocompletes/rest/BaseAutocompleteRest.tsx index f51b621ca5..19a98d8f32 100644 --- a/frontend/src/shared/autocompletes/rest/BaseAutocompleteRest.tsx +++ b/frontend/src/shared/autocompletes/rest/BaseAutocompleteRest.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { StyledAutocomplete, StyledTextField } from '../StyledAutocomplete'; import { useDebounce } from '@hooks/useDebounce'; +import { FormHelperText } from '@mui/material'; type OptionType = any; @@ -24,6 +25,8 @@ export function BaseAutocompleteRest({ autocompleteProps = {}, textFieldProps = {}, onDebouncedInputTextChanges, + required = false, + error = null, }: { value: string; disabled?: boolean; @@ -45,6 +48,8 @@ export function BaseAutocompleteRest({ autocompleteProps?: Record; textFieldProps?: Record; onDebouncedInputTextChanges: (text: string) => void; + required?: boolean; + error?: string; }): React.ReactElement { const [modifiedOptions, setModifiedOptions] = React.useState( [], @@ -97,28 +102,34 @@ export function BaseAutocompleteRest({ loading={isLoading} {...autocompleteProps} renderInput={(params) => ( - setInputValue(e.target.value)} - InputProps={{ - ...params.InputProps, - startAdornment, - endAdornment: ( - <> - {isLoading ? ( - - ) : null} - {params.InputProps.endAdornment} - - ), - }} - {...textFieldProps} - /> + <> + setInputValue(e.target.value)} + InputProps={{ + ...params.InputProps, + startAdornment, + endAdornment: ( + <> + {isLoading ? ( + + ) : null} + {params.InputProps.endAdornment} + + ), + }} + {...textFieldProps} + /> + {!!error && {error}} + )} /> ); diff --git a/frontend/src/shared/autocompletes/rest/ProgramCycleAutocompleteRest.tsx b/frontend/src/shared/autocompletes/rest/ProgramCycleAutocompleteRest.tsx new file mode 100644 index 0000000000..fe4f3d4afa --- /dev/null +++ b/frontend/src/shared/autocompletes/rest/ProgramCycleAutocompleteRest.tsx @@ -0,0 +1,65 @@ +import { useBaseUrl } from '@hooks/useBaseUrl'; +import { handleOptionSelected } from '@utils/utils'; +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { BaseAutocompleteRest } from './BaseAutocompleteRest'; +import { fetchProgramCycles, ProgramCyclesQuery } from '@api/programCycleApi'; + +export const ProgramCycleAutocompleteRest = ({ + value, + onChange, + required = false, + error = null, +}: { + value; + onChange: (e) => void; + required?: boolean; + error?: string; +}): React.ReactElement => { + const { t } = useTranslation(); + const [queryParams, setQueryParams] = useState({ + offset: 0, + limit: 10, + ordering: 'title', + status: ['ACTIVE', 'DRAFT'], + }); + const { businessArea, programId } = useBaseUrl(); + + // Define the mapOptions function + const mapOptions = (options) => { + return options.map((option) => ({ + name: option.title, + value: option.id, + })); + }; + return ( + { + onChange(selectedValue); + }} + handleOptionSelected={(option, value1) => + handleOptionSelected(option?.value, value1) + } + handleOptionLabel={(option) => { + return option === '' ? '' : option.name; + }} + onDebouncedInputTextChanges={(text) => { + setQueryParams((oldQueryParams) => ({ + ...oldQueryParams, + title: text, + })); + }} + startAdornment={null} + mapOptions={mapOptions} + queryParams={queryParams} + required={required} + error={error} + /> + ); +}; diff --git a/frontend/src/utils/utils.ts b/frontend/src/utils/utils.ts index 4242f9d73a..f346c49d52 100644 --- a/frontend/src/utils/utils.ts +++ b/frontend/src/utils/utils.ts @@ -452,6 +452,22 @@ export function reportStatusToColor( } } +export function programCycleStatusToColor( + theme: typeof themeObj, + status: string, +): string { + switch (status) { + case 'Draft': + return theme.hctPalette.gray; + case 'Active': + return theme.hctPalette.green; + case 'Finished': + return theme.hctPalette.gray; + default: + return theme.hctPalette.gray; + } +} + export function selectFields( fullObject, keys: string[], From f35a8d9f4d97b45c40b0fd2b58115d379867b779 Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Wed, 14 Aug 2024 01:00:38 +0200 Subject: [PATCH 014/118] render pdu fields --- frontend/src/__generated__/graphql.tsx | 17 +- .../attributes/ImportedIndividualFields.ts | 16 +- .../queries/targeting/AllFieldsAttributes.ts | 7 + .../EditTargetPopulation.tsx | 4 +- .../src/components/targeting/FieldChooser.tsx | 2 +- .../src/components/targeting/SubField.tsx | 340 ++++++++++-------- .../targeting/TargetPopulationCore.tsx | 8 +- ...s.tsx => TargetPopulationTableFilters.tsx} | 6 +- .../TargetingCriteriaDisabled.tsx | 73 ---- .../targeting/TargetingCriteria/index.ts | 3 - .../Criteria.tsx | 1 + .../CriteriaAutocomplete.tsx | 2 +- .../TargetingCriteriaDisplay.tsx} | 19 +- .../TargetingCriteriaDisplayDisabled.tsx | 71 ++++ .../VulnerabilityScoreComponent.tsx | 0 ...eriaForm.tsx => TargetingCriteriaForm.tsx} | 18 +- ...x => TargetingCriteriaHouseholdFilter.tsx} | 2 +- ...argetingCriteriaIndividualBlockFilter.tsx} | 3 +- ...rgetingCriteriaIndividualFilterBlocks.tsx} | 7 +- .../targeting/CreateTargetPopulationPage.tsx | 4 +- .../pages/targeting/TargetPopulationsPage.tsx | 6 +- .../FormikSelectField/FormikSelectField.tsx | 1 + frontend/src/utils/targetingUtils.ts | 61 +++- 23 files changed, 385 insertions(+), 286 deletions(-) rename frontend/src/components/targeting/{TargetPopulationFilters.tsx => TargetPopulationTableFilters.tsx} (96%) delete mode 100644 frontend/src/components/targeting/TargetingCriteria/TargetingCriteriaDisabled.tsx delete mode 100644 frontend/src/components/targeting/TargetingCriteria/index.ts rename frontend/src/components/targeting/{TargetingCriteria => TargetingCriteriaDisplay}/Criteria.tsx (99%) rename frontend/src/components/targeting/{TargetingCriteria => TargetingCriteriaDisplay}/CriteriaAutocomplete.tsx (98%) rename frontend/src/components/targeting/{TargetingCriteria/TargetingCriteria.tsx => TargetingCriteriaDisplay/TargetingCriteriaDisplay.tsx} (97%) create mode 100644 frontend/src/components/targeting/TargetingCriteriaDisplay/TargetingCriteriaDisplayDisabled.tsx rename frontend/src/components/targeting/{TargetingCriteria => TargetingCriteriaDisplay}/VulnerabilityScoreComponent.tsx (100%) rename frontend/src/containers/forms/{TargetCriteriaForm.tsx => TargetingCriteriaForm.tsx} (94%) rename frontend/src/containers/forms/{TargetCriteriaFilter.tsx => TargetingCriteriaHouseholdFilter.tsx} (97%) rename frontend/src/containers/forms/{TargetCriteriaBlockFilter.tsx => TargetingCriteriaIndividualBlockFilter.tsx} (93%) rename frontend/src/containers/forms/{TargetCriteriaFilterBlocks.tsx => TargetingCriteriaIndividualFilterBlocks.tsx} (95%) diff --git a/frontend/src/__generated__/graphql.tsx b/frontend/src/__generated__/graphql.tsx index c4316c4205..48d3633c02 100644 --- a/frontend/src/__generated__/graphql.tsx +++ b/frontend/src/__generated__/graphql.tsx @@ -10369,7 +10369,7 @@ export type ImportedIndividualFieldsQueryVariables = Exact<{ }>; -export type ImportedIndividualFieldsQuery = { __typename?: 'Query', allFieldsAttributes?: Array<{ __typename?: 'FieldAttributeNode', isFlexField?: boolean | null, id?: string | null, type?: string | null, name?: string | null, associatedWith?: string | null, labelEn?: string | null, hint?: string | null, labels?: Array<{ __typename?: 'LabelNode', language?: string | null, label?: string | null } | null> | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', labelEn?: string | null, value?: string | null, admin?: string | null, listName?: string | null, labels?: Array<{ __typename?: 'LabelNode', label?: string | null, language?: string | null } | null> | null } | null> | null } | null> | null }; +export type ImportedIndividualFieldsQuery = { __typename?: 'Query', allFieldsAttributes?: Array<{ __typename?: 'FieldAttributeNode', isFlexField?: boolean | null, id?: string | null, type?: string | null, name?: string | null, associatedWith?: string | null, labelEn?: string | null, hint?: string | null, labels?: Array<{ __typename?: 'LabelNode', language?: string | null, label?: string | null } | null> | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', labelEn?: string | null, value?: string | null, admin?: string | null, listName?: string | null, labels?: Array<{ __typename?: 'LabelNode', label?: string | null, language?: string | null } | null> | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null> | null }; export type PduSubtypeChoicesDataQueryVariables = Exact<{ [key: string]: never; }>; @@ -11279,7 +11279,7 @@ export type AllActiveTargetPopulationsQuery = { __typename?: 'Query', allActiveT export type AllFieldsAttributesQueryVariables = Exact<{ [key: string]: never; }>; -export type AllFieldsAttributesQuery = { __typename?: 'Query', allFieldsAttributes?: Array<{ __typename?: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, associatedWith?: string | null, isFlexField?: boolean | null } | null> | null }; +export type AllFieldsAttributesQuery = { __typename?: 'Query', allFieldsAttributes?: Array<{ __typename?: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, associatedWith?: string | null, isFlexField?: boolean | null, type?: string | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null> | null }; export type AllSteficonRulesQueryVariables = Exact<{ enabled?: InputMaybe; @@ -17700,6 +17700,12 @@ export const ImportedIndividualFieldsDocument = gql` admin listName } + pduData { + id + subtype + numberOfRounds + roundsNames + } } } `; @@ -23776,6 +23782,13 @@ export const AllFieldsAttributesDocument = gql` labelEn associatedWith isFlexField + type + pduData { + id + subtype + numberOfRounds + roundsNames + } } } `; diff --git a/frontend/src/apollo/queries/core/attributes/ImportedIndividualFields.ts b/frontend/src/apollo/queries/core/attributes/ImportedIndividualFields.ts index d81a195583..03c1ebb0e7 100644 --- a/frontend/src/apollo/queries/core/attributes/ImportedIndividualFields.ts +++ b/frontend/src/apollo/queries/core/attributes/ImportedIndividualFields.ts @@ -1,8 +1,14 @@ import { gql } from '@apollo/client'; export const ImportedIndividualFields = gql` - query ImportedIndividualFields($businessAreaSlug: String, $programId: String) { - allFieldsAttributes(businessAreaSlug: $businessAreaSlug, programId: $programId) { + query ImportedIndividualFields( + $businessAreaSlug: String + $programId: String + ) { + allFieldsAttributes( + businessAreaSlug: $businessAreaSlug + programId: $programId + ) { isFlexField id type @@ -24,6 +30,12 @@ export const ImportedIndividualFields = gql` admin listName } + pduData { + id + subtype + numberOfRounds + roundsNames + } } } `; diff --git a/frontend/src/apollo/queries/targeting/AllFieldsAttributes.ts b/frontend/src/apollo/queries/targeting/AllFieldsAttributes.ts index 226ff68c9d..d4bca76329 100644 --- a/frontend/src/apollo/queries/targeting/AllFieldsAttributes.ts +++ b/frontend/src/apollo/queries/targeting/AllFieldsAttributes.ts @@ -8,6 +8,13 @@ export const AllFieldsAttributes = gql` labelEn associatedWith isFlexField + type + pduData { + id + subtype + numberOfRounds + roundsNames + } } } `; diff --git a/frontend/src/components/targeting/EditTargetPopulation/EditTargetPopulation.tsx b/frontend/src/components/targeting/EditTargetPopulation/EditTargetPopulation.tsx index 6f8dc5dfee..f45dd9d6e2 100644 --- a/frontend/src/components/targeting/EditTargetPopulation/EditTargetPopulation.tsx +++ b/frontend/src/components/targeting/EditTargetPopulation/EditTargetPopulation.tsx @@ -19,8 +19,8 @@ import * as Yup from 'yup'; import { AndDivider, AndDividerLabel } from '../AndDivider'; import { Exclusions } from '../CreateTargetPopulation/Exclusions'; import { PaperContainer } from '../PaperContainer'; -import { TargetingCriteria } from '../TargetingCriteria'; import { EditTargetPopulationHeader } from './EditTargetPopulationHeader'; +import { TargetingCriteriaDisplay } from '../TargetingCriteriaDisplay/TargetingCriteriaDisplay'; interface EditTargetPopulationProps { targetPopulation: TargetPopulationQuery['targetPopulation']; @@ -171,7 +171,7 @@ export const EditTargetPopulation = ({ ( - - - - - - - - - ); - case 'DATE': - return ( - - - } - data-cy="date-from" - /> - - - } - data-cy="date-to" - /> - - - ); - case 'INTEGER': - return ( - - - - - - - - - ); - case 'SELECT_ONE': - return field.fieldName.includes('admin') ? ( - - ) : ( - - ); - case 'SELECT_MANY': - return ( - - ); - case 'STRING': - return ( - - ); - case 'BOOL': - return ( - - ); - default: - return

{field.fieldAttribute.type}

; - } + const fieldComponent =

{field.fieldAttribute.type}

; + + const renderFieldByType = (type) => { + switch (type) { + case 'DECIMAL': + return ( + + + + + + + + + ); + case 'DATE': + return ( + + + } + data-cy="date-from" + /> + + + } + data-cy="date-to" + /> + + + ); + case 'INTEGER': + return ( + + + + + + + + + ); + case 'SELECT_ONE': + return field.fieldName.includes('admin') ? ( + + ) : ( + + ); + case 'SELECT_MANY': + return ( + + ); + case 'STRING': + return ( + + ); + case 'BOOL': + return ( + + ); + case 'PDU': + console.log('field.pduData', field.pduData); + return ( + + + ({ + value: n + 1, + name: `${n + 1}`, + }), + ) + : [] + } + label="Round" + /> + + + {field.pduData && field.pduData.subtype + ? renderFieldByType(field.pduData.subtype) + : null} + + + ); + + default: + return fieldComponent; + } + }; + + return renderFieldByType(field.fieldAttribute.type); } diff --git a/frontend/src/components/targeting/TargetPopulationCore.tsx b/frontend/src/components/targeting/TargetPopulationCore.tsx index 36802db922..71d8b22381 100644 --- a/frontend/src/components/targeting/TargetPopulationCore.tsx +++ b/frontend/src/components/targeting/TargetPopulationCore.tsx @@ -11,11 +11,11 @@ import { } from '@generated/graphql'; import { PaperContainer } from './PaperContainer'; import { ResultsForHouseholds } from './ResultsForHouseholds'; -import { TargetingCriteria } from './TargetingCriteria'; import { TargetingHouseholds } from './TargetingHouseholds'; import { useBaseUrl } from '@hooks/useBaseUrl'; import { TargetPopulationPeopleTable } from '@containers/tables/targeting/TargetPopulationPeopleTable'; import { ResultsForPeople } from '@components/targeting/ResultsForPeople'; +import { TargetingCriteriaDisplay } from './TargetingCriteriaDisplay/TargetingCriteriaDisplay'; const Label = styled.p` color: #b1b1b5; @@ -111,7 +111,7 @@ export const TargetPopulationCore = ({ {individualIds} )} - diff --git a/frontend/src/components/targeting/TargetPopulationFilters.tsx b/frontend/src/components/targeting/TargetPopulationTableFilters.tsx similarity index 96% rename from frontend/src/components/targeting/TargetPopulationFilters.tsx rename to frontend/src/components/targeting/TargetPopulationTableFilters.tsx index d460ec45b6..bc2b47b948 100644 --- a/frontend/src/components/targeting/TargetPopulationFilters.tsx +++ b/frontend/src/components/targeting/TargetPopulationTableFilters.tsx @@ -14,20 +14,20 @@ import { SearchTextField } from '@core/SearchTextField'; import { SelectFilter } from '@core/SelectFilter'; import { FiltersSection } from '@core/FiltersSection'; -interface TargetPopulationFiltersProps { +interface TargetPopulationTableFiltersProps { filter; setFilter: (filter) => void; initialFilter; appliedFilter; setAppliedFilter: (filter) => void; } -export const TargetPopulationFilters = ({ +export const TargetPopulationTableFilters = ({ filter, setFilter, initialFilter, appliedFilter, setAppliedFilter, -}: TargetPopulationFiltersProps): React.ReactElement => { +}: TargetPopulationTableFiltersProps): React.ReactElement => { const { t } = useTranslation(); const navigate = useNavigate(); const location = useLocation(); diff --git a/frontend/src/components/targeting/TargetingCriteria/TargetingCriteriaDisabled.tsx b/frontend/src/components/targeting/TargetingCriteria/TargetingCriteriaDisabled.tsx deleted file mode 100644 index b3d1cc0e14..0000000000 --- a/frontend/src/components/targeting/TargetingCriteria/TargetingCriteriaDisabled.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import * as React from 'react'; -import styled from 'styled-components'; -import { useTranslation } from 'react-i18next'; -import { Tooltip, Box } from '@mui/material'; -import { AddCircleOutline } from '@mui/icons-material'; - - - -export const ContentWrapper = styled(Box)` - display: flex; - flex-wrap: wrap; -`; - -const IconWrapper = styled.div` - display: flex; - color: #a0b6d6; -`; - -const AddCriteria = styled.div` - display: flex; - align-items: center; - justify-content: center; - color: #003c8f; - border: 2px solid #a0b6d6; - border-radius: 3px; - font-size: 16px; - padding: ${({ theme }) => theme.spacing(6)} - ${({ theme }) => theme.spacing(28)}; - cursor: pointer; - p { - font-weight: 500; - margin: 0 0 0 ${({ theme }) => theme.spacing(2)}; - } -`; - -export function TargetingCriteriaDisabled({ - showTooltip = false, -}): React.ReactElement { - const { t } = useTranslation(); - return ( - <> - {showTooltip ? ( - - -
- null} - data-cy="button-target-population-disabled-add-criteria" - > - - -

{t('Add Filter')}

-
-
-
-
-
- ) : ( - - null} - data-cy="button-target-population-disabled-add-criteria" - > - - -

{t('Add Filter')}

-
-
-
- )} - - ); -} diff --git a/frontend/src/components/targeting/TargetingCriteria/index.ts b/frontend/src/components/targeting/TargetingCriteria/index.ts deleted file mode 100644 index 371e158105..0000000000 --- a/frontend/src/components/targeting/TargetingCriteria/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { TargetingCriteria } from './TargetingCriteria'; - -export { TargetingCriteria }; diff --git a/frontend/src/components/targeting/TargetingCriteria/Criteria.tsx b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx similarity index 99% rename from frontend/src/components/targeting/TargetingCriteria/Criteria.tsx rename to frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx index dc775e9ac7..37f444f5e1 100644 --- a/frontend/src/components/targeting/TargetingCriteria/Criteria.tsx +++ b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx @@ -70,6 +70,7 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { ? choices.find((each) => each.value === argument)?.labelEn : argument; }; + console.log('field', field); const { t } = useTranslation(); let fieldElement; switch (field.comparisonMethod) { diff --git a/frontend/src/components/targeting/TargetingCriteria/CriteriaAutocomplete.tsx b/frontend/src/components/targeting/TargetingCriteriaDisplay/CriteriaAutocomplete.tsx similarity index 98% rename from frontend/src/components/targeting/TargetingCriteria/CriteriaAutocomplete.tsx rename to frontend/src/components/targeting/TargetingCriteriaDisplay/CriteriaAutocomplete.tsx index dc8422ce6d..d8ab7a3654 100644 --- a/frontend/src/components/targeting/TargetingCriteria/CriteriaAutocomplete.tsx +++ b/frontend/src/components/targeting/TargetingCriteriaDisplay/CriteriaAutocomplete.tsx @@ -31,7 +31,7 @@ export function CriteriaAutocomplete({ index === self.findIndex((t) => t.name === choice.name), ); setChoicesWithoutDuplicates(uniqueChoices); - }, [ otherProps.choices]); + }, [otherProps.choices]); const isInvalid = get(otherProps.form.errors, field.name) && get(otherProps.form.touched, field.name); diff --git a/frontend/src/components/targeting/TargetingCriteria/TargetingCriteria.tsx b/frontend/src/components/targeting/TargetingCriteriaDisplay/TargetingCriteriaDisplay.tsx similarity index 97% rename from frontend/src/components/targeting/TargetingCriteria/TargetingCriteria.tsx rename to frontend/src/components/targeting/TargetingCriteriaDisplay/TargetingCriteriaDisplay.tsx index 585d27c090..b8150440d4 100644 --- a/frontend/src/components/targeting/TargetingCriteria/TargetingCriteria.tsx +++ b/frontend/src/components/targeting/TargetingCriteriaDisplay/TargetingCriteriaDisplay.tsx @@ -1,4 +1,4 @@ -import { TargetCriteriaForm } from '@containers/forms/TargetCriteriaForm'; +import { TargetingCriteriaForm } from '@containers/forms/TargetingCriteriaForm'; import { DataCollectingTypeType, TargetPopulationQuery, @@ -15,8 +15,8 @@ import styled from 'styled-components'; import { Criteria } from './Criteria'; import { ContentWrapper, - TargetingCriteriaDisabled, -} from './TargetingCriteriaDisabled'; + TargetingCriteriaDisplayDisabled, +} from './TargetingCriteriaDisplayDisabled'; import { VulnerabilityScoreComponent } from './VulnerabilityScoreComponent'; import { useProgramContext } from 'src/programContext'; import { useCachedImportedIndividualFieldsQuery } from '@hooks/useCachedImportedIndividualFields'; @@ -77,7 +77,7 @@ const NoWrapCheckbox = styled(FormControlLabel)` white-space: nowrap; `; -interface TargetingCriteriaProps { +interface TargetingCriteriaDisplayProps { rules?; helpers?; targetPopulation?: TargetPopulationQuery['targetPopulation']; @@ -88,7 +88,7 @@ interface TargetingCriteriaProps { category: string; } -export const TargetingCriteria = ({ +export const TargetingCriteriaDisplay = ({ rules, helpers, targetPopulation, @@ -97,17 +97,19 @@ export const TargetingCriteria = ({ isSocialDctType, isStandardDctType, category, -}: TargetingCriteriaProps): React.ReactElement => { +}: TargetingCriteriaDisplayProps): React.ReactElement => { const { t } = useTranslation(); const location = useLocation(); const { selectedProgram } = useProgramContext(); const { businessArea, programId } = useBaseUrl(); + const { data: allCoreFieldsAttributesData, loading } = useCachedImportedIndividualFieldsQuery(businessArea, programId); const [isOpen, setOpen] = useState(false); const [criteriaIndex, setIndex] = useState(null); const [criteriaObject, setCriteria] = useState({}); const [allDataChoicesDict, setAllDataChoicesDict] = useState(null); + useEffect(() => { if (loading) return; const allDataChoicesDictTmp = @@ -117,6 +119,7 @@ export const TargetingCriteria = ({ }, {}); setAllDataChoicesDict(allDataChoicesDictTmp); }, [allCoreFieldsAttributesData, loading]); + const regex = /(create|edit-tp)/; const isDetailsPage = !regex.test(location.pathname); const openModal = (criteria): void => { @@ -186,7 +189,7 @@ export const TargetingCriteria = ({ )} - closeModal()} @@ -387,5 +390,5 @@ export const TargetingCriteria = ({
); } - return ; + return ; }; diff --git a/frontend/src/components/targeting/TargetingCriteriaDisplay/TargetingCriteriaDisplayDisabled.tsx b/frontend/src/components/targeting/TargetingCriteriaDisplay/TargetingCriteriaDisplayDisabled.tsx new file mode 100644 index 0000000000..86dd3c5997 --- /dev/null +++ b/frontend/src/components/targeting/TargetingCriteriaDisplay/TargetingCriteriaDisplayDisabled.tsx @@ -0,0 +1,71 @@ +import * as React from 'react'; +import styled from 'styled-components'; +import { useTranslation } from 'react-i18next'; +import { Tooltip, Box } from '@mui/material'; +import { AddCircleOutline } from '@mui/icons-material'; + +export const ContentWrapper = styled(Box)` + display: flex; + flex-wrap: wrap; +`; + +const IconWrapper = styled.div` + display: flex; + color: #a0b6d6; +`; + +const AddCriteria = styled.div` + display: flex; + align-items: center; + justify-content: center; + color: #003c8f; + border: 2px solid #a0b6d6; + border-radius: 3px; + font-size: 16px; + padding: ${({ theme }) => theme.spacing(6)} + ${({ theme }) => theme.spacing(28)}; + cursor: pointer; + p { + font-weight: 500; + margin: 0 0 0 ${({ theme }) => theme.spacing(2)}; + } +`; + +export function TargetingCriteriaDisplayDisabled({ + showTooltip = false, +}): React.ReactElement { + const { t } = useTranslation(); + return ( + <> + {showTooltip ? ( + + +
+ null} + data-cy="button-target-population-disabled-add-criteria" + > + + +

{t('Add Filter')}

+
+
+
+
+
+ ) : ( + + null} + data-cy="button-target-population-disabled-add-criteria" + > + + +

{t('Add Filter')}

+
+
+
+ )} + + ); +} diff --git a/frontend/src/components/targeting/TargetingCriteria/VulnerabilityScoreComponent.tsx b/frontend/src/components/targeting/TargetingCriteriaDisplay/VulnerabilityScoreComponent.tsx similarity index 100% rename from frontend/src/components/targeting/TargetingCriteria/VulnerabilityScoreComponent.tsx rename to frontend/src/components/targeting/TargetingCriteriaDisplay/VulnerabilityScoreComponent.tsx diff --git a/frontend/src/containers/forms/TargetCriteriaForm.tsx b/frontend/src/containers/forms/TargetingCriteriaForm.tsx similarity index 94% rename from frontend/src/containers/forms/TargetCriteriaForm.tsx rename to frontend/src/containers/forms/TargetingCriteriaForm.tsx index 20ffc3d0e4..91da9de0da 100644 --- a/frontend/src/containers/forms/TargetCriteriaForm.tsx +++ b/frontend/src/containers/forms/TargetingCriteriaForm.tsx @@ -28,9 +28,9 @@ import { DialogContainer } from '../dialogs/DialogContainer'; import { DialogDescription } from '../dialogs/DialogDescription'; import { DialogFooter } from '../dialogs/DialogFooter'; import { DialogTitleWrapper } from '../dialogs/DialogTitleWrapper'; -import { TargetingCriteriaFilter } from './TargetCriteriaFilter'; -import { TargetCriteriaFilterBlocks } from './TargetCriteriaFilterBlocks'; +import { TargetingCriteriaIndividualFilterBlocks } from './TargetingCriteriaIndividualFilterBlocks'; import { AndDivider, AndDividerLabel } from '@components/targeting/AndDivider'; +import { TargetingCriteriaHouseholdFilter } from './TargetingCriteriaHouseholdFilter'; const ButtonBox = styled.div` width: 300px; @@ -77,7 +77,7 @@ class ArrayFieldWrapper extends React.Component { } } -interface TargetCriteriaFormPropTypes { +interface TargetingCriteriaFormPropTypes { criteria?; addCriteria: (values) => void; open: boolean; @@ -90,7 +90,7 @@ interface TargetCriteriaFormPropTypes { const associatedWith = (type) => (item) => item.associatedWith === type; const isNot = (type) => (item) => item.type !== type; -export const TargetCriteriaForm = ({ +export const TargetingCriteriaForm = ({ criteria, addCriteria, open, @@ -98,7 +98,7 @@ export const TargetCriteriaForm = ({ individualFiltersAvailable, householdFiltersAvailable, isSocialWorkingProgram, -}: TargetCriteriaFormPropTypes): React.ReactElement => { +}: TargetingCriteriaFormPropTypes): React.ReactElement => { const { t } = useTranslation(); const { businessArea, programId } = useBaseUrl(); @@ -140,6 +140,10 @@ export const TargetCriteriaForm = ({ if (!data) return null; + console.log('allDataChoicesDict', allDataChoicesDict); + //add mapping ---> isFlexField === false --> NOT_FLEX_FIELD + //add mapping ---> isFlexField === true && type !== 'PDU' --> FLEX_FIELD_NOT_PDU + //add mapping for flexfieldcategorization ---> isFlexField === true && type === 'PDU' --> FLEX_FIELD_PDU const validate = ({ filters, individualsFiltersBlocks, @@ -267,7 +271,7 @@ export const TargetCriteriaForm = ({ ref={filtersArrayWrapperRef} > {values.filters.map((each, index) => ( - {values.individualsFiltersBlocks.map((each, index) => ( - void; onDelete: () => void; }): React.ReactElement { + console.log('each', each); return (
theme.spacing(3)} ${({ theme }) => theme.spacing(5)}; `; -export function TargetCriteriaFilterBlocks({ +export function TargetingCriteriaIndividualFilterBlocks({ blockIndex, data, values, @@ -84,6 +84,7 @@ export function TargetCriteriaFilterBlocks({ const { t } = useTranslation(); const shouldShowAndDivider = blockIndex + 1 < values.individualsFiltersBlocks.length; + console.log('values', values); return (
Set Individual Criteria @@ -102,7 +103,7 @@ export function TargetCriteriaFilterBlocks({ return ( - { const { t } = useTranslation(); @@ -152,7 +152,7 @@ export const CreateTargetPopulationPage = (): React.ReactElement => { ( - { const location = useLocation(); const { t } = useTranslation(); const permissions = usePermissions(); - const { programId } = useBaseUrl(); + const { programId } = useBaseUrl(); const { data: programData } = useProgramQuery({ variables: { id: programId }, }); @@ -51,7 +51,7 @@ export const TargetPopulationsPage = (): React.ReactElement => { return ; if (!programData) return null; let Table = TargetPopulationTable; - let Filters = TargetPopulationFilters; + let Filters = TargetPopulationTableFilters; if (programData.program.isSocialWorkerProgram) { Table = TargetPopulationForPeopleTable; Filters = TargetPopulationForPeopleFilters; diff --git a/frontend/src/shared/Formik/FormikSelectField/FormikSelectField.tsx b/frontend/src/shared/Formik/FormikSelectField/FormikSelectField.tsx index b7e08e1dbd..ca16cf3c4c 100644 --- a/frontend/src/shared/Formik/FormikSelectField/FormikSelectField.tsx +++ b/frontend/src/shared/Formik/FormikSelectField/FormikSelectField.tsx @@ -33,6 +33,7 @@ export function FormikSelectField({ onChange, ...otherProps }): React.ReactElement { + console.log('choices', otherProps.choices); const isInvalid = Boolean( get(form.errors, field.name) && (get(form.touched, field.name) || form.submitCount > 0 || form.errors), diff --git a/frontend/src/utils/targetingUtils.ts b/frontend/src/utils/targetingUtils.ts index dc40476439..07c3ea88c5 100644 --- a/frontend/src/utils/targetingUtils.ts +++ b/frontend/src/utils/targetingUtils.ts @@ -1,38 +1,52 @@ -export const chooseFieldType = (value, arrayHelpers, index): void => { - const values = { - isFlexField: value.isFlexField, - associatedWith: value.associatedWith, +export const chooseFieldType = (fieldValue, arrayHelpers, index): void => { + console.log('fieldValue', fieldValue); + let flexFieldCategorization; + if (fieldValue.isFlexField === false) { + flexFieldCategorization = 'NOT_FLEX_FIELD'; + } else if (fieldValue.isFlexField === true && fieldValue.type !== 'PDU') { + flexFieldCategorization = 'FLEX_FIELD_NOT_PDU'; + } else if (fieldValue.isFlexField === true && fieldValue.type === 'PDU') { + flexFieldCategorization = 'FLEX_FIELD_PDU'; + } + + const updatedFieldValues = { + flexFieldCategorization, + associatedWith: fieldValue.associatedWith, fieldAttribute: { - labelEn: value.labelEn, - type: value.type, + labelEn: fieldValue.labelEn, + type: fieldValue.type, choices: null, }, value: null, + pduData: fieldValue.pduData, }; - switch (value.type) { + + switch (fieldValue.type) { case 'INTEGER': - values.value = { from: '', to: '' }; + updatedFieldValues.value = { from: '', to: '' }; break; case 'DATE': - values.value = { from: undefined, to: undefined }; + updatedFieldValues.value = { from: undefined, to: undefined }; break; case 'SELECT_ONE': - values.fieldAttribute.choices = value.choices; + updatedFieldValues.fieldAttribute.choices = fieldValue.choices; break; case 'SELECT_MANY': - values.value = []; - values.fieldAttribute.choices = value.choices; + updatedFieldValues.value = []; + updatedFieldValues.fieldAttribute.choices = fieldValue.choices; break; default: - values.value = null; + updatedFieldValues.value = null; break; } + arrayHelpers.replace(index, { - ...values, - fieldName: value.name, - type: value.type, + ...updatedFieldValues, + fieldName: fieldValue.name, + type: fieldValue.type, }); }; + export const clearField = (arrayHelpers, index): void => arrayHelpers.replace(index, {}); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -157,6 +171,7 @@ export function formatCriteriaFilters(filters) { fieldName: each.fieldName, isFlexField: each.isFlexField, fieldAttribute: each.fieldAttribute, + flexFieldClassification: each.flexFieldClassification, }; }); } @@ -175,13 +190,13 @@ function mapFilterToVariable(filter): { comparisonMethod: string; arguments; fieldName: string; - isFlexField: boolean; + flexFieldClassification: string; } { return { comparisonMethod: filter.comparisonMethod, arguments: filter.arguments, fieldName: filter.fieldName, - isFlexField: filter.isFlexField, + flexFieldClassification: filter.flexFieldClassification, }; } @@ -207,3 +222,13 @@ export function getTargetingCriteriaVariables(values) { }, }; } + +const flexFieldClassificationMap = { + NOT_FLEX_FIELD: 'Not a Flex Field', + FLEX_FIELD_NOT_PDU: 'Flex Field Not PDU', + FLEX_FIELD_PDU: 'Flex Field PDU', +}; + +export function mapFlexFieldClassification(key: string): string { + return flexFieldClassificationMap[key] || 'Unknown Classification'; +} From ed7a1675a138566d4c0b683e6d96a962a840808c Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Wed, 14 Aug 2024 05:58:48 +0200 Subject: [PATCH 015/118] fixes, adjustments, tests for PDU Targeting --- backend/hct_mis_api/apps/core/schema.py | 4 +- .../apps/periodic_data_update/utils.py | 4 +- backend/hct_mis_api/apps/targeting/choices.py | 2 +- .../apps/targeting/graphql_types.py | 2 +- .../targeting/migrations/0047_migration.py | 17 + backend/hct_mis_api/apps/targeting/models.py | 1 - .../targeting/services/targeting_service.py | 33 +- ..._test_create_target_population_mutation.py | 83 +++ .../test_create_target_population_mutation.py | 109 +++- .../tests/test_individual_block_filters.py | 245 ++++++++- .../tests/test_targeting_criteria.py | 24 +- .../test_targeting_criteria_rule_filter.py | 509 +++++++++++++++++- .../tests/test_targeting_validators.py | 6 +- .../hct_mis_api/apps/targeting/validators.py | 4 +- 14 files changed, 992 insertions(+), 51 deletions(-) create mode 100644 backend/hct_mis_api/apps/targeting/migrations/0047_migration.py diff --git a/backend/hct_mis_api/apps/core/schema.py b/backend/hct_mis_api/apps/core/schema.py index 633403dd74..8332e3a437 100644 --- a/backend/hct_mis_api/apps/core/schema.py +++ b/backend/hct_mis_api/apps/core/schema.py @@ -292,7 +292,9 @@ def get_fields_attr_generators( flex_field: Optional[bool] = None, business_area_slug: Optional[str] = None, program_id: Optional[str] = None ) -> Generator: if flex_field is not False: - yield from FlexibleAttribute.objects.order_by("created_at") + yield from FlexibleAttribute.objects.filter(Q(program__isnull=True) | Q(program__id=program_id)).order_by( + "created_at" + ) if flex_field is not True: if program_id and Program.objects.get(id=program_id).is_social_worker_program: yield from FieldFactory.from_only_scopes([Scope.XLSX_PEOPLE, Scope.TARGETING]).filtered_by_types( diff --git a/backend/hct_mis_api/apps/periodic_data_update/utils.py b/backend/hct_mis_api/apps/periodic_data_update/utils.py index fe42c36916..93c47e0837 100644 --- a/backend/hct_mis_api/apps/periodic_data_update/utils.py +++ b/backend/hct_mis_api/apps/periodic_data_update/utils.py @@ -8,5 +8,7 @@ def field_label_to_field_name(input_string: str) -> str: """ input_string = input_string.replace(" ", "_") - input_string = re.sub(r"[^\w\s-]", "", input_string) + input_string = re.sub(r"[^\w]", "", input_string) + input_string = re.sub(r"__+", "_", input_string) + input_string = input_string.strip("_") return input_string.lower() diff --git a/backend/hct_mis_api/apps/targeting/choices.py b/backend/hct_mis_api/apps/targeting/choices.py index 407714affb..e22e98b9dd 100644 --- a/backend/hct_mis_api/apps/targeting/choices.py +++ b/backend/hct_mis_api/apps/targeting/choices.py @@ -5,4 +5,4 @@ class FlexFieldClassification(models.TextChoices): NOT_FLEX_FIELD = "NOT_FLEX_FIELD", _("Not Flex Field") FLEX_FIELD_NOT_PDU = "FLEX_FIELD_NOT_PDU", _("Flex Field Not PDU") - FLEX_FIELD_PDU = "FLEX_FIELD_PDU", _("Flex Field PDU") \ No newline at end of file + FLEX_FIELD_PDU = "FLEX_FIELD_PDU", _("Flex Field PDU") diff --git a/backend/hct_mis_api/apps/targeting/graphql_types.py b/backend/hct_mis_api/apps/targeting/graphql_types.py index 9e10ba6285..8cdc38f876 100644 --- a/backend/hct_mis_api/apps/targeting/graphql_types.py +++ b/backend/hct_mis_api/apps/targeting/graphql_types.py @@ -18,7 +18,7 @@ from hct_mis_api.apps.core.field_attributes.fields_types import Scope from hct_mis_api.apps.core.models import FlexibleAttribute from hct_mis_api.apps.core.schema import ExtendedConnection, FieldAttributeNode -from hct_mis_api.apps.core.utils import decode_id_string_required, decode_id_string +from hct_mis_api.apps.core.utils import decode_id_string from hct_mis_api.apps.household.schema import HouseholdNode from hct_mis_api.apps.program.models import Program from hct_mis_api.apps.targeting.choices import FlexFieldClassification diff --git a/backend/hct_mis_api/apps/targeting/migrations/0047_migration.py b/backend/hct_mis_api/apps/targeting/migrations/0047_migration.py new file mode 100644 index 0000000000..9d88eaec6a --- /dev/null +++ b/backend/hct_mis_api/apps/targeting/migrations/0047_migration.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.25 on 2024-08-13 23:24 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('targeting', '0046_migration'), + ] + + operations = [ + migrations.RemoveField( + model_name='targetingcriteriarulefilter', + name='round_number', + ), + ] diff --git a/backend/hct_mis_api/apps/targeting/models.py b/backend/hct_mis_api/apps/targeting/models.py index af1b3217a9..7ead502691 100644 --- a/backend/hct_mis_api/apps/targeting/models.py +++ b/backend/hct_mis_api/apps/targeting/models.py @@ -466,7 +466,6 @@ class TargetingCriteriaRuleFilter(TimeStampedUUIDModel, TargetingCriteriaFilterB Array of arguments """ ) - round_number = models.PositiveIntegerField(null=True, blank=True) @property def is_social_worker_program(self) -> bool: diff --git a/backend/hct_mis_api/apps/targeting/services/targeting_service.py b/backend/hct_mis_api/apps/targeting/services/targeting_service.py index fb5ca11c11..1ebc316f9c 100644 --- a/backend/hct_mis_api/apps/targeting/services/targeting_service.py +++ b/backend/hct_mis_api/apps/targeting/services/targeting_service.py @@ -246,7 +246,7 @@ class TargetingCriteriaFilterBase: }, "IS_NULL": { "arguments": 1, - "lookup": "__isnull", + "lookup": "", "negative": False, "supported_types": ["PDU"], }, @@ -280,6 +280,8 @@ def prepare_arguments(self, arguments: List, field_attr: str) -> List: return arguments type = get_attr_value("type", field_attr, None) if type == FlexibleAttribute.PDU: + if arguments == [None]: + return arguments type = field_attr.pdu_data.subtype if type == TYPE_DECIMAL: return [float(arg) for arg in arguments] @@ -330,6 +332,13 @@ def get_query_for_lookup( query = Q(**{f"{lookup}{comparison_attribute.get('lookup')}": argument}) if comparison_attribute.get("negative"): return ~query + # ignore null values for PDU flex fields + if ( + self.comparison_method != "IS_NULL" + and self.flex_field_classification == FlexFieldClassification.FLEX_FIELD_PDU + ): + query &= ~Q(**{f"{lookup}": None}) + return query def get_query_for_core_field(self) -> Q: @@ -361,27 +370,31 @@ def get_query_for_core_field(self) -> Q: def get_query_for_flex_field(self) -> Q: if self.flex_field_classification == FlexFieldClassification.FLEX_FIELD_PDU: - program = self.targeting_criteria_rule.targeting_criteria.target_population.program - flex_field_attr = FlexibleAttribute.objects.get(name=self.field_name, program=program) + program = ( + self.individuals_filters_block.targeting_criteria_rule.targeting_criteria.target_population.program + ) + flex_field_attr = FlexibleAttribute.objects.filter(name=self.field_name, program=program).first() if not flex_field_attr: - logger.error(f"There is no PDU Flex Field Attribute associated with this fieldName {self.field_name} in program {program.name}") + logger.error( + f"There is no PDU Flex Field Attribute associated with this fieldName {self.field_name} in program {program.name}" + ) raise ValidationError( f"There is no PDU Flex Field Attribute associated with this fieldName {self.field_name} in program {program.name}" ) if not self.round_number: - logger.error(f"Round number is required for PDU Flex Field Attribute {self.field_name}") - raise ValidationError(f"Round number is required for PDU Flex Field Attribute {self.field_name}") + logger.error(f"Round number is missing for PDU Flex Field Attribute {self.field_name}") + raise ValidationError(f"Round number is missing for PDU Flex Field Attribute {self.field_name}") flex_field_attr_rounds_number = flex_field_attr.pdu_data.number_of_rounds if self.round_number > flex_field_attr_rounds_number: logger.error( - f"Round number {self.round_number} is greater than the number of rounds {flex_field_attr_rounds_number} for PDU Flex Field Attribute {self.field_name}" + f"Round number {self.round_number} is greater than the number of rounds for PDU Flex Field Attribute {self.field_name}" ) raise ValidationError( - f"Round number {self.round_number} is greater than the number of rounds {flex_field_attr_rounds_number} for PDU Flex Field Attribute {self.field_name}" + f"Round number {self.round_number} is greater than the number of rounds for PDU Flex Field Attribute {self.field_name}" ) - field_name_combined = f"{flex_field_attr.name}__{self.round_number}" + field_name_combined = f"{flex_field_attr.name}__{self.round_number}__value" else: - flex_field_attr = FlexibleAttribute.objects.get(name=self.field_name, program=None) + flex_field_attr = FlexibleAttribute.objects.filter(name=self.field_name, program=None).first() if not flex_field_attr: logger.error(f"There is no Flex Field Attributes associated with this fieldName {self.field_name}") raise ValidationError( diff --git a/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_create_target_population_mutation.py b/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_create_target_population_mutation.py index e17ec3b724..39cc8f15c6 100644 --- a/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_create_target_population_mutation.py +++ b/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_create_target_population_mutation.py @@ -29,6 +29,8 @@ 'fieldName': 'size', 'flexFieldClassification': 'NOT_FLEX_FIELD' } + ], + 'individualsFiltersBlocks': [ ] } ] @@ -285,3 +287,84 @@ } ] } + +snapshots['TestCreateTargetPopulationMutation::test_create_mutation_with_flex_field 1'] = { + 'data': { + 'createTargetPopulation': { + 'targetPopulation': { + 'hasEmptyCriteria': False, + 'hasEmptyIdsCriteria': True, + 'name': 'Example name 5', + 'status': 'OPEN', + 'targetingCriteria': { + 'householdIds': '', + 'individualIds': '', + 'rules': [ + { + 'filters': [ + ], + 'individualsFiltersBlocks': [ + { + 'individualBlockFilters': [ + { + 'arguments': [ + 'Average' + ], + 'comparisonMethod': 'CONTAINS', + 'fieldName': 'flex_field_1', + 'flexFieldClassification': 'FLEX_FIELD_NOT_PDU', + 'roundNumber': None + } + ] + } + ] + } + ] + }, + 'totalHouseholdsCount': None, + 'totalIndividualsCount': None + } + } + } +} + +snapshots['TestCreateTargetPopulationMutation::test_create_mutation_with_pdu_flex_field 1'] = { + 'data': { + 'createTargetPopulation': { + 'targetPopulation': { + 'hasEmptyCriteria': False, + 'hasEmptyIdsCriteria': True, + 'name': 'Example name 5', + 'status': 'OPEN', + 'targetingCriteria': { + 'householdIds': '', + 'individualIds': '', + 'rules': [ + { + 'filters': [ + ], + 'individualsFiltersBlocks': [ + { + 'individualBlockFilters': [ + { + 'arguments': [ + '2', + '3.5' + ], + 'comparisonMethod': 'RANGE', + 'fieldName': 'pdu_field_1', + 'flexFieldClassification': 'FLEX_FIELD_PDU', + 'roundNumber': 1 + } + ] + } + ] + } + ] + }, + 'totalHouseholdsCount': None, + 'totalIndividualsCount': None + } + } + } +} diff --git a/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py b/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py index e6c7a4a832..9841aef919 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py @@ -5,8 +5,16 @@ from hct_mis_api.apps.account.fixtures import UserFactory from hct_mis_api.apps.account.permissions import Permissions from hct_mis_api.apps.core.base_test_case import APITestCase -from hct_mis_api.apps.core.fixtures import create_afghanistan -from hct_mis_api.apps.core.models import BusinessArea +from hct_mis_api.apps.core.fixtures import ( + FlexibleAttributeForPDUFactory, + PeriodicFieldDataFactory, + create_afghanistan, +) +from hct_mis_api.apps.core.models import ( + BusinessArea, + FlexibleAttribute, + PeriodicFieldData, +) from hct_mis_api.apps.household.fixtures import create_household from hct_mis_api.apps.household.models import Household from hct_mis_api.apps.program.fixtures import ProgramFactory @@ -35,6 +43,15 @@ class TestCreateTargetPopulationMutation(APITestCase): arguments flexFieldClassification } + individualsFiltersBlocks{ + individualBlockFilters{ + comparisonMethod + fieldName + arguments + flexFieldClassification + roundNumber + } + } } } } @@ -57,6 +74,21 @@ def setUpTestData(cls) -> None: create_household( {"size": 4, "residence_status": "HOST", "program": cls.program}, ) + FlexibleAttribute.objects.create( + name="flex_field_1", + type=FlexibleAttribute.STRING, + associated_with=FlexibleAttribute.ASSOCIATED_WITH_INDIVIDUAL, + ) + pdu_data = PeriodicFieldDataFactory( + subtype=PeriodicFieldData.DECIMAL, + number_of_rounds=1, + rounds_names=["Round 1"], + ) + FlexibleAttributeForPDUFactory( + program=cls.program, + label="PDU Field 1", + pdu_data=pdu_data, + ) @parameterized.expand( [ @@ -278,3 +310,76 @@ def test_create_mutation_target_by_id(self) -> None: context={"user": self.user}, variables=variables, ) + + def test_create_mutation_with_flex_field(self) -> None: + self.create_user_role_with_permissions(self.user, [Permissions.TARGETING_CREATE], self.program.business_area) + + variables = { + "createTargetPopulationInput": { + "name": "Example name 5 ", + "businessAreaSlug": "afghanistan", + "programId": self.id_to_base64(self.program.id, "ProgramNode"), + "excludedIds": "", + "targetingCriteria": { + "rules": [ + { + "filters": [], + "individualsFiltersBlocks": [ + { + "individualBlockFilters": [ + { + "comparisonMethod": "CONTAINS", + "arguments": ["Average"], + "fieldName": "flex_field_1", + "flexFieldClassification": "FLEX_FIELD_NOT_PDU", + } + ] + } + ], + } + ] + }, + } + } + self.snapshot_graphql_request( + request_string=TestCreateTargetPopulationMutation.MUTATION_QUERY, + context={"user": self.user}, + variables=variables, + ) + + def test_create_mutation_with_pdu_flex_field(self) -> None: + self.create_user_role_with_permissions(self.user, [Permissions.TARGETING_CREATE], self.program.business_area) + + variables = { + "createTargetPopulationInput": { + "name": "Example name 5 ", + "businessAreaSlug": "afghanistan", + "programId": self.id_to_base64(self.program.id, "ProgramNode"), + "excludedIds": "", + "targetingCriteria": { + "rules": [ + { + "filters": [], + "individualsFiltersBlocks": [ + { + "individualBlockFilters": [ + { + "comparisonMethod": "CONTAINS", + "arguments": ["asdsaddsaads"], + "fieldName": "pdu_field_1", + "flexFieldClassification": "FLEX_FIELD_PDU", + "roundNumber": "1", + } + ] + } + ], + } + ] + }, + } + } + self.snapshot_graphql_request( + request_string=TestCreateTargetPopulationMutation.MUTATION_QUERY, + context={"user": self.user}, + variables=variables, + ) diff --git a/backend/hct_mis_api/apps/targeting/tests/test_individual_block_filters.py b/backend/hct_mis_api/apps/targeting/tests/test_individual_block_filters.py index 75fd0219b6..96152385e2 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_individual_block_filters.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_individual_block_filters.py @@ -1,10 +1,16 @@ from django.core.management import call_command from django.test import TestCase -from hct_mis_api.apps.core.fixtures import create_afghanistan -from hct_mis_api.apps.core.models import BusinessArea +from hct_mis_api.apps.core.fixtures import ( + FlexibleAttributeForPDUFactory, + PeriodicFieldDataFactory, + create_afghanistan, +) +from hct_mis_api.apps.core.models import FlexibleAttribute, PeriodicFieldData from hct_mis_api.apps.household.fixtures import create_household_and_individuals from hct_mis_api.apps.household.models import FEMALE, MALE, Household +from hct_mis_api.apps.program.fixtures import ProgramFactory +from hct_mis_api.apps.targeting.choices import FlexFieldClassification from hct_mis_api.apps.targeting.models import ( TargetingCriteria, TargetingCriteriaQueryingBase, @@ -21,22 +27,24 @@ class TestIndividualBlockFilter(TestCase): @classmethod def setUpTestData(cls) -> None: call_command("loadflexfieldsattributes") - create_afghanistan() - business_area = BusinessArea.objects.first() + cls.business_area = create_afghanistan() + cls.program = ProgramFactory(business_area=cls.business_area, name="Test Program") (household, individuals) = create_household_and_individuals( { - "business_area": business_area, + "business_area": cls.business_area, }, [{"sex": "MALE", "marital_status": "MARRIED"}], ) cls.household_1_indiv = household + cls.individual_1 = individuals[0] (household, individuals) = create_household_and_individuals( { - "business_area": business_area, + "business_area": cls.business_area, }, [{"sex": "MALE", "marital_status": "SINGLE"}, {"sex": "FEMALE", "marital_status": "MARRIED"}], ) cls.household_2_indiv = household + cls.individual_2 = individuals[0] def test_all_individuals_are_female(self) -> None: queryset = Household.objects.all() @@ -125,3 +133,228 @@ def test_two_separate_blocks_on_mixins(self) -> None: query = query.filter(tc.get_query()) self.assertEqual(query.count(), 1) self.assertEqual(query.first().id, self.household_2_indiv.id) + + def test_filter_on_flex_field_not_exist(self) -> None: + tp = TargetPopulation(program=self.program) + tc = TargetingCriteria() + tc.target_population = tp + tc.save() + tcr = TargetingCriteriaRule() + tcr.targeting_criteria = tc + tcr.save() + individuals_filters_block = TargetingIndividualRuleFilterBlock( + targeting_criteria_rule=tcr, target_only_hoh=False + ) + individuals_filters_block.save() + query = Household.objects.all() + flex_field_filter = TargetingIndividualBlockRuleFilter( + individuals_filters_block=individuals_filters_block, + comparison_method="CONTAINS", + field_name="flex_field_2", + arguments=["Average"], + flex_field_classification=FlexFieldClassification.FLEX_FIELD_NOT_PDU, + ) + flex_field_filter.save() + + with self.assertRaises(Exception) as e: + query.filter(tc.get_query()) + self.assertIn( + "There is no Flex Field Attributes associated with this fieldName flex_field_2", + str(e.exception), + ) + + def test_filter_on_flex_field(self) -> None: + tp = TargetPopulation(program=self.program) + tc = TargetingCriteria() + tc.target_population = tp + tc.save() + tcr = TargetingCriteriaRule() + tcr.targeting_criteria = tc + tcr.save() + individuals_filters_block = TargetingIndividualRuleFilterBlock( + targeting_criteria_rule=tcr, target_only_hoh=False + ) + individuals_filters_block.save() + FlexibleAttribute.objects.create( + name="flex_field_1", + type=FlexibleAttribute.STRING, + associated_with=FlexibleAttribute.ASSOCIATED_WITH_INDIVIDUAL, + ) + query = Household.objects.all() + flex_field_filter = TargetingIndividualBlockRuleFilter( + individuals_filters_block=individuals_filters_block, + comparison_method="CONTAINS", + field_name="flex_field_1", + arguments=["Average"], + flex_field_classification=FlexFieldClassification.FLEX_FIELD_NOT_PDU, + ) + flex_field_filter.save() + query = query.filter(tc.get_query()) + self.assertEqual(query.count(), 0) + + self.individual_1.flex_fields["flex_field_1"] = "Average value" + self.individual_1.save() + + query = query.filter(tc.get_query()) + + self.assertEqual(query.count(), 1) + self.assertEqual(query.first().id, self.household_1_indiv.id) + + def test_filter_on_pdu_flex_field_not_exist(self) -> None: + tp = TargetPopulation(program=self.program) + tc = TargetingCriteria() + tc.target_population = tp + tc.save() + tcr = TargetingCriteriaRule() + tcr.targeting_criteria = tc + tcr.save() + individuals_filters_block = TargetingIndividualRuleFilterBlock( + targeting_criteria_rule=tcr, target_only_hoh=False + ) + individuals_filters_block.save() + query = Household.objects.all() + pdu_filter = TargetingIndividualBlockRuleFilter( + individuals_filters_block=individuals_filters_block, + comparison_method="RANGE", + field_name="pdu_field_1", + arguments=["2", "3"], + round_number=1, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_PDU, + ) + pdu_filter.save() + + with self.assertRaises(Exception) as e: + query.filter(tc.get_query()) + self.assertIn( + "There is no PDU Flex Field Attribute associated with this fieldName pdu_field_1 in program Test Program", + str(e.exception), + ) + + def test_filter_on_pdu_flex_field_no_round_number(self) -> None: + tp = TargetPopulation(program=self.program) + tc = TargetingCriteria() + tc.target_population = tp + tc.save() + tcr = TargetingCriteriaRule() + tcr.targeting_criteria = tc + tcr.save() + individuals_filters_block = TargetingIndividualRuleFilterBlock( + targeting_criteria_rule=tcr, target_only_hoh=False + ) + individuals_filters_block.save() + pdu_data = PeriodicFieldDataFactory( + subtype=PeriodicFieldData.DECIMAL, + number_of_rounds=2, + rounds_names=["Round 1", "Rounds 2"], + ) + FlexibleAttributeForPDUFactory( + program=self.program, + label="PDU Field 1", + pdu_data=pdu_data, + ) + query = Household.objects.all() + pdu_filter = TargetingIndividualBlockRuleFilter( + individuals_filters_block=individuals_filters_block, + comparison_method="RANGE", + field_name="pdu_field_1", + arguments=["2", "3"], + flex_field_classification=FlexFieldClassification.FLEX_FIELD_PDU, + ) + pdu_filter.save() + + with self.assertRaises(Exception) as e: + query.filter(tc.get_query()) + self.assertIn( + "Round number is missing for PDU Flex Field Attribute pdu_field_1", + str(e.exception), + ) + + def test_filter_on_pdu_flex_field_incorrect_round_number(self) -> None: + tp = TargetPopulation(program=self.program) + tc = TargetingCriteria() + tc.target_population = tp + tc.save() + tcr = TargetingCriteriaRule() + tcr.targeting_criteria = tc + tcr.save() + individuals_filters_block = TargetingIndividualRuleFilterBlock( + targeting_criteria_rule=tcr, target_only_hoh=False + ) + individuals_filters_block.save() + pdu_data = PeriodicFieldDataFactory( + subtype=PeriodicFieldData.DECIMAL, + number_of_rounds=2, + rounds_names=["Round 1", "Rounds 2"], + ) + FlexibleAttributeForPDUFactory( + program=self.program, + label="PDU Field 1", + pdu_data=pdu_data, + ) + query = Household.objects.all() + pdu_filter = TargetingIndividualBlockRuleFilter( + individuals_filters_block=individuals_filters_block, + comparison_method="RANGE", + field_name="pdu_field_1", + arguments=["2", "3"], + round_number=3, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_PDU, + ) + pdu_filter.save() + + with self.assertRaises(Exception) as e: + query.filter(tc.get_query()) + self.assertIn( + "Round number 3 is greater than the number of rounds for PDU Flex Field Attribute pdu_field_1", + str(e.exception), + ) + + def test_filter_on_pdu_flex_field(self) -> None: + tp = TargetPopulation(program=self.program) + tc = TargetingCriteria() + tc.target_population = tp + tc.save() + tcr = TargetingCriteriaRule() + tcr.targeting_criteria = tc + tcr.save() + individuals_filters_block = TargetingIndividualRuleFilterBlock( + targeting_criteria_rule=tcr, target_only_hoh=False + ) + individuals_filters_block.save() + pdu_data = PeriodicFieldDataFactory( + subtype=PeriodicFieldData.DECIMAL, + number_of_rounds=2, + rounds_names=["Round 1", "Rounds 2"], + ) + FlexibleAttributeForPDUFactory( + program=self.program, + label="PDU Field 1", + pdu_data=pdu_data, + ) + query = Household.objects.all() + pdu_filter = TargetingIndividualBlockRuleFilter.objects.create( + individuals_filters_block=individuals_filters_block, + comparison_method="RANGE", + field_name="pdu_field_1", + arguments=["2", "3"], + round_number=1, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_PDU, + ) + pdu_filter.save() + + self.individual_1.flex_fields = {"pdu_field_1": {"1": {"value": None}, "2": {"value": None}}} + self.individual_1.save() + self.individual_2.flex_fields = { + "pdu_field_1": {"1": {"value": 1, "collection_date": "2021-01-01"}, "2": {"value": None}} + } + self.individual_2.save() + + query = query.filter(tc.get_query()) + self.assertEqual(query.count(), 0) + + self.individual_1.flex_fields["pdu_field_1"]["1"] = {"value": 2.5, "collection_date": "2021-01-01"} + self.individual_1.save() + + query = query.filter(tc.get_query()) + self.assertEqual(query.count(), 1) + self.assertEqual(query.first().id, self.household_1_indiv.id) diff --git a/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria.py b/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria.py index 4c25ce86da..ec01bc44bf 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria.py @@ -71,7 +71,7 @@ def test_size(self) -> None: "comparison_method": "EQUALS", "arguments": [2], "field_name": "size", - "is_flex_field": False, + "flex_field_classification": "NOT_FLEX_FIELD", } ).get_query() ) @@ -88,7 +88,7 @@ def test_residence_status(self) -> None: "comparison_method": "EQUALS", "arguments": ["REFUGEE"], "field_name": "residence_status", - "is_flex_field": False, + "flex_field_classification": "NOT_FLEX_FIELD", } ).get_query() ) @@ -105,7 +105,7 @@ def test_flex_field_variables(self) -> None: "comparison_method": "EQUALS", "arguments": ["0"], "field_name": "unaccompanied_child_h_f", - "is_flex_field": True, + "flex_field_classification": "FLEX_FIELD_NOT_PDU", } ).get_query() ) @@ -122,7 +122,7 @@ def test_select_many_variables(self) -> None: "comparison_method": "CONTAINS", "arguments": ["other_public", "pharmacy", "other_private"], "field_name": "treatment_facility_h_f", - "is_flex_field": True, + "flex_field_classification": "FLEX_FIELD_NOT_PDU", } ).get_query() ) @@ -214,13 +214,13 @@ def test_marital_status(self) -> None: "comparison_method": "EQUALS", "arguments": ["MARRIED"], "field_name": "marital_status", - "is_flex_field": False, + "flex_field_classification": "NOT_FLEX_FIELD", }, { "comparison_method": "EQUALS", "arguments": ["MALE"], "field_name": "sex", - "is_flex_field": False, + "flex_field_classification": "NOT_FLEX_FIELD", }, ] ).get_query() @@ -239,7 +239,7 @@ def test_observed_disability(self) -> None: "comparison_method": "CONTAINS", "arguments": ["COMMUNICATING", "HEARING", "MEMORY", "SEEING", "WALKING", "SELF_CARE"], "field_name": "observed_disability", - "is_flex_field": False, + "flex_field_classification": "NOT_FLEX_FIELD", }, ] ).get_query() @@ -258,7 +258,7 @@ def test_ranges(self) -> None: "comparison_method": "RANGE", "arguments": [20, 25], "field_name": "age", - "is_flex_field": False, + "flex_field_classification": "NOT_FLEX_FIELD", }, ] ).get_query() @@ -276,7 +276,7 @@ def test_ranges(self) -> None: "comparison_method": "RANGE", "arguments": [22, 26], "field_name": "age", - "is_flex_field": False, + "flex_field_classification": "NOT_FLEX_FIELD", }, ] ).get_query() @@ -294,7 +294,7 @@ def test_ranges(self) -> None: "comparison_method": "LESS_THAN", "arguments": [20], "field_name": "age", - "is_flex_field": False, + "flex_field_classification": "NOT_FLEX_FIELD", }, ] ).get_query() @@ -312,7 +312,7 @@ def test_ranges(self) -> None: "comparison_method": "LESS_THAN", "arguments": [24], "field_name": "age", - "is_flex_field": False, + "flex_field_classification": "NOT_FLEX_FIELD", }, ] ).get_query() @@ -330,7 +330,7 @@ def test_ranges(self) -> None: "comparison_method": "GREATER_THAN", "arguments": [20], "field_name": "age", - "is_flex_field": False, + "flex_field_classification": "NOT_FLEX_FIELD", }, ] ).get_query() diff --git a/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria_rule_filter.py b/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria_rule_filter.py index d7b6aa1ff6..7b0871ca21 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria_rule_filter.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria_rule_filter.py @@ -9,16 +9,26 @@ from freezegun import freeze_time from pytz import utc -from hct_mis_api.apps.core.fixtures import create_afghanistan -from hct_mis_api.apps.core.models import BusinessArea +from hct_mis_api.apps.core.fixtures import ( + FlexibleAttributeForPDUFactory, + PeriodicFieldDataFactory, + create_afghanistan, +) +from hct_mis_api.apps.core.models import PeriodicFieldData from hct_mis_api.apps.household.fixtures import ( create_household, create_household_and_individuals, ) from hct_mis_api.apps.household.models import Household, Individual +from hct_mis_api.apps.program.fixtures import ProgramFactory +from hct_mis_api.apps.targeting.choices import FlexFieldClassification from hct_mis_api.apps.targeting.models import ( + TargetingCriteria, + TargetingCriteriaRule, TargetingCriteriaRuleFilter, TargetingIndividualBlockRuleFilter, + TargetingIndividualRuleFilterBlock, + TargetPopulation, ) @@ -26,8 +36,7 @@ class TargetingCriteriaRuleFilterTestCase(TestCase): @classmethod def setUpTestData(cls) -> None: households = [] - create_afghanistan() - business_area = BusinessArea.objects.first() + business_area = create_afghanistan() (household, individuals) = create_household_and_individuals( { "size": 1, @@ -296,8 +305,7 @@ class TargetingCriteriaFlexRuleFilterTestCase(TestCase): @classmethod def setUpTestData(cls) -> None: call_command("loadflexfieldsattributes") - create_afghanistan() - business_area = BusinessArea.objects.first() + business_area = create_afghanistan() (household, individuals) = create_household( { "size": 1, @@ -331,7 +339,7 @@ def test_rule_filter_household_total_households_4(self) -> None: comparison_method="EQUALS", field_name="total_households_h_f", arguments=[4], - is_flex_field=True, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_NOT_PDU, ) query = rule_filter.get_query() queryset = Household.objects.filter(query) @@ -343,7 +351,7 @@ def test_rule_filter_select_multiple_treatment_facility(self) -> None: comparison_method="CONTAINS", field_name="treatment_facility_h_f", arguments=["other_public", "private_doctor"], - is_flex_field=True, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_NOT_PDU, ) query = rule_filter.get_query() queryset = Household.objects.filter(query) @@ -354,7 +362,7 @@ def test_rule_filter_select_multiple_treatment_facility_2(self) -> None: comparison_method="CONTAINS", field_name="treatment_facility_h_f", arguments=["other_public", "government_health_center"], - is_flex_field=True, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_NOT_PDU, ) query = rule_filter.get_query() queryset = Household.objects.filter(query) @@ -365,7 +373,7 @@ def test_rule_filter_select_multiple_treatment_facility_not_contains(self) -> No comparison_method="NOT_CONTAINS", field_name="treatment_facility_h_f", arguments=["other_public", "government_health_center"], - is_flex_field=True, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_NOT_PDU, ) query = rule_filter.get_query() queryset = Household.objects.filter(query) @@ -376,8 +384,487 @@ def test_rule_filter_string_contains(self) -> None: comparison_method="CONTAINS", field_name="other_treatment_facility_h_f", arguments=["other"], - is_flex_field=True, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_NOT_PDU, ) query = rule_filter.get_query() queryset = Household.objects.filter(query) self.assertEqual(queryset.count(), 1) + + +class TargetingCriteriaPDUFlexRuleFilterTestCase(TestCase): + @classmethod + def setUpTestData(cls) -> None: + call_command("loadflexfieldsattributes") + business_area = create_afghanistan() + cls.program = ProgramFactory(name="Test Program for PDU Flex Rule Filter", business_area=business_area) + + pdu_data_string = PeriodicFieldDataFactory( + subtype=PeriodicFieldData.STRING, + number_of_rounds=2, + rounds_names=["Round 1", "Rounds 2"], + ) + cls.pdu_field_string = FlexibleAttributeForPDUFactory( + program=cls.program, + label="PDU Field STRING", + pdu_data=pdu_data_string, + ) + + pdu_data_decimal = PeriodicFieldDataFactory( + subtype=PeriodicFieldData.DECIMAL, + number_of_rounds=1, + rounds_names=["Round 1"], + ) + cls.pdu_field_decimal = FlexibleAttributeForPDUFactory( + program=cls.program, + label="PDU Field DECIMAL", + pdu_data=pdu_data_decimal, + ) + + pdu_data_date = PeriodicFieldDataFactory( + subtype=PeriodicFieldData.DATE, + number_of_rounds=1, + rounds_names=["Round 1"], + ) + cls.pdu_field_date = FlexibleAttributeForPDUFactory( + program=cls.program, + label="PDU Field DATE", + pdu_data=pdu_data_date, + ) + + pdu_data_boolean = PeriodicFieldDataFactory( + subtype=PeriodicFieldData.BOOLEAN, + number_of_rounds=1, + rounds_names=["Round 1"], + ) + cls.pdu_field_boolean = FlexibleAttributeForPDUFactory( + program=cls.program, + label="PDU Field BOOLEAN", + pdu_data=pdu_data_boolean, + ) + + (household, individuals) = create_household( + { + "size": 1, + "business_area": business_area, + "program": cls.program, + }, + { + "flex_fields": { + cls.pdu_field_string.name: {"1": {"value": None}, "2": {"value": None}}, + cls.pdu_field_decimal.name: {"1": {"value": 2.5}}, + cls.pdu_field_date.name: {"1": {"value": "2020-10-10"}}, + cls.pdu_field_boolean.name: {"1": {"value": True}}, + }, + "business_area": business_area, + }, + ) + cls.individual1 = individuals[0] + cls.other_treatment_facility = household + (household, individuals) = create_household( + { + "size": 1, + "business_area": business_area, + "program": cls.program, + }, + { + "flex_fields": { + cls.pdu_field_string.name: { + "1": {"value": "some value", "collection_date": "2020-10-10"}, + "2": {"value": None}, + }, + cls.pdu_field_decimal.name: {"1": {"value": 3}}, + cls.pdu_field_date.name: {"1": {"value": None}}, + cls.pdu_field_boolean.name: {"1": {"value": True}}, + }, + "business_area": business_area, + }, + ) + cls.individual2 = individuals[0] + (household, individuals) = create_household( + { + "size": 1, + "business_area": business_area, + "program": cls.program, + }, + { + "flex_fields": { + cls.pdu_field_string.name: { + "1": {"value": "different value", "collection_date": "2020-10-10"}, + "2": {"value": None}, + }, + cls.pdu_field_decimal.name: {"1": {"value": 4}}, + cls.pdu_field_date.name: {"1": {"value": "2020-02-10"}}, + cls.pdu_field_boolean.name: {"1": {"value": None}}, + }, + "business_area": business_area, + }, + ) + cls.individual3 = individuals[0] + (household, individuals) = create_household( + { + "size": 1, + "business_area": business_area, + "program": cls.program, + }, + { + "flex_fields": { + cls.pdu_field_string.name: { + "1": {"value": "other value", "collection_date": "2020-10-10"}, + "2": {"value": None}, + }, + cls.pdu_field_decimal.name: {"1": {"value": None}}, + cls.pdu_field_date.name: {"1": {"value": "2022-10-10"}}, + cls.pdu_field_boolean.name: {"1": {"value": False}}, + }, + "business_area": business_area, + }, + ) + cls.individual4 = individuals[0] + cls.individuals = [cls.individual1, cls.individual2, cls.individual3, cls.individual4] + + def get_individuals_queryset(self) -> QuerySet[Household]: + return Individual.objects.filter(pk__in=[ind.pk for ind in self.individuals]) + + def test_rule_filter_pdu_string_contains(self) -> None: + tp = TargetPopulation(program=self.program) + tc = TargetingCriteria() + tc.target_population = tp + tc.save() + tcr = TargetingCriteriaRule() + tcr.targeting_criteria = tc + tcr.save() + individuals_filters_block = TargetingIndividualRuleFilterBlock( + targeting_criteria_rule=tcr, target_only_hoh=False + ) + individuals_filters_block.save() + rule_filter = TargetingIndividualBlockRuleFilter( + individuals_filters_block=individuals_filters_block, + comparison_method="CONTAINS", + field_name=self.pdu_field_string.name, + arguments=["some"], + round_number=1, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_PDU, + ) + query = rule_filter.get_query() + + queryset = self.get_individuals_queryset().filter(query).distinct() + self.assertEqual(queryset.count(), 1) + self.assertIn(self.individual2, queryset) + + def test_rule_filter_pdu_string_is_null(self) -> None: + tp = TargetPopulation(program=self.program) + tc = TargetingCriteria() + tc.target_population = tp + tc.save() + tcr = TargetingCriteriaRule() + tcr.targeting_criteria = tc + tcr.save() + individuals_filters_block = TargetingIndividualRuleFilterBlock( + targeting_criteria_rule=tcr, target_only_hoh=False + ) + individuals_filters_block.save() + rule_filter = TargetingIndividualBlockRuleFilter( + individuals_filters_block=individuals_filters_block, + comparison_method="IS_NULL", + field_name=self.pdu_field_string.name, + arguments=[None], + round_number=1, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_PDU, + ) + query = rule_filter.get_query() + + queryset = self.get_individuals_queryset().filter(query).distinct() + self.assertEqual(queryset.count(), 1) + self.assertIn(self.individual1, queryset) + + def test_rule_filter_pdu_decimal_range(self) -> None: + tp = TargetPopulation(program=self.program) + tc = TargetingCriteria() + tc.target_population = tp + tc.save() + tcr = TargetingCriteriaRule() + tcr.targeting_criteria = tc + tcr.save() + individuals_filters_block = TargetingIndividualRuleFilterBlock( + targeting_criteria_rule=tcr, target_only_hoh=False + ) + individuals_filters_block.save() + rule_filter = TargetingIndividualBlockRuleFilter( + individuals_filters_block=individuals_filters_block, + comparison_method="RANGE", + field_name=self.pdu_field_decimal.name, + arguments=["2", "3"], + round_number=1, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_PDU, + ) + query = rule_filter.get_query() + + queryset = self.get_individuals_queryset().filter(query).distinct() + self.assertEqual(queryset.count(), 2) + self.assertIn(self.individual1, queryset) + self.assertIn(self.individual2, queryset) + + def test_rule_filter_pdu_decimal_greater_than(self) -> None: + tp = TargetPopulation(program=self.program) + tc = TargetingCriteria() + tc.target_population = tp + tc.save() + tcr = TargetingCriteriaRule() + tcr.targeting_criteria = tc + tcr.save() + individuals_filters_block = TargetingIndividualRuleFilterBlock( + targeting_criteria_rule=tcr, target_only_hoh=False + ) + individuals_filters_block.save() + rule_filter = TargetingIndividualBlockRuleFilter( + individuals_filters_block=individuals_filters_block, + comparison_method="GREATER_THAN", + field_name=self.pdu_field_decimal.name, + arguments=["2.5"], + round_number=1, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_PDU, + ) + query = rule_filter.get_query() + + queryset = self.get_individuals_queryset().filter(query).distinct() + self.assertEqual(queryset.count(), 3) + self.assertIn(self.individual1, queryset) + self.assertIn(self.individual2, queryset) + self.assertIn(self.individual3, queryset) + + def test_rule_filter_pdu_decimal_less_than(self) -> None: + tp = TargetPopulation(program=self.program) + tc = TargetingCriteria() + tc.target_population = tp + tc.save() + tcr = TargetingCriteriaRule() + tcr.targeting_criteria = tc + tcr.save() + individuals_filters_block = TargetingIndividualRuleFilterBlock( + targeting_criteria_rule=tcr, target_only_hoh=False + ) + individuals_filters_block.save() + rule_filter = TargetingIndividualBlockRuleFilter( + individuals_filters_block=individuals_filters_block, + comparison_method="LESS_THAN", + field_name=self.pdu_field_decimal.name, + arguments=["2.5"], + round_number=1, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_PDU, + ) + query = rule_filter.get_query() + queryset = self.get_individuals_queryset().filter(query).distinct() + + self.assertEqual(queryset.count(), 1) + self.assertIn(self.individual1, queryset) + + def test_rule_filter_pdu_decimal_is_null(self) -> None: + tp = TargetPopulation(program=self.program) + tc = TargetingCriteria() + tc.target_population = tp + tc.save() + tcr = TargetingCriteriaRule() + tcr.targeting_criteria = tc + tcr.save() + individuals_filters_block = TargetingIndividualRuleFilterBlock( + targeting_criteria_rule=tcr, target_only_hoh=False + ) + individuals_filters_block.save() + rule_filter = TargetingIndividualBlockRuleFilter( + individuals_filters_block=individuals_filters_block, + comparison_method="IS_NULL", + field_name=self.pdu_field_decimal.name, + arguments=[None], + round_number=1, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_PDU, + ) + query = rule_filter.get_query() + + queryset = self.get_individuals_queryset().filter(query).distinct() + self.assertEqual(queryset.count(), 1) + self.assertIn(self.individual4, queryset) + + def test_rule_filter_pdu_date_range(self) -> None: + tp = TargetPopulation(program=self.program) + tc = TargetingCriteria() + tc.target_population = tp + tc.save() + tcr = TargetingCriteriaRule() + tcr.targeting_criteria = tc + tcr.save() + individuals_filters_block = TargetingIndividualRuleFilterBlock( + targeting_criteria_rule=tcr, target_only_hoh=False + ) + individuals_filters_block.save() + rule_filter = TargetingIndividualBlockRuleFilter( + individuals_filters_block=individuals_filters_block, + comparison_method="RANGE", + field_name=self.pdu_field_date.name, + arguments=["2020-02-10", "2020-10-10"], + round_number=1, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_PDU, + ) + query = rule_filter.get_query() + + queryset = self.get_individuals_queryset().filter(query).distinct() + self.assertEqual(queryset.count(), 2) + self.assertIn(self.individual1, queryset) + self.assertIn(self.individual3, queryset) + + def test_rule_filter_pdu_date_greater_than(self) -> None: + tp = TargetPopulation(program=self.program) + tc = TargetingCriteria() + tc.target_population = tp + tc.save() + tcr = TargetingCriteriaRule() + tcr.targeting_criteria = tc + tcr.save() + individuals_filters_block = TargetingIndividualRuleFilterBlock( + targeting_criteria_rule=tcr, target_only_hoh=False + ) + individuals_filters_block.save() + rule_filter = TargetingIndividualBlockRuleFilter( + individuals_filters_block=individuals_filters_block, + comparison_method="GREATER_THAN", + field_name=self.pdu_field_date.name, + arguments=["2020-10-11"], + round_number=1, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_PDU, + ) + query = rule_filter.get_query() + + queryset = self.get_individuals_queryset().filter(query).distinct() + self.assertEqual(queryset.count(), 1) + self.assertIn(self.individual4, queryset) + + def test_rule_filter_pdu_date_less_than(self) -> None: + tp = TargetPopulation(program=self.program) + tc = TargetingCriteria() + tc.target_population = tp + tc.save() + tcr = TargetingCriteriaRule() + tcr.targeting_criteria = tc + tcr.save() + individuals_filters_block = TargetingIndividualRuleFilterBlock( + targeting_criteria_rule=tcr, target_only_hoh=False + ) + individuals_filters_block.save() + rule_filter = TargetingIndividualBlockRuleFilter( + individuals_filters_block=individuals_filters_block, + comparison_method="LESS_THAN", + field_name=self.pdu_field_date.name, + arguments=["2020-10-11"], + round_number=1, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_PDU, + ) + query = rule_filter.get_query() + queryset = self.get_individuals_queryset().filter(query).distinct() + + self.assertEqual(queryset.count(), 2) + self.assertIn(self.individual1, queryset) + self.assertIn(self.individual3, queryset) + + def test_rule_filter_pdu_date_is_null(self) -> None: + tp = TargetPopulation(program=self.program) + tc = TargetingCriteria() + tc.target_population = tp + tc.save() + tcr = TargetingCriteriaRule() + tcr.targeting_criteria = tc + tcr.save() + individuals_filters_block = TargetingIndividualRuleFilterBlock( + targeting_criteria_rule=tcr, target_only_hoh=False + ) + individuals_filters_block.save() + rule_filter = TargetingIndividualBlockRuleFilter( + individuals_filters_block=individuals_filters_block, + comparison_method="IS_NULL", + field_name=self.pdu_field_date.name, + arguments=[None], + round_number=1, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_PDU, + ) + query = rule_filter.get_query() + + queryset = self.get_individuals_queryset().filter(query).distinct() + self.assertEqual(queryset.count(), 1) + self.assertIn(self.individual2, queryset) + + def test_rule_filter_pdu_boolean_true(self) -> None: + tp = TargetPopulation(program=self.program) + tc = TargetingCriteria() + tc.target_population = tp + tc.save() + tcr = TargetingCriteriaRule() + tcr.targeting_criteria = tc + tcr.save() + individuals_filters_block = TargetingIndividualRuleFilterBlock( + targeting_criteria_rule=tcr, target_only_hoh=False + ) + individuals_filters_block.save() + rule_filter = TargetingIndividualBlockRuleFilter( + individuals_filters_block=individuals_filters_block, + comparison_method="EQUALS", + field_name=self.pdu_field_boolean.name, + arguments=[True], + round_number=1, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_PDU, + ) + query = rule_filter.get_query() + + queryset = self.get_individuals_queryset().filter(query).distinct() + self.assertEqual(queryset.count(), 2) + self.assertIn(self.individual1, queryset) + self.assertIn(self.individual2, queryset) + + def test_rule_filter_pdu_boolean_false(self) -> None: + tp = TargetPopulation(program=self.program) + tc = TargetingCriteria() + tc.target_population = tp + tc.save() + tcr = TargetingCriteriaRule() + tcr.targeting_criteria = tc + tcr.save() + individuals_filters_block = TargetingIndividualRuleFilterBlock( + targeting_criteria_rule=tcr, target_only_hoh=False + ) + individuals_filters_block.save() + rule_filter = TargetingIndividualBlockRuleFilter( + individuals_filters_block=individuals_filters_block, + comparison_method="EQUALS", + field_name=self.pdu_field_boolean.name, + arguments=[False], + round_number=1, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_PDU, + ) + query = rule_filter.get_query() + + queryset = self.get_individuals_queryset().filter(query).distinct() + self.assertEqual(queryset.count(), 1) + self.assertIn(self.individual4, queryset) + + def test_rule_filter_pdu_boolean_is_null(self) -> None: + tp = TargetPopulation(program=self.program) + tc = TargetingCriteria() + tc.target_population = tp + tc.save() + tcr = TargetingCriteriaRule() + tcr.targeting_criteria = tc + tcr.save() + individuals_filters_block = TargetingIndividualRuleFilterBlock( + targeting_criteria_rule=tcr, target_only_hoh=False + ) + individuals_filters_block.save() + rule_filter = TargetingIndividualBlockRuleFilter( + individuals_filters_block=individuals_filters_block, + comparison_method="IS_NULL", + field_name=self.pdu_field_boolean.name, + arguments=[None], + round_number=1, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_PDU, + ) + query = rule_filter.get_query() + + queryset = self.get_individuals_queryset().filter(query).distinct() + self.assertEqual(queryset.count(), 1) + self.assertIn(self.individual3, queryset) diff --git a/backend/hct_mis_api/apps/targeting/tests/test_targeting_validators.py b/backend/hct_mis_api/apps/targeting/tests/test_targeting_validators.py index d8be7b3838..6438638ec5 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_targeting_validators.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_targeting_validators.py @@ -47,14 +47,14 @@ def test_TargetingCriteriaInputValidator(self) -> None: validator = TargetingCriteriaInputValidator create_household({"unicef_id": "HH-1", "size": 1}, {"unicef_id": "IND-1"}) with self.assertRaisesMessage( - ValidationError, "Target criteria can has only filters or ids, not possible to has both" + ValidationError, "Target criteria can only have filters or ids, not possible to have both" ): self._update_program(self.program_standard) validator.validate( {"rules": ["Rule1"], "household_ids": "HH-1", "individual_ids": "IND-1"}, self.program_standard ) - with self.assertRaisesMessage(ValidationError, "Target criteria can has only individual ids"): + with self.assertRaisesMessage(ValidationError, "Target criteria can only have individual ids"): self._update_program(self.program_social) validator.validate({"rules": [], "household_ids": "HH-1", "individual_ids": "IND-1"}, self.program_social) @@ -63,7 +63,7 @@ def test_TargetingCriteriaInputValidator(self) -> None: {"rules": [], "household_ids": "HH-1", "individual_ids": "IND-1"}, self.program_standard_ind_only ) - with self.assertRaisesMessage(ValidationError, "Target criteria can has only household ids"): + with self.assertRaisesMessage(ValidationError, "Target criteria can only have household ids"): self._update_program(self.program_standard_hh_only) validator.validate( {"rules": [], "household_ids": "HH-1", "individual_ids": "IND-1"}, self.program_standard_hh_only diff --git a/backend/hct_mis_api/apps/targeting/validators.py b/backend/hct_mis_api/apps/targeting/validators.py index d1a823b743..ee61007e46 100644 --- a/backend/hct_mis_api/apps/targeting/validators.py +++ b/backend/hct_mis_api/apps/targeting/validators.py @@ -153,8 +153,8 @@ def validate(targeting_criteria: Dict, program: Program) -> None: household_ids = targeting_criteria.get("household_ids") individual_ids = targeting_criteria.get("individual_ids") if rules and (household_ids or individual_ids): - logger.error("Target criteria can has only filters or ids, not possible to has both") - raise ValidationError("Target criteria can has only filters or ids, not possible to has both") + logger.error("Target criteria can only have filters or ids, not possible to have both") + raise ValidationError("Target criteria can only have filters or ids, not possible to have both") if household_ids and not ( program_dct.household_filters_available or program_dct.type == DataCollectingType.Type.SOCIAL From 30e08027f1c8104e5e03978c12934e9abb23ecda Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Wed, 14 Aug 2024 10:43:29 +0200 Subject: [PATCH 016/118] revert test data changed for debugging --- .../targeting/tests/test_create_target_population_mutation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py b/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py index 9841aef919..ae2c62fca9 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py @@ -364,8 +364,8 @@ def test_create_mutation_with_pdu_flex_field(self) -> None: { "individualBlockFilters": [ { - "comparisonMethod": "CONTAINS", - "arguments": ["asdsaddsaads"], + "comparisonMethod": "RANGE", + "arguments": ["2", "3.5"], "fieldName": "pdu_field_1", "flexFieldClassification": "FLEX_FIELD_PDU", "roundNumber": "1", From fc9676708a06799729a708f978a8daedc64d44fa Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Wed, 14 Aug 2024 11:00:37 +0200 Subject: [PATCH 017/118] fix failing tests --- .../tests/test_payment_plan_reconciliation.py | 2 +- ...t_recalculating_household_cash_received.py | 2 +- .../snapshots/snap_test_update_program.py | 104 +++++++++--------- .../apps/program/tests/test_update_program.py | 4 +- 4 files changed, 56 insertions(+), 56 deletions(-) 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 acffb564ce..7a8ba22d86 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 @@ -360,7 +360,7 @@ def test_receiving_reconciliations_from_fsp(self, mock_get_exchange_rate: Any) - "comparisonMethod": "EQUALS", "arguments": ["True"], "fieldName": "consent", - "isFlexField": False, + "flexFieldClassification": "NOT_FLEX_FIELD", } ], "individualsFiltersBlocks": [], 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 dc53b5f801..fa9614dbe2 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 @@ -132,7 +132,7 @@ def setUpTestData(cls) -> None: { "comparisonMethod": "EQUALS", "fieldName": "consent", - "isFlexField": False, + "flexFieldClassification": "NOT_FLEX_FIELD", "arguments": [True], } ], diff --git a/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_update_program.py b/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_update_program.py index bf867b02e0..7aa9d72497 100644 --- a/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_update_program.py +++ b/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_update_program.py @@ -769,7 +769,7 @@ 'pduFields': [ { 'label': '{"English(EN)": "PDU Field - New"}', - 'name': 'pdu_field_-_new', + 'name': 'pdu_field_new', 'pduData': { 'numberOfRounds': 4, 'roundsNames': [ @@ -782,27 +782,27 @@ } }, { - 'label': '{"English(EN)": "PDU Field - Updated"}', - 'name': 'pdu_field_-_updated', + 'label': '{"English(EN)": "PDU Field To Be Preserved"}', + 'name': 'pdu_field_to_be_preserved', 'pduData': { - 'numberOfRounds': 3, + 'numberOfRounds': 1, 'roundsNames': [ - 'Round 1 Updated', - 'Round 2 Updated', - 'Round 3 Updated' + 'Round To Be Preserved' ], - 'subtype': 'BOOLEAN' + 'subtype': 'DATE' } }, { - 'label': '{"English(EN)": "PDU Field To Be Preserved"}', - 'name': 'pdu_field_to_be_preserved', + 'label': '{"English(EN)": "PDU Field - Updated"}', + 'name': 'pdu_field_updated', 'pduData': { - 'numberOfRounds': 1, + 'numberOfRounds': 3, 'roundsNames': [ - 'Round To Be Preserved' + 'Round 1 Updated', + 'Round 2 Updated', + 'Round 3 Updated' ], - 'subtype': 'DATE' + 'subtype': 'BOOLEAN' } } ], @@ -819,7 +819,7 @@ 'pduFields': [ { 'label': '{"English(EN)": "PDU Field - New"}', - 'name': 'pdu_field_-_new', + 'name': 'pdu_field_new', 'pduData': { 'numberOfRounds': 4, 'roundsNames': [ @@ -832,27 +832,27 @@ } }, { - 'label': '{"English(EN)": "PDU Field - Updated"}', - 'name': 'pdu_field_-_updated', + 'label': '{"English(EN)": "PDU Field To Be Preserved"}', + 'name': 'pdu_field_to_be_preserved', 'pduData': { - 'numberOfRounds': 3, + 'numberOfRounds': 1, 'roundsNames': [ - 'Round 1 Updated', - 'Round 2 Updated', - 'Round 3 Updated' + 'Round To Be Preserved' ], - 'subtype': 'BOOLEAN' + 'subtype': 'DATE' } }, { - 'label': '{"English(EN)": "PDU Field To Be Preserved"}', - 'name': 'pdu_field_to_be_preserved', + 'label': '{"English(EN)": "PDU Field - Updated"}', + 'name': 'pdu_field_updated', 'pduData': { - 'numberOfRounds': 1, + 'numberOfRounds': 3, 'roundsNames': [ - 'Round To Be Preserved' + 'Round 1 Updated', + 'Round 2 Updated', + 'Round 3 Updated' ], - 'subtype': 'DATE' + 'subtype': 'BOOLEAN' } } ] @@ -905,19 +905,6 @@ } ], 'pduFields': [ - { - 'label': '{"English(EN)": "PDU Field - Updated"}', - 'name': 'pdu_field_-_updated', - 'pduData': { - 'numberOfRounds': 3, - 'roundsNames': [ - 'Round 1 Updated', - 'Round 2 Updated', - 'Round 3 Updated' - ], - 'subtype': 'BOOLEAN' - } - }, { 'label': '{"English(EN)": "PDU Field 1"}', 'name': 'pdu_field_1', @@ -942,6 +929,19 @@ ], 'subtype': 'DATE' } + }, + { + 'label': '{"English(EN)": "PDU Field - Updated"}', + 'name': 'pdu_field_updated', + 'pduData': { + 'numberOfRounds': 3, + 'roundsNames': [ + 'Round 1 Updated', + 'Round 2 Updated', + 'Round 3 Updated' + ], + 'subtype': 'BOOLEAN' + } } ], 'status': 'DRAFT' @@ -976,28 +976,28 @@ ], 'pduFields': [ { - 'label': '{"English(EN)": "PDU Field - New"}', - 'name': 'pdu_field_-_new', + 'label': '{"English(EN)": "PDU Field 1"}', + 'name': 'pdu_field_1', 'pduData': { - 'numberOfRounds': 4, + 'numberOfRounds': 3, 'roundsNames': [ - 'Round 1A', - 'Round 2B', - 'Round 3C', - 'Round 4D' + 'Round 1 Updated', + 'Round 2 Updated', + 'Round 3 Updated' ], 'subtype': 'BOOLEAN' } }, { - 'label': '{"English(EN)": "PDU Field 1"}', - 'name': 'pdu_field_1', + 'label': '{"English(EN)": "PDU Field - New"}', + 'name': 'pdu_field_new', 'pduData': { - 'numberOfRounds': 3, + 'numberOfRounds': 4, 'roundsNames': [ - 'Round 1 Updated', - 'Round 2 Updated', - 'Round 3 Updated' + 'Round 1A', + 'Round 2B', + 'Round 3C', + 'Round 4D' ], 'subtype': 'BOOLEAN' } 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 0f9f85987a..69ff0c26b8 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 @@ -690,9 +690,9 @@ def test_update_program_with_pdu_fields(self) -> None: self.assertIsNone(FlexibleAttribute.objects.filter(name="pdu_field_to_be_removed").first()) self.assertIsNone(FlexibleAttribute.objects.filter(name="pdu_field_to_be_updated").first()) self.assertEqual( - FlexibleAttribute.objects.filter(name="pdu_field_-_updated").first().pdu_data.subtype, "BOOLEAN" + FlexibleAttribute.objects.filter(name="pdu_field_updated").first().pdu_data.subtype, "BOOLEAN" ) - self.assertIsNotNone(FlexibleAttribute.objects.filter(name="pdu_field_-_new").first()) + self.assertIsNotNone(FlexibleAttribute.objects.filter(name="pdu_field_new").first()) self.assertIsNotNone(FlexibleAttribute.objects.filter(name="pdu_field_to_be_preserved").first()) def test_update_program_with_pdu_fields_invalid_data(self) -> None: From 9fb30cbeebd59f81234e2272e6de379fc947bfec Mon Sep 17 00:00:00 2001 From: Pavlo Mokiichuk Date: Wed, 14 Aug 2024 15:35:34 +0200 Subject: [PATCH 018/118] Cycle script changes (#4135) * script upd * default cycle hard delete * small refactoring --- .../program_cycle_data_migration.py | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/backend/hct_mis_api/one_time_scripts/program_cycle_data_migration.py b/backend/hct_mis_api/one_time_scripts/program_cycle_data_migration.py index 2bfc61bbb7..6b14bfd1ac 100644 --- a/backend/hct_mis_api/one_time_scripts/program_cycle_data_migration.py +++ b/backend/hct_mis_api/one_time_scripts/program_cycle_data_migration.py @@ -1,6 +1,7 @@ import logging from datetime import date, timedelta from random import randint +from typing import List from django.core.exceptions import ValidationError from django.db import transaction @@ -61,9 +62,12 @@ def create_new_program_cycle(program_id: str, status: str, start_date: date, end ) -def processing_with_finished_program(payment_plan: PaymentPlan, start_date: date, end_date: date) -> None: +def processing_with_finished_program(program: Program) -> None: + start_date = program.start_date + end_date = program.end_date + program_id_str = str(program.id) # update if exists or create new cycle - if cycle := ProgramCycle.objects.filter(program_id=payment_plan.program_id).first(): + if cycle := ProgramCycle.objects.filter(program_id=program.id).first(): if cycle.start_date != start_date: cycle.start_date = start_date if cycle.end_date != end_date: @@ -72,22 +76,24 @@ def processing_with_finished_program(payment_plan: PaymentPlan, start_date: date cycle.status = ProgramCycle.FINISHED cycle.save(update_fields=["start_date", "end_date", "status"]) else: - cycle = create_new_program_cycle(str(payment_plan.program_id), ProgramCycle.FINISHED, start_date, end_date) + cycle = create_new_program_cycle(str(program.id), ProgramCycle.FINISHED, start_date, end_date) # update TP - TargetPopulation.objects.filter(id=payment_plan.target_population_id).update(program_cycle=cycle) + TargetPopulation.objects.filter(program_id=program_id_str).update(program_cycle=cycle) # update Payment Plan - PaymentPlan.objects.filter(id=payment_plan.id).update(program_cycle=cycle) + PaymentPlan.objects.filter(program_id=program_id_str).update(program_cycle=cycle) -def processing_with_active_program(payment_plans_list: list, default_cycle: ProgramCycle) -> None: +def processing_with_active_program(payment_plans_list: list, default_cycle_id: List[str]) -> None: hhs_in_cycles_dict = dict() for comparing_with_pp in payment_plans_list: new_hh_ids = set( [str(hh_id) for hh_id in comparing_with_pp.eligible_payments.values_list("household_id", flat=True)] ) cycles = ( - ProgramCycle.objects.filter(program_id=comparing_with_pp.program_id).exclude(id=default_cycle.id).only("id") + ProgramCycle.objects.filter(program_id=comparing_with_pp.program_id) + .exclude(id__in=default_cycle_id) + .only("id") ) for cycle in cycles: cycle_id_str = str(cycle.id) @@ -132,20 +138,15 @@ def program_cycle_data_migration() -> None: for ba in BusinessArea.objects.all().only("id", "name"): logger.info(f"Started processing {ba.name}...") - # FINISHED program + # FINISHED programs for finished_program in ( Program.objects.filter(business_area_id=ba.id) .exclude(status__in=[Program.DRAFT, Program.ACTIVE]) .only("id", "name", "start_date", "end_date", "status") ): - start_data = finished_program.start_date - end_data = finished_program.end_date - for pp in PaymentPlan.objects.filter(program_id=finished_program.id).only( - "id", "program_id", "target_population_id" - ): - processing_with_finished_program(pp, start_data, end_data) - - # ACTIVE program + processing_with_finished_program(finished_program) + + # ACTIVE and DRAFT programs for program in ( Program.objects.filter(business_area_id=ba.id) .exclude(status=Program.FINISHED) @@ -157,7 +158,6 @@ def program_cycle_data_migration() -> None: default_cycle = ProgramCycle.objects.filter(program_id=program.id).first() if not default_cycle: logger.info(f"###### Default Program Cycles for program {program.name} does not exist") - continue payment_plan_qs = ( PaymentPlan.objects.filter(program_id=program.id) @@ -165,8 +165,12 @@ def program_cycle_data_migration() -> None: .only("id", "program_id", "target_population_id") ) PaymentPlan.objects.filter(program_id=program.id).update(program_cycle=None) - processing_with_active_program(list(payment_plan_qs), default_cycle) - default_cycle.delete() + # using list for .exclude__in=[] + default_cycle_id = [str(default_cycle.id)] if default_cycle else [] + processing_with_active_program(list(payment_plan_qs), default_cycle_id) + + if default_cycle: + default_cycle.delete(soft=False) # after create all Cycles let's adjust dates to find any overlapping adjust_cycles_start_and_end_dates_for_active_program(program) From 69d89e0ea7386874287ec15b3eee00546452a7d9 Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Thu, 15 Aug 2024 16:35:38 +0200 Subject: [PATCH 019/118] make it work --- .../src/components/targeting/SubField.tsx | 1 - .../TargetingCriteriaDisplay/Criteria.tsx | 67 ++-- .../TargetingCriteriaDisplay.tsx | 2 +- .../forms/TargetingCriteriaForm.tsx | 308 +++++++++--------- ...TargetingCriteriaIndividualBlockFilter.tsx | 1 - ...argetingCriteriaIndividualFilterBlocks.tsx | 1 - .../targeting/CreateTargetPopulationPage.tsx | 301 ++++++++--------- .../FormikSelectField/FormikSelectField.tsx | 1 - frontend/src/utils/targetingUtils.ts | 64 +++- 9 files changed, 411 insertions(+), 335 deletions(-) diff --git a/frontend/src/components/targeting/SubField.tsx b/frontend/src/components/targeting/SubField.tsx index 7255849941..742f654c00 100644 --- a/frontend/src/components/targeting/SubField.tsx +++ b/frontend/src/components/targeting/SubField.tsx @@ -178,7 +178,6 @@ export function SubField({ /> ); case 'PDU': - console.log('field.pduData', field.pduData); return ( diff --git a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx index 37f444f5e1..09f6dcc295 100644 --- a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx +++ b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx @@ -6,10 +6,12 @@ import styled from 'styled-components'; import GreaterThanEqual from '../../../assets/GreaterThanEqual.svg'; import LessThanEqual from '../../../assets/LessThanEqual.svg'; import { TargetingCriteriaRuleObjectType } from '@generated/graphql'; +import { Box } from '@mui/system'; interface CriteriaElementProps { alternative?: boolean; } + const CriteriaElement = styled.div` width: auto; max-width: 380px; @@ -60,6 +62,17 @@ const CriteriaSetBox = styled.div` margin: ${({ theme }) => theme.spacing(2)} 0; `; +const PduDataBox = styled(Box)` + display: flex; + justify-content: center; + align-items: center; + background-color: #fff; + border: 1px solid #ccc; + border-radius: 3px; + padding: ${({ theme }) => theme.spacing(3)}; + margin: ${({ theme }) => theme.spacing(3)}; +`; + const CriteriaField = ({ field, choicesDict }): React.ReactElement => { const extractChoiceLabel = (choiceField, argument) => { let choices = choicesDict?.[choiceField.fieldName]; @@ -70,15 +83,16 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { ? choices.find((each) => each.value === argument)?.labelEn : argument; }; - console.log('field', field); + const { t } = useTranslation(); let fieldElement; + switch (field.comparisonMethod) { case 'NOT_EQUALS': fieldElement = (

{field.fieldAttribute.labelEn || field.fieldName}:{' '} - {field.arguments[0]} + {field.arguments?.[0]}

); break; @@ -87,7 +101,7 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => {

{field.fieldAttribute.labelEn || field.fieldName}:{' '} - {field.arguments[0]} -{field.arguments[1]} + {field.arguments?.[0]} - {field.arguments?.[1]}

); @@ -97,9 +111,9 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => {

{field.fieldAttribute.labelEn || field.fieldName}:{' '} {field.fieldAttribute.type === 'BOOL' ? ( - {field.arguments[0] === 'True' ? t('Yes') : t('No')} + {field.arguments?.[0] === 'True' ? t('Yes') : t('No')} ) : ( - {extractChoiceLabel(field, field.arguments[0])} + {extractChoiceLabel(field, field.arguments?.[0])} )}

); @@ -109,7 +123,7 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => {

{field.fieldAttribute.labelEn || field.fieldName}:{' '} - {field.arguments[0]} + {field.arguments?.[0]}

); break; @@ -118,7 +132,7 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => {

{field.fieldAttribute.labelEn || field.fieldName}:{' '} - {field.arguments[0]} + {field.arguments?.[0]}

); break; @@ -126,11 +140,11 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { fieldElement = (

{field.fieldAttribute.labelEn || field.fieldName}:{' '} - {field.arguments.map((argument, index) => ( - <> + {field.arguments?.map((argument, index) => ( + {extractChoiceLabel(field, argument)} {index !== field.arguments.length - 1 && ', '} - + ))}

); @@ -138,12 +152,22 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { default: fieldElement = (

- {field.fieldAttribute.labelEn}:{field.arguments[0]} + {field.fieldAttribute.labelEn}: {field.arguments?.[0]}

); break; } - return fieldElement; + + return ( + <> + {fieldElement} + {field.type === 'PDU' && field.pduData && ( + + {field.round}-{field.pduData.roundsNames?.[field.round - 1]} + + )} + + ); }; interface CriteriaProps { @@ -169,16 +193,17 @@ export function Criteria({ }: CriteriaProps): React.ReactElement { return ( - {rules.map((each, index) => ( - // eslint-disable-next-line + {(rules || []).map((each, index) => ( ))} - {individualsFiltersBlocks.map((item) => ( - // eslint-disable-next-line - - {item.individualBlockFilters.map((filter) => ( - // eslint-disable-next-line - + {individualsFiltersBlocks.map((item, index) => ( + + {item.individualBlockFilters.map((filter, filterIndex) => ( + ))} ))} @@ -188,7 +213,7 @@ export function Criteria({ {canRemove && ( - + )} diff --git a/frontend/src/components/targeting/TargetingCriteriaDisplay/TargetingCriteriaDisplay.tsx b/frontend/src/components/targeting/TargetingCriteriaDisplay/TargetingCriteriaDisplay.tsx index b8150440d4..e55ae02573 100644 --- a/frontend/src/components/targeting/TargetingCriteriaDisplay/TargetingCriteriaDisplay.tsx +++ b/frontend/src/components/targeting/TargetingCriteriaDisplay/TargetingCriteriaDisplay.tsx @@ -202,7 +202,7 @@ export const TargetingCriteriaDisplay = ({ {rules.length - ? rules.map((criteria, index) => ( + ? rules?.map((criteria, index) => ( // eslint-disable-next-line isFlexField === false --> NOT_FLEX_FIELD - //add mapping ---> isFlexField === true && type !== 'PDU' --> FLEX_FIELD_NOT_PDU - //add mapping for flexfieldcategorization ---> isFlexField === true && type === 'PDU' --> FLEX_FIELD_PDU const validate = ({ filters, individualsFiltersBlocks, @@ -208,6 +204,7 @@ export const TargetingCriteriaForm = ({ const handleSubmit = (values, bag): void => { const filters = formatCriteriaFilters(values.filters); + const individualsFiltersBlocks = formatCriteriaIndividualsFiltersBlocks( values.individualsFiltersBlocks, ); @@ -225,166 +222,175 @@ export const TargetingCriteriaForm = ({ validationSchema={validationSchema} enableReinitialize > - {({ submitForm, values, resetForm, errors }) => ( - - {open && } - - - - {t('Add Filter')} - - - - - { - // @ts-ignore - errors.nonFieldErrors && ( - -
    - { - // @ts-ignore - errors.nonFieldErrors.map((message) => ( -
  • {message}
  • - )) - } -
-
- ) - } - - {isSocialWorkingProgram - ? '' - : 'All rules defined below have to be true for the entire household.'} - - ( - - {values.filters.map((each, index) => ( - { - if (object) { - return chooseFieldType(object, arrayHelpers, index); - } - return clearField(arrayHelpers, index); - }} - values={values} - onClick={() => arrayHelpers.remove(index)} - /> - ))} - - )} - /> - {householdFiltersAvailable || isSocialWorkingProgram ? ( - - - - - - ) : null} - {individualFiltersAvailable && !isSocialWorkingProgram ? ( - <> - {householdFiltersAvailable ? ( - - And - - ) : null} - ( - - {values.individualsFiltersBlocks.map((each, index) => ( - arrayHelpers.remove(index)} - /> - ))} - - )} - /> + {values.filters.map((each, index) => ( + { + if (object) { + return chooseFieldType( + object, + arrayHelpers, + index, + ); + } + return clearField(arrayHelpers, index); + }} + values={values} + onClick={() => arrayHelpers.remove(index)} + /> + ))} + + )} + /> + {householdFiltersAvailable || isSocialWorkingProgram ? ( - - ) : null} -
- - - -
- - -
-
-
-
-
- )} + ) : null} + {individualFiltersAvailable && !isSocialWorkingProgram ? ( + <> + {householdFiltersAvailable ? ( + + And + + ) : null} + ( + + {values.individualsFiltersBlocks.map( + (each, index) => ( + arrayHelpers.remove(index)} + /> + ), + )} + + )} + /> + + + + + + + ) : null} + + + + +
+ + +
+
+
+
+ + ); + }} ); diff --git a/frontend/src/containers/forms/TargetingCriteriaIndividualBlockFilter.tsx b/frontend/src/containers/forms/TargetingCriteriaIndividualBlockFilter.tsx index 853b169304..8fe03a4c65 100644 --- a/frontend/src/containers/forms/TargetingCriteriaIndividualBlockFilter.tsx +++ b/frontend/src/containers/forms/TargetingCriteriaIndividualBlockFilter.tsx @@ -20,7 +20,6 @@ export function TargetingCriteriaIndividualBlockFilter({ onChange: (e, object) => void; onDelete: () => void; }): React.ReactElement { - console.log('each', each); return (
Set Individual Criteria diff --git a/frontend/src/containers/pages/targeting/CreateTargetPopulationPage.tsx b/frontend/src/containers/pages/targeting/CreateTargetPopulationPage.tsx index 38c02fc400..058b91d0f7 100644 --- a/frontend/src/containers/pages/targeting/CreateTargetPopulationPage.tsx +++ b/frontend/src/containers/pages/targeting/CreateTargetPopulationPage.tsx @@ -116,167 +116,170 @@ export const CreateTargetPopulationPage = (): React.ReactElement => { validationSchema={validationSchema} onSubmit={handleSubmit} > - {({ submitForm, values }) => ( -
- - - - - {t('Targeting Criteria')} - - - - - - - - - - {values.program && category === 'filters' ? ( - ( - { + return ( + + + + + + {t('Targeting Criteria')} + + + + - )} - /> - ) : null} - {category === 'ids' ? ( - <> - - {householdFiltersAvailable && ( - - - - )} - {householdFiltersAvailable && individualFiltersAvailable && ( - - - OR - - + + + + + + {values.program && category === 'filters' ? ( + ( + )} - {individualFiltersAvailable && ( - - + /> + ) : null} + {category === 'ids' ? ( + <> + + {householdFiltersAvailable && ( + - - - )} - - - - {isStandardDctType && ( - - - - )} - {isSocialDctType && ( - - - - )} - {screenBeneficiary && isSocialDctType && ( - - )} - {screenBeneficiary && isStandardDctType && ( - - + {householdFiltersAvailable && + individualFiltersAvailable && ( + + + OR + + + )} + {individualFiltersAvailable && ( + + + + )} - - - ) : null} - - {category === 'filters' && } - - - {t('Save to see the list of households')} - - - {t('List of households will be available after saving')} - - - - )} + + + {isStandardDctType && ( + + + + )} + {isSocialDctType && ( + + + + )} + {screenBeneficiary && isSocialDctType && ( + + + + )} + {screenBeneficiary && isStandardDctType && ( + + + + )} + + + + ) : null} + + {category === 'filters' && } + + + {t('Save to see the list of households')} + + + {t('List of households will be available after saving')} + + + + ); + }} ); }; diff --git a/frontend/src/shared/Formik/FormikSelectField/FormikSelectField.tsx b/frontend/src/shared/Formik/FormikSelectField/FormikSelectField.tsx index ca16cf3c4c..b7e08e1dbd 100644 --- a/frontend/src/shared/Formik/FormikSelectField/FormikSelectField.tsx +++ b/frontend/src/shared/Formik/FormikSelectField/FormikSelectField.tsx @@ -33,7 +33,6 @@ export function FormikSelectField({ onChange, ...otherProps }): React.ReactElement { - console.log('choices', otherProps.choices); const isInvalid = Boolean( get(form.errors, field.name) && (get(form.touched, field.name) || form.submitCount > 0 || form.errors), diff --git a/frontend/src/utils/targetingUtils.ts b/frontend/src/utils/targetingUtils.ts index 07c3ea88c5..948cd99016 100644 --- a/frontend/src/utils/targetingUtils.ts +++ b/frontend/src/utils/targetingUtils.ts @@ -1,16 +1,15 @@ export const chooseFieldType = (fieldValue, arrayHelpers, index): void => { - console.log('fieldValue', fieldValue); - let flexFieldCategorization; + let flexFieldClassification; if (fieldValue.isFlexField === false) { - flexFieldCategorization = 'NOT_FLEX_FIELD'; + flexFieldClassification = 'NOT_FLEX_FIELD'; } else if (fieldValue.isFlexField === true && fieldValue.type !== 'PDU') { - flexFieldCategorization = 'FLEX_FIELD_NOT_PDU'; + flexFieldClassification = 'FLEX_FIELD_NOT_PDU'; } else if (fieldValue.isFlexField === true && fieldValue.type === 'PDU') { - flexFieldCategorization = 'FLEX_FIELD_PDU'; + flexFieldClassification = 'FLEX_FIELD_PDU'; } const updatedFieldValues = { - flexFieldCategorization, + flexFieldClassification, associatedWith: fieldValue.associatedWith, fieldAttribute: { labelEn: fieldValue.labelEn, @@ -162,20 +161,58 @@ export function formatCriteriaFilters(filters) { comparisonMethod = 'EQUALS'; values = [each.value]; break; + case 'PDU': + switch (each.pduData.subtype) { + case 'SELECT_ONE': + comparisonMethod = 'EQUALS'; + values = [each.value]; + break; + case 'SELECT_MANY': + comparisonMethod = 'CONTAINS'; + values = [...each.value]; + break; + case 'STRING': + comparisonMethod = 'CONTAINS'; + values = [each.value]; + break; + case 'DECIMAL': + case 'INTEGER': + case 'DATE': + if (each.value.from && each.value.to) { + comparisonMethod = 'RANGE'; + values = [each.value.from, each.value.to]; + } else if (each.value.from && !each.value.to) { + comparisonMethod = 'GREATER_THAN'; + values = [each.value.from]; + } else { + comparisonMethod = 'LESS_THAN'; + values = [each.value.to]; + } + break; + case 'BOOL': + comparisonMethod = 'EQUALS'; + values = [each.value]; + break; + default: + comparisonMethod = 'CONTAINS'; + values = [each.value]; + } + break; default: comparisonMethod = 'CONTAINS'; + values = [each.value]; } + return { + ...each, comparisonMethod, arguments: values, fieldName: each.fieldName, - isFlexField: each.isFlexField, fieldAttribute: each.fieldAttribute, flexFieldClassification: each.flexFieldClassification, }; }); } - // TODO Marcin make Type to this function // eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function formatCriteriaIndividualsFiltersBlocks( @@ -191,13 +228,22 @@ function mapFilterToVariable(filter): { arguments; fieldName: string; flexFieldClassification: string; + round?: number; } { - return { + const result = { comparisonMethod: filter.comparisonMethod, arguments: filter.arguments, fieldName: filter.fieldName, flexFieldClassification: filter.flexFieldClassification, }; + + //TODO MS: send some more fields? + if (filter.flexFieldClassification === 'FLEX_FIELD_PDU') { + // result.round = filter.round; + // result.roundName = filter.roundNames[filter.round - 1]; + } + + return result; } // TODO Marcin make Type to this function From 4154e1095695709ae601dce5ff4e2c35fc30f22d Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Fri, 16 Aug 2024 11:39:03 +0200 Subject: [PATCH 020/118] it is all temporary --- .../src/components/targeting/SubField.tsx | 10 +++++++++ .../targeting/CreateTargetPopulationPage.tsx | 4 ++++ frontend/src/utils/en.json | 3 ++- frontend/src/utils/targetingUtils.ts | 21 ++++++++++++++++--- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/targeting/SubField.tsx b/frontend/src/components/targeting/SubField.tsx index 742f654c00..9f1c0a1c17 100644 --- a/frontend/src/components/targeting/SubField.tsx +++ b/frontend/src/components/targeting/SubField.tsx @@ -9,6 +9,7 @@ import { FormikDecimalField } from '@shared/Formik/FormikDecimalField'; import { FormikSelectField } from '@shared/Formik/FormikSelectField'; import { FormikTextField } from '@shared/Formik/FormikTextField'; import { Grid } from '@mui/material'; +import { FormikCheckboxField } from '@shared/Formik/FormikCheckboxField'; const FlexWrapper = styled.div` display: flex; @@ -197,6 +198,15 @@ export function SubField({ label="Round" /> + + + {field.pduData && field.pduData.subtype ? renderFieldByType(field.pduData.subtype) diff --git a/frontend/src/containers/pages/targeting/CreateTargetPopulationPage.tsx b/frontend/src/containers/pages/targeting/CreateTargetPopulationPage.tsx index 058b91d0f7..a0e6009c55 100644 --- a/frontend/src/containers/pages/targeting/CreateTargetPopulationPage.tsx +++ b/frontend/src/containers/pages/targeting/CreateTargetPopulationPage.tsx @@ -88,6 +88,10 @@ export const CreateTargetPopulationPage = (): React.ReactElement => { }); const handleSubmit = async (values): Promise => { + console.log( + 'getTargetingCriteriaVariables', + getTargetingCriteriaVariables(values), + ); try { const res = await mutate({ variables: { diff --git a/frontend/src/utils/en.json b/frontend/src/utils/en.json index e45a64ba97..3d87a0c9ea 100644 --- a/frontend/src/utils/en.json +++ b/frontend/src/utils/en.json @@ -892,5 +892,6 @@ "Round Name is required": "Round Name is required", "Mark as Distinct": "Mark as Distinct", "Uniqueness": "Uniqueness", - "Are you sure you want to clear the selected ids? They won't be marked as a duplicate or distinct anymore.": "Are you sure you want to clear the selected ids? They won't be marked as a duplicate or distinct anymore." + "Are you sure you want to clear the selected ids? They won't be marked as a duplicate or distinct anymore.": "Are you sure you want to clear the selected ids? They won't be marked as a duplicate or distinct anymore.", + "Include records with null value for the round": "Include records with null value for the round" } diff --git a/frontend/src/utils/targetingUtils.ts b/frontend/src/utils/targetingUtils.ts index 948cd99016..afd9f8afb1 100644 --- a/frontend/src/utils/targetingUtils.ts +++ b/frontend/src/utils/targetingUtils.ts @@ -237,10 +237,25 @@ function mapFilterToVariable(filter): { flexFieldClassification: filter.flexFieldClassification, }; - //TODO MS: send some more fields? if (filter.flexFieldClassification === 'FLEX_FIELD_PDU') { - // result.round = filter.round; - // result.roundName = filter.roundNames[filter.round - 1]; + result.round = filter.round; + result.includeNullRound = filter.includeNullRound; + console.log('filter:', filter); + console.log('filter.round:', filter.round); + console.log('filter.roundNames:', filter.roundNames); + console.log( + 'filter.roundNames.length:', + filter.roundNames ? filter.roundNames.length : 'undefined', + ); + if ( + filter.roundNames && + filter.roundNames.length >= filter.round && + filter.round >= 1 + ) { + result.roundName = filter.roundNames[filter.round - 1]; + } else { + result.roundName = null; + } } return result; From 2202fdac25883c5263f9f247842ff6f6d7a190e4 Mon Sep 17 00:00:00 2001 From: Pavlo Mokiichuk Date: Fri, 16 Aug 2024 14:25:58 +0200 Subject: [PATCH 021/118] Final Optimisation Migration Script (#4139) * optimization * Final Optimization Migration Script --- .../program_cycle_data_migration.py | 108 +++++++++--------- 1 file changed, 57 insertions(+), 51 deletions(-) diff --git a/backend/hct_mis_api/one_time_scripts/program_cycle_data_migration.py b/backend/hct_mis_api/one_time_scripts/program_cycle_data_migration.py index 6b14bfd1ac..fdc2fe1b0b 100644 --- a/backend/hct_mis_api/one_time_scripts/program_cycle_data_migration.py +++ b/backend/hct_mis_api/one_time_scripts/program_cycle_data_migration.py @@ -16,7 +16,6 @@ def adjust_cycles_start_and_end_dates_for_active_program(program: Program) -> None: - logger.info("** ** ** Adjusting cycles start and end dates...") cycles_qs = ProgramCycle.objects.filter(program=program).only("start_date", "end_date").order_by("start_date") if not cycles_qs: return @@ -34,11 +33,11 @@ def adjust_cycles_start_and_end_dates_for_active_program(program: Program) -> No if cycle.end_date < cycle.start_date: cycle.end_date = cycle.start_date try: - cycle.save() + cycle.save(update_fields=["start_date", "end_date"]) except ValidationError: # if validation error just save one day cycle cycle.end_date = cycle.start_date - cycle.save() + cycle.save(update_fields=["start_date", "end_date"]) previous_cycle = cycle @@ -74,7 +73,12 @@ def processing_with_finished_program(program: Program) -> None: cycle.end_date = end_date if cycle.status != ProgramCycle.FINISHED: cycle.status = ProgramCycle.FINISHED - cycle.save(update_fields=["start_date", "end_date", "status"]) + try: + cycle.save(update_fields=["start_date", "end_date", "status"]) + except ValidationError: + # if validation error just save one day cycle + cycle.end_date = cycle.start_date + cycle.save(update_fields=["start_date", "end_date", "status"]) else: cycle = create_new_program_cycle(str(program.id), ProgramCycle.FINISHED, start_date, end_date) @@ -84,11 +88,17 @@ def processing_with_finished_program(program: Program) -> None: PaymentPlan.objects.filter(program_id=program_id_str).update(program_cycle=cycle) -def processing_with_active_program(payment_plans_list: list, default_cycle_id: List[str]) -> None: +def processing_with_active_program(payment_plans_list_ids: List[str], default_cycle_id: List[str]) -> None: hhs_in_cycles_dict = dict() - for comparing_with_pp in payment_plans_list: + for comparing_with_pp_id in payment_plans_list_ids: + comparing_with_pp = ( + PaymentPlan.objects.filter(id=comparing_with_pp_id).only("id", "program_id", "target_population_id").first() + ) new_hh_ids = set( - [str(hh_id) for hh_id in comparing_with_pp.eligible_payments.values_list("household_id", flat=True)] + [ + str(hh_id) + for hh_id in comparing_with_pp.eligible_payments.values_list("household_id", flat=True).iterator() + ] ) cycles = ( ProgramCycle.objects.filter(program_id=comparing_with_pp.program_id) @@ -101,13 +111,12 @@ def processing_with_active_program(payment_plans_list: list, default_cycle_id: L hhs_in_cycles_dict[cycle_id_str] = set( [ str(hh_id) - for hh_id in Payment.objects.filter(parent__program_cycle=cycle).values_list( - "household_id", flat=True - ) + for hh_id in Payment.objects.filter(parent__program_cycle=cycle) + .values_list("household_id", flat=True) + .iterator() ] ) hh_ids_in_cycles = hhs_in_cycles_dict[cycle_id_str] - # check any conflicts if new_hh_ids.intersection(hh_ids_in_cycles): continue @@ -133,47 +142,44 @@ def processing_with_active_program(payment_plans_list: list, default_cycle_id: L def program_cycle_data_migration() -> None: start_time = timezone.now() - logger.info("Hi There! Started Program Cycle Data Migration.") - logger.info(f"Cycles before running creation: {ProgramCycle.objects.all().count()}") + print("*** Starting Program Cycle Data Migration ***\n", "*" * 60) + print(f"Initial Cycles: {ProgramCycle.objects.all().count()}") + for ba in BusinessArea.objects.all().only("id", "name"): - logger.info(f"Started processing {ba.name}...") - - # FINISHED programs - for finished_program in ( - Program.objects.filter(business_area_id=ba.id) - .exclude(status__in=[Program.DRAFT, Program.ACTIVE]) - .only("id", "name", "start_date", "end_date", "status") - ): - processing_with_finished_program(finished_program) - - # ACTIVE and DRAFT programs - for program in ( - Program.objects.filter(business_area_id=ba.id) - .exclude(status=Program.FINISHED) - .only("id", "name", "start_date", "end_date", "status") - ): - with transaction.atomic(): - logger.info(f"** Creating Program Cycles for program {program.name}") - - default_cycle = ProgramCycle.objects.filter(program_id=program.id).first() - if not default_cycle: - logger.info(f"###### Default Program Cycles for program {program.name} does not exist") - - payment_plan_qs = ( - PaymentPlan.objects.filter(program_id=program.id) - .order_by("start_date", "created_at") - .only("id", "program_id", "target_population_id") - ) - PaymentPlan.objects.filter(program_id=program.id).update(program_cycle=None) - # using list for .exclude__in=[] - default_cycle_id = [str(default_cycle.id)] if default_cycle else [] - processing_with_active_program(list(payment_plan_qs), default_cycle_id) + program_qs = Program.objects.filter(business_area_id=ba.id).only( + "id", "name", "start_date", "end_date", "status" + ) + if program_qs: + print(f"Processing {program_qs.count()} programs for {ba.name}.") + for program in program_qs: + # FINISHED programs + if program.status == Program.FINISHED: + processing_with_finished_program(program) + + # ACTIVE and DRAFT programs + if program.status in [Program.DRAFT, Program.ACTIVE]: + with transaction.atomic(): + print(f"-- Creating Cycle for {program.name} [{program.id}]") + default_cycle = ProgramCycle.objects.filter(program_id=program.id).first() + + payment_plan_qs_ids = [ + str(pp_id) + for pp_id in PaymentPlan.objects.filter(program_id=program.id) + .order_by("start_date", "created_at") + .only("id") + .values_list("id", flat=True) + .iterator() + ] + PaymentPlan.objects.filter(program_id=program.id).update(program_cycle=None) + # using list for .exclude__in=[] + default_cycle_id = [str(default_cycle.id)] if default_cycle else [] + processing_with_active_program(payment_plan_qs_ids, default_cycle_id) - if default_cycle: - default_cycle.delete(soft=False) + if default_cycle: + default_cycle.delete(soft=False) - # after create all Cycles let's adjust dates to find any overlapping - adjust_cycles_start_and_end_dates_for_active_program(program) + # after create all Cycles let's adjust dates to find any overlapping + adjust_cycles_start_and_end_dates_for_active_program(program) - logger.info(f"Cycles after creation: {ProgramCycle.objects.all().count()}") - print(f"Congratulations! Done in {timezone.now() - start_time}") + print(f"Total Cycles: {ProgramCycle.objects.all().count()}") + print(f"Migration completed in {timezone.now() - start_time}\n", "*" * 60) From fb6d4d12a0bb1041788f2059f24ce59249923bdc Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Sat, 17 Aug 2024 16:56:54 +0200 Subject: [PATCH 022/118] handle null values --- .../src/components/targeting/SubField.tsx | 87 +++++++++++++++---- .../TargetingCriteriaDisplay/Criteria.tsx | 26 ++++-- .../forms/TargetingCriteriaForm.tsx | 12 +-- ...TargetingCriteriaIndividualBlockFilter.tsx | 1 + .../targeting/CreateTargetPopulationPage.tsx | 4 - frontend/src/utils/targetingUtils.ts | 25 +----- 6 files changed, 100 insertions(+), 55 deletions(-) diff --git a/frontend/src/components/targeting/SubField.tsx b/frontend/src/components/targeting/SubField.tsx index 9f1c0a1c17..0b1007802b 100644 --- a/frontend/src/components/targeting/SubField.tsx +++ b/frontend/src/components/targeting/SubField.tsx @@ -1,6 +1,7 @@ import CalendarTodayRoundedIcon from '@mui/icons-material/CalendarTodayRounded'; -import { Field } from 'formik'; +import { Field, useFormikContext } from 'formik'; import * as React from 'react'; +import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; import { FormikAutocomplete } from '@shared/Formik/FormikAutocomplete'; @@ -21,12 +22,34 @@ const InlineField = styled.div` export function SubField({ field, + blockIndex = undefined, index, baseName, choicesDict, }): React.ReactElement { const { t } = useTranslation(); const fieldComponent =

{field.fieldAttribute.type}

; + const { values, setFieldValue } = useFormikContext(); + if (blockIndex === undefined) { + const match = baseName.match(/block\[(\d+)\]/); + if (match) { + blockIndex = parseInt(match[1], 10); + } + } + + const isNullSelected = + blockIndex !== undefined && index !== undefined + ? values?.individualsFiltersBlocks?.[blockIndex] + ?.individualBlockFilters?.[index]?.isNull ?? false + : false; + + useEffect(() => { + if (isNullSelected) { + setFieldValue(`${baseName}.value.from`, ''); + setFieldValue(`${baseName}.value.to`, ''); + setFieldValue(`${baseName}.value`, ''); + } + }, [isNullSelected, setFieldValue, baseName]); const renderFieldByType = (type) => { switch (type) { @@ -36,21 +59,31 @@ export function SubField({ @@ -66,6 +99,7 @@ export function SubField({ component={FormikDateField} decoratorEnd={} data-cy="date-from" + disabled={isNullSelected} /> @@ -76,6 +110,7 @@ export function SubField({ component={FormikDateField} decoratorEnd={} data-cy="date-to" + disabled={isNullSelected} /> @@ -93,6 +128,7 @@ export function SubField({ fullWidth component={FormikTextField} data-cy="integer-from" + disabled={isNullSelected} /> @@ -105,6 +141,7 @@ export function SubField({ fullWidth component={FormikTextField} data-cy="integer-to" + disabled={isNullSelected} /> @@ -127,6 +164,7 @@ export function SubField({ index={index} component={FormikSelectField} data-cy="select-one-select" + disabled={isNullSelected} /> ); case 'SELECT_MANY': @@ -139,19 +177,37 @@ export function SubField({ multiple component={FormikSelectField} data-cy="select-many" + disabled={isNullSelected} /> ); case 'STRING': - return ( - - ); + if (field.pduData) { + return ( + + ); + } else { + return ( + + ); + } case 'BOOL': return ( ); case 'PDU': @@ -200,17 +257,15 @@ export function SubField({
- {field.pduData && field.pduData.subtype - ? renderFieldByType(field.pduData.subtype) - : null} + {renderFieldByType(field.pduData.subtype)} ); diff --git a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx index 09f6dcc295..82712061aa 100644 --- a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx +++ b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx @@ -84,6 +84,8 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { : argument; }; + const displayValueOrDash = (value) => (value ? value : '-'); + const { t } = useTranslation(); let fieldElement; @@ -92,7 +94,7 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { fieldElement = (

{field.fieldAttribute.labelEn || field.fieldName}:{' '} - {field.arguments?.[0]} + {displayValueOrDash(field.arguments?.[0])}

); break; @@ -101,7 +103,8 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => {

{field.fieldAttribute.labelEn || field.fieldName}:{' '} - {field.arguments?.[0]} - {field.arguments?.[1]} + {displayValueOrDash(field.arguments?.[0])} -{' '} + {displayValueOrDash(field.arguments?.[1])}

); @@ -113,7 +116,11 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { {field.fieldAttribute.type === 'BOOL' ? ( {field.arguments?.[0] === 'True' ? t('Yes') : t('No')} ) : ( - {extractChoiceLabel(field, field.arguments?.[0])} + + {displayValueOrDash( + extractChoiceLabel(field, field.arguments?.[0]), + )} + )}

); @@ -122,8 +129,7 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { fieldElement = (

{field.fieldAttribute.labelEn || field.fieldName}:{' '} - - {field.arguments?.[0]} + {displayValueOrDash(field.arguments?.[0])}

); break; @@ -131,8 +137,7 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { fieldElement = (

{field.fieldAttribute.labelEn || field.fieldName}:{' '} - - {field.arguments?.[0]} + {displayValueOrDash(field.arguments?.[0])}

); break; @@ -142,7 +147,9 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { {field.fieldAttribute.labelEn || field.fieldName}:{' '} {field.arguments?.map((argument, index) => ( - {extractChoiceLabel(field, argument)} + + {displayValueOrDash(extractChoiceLabel(field, argument))} + {index !== field.arguments.length - 1 && ', '} ))} @@ -152,7 +159,8 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { default: fieldElement = (

- {field.fieldAttribute.labelEn}: {field.arguments?.[0]} + {field.fieldAttribute.labelEn}:{' '} + {displayValueOrDash(field.arguments?.[0])}

); break; diff --git a/frontend/src/containers/forms/TargetingCriteriaForm.tsx b/frontend/src/containers/forms/TargetingCriteriaForm.tsx index 1a8ef48fa8..49694aca0f 100644 --- a/frontend/src/containers/forms/TargetingCriteriaForm.tsx +++ b/frontend/src/containers/forms/TargetingCriteriaForm.tsx @@ -145,13 +145,15 @@ export const TargetingCriteriaForm = ({ individualsFiltersBlocks, }): { nonFieldErrors?: string[] } => { const filterNullOrNoSelections = (filter): boolean => - filter.value === null || - filter.value === '' || - (filter?.fieldAttribute?.type === 'SELECT_MANY' && - filter.value && - filter.value.length === 0); + filter.fieldAttribute?.type !== 'PDU' && + (filter.value === null || + filter.value === '' || + (filter?.fieldAttribute?.type === 'SELECT_MANY' && + filter.value && + filter.value.length === 0)); const filterEmptyFromTo = (filter): boolean => + filter.fieldAttribute?.type !== 'PDU' && typeof filter.value === 'object' && filter.value !== null && Object.prototype.hasOwnProperty.call(filter.value, 'from') && diff --git a/frontend/src/containers/forms/TargetingCriteriaIndividualBlockFilter.tsx b/frontend/src/containers/forms/TargetingCriteriaIndividualBlockFilter.tsx index 8fe03a4c65..b902c520dc 100644 --- a/frontend/src/containers/forms/TargetingCriteriaIndividualBlockFilter.tsx +++ b/frontend/src/containers/forms/TargetingCriteriaIndividualBlockFilter.tsx @@ -35,6 +35,7 @@ export function TargetingCriteriaIndividualBlockFilter({
{ }); const handleSubmit = async (values): Promise => { - console.log( - 'getTargetingCriteriaVariables', - getTargetingCriteriaVariables(values), - ); try { const res = await mutate({ variables: { diff --git a/frontend/src/utils/targetingUtils.ts b/frontend/src/utils/targetingUtils.ts index afd9f8afb1..9eae3f19fb 100644 --- a/frontend/src/utils/targetingUtils.ts +++ b/frontend/src/utils/targetingUtils.ts @@ -228,34 +228,17 @@ function mapFilterToVariable(filter): { arguments; fieldName: string; flexFieldClassification: string; - round?: number; + roundNumber?: number; } { const result = { - comparisonMethod: filter.comparisonMethod, - arguments: filter.arguments, + comparisonMethod: filter.isNull ? 'IS_NULL' : filter.comparisonMethod, + arguments: filter.isNull ? [null] : filter.arguments, fieldName: filter.fieldName, flexFieldClassification: filter.flexFieldClassification, }; if (filter.flexFieldClassification === 'FLEX_FIELD_PDU') { - result.round = filter.round; - result.includeNullRound = filter.includeNullRound; - console.log('filter:', filter); - console.log('filter.round:', filter.round); - console.log('filter.roundNames:', filter.roundNames); - console.log( - 'filter.roundNames.length:', - filter.roundNames ? filter.roundNames.length : 'undefined', - ); - if ( - filter.roundNames && - filter.roundNames.length >= filter.round && - filter.round >= 1 - ) { - result.roundName = filter.roundNames[filter.round - 1]; - } else { - result.roundName = null; - } + result.roundNumber = filter.roundNumber; } return result; From 322977465d6bae128cfed23b3d22338a5cad3d0d Mon Sep 17 00:00:00 2001 From: Patryk Dabrowski Date: Mon, 19 Aug 2024 17:14:21 +0200 Subject: [PATCH 023/118] Add link to admin (#4142) --- backend/hct_mis_api/apps/program/api/serializers.py | 6 ++++++ backend/hct_mis_api/apps/program/api/views.py | 4 ---- backend/hct_mis_api/apps/program/models.py | 4 +++- .../apps/program/tests/test_program_cycle_rest_api.py | 5 +---- frontend/src/api/programCycleApi.ts | 1 + .../ProgramCycleDetails/ProgramCycleDetailsHeader.tsx | 2 ++ .../tables/ProgramCycle/ProgramCycleTable.tsx | 10 ++++++++-- 7 files changed, 21 insertions(+), 11 deletions(-) diff --git a/backend/hct_mis_api/apps/program/api/serializers.py b/backend/hct_mis_api/apps/program/api/serializers.py index 424e27ca46..57d79f39c7 100644 --- a/backend/hct_mis_api/apps/program/api/serializers.py +++ b/backend/hct_mis_api/apps/program/api/serializers.py @@ -43,6 +43,7 @@ class ProgramCycleListSerializer(EncodedIdSerializerMixin): end_date = serializers.DateField(format="%Y-%m-%d") program_start_date = serializers.DateField(format="%Y-%m-%d") program_end_date = serializers.DateField(format="%Y-%m-%d") + admin_url = serializers.SerializerMethodField() class Meta: model = ProgramCycle @@ -61,6 +62,7 @@ class Meta: "total_delivered_quantity_usd", "frequency_of_payments", "created_by", + "admin_url", ) def get_created_by(self, obj: ProgramCycle) -> str: @@ -68,6 +70,10 @@ def get_created_by(self, obj: ProgramCycle) -> str: return "-" return f"{obj.created_by.first_name} {obj.created_by.last_name}" + def get_admin_url(self, obj: ProgramCycle) -> Optional[str]: + user = self.context["request"].user + return obj.admin_url if user.is_superuser else None + class ProgramCycleCreateSerializer(EncodedIdSerializerMixin): title = serializers.CharField(required=True) diff --git a/backend/hct_mis_api/apps/program/api/views.py b/backend/hct_mis_api/apps/program/api/views.py index f2a3daffa7..65e7c1fabe 100644 --- a/backend/hct_mis_api/apps/program/api/views.py +++ b/backend/hct_mis_api/apps/program/api/views.py @@ -76,10 +76,6 @@ def get_queryset(self) -> QuerySet: def list(self, request: Request, *args: Any, **kwargs: Any) -> Response: return super().list(request, *args, **kwargs) - def retrieve(self, request: Request, *args: Any, **kwargs: Any) -> Response: - serializer = ProgramCycleListSerializer(self.get_object()) - return Response(serializer.data) - def perform_destroy(self, program_cycle: ProgramCycle) -> None: if program_cycle.program.status != Program.ACTIVE: raise ValidationError("Only Programme Cycle for Active Programme can be deleted.") diff --git a/backend/hct_mis_api/apps/program/models.py b/backend/hct_mis_api/apps/program/models.py index 1596272cf6..66f4ac7293 100644 --- a/backend/hct_mis_api/apps/program/models.py +++ b/backend/hct_mis_api/apps/program/models.py @@ -275,7 +275,9 @@ def validate_unique(self, exclude: Optional[Collection[str]] = ...) -> None: # super(Program, self).validate_unique() -class ProgramCycle(SoftDeletableModel, TimeStampedUUIDModel, UnicefIdentifiedModel, AbstractSyncable, ConcurrencyModel): +class ProgramCycle( + AdminUrlMixin, SoftDeletableModel, TimeStampedUUIDModel, UnicefIdentifiedModel, AbstractSyncable, ConcurrencyModel +): ACTIVITY_LOG_MAPPING = create_mapping_dict( [ "title", diff --git a/backend/hct_mis_api/apps/program/tests/test_program_cycle_rest_api.py b/backend/hct_mis_api/apps/program/tests/test_program_cycle_rest_api.py index 042586ebe9..f1b7d90d1c 100644 --- a/backend/hct_mis_api/apps/program/tests/test_program_cycle_rest_api.py +++ b/backend/hct_mis_api/apps/program/tests/test_program_cycle_rest_api.py @@ -22,7 +22,6 @@ from hct_mis_api.apps.payment.models import PaymentPlan from hct_mis_api.apps.program.api.serializers import ( ProgramCycleCreateSerializer, - ProgramCycleListSerializer, ProgramCycleUpdateSerializer, ) from hct_mis_api.apps.program.api.views import ProgramCycleViewSet @@ -42,7 +41,7 @@ def setUpTestData(cls) -> None: Permissions.PM_PROGRAMME_CYCLE_DELETE, ] partner = PartnerFactory(name="UNICEF") - cls.user = UserFactory(username="Hope_Test_DRF", password="SpeedUp", partner=partner) + cls.user = UserFactory(username="Hope_Test_DRF", password="SpeedUp", partner=partner, is_superuser=True) permission_list = [perm.value for perm in user_permissions] role, created = Role.objects.update_or_create(name="TestName", defaults={"permissions": permission_list}) user_role, _ = UserRole.objects.get_or_create(user=cls.user, role=role, business_area=cls.business_area) @@ -101,8 +100,6 @@ def test_retrieve_program_cycle(self) -> None: self.client.force_authenticate(user=self.user) response = self.client.get(self.cycle_1_detail_url) self.assertEqual(response.status_code, status.HTTP_200_OK) - serializer = ProgramCycleListSerializer(self.cycle1) - self.assertEqual(response.data, serializer.data) def test_create_program_cycle(self) -> None: self.client.force_authenticate(user=self.user) diff --git a/frontend/src/api/programCycleApi.ts b/frontend/src/api/programCycleApi.ts index 3c4f642812..d067bd2cef 100644 --- a/frontend/src/api/programCycleApi.ts +++ b/frontend/src/api/programCycleApi.ts @@ -39,6 +39,7 @@ export interface ProgramCycle { total_undelivered_quantity_usd: number; total_delivered_quantity_usd: number; frequency_of_payments: string; + admin_url?: string; } export const fetchProgramCycles = async ( diff --git a/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/ProgramCycleDetailsHeader.tsx b/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/ProgramCycleDetailsHeader.tsx index 6009262f14..1d887aefe2 100644 --- a/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/ProgramCycleDetailsHeader.tsx +++ b/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/ProgramCycleDetailsHeader.tsx @@ -16,6 +16,7 @@ import { useSnackbar } from '@hooks/useSnackBar'; import { decodeIdString } from '@utils/utils'; import { hasPermissions, PERMISSIONS } from '../../../../../config/permissions'; import { usePermissions } from '@hooks/usePermissions'; +import { AdminButton } from '@core/AdminButton'; interface ProgramCycleDetailsHeaderProps { programCycle: ProgramCycle; @@ -159,6 +160,7 @@ export const ProgramCycleDetailsHeader = ({ } breadCrumbs={breadCrumbsItems} + flags={} > {buttons} diff --git a/frontend/src/containers/tables/ProgramCycle/ProgramCycleTable.tsx b/frontend/src/containers/tables/ProgramCycle/ProgramCycleTable.tsx index 14f5da0171..cb450efbe6 100644 --- a/frontend/src/containers/tables/ProgramCycle/ProgramCycleTable.tsx +++ b/frontend/src/containers/tables/ProgramCycle/ProgramCycleTable.tsx @@ -27,7 +27,7 @@ export const ProgramCycleTable = ({ program }: ProgramCycleTableProps) => { limit: 5, ordering: 'created_at', }); - const { businessArea, baseUrl } = useBaseUrl(); + const { businessArea, baseUrl, programId } = useBaseUrl(); const permissions = usePermissions(); const canCreateProgramCycle = program.status !== 'DRAFT' && @@ -40,6 +40,8 @@ export const ProgramCycleTable = ({ program }: ProgramCycleTableProps) => { }, }); + const canViewDetails = programId !== 'all'; + const renderRow = (row: ProgramCycle): ReactElement => { const detailsUrl = `/${baseUrl}/payment-module/program-cycles/${row.id}`; const canEditProgramCycle = @@ -52,7 +54,11 @@ export const ProgramCycleTable = ({ program }: ProgramCycleTableProps) => { return ( - {row.unicef_id} + {canViewDetails ? ( + {row.unicef_id} + ) : ( + row.unicef_id + )} {row.title} From 5955c3210266d2144f1341c005e4f33ddd5add12 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Mon, 19 Aug 2024 17:50:18 +0200 Subject: [PATCH 024/118] change boolean into bool, adjust tests, fix program get --- .../apps/core/migrations/0083_migration.py | 18 +++++++++++ backend/hct_mis_api/apps/core/models.py | 4 +-- .../apps/core/tests/test_schema.py | 2 +- .../periodic_data_update_import_service.py | 2 +- ...est_periodic_data_update_import_service.py | 2 +- .../apps/program/tests/test_copy_program.py | 2 +- .../apps/program/tests/test_create_program.py | 2 +- .../apps/program/tests/test_program_query.py | 2 +- .../apps/program/tests/test_update_program.py | 30 +++++++++---------- backend/hct_mis_api/apps/program/utils.py | 4 +-- .../tasks/rdi_xlsx_create.py | 2 +- .../test_xlsx_upload_validators_methods.py | 4 +-- .../apps/registration_datahub/validators.py | 2 +- .../apps/targeting/graphql_types.py | 4 +-- .../test_targeting_criteria_rule_filter.py | 2 +- 15 files changed, 49 insertions(+), 33 deletions(-) create mode 100644 backend/hct_mis_api/apps/core/migrations/0083_migration.py diff --git a/backend/hct_mis_api/apps/core/migrations/0083_migration.py b/backend/hct_mis_api/apps/core/migrations/0083_migration.py new file mode 100644 index 0000000000..9f23e18611 --- /dev/null +++ b/backend/hct_mis_api/apps/core/migrations/0083_migration.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.25 on 2024-08-19 15:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0082_migration'), + ] + + operations = [ + migrations.AlterField( + model_name='periodicfielddata', + name='subtype', + field=models.CharField(choices=[('DATE', 'Date'), ('DECIMAL', 'Number'), ('STRING', 'Text'), ('BOOL', 'Boolean (true/false)')], max_length=16), + ), + ] diff --git a/backend/hct_mis_api/apps/core/models.py b/backend/hct_mis_api/apps/core/models.py index 3194215965..6dcecb5125 100644 --- a/backend/hct_mis_api/apps/core/models.py +++ b/backend/hct_mis_api/apps/core/models.py @@ -324,13 +324,13 @@ class PeriodicFieldData(models.Model): STRING = "STRING" DECIMAL = "DECIMAL" DATE = "DATE" - BOOLEAN = "BOOLEAN" + BOOL = "BOOL" TYPE_CHOICES = Choices( (DATE, _("Date")), (DECIMAL, _("Number")), (STRING, _("Text")), - (BOOLEAN, _("Boolean (true/false)")), + (BOOL, _("Boolean (true/false)")), ) subtype = models.CharField(max_length=16, choices=TYPE_CHOICES) diff --git a/backend/hct_mis_api/apps/core/tests/test_schema.py b/backend/hct_mis_api/apps/core/tests/test_schema.py index a2dafa75a1..6d67efc99a 100644 --- a/backend/hct_mis_api/apps/core/tests/test_schema.py +++ b/backend/hct_mis_api/apps/core/tests/test_schema.py @@ -117,7 +117,7 @@ def setUpTestData(cls) -> None: # Create a PDU field for a different program other_program = ProgramFactory(business_area=cls.business_area, status=Program.ACTIVE, name="Other Program") pdu_data_different_program = PeriodicFieldDataFactory( - subtype=PeriodicFieldData.BOOLEAN, + subtype=PeriodicFieldData.BOOL, number_of_rounds=1, rounds_names=["Round 1"], ) diff --git a/backend/hct_mis_api/apps/periodic_data_update/service/periodic_data_update_import_service.py b/backend/hct_mis_api/apps/periodic_data_update/service/periodic_data_update_import_service.py index ef0811ce11..bc6bc93caf 100644 --- a/backend/hct_mis_api/apps/periodic_data_update/service/periodic_data_update_import_service.py +++ b/backend/hct_mis_api/apps/periodic_data_update/service/periodic_data_update_import_service.py @@ -285,7 +285,7 @@ def _get_form_field_for_value(self, flexible_attribute: FlexibleAttribute) -> fo return forms.CharField(required=False) elif flexible_attribute.pdu_data.subtype == PeriodicFieldData.DECIMAL: return forms.DecimalField(required=False) - elif flexible_attribute.pdu_data.subtype == PeriodicFieldData.BOOLEAN: + elif flexible_attribute.pdu_data.subtype == PeriodicFieldData.BOOL: return StrictBooleanField(required=False) elif flexible_attribute.pdu_data.subtype == PeriodicFieldData.DATE: return forms.DateField(required=False) diff --git a/backend/hct_mis_api/apps/periodic_data_update/tests/test_periodic_data_update_import_service.py b/backend/hct_mis_api/apps/periodic_data_update/tests/test_periodic_data_update_import_service.py index 7a3ade76d1..9abc46ed7e 100644 --- a/backend/hct_mis_api/apps/periodic_data_update/tests/test_periodic_data_update_import_service.py +++ b/backend/hct_mis_api/apps/periodic_data_update/tests/test_periodic_data_update_import_service.py @@ -88,7 +88,7 @@ def setUpTestData(cls) -> None: ) cls.boolean_attribute = create_pdu_flexible_attribute( label="Boolean Attribute", - subtype=PeriodicFieldData.BOOLEAN, + subtype=PeriodicFieldData.BOOL, number_of_rounds=1, rounds_names=["May"], program=cls.program, diff --git a/backend/hct_mis_api/apps/program/tests/test_copy_program.py b/backend/hct_mis_api/apps/program/tests/test_copy_program.py index 84c5a65e4d..cb530d25fc 100644 --- a/backend/hct_mis_api/apps/program/tests/test_copy_program.py +++ b/backend/hct_mis_api/apps/program/tests/test_copy_program.py @@ -467,7 +467,7 @@ def test_copy_program_with_pdu_fields(self) -> None: { "label": "PDU Field 4", "pduData": { - "subtype": "BOOLEAN", + "subtype": "BOOL", "numberOfRounds": 4, "roundsNames": ["Round 1A", "Round 2B", "Round 3C", "Round 4D"], }, 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 7e8d5ba003..3da3f58dac 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 @@ -412,7 +412,7 @@ def test_create_program_with_pdu_fields(self) -> None: { "label": "PDU Field 4", "pduData": { - "subtype": "BOOLEAN", + "subtype": "BOOL", "numberOfRounds": 4, "roundsNames": ["Round 1A", "Round 2B", "Round 3C", "Round 4D"], }, diff --git a/backend/hct_mis_api/apps/program/tests/test_program_query.py b/backend/hct_mis_api/apps/program/tests/test_program_query.py index 55b3bcb1ca..5f653ed3aa 100644 --- a/backend/hct_mis_api/apps/program/tests/test_program_query.py +++ b/backend/hct_mis_api/apps/program/tests/test_program_query.py @@ -72,7 +72,7 @@ def setUpTestData(cls) -> None: pdu_data=pdu_data3, ) pdu_data4 = PeriodicFieldDataFactory( - subtype=PeriodicFieldData.BOOLEAN, + subtype=PeriodicFieldData.BOOL, number_of_rounds=2, rounds_names=["Round A", "Round B"], ) 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 baa5547aa9..bf2d57e3d3 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 @@ -651,7 +651,7 @@ def test_update_program_with_pdu_fields(self) -> None: "id": self.id_to_base64(self.pdu_field_to_be_updated.id, "PeriodicFieldNode"), "label": "PDU Field - Updated", "pduData": { - "subtype": "BOOLEAN", + "subtype": "BOOL", "numberOfRounds": 3, "roundsNames": ["Round 1 Updated", "Round 2 Updated", "Round 3 Updated"], }, @@ -659,7 +659,7 @@ def test_update_program_with_pdu_fields(self) -> None: { "label": "PDU Field - New", "pduData": { - "subtype": "BOOLEAN", + "subtype": "BOOL", "numberOfRounds": 4, "roundsNames": ["Round 1A", "Round 2B", "Round 3C", "Round 4D"], }, @@ -693,9 +693,7 @@ def test_update_program_with_pdu_fields(self) -> None: ) self.assertIsNone(FlexibleAttribute.objects.filter(name="pdu_field_to_be_removed").first()) self.assertIsNone(FlexibleAttribute.objects.filter(name="pdu_field_to_be_updated").first()) - self.assertEqual( - FlexibleAttribute.objects.filter(name="pdu_field_updated").first().pdu_data.subtype, "BOOLEAN" - ) + self.assertEqual(FlexibleAttribute.objects.filter(name="pdu_field_updated").first().pdu_data.subtype, "BOOL") self.assertIsNotNone(FlexibleAttribute.objects.filter(name="pdu_field_new").first()) self.assertIsNotNone(FlexibleAttribute.objects.filter(name="pdu_field_to_be_preserved").first()) @@ -719,7 +717,7 @@ def test_update_program_with_pdu_fields_invalid_data(self) -> None: "id": self.id_to_base64(self.pdu_field_to_be_updated.id, "PeriodicFieldNode"), "label": "PDU Field - Updated", "pduData": { - "subtype": "BOOLEAN", + "subtype": "BOOL", "numberOfRounds": 1, "roundsNames": ["Round 1 Updated", "Round 2 Updated", "Round 3 Updated"], }, @@ -727,7 +725,7 @@ def test_update_program_with_pdu_fields_invalid_data(self) -> None: { "label": "PDU Field - New", "pduData": { - "subtype": "BOOLEAN", + "subtype": "BOOL", "numberOfRounds": 3, "roundsNames": ["Round 1A", "Round 2B", "Round 3C", "Round 4D"], }, @@ -765,7 +763,7 @@ def test_update_program_with_pdu_fields_duplicated_field_names_in_input(self) -> "id": self.id_to_base64(self.pdu_field_to_be_updated.id, "PeriodicFieldNode"), "label": "PDU Field 1", "pduData": { - "subtype": "BOOLEAN", + "subtype": "BOOL", "numberOfRounds": 3, "roundsNames": ["Round 1 Updated", "Round 2 Updated", "Round 3 Updated"], }, @@ -773,7 +771,7 @@ def test_update_program_with_pdu_fields_duplicated_field_names_in_input(self) -> { "label": "PDU Field 1", "pduData": { - "subtype": "BOOLEAN", + "subtype": "BOOL", "numberOfRounds": 4, "roundsNames": ["Round 1A", "Round 2B", "Round 3C", "Round 4D"], }, @@ -822,7 +820,7 @@ def test_update_program_with_pdu_fields_existing_field_name_for_new_field(self) "id": self.id_to_base64(self.pdu_field_to_be_updated.id, "PeriodicFieldNode"), "label": "PDU Field - Updated", "pduData": { - "subtype": "BOOLEAN", + "subtype": "BOOL", "numberOfRounds": 3, "roundsNames": ["Round 1 Updated", "Round 2 Updated", "Round 3 Updated"], }, @@ -830,7 +828,7 @@ def test_update_program_with_pdu_fields_existing_field_name_for_new_field(self) { "label": "PDU Field 1", "pduData": { - "subtype": "BOOLEAN", + "subtype": "BOOL", "numberOfRounds": 4, "roundsNames": ["Round 1A", "Round 2B", "Round 3C", "Round 4D"], }, @@ -879,7 +877,7 @@ def test_update_program_with_pdu_fields_existing_field_name_for_updated_field(se "id": self.id_to_base64(self.pdu_field_to_be_updated.id, "PeriodicFieldNode"), "label": "PDU Field 1", "pduData": { - "subtype": "BOOLEAN", + "subtype": "BOOL", "numberOfRounds": 3, "roundsNames": ["Round 1 Updated", "Round 2 Updated", "Round 3 Updated"], }, @@ -887,7 +885,7 @@ def test_update_program_with_pdu_fields_existing_field_name_for_updated_field(se { "label": "PDU Field - New", "pduData": { - "subtype": "BOOLEAN", + "subtype": "BOOL", "numberOfRounds": 4, "roundsNames": ["Round 1A", "Round 2B", "Round 3C", "Round 4D"], }, @@ -916,7 +914,7 @@ def test_update_program_with_pdu_fields_program_has_RDI(self) -> None: "id": self.id_to_base64(self.pdu_field_to_be_updated.id, "PeriodicFieldNode"), "label": "PDU Field - NAME WILL NOT BE UPDATED", "pduData": { - "subtype": "BOOLEAN", # subtype will NOT be updated + "subtype": "BOOL", # subtype will NOT be updated "numberOfRounds": 4, "roundsNames": [ "Round 1 To Be Updated", @@ -950,7 +948,7 @@ def test_update_program_with_pdu_fields_program_has_RDI_new_field(self) -> None: { "label": "PDU Field - New", "pduData": { - "subtype": "BOOLEAN", + "subtype": "BOOL", "numberOfRounds": 4, "roundsNames": ["Round 1A", "Round 2B", "Round 3C", "Round 4D"], }, @@ -980,7 +978,7 @@ def test_update_program_with_pdu_fields_program_has_RDI_update_pdu_field(self) - "id": self.id_to_base64(self.pdu_field_to_be_updated.id, "PeriodicFieldNode"), "label": "PDU Field - Updated", "pduData": { - "subtype": "BOOLEAN", + "subtype": "BOOL", "numberOfRounds": 2, "roundsNames": ["Round 1 To Be Updated", "Round 2 To Be Updated"], }, diff --git a/backend/hct_mis_api/apps/program/utils.py b/backend/hct_mis_api/apps/program/utils.py index 9c79daf6f2..d58139c718 100644 --- a/backend/hct_mis_api/apps/program/utils.py +++ b/backend/hct_mis_api/apps/program/utils.py @@ -474,8 +474,8 @@ def copy_individual(individual: Individual, program: Program) -> tuple: original_individual_id = individual.id individual.copied_from_id = original_individual_id individual.pk = None - copied_flex_fields = get_flex_fields_without_pdu_values(individual) - individual.flex_fields = populate_pdu_with_null_values(program, copied_flex_fields) + individual.flex_fields = get_flex_fields_without_pdu_values(individual) + populate_pdu_with_null_values(program, individual.flex_fields) individual.program = program individual.household = None individual.registration_data_import = None diff --git a/backend/hct_mis_api/apps/registration_datahub/tasks/rdi_xlsx_create.py b/backend/hct_mis_api/apps/registration_datahub/tasks/rdi_xlsx_create.py index f94776a4f0..2d4b3b0ad5 100644 --- a/backend/hct_mis_api/apps/registration_datahub/tasks/rdi_xlsx_create.py +++ b/backend/hct_mis_api/apps/registration_datahub/tasks/rdi_xlsx_create.py @@ -519,7 +519,7 @@ def handle_pdu_fields(self, row: list[Any], header: list[Any], individual: Pendi PeriodicFieldData.DATE: self._handle_date_field, PeriodicFieldData.DECIMAL: self._handle_decimal_field, PeriodicFieldData.STRING: self._handle_string_field, - PeriodicFieldData.BOOLEAN: self._handle_bool_field, + PeriodicFieldData.BOOL: self._handle_bool_field, } for flexible_attribute in self.pdu_flexible_attributes: column_value = f"{flexible_attribute.name}_round_1_value" diff --git a/backend/hct_mis_api/apps/registration_datahub/tests/test_xlsx_upload_validators_methods.py b/backend/hct_mis_api/apps/registration_datahub/tests/test_xlsx_upload_validators_methods.py index d8025a8269..ec1be97383 100644 --- a/backend/hct_mis_api/apps/registration_datahub/tests/test_xlsx_upload_validators_methods.py +++ b/backend/hct_mis_api/apps/registration_datahub/tests/test_xlsx_upload_validators_methods.py @@ -777,7 +777,7 @@ def test_validate_delivery_mechanism_data_global_fields_only_dropped(self) -> No [ (PeriodicFieldData.STRING, ["Test", "2021-05-01"]), (PeriodicFieldData.DECIMAL, ["12.3", "2021-05-01"]), - (PeriodicFieldData.BOOLEAN, ["True", "2021-05-01"]), + (PeriodicFieldData.BOOL, ["True", "2021-05-01"]), (PeriodicFieldData.DATE, ["1996-06-21", "2021-05-01"]), ] ) @@ -801,7 +801,7 @@ def test_validate_pdu_string_valid(self, subtype: str, data_row: list) -> None: @parameterized.expand( [ (PeriodicFieldData.DECIMAL, ["foo", "2021-05-01"]), - (PeriodicFieldData.BOOLEAN, ["foo", "2021-05-01"]), + (PeriodicFieldData.BOOL, ["foo", "2021-05-01"]), (PeriodicFieldData.DATE, ["foo", "2021-05-01"]), ] ) diff --git a/backend/hct_mis_api/apps/registration_datahub/validators.py b/backend/hct_mis_api/apps/registration_datahub/validators.py index e584fba7f3..85e3d44366 100644 --- a/backend/hct_mis_api/apps/registration_datahub/validators.py +++ b/backend/hct_mis_api/apps/registration_datahub/validators.py @@ -1179,7 +1179,7 @@ def _validate_pdu(self, row: list[Any], header_row: list[Any], row_number: int) PeriodicFieldData.DATE: self.date_validator, PeriodicFieldData.DECIMAL: self.decimal_validator, PeriodicFieldData.STRING: self.string_validator, - PeriodicFieldData.BOOLEAN: self.bool_validator, + PeriodicFieldData.BOOL: self.bool_validator, } errors = [] for flexible_attribute in self.pdu_flexible_attributes: diff --git a/backend/hct_mis_api/apps/targeting/graphql_types.py b/backend/hct_mis_api/apps/targeting/graphql_types.py index 8cdc38f876..39f3d1cb1b 100644 --- a/backend/hct_mis_api/apps/targeting/graphql_types.py +++ b/backend/hct_mis_api/apps/targeting/graphql_types.py @@ -81,7 +81,7 @@ def resolve_field_attribute(parent, info: Any) -> Optional[Dict]: program = None if parent.flex_field_classification == "FLEX_FIELD_PDU": - encoded_program_id = input.get("program") or info.context.headers.get("Program") + encoded_program_id = info.context.headers.get("Program") program = Program.objects.get(id=decode_id_string(encoded_program_id)) return FlexibleAttribute.objects.get(name=parent.field_name, program=program) @@ -106,7 +106,7 @@ def resolve_field_attribute(parent, info: Any) -> Any: program = None if parent.flex_field_classification == "FLEX_FIELD_PDU": - encoded_program_id = input.get("program") or info.context.headers.get("Program") + encoded_program_id = info.context.headers.get("Program") program = Program.objects.get(id=decode_id_string(encoded_program_id)) return FlexibleAttribute.objects.get(name=parent.field_name, program=program) diff --git a/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria_rule_filter.py b/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria_rule_filter.py index 7b0871ca21..c927ea5bea 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria_rule_filter.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria_rule_filter.py @@ -432,7 +432,7 @@ def setUpTestData(cls) -> None: ) pdu_data_boolean = PeriodicFieldDataFactory( - subtype=PeriodicFieldData.BOOLEAN, + subtype=PeriodicFieldData.BOOL, number_of_rounds=1, rounds_names=["Round 1"], ) From 9c839c24cf10f94c6885cfed847cd375540b2fc0 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Mon, 19 Aug 2024 19:57:49 +0200 Subject: [PATCH 025/118] fix tests after latest change --- .../core/tests/snapshots/snap_test_schema.py | 2 +- .../tests/snapshots/snap_test_copy_program.py | 2 +- .../tests/snapshots/snap_test_update_program.py | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/backend/hct_mis_api/apps/core/tests/snapshots/snap_test_schema.py b/backend/hct_mis_api/apps/core/tests/snapshots/snap_test_schema.py index 2717b38034..57a114f558 100644 --- a/backend/hct_mis_api/apps/core/tests/snapshots/snap_test_schema.py +++ b/backend/hct_mis_api/apps/core/tests/snapshots/snap_test_schema.py @@ -77,7 +77,7 @@ }, { 'displayName': 'Boolean (true/false)', - 'value': 'BOOLEAN' + 'value': 'BOOL' } ] } diff --git a/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_copy_program.py b/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_copy_program.py index d908f6d7de..2ae6850fe0 100644 --- a/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_copy_program.py +++ b/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_copy_program.py @@ -321,7 +321,7 @@ 'Round 3C', 'Round 4D' ], - 'subtype': 'BOOLEAN' + 'subtype': 'BOOL' } } ], diff --git a/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_update_program.py b/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_update_program.py index 7aa9d72497..2b680ba818 100644 --- a/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_update_program.py +++ b/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_update_program.py @@ -778,7 +778,7 @@ 'Round 3C', 'Round 4D' ], - 'subtype': 'BOOLEAN' + 'subtype': 'BOOL' } }, { @@ -802,7 +802,7 @@ 'Round 2 Updated', 'Round 3 Updated' ], - 'subtype': 'BOOLEAN' + 'subtype': 'BOOL' } } ], @@ -828,7 +828,7 @@ 'Round 3C', 'Round 4D' ], - 'subtype': 'BOOLEAN' + 'subtype': 'BOOL' } }, { @@ -852,7 +852,7 @@ 'Round 2 Updated', 'Round 3 Updated' ], - 'subtype': 'BOOLEAN' + 'subtype': 'BOOL' } } ] @@ -916,7 +916,7 @@ 'Round 3C', 'Round 4D' ], - 'subtype': 'BOOLEAN' + 'subtype': 'BOOL' } }, { @@ -940,7 +940,7 @@ 'Round 2 Updated', 'Round 3 Updated' ], - 'subtype': 'BOOLEAN' + 'subtype': 'BOOL' } } ], @@ -985,7 +985,7 @@ 'Round 2 Updated', 'Round 3 Updated' ], - 'subtype': 'BOOLEAN' + 'subtype': 'BOOL' } }, { @@ -999,7 +999,7 @@ 'Round 3C', 'Round 4D' ], - 'subtype': 'BOOLEAN' + 'subtype': 'BOOL' } }, { From f2b0f81874bb2f84be514e5adbe8e12d6a4eb815 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Mon, 19 Aug 2024 21:38:20 +0200 Subject: [PATCH 026/118] button to populate indexes, populate indexes in enrolling from admin --- backend/hct_mis_api/apps/account/models.py | 1 + backend/hct_mis_api/apps/household/celery_tasks.py | 7 +++++++ backend/hct_mis_api/apps/program/admin.py | 14 ++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/backend/hct_mis_api/apps/account/models.py b/backend/hct_mis_api/apps/account/models.py index b5d3f83639..ff150f2a3a 100644 --- a/backend/hct_mis_api/apps/account/models.py +++ b/backend/hct_mis_api/apps/account/models.py @@ -303,6 +303,7 @@ class Meta: ("can_inspect", "Can inspect objects"), ("quick_links", "Can see quick links in admin"), ("restrict_help_desk", "Limit fields to be editable for help desk"), + ("can_reindex_programs", "Can reindex programs"), ) diff --git a/backend/hct_mis_api/apps/household/celery_tasks.py b/backend/hct_mis_api/apps/household/celery_tasks.py index 0152059472..49b4776f34 100644 --- a/backend/hct_mis_api/apps/household/celery_tasks.py +++ b/backend/hct_mis_api/apps/household/celery_tasks.py @@ -10,6 +10,7 @@ from constance import config from hct_mis_api.apps.core.celery import app +from hct_mis_api.apps.household.documents import HouseholdDocument, get_individual_doc from hct_mis_api.apps.household.models import ( COLLECT_TYPE_FULL, COLLECT_TYPE_PARTIAL, @@ -21,6 +22,7 @@ ) from hct_mis_api.apps.program.models import Program from hct_mis_api.apps.program.utils import enroll_households_to_program +from hct_mis_api.apps.utils.elasticsearch_utils import populate_index from hct_mis_api.apps.utils.logs import log_start_and_end from hct_mis_api.apps.utils.phone import calculate_phone_numbers_validity from hct_mis_api.apps.utils.sentry import sentry_tags, set_sentry_business_area_tag @@ -205,6 +207,11 @@ def enroll_households_to_program_task(households_ids: List, program_for_enroll_i households = Household.objects.filter(pk__in=households_ids) program_for_enroll = Program.objects.get(id=program_for_enroll_id) enroll_households_to_program(households, program_for_enroll) + populate_index( + Individual.objects.filter(program=program_for_enroll), + get_individual_doc(program_for_enroll.business_area.slug), + ) + populate_index(Household.objects.filter(program=program_for_enroll), HouseholdDocument) @app.task() diff --git a/backend/hct_mis_api/apps/program/admin.py b/backend/hct_mis_api/apps/program/admin.py index e664a8c92e..b1bbe1aec2 100644 --- a/backend/hct_mis_api/apps/program/admin.py +++ b/backend/hct_mis_api/apps/program/admin.py @@ -16,7 +16,9 @@ from hct_mis_api.apps.account.models import Partner from hct_mis_api.apps.geo.models import Area +from hct_mis_api.apps.household.documents import HouseholdDocument, get_individual_doc from hct_mis_api.apps.household.forms import CreateTargetPopulationTextForm +from hct_mis_api.apps.household.models import Household, Individual from hct_mis_api.apps.program.models import Program, ProgramCycle, ProgramPartnerThrough from hct_mis_api.apps.targeting.celery_tasks import create_tp_from_list from hct_mis_api.apps.targeting.models import TargetingCriteria @@ -25,6 +27,7 @@ LastSyncDateResetMixin, SoftDeletableAdminMixin, ) +from hct_mis_api.apps.utils.elasticsearch_utils import populate_index from mptt.forms import TreeNodeMultipleChoiceField @@ -159,3 +162,14 @@ def partners(self, request: HttpRequest, pk: int) -> Union[TemplateResponse, Htt return TemplateResponse(request, "admin/program/program/program_partner_access.html", context) else: return TemplateResponse(request, "admin/program/program/program_partner_access_readonly.html", context) + + @button(permission="account.can_reindex_programs") + def reindex_program(self, request: HttpRequest, pk: int) -> HttpResponseRedirect: + program = Program.objects.get(pk=pk) + populate_index( + Individual.all_merge_status_objects.filter(program=program), + get_individual_doc(program.business_area.slug), + ) + populate_index(Household.all_merge_status_objects.filter(program=program), HouseholdDocument) + messages.success(request, f"Program {program.name} reindexed.") + return HttpResponseRedirect(reverse("admin:program_program_changelist")) From 78da7d3e8fdcce6ee0cddd079e72b1db34d93da0 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Mon, 19 Aug 2024 21:45:23 +0200 Subject: [PATCH 027/118] add migrtation file --- .../hct_mis_api/apps/account/migrations/0073_migration.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/backend/hct_mis_api/apps/account/migrations/0073_migration.py b/backend/hct_mis_api/apps/account/migrations/0073_migration.py index e053413a2d..e5e220a672 100644 --- a/backend/hct_mis_api/apps/account/migrations/0073_migration.py +++ b/backend/hct_mis_api/apps/account/migrations/0073_migration.py @@ -1,4 +1,8 @@ +<<<<<<< Updated upstream # Generated by Django 3.2.25 on 2024-08-04 19:39 +======= +# Generated by Django 3.2.25 on 2024-08-01 20:24 +>>>>>>> Stashed changes from django.db import migrations, models import hct_mis_api.apps.account.fields @@ -14,6 +18,10 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='role', name='permissions', +<<<<<<< Updated upstream field=hct_mis_api.apps.account.fields.ChoiceArrayField(base_field=models.CharField(choices=[('RDI_VIEW_LIST', 'RDI VIEW LIST'), ('RDI_VIEW_DETAILS', 'RDI VIEW DETAILS'), ('RDI_IMPORT_DATA', 'RDI IMPORT DATA'), ('RDI_RERUN_DEDUPE', 'RDI RERUN DEDUPE'), ('RDI_MERGE_IMPORT', 'RDI MERGE IMPORT'), ('RDI_REFUSE_IMPORT', 'RDI REFUSE IMPORT'), ('POPULATION_VIEW_HOUSEHOLDS_LIST', 'POPULATION VIEW HOUSEHOLDS LIST'), ('POPULATION_VIEW_HOUSEHOLDS_DETAILS', 'POPULATION VIEW HOUSEHOLDS DETAILS'), ('POPULATION_VIEW_INDIVIDUALS_LIST', 'POPULATION VIEW INDIVIDUALS LIST'), ('POPULATION_VIEW_INDIVIDUALS_DETAILS', 'POPULATION VIEW INDIVIDUALS DETAILS'), ('PROGRAMME_VIEW_LIST_AND_DETAILS', 'PROGRAMME VIEW LIST AND DETAILS'), ('PROGRAMME_MANAGEMENT_VIEW', 'PROGRAMME MANAGEMENT VIEW'), ('PROGRAMME_VIEW_PAYMENT_RECORD_DETAILS', 'PROGRAMME VIEW PAYMENT RECORD DETAILS'), ('PROGRAMME_CREATE', 'PROGRAMME CREATE'), ('PROGRAMME_UPDATE', 'PROGRAMME UPDATE'), ('PROGRAMME_REMOVE', 'PROGRAMME REMOVE'), ('PROGRAMME_ACTIVATE', 'PROGRAMME ACTIVATE'), ('PROGRAMME_FINISH', 'PROGRAMME FINISH'), ('PROGRAMME_DUPLICATE', 'PROGRAMME DUPLICATE'), ('TARGETING_VIEW_LIST', 'TARGETING VIEW LIST'), ('TARGETING_VIEW_DETAILS', 'TARGETING VIEW DETAILS'), ('TARGETING_CREATE', 'TARGETING CREATE'), ('TARGETING_UPDATE', 'TARGETING UPDATE'), ('TARGETING_DUPLICATE', 'TARGETING DUPLICATE'), ('TARGETING_REMOVE', 'TARGETING REMOVE'), ('TARGETING_LOCK', 'TARGETING LOCK'), ('TARGETING_UNLOCK', 'TARGETING UNLOCK'), ('TARGETING_SEND', 'TARGETING SEND'), ('PAYMENT_VIEW_LIST_MANAGERIAL', 'PAYMENT VIEW LIST MANAGERIAL'), ('PAYMENT_VIEW_LIST_MANAGERIAL_RELEASED', 'PAYMENT VIEW LIST MANAGERIAL RELEASED'), ('PAYMENT_VERIFICATION_VIEW_LIST', 'PAYMENT VERIFICATION VIEW LIST'), ('PAYMENT_VERIFICATION_VIEW_DETAILS', 'PAYMENT VERIFICATION VIEW DETAILS'), ('PAYMENT_VERIFICATION_CREATE', 'PAYMENT VERIFICATION CREATE'), ('PAYMENT_VERIFICATION_UPDATE', 'PAYMENT VERIFICATION UPDATE'), ('PAYMENT_VERIFICATION_ACTIVATE', 'PAYMENT VERIFICATION ACTIVATE'), ('PAYMENT_VERIFICATION_DISCARD', 'PAYMENT VERIFICATION DISCARD'), ('PAYMENT_VERIFICATION_FINISH', 'PAYMENT VERIFICATION FINISH'), ('PAYMENT_VERIFICATION_EXPORT', 'PAYMENT VERIFICATION EXPORT'), ('PAYMENT_VERIFICATION_IMPORT', 'PAYMENT VERIFICATION IMPORT'), ('PAYMENT_VERIFICATION_VERIFY', 'PAYMENT VERIFICATION VERIFY'), ('PAYMENT_VERIFICATION_VIEW_PAYMENT_RECORD_DETAILS', 'PAYMENT VERIFICATION VIEW PAYMENT RECORD DETAILS'), ('PAYMENT_VERIFICATION_DELETE', 'PAYMENT VERIFICATION DELETE'), ('PAYMENT_VERIFICATION_INVALID', 'PAYMENT VERIFICATION INVALID'), ('PAYMENT_VERIFICATION_MARK_AS_FAILED', 'PAYMENT VERIFICATION MARK AS FAILED'), ('PM_VIEW_LIST', 'PM VIEW LIST'), ('PM_CREATE', 'PM CREATE'), ('PM_VIEW_DETAILS', 'PM VIEW DETAILS'), ('PM_IMPORT_XLSX_WITH_ENTITLEMENTS', 'PM IMPORT XLSX WITH ENTITLEMENTS'), ('PM_APPLY_RULE_ENGINE_FORMULA_WITH_ENTITLEMENTS', 'PM APPLY RULE ENGINE FORMULA WITH ENTITLEMENTS'), ('PM_SPLIT', 'PM SPLIT'), ('PM_LOCK_AND_UNLOCK', 'PM LOCK AND UNLOCK'), ('PM_LOCK_AND_UNLOCK_FSP', 'PM LOCK AND UNLOCK FSP'), ('PM_SEND_FOR_APPROVAL', 'PM SEND FOR APPROVAL'), ('PM_EXCLUDE_BENEFICIARIES_FROM_FOLLOW_UP_PP', 'PM EXCLUDE BENEFICIARIES FROM FOLLOW UP PP'), ('PM_ACCEPTANCE_PROCESS_APPROVE', 'PM ACCEPTANCE PROCESS APPROVE'), ('PM_ACCEPTANCE_PROCESS_AUTHORIZE', 'PM ACCEPTANCE PROCESS AUTHORIZE'), ('PM_ACCEPTANCE_PROCESS_FINANCIAL_REVIEW', 'PM ACCEPTANCE PROCESS FINANCIAL REVIEW'), ('PM_IMPORT_XLSX_WITH_RECONCILIATION', 'PM IMPORT XLSX WITH RECONCILIATION'), ('PM_EXPORT_XLSX_FOR_FSP', 'PM EXPORT XLSX FOR FSP'), ('PM_DOWNLOAD_XLSX_FOR_FSP', 'PM DOWNLOAD XLSX FOR FSP'), ('PM_MARK_PAYMENT_AS_FAILED', 'PM MARK PAYMENT AS FAILED'), ('PM_EXPORT_PDF_SUMMARY', 'PM EXPORT PDF SUMMARY'), ('PM_SEND_TO_PAYMENT_GATEWAY', 'PM SEND TO PAYMENT GATEWAY'), ('PM_VIEW_FSP_AUTH_CODE', 'PM VIEW FSP AUTH CODE'), ('PM_ADMIN_FINANCIAL_SERVICE_PROVIDER_UPDATE', 'PM ADMIN FINANCIAL SERVICE PROVIDER UPDATE'), ('PM_PROGRAMME_CYCLE_VIEW_LIST', 'PM PROGRAMME CYCLE VIEW LIST'), ('PM_PROGRAMME_CYCLE_VIEW_DETAILS', 'PM PROGRAMME CYCLE VIEW DETAILS'), ('PM_PROGRAMME_CYCLE_CREATE', 'PM PROGRAMME CYCLE CREATE'), ('PM_PROGRAMME_CYCLE_UPDATE', 'PM PROGRAMME CYCLE UPDATE'), ('PM_PROGRAMME_CYCLE_DELETE', 'PM PROGRAMME CYCLE DELETE'), ('USER_MANAGEMENT_VIEW_LIST', 'USER MANAGEMENT VIEW LIST'), ('DASHBOARD_VIEW_COUNTRY', 'DASHBOARD VIEW COUNTRY'), ('DASHBOARD_EXPORT', 'DASHBOARD EXPORT'), ('GRIEVANCES_VIEW_LIST_EXCLUDING_SENSITIVE', 'GRIEVANCES VIEW LIST EXCLUDING SENSITIVE'), ('GRIEVANCES_VIEW_LIST_EXCLUDING_SENSITIVE_AS_CREATOR', 'GRIEVANCES VIEW LIST EXCLUDING SENSITIVE AS CREATOR'), ('GRIEVANCES_VIEW_LIST_EXCLUDING_SENSITIVE_AS_OWNER', 'GRIEVANCES VIEW LIST EXCLUDING SENSITIVE AS OWNER'), ('GRIEVANCES_VIEW_LIST_SENSITIVE', 'GRIEVANCES VIEW LIST SENSITIVE'), ('GRIEVANCES_VIEW_LIST_SENSITIVE_AS_CREATOR', 'GRIEVANCES VIEW LIST SENSITIVE AS CREATOR'), ('GRIEVANCES_VIEW_LIST_SENSITIVE_AS_OWNER', 'GRIEVANCES VIEW LIST SENSITIVE AS OWNER'), ('GRIEVANCES_VIEW_DETAILS_EXCLUDING_SENSITIVE', 'GRIEVANCES VIEW DETAILS EXCLUDING SENSITIVE'), ('GRIEVANCES_VIEW_DETAILS_EXCLUDING_SENSITIVE_AS_CREATOR', 'GRIEVANCES VIEW DETAILS EXCLUDING SENSITIVE AS CREATOR'), ('GRIEVANCES_VIEW_DETAILS_EXCLUDING_SENSITIVE_AS_OWNER', 'GRIEVANCES VIEW DETAILS EXCLUDING SENSITIVE AS OWNER'), ('GRIEVANCES_VIEW_DETAILS_SENSITIVE', 'GRIEVANCES VIEW DETAILS SENSITIVE'), ('GRIEVANCES_VIEW_DETAILS_SENSITIVE_AS_CREATOR', 'GRIEVANCES VIEW DETAILS SENSITIVE AS CREATOR'), ('GRIEVANCES_VIEW_DETAILS_SENSITIVE_AS_OWNER', 'GRIEVANCES VIEW DETAILS SENSITIVE AS OWNER'), ('GRIEVANCES_VIEW_HOUSEHOLD_DETAILS', 'GRIEVANCES VIEW HOUSEHOLD DETAILS'), ('GRIEVANCES_VIEW_HOUSEHOLD_DETAILS_AS_CREATOR', 'GRIEVANCES VIEW HOUSEHOLD DETAILS AS CREATOR'), ('GRIEVANCES_VIEW_HOUSEHOLD_DETAILS_AS_OWNER', 'GRIEVANCES VIEW HOUSEHOLD DETAILS AS OWNER'), ('GRIEVANCES_VIEW_INDIVIDUALS_DETAILS', 'GRIEVANCES VIEW INDIVIDUALS DETAILS'), ('GRIEVANCES_VIEW_INDIVIDUALS_DETAILS_AS_CREATOR', 'GRIEVANCES VIEW INDIVIDUALS DETAILS AS CREATOR'), ('GRIEVANCES_VIEW_INDIVIDUALS_DETAILS_AS_OWNER', 'GRIEVANCES VIEW INDIVIDUALS DETAILS AS OWNER'), ('GRIEVANCES_CREATE', 'GRIEVANCES CREATE'), ('GRIEVANCES_UPDATE', 'GRIEVANCES UPDATE'), ('GRIEVANCES_UPDATE_AS_CREATOR', 'GRIEVANCES UPDATE AS CREATOR'), ('GRIEVANCES_UPDATE_AS_OWNER', 'GRIEVANCES UPDATE AS OWNER'), ('GRIEVANCES_UPDATE_REQUESTED_DATA_CHANGE', 'GRIEVANCES UPDATE REQUESTED DATA CHANGE'), ('GRIEVANCES_UPDATE_REQUESTED_DATA_CHANGE_AS_CREATOR', 'GRIEVANCES UPDATE REQUESTED DATA CHANGE AS CREATOR'), ('GRIEVANCES_UPDATE_REQUESTED_DATA_CHANGE_AS_OWNER', 'GRIEVANCES UPDATE REQUESTED DATA CHANGE AS OWNER'), ('GRIEVANCES_ADD_NOTE', 'GRIEVANCES ADD NOTE'), ('GRIEVANCES_ADD_NOTE_AS_CREATOR', 'GRIEVANCES ADD NOTE AS CREATOR'), ('GRIEVANCES_ADD_NOTE_AS_OWNER', 'GRIEVANCES ADD NOTE AS OWNER'), ('GRIEVANCES_SET_IN_PROGRESS', 'GRIEVANCES SET IN PROGRESS'), ('GRIEVANCES_SET_IN_PROGRESS_AS_CREATOR', 'GRIEVANCES SET IN PROGRESS AS CREATOR'), ('GRIEVANCES_SET_IN_PROGRESS_AS_OWNER', 'GRIEVANCES SET IN PROGRESS AS OWNER'), ('GRIEVANCES_SET_ON_HOLD', 'GRIEVANCES SET ON HOLD'), ('GRIEVANCES_SET_ON_HOLD_AS_CREATOR', 'GRIEVANCES SET ON HOLD AS CREATOR'), ('GRIEVANCES_SET_ON_HOLD_AS_OWNER', 'GRIEVANCES SET ON HOLD AS OWNER'), ('GRIEVANCES_SEND_FOR_APPROVAL', 'GRIEVANCES SEND FOR APPROVAL'), ('GRIEVANCES_SEND_FOR_APPROVAL_AS_CREATOR', 'GRIEVANCES SEND FOR APPROVAL AS CREATOR'), ('GRIEVANCES_SEND_FOR_APPROVAL_AS_OWNER', 'GRIEVANCES SEND FOR APPROVAL AS OWNER'), ('GRIEVANCES_SEND_BACK', 'GRIEVANCES SEND BACK'), ('GRIEVANCES_SEND_BACK_AS_CREATOR', 'GRIEVANCES SEND BACK AS CREATOR'), ('GRIEVANCES_SEND_BACK_AS_OWNER', 'GRIEVANCES SEND BACK AS OWNER'), ('GRIEVANCES_APPROVE_DATA_CHANGE', 'GRIEVANCES APPROVE DATA CHANGE'), ('GRIEVANCES_APPROVE_DATA_CHANGE_AS_CREATOR', 'GRIEVANCES APPROVE DATA CHANGE AS CREATOR'), ('GRIEVANCES_APPROVE_DATA_CHANGE_AS_OWNER', 'GRIEVANCES APPROVE DATA CHANGE AS OWNER'), ('GRIEVANCES_CLOSE_TICKET_EXCLUDING_FEEDBACK', 'GRIEVANCES CLOSE TICKET EXCLUDING FEEDBACK'), ('GRIEVANCES_CLOSE_TICKET_EXCLUDING_FEEDBACK_AS_CREATOR', 'GRIEVANCES CLOSE TICKET EXCLUDING FEEDBACK AS CREATOR'), ('GRIEVANCES_CLOSE_TICKET_EXCLUDING_FEEDBACK_AS_OWNER', 'GRIEVANCES CLOSE TICKET EXCLUDING FEEDBACK AS OWNER'), ('GRIEVANCES_CLOSE_TICKET_FEEDBACK', 'GRIEVANCES CLOSE TICKET FEEDBACK'), ('GRIEVANCES_CLOSE_TICKET_FEEDBACK_AS_CREATOR', 'GRIEVANCES CLOSE TICKET FEEDBACK AS CREATOR'), ('GRIEVANCES_CLOSE_TICKET_FEEDBACK_AS_OWNER', 'GRIEVANCES CLOSE TICKET FEEDBACK AS OWNER'), ('GRIEVANCES_APPROVE_FLAG_AND_DEDUPE', 'GRIEVANCES APPROVE FLAG AND DEDUPE'), ('GRIEVANCES_APPROVE_FLAG_AND_DEDUPE_AS_CREATOR', 'GRIEVANCES APPROVE FLAG AND DEDUPE AS CREATOR'), ('GRIEVANCES_APPROVE_FLAG_AND_DEDUPE_AS_OWNER', 'GRIEVANCES APPROVE FLAG AND DEDUPE AS OWNER'), ('GRIEVANCES_APPROVE_PAYMENT_VERIFICATION', 'GRIEVANCES APPROVE PAYMENT VERIFICATION'), ('GRIEVANCES_APPROVE_PAYMENT_VERIFICATION_AS_CREATOR', 'GRIEVANCES APPROVE PAYMENT VERIFICATION AS CREATOR'), ('GRIEVANCES_APPROVE_PAYMENT_VERIFICATION_AS_OWNER', 'GRIEVANCES APPROVE PAYMENT VERIFICATION AS OWNER'), ('GRIEVANCE_ASSIGN', 'GRIEVANCE ASSIGN'), ('GRIEVANCE_DOCUMENTS_UPLOAD', 'GRIEVANCE DOCUMENTS UPLOAD'), ('GRIEVANCES_CROSS_AREA_FILTER', 'GRIEVANCES CROSS AREA FILTER'), ('GRIEVANCES_FEEDBACK_VIEW_CREATE', 'GRIEVANCES FEEDBACK VIEW CREATE'), ('GRIEVANCES_FEEDBACK_VIEW_LIST', 'GRIEVANCES FEEDBACK VIEW LIST'), ('GRIEVANCES_FEEDBACK_VIEW_DETAILS', 'GRIEVANCES FEEDBACK VIEW DETAILS'), ('GRIEVANCES_FEEDBACK_VIEW_UPDATE', 'GRIEVANCES FEEDBACK VIEW UPDATE'), ('GRIEVANCES_FEEDBACK_MESSAGE_VIEW_CREATE', 'GRIEVANCES FEEDBACK MESSAGE VIEW CREATE'), ('REPORTING_EXPORT', 'REPORTING EXPORT'), ('PDU_VIEW_LIST_AND_DETAILS', 'PDU VIEW LIST AND DETAILS'), ('PDU_TEMPLATE_CREATE', 'PDU TEMPLATE CREATE'), ('PDU_TEMPLATE_DOWNLOAD', 'PDU TEMPLATE DOWNLOAD'), ('PDU_UPLOAD', 'PDU UPLOAD'), ('ALL_VIEW_PII_DATA_ON_LISTS', 'ALL VIEW PII DATA ON LISTS'), ('ACTIVITY_LOG_VIEW', 'ACTIVITY LOG VIEW'), ('ACTIVITY_LOG_DOWNLOAD', 'ACTIVITY LOG DOWNLOAD'), ('UPLOAD_STORAGE_FILE', 'UPLOAD STORAGE FILE'), ('DOWNLOAD_STORAGE_FILE', 'DOWNLOAD STORAGE FILE'), ('ACCOUNTABILITY_COMMUNICATION_MESSAGE_VIEW_LIST', 'ACCOUNTABILITY COMMUNICATION MESSAGE VIEW LIST'), ('ACCOUNTABILITY_COMMUNICATION_MESSAGE_VIEW_DETAILS', 'ACCOUNTABILITY COMMUNICATION MESSAGE VIEW DETAILS'), ('ACCOUNTABILITY_COMMUNICATION_MESSAGE_VIEW_CREATE', 'ACCOUNTABILITY COMMUNICATION MESSAGE VIEW CREATE'), ('ACCOUNTABILITY_COMMUNICATION_MESSAGE_VIEW_DETAILS_AS_CREATOR', 'ACCOUNTABILITY COMMUNICATION MESSAGE VIEW DETAILS AS CREATOR'), ('ACCOUNTABILITY_SURVEY_VIEW_CREATE', 'ACCOUNTABILITY SURVEY VIEW CREATE'), ('ACCOUNTABILITY_SURVEY_VIEW_LIST', 'ACCOUNTABILITY SURVEY VIEW LIST'), ('ACCOUNTABILITY_SURVEY_VIEW_DETAILS', 'ACCOUNTABILITY SURVEY VIEW DETAILS'), ('GEO_VIEW_LIST', 'GEO VIEW LIST'), ('CAN_ADD_BUSINESS_AREA_TO_PARTNER', 'CAN ADD BUSINESS AREA TO PARTNER')], max_length=255), blank=True, null=True, size=None), +======= + field=hct_mis_api.apps.account.fields.ChoiceArrayField(base_field=models.CharField(choices=[('RDI_VIEW_LIST', 'RDI VIEW LIST'), ('RDI_VIEW_DETAILS', 'RDI VIEW DETAILS'), ('RDI_IMPORT_DATA', 'RDI IMPORT DATA'), ('RDI_RERUN_DEDUPE', 'RDI RERUN DEDUPE'), ('RDI_MERGE_IMPORT', 'RDI MERGE IMPORT'), ('RDI_REFUSE_IMPORT', 'RDI REFUSE IMPORT'), ('POPULATION_VIEW_HOUSEHOLDS_LIST', 'POPULATION VIEW HOUSEHOLDS LIST'), ('POPULATION_VIEW_HOUSEHOLDS_DETAILS', 'POPULATION VIEW HOUSEHOLDS DETAILS'), ('POPULATION_VIEW_INDIVIDUALS_LIST', 'POPULATION VIEW INDIVIDUALS LIST'), ('POPULATION_VIEW_INDIVIDUALS_DETAILS', 'POPULATION VIEW INDIVIDUALS DETAILS'), ('PROGRAMME_VIEW_LIST_AND_DETAILS', 'PROGRAMME VIEW LIST AND DETAILS'), ('PROGRAMME_MANAGEMENT_VIEW', 'PROGRAMME MANAGEMENT VIEW'), ('PROGRAMME_VIEW_PAYMENT_RECORD_DETAILS', 'PROGRAMME VIEW PAYMENT RECORD DETAILS'), ('PROGRAMME_CREATE', 'PROGRAMME CREATE'), ('PROGRAMME_UPDATE', 'PROGRAMME UPDATE'), ('PROGRAMME_REMOVE', 'PROGRAMME REMOVE'), ('PROGRAMME_ACTIVATE', 'PROGRAMME ACTIVATE'), ('PROGRAMME_FINISH', 'PROGRAMME FINISH'), ('PROGRAMME_DUPLICATE', 'PROGRAMME DUPLICATE'), ('TARGETING_VIEW_LIST', 'TARGETING VIEW LIST'), ('TARGETING_VIEW_DETAILS', 'TARGETING VIEW DETAILS'), ('TARGETING_CREATE', 'TARGETING CREATE'), ('TARGETING_UPDATE', 'TARGETING UPDATE'), ('TARGETING_DUPLICATE', 'TARGETING DUPLICATE'), ('TARGETING_REMOVE', 'TARGETING REMOVE'), ('TARGETING_LOCK', 'TARGETING LOCK'), ('TARGETING_UNLOCK', 'TARGETING UNLOCK'), ('TARGETING_SEND', 'TARGETING SEND'), ('PAYMENT_VIEW_LIST_MANAGERIAL', 'PAYMENT VIEW LIST MANAGERIAL'), ('PAYMENT_VIEW_LIST_MANAGERIAL_RELEASED', 'PAYMENT VIEW LIST MANAGERIAL RELEASED'), ('PAYMENT_VERIFICATION_VIEW_LIST', 'PAYMENT VERIFICATION VIEW LIST'), ('PAYMENT_VERIFICATION_VIEW_DETAILS', 'PAYMENT VERIFICATION VIEW DETAILS'), ('PAYMENT_VERIFICATION_CREATE', 'PAYMENT VERIFICATION CREATE'), ('PAYMENT_VERIFICATION_UPDATE', 'PAYMENT VERIFICATION UPDATE'), ('PAYMENT_VERIFICATION_ACTIVATE', 'PAYMENT VERIFICATION ACTIVATE'), ('PAYMENT_VERIFICATION_DISCARD', 'PAYMENT VERIFICATION DISCARD'), ('PAYMENT_VERIFICATION_FINISH', 'PAYMENT VERIFICATION FINISH'), ('PAYMENT_VERIFICATION_EXPORT', 'PAYMENT VERIFICATION EXPORT'), ('PAYMENT_VERIFICATION_IMPORT', 'PAYMENT VERIFICATION IMPORT'), ('PAYMENT_VERIFICATION_VERIFY', 'PAYMENT VERIFICATION VERIFY'), ('PAYMENT_VERIFICATION_VIEW_PAYMENT_RECORD_DETAILS', 'PAYMENT VERIFICATION VIEW PAYMENT RECORD DETAILS'), ('PAYMENT_VERIFICATION_DELETE', 'PAYMENT VERIFICATION DELETE'), ('PAYMENT_VERIFICATION_INVALID', 'PAYMENT VERIFICATION INVALID'), ('PAYMENT_VERIFICATION_MARK_AS_FAILED', 'PAYMENT VERIFICATION MARK AS FAILED'), ('PM_VIEW_LIST', 'PM VIEW LIST'), ('PM_CREATE', 'PM CREATE'), ('PM_VIEW_DETAILS', 'PM VIEW DETAILS'), ('PM_IMPORT_XLSX_WITH_ENTITLEMENTS', 'PM IMPORT XLSX WITH ENTITLEMENTS'), ('PM_APPLY_RULE_ENGINE_FORMULA_WITH_ENTITLEMENTS', 'PM APPLY RULE ENGINE FORMULA WITH ENTITLEMENTS'), ('PM_SPLIT', 'PM SPLIT'), ('PM_LOCK_AND_UNLOCK', 'PM LOCK AND UNLOCK'), ('PM_LOCK_AND_UNLOCK_FSP', 'PM LOCK AND UNLOCK FSP'), ('PM_SEND_FOR_APPROVAL', 'PM SEND FOR APPROVAL'), ('PM_EXCLUDE_BENEFICIARIES_FROM_FOLLOW_UP_PP', 'PM EXCLUDE BENEFICIARIES FROM FOLLOW UP PP'), ('PM_ACCEPTANCE_PROCESS_APPROVE', 'PM ACCEPTANCE PROCESS APPROVE'), ('PM_ACCEPTANCE_PROCESS_AUTHORIZE', 'PM ACCEPTANCE PROCESS AUTHORIZE'), ('PM_ACCEPTANCE_PROCESS_FINANCIAL_REVIEW', 'PM ACCEPTANCE PROCESS FINANCIAL REVIEW'), ('PM_IMPORT_XLSX_WITH_RECONCILIATION', 'PM IMPORT XLSX WITH RECONCILIATION'), ('PM_EXPORT_XLSX_FOR_FSP', 'PM EXPORT XLSX FOR FSP'), ('PM_DOWNLOAD_XLSX_FOR_FSP', 'PM DOWNLOAD XLSX FOR FSP'), ('PM_MARK_PAYMENT_AS_FAILED', 'PM MARK PAYMENT AS FAILED'), ('PM_EXPORT_PDF_SUMMARY', 'PM EXPORT PDF SUMMARY'), ('PM_SEND_TO_PAYMENT_GATEWAY', 'PM SEND TO PAYMENT GATEWAY'), ('PM_VIEW_FSP_AUTH_CODE', 'PM VIEW FSP AUTH CODE'), ('PM_ADMIN_FINANCIAL_SERVICE_PROVIDER_UPDATE', 'PM ADMIN FINANCIAL SERVICE PROVIDER UPDATE'), ('USER_MANAGEMENT_VIEW_LIST', 'USER MANAGEMENT VIEW LIST'), ('DASHBOARD_VIEW_COUNTRY', 'DASHBOARD VIEW COUNTRY'), ('DASHBOARD_EXPORT', 'DASHBOARD EXPORT'), ('GRIEVANCES_VIEW_LIST_EXCLUDING_SENSITIVE', 'GRIEVANCES VIEW LIST EXCLUDING SENSITIVE'), ('GRIEVANCES_VIEW_LIST_EXCLUDING_SENSITIVE_AS_CREATOR', 'GRIEVANCES VIEW LIST EXCLUDING SENSITIVE AS CREATOR'), ('GRIEVANCES_VIEW_LIST_EXCLUDING_SENSITIVE_AS_OWNER', 'GRIEVANCES VIEW LIST EXCLUDING SENSITIVE AS OWNER'), ('GRIEVANCES_VIEW_LIST_SENSITIVE', 'GRIEVANCES VIEW LIST SENSITIVE'), ('GRIEVANCES_VIEW_LIST_SENSITIVE_AS_CREATOR', 'GRIEVANCES VIEW LIST SENSITIVE AS CREATOR'), ('GRIEVANCES_VIEW_LIST_SENSITIVE_AS_OWNER', 'GRIEVANCES VIEW LIST SENSITIVE AS OWNER'), ('GRIEVANCES_VIEW_DETAILS_EXCLUDING_SENSITIVE', 'GRIEVANCES VIEW DETAILS EXCLUDING SENSITIVE'), ('GRIEVANCES_VIEW_DETAILS_EXCLUDING_SENSITIVE_AS_CREATOR', 'GRIEVANCES VIEW DETAILS EXCLUDING SENSITIVE AS CREATOR'), ('GRIEVANCES_VIEW_DETAILS_EXCLUDING_SENSITIVE_AS_OWNER', 'GRIEVANCES VIEW DETAILS EXCLUDING SENSITIVE AS OWNER'), ('GRIEVANCES_VIEW_DETAILS_SENSITIVE', 'GRIEVANCES VIEW DETAILS SENSITIVE'), ('GRIEVANCES_VIEW_DETAILS_SENSITIVE_AS_CREATOR', 'GRIEVANCES VIEW DETAILS SENSITIVE AS CREATOR'), ('GRIEVANCES_VIEW_DETAILS_SENSITIVE_AS_OWNER', 'GRIEVANCES VIEW DETAILS SENSITIVE AS OWNER'), ('GRIEVANCES_VIEW_HOUSEHOLD_DETAILS', 'GRIEVANCES VIEW HOUSEHOLD DETAILS'), ('GRIEVANCES_VIEW_HOUSEHOLD_DETAILS_AS_CREATOR', 'GRIEVANCES VIEW HOUSEHOLD DETAILS AS CREATOR'), ('GRIEVANCES_VIEW_HOUSEHOLD_DETAILS_AS_OWNER', 'GRIEVANCES VIEW HOUSEHOLD DETAILS AS OWNER'), ('GRIEVANCES_VIEW_INDIVIDUALS_DETAILS', 'GRIEVANCES VIEW INDIVIDUALS DETAILS'), ('GRIEVANCES_VIEW_INDIVIDUALS_DETAILS_AS_CREATOR', 'GRIEVANCES VIEW INDIVIDUALS DETAILS AS CREATOR'), ('GRIEVANCES_VIEW_INDIVIDUALS_DETAILS_AS_OWNER', 'GRIEVANCES VIEW INDIVIDUALS DETAILS AS OWNER'), ('GRIEVANCES_CREATE', 'GRIEVANCES CREATE'), ('GRIEVANCES_UPDATE', 'GRIEVANCES UPDATE'), ('GRIEVANCES_UPDATE_AS_CREATOR', 'GRIEVANCES UPDATE AS CREATOR'), ('GRIEVANCES_UPDATE_AS_OWNER', 'GRIEVANCES UPDATE AS OWNER'), ('GRIEVANCES_UPDATE_REQUESTED_DATA_CHANGE', 'GRIEVANCES UPDATE REQUESTED DATA CHANGE'), ('GRIEVANCES_UPDATE_REQUESTED_DATA_CHANGE_AS_CREATOR', 'GRIEVANCES UPDATE REQUESTED DATA CHANGE AS CREATOR'), ('GRIEVANCES_UPDATE_REQUESTED_DATA_CHANGE_AS_OWNER', 'GRIEVANCES UPDATE REQUESTED DATA CHANGE AS OWNER'), ('GRIEVANCES_ADD_NOTE', 'GRIEVANCES ADD NOTE'), ('GRIEVANCES_ADD_NOTE_AS_CREATOR', 'GRIEVANCES ADD NOTE AS CREATOR'), ('GRIEVANCES_ADD_NOTE_AS_OWNER', 'GRIEVANCES ADD NOTE AS OWNER'), ('GRIEVANCES_SET_IN_PROGRESS', 'GRIEVANCES SET IN PROGRESS'), ('GRIEVANCES_SET_IN_PROGRESS_AS_CREATOR', 'GRIEVANCES SET IN PROGRESS AS CREATOR'), ('GRIEVANCES_SET_IN_PROGRESS_AS_OWNER', 'GRIEVANCES SET IN PROGRESS AS OWNER'), ('GRIEVANCES_SET_ON_HOLD', 'GRIEVANCES SET ON HOLD'), ('GRIEVANCES_SET_ON_HOLD_AS_CREATOR', 'GRIEVANCES SET ON HOLD AS CREATOR'), ('GRIEVANCES_SET_ON_HOLD_AS_OWNER', 'GRIEVANCES SET ON HOLD AS OWNER'), ('GRIEVANCES_SEND_FOR_APPROVAL', 'GRIEVANCES SEND FOR APPROVAL'), ('GRIEVANCES_SEND_FOR_APPROVAL_AS_CREATOR', 'GRIEVANCES SEND FOR APPROVAL AS CREATOR'), ('GRIEVANCES_SEND_FOR_APPROVAL_AS_OWNER', 'GRIEVANCES SEND FOR APPROVAL AS OWNER'), ('GRIEVANCES_SEND_BACK', 'GRIEVANCES SEND BACK'), ('GRIEVANCES_SEND_BACK_AS_CREATOR', 'GRIEVANCES SEND BACK AS CREATOR'), ('GRIEVANCES_SEND_BACK_AS_OWNER', 'GRIEVANCES SEND BACK AS OWNER'), ('GRIEVANCES_APPROVE_DATA_CHANGE', 'GRIEVANCES APPROVE DATA CHANGE'), ('GRIEVANCES_APPROVE_DATA_CHANGE_AS_CREATOR', 'GRIEVANCES APPROVE DATA CHANGE AS CREATOR'), ('GRIEVANCES_APPROVE_DATA_CHANGE_AS_OWNER', 'GRIEVANCES APPROVE DATA CHANGE AS OWNER'), ('GRIEVANCES_CLOSE_TICKET_EXCLUDING_FEEDBACK', 'GRIEVANCES CLOSE TICKET EXCLUDING FEEDBACK'), ('GRIEVANCES_CLOSE_TICKET_EXCLUDING_FEEDBACK_AS_CREATOR', 'GRIEVANCES CLOSE TICKET EXCLUDING FEEDBACK AS CREATOR'), ('GRIEVANCES_CLOSE_TICKET_EXCLUDING_FEEDBACK_AS_OWNER', 'GRIEVANCES CLOSE TICKET EXCLUDING FEEDBACK AS OWNER'), ('GRIEVANCES_CLOSE_TICKET_FEEDBACK', 'GRIEVANCES CLOSE TICKET FEEDBACK'), ('GRIEVANCES_CLOSE_TICKET_FEEDBACK_AS_CREATOR', 'GRIEVANCES CLOSE TICKET FEEDBACK AS CREATOR'), ('GRIEVANCES_CLOSE_TICKET_FEEDBACK_AS_OWNER', 'GRIEVANCES CLOSE TICKET FEEDBACK AS OWNER'), ('GRIEVANCES_APPROVE_FLAG_AND_DEDUPE', 'GRIEVANCES APPROVE FLAG AND DEDUPE'), ('GRIEVANCES_APPROVE_FLAG_AND_DEDUPE_AS_CREATOR', 'GRIEVANCES APPROVE FLAG AND DEDUPE AS CREATOR'), ('GRIEVANCES_APPROVE_FLAG_AND_DEDUPE_AS_OWNER', 'GRIEVANCES APPROVE FLAG AND DEDUPE AS OWNER'), ('GRIEVANCES_APPROVE_PAYMENT_VERIFICATION', 'GRIEVANCES APPROVE PAYMENT VERIFICATION'), ('GRIEVANCES_APPROVE_PAYMENT_VERIFICATION_AS_CREATOR', 'GRIEVANCES APPROVE PAYMENT VERIFICATION AS CREATOR'), ('GRIEVANCES_APPROVE_PAYMENT_VERIFICATION_AS_OWNER', 'GRIEVANCES APPROVE PAYMENT VERIFICATION AS OWNER'), ('GRIEVANCE_ASSIGN', 'GRIEVANCE ASSIGN'), ('GRIEVANCE_DOCUMENTS_UPLOAD', 'GRIEVANCE DOCUMENTS UPLOAD'), ('GRIEVANCES_CROSS_AREA_FILTER', 'GRIEVANCES CROSS AREA FILTER'), ('GRIEVANCES_FEEDBACK_VIEW_CREATE', 'GRIEVANCES FEEDBACK VIEW CREATE'), ('GRIEVANCES_FEEDBACK_VIEW_LIST', 'GRIEVANCES FEEDBACK VIEW LIST'), ('GRIEVANCES_FEEDBACK_VIEW_DETAILS', 'GRIEVANCES FEEDBACK VIEW DETAILS'), ('GRIEVANCES_FEEDBACK_VIEW_UPDATE', 'GRIEVANCES FEEDBACK VIEW UPDATE'), ('GRIEVANCES_FEEDBACK_MESSAGE_VIEW_CREATE', 'GRIEVANCES FEEDBACK MESSAGE VIEW CREATE'), ('REPORTING_EXPORT', 'REPORTING EXPORT'), ('PDU_VIEW_LIST_AND_DETAILS', 'PDU VIEW LIST AND DETAILS'), ('PDU_TEMPLATE_CREATE', 'PDU TEMPLATE CREATE'), ('PDU_TEMPLATE_DOWNLOAD', 'PDU TEMPLATE DOWNLOAD'), ('PDU_UPLOAD', 'PDU UPLOAD'), ('ALL_VIEW_PII_DATA_ON_LISTS', 'ALL VIEW PII DATA ON LISTS'), ('ACTIVITY_LOG_VIEW', 'ACTIVITY LOG VIEW'), ('ACTIVITY_LOG_DOWNLOAD', 'ACTIVITY LOG DOWNLOAD'), ('UPLOAD_STORAGE_FILE', 'UPLOAD STORAGE FILE'), ('DOWNLOAD_STORAGE_FILE', 'DOWNLOAD STORAGE FILE'), ('ACCOUNTABILITY_COMMUNICATION_MESSAGE_VIEW_LIST', 'ACCOUNTABILITY COMMUNICATION MESSAGE VIEW LIST'), ('ACCOUNTABILITY_COMMUNICATION_MESSAGE_VIEW_DETAILS', 'ACCOUNTABILITY COMMUNICATION MESSAGE VIEW DETAILS'), ('ACCOUNTABILITY_COMMUNICATION_MESSAGE_VIEW_CREATE', 'ACCOUNTABILITY COMMUNICATION MESSAGE VIEW CREATE'), ('ACCOUNTABILITY_COMMUNICATION_MESSAGE_VIEW_DETAILS_AS_CREATOR', 'ACCOUNTABILITY COMMUNICATION MESSAGE VIEW DETAILS AS CREATOR'), ('ACCOUNTABILITY_SURVEY_VIEW_CREATE', 'ACCOUNTABILITY SURVEY VIEW CREATE'), ('ACCOUNTABILITY_SURVEY_VIEW_LIST', 'ACCOUNTABILITY SURVEY VIEW LIST'), ('ACCOUNTABILITY_SURVEY_VIEW_DETAILS', 'ACCOUNTABILITY SURVEY VIEW DETAILS'), ('GEO_VIEW_LIST', 'GEO VIEW LIST'), ('CAN_ADD_BUSINESS_AREA_TO_PARTNER', 'CAN ADD BUSINESS AREA TO PARTNER')], max_length=255), blank=True, null=True, size=None), +>>>>>>> Stashed changes ), ] From cefa03678751c63c946d8017bbd1e7eb04c2c5ea Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Mon, 19 Aug 2024 21:48:21 +0200 Subject: [PATCH 028/118] fix migrations --- .../apps/account/migrations/0073_migration.py | 10 +--------- .../apps/account/migrations/0074_migration.py | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 9 deletions(-) create mode 100644 backend/hct_mis_api/apps/account/migrations/0074_migration.py diff --git a/backend/hct_mis_api/apps/account/migrations/0073_migration.py b/backend/hct_mis_api/apps/account/migrations/0073_migration.py index e5e220a672..41f6232e87 100644 --- a/backend/hct_mis_api/apps/account/migrations/0073_migration.py +++ b/backend/hct_mis_api/apps/account/migrations/0073_migration.py @@ -1,8 +1,4 @@ -<<<<<<< Updated upstream # Generated by Django 3.2.25 on 2024-08-04 19:39 -======= -# Generated by Django 3.2.25 on 2024-08-01 20:24 ->>>>>>> Stashed changes from django.db import migrations, models import hct_mis_api.apps.account.fields @@ -18,10 +14,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='role', name='permissions', -<<<<<<< Updated upstream field=hct_mis_api.apps.account.fields.ChoiceArrayField(base_field=models.CharField(choices=[('RDI_VIEW_LIST', 'RDI VIEW LIST'), ('RDI_VIEW_DETAILS', 'RDI VIEW DETAILS'), ('RDI_IMPORT_DATA', 'RDI IMPORT DATA'), ('RDI_RERUN_DEDUPE', 'RDI RERUN DEDUPE'), ('RDI_MERGE_IMPORT', 'RDI MERGE IMPORT'), ('RDI_REFUSE_IMPORT', 'RDI REFUSE IMPORT'), ('POPULATION_VIEW_HOUSEHOLDS_LIST', 'POPULATION VIEW HOUSEHOLDS LIST'), ('POPULATION_VIEW_HOUSEHOLDS_DETAILS', 'POPULATION VIEW HOUSEHOLDS DETAILS'), ('POPULATION_VIEW_INDIVIDUALS_LIST', 'POPULATION VIEW INDIVIDUALS LIST'), ('POPULATION_VIEW_INDIVIDUALS_DETAILS', 'POPULATION VIEW INDIVIDUALS DETAILS'), ('PROGRAMME_VIEW_LIST_AND_DETAILS', 'PROGRAMME VIEW LIST AND DETAILS'), ('PROGRAMME_MANAGEMENT_VIEW', 'PROGRAMME MANAGEMENT VIEW'), ('PROGRAMME_VIEW_PAYMENT_RECORD_DETAILS', 'PROGRAMME VIEW PAYMENT RECORD DETAILS'), ('PROGRAMME_CREATE', 'PROGRAMME CREATE'), ('PROGRAMME_UPDATE', 'PROGRAMME UPDATE'), ('PROGRAMME_REMOVE', 'PROGRAMME REMOVE'), ('PROGRAMME_ACTIVATE', 'PROGRAMME ACTIVATE'), ('PROGRAMME_FINISH', 'PROGRAMME FINISH'), ('PROGRAMME_DUPLICATE', 'PROGRAMME DUPLICATE'), ('TARGETING_VIEW_LIST', 'TARGETING VIEW LIST'), ('TARGETING_VIEW_DETAILS', 'TARGETING VIEW DETAILS'), ('TARGETING_CREATE', 'TARGETING CREATE'), ('TARGETING_UPDATE', 'TARGETING UPDATE'), ('TARGETING_DUPLICATE', 'TARGETING DUPLICATE'), ('TARGETING_REMOVE', 'TARGETING REMOVE'), ('TARGETING_LOCK', 'TARGETING LOCK'), ('TARGETING_UNLOCK', 'TARGETING UNLOCK'), ('TARGETING_SEND', 'TARGETING SEND'), ('PAYMENT_VIEW_LIST_MANAGERIAL', 'PAYMENT VIEW LIST MANAGERIAL'), ('PAYMENT_VIEW_LIST_MANAGERIAL_RELEASED', 'PAYMENT VIEW LIST MANAGERIAL RELEASED'), ('PAYMENT_VERIFICATION_VIEW_LIST', 'PAYMENT VERIFICATION VIEW LIST'), ('PAYMENT_VERIFICATION_VIEW_DETAILS', 'PAYMENT VERIFICATION VIEW DETAILS'), ('PAYMENT_VERIFICATION_CREATE', 'PAYMENT VERIFICATION CREATE'), ('PAYMENT_VERIFICATION_UPDATE', 'PAYMENT VERIFICATION UPDATE'), ('PAYMENT_VERIFICATION_ACTIVATE', 'PAYMENT VERIFICATION ACTIVATE'), ('PAYMENT_VERIFICATION_DISCARD', 'PAYMENT VERIFICATION DISCARD'), ('PAYMENT_VERIFICATION_FINISH', 'PAYMENT VERIFICATION FINISH'), ('PAYMENT_VERIFICATION_EXPORT', 'PAYMENT VERIFICATION EXPORT'), ('PAYMENT_VERIFICATION_IMPORT', 'PAYMENT VERIFICATION IMPORT'), ('PAYMENT_VERIFICATION_VERIFY', 'PAYMENT VERIFICATION VERIFY'), ('PAYMENT_VERIFICATION_VIEW_PAYMENT_RECORD_DETAILS', 'PAYMENT VERIFICATION VIEW PAYMENT RECORD DETAILS'), ('PAYMENT_VERIFICATION_DELETE', 'PAYMENT VERIFICATION DELETE'), ('PAYMENT_VERIFICATION_INVALID', 'PAYMENT VERIFICATION INVALID'), ('PAYMENT_VERIFICATION_MARK_AS_FAILED', 'PAYMENT VERIFICATION MARK AS FAILED'), ('PM_VIEW_LIST', 'PM VIEW LIST'), ('PM_CREATE', 'PM CREATE'), ('PM_VIEW_DETAILS', 'PM VIEW DETAILS'), ('PM_IMPORT_XLSX_WITH_ENTITLEMENTS', 'PM IMPORT XLSX WITH ENTITLEMENTS'), ('PM_APPLY_RULE_ENGINE_FORMULA_WITH_ENTITLEMENTS', 'PM APPLY RULE ENGINE FORMULA WITH ENTITLEMENTS'), ('PM_SPLIT', 'PM SPLIT'), ('PM_LOCK_AND_UNLOCK', 'PM LOCK AND UNLOCK'), ('PM_LOCK_AND_UNLOCK_FSP', 'PM LOCK AND UNLOCK FSP'), ('PM_SEND_FOR_APPROVAL', 'PM SEND FOR APPROVAL'), ('PM_EXCLUDE_BENEFICIARIES_FROM_FOLLOW_UP_PP', 'PM EXCLUDE BENEFICIARIES FROM FOLLOW UP PP'), ('PM_ACCEPTANCE_PROCESS_APPROVE', 'PM ACCEPTANCE PROCESS APPROVE'), ('PM_ACCEPTANCE_PROCESS_AUTHORIZE', 'PM ACCEPTANCE PROCESS AUTHORIZE'), ('PM_ACCEPTANCE_PROCESS_FINANCIAL_REVIEW', 'PM ACCEPTANCE PROCESS FINANCIAL REVIEW'), ('PM_IMPORT_XLSX_WITH_RECONCILIATION', 'PM IMPORT XLSX WITH RECONCILIATION'), ('PM_EXPORT_XLSX_FOR_FSP', 'PM EXPORT XLSX FOR FSP'), ('PM_DOWNLOAD_XLSX_FOR_FSP', 'PM DOWNLOAD XLSX FOR FSP'), ('PM_MARK_PAYMENT_AS_FAILED', 'PM MARK PAYMENT AS FAILED'), ('PM_EXPORT_PDF_SUMMARY', 'PM EXPORT PDF SUMMARY'), ('PM_SEND_TO_PAYMENT_GATEWAY', 'PM SEND TO PAYMENT GATEWAY'), ('PM_VIEW_FSP_AUTH_CODE', 'PM VIEW FSP AUTH CODE'), ('PM_ADMIN_FINANCIAL_SERVICE_PROVIDER_UPDATE', 'PM ADMIN FINANCIAL SERVICE PROVIDER UPDATE'), ('PM_PROGRAMME_CYCLE_VIEW_LIST', 'PM PROGRAMME CYCLE VIEW LIST'), ('PM_PROGRAMME_CYCLE_VIEW_DETAILS', 'PM PROGRAMME CYCLE VIEW DETAILS'), ('PM_PROGRAMME_CYCLE_CREATE', 'PM PROGRAMME CYCLE CREATE'), ('PM_PROGRAMME_CYCLE_UPDATE', 'PM PROGRAMME CYCLE UPDATE'), ('PM_PROGRAMME_CYCLE_DELETE', 'PM PROGRAMME CYCLE DELETE'), ('USER_MANAGEMENT_VIEW_LIST', 'USER MANAGEMENT VIEW LIST'), ('DASHBOARD_VIEW_COUNTRY', 'DASHBOARD VIEW COUNTRY'), ('DASHBOARD_EXPORT', 'DASHBOARD EXPORT'), ('GRIEVANCES_VIEW_LIST_EXCLUDING_SENSITIVE', 'GRIEVANCES VIEW LIST EXCLUDING SENSITIVE'), ('GRIEVANCES_VIEW_LIST_EXCLUDING_SENSITIVE_AS_CREATOR', 'GRIEVANCES VIEW LIST EXCLUDING SENSITIVE AS CREATOR'), ('GRIEVANCES_VIEW_LIST_EXCLUDING_SENSITIVE_AS_OWNER', 'GRIEVANCES VIEW LIST EXCLUDING SENSITIVE AS OWNER'), ('GRIEVANCES_VIEW_LIST_SENSITIVE', 'GRIEVANCES VIEW LIST SENSITIVE'), ('GRIEVANCES_VIEW_LIST_SENSITIVE_AS_CREATOR', 'GRIEVANCES VIEW LIST SENSITIVE AS CREATOR'), ('GRIEVANCES_VIEW_LIST_SENSITIVE_AS_OWNER', 'GRIEVANCES VIEW LIST SENSITIVE AS OWNER'), ('GRIEVANCES_VIEW_DETAILS_EXCLUDING_SENSITIVE', 'GRIEVANCES VIEW DETAILS EXCLUDING SENSITIVE'), ('GRIEVANCES_VIEW_DETAILS_EXCLUDING_SENSITIVE_AS_CREATOR', 'GRIEVANCES VIEW DETAILS EXCLUDING SENSITIVE AS CREATOR'), ('GRIEVANCES_VIEW_DETAILS_EXCLUDING_SENSITIVE_AS_OWNER', 'GRIEVANCES VIEW DETAILS EXCLUDING SENSITIVE AS OWNER'), ('GRIEVANCES_VIEW_DETAILS_SENSITIVE', 'GRIEVANCES VIEW DETAILS SENSITIVE'), ('GRIEVANCES_VIEW_DETAILS_SENSITIVE_AS_CREATOR', 'GRIEVANCES VIEW DETAILS SENSITIVE AS CREATOR'), ('GRIEVANCES_VIEW_DETAILS_SENSITIVE_AS_OWNER', 'GRIEVANCES VIEW DETAILS SENSITIVE AS OWNER'), ('GRIEVANCES_VIEW_HOUSEHOLD_DETAILS', 'GRIEVANCES VIEW HOUSEHOLD DETAILS'), ('GRIEVANCES_VIEW_HOUSEHOLD_DETAILS_AS_CREATOR', 'GRIEVANCES VIEW HOUSEHOLD DETAILS AS CREATOR'), ('GRIEVANCES_VIEW_HOUSEHOLD_DETAILS_AS_OWNER', 'GRIEVANCES VIEW HOUSEHOLD DETAILS AS OWNER'), ('GRIEVANCES_VIEW_INDIVIDUALS_DETAILS', 'GRIEVANCES VIEW INDIVIDUALS DETAILS'), ('GRIEVANCES_VIEW_INDIVIDUALS_DETAILS_AS_CREATOR', 'GRIEVANCES VIEW INDIVIDUALS DETAILS AS CREATOR'), ('GRIEVANCES_VIEW_INDIVIDUALS_DETAILS_AS_OWNER', 'GRIEVANCES VIEW INDIVIDUALS DETAILS AS OWNER'), ('GRIEVANCES_CREATE', 'GRIEVANCES CREATE'), ('GRIEVANCES_UPDATE', 'GRIEVANCES UPDATE'), ('GRIEVANCES_UPDATE_AS_CREATOR', 'GRIEVANCES UPDATE AS CREATOR'), ('GRIEVANCES_UPDATE_AS_OWNER', 'GRIEVANCES UPDATE AS OWNER'), ('GRIEVANCES_UPDATE_REQUESTED_DATA_CHANGE', 'GRIEVANCES UPDATE REQUESTED DATA CHANGE'), ('GRIEVANCES_UPDATE_REQUESTED_DATA_CHANGE_AS_CREATOR', 'GRIEVANCES UPDATE REQUESTED DATA CHANGE AS CREATOR'), ('GRIEVANCES_UPDATE_REQUESTED_DATA_CHANGE_AS_OWNER', 'GRIEVANCES UPDATE REQUESTED DATA CHANGE AS OWNER'), ('GRIEVANCES_ADD_NOTE', 'GRIEVANCES ADD NOTE'), ('GRIEVANCES_ADD_NOTE_AS_CREATOR', 'GRIEVANCES ADD NOTE AS CREATOR'), ('GRIEVANCES_ADD_NOTE_AS_OWNER', 'GRIEVANCES ADD NOTE AS OWNER'), ('GRIEVANCES_SET_IN_PROGRESS', 'GRIEVANCES SET IN PROGRESS'), ('GRIEVANCES_SET_IN_PROGRESS_AS_CREATOR', 'GRIEVANCES SET IN PROGRESS AS CREATOR'), ('GRIEVANCES_SET_IN_PROGRESS_AS_OWNER', 'GRIEVANCES SET IN PROGRESS AS OWNER'), ('GRIEVANCES_SET_ON_HOLD', 'GRIEVANCES SET ON HOLD'), ('GRIEVANCES_SET_ON_HOLD_AS_CREATOR', 'GRIEVANCES SET ON HOLD AS CREATOR'), ('GRIEVANCES_SET_ON_HOLD_AS_OWNER', 'GRIEVANCES SET ON HOLD AS OWNER'), ('GRIEVANCES_SEND_FOR_APPROVAL', 'GRIEVANCES SEND FOR APPROVAL'), ('GRIEVANCES_SEND_FOR_APPROVAL_AS_CREATOR', 'GRIEVANCES SEND FOR APPROVAL AS CREATOR'), ('GRIEVANCES_SEND_FOR_APPROVAL_AS_OWNER', 'GRIEVANCES SEND FOR APPROVAL AS OWNER'), ('GRIEVANCES_SEND_BACK', 'GRIEVANCES SEND BACK'), ('GRIEVANCES_SEND_BACK_AS_CREATOR', 'GRIEVANCES SEND BACK AS CREATOR'), ('GRIEVANCES_SEND_BACK_AS_OWNER', 'GRIEVANCES SEND BACK AS OWNER'), ('GRIEVANCES_APPROVE_DATA_CHANGE', 'GRIEVANCES APPROVE DATA CHANGE'), ('GRIEVANCES_APPROVE_DATA_CHANGE_AS_CREATOR', 'GRIEVANCES APPROVE DATA CHANGE AS CREATOR'), ('GRIEVANCES_APPROVE_DATA_CHANGE_AS_OWNER', 'GRIEVANCES APPROVE DATA CHANGE AS OWNER'), ('GRIEVANCES_CLOSE_TICKET_EXCLUDING_FEEDBACK', 'GRIEVANCES CLOSE TICKET EXCLUDING FEEDBACK'), ('GRIEVANCES_CLOSE_TICKET_EXCLUDING_FEEDBACK_AS_CREATOR', 'GRIEVANCES CLOSE TICKET EXCLUDING FEEDBACK AS CREATOR'), ('GRIEVANCES_CLOSE_TICKET_EXCLUDING_FEEDBACK_AS_OWNER', 'GRIEVANCES CLOSE TICKET EXCLUDING FEEDBACK AS OWNER'), ('GRIEVANCES_CLOSE_TICKET_FEEDBACK', 'GRIEVANCES CLOSE TICKET FEEDBACK'), ('GRIEVANCES_CLOSE_TICKET_FEEDBACK_AS_CREATOR', 'GRIEVANCES CLOSE TICKET FEEDBACK AS CREATOR'), ('GRIEVANCES_CLOSE_TICKET_FEEDBACK_AS_OWNER', 'GRIEVANCES CLOSE TICKET FEEDBACK AS OWNER'), ('GRIEVANCES_APPROVE_FLAG_AND_DEDUPE', 'GRIEVANCES APPROVE FLAG AND DEDUPE'), ('GRIEVANCES_APPROVE_FLAG_AND_DEDUPE_AS_CREATOR', 'GRIEVANCES APPROVE FLAG AND DEDUPE AS CREATOR'), ('GRIEVANCES_APPROVE_FLAG_AND_DEDUPE_AS_OWNER', 'GRIEVANCES APPROVE FLAG AND DEDUPE AS OWNER'), ('GRIEVANCES_APPROVE_PAYMENT_VERIFICATION', 'GRIEVANCES APPROVE PAYMENT VERIFICATION'), ('GRIEVANCES_APPROVE_PAYMENT_VERIFICATION_AS_CREATOR', 'GRIEVANCES APPROVE PAYMENT VERIFICATION AS CREATOR'), ('GRIEVANCES_APPROVE_PAYMENT_VERIFICATION_AS_OWNER', 'GRIEVANCES APPROVE PAYMENT VERIFICATION AS OWNER'), ('GRIEVANCE_ASSIGN', 'GRIEVANCE ASSIGN'), ('GRIEVANCE_DOCUMENTS_UPLOAD', 'GRIEVANCE DOCUMENTS UPLOAD'), ('GRIEVANCES_CROSS_AREA_FILTER', 'GRIEVANCES CROSS AREA FILTER'), ('GRIEVANCES_FEEDBACK_VIEW_CREATE', 'GRIEVANCES FEEDBACK VIEW CREATE'), ('GRIEVANCES_FEEDBACK_VIEW_LIST', 'GRIEVANCES FEEDBACK VIEW LIST'), ('GRIEVANCES_FEEDBACK_VIEW_DETAILS', 'GRIEVANCES FEEDBACK VIEW DETAILS'), ('GRIEVANCES_FEEDBACK_VIEW_UPDATE', 'GRIEVANCES FEEDBACK VIEW UPDATE'), ('GRIEVANCES_FEEDBACK_MESSAGE_VIEW_CREATE', 'GRIEVANCES FEEDBACK MESSAGE VIEW CREATE'), ('REPORTING_EXPORT', 'REPORTING EXPORT'), ('PDU_VIEW_LIST_AND_DETAILS', 'PDU VIEW LIST AND DETAILS'), ('PDU_TEMPLATE_CREATE', 'PDU TEMPLATE CREATE'), ('PDU_TEMPLATE_DOWNLOAD', 'PDU TEMPLATE DOWNLOAD'), ('PDU_UPLOAD', 'PDU UPLOAD'), ('ALL_VIEW_PII_DATA_ON_LISTS', 'ALL VIEW PII DATA ON LISTS'), ('ACTIVITY_LOG_VIEW', 'ACTIVITY LOG VIEW'), ('ACTIVITY_LOG_DOWNLOAD', 'ACTIVITY LOG DOWNLOAD'), ('UPLOAD_STORAGE_FILE', 'UPLOAD STORAGE FILE'), ('DOWNLOAD_STORAGE_FILE', 'DOWNLOAD STORAGE FILE'), ('ACCOUNTABILITY_COMMUNICATION_MESSAGE_VIEW_LIST', 'ACCOUNTABILITY COMMUNICATION MESSAGE VIEW LIST'), ('ACCOUNTABILITY_COMMUNICATION_MESSAGE_VIEW_DETAILS', 'ACCOUNTABILITY COMMUNICATION MESSAGE VIEW DETAILS'), ('ACCOUNTABILITY_COMMUNICATION_MESSAGE_VIEW_CREATE', 'ACCOUNTABILITY COMMUNICATION MESSAGE VIEW CREATE'), ('ACCOUNTABILITY_COMMUNICATION_MESSAGE_VIEW_DETAILS_AS_CREATOR', 'ACCOUNTABILITY COMMUNICATION MESSAGE VIEW DETAILS AS CREATOR'), ('ACCOUNTABILITY_SURVEY_VIEW_CREATE', 'ACCOUNTABILITY SURVEY VIEW CREATE'), ('ACCOUNTABILITY_SURVEY_VIEW_LIST', 'ACCOUNTABILITY SURVEY VIEW LIST'), ('ACCOUNTABILITY_SURVEY_VIEW_DETAILS', 'ACCOUNTABILITY SURVEY VIEW DETAILS'), ('GEO_VIEW_LIST', 'GEO VIEW LIST'), ('CAN_ADD_BUSINESS_AREA_TO_PARTNER', 'CAN ADD BUSINESS AREA TO PARTNER')], max_length=255), blank=True, null=True, size=None), -======= - field=hct_mis_api.apps.account.fields.ChoiceArrayField(base_field=models.CharField(choices=[('RDI_VIEW_LIST', 'RDI VIEW LIST'), ('RDI_VIEW_DETAILS', 'RDI VIEW DETAILS'), ('RDI_IMPORT_DATA', 'RDI IMPORT DATA'), ('RDI_RERUN_DEDUPE', 'RDI RERUN DEDUPE'), ('RDI_MERGE_IMPORT', 'RDI MERGE IMPORT'), ('RDI_REFUSE_IMPORT', 'RDI REFUSE IMPORT'), ('POPULATION_VIEW_HOUSEHOLDS_LIST', 'POPULATION VIEW HOUSEHOLDS LIST'), ('POPULATION_VIEW_HOUSEHOLDS_DETAILS', 'POPULATION VIEW HOUSEHOLDS DETAILS'), ('POPULATION_VIEW_INDIVIDUALS_LIST', 'POPULATION VIEW INDIVIDUALS LIST'), ('POPULATION_VIEW_INDIVIDUALS_DETAILS', 'POPULATION VIEW INDIVIDUALS DETAILS'), ('PROGRAMME_VIEW_LIST_AND_DETAILS', 'PROGRAMME VIEW LIST AND DETAILS'), ('PROGRAMME_MANAGEMENT_VIEW', 'PROGRAMME MANAGEMENT VIEW'), ('PROGRAMME_VIEW_PAYMENT_RECORD_DETAILS', 'PROGRAMME VIEW PAYMENT RECORD DETAILS'), ('PROGRAMME_CREATE', 'PROGRAMME CREATE'), ('PROGRAMME_UPDATE', 'PROGRAMME UPDATE'), ('PROGRAMME_REMOVE', 'PROGRAMME REMOVE'), ('PROGRAMME_ACTIVATE', 'PROGRAMME ACTIVATE'), ('PROGRAMME_FINISH', 'PROGRAMME FINISH'), ('PROGRAMME_DUPLICATE', 'PROGRAMME DUPLICATE'), ('TARGETING_VIEW_LIST', 'TARGETING VIEW LIST'), ('TARGETING_VIEW_DETAILS', 'TARGETING VIEW DETAILS'), ('TARGETING_CREATE', 'TARGETING CREATE'), ('TARGETING_UPDATE', 'TARGETING UPDATE'), ('TARGETING_DUPLICATE', 'TARGETING DUPLICATE'), ('TARGETING_REMOVE', 'TARGETING REMOVE'), ('TARGETING_LOCK', 'TARGETING LOCK'), ('TARGETING_UNLOCK', 'TARGETING UNLOCK'), ('TARGETING_SEND', 'TARGETING SEND'), ('PAYMENT_VIEW_LIST_MANAGERIAL', 'PAYMENT VIEW LIST MANAGERIAL'), ('PAYMENT_VIEW_LIST_MANAGERIAL_RELEASED', 'PAYMENT VIEW LIST MANAGERIAL RELEASED'), ('PAYMENT_VERIFICATION_VIEW_LIST', 'PAYMENT VERIFICATION VIEW LIST'), ('PAYMENT_VERIFICATION_VIEW_DETAILS', 'PAYMENT VERIFICATION VIEW DETAILS'), ('PAYMENT_VERIFICATION_CREATE', 'PAYMENT VERIFICATION CREATE'), ('PAYMENT_VERIFICATION_UPDATE', 'PAYMENT VERIFICATION UPDATE'), ('PAYMENT_VERIFICATION_ACTIVATE', 'PAYMENT VERIFICATION ACTIVATE'), ('PAYMENT_VERIFICATION_DISCARD', 'PAYMENT VERIFICATION DISCARD'), ('PAYMENT_VERIFICATION_FINISH', 'PAYMENT VERIFICATION FINISH'), ('PAYMENT_VERIFICATION_EXPORT', 'PAYMENT VERIFICATION EXPORT'), ('PAYMENT_VERIFICATION_IMPORT', 'PAYMENT VERIFICATION IMPORT'), ('PAYMENT_VERIFICATION_VERIFY', 'PAYMENT VERIFICATION VERIFY'), ('PAYMENT_VERIFICATION_VIEW_PAYMENT_RECORD_DETAILS', 'PAYMENT VERIFICATION VIEW PAYMENT RECORD DETAILS'), ('PAYMENT_VERIFICATION_DELETE', 'PAYMENT VERIFICATION DELETE'), ('PAYMENT_VERIFICATION_INVALID', 'PAYMENT VERIFICATION INVALID'), ('PAYMENT_VERIFICATION_MARK_AS_FAILED', 'PAYMENT VERIFICATION MARK AS FAILED'), ('PM_VIEW_LIST', 'PM VIEW LIST'), ('PM_CREATE', 'PM CREATE'), ('PM_VIEW_DETAILS', 'PM VIEW DETAILS'), ('PM_IMPORT_XLSX_WITH_ENTITLEMENTS', 'PM IMPORT XLSX WITH ENTITLEMENTS'), ('PM_APPLY_RULE_ENGINE_FORMULA_WITH_ENTITLEMENTS', 'PM APPLY RULE ENGINE FORMULA WITH ENTITLEMENTS'), ('PM_SPLIT', 'PM SPLIT'), ('PM_LOCK_AND_UNLOCK', 'PM LOCK AND UNLOCK'), ('PM_LOCK_AND_UNLOCK_FSP', 'PM LOCK AND UNLOCK FSP'), ('PM_SEND_FOR_APPROVAL', 'PM SEND FOR APPROVAL'), ('PM_EXCLUDE_BENEFICIARIES_FROM_FOLLOW_UP_PP', 'PM EXCLUDE BENEFICIARIES FROM FOLLOW UP PP'), ('PM_ACCEPTANCE_PROCESS_APPROVE', 'PM ACCEPTANCE PROCESS APPROVE'), ('PM_ACCEPTANCE_PROCESS_AUTHORIZE', 'PM ACCEPTANCE PROCESS AUTHORIZE'), ('PM_ACCEPTANCE_PROCESS_FINANCIAL_REVIEW', 'PM ACCEPTANCE PROCESS FINANCIAL REVIEW'), ('PM_IMPORT_XLSX_WITH_RECONCILIATION', 'PM IMPORT XLSX WITH RECONCILIATION'), ('PM_EXPORT_XLSX_FOR_FSP', 'PM EXPORT XLSX FOR FSP'), ('PM_DOWNLOAD_XLSX_FOR_FSP', 'PM DOWNLOAD XLSX FOR FSP'), ('PM_MARK_PAYMENT_AS_FAILED', 'PM MARK PAYMENT AS FAILED'), ('PM_EXPORT_PDF_SUMMARY', 'PM EXPORT PDF SUMMARY'), ('PM_SEND_TO_PAYMENT_GATEWAY', 'PM SEND TO PAYMENT GATEWAY'), ('PM_VIEW_FSP_AUTH_CODE', 'PM VIEW FSP AUTH CODE'), ('PM_ADMIN_FINANCIAL_SERVICE_PROVIDER_UPDATE', 'PM ADMIN FINANCIAL SERVICE PROVIDER UPDATE'), ('USER_MANAGEMENT_VIEW_LIST', 'USER MANAGEMENT VIEW LIST'), ('DASHBOARD_VIEW_COUNTRY', 'DASHBOARD VIEW COUNTRY'), ('DASHBOARD_EXPORT', 'DASHBOARD EXPORT'), ('GRIEVANCES_VIEW_LIST_EXCLUDING_SENSITIVE', 'GRIEVANCES VIEW LIST EXCLUDING SENSITIVE'), ('GRIEVANCES_VIEW_LIST_EXCLUDING_SENSITIVE_AS_CREATOR', 'GRIEVANCES VIEW LIST EXCLUDING SENSITIVE AS CREATOR'), ('GRIEVANCES_VIEW_LIST_EXCLUDING_SENSITIVE_AS_OWNER', 'GRIEVANCES VIEW LIST EXCLUDING SENSITIVE AS OWNER'), ('GRIEVANCES_VIEW_LIST_SENSITIVE', 'GRIEVANCES VIEW LIST SENSITIVE'), ('GRIEVANCES_VIEW_LIST_SENSITIVE_AS_CREATOR', 'GRIEVANCES VIEW LIST SENSITIVE AS CREATOR'), ('GRIEVANCES_VIEW_LIST_SENSITIVE_AS_OWNER', 'GRIEVANCES VIEW LIST SENSITIVE AS OWNER'), ('GRIEVANCES_VIEW_DETAILS_EXCLUDING_SENSITIVE', 'GRIEVANCES VIEW DETAILS EXCLUDING SENSITIVE'), ('GRIEVANCES_VIEW_DETAILS_EXCLUDING_SENSITIVE_AS_CREATOR', 'GRIEVANCES VIEW DETAILS EXCLUDING SENSITIVE AS CREATOR'), ('GRIEVANCES_VIEW_DETAILS_EXCLUDING_SENSITIVE_AS_OWNER', 'GRIEVANCES VIEW DETAILS EXCLUDING SENSITIVE AS OWNER'), ('GRIEVANCES_VIEW_DETAILS_SENSITIVE', 'GRIEVANCES VIEW DETAILS SENSITIVE'), ('GRIEVANCES_VIEW_DETAILS_SENSITIVE_AS_CREATOR', 'GRIEVANCES VIEW DETAILS SENSITIVE AS CREATOR'), ('GRIEVANCES_VIEW_DETAILS_SENSITIVE_AS_OWNER', 'GRIEVANCES VIEW DETAILS SENSITIVE AS OWNER'), ('GRIEVANCES_VIEW_HOUSEHOLD_DETAILS', 'GRIEVANCES VIEW HOUSEHOLD DETAILS'), ('GRIEVANCES_VIEW_HOUSEHOLD_DETAILS_AS_CREATOR', 'GRIEVANCES VIEW HOUSEHOLD DETAILS AS CREATOR'), ('GRIEVANCES_VIEW_HOUSEHOLD_DETAILS_AS_OWNER', 'GRIEVANCES VIEW HOUSEHOLD DETAILS AS OWNER'), ('GRIEVANCES_VIEW_INDIVIDUALS_DETAILS', 'GRIEVANCES VIEW INDIVIDUALS DETAILS'), ('GRIEVANCES_VIEW_INDIVIDUALS_DETAILS_AS_CREATOR', 'GRIEVANCES VIEW INDIVIDUALS DETAILS AS CREATOR'), ('GRIEVANCES_VIEW_INDIVIDUALS_DETAILS_AS_OWNER', 'GRIEVANCES VIEW INDIVIDUALS DETAILS AS OWNER'), ('GRIEVANCES_CREATE', 'GRIEVANCES CREATE'), ('GRIEVANCES_UPDATE', 'GRIEVANCES UPDATE'), ('GRIEVANCES_UPDATE_AS_CREATOR', 'GRIEVANCES UPDATE AS CREATOR'), ('GRIEVANCES_UPDATE_AS_OWNER', 'GRIEVANCES UPDATE AS OWNER'), ('GRIEVANCES_UPDATE_REQUESTED_DATA_CHANGE', 'GRIEVANCES UPDATE REQUESTED DATA CHANGE'), ('GRIEVANCES_UPDATE_REQUESTED_DATA_CHANGE_AS_CREATOR', 'GRIEVANCES UPDATE REQUESTED DATA CHANGE AS CREATOR'), ('GRIEVANCES_UPDATE_REQUESTED_DATA_CHANGE_AS_OWNER', 'GRIEVANCES UPDATE REQUESTED DATA CHANGE AS OWNER'), ('GRIEVANCES_ADD_NOTE', 'GRIEVANCES ADD NOTE'), ('GRIEVANCES_ADD_NOTE_AS_CREATOR', 'GRIEVANCES ADD NOTE AS CREATOR'), ('GRIEVANCES_ADD_NOTE_AS_OWNER', 'GRIEVANCES ADD NOTE AS OWNER'), ('GRIEVANCES_SET_IN_PROGRESS', 'GRIEVANCES SET IN PROGRESS'), ('GRIEVANCES_SET_IN_PROGRESS_AS_CREATOR', 'GRIEVANCES SET IN PROGRESS AS CREATOR'), ('GRIEVANCES_SET_IN_PROGRESS_AS_OWNER', 'GRIEVANCES SET IN PROGRESS AS OWNER'), ('GRIEVANCES_SET_ON_HOLD', 'GRIEVANCES SET ON HOLD'), ('GRIEVANCES_SET_ON_HOLD_AS_CREATOR', 'GRIEVANCES SET ON HOLD AS CREATOR'), ('GRIEVANCES_SET_ON_HOLD_AS_OWNER', 'GRIEVANCES SET ON HOLD AS OWNER'), ('GRIEVANCES_SEND_FOR_APPROVAL', 'GRIEVANCES SEND FOR APPROVAL'), ('GRIEVANCES_SEND_FOR_APPROVAL_AS_CREATOR', 'GRIEVANCES SEND FOR APPROVAL AS CREATOR'), ('GRIEVANCES_SEND_FOR_APPROVAL_AS_OWNER', 'GRIEVANCES SEND FOR APPROVAL AS OWNER'), ('GRIEVANCES_SEND_BACK', 'GRIEVANCES SEND BACK'), ('GRIEVANCES_SEND_BACK_AS_CREATOR', 'GRIEVANCES SEND BACK AS CREATOR'), ('GRIEVANCES_SEND_BACK_AS_OWNER', 'GRIEVANCES SEND BACK AS OWNER'), ('GRIEVANCES_APPROVE_DATA_CHANGE', 'GRIEVANCES APPROVE DATA CHANGE'), ('GRIEVANCES_APPROVE_DATA_CHANGE_AS_CREATOR', 'GRIEVANCES APPROVE DATA CHANGE AS CREATOR'), ('GRIEVANCES_APPROVE_DATA_CHANGE_AS_OWNER', 'GRIEVANCES APPROVE DATA CHANGE AS OWNER'), ('GRIEVANCES_CLOSE_TICKET_EXCLUDING_FEEDBACK', 'GRIEVANCES CLOSE TICKET EXCLUDING FEEDBACK'), ('GRIEVANCES_CLOSE_TICKET_EXCLUDING_FEEDBACK_AS_CREATOR', 'GRIEVANCES CLOSE TICKET EXCLUDING FEEDBACK AS CREATOR'), ('GRIEVANCES_CLOSE_TICKET_EXCLUDING_FEEDBACK_AS_OWNER', 'GRIEVANCES CLOSE TICKET EXCLUDING FEEDBACK AS OWNER'), ('GRIEVANCES_CLOSE_TICKET_FEEDBACK', 'GRIEVANCES CLOSE TICKET FEEDBACK'), ('GRIEVANCES_CLOSE_TICKET_FEEDBACK_AS_CREATOR', 'GRIEVANCES CLOSE TICKET FEEDBACK AS CREATOR'), ('GRIEVANCES_CLOSE_TICKET_FEEDBACK_AS_OWNER', 'GRIEVANCES CLOSE TICKET FEEDBACK AS OWNER'), ('GRIEVANCES_APPROVE_FLAG_AND_DEDUPE', 'GRIEVANCES APPROVE FLAG AND DEDUPE'), ('GRIEVANCES_APPROVE_FLAG_AND_DEDUPE_AS_CREATOR', 'GRIEVANCES APPROVE FLAG AND DEDUPE AS CREATOR'), ('GRIEVANCES_APPROVE_FLAG_AND_DEDUPE_AS_OWNER', 'GRIEVANCES APPROVE FLAG AND DEDUPE AS OWNER'), ('GRIEVANCES_APPROVE_PAYMENT_VERIFICATION', 'GRIEVANCES APPROVE PAYMENT VERIFICATION'), ('GRIEVANCES_APPROVE_PAYMENT_VERIFICATION_AS_CREATOR', 'GRIEVANCES APPROVE PAYMENT VERIFICATION AS CREATOR'), ('GRIEVANCES_APPROVE_PAYMENT_VERIFICATION_AS_OWNER', 'GRIEVANCES APPROVE PAYMENT VERIFICATION AS OWNER'), ('GRIEVANCE_ASSIGN', 'GRIEVANCE ASSIGN'), ('GRIEVANCE_DOCUMENTS_UPLOAD', 'GRIEVANCE DOCUMENTS UPLOAD'), ('GRIEVANCES_CROSS_AREA_FILTER', 'GRIEVANCES CROSS AREA FILTER'), ('GRIEVANCES_FEEDBACK_VIEW_CREATE', 'GRIEVANCES FEEDBACK VIEW CREATE'), ('GRIEVANCES_FEEDBACK_VIEW_LIST', 'GRIEVANCES FEEDBACK VIEW LIST'), ('GRIEVANCES_FEEDBACK_VIEW_DETAILS', 'GRIEVANCES FEEDBACK VIEW DETAILS'), ('GRIEVANCES_FEEDBACK_VIEW_UPDATE', 'GRIEVANCES FEEDBACK VIEW UPDATE'), ('GRIEVANCES_FEEDBACK_MESSAGE_VIEW_CREATE', 'GRIEVANCES FEEDBACK MESSAGE VIEW CREATE'), ('REPORTING_EXPORT', 'REPORTING EXPORT'), ('PDU_VIEW_LIST_AND_DETAILS', 'PDU VIEW LIST AND DETAILS'), ('PDU_TEMPLATE_CREATE', 'PDU TEMPLATE CREATE'), ('PDU_TEMPLATE_DOWNLOAD', 'PDU TEMPLATE DOWNLOAD'), ('PDU_UPLOAD', 'PDU UPLOAD'), ('ALL_VIEW_PII_DATA_ON_LISTS', 'ALL VIEW PII DATA ON LISTS'), ('ACTIVITY_LOG_VIEW', 'ACTIVITY LOG VIEW'), ('ACTIVITY_LOG_DOWNLOAD', 'ACTIVITY LOG DOWNLOAD'), ('UPLOAD_STORAGE_FILE', 'UPLOAD STORAGE FILE'), ('DOWNLOAD_STORAGE_FILE', 'DOWNLOAD STORAGE FILE'), ('ACCOUNTABILITY_COMMUNICATION_MESSAGE_VIEW_LIST', 'ACCOUNTABILITY COMMUNICATION MESSAGE VIEW LIST'), ('ACCOUNTABILITY_COMMUNICATION_MESSAGE_VIEW_DETAILS', 'ACCOUNTABILITY COMMUNICATION MESSAGE VIEW DETAILS'), ('ACCOUNTABILITY_COMMUNICATION_MESSAGE_VIEW_CREATE', 'ACCOUNTABILITY COMMUNICATION MESSAGE VIEW CREATE'), ('ACCOUNTABILITY_COMMUNICATION_MESSAGE_VIEW_DETAILS_AS_CREATOR', 'ACCOUNTABILITY COMMUNICATION MESSAGE VIEW DETAILS AS CREATOR'), ('ACCOUNTABILITY_SURVEY_VIEW_CREATE', 'ACCOUNTABILITY SURVEY VIEW CREATE'), ('ACCOUNTABILITY_SURVEY_VIEW_LIST', 'ACCOUNTABILITY SURVEY VIEW LIST'), ('ACCOUNTABILITY_SURVEY_VIEW_DETAILS', 'ACCOUNTABILITY SURVEY VIEW DETAILS'), ('GEO_VIEW_LIST', 'GEO VIEW LIST'), ('CAN_ADD_BUSINESS_AREA_TO_PARTNER', 'CAN ADD BUSINESS AREA TO PARTNER')], max_length=255), blank=True, null=True, size=None), ->>>>>>> Stashed changes ), - ] + ] \ No newline at end of file diff --git a/backend/hct_mis_api/apps/account/migrations/0074_migration.py b/backend/hct_mis_api/apps/account/migrations/0074_migration.py new file mode 100644 index 0000000000..fda96075c1 --- /dev/null +++ b/backend/hct_mis_api/apps/account/migrations/0074_migration.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.25 on 2024-08-19 19:06 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0073_migration'), + ] + + operations = [ + migrations.AlterModelOptions( + name='user', + options={'permissions': (('can_load_from_ad', 'Can load users from ActiveDirectory'), ('can_sync_with_ad', 'Can synchronise user with ActiveDirectory'), ('can_create_kobo_user', 'Can create users in Kobo'), ('can_import_from_kobo', 'Can import and sync users from Kobo'), ('can_upload_to_kobo', 'Can upload CSV file to Kobo'), ('can_debug', 'Can access debug informations'), ('can_inspect', 'Can inspect objects'), ('quick_links', 'Can see quick links in admin'), ('restrict_help_desk', 'Limit fields to be editable for help desk'), ('can_reindex_programs', 'Can reindex programs'))}, + ), + ] From ad2e713bbd58e31aa9393b54bf7dc4fa91154d60 Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Tue, 20 Aug 2024 01:34:11 +0200 Subject: [PATCH 029/118] fix found issues --- frontend/data/schema.graphql | 4 +-- frontend/src/__generated__/graphql.tsx | 33 +++++++++++------ .../fragments/TargetPopulationFragments.ts | 13 +++++++ .../src/components/targeting/SubField.tsx | 35 ++++++++++++------- .../TargetingCriteriaDisplay/Criteria.tsx | 32 ++++++++--------- .../TargetingCriteriaDisplay.tsx | 3 +- frontend/src/utils/en.json | 5 +-- frontend/src/utils/targetingUtils.ts | 6 +++- 8 files changed, 86 insertions(+), 45 deletions(-) diff --git a/frontend/data/schema.graphql b/frontend/data/schema.graphql index f402da84d1..2332ecda50 100644 --- a/frontend/data/schema.graphql +++ b/frontend/data/schema.graphql @@ -1342,6 +1342,7 @@ type FinancialServiceProviderXlsxTemplateNode implements Node { name: String! columns: [String] coreFields: [String!]! + flexFields: [String!]! financialServiceProviders(offset: Int, before: String, after: String, first: Int, last: Int): FinancialServiceProviderNodeConnection! } @@ -3654,7 +3655,7 @@ enum PeriodicFieldDataSubtype { DATE DECIMAL STRING - BOOLEAN + BOOL } type PeriodicFieldNode implements Node { @@ -4719,7 +4720,6 @@ type TargetingCriteriaRuleFilterNode { flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification! fieldName: String! arguments: [Arg] - roundNumber: Int fieldAttribute: FieldAttributeNode } diff --git a/frontend/src/__generated__/graphql.tsx b/frontend/src/__generated__/graphql.tsx index 96075594be..861511973a 100644 --- a/frontend/src/__generated__/graphql.tsx +++ b/frontend/src/__generated__/graphql.tsx @@ -1981,6 +1981,7 @@ export type FinancialServiceProviderXlsxTemplateNode = Node & { createdAt: Scalars['DateTime']['output']; createdBy?: Maybe; financialServiceProviders: FinancialServiceProviderNodeConnection; + flexFields: Array; id: Scalars['ID']['output']; name: Scalars['String']['output']; updatedAt: Scalars['DateTime']['output']; @@ -5572,7 +5573,7 @@ export type PeriodicFieldDataNode = { }; export enum PeriodicFieldDataSubtype { - Boolean = 'BOOLEAN', + Bool = 'BOOL', Date = 'DATE', Decimal = 'DECIMAL', String = 'STRING' @@ -8206,7 +8207,6 @@ export type TargetingCriteriaRuleFilterNode = { fieldName: Scalars['String']['output']; flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification; id: Scalars['UUID']['output']; - roundNumber?: Maybe; targetingCriteriaRule: TargetingCriteriaRuleNode; updatedAt: Scalars['DateTime']['output']; }; @@ -9516,7 +9516,7 @@ export type ImportedIndividualDetailedFragment = { __typename?: 'ImportedIndivid export type TargetPopulationMinimalFragment = { __typename: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, createdAt: any, updatedAt: any, totalHouseholdsCount?: number | null, totalHouseholdsCountWithValidPhoneNo?: number | null, totalIndividualsCount?: number | null, program?: { __typename: 'ProgramNode', id: string, name: string } | null, createdBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null }; -export type TargetPopulationDetailedFragment = { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null } | null }; +export type TargetPopulationDetailedFragment = { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, roundNumber?: number | null, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null } | null }; export type CreateFeedbackTicketMutationVariables = Exact<{ input: CreateFeedbackInput; @@ -10108,35 +10108,35 @@ export type FinalizeTpMutationVariables = Exact<{ }>; -export type FinalizeTpMutation = { __typename?: 'Mutations', finalizeTargetPopulation?: { __typename?: 'FinalizeTargetPopulationMutation', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null } | null } | null } | null }; +export type FinalizeTpMutation = { __typename?: 'Mutations', finalizeTargetPopulation?: { __typename?: 'FinalizeTargetPopulationMutation', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, roundNumber?: number | null, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null } | null } | null } | null }; export type LockTpMutationVariables = Exact<{ id: Scalars['ID']['input']; }>; -export type LockTpMutation = { __typename?: 'Mutations', lockTargetPopulation?: { __typename?: 'LockTargetPopulationMutation', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null } | null } | null } | null }; +export type LockTpMutation = { __typename?: 'Mutations', lockTargetPopulation?: { __typename?: 'LockTargetPopulationMutation', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, roundNumber?: number | null, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null } | null } | null } | null }; export type RebuildTpMutationVariables = Exact<{ id: Scalars['ID']['input']; }>; -export type RebuildTpMutation = { __typename?: 'Mutations', targetPopulationRebuild?: { __typename?: 'RebuildTargetPopulationMutation', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null } | null } | null } | null }; +export type RebuildTpMutation = { __typename?: 'Mutations', targetPopulationRebuild?: { __typename?: 'RebuildTargetPopulationMutation', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, roundNumber?: number | null, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null } | null } | null } | null }; export type SetSteficonRuleOnTargetPopulationMutationVariables = Exact<{ input: SetSteficonRuleOnTargetPopulationMutationInput; }>; -export type SetSteficonRuleOnTargetPopulationMutation = { __typename?: 'Mutations', setSteficonRuleOnTargetPopulation?: { __typename?: 'SetSteficonRuleOnTargetPopulationMutationPayload', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null } | null } | null } | null }; +export type SetSteficonRuleOnTargetPopulationMutation = { __typename?: 'Mutations', setSteficonRuleOnTargetPopulation?: { __typename?: 'SetSteficonRuleOnTargetPopulationMutationPayload', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, roundNumber?: number | null, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null } | null } | null } | null }; export type UnlockTpMutationVariables = Exact<{ id: Scalars['ID']['input']; }>; -export type UnlockTpMutation = { __typename?: 'Mutations', unlockTargetPopulation?: { __typename?: 'UnlockTargetPopulationMutation', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null } | null } | null } | null }; +export type UnlockTpMutation = { __typename?: 'Mutations', unlockTargetPopulation?: { __typename?: 'UnlockTargetPopulationMutation', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, roundNumber?: number | null, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null } | null } | null } | null }; export type UpdateTpMutationVariables = Exact<{ input: UpdateTargetPopulationInput; @@ -11331,7 +11331,7 @@ export type TargetPopulationQueryVariables = Exact<{ }>; -export type TargetPopulationQuery = { __typename?: 'Query', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null } | null } | null }; +export type TargetPopulationQuery = { __typename?: 'Query', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, roundNumber?: number | null, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null } | null } | null }; export type TargetPopulationHouseholdsQueryVariables = Exact<{ targetPopulation: Scalars['ID']['input']; @@ -12638,6 +12638,7 @@ export const TargetPopulationDetailedFragmentDoc = gql` id fieldName flexFieldClassification + roundNumber arguments comparisonMethod fieldAttribute { @@ -12650,6 +12651,12 @@ export const TargetPopulationDetailedFragmentDoc = gql` value labelEn } + pduData { + id + subtype + numberOfRounds + roundsNames + } } } } @@ -12670,6 +12677,12 @@ export const TargetPopulationDetailedFragmentDoc = gql` value labelEn } + pduData { + id + subtype + numberOfRounds + roundsNames + } } } } @@ -26229,6 +26242,7 @@ export type FinancialServiceProviderXlsxTemplateNodeResolvers; createdBy?: Resolver, ParentType, ContextType>; financialServiceProviders?: Resolver>; + flexFields?: Resolver, ParentType, ContextType>; id?: Resolver; name?: Resolver; updatedAt?: Resolver; @@ -28747,7 +28761,6 @@ export type TargetingCriteriaRuleFilterNodeResolvers; flexFieldClassification?: Resolver; id?: Resolver; - roundNumber?: Resolver, ParentType, ContextType>; targetingCriteriaRule?: Resolver; updatedAt?: Resolver; __isTypeOf?: IsTypeOfResolverFn; diff --git a/frontend/src/apollo/fragments/TargetPopulationFragments.ts b/frontend/src/apollo/fragments/TargetPopulationFragments.ts index 2141580e09..318ef0d089 100644 --- a/frontend/src/apollo/fragments/TargetPopulationFragments.ts +++ b/frontend/src/apollo/fragments/TargetPopulationFragments.ts @@ -95,6 +95,7 @@ export const targetPopulationDetailed = gql` id fieldName flexFieldClassification + roundNumber arguments comparisonMethod fieldAttribute { @@ -107,6 +108,12 @@ export const targetPopulationDetailed = gql` value labelEn } + pduData { + id + subtype + numberOfRounds + roundsNames + } } } } @@ -127,6 +134,12 @@ export const targetPopulationDetailed = gql` value labelEn } + pduData { + id + subtype + numberOfRounds + roundsNames + } } } } diff --git a/frontend/src/components/targeting/SubField.tsx b/frontend/src/components/targeting/SubField.tsx index 0b1007802b..2f67976100 100644 --- a/frontend/src/components/targeting/SubField.tsx +++ b/frontend/src/components/targeting/SubField.tsx @@ -28,7 +28,6 @@ export function SubField({ choicesDict, }): React.ReactElement { const { t } = useTranslation(); - const fieldComponent =

{field.fieldAttribute.type}

; const { values, setFieldValue } = useFormikContext(); if (blockIndex === undefined) { const match = baseName.match(/block\[(\d+)\]/); @@ -51,7 +50,12 @@ export function SubField({ } }, [isNullSelected, setFieldValue, baseName]); + if (!field) { + return null; + } + const renderFieldByType = (type) => { + console.log('field', type); switch (type) { case 'DECIMAL': return ( @@ -240,16 +244,20 @@ export function SubField({ ({ - value: n + 1, - name: `${n + 1}`, - }), - ) + field.pduData?.numberOfRounds || + field.fieldAttribute?.pduData?.numberOfRounds + ? [ + ...Array( + field.pduData?.numberOfRounds || + field.fieldAttribute?.pduData?.numberOfRounds, + ).keys(), + ].map((n) => ({ + value: n + 1, + name: `${n + 1}`, + })) : [] } label="Round" @@ -258,20 +266,23 @@ export function SubField({ - {renderFieldByType(field.pduData.subtype)} + {renderFieldByType( + field.pduData?.subtype || + field.fieldAttribute?.pduData?.subtype, + )} ); default: - return fieldComponent; + return <>; } }; diff --git a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx index 82712061aa..4c01089fb9 100644 --- a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx +++ b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx @@ -84,7 +84,7 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { : argument; }; - const displayValueOrDash = (value) => (value ? value : '-'); + const displayValueOrEmpty = (value) => (value ? value : 'Empty'); const { t } = useTranslation(); let fieldElement; @@ -94,7 +94,7 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { fieldElement = (

{field.fieldAttribute.labelEn || field.fieldName}:{' '} - {displayValueOrDash(field.arguments?.[0])} + {displayValueOrEmpty(field.arguments?.[0])}

); break; @@ -103,8 +103,8 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => {

{field.fieldAttribute.labelEn || field.fieldName}:{' '} - {displayValueOrDash(field.arguments?.[0])} -{' '} - {displayValueOrDash(field.arguments?.[1])} + {displayValueOrEmpty(field.arguments?.[0])} -{' '} + {displayValueOrEmpty(field.arguments?.[1])}

); @@ -117,7 +117,7 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { {field.arguments?.[0] === 'True' ? t('Yes') : t('No')} ) : ( - {displayValueOrDash( + {displayValueOrEmpty( extractChoiceLabel(field, field.arguments?.[0]), )} @@ -126,21 +126,21 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { ); break; case 'LESS_THAN': + case 'GREATER_THAN': { + const isLessThan = field.type === 'LESS_THAN'; + const MathSignComponent = isLessThan ? LessThanEqual : GreaterThanEqual; + const altText = isLessThan ? 'less_than' : 'greater_than'; + const displayValue = field.arguments?.[0]; + fieldElement = (

{field.fieldAttribute.labelEn || field.fieldName}:{' '} - {displayValueOrDash(field.arguments?.[0])} -

- ); - break; - case 'GREATER_THAN': - fieldElement = ( -

- {field.fieldAttribute.labelEn || field.fieldName}:{' '} - {displayValueOrDash(field.arguments?.[0])} + {displayValue && } + {displayValueOrEmpty(displayValue)}

); break; + } case 'CONTAINS': fieldElement = (

@@ -148,7 +148,7 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { {field.arguments?.map((argument, index) => ( - {displayValueOrDash(extractChoiceLabel(field, argument))} + {displayValueOrEmpty(extractChoiceLabel(field, argument))} {index !== field.arguments.length - 1 && ', '} @@ -160,7 +160,7 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { fieldElement = (

{field.fieldAttribute.labelEn}:{' '} - {displayValueOrDash(field.arguments?.[0])} + {displayValueOrEmpty(field.arguments?.[0])}

); break; diff --git a/frontend/src/components/targeting/TargetingCriteriaDisplay/TargetingCriteriaDisplay.tsx b/frontend/src/components/targeting/TargetingCriteriaDisplay/TargetingCriteriaDisplay.tsx index e55ae02573..5b6def0d60 100644 --- a/frontend/src/components/targeting/TargetingCriteriaDisplay/TargetingCriteriaDisplay.tsx +++ b/frontend/src/components/targeting/TargetingCriteriaDisplay/TargetingCriteriaDisplay.tsx @@ -182,8 +182,7 @@ export const TargetingCriteriaDisplay = ({ onClick={() => setOpen(true)} data-cy="button-target-population-add-criteria" > - {t('Add')} 'Or' - {t('Filter')} + {t('Add')} 'Or' {t('Filter')} )} diff --git a/frontend/src/utils/en.json b/frontend/src/utils/en.json index 90d361dc2c..812ddc1578 100644 --- a/frontend/src/utils/en.json +++ b/frontend/src/utils/en.json @@ -893,7 +893,8 @@ "Mark as Distinct": "Mark as Distinct", "Uniqueness": "Uniqueness", "Are you sure you want to clear the selected ids? They won't be marked as a duplicate or distinct anymore.": "Are you sure you want to clear the selected ids? They won't be marked as a duplicate or distinct anymore.", - "Include records with null value for the round": "Include records with null value for the round" + "Include records with null value for the round": "Include records with null value for the round", "Programme does not have defined fields for periodic updates.": "Programme does not have defined fields for periodic updates.", - "There are no records available": "There are no records available" + "There are no records available": "There are no records available", + "Only Empty Values": "Only Empty Values" } diff --git a/frontend/src/utils/targetingUtils.ts b/frontend/src/utils/targetingUtils.ts index 9eae3f19fb..db9956e26e 100644 --- a/frontend/src/utils/targetingUtils.ts +++ b/frontend/src/utils/targetingUtils.ts @@ -162,7 +162,10 @@ export function formatCriteriaFilters(filters) { values = [each.value]; break; case 'PDU': - switch (each.pduData.subtype) { + switch ( + each.pduData?.subtype || + each.fieldAttribute?.pduData?.subtype + ) { case 'SELECT_ONE': comparisonMethod = 'EQUALS'; values = [each.value]; @@ -230,6 +233,7 @@ function mapFilterToVariable(filter): { flexFieldClassification: string; roundNumber?: number; } { + console.log('xxxfilter', filter); const result = { comparisonMethod: filter.isNull ? 'IS_NULL' : filter.comparisonMethod, arguments: filter.isNull ? [null] : filter.arguments, From 4122ae090a639de52cf06d1ea940e945e74aafda Mon Sep 17 00:00:00 2001 From: Patryk Dabrowski Date: Tue, 20 Aug 2024 09:27:49 +0200 Subject: [PATCH 030/118] Make mandatory data_collecting_type in the program (#4141) --- .../hct_mis_api/api/endpoints/rdi/program.py | 1 + backend/hct_mis_api/api/tests/test_program.py | 6 + .../tests/test_pull_from_datahub.py | 35 +- backend/hct_mis_api/apps/payment/fixtures.py | 2 + .../apps/program/migrations/0050_migration.py | 20 + backend/hct_mis_api/apps/program/models.py | 2 +- .../one_time_scripts/delete_pplans_and_rdi.py | 66 ---- .../tests/test_delete_pplans_and_rdi.py | 356 ------------------ 8 files changed, 49 insertions(+), 439 deletions(-) create mode 100644 backend/hct_mis_api/apps/program/migrations/0050_migration.py delete mode 100644 backend/hct_mis_api/one_time_scripts/delete_pplans_and_rdi.py delete mode 100644 backend/hct_mis_api/one_time_scripts/tests/test_delete_pplans_and_rdi.py diff --git a/backend/hct_mis_api/api/endpoints/rdi/program.py b/backend/hct_mis_api/api/endpoints/rdi/program.py index 01a22b778a..fe783b388e 100644 --- a/backend/hct_mis_api/api/endpoints/rdi/program.py +++ b/backend/hct_mis_api/api/endpoints/rdi/program.py @@ -28,6 +28,7 @@ class Meta: "sector", "cash_plus", "population_goal", + "data_collecting_type", ) diff --git a/backend/hct_mis_api/api/tests/test_program.py b/backend/hct_mis_api/api/tests/test_program.py index 9fc0af9130..eb44e8bc27 100644 --- a/backend/hct_mis_api/api/tests/test_program.py +++ b/backend/hct_mis_api/api/tests/test_program.py @@ -6,6 +6,7 @@ from hct_mis_api.api.models import APIToken, Grant from hct_mis_api.api.tests.base import HOPEApiTestCase from hct_mis_api.apps.account.fixtures import BusinessAreaFactory +from hct_mis_api.apps.core.fixtures import DataCollectingTypeFactory from hct_mis_api.apps.program.fixtures import ProgramFactory from hct_mis_api.apps.program.models import Program @@ -31,6 +32,7 @@ def setUpTestData(cls) -> None: cls.list_url = reverse("api:program-list", args=[cls.business_area.slug]) def test_create_program(self) -> None: + data_collecting_type = DataCollectingTypeFactory() data = { "name": "Program #1", "start_date": "2022-09-27", @@ -40,6 +42,7 @@ def test_create_program(self) -> None: "sector": "CHILD_PROTECTION", "cash_plus": True, "population_goal": 101, + "data_collecting_type": data_collecting_type.id, } response = self.client.post(self.create_url, data, format="json") assert response.status_code == 403 @@ -69,6 +72,7 @@ def test_create_program(self) -> None: "population_goal": 101, "sector": "CHILD_PROTECTION", "start_date": "2022-09-27", + "data_collecting_type": data_collecting_type.id, }, ) @@ -123,6 +127,7 @@ def test_list_program(self) -> None: "population_goal": program1.population_goal, "sector": program1.sector, "start_date": program1.start_date.strftime("%Y-%m-%d"), + "data_collecting_type": program1.data_collecting_type_id, }, response.json(), ) @@ -137,6 +142,7 @@ def test_list_program(self) -> None: "population_goal": program2.population_goal, "sector": program2.sector, "start_date": program2.start_date.strftime("%Y-%m-%d"), + "data_collecting_type": program2.data_collecting_type_id, }, response.json(), ) diff --git a/backend/hct_mis_api/apps/cash_assist_datahub/tests/test_pull_from_datahub.py b/backend/hct_mis_api/apps/cash_assist_datahub/tests/test_pull_from_datahub.py index e4506c1253..6fa02ca031 100644 --- a/backend/hct_mis_api/apps/cash_assist_datahub/tests/test_pull_from_datahub.py +++ b/backend/hct_mis_api/apps/cash_assist_datahub/tests/test_pull_from_datahub.py @@ -40,7 +40,10 @@ PaymentRecord, ServiceProvider, ) -from hct_mis_api.apps.program.fixtures import get_program_with_dct_type_and_name +from hct_mis_api.apps.program.fixtures import ( + ProgramFactory, + get_program_with_dct_type_and_name, +) from hct_mis_api.apps.program.models import Program from hct_mis_api.apps.targeting.models import TargetPopulation @@ -74,21 +77,21 @@ def _setup_in_app_data(cls) -> None: business_area=cls.business_area, ) - program = Program() - program.name = "Test Program" - program.status = Program.ACTIVE - program.start_date = timezone.now() - program.end_date = timezone.now() + timedelta(days=10) - program.description = "Test Program description" - program.business_area = BusinessArea.objects.first() - program.budget = 1000 - program.frequency_of_payments = Program.REGULAR - program.sector = Program.CHILD_PROTECTION - program.scope = Program.SCOPE_UNICEF - program.cash_plus = True - program.population_goal = 1000 - program.administrative_areas_of_implementation = "Test something" - program.save() + program = ProgramFactory( + name="Test Program", + status=Program.ACTIVE, + start_date=timezone.now(), + end_date=timezone.now() + timedelta(days=10), + description="Test Program description", + business_area=BusinessArea.objects.first(), + budget=1000, + frequency_of_payments=Program.REGULAR, + sector=Program.CHILD_PROTECTION, + scope=Program.SCOPE_UNICEF, + cash_plus=True, + population_goal=1000, + administrative_areas_of_implementation="Test something", + ) (household, individuals) = create_household(household_args={"size": 1}) cls.household = household cls.target_population = target_population diff --git a/backend/hct_mis_api/apps/payment/fixtures.py b/backend/hct_mis_api/apps/payment/fixtures.py index 7c55f91f26..4937be9a1c 100644 --- a/backend/hct_mis_api/apps/payment/fixtures.py +++ b/backend/hct_mis_api/apps/payment/fixtures.py @@ -16,6 +16,7 @@ from hct_mis_api.apps.account.fixtures import UserFactory from hct_mis_api.apps.account.models import User from hct_mis_api.apps.core.currencies import CURRENCY_CHOICES +from hct_mis_api.apps.core.fixtures import DataCollectingTypeFactory from hct_mis_api.apps.core.models import BusinessArea, DataCollectingType from hct_mis_api.apps.core.utils import CaIdIterator from hct_mis_api.apps.geo.models import Area @@ -390,6 +391,7 @@ class Meta: programme_code = factory.LazyAttribute( lambda o: "".join(random.choice(string.ascii_uppercase + string.digits) for _ in range(4)) ) + data_collecting_type = factory.SubFactory(DataCollectingTypeFactory) @factory.post_generation def cycle(self, create: bool, extracted: bool, **kwargs: Any) -> None: diff --git a/backend/hct_mis_api/apps/program/migrations/0050_migration.py b/backend/hct_mis_api/apps/program/migrations/0050_migration.py new file mode 100644 index 0000000000..3da6ffd244 --- /dev/null +++ b/backend/hct_mis_api/apps/program/migrations/0050_migration.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.25 on 2024-08-16 10:26 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0083_migration'), + ('program', '0049_migration'), + ] + + operations = [ + migrations.AlterField( + model_name='program', + name='data_collecting_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='programs', to='core.datacollectingtype'), + ), + ] diff --git a/backend/hct_mis_api/apps/program/models.py b/backend/hct_mis_api/apps/program/models.py index 66f4ac7293..ed599fd2d2 100644 --- a/backend/hct_mis_api/apps/program/models.py +++ b/backend/hct_mis_api/apps/program/models.py @@ -186,7 +186,7 @@ class Program(SoftDeletableModel, TimeStampedUUIDModel, AbstractSyncable, Concur validators=[MinLengthValidator(3), MaxLengthValidator(255)], ) data_collecting_type = models.ForeignKey( - "core.DataCollectingType", related_name="programs", on_delete=models.PROTECT, null=True, blank=True + "core.DataCollectingType", related_name="programs", on_delete=models.PROTECT ) is_visible = models.BooleanField(default=True) household_count = models.PositiveIntegerField(default=0) diff --git a/backend/hct_mis_api/one_time_scripts/delete_pplans_and_rdi.py b/backend/hct_mis_api/one_time_scripts/delete_pplans_and_rdi.py deleted file mode 100644 index 5fd4cd9baf..0000000000 --- a/backend/hct_mis_api/one_time_scripts/delete_pplans_and_rdi.py +++ /dev/null @@ -1,66 +0,0 @@ -import logging - -from hct_mis_api.apps.grievance.models import GrievanceTicket -from hct_mis_api.apps.household.documents import get_individual_doc -from hct_mis_api.apps.household.models import Individual -from hct_mis_api.apps.payment.models import PaymentPlan -from hct_mis_api.apps.program.models import Program -from hct_mis_api.apps.registration_data.admin import RegistrationDataImportAdmin -from hct_mis_api.apps.registration_data.models import RegistrationDataImport -from hct_mis_api.apps.registration_datahub import models as datahub_models -from hct_mis_api.apps.registration_datahub.documents import get_imported_individual_doc -from hct_mis_api.apps.utils.elasticsearch_utils import ( - remove_elasticsearch_documents_by_matching_ids, -) - -logger = logging.getLogger(__name__) - - -def delete_plans_and_rdi() -> None: - delete_plans_and_rdi_for_nigeria() - delete_rdi_for_palestine() - - -def delete_plans_and_rdi_for_nigeria() -> None: - # delete data for Nigeria - program_nigeria = Program.objects.get(name="VCM Network for Outbreak response", business_area__slug="nigeria") - rdi_nigeria = RegistrationDataImport.objects.get(name="VCM RDI all data Katsina", program=program_nigeria) - pplans_ids_to_remove = ["PP-3210-24-00000021", "PP-3210-24-00000022", "PP-3210-24-00000023", "PP-3210-24-00000024"] - pplans_to_remove = PaymentPlan.objects.filter(unicef_id__in=pplans_ids_to_remove, program=program_nigeria) - - # remove PaymentPlans - for pplan in pplans_to_remove: - pplan.delete(soft=False) - logger.info(f"Deleted {pplans_to_remove.count()} PaymentPlans") - - # remove RDI and related data - _delete_rdi(rdi=rdi_nigeria) - - -def delete_rdi_for_palestine() -> None: - # delete RDI for Palestine - program_palestine = Program.objects.get( - name="HCT_Gaza_Response_MPCA_Oct7", business_area__slug="palestine-state-of" - ) - rdi_palestine = RegistrationDataImport.objects.get(name="HCT_Gaza_July24_B23.1_1", program=program_palestine) - _delete_rdi(rdi=rdi_palestine) - - -def _delete_rdi(rdi: RegistrationDataImport) -> None: - rdi_datahub = datahub_models.RegistrationDataImportDatahub.objects.get(id=rdi.datahub_id) - datahub_individuals_ids = list( - datahub_models.ImportedIndividual.objects.filter(registration_data_import=rdi_datahub).values_list( - "id", flat=True - ) - ) - individuals_ids = list(Individual.objects.filter(registration_data_import=rdi).values_list("id", flat=True)) - rdi_datahub.delete() - GrievanceTicket.objects.filter(RegistrationDataImportAdmin.generate_query_for_all_grievances_tickets(rdi)).delete() - rdi.delete() - # remove elastic search records linked to individuals - business_area_slug = rdi.business_area.slug - remove_elasticsearch_documents_by_matching_ids( - datahub_individuals_ids, get_imported_individual_doc(business_area_slug) - ) - remove_elasticsearch_documents_by_matching_ids(individuals_ids, get_individual_doc(business_area_slug)) - logger.info(f"Deleted RDI and related data for {rdi.name}") diff --git a/backend/hct_mis_api/one_time_scripts/tests/test_delete_pplans_and_rdi.py b/backend/hct_mis_api/one_time_scripts/tests/test_delete_pplans_and_rdi.py deleted file mode 100644 index e718a414cb..0000000000 --- a/backend/hct_mis_api/one_time_scripts/tests/test_delete_pplans_and_rdi.py +++ /dev/null @@ -1,356 +0,0 @@ -from django.test import TestCase - -from hct_mis_api.apps.account.fixtures import BusinessAreaFactory -from hct_mis_api.apps.grievance.fixtures import GrievanceTicketFactory -from hct_mis_api.apps.grievance.models import ( - GrievanceTicket, - TicketComplaintDetails, - TicketIndividualDataUpdateDetails, -) -from hct_mis_api.apps.household.fixtures import ( - DocumentFactory, - create_household_and_individuals, -) -from hct_mis_api.apps.household.models import Document, Household, Individual -from hct_mis_api.apps.payment.fixtures import PaymentPlanFactory, PaymentRecordFactory -from hct_mis_api.apps.payment.models import PaymentPlan, PaymentRecord -from hct_mis_api.apps.program.fixtures import ProgramFactory -from hct_mis_api.apps.registration_data.fixtures import RegistrationDataImportFactory -from hct_mis_api.apps.registration_data.models import RegistrationDataImport -from hct_mis_api.apps.registration_datahub.fixtures import ( - RegistrationDataImportDatahubFactory, -) -from hct_mis_api.apps.registration_datahub.models import RegistrationDataImportDatahub -from hct_mis_api.apps.targeting.fixtures import ( - HouseholdSelectionFactory, - TargetPopulationFactory, -) -from hct_mis_api.apps.targeting.models import HouseholdSelection -from hct_mis_api.one_time_scripts.delete_pplans_and_rdi import ( - delete_plans_and_rdi_for_nigeria, - delete_rdi_for_palestine, -) - - -class TestDeletePPlansAndRDIForNigeria(TestCase): - databases = {"default", "registration_datahub"} - - @classmethod - def setUpTestData(cls) -> None: - nigeria = BusinessAreaFactory(name="Nigeria", slug="nigeria") - program = ProgramFactory(name="VCM Network for Outbreak response", business_area=nigeria) - cls.rdi_datahub = RegistrationDataImportDatahubFactory() - cls.rdi = RegistrationDataImportFactory( - name="VCM RDI all data Katsina", - business_area=nigeria, - program=program, - datahub_id=cls.rdi_datahub.id, - ) - cls.household, cls.individuals = create_household_and_individuals( - household_data={ - "business_area": nigeria, - "program": program, - "registration_data_import": cls.rdi, - }, - individuals_data=[ - { - "business_area": nigeria, - "program": program, - "registration_data_import": cls.rdi, - }, - { - "business_area": nigeria, - "program": program, - "registration_data_import": cls.rdi, - }, - ], - ) - - cls.document = DocumentFactory( - individual=cls.individuals[0], - program=program, - ) - cls.grievance_ticket1 = GrievanceTicketFactory(status=GrievanceTicket.STATUS_IN_PROGRESS) - cls.ticket_complaint_details = TicketComplaintDetails.objects.create( - ticket=cls.grievance_ticket1, - household=cls.household, - ) - cls.grievance_ticket2 = GrievanceTicketFactory(status=GrievanceTicket.STATUS_CLOSED) - cls.ticket_individual_data_update = TicketIndividualDataUpdateDetails.objects.create( - ticket=cls.grievance_ticket2, - individual=cls.individuals[0], - ) - - cls.target_population = TargetPopulationFactory(business_area=nigeria, program=program) - cls.household_selection = HouseholdSelectionFactory( - household=cls.household, target_population=cls.target_population - ) - - cls.payment_record = PaymentRecordFactory(household=cls.household) - - cls.payment_plan1 = PaymentPlanFactory(program=program) - cls.payment_plan1.unicef_id = "PP-3210-24-00000021" - cls.payment_plan1.save() - cls.payment_plan2 = PaymentPlanFactory(program=program) - cls.payment_plan2.unicef_id = "PP-3210-24-00000022" - cls.payment_plan2.save() - cls.payment_plan3 = PaymentPlanFactory(program=program) # different unicef_id - cls.payment_plan3.unicef_id = "PP-1111-24-00000000" - cls.payment_plan3.save() - - program_other = ProgramFactory(name="Other Program", business_area=nigeria) - cls.rdi_datahub_other = RegistrationDataImportDatahubFactory() - cls.rdi_other = RegistrationDataImportFactory( - name="Other RDI", - business_area=nigeria, - program=program_other, - datahub_id=cls.rdi_datahub_other.id, - ) - cls.household_other, cls.individuals_other = create_household_and_individuals( - household_data={ - "business_area": nigeria, - "program": program_other, - "registration_data_import": cls.rdi_other, - }, - individuals_data=[ - { - "business_area": nigeria, - "program": program_other, - "registration_data_import": cls.rdi_other, - }, - { - "business_area": nigeria, - "program": program_other, - "registration_data_import": cls.rdi_other, - }, - ], - ) - cls.grievance_ticket_other = GrievanceTicketFactory(status=GrievanceTicket.STATUS_CLOSED) - cls.ticket_complaint_details_other = TicketIndividualDataUpdateDetails.objects.create( - ticket=cls.grievance_ticket_other, - individual=cls.individuals_other[0], - ) - cls.target_population_other = TargetPopulationFactory(business_area=nigeria, program=program_other) - cls.household_selection = HouseholdSelectionFactory( - household=cls.household_other, target_population=cls.target_population_other - ) - - cls.payment_record_other = PaymentRecordFactory(household=cls.household_other) - - def test_delete_plans_and_rdi_for_nigeria(self) -> None: - self.assertEqual(PaymentPlan.objects.count(), 3) - self.assertEqual(PaymentRecord.objects.count(), 2) - self.assertEqual(GrievanceTicket.objects.count(), 3) - self.assertEqual(TicketIndividualDataUpdateDetails.objects.count(), 2) - self.assertEqual(TicketComplaintDetails.objects.count(), 1) - self.assertEqual(HouseholdSelection.objects.count(), 2) - self.assertEqual(HouseholdSelection.objects.count(), 2) - - self.assertEqual(RegistrationDataImport.objects.count(), 2) - self.assertEqual(RegistrationDataImportDatahub.objects.count(), 2) - - self.assertEqual(Household.objects.count(), 2) - self.assertEqual(Individual.objects.count(), 4) - self.assertEqual(Document.objects.count(), 1) - - delete_plans_and_rdi_for_nigeria() - - self.assertEqual(PaymentPlan.objects.count(), 1) - self.assertIsNone(PaymentPlan.objects.filter(unicef_id="PP-3210-24-00000021").first()) - self.assertIsNone(PaymentPlan.objects.filter(unicef_id="PP-3210-24-00000022").first()) - self.assertIsNotNone(PaymentPlan.objects.filter(unicef_id="PP-1111-24-00000000").first()) - - self.assertEqual(PaymentRecord.objects.count(), 1) - self.assertIsNone(PaymentRecord.objects.filter(household=self.household).first()) - self.assertIsNotNone(PaymentRecord.objects.filter(household=self.household_other).first()) - - self.assertEqual(GrievanceTicket.objects.count(), 1) - self.assertIsNone(GrievanceTicket.objects.filter(id=self.grievance_ticket1.id).first()) - self.assertIsNone(GrievanceTicket.objects.filter(id=self.grievance_ticket2.id).first()) - self.assertIsNotNone(GrievanceTicket.objects.filter(id=self.grievance_ticket_other.id).first()) - - self.assertEqual(TicketIndividualDataUpdateDetails.objects.count(), 1) - self.assertIsNone(TicketIndividualDataUpdateDetails.objects.filter(ticket=self.grievance_ticket2).first()) - self.assertIsNotNone( - TicketIndividualDataUpdateDetails.objects.filter(ticket=self.grievance_ticket_other).first() - ) - - self.assertEqual(TicketComplaintDetails.objects.count(), 0) - self.assertIsNone(TicketComplaintDetails.objects.filter(ticket=self.grievance_ticket1).first()) - - self.assertEqual(HouseholdSelection.objects.count(), 1) - self.assertIsNone(HouseholdSelection.objects.filter(household=self.household).first()) - self.assertIsNotNone(HouseholdSelection.objects.filter(household=self.household_other).first()) - - self.assertEqual(RegistrationDataImport.objects.count(), 1) - self.assertIsNone(RegistrationDataImport.objects.filter(id=self.rdi.id).first()) - self.assertIsNotNone(RegistrationDataImport.objects.filter(id=self.rdi_other.id).first()) - - self.assertEqual(RegistrationDataImportDatahub.objects.count(), 1) - self.assertIsNone(RegistrationDataImportDatahub.objects.filter(id=self.rdi_datahub.id).first()) - self.assertIsNotNone(RegistrationDataImportDatahub.objects.filter(id=self.rdi_datahub_other.id).first()) - - self.assertEqual(Household.objects.count(), 1) - self.assertIsNone(Household.objects.filter(id=self.household.id).first()) - self.assertIsNotNone(Household.objects.filter(id=self.household_other.id).first()) - - self.assertEqual(Individual.objects.count(), 2) - self.assertIsNone(Individual.objects.filter(id=self.individuals[0].id).first()) - self.assertIsNotNone(Individual.objects.filter(id=self.individuals_other[0].id).first()) - - self.assertEqual(Document.objects.count(), 0) - self.assertIsNone(Document.objects.filter(id=self.document.id).first()) - - -class TestDeletePPlansAndRDIForPalestine(TestCase): - databases = {"default", "registration_datahub"} - - @classmethod - def setUpTestData(cls) -> None: - palestine = BusinessAreaFactory(name="Palestine, State Of", slug="palestine-state-of") - program = ProgramFactory(name="HCT_Gaza_Response_MPCA_Oct7", business_area=palestine) - cls.rdi_datahub = RegistrationDataImportDatahubFactory() - cls.rdi = RegistrationDataImportFactory( - name="HCT_Gaza_July24_B23.1_1", - business_area=palestine, - program=program, - datahub_id=cls.rdi_datahub.id, - ) - cls.household, cls.individuals = create_household_and_individuals( - household_data={ - "business_area": palestine, - "program": program, - "registration_data_import": cls.rdi, - }, - individuals_data=[ - { - "business_area": palestine, - "program": program, - "registration_data_import": cls.rdi, - }, - { - "business_area": palestine, - "program": program, - "registration_data_import": cls.rdi, - }, - ], - ) - - cls.document = DocumentFactory( - individual=cls.individuals[0], - program=program, - ) - cls.grievance_ticket1 = GrievanceTicketFactory(status=GrievanceTicket.STATUS_IN_PROGRESS) - cls.ticket_complaint_details = TicketComplaintDetails.objects.create( - ticket=cls.grievance_ticket1, - household=cls.household, - ) - cls.grievance_ticket2 = GrievanceTicketFactory(status=GrievanceTicket.STATUS_CLOSED) - cls.ticket_individual_data_update = TicketIndividualDataUpdateDetails.objects.create( - ticket=cls.grievance_ticket2, - individual=cls.individuals[0], - ) - - cls.target_population = TargetPopulationFactory(business_area=palestine, program=program) - cls.household_selection = HouseholdSelectionFactory( - household=cls.household, target_population=cls.target_population - ) - - cls.payment_record = PaymentRecordFactory(household=cls.household) - - program_other = ProgramFactory(name="Other Program", business_area=palestine) - cls.rdi_datahub_other = RegistrationDataImportDatahubFactory() - cls.rdi_other = RegistrationDataImportFactory( - name="Other RDI", - business_area=palestine, - program=program_other, - datahub_id=cls.rdi_datahub_other.id, - ) - cls.household_other, cls.individuals_other = create_household_and_individuals( - household_data={ - "business_area": palestine, - "program": program_other, - "registration_data_import": cls.rdi_other, - }, - individuals_data=[ - { - "business_area": palestine, - "program": program_other, - "registration_data_import": cls.rdi_other, - }, - { - "business_area": palestine, - "program": program_other, - "registration_data_import": cls.rdi_other, - }, - ], - ) - cls.grievance_ticket_other = GrievanceTicketFactory(status=GrievanceTicket.STATUS_CLOSED) - cls.ticket_complaint_details_other = TicketIndividualDataUpdateDetails.objects.create( - ticket=cls.grievance_ticket_other, - individual=cls.individuals_other[0], - ) - cls.target_population_other = TargetPopulationFactory(business_area=palestine, program=program_other) - cls.household_selection = HouseholdSelectionFactory( - household=cls.household_other, target_population=cls.target_population_other - ) - - cls.payment_record_other = PaymentRecordFactory(household=cls.household_other) - - def test_delete_plans_and_rdi_for_palestine(self) -> None: - self.assertEqual(PaymentRecord.objects.count(), 2) - self.assertEqual(GrievanceTicket.objects.count(), 3) - self.assertEqual(TicketIndividualDataUpdateDetails.objects.count(), 2) - self.assertEqual(TicketComplaintDetails.objects.count(), 1) - self.assertEqual(HouseholdSelection.objects.count(), 2) - self.assertEqual(HouseholdSelection.objects.count(), 2) - - self.assertEqual(RegistrationDataImport.objects.count(), 2) - self.assertEqual(RegistrationDataImportDatahub.objects.count(), 2) - - self.assertEqual(Household.objects.count(), 2) - self.assertEqual(Individual.objects.count(), 4) - self.assertEqual(Document.objects.count(), 1) - - delete_rdi_for_palestine() - - self.assertEqual(PaymentRecord.objects.count(), 1) - self.assertIsNone(PaymentRecord.objects.filter(household=self.household).first()) - self.assertIsNotNone(PaymentRecord.objects.filter(household=self.household_other).first()) - - self.assertEqual(GrievanceTicket.objects.count(), 1) - self.assertIsNone(GrievanceTicket.objects.filter(id=self.grievance_ticket1.id).first()) - self.assertIsNone(GrievanceTicket.objects.filter(id=self.grievance_ticket2.id).first()) - self.assertIsNotNone(GrievanceTicket.objects.filter(id=self.grievance_ticket_other.id).first()) - - self.assertEqual(TicketIndividualDataUpdateDetails.objects.count(), 1) - self.assertIsNone(TicketIndividualDataUpdateDetails.objects.filter(ticket=self.grievance_ticket2).first()) - self.assertIsNotNone( - TicketIndividualDataUpdateDetails.objects.filter(ticket=self.grievance_ticket_other).first() - ) - - self.assertEqual(TicketComplaintDetails.objects.count(), 0) - self.assertIsNone(TicketComplaintDetails.objects.filter(ticket=self.grievance_ticket1).first()) - - self.assertEqual(HouseholdSelection.objects.count(), 1) - self.assertIsNone(HouseholdSelection.objects.filter(household=self.household).first()) - self.assertIsNotNone(HouseholdSelection.objects.filter(household=self.household_other).first()) - - self.assertEqual(RegistrationDataImport.objects.count(), 1) - self.assertIsNone(RegistrationDataImport.objects.filter(id=self.rdi.id).first()) - self.assertIsNotNone(RegistrationDataImport.objects.filter(id=self.rdi_other.id).first()) - - self.assertEqual(RegistrationDataImportDatahub.objects.count(), 1) - self.assertIsNone(RegistrationDataImportDatahub.objects.filter(id=self.rdi_datahub.id).first()) - self.assertIsNotNone(RegistrationDataImportDatahub.objects.filter(id=self.rdi_datahub_other.id).first()) - - self.assertEqual(Household.objects.count(), 1) - self.assertIsNone(Household.objects.filter(id=self.household.id).first()) - self.assertIsNotNone(Household.objects.filter(id=self.household_other.id).first()) - - self.assertEqual(Individual.objects.count(), 2) - self.assertIsNone(Individual.objects.filter(id=self.individuals[0].id).first()) - self.assertIsNotNone(Individual.objects.filter(id=self.individuals_other[0].id).first()) - - self.assertEqual(Document.objects.count(), 0) - self.assertIsNone(Document.objects.filter(id=self.document.id).first()) From 2619bc70e1d96404d2def0060c6238a8c207491b Mon Sep 17 00:00:00 2001 From: szymon-kellton <130459593+szymon-kellton@users.noreply.github.com> Date: Tue, 20 Aug 2024 09:30:04 +0200 Subject: [PATCH 031/118] Selenium: Feedback module tests (#4126) * Init * In progress * Added trest test_create_linked_ticket * black * In progress * In progress test_feedback_identity_verification * In progress test_feedback_errors * in progress test_feedback_errors * Added test_feedback_errors * Fix test_edit_feedback * Fix test_feedback_identity_verification * Fix test_feedback_identity_verification --- .../grievance/feedback/test_feedback.py | 198 +++++++++++++++++- .../page_object/grievance/new_feedback.py | 146 +++++++++++++ .../program_details/test_program_details.py | 1 - .../test_programme_management.py | 1 - .../FormikCheckboxField.tsx | 2 +- 5 files changed, 342 insertions(+), 6 deletions(-) diff --git a/backend/selenium_tests/grievance/feedback/test_feedback.py b/backend/selenium_tests/grievance/feedback/test_feedback.py index c6453246e3..4833a14782 100644 --- a/backend/selenium_tests/grievance/feedback/test_feedback.py +++ b/backend/selenium_tests/grievance/feedback/test_feedback.py @@ -11,6 +11,15 @@ from pytest_django import DjangoDbBlocker from selenium.webdriver import Keys +from hct_mis_api.apps.geo.models import Area, Country +from hct_mis_api.apps.household.fixtures import create_household_and_individuals +from hct_mis_api.apps.household.models import HOST, Household +from selenium_tests.helpers.fixtures import get_program_with_dct_type_and_name +from selenium_tests.page_object.grievance.details_grievance_page import ( + GrievanceDetailsPage, +) +from selenium_tests.page_object.grievance.new_ticket import NewTicket + pytestmark = pytest.mark.django_db(transaction=True) @@ -37,6 +46,59 @@ def create_programs(django_db_setup: Generator[None, None, None], django_db_bloc yield +@pytest.fixture +def create_households_and_individuals() -> Household: + hh = create_custom_household(observed_disability=[]) + hh.male_children_count = 1 + hh.male_age_group_0_5_count = 1 + hh.female_children_count = 2 + hh.female_age_group_0_5_count = 2 + hh.children_count = 3 + hh.village = "Wroclaw" + hh.country_origin = Country.objects.filter(iso_code2="UA").first() + hh.address = "Karta-e-Mamorin KABUL/5TH DISTRICT, Afghanistan" + hh.admin1 = Area.objects.first() + hh.admin2 = Area.objects.get(name="Kaluskyi") + hh.save() + hh.set_admin_areas() + hh.refresh_from_db() + yield hh + + +def create_custom_household(observed_disability: list[str], residence_status: str = HOST) -> Household: + program = get_program_with_dct_type_and_name("Test Program", "1234") + household, _ = create_household_and_individuals( + household_data={ + "unicef_id": "HH-20-0000.0002", + "rdi_merge_status": "MERGED", + "business_area": program.business_area, + "program": program, + "residence_status": residence_status, + }, + individuals_data=[ + { + "unicef_id": "IND-00-0000.0011", + "rdi_merge_status": "MERGED", + "business_area": program.business_area, + "observed_disability": observed_disability, + }, + { + "unicef_id": "IND-00-0000.0022", + "rdi_merge_status": "MERGED", + "business_area": program.business_area, + "observed_disability": observed_disability, + }, + { + "unicef_id": "IND-00-0000.0033", + "rdi_merge_status": "MERGED", + "business_area": program.business_area, + "observed_disability": observed_disability, + }, + ], + ) + return household + + @pytest.mark.usefixtures("login") class TestSmokeFeedback: def test_check_feedback_page( @@ -347,8 +409,7 @@ def test_edit_feedback( pageNewFeedback.getComments().send_keys("New comment, new comment. New comment?") pageNewFeedback.getInputArea().send_keys("Abkamari") pageNewFeedback.getInputLanguage().send_keys("English") - # ToDo: Enable after Fix bug - # pageNewFeedback.selectArea("Abband") + pageNewFeedback.selectArea("Abband") pageNewFeedback.getButtonNext().click() # Check edited Feedback assert "Draft Program" in pageFeedbackDetails.getProgramme().text @@ -357,11 +418,142 @@ def test_edit_feedback( assert "Abkamari" in pageFeedbackDetails.getAreaVillagePayPoint().text assert "English" in pageFeedbackDetails.getLanguagesSpoken().text - @pytest.mark.skip(reason="Create during Grievance tickets creation tests") def test_create_linked_ticket( self, + pageGrievanceNewTicket: NewTicket, + pageGrievanceDetailsPage: GrievanceDetailsPage, + pageFeedback: Feedback, + pageFeedbackDetails: FeedbackDetailsPage, + add_feedbacks: None, + ) -> None: + # Go to Feedback + pageFeedback.getNavGrievance().click() + pageFeedback.getNavFeedback().click() + pageFeedback.waitForRows()[0].click() + pageFeedbackDetails.getButtonCreateLinkedTicket().click() + pageGrievanceNewTicket.getSelectCategory().click() + pageGrievanceNewTicket.select_option_by_name("Referral") + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getHouseholdTab() + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getReceivedConsent().click() + pageGrievanceNewTicket.getButtonNext().click() + pageGrievanceNewTicket.getDescription().send_keys("Linked Ticket Referral") + pageGrievanceNewTicket.getButtonNext().click() + assert "Linked Ticket Referral" in pageGrievanceDetailsPage.getTicketDescription().text + grievance_ticket = pageGrievanceDetailsPage.getTitle().text.split(" ")[-1] + pageFeedback.getNavFeedback().click() + assert grievance_ticket in pageFeedback.waitForRows()[0].text + pageFeedback.waitForRows()[0].click() + assert grievance_ticket in pageGrievanceDetailsPage.getTitle().text.split(" ")[-1] + pageFeedback.getNavFeedback().click() + pageFeedback.waitForRows()[0].find_elements("tag name", "a")[0].click() + + def test_feedback_errors( + self, + pageFeedback: Feedback, + pageNewFeedback: NewFeedback, + pageFeedbackDetails: FeedbackDetailsPage, + create_households_and_individuals: Household, + ) -> None: + # Go to Feedback + pageFeedback.getNavGrievance().click() + pageFeedback.getNavFeedback().click() + # Create Feedback + pageFeedback.getButtonSubmitNewFeedback().click() + # ToDo: Uncomment after fix 209087 + # pageNewFeedback.getButtonNext().click() + # assert for pageNewFeedback.getError().text + # with pytest.raises(Exception): + # pageNewFeedback.getHouseholdTab() + pageNewFeedback.chooseOptionByName("Negative feedback") + pageNewFeedback.getButtonNext().click() + pageNewFeedback.getHouseholdTab() + pageNewFeedback.getButtonNext().click() + pageNewFeedback.getReceivedConsent() + pageNewFeedback.getButtonNext().click() + assert "Consent is required" in pageNewFeedback.getError().text + pageNewFeedback.getReceivedConsent().click() + pageNewFeedback.getButtonNext().click() + pageNewFeedback.getDescription() + pageNewFeedback.getButtonNext().click() + assert "Description is required" in pageNewFeedback.getDivDescription().text + pageNewFeedback.getDescription().send_keys("New description") + pageNewFeedback.getButtonNext().click() + assert "New description" in pageFeedbackDetails.getDescription().text + + def test_feedback_identity_verification( + self, + create_households_and_individuals: Household, pageFeedback: Feedback, + pageFeedbackDetails: FeedbackDetailsPage, + pageNewFeedback: NewFeedback, ) -> None: + pageFeedback.getMenuUserProfile().click() + pageFeedback.getMenuItemClearCache().click() # Go to Feedback pageFeedback.getNavGrievance().click() pageFeedback.getNavFeedback().click() + # Create Feedback + pageFeedback.getButtonSubmitNewFeedback().click() + pageNewFeedback.chooseOptionByName("Negative feedback") + pageNewFeedback.getButtonNext().click() + pageNewFeedback.getHouseholdTab() + pageNewFeedback.getHouseholdTableRows(0).click() + pageNewFeedback.getIndividualTab().click() + individual_name = pageNewFeedback.getIndividualTableRow(0).text.split(" HH")[0][17:] + individual_unicef_id = pageNewFeedback.getIndividualTableRow(0).text.split(" ")[0] + pageNewFeedback.getIndividualTableRow(0).click() + pageNewFeedback.getButtonNext().click() + + pageNewFeedback.getInputQuestionnaire_size().click() + # ToDo: Uncomment after fix: 211708 + # assert "-" in pageNewFeedback.getLabelHouseholdSize().text + pageNewFeedback.getInputQuestionnaire_malechildrencount().click() + # ToDo: Uncomment after fix: 211708 + # assert "-" in pageNewFeedback.getLabelNumberOfMaleChildren().text + pageNewFeedback.getInputQuestionnaire_femalechildrencount().click() + # ToDo: Uncomment after fix: 211708 + # assert "-" in pageNewFeedback.getLabelNumberOfFemaleChildren().text + pageNewFeedback.getInputQuestionnaire_childrendisabledcount().click() + assert "-" in pageNewFeedback.getLabelNumberOfDisabledChildren().text + pageNewFeedback.getInputQuestionnaire_headofhousehold().click() + # ToDo: Uncomment after fix: 211708 + # assert "" in pageNewFeedback.getLabelHeadOfHousehold().text + pageNewFeedback.getInputQuestionnaire_countryorigin().click() + # ToDo: Uncomment after fix: 211708 + # assert "-" in pageNewFeedback.getLabelCountryOfOrigin().text + pageNewFeedback.getInputQuestionnaire_address().click() + # ToDo: Uncomment after fix: 211708 + # assert "-" in pageNewFeedback.getLabelAddress().text + pageNewFeedback.getInputQuestionnaire_village().click() + # ToDo: Uncomment after fix: 211708 + # assert "-" in pageNewFeedback.getLabelVillage().text + pageNewFeedback.getInputQuestionnaire_admin1().click() + # ToDo: Uncomment after fix: 211708 + # assert "-" in pageNewFeedback.getLabelAdministrativeLevel1().text + pageNewFeedback.getInputQuestionnaire_admin2().click() + assert "Kaluskyi" in pageNewFeedback.getLabelAdministrativeLevel2().text + pageNewFeedback.getInputQuestionnaire_admin3().click() + assert "-" in pageNewFeedback.getLabelAdministrativeLevel3().text + pageNewFeedback.getInputQuestionnaire_admin4().click() + assert "-" in pageNewFeedback.getLabelAdministrativeLevel4().text + pageNewFeedback.getInputQuestionnaire_months_displaced_h_f().click() + assert "-" in pageNewFeedback.getLabelLengthOfTimeSinceArrival().text + pageNewFeedback.getInputQuestionnaire_fullname().click() + assert ( + create_households_and_individuals.active_individuals.get(unicef_id=individual_unicef_id).full_name + in pageNewFeedback.getLabelIndividualFullName().text + ) + assert individual_name in pageNewFeedback.getLabelIndividualFullName().text + pageNewFeedback.getInputQuestionnaire_birthdate().click() + # ToDo: Uncomment after fix: 211708 + # assert "-" in pageNewFeedback.getLabelBirthDate().text + pageNewFeedback.getInputQuestionnaire_phoneno().click() + assert "-" in pageNewFeedback.getLabelPhoneNumber().text + pageNewFeedback.getInputQuestionnaire_relationship().click() + # ToDo: Uncomment after fix: 211708 + # assert "Head of Household" in pageNewFeedback.getLabelRelationshipToHoh().text + pageNewFeedback.getReceivedConsent().click() + pageNewFeedback.getButtonNext().click() + assert "Feedback" in pageNewFeedback.getLabelCategory().text diff --git a/backend/selenium_tests/page_object/grievance/new_feedback.py b/backend/selenium_tests/page_object/grievance/new_feedback.py index e09bc6c77c..0ae4c73cd2 100644 --- a/backend/selenium_tests/page_object/grievance/new_feedback.py +++ b/backend/selenium_tests/page_object/grievance/new_feedback.py @@ -20,6 +20,8 @@ class NewFeedback(BaseComponents): lookUpTabsHouseHold = 'button[role="tab"]' lookUpTabsIndividual = 'button[role="tab"]' receivedConsent = 'span[data-cy="input-consent"]' + error = 'p[data-cy="checkbox-error"]' + divDescription = 'div[data-cy="input-description"]' description = 'textarea[data-cy="input-description"]' comments = 'textarea[data-cy="input-comments"]' adminAreaAutocomplete = 'div[data-cy="admin-area-autocomplete"]' @@ -28,6 +30,39 @@ class NewFeedback(BaseComponents): programmeSelect = 'div[data-cy="select-program"]' hhRadioButton = 'span[data-cy="input-radio-household"]' individualRadioButton = 'span[data-cy="input-radio-individual"]' + inputQuestionnaire_size = 'span[data-cy="input-questionnaire_size"]' + labelHouseholdSize = 'div[data-cy="label-Household Size"]' + inputQuestionnaire_malechildrencount = 'span[data-cy="input-questionnaire_maleChildrenCount"]' + labelNumberOfMaleChildren = 'div[data-cy="label-Number of Male Children"]' + inputQuestionnaire_femalechildrencount = 'span[data-cy="input-questionnaire_femaleChildrenCount"]' + labelNumberOfFemaleChildren = 'div[data-cy="label-Number of Female Children"]' + inputQuestionnaire_childrendisabledcount = 'span[data-cy="input-questionnaire_childrenDisabledCount"]' + labelNumberOfDisabledChildren = 'div[data-cy="label-Number of Disabled Children"]' + inputQuestionnaire_headofhousehold = 'span[data-cy="input-questionnaire_headOfHousehold"]' + labelHeadOfHousehold = 'div[data-cy="label-Head of Household"]' + inputQuestionnaire_countryorigin = 'span[data-cy="input-questionnaire_countryOrigin"]' + labelCountryOfOrigin = 'div[data-cy="label-Country of Origin"]' + inputQuestionnaire_address = 'span[data-cy="input-questionnaire_address"]' + labelAddress = 'div[data-cy="label-Address"]' + inputQuestionnaire_village = 'span[data-cy="input-questionnaire_village"]' + labelVillage = 'div[data-cy="label-Village"]' + inputQuestionnaire_admin1 = 'span[data-cy="input-questionnaire_admin1"]' + labelAdministrativeLevel1 = 'div[data-cy="label-Administrative Level 1"]' + inputQuestionnaire_admin2 = 'span[data-cy="input-questionnaire_admin2"]' + labelAdministrativeLevel2 = 'div[data-cy="label-Administrative Level 2"]' + inputQuestionnaire_admin3 = 'span[data-cy="input-questionnaire_admin3"]' + labelAdministrativeLevel3 = 'div[data-cy="label-Administrative Level 3"]' + inputQuestionnaire_admin4 = 'span[data-cy="input-questionnaire_admin4"]' + labelAdministrativeLevel4 = 'div[data-cy="label-Administrative Level 4"]' + inputQuestionnaire_months_displaced_h_f = 'span[data-cy="input-questionnaire_months_displaced_h_f"]' + labelLengthOfTimeSinceArrival = 'div[data-cy="label-LENGTH OF TIME SINCE ARRIVAL"]' + inputQuestionnaire_fullname = 'span[data-cy="input-questionnaire_fullName"]' + labelIndividualFullName = 'div[data-cy="label-Individual Full Name"]' + inputQuestionnaire_birthdate = 'span[data-cy="input-questionnaire_birthDate"]' + labelBirthDate = 'div[data-cy="label-Birth Date"]' + inputQuestionnaire_phoneno = 'span[data-cy="input-questionnaire_phoneNo"]' + labelPhoneNumber = 'div[data-cy="label-Phone Number"]' + inputQuestionnaire_relationship = 'span[data-cy="input-questionnaire_relationship"]' # Texts textTitle = "New Feedback" @@ -95,9 +130,15 @@ def getIndividualTableRow(self, number: int) -> WebElement: def getReceivedConsent(self) -> WebElement: return self.wait_for(self.receivedConsent) + def getError(self) -> WebElement: + return self.wait_for(self.error) + def getDescription(self) -> WebElement: return self.wait_for(self.description) + def getDivDescription(self) -> WebElement: + return self.wait_for(self.divDescription) + def getComments(self) -> WebElement: return self.wait_for(self.comments) @@ -138,3 +179,108 @@ def checkElementsOnPage(self) -> None: def chooseOptionByName(self, name: str) -> None: self.getSelectIssueType().click() self.select_listbox_element(name) + + def getInputQuestionnaire_size(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_size) + + def getLabelHouseholdSize(self) -> WebElement: + return self.wait_for(self.labelHouseholdSize) + + def getInputQuestionnaire_malechildrencount(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_malechildrencount) + + def getLabelNumberOfMaleChildren(self) -> WebElement: + return self.wait_for(self.labelNumberOfMaleChildren) + + def getInputQuestionnaire_femalechildrencount(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_femalechildrencount) + + def getLabelNumberOfFemaleChildren(self) -> WebElement: + return self.wait_for(self.labelNumberOfFemaleChildren) + + def getInputQuestionnaire_childrendisabledcount(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_childrendisabledcount) + + def getLabelNumberOfDisabledChildren(self) -> WebElement: + return self.wait_for(self.labelNumberOfDisabledChildren) + + def getInputQuestionnaire_headofhousehold(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_headofhousehold) + + def getLabelHeadOfHousehold(self) -> WebElement: + return self.wait_for(self.labelHeadOfHousehold) + + def getInputQuestionnaire_countryorigin(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_countryorigin) + + def getLabelCountryOfOrigin(self) -> WebElement: + return self.wait_for(self.labelCountryOfOrigin) + + def getInputQuestionnaire_address(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_address) + + def getLabelAddress(self) -> WebElement: + return self.wait_for(self.labelAddress) + + def getInputQuestionnaire_village(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_village) + + def getLabelVillage(self) -> WebElement: + return self.wait_for(self.labelVillage) + + def getInputQuestionnaire_admin1(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_admin1) + + def getLabelAdministrativeLevel1(self) -> WebElement: + return self.wait_for(self.labelAdministrativeLevel1) + + def getInputQuestionnaire_admin2(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_admin2) + + def getLabelAdministrativeLevel2(self) -> WebElement: + return self.wait_for(self.labelAdministrativeLevel2) + + def getInputQuestionnaire_admin3(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_admin3) + + def getLabelAdministrativeLevel3(self) -> WebElement: + return self.wait_for(self.labelAdministrativeLevel3) + + def getInputQuestionnaire_admin4(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_admin4) + + def getLabelAdministrativeLevel4(self) -> WebElement: + return self.wait_for(self.labelAdministrativeLevel4) + + def getInputQuestionnaire_months_displaced_h_f(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_months_displaced_h_f) + + def getLabelLengthOfTimeSinceArrival(self) -> WebElement: + return self.wait_for(self.labelLengthOfTimeSinceArrival) + + def getInputQuestionnaire_fullname(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_fullname) + + def getLabelIndividualFullName(self) -> WebElement: + return self.wait_for(self.labelIndividualFullName) + + def getInputQuestionnaire_birthdate(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_birthdate) + + def getLabelBirthDate(self) -> WebElement: + return self.wait_for(self.labelBirthDate) + + def getInputQuestionnaire_phoneno(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_phoneno) + + def getLabelPhoneNumber(self) -> WebElement: + return self.wait_for(self.labelPhoneNumber) + + def getInputQuestionnaire_relationship(self) -> WebElement: + return self.wait_for(self.inputQuestionnaire_relationship) + + def getLabelRelationshipToHoh(self) -> WebElement: + return self.wait_for(self.labelRelationshipToHoh) + + def getInputConsent(self) -> WebElement: + return self.wait_for(self.inputConsent) diff --git a/backend/selenium_tests/program_details/test_program_details.py b/backend/selenium_tests/program_details/test_program_details.py index 902e9ec392..55cb453039 100644 --- a/backend/selenium_tests/program_details/test_program_details.py +++ b/backend/selenium_tests/program_details/test_program_details.py @@ -199,7 +199,6 @@ def test_program_details_happy_path( assert "Programme Cycles" in pageProgrammeDetails.getTableTitle().text assert "Rows per page: 5 1–1 of 1" in pageProgrammeDetails.getTablePagination().text.replace("\n", " ") pageProgrammeDetails.getButtonFinishProgram().click() - pageProgrammeDetails.screenshot("123") pageProgrammeDetails.clickButtonFinishProgramPopup() for _ in range(10): if "FINISHED" in pageProgrammeDetails.getProgramStatus().text: diff --git a/backend/selenium_tests/programme_management/test_programme_management.py b/backend/selenium_tests/programme_management/test_programme_management.py index 2da79d47fc..05c39459b3 100644 --- a/backend/selenium_tests/programme_management/test_programme_management.py +++ b/backend/selenium_tests/programme_management/test_programme_management.py @@ -439,7 +439,6 @@ def test_create_programme_add_partners_Business_Area( assert "UNHCR" in pageProgrammeDetails.getLabelPartnerName().text assert "Business Area" in pageProgrammeDetails.getLabelAreaAccess().text - # @pytest.mark.skip(reason="Unstable test") @pytest.mark.parametrize( "test_data", [ diff --git a/frontend/src/shared/Formik/FormikCheckboxField/FormikCheckboxField.tsx b/frontend/src/shared/Formik/FormikCheckboxField/FormikCheckboxField.tsx index f56f359524..a280fd9dc5 100644 --- a/frontend/src/shared/Formik/FormikCheckboxField/FormikCheckboxField.tsx +++ b/frontend/src/shared/Formik/FormikCheckboxField/FormikCheckboxField.tsx @@ -61,7 +61,7 @@ export function Check({
)} {isInvalid && get(form.errors, field.name) && ( - {get(form.errors, field.name)} + {get(form.errors, field.name)} )} ); From aa59f4ba6568640922ea39c01b4eacb7ec5d8514 Mon Sep 17 00:00:00 2001 From: szymon-kellton <130459593+szymon-kellton@users.noreply.github.com> Date: Tue, 20 Aug 2024 09:48:57 +0200 Subject: [PATCH 032/118] Selenium: Night run (#4143) * Init * Added docker-compose.selenium-night.yml * Added docker-compose.selenium-night.yml * test @pytest.mark.night * test @pytest.mark.night * black * Fix * Fix * CI/Night * black --- backend/selenium_tests/conftest.py | 4 +- .../selenium_tests/filters/test_filters.py | 1 + .../test_grievance_tickets.py | 1 + .../test_programme_management.py | 6 ++ .../test_periodic_data_templates.py | 3 + .../test_periodic_data_update_upload.py | 4 +- .../test_registration_data_import.py | 2 + .../targeting/test_targeting.py | 2 + deployment/docker-compose.selenium-night.yml | 98 +++++++++++++++++++ deployment/docker-compose.selenium.yml | 2 +- 10 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 deployment/docker-compose.selenium-night.yml diff --git a/backend/selenium_tests/conftest.py b/backend/selenium_tests/conftest.py index 3be8a72f1b..329b59d88a 100644 --- a/backend/selenium_tests/conftest.py +++ b/backend/selenium_tests/conftest.py @@ -87,7 +87,9 @@ def pytest_addoption(parser) -> None: # type: ignore parser.addoption("--mapping", action="store_true", default=False, help="Enable mapping mode") -def pytest_configure() -> None: +def pytest_configure(config) -> None: # type: ignore + config.addinivalue_line("markers", "night: This marker is intended for e2e tests conducted during the night on CI") + # delete all old screenshots for file in os.listdir("report/screenshot"): os.remove(os.path.join("report/screenshot", file)) diff --git a/backend/selenium_tests/filters/test_filters.py b/backend/selenium_tests/filters/test_filters.py index bc7a237c33..7afbc65395 100644 --- a/backend/selenium_tests/filters/test_filters.py +++ b/backend/selenium_tests/filters/test_filters.py @@ -549,6 +549,7 @@ def test_filters_happy_path_search_filter( filters.getButtonFiltersApply().click() assert filters.waitForNumberOfRows(1) + @pytest.mark.night @pytest.mark.skip("ToDo") def test_grievance_tickets_filters_of_households_and_individuals( self, diff --git a/backend/selenium_tests/grievance/grievance_tickets/test_grievance_tickets.py b/backend/selenium_tests/grievance/grievance_tickets/test_grievance_tickets.py index 996a7315dc..5de95d13ce 100644 --- a/backend/selenium_tests/grievance/grievance_tickets/test_grievance_tickets.py +++ b/backend/selenium_tests/grievance/grievance_tickets/test_grievance_tickets.py @@ -454,6 +454,7 @@ def test_grievance_tickets_create_new_ticket_referral( assert "Not set" in pageGrievanceDetailsPage.getTicketUrgency().text +@pytest.mark.night @pytest.mark.usefixtures("login") class TestGrievanceTickets: @pytest.mark.parametrize( diff --git a/backend/selenium_tests/programme_management/test_programme_management.py b/backend/selenium_tests/programme_management/test_programme_management.py index 05c39459b3..d80cec7adc 100644 --- a/backend/selenium_tests/programme_management/test_programme_management.py +++ b/backend/selenium_tests/programme_management/test_programme_management.py @@ -206,6 +206,7 @@ def test_create_programme_Frequency_of_Payment( assert "No" in pageProgrammeDetails.getLabelCashPlus().text assert "0" in pageProgrammeDetails.getLabelProgramSize().text + @pytest.mark.night @pytest.mark.parametrize( "test_data", [ @@ -257,6 +258,7 @@ def test_create_programme_Cash_Plus( assert "Yes" in pageProgrammeDetails.getLabelCashPlus().text assert "0" in pageProgrammeDetails.getLabelProgramSize().text + @pytest.mark.night @pytest.mark.parametrize( "test_data", [ @@ -385,6 +387,7 @@ def test_create_programme_cancel_scenario( # ToDo: Check Unicef partner! and delete classes +@pytest.mark.night @pytest.mark.usefixtures("login") class TestBusinessAreas: @pytest.mark.parametrize( @@ -512,6 +515,7 @@ def test_copy_programme( assert "New Programme" in pageProgrammeDetails.getHeaderTitle().text +@pytest.mark.night @pytest.mark.usefixtures("login") class TestAdminAreas: @pytest.mark.parametrize( @@ -572,6 +576,7 @@ def test_create_programme_add_partners_Admin_Area( assert "15" in pageProgrammeDetails.getLabelAdminArea2().text +@pytest.mark.night @pytest.mark.usefixtures("login") class TestComeBackScenarios: @pytest.mark.parametrize( @@ -648,6 +653,7 @@ def test_create_programme_back_scenarios( assert "UNHCR" in pageProgrammeDetails.getLabelPartnerName().text +@pytest.mark.night @pytest.mark.usefixtures("login") class TestManualCalendar: @pytest.mark.skip(reason="ToDo") diff --git a/backend/selenium_tests/programme_population/test_periodic_data_templates.py b/backend/selenium_tests/programme_population/test_periodic_data_templates.py index a5912f0c0c..eff90b154f 100644 --- a/backend/selenium_tests/programme_population/test_periodic_data_templates.py +++ b/backend/selenium_tests/programme_population/test_periodic_data_templates.py @@ -145,6 +145,7 @@ def test_periodic_data_template_export_and_download( is True ) + @pytest.mark.night def test_periodic_data_template_list( self, program: Program, @@ -192,6 +193,7 @@ def test_periodic_data_template_list( assert "EXPORTED" in pagePeriodicDataUpdateTemplates.getTemplateStatus(index).text + @pytest.mark.night def test_periodic_data_template_details( self, program: Program, @@ -237,6 +239,7 @@ def test_periodic_data_template_details( in pagePeriodicDataUpdateTemplates.getTemplateNumberOfIndividuals(0).text ) + @pytest.mark.night def test_periodic_data_template_create_and_download( self, program: Program, diff --git a/backend/selenium_tests/programme_population/test_periodic_data_update_upload.py b/backend/selenium_tests/programme_population/test_periodic_data_update_upload.py index 24ce2008c5..19b1a67123 100644 --- a/backend/selenium_tests/programme_population/test_periodic_data_update_upload.py +++ b/backend/selenium_tests/programme_population/test_periodic_data_update_upload.py @@ -180,7 +180,7 @@ def test_periodic_data_update_upload_success( assert individual.flex_fields[flexible_attribute.name]["1"]["collection_date"] == "2021-05-02" assert pageIndividuals.getUpdateStatus(periodic_data_update_upload.pk).text == "SUCCESSFUL" - # @flaky(max_runs=5, min_passes=1) + @pytest.mark.night def test_periodic_data_update_upload_form_error( self, clear_downloaded_files: None, @@ -220,6 +220,7 @@ def test_periodic_data_update_upload_form_error( error_text = "Row: 2\ntest_date_attribute__round_value\nEnter a valid date." assert pageIndividuals.getPduFormErrors().text == error_text + @pytest.mark.night def test_periodic_data_update_upload_error( self, clear_downloaded_files: None, @@ -262,6 +263,7 @@ def test_periodic_data_update_upload_error( error_text = pageIndividuals.getPduUploadError().text assert error_text == "Periodic Data Update Template with ID -1 not found" + @pytest.mark.night def test_periodic_data_uploads_list( self, clear_downloaded_files: None, diff --git a/backend/selenium_tests/registration_data_import/test_registration_data_import.py b/backend/selenium_tests/registration_data_import/test_registration_data_import.py index dbd5424b75..17846df476 100644 --- a/backend/selenium_tests/registration_data_import/test_registration_data_import.py +++ b/backend/selenium_tests/registration_data_import/test_registration_data_import.py @@ -202,6 +202,7 @@ def test_registration_data_import_happy_path( pageDetailsRegistrationDataImport.getImportedHouseholdsRow(0).find_elements("tag name", "td")[1].click() assert hausehold_id in pageHouseholdsDetails.getPageHeaderTitle().text + @pytest.mark.night @pytest.mark.skip(reason="Kobo form is not available. This is a external service, we cannot control it.") @pytest.mark.vcr(ignore_localhost=True) def test_import_empty_kobo_form( @@ -228,6 +229,7 @@ def test_import_empty_kobo_form( pageRegistrationDataImport.getButtonImportFile().click() pageRegistrationDataImport.checkAlert("Cannot import empty form") + @pytest.mark.night @pytest.mark.skip(reason="Kobo form is not available. This is a external service, we cannot control it.") @pytest.mark.vcr(ignore_localhost=True, ignore_hosts=["elasticsearch"]) def test_import_kobo_form( diff --git a/backend/selenium_tests/targeting/test_targeting.py b/backend/selenium_tests/targeting/test_targeting.py index 37dd87d5be..766b41e19f 100644 --- a/backend/selenium_tests/targeting/test_targeting.py +++ b/backend/selenium_tests/targeting/test_targeting.py @@ -229,6 +229,7 @@ def test_smoke_targeting_details_page( assert expected_menu_items == [i.text for i in pageTargetingDetails.getTableLabel()] +@pytest.mark.night @pytest.mark.usefixtures("login") class TestCreateTargeting: def test_create_targeting_for_people( @@ -313,6 +314,7 @@ def test_create_targeting_for_normal_program( assert len(pageTargetingDetails.getHouseholdTableRows()) == 1 +@pytest.mark.night @pytest.mark.usefixtures("login") class TestTargeting: def test_targeting_create_use_ids_hh( diff --git a/deployment/docker-compose.selenium-night.yml b/deployment/docker-compose.selenium-night.yml new file mode 100644 index 0000000000..ffe2d860b7 --- /dev/null +++ b/deployment/docker-compose.selenium-night.yml @@ -0,0 +1,98 @@ +version: '3.7' +volumes: + backend-web-app: +services: + selenium-night: + stdin_open: true + environment: + - REDIS_INSTANCE=redis:6379 + - PYTHONUNBUFFERED=1 + - SECRET_KEY=secretkey + - ENV=dev + - DEBUG=true + - CELERY_BROKER_URL=redis://redis:6379/0 + - CELERY_RESULT_BACKEND=redis://redis:6379/0 + - CACHE_LOCATION=redis://redis:6379/1 + - DATABASE_URL=postgis://postgres:postgres@db:5432/postgres + - DATABASE_URL_HUB_MIS=postgis://postgres:postgres@db:5432/mis_datahub + - DATABASE_URL_HUB_CA=postgis://postgres:postgres@db:5432/ca_datahub + - DATABASE_URL_HUB_ERP=postgis://postgres:postgres@db:5432/erp_datahub + - DATABASE_URL_HUB_REGISTRATION=postgis://postgres:postgres@db:5432/rdi_datahub + - USE_DUMMY_EXCHANGE_RATES=yes + - CELERY_TASK_ALWAYS_EAGER=true + image: ${dev_backend_image} + volumes: + - ../backend:/code/ + - ../backend/report/screenshot/:/code/screenshot/ + - ../backend/report/:/code/report/ + - type: volume + source: backend-web-app + target: /code/hct_mis_api/apps/web + volume: + nocopy: false + command: | + bash -c " + waitforit -host=db -port=5432 -timeout=30 + pytest -svvv selenium_tests --cov-report xml:./coverage.xml --html-report=./report/report.html --randomly-seed=42 + " + depends_on: + db: + condition: service_started + redis: + condition: service_started + elasticsearch: + condition: service_started + init_fe: + condition: service_completed_successfully + + init_fe: + image: ${dist_backend_image} + volumes: + - backend-web-app:/tmp/ + command: | + sh -c " + cp -r ./hct_mis_api/apps/web/* /tmp/ + " + restart: "no" + + + redis: + restart: always + image: redis:4.0.11-alpine3.8 + expose: + - "6379" + + db: + image: kartoza/postgis:14-3 + volumes: + - ./postgres/init:/docker-entrypoint-initdb.d + environment: + - POSTGRES_MULTIPLE_DATABASES=unicef_hct_mis_cashassist,rdi_datahub,mis_datahub,erp_datahub,ca_datahub + - POSTGRES_DB=postgres + - POSTGRES_USER=postgres + - POSTGRES_PASS=postgres + - PGUSER=postgres + - POSTGRES_HOST_AUTH_METHOD=trust + - POSTGRES_SSL_MODE=off + ports: + - "5433:5432" + + elasticsearch: + image: unicef/hct-elasticsearch + container_name: elasticsearch + build: + context: ../elasticsearch + dockerfile: Dockerfile + environment: + - node.name=es01 + - cluster.name=es-docker-cluster + - cluster.initial_master_nodes=es01 + - bootstrap.memory_lock=true + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + - xpack.security.enabled=false + ulimits: + memlock: + soft: -1 + hard: -1 + ports: + - 9200:9200 diff --git a/deployment/docker-compose.selenium.yml b/deployment/docker-compose.selenium.yml index 7d17e14278..c7ab7ae1d6 100644 --- a/deployment/docker-compose.selenium.yml +++ b/deployment/docker-compose.selenium.yml @@ -33,7 +33,7 @@ services: command: | bash -c " waitforit -host=db -port=5432 -timeout=30 - pytest -svvv selenium_tests --cov-report xml:./coverage.xml --html-report=./report/report.html --randomly-seed=42 + pytest -svvv -m 'not night' selenium_tests --cov-report xml:./coverage.xml --html-report=./report/report.html --randomly-seed=42 " depends_on: db: From c0e96a0f7e06c7670a6033331d620fa41d910b7c Mon Sep 17 00:00:00 2001 From: Szymon Wyderka Date: Tue, 20 Aug 2024 11:12:29 +0200 Subject: [PATCH 033/118] Init --- .../programme_management/test_programme_management.py | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/selenium_tests/programme_management/test_programme_management.py b/backend/selenium_tests/programme_management/test_programme_management.py index d80cec7adc..a2f59723c8 100644 --- a/backend/selenium_tests/programme_management/test_programme_management.py +++ b/backend/selenium_tests/programme_management/test_programme_management.py @@ -656,7 +656,6 @@ def test_create_programme_back_scenarios( @pytest.mark.night @pytest.mark.usefixtures("login") class TestManualCalendar: - @pytest.mark.skip(reason="ToDo") @pytest.mark.parametrize( "test_data", [ From e06a2ccd664d43c1746d9b9459a6b749f79fea22 Mon Sep 17 00:00:00 2001 From: marekbiczysko Date: Tue, 20 Aug 2024 11:41:22 +0200 Subject: [PATCH 034/118] backmerge fix --- .../apps/payment/migrations/0142_migration.py | 208 +----------------- 1 file changed, 1 insertion(+), 207 deletions(-) diff --git a/backend/hct_mis_api/apps/payment/migrations/0142_migration.py b/backend/hct_mis_api/apps/payment/migrations/0142_migration.py index 5392012efd..80834a1283 100644 --- a/backend/hct_mis_api/apps/payment/migrations/0142_migration.py +++ b/backend/hct_mis_api/apps/payment/migrations/0142_migration.py @@ -1,84 +1,18 @@ -<<<<<<< HEAD # Generated by Django 3.2.25 on 2024-08-05 13:12 from django.db import migrations, models import django.db.models.deletion -======= -# Generated by Django 3.2.25 on 2024-07-17 13:59 - -from django.db import migrations, models -import django.db.models.deletion -import hct_mis_api.apps.payment.fields - - -def data_migration_financialserviceprovider_delivery_mechanisms(apps, schema_editor): - FinancialServiceProvider = apps.get_model("payment", "FinancialServiceProvider") - DeliveryMechanism = apps.get_model("payment", "DeliveryMechanism") - dms = list(DeliveryMechanism.objects.all()) - - fsps = FinancialServiceProvider.objects.exclude(delivery_mechanisms_choices__isnull=True) - for fsp in fsps: - fsp_dms = [dm for dm in dms if dm.name in fsp.delivery_mechanisms_choices] - fsp.delivery_mechanisms.set(fsp_dms) - - -def data_migration_deliverymechanismdata_delivery_mechanism(apps, schema_editor): - DeliveryMechanismData = apps.get_model("payment", "DeliveryMechanismData") - DeliveryMechanism = apps.get_model("payment", "DeliveryMechanism") - - for dm in DeliveryMechanism.objects.all(): - DeliveryMechanismData.objects.filter(delivery_mechanism_choice=dm.name).update(delivery_mechanism=dm) - - -def data_migration_deliverymechanismperpaymentplan_delivery_mechanism(apps, schema_editor): - DeliveryMechanismPerPaymentPlan = apps.get_model("payment", "DeliveryMechanismPerPaymentPlan") - DeliveryMechanism = apps.get_model("payment", "DeliveryMechanism") - - for dm in DeliveryMechanism.objects.all(): - DeliveryMechanismPerPaymentPlan.objects.filter(delivery_mechanism_choice=dm.name).update(delivery_mechanism=dm) - - -def data_migration_fspxlsxtemplateperdeliverymechanism_delivery_mechanism(apps, schema_editor): - FspXlsxTemplatePerDeliveryMechanism = apps.get_model("payment", "FspXlsxTemplatePerDeliveryMechanism") - DeliveryMechanism = apps.get_model("payment", "DeliveryMechanism") - - for dm in DeliveryMechanism.objects.all(): - FspXlsxTemplatePerDeliveryMechanism.objects.filter(delivery_mechanism_choice=dm.name).update( - delivery_mechanism=dm - ) - - -def data_migration_payment_delivery_type(apps, schema_editor): - Payment = apps.get_model("payment", "Payment") - DeliveryMechanism = apps.get_model("payment", "DeliveryMechanism") - - for dm in DeliveryMechanism.objects.all(): - Payment.objects.filter(delivery_type_choice=dm.name).update(delivery_type=dm) - - -def data_migration_payment_record_delivery_type(apps, schema_editor): - PaymentRecord = apps.get_model("payment", "PaymentRecord") - DeliveryMechanism = apps.get_model("payment", "DeliveryMechanism") - - for dm in DeliveryMechanism.objects.all(): - PaymentRecord.objects.filter(delivery_type_choice=dm.name).update(delivery_type=dm) ->>>>>>> origin/staging class Migration(migrations.Migration): dependencies = [ -<<<<<<< HEAD ('program', '0049_migration'), - ('payment', '0140_migration'), -======= - ("payment", "0140_migration"), ->>>>>>> origin/staging + ('payment', '0141_migration'), ] operations = [ migrations.AlterField( -<<<<<<< HEAD model_name='cashplan', name='end_date', field=models.DateTimeField(blank=True, db_index=True, null=True), @@ -102,145 +36,5 @@ class Migration(migrations.Migration): model_name='paymentplan', name='start_date', field=models.DateTimeField(blank=True, db_index=True, null=True), -======= - model_name="financialserviceproviderxlsxtemplate", - name="core_fields", - field=hct_mis_api.apps.payment.fields.DynamicChoiceArrayField( - base_field=models.CharField(blank=True, max_length=255), blank=True, default=list, size=None - ), - ), - migrations.RenameField( - model_name="financialserviceprovider", - old_name="delivery_mechanisms", - new_name="delivery_mechanisms_choices", - ), - migrations.AlterField( - model_name="financialserviceprovider", - name="delivery_mechanisms_choices", - field=hct_mis_api.apps.account.models.HorizontalChoiceArrayField( - base_field=models.CharField( - choices=[ - ("Cardless cash withdrawal", "Cardless cash withdrawal"), - ("Cash", "Cash"), - ("Cash by FSP", "Cash by FSP"), - ("Cheque", "Cheque"), - ("Deposit to Card", "Deposit to Card"), - ("Mobile Money", "Mobile Money"), - ("Pre-paid card", "Pre-paid card"), - ("Referral", "Referral"), - ("Transfer", "Transfer"), - ("Transfer to Account", "Transfer to Account"), - ("Voucher", "Voucher"), - ("ATM Card", "ATM Card"), - ("Cash over the counter", "Cash over the counter"), - ("Transfer to Digital Wallet", "Transfer to Digital Wallet"), - ], - max_length=32, - ), - null=True, - size=None, - ), - ), - migrations.AddField( - model_name="financialserviceprovider", - name="delivery_mechanisms", - field=models.ManyToManyField(to="payment.DeliveryMechanism"), - ), - migrations.RunPython( - data_migration_financialserviceprovider_delivery_mechanisms, - migrations.RunPython.noop, - ), - migrations.RenameField( - model_name="deliverymechanismdata", - old_name="delivery_mechanism", - new_name="delivery_mechanism_choice", - ), - migrations.AddField( - model_name="deliverymechanismdata", - name="delivery_mechanism", - field=models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, to="payment.deliverymechanism", null=True - ), - ), - migrations.RunPython( - data_migration_deliverymechanismdata_delivery_mechanism, - migrations.RunPython.noop, - ), - migrations.AlterField( - model_name="deliverymechanismdata", - name="delivery_mechanism", - field=models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - to="payment.deliverymechanism", - ), - ), - migrations.RenameField( - model_name="deliverymechanismperpaymentplan", - old_name="delivery_mechanism", - new_name="delivery_mechanism_choice", - ), - migrations.AddField( - model_name="deliverymechanismperpaymentplan", - name="delivery_mechanism", - field=models.ForeignKey( - on_delete=django.db.models.deletion.SET_NULL, to="payment.deliverymechanism", null=True - ), - ), - migrations.RunPython( - data_migration_deliverymechanismperpaymentplan_delivery_mechanism, - migrations.RunPython.noop, - ), - migrations.RenameField( - model_name="fspxlsxtemplateperdeliverymechanism", - old_name="delivery_mechanism", - new_name="delivery_mechanism_choice", - ), - migrations.AddField( - model_name="fspxlsxtemplateperdeliverymechanism", - name="delivery_mechanism", - field=models.ForeignKey( - on_delete=django.db.models.deletion.SET_NULL, to="payment.deliverymechanism", null=True - ), - ), - migrations.RunPython( - data_migration_fspxlsxtemplateperdeliverymechanism_delivery_mechanism, - migrations.RunPython.noop, - ), - migrations.AlterUniqueTogether( - name="fspxlsxtemplateperdeliverymechanism", - unique_together={("financial_service_provider", "delivery_mechanism")}, - ), - migrations.RenameField( - model_name="payment", - old_name="delivery_type", - new_name="delivery_type_choice", - ), - migrations.RenameField( - model_name="paymentrecord", - old_name="delivery_type", - new_name="delivery_type_choice", - ), - migrations.AddField( - model_name="payment", - name="delivery_type", - field=models.ForeignKey( - on_delete=django.db.models.deletion.SET_NULL, to="payment.deliverymechanism", null=True - ), - ), - migrations.AddField( - model_name="paymentrecord", - name="delivery_type", - field=models.ForeignKey( - on_delete=django.db.models.deletion.SET_NULL, to="payment.deliverymechanism", null=True - ), - ), - migrations.RunPython( - data_migration_payment_delivery_type, - migrations.RunPython.noop, - ), - migrations.RunPython( - data_migration_payment_record_delivery_type, - migrations.RunPython.noop, ->>>>>>> origin/staging ), ] From 769e5a9b220aae73e6246fff01339ce18ef4d2a1 Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Tue, 20 Aug 2024 11:44:23 +0200 Subject: [PATCH 035/118] add targetcriteriaform validation --- .../src/components/targeting/SubField.tsx | 1 + .../forms/TargetingCriteriaForm.tsx | 28 +++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/targeting/SubField.tsx b/frontend/src/components/targeting/SubField.tsx index 2f67976100..b033d6cedd 100644 --- a/frontend/src/components/targeting/SubField.tsx +++ b/frontend/src/components/targeting/SubField.tsx @@ -245,6 +245,7 @@ export function SubField({ { + const parent = schema.parent; + console.log('Parent:', parent); + if ( + parent && + parent.fieldAttribute && + parent.fieldAttribute.type === 'PDU' + ) { + console.log('Round Number is required'); + return Yup.string().required('Round Number is required'); + } + console.log('Round Number is not required'); + return Yup.string().notRequired(); + }, + ), }), ), }), ), }); + interface ArrayFieldWrapperProps { arrayHelpers; children: React.ReactNode; @@ -145,7 +168,7 @@ export const TargetingCriteriaForm = ({ individualsFiltersBlocks, }): { nonFieldErrors?: string[] } => { const filterNullOrNoSelections = (filter): boolean => - filter.fieldAttribute?.type !== 'PDU' && + !filter.isNull && (filter.value === null || filter.value === '' || (filter?.fieldAttribute?.type === 'SELECT_MANY' && @@ -153,7 +176,7 @@ export const TargetingCriteriaForm = ({ filter.value.length === 0)); const filterEmptyFromTo = (filter): boolean => - filter.fieldAttribute?.type !== 'PDU' && + !filter.isNull && typeof filter.value === 'object' && filter.value !== null && Object.prototype.hasOwnProperty.call(filter.value, 'from') && @@ -174,6 +197,7 @@ export const TargetingCriteriaForm = ({ const hasIndividualsFiltersBlocksErrors = individualsFiltersBlocks.some( (block) => { + console.log('xxxblock', block); const hasNulls = block.individualBlockFilters.some( filterNullOrNoSelections, ); From 1f704b97bb8cafdf4df112d32d63d8d97b9d5629 Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Tue, 20 Aug 2024 11:46:15 +0200 Subject: [PATCH 036/118] remove console logs --- frontend/src/components/targeting/SubField.tsx | 1 - frontend/src/containers/forms/TargetingCriteriaForm.tsx | 4 ---- frontend/src/utils/targetingUtils.ts | 1 - 3 files changed, 6 deletions(-) diff --git a/frontend/src/components/targeting/SubField.tsx b/frontend/src/components/targeting/SubField.tsx index b033d6cedd..22dfa50103 100644 --- a/frontend/src/components/targeting/SubField.tsx +++ b/frontend/src/components/targeting/SubField.tsx @@ -55,7 +55,6 @@ export function SubField({ } const renderFieldByType = (type) => { - console.log('field', type); switch (type) { case 'DECIMAL': return ( diff --git a/frontend/src/containers/forms/TargetingCriteriaForm.tsx b/frontend/src/containers/forms/TargetingCriteriaForm.tsx index f2e3244c4c..1d42b30bfa 100644 --- a/frontend/src/containers/forms/TargetingCriteriaForm.tsx +++ b/frontend/src/containers/forms/TargetingCriteriaForm.tsx @@ -65,16 +65,13 @@ const validationSchema = Yup.object().shape({ ['fieldName', 'fieldAttribute'], (fieldName, fieldAttribute, schema) => { const parent = schema.parent; - console.log('Parent:', parent); if ( parent && parent.fieldAttribute && parent.fieldAttribute.type === 'PDU' ) { - console.log('Round Number is required'); return Yup.string().required('Round Number is required'); } - console.log('Round Number is not required'); return Yup.string().notRequired(); }, ), @@ -197,7 +194,6 @@ export const TargetingCriteriaForm = ({ const hasIndividualsFiltersBlocksErrors = individualsFiltersBlocks.some( (block) => { - console.log('xxxblock', block); const hasNulls = block.individualBlockFilters.some( filterNullOrNoSelections, ); diff --git a/frontend/src/utils/targetingUtils.ts b/frontend/src/utils/targetingUtils.ts index db9956e26e..a7b66f7239 100644 --- a/frontend/src/utils/targetingUtils.ts +++ b/frontend/src/utils/targetingUtils.ts @@ -233,7 +233,6 @@ function mapFilterToVariable(filter): { flexFieldClassification: string; roundNumber?: number; } { - console.log('xxxfilter', filter); const result = { comparisonMethod: filter.isNull ? 'IS_NULL' : filter.comparisonMethod, arguments: filter.isNull ? [null] : filter.arguments, From ecdd43c251c0c499e41cd2e880a33185987319b9 Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Tue, 20 Aug 2024 12:53:47 +0200 Subject: [PATCH 037/118] display value only if elements inside are there --- .../targeting/TargetingCriteriaDisplay/Criteria.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx index 4c01089fb9..154f7d6d65 100644 --- a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx +++ b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx @@ -169,11 +169,14 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { return ( <> {fieldElement} - {field.type === 'PDU' && field.pduData && ( - - {field.round}-{field.pduData.roundsNames?.[field.round - 1]} - - )} + {field.type === 'PDU' && + field.pduData && + field.round && + field.pduData.roundsNames?.[field.round - 1] && ( + + {field.round}-{field.pduData.roundsNames[field.round - 1]} + + )} ); }; From 081d65fa70b3a00098471164a04c577e05e9d3ee Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Tue, 20 Aug 2024 13:27:24 +0200 Subject: [PATCH 038/118] remove exist doesnt exist --- .../src/components/targeting/SubField.tsx | 38 ++++++------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/frontend/src/components/targeting/SubField.tsx b/frontend/src/components/targeting/SubField.tsx index 22dfa50103..d0352928ea 100644 --- a/frontend/src/components/targeting/SubField.tsx +++ b/frontend/src/components/targeting/SubField.tsx @@ -184,33 +184,17 @@ export function SubField({ /> ); case 'STRING': - if (field.pduData) { - return ( - - ); - } else { - return ( - - ); - } + return ( + + ); case 'BOOL': return ( Date: Tue, 20 Aug 2024 13:33:59 +0200 Subject: [PATCH 039/118] pdu ut fix --- .../snapshots/snap_test_update_program.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_update_program.py b/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_update_program.py index 29904c3822..65067a2ce1 100644 --- a/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_update_program.py +++ b/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_update_program.py @@ -52,6 +52,17 @@ } ], 'pduFields': [ + { + 'label': '{"English(EN)": "PDU Field To Be Preserved"}', + 'name': 'pdu_field_to_be_preserved', + 'pduData': { + 'numberOfRounds': 1, + 'roundsNames': [ + 'Round To Be Preserved' + ], + 'subtype': 'DATE' + } + }, { 'label': '{"English(EN)": "PDU Field To Be Removed"}', 'name': 'pdu_field_to_be_removed', @@ -76,17 +87,6 @@ ], 'subtype': 'STRING' } - }, - { - 'label': '{"English(EN)": "PDU Field To Be Preserved"}', - 'name': 'pdu_field_to_be_preserved', - 'pduData': { - 'numberOfRounds': 1, - 'roundsNames': [ - 'Round To Be Preserved' - ], - 'subtype': 'DATE' - } } ], 'status': 'FINISHED' From c42f10d41648ee2dc0005b2cdc89d52dad92ea85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Wo=C5=BAniak?= <17177420+wozniakpl@users.noreply.github.com> Date: Tue, 20 Aug 2024 14:07:06 +0200 Subject: [PATCH 040/118] Add nightly e2e pipeline (#4149) --- .github/workflows/ci.yml | 35 +++++++++++-- .github/workflows/nightly.yml | 53 ++++++++++++++++++++ deployment/docker-compose.selenium-night.yml | 2 +- 3 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/nightly.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd6cce98b3..02d68ceaa4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,16 +59,30 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Determine Branch Name + id: branch_name + run: | + if [ "${{ github.event_name }}" = "push" ]; then + echo "BRANCH_NAME=${GITHUB_REF##*/}" >> $GITHUB_ENV + fi + - name: Push dev run: | docker buildx create --use + + tags="-t ${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:core-${{ github.sha }}-dev \ + -t ${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:core-latest-dev" + + if [ -n "${{ env.BRANCH_NAME }}" ]; then + tags="$tags -t ${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:core-${{ env.BRANCH_NAME }}-latest-dev" + fi + docker buildx build \ --cache-from ${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:cache-core-${{ github.sha }}-dev \ --cache-from ${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:cache-core-latest-dev \ --cache-to ${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:cache-core-${{ github.sha }}-dev \ --cache-to ${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:cache-core-latest-dev \ - -t ${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:core-${{ github.sha }}-dev \ - -t ${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:core-latest-dev \ + $tags \ -f ./docker/Dockerfile \ --target dev \ --push \ @@ -160,10 +174,24 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Determine Branch Name + id: branch_name + run: | + if [ "${{ github.event_name }}" = "push" ]; then + echo "BRANCH_NAME=${GITHUB_REF##*/}" >> $GITHUB_ENV + fi + - name: Push dist run: | docker buildx create --use + tags="-t ${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:core-${{ github.sha }}-dist \ + -t ${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:core-${{ github.sha }}" + + if [ -n "${{ env.BRANCH_NAME }}" ]; then + tags="$tags -t ${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:core-${{ env.BRANCH_NAME }}-latest-dist" + fi + # Base part of the command build_command="docker buildx build \ --progress=plain \ @@ -173,8 +201,7 @@ jobs: --cache-from ${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:cache-core-latest-dist \ --cache-to ${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:cache-core-${{ github.sha }}-dist \ --cache-to ${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:cache-core-latest-dist \ - -t ${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:core-${{ github.sha }}-dist \ - -t ${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:core-${{ github.sha }} \ + $tags \ -f ./docker/Dockerfile \ --target dist \ --push ./" diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 0000000000..e1cf0389f6 --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,53 @@ +name: Nightly E2E Tests + +on: + workflow_dispatch: + schedule: + # Run at 2:00 AM every day + - cron: '0 2 * * *' + +jobs: + e2e_tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: DockerHub login + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Pull Latest Docker Images + run: | + docker pull ${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:core-develop-latest-dist + docker pull ${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:core-develop-latest-dev + + - name: Run Selenium Nightly E2E tests + run: | + dist_backend_image=${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:core-develop-latest-dist \ + dev_backend_image=${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:core-develop-latest-dev \ + docker compose \ + -f ./deployment/docker-compose.selenium-night.yml \ + run selenium + + - name: Upload Nightly Test Artifacts + uses: actions/upload-artifact@v4 + if: always() + continue-on-error: true + with: + name: nightly-e2e-report + path: ./backend/report/ + retention-days: 5 + + - name: Upload Nightly Coverage to Codecov + uses: codecov/codecov-action@v4 + if: always() + continue-on-error: true + with: + files: ./backend/coverage.xml + flags: nightly-e2e + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true diff --git a/deployment/docker-compose.selenium-night.yml b/deployment/docker-compose.selenium-night.yml index ffe2d860b7..7d17e14278 100644 --- a/deployment/docker-compose.selenium-night.yml +++ b/deployment/docker-compose.selenium-night.yml @@ -2,7 +2,7 @@ version: '3.7' volumes: backend-web-app: services: - selenium-night: + selenium: stdin_open: true environment: - REDIS_INSTANCE=redis:6379 From 174ed73dff76a08de8ff11cfff69c3ee01ddcb7c Mon Sep 17 00:00:00 2001 From: Patryk Dabrowski Date: Tue, 20 Aug 2024 14:30:52 +0200 Subject: [PATCH 041/118] Fix targeting results --- .../services/targeting_stats_refresher.py | 56 ++++++++----------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/backend/hct_mis_api/apps/targeting/services/targeting_stats_refresher.py b/backend/hct_mis_api/apps/targeting/services/targeting_stats_refresher.py index dbf3022ad2..c41e0220f0 100644 --- a/backend/hct_mis_api/apps/targeting/services/targeting_stats_refresher.py +++ b/backend/hct_mis_api/apps/targeting/services/targeting_stats_refresher.py @@ -1,44 +1,34 @@ -from django.db.models import Count, Sum -from django.db.models.functions import Coalesce +from datetime import datetime + +from django.db.models import Count, Q from django.utils import timezone -from hct_mis_api.apps.household.models import Household +from dateutil.relativedelta import relativedelta + +from hct_mis_api.apps.household.models import FEMALE, MALE, Household, Individual from hct_mis_api.apps.targeting.models import TargetPopulation def refresh_stats(target_population: TargetPopulation) -> TargetPopulation: - targeting_details = target_population.household_list.annotate( - child_male=Coalesce("male_age_group_0_5_count", 0) - + Coalesce("male_age_group_6_11_count", 0) - + Coalesce("male_age_group_12_17_count", 0) - + Coalesce("male_age_group_0_5_disabled_count", 0) - + Coalesce("male_age_group_6_11_disabled_count", 0) - + Coalesce("male_age_group_12_17_disabled_count", 0), - child_female=Coalesce("female_age_group_0_5_count", 0) - + Coalesce("female_age_group_6_11_count", 0) - + Coalesce("female_age_group_12_17_count", 0) - + Coalesce("female_age_group_0_5_disabled_count", 0) - + Coalesce("female_age_group_6_11_disabled_count", 0) - + Coalesce("female_age_group_12_17_disabled_count", 0), - adult_male=Coalesce("male_age_group_18_59_count", 0) - + Coalesce("male_age_group_60_count", 0) - + Coalesce("male_age_group_18_59_disabled_count", 0) - + Coalesce("male_age_group_60_disabled_count", 0), - adult_female=Coalesce("female_age_group_18_59_count", 0) - + Coalesce("female_age_group_60_count", 0) - + Coalesce("female_age_group_18_59_disabled_count", 0) - + Coalesce("female_age_group_60_disabled_count", 0), - ).aggregate( - child_male_count=Sum("child_male"), - child_female_count=Sum("child_female"), - adult_male_count=Sum("adult_male"), - adult_female_count=Sum("adult_female"), - total_individuals_count=Sum("size"), - total_households_count=Count("id"), + households_ids = target_population.household_list.values_list("id", flat=True) + + delta18 = relativedelta(years=+18) + date18ago = datetime.now() - delta18 + + targeted_individuals = Individual.objects.filter(household__id__in=households_ids).aggregate( + child_male_count=Count("id", distinct=True, filter=Q(birth_date__gt=date18ago, sex=MALE)), + child_female_count=Count("id", distinct=True, filter=Q(birth_date__gt=date18ago, sex=FEMALE)), + adult_male_count=Count("id", distinct=True, filter=Q(birth_date__lte=date18ago, sex=MALE)), + adult_female_count=Count("id", distinct=True, filter=Q(birth_date__lte=date18ago, sex=FEMALE)), + total_individuals_count=Count("id", distinct=True), ) - for key, value in targeting_details.items(): - setattr(target_population, key, value) + target_population.child_male_count = targeted_individuals["child_male_count"] + target_population.child_female_count = targeted_individuals["child_female_count"] + target_population.adult_male_count = targeted_individuals["adult_male_count"] + target_population.adult_female_count = targeted_individuals["adult_female_count"] + target_population.total_individuals_count = targeted_individuals["total_individuals_count"] + target_population.total_households_count = households_ids.count() target_population.build_status = TargetPopulation.BUILD_STATUS_OK target_population.built_at = timezone.now() From d996f9811acc9b727c3e0fc7ae96399a8b39558c Mon Sep 17 00:00:00 2001 From: Szymon Wyderka Date: Tue, 20 Aug 2024 14:46:33 +0200 Subject: [PATCH 042/118] Added test_program_details_edit_default_cycle_by_add_new, test_program_details_check_default_cycle --- .../programme_details/programme_details.py | 85 +++++++++++ .../program_details/test_program_details.py | 134 ++++++++++++++++-- .../NewProgramCycle/CreateProgramCycle.tsx | 4 +- .../tables/ProgramCycle/ProgramCycleTable.tsx | 18 +-- 4 files changed, 218 insertions(+), 23 deletions(-) diff --git a/backend/selenium_tests/page_object/programme_details/programme_details.py b/backend/selenium_tests/page_object/programme_details/programme_details.py index f2d8a6cff1..9a3e546495 100644 --- a/backend/selenium_tests/page_object/programme_details/programme_details.py +++ b/backend/selenium_tests/page_object/programme_details/programme_details.py @@ -29,6 +29,91 @@ class ProgrammeDetails(BaseComponents): tableTitle = 'h6[data-cy="table-title"]' buttonAddNewProgrammeCycle = 'button[data-cy="button-add-new-programme-cycle"]' tablePagination = 'div[data-cy="table-pagination"]' + programCycleRow = 'tr[data-cy="program-cycle-row"]' + programCycleId = 'td[data-cy="program-cycle-id"]' + programCycleTitle = 'td[data-cy="program-cycle-title"]' + programCycleStatus = 'td[data-cy="program-cycle-status"]' + statusContainer = 'div[data-cy="status-container"]' + programCycleTotalEntitledQuantity = 'td[data-cy="program-cycle-total-entitled-quantity"]' + programCycleTotalUndeliveredQuantity = 'td[data-cy="program-cycle-total-undelivered-quantity"]' + programCycleTotalDeliveredQuantity = 'td[data-cy="program-cycle-total-delivered-quantity"]' + programCycleStartDate = 'td[data-cy="program-cycle-start-date"]' + programCycleEndDate = 'td[data-cy="program-cycle-end-date"]' + programCycleDetailsBtn = 'td[data-cy="program-cycle-details-btn"]' + buttonEditProgramCycle = 'button[data-cy="button-edit-program-cycle"]' + startDateCycle = 'div[data-cy="start-date-cycle"]' + dataPickerFilter = 'div[data-cy="date-picker-filter"]' + endDateCycle = 'div[data-cy="end-date-cycle"]' + buttonNext = 'button[data-cy="button-update-program-cycle-modal"]' + buttonCreateProgramCycle = 'button[data-cy="button-create-program-cycle"]' + inputTitle = 'input[data-cy="input-title"]' + + def getProgramCycleRow(self) -> WebElement: + self.wait_for(self.programCycleRow) + return self.get_elements(self.programCycleRow) + + def getProgramCycleId(self) -> WebElement: + self.wait_for(self.programCycleId) + return self.get_elements(self.programCycleId) + + def getProgramCycleTitle(self) -> WebElement: + self.wait_for(self.programCycleTitle) + return self.get_elements(self.programCycleTitle) + + def getProgramCycleStatus(self) -> WebElement: + self.wait_for(self.programCycleStatus) + return self.get_elements(self.programCycleStatus) + + def getStatusContainer(self) -> WebElement: + self.wait_for(self.statusContainer) + return self.get_elements(self.statusContainer) + + def getProgramCycleTotalEntitledQuantity(self) -> WebElement: + self.wait_for(self.programCycleTotalEntitledQuantity) + return self.get_elements(self.programCycleTotalEntitledQuantity) + + def getProgramCycleTotalUndeliveredQuantity(self) -> WebElement: + self.wait_for(self.programCycleTotalUndeliveredQuantity) + return self.get_elements(self.programCycleTotalUndeliveredQuantity) + + def getProgramCycleTotalDeliveredQuantity(self) -> WebElement: + self.wait_for(self.programCycleTotalDeliveredQuantity) + return self.get_elements(self.programCycleTotalDeliveredQuantity) + + def getProgramCycleStartDate(self) -> WebElement: + self.wait_for(self.programCycleStartDate) + return self.get_elements(self.programCycleStartDate) + + def getProgramCycleEndDate(self) -> WebElement: + self.wait_for(self.programCycleEndDate) + return self.get_elements(self.programCycleEndDate) + + def getProgramCycleDetailsBtn(self) -> WebElement: + self.wait_for(self.programCycleDetailsBtn) + return self.get_elements(self.programCycleDetailsBtn) + + def getButtonEditProgramCycle(self) -> WebElement: + self.wait_for(self.buttonEditProgramCycle) + return self.get_elements(self.buttonEditProgramCycle) + + def getDataPickerFilter(self) -> WebElement: + self.wait_for(self.dataPickerFilter) + return self.get_elements(self.dataPickerFilter)[0].find_elements("tag name", "input")[0] + + def getButtonNext(self) -> WebElement: + return self.wait_for(self.buttonNext) + + def getInputTitle(self) -> WebElement: + return self.wait_for(self.inputTitle) + + def getStartDateCycle(self) -> WebElement: + return self.wait_for(self.startDateCycle).find_elements("tag name", "input")[0] + + def getEndDateCycle(self) -> WebElement: + return self.wait_for(self.endDateCycle).find_elements("tag name", "input")[0] + + def getButtonCreateProgramCycle(self) -> WebElement: + return self.wait_for(self.buttonCreateProgramCycle) def getLabelPartnerName(self) -> WebElement: return self.wait_for(self.labelPartnerName) diff --git a/backend/selenium_tests/program_details/test_program_details.py b/backend/selenium_tests/program_details/test_program_details.py index 55cb453039..5974d1a93e 100644 --- a/backend/selenium_tests/program_details/test_program_details.py +++ b/backend/selenium_tests/program_details/test_program_details.py @@ -36,7 +36,10 @@ def standard_program() -> Program: def get_program_with_dct_type_and_name( - name: str, programme_code: str, dct_type: str = DataCollectingType.Type.STANDARD, status: str = Program.DRAFT + name: str, programme_code: str, dct_type: str = DataCollectingType.Type.STANDARD, status: str = Program.DRAFT, + program_cycle_status: str = ProgramCycle.FINISHED, + cycle_start_date: datetime = datetime.now() - relativedelta(days=25), + cycle_end_date: datetime = datetime.now() + relativedelta(days=10), ) -> Program: BusinessArea.objects.filter(slug="afghanistan").update(is_payment_plan_applicable=True) dct = DataCollectingTypeFactory(type=dct_type) @@ -47,9 +50,41 @@ def get_program_with_dct_type_and_name( end_date=datetime.now() + relativedelta(months=1), data_collecting_type=dct, status=status, - cycle__status=ProgramCycle.FINISHED, - cycle__start_date=datetime.now() - relativedelta(days=25), - cycle__end_date=datetime.now() + relativedelta(days=10), + cycle__status=program_cycle_status, + cycle__start_date=cycle_start_date, + cycle__end_date=cycle_end_date, + ) + return program + + +@pytest.fixture +def standard_program_with_draft_programme_cycle() -> Program: + yield get_program_without_cycle_end_date("Active Programme", "9876", status=Program.ACTIVE, + program_cycle_status=ProgramCycle.DRAFT) + +@pytest.fixture +def standard_active_program() -> Program: + yield get_program_with_dct_type_and_name("Active Programme", "9876", status=Program.ACTIVE, + program_cycle_status=ProgramCycle.FINISHED) + +def get_program_without_cycle_end_date( + name: str, programme_code: str, dct_type: str = DataCollectingType.Type.STANDARD, status: str = Program.ACTIVE, + program_cycle_status: str = ProgramCycle.FINISHED, + cycle_start_date: datetime = datetime.now() - relativedelta(days=25), +) -> Program: + BusinessArea.objects.filter(slug="afghanistan").update(is_payment_plan_applicable=True) + dct = DataCollectingTypeFactory(type=dct_type) + program = ProgramFactory( + name=name, + programme_code=programme_code, + start_date=datetime.now() - relativedelta(months=1), + end_date=datetime.now() + relativedelta(months=1), + data_collecting_type=dct, + status=status, + cycle__title="Default Programme Cycle", + cycle__status=program_cycle_status, + cycle__start_date=cycle_start_date, + cycle__end_date=None, ) return program @@ -116,7 +151,7 @@ def create_programs() -> None: @pytest.mark.usefixtures("login") -class TestProgrammeDetails: +class TestSmokeProgrammeDetails: def test_program_details(self, standard_program: Program, pageProgrammeDetails: ProgrammeDetails) -> None: program = Program.objects.get(name="Test For Edit") # Go to Programme Details @@ -140,8 +175,8 @@ def test_program_details(self, standard_program: Program, pageProgrammeDetails: assert program.sector.replace("_", " ").title() in pageProgrammeDetails.getLabelSelector().text assert program.data_collecting_type.label in pageProgrammeDetails.getLabelDataCollectingType().text assert ( - program.frequency_of_payments.replace("_", "-").capitalize() - in pageProgrammeDetails.getLabelFreqOfPayment().text + program.frequency_of_payments.replace("_", "-").capitalize() + in pageProgrammeDetails.getLabelFreqOfPayment().text ) assert program.administrative_areas_of_implementation in pageProgrammeDetails.getLabelAdministrativeAreas().text assert program.description in pageProgrammeDetails.getLabelDescription().text @@ -151,10 +186,10 @@ def test_program_details(self, standard_program: Program, pageProgrammeDetails: @pytest.mark.skip("Unskip after fix bug") def test_edit_programme_from_details( - self, - create_programs: None, - pageProgrammeDetails: ProgrammeDetails, - pageProgrammeManagement: ProgrammeManagement, + self, + create_programs: None, + pageProgrammeDetails: ProgrammeDetails, + pageProgrammeManagement: ProgrammeManagement, ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Test Programm") pageProgrammeDetails.getButtonEditProgram().click() @@ -180,7 +215,7 @@ def test_edit_programme_from_details( assert FormatTime(1, 10, 2022).date_in_text_format in pageProgrammeDetails.getLabelEndDate().text def test_program_details_happy_path( - self, create_payment_plan: Program, pageProgrammeDetails: ProgrammeDetails + self, create_payment_plan: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Test For Edit") assert "DRAFT" in pageProgrammeDetails.getProgramStatus().text @@ -207,3 +242,78 @@ def test_program_details_happy_path( else: assert "FINISHED" in pageProgrammeDetails.getProgramStatus().text assert "1" in pageProgrammeDetails.getLabelProgramSize().text + + +@pytest.mark.usefixtures("login") +class TestProgrammeDetails: + + def test_program_details_check_default_cycle(self, pageProgrammeManagement: ProgrammeManagement, + pageProgrammeDetails: ProgrammeDetails + ) -> None: + # Go to Programme Management + pageProgrammeManagement.getNavProgrammeManagement().click() + # Create Programme + pageProgrammeManagement.getButtonNewProgram().click() + pageProgrammeManagement.getInputProgrammeName().send_keys("Test 1234 Program") + pageProgrammeManagement.getInputStartDate().click() + pageProgrammeManagement.getInputStartDate().send_keys(FormatTime(1, 1, 2022).numerically_formatted_date) + pageProgrammeManagement.getInputEndDate().click() + pageProgrammeManagement.getInputEndDate().send_keys(FormatTime(1, 2, 2032).numerically_formatted_date) + pageProgrammeManagement.chooseOptionSelector("Health") + pageProgrammeManagement.chooseOptionDataCollectingType("Partial") + pageProgrammeManagement.getButtonNext().click() + # 2nd step (Time Series Fields) + pageProgrammeManagement.getButtonAddTimeSeriesField() + pageProgrammeManagement.getButtonNext().click() + # 3rd step (Partners) + programme_creation_url = pageProgrammeManagement.driver.current_url + pageProgrammeManagement.getButtonSave().click() + # Check Details page + assert "details" in pageProgrammeDetails.wait_for_new_url(programme_creation_url).split("/") + pageProgrammeDetails.getButtonActivateProgram().click() + pageProgrammeDetails.getButtonActivateProgramModal().click() + assert 1 == len(pageProgrammeDetails.getProgramCycleRow()) + assert "Draft" in pageProgrammeDetails.getProgramCycleStatus()[0].text + assert "-" in pageProgrammeDetails.getProgramCycleEndDate()[0].text + assert "Default Programme Cycle" in pageProgrammeDetails.getProgramCycleTitle()[0].text + + def test_program_details_edit_default_cycle_by_add_new( + self, standard_program_with_draft_programme_cycle: Program, pageProgrammeDetails: ProgrammeDetails + ) -> None: + pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") + assert "ACTIVE" in pageProgrammeDetails.getProgramStatus().text + assert "0" in pageProgrammeDetails.getLabelProgramSize().text + assert "Programme Cycles" in pageProgrammeDetails.getTableTitle().text + pageProgrammeDetails.getButtonAddNewProgrammeCycle().click() + pageProgrammeDetails.getDataPickerFilter().click() + pageProgrammeDetails.getDataPickerFilter().send_keys(datetime.now().strftime("%Y-%m-%d")) + pageProgrammeDetails.getButtonNext().click() + pageProgrammeDetails.getInputTitle().send_keys("Test Title") + pageProgrammeDetails.getStartDateCycle().click() + pageProgrammeDetails.getStartDateCycle().send_keys( + (datetime.now() + relativedelta(days=1)).strftime("%Y-%m-%d")) + pageProgrammeDetails.getEndDateCycle().click() + pageProgrammeDetails.getEndDateCycle().send_keys( + (datetime.now() + relativedelta(days=1)).strftime("%Y-%m-%d")) + pageProgrammeDetails.getButtonCreateProgramCycle().click() + pageProgrammeDetails.getProgramCycleRow() + assert 2 == len(pageProgrammeDetails.getProgramCycleRow()) + + assert "Draft" in pageProgrammeDetails.getProgramCycleStatus()[0].text + assert datetime.now().strftime("%-d %b %Y") in pageProgrammeDetails.getProgramCycleEndDate()[0].text + assert "Default Programme Cycle" in pageProgrammeDetails.getProgramCycleTitle()[0].text + + assert "Draft" in pageProgrammeDetails.getProgramCycleStatus()[1].text + assert (datetime.now() + relativedelta(days=1)).strftime("%-d %b %Y") in \ + pageProgrammeDetails.getProgramCycleEndDate()[1].text + assert (datetime.now() + relativedelta(days=1)).strftime("%-d %b %Y") in \ + pageProgrammeDetails.getProgramCycleStartDate()[1].text + assert "Test Title" in pageProgrammeDetails.getProgramCycleTitle()[1].text + + def test_program_details_add_new_programme_cycle( + self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails + ) -> None: + pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") + assert "ACTIVE" in pageProgrammeDetails.getProgramStatus().text + pageProgrammeDetails.getButtonAddNewProgrammeCycle().click() + pageProgrammeDetails.screenshot("1") \ No newline at end of file diff --git a/frontend/src/containers/tables/ProgramCycle/NewProgramCycle/CreateProgramCycle.tsx b/frontend/src/containers/tables/ProgramCycle/NewProgramCycle/CreateProgramCycle.tsx index 68eaafb557..95aaf5206d 100644 --- a/frontend/src/containers/tables/ProgramCycle/NewProgramCycle/CreateProgramCycle.tsx +++ b/frontend/src/containers/tables/ProgramCycle/NewProgramCycle/CreateProgramCycle.tsx @@ -155,7 +155,7 @@ export const CreateProgramCycle = ({ {error.data.title} )} - + )} - + { data.results.length > 1 && hasPermissions(PERMISSIONS.PM_PROGRAMME_CYCLE_DELETE, permissions); return ( - - + + {canViewDetails ? ( {row.unicef_id} ) : ( @@ -61,31 +61,31 @@ export const ProgramCycleTable = ({ program }: ProgramCycleTableProps) => { )} {row.title} - + - + {row.total_entitled_quantity_usd || '-'} {row.total_undelivered_quantity_usd || '-'} - + {row.total_delivered_quantity_usd || '-'} - + {row.start_date} - + {row.end_date} - + {program.status === 'ACTIVE' && ( <> {canEditProgramCycle && ( From 2cd9574c1224c4055054984bcd0fe7b095130da2 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Tue, 20 Aug 2024 14:48:44 +0200 Subject: [PATCH 043/118] fix tests --- .../apps/program/tests/snapshots/snap_test_create_program.py | 2 +- .../apps/program/tests/snapshots/snap_test_program_query.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_create_program.py b/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_create_program.py index db679a1eb6..368164cc92 100644 --- a/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_create_program.py +++ b/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_create_program.py @@ -469,7 +469,7 @@ 'Round 3C', 'Round 4D' ], - 'subtype': 'BOOLEAN' + 'subtype': 'BOOL' } } ], diff --git a/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_program_query.py b/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_program_query.py index 3ac42391a1..706df485bd 100644 --- a/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_program_query.py +++ b/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_program_query.py @@ -57,7 +57,7 @@ 'Round A', 'Round B' ], - 'subtype': 'BOOLEAN' + 'subtype': 'BOOL' } } ], From b205bc4793329667d792d5cd5f3ae894d97c3482 Mon Sep 17 00:00:00 2001 From: Szymon Wyderka Date: Tue, 20 Aug 2024 14:50:38 +0200 Subject: [PATCH 044/118] black --- .../program_details/test_program_details.py | 88 ++++++++++++------- 1 file changed, 56 insertions(+), 32 deletions(-) diff --git a/backend/selenium_tests/program_details/test_program_details.py b/backend/selenium_tests/program_details/test_program_details.py index 5974d1a93e..ba122d996e 100644 --- a/backend/selenium_tests/program_details/test_program_details.py +++ b/backend/selenium_tests/program_details/test_program_details.py @@ -36,10 +36,13 @@ def standard_program() -> Program: def get_program_with_dct_type_and_name( - name: str, programme_code: str, dct_type: str = DataCollectingType.Type.STANDARD, status: str = Program.DRAFT, - program_cycle_status: str = ProgramCycle.FINISHED, - cycle_start_date: datetime = datetime.now() - relativedelta(days=25), - cycle_end_date: datetime = datetime.now() + relativedelta(days=10), + name: str, + programme_code: str, + dct_type: str = DataCollectingType.Type.STANDARD, + status: str = Program.DRAFT, + program_cycle_status: str = ProgramCycle.FINISHED, + cycle_start_date: datetime = datetime.now() - relativedelta(days=25), + cycle_end_date: datetime = datetime.now() + relativedelta(days=10), ) -> Program: BusinessArea.objects.filter(slug="afghanistan").update(is_payment_plan_applicable=True) dct = DataCollectingTypeFactory(type=dct_type) @@ -59,18 +62,29 @@ def get_program_with_dct_type_and_name( @pytest.fixture def standard_program_with_draft_programme_cycle() -> Program: - yield get_program_without_cycle_end_date("Active Programme", "9876", status=Program.ACTIVE, - program_cycle_status=ProgramCycle.DRAFT) + yield get_program_without_cycle_end_date( + "Active Programme", "9876", status=Program.ACTIVE, program_cycle_status=ProgramCycle.DRAFT + ) + @pytest.fixture def standard_active_program() -> Program: - yield get_program_with_dct_type_and_name("Active Programme", "9876", status=Program.ACTIVE, - program_cycle_status=ProgramCycle.FINISHED) + yield get_program_with_dct_type_and_name( + "Active Programme", + "9876", + status=Program.ACTIVE, + program_cycle_status=ProgramCycle.FINISHED, + cycle_end_date=datetime.now(), + ) + def get_program_without_cycle_end_date( - name: str, programme_code: str, dct_type: str = DataCollectingType.Type.STANDARD, status: str = Program.ACTIVE, - program_cycle_status: str = ProgramCycle.FINISHED, - cycle_start_date: datetime = datetime.now() - relativedelta(days=25), + name: str, + programme_code: str, + dct_type: str = DataCollectingType.Type.STANDARD, + status: str = Program.ACTIVE, + program_cycle_status: str = ProgramCycle.FINISHED, + cycle_start_date: datetime = datetime.now() - relativedelta(days=25), ) -> Program: BusinessArea.objects.filter(slug="afghanistan").update(is_payment_plan_applicable=True) dct = DataCollectingTypeFactory(type=dct_type) @@ -175,8 +189,8 @@ def test_program_details(self, standard_program: Program, pageProgrammeDetails: assert program.sector.replace("_", " ").title() in pageProgrammeDetails.getLabelSelector().text assert program.data_collecting_type.label in pageProgrammeDetails.getLabelDataCollectingType().text assert ( - program.frequency_of_payments.replace("_", "-").capitalize() - in pageProgrammeDetails.getLabelFreqOfPayment().text + program.frequency_of_payments.replace("_", "-").capitalize() + in pageProgrammeDetails.getLabelFreqOfPayment().text ) assert program.administrative_areas_of_implementation in pageProgrammeDetails.getLabelAdministrativeAreas().text assert program.description in pageProgrammeDetails.getLabelDescription().text @@ -186,10 +200,10 @@ def test_program_details(self, standard_program: Program, pageProgrammeDetails: @pytest.mark.skip("Unskip after fix bug") def test_edit_programme_from_details( - self, - create_programs: None, - pageProgrammeDetails: ProgrammeDetails, - pageProgrammeManagement: ProgrammeManagement, + self, + create_programs: None, + pageProgrammeDetails: ProgrammeDetails, + pageProgrammeManagement: ProgrammeManagement, ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Test Programm") pageProgrammeDetails.getButtonEditProgram().click() @@ -215,7 +229,7 @@ def test_edit_programme_from_details( assert FormatTime(1, 10, 2022).date_in_text_format in pageProgrammeDetails.getLabelEndDate().text def test_program_details_happy_path( - self, create_payment_plan: Program, pageProgrammeDetails: ProgrammeDetails + self, create_payment_plan: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Test For Edit") assert "DRAFT" in pageProgrammeDetails.getProgramStatus().text @@ -246,10 +260,9 @@ def test_program_details_happy_path( @pytest.mark.usefixtures("login") class TestProgrammeDetails: - - def test_program_details_check_default_cycle(self, pageProgrammeManagement: ProgrammeManagement, - pageProgrammeDetails: ProgrammeDetails - ) -> None: + def test_program_details_check_default_cycle( + self, pageProgrammeManagement: ProgrammeManagement, pageProgrammeDetails: ProgrammeDetails + ) -> None: # Go to Programme Management pageProgrammeManagement.getNavProgrammeManagement().click() # Create Programme @@ -278,7 +291,7 @@ def test_program_details_check_default_cycle(self, pageProgrammeManagement: Prog assert "Default Programme Cycle" in pageProgrammeDetails.getProgramCycleTitle()[0].text def test_program_details_edit_default_cycle_by_add_new( - self, standard_program_with_draft_programme_cycle: Program, pageProgrammeDetails: ProgrammeDetails + self, standard_program_with_draft_programme_cycle: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") assert "ACTIVE" in pageProgrammeDetails.getProgramStatus().text @@ -291,10 +304,10 @@ def test_program_details_edit_default_cycle_by_add_new( pageProgrammeDetails.getInputTitle().send_keys("Test Title") pageProgrammeDetails.getStartDateCycle().click() pageProgrammeDetails.getStartDateCycle().send_keys( - (datetime.now() + relativedelta(days=1)).strftime("%Y-%m-%d")) + (datetime.now() + relativedelta(days=1)).strftime("%Y-%m-%d") + ) pageProgrammeDetails.getEndDateCycle().click() - pageProgrammeDetails.getEndDateCycle().send_keys( - (datetime.now() + relativedelta(days=1)).strftime("%Y-%m-%d")) + pageProgrammeDetails.getEndDateCycle().send_keys((datetime.now() + relativedelta(days=1)).strftime("%Y-%m-%d")) pageProgrammeDetails.getButtonCreateProgramCycle().click() pageProgrammeDetails.getProgramCycleRow() assert 2 == len(pageProgrammeDetails.getProgramCycleRow()) @@ -304,16 +317,27 @@ def test_program_details_edit_default_cycle_by_add_new( assert "Default Programme Cycle" in pageProgrammeDetails.getProgramCycleTitle()[0].text assert "Draft" in pageProgrammeDetails.getProgramCycleStatus()[1].text - assert (datetime.now() + relativedelta(days=1)).strftime("%-d %b %Y") in \ - pageProgrammeDetails.getProgramCycleEndDate()[1].text - assert (datetime.now() + relativedelta(days=1)).strftime("%-d %b %Y") in \ - pageProgrammeDetails.getProgramCycleStartDate()[1].text + assert (datetime.now() + relativedelta(days=1)).strftime( + "%-d %b %Y" + ) in pageProgrammeDetails.getProgramCycleEndDate()[1].text + assert (datetime.now() + relativedelta(days=1)).strftime( + "%-d %b %Y" + ) in pageProgrammeDetails.getProgramCycleStartDate()[1].text assert "Test Title" in pageProgrammeDetails.getProgramCycleTitle()[1].text def test_program_details_add_new_programme_cycle( - self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails + self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") assert "ACTIVE" in pageProgrammeDetails.getProgramStatus().text pageProgrammeDetails.getButtonAddNewProgrammeCycle().click() - pageProgrammeDetails.screenshot("1") \ No newline at end of file + pageProgrammeDetails.getInputTitle().send_keys("Test Title") + pageProgrammeDetails.getStartDateCycle().click() + pageProgrammeDetails.getStartDateCycle().send_keys( + (datetime.now() + relativedelta(days=1)).strftime("%Y-%m-%d") + ) + pageProgrammeDetails.getEndDateCycle().click() + pageProgrammeDetails.getEndDateCycle().send_keys((datetime.now() + relativedelta(days=2)).strftime("%Y-%m-%d")) + pageProgrammeDetails.getButtonCreateProgramCycle().click() + pageProgrammeDetails.getProgramCycleRow() + pageProgrammeDetails.screenshot("1") From bad355869c9fba0a10dffbd84f47b0016d3f5ae1 Mon Sep 17 00:00:00 2001 From: Szymon Wyderka Date: Tue, 20 Aug 2024 14:54:20 +0200 Subject: [PATCH 045/118] flaske8 --- .../program_details/test_program_details.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/backend/selenium_tests/program_details/test_program_details.py b/backend/selenium_tests/program_details/test_program_details.py index ba122d996e..b245657431 100644 --- a/backend/selenium_tests/program_details/test_program_details.py +++ b/backend/selenium_tests/program_details/test_program_details.py @@ -41,9 +41,13 @@ def get_program_with_dct_type_and_name( dct_type: str = DataCollectingType.Type.STANDARD, status: str = Program.DRAFT, program_cycle_status: str = ProgramCycle.FINISHED, - cycle_start_date: datetime = datetime.now() - relativedelta(days=25), - cycle_end_date: datetime = datetime.now() + relativedelta(days=10), + cycle_start_date: datetime | bool = False, + cycle_end_date: datetime | bool = False, ) -> Program: + if not cycle_start_date: + cycle_start_date = datetime.now() - relativedelta(days=25) + if not cycle_end_date: + cycle_end_date = datetime.now() + relativedelta(days=10) BusinessArea.objects.filter(slug="afghanistan").update(is_payment_plan_applicable=True) dct = DataCollectingTypeFactory(type=dct_type) program = ProgramFactory( @@ -84,8 +88,10 @@ def get_program_without_cycle_end_date( dct_type: str = DataCollectingType.Type.STANDARD, status: str = Program.ACTIVE, program_cycle_status: str = ProgramCycle.FINISHED, - cycle_start_date: datetime = datetime.now() - relativedelta(days=25), + cycle_start_date: datetime | bool = False, ) -> Program: + if not cycle_start_date: + cycle_start_date = datetime.now() - relativedelta(days=25) BusinessArea.objects.filter(slug="afghanistan").update(is_payment_plan_applicable=True) dct = DataCollectingTypeFactory(type=dct_type) program = ProgramFactory( From 9a022039ade27997b74368cc2a38933b3fff4d8e Mon Sep 17 00:00:00 2001 From: Szymon Wyderka Date: Tue, 20 Aug 2024 15:04:52 +0200 Subject: [PATCH 046/118] Add test_program_details_add_new_programme_cycle --- .../program_details/test_program_details.py | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/backend/selenium_tests/program_details/test_program_details.py b/backend/selenium_tests/program_details/test_program_details.py index b245657431..38a6cf4e95 100644 --- a/backend/selenium_tests/program_details/test_program_details.py +++ b/backend/selenium_tests/program_details/test_program_details.py @@ -337,13 +337,41 @@ def test_program_details_add_new_programme_cycle( pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") assert "ACTIVE" in pageProgrammeDetails.getProgramStatus().text pageProgrammeDetails.getButtonAddNewProgrammeCycle().click() - pageProgrammeDetails.getInputTitle().send_keys("Test Title") + pageProgrammeDetails.getInputTitle().send_keys("123") pageProgrammeDetails.getStartDateCycle().click() pageProgrammeDetails.getStartDateCycle().send_keys( (datetime.now() + relativedelta(days=1)).strftime("%Y-%m-%d") ) pageProgrammeDetails.getEndDateCycle().click() - pageProgrammeDetails.getEndDateCycle().send_keys((datetime.now() + relativedelta(days=2)).strftime("%Y-%m-%d")) + pageProgrammeDetails.getEndDateCycle().send_keys((datetime.now() + relativedelta(days=10)).strftime("%Y-%m-%d")) + pageProgrammeDetails.getButtonCreateProgramCycle().click() + + pageProgrammeDetails.getButtonAddNewProgrammeCycle().click() + pageProgrammeDetails.getInputTitle().send_keys("Test %$ What?") + pageProgrammeDetails.getStartDateCycle().click() + pageProgrammeDetails.getStartDateCycle().send_keys( + (datetime.now() + relativedelta(days=11)).strftime("%Y-%m-%d") + ) + pageProgrammeDetails.getEndDateCycle().click() + pageProgrammeDetails.getEndDateCycle().send_keys((datetime.now() + relativedelta(days=21)).strftime("%Y-%m-%d")) pageProgrammeDetails.getButtonCreateProgramCycle().click() + pageProgrammeDetails.getProgramCycleRow() - pageProgrammeDetails.screenshot("1") + + assert "Draft" in pageProgrammeDetails.getProgramCycleStatus()[1].text + assert (datetime.now() + relativedelta(days=1)).strftime( + "%-d %b %Y" + ) in pageProgrammeDetails.getProgramCycleStartDate()[1].text + assert (datetime.now() + relativedelta(days=10)).strftime( + "%-d %b %Y" + ) in pageProgrammeDetails.getProgramCycleEndDate()[1].text + assert "123" in pageProgrammeDetails.getProgramCycleTitle()[1].text + + assert "Draft" in pageProgrammeDetails.getProgramCycleStatus()[2].text + assert (datetime.now() + relativedelta(days=11)).strftime( + "%-d %b %Y" + ) in pageProgrammeDetails.getProgramCycleStartDate()[2].text + assert (datetime.now() + relativedelta(days=21)).strftime( + "%-d %b %Y" + ) in pageProgrammeDetails.getProgramCycleEndDate()[2].text + assert "123" in pageProgrammeDetails.getProgramCycleTitle()[2].text From 5d1e04e12f1e9bec747d2f737061c0f6dbef1b95 Mon Sep 17 00:00:00 2001 From: Szymon Wyderka Date: Tue, 20 Aug 2024 15:05:24 +0200 Subject: [PATCH 047/118] Add test_program_details_add_new_programme_cycle --- backend/selenium_tests/program_details/test_program_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/selenium_tests/program_details/test_program_details.py b/backend/selenium_tests/program_details/test_program_details.py index 38a6cf4e95..09b9f916d4 100644 --- a/backend/selenium_tests/program_details/test_program_details.py +++ b/backend/selenium_tests/program_details/test_program_details.py @@ -374,4 +374,4 @@ def test_program_details_add_new_programme_cycle( assert (datetime.now() + relativedelta(days=21)).strftime( "%-d %b %Y" ) in pageProgrammeDetails.getProgramCycleEndDate()[2].text - assert "123" in pageProgrammeDetails.getProgramCycleTitle()[2].text + assert "Test %$ What?" in pageProgrammeDetails.getProgramCycleTitle()[2].text From 8bd5bd1c1928b8baa859b65047bb16f0ab3dfe62 Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Tue, 20 Aug 2024 14:50:30 +0200 Subject: [PATCH 048/118] truth --- frontend/src/utils/targetingUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/utils/targetingUtils.ts b/frontend/src/utils/targetingUtils.ts index a7b66f7239..88685020da 100644 --- a/frontend/src/utils/targetingUtils.ts +++ b/frontend/src/utils/targetingUtils.ts @@ -194,7 +194,7 @@ export function formatCriteriaFilters(filters) { break; case 'BOOL': comparisonMethod = 'EQUALS'; - values = [each.value]; + values = [each.value === 'True']; break; default: comparisonMethod = 'CONTAINS'; From 1789e966698c102db9c346577c516e799e9245c2 Mon Sep 17 00:00:00 2001 From: Szymon Wyderka Date: Tue, 20 Aug 2024 16:20:11 +0200 Subject: [PATCH 049/118] Fix --- .../selenium_tests/program_details/test_program_details.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/selenium_tests/program_details/test_program_details.py b/backend/selenium_tests/program_details/test_program_details.py index 09b9f916d4..effea70cb3 100644 --- a/backend/selenium_tests/program_details/test_program_details.py +++ b/backend/selenium_tests/program_details/test_program_details.py @@ -316,7 +316,12 @@ def test_program_details_edit_default_cycle_by_add_new( pageProgrammeDetails.getEndDateCycle().send_keys((datetime.now() + relativedelta(days=1)).strftime("%Y-%m-%d")) pageProgrammeDetails.getButtonCreateProgramCycle().click() pageProgrammeDetails.getProgramCycleRow() - assert 2 == len(pageProgrammeDetails.getProgramCycleRow()) + for _ in range(50): + if 2 == len(pageProgrammeDetails.getProgramCycleRow()): + break + sleep(0.1) + else: + assert 2 == len(pageProgrammeDetails.getProgramCycleRow()) assert "Draft" in pageProgrammeDetails.getProgramCycleStatus()[0].text assert datetime.now().strftime("%-d %b %Y") in pageProgrammeDetails.getProgramCycleEndDate()[0].text From b02948b791a8d26ccec72478555a2462500eeb03 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Wed, 21 Aug 2024 04:21:46 +0200 Subject: [PATCH 050/118] fix incorrectly resolved conflict --- .../service/periodic_data_update_import_service.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/hct_mis_api/apps/periodic_data_update/service/periodic_data_update_import_service.py b/backend/hct_mis_api/apps/periodic_data_update/service/periodic_data_update_import_service.py index 481dd3921b..c3f0eb8a89 100644 --- a/backend/hct_mis_api/apps/periodic_data_update/service/periodic_data_update_import_service.py +++ b/backend/hct_mis_api/apps/periodic_data_update/service/periodic_data_update_import_service.py @@ -284,9 +284,9 @@ def _get_form_field_for_value(self, flexible_attribute: FlexibleAttribute) -> fo if flexible_attribute.pdu_data.subtype == PeriodicFieldData.STRING: return forms.CharField(required=False) elif flexible_attribute.pdu_data.subtype == PeriodicFieldData.DECIMAL: - return forms.DecimalField(required=False) - elif flexible_attribute.pdu_data.subtype == PeriodicFieldData.BOOL: return forms.FloatField(required=False) + elif flexible_attribute.pdu_data.subtype == PeriodicFieldData.BOOL: + return StrictBooleanField(required=False) elif flexible_attribute.pdu_data.subtype == PeriodicFieldData.DATE: return forms.DateField(required=False) raise ValidationError(f"Invalid subtype for field {flexible_attribute.name}") From beabe8d8c5ad9dc4c7a87eb5edfbed5853321d97 Mon Sep 17 00:00:00 2001 From: Szymon Wyderka Date: Wed, 21 Aug 2024 09:34:37 +0200 Subject: [PATCH 051/118] Fix test_edit_programme_from_details --- .../selenium_tests/program_details/test_program_details.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/selenium_tests/program_details/test_program_details.py b/backend/selenium_tests/program_details/test_program_details.py index effea70cb3..6c3401ec96 100644 --- a/backend/selenium_tests/program_details/test_program_details.py +++ b/backend/selenium_tests/program_details/test_program_details.py @@ -204,7 +204,6 @@ def test_program_details(self, standard_program: Program, pageProgrammeDetails: assert "Only selected partners within the business area" in pageProgrammeDetails.getLabelPartnerAccess().text assert "0" in pageProgrammeDetails.getLabelProgramSize().text - @pytest.mark.skip("Unskip after fix bug") def test_edit_programme_from_details( self, create_programs: None, @@ -221,13 +220,15 @@ def test_edit_programme_from_details( pageProgrammeManagement.getInputStartDate().send_keys(Keys.CONTROL + "a") pageProgrammeManagement.getInputStartDate().send_keys(str(FormatTime(1, 1, 2022).numerically_formatted_date)) pageProgrammeManagement.getInputEndDate().click() - pageProgrammeManagement.getInputStartDate().send_keys(Keys.CONTROL + "a") + pageProgrammeManagement.getInputEndDate().send_keys(Keys.CONTROL + "a") pageProgrammeManagement.getInputEndDate().send_keys(FormatTime(1, 10, 2022).numerically_formatted_date) pageProgrammeManagement.getButtonNext().click() + pageProgrammeManagement.getButtonAddTimeSeriesField() + pageProgrammeManagement.getButtonNext().click() programme_creation_url = pageProgrammeDetails.driver.current_url - pageProgrammeManagement.getButtonSave().click() pageProgrammeManagement.getAccessToProgram().click() pageProgrammeManagement.selectWhoAccessToProgram("None of the partners should have access") + pageProgrammeManagement.getButtonSave().click() # Check Details page assert "details" in pageProgrammeDetails.wait_for_new_url(programme_creation_url).split("/") assert "New name after Edit" in pageProgrammeDetails.getHeaderTitle().text From c081432d7b44e8c64236a60b78f6ac1557d6b884 Mon Sep 17 00:00:00 2001 From: Szymon Wyderka Date: Wed, 21 Aug 2024 09:50:04 +0200 Subject: [PATCH 052/118] Tests --- .../program_details/test_program_details.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/backend/selenium_tests/program_details/test_program_details.py b/backend/selenium_tests/program_details/test_program_details.py index 6c3401ec96..a3a95d3bda 100644 --- a/backend/selenium_tests/program_details/test_program_details.py +++ b/backend/selenium_tests/program_details/test_program_details.py @@ -381,3 +381,59 @@ def test_program_details_add_new_programme_cycle( "%-d %b %Y" ) in pageProgrammeDetails.getProgramCycleEndDate()[2].text assert "Test %$ What?" in pageProgrammeDetails.getProgramCycleTitle()[2].text + + def test_program_details_edit_programme_cycle( + self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails + ) -> None: + pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") + + def test_program_details_delete_programme_cycle( + self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails + ) -> None: + pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") + + def test_program_details_buttons_vs_programme_cycle_status( + self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails + ) -> None: + pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") + # Draft + # Active + # Finished + + def test_program_details_add_new_cycle_with_wrong_date( + self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails + ) -> None: + pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") + # end date cycle vs end date program + # end date cycle vs end other cycle + # start date cycle vs start date program + # start date cycle vs other date cycle + + def test_program_details_edit_cycle_with_wrong_date( + self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails + ) -> None: + pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") + # end date cycle vs end date program + # end date cycle vs end other cycle + # start date cycle vs start date program + # start date cycle vs other date cycle + + def test_edit_program_details_with_wrong_date( + self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails + ) -> None: + pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") + # end date program vs end date cycle + # start date program vs start date cycle + + def test_program_details_edit_default_cycle_by_add_new_cancel( + self, standard_program_with_draft_programme_cycle: Program, pageProgrammeDetails: ProgrammeDetails + ) -> None: + pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") + + def test_program_details_program_cycle_total_quantities( + self, standard_program_with_draft_programme_cycle: Program, pageProgrammeDetails: ProgrammeDetails + ) -> None: + pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") + # Total Entitled Quantity + # Total Undelivered Quantity + # Total Delivered Quantity From cd5c9f14fcbc38fcd1e365bc48e29d8769e33605 Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Wed, 21 Aug 2024 11:30:38 +0200 Subject: [PATCH 053/118] fix bool display --- .../targeting/TargetingCriteriaDisplay/Criteria.tsx | 13 ++++++++----- frontend/src/utils/targetingUtils.ts | 8 +++++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx index 154f7d6d65..2602a03c86 100644 --- a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx +++ b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx @@ -113,13 +113,16 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { fieldElement = (

{field.fieldAttribute.labelEn || field.fieldName}:{' '} - {field.fieldAttribute.type === 'BOOL' ? ( - {field.arguments?.[0] === 'True' ? t('Yes') : t('No')} + {field.fieldAttribute.type === 'BOOL' || + field.fieldAttribute?.pduData?.subtype ? ( + field.isNull ? ( + {t('Empty')} + ) : ( + {field.arguments?.[0] ? t('Yes') : t('No')} + ) ) : ( - {displayValueOrEmpty( - extractChoiceLabel(field, field.arguments?.[0]), - )} + {field.arguments?.[0] != null ? field.arguments[0] : t('Empty')} )}

diff --git a/frontend/src/utils/targetingUtils.ts b/frontend/src/utils/targetingUtils.ts index 88685020da..e18e6076cc 100644 --- a/frontend/src/utils/targetingUtils.ts +++ b/frontend/src/utils/targetingUtils.ts @@ -81,7 +81,13 @@ export function mapFiltersToInitialValues(filters): any[] { case 'EQUALS': return mappedFilters.push({ ...each, - value: each.arguments[0], + value: + each.fieldAttribute.type === 'BOOL' || + each.fieldAttribute?.pduData?.subtype + ? each.arguments[0] + ? 'Yes' + : 'No' + : each.arguments[0], }); case 'CONTAINS': // eslint-disable-next-line no-case-declarations From 90398c3649c1929f203fc40f3bd7b6a96b460691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Wo=C5=BAniak?= <17177420+wozniakpl@users.noreply.github.com> Date: Wed, 21 Aug 2024 13:41:35 +0200 Subject: [PATCH 054/118] Run shorter suite of tests on PR and the longer one on push (#4156) --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 02d68ceaa4..6f07b773fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -253,8 +253,12 @@ jobs: - name: E2E tests run: | + compose_file=./deployment/docker-compose.selenium-night.yml + if [ "${{ github.event_name }}" = "pull_request" ]; then + compose_file=./deployment/docker-compose.selenium.yml + fi dist_backend_image=${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:core-${{ github.sha }}-dist dev_backend_image=${{ vars.DOCKERHUB_ORGANIZATION }}/hope-support-images:core-${{ github.sha }}-dev docker compose \ - -f ./deployment/docker-compose.selenium.yml \ + -f $compose_file \ run selenium - name: Upload Artifact uses: actions/upload-artifact@v4 From 37b4ff24ee26db14d122faa80ec9f8f189e71eb8 Mon Sep 17 00:00:00 2001 From: Szymon Wyderka Date: Wed, 21 Aug 2024 13:42:55 +0200 Subject: [PATCH 055/118] Added test_program_details_delete_programme_cycle and test_program_details_edit_programme_cycle --- .../programme_details/programme_details.py | 13 ++ .../program_details/test_program_details.py | 127 +++++++++++++----- .../ProgramCycle/DeleteProgramCycle.tsx | 2 +- .../tables/ProgramCycle/EditProgramCycle.tsx | 4 +- 4 files changed, 113 insertions(+), 33 deletions(-) diff --git a/backend/selenium_tests/page_object/programme_details/programme_details.py b/backend/selenium_tests/page_object/programme_details/programme_details.py index 9a3e546495..e00335b33a 100644 --- a/backend/selenium_tests/page_object/programme_details/programme_details.py +++ b/backend/selenium_tests/page_object/programme_details/programme_details.py @@ -45,13 +45,20 @@ class ProgrammeDetails(BaseComponents): dataPickerFilter = 'div[data-cy="date-picker-filter"]' endDateCycle = 'div[data-cy="end-date-cycle"]' buttonNext = 'button[data-cy="button-update-program-cycle-modal"]' + buttonSave = 'button[data-cy="button-save"]' buttonCreateProgramCycle = 'button[data-cy="button-create-program-cycle"]' inputTitle = 'input[data-cy="input-title"]' + deleteProgrammeCycle = 'button[data-cy="delete-programme-cycle"]' + buttonDelete = 'button[data-cy="button-delete"]' def getProgramCycleRow(self) -> WebElement: self.wait_for(self.programCycleRow) return self.get_elements(self.programCycleRow) + def getDeleteProgrammeCycle(self) -> WebElement: + self.wait_for(self.deleteProgrammeCycle) + return self.get_elements(self.deleteProgrammeCycle) + def getProgramCycleId(self) -> WebElement: self.wait_for(self.programCycleId) return self.get_elements(self.programCycleId) @@ -103,6 +110,9 @@ def getDataPickerFilter(self) -> WebElement: def getButtonNext(self) -> WebElement: return self.wait_for(self.buttonNext) + def getButtonSave(self) -> WebElement: + return self.wait_for(self.buttonSave) + def getInputTitle(self) -> WebElement: return self.wait_for(self.inputTitle) @@ -193,6 +203,9 @@ def getButtonAddNewProgrammeCycle(self) -> WebElement: def getTablePagination(self) -> WebElement: return self.wait_for(self.tablePagination) + def getButtonDelete(self) -> WebElement: + return self.wait_for(self.buttonDelete) + def clickButtonFinishProgramPopup(self) -> None: self.wait_for('[data-cy="dialog-actions-container"]') self.get_elements(self.buttonFinishProgram)[1].click() diff --git a/backend/selenium_tests/program_details/test_program_details.py b/backend/selenium_tests/program_details/test_program_details.py index a3a95d3bda..5a9af51e92 100644 --- a/backend/selenium_tests/program_details/test_program_details.py +++ b/backend/selenium_tests/program_details/test_program_details.py @@ -18,7 +18,7 @@ from hct_mis_api.apps.household.fixtures import create_household from hct_mis_api.apps.household.models import Household from hct_mis_api.apps.payment.models import PaymentPlan -from hct_mis_api.apps.program.fixtures import ProgramFactory +from hct_mis_api.apps.program.fixtures import ProgramFactory, ProgramCycleFactory from hct_mis_api.apps.program.models import Program, ProgramCycle from hct_mis_api.apps.registration_data.fixtures import RegistrationDataImportFactory from hct_mis_api.apps.targeting.fixtures import ( @@ -35,14 +35,24 @@ def standard_program() -> Program: yield get_program_with_dct_type_and_name("Test For Edit", "TEST") +@pytest.fixture +def program_with_three_cycles() -> Program: + program = get_program_with_dct_type_and_name("ThreeCyclesProgramme", "cycl", status=Program.ACTIVE, + program_cycle_status=ProgramCycle.DRAFT) + ProgramCycleFactory(program=program, status=ProgramCycle.DRAFT) + ProgramCycleFactory(program=program, status=ProgramCycle.DRAFT) + program.save() + yield program + + def get_program_with_dct_type_and_name( - name: str, - programme_code: str, - dct_type: str = DataCollectingType.Type.STANDARD, - status: str = Program.DRAFT, - program_cycle_status: str = ProgramCycle.FINISHED, - cycle_start_date: datetime | bool = False, - cycle_end_date: datetime | bool = False, + name: str, + programme_code: str, + dct_type: str = DataCollectingType.Type.STANDARD, + status: str = Program.DRAFT, + program_cycle_status: str = ProgramCycle.FINISHED, + cycle_start_date: datetime | bool = False, + cycle_end_date: datetime | bool = False, ) -> Program: if not cycle_start_date: cycle_start_date = datetime.now() - relativedelta(days=25) @@ -82,13 +92,24 @@ def standard_active_program() -> Program: ) +@pytest.fixture +def standard_active_program_with_draft_program_cycle() -> Program: + yield get_program_with_dct_type_and_name( + "Active Programme And DRAFT Programme Cycle", + "LILI", + status=Program.ACTIVE, + program_cycle_status=ProgramCycle.DRAFT, + cycle_end_date=datetime.now(), + ) + + def get_program_without_cycle_end_date( - name: str, - programme_code: str, - dct_type: str = DataCollectingType.Type.STANDARD, - status: str = Program.ACTIVE, - program_cycle_status: str = ProgramCycle.FINISHED, - cycle_start_date: datetime | bool = False, + name: str, + programme_code: str, + dct_type: str = DataCollectingType.Type.STANDARD, + status: str = Program.ACTIVE, + program_cycle_status: str = ProgramCycle.FINISHED, + cycle_start_date: datetime | bool = False, ) -> Program: if not cycle_start_date: cycle_start_date = datetime.now() - relativedelta(days=25) @@ -195,8 +216,8 @@ def test_program_details(self, standard_program: Program, pageProgrammeDetails: assert program.sector.replace("_", " ").title() in pageProgrammeDetails.getLabelSelector().text assert program.data_collecting_type.label in pageProgrammeDetails.getLabelDataCollectingType().text assert ( - program.frequency_of_payments.replace("_", "-").capitalize() - in pageProgrammeDetails.getLabelFreqOfPayment().text + program.frequency_of_payments.replace("_", "-").capitalize() + in pageProgrammeDetails.getLabelFreqOfPayment().text ) assert program.administrative_areas_of_implementation in pageProgrammeDetails.getLabelAdministrativeAreas().text assert program.description in pageProgrammeDetails.getLabelDescription().text @@ -205,10 +226,10 @@ def test_program_details(self, standard_program: Program, pageProgrammeDetails: assert "0" in pageProgrammeDetails.getLabelProgramSize().text def test_edit_programme_from_details( - self, - create_programs: None, - pageProgrammeDetails: ProgrammeDetails, - pageProgrammeManagement: ProgrammeManagement, + self, + create_programs: None, + pageProgrammeDetails: ProgrammeDetails, + pageProgrammeManagement: ProgrammeManagement, ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Test Programm") pageProgrammeDetails.getButtonEditProgram().click() @@ -236,7 +257,7 @@ def test_edit_programme_from_details( assert FormatTime(1, 10, 2022).date_in_text_format in pageProgrammeDetails.getLabelEndDate().text def test_program_details_happy_path( - self, create_payment_plan: Program, pageProgrammeDetails: ProgrammeDetails + self, create_payment_plan: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Test For Edit") assert "DRAFT" in pageProgrammeDetails.getProgramStatus().text @@ -268,7 +289,7 @@ def test_program_details_happy_path( @pytest.mark.usefixtures("login") class TestProgrammeDetails: def test_program_details_check_default_cycle( - self, pageProgrammeManagement: ProgrammeManagement, pageProgrammeDetails: ProgrammeDetails + self, pageProgrammeManagement: ProgrammeManagement, pageProgrammeDetails: ProgrammeDetails ) -> None: # Go to Programme Management pageProgrammeManagement.getNavProgrammeManagement().click() @@ -298,7 +319,7 @@ def test_program_details_check_default_cycle( assert "Default Programme Cycle" in pageProgrammeDetails.getProgramCycleTitle()[0].text def test_program_details_edit_default_cycle_by_add_new( - self, standard_program_with_draft_programme_cycle: Program, pageProgrammeDetails: ProgrammeDetails + self, standard_program_with_draft_programme_cycle: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") assert "ACTIVE" in pageProgrammeDetails.getProgramStatus().text @@ -338,7 +359,7 @@ def test_program_details_edit_default_cycle_by_add_new( assert "Test Title" in pageProgrammeDetails.getProgramCycleTitle()[1].text def test_program_details_add_new_programme_cycle( - self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails + self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") assert "ACTIVE" in pageProgrammeDetails.getProgramStatus().text @@ -383,17 +404,63 @@ def test_program_details_add_new_programme_cycle( assert "Test %$ What?" in pageProgrammeDetails.getProgramCycleTitle()[2].text def test_program_details_edit_programme_cycle( - self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails + self, standard_active_program_with_draft_program_cycle: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") + pageProgrammeDetails.getButtonEditProgramCycle()[0].click() + pageProgrammeDetails.getInputTitle().send_keys(Keys.CONTROL, "a") + pageProgrammeDetails.getInputTitle().send_keys("Edited title check") + pageProgrammeDetails.getStartDateCycle().click() + pageProgrammeDetails.getStartDateCycle().send_keys( + (datetime.now() + relativedelta(days=11)).strftime("%Y-%m-%d") + ) + pageProgrammeDetails.getEndDateCycle().click() + pageProgrammeDetails.getEndDateCycle().send_keys( + (datetime.now() + relativedelta(days=12)).strftime("%Y-%m-%d") + ) + pageProgrammeDetails.getButtonSave().click() + assert "Draft" in pageProgrammeDetails.getProgramCycleStatus()[0].text + start_date = (datetime.now() + relativedelta(days=11)).strftime("%-d %b %Y") + for _ in range(50): + if start_date in pageProgrammeDetails.getProgramCycleStartDate()[0].text: + break + sleep(0.1) + else: + assert start_date in pageProgrammeDetails.getProgramCycleStartDate()[0].text + assert (datetime.now() + relativedelta(days=12)).strftime( + "%-d %b %Y" + ) in pageProgrammeDetails.getProgramCycleEndDate()[0].text + assert "Edited title check" in pageProgrammeDetails.getProgramCycleTitle()[0].text def test_program_details_delete_programme_cycle( - self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails + self, program_with_three_cycles: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: - pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") + pageProgrammeDetails.selectGlobalProgramFilter("ThreeCyclesProgramme") + for _ in range(50): + if 3 == len(pageProgrammeDetails.getProgramCycleId()): + break + sleep(0.1) + else: + assert 3 == len(pageProgrammeDetails.getProgramCycleId()) + program_cycle_1 = pageProgrammeDetails.getProgramCycleId()[0].text + program_cycle_3 = pageProgrammeDetails.getProgramCycleId()[2].text + pageProgrammeDetails.getDeleteProgrammeCycle()[1].click() + pageProgrammeDetails.getButtonDelete().click() + for _ in range(50): + if 3 == len(pageProgrammeDetails.getProgramCycleId()): + break + sleep(0.1) + else: + assert 2 == len(pageProgrammeDetails.getProgramCycleId()) + + assert program_cycle_1 in pageProgrammeDetails.getProgramCycleId()[0].text + assert program_cycle_3 in pageProgrammeDetails.getProgramCycleId()[1].text + from selenium_tests.tools.tag_name_finder import printing + printing("Mapping", pageProgrammeDetails.driver) + printing("Methods", pageProgrammeDetails.driver) def test_program_details_buttons_vs_programme_cycle_status( - self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails + self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") # Draft @@ -426,12 +493,12 @@ def test_edit_program_details_with_wrong_date( # start date program vs start date cycle def test_program_details_edit_default_cycle_by_add_new_cancel( - self, standard_program_with_draft_programme_cycle: Program, pageProgrammeDetails: ProgrammeDetails + self, standard_program_with_draft_programme_cycle: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") def test_program_details_program_cycle_total_quantities( - self, standard_program_with_draft_programme_cycle: Program, pageProgrammeDetails: ProgrammeDetails + self, standard_program_with_draft_programme_cycle: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") # Total Entitled Quantity diff --git a/frontend/src/containers/tables/ProgramCycle/DeleteProgramCycle.tsx b/frontend/src/containers/tables/ProgramCycle/DeleteProgramCycle.tsx index 8b9c6d22a8..7dad7742c3 100644 --- a/frontend/src/containers/tables/ProgramCycle/DeleteProgramCycle.tsx +++ b/frontend/src/containers/tables/ProgramCycle/DeleteProgramCycle.tsx @@ -68,7 +68,7 @@ export const DeleteProgramCycle = ({ return ( <> - setOpen(true)}> + setOpen(true)}> setOpen(false)} scroll="paper"> diff --git a/frontend/src/containers/tables/ProgramCycle/EditProgramCycle.tsx b/frontend/src/containers/tables/ProgramCycle/EditProgramCycle.tsx index 25214c8078..0786c87a71 100644 --- a/frontend/src/containers/tables/ProgramCycle/EditProgramCycle.tsx +++ b/frontend/src/containers/tables/ProgramCycle/EditProgramCycle.tsx @@ -167,7 +167,7 @@ export const EditProgramCycle = ({ required />
- + - + Date: Wed, 21 Aug 2024 13:44:10 +0200 Subject: [PATCH 056/118] black --- .../program_details/test_program_details.py | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/backend/selenium_tests/program_details/test_program_details.py b/backend/selenium_tests/program_details/test_program_details.py index 5a9af51e92..b806eea85d 100644 --- a/backend/selenium_tests/program_details/test_program_details.py +++ b/backend/selenium_tests/program_details/test_program_details.py @@ -18,7 +18,7 @@ from hct_mis_api.apps.household.fixtures import create_household from hct_mis_api.apps.household.models import Household from hct_mis_api.apps.payment.models import PaymentPlan -from hct_mis_api.apps.program.fixtures import ProgramFactory, ProgramCycleFactory +from hct_mis_api.apps.program.fixtures import ProgramCycleFactory, ProgramFactory from hct_mis_api.apps.program.models import Program, ProgramCycle from hct_mis_api.apps.registration_data.fixtures import RegistrationDataImportFactory from hct_mis_api.apps.targeting.fixtures import ( @@ -37,8 +37,9 @@ def standard_program() -> Program: @pytest.fixture def program_with_three_cycles() -> Program: - program = get_program_with_dct_type_and_name("ThreeCyclesProgramme", "cycl", status=Program.ACTIVE, - program_cycle_status=ProgramCycle.DRAFT) + program = get_program_with_dct_type_and_name( + "ThreeCyclesProgramme", "cycl", status=Program.ACTIVE, program_cycle_status=ProgramCycle.DRAFT + ) ProgramCycleFactory(program=program, status=ProgramCycle.DRAFT) ProgramCycleFactory(program=program, status=ProgramCycle.DRAFT) program.save() @@ -46,13 +47,13 @@ def program_with_three_cycles() -> Program: def get_program_with_dct_type_and_name( - name: str, - programme_code: str, - dct_type: str = DataCollectingType.Type.STANDARD, - status: str = Program.DRAFT, - program_cycle_status: str = ProgramCycle.FINISHED, - cycle_start_date: datetime | bool = False, - cycle_end_date: datetime | bool = False, + name: str, + programme_code: str, + dct_type: str = DataCollectingType.Type.STANDARD, + status: str = Program.DRAFT, + program_cycle_status: str = ProgramCycle.FINISHED, + cycle_start_date: datetime | bool = False, + cycle_end_date: datetime | bool = False, ) -> Program: if not cycle_start_date: cycle_start_date = datetime.now() - relativedelta(days=25) @@ -104,12 +105,12 @@ def standard_active_program_with_draft_program_cycle() -> Program: def get_program_without_cycle_end_date( - name: str, - programme_code: str, - dct_type: str = DataCollectingType.Type.STANDARD, - status: str = Program.ACTIVE, - program_cycle_status: str = ProgramCycle.FINISHED, - cycle_start_date: datetime | bool = False, + name: str, + programme_code: str, + dct_type: str = DataCollectingType.Type.STANDARD, + status: str = Program.ACTIVE, + program_cycle_status: str = ProgramCycle.FINISHED, + cycle_start_date: datetime | bool = False, ) -> Program: if not cycle_start_date: cycle_start_date = datetime.now() - relativedelta(days=25) @@ -216,8 +217,8 @@ def test_program_details(self, standard_program: Program, pageProgrammeDetails: assert program.sector.replace("_", " ").title() in pageProgrammeDetails.getLabelSelector().text assert program.data_collecting_type.label in pageProgrammeDetails.getLabelDataCollectingType().text assert ( - program.frequency_of_payments.replace("_", "-").capitalize() - in pageProgrammeDetails.getLabelFreqOfPayment().text + program.frequency_of_payments.replace("_", "-").capitalize() + in pageProgrammeDetails.getLabelFreqOfPayment().text ) assert program.administrative_areas_of_implementation in pageProgrammeDetails.getLabelAdministrativeAreas().text assert program.description in pageProgrammeDetails.getLabelDescription().text @@ -226,10 +227,10 @@ def test_program_details(self, standard_program: Program, pageProgrammeDetails: assert "0" in pageProgrammeDetails.getLabelProgramSize().text def test_edit_programme_from_details( - self, - create_programs: None, - pageProgrammeDetails: ProgrammeDetails, - pageProgrammeManagement: ProgrammeManagement, + self, + create_programs: None, + pageProgrammeDetails: ProgrammeDetails, + pageProgrammeManagement: ProgrammeManagement, ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Test Programm") pageProgrammeDetails.getButtonEditProgram().click() @@ -257,7 +258,7 @@ def test_edit_programme_from_details( assert FormatTime(1, 10, 2022).date_in_text_format in pageProgrammeDetails.getLabelEndDate().text def test_program_details_happy_path( - self, create_payment_plan: Program, pageProgrammeDetails: ProgrammeDetails + self, create_payment_plan: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Test For Edit") assert "DRAFT" in pageProgrammeDetails.getProgramStatus().text @@ -289,7 +290,7 @@ def test_program_details_happy_path( @pytest.mark.usefixtures("login") class TestProgrammeDetails: def test_program_details_check_default_cycle( - self, pageProgrammeManagement: ProgrammeManagement, pageProgrammeDetails: ProgrammeDetails + self, pageProgrammeManagement: ProgrammeManagement, pageProgrammeDetails: ProgrammeDetails ) -> None: # Go to Programme Management pageProgrammeManagement.getNavProgrammeManagement().click() @@ -319,7 +320,7 @@ def test_program_details_check_default_cycle( assert "Default Programme Cycle" in pageProgrammeDetails.getProgramCycleTitle()[0].text def test_program_details_edit_default_cycle_by_add_new( - self, standard_program_with_draft_programme_cycle: Program, pageProgrammeDetails: ProgrammeDetails + self, standard_program_with_draft_programme_cycle: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") assert "ACTIVE" in pageProgrammeDetails.getProgramStatus().text @@ -359,7 +360,7 @@ def test_program_details_edit_default_cycle_by_add_new( assert "Test Title" in pageProgrammeDetails.getProgramCycleTitle()[1].text def test_program_details_add_new_programme_cycle( - self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails + self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") assert "ACTIVE" in pageProgrammeDetails.getProgramStatus().text @@ -404,7 +405,7 @@ def test_program_details_add_new_programme_cycle( assert "Test %$ What?" in pageProgrammeDetails.getProgramCycleTitle()[2].text def test_program_details_edit_programme_cycle( - self, standard_active_program_with_draft_program_cycle: Program, pageProgrammeDetails: ProgrammeDetails + self, standard_active_program_with_draft_program_cycle: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") pageProgrammeDetails.getButtonEditProgramCycle()[0].click() @@ -415,9 +416,7 @@ def test_program_details_edit_programme_cycle( (datetime.now() + relativedelta(days=11)).strftime("%Y-%m-%d") ) pageProgrammeDetails.getEndDateCycle().click() - pageProgrammeDetails.getEndDateCycle().send_keys( - (datetime.now() + relativedelta(days=12)).strftime("%Y-%m-%d") - ) + pageProgrammeDetails.getEndDateCycle().send_keys((datetime.now() + relativedelta(days=12)).strftime("%Y-%m-%d")) pageProgrammeDetails.getButtonSave().click() assert "Draft" in pageProgrammeDetails.getProgramCycleStatus()[0].text start_date = (datetime.now() + relativedelta(days=11)).strftime("%-d %b %Y") @@ -433,7 +432,7 @@ def test_program_details_edit_programme_cycle( assert "Edited title check" in pageProgrammeDetails.getProgramCycleTitle()[0].text def test_program_details_delete_programme_cycle( - self, program_with_three_cycles: Program, pageProgrammeDetails: ProgrammeDetails + self, program_with_three_cycles: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("ThreeCyclesProgramme") for _ in range(50): @@ -456,11 +455,12 @@ def test_program_details_delete_programme_cycle( assert program_cycle_1 in pageProgrammeDetails.getProgramCycleId()[0].text assert program_cycle_3 in pageProgrammeDetails.getProgramCycleId()[1].text from selenium_tests.tools.tag_name_finder import printing + printing("Mapping", pageProgrammeDetails.driver) printing("Methods", pageProgrammeDetails.driver) def test_program_details_buttons_vs_programme_cycle_status( - self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails + self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") # Draft @@ -468,7 +468,7 @@ def test_program_details_buttons_vs_programme_cycle_status( # Finished def test_program_details_add_new_cycle_with_wrong_date( - self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails + self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") # end date cycle vs end date program @@ -477,7 +477,7 @@ def test_program_details_add_new_cycle_with_wrong_date( # start date cycle vs other date cycle def test_program_details_edit_cycle_with_wrong_date( - self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails + self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") # end date cycle vs end date program @@ -486,19 +486,19 @@ def test_program_details_edit_cycle_with_wrong_date( # start date cycle vs other date cycle def test_edit_program_details_with_wrong_date( - self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails + self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") # end date program vs end date cycle # start date program vs start date cycle def test_program_details_edit_default_cycle_by_add_new_cancel( - self, standard_program_with_draft_programme_cycle: Program, pageProgrammeDetails: ProgrammeDetails + self, standard_program_with_draft_programme_cycle: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") def test_program_details_program_cycle_total_quantities( - self, standard_program_with_draft_programme_cycle: Program, pageProgrammeDetails: ProgrammeDetails + self, standard_program_with_draft_programme_cycle: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") # Total Entitled Quantity From cedb097abda34ab8c8fc6f00a223e35af5733a41 Mon Sep 17 00:00:00 2001 From: Szymon Wyderka Date: Wed, 21 Aug 2024 15:20:58 +0200 Subject: [PATCH 057/118] black --- .../program_details/test_program_details.py | 22 +++++++++++++------ .../targeting/test_targeting.py | 9 ++++++++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/backend/selenium_tests/program_details/test_program_details.py b/backend/selenium_tests/program_details/test_program_details.py index b806eea85d..65980b1f46 100644 --- a/backend/selenium_tests/program_details/test_program_details.py +++ b/backend/selenium_tests/program_details/test_program_details.py @@ -46,6 +46,17 @@ def program_with_three_cycles() -> Program: yield program +@pytest.fixture +def program_with_different_cycles() -> Program: + program = get_program_with_dct_type_and_name( + "ThreeCyclesProgramme", "cycl", status=Program.ACTIVE, program_cycle_status=ProgramCycle.DRAFT + ) + ProgramCycleFactory(program=program, status=ProgramCycle.ACTIVE) + ProgramCycleFactory(program=program, status=ProgramCycle.FINISHED) + program.save() + yield program + + def get_program_with_dct_type_and_name( name: str, programme_code: str, @@ -454,18 +465,15 @@ def test_program_details_delete_programme_cycle( assert program_cycle_1 in pageProgrammeDetails.getProgramCycleId()[0].text assert program_cycle_3 in pageProgrammeDetails.getProgramCycleId()[1].text - from selenium_tests.tools.tag_name_finder import printing - - printing("Mapping", pageProgrammeDetails.driver) - printing("Methods", pageProgrammeDetails.driver) def test_program_details_buttons_vs_programme_cycle_status( self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") - # Draft - # Active - # Finished + from selenium_tests.tools.tag_name_finder import printing + + printing("Mapping", pageProgrammeDetails.driver) + printing("Methods", pageProgrammeDetails.driver) def test_program_details_add_new_cycle_with_wrong_date( self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails diff --git a/backend/selenium_tests/targeting/test_targeting.py b/backend/selenium_tests/targeting/test_targeting.py index 766b41e19f..e6c2a8371b 100644 --- a/backend/selenium_tests/targeting/test_targeting.py +++ b/backend/selenium_tests/targeting/test_targeting.py @@ -823,3 +823,12 @@ def test_targeting_parametrized_rules_filters_and_or( "Males age 0 - 5 with disability: 1 -10" in pageTargetingCreate.get_elements(pageTargetingCreate.criteriaContainer)[1].text ) + + @pytest.mark.skip("ToDo") + def test_targeting_edit_programme_cycle( + self, + pageTargeting: Targeting, + pageTargetingCreate: TargetingCreate, + ) -> None: + # Todo: write a test + pass From 2486ad25810e2b1899e73bcac435eb95567373ef Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Wed, 21 Aug 2024 16:13:29 +0200 Subject: [PATCH 058/118] prepare data for e2e --- .../page_object/targeting/targeting_create.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/backend/selenium_tests/page_object/targeting/targeting_create.py b/backend/selenium_tests/page_object/targeting/targeting_create.py index 712cb10763..2ef20aed90 100644 --- a/backend/selenium_tests/page_object/targeting/targeting_create.py +++ b/backend/selenium_tests/page_object/targeting/targeting_create.py @@ -52,6 +52,15 @@ class TargetingCreate(BaseComponents): autocompleteTargetCriteriaValues = 'div[data-cy="autocomplete-target-criteria-values"]' selectMany = 'div[data-cy="select-many"]' buttonEdit = 'button[data-cy="button-edit"]' + datePickerFilter = 'div[data-cy="date-picker-filter"]' + boolField = 'div[data-cy="bool-field"]' + textField = 'div[data-cy="string-textfield"]' + selectOptionsIndividualsFiltersBlocksRoundNumber = 'div[data-cy="select-options-individualsFiltersBlocks[{}].individualBlockFilters[{}].roundNumber"]' + selectIndividualsFiltersBlocksRoundNumber = 'div[data-cy="select-individualsFiltersBlocks[{}].individualBlockFilters[{}].roundNumber"]' + selectIndividualsFiltersBlocksIsNull = 'span[data-cy="input-individualsFiltersBlocks[{}].individualBlockFilters[{}].isNull"]' + inputIndividualsFiltersBlocksValueFrom = 'input[data-cy="input-individualsFiltersBlocks[{}].individualBlockFilters[{}].value.from"]' + inputIndividualsFiltersBlocksValueTo = 'input[data-cy="input-individualsFiltersBlocks[{}].individualBlockFilters[{}].value.to"]' + inputIndividualsFiltersBlocksValue = 'input[data-cy="input-individualsFiltersBlocks[{}].individualBlockFilters[{}].value"]' # Texts textTargetingCriteria = "Targeting Criteria" @@ -194,3 +203,30 @@ def getSelectMany(self) -> WebElement: def getButtonEdit(self) -> WebElement: return self.wait_for(self.buttonEdit) + + def getTextField(self) -> WebElement: + return self.wait_for(self.textField) + + def getBoolField(self) -> WebElement: + return self.wait_for(self.boolField) + + def getDatePickerFilter(self) -> WebElement: + return self.wait_for(self.datePickerFilter) + + def getSelectOptionsIndividualsFiltersBlocksRoundNumber(self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0) -> WebElement: + return self.wait_for(self.selectOptionsIndividualsFiltersBlocksRoundNumber.format(individuals_filters_blocks_number, individual_block_filters_number)) + + def getSelectIndividualsiFltersBlocksRoundNumber(self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0) -> WebElement: + return self.wait_for(self.selectIndividualsFiltersBlocksRoundNumber.format(individuals_filters_blocks_number, individual_block_filters_number)) + + def getSelectIndividualsiFltersBlocksIsNull(self) -> WebElement: + return self.wait_for(self.selectIndividualsFiltersBlocksIsNull) + + def getInputIndividualsFiltersBlocksValueFrom(self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0) -> WebElement: + return self.wait_for(self.inputIndividualsFiltersBlocksValueFrom.format(individuals_filters_blocks_number, individual_block_filters_number)) + + def getInputIndividualsFiltersBlocksValueTo(self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0) -> WebElement: + return self.wait_for(self.inputIndividualsFiltersBlocksValueTo.format(individuals_filters_blocks_number, individual_block_filters_number)) + + def getInputIndividualsFiltersBlocksValue(self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0) -> WebElement: + return self.wait_for(self.inputIndividualsFiltersBlocksValue.format(individuals_filters_blocks_number, individual_block_filters_number)) From 2d41c37ef353199755ca5915e1b3bd15e67db3e9 Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Wed, 21 Aug 2024 17:40:54 +0200 Subject: [PATCH 059/118] fix issues with build --- .../src/components/targeting/SubField.tsx | 36 ++++++++++++++----- frontend/src/utils/targetingUtils.ts | 19 +++++++--- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/targeting/SubField.tsx b/frontend/src/components/targeting/SubField.tsx index d0352928ea..8c85065509 100644 --- a/frontend/src/components/targeting/SubField.tsx +++ b/frontend/src/components/targeting/SubField.tsx @@ -20,15 +20,32 @@ const InlineField = styled.div` width: 48%; `; -export function SubField({ - field, - blockIndex = undefined, - index, +interface Values { + individualsFiltersBlocks?: { + individualBlockFilters?: { + isNull?: boolean; + }[]; + }[]; +} + +interface SubFieldProps { + baseName: string; + blockIndex?: number; + index?: number; + field?: any; // Adjust the type of field as necessary + choicesDict?: any; // Adjust the type of choicesDict as necessary +} + +const SubField: React.FC = ({ baseName, + blockIndex, + index, + field, choicesDict, -}): React.ReactElement { +}) => { const { t } = useTranslation(); - const { values, setFieldValue } = useFormikContext(); + const { values, setFieldValue } = useFormikContext(); + if (blockIndex === undefined) { const match = baseName.match(/block\[(\d+)\]/); if (match) { @@ -54,7 +71,7 @@ export function SubField({ return null; } - const renderFieldByType = (type) => { + const renderFieldByType = (type: string) => { switch (type) { case 'DECIMAL': return ( @@ -245,6 +262,7 @@ export function SubField({ : [] } label="Round" + data-cy="input-round-number" /> @@ -271,4 +289,6 @@ export function SubField({ }; return renderFieldByType(field.fieldAttribute.type); -} +}; + +export default SubField; diff --git a/frontend/src/utils/targetingUtils.ts b/frontend/src/utils/targetingUtils.ts index e18e6076cc..377549a65c 100644 --- a/frontend/src/utils/targetingUtils.ts +++ b/frontend/src/utils/targetingUtils.ts @@ -232,14 +232,25 @@ export function formatCriteriaIndividualsFiltersBlocks( })); } -function mapFilterToVariable(filter): { +interface Filter { + isNull: boolean; comparisonMethod: string; - arguments; + arguments: any[]; fieldName: string; flexFieldClassification: string; roundNumber?: number; -} { - const result = { +} + +interface Result { + comparisonMethod: string; + arguments: any[]; + fieldName: string; + flexFieldClassification: string; + roundNumber?: number; +} + +function mapFilterToVariable(filter: Filter): Result { + const result: Result = { comparisonMethod: filter.isNull ? 'IS_NULL' : filter.comparisonMethod, arguments: filter.isNull ? [null] : filter.arguments, fieldName: filter.fieldName, From 136a8a9cd0a220fb8e53fa790f37ef6651a6d3a2 Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Wed, 21 Aug 2024 17:43:43 +0200 Subject: [PATCH 060/118] fix import --- frontend/src/components/targeting/SubField.tsx | 4 +--- .../src/containers/forms/TargetingCriteriaHouseholdFilter.tsx | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/targeting/SubField.tsx b/frontend/src/components/targeting/SubField.tsx index 8c85065509..c981755168 100644 --- a/frontend/src/components/targeting/SubField.tsx +++ b/frontend/src/components/targeting/SubField.tsx @@ -36,7 +36,7 @@ interface SubFieldProps { choicesDict?: any; // Adjust the type of choicesDict as necessary } -const SubField: React.FC = ({ +export const SubField: React.FC = ({ baseName, blockIndex, index, @@ -290,5 +290,3 @@ const SubField: React.FC = ({ return renderFieldByType(field.fieldAttribute.type); }; - -export default SubField; diff --git a/frontend/src/containers/forms/TargetingCriteriaHouseholdFilter.tsx b/frontend/src/containers/forms/TargetingCriteriaHouseholdFilter.tsx index 87133b3e77..8e35bb2c4e 100644 --- a/frontend/src/containers/forms/TargetingCriteriaHouseholdFilter.tsx +++ b/frontend/src/containers/forms/TargetingCriteriaHouseholdFilter.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; import { ImportedIndividualFieldsQuery } from '@generated/graphql'; import { FieldChooser } from '@components/targeting/FieldChooser'; -import { SubField } from '@components/targeting/SubField'; +import SubField from '@components/targeting/SubField'; const Divider = styled.div` border-top: 1px solid #b1b1b5; From fc1e367e42c596f28b56c1dc7f1ee524cc2a510d Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Wed, 21 Aug 2024 17:44:59 +0200 Subject: [PATCH 061/118] fix again --- .../src/containers/forms/TargetingCriteriaHouseholdFilter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/containers/forms/TargetingCriteriaHouseholdFilter.tsx b/frontend/src/containers/forms/TargetingCriteriaHouseholdFilter.tsx index 8e35bb2c4e..87133b3e77 100644 --- a/frontend/src/containers/forms/TargetingCriteriaHouseholdFilter.tsx +++ b/frontend/src/containers/forms/TargetingCriteriaHouseholdFilter.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; import { ImportedIndividualFieldsQuery } from '@generated/graphql'; import { FieldChooser } from '@components/targeting/FieldChooser'; -import SubField from '@components/targeting/SubField'; +import { SubField } from '@components/targeting/SubField'; const Divider = styled.div` border-top: 1px solid #b1b1b5; From 5c7afac9df436f57b962bef7f4c67125f3461889 Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Wed, 21 Aug 2024 19:41:30 +0200 Subject: [PATCH 062/118] fix bool --- .../TargetingCriteriaDisplay/Criteria.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx index 2602a03c86..bc619f1d22 100644 --- a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx +++ b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx @@ -113,17 +113,18 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { fieldElement = (

{field.fieldAttribute.labelEn || field.fieldName}:{' '} - {field.fieldAttribute.type === 'BOOL' || - field.fieldAttribute?.pduData?.subtype ? ( - field.isNull ? ( - {t('Empty')} + {field.arguments?.[0] != null ? ( + typeof field.arguments[0] === 'boolean' ? ( + field.arguments[0] ? ( + t('Yes') + ) : ( + t('No') + ) ) : ( - {field.arguments?.[0] ? t('Yes') : t('No')} + {extractChoiceLabel(field, field.arguments[0])} ) ) : ( - - {field.arguments?.[0] != null ? field.arguments[0] : t('Empty')} - + t('Empty') )}

); From 24cffd25579b6e33ab20d919e7331a234e91f318 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Wed, 21 Aug 2024 19:38:54 +0200 Subject: [PATCH 063/118] add e2e for targeting by pdu string, add additional page obejcts --- .../apps/targeting/fixtures/data-cypress.json | 4 +- .../page_object/targeting/targeting_create.py | 12 +- .../targeting/test_targeting.py | 146 +++++++++++++++++- .../targeting/ResultsForHouseholds.tsx | 2 +- 4 files changed, 154 insertions(+), 10 deletions(-) diff --git a/backend/hct_mis_api/apps/targeting/fixtures/data-cypress.json b/backend/hct_mis_api/apps/targeting/fixtures/data-cypress.json index 301a665a14..b07e451b78 100644 --- a/backend/hct_mis_api/apps/targeting/fixtures/data-cypress.json +++ b/backend/hct_mis_api/apps/targeting/fixtures/data-cypress.json @@ -161,7 +161,7 @@ "updated_at": "2023-06-02T10:00:40.202Z", "comparison_method": "RANGE", "targeting_criteria_rule": "5c632b70-1b2a-4eb2-957b-2f1b20d77228", - "is_flex_field": false, + "flex_field_classification": "NOT_FLEX_FIELD", "field_name": "size", "arguments": [1, 11] } @@ -174,7 +174,7 @@ "updated_at": "2023-06-02T10:00:08.547Z", "comparison_method": "RANGE", "targeting_criteria_rule": "f99cd30f-c986-47b9-84ee-bc5ed1c1378c", - "is_flex_field": false, + "flex_field_classification": "NOT_FLEX_FIELD", "field_name": "size", "arguments": [1, 11] } diff --git a/backend/selenium_tests/page_object/targeting/targeting_create.py b/backend/selenium_tests/page_object/targeting/targeting_create.py index 2ef20aed90..03f93b325c 100644 --- a/backend/selenium_tests/page_object/targeting/targeting_create.py +++ b/backend/selenium_tests/page_object/targeting/targeting_create.py @@ -55,12 +55,13 @@ class TargetingCreate(BaseComponents): datePickerFilter = 'div[data-cy="date-picker-filter"]' boolField = 'div[data-cy="bool-field"]' textField = 'div[data-cy="string-textfield"]' - selectOptionsIndividualsFiltersBlocksRoundNumber = 'div[data-cy="select-options-individualsFiltersBlocks[{}].individualBlockFilters[{}].roundNumber"]' selectIndividualsFiltersBlocksRoundNumber = 'div[data-cy="select-individualsFiltersBlocks[{}].individualBlockFilters[{}].roundNumber"]' + selectRoundOption = 'li[data-cy="select-option-{}"]' selectIndividualsFiltersBlocksIsNull = 'span[data-cy="input-individualsFiltersBlocks[{}].individualBlockFilters[{}].isNull"]' inputIndividualsFiltersBlocksValueFrom = 'input[data-cy="input-individualsFiltersBlocks[{}].individualBlockFilters[{}].value.from"]' inputIndividualsFiltersBlocksValueTo = 'input[data-cy="input-individualsFiltersBlocks[{}].individualBlockFilters[{}].value.to"]' inputIndividualsFiltersBlocksValue = 'input[data-cy="input-individualsFiltersBlocks[{}].individualBlockFilters[{}].value"]' + totalNumberOfHouseholdsCount = 'div[data-cy="total-number-of-households-count"]' # Texts textTargetingCriteria = "Targeting Criteria" @@ -213,12 +214,12 @@ def getBoolField(self) -> WebElement: def getDatePickerFilter(self) -> WebElement: return self.wait_for(self.datePickerFilter) - def getSelectOptionsIndividualsFiltersBlocksRoundNumber(self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0) -> WebElement: - return self.wait_for(self.selectOptionsIndividualsFiltersBlocksRoundNumber.format(individuals_filters_blocks_number, individual_block_filters_number)) - def getSelectIndividualsiFltersBlocksRoundNumber(self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0) -> WebElement: return self.wait_for(self.selectIndividualsFiltersBlocksRoundNumber.format(individuals_filters_blocks_number, individual_block_filters_number)) + def getSelectRoundOption(self, round_number: int = 0) -> WebElement: + return self.wait_for(self.selectRoundOption.format(round_number)) + def getSelectIndividualsiFltersBlocksIsNull(self) -> WebElement: return self.wait_for(self.selectIndividualsFiltersBlocksIsNull) @@ -230,3 +231,6 @@ def getInputIndividualsFiltersBlocksValueTo(self, individuals_filters_blocks_num def getInputIndividualsFiltersBlocksValue(self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0) -> WebElement: return self.wait_for(self.inputIndividualsFiltersBlocksValue.format(individuals_filters_blocks_number, individual_block_filters_number)) + + def getTotalNumberOfHouseholdsCount(self) -> WebElement: + return self.wait_for(self.totalNumberOfHouseholdsCount) \ No newline at end of file diff --git a/backend/selenium_tests/targeting/test_targeting.py b/backend/selenium_tests/targeting/test_targeting.py index ac0649f7df..e5bdc7aba0 100644 --- a/backend/selenium_tests/targeting/test_targeting.py +++ b/backend/selenium_tests/targeting/test_targeting.py @@ -1,4 +1,5 @@ from datetime import datetime +from typing import Callable from uuid import UUID from django.conf import settings @@ -6,6 +7,9 @@ import pytest from dateutil.relativedelta import relativedelta + +from hct_mis_api.apps.periodic_data_update.utils import populate_pdu_with_null_values, field_label_to_field_name +from hct_mis_api.apps.registration_data.fixtures import RegistrationDataImportFactory from page_object.targeting.targeting import Targeting from page_object.targeting.targeting_create import TargetingCreate from page_object.targeting.targeting_details import TargetingDetails @@ -14,13 +18,13 @@ from selenium.webdriver.common.by import By from hct_mis_api.apps.account.models import User -from hct_mis_api.apps.core.fixtures import DataCollectingTypeFactory -from hct_mis_api.apps.core.models import BusinessArea, DataCollectingType +from hct_mis_api.apps.core.fixtures import DataCollectingTypeFactory, create_afghanistan +from hct_mis_api.apps.core.models import BusinessArea, DataCollectingType, FlexibleAttribute, PeriodicFieldData from hct_mis_api.apps.household.fixtures import ( create_household, create_household_and_individuals, ) -from hct_mis_api.apps.household.models import HEARING, HOST, REFUGEE, SEEING, Household +from hct_mis_api.apps.household.models import HEARING, HOST, REFUGEE, SEEING, Household, Individual from hct_mis_api.apps.program.fixtures import ProgramFactory from hct_mis_api.apps.program.models import Program from hct_mis_api.apps.targeting.fixtures import TargetingCriteriaFactory @@ -43,6 +47,98 @@ def non_sw_program() -> Program: "Test Programm", dct_type=DataCollectingType.Type.STANDARD, status=Program.ACTIVE ) +@pytest.fixture +def program() -> Program: + business_area = create_afghanistan() + return ProgramFactory(name="Test Program", status=Program.ACTIVE, business_area=business_area) + + +@pytest.fixture +def individual(program: Program) -> Individual: + def _individual(): + business_area = create_afghanistan() + rdi = RegistrationDataImportFactory() + household, individuals = create_household_and_individuals( + household_data={ + "business_area": business_area, + "program_id": program.pk, + "registration_data_import": rdi, + }, + individuals_data=[ + { + "business_area": business_area, + "program_id": program.pk, + "registration_data_import": rdi, + }, + ], + ) + individual = individuals[0] + individual.flex_fields = populate_pdu_with_null_values(program, individual.flex_fields) + return individual + return _individual + + +@pytest.fixture +def string_attribute(program: Program) -> FlexibleAttribute: + return create_flexible_attribute( + label="Test String Attribute", + subtype=PeriodicFieldData.STRING, + number_of_rounds=1, + rounds_names=["Test Round"], + program=program, + ) + + +@pytest.fixture +def date_attribute(program: Program) -> FlexibleAttribute: + return create_flexible_attribute( + label="Test Date Attribute", + subtype=PeriodicFieldData.DATE, + number_of_rounds=1, + rounds_names=["Test Round"], + program=program, + ) + + +@pytest.fixture +def bool_attribute(program: Program) -> FlexibleAttribute: + return create_flexible_attribute( + label="Test Bool Attribute", + subtype=PeriodicFieldData.BOOL, + number_of_rounds=1, + rounds_names=["Test Round"], + program=program, + ) + + +@pytest.fixture +def decimal_attribute(program: Program) -> FlexibleAttribute: + return create_flexible_attribute( + label="Test Decimal Attribute", + subtype=PeriodicFieldData.DECIMAL, + number_of_rounds=1, + rounds_names=["Test Round"], + program=program, + ) + + +def create_flexible_attribute( + label: str, subtype: str, number_of_rounds: int, rounds_names: list[str], program: Program +) -> FlexibleAttribute: + name = field_label_to_field_name(label) + flexible_attribute = FlexibleAttribute.objects.create( + label={"English(EN)": label}, + name=name, + type=FlexibleAttribute.PDU, + associated_with=FlexibleAttribute.ASSOCIATED_WITH_INDIVIDUAL, + program=program, + ) + flexible_attribute.pdu_data = PeriodicFieldData.objects.create( + subtype=subtype, number_of_rounds=number_of_rounds, rounds_names=rounds_names + ) + flexible_attribute.save() + return flexible_attribute + def create_custom_household(observed_disability: list[str], residence_status: str = HOST) -> Household: program = Program.objects.get(name="Test Programm") @@ -287,7 +383,10 @@ def test_create_targeting_for_normal_program( pageTargetingCreate.getTargetingCriteriaAutoComplete().send_keys(Keys.ENTER) pageTargetingCreate.getTargetingCriteriaValue().click() pageTargetingCreate.select_option_by_name(REFUGEE) + pageTargetingCreate.screenshot("po refugee") pageTargetingCreate.getTargetingCriteriaAddDialogSaveButton().click() + pageTargetingCreate.screenshot("po refugee click") + disability_expected_criteria_text = "Residence status: Displaced | Refugee / Asylum Seeker" assert pageTargetingCreate.getCriteriaContainer().text == disability_expected_criteria_text targeting_name = "Test targeting people" @@ -303,6 +402,47 @@ def test_create_targeting_for_normal_program( actions.move_to_element(pageTargetingDetails.getHouseholdTableCell(1, 1)).perform() # type: ignore assert len(pageTargetingDetails.getHouseholdTableRows()) == 1 + def test_create_targeting_with_pdu_string_criteria( + self, + program: Program, + pageTargeting: Targeting, + pageTargetingCreate: TargetingCreate, + pageTargetingDetails: TargetingDetails, + individual: Callable, + string_attribute: FlexibleAttribute, + ) -> None: + individual1 = individual() + individual1.flex_fields[string_attribute.name]["1"]["value"] = "Text" + individual1.save() + individual2 = individual() + individual2.flex_fields[string_attribute.name]["1"]["value"] = "Test" + individual() + pageTargeting.navigate_to_page("afghanistan", program.id) + pageTargeting.getButtonCreateNew().click() + pageTargeting.getButtonCreateNewByFilters().click() + assert "New Target Population" in pageTargetingCreate.getTitlePage().text + pageTargetingCreate.getAddCriteriaButton().click() + pageTargetingCreate.getAddIndividualRuleButton().click() + pageTargetingCreate.getTargetingCriteriaAutoComplete().click() + pageTargetingCreate.getTargetingCriteriaAutoComplete().send_keys("Test String Attribute") + pageTargetingCreate.getTargetingCriteriaAutoComplete().send_keys(Keys.ARROW_DOWN) + pageTargetingCreate.getTargetingCriteriaAutoComplete().send_keys(Keys.ENTER) + pageTargetingCreate.getSelectIndividualsiFltersBlocksRoundNumber().click() + pageTargetingCreate.getSelectRoundOption(1).click() + pageTargetingCreate.getInputIndividualsFiltersBlocksValue().send_keys("Text") + targeting_name = "Test Targeting PDU string" + pageTargetingCreate.getTargetingCriteriaAddDialogSaveButton().click() + pageTargetingCreate.getFieldName().send_keys(targeting_name) + pageTargetingCreate.getTargetPopulationSaveButton().click() + pageTargetingDetails.getLockButton() + + assert pageTargetingDetails.getTitlePage().text == targeting_name + assert pageTargetingDetails.getCriteriaContainer().text == "Test String Attribute: Text" + assert Household.objects.count() == 2 + assert pageTargetingDetails.getHouseholdTableCell(1, 1).text == individual1.household.unicef_id + assert pageTargetingCreate.getTotalNumberOfHouseholdsCount().text == "1" + assert len(pageTargetingDetails.getHouseholdTableRows()) == 1 + @pytest.mark.usefixtures("login") class TestTargeting: diff --git a/frontend/src/components/targeting/ResultsForHouseholds.tsx b/frontend/src/components/targeting/ResultsForHouseholds.tsx index e519f5e24b..162a59fe7d 100644 --- a/frontend/src/components/targeting/ResultsForHouseholds.tsx +++ b/frontend/src/components/targeting/ResultsForHouseholds.tsx @@ -157,7 +157,7 @@ export function ResultsForHouseholds({ - + {targetPopulation.totalHouseholdsCount || '0'} From 001083c5220185c8b2c9ee3dd9ba3688ddc34521 Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Wed, 21 Aug 2024 19:59:03 +0200 Subject: [PATCH 064/118] add data-cy --- .../TargetingCriteriaDisplay/Criteria.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx index bc619f1d22..2351cecdfd 100644 --- a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx +++ b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx @@ -170,15 +170,17 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { break; } + console.log('aaafield', field); return ( <> {fieldElement} - {field.type === 'PDU' && - field.pduData && - field.round && - field.pduData.roundsNames?.[field.round - 1] && ( - - {field.round}-{field.pduData.roundsNames[field.round - 1]} + {field.fieldAttribute.type === 'PDU' && + field.fieldAttribute.pduData && + field.roundNumber && + field.fieldAttribute.pduData.roundsNames?.[field.roundNumber - 1] && ( + + Round {field.roundNumber}( + {field.fieldAttribute.pduData.roundsNames[field.roundNumber - 1]}) )} From c8ff935714c2d723e4691d5116374d0c9e0aada4 Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Wed, 21 Aug 2024 20:08:29 +0200 Subject: [PATCH 065/118] fix checkbox --- .../targeting/TargetingCriteriaDisplay/Criteria.tsx | 1 - frontend/src/utils/targetingUtils.ts | 7 ++++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx index 2351cecdfd..dae4682524 100644 --- a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx +++ b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx @@ -170,7 +170,6 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { break; } - console.log('aaafield', field); return ( <> {fieldElement} diff --git a/frontend/src/utils/targetingUtils.ts b/frontend/src/utils/targetingUtils.ts index 377549a65c..d7bab37732 100644 --- a/frontend/src/utils/targetingUtils.ts +++ b/frontend/src/utils/targetingUtils.ts @@ -125,7 +125,12 @@ export function mapCriteriaToInitialValues(criteria) { individualsFiltersBlocks: individualsFiltersBlocks.map((block) => ({ individualBlockFilters: mapFiltersToInitialValues( block.individualBlockFilters, - ), + ).map((filter) => { + return { + ...filter, + isNull: filter.comparisonMethod === 'IS_NULL', + }; + }), })), }; } From 4a1b660341543b2bee195a307aedfba1e484d26e Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Wed, 21 Aug 2024 20:30:38 +0200 Subject: [PATCH 066/118] fix boolean --- .../TargetingCriteriaDisplay/Criteria.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx index dae4682524..6753c7c709 100644 --- a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx +++ b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx @@ -113,18 +113,16 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { fieldElement = (

{field.fieldAttribute.labelEn || field.fieldName}:{' '} - {field.arguments?.[0] != null ? ( - typeof field.arguments[0] === 'boolean' ? ( - field.arguments[0] ? ( - t('Yes') - ) : ( - t('No') - ) + {field.isNull === true || field.comparisonMethod === 'IS_NULL' ? ( + t('Empty') + ) : typeof field.arguments?.[0] === 'boolean' ? ( + field.arguments[0] ? ( + t('Yes') ) : ( - {extractChoiceLabel(field, field.arguments[0])} + t('No') ) ) : ( - t('Empty') + {extractChoiceLabel(field, field.arguments?.[0])} )}

); From caeadb93b73b573fe8638187510046214b6504d8 Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Wed, 21 Aug 2024 21:05:12 +0200 Subject: [PATCH 067/118] fix yes no --- .../TargetingCriteriaDisplay/Criteria.tsx | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx index 6753c7c709..0c1956d6b8 100644 --- a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx +++ b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx @@ -122,7 +122,25 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { t('No') ) ) : ( - {extractChoiceLabel(field, field.arguments?.[0])} + <> + {field.arguments?.[0] != null ? ( + typeof field.arguments[0] === 'boolean' ? ( + field.arguments[0] === true ? ( + t('Yes') + ) : ( + t('No') + ) + ) : field.arguments[0] === 'True' ? ( + t('Yes') + ) : field.arguments[0] === 'False' ? ( + t('No') + ) : ( + {extractChoiceLabel(field, field.arguments[0])} + ) + ) : ( + <>{t('Empty')} + )} + )}

); From 02fb45409d374160e6e26531f9c3c97db4ed2de4 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Wed, 21 Aug 2024 21:32:31 +0200 Subject: [PATCH 068/118] add e2e for targeting by pdu bool --- .../page_object/targeting/targeting_create.py | 4 + .../targeting/test_targeting.py | 84 +++++++++++++++++-- 2 files changed, 79 insertions(+), 9 deletions(-) diff --git a/backend/selenium_tests/page_object/targeting/targeting_create.py b/backend/selenium_tests/page_object/targeting/targeting_create.py index 03f93b325c..22e2e935c5 100644 --- a/backend/selenium_tests/page_object/targeting/targeting_create.py +++ b/backend/selenium_tests/page_object/targeting/targeting_create.py @@ -61,6 +61,7 @@ class TargetingCreate(BaseComponents): inputIndividualsFiltersBlocksValueFrom = 'input[data-cy="input-individualsFiltersBlocks[{}].individualBlockFilters[{}].value.from"]' inputIndividualsFiltersBlocksValueTo = 'input[data-cy="input-individualsFiltersBlocks[{}].individualBlockFilters[{}].value.to"]' inputIndividualsFiltersBlocksValue = 'input[data-cy="input-individualsFiltersBlocks[{}].individualBlockFilters[{}].value"]' + selectIndividualsFiltersBlocksValue = 'div[data-cy="select-individualsFiltersBlocks[{}].individualBlockFilters[{}].value"]' totalNumberOfHouseholdsCount = 'div[data-cy="total-number-of-households-count"]' # Texts @@ -232,5 +233,8 @@ def getInputIndividualsFiltersBlocksValueTo(self, individuals_filters_blocks_num def getInputIndividualsFiltersBlocksValue(self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0) -> WebElement: return self.wait_for(self.inputIndividualsFiltersBlocksValue.format(individuals_filters_blocks_number, individual_block_filters_number)) + def getSelectIndividualsFiltersBlocksValue(self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0) -> WebElement: + return self.wait_for(self.selectIndividualsFiltersBlocksValue.format(individuals_filters_blocks_number, individual_block_filters_number)) + def getTotalNumberOfHouseholdsCount(self) -> WebElement: return self.wait_for(self.totalNumberOfHouseholdsCount) \ No newline at end of file diff --git a/backend/selenium_tests/targeting/test_targeting.py b/backend/selenium_tests/targeting/test_targeting.py index e5bdc7aba0..16a1e40a2e 100644 --- a/backend/selenium_tests/targeting/test_targeting.py +++ b/backend/selenium_tests/targeting/test_targeting.py @@ -84,7 +84,7 @@ def string_attribute(program: Program) -> FlexibleAttribute: label="Test String Attribute", subtype=PeriodicFieldData.STRING, number_of_rounds=1, - rounds_names=["Test Round"], + rounds_names=["Test Round String"], program=program, ) @@ -95,7 +95,7 @@ def date_attribute(program: Program) -> FlexibleAttribute: label="Test Date Attribute", subtype=PeriodicFieldData.DATE, number_of_rounds=1, - rounds_names=["Test Round"], + rounds_names=["Test Round Date"], program=program, ) @@ -105,8 +105,8 @@ def bool_attribute(program: Program) -> FlexibleAttribute: return create_flexible_attribute( label="Test Bool Attribute", subtype=PeriodicFieldData.BOOL, - number_of_rounds=1, - rounds_names=["Test Round"], + number_of_rounds=2, + rounds_names=["Test Round Bool 1", "Test Round Bool 2"], program=program, ) @@ -117,7 +117,7 @@ def decimal_attribute(program: Program) -> FlexibleAttribute: label="Test Decimal Attribute", subtype=PeriodicFieldData.DECIMAL, number_of_rounds=1, - rounds_names=["Test Round"], + rounds_names=["Test Round Decimal"], program=program, ) @@ -383,9 +383,7 @@ def test_create_targeting_for_normal_program( pageTargetingCreate.getTargetingCriteriaAutoComplete().send_keys(Keys.ENTER) pageTargetingCreate.getTargetingCriteriaValue().click() pageTargetingCreate.select_option_by_name(REFUGEE) - pageTargetingCreate.screenshot("po refugee") pageTargetingCreate.getTargetingCriteriaAddDialogSaveButton().click() - pageTargetingCreate.screenshot("po refugee click") disability_expected_criteria_text = "Residence status: Displaced | Refugee / Asylum Seeker" assert pageTargetingCreate.getCriteriaContainer().text == disability_expected_criteria_text @@ -416,6 +414,7 @@ def test_create_targeting_with_pdu_string_criteria( individual1.save() individual2 = individual() individual2.flex_fields[string_attribute.name]["1"]["value"] = "Test" + individual2.save() individual() pageTargeting.navigate_to_page("afghanistan", program.id) pageTargeting.getButtonCreateNew().click() @@ -430,19 +429,86 @@ def test_create_targeting_with_pdu_string_criteria( pageTargetingCreate.getSelectIndividualsiFltersBlocksRoundNumber().click() pageTargetingCreate.getSelectRoundOption(1).click() pageTargetingCreate.getInputIndividualsFiltersBlocksValue().send_keys("Text") - targeting_name = "Test Targeting PDU string" pageTargetingCreate.getTargetingCriteriaAddDialogSaveButton().click() + targeting_name = "Test Targeting PDU string" pageTargetingCreate.getFieldName().send_keys(targeting_name) pageTargetingCreate.getTargetPopulationSaveButton().click() pageTargetingDetails.getLockButton() assert pageTargetingDetails.getTitlePage().text == targeting_name assert pageTargetingDetails.getCriteriaContainer().text == "Test String Attribute: Text" - assert Household.objects.count() == 2 + assert Household.objects.count() == 3 assert pageTargetingDetails.getHouseholdTableCell(1, 1).text == individual1.household.unicef_id assert pageTargetingCreate.getTotalNumberOfHouseholdsCount().text == "1" assert len(pageTargetingDetails.getHouseholdTableRows()) == 1 + def test_create_targeting_with_pdu_bool_criteria( + self, + program: Program, + pageTargeting: Targeting, + pageTargetingCreate: TargetingCreate, + pageTargetingDetails: TargetingDetails, + individual: Callable, + bool_attribute: FlexibleAttribute, + ) -> None: + individual1 = individual() + individual1.flex_fields[bool_attribute.name]["2"]["value"] = True + individual1.save() + individual2 = individual() + individual2.flex_fields[bool_attribute.name]["2"]["value"] = False + individual2.save() + individual() + pageTargeting.navigate_to_page("afghanistan", program.id) + pageTargeting.getButtonCreateNew().click() + pageTargeting.getButtonCreateNewByFilters().click() + assert "New Target Population" in pageTargetingCreate.getTitlePage().text + pageTargetingCreate.getAddCriteriaButton().click() + pageTargetingCreate.getAddIndividualRuleButton().click() + pageTargetingCreate.getTargetingCriteriaAutoComplete().click() + pageTargetingCreate.getTargetingCriteriaAutoComplete().send_keys("Test Bool Attribute") + pageTargetingCreate.getTargetingCriteriaAutoComplete().send_keys(Keys.ARROW_DOWN) + pageTargetingCreate.getTargetingCriteriaAutoComplete().send_keys(Keys.ENTER) + pageTargetingCreate.getSelectIndividualsiFltersBlocksRoundNumber().click() + pageTargetingCreate.getSelectRoundOption(2).click() + pageTargetingCreate.getSelectIndividualsFiltersBlocksValue().click() + pageTargetingCreate.select_option_by_name("True") + bool_yes_expected_criteria_text = "Test Bool Attribute: Yes" + pageTargetingCreate.getTargetingCriteriaAddDialogSaveButton().click() + assert pageTargetingCreate.getCriteriaContainer().text == bool_yes_expected_criteria_text + + targeting_name = "Test Targeting PDU bool" + pageTargetingCreate.getFieldName().send_keys(targeting_name) + pageTargetingCreate.getTargetPopulationSaveButton().click() + + pageTargetingDetails.getLockButton() + + assert pageTargetingDetails.getTitlePage().text == targeting_name + assert pageTargetingDetails.getCriteriaContainer().text == bool_yes_expected_criteria_text + assert Household.objects.count() == 3 + assert pageTargetingDetails.getHouseholdTableCell(1, 1).text == individual1.household.unicef_id + assert pageTargetingCreate.getTotalNumberOfHouseholdsCount().text == "1" + assert len(pageTargetingDetails.getHouseholdTableRows()) == 1 + + # edit to False + pageTargetingDetails.getButtonEdit().click() + pageTargetingDetails.getButtonIconEdit().click() + pageTargetingCreate.getSelectIndividualsFiltersBlocksValue().click() + pageTargetingCreate.select_option_by_name("False") + bool_no_expected_criteria_text = "Test Bool Attribute: No" + + pageTargetingCreate.get_elements(pageTargetingCreate.targetingCriteriaAddDialogSaveButton)[1].click() + + assert pageTargetingCreate.getCriteriaContainer().text == bool_no_expected_criteria_text + pageTargetingCreate.getButtonSave().click() + pageTargetingDetails.getLockButton() + + pageTargetingDetails.screenshot("bool test after 122222211") + + assert pageTargetingDetails.getCriteriaContainer().text == bool_no_expected_criteria_text + assert pageTargetingDetails.getHouseholdTableCell(1, 1).text == individual2.household.unicef_id + assert pageTargetingCreate.getTotalNumberOfHouseholdsCount().text == "1" + assert len(pageTargetingDetails.getHouseholdTableRows()) == 1 + @pytest.mark.usefixtures("login") class TestTargeting: From 8c81c3dabb18f3e11d8ce80fd87f8b9ab7470288 Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Wed, 21 Aug 2024 21:45:09 +0200 Subject: [PATCH 069/118] fix for mapping Yes No True False to variables --- frontend/src/utils/targetingUtils.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/frontend/src/utils/targetingUtils.ts b/frontend/src/utils/targetingUtils.ts index d7bab37732..f63c90dab4 100644 --- a/frontend/src/utils/targetingUtils.ts +++ b/frontend/src/utils/targetingUtils.ts @@ -257,7 +257,15 @@ interface Result { function mapFilterToVariable(filter: Filter): Result { const result: Result = { comparisonMethod: filter.isNull ? 'IS_NULL' : filter.comparisonMethod, - arguments: filter.isNull ? [null] : filter.arguments, + arguments: filter.isNull + ? [null] + : filter.arguments.map((arg) => + arg === 'True' || arg === 'Yes' + ? true + : arg === 'False' || arg === 'No' + ? false + : arg, + ), fieldName: filter.fieldName, flexFieldClassification: filter.flexFieldClassification, }; From b3bde3d01743c04fd93709d10ffa1942a2834a84 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Thu, 22 Aug 2024 00:09:59 +0200 Subject: [PATCH 070/118] assert text with round data --- backend/selenium_tests/targeting/test_targeting.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/backend/selenium_tests/targeting/test_targeting.py b/backend/selenium_tests/targeting/test_targeting.py index 16a1e40a2e..ec023125a4 100644 --- a/backend/selenium_tests/targeting/test_targeting.py +++ b/backend/selenium_tests/targeting/test_targeting.py @@ -430,13 +430,14 @@ def test_create_targeting_with_pdu_string_criteria( pageTargetingCreate.getSelectRoundOption(1).click() pageTargetingCreate.getInputIndividualsFiltersBlocksValue().send_keys("Text") pageTargetingCreate.getTargetingCriteriaAddDialogSaveButton().click() + expected_criteria_text = "Test String Attribute: Text\nRound 1 (Test Round String 1)" + assert pageTargetingCreate.getCriteriaContainer().text == expected_criteria_text targeting_name = "Test Targeting PDU string" pageTargetingCreate.getFieldName().send_keys(targeting_name) pageTargetingCreate.getTargetPopulationSaveButton().click() pageTargetingDetails.getLockButton() - assert pageTargetingDetails.getTitlePage().text == targeting_name - assert pageTargetingDetails.getCriteriaContainer().text == "Test String Attribute: Text" + assert pageTargetingDetails.getCriteriaContainer().text == expected_criteria_text assert Household.objects.count() == 3 assert pageTargetingDetails.getHouseholdTableCell(1, 1).text == individual1.household.unicef_id assert pageTargetingCreate.getTotalNumberOfHouseholdsCount().text == "1" @@ -472,8 +473,8 @@ def test_create_targeting_with_pdu_bool_criteria( pageTargetingCreate.getSelectRoundOption(2).click() pageTargetingCreate.getSelectIndividualsFiltersBlocksValue().click() pageTargetingCreate.select_option_by_name("True") - bool_yes_expected_criteria_text = "Test Bool Attribute: Yes" pageTargetingCreate.getTargetingCriteriaAddDialogSaveButton().click() + bool_yes_expected_criteria_text = "Test Bool Attribute: Yes\nRound 2 (Test Round Bool 2)" assert pageTargetingCreate.getCriteriaContainer().text == bool_yes_expected_criteria_text targeting_name = "Test Targeting PDU bool" @@ -494,7 +495,7 @@ def test_create_targeting_with_pdu_bool_criteria( pageTargetingDetails.getButtonIconEdit().click() pageTargetingCreate.getSelectIndividualsFiltersBlocksValue().click() pageTargetingCreate.select_option_by_name("False") - bool_no_expected_criteria_text = "Test Bool Attribute: No" + bool_no_expected_criteria_text = "Test Bool Attribute: No\nRound 2 (Test Round Bool 2)" pageTargetingCreate.get_elements(pageTargetingCreate.targetingCriteriaAddDialogSaveButton)[1].click() @@ -502,8 +503,6 @@ def test_create_targeting_with_pdu_bool_criteria( pageTargetingCreate.getButtonSave().click() pageTargetingDetails.getLockButton() - pageTargetingDetails.screenshot("bool test after 122222211") - assert pageTargetingDetails.getCriteriaContainer().text == bool_no_expected_criteria_text assert pageTargetingDetails.getHouseholdTableCell(1, 1).text == individual2.household.unicef_id assert pageTargetingCreate.getTotalNumberOfHouseholdsCount().text == "1" From 46da3bae791d21c2626c9e8a937d1d4f0bc11f97 Mon Sep 17 00:00:00 2001 From: Szymon Wyderka Date: Thu, 22 Aug 2024 12:12:18 +0200 Subject: [PATCH 071/118] Fix test test_edit_programme_with_rdi --- backend/selenium_tests/helpers/helper.py | 18 ++++++++++++++++++ .../test_programme_management.py | 12 ++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/backend/selenium_tests/helpers/helper.py b/backend/selenium_tests/helpers/helper.py index ace2b5c124..2887df6f07 100644 --- a/backend/selenium_tests/helpers/helper.py +++ b/backend/selenium_tests/helpers/helper.py @@ -98,6 +98,24 @@ def select_listbox_element( else: raise AssertionError(f"Element: {name} is not in the list: {[item.text for item in items]}") + def get_listbox_element( + self, + name: str, + listbox: str = 'ul[role="listbox"]', + tag_name: str = "li", + delay_before: int = 2, + delay_between_checks: float = 0.5, + ) -> WebElement: + sleep(delay_before) + select_element = self.wait_for(listbox) + items = select_element.find_elements("tag name", tag_name) + for item in items: + sleep(delay_between_checks) + if name in item.text: + return item + else: + raise AssertionError(f"Element: {name} is not in the list: {[item.text for item in items]}") + def check_page_after_click(self, button: WebElement, url_fragment: str) -> None: programme_creation_url = self.driver.current_url button.click() diff --git a/backend/selenium_tests/programme_management/test_programme_management.py b/backend/selenium_tests/programme_management/test_programme_management.py index f5caac43db..0f9c278b26 100644 --- a/backend/selenium_tests/programme_management/test_programme_management.py +++ b/backend/selenium_tests/programme_management/test_programme_management.py @@ -827,9 +827,9 @@ def test_edit_programme_with_rdi( pageProgrammeManagement.getButtonAddTimeSeriesField().click() pageProgrammeManagement.getInputPduFieldsObjectLabel(0).send_keys("Time Series Field Name 1") pageProgrammeManagement.getSelectPduFieldsObjectPduDataSubtype(0).click() - pageProgrammeManagement.select_listbox_element("Text").click() + pageProgrammeManagement.select_listbox_element("Text") pageProgrammeManagement.getSelectPduFieldsObjectPduDataNumberOfRounds(0).click() - pageProgrammeManagement.select_listbox_element("2").click() + pageProgrammeManagement.select_listbox_element("2") pageProgrammeManagement.getInputPduFieldsRoundsNames(0, 0).send_keys("Round 1") pageProgrammeManagement.getInputPduFieldsRoundsNames(0, 1).send_keys("Round 2") pageProgrammeManagement.getButtonNext().click() @@ -867,19 +867,19 @@ def test_edit_programme_with_rdi( # only possible to increase number of rounds pageProgrammeManagement.getSelectPduFieldsObjectPduDataNumberOfRounds(0).click() - is_disabled_decrease_round_number = pageProgrammeManagement.select_listbox_element("1").get_attribute( + is_disabled_decrease_round_number = pageProgrammeManagement.get_listbox_element("1").get_attribute( "aria-disabled" ) assert is_disabled_decrease_round_number == "true" - is_disabled_decrease_round_number = pageProgrammeManagement.select_listbox_element("2").get_attribute( + is_disabled_decrease_round_number = pageProgrammeManagement.get_listbox_element("2").get_attribute( "aria-disabled" ) assert is_disabled_decrease_round_number is None - is_disabled_decrease_round_number = pageProgrammeManagement.select_listbox_element("3").get_attribute( + is_disabled_decrease_round_number = pageProgrammeManagement.get_listbox_element("3").get_attribute( "aria-disabled" ) assert is_disabled_decrease_round_number is None - pageProgrammeManagement.select_listbox_element("3").click() + pageProgrammeManagement.select_listbox_element("3") is_disabled_edit_time_series_existing_round_name_1 = pageProgrammeManagement.getInputPduFieldsRoundsNames( 0, 0 From bf31e6f3c0aa148604717d769685d106f713c383 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Thu, 22 Aug 2024 14:07:27 +0200 Subject: [PATCH 072/118] not allow edit pdu fields if program has tp, just increase round numbers; linter --- .../service/flexible_attribute_service.py | 6 +- .../snapshots/snap_test_update_program.py | 4 +- .../page_object/targeting/targeting_create.py | 82 ++++++++++++++----- .../targeting/test_targeting.py | 30 +++++-- 4 files changed, 90 insertions(+), 32 deletions(-) diff --git a/backend/hct_mis_api/apps/periodic_data_update/service/flexible_attribute_service.py b/backend/hct_mis_api/apps/periodic_data_update/service/flexible_attribute_service.py index 4e297360af..97107493c1 100644 --- a/backend/hct_mis_api/apps/periodic_data_update/service/flexible_attribute_service.py +++ b/backend/hct_mis_api/apps/periodic_data_update/service/flexible_attribute_service.py @@ -75,7 +75,7 @@ def update_pdu_flex_attributes(self) -> None: self.delete_pdu_flex_attributes(flexible_attribute_ids_to_preserve=flexible_attribute_ids_to_preserve) def update_pdu_flex_attributes_in_program_update(self) -> None: - if self.program.registration_imports.exists(): + if self.program.registration_imports.exists() or self.program.targetpopulation_set.exists(): self.increase_pdu_rounds_for_program_with_rdi() else: self.update_pdu_flex_attributes() @@ -116,6 +116,6 @@ def _validate_pdu_data_for_program_with_rdi(pdu_data_object: PeriodicFieldData, new_number_of_rounds = pdu_data["number_of_rounds"] new_rounds_names = pdu_data["rounds_names"] if new_number_of_rounds <= current_number_of_rounds: - raise GraphQLError("It is not possible to decrease the number of rounds for a Program with RDI") + raise GraphQLError("It is not possible to decrease the number of rounds for a Program with RDI or TP") if current_rounds_names != new_rounds_names[:current_number_of_rounds]: - raise GraphQLError("It is not possible to change the names of existing rounds for a Program with RDI") + raise GraphQLError("It is not possible to change the names of existing rounds for a Program with RDI or TP") diff --git a/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_update_program.py b/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_update_program.py index 2b680ba818..10987f748d 100644 --- a/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_update_program.py +++ b/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_update_program.py @@ -1122,7 +1122,7 @@ 'line': 3 } ], - 'message': 'It is not possible to change the names of existing rounds for a Program with RDI', + 'message': 'It is not possible to change the names of existing rounds for a Program with RDI or TP', 'path': [ 'updateProgram' ] @@ -1142,7 +1142,7 @@ 'line': 3 } ], - 'message': 'It is not possible to decrease the number of rounds for a Program with RDI', + 'message': 'It is not possible to decrease the number of rounds for a Program with RDI or TP', 'path': [ 'updateProgram' ] diff --git a/backend/selenium_tests/page_object/targeting/targeting_create.py b/backend/selenium_tests/page_object/targeting/targeting_create.py index 22e2e935c5..8454796fd5 100644 --- a/backend/selenium_tests/page_object/targeting/targeting_create.py +++ b/backend/selenium_tests/page_object/targeting/targeting_create.py @@ -55,13 +55,25 @@ class TargetingCreate(BaseComponents): datePickerFilter = 'div[data-cy="date-picker-filter"]' boolField = 'div[data-cy="bool-field"]' textField = 'div[data-cy="string-textfield"]' - selectIndividualsFiltersBlocksRoundNumber = 'div[data-cy="select-individualsFiltersBlocks[{}].individualBlockFilters[{}].roundNumber"]' + selectIndividualsFiltersBlocksRoundNumber = ( + 'div[data-cy="select-individualsFiltersBlocks[{}].individualBlockFilters[{}].roundNumber"]' + ) selectRoundOption = 'li[data-cy="select-option-{}"]' - selectIndividualsFiltersBlocksIsNull = 'span[data-cy="input-individualsFiltersBlocks[{}].individualBlockFilters[{}].isNull"]' - inputIndividualsFiltersBlocksValueFrom = 'input[data-cy="input-individualsFiltersBlocks[{}].individualBlockFilters[{}].value.from"]' - inputIndividualsFiltersBlocksValueTo = 'input[data-cy="input-individualsFiltersBlocks[{}].individualBlockFilters[{}].value.to"]' - inputIndividualsFiltersBlocksValue = 'input[data-cy="input-individualsFiltersBlocks[{}].individualBlockFilters[{}].value"]' - selectIndividualsFiltersBlocksValue = 'div[data-cy="select-individualsFiltersBlocks[{}].individualBlockFilters[{}].value"]' + selectIndividualsFiltersBlocksIsNull = ( + 'span[data-cy="input-individualsFiltersBlocks[{}].individualBlockFilters[{}].isNull"]' + ) + inputIndividualsFiltersBlocksValueFrom = ( + 'input[data-cy="input-individualsFiltersBlocks[{}].individualBlockFilters[{}].value.from"]' + ) + inputIndividualsFiltersBlocksValueTo = ( + 'input[data-cy="input-individualsFiltersBlocks[{}].individualBlockFilters[{}].value.to"]' + ) + inputIndividualsFiltersBlocksValue = ( + 'input[data-cy="input-individualsFiltersBlocks[{}].individualBlockFilters[{}].value"]' + ) + selectIndividualsFiltersBlocksValue = ( + 'div[data-cy="select-individualsFiltersBlocks[{}].individualBlockFilters[{}].value"]' + ) totalNumberOfHouseholdsCount = 'div[data-cy="total-number-of-households-count"]' # Texts @@ -215,8 +227,14 @@ def getBoolField(self) -> WebElement: def getDatePickerFilter(self) -> WebElement: return self.wait_for(self.datePickerFilter) - def getSelectIndividualsiFltersBlocksRoundNumber(self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0) -> WebElement: - return self.wait_for(self.selectIndividualsFiltersBlocksRoundNumber.format(individuals_filters_blocks_number, individual_block_filters_number)) + def getSelectIndividualsiFltersBlocksRoundNumber( + self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0 + ) -> WebElement: + return self.wait_for( + self.selectIndividualsFiltersBlocksRoundNumber.format( + individuals_filters_blocks_number, individual_block_filters_number + ) + ) def getSelectRoundOption(self, round_number: int = 0) -> WebElement: return self.wait_for(self.selectRoundOption.format(round_number)) @@ -224,17 +242,41 @@ def getSelectRoundOption(self, round_number: int = 0) -> WebElement: def getSelectIndividualsiFltersBlocksIsNull(self) -> WebElement: return self.wait_for(self.selectIndividualsFiltersBlocksIsNull) - def getInputIndividualsFiltersBlocksValueFrom(self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0) -> WebElement: - return self.wait_for(self.inputIndividualsFiltersBlocksValueFrom.format(individuals_filters_blocks_number, individual_block_filters_number)) - - def getInputIndividualsFiltersBlocksValueTo(self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0) -> WebElement: - return self.wait_for(self.inputIndividualsFiltersBlocksValueTo.format(individuals_filters_blocks_number, individual_block_filters_number)) - - def getInputIndividualsFiltersBlocksValue(self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0) -> WebElement: - return self.wait_for(self.inputIndividualsFiltersBlocksValue.format(individuals_filters_blocks_number, individual_block_filters_number)) - - def getSelectIndividualsFiltersBlocksValue(self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0) -> WebElement: - return self.wait_for(self.selectIndividualsFiltersBlocksValue.format(individuals_filters_blocks_number, individual_block_filters_number)) + def getInputIndividualsFiltersBlocksValueFrom( + self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0 + ) -> WebElement: + return self.wait_for( + self.inputIndividualsFiltersBlocksValueFrom.format( + individuals_filters_blocks_number, individual_block_filters_number + ) + ) + + def getInputIndividualsFiltersBlocksValueTo( + self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0 + ) -> WebElement: + return self.wait_for( + self.inputIndividualsFiltersBlocksValueTo.format( + individuals_filters_blocks_number, individual_block_filters_number + ) + ) + + def getInputIndividualsFiltersBlocksValue( + self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0 + ) -> WebElement: + return self.wait_for( + self.inputIndividualsFiltersBlocksValue.format( + individuals_filters_blocks_number, individual_block_filters_number + ) + ) + + def getSelectIndividualsFiltersBlocksValue( + self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0 + ) -> WebElement: + return self.wait_for( + self.selectIndividualsFiltersBlocksValue.format( + individuals_filters_blocks_number, individual_block_filters_number + ) + ) def getTotalNumberOfHouseholdsCount(self) -> WebElement: - return self.wait_for(self.totalNumberOfHouseholdsCount) \ No newline at end of file + return self.wait_for(self.totalNumberOfHouseholdsCount) diff --git a/backend/selenium_tests/targeting/test_targeting.py b/backend/selenium_tests/targeting/test_targeting.py index ec023125a4..44ea996127 100644 --- a/backend/selenium_tests/targeting/test_targeting.py +++ b/backend/selenium_tests/targeting/test_targeting.py @@ -7,9 +7,6 @@ import pytest from dateutil.relativedelta import relativedelta - -from hct_mis_api.apps.periodic_data_update.utils import populate_pdu_with_null_values, field_label_to_field_name -from hct_mis_api.apps.registration_data.fixtures import RegistrationDataImportFactory from page_object.targeting.targeting import Targeting from page_object.targeting.targeting_create import TargetingCreate from page_object.targeting.targeting_details import TargetingDetails @@ -19,14 +16,31 @@ from hct_mis_api.apps.account.models import User from hct_mis_api.apps.core.fixtures import DataCollectingTypeFactory, create_afghanistan -from hct_mis_api.apps.core.models import BusinessArea, DataCollectingType, FlexibleAttribute, PeriodicFieldData +from hct_mis_api.apps.core.models import ( + BusinessArea, + DataCollectingType, + FlexibleAttribute, + PeriodicFieldData, +) from hct_mis_api.apps.household.fixtures import ( create_household, create_household_and_individuals, ) -from hct_mis_api.apps.household.models import HEARING, HOST, REFUGEE, SEEING, Household, Individual +from hct_mis_api.apps.household.models import ( + HEARING, + HOST, + REFUGEE, + SEEING, + Household, + Individual, +) +from hct_mis_api.apps.periodic_data_update.utils import ( + field_label_to_field_name, + populate_pdu_with_null_values, +) from hct_mis_api.apps.program.fixtures import ProgramFactory from hct_mis_api.apps.program.models import Program +from hct_mis_api.apps.registration_data.fixtures import RegistrationDataImportFactory from hct_mis_api.apps.targeting.fixtures import TargetingCriteriaFactory from hct_mis_api.apps.targeting.models import TargetPopulation from selenium_tests.page_object.filters import Filters @@ -47,6 +61,7 @@ def non_sw_program() -> Program: "Test Programm", dct_type=DataCollectingType.Type.STANDARD, status=Program.ACTIVE ) + @pytest.fixture def program() -> Program: business_area = create_afghanistan() @@ -54,8 +69,8 @@ def program() -> Program: @pytest.fixture -def individual(program: Program) -> Individual: - def _individual(): +def individual(program: Program) -> Callable: + def _individual() -> Individual: business_area = create_afghanistan() rdi = RegistrationDataImportFactory() household, individuals = create_household_and_individuals( @@ -75,6 +90,7 @@ def _individual(): individual = individuals[0] individual.flex_fields = populate_pdu_with_null_values(program, individual.flex_fields) return individual + return _individual From 27205fb1ff393c6b9eaafbed5836de896732f38f Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Thu, 22 Aug 2024 18:22:22 +0200 Subject: [PATCH 073/118] fix round display --- frontend/src/__generated__/graphql.tsx | 9 ++++++--- .../apollo/fragments/ProgramDetailsFragment.ts | 3 +++ .../CreateProgram/ProgramFieldSeriesStep.tsx | 16 ++++++++++------ .../TargetingCriteriaDisplay/Criteria.tsx | 13 ++++++++----- .../containers/pages/program/EditProgramPage.tsx | 5 ++++- 5 files changed, 31 insertions(+), 15 deletions(-) diff --git a/frontend/src/__generated__/graphql.tsx b/frontend/src/__generated__/graphql.tsx index 861511973a..33a0452ccf 100644 --- a/frontend/src/__generated__/graphql.tsx +++ b/frontend/src/__generated__/graphql.tsx @@ -9500,7 +9500,7 @@ export type MergedIndividualMinimalFragment = { __typename?: 'IndividualNode', i export type PaymentRecordDetailsFragment = { __typename?: 'PaymentRecordNode', id: string, status: PaymentRecordStatus, statusDate: any, caId?: string | null, caHashId?: any | null, registrationCaId?: string | null, fullName: string, distributionModality: string, totalPersonsCovered: number, currency: string, entitlementQuantity?: number | null, deliveredQuantity?: number | null, deliveredQuantityUsd?: number | null, deliveryDate?: any | null, entitlementCardIssueDate?: any | null, entitlementCardNumber?: string | null, transactionReferenceId?: string | null, verification?: { __typename?: 'PaymentVerificationNode', id: string, status: PaymentVerificationStatus, statusDate?: any | null, receivedAmount?: number | null } | null, household: { __typename?: 'HouseholdNode', id: string, status?: string | null, size?: number | null, unicefId?: string | null, headOfHousehold?: { __typename?: 'IndividualNode', id: string, phoneNo: string, phoneNoAlternative: string, phoneNoValid?: boolean | null, phoneNoAlternativeValid?: boolean | null } | null }, targetPopulation: { __typename?: 'TargetPopulationNode', id: string, name: string }, parent?: { __typename?: 'CashPlanNode', id: string, caId?: string | null, program: { __typename?: 'ProgramNode', id: string, name: string }, verificationPlans?: { __typename?: 'PaymentVerificationPlanNodeConnection', edges: Array<{ __typename?: 'PaymentVerificationPlanNodeEdge', node?: { __typename?: 'PaymentVerificationPlanNode', id: string, status: PaymentVerificationPlanStatus, verificationChannel: PaymentVerificationPlanVerificationChannel } | null } | null> } | null } | null, deliveryType?: { __typename?: 'DeliveryMechanismNode', name?: string | null } | null, serviceProvider: { __typename?: 'ServiceProviderNode', id: string, fullName?: string | null, shortName?: string | null } }; -export type ProgramDetailsFragment = { __typename?: 'ProgramNode', id: string, name: string, programmeCode?: string | null, startDate: any, endDate: any, status: ProgramStatus, caId?: string | null, caHashId?: string | null, description: string, budget?: any | null, frequencyOfPayments: ProgramFrequencyOfPayments, cashPlus: boolean, populationGoal: number, scope?: ProgramScope | null, sector: ProgramSector, totalNumberOfHouseholds?: number | null, totalNumberOfHouseholdsWithTpInProgram?: number | null, administrativeAreasOfImplementation: string, isSocialWorkerProgram?: boolean | null, version: any, adminUrl?: string | null, partnerAccess: ProgramPartnerAccess, dataCollectingType?: { __typename?: 'DataCollectingTypeNode', id: string, code: string, label: string, active: boolean, individualFiltersAvailable: boolean, householdFiltersAvailable: boolean, description: string, type?: DataCollectingTypeType | null } | null, partners?: Array<{ __typename?: 'PartnerNode', id: string, name?: string | null, areaAccess?: string | null, areas?: Array<{ __typename?: 'AreaNode', id: string, level: number } | null> | null } | null> | null, registrationImports: { __typename?: 'RegistrationDataImportNodeConnection', totalCount?: number | null }, pduFields?: Array<{ __typename?: 'PeriodicFieldNode', id: string, label: any, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null> | null }; +export type ProgramDetailsFragment = { __typename?: 'ProgramNode', id: string, name: string, programmeCode?: string | null, startDate: any, endDate: any, status: ProgramStatus, caId?: string | null, caHashId?: string | null, description: string, budget?: any | null, frequencyOfPayments: ProgramFrequencyOfPayments, cashPlus: boolean, populationGoal: number, scope?: ProgramScope | null, sector: ProgramSector, totalNumberOfHouseholds?: number | null, totalNumberOfHouseholdsWithTpInProgram?: number | null, administrativeAreasOfImplementation: string, isSocialWorkerProgram?: boolean | null, version: any, adminUrl?: string | null, partnerAccess: ProgramPartnerAccess, dataCollectingType?: { __typename?: 'DataCollectingTypeNode', id: string, code: string, label: string, active: boolean, individualFiltersAvailable: boolean, householdFiltersAvailable: boolean, description: string, type?: DataCollectingTypeType | null } | null, partners?: Array<{ __typename?: 'PartnerNode', id: string, name?: string | null, areaAccess?: string | null, areas?: Array<{ __typename?: 'AreaNode', id: string, level: number } | null> | null } | null> | null, registrationImports: { __typename?: 'RegistrationDataImportNodeConnection', totalCount?: number | null }, targetpopulationSet: { __typename?: 'TargetPopulationNodeConnection', totalCount?: number | null }, pduFields?: Array<{ __typename?: 'PeriodicFieldNode', id: string, label: any, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null> | null }; export type RegistrationMinimalFragment = { __typename?: 'RegistrationDataImportNode', id: string, createdAt: any, name: string, status: RegistrationDataImportStatus, erased: boolean, importDate: any, dataSource: RegistrationDataImportDataSource, numberOfHouseholds: number, numberOfIndividuals: number, refuseReason?: string | null, totalHouseholdsCountWithValidPhoneNo?: number | null, adminUrl?: string | null, importedBy?: { __typename?: 'UserNode', id: string, firstName: string, lastName: string, email: string } | null, program?: { __typename?: 'ProgramNode', id: string, name: string, startDate: any, endDate: any, status: ProgramStatus } | null }; @@ -9981,7 +9981,7 @@ export type UpdateProgramMutationVariables = Exact<{ }>; -export type UpdateProgramMutation = { __typename?: 'Mutations', updateProgram?: { __typename?: 'UpdateProgram', validationErrors?: any | null, program?: { __typename?: 'ProgramNode', id: string, name: string, programmeCode?: string | null, startDate: any, endDate: any, status: ProgramStatus, caId?: string | null, caHashId?: string | null, description: string, budget?: any | null, frequencyOfPayments: ProgramFrequencyOfPayments, cashPlus: boolean, populationGoal: number, scope?: ProgramScope | null, sector: ProgramSector, totalNumberOfHouseholds?: number | null, totalNumberOfHouseholdsWithTpInProgram?: number | null, administrativeAreasOfImplementation: string, isSocialWorkerProgram?: boolean | null, version: any, adminUrl?: string | null, partnerAccess: ProgramPartnerAccess, dataCollectingType?: { __typename?: 'DataCollectingTypeNode', id: string, code: string, label: string, active: boolean, individualFiltersAvailable: boolean, householdFiltersAvailable: boolean, description: string, type?: DataCollectingTypeType | null } | null, partners?: Array<{ __typename?: 'PartnerNode', id: string, name?: string | null, areaAccess?: string | null, areas?: Array<{ __typename?: 'AreaNode', id: string, level: number } | null> | null } | null> | null, registrationImports: { __typename?: 'RegistrationDataImportNodeConnection', totalCount?: number | null }, pduFields?: Array<{ __typename?: 'PeriodicFieldNode', id: string, label: any, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null> | null } | null } | null }; +export type UpdateProgramMutation = { __typename?: 'Mutations', updateProgram?: { __typename?: 'UpdateProgram', validationErrors?: any | null, program?: { __typename?: 'ProgramNode', id: string, name: string, programmeCode?: string | null, startDate: any, endDate: any, status: ProgramStatus, caId?: string | null, caHashId?: string | null, description: string, budget?: any | null, frequencyOfPayments: ProgramFrequencyOfPayments, cashPlus: boolean, populationGoal: number, scope?: ProgramScope | null, sector: ProgramSector, totalNumberOfHouseholds?: number | null, totalNumberOfHouseholdsWithTpInProgram?: number | null, administrativeAreasOfImplementation: string, isSocialWorkerProgram?: boolean | null, version: any, adminUrl?: string | null, partnerAccess: ProgramPartnerAccess, dataCollectingType?: { __typename?: 'DataCollectingTypeNode', id: string, code: string, label: string, active: boolean, individualFiltersAvailable: boolean, householdFiltersAvailable: boolean, description: string, type?: DataCollectingTypeType | null } | null, partners?: Array<{ __typename?: 'PartnerNode', id: string, name?: string | null, areaAccess?: string | null, areas?: Array<{ __typename?: 'AreaNode', id: string, level: number } | null> | null } | null> | null, registrationImports: { __typename?: 'RegistrationDataImportNodeConnection', totalCount?: number | null }, targetpopulationSet: { __typename?: 'TargetPopulationNodeConnection', totalCount?: number | null }, pduFields?: Array<{ __typename?: 'PeriodicFieldNode', id: string, label: any, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null> | null } | null } | null }; export type CreateRegistrationKoboImportMutationVariables = Exact<{ registrationDataImportData: RegistrationKoboImportMutationInput; @@ -10971,7 +10971,7 @@ export type ProgramQueryVariables = Exact<{ }>; -export type ProgramQuery = { __typename?: 'Query', program?: { __typename?: 'ProgramNode', id: string, name: string, programmeCode?: string | null, startDate: any, endDate: any, status: ProgramStatus, caId?: string | null, caHashId?: string | null, description: string, budget?: any | null, frequencyOfPayments: ProgramFrequencyOfPayments, cashPlus: boolean, populationGoal: number, scope?: ProgramScope | null, sector: ProgramSector, totalNumberOfHouseholds?: number | null, totalNumberOfHouseholdsWithTpInProgram?: number | null, administrativeAreasOfImplementation: string, isSocialWorkerProgram?: boolean | null, version: any, adminUrl?: string | null, partnerAccess: ProgramPartnerAccess, dataCollectingType?: { __typename?: 'DataCollectingTypeNode', id: string, code: string, label: string, active: boolean, individualFiltersAvailable: boolean, householdFiltersAvailable: boolean, description: string, type?: DataCollectingTypeType | null } | null, partners?: Array<{ __typename?: 'PartnerNode', id: string, name?: string | null, areaAccess?: string | null, areas?: Array<{ __typename?: 'AreaNode', id: string, level: number } | null> | null } | null> | null, registrationImports: { __typename?: 'RegistrationDataImportNodeConnection', totalCount?: number | null }, pduFields?: Array<{ __typename?: 'PeriodicFieldNode', id: string, label: any, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null> | null } | null }; +export type ProgramQuery = { __typename?: 'Query', program?: { __typename?: 'ProgramNode', id: string, name: string, programmeCode?: string | null, startDate: any, endDate: any, status: ProgramStatus, caId?: string | null, caHashId?: string | null, description: string, budget?: any | null, frequencyOfPayments: ProgramFrequencyOfPayments, cashPlus: boolean, populationGoal: number, scope?: ProgramScope | null, sector: ProgramSector, totalNumberOfHouseholds?: number | null, totalNumberOfHouseholdsWithTpInProgram?: number | null, administrativeAreasOfImplementation: string, isSocialWorkerProgram?: boolean | null, version: any, adminUrl?: string | null, partnerAccess: ProgramPartnerAccess, dataCollectingType?: { __typename?: 'DataCollectingTypeNode', id: string, code: string, label: string, active: boolean, individualFiltersAvailable: boolean, householdFiltersAvailable: boolean, description: string, type?: DataCollectingTypeType | null } | null, partners?: Array<{ __typename?: 'PartnerNode', id: string, name?: string | null, areaAccess?: string | null, areas?: Array<{ __typename?: 'AreaNode', id: string, level: number } | null> | null } | null> | null, registrationImports: { __typename?: 'RegistrationDataImportNodeConnection', totalCount?: number | null }, targetpopulationSet: { __typename?: 'TargetPopulationNodeConnection', totalCount?: number | null }, pduFields?: Array<{ __typename?: 'PeriodicFieldNode', id: string, label: any, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null> | null } | null }; export type ProgrammeChoiceDataQueryVariables = Exact<{ [key: string]: never; }>; @@ -12317,6 +12317,9 @@ export const ProgramDetailsFragmentDoc = gql` registrationImports { totalCount } + targetpopulationSet { + totalCount + } pduFields { id label diff --git a/frontend/src/apollo/fragments/ProgramDetailsFragment.ts b/frontend/src/apollo/fragments/ProgramDetailsFragment.ts index 6409c85cd5..ca1ceee22b 100644 --- a/frontend/src/apollo/fragments/ProgramDetailsFragment.ts +++ b/frontend/src/apollo/fragments/ProgramDetailsFragment.ts @@ -46,6 +46,9 @@ export const programDetails = gql` registrationImports { totalCount } + targetpopulationSet { + totalCount + } pduFields { id label diff --git a/frontend/src/components/programs/CreateProgram/ProgramFieldSeriesStep.tsx b/frontend/src/components/programs/CreateProgram/ProgramFieldSeriesStep.tsx index ca00e467e7..a9f245e8af 100644 --- a/frontend/src/components/programs/CreateProgram/ProgramFieldSeriesStep.tsx +++ b/frontend/src/components/programs/CreateProgram/ProgramFieldSeriesStep.tsx @@ -20,6 +20,7 @@ interface ProgramFieldSeriesStepProps { setStep: (step: number) => void; step: number; programHasRdi?: boolean; + programHasTp?: boolean; pdusubtypeChoicesData?: PduSubtypeChoicesDataQuery; errors: any; programId?: string; @@ -33,6 +34,7 @@ export const ProgramFieldSeriesStep = ({ setStep, step, programHasRdi, + programHasTp, pdusubtypeChoicesData, errors, programId: formProgramId, @@ -56,6 +58,8 @@ export const ProgramFieldSeriesStep = ({ 'Are you sure you want to delete this field? This action cannot be reversed.', ); + const fieldDisabled = programHasRdi || programHasTp; + return ( <>
@@ -87,7 +91,7 @@ export const ProgramFieldSeriesStep = ({ label={t('Data Type')} component={FormikSelectField} choices={mappedPduSubtypeChoices} - disabled={programHasRdi} + disabled={fieldDisabled} /> @@ -134,7 +138,7 @@ export const ProgramFieldSeriesStep = ({ choices={[...Array(20).keys()].map((n) => { const isDisabled = values.editMode && - programHasRdi && + fieldDisabled && n + 2 <= (program?.pduFields[index]?.pduData ?.numberOfRounds || 0); @@ -156,7 +160,7 @@ export const ProgramFieldSeriesStep = ({ type: 'error', }).then(() => arrayHelpers.remove(index)) } - disabled={programHasRdi} + disabled={fieldDisabled} > @@ -171,7 +175,7 @@ export const ProgramFieldSeriesStep = ({ program?.pduFields?.[index]?.pduData ?.numberOfRounds || 0; const isDisabled = - programHasRdi && + fieldDisabled && values.editMode && round + 1 <= selectedNumberOfRounds; return ( @@ -212,7 +216,7 @@ export const ProgramFieldSeriesStep = ({ }) } endIcon={} - disabled={programHasRdi} + disabled={fieldDisabled} data-cy="button-add-time-series-field" > {t('Add Time Series Fields')} diff --git a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx index 0c1956d6b8..bfd1c3e203 100644 --- a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx +++ b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx @@ -190,12 +190,15 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { <> {fieldElement} {field.fieldAttribute.type === 'PDU' && - field.fieldAttribute.pduData && - field.roundNumber && - field.fieldAttribute.pduData.roundsNames?.[field.roundNumber - 1] && ( + (field.pduData || field.fieldAttribute.pduData) && ( - Round {field.roundNumber}( - {field.fieldAttribute.pduData.roundsNames[field.roundNumber - 1]}) + Round {field.roundNumber} ( + { + (field.pduData || field.fieldAttribute.pduData).roundsNames[ + field.roundNumber - 1 + ] + } + ) )} diff --git a/frontend/src/containers/pages/program/EditProgramPage.tsx b/frontend/src/containers/pages/program/EditProgramPage.tsx index 1302b0255c..b5931cc5c2 100644 --- a/frontend/src/containers/pages/program/EditProgramPage.tsx +++ b/frontend/src/containers/pages/program/EditProgramPage.tsx @@ -95,9 +95,11 @@ export const EditProgramPage = (): ReactElement => { partnerAccess = ProgramPartnerAccess.AllPartnersAccess, registrationImports, pduFields, + targetpopulationSet, } = data.program; - const programHasRdi = registrationImports.totalCount > 0; + const programHasRdi = registrationImports?.totalCount > 0; + const programHasTP = targetpopulationSet?.totalCount > 0; const handleSubmit = async (values): Promise => { const budgetValue = parseFloat(values.budget) ?? 0; @@ -350,6 +352,7 @@ export const EditProgramPage = (): ReactElement => { setErrors={setErrors} setFieldTouched={setFieldTouched} programHasRdi={programHasRdi} + programHasTP={programHasTP} programId={id} program={data.program} setFieldValue={setFieldValue} From 2ffbdda1662a7fd84cd9c770e4b9da1c90b813ba Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Thu, 22 Aug 2024 18:29:20 +0200 Subject: [PATCH 074/118] fix checkbox --- frontend/src/utils/targetingUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/utils/targetingUtils.ts b/frontend/src/utils/targetingUtils.ts index f63c90dab4..cc1567d54d 100644 --- a/frontend/src/utils/targetingUtils.ts +++ b/frontend/src/utils/targetingUtils.ts @@ -128,7 +128,7 @@ export function mapCriteriaToInitialValues(criteria) { ).map((filter) => { return { ...filter, - isNull: filter.comparisonMethod === 'IS_NULL', + isNull: filter.comparisonMethod === 'IS_NULL' || filter.isNull, }; }), })), From 8b590492b67cc60a3a4c2d51435493172fd0b8c3 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Thu, 22 Aug 2024 21:54:18 +0200 Subject: [PATCH 075/118] add E2E for targeting by pdu decimal, date and Only Empty checkbox --- .../page_object/targeting/targeting_create.py | 10 +- .../targeting/test_targeting.py | 178 +++++++++++++++++- 2 files changed, 183 insertions(+), 5 deletions(-) diff --git a/backend/selenium_tests/page_object/targeting/targeting_create.py b/backend/selenium_tests/page_object/targeting/targeting_create.py index 8454796fd5..b8411bc081 100644 --- a/backend/selenium_tests/page_object/targeting/targeting_create.py +++ b/backend/selenium_tests/page_object/targeting/targeting_create.py @@ -239,8 +239,14 @@ def getSelectIndividualsiFltersBlocksRoundNumber( def getSelectRoundOption(self, round_number: int = 0) -> WebElement: return self.wait_for(self.selectRoundOption.format(round_number)) - def getSelectIndividualsiFltersBlocksIsNull(self) -> WebElement: - return self.wait_for(self.selectIndividualsFiltersBlocksIsNull) + def getSelectIndividualsiFltersBlocksIsNull( + self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0 + ) -> WebElement: + return self.wait_for( + self.selectIndividualsFiltersBlocksIsNull.format( + individuals_filters_blocks_number, individual_block_filters_number + ) + ) def getInputIndividualsFiltersBlocksValueFrom( self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0 diff --git a/backend/selenium_tests/targeting/test_targeting.py b/backend/selenium_tests/targeting/test_targeting.py index 44ea996127..7707ed1edd 100644 --- a/backend/selenium_tests/targeting/test_targeting.py +++ b/backend/selenium_tests/targeting/test_targeting.py @@ -89,6 +89,7 @@ def _individual() -> Individual: ) individual = individuals[0] individual.flex_fields = populate_pdu_with_null_values(program, individual.flex_fields) + individual.save() return individual return _individual @@ -100,7 +101,7 @@ def string_attribute(program: Program) -> FlexibleAttribute: label="Test String Attribute", subtype=PeriodicFieldData.STRING, number_of_rounds=1, - rounds_names=["Test Round String"], + rounds_names=["Test Round String 1"], program=program, ) @@ -111,7 +112,7 @@ def date_attribute(program: Program) -> FlexibleAttribute: label="Test Date Attribute", subtype=PeriodicFieldData.DATE, number_of_rounds=1, - rounds_names=["Test Round Date"], + rounds_names=["Test Round Date 1"], program=program, ) @@ -133,7 +134,7 @@ def decimal_attribute(program: Program) -> FlexibleAttribute: label="Test Decimal Attribute", subtype=PeriodicFieldData.DECIMAL, number_of_rounds=1, - rounds_names=["Test Round Decimal"], + rounds_names=["Test Round Decimal 1"], program=program, ) @@ -524,6 +525,177 @@ def test_create_targeting_with_pdu_bool_criteria( assert pageTargetingCreate.getTotalNumberOfHouseholdsCount().text == "1" assert len(pageTargetingDetails.getHouseholdTableRows()) == 1 + def test_create_targeting_with_pdu_decimal_criteria( + self, + program: Program, + pageTargeting: Targeting, + pageTargetingCreate: TargetingCreate, + pageTargetingDetails: TargetingDetails, + individual: Callable, + decimal_attribute: FlexibleAttribute, + ) -> None: + individual1 = individual() + individual1.flex_fields[decimal_attribute.name]["1"]["value"] = 2.5 + individual1.save() + individual2 = individual() + individual2.flex_fields[decimal_attribute.name]["1"]["value"] = 5.0 + individual2.save() + individual() + pageTargeting.navigate_to_page("afghanistan", program.id) + pageTargeting.getButtonCreateNew().click() + pageTargeting.getButtonCreateNewByFilters().click() + assert "New Target Population" in pageTargetingCreate.getTitlePage().text + pageTargetingCreate.getAddCriteriaButton().click() + pageTargetingCreate.getAddIndividualRuleButton().click() + pageTargetingCreate.getTargetingCriteriaAutoComplete().click() + pageTargetingCreate.getTargetingCriteriaAutoComplete().send_keys("Test Decimal Attribute") + pageTargetingCreate.getTargetingCriteriaAutoComplete().send_keys(Keys.ARROW_DOWN) + pageTargetingCreate.getTargetingCriteriaAutoComplete().send_keys(Keys.ENTER) + pageTargetingCreate.getSelectIndividualsiFltersBlocksRoundNumber().click() + pageTargetingCreate.getSelectRoundOption(1).click() + pageTargetingCreate.getInputIndividualsFiltersBlocksValueFrom().send_keys("2") + pageTargetingCreate.getInputIndividualsFiltersBlocksValueTo().send_keys("4") + pageTargetingCreate.getTargetingCriteriaAddDialogSaveButton().click() + expected_criteria_text = "Test Decimal Attribute: 2 - 4\nRound 1 (Test Round Decimal 1)" + assert pageTargetingCreate.getCriteriaContainer().text == expected_criteria_text + targeting_name = "Test Targeting PDU decimal" + pageTargetingCreate.getFieldName().send_keys(targeting_name) + pageTargetingCreate.getTargetPopulationSaveButton().click() + pageTargetingDetails.getLockButton() + assert pageTargetingDetails.getTitlePage().text == targeting_name + assert pageTargetingDetails.getCriteriaContainer().text == expected_criteria_text + assert Household.objects.count() == 3 + assert pageTargetingDetails.getHouseholdTableCell(1, 1).text == individual1.household.unicef_id + assert pageTargetingCreate.getTotalNumberOfHouseholdsCount().text == "1" + assert len(pageTargetingDetails.getHouseholdTableRows()) == 1 + + # edit range + pageTargetingDetails.getButtonEdit().click() + pageTargetingDetails.getButtonIconEdit().click() + pageTargetingCreate.getInputIndividualsFiltersBlocksValueTo().send_keys(Keys.BACKSPACE) + pageTargetingCreate.getInputIndividualsFiltersBlocksValueTo().send_keys("5") + bool_no_expected_criteria_text = "Test Decimal Attribute: 2 - 5\nRound 1 (Test Round Decimal 1)" + + pageTargetingCreate.get_elements(pageTargetingCreate.targetingCriteriaAddDialogSaveButton)[1].click() + + assert pageTargetingCreate.getCriteriaContainer().text == bool_no_expected_criteria_text + pageTargetingCreate.getButtonSave().click() + pageTargetingDetails.getLockButton() + + assert pageTargetingDetails.getCriteriaContainer().text == bool_no_expected_criteria_text + assert pageTargetingDetails.getHouseholdTableCell(1, 1).text == individual1.household.unicef_id + assert pageTargetingDetails.getHouseholdTableCell(2, 1).text == individual2.household.unicef_id + assert pageTargetingCreate.getTotalNumberOfHouseholdsCount().text == "2" + assert len(pageTargetingDetails.getHouseholdTableRows()) == 2 + + def test_create_targeting_with_pdu_date_criteria( + self, + program: Program, + pageTargeting: Targeting, + pageTargetingCreate: TargetingCreate, + pageTargetingDetails: TargetingDetails, + individual: Callable, + date_attribute: FlexibleAttribute, + ) -> None: + individual1 = individual() + individual1.flex_fields[date_attribute.name]["1"]["value"] = "2022-02-02" + individual1.save() + individual2 = individual() + individual2.flex_fields[date_attribute.name]["1"]["value"] = "2022-10-02" + individual2.save() + individual() + pageTargeting.navigate_to_page("afghanistan", program.id) + pageTargeting.getButtonCreateNew().click() + pageTargeting.getButtonCreateNewByFilters().click() + assert "New Target Population" in pageTargetingCreate.getTitlePage().text + pageTargetingCreate.getAddCriteriaButton().click() + pageTargetingCreate.getAddIndividualRuleButton().click() + pageTargetingCreate.getTargetingCriteriaAutoComplete().click() + pageTargetingCreate.getTargetingCriteriaAutoComplete().send_keys("Test Date Attribute") + pageTargetingCreate.getTargetingCriteriaAutoComplete().send_keys(Keys.ARROW_DOWN) + pageTargetingCreate.getTargetingCriteriaAutoComplete().send_keys(Keys.ENTER) + pageTargetingCreate.getSelectIndividualsiFltersBlocksRoundNumber().click() + pageTargetingCreate.getSelectRoundOption(1).click() + pageTargetingCreate.getInputIndividualsFiltersBlocksValueFrom().send_keys("2022-01-01") + pageTargetingCreate.getInputIndividualsFiltersBlocksValueTo().send_keys("2022-03-03") + pageTargetingCreate.getTargetingCriteriaAddDialogSaveButton().click() + expected_criteria_text = "Test Date Attribute: 2022-01-01 - 2022-03-03\nRound 1 (Test Round Date 1)" + assert pageTargetingCreate.getCriteriaContainer().text == expected_criteria_text + targeting_name = "Test Targeting PDU date" + pageTargetingCreate.getFieldName().send_keys(targeting_name) + pageTargetingCreate.getTargetPopulationSaveButton().click() + pageTargetingDetails.getLockButton() + assert pageTargetingDetails.getTitlePage().text == targeting_name + assert pageTargetingDetails.getCriteriaContainer().text == expected_criteria_text + assert Household.objects.count() == 3 + assert pageTargetingDetails.getHouseholdTableCell(1, 1).text == individual1.household.unicef_id + assert pageTargetingCreate.getTotalNumberOfHouseholdsCount().text == "1" + assert len(pageTargetingDetails.getHouseholdTableRows()) == 1 + + # edit range + pageTargetingDetails.getButtonEdit().click() + pageTargetingDetails.getButtonIconEdit().click() + pageTargetingCreate.getInputIndividualsFiltersBlocksValueTo().send_keys(Keys.BACKSPACE) + pageTargetingCreate.getInputIndividualsFiltersBlocksValueTo().send_keys("5") + bool_no_expected_criteria_text = "Test Bool Attribute: 2 - 5\nRound 2 (Test Round Bool 2)" + + pageTargetingCreate.get_elements(pageTargetingCreate.targetingCriteriaAddDialogSaveButton)[1].click() + + assert pageTargetingCreate.getCriteriaContainer().text == bool_no_expected_criteria_text + pageTargetingCreate.getButtonSave().click() + pageTargetingDetails.getLockButton() + + assert pageTargetingDetails.getCriteriaContainer().text == bool_no_expected_criteria_text + assert pageTargetingDetails.getHouseholdTableCell(2, 1).text == individual1.household.unicef_id + assert pageTargetingCreate.getTotalNumberOfHouseholdsCount().text == "2" + assert len(pageTargetingDetails.getHouseholdTableRows()) == 2 + + def test_create_targeting_with_pdu_null_criteria( + self, + program: Program, + pageTargeting: Targeting, + pageTargetingCreate: TargetingCreate, + pageTargetingDetails: TargetingDetails, + individual: Callable, + string_attribute: FlexibleAttribute, + ) -> None: + individual1 = individual() + individual1.flex_fields[string_attribute.name]["1"]["value"] = "Text" + individual1.save() + individual2 = individual() + individual2.flex_fields[string_attribute.name]["1"]["value"] = "Test" + individual2.save() + individual3 = individual() + pageTargeting.navigate_to_page("afghanistan", program.id) + pageTargeting.getButtonCreateNew().click() + pageTargeting.getButtonCreateNewByFilters().click() + assert "New Target Population" in pageTargetingCreate.getTitlePage().text + pageTargetingCreate.getAddCriteriaButton().click() + pageTargetingCreate.getAddIndividualRuleButton().click() + pageTargetingCreate.getTargetingCriteriaAutoComplete().click() + pageTargetingCreate.getTargetingCriteriaAutoComplete().send_keys("Test String Attribute") + pageTargetingCreate.getTargetingCriteriaAutoComplete().send_keys(Keys.ARROW_DOWN) + pageTargetingCreate.getTargetingCriteriaAutoComplete().send_keys(Keys.ENTER) + pageTargetingCreate.getSelectIndividualsiFltersBlocksRoundNumber().click() + pageTargetingCreate.getSelectRoundOption(1).click() + pageTargetingCreate.screenshot("if before click") + pageTargetingCreate.getSelectIndividualsiFltersBlocksIsNull().click() + pageTargetingCreate.screenshot("if click click") + pageTargetingCreate.getTargetingCriteriaAddDialogSaveButton().click() + expected_criteria_text = "Test String Attribute: Empty\nRound 1 (Test Round String 1)" + assert pageTargetingCreate.getCriteriaContainer().text == expected_criteria_text + targeting_name = "Test Targeting PDU null" + pageTargetingCreate.getFieldName().send_keys(targeting_name) + pageTargetingCreate.getTargetPopulationSaveButton().click() + pageTargetingDetails.getLockButton() + assert pageTargetingDetails.getTitlePage().text == targeting_name + assert pageTargetingDetails.getCriteriaContainer().text == expected_criteria_text + assert Household.objects.count() == 3 + + assert pageTargetingDetails.getHouseholdTableCell(1, 1).text == individual3.household.unicef_id + assert pageTargetingCreate.getTotalNumberOfHouseholdsCount().text == "1" + assert len(pageTargetingDetails.getHouseholdTableRows()) == 1 + @pytest.mark.usefixtures("login") class TestTargeting: From e734a63382fd3611efbc36ea7c6770c48899ab94 Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Thu, 22 Aug 2024 22:24:15 +0200 Subject: [PATCH 076/118] fix data-cy for targeting datefields --- .../TargetingCriteriaDisplay/Criteria.tsx | 21 ++++++---- .../FormikDateField/FormikDateField.tsx | 41 ++++++------------- 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx index bfd1c3e203..1e4bf2e22a 100644 --- a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx +++ b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx @@ -192,13 +192,20 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { {field.fieldAttribute.type === 'PDU' && (field.pduData || field.fieldAttribute.pduData) && ( - Round {field.roundNumber} ( - { - (field.pduData || field.fieldAttribute.pduData).roundsNames[ - field.roundNumber - 1 - ] - } - ) + Round {field.roundNumber} + {(field.pduData || field.fieldAttribute.pduData).roundsNames[ + field.roundNumber - 1 + ] && ( + <> + ( + { + (field.pduData || field.fieldAttribute.pduData).roundsNames[ + field.roundNumber - 1 + ] + } + ) + + )} )} diff --git a/frontend/src/shared/Formik/FormikDateField/FormikDateField.tsx b/frontend/src/shared/Formik/FormikDateField/FormikDateField.tsx index 85a1e52cc7..8a64e61744 100644 --- a/frontend/src/shared/Formik/FormikDateField/FormikDateField.tsx +++ b/frontend/src/shared/Formik/FormikDateField/FormikDateField.tsx @@ -44,6 +44,19 @@ export const FormikDateField = ({ size: 'small', error: isInvalid, helperText: isInvalid && get(form.errors, field.name), + InputProps: { + startAdornment: decoratorStart && ( + + {decoratorStart} + + ), + endAdornment: decoratorEnd && ( + {decoratorEnd} + ), + }, + inputProps: { + 'data-cy': `date-input-${field.name}`, + }, }, }} sx={{ @@ -51,34 +64,6 @@ export const FormikDateField = ({ outline: 'none', }, }} - slots={{ - TextField: (props) => ( - - {decoratorStart} - - ), - endAdornment: decoratorEnd && ( - {decoratorEnd} - ), - }} - required={required} - // https://github.com/mui-org/material-ui/issues/12805 - // eslint-disable-next-line react/jsx-no-duplicate-props - inputProps={{ - 'data-cy': `date-input-${field.name}`, - }} - /> - ), - }} value={formattedValue || null} onBlur={() => { setTimeout(() => { From af8ca84ce926ec9c786f2e3e97943a64d569a4c0 Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Thu, 22 Aug 2024 22:36:33 +0200 Subject: [PATCH 077/118] remove tp --- frontend/src/__generated__/graphql.tsx | 9 +++------ frontend/src/apollo/fragments/ProgramDetailsFragment.ts | 3 --- .../programs/CreateProgram/ProgramFieldSeriesStep.tsx | 3 +-- .../src/containers/pages/program/EditProgramPage.tsx | 2 -- 4 files changed, 4 insertions(+), 13 deletions(-) diff --git a/frontend/src/__generated__/graphql.tsx b/frontend/src/__generated__/graphql.tsx index 33a0452ccf..861511973a 100644 --- a/frontend/src/__generated__/graphql.tsx +++ b/frontend/src/__generated__/graphql.tsx @@ -9500,7 +9500,7 @@ export type MergedIndividualMinimalFragment = { __typename?: 'IndividualNode', i export type PaymentRecordDetailsFragment = { __typename?: 'PaymentRecordNode', id: string, status: PaymentRecordStatus, statusDate: any, caId?: string | null, caHashId?: any | null, registrationCaId?: string | null, fullName: string, distributionModality: string, totalPersonsCovered: number, currency: string, entitlementQuantity?: number | null, deliveredQuantity?: number | null, deliveredQuantityUsd?: number | null, deliveryDate?: any | null, entitlementCardIssueDate?: any | null, entitlementCardNumber?: string | null, transactionReferenceId?: string | null, verification?: { __typename?: 'PaymentVerificationNode', id: string, status: PaymentVerificationStatus, statusDate?: any | null, receivedAmount?: number | null } | null, household: { __typename?: 'HouseholdNode', id: string, status?: string | null, size?: number | null, unicefId?: string | null, headOfHousehold?: { __typename?: 'IndividualNode', id: string, phoneNo: string, phoneNoAlternative: string, phoneNoValid?: boolean | null, phoneNoAlternativeValid?: boolean | null } | null }, targetPopulation: { __typename?: 'TargetPopulationNode', id: string, name: string }, parent?: { __typename?: 'CashPlanNode', id: string, caId?: string | null, program: { __typename?: 'ProgramNode', id: string, name: string }, verificationPlans?: { __typename?: 'PaymentVerificationPlanNodeConnection', edges: Array<{ __typename?: 'PaymentVerificationPlanNodeEdge', node?: { __typename?: 'PaymentVerificationPlanNode', id: string, status: PaymentVerificationPlanStatus, verificationChannel: PaymentVerificationPlanVerificationChannel } | null } | null> } | null } | null, deliveryType?: { __typename?: 'DeliveryMechanismNode', name?: string | null } | null, serviceProvider: { __typename?: 'ServiceProviderNode', id: string, fullName?: string | null, shortName?: string | null } }; -export type ProgramDetailsFragment = { __typename?: 'ProgramNode', id: string, name: string, programmeCode?: string | null, startDate: any, endDate: any, status: ProgramStatus, caId?: string | null, caHashId?: string | null, description: string, budget?: any | null, frequencyOfPayments: ProgramFrequencyOfPayments, cashPlus: boolean, populationGoal: number, scope?: ProgramScope | null, sector: ProgramSector, totalNumberOfHouseholds?: number | null, totalNumberOfHouseholdsWithTpInProgram?: number | null, administrativeAreasOfImplementation: string, isSocialWorkerProgram?: boolean | null, version: any, adminUrl?: string | null, partnerAccess: ProgramPartnerAccess, dataCollectingType?: { __typename?: 'DataCollectingTypeNode', id: string, code: string, label: string, active: boolean, individualFiltersAvailable: boolean, householdFiltersAvailable: boolean, description: string, type?: DataCollectingTypeType | null } | null, partners?: Array<{ __typename?: 'PartnerNode', id: string, name?: string | null, areaAccess?: string | null, areas?: Array<{ __typename?: 'AreaNode', id: string, level: number } | null> | null } | null> | null, registrationImports: { __typename?: 'RegistrationDataImportNodeConnection', totalCount?: number | null }, targetpopulationSet: { __typename?: 'TargetPopulationNodeConnection', totalCount?: number | null }, pduFields?: Array<{ __typename?: 'PeriodicFieldNode', id: string, label: any, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null> | null }; +export type ProgramDetailsFragment = { __typename?: 'ProgramNode', id: string, name: string, programmeCode?: string | null, startDate: any, endDate: any, status: ProgramStatus, caId?: string | null, caHashId?: string | null, description: string, budget?: any | null, frequencyOfPayments: ProgramFrequencyOfPayments, cashPlus: boolean, populationGoal: number, scope?: ProgramScope | null, sector: ProgramSector, totalNumberOfHouseholds?: number | null, totalNumberOfHouseholdsWithTpInProgram?: number | null, administrativeAreasOfImplementation: string, isSocialWorkerProgram?: boolean | null, version: any, adminUrl?: string | null, partnerAccess: ProgramPartnerAccess, dataCollectingType?: { __typename?: 'DataCollectingTypeNode', id: string, code: string, label: string, active: boolean, individualFiltersAvailable: boolean, householdFiltersAvailable: boolean, description: string, type?: DataCollectingTypeType | null } | null, partners?: Array<{ __typename?: 'PartnerNode', id: string, name?: string | null, areaAccess?: string | null, areas?: Array<{ __typename?: 'AreaNode', id: string, level: number } | null> | null } | null> | null, registrationImports: { __typename?: 'RegistrationDataImportNodeConnection', totalCount?: number | null }, pduFields?: Array<{ __typename?: 'PeriodicFieldNode', id: string, label: any, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null> | null }; export type RegistrationMinimalFragment = { __typename?: 'RegistrationDataImportNode', id: string, createdAt: any, name: string, status: RegistrationDataImportStatus, erased: boolean, importDate: any, dataSource: RegistrationDataImportDataSource, numberOfHouseholds: number, numberOfIndividuals: number, refuseReason?: string | null, totalHouseholdsCountWithValidPhoneNo?: number | null, adminUrl?: string | null, importedBy?: { __typename?: 'UserNode', id: string, firstName: string, lastName: string, email: string } | null, program?: { __typename?: 'ProgramNode', id: string, name: string, startDate: any, endDate: any, status: ProgramStatus } | null }; @@ -9981,7 +9981,7 @@ export type UpdateProgramMutationVariables = Exact<{ }>; -export type UpdateProgramMutation = { __typename?: 'Mutations', updateProgram?: { __typename?: 'UpdateProgram', validationErrors?: any | null, program?: { __typename?: 'ProgramNode', id: string, name: string, programmeCode?: string | null, startDate: any, endDate: any, status: ProgramStatus, caId?: string | null, caHashId?: string | null, description: string, budget?: any | null, frequencyOfPayments: ProgramFrequencyOfPayments, cashPlus: boolean, populationGoal: number, scope?: ProgramScope | null, sector: ProgramSector, totalNumberOfHouseholds?: number | null, totalNumberOfHouseholdsWithTpInProgram?: number | null, administrativeAreasOfImplementation: string, isSocialWorkerProgram?: boolean | null, version: any, adminUrl?: string | null, partnerAccess: ProgramPartnerAccess, dataCollectingType?: { __typename?: 'DataCollectingTypeNode', id: string, code: string, label: string, active: boolean, individualFiltersAvailable: boolean, householdFiltersAvailable: boolean, description: string, type?: DataCollectingTypeType | null } | null, partners?: Array<{ __typename?: 'PartnerNode', id: string, name?: string | null, areaAccess?: string | null, areas?: Array<{ __typename?: 'AreaNode', id: string, level: number } | null> | null } | null> | null, registrationImports: { __typename?: 'RegistrationDataImportNodeConnection', totalCount?: number | null }, targetpopulationSet: { __typename?: 'TargetPopulationNodeConnection', totalCount?: number | null }, pduFields?: Array<{ __typename?: 'PeriodicFieldNode', id: string, label: any, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null> | null } | null } | null }; +export type UpdateProgramMutation = { __typename?: 'Mutations', updateProgram?: { __typename?: 'UpdateProgram', validationErrors?: any | null, program?: { __typename?: 'ProgramNode', id: string, name: string, programmeCode?: string | null, startDate: any, endDate: any, status: ProgramStatus, caId?: string | null, caHashId?: string | null, description: string, budget?: any | null, frequencyOfPayments: ProgramFrequencyOfPayments, cashPlus: boolean, populationGoal: number, scope?: ProgramScope | null, sector: ProgramSector, totalNumberOfHouseholds?: number | null, totalNumberOfHouseholdsWithTpInProgram?: number | null, administrativeAreasOfImplementation: string, isSocialWorkerProgram?: boolean | null, version: any, adminUrl?: string | null, partnerAccess: ProgramPartnerAccess, dataCollectingType?: { __typename?: 'DataCollectingTypeNode', id: string, code: string, label: string, active: boolean, individualFiltersAvailable: boolean, householdFiltersAvailable: boolean, description: string, type?: DataCollectingTypeType | null } | null, partners?: Array<{ __typename?: 'PartnerNode', id: string, name?: string | null, areaAccess?: string | null, areas?: Array<{ __typename?: 'AreaNode', id: string, level: number } | null> | null } | null> | null, registrationImports: { __typename?: 'RegistrationDataImportNodeConnection', totalCount?: number | null }, pduFields?: Array<{ __typename?: 'PeriodicFieldNode', id: string, label: any, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null> | null } | null } | null }; export type CreateRegistrationKoboImportMutationVariables = Exact<{ registrationDataImportData: RegistrationKoboImportMutationInput; @@ -10971,7 +10971,7 @@ export type ProgramQueryVariables = Exact<{ }>; -export type ProgramQuery = { __typename?: 'Query', program?: { __typename?: 'ProgramNode', id: string, name: string, programmeCode?: string | null, startDate: any, endDate: any, status: ProgramStatus, caId?: string | null, caHashId?: string | null, description: string, budget?: any | null, frequencyOfPayments: ProgramFrequencyOfPayments, cashPlus: boolean, populationGoal: number, scope?: ProgramScope | null, sector: ProgramSector, totalNumberOfHouseholds?: number | null, totalNumberOfHouseholdsWithTpInProgram?: number | null, administrativeAreasOfImplementation: string, isSocialWorkerProgram?: boolean | null, version: any, adminUrl?: string | null, partnerAccess: ProgramPartnerAccess, dataCollectingType?: { __typename?: 'DataCollectingTypeNode', id: string, code: string, label: string, active: boolean, individualFiltersAvailable: boolean, householdFiltersAvailable: boolean, description: string, type?: DataCollectingTypeType | null } | null, partners?: Array<{ __typename?: 'PartnerNode', id: string, name?: string | null, areaAccess?: string | null, areas?: Array<{ __typename?: 'AreaNode', id: string, level: number } | null> | null } | null> | null, registrationImports: { __typename?: 'RegistrationDataImportNodeConnection', totalCount?: number | null }, targetpopulationSet: { __typename?: 'TargetPopulationNodeConnection', totalCount?: number | null }, pduFields?: Array<{ __typename?: 'PeriodicFieldNode', id: string, label: any, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null> | null } | null }; +export type ProgramQuery = { __typename?: 'Query', program?: { __typename?: 'ProgramNode', id: string, name: string, programmeCode?: string | null, startDate: any, endDate: any, status: ProgramStatus, caId?: string | null, caHashId?: string | null, description: string, budget?: any | null, frequencyOfPayments: ProgramFrequencyOfPayments, cashPlus: boolean, populationGoal: number, scope?: ProgramScope | null, sector: ProgramSector, totalNumberOfHouseholds?: number | null, totalNumberOfHouseholdsWithTpInProgram?: number | null, administrativeAreasOfImplementation: string, isSocialWorkerProgram?: boolean | null, version: any, adminUrl?: string | null, partnerAccess: ProgramPartnerAccess, dataCollectingType?: { __typename?: 'DataCollectingTypeNode', id: string, code: string, label: string, active: boolean, individualFiltersAvailable: boolean, householdFiltersAvailable: boolean, description: string, type?: DataCollectingTypeType | null } | null, partners?: Array<{ __typename?: 'PartnerNode', id: string, name?: string | null, areaAccess?: string | null, areas?: Array<{ __typename?: 'AreaNode', id: string, level: number } | null> | null } | null> | null, registrationImports: { __typename?: 'RegistrationDataImportNodeConnection', totalCount?: number | null }, pduFields?: Array<{ __typename?: 'PeriodicFieldNode', id: string, label: any, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null> | null } | null }; export type ProgrammeChoiceDataQueryVariables = Exact<{ [key: string]: never; }>; @@ -12317,9 +12317,6 @@ export const ProgramDetailsFragmentDoc = gql` registrationImports { totalCount } - targetpopulationSet { - totalCount - } pduFields { id label diff --git a/frontend/src/apollo/fragments/ProgramDetailsFragment.ts b/frontend/src/apollo/fragments/ProgramDetailsFragment.ts index ca1ceee22b..6409c85cd5 100644 --- a/frontend/src/apollo/fragments/ProgramDetailsFragment.ts +++ b/frontend/src/apollo/fragments/ProgramDetailsFragment.ts @@ -46,9 +46,6 @@ export const programDetails = gql` registrationImports { totalCount } - targetpopulationSet { - totalCount - } pduFields { id label diff --git a/frontend/src/components/programs/CreateProgram/ProgramFieldSeriesStep.tsx b/frontend/src/components/programs/CreateProgram/ProgramFieldSeriesStep.tsx index a9f245e8af..9927dff614 100644 --- a/frontend/src/components/programs/CreateProgram/ProgramFieldSeriesStep.tsx +++ b/frontend/src/components/programs/CreateProgram/ProgramFieldSeriesStep.tsx @@ -34,7 +34,6 @@ export const ProgramFieldSeriesStep = ({ setStep, step, programHasRdi, - programHasTp, pdusubtypeChoicesData, errors, programId: formProgramId, @@ -58,7 +57,7 @@ export const ProgramFieldSeriesStep = ({ 'Are you sure you want to delete this field? This action cannot be reversed.', ); - const fieldDisabled = programHasRdi || programHasTp; + const fieldDisabled = programHasRdi; return ( <> diff --git a/frontend/src/containers/pages/program/EditProgramPage.tsx b/frontend/src/containers/pages/program/EditProgramPage.tsx index b5931cc5c2..589566df1c 100644 --- a/frontend/src/containers/pages/program/EditProgramPage.tsx +++ b/frontend/src/containers/pages/program/EditProgramPage.tsx @@ -99,7 +99,6 @@ export const EditProgramPage = (): ReactElement => { } = data.program; const programHasRdi = registrationImports?.totalCount > 0; - const programHasTP = targetpopulationSet?.totalCount > 0; const handleSubmit = async (values): Promise => { const budgetValue = parseFloat(values.budget) ?? 0; @@ -352,7 +351,6 @@ export const EditProgramPage = (): ReactElement => { setErrors={setErrors} setFieldTouched={setFieldTouched} programHasRdi={programHasRdi} - programHasTP={programHasTP} programId={id} program={data.program} setFieldValue={setFieldValue} From 156b7ff9524039ac381ce45cdba0297f13b00ef6 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Thu, 22 Aug 2024 22:40:39 +0200 Subject: [PATCH 078/118] add TargetPopulation count for Program Node --- backend/hct_mis_api/apps/program/schema.py | 5 +++++ frontend/data/schema.graphql | 1 + frontend/src/__generated__/graphql.tsx | 2 ++ 3 files changed, 8 insertions(+) diff --git a/backend/hct_mis_api/apps/program/schema.py b/backend/hct_mis_api/apps/program/schema.py index 84c32c6e11..2f34103ea6 100644 --- a/backend/hct_mis_api/apps/program/schema.py +++ b/backend/hct_mis_api/apps/program/schema.py @@ -82,6 +82,7 @@ class ProgramNode(BaseNodePermissionMixin, AdminUrlNodeMixin, DjangoObjectType): partners = graphene.List(PartnerNode) is_social_worker_program = graphene.Boolean() pdu_fields = graphene.List(PeriodicFieldNode) + target_populations_count = graphene.Int() class Meta: model = Program @@ -118,6 +119,10 @@ def resolve_is_social_worker_program(program: Program, info: Any, **kwargs: Any) def resolve_pdu_fields(program: Program, info: Any, **kwargs: Any) -> QuerySet: return program.pdu_fields.order_by("name") + @staticmethod + def resolve_target_populations_count(program: Program, info: Any, **kwargs: Any) -> int: + return program.targetpopulation_set.count() + class CashPlanNode(BaseNodePermissionMixin, DjangoObjectType): permission_classes: Tuple[Type[BasePermission], ...] = ( diff --git a/frontend/data/schema.graphql b/frontend/data/schema.graphql index 2332ecda50..80aa197e9f 100644 --- a/frontend/data/schema.graphql +++ b/frontend/data/schema.graphql @@ -3727,6 +3727,7 @@ type ProgramNode implements Node { totalNumberOfHouseholds: Int totalNumberOfHouseholdsWithTpInProgram: Int isSocialWorkerProgram: Boolean + targetPopulationsCount: Int } type ProgramNodeConnection { diff --git a/frontend/src/__generated__/graphql.tsx b/frontend/src/__generated__/graphql.tsx index 861511973a..8ec1f94006 100644 --- a/frontend/src/__generated__/graphql.tsx +++ b/frontend/src/__generated__/graphql.tsx @@ -5642,6 +5642,7 @@ export type ProgramNode = Node & { startDate: Scalars['Date']['output']; status: ProgramStatus; surveys: SurveyNodeConnection; + targetPopulationsCount?: Maybe; targetpopulationSet: TargetPopulationNodeConnection; totalDeliveredQuantity?: Maybe; totalEntitledQuantity?: Maybe; @@ -27901,6 +27902,7 @@ export type ProgramNodeResolvers; status?: Resolver; surveys?: Resolver>; + targetPopulationsCount?: Resolver, ParentType, ContextType>; targetpopulationSet?: Resolver>; totalDeliveredQuantity?: Resolver, ParentType, ContextType>; totalEntitledQuantity?: Resolver, ParentType, ContextType>; From dd6da8cb7c8c95497121bc297b7d26bb8f5e8aab Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Thu, 22 Aug 2024 23:23:36 +0200 Subject: [PATCH 079/118] space between round number and round name --- .../components/targeting/TargetingCriteriaDisplay/Criteria.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx index 1e4bf2e22a..a607277fca 100644 --- a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx +++ b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx @@ -197,6 +197,7 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => { field.roundNumber - 1 ] && ( <> + {' '} ( { (field.pduData || field.fieldAttribute.pduData).roundsNames[ From 6d61a3506ba50dea8630b077fab401aab33fe6d4 Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Fri, 23 Aug 2024 00:57:40 +0200 Subject: [PATCH 080/118] disable edit time series fields if program has tp --- frontend/src/__generated__/graphql.tsx | 7 ++++--- frontend/src/apollo/fragments/ProgramDetailsFragment.ts | 1 + .../programs/CreateProgram/ProgramFieldSeriesStep.tsx | 3 ++- frontend/src/containers/pages/program/EditProgramPage.tsx | 4 +++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/frontend/src/__generated__/graphql.tsx b/frontend/src/__generated__/graphql.tsx index 8ec1f94006..85c41ea2f0 100644 --- a/frontend/src/__generated__/graphql.tsx +++ b/frontend/src/__generated__/graphql.tsx @@ -9501,7 +9501,7 @@ export type MergedIndividualMinimalFragment = { __typename?: 'IndividualNode', i export type PaymentRecordDetailsFragment = { __typename?: 'PaymentRecordNode', id: string, status: PaymentRecordStatus, statusDate: any, caId?: string | null, caHashId?: any | null, registrationCaId?: string | null, fullName: string, distributionModality: string, totalPersonsCovered: number, currency: string, entitlementQuantity?: number | null, deliveredQuantity?: number | null, deliveredQuantityUsd?: number | null, deliveryDate?: any | null, entitlementCardIssueDate?: any | null, entitlementCardNumber?: string | null, transactionReferenceId?: string | null, verification?: { __typename?: 'PaymentVerificationNode', id: string, status: PaymentVerificationStatus, statusDate?: any | null, receivedAmount?: number | null } | null, household: { __typename?: 'HouseholdNode', id: string, status?: string | null, size?: number | null, unicefId?: string | null, headOfHousehold?: { __typename?: 'IndividualNode', id: string, phoneNo: string, phoneNoAlternative: string, phoneNoValid?: boolean | null, phoneNoAlternativeValid?: boolean | null } | null }, targetPopulation: { __typename?: 'TargetPopulationNode', id: string, name: string }, parent?: { __typename?: 'CashPlanNode', id: string, caId?: string | null, program: { __typename?: 'ProgramNode', id: string, name: string }, verificationPlans?: { __typename?: 'PaymentVerificationPlanNodeConnection', edges: Array<{ __typename?: 'PaymentVerificationPlanNodeEdge', node?: { __typename?: 'PaymentVerificationPlanNode', id: string, status: PaymentVerificationPlanStatus, verificationChannel: PaymentVerificationPlanVerificationChannel } | null } | null> } | null } | null, deliveryType?: { __typename?: 'DeliveryMechanismNode', name?: string | null } | null, serviceProvider: { __typename?: 'ServiceProviderNode', id: string, fullName?: string | null, shortName?: string | null } }; -export type ProgramDetailsFragment = { __typename?: 'ProgramNode', id: string, name: string, programmeCode?: string | null, startDate: any, endDate: any, status: ProgramStatus, caId?: string | null, caHashId?: string | null, description: string, budget?: any | null, frequencyOfPayments: ProgramFrequencyOfPayments, cashPlus: boolean, populationGoal: number, scope?: ProgramScope | null, sector: ProgramSector, totalNumberOfHouseholds?: number | null, totalNumberOfHouseholdsWithTpInProgram?: number | null, administrativeAreasOfImplementation: string, isSocialWorkerProgram?: boolean | null, version: any, adminUrl?: string | null, partnerAccess: ProgramPartnerAccess, dataCollectingType?: { __typename?: 'DataCollectingTypeNode', id: string, code: string, label: string, active: boolean, individualFiltersAvailable: boolean, householdFiltersAvailable: boolean, description: string, type?: DataCollectingTypeType | null } | null, partners?: Array<{ __typename?: 'PartnerNode', id: string, name?: string | null, areaAccess?: string | null, areas?: Array<{ __typename?: 'AreaNode', id: string, level: number } | null> | null } | null> | null, registrationImports: { __typename?: 'RegistrationDataImportNodeConnection', totalCount?: number | null }, pduFields?: Array<{ __typename?: 'PeriodicFieldNode', id: string, label: any, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null> | null }; +export type ProgramDetailsFragment = { __typename?: 'ProgramNode', id: string, name: string, programmeCode?: string | null, startDate: any, endDate: any, status: ProgramStatus, caId?: string | null, caHashId?: string | null, description: string, budget?: any | null, frequencyOfPayments: ProgramFrequencyOfPayments, cashPlus: boolean, populationGoal: number, scope?: ProgramScope | null, sector: ProgramSector, totalNumberOfHouseholds?: number | null, totalNumberOfHouseholdsWithTpInProgram?: number | null, administrativeAreasOfImplementation: string, isSocialWorkerProgram?: boolean | null, version: any, adminUrl?: string | null, partnerAccess: ProgramPartnerAccess, targetPopulationsCount?: number | null, dataCollectingType?: { __typename?: 'DataCollectingTypeNode', id: string, code: string, label: string, active: boolean, individualFiltersAvailable: boolean, householdFiltersAvailable: boolean, description: string, type?: DataCollectingTypeType | null } | null, partners?: Array<{ __typename?: 'PartnerNode', id: string, name?: string | null, areaAccess?: string | null, areas?: Array<{ __typename?: 'AreaNode', id: string, level: number } | null> | null } | null> | null, registrationImports: { __typename?: 'RegistrationDataImportNodeConnection', totalCount?: number | null }, pduFields?: Array<{ __typename?: 'PeriodicFieldNode', id: string, label: any, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null> | null }; export type RegistrationMinimalFragment = { __typename?: 'RegistrationDataImportNode', id: string, createdAt: any, name: string, status: RegistrationDataImportStatus, erased: boolean, importDate: any, dataSource: RegistrationDataImportDataSource, numberOfHouseholds: number, numberOfIndividuals: number, refuseReason?: string | null, totalHouseholdsCountWithValidPhoneNo?: number | null, adminUrl?: string | null, importedBy?: { __typename?: 'UserNode', id: string, firstName: string, lastName: string, email: string } | null, program?: { __typename?: 'ProgramNode', id: string, name: string, startDate: any, endDate: any, status: ProgramStatus } | null }; @@ -9982,7 +9982,7 @@ export type UpdateProgramMutationVariables = Exact<{ }>; -export type UpdateProgramMutation = { __typename?: 'Mutations', updateProgram?: { __typename?: 'UpdateProgram', validationErrors?: any | null, program?: { __typename?: 'ProgramNode', id: string, name: string, programmeCode?: string | null, startDate: any, endDate: any, status: ProgramStatus, caId?: string | null, caHashId?: string | null, description: string, budget?: any | null, frequencyOfPayments: ProgramFrequencyOfPayments, cashPlus: boolean, populationGoal: number, scope?: ProgramScope | null, sector: ProgramSector, totalNumberOfHouseholds?: number | null, totalNumberOfHouseholdsWithTpInProgram?: number | null, administrativeAreasOfImplementation: string, isSocialWorkerProgram?: boolean | null, version: any, adminUrl?: string | null, partnerAccess: ProgramPartnerAccess, dataCollectingType?: { __typename?: 'DataCollectingTypeNode', id: string, code: string, label: string, active: boolean, individualFiltersAvailable: boolean, householdFiltersAvailable: boolean, description: string, type?: DataCollectingTypeType | null } | null, partners?: Array<{ __typename?: 'PartnerNode', id: string, name?: string | null, areaAccess?: string | null, areas?: Array<{ __typename?: 'AreaNode', id: string, level: number } | null> | null } | null> | null, registrationImports: { __typename?: 'RegistrationDataImportNodeConnection', totalCount?: number | null }, pduFields?: Array<{ __typename?: 'PeriodicFieldNode', id: string, label: any, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null> | null } | null } | null }; +export type UpdateProgramMutation = { __typename?: 'Mutations', updateProgram?: { __typename?: 'UpdateProgram', validationErrors?: any | null, program?: { __typename?: 'ProgramNode', id: string, name: string, programmeCode?: string | null, startDate: any, endDate: any, status: ProgramStatus, caId?: string | null, caHashId?: string | null, description: string, budget?: any | null, frequencyOfPayments: ProgramFrequencyOfPayments, cashPlus: boolean, populationGoal: number, scope?: ProgramScope | null, sector: ProgramSector, totalNumberOfHouseholds?: number | null, totalNumberOfHouseholdsWithTpInProgram?: number | null, administrativeAreasOfImplementation: string, isSocialWorkerProgram?: boolean | null, version: any, adminUrl?: string | null, partnerAccess: ProgramPartnerAccess, targetPopulationsCount?: number | null, dataCollectingType?: { __typename?: 'DataCollectingTypeNode', id: string, code: string, label: string, active: boolean, individualFiltersAvailable: boolean, householdFiltersAvailable: boolean, description: string, type?: DataCollectingTypeType | null } | null, partners?: Array<{ __typename?: 'PartnerNode', id: string, name?: string | null, areaAccess?: string | null, areas?: Array<{ __typename?: 'AreaNode', id: string, level: number } | null> | null } | null> | null, registrationImports: { __typename?: 'RegistrationDataImportNodeConnection', totalCount?: number | null }, pduFields?: Array<{ __typename?: 'PeriodicFieldNode', id: string, label: any, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null> | null } | null } | null }; export type CreateRegistrationKoboImportMutationVariables = Exact<{ registrationDataImportData: RegistrationKoboImportMutationInput; @@ -10972,7 +10972,7 @@ export type ProgramQueryVariables = Exact<{ }>; -export type ProgramQuery = { __typename?: 'Query', program?: { __typename?: 'ProgramNode', id: string, name: string, programmeCode?: string | null, startDate: any, endDate: any, status: ProgramStatus, caId?: string | null, caHashId?: string | null, description: string, budget?: any | null, frequencyOfPayments: ProgramFrequencyOfPayments, cashPlus: boolean, populationGoal: number, scope?: ProgramScope | null, sector: ProgramSector, totalNumberOfHouseholds?: number | null, totalNumberOfHouseholdsWithTpInProgram?: number | null, administrativeAreasOfImplementation: string, isSocialWorkerProgram?: boolean | null, version: any, adminUrl?: string | null, partnerAccess: ProgramPartnerAccess, dataCollectingType?: { __typename?: 'DataCollectingTypeNode', id: string, code: string, label: string, active: boolean, individualFiltersAvailable: boolean, householdFiltersAvailable: boolean, description: string, type?: DataCollectingTypeType | null } | null, partners?: Array<{ __typename?: 'PartnerNode', id: string, name?: string | null, areaAccess?: string | null, areas?: Array<{ __typename?: 'AreaNode', id: string, level: number } | null> | null } | null> | null, registrationImports: { __typename?: 'RegistrationDataImportNodeConnection', totalCount?: number | null }, pduFields?: Array<{ __typename?: 'PeriodicFieldNode', id: string, label: any, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null> | null } | null }; +export type ProgramQuery = { __typename?: 'Query', program?: { __typename?: 'ProgramNode', id: string, name: string, programmeCode?: string | null, startDate: any, endDate: any, status: ProgramStatus, caId?: string | null, caHashId?: string | null, description: string, budget?: any | null, frequencyOfPayments: ProgramFrequencyOfPayments, cashPlus: boolean, populationGoal: number, scope?: ProgramScope | null, sector: ProgramSector, totalNumberOfHouseholds?: number | null, totalNumberOfHouseholdsWithTpInProgram?: number | null, administrativeAreasOfImplementation: string, isSocialWorkerProgram?: boolean | null, version: any, adminUrl?: string | null, partnerAccess: ProgramPartnerAccess, targetPopulationsCount?: number | null, dataCollectingType?: { __typename?: 'DataCollectingTypeNode', id: string, code: string, label: string, active: boolean, individualFiltersAvailable: boolean, householdFiltersAvailable: boolean, description: string, type?: DataCollectingTypeType | null } | null, partners?: Array<{ __typename?: 'PartnerNode', id: string, name?: string | null, areaAccess?: string | null, areas?: Array<{ __typename?: 'AreaNode', id: string, level: number } | null> | null } | null> | null, registrationImports: { __typename?: 'RegistrationDataImportNodeConnection', totalCount?: number | null }, pduFields?: Array<{ __typename?: 'PeriodicFieldNode', id: string, label: any, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null> | null } | null }; export type ProgrammeChoiceDataQueryVariables = Exact<{ [key: string]: never; }>; @@ -12318,6 +12318,7 @@ export const ProgramDetailsFragmentDoc = gql` registrationImports { totalCount } + targetPopulationsCount pduFields { id label diff --git a/frontend/src/apollo/fragments/ProgramDetailsFragment.ts b/frontend/src/apollo/fragments/ProgramDetailsFragment.ts index 6409c85cd5..ffd8e4305c 100644 --- a/frontend/src/apollo/fragments/ProgramDetailsFragment.ts +++ b/frontend/src/apollo/fragments/ProgramDetailsFragment.ts @@ -46,6 +46,7 @@ export const programDetails = gql` registrationImports { totalCount } + targetPopulationsCount pduFields { id label diff --git a/frontend/src/components/programs/CreateProgram/ProgramFieldSeriesStep.tsx b/frontend/src/components/programs/CreateProgram/ProgramFieldSeriesStep.tsx index 9927dff614..a9f245e8af 100644 --- a/frontend/src/components/programs/CreateProgram/ProgramFieldSeriesStep.tsx +++ b/frontend/src/components/programs/CreateProgram/ProgramFieldSeriesStep.tsx @@ -34,6 +34,7 @@ export const ProgramFieldSeriesStep = ({ setStep, step, programHasRdi, + programHasTp, pdusubtypeChoicesData, errors, programId: formProgramId, @@ -57,7 +58,7 @@ export const ProgramFieldSeriesStep = ({ 'Are you sure you want to delete this field? This action cannot be reversed.', ); - const fieldDisabled = programHasRdi; + const fieldDisabled = programHasRdi || programHasTp; return ( <> diff --git a/frontend/src/containers/pages/program/EditProgramPage.tsx b/frontend/src/containers/pages/program/EditProgramPage.tsx index 589566df1c..02fdd0f345 100644 --- a/frontend/src/containers/pages/program/EditProgramPage.tsx +++ b/frontend/src/containers/pages/program/EditProgramPage.tsx @@ -95,10 +95,11 @@ export const EditProgramPage = (): ReactElement => { partnerAccess = ProgramPartnerAccess.AllPartnersAccess, registrationImports, pduFields, - targetpopulationSet, + targetPopulationsCount, } = data.program; const programHasRdi = registrationImports?.totalCount > 0; + const programHasTp = targetPopulationsCount > 0; const handleSubmit = async (values): Promise => { const budgetValue = parseFloat(values.budget) ?? 0; @@ -351,6 +352,7 @@ export const EditProgramPage = (): ReactElement => { setErrors={setErrors} setFieldTouched={setFieldTouched} programHasRdi={programHasRdi} + programHasTp={programHasTp} programId={id} program={data.program} setFieldValue={setFieldValue} From 4b8d76b99003cb6c78dfe843178490ee4bd7794c Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Fri, 23 Aug 2024 02:03:44 +0200 Subject: [PATCH 081/118] adjust e2e test for targeting by pdu date --- .../targeting/services/targeting_service.py | 2 +- .../page_object/targeting/targeting_create.py | 24 +++++++++++++++++++ .../targeting/test_targeting.py | 24 ++++--------------- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/backend/hct_mis_api/apps/targeting/services/targeting_service.py b/backend/hct_mis_api/apps/targeting/services/targeting_service.py index 1ebc316f9c..84e810bbb8 100644 --- a/backend/hct_mis_api/apps/targeting/services/targeting_service.py +++ b/backend/hct_mis_api/apps/targeting/services/targeting_service.py @@ -248,7 +248,7 @@ class TargetingCriteriaFilterBase: "arguments": 1, "lookup": "", "negative": False, - "supported_types": ["PDU"], + "supported_types": ["DECIMAL", "DATE", "STRING", "BOOL"], }, } diff --git a/backend/selenium_tests/page_object/targeting/targeting_create.py b/backend/selenium_tests/page_object/targeting/targeting_create.py index b8411bc081..811ee55c18 100644 --- a/backend/selenium_tests/page_object/targeting/targeting_create.py +++ b/backend/selenium_tests/page_object/targeting/targeting_create.py @@ -68,6 +68,12 @@ class TargetingCreate(BaseComponents): inputIndividualsFiltersBlocksValueTo = ( 'input[data-cy="input-individualsFiltersBlocks[{}].individualBlockFilters[{}].value.to"]' ) + inputDateIndividualsFiltersBlocksValueFrom = ( + 'input[data-cy="date-input-individualsFiltersBlocks[{}].individualBlockFilters[{}].value.from"]' + ) + inputDateIndividualsFiltersBlocksValueTo = ( + 'input[data-cy="date-input-individualsFiltersBlocks[{}].individualBlockFilters[{}].value.to"]' + ) inputIndividualsFiltersBlocksValue = ( 'input[data-cy="input-individualsFiltersBlocks[{}].individualBlockFilters[{}].value"]' ) @@ -266,6 +272,24 @@ def getInputIndividualsFiltersBlocksValueTo( ) ) + def getInputDateIndividualsFiltersBlocksValueFrom( + self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0 + ) -> WebElement: + return self.wait_for( + self.inputDateIndividualsFiltersBlocksValueFrom.format( + individuals_filters_blocks_number, individual_block_filters_number + ) + ) + + def getInputDateIndividualsFiltersBlocksValueTo( + self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0 + ) -> WebElement: + return self.wait_for( + self.inputDateIndividualsFiltersBlocksValueTo.format( + individuals_filters_blocks_number, individual_block_filters_number + ) + ) + def getInputIndividualsFiltersBlocksValue( self, individuals_filters_blocks_number: int = 0, individual_block_filters_number: int = 0 ) -> WebElement: diff --git a/backend/selenium_tests/targeting/test_targeting.py b/backend/selenium_tests/targeting/test_targeting.py index 7707ed1edd..bb38e7b990 100644 --- a/backend/selenium_tests/targeting/test_targeting.py +++ b/backend/selenium_tests/targeting/test_targeting.py @@ -616,8 +616,10 @@ def test_create_targeting_with_pdu_date_criteria( pageTargetingCreate.getTargetingCriteriaAutoComplete().send_keys(Keys.ENTER) pageTargetingCreate.getSelectIndividualsiFltersBlocksRoundNumber().click() pageTargetingCreate.getSelectRoundOption(1).click() - pageTargetingCreate.getInputIndividualsFiltersBlocksValueFrom().send_keys("2022-01-01") - pageTargetingCreate.getInputIndividualsFiltersBlocksValueTo().send_keys("2022-03-03") + pageTargetingCreate.getInputDateIndividualsFiltersBlocksValueFrom().click() + pageTargetingCreate.getInputDateIndividualsFiltersBlocksValueFrom().send_keys("2022-01-01") + pageTargetingCreate.getInputDateIndividualsFiltersBlocksValueTo().click() + pageTargetingCreate.getInputDateIndividualsFiltersBlocksValueTo().send_keys("2022-03-03") pageTargetingCreate.getTargetingCriteriaAddDialogSaveButton().click() expected_criteria_text = "Test Date Attribute: 2022-01-01 - 2022-03-03\nRound 1 (Test Round Date 1)" assert pageTargetingCreate.getCriteriaContainer().text == expected_criteria_text @@ -632,24 +634,6 @@ def test_create_targeting_with_pdu_date_criteria( assert pageTargetingCreate.getTotalNumberOfHouseholdsCount().text == "1" assert len(pageTargetingDetails.getHouseholdTableRows()) == 1 - # edit range - pageTargetingDetails.getButtonEdit().click() - pageTargetingDetails.getButtonIconEdit().click() - pageTargetingCreate.getInputIndividualsFiltersBlocksValueTo().send_keys(Keys.BACKSPACE) - pageTargetingCreate.getInputIndividualsFiltersBlocksValueTo().send_keys("5") - bool_no_expected_criteria_text = "Test Bool Attribute: 2 - 5\nRound 2 (Test Round Bool 2)" - - pageTargetingCreate.get_elements(pageTargetingCreate.targetingCriteriaAddDialogSaveButton)[1].click() - - assert pageTargetingCreate.getCriteriaContainer().text == bool_no_expected_criteria_text - pageTargetingCreate.getButtonSave().click() - pageTargetingDetails.getLockButton() - - assert pageTargetingDetails.getCriteriaContainer().text == bool_no_expected_criteria_text - assert pageTargetingDetails.getHouseholdTableCell(2, 1).text == individual1.household.unicef_id - assert pageTargetingCreate.getTotalNumberOfHouseholdsCount().text == "2" - assert len(pageTargetingDetails.getHouseholdTableRows()) == 2 - def test_create_targeting_with_pdu_null_criteria( self, program: Program, From 4e73ceb8fab2f5ca7be5ab32925de69b391077f5 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Fri, 23 Aug 2024 02:27:00 +0200 Subject: [PATCH 082/118] add validation for individual rules, add terst coverage for TP query resolvers --- .../tests/snapshots/snap_test_target_query.py | 88 ++++++++++++++++++ .../tests/test_individual_block_filters.py | 6 +- .../apps/targeting/tests/test_target_query.py | 92 ++++++++++++++++++- .../test_targeting_criteria_rule_filter.py | 2 +- .../hct_mis_api/apps/targeting/validators.py | 9 +- 5 files changed, 190 insertions(+), 7 deletions(-) diff --git a/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_target_query.py b/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_target_query.py index d7851fc868..c13020d836 100644 --- a/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_target_query.py +++ b/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_target_query.py @@ -35,6 +35,18 @@ 'totalIndividualsCount': 2 } }, + { + 'node': { + 'createdBy': { + 'firstName': 'Second', + 'lastName': 'User' + }, + 'name': 'target_population_with_pdu_filter', + 'status': 'LOCKED', + 'totalHouseholdsCount': 1, + 'totalIndividualsCount': 3 + } + }, { 'node': { 'createdBy': { @@ -79,6 +91,14 @@ 'totalHouseholdsCount': 2, 'totalIndividualsCount': 2 } + }, + { + 'node': { + 'name': 'target_population_with_pdu_filter', + 'status': 'LOCKED', + 'totalHouseholdsCount': 1, + 'totalIndividualsCount': 3 + } } ] } @@ -132,6 +152,14 @@ 'totalHouseholdsCount': 2, 'totalIndividualsCount': 2 } + }, + { + 'node': { + 'name': 'target_population_with_pdu_filter', + 'status': 'LOCKED', + 'totalHouseholdsCount': 1, + 'totalIndividualsCount': 3 + } } ] } @@ -161,6 +189,8 @@ 'fieldName': 'size', 'flexFieldClassification': 'NOT_FLEX_FIELD' } + ], + 'individualsFiltersBlocks': [ ] } ] @@ -214,6 +244,8 @@ 'fieldName': 'residence_status', 'flexFieldClassification': 'NOT_FLEX_FIELD' } + ], + 'individualsFiltersBlocks': [ ] } ] @@ -243,3 +275,59 @@ } ] } + +snapshots['TestTargetPopulationQuery::test_simple_target_query_pdu_0_with_permission 1'] = { + 'data': { + 'targetPopulation': { + 'hasEmptyCriteria': False, + 'hasEmptyIdsCriteria': True, + 'name': 'target_population_with_pdu_filter', + 'status': 'LOCKED', + 'targetingCriteria': { + 'rules': [ + { + 'filters': [ + ], + 'individualsFiltersBlocks': [ + { + 'individualBlockFilters': [ + { + 'arguments': [ + 'some' + ], + 'comparisonMethod': 'EQUALS', + 'fieldName': 'pdu_field_string', + 'flexFieldClassification': 'FLEX_FIELD_PDU', + 'roundNumber': 1 + } + ] + } + ] + } + ] + }, + 'totalHouseholdsCount': 1, + 'totalIndividualsCount': 3 + } + } +} + +snapshots['TestTargetPopulationQuery::test_simple_target_query_pdu_1_without_permission 1'] = { + 'data': { + 'targetPopulation': None + }, + 'errors': [ + { + 'locations': [ + { + 'column': 11, + 'line': 3 + } + ], + 'message': 'Permission Denied', + 'path': [ + 'targetPopulation' + ] + } + ] +} diff --git a/backend/hct_mis_api/apps/targeting/tests/test_individual_block_filters.py b/backend/hct_mis_api/apps/targeting/tests/test_individual_block_filters.py index 96152385e2..a57927403c 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_individual_block_filters.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_individual_block_filters.py @@ -245,7 +245,7 @@ def test_filter_on_pdu_flex_field_no_round_number(self) -> None: pdu_data = PeriodicFieldDataFactory( subtype=PeriodicFieldData.DECIMAL, number_of_rounds=2, - rounds_names=["Round 1", "Rounds 2"], + rounds_names=["Round 1", "Round 2"], ) FlexibleAttributeForPDUFactory( program=self.program, @@ -284,7 +284,7 @@ def test_filter_on_pdu_flex_field_incorrect_round_number(self) -> None: pdu_data = PeriodicFieldDataFactory( subtype=PeriodicFieldData.DECIMAL, number_of_rounds=2, - rounds_names=["Round 1", "Rounds 2"], + rounds_names=["Round 1", "Round 2"], ) FlexibleAttributeForPDUFactory( program=self.program, @@ -324,7 +324,7 @@ def test_filter_on_pdu_flex_field(self) -> None: pdu_data = PeriodicFieldDataFactory( subtype=PeriodicFieldData.DECIMAL, number_of_rounds=2, - rounds_names=["Round 1", "Rounds 2"], + rounds_names=["Round 1", "Round 2"], ) FlexibleAttributeForPDUFactory( program=self.program, diff --git a/backend/hct_mis_api/apps/targeting/tests/test_target_query.py b/backend/hct_mis_api/apps/targeting/tests/test_target_query.py index 36528f257e..6fd98c5d12 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_target_query.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_target_query.py @@ -5,15 +5,23 @@ from hct_mis_api.apps.account.fixtures import PartnerFactory, UserFactory from hct_mis_api.apps.account.permissions import Permissions from hct_mis_api.apps.core.base_test_case import APITestCase -from hct_mis_api.apps.core.fixtures import create_afghanistan -from hct_mis_api.apps.core.models import BusinessArea +from hct_mis_api.apps.core.fixtures import ( + FlexibleAttributeForPDUFactory, + PeriodicFieldDataFactory, + create_afghanistan, +) +from hct_mis_api.apps.core.models import BusinessArea, PeriodicFieldData from hct_mis_api.apps.household.fixtures import create_household +from hct_mis_api.apps.periodic_data_update.utils import populate_pdu_with_null_values from hct_mis_api.apps.program.fixtures import ProgramFactory from hct_mis_api.apps.program.models import Program +from hct_mis_api.apps.targeting.choices import FlexFieldClassification from hct_mis_api.apps.targeting.models import ( TargetingCriteria, TargetingCriteriaRule, TargetingCriteriaRuleFilter, + TargetingIndividualBlockRuleFilter, + TargetingIndividualRuleFilterBlock, TargetPopulation, ) from hct_mis_api.apps.targeting.services.targeting_stats_refresher import full_rebuild @@ -75,6 +83,15 @@ class TestTargetPopulationQuery(APITestCase): type } } + individualsFiltersBlocks{ + individualBlockFilters{ + comparisonMethod + fieldName + arguments + flexFieldClassification + roundNumber + } + } } } } @@ -147,6 +164,52 @@ def setUpTestData(cls) -> None: cls.target_population_size_1_approved = full_rebuild(cls.target_population_size_1_approved) cls.target_population_size_1_approved.save() + pdu_data_string = PeriodicFieldDataFactory( + subtype=PeriodicFieldData.STRING, + number_of_rounds=2, + rounds_names=["Round 1", "Round 2"], + ) + cls.pdu_field_string = FlexibleAttributeForPDUFactory( + program=cls.program, + label="PDU Field STRING", + pdu_data=pdu_data_string, + ) + (household, individuals) = create_household( + {"size": 3, "residence_status": "HOST", "business_area": cls.business_area, "program": cls.program}, + ) + individual_with_pdu_value = individuals[0] + populate_pdu_with_null_values(cls.program, individual_with_pdu_value.flex_fields) + individual_with_pdu_value.flex_fields[cls.pdu_field_string.name]["1"]["value"] = "some" + individual_with_pdu_value.save() + targeting_criteria = TargetingCriteria() + targeting_criteria.save() + rule = TargetingCriteriaRule(targeting_criteria=targeting_criteria) + rule.save() + individuals_filters_block = TargetingIndividualRuleFilterBlock( + targeting_criteria_rule=rule, target_only_hoh=False + ) + individuals_filters_block.save() + rule_filter = TargetingIndividualBlockRuleFilter( + individuals_filters_block=individuals_filters_block, + comparison_method="EQUALS", + field_name=cls.pdu_field_string.name, + arguments=["some"], + round_number=1, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_PDU, + ) + rule_filter.save() + cls.target_population_with_pdu_filter = TargetPopulation( + name="target_population_with_pdu_filter", + created_by=user_second, + targeting_criteria=targeting_criteria, + status=TargetPopulation.STATUS_LOCKED, + business_area=cls.business_area, + program=cls.program, + ) + cls.target_population_with_pdu_filter.save() + cls.target_population_with_pdu_filter = full_rebuild(cls.target_population_with_pdu_filter) + cls.target_population_with_pdu_filter.save() + @staticmethod def get_targeting_criteria_for_rule(rule_filter: Dict) -> TargetingCriteria: targeting_criteria = TargetingCriteria() @@ -242,3 +305,28 @@ def test_simple_target_query_next(self, _: Any, permissions: List[Permissions]) ) }, ) + + @parameterized.expand( + [ + ( + "with_permission", + [Permissions.TARGETING_VIEW_DETAILS], + ), + ( + "without_permission", + [], + ), + ] + ) + def test_simple_target_query_pdu(self, _: Any, permissions: List[Permissions]) -> None: + self.create_user_role_with_permissions(self.user, permissions, self.business_area, self.program) + self.snapshot_graphql_request( + request_string=TestTargetPopulationQuery.TARGET_POPULATION_QUERY, + context={"user": self.user}, + variables={ + "id": self.id_to_base64( + self.target_population_with_pdu_filter.id, + "TargetPopulationNode", + ) + }, + ) diff --git a/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria_rule_filter.py b/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria_rule_filter.py index c927ea5bea..ad3e5bf622 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria_rule_filter.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria_rule_filter.py @@ -401,7 +401,7 @@ def setUpTestData(cls) -> None: pdu_data_string = PeriodicFieldDataFactory( subtype=PeriodicFieldData.STRING, number_of_rounds=2, - rounds_names=["Round 1", "Rounds 2"], + rounds_names=["Round 1", "Round 2"], ) cls.pdu_field_string = FlexibleAttributeForPDUFactory( program=cls.program, diff --git a/backend/hct_mis_api/apps/targeting/validators.py b/backend/hct_mis_api/apps/targeting/validators.py index ee61007e46..6f217c7290 100644 --- a/backend/hct_mis_api/apps/targeting/validators.py +++ b/backend/hct_mis_api/apps/targeting/validators.py @@ -120,7 +120,10 @@ def validate(rule_filter: Any, program: Program) -> None: f"Comparison method '{rule_filter.comparison_method}' " f"expected {args_count} arguments, {given_args_count} given" ) - if get_attr_value("type", attribute) not in comparison_attribute.get("supported_types"): + type = get_attr_value("type", attribute, None) + if type == FlexibleAttribute.PDU: + type = attribute.pdu_data.subtype + if type not in comparison_attribute.get("supported_types"): raise ValidationError( f"{rule_filter.field_name} is '{get_attr_value('type', attribute)}' type filter " f"and does not accept '{rule_filter.comparison_method}' comparison method" @@ -143,6 +146,10 @@ def validate(rule: "Rule", program: "Program") -> None: raise ValidationError("There should be at least 1 filter or block in rules") for rule_filter in filters: TargetingCriteriaRuleFilterInputValidator.validate(rule_filter=rule_filter, program=program) + for individuals_filters_block in individuals_filters_blocks: + individual_block_filters = individuals_filters_block.get("individual_block_filters") + for individual_block_filter in individual_block_filters: + TargetingCriteriaRuleFilterInputValidator.validate(rule_filter=individual_block_filter, program=program) class TargetingCriteriaInputValidator: From fd601f1b7abaf4b3665d9e2e63ab3cd43eb8f8f0 Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Fri, 23 Aug 2024 10:57:32 +0200 Subject: [PATCH 083/118] fix date field --- .../FormikDateField/FormikDateField.tsx | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/frontend/src/shared/Formik/FormikDateField/FormikDateField.tsx b/frontend/src/shared/Formik/FormikDateField/FormikDateField.tsx index 8a64e61744..b961f912a3 100644 --- a/frontend/src/shared/Formik/FormikDateField/FormikDateField.tsx +++ b/frontend/src/shared/Formik/FormikDateField/FormikDateField.tsx @@ -44,16 +44,6 @@ export const FormikDateField = ({ size: 'small', error: isInvalid, helperText: isInvalid && get(form.errors, field.name), - InputProps: { - startAdornment: decoratorStart && ( - - {decoratorStart} - - ), - endAdornment: decoratorEnd && ( - {decoratorEnd} - ), - }, inputProps: { 'data-cy': `date-input-${field.name}`, }, @@ -64,6 +54,33 @@ export const FormikDateField = ({ outline: 'none', }, }} + slots={{ + TextField: (props) => ( + + {decoratorStart} + + ), + endAdornment: decoratorEnd && ( + {decoratorEnd} + ), + }} + required={required} + inputProps={{ + ...props.inputProps, + 'data-cy': `date-input-${field.name}`, + }} + /> + ), + }} value={formattedValue || null} onBlur={() => { setTimeout(() => { From 6d7338d92dbcbd9763f553218f9dbe26187eb9fe Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Fri, 23 Aug 2024 11:26:33 +0200 Subject: [PATCH 084/118] fix booleans + blue text --- .../src/components/targeting/SubField.tsx | 4 ++-- .../TargetingCriteriaDisplay/Criteria.tsx | 21 ++++++++++--------- frontend/src/utils/targetingUtils.ts | 8 ++----- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/frontend/src/components/targeting/SubField.tsx b/frontend/src/components/targeting/SubField.tsx index c981755168..e33659ce85 100644 --- a/frontend/src/components/targeting/SubField.tsx +++ b/frontend/src/components/targeting/SubField.tsx @@ -223,14 +223,14 @@ export const SubField: React.FC = ({ labelEn: t('Yes'), labels: [{ label: t('Yes'), language: 'English(EN)' }], listName: null, - value: 'True', + value: 'Yes', }, { admin: null, labelEn: t('No'), labels: [{ label: t('No'), language: 'English(EN)' }], listName: null, - value: 'False', + value: 'No', }, ]} index={index} diff --git a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx index a607277fca..f9270945fb 100644 --- a/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx +++ b/frontend/src/components/targeting/TargetingCriteriaDisplay/Criteria.tsx @@ -7,6 +7,7 @@ import GreaterThanEqual from '../../../assets/GreaterThanEqual.svg'; import LessThanEqual from '../../../assets/LessThanEqual.svg'; import { TargetingCriteriaRuleObjectType } from '@generated/graphql'; import { Box } from '@mui/system'; +import { BlueText } from '@components/grievances/LookUps/LookUpStyles'; interface CriteriaElementProps { alternative?: boolean; @@ -114,31 +115,31 @@ const CriteriaField = ({ field, choicesDict }): React.ReactElement => {

{field.fieldAttribute.labelEn || field.fieldName}:{' '} {field.isNull === true || field.comparisonMethod === 'IS_NULL' ? ( - t('Empty') + {t('Empty')} ) : typeof field.arguments?.[0] === 'boolean' ? ( field.arguments[0] ? ( - t('Yes') + {t('Yes')} ) : ( - t('No') + {t('No')} ) ) : ( <> {field.arguments?.[0] != null ? ( typeof field.arguments[0] === 'boolean' ? ( field.arguments[0] === true ? ( - t('Yes') + {t('Yes')} ) : ( - t('No') + {t('No')} ) - ) : field.arguments[0] === 'True' ? ( - t('Yes') - ) : field.arguments[0] === 'False' ? ( - t('No') + ) : field.arguments[0] === 'Yes' ? ( + {t('Yes')} + ) : field.arguments[0] === 'No' ? ( + {t('No')} ) : ( {extractChoiceLabel(field, field.arguments[0])} ) ) : ( - <>{t('Empty')} + {t('Empty')} )} )} diff --git a/frontend/src/utils/targetingUtils.ts b/frontend/src/utils/targetingUtils.ts index cc1567d54d..cbb0191800 100644 --- a/frontend/src/utils/targetingUtils.ts +++ b/frontend/src/utils/targetingUtils.ts @@ -205,7 +205,7 @@ export function formatCriteriaFilters(filters) { break; case 'BOOL': comparisonMethod = 'EQUALS'; - values = [each.value === 'True']; + values = [each.value === 'Yes']; break; default: comparisonMethod = 'CONTAINS'; @@ -260,11 +260,7 @@ function mapFilterToVariable(filter: Filter): Result { arguments: filter.isNull ? [null] : filter.arguments.map((arg) => - arg === 'True' || arg === 'Yes' - ? true - : arg === 'False' || arg === 'No' - ? false - : arg, + arg === 'Yes' ? true : arg === 'No' ? false : arg, ), fieldName: filter.fieldName, flexFieldClassification: filter.flexFieldClassification, From b90050a95c55aa138b55434dc35353dac4b4f5c7 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Fri, 23 Aug 2024 11:12:11 +0200 Subject: [PATCH 085/118] fix --- backend/hct_mis_api/apps/targeting/validators.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/hct_mis_api/apps/targeting/validators.py b/backend/hct_mis_api/apps/targeting/validators.py index 6f217c7290..6dd2924a8a 100644 --- a/backend/hct_mis_api/apps/targeting/validators.py +++ b/backend/hct_mis_api/apps/targeting/validators.py @@ -135,7 +135,7 @@ class TargetingCriteriaRuleInputValidator: def validate(rule: "Rule", program: "Program") -> None: total_len = 0 filters = rule.get("filters") - individuals_filters_blocks = rule.get("individuals_filters_blocks") + individuals_filters_blocks = rule.get("individuals_filters_blocks", []) if filters is not None: total_len += len(filters) if individuals_filters_blocks is not None: @@ -147,7 +147,7 @@ def validate(rule: "Rule", program: "Program") -> None: for rule_filter in filters: TargetingCriteriaRuleFilterInputValidator.validate(rule_filter=rule_filter, program=program) for individuals_filters_block in individuals_filters_blocks: - individual_block_filters = individuals_filters_block.get("individual_block_filters") + individual_block_filters = individuals_filters_block.get("individual_block_filters", []) for individual_block_filter in individual_block_filters: TargetingCriteriaRuleFilterInputValidator.validate(rule_filter=individual_block_filter, program=program) From d10db1a7c7ee50f4b7e9c4c94cea2084565ce74d Mon Sep 17 00:00:00 2001 From: Pavlo Mokiichuk Date: Fri, 23 Aug 2024 12:13:50 +0200 Subject: [PATCH 086/118] Cycle demo changes (#4144) * init changes * fix cycle fixtures & add migration * fix unit tests * style * fix tests * fix unit tests * fix fixtures init data * small test fixes * fix selenium * revert last changes for validation date * upd tests * fix tests * add more tests * fix selenium test * coverage * remove unused validation * fix validation between program dates --- .../apps/core/migrations/0084_migration.py | 18 +++ backend/hct_mis_api/apps/payment/fixtures.py | 6 - .../apps/payment/migrations/0143_migration.py | 19 +++ .../payment/services/payment_plan_services.py | 6 +- .../tests/test_all_payment_plan_queries.py | 16 +-- .../tests/test_payment_gateway_service.py | 4 +- .../tests/test_payment_plan_reconciliation.py | 3 + .../tests/test_payment_plan_services.py | 35 ++--- .../payment/tests/test_payment_signature.py | 4 +- ...t_recalculating_household_cash_received.py | 6 + backend/hct_mis_api/apps/program/admin.py | 9 +- .../hct_mis_api/apps/program/api/filters.py | 2 +- .../apps/program/api/serializers.py | 46 +++++-- backend/hct_mis_api/apps/program/fixtures.py | 17 ++- .../apps/program/fixtures/data-cypress.json | 1 - .../apps/program/fixtures/data.json | 1 - .../apps/program/migrations/0051_migration.py | 17 +++ backend/hct_mis_api/apps/program/models.py | 13 +- backend/hct_mis_api/apps/program/schema.py | 3 + .../apps/program/tests/test_create_program.py | 2 + .../apps/program/tests/test_program_cycle.py | 18 +++ .../tests/test_program_cycle_rest_api.py | 128 ++++++++++++++---- .../tests/test_rdi_xlsx_create.py | 7 +- .../test_create_target_population_mutation.py | 107 ++++----------- .../program_cycle_data_migration.py | 12 +- backend/selenium_tests/helpers/fixtures.py | 4 +- .../payment_module/test_payment_plans.py | 21 +-- .../payment_module/test_program_cycles.py | 6 +- frontend/data/schema.graphql | 35 +---- frontend/src/__generated__/graphql.tsx | 92 +------------ .../CreatePaymentPlanHeader.tsx | 2 +- .../PaymentPlanDetailsHeader.tsx | 2 +- .../ProgramCycleDetailsHeader.tsx | 2 +- .../tables/ProgramCycle/HeadCells.ts | 7 - .../tables/ProgramCycle/ProgramCycleTable.tsx | 7 +- .../tables/ProgramCyclesTable/HeadCells.ts | 8 -- .../ProgramCyclesTable/ProgramCyclesTable.tsx | 5 +- 37 files changed, 349 insertions(+), 342 deletions(-) create mode 100644 backend/hct_mis_api/apps/core/migrations/0084_migration.py create mode 100644 backend/hct_mis_api/apps/payment/migrations/0143_migration.py create mode 100644 backend/hct_mis_api/apps/program/migrations/0051_migration.py diff --git a/backend/hct_mis_api/apps/core/migrations/0084_migration.py b/backend/hct_mis_api/apps/core/migrations/0084_migration.py new file mode 100644 index 0000000000..e119fd912c --- /dev/null +++ b/backend/hct_mis_api/apps/core/migrations/0084_migration.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.25 on 2024-08-19 10:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0083_migration'), + ] + + operations = [ + migrations.AlterField( + model_name='periodicfielddata', + name='subtype', + field=models.CharField(choices=[('DATE', 'Date'), ('DECIMAL', 'Number'), ('STRING', 'Text'), ('BOOLEAN', 'Boolean (true/false)')], max_length=16), + ), + ] diff --git a/backend/hct_mis_api/apps/payment/fixtures.py b/backend/hct_mis_api/apps/payment/fixtures.py index 4937be9a1c..3af3b90089 100644 --- a/backend/hct_mis_api/apps/payment/fixtures.py +++ b/backend/hct_mis_api/apps/payment/fixtures.py @@ -180,12 +180,6 @@ def payment_verification_summary(self, create: bool, extracted: bool, **kwargs: PaymentVerificationSummaryFactory(generic_fk_obj=self) - @factory.post_generation - def cycle(self, create: bool, extracted: bool, **kwargs: Any) -> None: - if not create: - return - ProgramCycleFactory(program=self.program, **kwargs) - class ServiceProviderFactory(DjangoModelFactory): class Meta: diff --git a/backend/hct_mis_api/apps/payment/migrations/0143_migration.py b/backend/hct_mis_api/apps/payment/migrations/0143_migration.py new file mode 100644 index 0000000000..8562ac2e08 --- /dev/null +++ b/backend/hct_mis_api/apps/payment/migrations/0143_migration.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.25 on 2024-08-20 16:20 + +from django.db import migrations, models +import hct_mis_api.apps.payment.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('payment', '0142_migration'), + ] + + operations = [ + migrations.AlterField( + model_name='financialserviceproviderxlsxtemplate', + name='flex_fields', + field=hct_mis_api.apps.payment.models.FlexFieldArrayField(base_field=models.CharField(blank=True, max_length=255), blank=True, default=list, size=None), + ), + ] diff --git a/backend/hct_mis_api/apps/payment/services/payment_plan_services.py b/backend/hct_mis_api/apps/payment/services/payment_plan_services.py index 2d6d66b352..7a51cbacad 100644 --- a/backend/hct_mis_api/apps/payment/services/payment_plan_services.py +++ b/backend/hct_mis_api/apps/payment/services/payment_plan_services.py @@ -380,13 +380,11 @@ def create(input_data: Dict, user: "User") -> PaymentPlan: raise GraphQLError("TargetPopulation should have related Program defined") if not target_population.program_cycle: - raise GraphQLError("Target Population should have assigned Program Cycle") + raise GraphQLError("Target Population should have assigned Programme Cycle") program_cycle = target_population.program_cycle if program_cycle.status not in (ProgramCycle.DRAFT, ProgramCycle.ACTIVE): - raise GraphQLError("Impossible to create Payment Plan for Program Cycle within Finished status") - if not program_cycle.start_date or not program_cycle.end_date: - raise GraphQLError("Impossible to create Payment Plan for Program Cycle without start and/or end dates") + raise GraphQLError("Impossible to create Payment Plan for Programme Cycle within Finished status") dispersion_end_date = input_data["dispersion_end_date"] if not dispersion_end_date or dispersion_end_date <= timezone.now().date(): diff --git a/backend/hct_mis_api/apps/payment/tests/test_all_payment_plan_queries.py b/backend/hct_mis_api/apps/payment/tests/test_all_payment_plan_queries.py index 9fe0c6a9b5..215a4d18c7 100644 --- a/backend/hct_mis_api/apps/payment/tests/test_all_payment_plan_queries.py +++ b/backend/hct_mis_api/apps/payment/tests/test_all_payment_plan_queries.py @@ -36,8 +36,8 @@ def create_child_payment_plans(pp: PaymentPlan) -> None: dispersion_end_date=datetime(2020, 12, 10), is_follow_up=True, source_payment_plan=pp, - program__cycle__start_date=timezone.datetime(2020, 9, 10, tzinfo=utc), - program__cycle__end_date=timezone.datetime(2020, 11, 10, tzinfo=utc), + program__cycle__start_date=timezone.datetime(2020, 9, 10, tzinfo=utc).date(), + program__cycle__end_date=timezone.datetime(2020, 11, 10, tzinfo=utc).date(), ) fpp1.unicef_id = "PP-0060-20-00000003" fpp1.save() @@ -48,8 +48,8 @@ def create_child_payment_plans(pp: PaymentPlan) -> None: dispersion_end_date=datetime(2020, 12, 10), is_follow_up=True, source_payment_plan=pp, - program__cycle__start_date=timezone.datetime(2020, 9, 10, tzinfo=utc), - program__cycle__end_date=timezone.datetime(2020, 11, 10, tzinfo=utc), + program__cycle__start_date=timezone.datetime(2020, 9, 10, tzinfo=utc).date(), + program__cycle__end_date=timezone.datetime(2020, 11, 10, tzinfo=utc).date(), ) fpp2.unicef_id = "PP-0060-20-00000004" fpp2.save() @@ -219,8 +219,8 @@ def setUpTestData(cls) -> None: with freeze_time("2020-10-10"): program = RealProgramFactory( - cycle__start_date=timezone.datetime(2020, 9, 10, tzinfo=utc), - cycle__end_date=timezone.datetime(2020, 11, 10, tzinfo=utc), + cycle__start_date=timezone.datetime(2020, 9, 10, tzinfo=utc).date(), + cycle__end_date=timezone.datetime(2020, 11, 10, tzinfo=utc).date(), ) program_cycle = program.cycles.first() cls.pp = PaymentPlanFactory( @@ -439,8 +439,8 @@ def test_fetch_payment_plan_status_choices(self) -> None: def test_payment_node_with_legacy_data(self) -> None: # test get snapshot data only program = RealProgramFactory( - cycle__start_date=timezone.datetime(2023, 9, 10, tzinfo=utc), - cycle__end_date=timezone.datetime(2023, 11, 10, tzinfo=utc), + cycle__start_date=timezone.datetime(2023, 9, 10, tzinfo=utc).date(), + cycle__end_date=timezone.datetime(2023, 11, 10, tzinfo=utc).date(), ) new_pp = PaymentPlanFactory( program=program, diff --git a/backend/hct_mis_api/apps/payment/tests/test_payment_gateway_service.py b/backend/hct_mis_api/apps/payment/tests/test_payment_gateway_service.py index bcca5d3214..a0f33cceb1 100644 --- a/backend/hct_mis_api/apps/payment/tests/test_payment_gateway_service.py +++ b/backend/hct_mis_api/apps/payment/tests/test_payment_gateway_service.py @@ -68,8 +68,8 @@ def setUpTestData(cls) -> None: cls.user = UserFactory.create() cls.pp = PaymentPlanFactory( - program__cycle__start_date=timezone.datetime(2021, 6, 10, tzinfo=utc), - program__cycle__end_date=timezone.datetime(2021, 7, 10, tzinfo=utc), + program__cycle__start_date=timezone.datetime(2021, 6, 10, tzinfo=utc).date(), + program__cycle__end_date=timezone.datetime(2021, 7, 10, tzinfo=utc).date(), status=PaymentPlan.Status.ACCEPTED, ) cls.pg_fsp = FinancialServiceProviderFactory( 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 fa3a4e5e4f..b946ae8044 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 @@ -352,6 +352,9 @@ def test_receiving_reconciliations_from_fsp(self, mock_get_exchange_rate: Any) - ) program = Program.objects.get(id=decode_id_string_required(program_id)) + cycle = program.cycles.first() + cycle.end_date = timezone.datetime(2022, 8, 24, tzinfo=utc).date() + cycle.save() program_cycle_id = create_programme_response["data"]["createProgram"]["program"]["cycles"]["edges"][0]["node"][ "id" ] diff --git a/backend/hct_mis_api/apps/payment/tests/test_payment_plan_services.py b/backend/hct_mis_api/apps/payment/tests/test_payment_plan_services.py index 2500f7b091..f8a3af0630 100644 --- a/backend/hct_mis_api/apps/payment/tests/test_payment_plan_services.py +++ b/backend/hct_mis_api/apps/payment/tests/test_payment_plan_services.py @@ -113,8 +113,8 @@ def test_create_validation_errors(self) -> None: status=Program.ACTIVE, start_date=timezone.datetime(2019, 10, 12, tzinfo=utc).date(), end_date=timezone.datetime(2099, 12, 10, tzinfo=utc).date(), - cycle__start_date=timezone.datetime(2021, 10, 10, tzinfo=utc), - cycle__end_date=timezone.datetime(2021, 12, 10, tzinfo=utc), + cycle__start_date=timezone.datetime(2021, 10, 10, tzinfo=utc).date(), + cycle__end_date=timezone.datetime(2021, 12, 10, tzinfo=utc).date(), ) targeting = TargetPopulationFactory(program=program, program_cycle=program.cycles.first()) @@ -292,8 +292,8 @@ def test_update(self, get_exchange_rate_mock: Any) -> None: def test_create_follow_up_pp(self, get_exchange_rate_mock: Any) -> None: pp = PaymentPlanFactory( total_households_count=1, - program__cycle__start_date=timezone.datetime(2021, 6, 10, tzinfo=utc), - program__cycle__end_date=timezone.datetime(2021, 7, 10, tzinfo=utc), + program__cycle__start_date=timezone.datetime(2021, 6, 10, tzinfo=utc).date(), + program__cycle__end_date=timezone.datetime(2021, 7, 10, tzinfo=utc).date(), ) new_targeting = TargetPopulationFactory( status=TargetPopulation.STATUS_READY_FOR_PAYMENT_MODULE, @@ -414,8 +414,8 @@ def test_split(self, min_no_of_payments_in_chunk_mock: Any, get_exchange_rate_mo min_no_of_payments_in_chunk_mock.__get__ = mock.Mock(return_value=2) pp = PaymentPlanFactory( - program__cycle__start_date=timezone.datetime(2021, 6, 10, tzinfo=utc), - program__cycle__end_date=timezone.datetime(2021, 7, 10, tzinfo=utc), + program__cycle__start_date=timezone.datetime(2021, 6, 10, tzinfo=utc).date(), + program__cycle__end_date=timezone.datetime(2021, 7, 10, tzinfo=utc).date(), ) with self.assertRaisesMessage(GraphQLError, "No payments to split"): @@ -522,8 +522,8 @@ def test_split(self, min_no_of_payments_in_chunk_mock: Any, get_exchange_rate_mo @mock.patch("hct_mis_api.apps.payment.models.PaymentPlan.get_exchange_rate", return_value=2.0) def test_send_to_payment_gateway(self, get_exchange_rate_mock: Any) -> None: pp = PaymentPlanFactory( - program__cycle__start_date=timezone.datetime(2021, 6, 10, tzinfo=utc), - program__cycle__end_date=timezone.datetime(2021, 7, 10, tzinfo=utc), + program__cycle__start_date=timezone.datetime(2021, 6, 10, tzinfo=utc).date(), + program__cycle__end_date=timezone.datetime(2021, 7, 10, tzinfo=utc).date(), status=PaymentPlan.Status.ACCEPTED, ) pp.background_action_status_send_to_payment_gateway() @@ -570,8 +570,8 @@ def test_create_with_program_cycle_validation_error(self) -> None: status=Program.ACTIVE, start_date=timezone.datetime(2000, 9, 10, tzinfo=utc).date(), end_date=timezone.datetime(2099, 10, 10, tzinfo=utc).date(), - cycle__start_date=timezone.datetime(2021, 10, 10, tzinfo=utc), - cycle__end_date=timezone.datetime(2021, 12, 10, tzinfo=utc), + cycle__start_date=timezone.datetime(2021, 10, 10, tzinfo=utc).date(), + cycle__end_date=timezone.datetime(2021, 12, 10, tzinfo=utc).date(), ), ) cycle = targeting.program.cycles.first() @@ -587,23 +587,14 @@ def test_create_with_program_cycle_validation_error(self) -> None: with self.assertRaisesMessage( GraphQLError, - "Impossible to create Payment Plan for Program Cycle within Finished status", + "Impossible to create Payment Plan for Programme Cycle within Finished status", ): cycle.status = ProgramCycle.FINISHED cycle.save() PaymentPlanService.create(input_data=input_data, user=self.user) - with self.assertRaisesMessage( - GraphQLError, - "Impossible to create Payment Plan for Program Cycle without start and/or end dates", - ): - cycle.status = ProgramCycle.ACTIVE - cycle.end_date = None - cycle.save() - PaymentPlanService.create(input_data=input_data, user=self.user) - cycle.status = ProgramCycle.DRAFT - cycle.end_date = parse_date("2021-11-25") + cycle.end_date = None cycle.save() PaymentPlanService.create(input_data=input_data, user=self.user) cycle.refresh_from_db() @@ -624,5 +615,5 @@ def test_create_pp_validation_errors_if_tp_without_cycle(self) -> None: ) self.assertIsNone(targeting_without_cycle.program_cycle) - with self.assertRaisesMessage(GraphQLError, "Target Population should have assigned Program Cycle"): + with self.assertRaisesMessage(GraphQLError, "Target Population should have assigned Programme Cycle"): PaymentPlanService.create(input_data=input_data, user=self.user) diff --git a/backend/hct_mis_api/apps/payment/tests/test_payment_signature.py b/backend/hct_mis_api/apps/payment/tests/test_payment_signature.py index 0701e34722..6a28d9ad57 100644 --- a/backend/hct_mis_api/apps/payment/tests/test_payment_signature.py +++ b/backend/hct_mis_api/apps/payment/tests/test_payment_signature.py @@ -120,8 +120,8 @@ def test_signature_after_prepare_payment_plan(self, get_exchange_rate_mock: Any) status=Program.ACTIVE, start_date=timezone.datetime(2000, 9, 10, tzinfo=utc).date(), end_date=timezone.datetime(2099, 10, 10, tzinfo=utc).date(), - cycle__start_date=timezone.datetime(2021, 10, 10, tzinfo=utc), - cycle__end_date=timezone.datetime(2021, 12, 10, tzinfo=utc), + cycle__start_date=timezone.datetime(2021, 10, 10, tzinfo=utc).date(), + cycle__end_date=timezone.datetime(2021, 12, 10, tzinfo=utc).date(), ) hoh1 = IndividualFactory(household=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 670db5cc59..c3e7300652 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 @@ -2,6 +2,8 @@ from typing import Any, Dict from unittest.mock import MagicMock +from django.utils.dateparse import parse_date + import hct_mis_api.apps.cash_assist_datahub.fixtures as ca_fixtures import hct_mis_api.apps.cash_assist_datahub.models as ca_models import hct_mis_api.apps.payment.fixtures as payment_fixtures @@ -13,8 +15,10 @@ from hct_mis_api.apps.core.base_test_case import APITestCase from hct_mis_api.apps.core.fixtures import create_afghanistan from hct_mis_api.apps.core.models import BusinessArea, DataCollectingType +from hct_mis_api.apps.core.utils import decode_id_string from hct_mis_api.apps.household.fixtures import create_household from hct_mis_api.apps.payment.fixtures import CashPlanFactory +from hct_mis_api.apps.program.models import ProgramCycle class TestRecalculatingCash(APITestCase): @@ -235,6 +239,8 @@ def test_household_cash_received_update(self) -> None: program_id = program_response["data"]["createProgram"]["program"]["id"] program_cycle_id = program_response["data"]["createProgram"]["program"]["cycles"]["edges"][0]["node"]["id"] + ProgramCycle.objects.filter(id=decode_id_string(program_cycle_id)).update(end_date=parse_date("2033-01-01")) + self.activate_program(program_id) target_population_response = self.create_target_population(program_id, program_cycle_id) diff --git a/backend/hct_mis_api/apps/program/admin.py b/backend/hct_mis_api/apps/program/admin.py index e664a8c92e..661b09daed 100644 --- a/backend/hct_mis_api/apps/program/admin.py +++ b/backend/hct_mis_api/apps/program/admin.py @@ -31,9 +31,13 @@ @admin.register(ProgramCycle) class ProgramCycleAdmin(SoftDeletableAdminMixin, LastSyncDateResetMixin, HOPEModelAdminBase): list_display = ("program", "status", "start_date", "end_date") - date_hierarchy = "program__start_date" - list_filter = (("status", ChoicesFieldComboFilter),) + date_hierarchy = "start_date" + list_filter = ( + ("status", ChoicesFieldComboFilter), + ("program", AutoCompleteFilter), + ) raw_id_fields = ("program", "created_by") + exclude = ("unicef_id",) class ProgramCycleAdminInline(admin.TabularInline): @@ -43,6 +47,7 @@ class ProgramCycleAdminInline(admin.TabularInline): "created_at", "updated_at", ) + exclude = ("unicef_id",) class PartnerAreaForm(forms.Form): diff --git a/backend/hct_mis_api/apps/program/api/filters.py b/backend/hct_mis_api/apps/program/api/filters.py index 967dbe89bf..f0d6950b51 100644 --- a/backend/hct_mis_api/apps/program/api/filters.py +++ b/backend/hct_mis_api/apps/program/api/filters.py @@ -43,7 +43,7 @@ def search_filter(self, qs: QuerySet, name: str, value: Any) -> QuerySet: values = value.split(" ") q_obj = Q() for value in values: - q_obj |= Q(Q(title__istartswith=value) | Q(unicef_id__istartswith=value)) + q_obj |= Q(Q(title__istartswith=value)) return qs.filter(q_obj) def filter_total_delivered_quantity_usd(self, queryset: QuerySet, name: str, value: Any) -> QuerySet: diff --git a/backend/hct_mis_api/apps/program/api/serializers.py b/backend/hct_mis_api/apps/program/api/serializers.py index 57d79f39c7..5f42fd7ec3 100644 --- a/backend/hct_mis_api/apps/program/api/serializers.py +++ b/backend/hct_mis_api/apps/program/api/serializers.py @@ -2,6 +2,7 @@ from django.db.models import Q from django.shortcuts import get_object_or_404 +from django.utils.dateparse import parse_date from rest_framework import serializers @@ -49,7 +50,6 @@ class Meta: model = ProgramCycle fields = ( "id", - "unicef_id", "title", "status", "start_date", @@ -100,26 +100,28 @@ def validate(self, data: Dict[str, Any]) -> Dict[str, Any]: program = self.get_program(self.context["request"].parser_context["kwargs"]["program_id"]) data["program"] = program data["created_by"] = self.context["request"].user - start_date = data.get("start_date") + start_date = data["start_date"] end_date = data.get("end_date") if program.status != Program.ACTIVE: raise serializers.ValidationError("Create Programme Cycle is possible only for Active Programme.") - - if start_date and start_date < program.start_date: - raise serializers.ValidationError( - {"start_date": "Programme Cycle start date cannot be earlier than programme start date"} - ) - if end_date and end_date > program.end_date: + if not (program.start_date <= start_date <= program.end_date): raise serializers.ValidationError( - {"end_date": "Programme Cycle end date cannot be later than programme end date"} + {"start_date": "Programme Cycle start date must be between programme start and end dates."} ) + if end_date: + if not (program.start_date <= end_date <= program.end_date): + raise serializers.ValidationError( + {"end_date": "Programme Cycle end date must be between programme start and end dates."} + ) + if end_date < start_date: + raise serializers.ValidationError({"end_date": "End date cannot be before start date."}) if program.cycles.filter(end_date__isnull=True).exists(): raise serializers.ValidationError("All Programme Cycles should have end date for creation new one.") - # timeframes overlapping - validate_cycle_timeframes_overlapping(program, start_date, end_date) + if program.cycles.filter(end_date__gte=start_date).exists(): + raise serializers.ValidationError({"start_date": "Start date must be after the latest cycle."}) return data @@ -144,16 +146,34 @@ def validate_title(self, value: str) -> str: def validate(self, data: Dict[str, Any]) -> Dict[str, Any]: program = self.instance.program + program_start_date = ( + parse_date(program.start_date) if isinstance(program.start_date, str) else program.start_date + ) + program_end_date = parse_date(program.end_date) if isinstance(program.end_date, str) else program.end_date + start_date = data.get("start_date") + end_date = data.get("end_date") if program.status != Program.ACTIVE: raise serializers.ValidationError("Update Programme Cycle is possible only for Active Programme.") - if self.instance.end_date and "end_date" in data and data.get("end_date") is None: + if self.instance.end_date and "end_date" in data and end_date is None: raise serializers.ValidationError( { "end_date": "Not possible leave the Programme Cycle end date empty if it was not empty upon starting the edit." } ) - validate_cycle_timeframes_overlapping(program, data.get("start_date"), data.get("end_date"), self.instance.pk) + if start_date: + if not (program_start_date <= start_date <= program_end_date): + raise serializers.ValidationError( + {"start_date": "Programme Cycle start date must be between programme start and end dates."} + ) + if end_date and end_date < start_date: + raise serializers.ValidationError({"end_date": "End date cannot be before start date."}) + if end_date: + if not (program_start_date <= end_date <= program_end_date): + raise serializers.ValidationError( + {"end_date": "Programme Cycle end date must be between programme start and end dates."} + ) + validate_cycle_timeframes_overlapping(program, start_date, end_date, str(self.instance.pk)) return data diff --git a/backend/hct_mis_api/apps/program/fixtures.py b/backend/hct_mis_api/apps/program/fixtures.py index 5fd54e3038..73524762c8 100644 --- a/backend/hct_mis_api/apps/program/fixtures.py +++ b/backend/hct_mis_api/apps/program/fixtures.py @@ -10,11 +10,14 @@ from dateutil.relativedelta import relativedelta from factory import fuzzy from factory.django import DjangoModelFactory +from faker import Faker from hct_mis_api.apps.core.fixtures import DataCollectingTypeFactory from hct_mis_api.apps.core.models import BusinessArea, DataCollectingType from hct_mis_api.apps.program.models import Program, ProgramCycle +fake = Faker() + class ProgramCycleFactory(DjangoModelFactory): class Meta: @@ -22,13 +25,15 @@ class Meta: django_get_or_create = ("program", "title") status = ProgramCycle.ACTIVE - start_date = factory.Faker( - "date_time_this_decade", - before_now=True, - after_now=False, - tzinfo=utc, + start_date = factory.LazyAttribute( + lambda o: ( + o.program.cycles.latest("start_date").end_date + timedelta(days=1) + if o.program.cycles.exists() + else fake.date_time_this_decade(before_now=True, after_now=True, tzinfo=utc).date() + ) ) - end_date = factory.LazyAttribute(lambda o: o.start_date + timedelta(days=randint(60, 1000))) + + end_date = factory.LazyAttribute(lambda o: (o.start_date + timedelta(days=randint(60, 1000)))) title = factory.Faker( "sentence", nb_words=3, diff --git a/backend/hct_mis_api/apps/program/fixtures/data-cypress.json b/backend/hct_mis_api/apps/program/fixtures/data-cypress.json index 177b1afecf..9f9d11a7fe 100644 --- a/backend/hct_mis_api/apps/program/fixtures/data-cypress.json +++ b/backend/hct_mis_api/apps/program/fixtures/data-cypress.json @@ -64,7 +64,6 @@ "is_removed": false, "created_at": "2024-08-07T13:41:22.721Z", "updated_at": "2024-08-07T13:41:22.721Z", - "last_sync_at": null, "version": 1723037136036459, "unicef_id": "PC-0060-24-000078", "title": "First Cycle In Programme", diff --git a/backend/hct_mis_api/apps/program/fixtures/data.json b/backend/hct_mis_api/apps/program/fixtures/data.json index 9cc383c19e..fb3caac447 100644 --- a/backend/hct_mis_api/apps/program/fixtures/data.json +++ b/backend/hct_mis_api/apps/program/fixtures/data.json @@ -37,7 +37,6 @@ "is_removed": false, "created_at": "2023-06-19T14:47:53.780Z", "updated_at": "2023-06-19T14:47:53.780Z", - "last_sync_at": null, "version": 1687185127096102, "status": "DRAFT", "start_date": "2023-06-19", diff --git a/backend/hct_mis_api/apps/program/migrations/0051_migration.py b/backend/hct_mis_api/apps/program/migrations/0051_migration.py new file mode 100644 index 0000000000..342acff281 --- /dev/null +++ b/backend/hct_mis_api/apps/program/migrations/0051_migration.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.25 on 2024-08-20 16:20 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('program', '0050_migration'), + ] + + operations = [ + migrations.RemoveField( + model_name='programcycle', + name='last_sync_at', + ), + ] diff --git a/backend/hct_mis_api/apps/program/models.py b/backend/hct_mis_api/apps/program/models.py index ed599fd2d2..23daa01295 100644 --- a/backend/hct_mis_api/apps/program/models.py +++ b/backend/hct_mis_api/apps/program/models.py @@ -16,6 +16,7 @@ from django.db import models from django.db.models import Q, QuerySet, Sum from django.db.models.constraints import UniqueConstraint +from django.utils.dateparse import parse_date from django.utils.translation import gettext_lazy as _ from model_utils.models import SoftDeletableModel @@ -275,9 +276,7 @@ def validate_unique(self, exclude: Optional[Collection[str]] = ...) -> None: # super(Program, self).validate_unique() -class ProgramCycle( - AdminUrlMixin, SoftDeletableModel, TimeStampedUUIDModel, UnicefIdentifiedModel, AbstractSyncable, ConcurrencyModel -): +class ProgramCycle(AdminUrlMixin, SoftDeletableModel, TimeStampedUUIDModel, UnicefIdentifiedModel, ConcurrencyModel): ACTIVITY_LOG_MAPPING = create_mapping_dict( [ "title", @@ -321,9 +320,15 @@ class Meta: verbose_name = "ProgrammeCycle" def clean(self) -> None: - if self.end_date and self.end_date < self.start_date: + start_date = parse_date(self.start_date) if isinstance(self.start_date, str) else self.start_date + end_date = parse_date(self.end_date) if isinstance(self.end_date, str) else self.end_date + + if end_date and end_date < start_date: raise ValidationError("End date cannot be before start date.") + if self._state.adding and self.program.cycles.exclude(pk=self.pk).filter(end_date__gte=start_date).exists(): + raise ValidationError("Start date must be after the latest cycle.") + def save(self, *args: Any, **kwargs: Any) -> None: self.clean() super().save(*args, **kwargs) diff --git a/backend/hct_mis_api/apps/program/schema.py b/backend/hct_mis_api/apps/program/schema.py index 335ae2766e..26de660aed 100644 --- a/backend/hct_mis_api/apps/program/schema.py +++ b/backend/hct_mis_api/apps/program/schema.py @@ -90,6 +90,9 @@ class Meta: filter_fields = [ "status", ] + exclude = [ + "unicef_id", + ] interfaces = (relay.Node,) connection_class = ExtendedConnection 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 7e8d5ba003..03c214bd68 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 @@ -1,5 +1,6 @@ from typing import Any, List +import freezegun from parameterized import parameterized from hct_mis_api.apps.account.fixtures import ( @@ -24,6 +25,7 @@ from hct_mis_api.apps.program.models import Program +@freezegun.freeze_time("2019-01-01") class TestCreateProgram(APITestCase): CREATE_PROGRAM_MUTATION = """ mutation CreateProgram($programData: CreateProgramInput!) { diff --git a/backend/hct_mis_api/apps/program/tests/test_program_cycle.py b/backend/hct_mis_api/apps/program/tests/test_program_cycle.py index a40f4bcd80..b6b60c0b8f 100644 --- a/backend/hct_mis_api/apps/program/tests/test_program_cycle.py +++ b/backend/hct_mis_api/apps/program/tests/test_program_cycle.py @@ -1,6 +1,9 @@ from decimal import Decimal +from django.core.exceptions import ValidationError as DjangoValidationError from django.test import TestCase +from django.utils import timezone +from django.utils.dateparse import parse_date from rest_framework.exceptions import ValidationError @@ -118,3 +121,18 @@ def test_total_delivered_quantity_usd(self) -> None: self.assertEqual(self.cycle.total_delivered_quantity_usd, Decimal("0.0")) PaymentPlanFactory(program=self.program, program_cycle=self.cycle, total_delivered_quantity_usd=Decimal(333.11)) self.assertEqual(self.cycle.total_delivered_quantity_usd, Decimal("333.11")) + + def test_cycle_validation_start_date(self) -> None: + with self.assertRaisesMessage(DjangoValidationError, "Start date must be after the latest cycle."): + ProgramCycleFactory(program=self.program, start_date=parse_date("2021-01-01")) + + with self.assertRaisesMessage(DjangoValidationError, "End date cannot be before start date."): + ProgramCycleFactory( + program=self.program, start_date=parse_date("2021-01-05"), end_date=parse_date("2021-01-01") + ) + + cycle2 = ProgramCycleFactory(program=self.program) + self.assertTrue(cycle2.start_date > parse_date(self.cycle.start_date)) + + cycle_new = ProgramCycleFactory(program=self.program, start_date=parse_date("2099-01-01")) + self.assertTrue(cycle_new.start_date > timezone.now().date()) diff --git a/backend/hct_mis_api/apps/program/tests/test_program_cycle_rest_api.py b/backend/hct_mis_api/apps/program/tests/test_program_cycle_rest_api.py index f1b7d90d1c..f338232fb4 100644 --- a/backend/hct_mis_api/apps/program/tests/test_program_cycle_rest_api.py +++ b/backend/hct_mis_api/apps/program/tests/test_program_cycle_rest_api.py @@ -5,6 +5,7 @@ from django.test import TestCase from django.urls import reverse +from django.utils.dateparse import parse_date from rest_framework import status from rest_framework.exceptions import ValidationError @@ -105,7 +106,7 @@ def test_create_program_cycle(self) -> None: self.client.force_authenticate(user=self.user) data = { "title": "New Created Cycle", - "start_date": "2024-01-10", + "start_date": parse_date("2024-05-26"), } response = self.client.post(self.list_url, data, format="json") self.assertEqual(response.status_code, status.HTTP_201_CREATED) @@ -115,7 +116,11 @@ def test_create_program_cycle(self) -> None: def test_full_update_program_cycle(self) -> None: self.client.force_authenticate(user=self.user) - data = {"title": "Updated Fully Title", "start_date": "2023-02-02", "end_date": "2023-02-22"} + data = { + "title": "Updated Fully Title", + "start_date": parse_date("2023-02-02"), + "end_date": parse_date("2023-02-22"), + } response = self.client.put(self.cycle_1_detail_url, data, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) self.cycle1.refresh_from_db() @@ -125,7 +130,7 @@ def test_full_update_program_cycle(self) -> None: def test_partial_update_program_cycle(self) -> None: self.client.force_authenticate(user=self.user) - data = {"title": "Title Title New", "start_date": "2023-02-11"} + data = {"title": "Title Title New", "start_date": parse_date("2023-02-11")} response = self.client.patch(self.cycle_1_detail_url, data, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) self.cycle1.refresh_from_db() @@ -256,7 +261,7 @@ def get_serializer_context(self) -> Dict[str, Any]: def test_validate_title_unique(self) -> None: ProgramCycleFactory(program=self.program, title="Cycle 1") - data = {"title": "Cycle 1", "start_date": "2033-01-02", "end_date": "2033-01-12"} + data = {"title": "Cycle 1", "start_date": parse_date("2033-01-02"), "end_date": parse_date("2033-01-12")} serializer = ProgramCycleCreateSerializer(data=data, context=self.get_serializer_context()) with self.assertRaises(ValidationError) as error: serializer.is_valid(raise_exception=True) @@ -264,7 +269,7 @@ def test_validate_title_unique(self) -> None: def test_validate_if_no_end_date(self) -> None: ProgramCycleFactory(program=self.program, title="Cycle 1", end_date=None) - data = {"title": "Cycle 123123", "start_date": "2025-01-02", "end_date": "2025-01-12"} + data = {"title": "Cycle 123123", "start_date": parse_date("2025-01-02"), "end_date": parse_date("2025-01-12")} serializer = ProgramCycleCreateSerializer(data=data, context=self.get_serializer_context()) with self.assertRaises(ValidationError) as error: serializer.is_valid(raise_exception=True) @@ -280,40 +285,44 @@ def test_validate_program_status(self) -> None: self.assertIn("Create Programme Cycle is possible only for Active Programme.", str(error.exception)) def test_validate_start_date(self) -> None: - data = {"title": "Cycle 3", "start_date": "2022-01-01", "end_date": "2023-01-01"} + # before program start date + data = {"title": "Cycle 3", "start_date": parse_date("2022-01-01"), "end_date": parse_date("2023-01-01")} serializer = ProgramCycleCreateSerializer(data=data, context=self.get_serializer_context()) with self.assertRaises(ValidationError) as error: serializer.is_valid(raise_exception=True) - self.assertIn("Programme Cycle start date cannot be earlier than programme start date", str(error.exception)) - - def test_validate_end_date(self) -> None: - data = {"title": "Cycle new", "start_date": "2098-01-01", "end_date": "2111-01-01"} + self.assertIn("Programme Cycle start date must be between programme start and end dates.", str(error.exception)) + # after program end date + data = {"title": "Cycle 3", "start_date": parse_date("2100-01-01"), "end_date": parse_date("2100-01-11")} + serializer = ProgramCycleCreateSerializer(data=data, context=self.get_serializer_context()) + with self.assertRaises(ValidationError) as error: + serializer.is_valid(raise_exception=True) + self.assertIn("Programme Cycle start date must be between programme start and end dates.", str(error.exception)) + # before latest cycle + data = {"title": "Cycle 34567", "start_date": parse_date("2023-01-09"), "end_date": parse_date("2023-01-30")} serializer = ProgramCycleCreateSerializer(data=data, context=self.get_serializer_context()) with self.assertRaises(ValidationError) as error: serializer.is_valid(raise_exception=True) - self.assertIn("Programme Cycle end date cannot be later than programme end date", str(error.exception)) + self.assertIn("Start date must be after the latest cycle.", str(error.exception)) - def test_validate_overlapping_cycles(self) -> None: - ProgramCycleFactory(program=self.program, start_date="2023-02-01", end_date="2023-02-20") - data3 = {"title": "Cycle new3", "start_date": "2023-02-01", "end_date": "2023-02-28"} - serializer = ProgramCycleCreateSerializer(data=data3, context=self.get_serializer_context()) + def test_validate_end_date(self) -> None: + # after program end date + data = {"title": "Cycle", "start_date": parse_date("2098-01-01"), "end_date": parse_date("2111-01-01")} + serializer = ProgramCycleCreateSerializer(data=data, context=self.get_serializer_context()) with self.assertRaises(ValidationError) as error: serializer.is_valid(raise_exception=True) - self.assertIn( - "Programme Cycles' timeframes must not overlap with the provided start date.", str(error.exception) - ) - data = {"title": "Cycle new", "start_date": "2023-02-01", "end_date": "2023-02-20"} + self.assertIn("Programme Cycle end date must be between programme start and end dates", str(error.exception)) + # before program start date + data = {"title": "Cycle", "start_date": parse_date("2023-01-01"), "end_date": parse_date("2022-01-01")} serializer = ProgramCycleCreateSerializer(data=data, context=self.get_serializer_context()) with self.assertRaises(ValidationError) as error: serializer.is_valid(raise_exception=True) - self.assertIn( - "Programme Cycles' timeframes must not overlap with the provided start date.", str(error.exception) - ) - data2 = {"title": "Cycle new2", "start_date": "2023-01-15", "end_date": "2023-02-20"} - serializer = ProgramCycleCreateSerializer(data=data2, context=self.get_serializer_context()) + self.assertIn("Programme Cycle end date must be between programme start and end dates", str(error.exception)) + # end before start date + data = {"title": "Cycle", "start_date": parse_date("2023-02-22"), "end_date": parse_date("2023-02-11")} + serializer = ProgramCycleCreateSerializer(data=data, context=self.get_serializer_context()) with self.assertRaises(ValidationError) as error: serializer.is_valid(raise_exception=True) - self.assertIn("Programme Cycles' timeframes must not overlap with the provided end date.", str(error.exception)) + self.assertIn("End date cannot be before start date", str(error.exception)) class ProgramCycleUpdateSerializerTest(TestCase): @@ -353,10 +362,42 @@ def test_validate_program_status(self) -> None: serializer.is_valid(raise_exception=True) self.assertIn("Update Programme Cycle is possible only for Active Programme.", str(error.exception)) + def test_validate_start_date(self) -> None: + cycle_2 = ProgramCycleFactory( + program=self.program, title="Cycle 2222", start_date="2023-12-20", end_date="2023-12-25" + ) + data = {"start_date": parse_date("2023-12-20"), "end_date": parse_date("2023-12-19")} + serializer = ProgramCycleUpdateSerializer(instance=cycle_2, data=data, context=self.get_serializer_context()) + with self.assertRaises(ValidationError) as error: + serializer.is_valid(raise_exception=True) + self.assertIn("End date cannot be before start date", str(error.exception)) + + data = {"start_date": parse_date("2023-12-10"), "end_date": parse_date("2023-12-26")} + serializer = ProgramCycleUpdateSerializer(instance=cycle_2, data=data, context=self.get_serializer_context()) + with self.assertRaises(ValidationError) as error: + serializer.is_valid(raise_exception=True) + self.assertIn( + "Programme Cycles' timeframes must not overlap with the provided start date.", str(error.exception) + ) + # before program start date + serializer = ProgramCycleUpdateSerializer( + instance=cycle_2, data={"start_date": parse_date("1999-12-10")}, context=self.get_serializer_context() + ) + with self.assertRaises(ValidationError) as error: + serializer.is_valid(raise_exception=True) + self.assertIn("Programme Cycle start date must be between programme start and end dates.", str(error.exception)) + # after program end date + serializer = ProgramCycleUpdateSerializer( + instance=cycle_2, data={"start_date": parse_date("2100-01-01")}, context=self.get_serializer_context() + ) + with self.assertRaises(ValidationError) as error: + serializer.is_valid(raise_exception=True) + self.assertIn("Programme Cycle start date must be between programme start and end dates.", str(error.exception)) + def test_validate_end_date(self) -> None: self.cycle.end_date = datetime.strptime("2023-02-03", "%Y-%m-%d").date() self.cycle.save() - data = {"start_date": "2023-02-02", "end_date": None} + data = {"start_date": parse_date("2023-02-02"), "end_date": None} serializer = ProgramCycleUpdateSerializer(instance=self.cycle, data=data, context=self.get_serializer_context()) with self.assertRaises(ValidationError) as error: serializer.is_valid(raise_exception=True) @@ -364,6 +405,26 @@ def test_validate_end_date(self) -> None: "This field may not be null.", str(error.exception), ) + # end date before program start date + serializer = ProgramCycleUpdateSerializer( + instance=self.cycle, data={"end_date": parse_date("1999-10-10")}, context=self.get_serializer_context() + ) + with self.assertRaises(ValidationError) as error: + serializer.is_valid(raise_exception=True) + self.assertIn( + "Programme Cycle end date must be between programme start and end dates.", + str(error.exception), + ) + # end date after program end date + serializer = ProgramCycleUpdateSerializer( + instance=self.cycle, data={"end_date": parse_date("2100-10-10")}, context=self.get_serializer_context() + ) + with self.assertRaises(ValidationError) as error: + serializer.is_valid(raise_exception=True) + self.assertIn( + "Programme Cycle end date must be between programme start and end dates.", + str(error.exception), + ) class ProgramCycleViewSetTestCase(TestCase): @@ -372,21 +433,30 @@ def setUp(self) -> None: self.viewset = ProgramCycleViewSet() def test_delete_non_active_program(self) -> None: - program = ProgramFactory(status=Program.DRAFT, cycle__status=ProgramCycle.DRAFT) + program = ProgramFactory( + status=Program.DRAFT, + cycle__status=ProgramCycle.DRAFT, + ) cycle = program.cycles.first() with self.assertRaises(ValidationError) as context: self.viewset.perform_destroy(cycle) self.assertEqual(context.exception.detail[0], "Only Programme Cycle for Active Programme can be deleted.") # type: ignore def test_delete_non_draft_cycle(self) -> None: - program = ProgramFactory(status=Program.ACTIVE, cycle__status=ProgramCycle.ACTIVE) + program = ProgramFactory( + status=Program.ACTIVE, + cycle__status=ProgramCycle.ACTIVE, + ) cycle = program.cycles.first() with self.assertRaises(ValidationError) as context: self.viewset.perform_destroy(cycle) self.assertEqual(context.exception.detail[0], "Only Draft Programme Cycle can be deleted.") # type: ignore def test_delete_last_cycle(self) -> None: - program = ProgramFactory(status=Program.ACTIVE, cycle__status=ProgramCycle.DRAFT) + program = ProgramFactory( + status=Program.ACTIVE, + cycle__status=ProgramCycle.DRAFT, + ) cycle = program.cycles.first() with self.assertRaises(ValidationError) as context: self.viewset.perform_destroy(cycle) diff --git a/backend/hct_mis_api/apps/registration_datahub/tests/test_rdi_xlsx_create.py b/backend/hct_mis_api/apps/registration_datahub/tests/test_rdi_xlsx_create.py index df6c203d2d..34c22e08e8 100644 --- a/backend/hct_mis_api/apps/registration_datahub/tests/test_rdi_xlsx_create.py +++ b/backend/hct_mis_api/apps/registration_datahub/tests/test_rdi_xlsx_create.py @@ -10,6 +10,7 @@ from django.contrib.gis.geos import Point from django.core.files import File from django.forms import model_to_dict +from django.utils.dateparse import parse_datetime from django_countries.fields import Country from PIL import Image @@ -382,7 +383,7 @@ def test_handle_document_fields(self) -> None: ) @mock.patch( "hct_mis_api.apps.registration_datahub.tasks.rdi_xlsx_create.timezone.now", - lambda: "2020-06-22 12:00:00-0000", + lambda: parse_datetime("2020-06-22 12:00:00-0000"), ) def test_handle_document_photo_fields(self) -> None: task = self.RdiXlsxCreateTask() @@ -400,7 +401,7 @@ def test_handle_document_photo_fields(self) -> None: self.assertIn("individual_14_birth_certificate_i_c", task.documents.keys()) birth_certificate = task.documents["individual_14_birth_certificate_i_c"] self.assertEqual(birth_certificate["individual"], individual) - self.assertEqual(birth_certificate["photo"].name, "12-2020-06-22 12:00:00-0000.jpg") + self.assertEqual(birth_certificate["photo"].name, "12-2020-06-22 12:00:00+00:00.jpg") birth_cert_doc = { "individual_14_birth_certificate_i_c": { @@ -423,7 +424,7 @@ def test_handle_document_photo_fields(self) -> None: self.assertEqual(birth_certificate["name"], "Birth Certificate") self.assertEqual(birth_certificate["type"], "BIRTH_CERTIFICATE") self.assertEqual(birth_certificate["value"], "CD1247246Q12W") - self.assertEqual(birth_certificate["photo"].name, "12-2020-06-22 12:00:00-0000.jpg") + self.assertEqual(birth_certificate["photo"].name, "12-2020-06-22 12:00:00+00:00.jpg") def test_handle_geopoint_field(self) -> None: empty_geopoint = "" diff --git a/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py b/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py index 77d3159f40..b96a85a566 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py @@ -63,6 +63,29 @@ def setUpTestData(cls) -> None: create_household( {"size": 4, "residence_status": "HOST", "program": cls.program}, ) + cls.variables = { + "createTargetPopulationInput": { + "name": "Example name 5", + "businessAreaSlug": "afghanistan", + "programId": cls.id_to_base64(cls.program.id, "ProgramNode"), + "programCycleId": cls.id_to_base64(cls.program_cycle.id, "ProgramCycleNode"), + "excludedIds": "", + "targetingCriteria": { + "rules": [ + { + "filters": [ + { + "comparisonMethod": "EQUALS", + "fieldName": "size", + "arguments": [3], + "isFlexField": False, + } + ] + } + ] + }, + } + } @parameterized.expand( [ @@ -146,34 +169,10 @@ def test_targeting_in_draft_program(self) -> None: self.program.status = Program.DRAFT self.program.save() - variables = { - "createTargetPopulationInput": { - "name": "Example name 5", - "businessAreaSlug": "afghanistan", - "programId": self.id_to_base64(self.program.id, "ProgramNode"), - "programCycleId": self.id_to_base64(self.program_cycle.id, "ProgramCycleNode"), - "excludedIds": "", - "targetingCriteria": { - "rules": [ - { - "filters": [ - { - "comparisonMethod": "EQUALS", - "fieldName": "size", - "arguments": [3], - "isFlexField": False, - } - ] - } - ] - }, - } - } - response_error = self.graphql_request( request_string=TestCreateTargetPopulationMutation.MUTATION_QUERY, context={"user": self.user}, - variables=variables, + variables=self.variables, ) self.assertEqual(TargetPopulation.objects.count(), 0) assert "errors" in response_error @@ -185,37 +184,13 @@ def test_targeting_in_draft_program(self) -> None: def test_targeting_unique_constraints(self) -> None: self.create_user_role_with_permissions(self.user, [Permissions.TARGETING_CREATE], self.program.business_area) - variables = { - "createTargetPopulationInput": { - "name": "Example name 5", - "businessAreaSlug": "afghanistan", - "programId": self.id_to_base64(self.program.id, "ProgramNode"), - "programCycleId": self.id_to_base64(self.program_cycle.id, "ProgramCycleNode"), - "excludedIds": "", - "targetingCriteria": { - "rules": [ - { - "filters": [ - { - "comparisonMethod": "EQUALS", - "fieldName": "size", - "arguments": [3], - "isFlexField": False, - } - ] - } - ] - }, - } - } - self.assertEqual(TargetPopulation.objects.count(), 0) # First, response is ok and tp is created response_ok = self.graphql_request( request_string=TestCreateTargetPopulationMutation.MUTATION_QUERY, context={"user": self.user}, - variables=variables, + variables=self.variables, ) assert "errors" not in response_ok self.assertEqual(TargetPopulation.objects.count(), 1) @@ -224,12 +199,12 @@ def test_targeting_unique_constraints(self) -> None: response_error = self.graphql_request( request_string=TestCreateTargetPopulationMutation.MUTATION_QUERY, context={"user": self.user}, - variables=variables, + variables=self.variables, ) assert "errors" in response_error self.assertEqual(TargetPopulation.objects.count(), 1) self.assertIn( - f"Target population with name: {variables['createTargetPopulationInput']['name']} and program: {self.program.name} already exists.", + f"Target population with name: {self.variables['createTargetPopulationInput']['name']} and program: {self.program.name} already exists.", response_error["errors"][0]["message"], ) @@ -241,7 +216,7 @@ def test_targeting_unique_constraints(self) -> None: response_ok = self.graphql_request( request_string=TestCreateTargetPopulationMutation.MUTATION_QUERY, context={"user": self.user}, - variables=variables, + variables=self.variables, ) assert "errors" not in response_ok self.assertEqual(TargetPopulation.objects.count(), 1) @@ -295,34 +270,10 @@ def test_create_targeting_if_program_cycle_finished(self) -> None: self.program_cycle.status = Program.FINISHED self.program_cycle.save() - variables = { - "createTargetPopulationInput": { - "name": "Example name 5", - "businessAreaSlug": "afghanistan", - "programId": self.id_to_base64(self.program.id, "ProgramNode"), - "programCycleId": self.id_to_base64(self.program_cycle.id, "ProgramCycleNode"), - "excludedIds": "", - "targetingCriteria": { - "rules": [ - { - "filters": [ - { - "comparisonMethod": "EQUALS", - "fieldName": "size", - "arguments": [3], - "isFlexField": False, - } - ] - } - ] - }, - } - } - response_error = self.graphql_request( request_string=TestCreateTargetPopulationMutation.MUTATION_QUERY, context={"user": self.user}, - variables=variables, + variables=self.variables, ) self.assertEqual(TargetPopulation.objects.count(), 0) assert "errors" in response_error diff --git a/backend/hct_mis_api/one_time_scripts/program_cycle_data_migration.py b/backend/hct_mis_api/one_time_scripts/program_cycle_data_migration.py index fdc2fe1b0b..8a94841e31 100644 --- a/backend/hct_mis_api/one_time_scripts/program_cycle_data_migration.py +++ b/backend/hct_mis_api/one_time_scripts/program_cycle_data_migration.py @@ -23,6 +23,9 @@ def adjust_cycles_start_and_end_dates_for_active_program(program: Program) -> No previous_cycle = None with transaction.atomic(): for cycle in cycles_qs: + # skip validations for migration data + cycle.clean = lambda: None + # probably it's not possible but to be sure that no any cycles without end_date if cycle.end_date is None: cycle.end_date = cycle.start_date @@ -51,7 +54,7 @@ def generate_unique_cycle_title(start_date: str) -> str: def create_new_program_cycle(program_id: str, status: str, start_date: date, end_date: date) -> ProgramCycle: - return ProgramCycle.objects.create( + cycle = ProgramCycle( title=generate_unique_cycle_title(str(start_date)), program_id=program_id, status=status, @@ -59,6 +62,10 @@ def create_new_program_cycle(program_id: str, status: str, start_date: date, end end_date=end_date, created_by=None, ) + # skip validations for migration data + cycle.clean = lambda: None # type: ignore + cycle.save() + return ProgramCycle.objects.get(pk=cycle.pk) def processing_with_finished_program(program: Program) -> None: @@ -67,6 +74,9 @@ def processing_with_finished_program(program: Program) -> None: program_id_str = str(program.id) # update if exists or create new cycle if cycle := ProgramCycle.objects.filter(program_id=program.id).first(): + # skip validations for migration data + cycle.clean = lambda: None + if cycle.start_date != start_date: cycle.start_date = start_date if cycle.end_date != end_date: diff --git a/backend/selenium_tests/helpers/fixtures.py b/backend/selenium_tests/helpers/fixtures.py index edea9bc89f..f1694e4f49 100644 --- a/backend/selenium_tests/helpers/fixtures.py +++ b/backend/selenium_tests/helpers/fixtures.py @@ -21,7 +21,7 @@ def get_program_with_dct_type_and_name( data_collecting_type=dct, status=status, cycle__status=ProgramCycle.FINISHED, - cycle__start_date=datetime.now() - relativedelta(days=25), - cycle__end_date=datetime.now() + relativedelta(days=10), + cycle__start_date=(datetime.now() - relativedelta(days=25)).date(), + cycle__end_date=(datetime.now() + relativedelta(days=10)).date(), ) return program diff --git a/backend/selenium_tests/payment_module/test_payment_plans.py b/backend/selenium_tests/payment_module/test_payment_plans.py index 4876ea3fab..8935fc0ad4 100644 --- a/backend/selenium_tests/payment_module/test_payment_plans.py +++ b/backend/selenium_tests/payment_module/test_payment_plans.py @@ -136,16 +136,16 @@ def create_payment_plan(create_targeting: None) -> PaymentPlan: program=tp.program, title="Cycle for PaymentPlan", status=ProgramCycle.ACTIVE, - start_date=datetime.now(), - end_date=datetime.now() + relativedelta(days=14), + start_date=datetime.now() + relativedelta(days=10), + end_date=datetime.now() + relativedelta(days=15), ) payment_plan = PaymentPlan.objects.update_or_create( business_area=BusinessArea.objects.only("is_payment_plan_applicable").get(slug="afghanistan"), target_population=tp, program_cycle=cycle, currency="USD", - dispersion_start_date=datetime.now(), - dispersion_end_date=datetime.now() + relativedelta(days=14), + dispersion_start_date=datetime.now() + relativedelta(days=10), + dispersion_end_date=datetime.now() + relativedelta(days=15), status_date=datetime.now(), status=PaymentPlan.Status.ACCEPTED, created_by=User.objects.first(), @@ -198,7 +198,9 @@ def test_smoke_new_payment_plan( pagePaymentModule.selectGlobalProgramFilter("Test Program") pagePaymentModule.getNavPaymentModule().click() pageProgramCycle.getNavProgrammeCycles().click() - pageProgramCycle.getProgramCycleRow()[0].find_element(By.CSS_SELECTOR, 'td[data-cy="program-cycle-id"]').click() + pageProgramCycle.getProgramCycleRow()[0].find_element( + By.CSS_SELECTOR, 'td[data-cy="program-cycle-title"]' + ).find_element(By.TAG_NAME, "a").click() pageProgramCycleDetails.getButtonCreatePaymentPlan().click() assert "New Payment Plan" in pageNewPaymentPlan.getPageHeaderTitle().text assert "SAVE" in pageNewPaymentPlan.getButtonSavePaymentPlan().text @@ -221,10 +223,11 @@ def test_smoke_details_payment_plan( assert "EXPORT XLSX" in pagePaymentModuleDetails.getButtonExportXlsx().text assert "USD" in pagePaymentModuleDetails.getLabelCurrency().text assert ( - str((datetime.now()).strftime("%-d %b %Y")) in pagePaymentModuleDetails.getLabelDispersionStartDate().text + str((datetime.now() + relativedelta(days=10)).strftime("%-d %b %Y")) + in pagePaymentModuleDetails.getLabelDispersionStartDate().text ) assert ( - str((datetime.now() + relativedelta(days=14)).strftime("%-d %b %Y")) + str((datetime.now() + relativedelta(days=15)).strftime("%-d %b %Y")) in pagePaymentModuleDetails.getLabelDispersionEndDate().text ) assert "-" in pagePaymentModuleDetails.getLabelRelatedFollowUpPaymentPlans().text @@ -271,7 +274,9 @@ def test_payment_plan_happy_path( .find_element(By.CSS_SELECTOR, 'td[data-cy="program-cycle-status"]') .text ) - pageProgramCycle.getProgramCycleRow()[0].find_element(By.CSS_SELECTOR, 'td[data-cy="program-cycle-id"]').click() + pageProgramCycle.getProgramCycleRow()[0].find_element( + By.CSS_SELECTOR, 'td[data-cy="program-cycle-title"]' + ).find_element(By.TAG_NAME, "a").click() pageProgramCycleDetails.getButtonCreatePaymentPlan().click() pageNewPaymentPlan.getInputTargetPopulation().click() pageNewPaymentPlan.select_listbox_element(targeting.name) diff --git a/backend/selenium_tests/payment_module/test_program_cycles.py b/backend/selenium_tests/payment_module/test_program_cycles.py index 0f44dfe1fd..7d9234a1a1 100644 --- a/backend/selenium_tests/payment_module/test_program_cycles.py +++ b/backend/selenium_tests/payment_module/test_program_cycles.py @@ -26,8 +26,8 @@ def create_test_program() -> Program: data_collecting_type=dct, status=Program.ACTIVE, cycle__title="Default Programme Cycle", - cycle__start_date=datetime.now() - relativedelta(days=25), - cycle__end_date=datetime.now() - relativedelta(days=20), + cycle__start_date=(datetime.now() - relativedelta(days=25)).date(), + cycle__end_date=(datetime.now() - relativedelta(days=20)).date(), ) @@ -66,7 +66,7 @@ def test_smoke_program_cycles(self, create_program_cycle: ProgramCycle, pageProg assert "CLEAR" in pageProgramCycle.getButtonFiltersClear().text assert "APPLY" in pageProgramCycle.getButtonFiltersApply().text assert "Programme Cycles" in pageProgramCycle.getTableTitle().text - assert "Programme Cycle ID" in pageProgramCycle.getHeadCellId().text + # assert "Programme Cycle ID" in pageProgramCycle.getHeadCellId().text assert "Programme Cycle Title" in pageProgramCycle.getHeadCellProgrammeCyclesTitle().text assert "Status" in pageProgramCycle.getHeadCellStatus().text assert "Total Entitled Quantity" in pageProgramCycle.getHeadCellTotalEntitledQuantity().text diff --git a/frontend/data/schema.graphql b/frontend/data/schema.graphql index f7b5305eef..e2d83d00e8 100644 --- a/frontend/data/schema.graphql +++ b/frontend/data/schema.graphql @@ -770,17 +770,6 @@ type CreateProgram { program: ProgramNode } -type CreateProgramCycle { - validationErrors: Arg - program: ProgramNode -} - -input CreateProgramCycleInput { - title: String! - startDate: Date! - endDate: Date -} - input CreateProgramInput { name: String startDate: Date @@ -932,10 +921,6 @@ type DeleteProgram { ok: Boolean } -type DeleteProgramCycle { - program: ProgramNode -} - type DeleteRegistrationDataImport { ok: Boolean } @@ -1025,7 +1010,7 @@ type DeliveryMechanismPerPaymentPlanNode implements Node { sentBy: UserNode status: String! deliveryMechanismChoice: DeliveryMechanismPerPaymentPlanDeliveryMechanismChoice - deliveryMechanism: DeliveryMechanismNode! + deliveryMechanism: DeliveryMechanismNode deliveryMechanismOrder: Int! sentToPaymentGateway: Boolean! chosenConfiguration: String @@ -1357,6 +1342,7 @@ type FinancialServiceProviderXlsxTemplateNode implements Node { name: String! columns: [String] coreFields: [String!]! + flexFields: [String!]! financialServiceProviders(offset: Int, before: String, after: String, first: Int, last: Int): FinancialServiceProviderNodeConnection! } @@ -2862,9 +2848,6 @@ type Mutations { updateProgram(programData: UpdateProgramInput, version: BigInt): UpdateProgram deleteProgram(programId: String!): DeleteProgram copyProgram(programData: CopyProgramInput!): CopyProgram - createProgramCycle(programCycleData: CreateProgramCycleInput!): CreateProgramCycle - updateProgramCycle(programCycleData: UpdateProgramCycleInput, version: BigInt): UpdateProgramCycle - deleteProgramCycle(programCycleId: ID!): DeleteProgramCycle uploadImportDataXlsxFileAsync(businessAreaSlug: String!, file: Upload!): UploadImportDataXLSXFileAsync deleteRegistrationDataImport(registrationDataImportId: String!): DeleteRegistrationDataImport registrationXlsxImport(registrationDataImportData: RegistrationXlsxImportMutationInput!): RegistrationXlsxImportMutation @@ -3687,9 +3670,7 @@ type ProgramCycleNode implements Node { id: ID! createdAt: DateTime! updatedAt: DateTime! - lastSyncAt: DateTime version: BigInt! - unicefId: String title: String status: ProgramCycleStatus! startDate: Date! @@ -5269,18 +5250,6 @@ type UpdateProgram { program: ProgramNode } -type UpdateProgramCycle { - validationErrors: Arg - program: ProgramNode -} - -input UpdateProgramCycleInput { - programCycleId: ID! - title: String - startDate: Date - endDate: Date -} - input UpdateProgramInput { id: String! name: String diff --git a/frontend/src/__generated__/graphql.tsx b/frontend/src/__generated__/graphql.tsx index 1c445875ab..7c62a1fc93 100644 --- a/frontend/src/__generated__/graphql.tsx +++ b/frontend/src/__generated__/graphql.tsx @@ -1230,18 +1230,6 @@ export type CreateProgram = { validationErrors?: Maybe; }; -export type CreateProgramCycle = { - __typename?: 'CreateProgramCycle'; - program?: Maybe; - validationErrors?: Maybe; -}; - -export type CreateProgramCycleInput = { - endDate?: InputMaybe; - startDate: Scalars['Date']['input']; - title: Scalars['String']['input']; -}; - export type CreateProgramInput = { administrativeAreasOfImplementation?: InputMaybe; budget?: InputMaybe; @@ -1420,11 +1408,6 @@ export type DeleteProgram = { ok?: Maybe; }; -export type DeleteProgramCycle = { - __typename?: 'DeleteProgramCycle'; - program?: Maybe; -}; - export type DeleteRegistrationDataImport = { __typename?: 'DeleteRegistrationDataImport'; ok?: Maybe; @@ -1551,7 +1534,7 @@ export type DeliveryMechanismPerPaymentPlanNode = Node & { code?: Maybe; createdAt: Scalars['DateTime']['output']; createdBy: UserNode; - deliveryMechanism: DeliveryMechanismNode; + deliveryMechanism?: Maybe; deliveryMechanismChoice?: Maybe; deliveryMechanismOrder: Scalars['Int']['output']; financialServiceProvider?: Maybe; @@ -1999,6 +1982,7 @@ export type FinancialServiceProviderXlsxTemplateNode = Node & { createdAt: Scalars['DateTime']['output']; createdBy?: Maybe; financialServiceProviders: FinancialServiceProviderNodeConnection; + flexFields: Array; id: Scalars['ID']['output']; name: Scalars['String']['output']; updatedAt: Scalars['DateTime']['output']; @@ -4019,7 +4003,6 @@ export type Mutations = { createPaymentPlan?: Maybe; createPaymentVerificationPlan?: Maybe; createProgram?: Maybe; - createProgramCycle?: Maybe; createReport?: Maybe; createSurvey?: Maybe; createTargetPopulation?: Maybe; @@ -4027,7 +4010,6 @@ export type Mutations = { deletePaymentPlan?: Maybe; deletePaymentVerificationPlan?: Maybe; deleteProgram?: Maybe; - deleteProgramCycle?: Maybe; deleteRegistrationDataImport?: Maybe; deleteTargetPopulation?: Maybe; discardPaymentVerificationPlan?: Maybe; @@ -4071,7 +4053,6 @@ export type Mutations = { updatePaymentVerificationReceivedAndReceivedAmount?: Maybe; updatePaymentVerificationStatusAndReceivedAmount?: Maybe; updateProgram?: Maybe; - updateProgramCycle?: Maybe; updateTargetPopulation?: Maybe; uploadImportDataXlsxFileAsync?: Maybe; }; @@ -4263,11 +4244,6 @@ export type MutationsCreateProgramArgs = { }; -export type MutationsCreateProgramCycleArgs = { - programCycleData: CreateProgramCycleInput; -}; - - export type MutationsCreateReportArgs = { reportData: CreateReportInput; }; @@ -4305,11 +4281,6 @@ export type MutationsDeleteProgramArgs = { }; -export type MutationsDeleteProgramCycleArgs = { - programCycleId: Scalars['ID']['input']; -}; - - export type MutationsDeleteRegistrationDataImportArgs = { registrationDataImportId: Scalars['String']['input']; }; @@ -4569,12 +4540,6 @@ export type MutationsUpdateProgramArgs = { }; -export type MutationsUpdateProgramCycleArgs = { - programCycleData?: InputMaybe; - version?: InputMaybe; -}; - - export type MutationsUpdateTargetPopulationArgs = { input: UpdateTargetPopulationInput; version?: InputMaybe; @@ -5631,7 +5596,6 @@ export type ProgramCycleNode = Node & { endDate?: Maybe; id: Scalars['ID']['output']; isRemoved: Scalars['Boolean']['output']; - lastSyncAt?: Maybe; paymentPlans: PaymentPlanNodeConnection; program: ProgramNode; startDate: Scalars['Date']['output']; @@ -5641,7 +5605,6 @@ export type ProgramCycleNode = Node & { totalDeliveredQuantityUsd?: Maybe; totalEntitledQuantityUsd?: Maybe; totalUndeliveredQuantityUsd?: Maybe; - unicefId?: Maybe; updatedAt: Scalars['DateTime']['output']; version: Scalars['BigInt']['output']; }; @@ -8917,19 +8880,6 @@ export type UpdateProgram = { validationErrors?: Maybe; }; -export type UpdateProgramCycle = { - __typename?: 'UpdateProgramCycle'; - program?: Maybe; - validationErrors?: Maybe; -}; - -export type UpdateProgramCycleInput = { - endDate?: InputMaybe; - programCycleId: Scalars['ID']['input']; - startDate?: InputMaybe; - title?: InputMaybe; -}; - export type UpdateProgramInput = { administrativeAreasOfImplementation?: InputMaybe; budget?: InputMaybe; @@ -24444,8 +24394,6 @@ export type ResolversTypes = { CreatePaymentPlanMutation: ResolverTypeWrapper; CreatePaymentVerificationInput: CreatePaymentVerificationInput; CreateProgram: ResolverTypeWrapper; - CreateProgramCycle: ResolverTypeWrapper; - CreateProgramCycleInput: CreateProgramCycleInput; CreateProgramInput: CreateProgramInput; CreateReport: ResolverTypeWrapper; CreateReportInput: CreateReportInput; @@ -24469,7 +24417,6 @@ export type ResolversTypes = { DeletePaymentPlanMutation: ResolverTypeWrapper; DeletePaymentVerificationPlan: ResolverTypeWrapper; DeleteProgram: ResolverTypeWrapper; - DeleteProgramCycle: ResolverTypeWrapper; DeleteRegistrationDataImport: ResolverTypeWrapper; DeleteTargetPopulationMutationInput: DeleteTargetPopulationMutationInput; DeleteTargetPopulationMutationPayload: ResolverTypeWrapper; @@ -24866,8 +24813,6 @@ export type ResolversTypes = { UpdatePaymentVerificationReceivedAndReceivedAmount: ResolverTypeWrapper; UpdatePaymentVerificationStatusAndReceivedAmount: ResolverTypeWrapper; UpdateProgram: ResolverTypeWrapper; - UpdateProgramCycle: ResolverTypeWrapper; - UpdateProgramCycleInput: UpdateProgramCycleInput; UpdateProgramInput: UpdateProgramInput; UpdateTargetPopulationInput: UpdateTargetPopulationInput; UpdateTargetPopulationMutation: ResolverTypeWrapper; @@ -24977,8 +24922,6 @@ export type ResolversParentTypes = { CreatePaymentPlanMutation: CreatePaymentPlanMutation; CreatePaymentVerificationInput: CreatePaymentVerificationInput; CreateProgram: CreateProgram; - CreateProgramCycle: CreateProgramCycle; - CreateProgramCycleInput: CreateProgramCycleInput; CreateProgramInput: CreateProgramInput; CreateReport: CreateReport; CreateReportInput: CreateReportInput; @@ -25001,7 +24944,6 @@ export type ResolversParentTypes = { DeletePaymentPlanMutation: DeletePaymentPlanMutation; DeletePaymentVerificationPlan: DeletePaymentVerificationPlan; DeleteProgram: DeleteProgram; - DeleteProgramCycle: DeleteProgramCycle; DeleteRegistrationDataImport: DeleteRegistrationDataImport; DeleteTargetPopulationMutationInput: DeleteTargetPopulationMutationInput; DeleteTargetPopulationMutationPayload: DeleteTargetPopulationMutationPayload; @@ -25332,8 +25274,6 @@ export type ResolversParentTypes = { UpdatePaymentVerificationReceivedAndReceivedAmount: UpdatePaymentVerificationReceivedAndReceivedAmount; UpdatePaymentVerificationStatusAndReceivedAmount: UpdatePaymentVerificationStatusAndReceivedAmount; UpdateProgram: UpdateProgram; - UpdateProgramCycle: UpdateProgramCycle; - UpdateProgramCycleInput: UpdateProgramCycleInput; UpdateProgramInput: UpdateProgramInput; UpdateTargetPopulationInput: UpdateTargetPopulationInput; UpdateTargetPopulationMutation: UpdateTargetPopulationMutation; @@ -25935,12 +25875,6 @@ export type CreateProgramResolvers; }; -export type CreateProgramCycleResolvers = { - program?: Resolver, ParentType, ContextType>; - validationErrors?: Resolver, ParentType, ContextType>; - __isTypeOf?: IsTypeOfResolverFn; -}; - export type CreateReportResolvers = { report?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -26051,11 +25985,6 @@ export type DeleteProgramResolvers; }; -export type DeleteProgramCycleResolvers = { - program?: Resolver, ParentType, ContextType>; - __isTypeOf?: IsTypeOfResolverFn; -}; - export type DeleteRegistrationDataImportResolvers = { ok?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -26111,7 +26040,7 @@ export type DeliveryMechanismPerPaymentPlanNodeResolvers, ParentType, ContextType>; createdAt?: Resolver; createdBy?: Resolver; - deliveryMechanism?: Resolver; + deliveryMechanism?: Resolver, ParentType, ContextType>; deliveryMechanismChoice?: Resolver, ParentType, ContextType>; deliveryMechanismOrder?: Resolver; financialServiceProvider?: Resolver, ParentType, ContextType>; @@ -26399,6 +26328,7 @@ export type FinancialServiceProviderXlsxTemplateNodeResolvers; createdBy?: Resolver, ParentType, ContextType>; financialServiceProviders?: Resolver>; + flexFields?: Resolver, ParentType, ContextType>; id?: Resolver; name?: Resolver; updatedAt?: Resolver; @@ -27443,7 +27373,6 @@ export type MutationsResolvers, ParentType, ContextType, RequireFields>; createPaymentVerificationPlan?: Resolver, ParentType, ContextType, RequireFields>; createProgram?: Resolver, ParentType, ContextType, RequireFields>; - createProgramCycle?: Resolver, ParentType, ContextType, RequireFields>; createReport?: Resolver, ParentType, ContextType, RequireFields>; createSurvey?: Resolver, ParentType, ContextType, RequireFields>; createTargetPopulation?: Resolver, ParentType, ContextType, RequireFields>; @@ -27451,7 +27380,6 @@ export type MutationsResolvers, ParentType, ContextType, RequireFields>; deletePaymentVerificationPlan?: Resolver, ParentType, ContextType, RequireFields>; deleteProgram?: Resolver, ParentType, ContextType, RequireFields>; - deleteProgramCycle?: Resolver, ParentType, ContextType, RequireFields>; deleteRegistrationDataImport?: Resolver, ParentType, ContextType, RequireFields>; deleteTargetPopulation?: Resolver, ParentType, ContextType, RequireFields>; discardPaymentVerificationPlan?: Resolver, ParentType, ContextType, RequireFields>; @@ -27495,7 +27423,6 @@ export type MutationsResolvers, ParentType, ContextType, RequireFields>; updatePaymentVerificationStatusAndReceivedAmount?: Resolver, ParentType, ContextType, RequireFields>; updateProgram?: Resolver, ParentType, ContextType, Partial>; - updateProgramCycle?: Resolver, ParentType, ContextType, Partial>; updateTargetPopulation?: Resolver, ParentType, ContextType, RequireFields>; uploadImportDataXlsxFileAsync?: Resolver, ParentType, ContextType, RequireFields>; }; @@ -28023,7 +27950,6 @@ export type ProgramCycleNodeResolvers, ParentType, ContextType>; id?: Resolver; isRemoved?: Resolver; - lastSyncAt?: Resolver, ParentType, ContextType>; paymentPlans?: Resolver>; program?: Resolver; startDate?: Resolver; @@ -28033,7 +27959,6 @@ export type ProgramCycleNodeResolvers, ParentType, ContextType>; totalEntitledQuantityUsd?: Resolver, ParentType, ContextType>; totalUndeliveredQuantityUsd?: Resolver, ParentType, ContextType>; - unicefId?: Resolver, ParentType, ContextType>; updatedAt?: Resolver; version?: Resolver; __isTypeOf?: IsTypeOfResolverFn; @@ -29410,12 +29335,6 @@ export type UpdateProgramResolvers; }; -export type UpdateProgramCycleResolvers = { - program?: Resolver, ParentType, ContextType>; - validationErrors?: Resolver, ParentType, ContextType>; - __isTypeOf?: IsTypeOfResolverFn; -}; - export type UpdateTargetPopulationMutationResolvers = { targetPopulation?: Resolver, ParentType, ContextType>; validationErrors?: Resolver, ParentType, ContextType>; @@ -29687,7 +29606,6 @@ export type Resolvers = { CreateGrievanceTicketMutation?: CreateGrievanceTicketMutationResolvers; CreatePaymentPlanMutation?: CreatePaymentPlanMutationResolvers; CreateProgram?: CreateProgramResolvers; - CreateProgramCycle?: CreateProgramCycleResolvers; CreateReport?: CreateReportResolvers; CreateSurveyMutation?: CreateSurveyMutationResolvers; CreateTargetPopulationMutation?: CreateTargetPopulationMutationResolvers; @@ -29705,7 +29623,6 @@ export type Resolvers = { DeletePaymentPlanMutation?: DeletePaymentPlanMutationResolvers; DeletePaymentVerificationPlan?: DeletePaymentVerificationPlanResolvers; DeleteProgram?: DeleteProgramResolvers; - DeleteProgramCycle?: DeleteProgramCycleResolvers; DeleteRegistrationDataImport?: DeleteRegistrationDataImportResolvers; DeleteTargetPopulationMutationPayload?: DeleteTargetPopulationMutationPayloadResolvers; DeliveredQuantityNode?: DeliveredQuantityNodeResolvers; @@ -29982,7 +29899,6 @@ export type Resolvers = { UpdatePaymentVerificationReceivedAndReceivedAmount?: UpdatePaymentVerificationReceivedAndReceivedAmountResolvers; UpdatePaymentVerificationStatusAndReceivedAmount?: UpdatePaymentVerificationStatusAndReceivedAmountResolvers; UpdateProgram?: UpdateProgramResolvers; - UpdateProgramCycle?: UpdateProgramCycleResolvers; UpdateTargetPopulationMutation?: UpdateTargetPopulationMutationResolvers; Upload?: GraphQLScalarType; UploadImportDataXLSXFileAsync?: UploadImportDataXlsxFileAsyncResolvers; diff --git a/frontend/src/components/paymentmodule/CreatePaymentPlan/CreatePaymentPlanHeader/CreatePaymentPlanHeader.tsx b/frontend/src/components/paymentmodule/CreatePaymentPlan/CreatePaymentPlanHeader/CreatePaymentPlanHeader.tsx index dd3c5d58de..4795bedefe 100644 --- a/frontend/src/components/paymentmodule/CreatePaymentPlan/CreatePaymentPlanHeader/CreatePaymentPlanHeader.tsx +++ b/frontend/src/components/paymentmodule/CreatePaymentPlan/CreatePaymentPlanHeader/CreatePaymentPlanHeader.tsx @@ -56,7 +56,7 @@ export function CreatePaymentPlanHeader({ to: '../../..', }, { - title: `${programCycleData.title} (ID: ${programCycleData.unicef_id})`, + title: `${programCycleData.title}`, to: '../..', }, ]; diff --git a/frontend/src/containers/pages/paymentmodule/ProgramCycle/PaymentPlanDetails/PaymentPlanDetailsHeader.tsx b/frontend/src/containers/pages/paymentmodule/ProgramCycle/PaymentPlanDetails/PaymentPlanDetailsHeader.tsx index 834c4a4bf2..39b74f966a 100644 --- a/frontend/src/containers/pages/paymentmodule/ProgramCycle/PaymentPlanDetails/PaymentPlanDetailsHeader.tsx +++ b/frontend/src/containers/pages/paymentmodule/ProgramCycle/PaymentPlanDetails/PaymentPlanDetailsHeader.tsx @@ -72,7 +72,7 @@ export const PaymentPlanDetailsHeader = ({ to: '../../..', }); breadCrumbsItems.push({ - title: `${programCycleData.title} (ID: ${programCycleData.unicef_id})`, + title: `${programCycleData.title}`, to: '../..', }); } else { diff --git a/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/ProgramCycleDetailsHeader.tsx b/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/ProgramCycleDetailsHeader.tsx index 1d887aefe2..05d5c5a7a4 100644 --- a/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/ProgramCycleDetailsHeader.tsx +++ b/frontend/src/containers/pages/paymentmodule/ProgramCycle/ProgramCycleDetails/ProgramCycleDetailsHeader.tsx @@ -154,7 +154,7 @@ export const ProgramCycleDetailsHeader = ({ - {programCycle.title} (ID: {programCycle.unicef_id}) + {programCycle.title} diff --git a/frontend/src/containers/tables/ProgramCycle/HeadCells.ts b/frontend/src/containers/tables/ProgramCycle/HeadCells.ts index af9673edfa..71f743ea97 100644 --- a/frontend/src/containers/tables/ProgramCycle/HeadCells.ts +++ b/frontend/src/containers/tables/ProgramCycle/HeadCells.ts @@ -2,13 +2,6 @@ import { HeadCell } from '@core/Table/EnhancedTableHead'; import { ProgramCycle } from '@api/programCycleApi'; const headCells: HeadCell[] = [ - { - id: 'unicef_id', - numeric: false, - disablePadding: false, - label: 'Programme Cycle ID', - dataCy: 'head-cell-id', - }, { id: 'title', numeric: false, diff --git a/frontend/src/containers/tables/ProgramCycle/ProgramCycleTable.tsx b/frontend/src/containers/tables/ProgramCycle/ProgramCycleTable.tsx index cb450efbe6..710b4b16e1 100644 --- a/frontend/src/containers/tables/ProgramCycle/ProgramCycleTable.tsx +++ b/frontend/src/containers/tables/ProgramCycle/ProgramCycleTable.tsx @@ -53,14 +53,13 @@ export const ProgramCycleTable = ({ program }: ProgramCycleTableProps) => { hasPermissions(PERMISSIONS.PM_PROGRAMME_CYCLE_DELETE, permissions); return ( - + {canViewDetails ? ( - {row.unicef_id} + {row.title} ) : ( - row.unicef_id + row.title )} - {row.title} [] = [ - { - id: 'unicef_id', - numeric: false, - disablePadding: false, - label: 'Programme Cycle ID', - disableSort: true, - dataCy: 'head-cell-id', - }, { id: 'title', numeric: false, diff --git a/frontend/src/containers/tables/ProgramCyclesTable/ProgramCyclesTable.tsx b/frontend/src/containers/tables/ProgramCyclesTable/ProgramCyclesTable.tsx index d02e9755da..a075d21a57 100644 --- a/frontend/src/containers/tables/ProgramCyclesTable/ProgramCyclesTable.tsx +++ b/frontend/src/containers/tables/ProgramCyclesTable/ProgramCyclesTable.tsx @@ -102,10 +102,9 @@ export const ProgramCyclesTable = ({ const renderRow = (row: ProgramCycle): ReactElement => ( - - {row.unicef_id} + + {row.title} - {row.title} Date: Fri, 23 Aug 2024 12:23:27 +0200 Subject: [PATCH 087/118] Optimise programs list --- frontend/data/schema.graphql | 33 +-- .../programs/fakeApolloAllPrograms.ts | 8 +- frontend/src/__generated__/graphql.tsx | 196 ++++++++++-------- .../queries/program/AllProgramsForTable.ts | 59 ++++++ .../ProgrammesTable/ProgrammesHeadCells.tsx | 4 +- .../ProgrammesTable/ProgrammesTable.test.tsx | 8 +- .../ProgrammesTable/ProgrammesTable.tsx | 14 +- .../ProgrammesTable/ProgrammesTableRow.tsx | 7 +- 8 files changed, 192 insertions(+), 137 deletions(-) create mode 100644 frontend/src/apollo/queries/program/AllProgramsForTable.ts diff --git a/frontend/data/schema.graphql b/frontend/data/schema.graphql index f7b5305eef..67968eb548 100644 --- a/frontend/data/schema.graphql +++ b/frontend/data/schema.graphql @@ -770,17 +770,6 @@ type CreateProgram { program: ProgramNode } -type CreateProgramCycle { - validationErrors: Arg - program: ProgramNode -} - -input CreateProgramCycleInput { - title: String! - startDate: Date! - endDate: Date -} - input CreateProgramInput { name: String startDate: Date @@ -932,10 +921,6 @@ type DeleteProgram { ok: Boolean } -type DeleteProgramCycle { - program: ProgramNode -} - type DeleteRegistrationDataImport { ok: Boolean } @@ -1025,7 +1010,7 @@ type DeliveryMechanismPerPaymentPlanNode implements Node { sentBy: UserNode status: String! deliveryMechanismChoice: DeliveryMechanismPerPaymentPlanDeliveryMechanismChoice - deliveryMechanism: DeliveryMechanismNode! + deliveryMechanism: DeliveryMechanismNode deliveryMechanismOrder: Int! sentToPaymentGateway: Boolean! chosenConfiguration: String @@ -1357,6 +1342,7 @@ type FinancialServiceProviderXlsxTemplateNode implements Node { name: String! columns: [String] coreFields: [String!]! + flexFields: [String!]! financialServiceProviders(offset: Int, before: String, after: String, first: Int, last: Int): FinancialServiceProviderNodeConnection! } @@ -2862,9 +2848,6 @@ type Mutations { updateProgram(programData: UpdateProgramInput, version: BigInt): UpdateProgram deleteProgram(programId: String!): DeleteProgram copyProgram(programData: CopyProgramInput!): CopyProgram - createProgramCycle(programCycleData: CreateProgramCycleInput!): CreateProgramCycle - updateProgramCycle(programCycleData: UpdateProgramCycleInput, version: BigInt): UpdateProgramCycle - deleteProgramCycle(programCycleId: ID!): DeleteProgramCycle uploadImportDataXlsxFileAsync(businessAreaSlug: String!, file: Upload!): UploadImportDataXLSXFileAsync deleteRegistrationDataImport(registrationDataImportId: String!): DeleteRegistrationDataImport registrationXlsxImport(registrationDataImportData: RegistrationXlsxImportMutationInput!): RegistrationXlsxImportMutation @@ -5269,18 +5252,6 @@ type UpdateProgram { program: ProgramNode } -type UpdateProgramCycle { - validationErrors: Arg - program: ProgramNode -} - -input UpdateProgramCycleInput { - programCycleId: ID! - title: String - startDate: Date - endDate: Date -} - input UpdateProgramInput { id: String! name: String diff --git a/frontend/fixtures/programs/fakeApolloAllPrograms.ts b/frontend/fixtures/programs/fakeApolloAllPrograms.ts index ce57098174..b968860318 100644 --- a/frontend/fixtures/programs/fakeApolloAllPrograms.ts +++ b/frontend/fixtures/programs/fakeApolloAllPrograms.ts @@ -1,9 +1,9 @@ -import { AllProgramsDocument } from '../../src/__generated__/graphql'; +import { AllProgramsForTableDocument } from '../../src/__generated__/graphql'; export const fakeApolloAllPrograms = [ { request: { - query: AllProgramsDocument, + query: AllProgramsForTableDocument, variables: { businessArea: 'afghanistan', search: '', @@ -34,8 +34,7 @@ export const fakeApolloAllPrograms = [ { cursor: 'YXJyYXljb25uZWN0aW9uOjA=', node: { - id: - 'UHJvZ3JhbU5vZGU6ZDM4YWI4MTQtOTQyNy00ZmJkLTg4ODctOGUyYzlkMzcxYjg2', + id: 'UHJvZ3JhbU5vZGU6ZDM4YWI4MTQtOTQyNy00ZmJkLTg4ODctOGUyYzlkMzcxYjg2', name: 'Notice hair fall college enough perhaps.', startDate: '2020-01-20', endDate: '2020-08-19', @@ -48,7 +47,6 @@ export const fakeApolloAllPrograms = [ populationGoal: 507376, sector: 'EDUCATION', totalNumberOfHouseholds: 12, - totalNumberOfHouseholdsWithTpInProgram: 12, __typename: 'ProgramNode', }, __typename: 'ProgramNodeEdge', diff --git a/frontend/src/__generated__/graphql.tsx b/frontend/src/__generated__/graphql.tsx index 1c445875ab..0ef8f04d7b 100644 --- a/frontend/src/__generated__/graphql.tsx +++ b/frontend/src/__generated__/graphql.tsx @@ -1230,18 +1230,6 @@ export type CreateProgram = { validationErrors?: Maybe; }; -export type CreateProgramCycle = { - __typename?: 'CreateProgramCycle'; - program?: Maybe; - validationErrors?: Maybe; -}; - -export type CreateProgramCycleInput = { - endDate?: InputMaybe; - startDate: Scalars['Date']['input']; - title: Scalars['String']['input']; -}; - export type CreateProgramInput = { administrativeAreasOfImplementation?: InputMaybe; budget?: InputMaybe; @@ -1420,11 +1408,6 @@ export type DeleteProgram = { ok?: Maybe; }; -export type DeleteProgramCycle = { - __typename?: 'DeleteProgramCycle'; - program?: Maybe; -}; - export type DeleteRegistrationDataImport = { __typename?: 'DeleteRegistrationDataImport'; ok?: Maybe; @@ -1551,7 +1534,7 @@ export type DeliveryMechanismPerPaymentPlanNode = Node & { code?: Maybe; createdAt: Scalars['DateTime']['output']; createdBy: UserNode; - deliveryMechanism: DeliveryMechanismNode; + deliveryMechanism?: Maybe; deliveryMechanismChoice?: Maybe; deliveryMechanismOrder: Scalars['Int']['output']; financialServiceProvider?: Maybe; @@ -1999,6 +1982,7 @@ export type FinancialServiceProviderXlsxTemplateNode = Node & { createdAt: Scalars['DateTime']['output']; createdBy?: Maybe; financialServiceProviders: FinancialServiceProviderNodeConnection; + flexFields: Array; id: Scalars['ID']['output']; name: Scalars['String']['output']; updatedAt: Scalars['DateTime']['output']; @@ -4019,7 +4003,6 @@ export type Mutations = { createPaymentPlan?: Maybe; createPaymentVerificationPlan?: Maybe; createProgram?: Maybe; - createProgramCycle?: Maybe; createReport?: Maybe; createSurvey?: Maybe; createTargetPopulation?: Maybe; @@ -4027,7 +4010,6 @@ export type Mutations = { deletePaymentPlan?: Maybe; deletePaymentVerificationPlan?: Maybe; deleteProgram?: Maybe; - deleteProgramCycle?: Maybe; deleteRegistrationDataImport?: Maybe; deleteTargetPopulation?: Maybe; discardPaymentVerificationPlan?: Maybe; @@ -4071,7 +4053,6 @@ export type Mutations = { updatePaymentVerificationReceivedAndReceivedAmount?: Maybe; updatePaymentVerificationStatusAndReceivedAmount?: Maybe; updateProgram?: Maybe; - updateProgramCycle?: Maybe; updateTargetPopulation?: Maybe; uploadImportDataXlsxFileAsync?: Maybe; }; @@ -4263,11 +4244,6 @@ export type MutationsCreateProgramArgs = { }; -export type MutationsCreateProgramCycleArgs = { - programCycleData: CreateProgramCycleInput; -}; - - export type MutationsCreateReportArgs = { reportData: CreateReportInput; }; @@ -4305,11 +4281,6 @@ export type MutationsDeleteProgramArgs = { }; -export type MutationsDeleteProgramCycleArgs = { - programCycleId: Scalars['ID']['input']; -}; - - export type MutationsDeleteRegistrationDataImportArgs = { registrationDataImportId: Scalars['String']['input']; }; @@ -4569,12 +4540,6 @@ export type MutationsUpdateProgramArgs = { }; -export type MutationsUpdateProgramCycleArgs = { - programCycleData?: InputMaybe; - version?: InputMaybe; -}; - - export type MutationsUpdateTargetPopulationArgs = { input: UpdateTargetPopulationInput; version?: InputMaybe; @@ -8917,19 +8882,6 @@ export type UpdateProgram = { validationErrors?: Maybe; }; -export type UpdateProgramCycle = { - __typename?: 'UpdateProgramCycle'; - program?: Maybe; - validationErrors?: Maybe; -}; - -export type UpdateProgramCycleInput = { - endDate?: InputMaybe; - programCycleId: Scalars['ID']['input']; - startDate?: InputMaybe; - title?: InputMaybe; -}; - export type UpdateProgramInput = { administrativeAreasOfImplementation?: InputMaybe; budget?: InputMaybe; @@ -11112,6 +11064,26 @@ export type AllProgramsForChoicesQueryVariables = Exact<{ export type AllProgramsForChoicesQuery = { __typename?: 'Query', allPrograms?: { __typename?: 'ProgramNodeConnection', totalCount?: number | null, edgeCount?: number | null, pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, endCursor?: string | null, startCursor?: string | null }, edges: Array<{ __typename?: 'ProgramNodeEdge', cursor: string, node?: { __typename?: 'ProgramNode', id: string, name: string, status: ProgramStatus, dataCollectingType?: { __typename?: 'DataCollectingTypeNode', id: string, code: string, type?: DataCollectingTypeType | null, label: string, active: boolean, individualFiltersAvailable: boolean, householdFiltersAvailable: boolean, description: string } | null, pduFields?: Array<{ __typename?: 'PeriodicFieldNode', id: string } | null> | null } | null } | null> } | null }; +export type AllProgramsForTableQueryVariables = Exact<{ + before?: InputMaybe; + after?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + status?: InputMaybe> | InputMaybe>; + sector?: InputMaybe> | InputMaybe>; + businessArea: Scalars['String']['input']; + search?: InputMaybe; + numberOfHouseholds?: InputMaybe; + budget?: InputMaybe; + startDate?: InputMaybe; + endDate?: InputMaybe; + orderBy?: InputMaybe; + dataCollectingType?: InputMaybe; +}>; + + +export type AllProgramsForTableQuery = { __typename?: 'Query', allPrograms?: { __typename?: 'ProgramNodeConnection', totalCount?: number | null, edgeCount?: number | null, pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, endCursor?: string | null, startCursor?: string | null }, edges: Array<{ __typename?: 'ProgramNodeEdge', cursor: string, node?: { __typename?: 'ProgramNode', id: string, name: string, startDate: any, endDate: any, status: ProgramStatus, budget?: any | null, sector: ProgramSector, totalNumberOfHouseholds?: number | null } | null } | null> } | null }; + export type ProgramQueryVariables = Exact<{ id: Scalars['ID']['input']; }>; @@ -21963,6 +21935,94 @@ export type AllProgramsForChoicesQueryHookResult = ReturnType; export type AllProgramsForChoicesSuspenseQueryHookResult = ReturnType; export type AllProgramsForChoicesQueryResult = Apollo.QueryResult; +export const AllProgramsForTableDocument = gql` + query AllProgramsForTable($before: String, $after: String, $first: Int, $last: Int, $status: [String], $sector: [String], $businessArea: String!, $search: String, $numberOfHouseholds: String, $budget: String, $startDate: Date, $endDate: Date, $orderBy: String, $dataCollectingType: String) { + allPrograms( + before: $before + after: $after + first: $first + last: $last + status: $status + sector: $sector + businessArea: $businessArea + search: $search + numberOfHouseholds: $numberOfHouseholds + budget: $budget + orderBy: $orderBy + startDate: $startDate + endDate: $endDate + dataCollectingType: $dataCollectingType + ) { + pageInfo { + hasNextPage + hasPreviousPage + endCursor + startCursor + } + totalCount + edgeCount + edges { + cursor + node { + id + name + startDate + endDate + status + budget + sector + totalNumberOfHouseholds + } + } + } +} + `; + +/** + * __useAllProgramsForTableQuery__ + * + * To run a query within a React component, call `useAllProgramsForTableQuery` and pass it any options that fit your needs. + * When your component renders, `useAllProgramsForTableQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useAllProgramsForTableQuery({ + * variables: { + * before: // value for 'before' + * after: // value for 'after' + * first: // value for 'first' + * last: // value for 'last' + * status: // value for 'status' + * sector: // value for 'sector' + * businessArea: // value for 'businessArea' + * search: // value for 'search' + * numberOfHouseholds: // value for 'numberOfHouseholds' + * budget: // value for 'budget' + * startDate: // value for 'startDate' + * endDate: // value for 'endDate' + * orderBy: // value for 'orderBy' + * dataCollectingType: // value for 'dataCollectingType' + * }, + * }); + */ +export function useAllProgramsForTableQuery(baseOptions: Apollo.QueryHookOptions & ({ variables: AllProgramsForTableQueryVariables; skip?: boolean; } | { skip: boolean; }) ) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(AllProgramsForTableDocument, options); + } +export function useAllProgramsForTableLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(AllProgramsForTableDocument, options); + } +export function useAllProgramsForTableSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(AllProgramsForTableDocument, options); + } +export type AllProgramsForTableQueryHookResult = ReturnType; +export type AllProgramsForTableLazyQueryHookResult = ReturnType; +export type AllProgramsForTableSuspenseQueryHookResult = ReturnType; +export type AllProgramsForTableQueryResult = Apollo.QueryResult; export const ProgramDocument = gql` query Program($id: ID!) { program(id: $id) { @@ -24444,8 +24504,6 @@ export type ResolversTypes = { CreatePaymentPlanMutation: ResolverTypeWrapper; CreatePaymentVerificationInput: CreatePaymentVerificationInput; CreateProgram: ResolverTypeWrapper; - CreateProgramCycle: ResolverTypeWrapper; - CreateProgramCycleInput: CreateProgramCycleInput; CreateProgramInput: CreateProgramInput; CreateReport: ResolverTypeWrapper; CreateReportInput: CreateReportInput; @@ -24469,7 +24527,6 @@ export type ResolversTypes = { DeletePaymentPlanMutation: ResolverTypeWrapper; DeletePaymentVerificationPlan: ResolverTypeWrapper; DeleteProgram: ResolverTypeWrapper; - DeleteProgramCycle: ResolverTypeWrapper; DeleteRegistrationDataImport: ResolverTypeWrapper; DeleteTargetPopulationMutationInput: DeleteTargetPopulationMutationInput; DeleteTargetPopulationMutationPayload: ResolverTypeWrapper; @@ -24866,8 +24923,6 @@ export type ResolversTypes = { UpdatePaymentVerificationReceivedAndReceivedAmount: ResolverTypeWrapper; UpdatePaymentVerificationStatusAndReceivedAmount: ResolverTypeWrapper; UpdateProgram: ResolverTypeWrapper; - UpdateProgramCycle: ResolverTypeWrapper; - UpdateProgramCycleInput: UpdateProgramCycleInput; UpdateProgramInput: UpdateProgramInput; UpdateTargetPopulationInput: UpdateTargetPopulationInput; UpdateTargetPopulationMutation: ResolverTypeWrapper; @@ -24977,8 +25032,6 @@ export type ResolversParentTypes = { CreatePaymentPlanMutation: CreatePaymentPlanMutation; CreatePaymentVerificationInput: CreatePaymentVerificationInput; CreateProgram: CreateProgram; - CreateProgramCycle: CreateProgramCycle; - CreateProgramCycleInput: CreateProgramCycleInput; CreateProgramInput: CreateProgramInput; CreateReport: CreateReport; CreateReportInput: CreateReportInput; @@ -25001,7 +25054,6 @@ export type ResolversParentTypes = { DeletePaymentPlanMutation: DeletePaymentPlanMutation; DeletePaymentVerificationPlan: DeletePaymentVerificationPlan; DeleteProgram: DeleteProgram; - DeleteProgramCycle: DeleteProgramCycle; DeleteRegistrationDataImport: DeleteRegistrationDataImport; DeleteTargetPopulationMutationInput: DeleteTargetPopulationMutationInput; DeleteTargetPopulationMutationPayload: DeleteTargetPopulationMutationPayload; @@ -25332,8 +25384,6 @@ export type ResolversParentTypes = { UpdatePaymentVerificationReceivedAndReceivedAmount: UpdatePaymentVerificationReceivedAndReceivedAmount; UpdatePaymentVerificationStatusAndReceivedAmount: UpdatePaymentVerificationStatusAndReceivedAmount; UpdateProgram: UpdateProgram; - UpdateProgramCycle: UpdateProgramCycle; - UpdateProgramCycleInput: UpdateProgramCycleInput; UpdateProgramInput: UpdateProgramInput; UpdateTargetPopulationInput: UpdateTargetPopulationInput; UpdateTargetPopulationMutation: UpdateTargetPopulationMutation; @@ -25935,12 +25985,6 @@ export type CreateProgramResolvers; }; -export type CreateProgramCycleResolvers = { - program?: Resolver, ParentType, ContextType>; - validationErrors?: Resolver, ParentType, ContextType>; - __isTypeOf?: IsTypeOfResolverFn; -}; - export type CreateReportResolvers = { report?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -26051,11 +26095,6 @@ export type DeleteProgramResolvers; }; -export type DeleteProgramCycleResolvers = { - program?: Resolver, ParentType, ContextType>; - __isTypeOf?: IsTypeOfResolverFn; -}; - export type DeleteRegistrationDataImportResolvers = { ok?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -26111,7 +26150,7 @@ export type DeliveryMechanismPerPaymentPlanNodeResolvers, ParentType, ContextType>; createdAt?: Resolver; createdBy?: Resolver; - deliveryMechanism?: Resolver; + deliveryMechanism?: Resolver, ParentType, ContextType>; deliveryMechanismChoice?: Resolver, ParentType, ContextType>; deliveryMechanismOrder?: Resolver; financialServiceProvider?: Resolver, ParentType, ContextType>; @@ -26399,6 +26438,7 @@ export type FinancialServiceProviderXlsxTemplateNodeResolvers; createdBy?: Resolver, ParentType, ContextType>; financialServiceProviders?: Resolver>; + flexFields?: Resolver, ParentType, ContextType>; id?: Resolver; name?: Resolver; updatedAt?: Resolver; @@ -27443,7 +27483,6 @@ export type MutationsResolvers, ParentType, ContextType, RequireFields>; createPaymentVerificationPlan?: Resolver, ParentType, ContextType, RequireFields>; createProgram?: Resolver, ParentType, ContextType, RequireFields>; - createProgramCycle?: Resolver, ParentType, ContextType, RequireFields>; createReport?: Resolver, ParentType, ContextType, RequireFields>; createSurvey?: Resolver, ParentType, ContextType, RequireFields>; createTargetPopulation?: Resolver, ParentType, ContextType, RequireFields>; @@ -27451,7 +27490,6 @@ export type MutationsResolvers, ParentType, ContextType, RequireFields>; deletePaymentVerificationPlan?: Resolver, ParentType, ContextType, RequireFields>; deleteProgram?: Resolver, ParentType, ContextType, RequireFields>; - deleteProgramCycle?: Resolver, ParentType, ContextType, RequireFields>; deleteRegistrationDataImport?: Resolver, ParentType, ContextType, RequireFields>; deleteTargetPopulation?: Resolver, ParentType, ContextType, RequireFields>; discardPaymentVerificationPlan?: Resolver, ParentType, ContextType, RequireFields>; @@ -27495,7 +27533,6 @@ export type MutationsResolvers, ParentType, ContextType, RequireFields>; updatePaymentVerificationStatusAndReceivedAmount?: Resolver, ParentType, ContextType, RequireFields>; updateProgram?: Resolver, ParentType, ContextType, Partial>; - updateProgramCycle?: Resolver, ParentType, ContextType, Partial>; updateTargetPopulation?: Resolver, ParentType, ContextType, RequireFields>; uploadImportDataXlsxFileAsync?: Resolver, ParentType, ContextType, RequireFields>; }; @@ -29410,12 +29447,6 @@ export type UpdateProgramResolvers; }; -export type UpdateProgramCycleResolvers = { - program?: Resolver, ParentType, ContextType>; - validationErrors?: Resolver, ParentType, ContextType>; - __isTypeOf?: IsTypeOfResolverFn; -}; - export type UpdateTargetPopulationMutationResolvers = { targetPopulation?: Resolver, ParentType, ContextType>; validationErrors?: Resolver, ParentType, ContextType>; @@ -29687,7 +29718,6 @@ export type Resolvers = { CreateGrievanceTicketMutation?: CreateGrievanceTicketMutationResolvers; CreatePaymentPlanMutation?: CreatePaymentPlanMutationResolvers; CreateProgram?: CreateProgramResolvers; - CreateProgramCycle?: CreateProgramCycleResolvers; CreateReport?: CreateReportResolvers; CreateSurveyMutation?: CreateSurveyMutationResolvers; CreateTargetPopulationMutation?: CreateTargetPopulationMutationResolvers; @@ -29705,7 +29735,6 @@ export type Resolvers = { DeletePaymentPlanMutation?: DeletePaymentPlanMutationResolvers; DeletePaymentVerificationPlan?: DeletePaymentVerificationPlanResolvers; DeleteProgram?: DeleteProgramResolvers; - DeleteProgramCycle?: DeleteProgramCycleResolvers; DeleteRegistrationDataImport?: DeleteRegistrationDataImportResolvers; DeleteTargetPopulationMutationPayload?: DeleteTargetPopulationMutationPayloadResolvers; DeliveredQuantityNode?: DeliveredQuantityNodeResolvers; @@ -29982,7 +30011,6 @@ export type Resolvers = { UpdatePaymentVerificationReceivedAndReceivedAmount?: UpdatePaymentVerificationReceivedAndReceivedAmountResolvers; UpdatePaymentVerificationStatusAndReceivedAmount?: UpdatePaymentVerificationStatusAndReceivedAmountResolvers; UpdateProgram?: UpdateProgramResolvers; - UpdateProgramCycle?: UpdateProgramCycleResolvers; UpdateTargetPopulationMutation?: UpdateTargetPopulationMutationResolvers; Upload?: GraphQLScalarType; UploadImportDataXLSXFileAsync?: UploadImportDataXlsxFileAsyncResolvers; diff --git a/frontend/src/apollo/queries/program/AllProgramsForTable.ts b/frontend/src/apollo/queries/program/AllProgramsForTable.ts new file mode 100644 index 0000000000..e0c91b36c3 --- /dev/null +++ b/frontend/src/apollo/queries/program/AllProgramsForTable.ts @@ -0,0 +1,59 @@ +import { gql } from '@apollo/client'; + +export const AllProgramsForTable = gql` + query AllProgramsForTable( + $before: String + $after: String + $first: Int + $last: Int + $status: [String] + $sector: [String] + $businessArea: String! + $search: String + $numberOfHouseholds: String + $budget: String + $startDate: Date + $endDate: Date + $orderBy: String + $dataCollectingType: String + ) { + allPrograms( + before: $before + after: $after + first: $first + last: $last + status: $status + sector: $sector + businessArea: $businessArea + search: $search + numberOfHouseholds: $numberOfHouseholds + budget: $budget + orderBy: $orderBy + startDate: $startDate + endDate: $endDate + dataCollectingType: $dataCollectingType + ) { + pageInfo { + hasNextPage + hasPreviousPage + endCursor + startCursor + } + totalCount + edgeCount + edges { + cursor + node { + id + name + startDate + endDate + status + budget + sector + totalNumberOfHouseholds + } + } + } + } +`; diff --git a/frontend/src/containers/tables/ProgrammesTable/ProgrammesHeadCells.tsx b/frontend/src/containers/tables/ProgrammesTable/ProgrammesHeadCells.tsx index d64076c10f..e097fa6939 100644 --- a/frontend/src/containers/tables/ProgrammesTable/ProgrammesHeadCells.tsx +++ b/frontend/src/containers/tables/ProgrammesTable/ProgrammesHeadCells.tsx @@ -1,8 +1,8 @@ -import { AllProgramsQuery } from '@generated/graphql'; +import { AllProgramsForTableQuery } from '@generated/graphql'; import { HeadCell } from '@components/core/Table/EnhancedTableHead'; export const headCells: HeadCell< -AllProgramsQuery['allPrograms']['edges'][number]['node'] + AllProgramsForTableQuery['allPrograms']['edges'][number]['node'] >[] = [ { disablePadding: false, diff --git a/frontend/src/containers/tables/ProgrammesTable/ProgrammesTable.test.tsx b/frontend/src/containers/tables/ProgrammesTable/ProgrammesTable.test.tsx index b6047cc59e..1c55846b08 100644 --- a/frontend/src/containers/tables/ProgrammesTable/ProgrammesTable.test.tsx +++ b/frontend/src/containers/tables/ProgrammesTable/ProgrammesTable.test.tsx @@ -3,10 +3,9 @@ import * as React from 'react'; import { act } from '@testing-library/react'; import wait from 'waait'; import { ProgrammesTable } from '.'; -import { render, ApolloLoadingLink } from '../../../testUtils/testUtils'; +import { render } from '../../../testUtils/testUtils'; import { fakeProgramChoices } from '../../../../fixtures/programs/fakeProgramChoices'; import { fakeApolloAllPrograms } from '../../../../fixtures/programs/fakeApolloAllPrograms'; -import {ApolloLink} from "@apollo/client"; describe('containers/tables/ProgrammesTable', () => { const initialFilter = { @@ -39,10 +38,7 @@ describe('containers/tables/ProgrammesTable', () => { it('should render loading', () => { const { container } = render( - + title={t('Programmes')} headCells={headCells} - query={useAllProgramsQuery} + query={useAllProgramsForTableQuery} queriedObjectName="allPrograms" initialVariables={initialVariables} renderRow={(row) => ( diff --git a/frontend/src/containers/tables/ProgrammesTable/ProgrammesTableRow.tsx b/frontend/src/containers/tables/ProgrammesTable/ProgrammesTableRow.tsx index 63499e274f..af6dc5fe32 100644 --- a/frontend/src/containers/tables/ProgrammesTable/ProgrammesTableRow.tsx +++ b/frontend/src/containers/tables/ProgrammesTable/ProgrammesTableRow.tsx @@ -1,7 +1,10 @@ import TableCell from '@mui/material/TableCell'; import * as React from 'react'; import { useNavigate } from 'react-router-dom'; -import { AllProgramsQuery, ProgrammeChoiceDataQuery } from '@generated/graphql'; +import { + AllProgramsForTableQuery, + ProgrammeChoiceDataQuery, +} from '@generated/graphql'; import { BlackLink } from '@components/core/BlackLink'; import { StatusBox } from '@components/core/StatusBox'; import { ClickableTableRow } from '@components/core/Table/ClickableTableRow'; @@ -14,7 +17,7 @@ import { } from '@utils/utils'; interface ProgrammesTableRowProps { - program: AllProgramsQuery['allPrograms']['edges'][number]['node']; + program: AllProgramsForTableQuery['allPrograms']['edges'][number]['node']; choicesData: ProgrammeChoiceDataQuery; } From 00ce8a20dd35f42c9aa84a270aa4ec804a4c0f3a Mon Sep 17 00:00:00 2001 From: Szymon Wyderka Date: Fri, 23 Aug 2024 12:26:37 +0200 Subject: [PATCH 088/118] Added tests test_program_details_edit_cycle_with_wrong_date and test_program_details_add_new_cycle_with_wrong_date --- .../programme_details/programme_details.py | 10 + .../program_details/test_program_details.py | 233 ++++++++++++++++-- 2 files changed, 224 insertions(+), 19 deletions(-) diff --git a/backend/selenium_tests/page_object/programme_details/programme_details.py b/backend/selenium_tests/page_object/programme_details/programme_details.py index e00335b33a..995152e1fa 100644 --- a/backend/selenium_tests/page_object/programme_details/programme_details.py +++ b/backend/selenium_tests/page_object/programme_details/programme_details.py @@ -50,6 +50,7 @@ class ProgrammeDetails(BaseComponents): inputTitle = 'input[data-cy="input-title"]' deleteProgrammeCycle = 'button[data-cy="delete-programme-cycle"]' buttonDelete = 'button[data-cy="button-delete"]' + buttonCancel = 'button[data-cy="button-cancel"]' def getProgramCycleRow(self) -> WebElement: self.wait_for(self.programCycleRow) @@ -122,6 +123,12 @@ def getStartDateCycle(self) -> WebElement: def getEndDateCycle(self) -> WebElement: return self.wait_for(self.endDateCycle).find_elements("tag name", "input")[0] + def getStartDateCycleDiv(self) -> WebElement: + return self.wait_for(self.startDateCycle) + + def getEndDateCycleDiv(self) -> WebElement: + return self.wait_for(self.endDateCycle) + def getButtonCreateProgramCycle(self) -> WebElement: return self.wait_for(self.buttonCreateProgramCycle) @@ -206,6 +213,9 @@ def getTablePagination(self) -> WebElement: def getButtonDelete(self) -> WebElement: return self.wait_for(self.buttonDelete) + def getButtonCancel(self) -> WebElement: + return self.wait_for(self.buttonCancel) + def clickButtonFinishProgramPopup(self) -> None: self.wait_for('[data-cy="dialog-actions-container"]') self.get_elements(self.buttonFinishProgram)[1].click() diff --git a/backend/selenium_tests/program_details/test_program_details.py b/backend/selenium_tests/program_details/test_program_details.py index 65980b1f46..bfb8dea3f8 100644 --- a/backend/selenium_tests/program_details/test_program_details.py +++ b/backend/selenium_tests/program_details/test_program_details.py @@ -103,6 +103,15 @@ def standard_active_program() -> Program: cycle_end_date=datetime.now(), ) +@pytest.fixture +def standard_active_program_cycle_draft() -> Program: + yield get_program_with_dct_type_and_name( + "Active Programme", + "9876", + status=Program.ACTIVE, + program_cycle_status=ProgramCycle.ACTIVE, + cycle_end_date=datetime.now(), + ) @pytest.fixture def standard_active_program_with_draft_program_cycle() -> Program: @@ -370,6 +379,47 @@ def test_program_details_edit_default_cycle_by_add_new( ) in pageProgrammeDetails.getProgramCycleStartDate()[1].text assert "Test Title" in pageProgrammeDetails.getProgramCycleTitle()[1].text + def test_program_details_add_new_programme_cycle_without_end_date( + self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails + ) -> None: + pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") + assert "ACTIVE" in pageProgrammeDetails.getProgramStatus().text + pageProgrammeDetails.getButtonAddNewProgrammeCycle().click() + pageProgrammeDetails.getInputTitle().send_keys("123") + pageProgrammeDetails.getStartDateCycle().click() + pageProgrammeDetails.getStartDateCycle().send_keys( + (datetime.now() + relativedelta(days=1)).strftime("%Y-%m-%d") + ) + pageProgrammeDetails.getEndDateCycle().click() + pageProgrammeDetails.getEndDateCycle().send_keys((datetime.now() + relativedelta(days=10)).strftime("%Y-%m-%d")) + pageProgrammeDetails.getButtonCreateProgramCycle().click() + + pageProgrammeDetails.getButtonAddNewProgrammeCycle().click() + pageProgrammeDetails.getInputTitle().send_keys("Test %$ What?") + pageProgrammeDetails.getStartDateCycle().click() + pageProgrammeDetails.getStartDateCycle().send_keys( + (datetime.now() + relativedelta(days=11)).strftime("%Y-%m-%d") + ) + pageProgrammeDetails.getButtonCreateProgramCycle().click() + + pageProgrammeDetails.getProgramCycleRow() + + assert "Draft" in pageProgrammeDetails.getProgramCycleStatus()[1].text + assert (datetime.now() + relativedelta(days=1)).strftime( + "%-d %b %Y" + ) in pageProgrammeDetails.getProgramCycleStartDate()[1].text + assert (datetime.now() + relativedelta(days=10)).strftime( + "%-d %b %Y" + ) in pageProgrammeDetails.getProgramCycleEndDate()[1].text + assert "123" in pageProgrammeDetails.getProgramCycleTitle()[1].text + + assert "Draft" in pageProgrammeDetails.getProgramCycleStatus()[2].text + assert (datetime.now() + relativedelta(days=11)).strftime( + "%-d %b %Y" + ) in pageProgrammeDetails.getProgramCycleStartDate()[2].text + assert "-" in pageProgrammeDetails.getProgramCycleEndDate()[2].text + assert "Test %$ What?" in pageProgrammeDetails.getProgramCycleTitle()[2].text + def test_program_details_add_new_programme_cycle( self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: @@ -467,31 +517,181 @@ def test_program_details_delete_programme_cycle( assert program_cycle_3 in pageProgrammeDetails.getProgramCycleId()[1].text def test_program_details_buttons_vs_programme_cycle_status( - self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails + self, program_with_different_cycles: Program, pageProgrammeDetails: ProgrammeDetails + ) -> None: + pageProgrammeDetails.selectGlobalProgramFilter("ThreeCyclesProgramme") + for _ in range(50): + if 3 == len(pageProgrammeDetails.getProgramCycleRow()): + break + sleep(0.1) + else: + assert 3 == len(pageProgrammeDetails.getProgramCycleRow()) + assert pageProgrammeDetails.getButtonEditProgramCycle()[0] + assert pageProgrammeDetails.getButtonEditProgramCycle()[1] + with pytest.raises(Exception): + assert pageProgrammeDetails.getButtonEditProgramCycle()[2] + + assert pageProgrammeDetails.getDeleteProgrammeCycle()[0] + with pytest.raises(Exception): + assert pageProgrammeDetails.getDeleteProgrammeCycle()[1] + with pytest.raises(Exception): + assert pageProgrammeDetails.getDeleteProgrammeCycle()[2] + + @pytest.mark.skip(reason="Unskip after fix 211823") + def test_program_details_edit_default_cycle_by_add_new_cancel( + self, standard_program_with_draft_programme_cycle: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") - from selenium_tests.tools.tag_name_finder import printing + assert "ACTIVE" in pageProgrammeDetails.getProgramStatus().text + assert "0" in pageProgrammeDetails.getLabelProgramSize().text + assert "Programme Cycles" in pageProgrammeDetails.getTableTitle().text + pageProgrammeDetails.getButtonAddNewProgrammeCycle().click() + pageProgrammeDetails.getDataPickerFilter().click() + pageProgrammeDetails.getDataPickerFilter().send_keys(datetime.now().strftime("%Y-%m-%d")) + pageProgrammeDetails.getButtonNext().click() + pageProgrammeDetails.getButtonCancel().click() - printing("Mapping", pageProgrammeDetails.driver) - printing("Methods", pageProgrammeDetails.driver) + assert "Draft" in pageProgrammeDetails.getProgramCycleStatus()[0].text + assert datetime.now().strftime("%-d %b %Y") in pageProgrammeDetails.getProgramCycleEndDate()[0].text + assert "Default Programme Cycle" in pageProgrammeDetails.getProgramCycleTitle()[0].text + + pageProgrammeDetails.getButtonAddNewProgrammeCycle().click() + pageProgrammeDetails.getInputTitle().send_keys("Test %$ What?") + pageProgrammeDetails.getStartDateCycle().click() + pageProgrammeDetails.getStartDateCycle().send_keys( + (datetime.now() + relativedelta(days=11)).strftime("%Y-%m-%d") + ) + pageProgrammeDetails.getEndDateCycle().click() + pageProgrammeDetails.getEndDateCycle().send_keys((datetime.now() + relativedelta(days=21)).strftime("%Y-%m-%d")) + pageProgrammeDetails.getButtonCreateProgramCycle().click() + + assert "Draft" in pageProgrammeDetails.getProgramCycleStatus()[2].text + assert (datetime.now() + relativedelta(days=11)).strftime( + "%-d %b %Y" + ) in pageProgrammeDetails.getProgramCycleStartDate()[2].text + assert (datetime.now() + relativedelta(days=21)).strftime( + "%-d %b %Y" + ) in pageProgrammeDetails.getProgramCycleEndDate()[2].text + assert "Test %$ What?" in pageProgrammeDetails.getProgramCycleTitle()[2].text def test_program_details_add_new_cycle_with_wrong_date( - self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails + self, standard_active_program_cycle_draft: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") - # end date cycle vs end date program - # end date cycle vs end other cycle - # start date cycle vs start date program - # start date cycle vs other date cycle + assert "ACTIVE" in pageProgrammeDetails.getProgramStatus().text + pageProgrammeDetails.getButtonAddNewProgrammeCycle().click() + pageProgrammeDetails.getInputTitle().send_keys("New cycle with wrong date") + pageProgrammeDetails.getStartDateCycle().click() + pageProgrammeDetails.getStartDateCycle().send_keys( + (datetime.now() - relativedelta(days=40)).strftime("%Y-%m-%d") + ) + pageProgrammeDetails.getButtonCreateProgramCycle().click() + for _ in range(50): + if "Start Date cannot be before Programme Start Date" in pageProgrammeDetails.getStartDateCycleDiv().text: + break + sleep(0.1) + assert "Start Date cannot be before Programme Start Date" in pageProgrammeDetails.getStartDateCycleDiv().text + + pageProgrammeDetails.getStartDateCycle().click() + pageProgrammeDetails.getStartDateCycle().send_keys( + (datetime.now() - relativedelta(days=24)).strftime("%Y-%m-%d") + ) + pageProgrammeDetails.getEndDateCycle().click() + pageProgrammeDetails.getEndDateCycle().send_keys((datetime.now() + relativedelta(days=121)).strftime("%Y-%m-%d")) + pageProgrammeDetails.getButtonCreateProgramCycle().click() + for _ in range(50): + if "End Date cannot be after Programme End Date" in pageProgrammeDetails.getEndDateCycleDiv().text: + break + sleep(0.1) + assert "End Date cannot be after Programme End Date" in pageProgrammeDetails.getEndDateCycleDiv().text + pageProgrammeDetails.getEndDateCycle().click() + pageProgrammeDetails.getEndDateCycle().send_keys(Keys.CONTROL + "a") + + pageProgrammeDetails.getEndDateCycle().send_keys((datetime.now() + relativedelta(days=1)).strftime("%Y-%m-%d")) + pageProgrammeDetails.getButtonCreateProgramCycle().click() + + for _ in range(50): + if "Programme Cycles' timeframes must not overlap with the provided start date." in pageProgrammeDetails.getStartDateCycleDiv().text: + break + sleep(0.1) + assert "Programme Cycles' timeframes must not overlap with the provided start date." in pageProgrammeDetails.getStartDateCycleDiv().text + pageProgrammeDetails.getStartDateCycle().click() + pageProgrammeDetails.getStartDateCycle().send_keys( + (datetime.now() + relativedelta(days=1)).strftime("%Y-%m-%d") + ) + pageProgrammeDetails.getButtonCreateProgramCycle().click() + + pageProgrammeDetails.getButtonAddNewProgrammeCycle() + pageProgrammeDetails.getProgramCycleRow() + + assert "Draft" in pageProgrammeDetails.getProgramCycleStatus()[1].text + assert (datetime.now() + relativedelta(days=1)).strftime( + "%-d %b %Y" + ) in pageProgrammeDetails.getProgramCycleStartDate()[1].text + assert (datetime.now() + relativedelta(days=1)).strftime( + "%-d %b %Y" + ) in pageProgrammeDetails.getProgramCycleEndDate()[1].text + assert "New cycle with wrong date" in pageProgrammeDetails.getProgramCycleTitle()[1].text def test_program_details_edit_cycle_with_wrong_date( - self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails + self, standard_active_program_cycle_draft: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") - # end date cycle vs end date program - # end date cycle vs end other cycle - # start date cycle vs start date program - # start date cycle vs other date cycle + assert "ACTIVE" in pageProgrammeDetails.getProgramStatus().text + pageProgrammeDetails.getButtonEditProgramCycle()[0].click() + pageProgrammeDetails.getInputTitle().send_keys("New cycle with wrong date") + pageProgrammeDetails.getStartDateCycle().click() + pageProgrammeDetails.getStartDateCycle().send_keys( + (datetime.now() - relativedelta(days=40)).strftime("%Y-%m-%d") + ) + pageProgrammeDetails.getButtonCreateProgramCycle().click() + for _ in range(50): + if "Start Date cannot be before Programme Start Date" in pageProgrammeDetails.getStartDateCycleDiv().text: + break + sleep(0.1) + assert "Start Date cannot be before Programme Start Date" in pageProgrammeDetails.getStartDateCycleDiv().text + + pageProgrammeDetails.getStartDateCycle().click() + pageProgrammeDetails.getStartDateCycle().send_keys( + (datetime.now() - relativedelta(days=24)).strftime("%Y-%m-%d") + ) + pageProgrammeDetails.getEndDateCycle().click() + pageProgrammeDetails.getEndDateCycle().send_keys( + (datetime.now() + relativedelta(days=121)).strftime("%Y-%m-%d")) + pageProgrammeDetails.getButtonCreateProgramCycle().click() + for _ in range(50): + if "End Date cannot be after Programme End Date" in pageProgrammeDetails.getEndDateCycleDiv().text: + break + sleep(0.1) + assert "End Date cannot be after Programme End Date" in pageProgrammeDetails.getEndDateCycleDiv().text + pageProgrammeDetails.getEndDateCycle().click() + pageProgrammeDetails.getEndDateCycle().send_keys(Keys.CONTROL + "a") + + pageProgrammeDetails.getEndDateCycle().send_keys((datetime.now() + relativedelta(days=1)).strftime("%Y-%m-%d")) + pageProgrammeDetails.getButtonCreateProgramCycle().click() + + for _ in range(50): + if "Programme Cycles' timeframes must not overlap with the provided start date." in pageProgrammeDetails.getStartDateCycleDiv().text: + break + sleep(0.1) + assert "Programme Cycles' timeframes must not overlap with the provided start date." in pageProgrammeDetails.getStartDateCycleDiv().text + pageProgrammeDetails.getStartDateCycle().click() + pageProgrammeDetails.getStartDateCycle().send_keys( + (datetime.now() + relativedelta(days=1)).strftime("%Y-%m-%d") + ) + pageProgrammeDetails.getButtonCreateProgramCycle().click() + + pageProgrammeDetails.getButtonAddNewProgrammeCycle() + pageProgrammeDetails.getProgramCycleRow() + + assert "Draft" in pageProgrammeDetails.getProgramCycleStatus()[1].text + assert (datetime.now() + relativedelta(days=1)).strftime( + "%-d %b %Y" + ) in pageProgrammeDetails.getProgramCycleStartDate()[1].text + assert (datetime.now() + relativedelta(days=1)).strftime( + "%-d %b %Y" + ) in pageProgrammeDetails.getProgramCycleEndDate()[1].text + assert "New cycle with wrong date" in pageProgrammeDetails.getProgramCycleTitle()[1].text def test_edit_program_details_with_wrong_date( self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails @@ -500,11 +700,6 @@ def test_edit_program_details_with_wrong_date( # end date program vs end date cycle # start date program vs start date cycle - def test_program_details_edit_default_cycle_by_add_new_cancel( - self, standard_program_with_draft_programme_cycle: Program, pageProgrammeDetails: ProgrammeDetails - ) -> None: - pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") - def test_program_details_program_cycle_total_quantities( self, standard_program_with_draft_programme_cycle: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: From 9a72d4915d17fd0f07170de615e5451ece429c51 Mon Sep 17 00:00:00 2001 From: Szymon Wyderka Date: Fri, 23 Aug 2024 12:31:10 +0200 Subject: [PATCH 089/118] Added tests test_program_details_edit_cycle_with_wrong_date and test_program_details_add_new_cycle_with_wrong_date --- .../program_details/test_program_details.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/selenium_tests/program_details/test_program_details.py b/backend/selenium_tests/program_details/test_program_details.py index bfb8dea3f8..9bf1d23d9d 100644 --- a/backend/selenium_tests/program_details/test_program_details.py +++ b/backend/selenium_tests/program_details/test_program_details.py @@ -644,7 +644,7 @@ def test_program_details_edit_cycle_with_wrong_date( pageProgrammeDetails.getStartDateCycle().send_keys( (datetime.now() - relativedelta(days=40)).strftime("%Y-%m-%d") ) - pageProgrammeDetails.getButtonCreateProgramCycle().click() + pageProgrammeDetails.getButtonSave().click() for _ in range(50): if "Start Date cannot be before Programme Start Date" in pageProgrammeDetails.getStartDateCycleDiv().text: break @@ -658,7 +658,7 @@ def test_program_details_edit_cycle_with_wrong_date( pageProgrammeDetails.getEndDateCycle().click() pageProgrammeDetails.getEndDateCycle().send_keys( (datetime.now() + relativedelta(days=121)).strftime("%Y-%m-%d")) - pageProgrammeDetails.getButtonCreateProgramCycle().click() + pageProgrammeDetails.getButtonSave().click() for _ in range(50): if "End Date cannot be after Programme End Date" in pageProgrammeDetails.getEndDateCycleDiv().text: break @@ -668,7 +668,7 @@ def test_program_details_edit_cycle_with_wrong_date( pageProgrammeDetails.getEndDateCycle().send_keys(Keys.CONTROL + "a") pageProgrammeDetails.getEndDateCycle().send_keys((datetime.now() + relativedelta(days=1)).strftime("%Y-%m-%d")) - pageProgrammeDetails.getButtonCreateProgramCycle().click() + pageProgrammeDetails.getButtonSave().click() for _ in range(50): if "Programme Cycles' timeframes must not overlap with the provided start date." in pageProgrammeDetails.getStartDateCycleDiv().text: @@ -679,7 +679,7 @@ def test_program_details_edit_cycle_with_wrong_date( pageProgrammeDetails.getStartDateCycle().send_keys( (datetime.now() + relativedelta(days=1)).strftime("%Y-%m-%d") ) - pageProgrammeDetails.getButtonCreateProgramCycle().click() + pageProgrammeDetails.getButtonSave().click() pageProgrammeDetails.getButtonAddNewProgrammeCycle() pageProgrammeDetails.getProgramCycleRow() From 86aa53f83519426470fe060bca4536812f4ccc02 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Fri, 23 Aug 2024 13:41:11 +0200 Subject: [PATCH 090/118] change FLEX_FIELD_NOT_PDU to FLEX_FIELD_BASIC --- backend/hct_mis_api/apps/targeting/choices.py | 2 +- .../apps/targeting/graphql_types.py | 8 +++---- .../targeting/migrations/0048_migration.py | 23 +++++++++++++++++++ ..._test_create_target_population_mutation.py | 2 +- .../test_create_target_population_mutation.py | 2 +- .../tests/test_individual_block_filters.py | 4 ++-- .../tests/test_targeting_criteria.py | 4 ++-- .../test_targeting_criteria_rule_filter.py | 10 ++++---- .../test_update_target_population_mutation.py | 2 +- .../hct_mis_api/apps/targeting/validators.py | 2 +- 10 files changed, 41 insertions(+), 18 deletions(-) create mode 100644 backend/hct_mis_api/apps/targeting/migrations/0048_migration.py diff --git a/backend/hct_mis_api/apps/targeting/choices.py b/backend/hct_mis_api/apps/targeting/choices.py index e22e98b9dd..213f617579 100644 --- a/backend/hct_mis_api/apps/targeting/choices.py +++ b/backend/hct_mis_api/apps/targeting/choices.py @@ -4,5 +4,5 @@ class FlexFieldClassification(models.TextChoices): NOT_FLEX_FIELD = "NOT_FLEX_FIELD", _("Not Flex Field") - FLEX_FIELD_NOT_PDU = "FLEX_FIELD_NOT_PDU", _("Flex Field Not PDU") + FLEX_FIELD_BASIC = "FLEX_FIELD_BASIC", _("Flex Field Basic") FLEX_FIELD_PDU = "FLEX_FIELD_PDU", _("Flex Field PDU") diff --git a/backend/hct_mis_api/apps/targeting/graphql_types.py b/backend/hct_mis_api/apps/targeting/graphql_types.py index 39f3d1cb1b..c531c97c83 100644 --- a/backend/hct_mis_api/apps/targeting/graphql_types.py +++ b/backend/hct_mis_api/apps/targeting/graphql_types.py @@ -58,7 +58,7 @@ def filter_choices(field: Optional[Dict], args: List) -> Optional[Dict]: class FlexFieldClassificationChoices(graphene.Enum): NOT_FLEX_FIELD = "NOT_FLEX_FIELD" - FLEX_FIELD_NOT_PDU = "FLEX_FIELD_NOT_PDU" + FLEX_FIELD_BASIC = "FLEX_FIELD_BASIC" FLEX_FIELD_PDU = "FLEX_FIELD_PDU" @@ -80,7 +80,7 @@ def resolve_field_attribute(parent, info: Any) -> Optional[Dict]: ) program = None - if parent.flex_field_classification == "FLEX_FIELD_PDU": + if parent.flex_field_classification == FlexFieldClassification.FLEX_FIELD_PDU: encoded_program_id = info.context.headers.get("Program") program = Program.objects.get(id=decode_id_string(encoded_program_id)) return FlexibleAttribute.objects.get(name=parent.field_name, program=program) @@ -97,7 +97,7 @@ def resolve_arguments(self, info: Any) -> "GrapheneList": return self.arguments def resolve_field_attribute(parent, info: Any) -> Any: - if parent.flex_field_classification == "NOT_FLEX_FIELD": + if parent.flex_field_classification == FlexFieldClassification.NOT_FLEX_FIELD: field_attribute = get_field_by_name( parent.field_name, parent.individuals_filters_block.targeting_criteria_rule.targeting_criteria.target_population, @@ -105,7 +105,7 @@ def resolve_field_attribute(parent, info: Any) -> Any: return filter_choices(field_attribute, parent.arguments) # type: ignore # can't convert graphene list to list program = None - if parent.flex_field_classification == "FLEX_FIELD_PDU": + if parent.flex_field_classification == FlexFieldClassification.FLEX_FIELD_PDU: encoded_program_id = info.context.headers.get("Program") program = Program.objects.get(id=decode_id_string(encoded_program_id)) return FlexibleAttribute.objects.get(name=parent.field_name, program=program) diff --git a/backend/hct_mis_api/apps/targeting/migrations/0048_migration.py b/backend/hct_mis_api/apps/targeting/migrations/0048_migration.py new file mode 100644 index 0000000000..840c8e7565 --- /dev/null +++ b/backend/hct_mis_api/apps/targeting/migrations/0048_migration.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.25 on 2024-08-23 11:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('targeting', '0047_migration'), + ] + + operations = [ + migrations.AlterField( + model_name='targetingcriteriarulefilter', + name='flex_field_classification', + field=models.CharField(choices=[('NOT_FLEX_FIELD', 'Not Flex Field'), ('FLEX_FIELD_BASIC', 'Flex Field Basic'), ('FLEX_FIELD_PDU', 'Flex Field PDU')], default='NOT_FLEX_FIELD', max_length=20), + ), + migrations.AlterField( + model_name='targetingindividualblockrulefilter', + name='flex_field_classification', + field=models.CharField(choices=[('NOT_FLEX_FIELD', 'Not Flex Field'), ('FLEX_FIELD_BASIC', 'Flex Field Basic'), ('FLEX_FIELD_PDU', 'Flex Field PDU')], default='NOT_FLEX_FIELD', max_length=20), + ), + ] diff --git a/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_create_target_population_mutation.py b/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_create_target_population_mutation.py index 39cc8f15c6..5d6a2279f1 100644 --- a/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_create_target_population_mutation.py +++ b/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_create_target_population_mutation.py @@ -312,7 +312,7 @@ ], 'comparisonMethod': 'CONTAINS', 'fieldName': 'flex_field_1', - 'flexFieldClassification': 'FLEX_FIELD_NOT_PDU', + 'flexFieldClassification': 'FLEX_FIELD_BASIC', 'roundNumber': None } ] diff --git a/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py b/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py index ae2c62fca9..8a3814c245 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py @@ -331,7 +331,7 @@ def test_create_mutation_with_flex_field(self) -> None: "comparisonMethod": "CONTAINS", "arguments": ["Average"], "fieldName": "flex_field_1", - "flexFieldClassification": "FLEX_FIELD_NOT_PDU", + "flexFieldClassification": "FLEX_FIELD_BASIC", } ] } diff --git a/backend/hct_mis_api/apps/targeting/tests/test_individual_block_filters.py b/backend/hct_mis_api/apps/targeting/tests/test_individual_block_filters.py index a57927403c..82ae1cbf4b 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_individual_block_filters.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_individual_block_filters.py @@ -152,7 +152,7 @@ def test_filter_on_flex_field_not_exist(self) -> None: comparison_method="CONTAINS", field_name="flex_field_2", arguments=["Average"], - flex_field_classification=FlexFieldClassification.FLEX_FIELD_NOT_PDU, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_BASIC, ) flex_field_filter.save() @@ -186,7 +186,7 @@ def test_filter_on_flex_field(self) -> None: comparison_method="CONTAINS", field_name="flex_field_1", arguments=["Average"], - flex_field_classification=FlexFieldClassification.FLEX_FIELD_NOT_PDU, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_BASIC, ) flex_field_filter.save() query = query.filter(tc.get_query()) diff --git a/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria.py b/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria.py index ec01bc44bf..f262f2fbbd 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria.py @@ -105,7 +105,7 @@ def test_flex_field_variables(self) -> None: "comparison_method": "EQUALS", "arguments": ["0"], "field_name": "unaccompanied_child_h_f", - "flex_field_classification": "FLEX_FIELD_NOT_PDU", + "flex_field_classification": "FLEX_FIELD_BASIC", } ).get_query() ) @@ -122,7 +122,7 @@ def test_select_many_variables(self) -> None: "comparison_method": "CONTAINS", "arguments": ["other_public", "pharmacy", "other_private"], "field_name": "treatment_facility_h_f", - "flex_field_classification": "FLEX_FIELD_NOT_PDU", + "flex_field_classification": "FLEX_FIELD_BASIC", } ).get_query() ) diff --git a/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria_rule_filter.py b/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria_rule_filter.py index ad3e5bf622..c457a025a6 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria_rule_filter.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_targeting_criteria_rule_filter.py @@ -339,7 +339,7 @@ def test_rule_filter_household_total_households_4(self) -> None: comparison_method="EQUALS", field_name="total_households_h_f", arguments=[4], - flex_field_classification=FlexFieldClassification.FLEX_FIELD_NOT_PDU, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_BASIC, ) query = rule_filter.get_query() queryset = Household.objects.filter(query) @@ -351,7 +351,7 @@ def test_rule_filter_select_multiple_treatment_facility(self) -> None: comparison_method="CONTAINS", field_name="treatment_facility_h_f", arguments=["other_public", "private_doctor"], - flex_field_classification=FlexFieldClassification.FLEX_FIELD_NOT_PDU, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_BASIC, ) query = rule_filter.get_query() queryset = Household.objects.filter(query) @@ -362,7 +362,7 @@ def test_rule_filter_select_multiple_treatment_facility_2(self) -> None: comparison_method="CONTAINS", field_name="treatment_facility_h_f", arguments=["other_public", "government_health_center"], - flex_field_classification=FlexFieldClassification.FLEX_FIELD_NOT_PDU, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_BASIC, ) query = rule_filter.get_query() queryset = Household.objects.filter(query) @@ -373,7 +373,7 @@ def test_rule_filter_select_multiple_treatment_facility_not_contains(self) -> No comparison_method="NOT_CONTAINS", field_name="treatment_facility_h_f", arguments=["other_public", "government_health_center"], - flex_field_classification=FlexFieldClassification.FLEX_FIELD_NOT_PDU, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_BASIC, ) query = rule_filter.get_query() queryset = Household.objects.filter(query) @@ -384,7 +384,7 @@ def test_rule_filter_string_contains(self) -> None: comparison_method="CONTAINS", field_name="other_treatment_facility_h_f", arguments=["other"], - flex_field_classification=FlexFieldClassification.FLEX_FIELD_NOT_PDU, + flex_field_classification=FlexFieldClassification.FLEX_FIELD_BASIC, ) query = rule_filter.get_query() queryset = Household.objects.filter(query) diff --git a/backend/hct_mis_api/apps/targeting/tests/test_update_target_population_mutation.py b/backend/hct_mis_api/apps/targeting/tests/test_update_target_population_mutation.py index b9da20cb68..076ccf4614 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_update_target_population_mutation.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_update_target_population_mutation.py @@ -128,7 +128,7 @@ "comparisonMethod": "EQUALS", "fieldName": "foo_bar", "arguments": [3], - "flexFieldClassification": "FLEX_FIELD_NOT_PDU", + "flexFieldClassification": "FLEX_FIELD_BASIC", } ] } diff --git a/backend/hct_mis_api/apps/targeting/validators.py b/backend/hct_mis_api/apps/targeting/validators.py index 6dd2924a8a..0aa1b67b53 100644 --- a/backend/hct_mis_api/apps/targeting/validators.py +++ b/backend/hct_mis_api/apps/targeting/validators.py @@ -85,7 +85,7 @@ def validate(rule_filter: Any, program: Program) -> None: raise ValidationError( f"Can't find any core field attribute associated with {rule_filter.field_name} field name" ) - elif flex_field_classification == FlexFieldClassification.FLEX_FIELD_NOT_PDU: + elif flex_field_classification == FlexFieldClassification.FLEX_FIELD_BASIC: try: attribute = FlexibleAttribute.objects.get(name=rule_filter.field_name, program=None) except FlexibleAttribute.DoesNotExist: From 09036f9e5b25d2a82791bfcb3b42cebf91cfbd9d Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Fri, 23 Aug 2024 13:41:38 +0200 Subject: [PATCH 091/118] fix notpdu --> basic --- frontend/src/utils/targetingUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/utils/targetingUtils.ts b/frontend/src/utils/targetingUtils.ts index cbb0191800..acb79e6c31 100644 --- a/frontend/src/utils/targetingUtils.ts +++ b/frontend/src/utils/targetingUtils.ts @@ -3,7 +3,7 @@ export const chooseFieldType = (fieldValue, arrayHelpers, index): void => { if (fieldValue.isFlexField === false) { flexFieldClassification = 'NOT_FLEX_FIELD'; } else if (fieldValue.isFlexField === true && fieldValue.type !== 'PDU') { - flexFieldClassification = 'FLEX_FIELD_NOT_PDU'; + flexFieldClassification = 'FLEX_FIELD_BASIC'; } else if (fieldValue.isFlexField === true && fieldValue.type === 'PDU') { flexFieldClassification = 'FLEX_FIELD_PDU'; } @@ -298,7 +298,7 @@ export function getTargetingCriteriaVariables(values) { const flexFieldClassificationMap = { NOT_FLEX_FIELD: 'Not a Flex Field', - FLEX_FIELD_NOT_PDU: 'Flex Field Not PDU', + FLEX_FIELD_BASIC: 'Flex Field Basic', FLEX_FIELD_PDU: 'Flex Field PDU', }; From a3c668c16ef8d459abdc37d51d52170e2f7e3f39 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Fri, 23 Aug 2024 13:48:44 +0200 Subject: [PATCH 092/118] True -> Yes in test --- backend/selenium_tests/targeting/test_targeting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/selenium_tests/targeting/test_targeting.py b/backend/selenium_tests/targeting/test_targeting.py index bb38e7b990..90c8efa822 100644 --- a/backend/selenium_tests/targeting/test_targeting.py +++ b/backend/selenium_tests/targeting/test_targeting.py @@ -489,7 +489,7 @@ def test_create_targeting_with_pdu_bool_criteria( pageTargetingCreate.getSelectIndividualsiFltersBlocksRoundNumber().click() pageTargetingCreate.getSelectRoundOption(2).click() pageTargetingCreate.getSelectIndividualsFiltersBlocksValue().click() - pageTargetingCreate.select_option_by_name("True") + pageTargetingCreate.select_option_by_name("Yes") pageTargetingCreate.getTargetingCriteriaAddDialogSaveButton().click() bool_yes_expected_criteria_text = "Test Bool Attribute: Yes\nRound 2 (Test Round Bool 2)" assert pageTargetingCreate.getCriteriaContainer().text == bool_yes_expected_criteria_text @@ -511,7 +511,7 @@ def test_create_targeting_with_pdu_bool_criteria( pageTargetingDetails.getButtonEdit().click() pageTargetingDetails.getButtonIconEdit().click() pageTargetingCreate.getSelectIndividualsFiltersBlocksValue().click() - pageTargetingCreate.select_option_by_name("False") + pageTargetingCreate.select_option_by_name("No") bool_no_expected_criteria_text = "Test Bool Attribute: No\nRound 2 (Test Round Bool 2)" pageTargetingCreate.get_elements(pageTargetingCreate.targetingCriteriaAddDialogSaveButton)[1].click() From c3e1f695d1bfd09d085ede0c70882f69cff4500c Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Fri, 23 Aug 2024 13:48:57 +0200 Subject: [PATCH 093/118] remove screenshots --- backend/selenium_tests/targeting/test_targeting.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/selenium_tests/targeting/test_targeting.py b/backend/selenium_tests/targeting/test_targeting.py index 90c8efa822..7ddaed662c 100644 --- a/backend/selenium_tests/targeting/test_targeting.py +++ b/backend/selenium_tests/targeting/test_targeting.py @@ -662,9 +662,7 @@ def test_create_targeting_with_pdu_null_criteria( pageTargetingCreate.getTargetingCriteriaAutoComplete().send_keys(Keys.ENTER) pageTargetingCreate.getSelectIndividualsiFltersBlocksRoundNumber().click() pageTargetingCreate.getSelectRoundOption(1).click() - pageTargetingCreate.screenshot("if before click") pageTargetingCreate.getSelectIndividualsiFltersBlocksIsNull().click() - pageTargetingCreate.screenshot("if click click") pageTargetingCreate.getTargetingCriteriaAddDialogSaveButton().click() expected_criteria_text = "Test String Attribute: Empty\nRound 1 (Test Round String 1)" assert pageTargetingCreate.getCriteriaContainer().text == expected_criteria_text From 3839c0f16ef79f84e9fd6bb0e5620813dea68849 Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Fri, 23 Aug 2024 14:22:45 +0200 Subject: [PATCH 094/118] schema --- frontend/data/schema.graphql | 5504 ---------------------------------- 1 file changed, 5504 deletions(-) diff --git a/frontend/data/schema.graphql b/frontend/data/schema.graphql index 3b0be5227e..e69de29bb2 100644 --- a/frontend/data/schema.graphql +++ b/frontend/data/schema.graphql @@ -1,5504 +0,0 @@ -schema { - query: Query - mutation: Mutations -} - -input AccountabilityCommunicationMessageAgeInput { - min: Int - max: Int -} - -input AccountabilityFullListArguments { - excludedAdminAreas: [String] -} - -input AccountabilityRandomSamplingArguments { - excludedAdminAreas: [String] - confidenceInterval: Float! - marginOfError: Float! - age: AccountabilityCommunicationMessageAgeInput - sex: String -} - -input AccountabilitySampleSizeInput { - targetPopulation: ID - program: ID - samplingType: String! - fullListArguments: AccountabilityFullListArguments - randomSamplingArguments: AccountabilityRandomSamplingArguments -} - -type AccountabilitySampleSizeNode { - numberOfRecipients: Int - sampleSize: Int -} - -enum Action { - LOCK - LOCK_FSP - UNLOCK - UNLOCK_FSP - SEND_FOR_APPROVAL - APPROVE - AUTHORIZE - REVIEW - REJECT - FINISH - SEND_TO_PAYMENT_GATEWAY -} - -input ActionPaymentPlanInput { - paymentPlanId: ID! - action: Action! - comment: String -} - -type ActionPaymentPlanMutation { - paymentPlan: PaymentPlanNode -} - -type ActivatePaymentVerificationPlan { - validationErrors: Arg - paymentPlan: GenericPaymentPlanNode -} - -input AddIndividualDataObjectType { - fullName: String! - givenName: String - middleName: String - familyName: String - sex: String! - birthDate: Date! - estimatedBirthDate: Boolean! - maritalStatus: String - phoneNo: String - phoneNoAlternative: String - email: String - relationship: String! - disability: String - workStatus: String - enrolledInNutritionProgramme: Boolean - administrationOfRutf: Boolean - pregnant: Boolean - observedDisability: [String] - seeingDisability: String - hearingDisability: String - physicalDisability: String - memoryDisability: String - selfcareDisability: String - commsDisability: String - whoAnswersPhone: String - whoAnswersAltPhone: String - role: String! - documents: [IndividualDocumentObjectType] - identities: [IndividualIdentityObjectType] - paymentChannels: [BankTransferObjectType] - businessArea: String - preferredLanguage: String - flexFields: Arg - paymentDeliveryPhoneNo: String - blockchainName: String - walletAddress: String - walletName: String -} - -input AddIndividualIssueTypeExtras { - household: ID! - individualData: AddIndividualDataObjectType! -} - -type AgeFilterObject { - min: Int - max: Int -} - -input AgeInput { - min: Int - max: Int -} - -type ApprovalNode { - createdAt: DateTime! - comment: String - createdBy: UserNode - info: String -} - -type ApprovalProcessNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - sentForApprovalBy: UserNode - sentForApprovalDate: DateTime - sentForAuthorizationBy: UserNode - sentForAuthorizationDate: DateTime - sentForFinanceReleaseBy: UserNode - sentForFinanceReleaseDate: DateTime - paymentPlan: PaymentPlanNode! - approvalNumberRequired: Int! - authorizationNumberRequired: Int! - financeReleaseNumberRequired: Int! - rejectedOn: String - actions: FilteredActionsListNode -} - -type ApprovalProcessNodeConnection { - pageInfo: PageInfo! - edges: [ApprovalProcessNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type ApprovalProcessNodeEdge { - node: ApprovalProcessNode - cursor: String! -} - -type AreaNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - originalId: UUID - name: String! - parent: AreaNode - pCode: String - areaType: AreaTypeNode! - validFrom: DateTime - validUntil: DateTime - extras: JSONString! - lft: Int! - rght: Int! - treeId: Int! - level: Int! - areaSet(offset: Int, before: String, after: String, first: Int, last: Int, name: String): AreaNodeConnection! - householdSet(offset: Int, before: String, after: String, first: Int, last: Int): HouseholdNodeConnection! - grievanceticketSet(offset: Int, before: String, after: String, first: Int, last: Int): GrievanceTicketNodeConnection! - programs(offset: Int, before: String, after: String, first: Int, last: Int, name: String): ProgramNodeConnection! - reports(offset: Int, before: String, after: String, first: Int, last: Int): ReportNodeConnection! - feedbackSet(offset: Int, before: String, after: String, first: Int, last: Int): FeedbackNodeConnection! -} - -type AreaNodeConnection { - pageInfo: PageInfo! - edges: [AreaNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type AreaNodeEdge { - node: AreaNode - cursor: String! -} - -type AreaTreeNode { - id: ID - name: String - pCode: String - areas: [AreaTreeNode] - level: Int -} - -type AreaTypeNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - originalId: UUID - name: String! - areaLevel: Int! - parent: AreaTypeNode - validFrom: DateTime - validUntil: DateTime - extras: JSONString! - lft: Int! - rght: Int! - treeId: Int! - level: Int! - areatypeSet(offset: Int, before: String, after: String, first: Int, last: Int): AreaTypeNodeConnection! - areaSet(offset: Int, before: String, after: String, first: Int, last: Int, name: String): AreaNodeConnection! -} - -type AreaTypeNodeConnection { - pageInfo: PageInfo! - edges: [AreaTypeNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type AreaTypeNodeEdge { - node: AreaTypeNode - cursor: String! -} - -scalar Arg - -input AssignFspToDeliveryMechanismInput { - paymentPlanId: ID! - mappings: [FSPToDeliveryMechanismMappingInput]! -} - -type AssignFspToDeliveryMechanismMutation { - paymentPlan: PaymentPlanNode -} - -input AvailableFspsForDeliveryMechanismsInput { - paymentPlanId: ID! -} - -type BankAccountInfoNode implements Node { - id: ID! - rdiMergeStatus: BankAccountInfoRdiMergeStatus! - isOriginal: Boolean! - createdAt: DateTime! - updatedAt: DateTime! - isRemoved: Boolean! - removedDate: DateTime - lastSyncAt: DateTime - individual: IndividualNode! - bankName: String! - bankAccountNumber: String! - bankBranchName: String! - accountHolderName: String! - isMigrationHandled: Boolean! - copiedFrom: BankAccountInfoNode - copiedTo(offset: Int, before: String, after: String, first: Int, last: Int): BankAccountInfoNodeConnection! - type: String -} - -type BankAccountInfoNodeConnection { - pageInfo: PageInfo! - edges: [BankAccountInfoNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type BankAccountInfoNodeEdge { - node: BankAccountInfoNode - cursor: String! -} - -enum BankAccountInfoRdiMergeStatus { - PENDING - MERGED -} - -input BankTransferObjectType { - type: String! - bankName: String! - bankAccountNumber: String! - bankBranchName: String - accountHolderName: String! -} - -scalar BigInt - -type BulkGrievanceAddNoteMutation { - grievanceTickets: [GrievanceTicketNode] -} - -type BulkUpdateGrievanceTicketsAssigneesMutation { - grievanceTickets: [GrievanceTicketNode] -} - -type BulkUpdateGrievanceTicketsPriorityMutation { - grievanceTickets: [GrievanceTicketNode] -} - -type BulkUpdateGrievanceTicketsUrgencyMutation { - grievanceTickets: [GrievanceTicketNode] -} - -type BusinessAreaNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - code: String! - name: String! - longName: String! - regionCode: String! - regionName: String! - koboUsername: String - koboToken: String - koboUrl: String - rapidProHost: String - rapidProPaymentVerificationToken: String - rapidProMessagesToken: String - rapidProSurveyToken: String - slug: String! - customFields: JSONString! - hasDataSharingAgreement: Boolean! - parent: UserBusinessAreaNode - isSplit: Boolean! - postponeDeduplication: Boolean! - deduplicationDuplicateScore: Float! - deduplicationPossibleDuplicateScore: Float! - deduplicationBatchDuplicatesPercentage: Int! - deduplicationBatchDuplicatesAllowed: Int! - deduplicationGoldenRecordDuplicatesPercentage: Int! - deduplicationGoldenRecordDuplicatesAllowed: Int! - screenBeneficiary: Boolean! - deduplicationIgnoreWithdraw: Boolean! - isPaymentPlanApplicable: Boolean! - isAccountabilityApplicable: Boolean - active: Boolean! - enableEmailNotification: Boolean! - partners: [PartnerNode!]! - businessAreaPartnerThrough: [PartnerRoleNode!]! - children(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID): UserBusinessAreaNodeConnection! - dataCollectingTypes(offset: Int, before: String, after: String, first: Int, last: Int): DataCollectingTypeNodeConnection! - partnerSet: [PartnerNode!]! - userRoles: [UserRoleNode!]! - householdSet(offset: Int, before: String, after: String, first: Int, last: Int): HouseholdNodeConnection! - individualSet(offset: Int, before: String, after: String, first: Int, last: Int): IndividualNodeConnection! - registrationdataimportSet(offset: Int, before: String, after: String, first: Int, last: Int): RegistrationDataImportNodeConnection! - ruleSet(offset: Int, before: String, after: String, first: Int, last: Int): SteficonRuleNodeConnection! - paymentplanSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentPlanNodeConnection! - financialserviceproviderSet(offset: Int, before: String, after: String, first: Int, last: Int): FinancialServiceProviderNodeConnection! - cashplanSet(offset: Int, before: String, after: String, first: Int, last: Int): CashPlanNodeConnection! - paymentrecordSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentRecordNodeConnection! - paymentSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentNodeConnection! - serviceproviderSet(offset: Int, before: String, after: String, first: Int, last: Int): ServiceProviderNodeConnection! - tickets(offset: Int, before: String, after: String, first: Int, last: Int): GrievanceTicketNodeConnection! - targetpopulationSet(offset: Int, before: String, after: String, first: Int, last: Int, program: [ID], createdAt: DateTime, createdAt_Lte: DateTime, createdAt_Gte: DateTime, updatedAt: DateTime, updatedAt_Lte: DateTime, updatedAt_Gte: DateTime, status: String, households: [ID], name: String, createdByName: String, totalHouseholdsCountMin: Int, totalHouseholdsCountMax: Int, totalIndividualsCountMin: Int, totalIndividualsCountMax: Int, businessArea: String, createdAtRange: String, paymentPlanApplicable: Boolean, statusNot: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, programCycle: String, orderBy: String): TargetPopulationNodeConnection! - programSet(offset: Int, before: String, after: String, first: Int, last: Int, name: String): ProgramNodeConnection! - reports(offset: Int, before: String, after: String, first: Int, last: Int): ReportNodeConnection! - logentrySet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationLogEntryNodeConnection! - messageSet(offset: Int, before: String, after: String, first: Int, last: Int): CommunicationMessageNodeConnection! - feedbackSet(offset: Int, before: String, after: String, first: Int, last: Int): FeedbackNodeConnection! - surveySet(offset: Int, before: String, after: String, first: Int, last: Int): SurveyNodeConnection! -} - -type BusinessAreaNodeConnection { - pageInfo: PageInfo! - edges: [BusinessAreaNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type BusinessAreaNodeEdge { - node: BusinessAreaNode - cursor: String! -} - -type CashPlanAndPaymentPlanEdges { - cursor: String - node: CashPlanAndPaymentPlanNode -} - -type CashPlanAndPaymentPlanNode { - adminUrl: String - objType: String - id: String - unicefId: String - verificationStatus: String - status: String - currency: String - totalDeliveredQuantity: Float - startDate: String - endDate: String - programName: String - updatedAt: String - verificationPlans: [PaymentVerificationPlanNode] - totalNumberOfHouseholds: Int - totalEntitledQuantity: Float - totalUndeliveredQuantity: Float - assistanceMeasurement: String - dispersionDate: String - serviceProviderFullName: String -} - -type CashPlanNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - version: BigInt! - businessArea: UserBusinessAreaNode! - statusDate: DateTime! - startDate: DateTime - endDate: DateTime - program: ProgramNode! - exchangeRate: Float - totalEntitledQuantity: Float - totalEntitledQuantityUsd: Float - totalEntitledQuantityRevised: Float - totalEntitledQuantityRevisedUsd: Float - totalDeliveredQuantity: Float - totalDeliveredQuantityUsd: Float - totalUndeliveredQuantity: Float - totalUndeliveredQuantityUsd: Float - name: String! - caId: String - caHashId: UUID - status: CashPlanStatus! - distributionLevel: String! - dispersionDate: DateTime! - coverageDuration: Int! - coverageUnit: String! - comments: String - deliveryType: String - assistanceMeasurement: String! - assistanceThrough: String! - serviceProvider: ServiceProviderNode - visionId: String - fundsCommitment: String - downPayment: String - validationAlertsCount: Int! - totalPersonsCovered: Int! - totalPersonsCoveredRevised: Int! - paymentItems(offset: Int, before: String, after: String, first: Int, last: Int): PaymentRecordNodeConnection! - bankReconciliationSuccess: Int - bankReconciliationError: Int - totalNumberOfHouseholds: Int - currency: String - canCreatePaymentVerificationPlan: Boolean - availablePaymentRecordsCount: Int - verificationPlans(offset: Int, before: String, after: String, first: Int, last: Int, programId: String): PaymentVerificationPlanNodeConnection - paymentVerificationSummary: PaymentVerificationSummaryNode - unicefId: String -} - -type CashPlanNodeConnection { - pageInfo: PageInfo! - edges: [CashPlanNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type CashPlanNodeEdge { - node: CashPlanNode - cursor: String! -} - -enum CashPlanStatus { - DISTRIBUTION_COMPLETED - DISTRIBUTION_COMPLETED_WITH_ERRORS - TRANSACTION_COMPLETED - TRANSACTION_COMPLETED_WITH_ERRORS -} - -input CategoryExtrasInput { - sensitiveGrievanceTicketExtras: SensitiveGrievanceTicketExtras - grievanceComplaintTicketExtras: GrievanceComplaintTicketExtras - positiveFeedbackTicketExtras: PositiveFeedbackTicketExtras - negativeFeedbackTicketExtras: NegativeFeedbackTicketExtras - referralTicketExtras: ReferralTicketExtras -} - -type ChartDatasetNode { - labels: [String] - datasets: [_DatasetsNode] -} - -type ChartDetailedDatasetsNode { - labels: [String] - datasets: [_DetailedDatasetsNode] -} - -type ChartGrievanceTicketsNode { - labels: [String] - datasets: [_DatasetsNode] - totalNumberOfGrievances: Int - totalNumberOfFeedback: Int - totalNumberOfOpenSensitive: Int -} - -type ChartPaymentVerification { - labels: [String] - datasets: [_DetailedDatasetsNode] - households: Int - averageSampleSize: Float -} - -type CheckAgainstSanctionListMutation { - ok: Boolean - errors: [XlsxRowErrorNode] -} - -type ChoiceObject { - name: String - value: String -} - -type ChoiceObjectInt { - name: String - value: Int -} - -input ChooseDeliveryMechanismsForPaymentPlanInput { - paymentPlanId: ID! - deliveryMechanisms: [String]! -} - -type ChooseDeliveryMechanismsForPaymentPlanMutation { - paymentPlan: PaymentPlanNode -} - -type CommunicationMessageNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - unicefId: String - title: String! - body: String! - createdBy: UserNode - numberOfRecipients: Int! - businessArea: UserBusinessAreaNode! - households(offset: Int, before: String, after: String, first: Int, last: Int): HouseholdNodeConnection! - targetPopulation: TargetPopulationNode - registrationDataImport: RegistrationDataImportNode - samplingType: MessageSamplingType! - fullListArguments: JSONString - randomSamplingArguments: JSONString - sampleSize: Int! - program: ProgramNode - isOriginal: Boolean! - isMigrationHandled: Boolean! - migratedAt: DateTime - copiedFrom: CommunicationMessageNode - copiedTo(offset: Int, before: String, after: String, first: Int, last: Int): CommunicationMessageNodeConnection! - adminUrl: String -} - -type CommunicationMessageNodeConnection { - pageInfo: PageInfo! - edges: [CommunicationMessageNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type CommunicationMessageNodeEdge { - node: CommunicationMessageNode - cursor: String! -} - -type CommunicationMessageRecipientMapNode implements Node { - id: ID! - size: Int - headOfHousehold: IndividualNode -} - -type CommunicationMessageRecipientMapNodeConnection { - pageInfo: PageInfo! - edges: [CommunicationMessageRecipientMapNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type CommunicationMessageRecipientMapNodeEdge { - node: CommunicationMessageRecipientMapNode - cursor: String! -} - -type ContentTypeObjectType { - id: ID! - appLabel: String! - model: String! - paymentverificationplanSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationPlanNodeConnection! - paymentverificationSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationNodeConnection! - paymentverificationsummarySet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationSummaryNodeConnection! - ticketcomplaintdetailsSet(offset: Int, before: String, after: String, first: Int, last: Int): TicketComplaintDetailsNodeConnection! - ticketsensitivedetailsSet(offset: Int, before: String, after: String, first: Int, last: Int): TicketSensitiveDetailsNodeConnection! - logEntries(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationLogEntryNodeConnection! - name: String -} - -type CopyProgram { - validationErrors: Arg - program: ProgramNode -} - -input CopyProgramInput { - id: String! - name: String - startDate: Date - endDate: Date - description: String - budget: Decimal - frequencyOfPayments: String - sector: String - cashPlus: Boolean - populationGoal: Int - administrativeAreasOfImplementation: String - businessAreaSlug: String - dataCollectingTypeCode: String - partners: [ProgramPartnerThroughInput] - partnerAccess: String - programmeCode: String - pduFields: [PDUFieldInput] -} - -input CopyTargetPopulationInput { - id: ID - name: String - programCycleId: ID! -} - -input CopyTargetPopulationMutationInput { - targetPopulationData: CopyTargetPopulationInput - clientMutationId: String -} - -type CopyTargetPopulationMutationPayload { - targetPopulation: TargetPopulationNode - validationErrors: Arg - clientMutationId: String -} - -type CoreFieldChoiceObject { - labels: [LabelNode] - labelEn: String - value: String - admin: String - listName: String -} - -type CountAndPercentageNode { - count: Int - percentage: Float -} - -input CreateAccountabilityCommunicationMessageInput { - households: [ID] - targetPopulation: ID - registrationDataImport: ID - samplingType: SamplingChoices! - fullListArguments: AccountabilityFullListArguments - randomSamplingArguments: AccountabilityRandomSamplingArguments - title: String! - body: String! -} - -type CreateCommunicationMessageMutation { - message: CommunicationMessageNode -} - -type CreateDashboardReport { - success: Boolean -} - -input CreateDashboardReportInput { - reportTypes: [String]! - businessAreaSlug: String! - year: Int! - adminArea: ID - program: ID -} - -input CreateFeedbackInput { - issueType: String! - householdLookup: ID - individualLookup: ID - description: String! - comments: String - admin2: ID - area: String - language: String - consent: Boolean - program: ID -} - -input CreateFeedbackMessageInput { - description: String! - feedback: ID! -} - -type CreateFeedbackMessageMutation { - feedbackMessage: FeedbackMessageNode -} - -type CreateFeedbackMutation { - feedback: FeedbackNode -} - -type CreateFollowUpPaymentPlanMutation { - paymentPlan: PaymentPlanNode -} - -input CreateGrievanceTicketExtrasInput { - category: CategoryExtrasInput - issueType: IssueTypeExtrasInput -} - -input CreateGrievanceTicketInput { - description: String! - assignedTo: ID - category: Int! - issueType: Int - admin: ID - area: String - language: String! - consent: Boolean! - businessArea: ID! - linkedTickets: [ID] - extras: CreateGrievanceTicketExtrasInput - priority: Int - urgency: Int - partner: Int - program: ID - comments: String - linkedFeedbackId: ID - documentation: [GrievanceDocumentInput] -} - -type CreateGrievanceTicketMutation { - grievanceTickets: [GrievanceTicketNode] -} - -input CreatePaymentPlanInput { - businessAreaSlug: String! - targetingId: ID! - dispersionStartDate: Date! - dispersionEndDate: Date! - currency: String! -} - -type CreatePaymentPlanMutation { - paymentPlan: PaymentPlanNode -} - -input CreatePaymentVerificationInput { - sampling: String! - verificationChannel: String! - businessAreaSlug: String! - fullListArguments: FullListArguments - randomSamplingArguments: RandomSamplingArguments - rapidProArguments: RapidProArguments - cashOrPaymentPlanId: ID! -} - -type CreateProgram { - validationErrors: Arg - program: ProgramNode -} - -input CreateProgramInput { - name: String - startDate: Date - endDate: Date - description: String - budget: Decimal - frequencyOfPayments: String - sector: String - cashPlus: Boolean - populationGoal: Int - administrativeAreasOfImplementation: String - businessAreaSlug: String - dataCollectingTypeCode: String - partners: [ProgramPartnerThroughInput] - partnerAccess: String - programmeCode: String - pduFields: [PDUFieldInput] -} - -type CreateReport { - report: ReportNode -} - -input CreateReportInput { - reportType: Int! - businessAreaSlug: String! - dateFrom: Date! - dateTo: Date! - program: ID - adminArea1: ID - adminArea2: [ID] -} - -input CreateSurveyInput { - title: String! - body: String - category: String! - targetPopulation: ID - program: ID - samplingType: String! - fullListArguments: AccountabilityFullListArguments - randomSamplingArguments: AccountabilityRandomSamplingArguments - flow: String! -} - -type CreateSurveyMutation { - survey: SurveyNode -} - -input CreateTargetPopulationInput { - name: String! - targetingCriteria: TargetingCriteriaObjectType! - businessAreaSlug: String! - programId: ID! - programCycleId: ID! - excludedIds: String! - exclusionReason: String -} - -type CreateTargetPopulationMutation { - validationErrors: Arg - targetPopulation: TargetPopulationNode -} - -input CreateTicketNoteInput { - description: String! - ticket: ID! -} - -type CreateTicketNoteMutation { - grievanceTicketNote: TicketNoteNode -} - -type CreateVerificationPlanMutation { - paymentPlan: GenericPaymentPlanNode -} - -type DataCollectingTypeChoiceObject { - name: String - value: String - description: String -} - -type DataCollectingTypeNode implements Node { - id: ID! - created: DateTime! - modified: DateTime! - label: String! - code: String! - type: DataCollectingTypeType - description: String! - active: Boolean! - deprecated: Boolean! - individualFiltersAvailable: Boolean! - householdFiltersAvailable: Boolean! - recalculateComposition: Boolean! - weight: Int! - datacollectingtypeSet(offset: Int, before: String, after: String, first: Int, last: Int): DataCollectingTypeNodeConnection! - programs(offset: Int, before: String, after: String, first: Int, last: Int, name: String): ProgramNodeConnection! -} - -type DataCollectingTypeNodeConnection { - pageInfo: PageInfo! - edges: [DataCollectingTypeNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type DataCollectingTypeNodeEdge { - node: DataCollectingTypeNode - cursor: String! -} - -enum DataCollectingTypeType { - STANDARD - SOCIAL -} - -scalar Date - -scalar DateTime - -scalar Decimal - -type DeduplicationResultNode { - hitId: ID - fullName: String - score: Float - proximityToScore: Float - location: String - age: Int - duplicate: Boolean - distinct: Boolean -} - -type DeleteHouseholdApproveMutation { - grievanceTicket: GrievanceTicketNode -} - -type DeletePaymentPlanMutation { - paymentPlan: PaymentPlanNode -} - -type DeletePaymentVerificationPlan { - paymentPlan: GenericPaymentPlanNode -} - -type DeleteProgram { - ok: Boolean -} - -type DeleteRegistrationDataImport { - ok: Boolean -} - -input DeleteTargetPopulationMutationInput { - targetId: ID! - clientMutationId: String -} - -type DeleteTargetPopulationMutationPayload { - ok: Boolean - clientMutationId: String -} - -type DeliveredQuantityNode { - totalDeliveredQuantity: Decimal - currency: String -} - -input DeliveryMechanismDataObjectType { - label: String! - approveStatus: Boolean! - dataFields: [DeliveryMechanismDataPayloadFieldObjectType]! -} - -input DeliveryMechanismDataPayloadFieldObjectType { - name: String! - value: String! - previousValue: String -} - -type DeliveryMechanismNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - paymentGatewayId: String - code: String - name: String - optionalFields: [String!]! - requiredFields: [String!]! - uniqueFields: [String!]! - isActive: Boolean! - transferType: DeliveryMechanismTransferType! - financialserviceproviderSet(offset: Int, before: String, after: String, first: Int, last: Int): FinancialServiceProviderNodeConnection! - deliverymechanismperpaymentplanSet(offset: Int, before: String, after: String, first: Int, last: Int): DeliveryMechanismPerPaymentPlanNodeConnection! - paymentrecordSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentRecordNodeConnection! - paymentSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentNodeConnection! -} - -type DeliveryMechanismNodeConnection { - pageInfo: PageInfo! - edges: [DeliveryMechanismNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type DeliveryMechanismNodeEdge { - node: DeliveryMechanismNode - cursor: String! -} - -enum DeliveryMechanismPerPaymentPlanDeliveryMechanismChoice { - CARDLESS_CASH_WITHDRAWAL - CASH - CASH_BY_FSP - CHEQUE - DEPOSIT_TO_CARD - MOBILE_MONEY - PRE_PAID_CARD - REFERRAL - TRANSFER - TRANSFER_TO_ACCOUNT - VOUCHER - ATM_CARD - CASH_OVER_THE_COUNTER - TRANSFER_TO_DIGITAL_WALLET -} - -type DeliveryMechanismPerPaymentPlanNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - paymentPlan: PaymentPlanNode! - financialServiceProvider: FinancialServiceProviderNode - createdBy: UserNode! - sentDate: DateTime! - sentBy: UserNode - status: String! - deliveryMechanismChoice: DeliveryMechanismPerPaymentPlanDeliveryMechanismChoice - deliveryMechanism: DeliveryMechanismNode - deliveryMechanismOrder: Int! - sentToPaymentGateway: Boolean! - chosenConfiguration: String - name: String - code: String - order: Int - fsp: FinancialServiceProviderNode -} - -type DeliveryMechanismPerPaymentPlanNodeConnection { - pageInfo: PageInfo! - edges: [DeliveryMechanismPerPaymentPlanNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type DeliveryMechanismPerPaymentPlanNodeEdge { - node: DeliveryMechanismPerPaymentPlanNode - cursor: String! -} - -enum DeliveryMechanismTransferType { - CASH - VOUCHER - DIGITAL -} - -type DiscardPaymentVerificationPlan { - paymentPlan: GenericPaymentPlanNode -} - -type DjangoDebug { - sql: [DjangoDebugSQL] -} - -type DjangoDebugSQL { - vendor: String! - alias: String! - sql: String - duration: Float! - rawSql: String! - params: String! - startTime: Float! - stopTime: Float! - isSlow: Boolean! - isSelect: Boolean! - transId: String - transStatus: String - isoLevel: String - encoding: String -} - -type DocumentNode implements Node { - id: ID! - rdiMergeStatus: DocumentRdiMergeStatus! - isRemoved: Boolean! - isOriginal: Boolean! - createdAt: DateTime! - updatedAt: DateTime! - lastSyncAt: DateTime - documentNumber: String! - photo: String - individual: IndividualNode! - type: DocumentTypeNode! - country: String - status: DocumentStatus! - cleared: Boolean! - clearedDate: DateTime! - clearedBy: UserNode - issuanceDate: DateTime - expiryDate: DateTime - program: ProgramNode - isMigrationHandled: Boolean! - copiedFrom: DocumentNode - copiedTo(offset: Int, before: String, after: String, first: Int, last: Int): DocumentNodeConnection! - countryIso3: String -} - -type DocumentNodeConnection { - pageInfo: PageInfo! - edges: [DocumentNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type DocumentNodeEdge { - node: DocumentNode - cursor: String! -} - -enum DocumentRdiMergeStatus { - PENDING - MERGED -} - -enum DocumentStatus { - PENDING - VALID - NEED_INVESTIGATION - INVALID -} - -type DocumentTypeNode { - id: UUID! - createdAt: DateTime! - updatedAt: DateTime! - label: String! - key: String! - isIdentityDocument: Boolean! - uniqueForIndividual: Boolean! - validForDeduplication: Boolean! - documents(offset: Int, before: String, after: String, first: Int, last: Int): DocumentNodeConnection! -} - -input EditBankTransferObjectType { - id: ID! - type: String! - bankName: String! - bankAccountNumber: String! - bankBranchName: String - accountHolderName: String! -} - -input EditDeliveryMechanismDataObjectType { - id: ID! - label: String! - approveStatus: Boolean! - dataFields: [DeliveryMechanismDataPayloadFieldObjectType]! -} - -input EditIndividualDocumentObjectType { - id: ID! - country: String! - key: String! - number: String! - photo: Arg - photoraw: Arg -} - -input EditIndividualIdentityObjectType { - id: ID! - country: String! - partner: String! - number: String! -} - -input EditPaymentVerificationInput { - sampling: String! - verificationChannel: String! - businessAreaSlug: String! - fullListArguments: FullListArguments - randomSamplingArguments: RandomSamplingArguments - rapidProArguments: RapidProArguments - paymentVerificationPlanId: ID! -} - -type EditPaymentVerificationMutation { - paymentPlan: GenericPaymentPlanNode -} - -type EraseRegistrationDataImportMutation { - registrationDataImport: RegistrationDataImportNode -} - -type ExcludeHouseholdsMutation { - paymentPlan: PaymentPlanNode -} - -type ExportPDFPaymentPlanSummaryMutation { - paymentPlan: PaymentPlanNode -} - -type ExportSurveySampleMutationMutation { - survey: SurveyNode -} - -type ExportXLSXPaymentPlanPaymentListMutation { - paymentPlan: PaymentPlanNode -} - -type ExportXLSXPaymentPlanPaymentListPerFSPMutation { - paymentPlan: PaymentPlanNode -} - -type ExportXlsxPaymentVerificationPlanFile { - paymentPlan: GenericPaymentPlanNode -} - -input FSPToDeliveryMechanismMappingInput { - fspId: ID! - deliveryMechanism: String! - chosenConfiguration: String - order: Int! -} - -enum FeedbackIssueType { - POSITIVE_FEEDBACK - NEGATIVE_FEEDBACK -} - -type FeedbackMessageNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - description: String! - createdBy: UserNode -} - -type FeedbackMessageNodeConnection { - pageInfo: PageInfo! - edges: [FeedbackMessageNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type FeedbackMessageNodeEdge { - node: FeedbackMessageNode - cursor: String! -} - -type FeedbackNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - unicefId: String - businessArea: UserBusinessAreaNode! - issueType: FeedbackIssueType! - householdLookup: HouseholdNode - individualLookup: IndividualNode - description: String! - comments: String - admin2: AreaNode - area: String! - language: String! - consent: Boolean! - program: ProgramNode - createdBy: UserNode - linkedGrievance: GrievanceTicketNode - isOriginal: Boolean! - isMigrationHandled: Boolean! - migratedAt: DateTime - copiedFrom: FeedbackNode - copiedTo(offset: Int, before: String, after: String, first: Int, last: Int): FeedbackNodeConnection! - feedbackMessages(offset: Int, before: String, after: String, first: Int, last: Int): FeedbackMessageNodeConnection! - adminUrl: String -} - -type FeedbackNodeConnection { - pageInfo: PageInfo! - edges: [FeedbackNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type FeedbackNodeEdge { - node: FeedbackNode - cursor: String! -} - -type FieldAttributeNode { - id: String - type: String - name: String - labels: [LabelNode] - labelEn: String - hint: String - required: Boolean - choices: [CoreFieldChoiceObject] - associatedWith: String - isFlexField: Boolean - pduData: PeriodicFieldDataNode -} - -type FilteredActionsListNode { - approval: [ApprovalNode] - authorization: [ApprovalNode] - financeRelease: [ApprovalNode] - reject: [ApprovalNode] -} - -type FinalizeTargetPopulationMutation { - targetPopulation: TargetPopulationNode -} - -enum FinancialServiceProviderCommunicationChannel { - API - SFTP - XLSX -} - -type FinancialServiceProviderNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - allowedBusinessAreas(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID): UserBusinessAreaNodeConnection! - createdBy: UserNode - name: String! - visionVendorNumber: String! - deliveryMechanismsChoices: [String!] - deliveryMechanisms(offset: Int, before: String, after: String, first: Int, last: Int): DeliveryMechanismNodeConnection! - distributionLimit: Float - communicationChannel: FinancialServiceProviderCommunicationChannel! - dataTransferConfiguration: JSONString - xlsxTemplates(offset: Int, before: String, after: String, first: Int, last: Int): FinancialServiceProviderXlsxTemplateNodeConnection! - paymentGatewayId: String - deliveryMechanismsPerPaymentPlan(offset: Int, before: String, after: String, first: Int, last: Int): DeliveryMechanismPerPaymentPlanNodeConnection! - paymentSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentNodeConnection! - fullName: String - isPaymentGateway: Boolean -} - -type FinancialServiceProviderNodeConnection { - pageInfo: PageInfo! - edges: [FinancialServiceProviderNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type FinancialServiceProviderNodeEdge { - node: FinancialServiceProviderNode - cursor: String! -} - -type FinancialServiceProviderXlsxTemplateNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - createdBy: UserNode - name: String! - columns: [String] - coreFields: [String!]! - flexFields: [String!]! - financialServiceProviders(offset: Int, before: String, after: String, first: Int, last: Int): FinancialServiceProviderNodeConnection! -} - -type FinancialServiceProviderXlsxTemplateNodeConnection { - pageInfo: PageInfo! - edges: [FinancialServiceProviderXlsxTemplateNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type FinancialServiceProviderXlsxTemplateNodeEdge { - node: FinancialServiceProviderXlsxTemplateNode - cursor: String! -} - -type FinishPaymentVerificationPlan { - paymentPlan: GenericPaymentPlanNode -} - -enum FlexFieldClassificationChoices { - NOT_FLEX_FIELD - FLEX_FIELD_NOT_PDU - FLEX_FIELD_PDU -} - -scalar FlexFieldsScalar - -type FspChoice { - id: String - name: String - configurations: [FspConfiguration] -} - -type FspChoices { - deliveryMechanism: String - fsps: [FspChoice] -} - -type FspConfiguration { - id: String - key: String - label: String -} - -input FullListArguments { - excludedAdminAreas: [String] -} - -type GenericPaymentNode { - id: String - objType: String - unicefId: String - currency: String - deliveredQuantity: Float - deliveredQuantityUsd: Float - household: HouseholdNode -} - -type GenericPaymentPlanNode { - id: String - objType: String - paymentVerificationSummary: PaymentVerificationSummaryNode - availablePaymentRecordsCount: Int - verificationPlans(offset: Int, before: String, after: String, first: Int, last: Int, programId: String): PaymentVerificationPlanNodeConnection - statusDate: DateTime - status: String - bankReconciliationSuccess: Int - bankReconciliationError: Int - deliveryType: String - totalNumberOfHouseholds: Int - currency: String - totalDeliveredQuantity: Float - totalEntitledQuantity: Float - totalUndeliveredQuantity: Float - canCreatePaymentVerificationPlan: Boolean -} - -scalar GeoJSON - -input GetAccountabilityCommunicationMessageSampleSizeInput { - households: [ID] - targetPopulation: ID - registrationDataImport: ID - samplingType: SamplingChoices! - fullListArguments: AccountabilityFullListArguments - randomSamplingArguments: AccountabilityRandomSamplingArguments -} - -input GetCashplanVerificationSampleSizeInput { - cashOrPaymentPlanId: ID - paymentVerificationPlanId: ID - sampling: String! - verificationChannel: String! - businessAreaSlug: String! - fullListArguments: FullListArguments - randomSamplingArguments: RandomSamplingArguments - rapidProArguments: RapidProArguments -} - -type GetCashplanVerificationSampleSizeObject { - paymentRecordCount: Int - sampleSize: Int -} - -type GetCommunicationMessageSampleSizeNode { - numberOfRecipients: Int - sampleSize: Int -} - -input GrievanceComplaintTicketExtras { - household: ID - individual: ID - paymentRecord: [ID] -} - -input GrievanceDocumentInput { - name: String! - file: Upload! -} - -type GrievanceDocumentNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - name: String - createdBy: UserNode - grievanceTicket: GrievanceTicketNode - fileSize: Int - contentType: String! - filePath: String - fileName: String -} - -type GrievanceDocumentNodeConnection { - pageInfo: PageInfo! - edges: [GrievanceDocumentNodeEdge]! -} - -type GrievanceDocumentNodeEdge { - node: GrievanceDocumentNode - cursor: String! -} - -input GrievanceDocumentUpdateInput { - id: ID! - name: String - file: Upload -} - -type GrievanceStatusChangeMutation { - grievanceTicket: GrievanceTicketNode -} - -type GrievanceTicketNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - version: BigInt! - unicefId: String - userModified: DateTime - lastNotificationSent: DateTime - createdBy: UserNode - assignedTo: UserNode - status: Int! - category: Int! - issueType: Int - description: String! - admin2: AreaNode - area: String! - language: String! - consent: Boolean! - businessArea: UserBusinessAreaNode! - linkedTickets: [GrievanceTicketNode] - registrationDataImport: RegistrationDataImportNode - extras: JSONString! - ignored: Boolean! - householdUnicefId: String - priority: Int - urgency: Int - partner: PartnerType - programs: [ProgramNode] - comments: String - isOriginal: Boolean! - isMigrationHandled: Boolean! - migratedAt: DateTime - copiedFrom: GrievanceTicketNode - copiedTo(offset: Int, before: String, after: String, first: Int, last: Int): GrievanceTicketNodeConnection! - ticketNotes(offset: Int, before: String, after: String, first: Int, last: Int): TicketNoteNodeConnection! - complaintTicketDetails: TicketComplaintDetailsNode - sensitiveTicketDetails: TicketSensitiveDetailsNode - householdDataUpdateTicketDetails: TicketHouseholdDataUpdateDetailsNode - individualDataUpdateTicketDetails: TicketIndividualDataUpdateDetailsNode - addIndividualTicketDetails: TicketAddIndividualDetailsNode - deleteIndividualTicketDetails: TicketDeleteIndividualDetailsNode - deleteHouseholdTicketDetails: TicketDeleteHouseholdDetailsNode - systemFlaggingTicketDetails: TicketSystemFlaggingDetailsNode - needsAdjudicationTicketDetails: TicketNeedsAdjudicationDetailsNode - paymentVerificationTicketDetails: TicketPaymentVerificationDetailsNode - positiveFeedbackTicketDetails: TicketPositiveFeedbackDetailsNode - negativeFeedbackTicketDetails: TicketNegativeFeedbackDetailsNode - referralTicketDetails: TicketReferralDetailsNode - supportDocuments(offset: Int, before: String, after: String, first: Int, last: Int): GrievanceDocumentNodeConnection! - feedback: FeedbackNode - adminUrl: String - household: HouseholdNode - individual: IndividualNode - paymentRecord: PaymentRecordAndPaymentNode - admin: String - existingTickets: [GrievanceTicketNode] - relatedTickets: [GrievanceTicketNode] - totalDays: String - documentation: [GrievanceDocumentNode] -} - -type GrievanceTicketNodeConnection { - pageInfo: PageInfo! - edges: [GrievanceTicketNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type GrievanceTicketNodeEdge { - node: GrievanceTicketNode - cursor: String! -} - -type GroupAttributeNode { - id: UUID! - name: String! - label: JSONString! - flexAttributes(flexField: Boolean): [FieldAttributeNode] - labelEn: String -} - -enum HouseholdCollectIndividualData { - A_ - A_2 - A_1 - A_3 - A_0 -} - -enum HouseholdCollectType { - STANDARD - SINGLE -} - -enum HouseholdConsentSharing { - A_ - GOVERNMENT_PARTNER - HUMANITARIAN_PARTNER - PRIVATE_PARTNER - UNICEF -} - -enum HouseholdCurrency { - A_ - AED - AFN - ALL - AMD - ANG - AOA - ARS - AUD - AWG - AZN - BAM - BBD - BDT - BGN - BHD - BIF - BMD - BND - BOB - BOV - BRL - BSD - BTN - BWP - BYN - BZD - CAD - CDF - CHF - CLP - CNY - COP - CRC - CUC - CUP - CVE - CZK - DJF - DKK - DOP - DZD - EGP - ERN - ETB - EUR - FJD - FKP - GBP - GEL - GHS - GIP - GMD - GNF - GTQ - GYD - HKD - HNL - HRK - HTG - HUF - IDR - ILS - INR - IQD - IRR - ISK - JMD - JOD - JPY - KES - KGS - KHR - KMF - KPW - KRW - KWD - KYD - KZT - LAK - LBP - LKR - LRD - LSL - LYD - MAD - MDL - MGA - MKD - MMK - MNT - MOP - MRU - MUR - MVR - MWK - MXN - MYR - MZN - NAD - NGN - NIO - NOK - NPR - NZD - OMR - PAB - PEN - PGK - PHP - PKR - PLN - PYG - QAR - RON - RSD - RUB - RWF - SAR - SBD - SCR - SDG - SEK - SGD - SHP - SLL - SOS - SRD - SSP - STN - SVC - SYP - SZL - THB - TJS - TMT - TND - TOP - TRY - TTD - TWD - TZS - UAH - UGX - USD - UYU - UYW - UZS - VES - VND - VUV - WST - XAF - XAG - XAU - XCD - XOF - XPF - YER - ZAR - ZMW - ZWL - USDC -} - -type HouseholdDataChangeApproveMutation { - grievanceTicket: GrievanceTicketNode -} - -input HouseholdDataUpdateIssueTypeExtras { - household: ID! - householdData: HouseholdUpdateDataObjectType! -} - -input HouseholdDeleteIssueTypeExtras { - household: ID! -} - -type HouseholdNode implements Node { - id: ID! - size: Int - headOfHousehold: IndividualNode - rdiMergeStatus: HouseholdRdiMergeStatus! - isOriginal: Boolean! - createdAt: DateTime! - updatedAt: DateTime! - isRemoved: Boolean! - removedDate: DateTime - lastSyncAt: DateTime - version: BigInt! - unicefId: String - withdrawn: Boolean! - withdrawnDate: DateTime - consentSign: String! - consent: Boolean - consentSharing: [String] - residenceStatus: String - countryOrigin: String - country: String - address: String! - zipCode: String - adminArea: AreaNode - admin1: AreaNode - admin2: AreaNode - admin3: AreaNode - admin4: AreaNode - geopoint: GeoJSON - femaleAgeGroup05Count: Int - femaleAgeGroup611Count: Int - femaleAgeGroup1217Count: Int - femaleAgeGroup1859Count: Int - femaleAgeGroup60Count: Int - pregnantCount: Int - maleAgeGroup05Count: Int - maleAgeGroup611Count: Int - maleAgeGroup1217Count: Int - maleAgeGroup1859Count: Int - maleAgeGroup60Count: Int - femaleAgeGroup05DisabledCount: Int - femaleAgeGroup611DisabledCount: Int - femaleAgeGroup1217DisabledCount: Int - femaleAgeGroup1859DisabledCount: Int - femaleAgeGroup60DisabledCount: Int - maleAgeGroup05DisabledCount: Int - maleAgeGroup611DisabledCount: Int - maleAgeGroup1217DisabledCount: Int - maleAgeGroup1859DisabledCount: Int - maleAgeGroup60DisabledCount: Int - childrenCount: Int - maleChildrenCount: Int - femaleChildrenCount: Int - childrenDisabledCount: Int - maleChildrenDisabledCount: Int - femaleChildrenDisabledCount: Int - registrationDataImport: RegistrationDataImportNode - returnee: Boolean - flexFields: FlexFieldsScalar - firstRegistrationDate: DateTime! - lastRegistrationDate: DateTime! - fchildHoh: Boolean - childHoh: Boolean - businessArea: UserBusinessAreaNode! - start: DateTime - deviceid: String! - nameEnumerator: String! - orgEnumerator: HouseholdOrgEnumerator! - orgNameEnumerator: String! - village: String! - registrationMethod: HouseholdRegistrationMethod! - collectIndividualData: HouseholdCollectIndividualData! - currency: String - unhcrId: String! - userFields: JSONString! - detailId: String - registrationId: String - programRegistrationId: String - totalCashReceivedUsd: Decimal - totalCashReceived: Decimal - familyId: String - program: ProgramNode - copiedFrom: HouseholdNode - originUnicefId: String - isMigrationHandled: Boolean! - migratedAt: DateTime - isRecalculatedGroupAges: Boolean! - collectType: HouseholdCollectType! - koboSubmissionUuid: UUID - koboSubmissionTime: DateTime - enumeratorRecId: Int - misUnicefId: String - flexRegistrationsRecordId: Int - representatives(offset: Int, before: String, after: String, first: Int, last: Int): IndividualNodeConnection! - programs(offset: Int, before: String, after: String, first: Int, last: Int, name: String): ProgramNodeConnection! - copiedTo(offset: Int, before: String, after: String, first: Int, last: Int): HouseholdNodeConnection! - individualsAndRoles: [IndividualRoleInHouseholdNode!]! - individuals(offset: Int, before: String, after: String, first: Int, last: Int, household_Id: UUID, businessArea: String, fullName: String, fullName_Startswith: String, fullName_Endswith: String, sex: [String], household_AdminArea: ID, withdrawn: Boolean, program: ID, age: String, programs: [ID], search: String, documentType: String, documentNumber: String, lastRegistrationDate: String, admin1: [ID], admin2: [ID], status: [String], excludedId: String, flags: [String], isActiveProgram: Boolean, orderBy: String): IndividualNodeConnection - paymentrecordSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentRecordNodeConnection! - paymentSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentNodeConnection! - complaintTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketComplaintDetailsNodeConnection! - sensitiveTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketSensitiveDetailsNodeConnection! - householdDataUpdateTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketHouseholdDataUpdateDetailsNodeConnection! - addIndividualTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketAddIndividualDetailsNodeConnection! - deleteHouseholdTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketDeleteHouseholdDetailsNodeConnection! - positiveFeedbackTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketPositiveFeedbackDetailsNodeConnection! - negativeFeedbackTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketNegativeFeedbackDetailsNodeConnection! - referralTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketReferralDetailsNodeConnection! - targetPopulations(offset: Int, before: String, after: String, first: Int, last: Int, program: [ID], createdAt: DateTime, createdAt_Lte: DateTime, createdAt_Gte: DateTime, updatedAt: DateTime, updatedAt_Lte: DateTime, updatedAt_Gte: DateTime, status: String, households: [ID], name: String, createdByName: String, totalHouseholdsCountMin: Int, totalHouseholdsCountMax: Int, totalIndividualsCountMin: Int, totalIndividualsCountMax: Int, businessArea: String, createdAtRange: String, paymentPlanApplicable: Boolean, statusNot: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, programCycle: String, orderBy: String): TargetPopulationNodeConnection! - selections: [HouseholdSelectionNode!]! - messages(offset: Int, before: String, after: String, first: Int, last: Int): CommunicationMessageNodeConnection! - feedbacks(offset: Int, before: String, after: String, first: Int, last: Int): FeedbackNodeConnection! - surveys(offset: Int, before: String, after: String, first: Int, last: Int): SurveyNodeConnection! - adminUrl: String - selection: HouseholdSelectionNode - sanctionListPossibleMatch: Boolean - sanctionListConfirmedMatch: Boolean - hasDuplicates: Boolean - adminAreaTitle: String - status: String - deliveredQuantities: [DeliveredQuantityNode] - activeIndividualsCount: Int - importId: String -} - -type HouseholdNodeConnection { - pageInfo: PageInfo! - edges: [HouseholdNodeEdge]! - totalCount: Int - individualsCount: Int - edgeCount: Int -} - -type HouseholdNodeEdge { - node: HouseholdNode - cursor: String! -} - -enum HouseholdOrgEnumerator { - A_ - PARTNER - UNICEF -} - -enum HouseholdRdiMergeStatus { - PENDING - MERGED -} - -enum HouseholdRegistrationMethod { - A_ - COMMUNITY - HH_REGISTRATION -} - -enum HouseholdResidenceStatus { - A_ - IDP - REFUGEE - OTHERS_OF_CONCERN - HOST - NON_HOST - RETURNEE -} - -type HouseholdSelectionNode { - id: UUID! - createdAt: DateTime! - updatedAt: DateTime! - household: HouseholdNode! - targetPopulation: TargetPopulationNode! - vulnerabilityScore: Float - isOriginal: Boolean! - isMigrationHandled: Boolean! -} - -input HouseholdUpdateDataObjectType { - adminAreaTitle: String - status: String - consent: Boolean - consentSharing: [String] - residenceStatus: String - countryOrigin: String - country: String - size: Int - address: String - femaleAgeGroup05Count: Int - femaleAgeGroup611Count: Int - femaleAgeGroup1217Count: Int - femaleAgeGroup1859Count: Int - femaleAgeGroup60Count: Int - pregnantCount: Int - maleAgeGroup05Count: Int - maleAgeGroup611Count: Int - maleAgeGroup1217Count: Int - maleAgeGroup1859Count: Int - maleAgeGroup60Count: Int - femaleAgeGroup05DisabledCount: Int - femaleAgeGroup611DisabledCount: Int - femaleAgeGroup1217DisabledCount: Int - femaleAgeGroup1859DisabledCount: Int - femaleAgeGroup60DisabledCount: Int - maleAgeGroup05DisabledCount: Int - maleAgeGroup611DisabledCount: Int - maleAgeGroup1217DisabledCount: Int - maleAgeGroup1859DisabledCount: Int - maleAgeGroup60DisabledCount: Int - returnee: Boolean - fchildHoh: Boolean - childHoh: Boolean - start: DateTime - end: DateTime - nameEnumerator: String - orgEnumerator: String - orgNameEnumerator: String - village: String - registrationMethod: String - collectIndividualData: String - currency: String - unhcrId: String - flexFields: Arg -} - -enum ImportDataDataType { - XLSX - JSON - FLEX -} - -type ImportDataNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - status: ImportDataStatus! - businessAreaSlug: String! - file: String - dataType: ImportDataDataType! - numberOfHouseholds: Int - numberOfIndividuals: Int - error: String! - validationErrors: String! - deliveryMechanismsValidationErrors: String! - createdById: UUID - registrationDataImportHope: RegistrationDataImportNode - koboimportdata: KoboImportDataNode - registrationDataImport: RegistrationDataImportDatahubNode - xlsxValidationErrors: [XlsxRowErrorNode] -} - -enum ImportDataStatus { - PENDING - RUNNING - FINISHED - ERROR - VALIDATION_ERROR - DELIVERY_MECHANISMS_VALIDATION_ERROR -} - -type ImportXLSXPaymentPlanPaymentListMutation { - paymentPlan: PaymentPlanNode - errors: [XlsxErrorNode] -} - -type ImportXLSXPaymentPlanPaymentListPerFSPMutation { - paymentPlan: PaymentPlanNode - errors: [XlsxErrorNode] -} - -type ImportXlsxPaymentVerificationPlanFile { - paymentPlan: GenericPaymentPlanNode - errors: [XlsxErrorNode] -} - -type ImportedDocumentNode implements Node { - id: ID! - rdiMergeStatus: DocumentRdiMergeStatus! - isRemoved: Boolean! - isOriginal: Boolean! - createdAt: DateTime! - updatedAt: DateTime! - lastSyncAt: DateTime - documentNumber: String! - photo: String - individual: IndividualNode! - type: DocumentTypeNode! - country: String - status: DocumentStatus! - cleared: Boolean! - clearedDate: DateTime! - clearedBy: UserNode - issuanceDate: DateTime - expiryDate: DateTime - program: ProgramNode - isMigrationHandled: Boolean! - copiedFrom: DocumentNode -} - -type ImportedDocumentNodeConnection { - pageInfo: PageInfo! - edges: [ImportedDocumentNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type ImportedDocumentNodeEdge { - node: ImportedDocumentNode - cursor: String! -} - -type ImportedHouseholdNode implements Node { - id: ID! - size: Int - headOfHousehold: IndividualNode - rdiMergeStatus: HouseholdRdiMergeStatus! - isOriginal: Boolean! - createdAt: DateTime! - updatedAt: DateTime! - isRemoved: Boolean! - removedDate: DateTime - lastSyncAt: DateTime - version: BigInt! - unicefId: String - withdrawn: Boolean! - withdrawnDate: DateTime - consentSign: String! - consent: Boolean - consentSharing: HouseholdConsentSharing! - residenceStatus: HouseholdResidenceStatus - countryOrigin: String - country: String - address: String! - zipCode: String - adminArea: AreaNode - admin1: AreaNode - admin2: AreaNode - admin3: AreaNode - admin4: AreaNode - geopoint: GeoJSON - femaleAgeGroup05Count: Int - femaleAgeGroup611Count: Int - femaleAgeGroup1217Count: Int - femaleAgeGroup1859Count: Int - femaleAgeGroup60Count: Int - pregnantCount: Int - maleAgeGroup05Count: Int - maleAgeGroup611Count: Int - maleAgeGroup1217Count: Int - maleAgeGroup1859Count: Int - maleAgeGroup60Count: Int - femaleAgeGroup05DisabledCount: Int - femaleAgeGroup611DisabledCount: Int - femaleAgeGroup1217DisabledCount: Int - femaleAgeGroup1859DisabledCount: Int - femaleAgeGroup60DisabledCount: Int - maleAgeGroup05DisabledCount: Int - maleAgeGroup611DisabledCount: Int - maleAgeGroup1217DisabledCount: Int - maleAgeGroup1859DisabledCount: Int - maleAgeGroup60DisabledCount: Int - childrenCount: Int - maleChildrenCount: Int - femaleChildrenCount: Int - childrenDisabledCount: Int - maleChildrenDisabledCount: Int - femaleChildrenDisabledCount: Int - registrationDataImport: RegistrationDataImportNode - returnee: Boolean - flexFields: Arg - firstRegistrationDate: DateTime! - lastRegistrationDate: DateTime! - fchildHoh: Boolean - childHoh: Boolean - businessArea: UserBusinessAreaNode! - start: DateTime - deviceid: String! - nameEnumerator: String! - orgEnumerator: HouseholdOrgEnumerator! - orgNameEnumerator: String! - village: String! - registrationMethod: HouseholdRegistrationMethod! - collectIndividualData: HouseholdCollectIndividualData! - currency: HouseholdCurrency! - unhcrId: String! - userFields: JSONString! - detailId: String - registrationId: String - programRegistrationId: String - totalCashReceivedUsd: Float - totalCashReceived: Float - familyId: String - program: ProgramNode - copiedFrom: HouseholdNode - originUnicefId: String - isMigrationHandled: Boolean! - migratedAt: DateTime - isRecalculatedGroupAges: Boolean! - collectType: HouseholdCollectType! - koboSubmissionUuid: UUID - koboSubmissionTime: DateTime - enumeratorRecId: Int - misUnicefId: String - flexRegistrationsRecordId: Int - individuals(offset: Int, before: String, after: String, first: Int, last: Int): ImportedIndividualNodeConnection - hasDuplicates: Boolean - importId: String -} - -type ImportedHouseholdNodeConnection { - pageInfo: PageInfo! - edges: [ImportedHouseholdNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type ImportedHouseholdNodeEdge { - node: ImportedHouseholdNode - cursor: String! -} - -type ImportedIndividualIdentityNode implements Node { - id: ID! - created: DateTime! - modified: DateTime! - rdiMergeStatus: IndividualIdentityRdiMergeStatus! - isRemoved: Boolean! - isOriginal: Boolean! - individual: IndividualNode! - number: String! - partner: String - country: String - isMigrationHandled: Boolean! - copiedFrom: IndividualIdentityNode - copiedTo(offset: Int, before: String, after: String, first: Int, last: Int): IndividualIdentityNodeConnection! -} - -type ImportedIndividualIdentityNodeConnection { - pageInfo: PageInfo! - edges: [ImportedIndividualIdentityNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type ImportedIndividualIdentityNodeEdge { - node: ImportedIndividualIdentityNode - cursor: String! -} - -type ImportedIndividualNode implements Node { - id: ID! - rdiMergeStatus: IndividualRdiMergeStatus! - isOriginal: Boolean! - createdAt: DateTime! - updatedAt: DateTime! - isRemoved: Boolean! - removedDate: DateTime - lastSyncAt: DateTime - version: BigInt! - unicefId: String - duplicate: Boolean! - duplicateDate: DateTime - withdrawn: Boolean! - withdrawnDate: DateTime - individualId: String! - photo: String! - fullName: String! - givenName: String! - middleName: String! - familyName: String! - sex: IndividualSex! - birthDate: Date! - estimatedBirthDate: Boolean - maritalStatus: IndividualMaritalStatus! - phoneNo: String! - phoneNoValid: Boolean - phoneNoAlternative: String! - phoneNoAlternativeValid: Boolean - email: String - paymentDeliveryPhoneNo: String - relationship: String - household: HouseholdNode - registrationDataImport: RegistrationDataImportNode - workStatus: String! - firstRegistrationDate: Date! - lastRegistrationDate: Date! - flexFields: FlexFieldsScalar - userFields: JSONString! - enrolledInNutritionProgramme: Boolean - administrationOfRutf: Boolean - deduplicationGoldenRecordStatus: IndividualDeduplicationGoldenRecordStatus! - deduplicationBatchStatus: IndividualDeduplicationBatchStatus! - deduplicationGoldenRecordResults: [DeduplicationResultNode] - deduplicationBatchResults: [DeduplicationResultNode] - importedIndividualId: UUID - sanctionListPossibleMatch: Boolean! - sanctionListConfirmedMatch: Boolean! - pregnant: Boolean - disability: IndividualDisability! - observedDisability: [String] - disabilityCertificatePicture: String - seeingDisability: String! - hearingDisability: String! - physicalDisability: String! - memoryDisability: String! - selfcareDisability: String! - commsDisability: String! - whoAnswersPhone: String! - whoAnswersAltPhone: String! - businessArea: UserBusinessAreaNode! - fchildHoh: Boolean! - childHoh: Boolean! - detailId: String - registrationId: String - programRegistrationId: String - preferredLanguage: String - relationshipConfirmed: Boolean! - ageAtRegistration: Int - walletName: String! - blockchainName: String! - walletAddress: String! - program: ProgramNode - copiedFrom: IndividualNode - originUnicefId: String - isMigrationHandled: Boolean! - migratedAt: DateTime - misUnicefId: String - role: String - age: Int - importId: String - documents(offset: Int, before: String, after: String, first: Int, last: Int): ImportedDocumentNodeConnection - identities(offset: Int, before: String, after: String, first: Int, last: Int): ImportedIndividualIdentityNodeConnection -} - -type ImportedIndividualNodeConnection { - pageInfo: PageInfo! - edges: [ImportedIndividualNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type ImportedIndividualNodeEdge { - node: ImportedIndividualNode - cursor: String! -} - -type IndividualDataChangeApproveMutation { - grievanceTicket: GrievanceTicketNode -} - -input IndividualDataUpdateIssueTypeExtras { - individual: ID! - individualData: IndividualUpdateDataObjectType! -} - -enum IndividualDeduplicationBatchStatus { - DUPLICATE_IN_BATCH - NOT_PROCESSED - SIMILAR_IN_BATCH - UNIQUE_IN_BATCH -} - -enum IndividualDeduplicationGoldenRecordStatus { - DUPLICATE - NEEDS_ADJUDICATION - NOT_PROCESSED - POSTPONE - UNIQUE -} - -input IndividualDeleteIssueTypeExtras { - individual: ID! -} - -enum IndividualDisability { - DISABLED - NOT_DISABLED -} - -input IndividualDocumentObjectType { - country: String! - key: String! - number: String! - photo: Arg - photoraw: Arg -} - -type IndividualIdentityNode implements Node { - id: ID! - created: DateTime! - modified: DateTime! - rdiMergeStatus: IndividualIdentityRdiMergeStatus! - isRemoved: Boolean! - isOriginal: Boolean! - individual: IndividualNode! - number: String! - partner: String - country: String - isMigrationHandled: Boolean! - copiedFrom: IndividualIdentityNode - copiedTo(offset: Int, before: String, after: String, first: Int, last: Int): IndividualIdentityNodeConnection! - countryIso3: String -} - -type IndividualIdentityNodeConnection { - pageInfo: PageInfo! - edges: [IndividualIdentityNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type IndividualIdentityNodeEdge { - node: IndividualIdentityNode - cursor: String! -} - -input IndividualIdentityObjectType { - country: String! - partner: String! - number: String! -} - -enum IndividualIdentityRdiMergeStatus { - PENDING - MERGED -} - -enum IndividualMaritalStatus { - A_ - DIVORCED - MARRIED - SEPARATED - SINGLE - WIDOWED -} - -type IndividualNode implements Node { - id: ID! - rdiMergeStatus: IndividualRdiMergeStatus! - isOriginal: Boolean! - createdAt: DateTime! - updatedAt: DateTime! - isRemoved: Boolean! - removedDate: DateTime - lastSyncAt: DateTime - version: BigInt! - unicefId: String - duplicate: Boolean! - duplicateDate: DateTime - withdrawn: Boolean! - withdrawnDate: DateTime - individualId: String! - photo: String - fullName: String! - givenName: String! - middleName: String! - familyName: String! - sex: IndividualSex! - birthDate: Date! - estimatedBirthDate: Boolean - maritalStatus: IndividualMaritalStatus! - phoneNo: String! - phoneNoValid: Boolean - phoneNoAlternative: String! - phoneNoAlternativeValid: Boolean - email: String! - paymentDeliveryPhoneNo: String - relationship: IndividualRelationship - household: HouseholdNode - registrationDataImport: RegistrationDataImportNode - workStatus: String! - firstRegistrationDate: Date! - lastRegistrationDate: Date! - flexFields: FlexFieldsScalar - userFields: JSONString! - enrolledInNutritionProgramme: Boolean - administrationOfRutf: Boolean - deduplicationGoldenRecordStatus: IndividualDeduplicationGoldenRecordStatus! - deduplicationBatchStatus: IndividualDeduplicationBatchStatus! - deduplicationGoldenRecordResults: [DeduplicationResultNode] - deduplicationBatchResults: [DeduplicationResultNode] - importedIndividualId: UUID - sanctionListPossibleMatch: Boolean! - sanctionListConfirmedMatch: Boolean! - pregnant: Boolean - disability: IndividualDisability! - observedDisability: [String] - disabilityCertificatePicture: String - seeingDisability: String! - hearingDisability: String! - physicalDisability: String! - memoryDisability: String! - selfcareDisability: String! - commsDisability: String! - whoAnswersPhone: String! - whoAnswersAltPhone: String! - businessArea: UserBusinessAreaNode! - fchildHoh: Boolean! - childHoh: Boolean! - detailId: String - registrationId: String - programRegistrationId: String - preferredLanguage: String - relationshipConfirmed: Boolean! - ageAtRegistration: Int - walletName: String! - blockchainName: String! - walletAddress: String! - program: ProgramNode - copiedFrom: IndividualNode - originUnicefId: String - isMigrationHandled: Boolean! - migratedAt: DateTime - misUnicefId: String - representedHouseholds(offset: Int, before: String, after: String, first: Int, last: Int): HouseholdNodeConnection! - headingHousehold: HouseholdNode - documents(offset: Int, before: String, after: String, first: Int, last: Int): DocumentNodeConnection! - identities(offset: Int, before: String, after: String, first: Int, last: Int): IndividualIdentityNodeConnection! - householdsAndRoles: [IndividualRoleInHouseholdNode!]! - copiedTo(offset: Int, before: String, after: String, first: Int, last: Int): IndividualNodeConnection! - bankAccountInfo: BankAccountInfoNode - paymentrecordSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentRecordNodeConnection! - collectorPayments(offset: Int, before: String, after: String, first: Int, last: Int): PaymentNodeConnection! - paymentSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentNodeConnection! - complaintTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketComplaintDetailsNodeConnection! - sensitiveTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketSensitiveDetailsNodeConnection! - individualDataUpdateTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketIndividualDataUpdateDetailsNodeConnection! - deleteIndividualTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketDeleteIndividualDetailsNodeConnection! - ticketsystemflaggingdetailsSet(offset: Int, before: String, after: String, first: Int, last: Int): TicketSystemFlaggingDetailsNodeConnection! - ticketGoldenRecords(offset: Int, before: String, after: String, first: Int, last: Int): TicketNeedsAdjudicationDetailsNodeConnection! - ticketDuplicates(offset: Int, before: String, after: String, first: Int, last: Int): TicketNeedsAdjudicationDetailsNodeConnection! - selectedDistinct(offset: Int, before: String, after: String, first: Int, last: Int): TicketNeedsAdjudicationDetailsNodeConnection! - ticketSelected(offset: Int, before: String, after: String, first: Int, last: Int): TicketNeedsAdjudicationDetailsNodeConnection! - positiveFeedbackTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketPositiveFeedbackDetailsNodeConnection! - negativeFeedbackTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketNegativeFeedbackDetailsNodeConnection! - referralTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketReferralDetailsNodeConnection! - feedbacks(offset: Int, before: String, after: String, first: Int, last: Int): FeedbackNodeConnection! - adminUrl: String - status: String - role: String - age: Int - sanctionListLastCheck: DateTime - paymentChannels: [BankAccountInfoNode] -} - -type IndividualNodeConnection { - pageInfo: PageInfo! - edges: [IndividualNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type IndividualNodeEdge { - node: IndividualNode - cursor: String! -} - -enum IndividualRdiMergeStatus { - PENDING - MERGED -} - -enum IndividualRelationship { - UNKNOWN - AUNT_UNCLE - BROTHER_SISTER - COUSIN - DAUGHTERINLAW_SONINLAW - GRANDDAUGHER_GRANDSON - GRANDMOTHER_GRANDFATHER - HEAD - MOTHER_FATHER - MOTHERINLAW_FATHERINLAW - NEPHEW_NIECE - NON_BENEFICIARY - OTHER - SISTERINLAW_BROTHERINLAW - SON_DAUGHTER - WIFE_HUSBAND - FOSTER_CHILD - FREE_UNION -} - -type IndividualRoleInHouseholdNode { - id: UUID! - rdiMergeStatus: IndividualRoleInHouseholdRdiMergeStatus! - isRemoved: Boolean! - isOriginal: Boolean! - createdAt: DateTime! - updatedAt: DateTime! - lastSyncAt: DateTime - individual: IndividualNode! - household: HouseholdNode! - role: IndividualRoleInHouseholdRole - isMigrationHandled: Boolean! - migratedAt: DateTime - copiedFrom: IndividualRoleInHouseholdNode - copiedTo: [IndividualRoleInHouseholdNode!]! -} - -enum IndividualRoleInHouseholdRdiMergeStatus { - PENDING - MERGED -} - -enum IndividualRoleInHouseholdRole { - NO_ROLE - ALTERNATE - PRIMARY -} - -enum IndividualSex { - MALE - FEMALE -} - -input IndividualUpdateDataObjectType { - status: String - fullName: String - givenName: String - middleName: String - familyName: String - sex: String - birthDate: Date - estimatedBirthDate: Boolean - maritalStatus: String - phoneNo: String - phoneNoAlternative: String - email: String - relationship: String - disability: String - workStatus: String - enrolledInNutritionProgramme: Boolean - administrationOfRutf: Boolean - pregnant: Boolean - observedDisability: [String] - seeingDisability: String - hearingDisability: String - physicalDisability: String - memoryDisability: String - selfcareDisability: String - commsDisability: String - whoAnswersPhone: String - whoAnswersAltPhone: String - role: String - documents: [IndividualDocumentObjectType] - documentsToRemove: [ID] - documentsToEdit: [EditIndividualDocumentObjectType] - identities: [IndividualIdentityObjectType] - identitiesToRemove: [ID] - identitiesToEdit: [EditIndividualIdentityObjectType] - paymentChannels: [BankTransferObjectType] - paymentChannelsToEdit: [EditBankTransferObjectType] - paymentChannelsToRemove: [ID] - preferredLanguage: String - flexFields: Arg - paymentDeliveryPhoneNo: String - blockchainName: String - walletAddress: String - walletName: String - deliveryMechanismData: [DeliveryMechanismDataObjectType] - deliveryMechanismDataToEdit: [EditDeliveryMechanismDataObjectType] - deliveryMechanismDataToRemove: [ID] -} - -type InvalidPaymentVerificationPlan { - paymentPlan: GenericPaymentPlanNode -} - -input IssueTypeExtrasInput { - householdDataUpdateIssueTypeExtras: HouseholdDataUpdateIssueTypeExtras - individualDataUpdateIssueTypeExtras: IndividualDataUpdateIssueTypeExtras - individualDeleteIssueTypeExtras: IndividualDeleteIssueTypeExtras - householdDeleteIssueTypeExtras: HouseholdDeleteIssueTypeExtras - addIndividualIssueTypeExtras: AddIndividualIssueTypeExtras -} - -type IssueTypesObject { - category: String - label: String - subCategories: [ChoiceObject] -} - -scalar JSONString - -type KoboAssetObject { - id: String - name: String - sector: String - country: String - assetType: String - dateModified: DateTime - deploymentActive: Boolean - hasDeployment: Boolean - xlsLink: String -} - -type KoboAssetObjectConnection { - pageInfo: PageInfo! - edges: [KoboAssetObjectEdge]! - totalCount: Int -} - -type KoboAssetObjectEdge { - node: KoboAssetObject - cursor: String! -} - -type KoboErrorNode { - header: String - message: String -} - -type KoboImportDataNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - status: ImportDataStatus! - businessAreaSlug: String! - file: String - dataType: ImportDataDataType! - numberOfHouseholds: Int - numberOfIndividuals: Int - error: String! - validationErrors: String! - deliveryMechanismsValidationErrors: String! - createdById: UUID - importdataPtr: ImportDataNode! - koboAssetId: String! - onlyActiveSubmissions: Boolean! - pullPictures: Boolean! - koboValidationErrors: [KoboErrorNode] -} - -type LabelNode { - language: String - label: String -} - -type LanguageObject { - english: String - code: String -} - -type LanguageObjectConnection { - pageInfo: PageInfo! - edges: [LanguageObjectEdge]! - totalCount: Int -} - -type LanguageObjectEdge { - node: LanguageObject - cursor: String! -} - -type LockTargetPopulationMutation { - targetPopulation: TargetPopulationNode -} - -enum LogEntryAction { - CREATE - UPDATE - DELETE - SOFT_DELETE -} - -type LogEntryNode implements Node { - id: ID! - contentType: ContentTypeObjectType - objectId: UUID - action: LogEntryAction! - objectRepr: String! - changes: Arg - user: UserNode - businessArea: UserBusinessAreaNode - programs(offset: Int, before: String, after: String, first: Int, last: Int, name: String): ProgramNodeConnection! - timestamp: DateTime - isUserGenerated: Boolean -} - -type LogEntryNodeConnection { - pageInfo: PageInfo! - edges: [LogEntryNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type LogEntryNodeEdge { - node: LogEntryNode - cursor: String! -} - -type MarkPaymentAsFailedMutation { - payment: PaymentNode -} - -type MarkPaymentRecordAsFailedMutation { - paymentRecord: PaymentRecordNode -} - -type MergeRegistrationDataImportMutation { - registrationDataImport: RegistrationDataImportNode -} - -enum MessageSamplingType { - FULL_LIST - RANDOM -} - -type Mutations { - createAccountabilityCommunicationMessage(input: CreateAccountabilityCommunicationMessageInput!): CreateCommunicationMessageMutation - createFeedback(input: CreateFeedbackInput!): CreateFeedbackMutation - updateFeedback(input: UpdateFeedbackInput!): UpdateFeedbackMutation - createFeedbackMessage(input: CreateFeedbackMessageInput!): CreateFeedbackMessageMutation - createSurvey(input: CreateSurveyInput!): CreateSurveyMutation - exportSurveySample(surveyId: ID!): ExportSurveySampleMutationMutation - createReport(reportData: CreateReportInput!): CreateReport - restartCreateReport(reportData: RestartCreateReportInput!): RestartCreateReport - createDashboardReport(reportData: CreateDashboardReportInput!): CreateDashboardReport - createGrievanceTicket(input: CreateGrievanceTicketInput!): CreateGrievanceTicketMutation - updateGrievanceTicket(input: UpdateGrievanceTicketInput!, version: BigInt): UpdateGrievanceTicketMutation - grievanceStatusChange(grievanceTicketId: ID, status: Int, version: BigInt): GrievanceStatusChangeMutation - createTicketNote(noteInput: CreateTicketNoteInput!, version: BigInt): CreateTicketNoteMutation - approveIndividualDataChange(approvedDeliveryMechanismDataToCreate: [Int], approvedDeliveryMechanismDataToEdit: [Int], approvedDeliveryMechanismDataToRemove: [Int], approvedDocumentsToCreate: [Int], approvedDocumentsToEdit: [Int], approvedDocumentsToRemove: [Int], approvedIdentitiesToCreate: [Int], approvedIdentitiesToEdit: [Int], approvedIdentitiesToRemove: [Int], approvedPaymentChannelsToCreate: [Int], approvedPaymentChannelsToEdit: [Int], approvedPaymentChannelsToRemove: [Int], flexFieldsApproveData: JSONString, grievanceTicketId: ID!, individualApproveData: JSONString, version: BigInt): IndividualDataChangeApproveMutation - approveHouseholdDataChange(flexFieldsApproveData: JSONString, grievanceTicketId: ID!, householdApproveData: JSONString, version: BigInt): HouseholdDataChangeApproveMutation - approveAddIndividual(approveStatus: Boolean!, grievanceTicketId: ID!, version: BigInt): SimpleApproveMutation - approveDeleteIndividual(approveStatus: Boolean!, grievanceTicketId: ID!, version: BigInt): SimpleApproveMutation - approveDeleteHousehold(approveStatus: Boolean!, grievanceTicketId: ID!, reasonHhId: String, version: BigInt): DeleteHouseholdApproveMutation - approveSystemFlagging(approveStatus: Boolean!, grievanceTicketId: ID!, version: BigInt): SimpleApproveMutation - approveNeedsAdjudication(clearIndividualIds: [ID], distinctIndividualIds: [ID], duplicateIndividualIds: [ID], grievanceTicketId: ID!, selectedIndividualId: ID, version: BigInt): NeedsAdjudicationApproveMutation - approvePaymentDetails(approveStatus: Boolean!, grievanceTicketId: ID!, version: BigInt): PaymentDetailsApproveMutation - reassignRole(grievanceTicketId: ID!, householdId: ID!, householdVersion: BigInt, individualId: ID!, individualVersion: BigInt, newIndividualId: ID, role: String!, version: BigInt): ReassignRoleMutation - bulkUpdateGrievanceAssignee(assignedTo: String!, businessAreaSlug: String!, grievanceTicketIds: [ID]!): BulkUpdateGrievanceTicketsAssigneesMutation - bulkUpdateGrievancePriority(businessAreaSlug: String!, grievanceTicketIds: [ID]!, priority: Int!): BulkUpdateGrievanceTicketsPriorityMutation - bulkUpdateGrievanceUrgency(businessAreaSlug: String!, grievanceTicketIds: [ID]!, urgency: Int!): BulkUpdateGrievanceTicketsUrgencyMutation - bulkGrievanceAddNote(businessAreaSlug: String!, grievanceTicketIds: [ID]!, note: String!): BulkGrievanceAddNoteMutation - createPaymentVerificationPlan(input: CreatePaymentVerificationInput!, version: BigInt): CreateVerificationPlanMutation - editPaymentVerificationPlan(input: EditPaymentVerificationInput!, version: BigInt): EditPaymentVerificationMutation - exportXlsxPaymentVerificationPlanFile(paymentVerificationPlanId: ID!): ExportXlsxPaymentVerificationPlanFile - importXlsxPaymentVerificationPlanFile(file: Upload!, paymentVerificationPlanId: ID!): ImportXlsxPaymentVerificationPlanFile - activatePaymentVerificationPlan(paymentVerificationPlanId: ID!, version: BigInt): ActivatePaymentVerificationPlan - finishPaymentVerificationPlan(paymentVerificationPlanId: ID!, version: BigInt): FinishPaymentVerificationPlan - discardPaymentVerificationPlan(paymentVerificationPlanId: ID!, version: BigInt): DiscardPaymentVerificationPlan - invalidPaymentVerificationPlan(paymentVerificationPlanId: ID!, version: BigInt): InvalidPaymentVerificationPlan - deletePaymentVerificationPlan(paymentVerificationPlanId: ID!, version: BigInt): DeletePaymentVerificationPlan - updatePaymentVerificationStatusAndReceivedAmount(paymentVerificationId: ID!, receivedAmount: Decimal!, status: PaymentVerificationStatusForUpdate, version: BigInt): UpdatePaymentVerificationStatusAndReceivedAmount - markPaymentRecordAsFailed(paymentRecordId: ID!): MarkPaymentRecordAsFailedMutation - revertMarkPaymentRecordAsFailed(deliveredQuantity: Decimal!, deliveryDate: Date!, paymentRecordId: ID!): RevertMarkPaymentRecordAsFailedMutation - markPaymentAsFailed(paymentId: ID!): MarkPaymentAsFailedMutation - revertMarkPaymentAsFailed(deliveredQuantity: Decimal!, deliveryDate: Date!, paymentId: ID!): RevertMarkPaymentAsFailedMutation - updatePaymentVerificationReceivedAndReceivedAmount(paymentVerificationId: ID!, received: Boolean!, receivedAmount: Decimal!, version: BigInt): UpdatePaymentVerificationReceivedAndReceivedAmount - actionPaymentPlanMutation(input: ActionPaymentPlanInput!): ActionPaymentPlanMutation - createPaymentPlan(input: CreatePaymentPlanInput!): CreatePaymentPlanMutation - createFollowUpPaymentPlan(dispersionEndDate: Date!, dispersionStartDate: Date!, paymentPlanId: ID!): CreateFollowUpPaymentPlanMutation - updatePaymentPlan(input: UpdatePaymentPlanInput!): UpdatePaymentPlanMutation - deletePaymentPlan(paymentPlanId: ID!): DeletePaymentPlanMutation - chooseDeliveryMechanismsForPaymentPlan(input: ChooseDeliveryMechanismsForPaymentPlanInput!): ChooseDeliveryMechanismsForPaymentPlanMutation - assignFspToDeliveryMechanism(input: AssignFspToDeliveryMechanismInput!): AssignFspToDeliveryMechanismMutation - splitPaymentPlan(paymentPlanId: ID!, paymentsNo: Int, splitType: String!): SplitPaymentPlanMutation - exportXlsxPaymentPlanPaymentList(paymentPlanId: ID!): ExportXLSXPaymentPlanPaymentListMutation - exportXlsxPaymentPlanPaymentListPerFsp(paymentPlanId: ID!): ExportXLSXPaymentPlanPaymentListPerFSPMutation - importXlsxPaymentPlanPaymentList(file: Upload!, paymentPlanId: ID!): ImportXLSXPaymentPlanPaymentListMutation - importXlsxPaymentPlanPaymentListPerFsp(file: Upload!, paymentPlanId: ID!): ImportXLSXPaymentPlanPaymentListPerFSPMutation - setSteficonRuleOnPaymentPlanPaymentList(paymentPlanId: ID!, steficonRuleId: ID!): SetSteficonRuleOnPaymentPlanPaymentListMutation - excludeHouseholds(excludedHouseholdsIds: [String]!, exclusionReason: String, paymentPlanId: ID!): ExcludeHouseholdsMutation - exportPdfPaymentPlanSummary(paymentPlanId: ID!): ExportPDFPaymentPlanSummaryMutation - createTargetPopulation(input: CreateTargetPopulationInput!): CreateTargetPopulationMutation - updateTargetPopulation(input: UpdateTargetPopulationInput!, version: BigInt): UpdateTargetPopulationMutation - copyTargetPopulation(input: CopyTargetPopulationMutationInput!): CopyTargetPopulationMutationPayload - deleteTargetPopulation(input: DeleteTargetPopulationMutationInput!): DeleteTargetPopulationMutationPayload - lockTargetPopulation(id: ID!, version: BigInt): LockTargetPopulationMutation - unlockTargetPopulation(id: ID!, version: BigInt): UnlockTargetPopulationMutation - finalizeTargetPopulation(id: ID!, version: BigInt): FinalizeTargetPopulationMutation - setSteficonRuleOnTargetPopulation(input: SetSteficonRuleOnTargetPopulationMutationInput!): SetSteficonRuleOnTargetPopulationMutationPayload - targetPopulationRebuild(id: ID!): RebuildTargetPopulationMutation - createProgram(programData: CreateProgramInput!): CreateProgram - updateProgram(programData: UpdateProgramInput, version: BigInt): UpdateProgram - deleteProgram(programId: String!): DeleteProgram - copyProgram(programData: CopyProgramInput!): CopyProgram - uploadImportDataXlsxFileAsync(businessAreaSlug: String!, file: Upload!): UploadImportDataXLSXFileAsync - deleteRegistrationDataImport(registrationDataImportId: String!): DeleteRegistrationDataImport - registrationXlsxImport(registrationDataImportData: RegistrationXlsxImportMutationInput!): RegistrationXlsxImportMutation - registrationProgramPopulationImport(registrationDataImportData: RegistrationProgramPopulationImportMutationInput!): RegistrationProgramPopulationImportMutation - registrationKoboImport(registrationDataImportData: RegistrationKoboImportMutationInput!): RegistrationKoboImportMutation - saveKoboImportDataAsync(businessAreaSlug: String!, onlyActiveSubmissions: Boolean!, pullPictures: Boolean!, uid: Upload!): SaveKoboProjectImportDataAsync - mergeRegistrationDataImport(id: ID!, version: BigInt): MergeRegistrationDataImportMutation - refuseRegistrationDataImport(id: ID!, refuseReason: String, version: BigInt): RefuseRegistrationDataImportMutation - rerunDedupe(registrationDataImportId: ID!, version: BigInt): RegistrationDeduplicationMutation - eraseRegistrationDataImport(id: ID!, version: BigInt): EraseRegistrationDataImportMutation - checkAgainstSanctionList(file: Upload!): CheckAgainstSanctionListMutation -} - -type NeedsAdjudicationApproveMutation { - grievanceTicket: GrievanceTicketNode -} - -input NegativeFeedbackTicketExtras { - household: ID - individual: ID -} - -interface Node { - id: ID! -} - -input PDUFieldInput { - id: String - label: String - pduData: PeriodicFieldDataInput -} - -type PDUSubtypeChoiceObject { - value: String - displayName: String -} - -type PageInfo { - hasNextPage: Boolean! - hasPreviousPage: Boolean! - startCursor: String - endCursor: String -} - -type PageInfoNode { - startCursor: String - endCursor: String - hasNextPage: Boolean - hasPreviousPage: Boolean -} - -type PaginatedCashPlanAndPaymentPlanNode { - pageInfo: PageInfoNode - edges: [CashPlanAndPaymentPlanEdges] - totalCount: Int -} - -type PaginatedPaymentRecordsAndPaymentsNode { - pageInfo: PageInfoNode - edges: [PaymentRecordsAndPaymentsEdges] - totalCount: Int -} - -type PartnerNode { - id: ID! - allowedBusinessAreas(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID): UserBusinessAreaNodeConnection! - name: String - parent: PartnerNode - isUn: Boolean! - permissions: JSONString! - lft: Int! - rght: Int! - treeId: Int! - level: Int! - businessAreaPartnerThrough: [PartnerRoleNode!]! - businessAreas(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID): UserBusinessAreaNodeConnection! - partnerSet: [PartnerNode!]! - userSet(offset: Int, before: String, after: String, first: Int, last: Int): UserNodeConnection! - individualIdentities(offset: Int, before: String, after: String, first: Int, last: Int): IndividualIdentityNodeConnection! - grievanceticketSet(offset: Int, before: String, after: String, first: Int, last: Int): GrievanceTicketNodeConnection! - programs(offset: Int, before: String, after: String, first: Int, last: Int, name: String): ProgramNodeConnection! - areas: [AreaNode] - areaAccess: String -} - -type PartnerRoleNode { - createdAt: DateTime! - updatedAt: DateTime! - businessArea: UserBusinessAreaNode! - partner: PartnerNode! - roles: [RoleNode!]! -} - -type PartnerType { - id: ID! - allowedBusinessAreas(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID): UserBusinessAreaNodeConnection! - name: String! - parent: PartnerNode - isUn: Boolean! - permissions: JSONString! - lft: Int! - rght: Int! - treeId: Int! - level: Int! - businessAreaPartnerThrough: [PartnerRoleNode!]! - businessAreas(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID): UserBusinessAreaNodeConnection! - partnerSet: [PartnerNode!]! - userSet(offset: Int, before: String, after: String, first: Int, last: Int): UserNodeConnection! - individualIdentities(offset: Int, before: String, after: String, first: Int, last: Int): IndividualIdentityNodeConnection! - grievanceticketSet(offset: Int, before: String, after: String, first: Int, last: Int): GrievanceTicketNodeConnection! - programs(offset: Int, before: String, after: String, first: Int, last: Int, name: String): ProgramNodeConnection! -} - -type PaymentConflictDataNode { - paymentPlanId: String - paymentPlanUnicefId: String - paymentPlanStartDate: String - paymentPlanEndDate: String - paymentPlanStatus: String - paymentId: String - paymentUnicefId: String -} - -enum PaymentDeliveryTypeChoice { - CARDLESS_CASH_WITHDRAWAL - CASH - CASH_BY_FSP - CHEQUE - DEPOSIT_TO_CARD - MOBILE_MONEY - PRE_PAID_CARD - REFERRAL - TRANSFER - TRANSFER_TO_ACCOUNT - VOUCHER - ATM_CARD - CASH_OVER_THE_COUNTER - TRANSFER_TO_DIGITAL_WALLET -} - -type PaymentDetailsApproveMutation { - grievanceTicket: GrievanceTicketNode -} - -type PaymentHouseholdSnapshotNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - snapshotData: JSONString! - householdId: UUID! - payment: PaymentNode! -} - -type PaymentNode implements Node { - isRemoved: Boolean! - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - unicefId: String - signatureHash: String! - businessArea: UserBusinessAreaNode! - status: PaymentStatus! - statusDate: DateTime! - household: HouseholdNode! - headOfHousehold: IndividualNode - deliveryTypeChoice: PaymentDeliveryTypeChoice - deliveryType: DeliveryMechanismNode - currency: String! - entitlementQuantity: Float - entitlementQuantityUsd: Float - deliveredQuantity: Float - deliveredQuantityUsd: Float - deliveryDate: DateTime - transactionReferenceId: String - transactionStatusBlockchainLink: String - parent: PaymentPlanNode! - conflicted: Boolean! - excluded: Boolean! - entitlementDate: DateTime - financialServiceProvider: FinancialServiceProviderNode - collector: IndividualNode! - sourcePayment: PaymentNode - isFollowUp: Boolean! - reasonForUnsuccessfulPayment: String - program: ProgramNode - orderNumber: Int - tokenNumber: Int - additionalCollectorName: String - additionalDocumentType: String - additionalDocumentNumber: String - fspAuthCode: String - followUps(offset: Int, before: String, after: String, first: Int, last: Int): PaymentNodeConnection! - householdSnapshot: PaymentHouseholdSnapshotNode - adminUrl: String - paymentPlanHardConflicted: Boolean - paymentPlanHardConflictedData: [PaymentConflictDataNode] - paymentPlanSoftConflicted: Boolean - paymentPlanSoftConflictedData: [PaymentConflictDataNode] - fullName: String - targetPopulation: TargetPopulationNode - verification: PaymentVerificationNode - distributionModality: String - serviceProvider: FinancialServiceProviderNode - debitCardNumber: String - debitCardIssuer: String - totalPersonsCovered: Int - snapshotCollectorFullName: String - snapshotCollectorDeliveryPhoneNo: String - snapshotCollectorBankName: String - snapshotCollectorBankAccountNumber: String - snapshotCollectorDebitCardNumber: String -} - -type PaymentNodeConnection { - pageInfo: PageInfo! - edges: [PaymentNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type PaymentNodeEdge { - node: PaymentNode - cursor: String! -} - -enum PaymentPlanBackgroundActionStatus { - RULE_ENGINE_RUN - RULE_ENGINE_ERROR - XLSX_EXPORTING - XLSX_EXPORT_ERROR - XLSX_IMPORT_ERROR - XLSX_IMPORTING_ENTITLEMENTS - XLSX_IMPORTING_RECONCILIATION - EXCLUDE_BENEFICIARIES - EXCLUDE_BENEFICIARIES_ERROR - SEND_TO_PAYMENT_GATEWAY - SEND_TO_PAYMENT_GATEWAY_ERROR -} - -enum PaymentPlanCurrency { - A_ - AED - AFN - ALL - AMD - ANG - AOA - ARS - AUD - AWG - AZN - BAM - BBD - BDT - BGN - BHD - BIF - BMD - BND - BOB - BOV - BRL - BSD - BTN - BWP - BYN - BZD - CAD - CDF - CHF - CLP - CNY - COP - CRC - CUC - CUP - CVE - CZK - DJF - DKK - DOP - DZD - EGP - ERN - ETB - EUR - FJD - FKP - GBP - GEL - GHS - GIP - GMD - GNF - GTQ - GYD - HKD - HNL - HRK - HTG - HUF - IDR - ILS - INR - IQD - IRR - ISK - JMD - JOD - JPY - KES - KGS - KHR - KMF - KPW - KRW - KWD - KYD - KZT - LAK - LBP - LKR - LRD - LSL - LYD - MAD - MDL - MGA - MKD - MMK - MNT - MOP - MRU - MUR - MVR - MWK - MXN - MYR - MZN - NAD - NGN - NIO - NOK - NPR - NZD - OMR - PAB - PEN - PGK - PHP - PKR - PLN - PYG - QAR - RON - RSD - RUB - RWF - SAR - SBD - SCR - SDG - SEK - SGD - SHP - SLL - SOS - SRD - SSP - STN - SVC - SYP - SZL - THB - TJS - TMT - TND - TOP - TRY - TTD - TWD - TZS - UAH - UGX - USD - UYU - UYW - UZS - VES - VND - VUV - WST - XAF - XAG - XAU - XCD - XOF - XPF - YER - ZAR - ZMW - ZWL - USDC -} - -type PaymentPlanNode implements Node { - isRemoved: Boolean! - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - version: BigInt! - unicefId: String - businessArea: UserBusinessAreaNode! - statusDate: DateTime! - startDate: Date - endDate: Date - program: ProgramNode! - exchangeRate: Float - totalEntitledQuantity: Float - totalEntitledQuantityUsd: Float - totalEntitledQuantityRevised: Float - totalEntitledQuantityRevisedUsd: Float - totalDeliveredQuantity: Float - totalDeliveredQuantityUsd: Float - totalUndeliveredQuantity: Float - totalUndeliveredQuantityUsd: Float - programCycle: ProgramCycleNode - createdBy: UserNode! - status: PaymentPlanStatus! - backgroundActionStatus: PaymentPlanBackgroundActionStatus - targetPopulation: TargetPopulationNode! - currency: PaymentPlanCurrency! - dispersionStartDate: Date - dispersionEndDate: Date - femaleChildrenCount: Int! - maleChildrenCount: Int! - femaleAdultsCount: Int! - maleAdultsCount: Int! - totalHouseholdsCount: Int! - totalIndividualsCount: Int! - importedFileDate: DateTime - steficonRule: RuleCommitNode - steficonAppliedDate: DateTime - sourcePaymentPlan: PaymentPlanNode - isFollowUp: Boolean! - exclusionReason: String! - excludeHouseholdError: String! - name: String - followUps(offset: Int, before: String, after: String, first: Int, last: Int): PaymentPlanNodeConnection! - deliveryMechanisms: [DeliveryMechanismPerPaymentPlanNode] - paymentItems(offset: Int, before: String, after: String, first: Int, last: Int): PaymentNodeConnection! - approvalProcess(offset: Int, before: String, after: String, first: Int, last: Int): ApprovalProcessNodeConnection! - adminUrl: String - currencyName: String - hasPaymentListExportFile: Boolean - hasFspDeliveryMechanismXlsxTemplate: Boolean - importedFileName: String - paymentsConflictsCount: Int - volumeByDeliveryMechanism: [VolumeByDeliveryMechanismNode] - splitChoices: [ChoiceObject] - verificationPlans(offset: Int, before: String, after: String, first: Int, last: Int, programId: String): PaymentVerificationPlanNodeConnection - paymentVerificationSummary: PaymentVerificationSummaryNode - bankReconciliationSuccess: Int - bankReconciliationError: Int - canCreatePaymentVerificationPlan: Boolean - availablePaymentRecordsCount: Int - reconciliationSummary: ReconciliationSummaryNode - excludedHouseholds: [HouseholdNode] - canCreateFollowUp: Boolean - totalWithdrawnHouseholdsCount: Int - unsuccessfulPaymentsCount: Int - canSendToPaymentGateway: Boolean - canSplit: Boolean -} - -type PaymentPlanNodeConnection { - pageInfo: PageInfo! - edges: [PaymentPlanNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type PaymentPlanNodeEdge { - node: PaymentPlanNode - cursor: String! -} - -enum PaymentPlanStatus { - PREPARING - OPEN - LOCKED - LOCKED_FSP - IN_APPROVAL - IN_AUTHORIZATION - IN_REVIEW - ACCEPTED - FINISHED -} - -type PaymentRecordAndPaymentNode { - objType: String - id: String - caId: String - status: String - fullName: String - parent: CashPlanAndPaymentPlanNode - entitlementQuantity: Float - deliveredQuantity: Float - deliveredQuantityUsd: Float - currency: String - deliveryDate: String - verification: PaymentVerificationNode -} - -enum PaymentRecordDeliveryTypeChoice { - CARDLESS_CASH_WITHDRAWAL - CASH - CASH_BY_FSP - CHEQUE - DEPOSIT_TO_CARD - MOBILE_MONEY - PRE_PAID_CARD - REFERRAL - TRANSFER - TRANSFER_TO_ACCOUNT - VOUCHER - ATM_CARD - CASH_OVER_THE_COUNTER - TRANSFER_TO_DIGITAL_WALLET -} - -enum PaymentRecordEntitlementCardStatus { - ACTIVE - INACTIVE -} - -type PaymentRecordNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - version: BigInt! - businessArea: UserBusinessAreaNode! - status: PaymentRecordStatus! - statusDate: DateTime! - household: HouseholdNode! - headOfHousehold: IndividualNode - deliveryTypeChoice: PaymentRecordDeliveryTypeChoice - deliveryType: DeliveryMechanismNode - currency: String! - entitlementQuantity: Float - entitlementQuantityUsd: Float - deliveredQuantity: Float - deliveredQuantityUsd: Float - deliveryDate: DateTime - transactionReferenceId: String - transactionStatusBlockchainLink: String - caId: String - caHashId: UUID - parent: CashPlanNode - fullName: String! - totalPersonsCovered: Int! - distributionModality: String! - targetPopulation: TargetPopulationNode! - targetPopulationCashAssistId: String! - entitlementCardNumber: String - entitlementCardStatus: PaymentRecordEntitlementCardStatus - entitlementCardIssueDate: Date - visionId: String - registrationCaId: String - serviceProvider: ServiceProviderNode! - adminUrl: String - verification: PaymentVerificationNode - unicefId: String -} - -type PaymentRecordNodeConnection { - pageInfo: PageInfo! - edges: [PaymentRecordNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type PaymentRecordNodeEdge { - node: PaymentRecordNode - cursor: String! -} - -enum PaymentRecordStatus { - DISTRIBUTION_SUCCESSFUL - NOT_DISTRIBUTED - TRANSACTION_SUCCESSFUL - TRANSACTION_ERRONEOUS - FORCE_FAILED - PARTIALLY_DISTRIBUTED - PENDING - SENT_TO_PAYMENT_GATEWAY - SENT_TO_FSP - MANUALLY_CANCELLED -} - -type PaymentRecordsAndPaymentsEdges { - cursor: String - node: PaymentRecordAndPaymentNode -} - -enum PaymentStatus { - DISTRIBUTION_SUCCESSFUL - NOT_DISTRIBUTED - TRANSACTION_SUCCESSFUL - TRANSACTION_ERRONEOUS - FORCE_FAILED - PARTIALLY_DISTRIBUTED - PENDING - SENT_TO_PAYMENT_GATEWAY - SENT_TO_FSP - MANUALLY_CANCELLED -} - -type PaymentVerificationLogEntryNode implements Node { - id: ID! - contentType: ContentTypeObjectType - objectId: UUID - action: LogEntryAction! - objectRepr: String! - changes: Arg - user: UserNode - businessArea: UserBusinessAreaNode - programs(offset: Int, before: String, after: String, first: Int, last: Int, name: String): ProgramNodeConnection! - timestamp: DateTime - isUserGenerated: Boolean - contentObject: PaymentVerificationPlanNode -} - -type PaymentVerificationLogEntryNodeConnection { - pageInfo: PageInfo! - edges: [PaymentVerificationLogEntryNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type PaymentVerificationLogEntryNodeEdge { - node: PaymentVerificationLogEntryNode - cursor: String! -} - -type PaymentVerificationNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - version: BigInt! - paymentVerificationPlan: PaymentVerificationPlanNode! - paymentContentType: ContentTypeObjectType! - paymentObjectId: UUID! - status: PaymentVerificationStatus! - statusDate: DateTime - receivedAmount: Float - sentToRapidPro: Boolean! - ticketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketPaymentVerificationDetailsNodeConnection! - ticketDetail(offset: Int, before: String, after: String, first: Int, last: Int): TicketPaymentVerificationDetailsNodeConnection! - adminUrl: String - isManuallyEditable: Boolean - payment: GenericPaymentNode -} - -type PaymentVerificationNodeConnection { - pageInfo: PageInfo! - edges: [PaymentVerificationNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type PaymentVerificationNodeEdge { - node: PaymentVerificationNode - cursor: String! -} - -type PaymentVerificationPlanNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - version: BigInt! - unicefId: String - status: PaymentVerificationPlanStatus! - paymentPlanContentType: ContentTypeObjectType! - paymentPlanObjectId: UUID! - sampling: PaymentVerificationPlanSampling! - verificationChannel: PaymentVerificationPlanVerificationChannel! - sampleSize: Int - respondedCount: Int - receivedCount: Int - notReceivedCount: Int - receivedWithProblemsCount: Int - confidenceInterval: Float - marginOfError: Float - rapidProFlowId: String! - rapidProFlowStartUuids: [String!]! - ageFilter: AgeFilterObject - excludedAdminAreasFilter: [String] - sexFilter: String - activationDate: DateTime - completionDate: DateTime - xlsxFileExporting: Boolean! - xlsxFileImported: Boolean! - error: String - paymentRecordVerifications(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationNodeConnection! - adminUrl: String - xlsxFileWasDownloaded: Boolean - hasXlsxFile: Boolean - paymentPlan: PaymentPlanNode -} - -type PaymentVerificationPlanNodeConnection { - pageInfo: PageInfo! - edges: [PaymentVerificationPlanNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type PaymentVerificationPlanNodeEdge { - node: PaymentVerificationPlanNode - cursor: String! -} - -enum PaymentVerificationPlanSampling { - FULL_LIST - RANDOM -} - -enum PaymentVerificationPlanStatus { - ACTIVE - FINISHED - PENDING - INVALID - RAPID_PRO_ERROR -} - -enum PaymentVerificationPlanVerificationChannel { - MANUAL - RAPIDPRO - XLSX -} - -enum PaymentVerificationStatus { - NOT_RECEIVED - PENDING - RECEIVED - RECEIVED_WITH_ISSUES -} - -enum PaymentVerificationStatusForUpdate { - NOT_RECEIVED - PENDING - RECEIVED - RECEIVED_WITH_ISSUES -} - -type PaymentVerificationSummaryNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - status: PaymentVerificationSummaryStatus! - activationDate: DateTime - completionDate: DateTime - paymentPlanContentType: ContentTypeObjectType! - paymentPlanObjectId: UUID! -} - -type PaymentVerificationSummaryNodeConnection { - pageInfo: PageInfo! - edges: [PaymentVerificationSummaryNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type PaymentVerificationSummaryNodeEdge { - node: PaymentVerificationSummaryNode - cursor: String! -} - -enum PaymentVerificationSummaryStatus { - ACTIVE - FINISHED - PENDING -} - -input PeriodicFieldDataInput { - subtype: String - numberOfRounds: Int - roundsNames: [String] -} - -type PeriodicFieldDataNode { - id: ID! - subtype: PeriodicFieldDataSubtype! - numberOfRounds: Int! - roundsNames: [String!]! -} - -enum PeriodicFieldDataSubtype { - DATE - DECIMAL - STRING - BOOL -} - -type PeriodicFieldNode implements Node { - name: String! - pduData: PeriodicFieldDataNode - label: JSONString! - id: ID! -} - -input PositiveFeedbackTicketExtras { - household: ID - individual: ID -} - -type ProgramCycleNode implements Node { - isRemoved: Boolean! - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - version: BigInt! - title: String - status: ProgramCycleStatus! - startDate: Date! - endDate: Date - program: ProgramNode! - createdBy: UserNode - paymentPlans(offset: Int, before: String, after: String, first: Int, last: Int): PaymentPlanNodeConnection! - targetPopulations(offset: Int, before: String, after: String, first: Int, last: Int, program: [ID], createdAt: DateTime, createdAt_Lte: DateTime, createdAt_Gte: DateTime, updatedAt: DateTime, updatedAt_Lte: DateTime, updatedAt_Gte: DateTime, status: String, households: [ID], name: String, createdByName: String, totalHouseholdsCountMin: Int, totalHouseholdsCountMax: Int, totalIndividualsCountMin: Int, totalIndividualsCountMax: Int, businessArea: String, createdAtRange: String, paymentPlanApplicable: Boolean, statusNot: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, programCycle: String, orderBy: String): TargetPopulationNodeConnection! - totalDeliveredQuantityUsd: Float - totalEntitledQuantityUsd: Float - totalUndeliveredQuantityUsd: Float -} - -type ProgramCycleNodeConnection { - pageInfo: PageInfo! - edges: [ProgramCycleNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type ProgramCycleNodeEdge { - node: ProgramCycleNode - cursor: String! -} - -enum ProgramCycleStatus { - DRAFT - ACTIVE - FINISHED -} - -enum ProgramFrequencyOfPayments { - ONE_OFF - REGULAR -} - -type ProgramNode implements Node { - isRemoved: Boolean! - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - lastSyncAt: DateTime - version: BigInt! - name: String! - status: ProgramStatus! - startDate: Date! - endDate: Date! - description: String! - caId: String - caHashId: String - adminAreas(offset: Int, before: String, after: String, first: Int, last: Int, name: String): AreaNodeConnection! - businessArea: UserBusinessAreaNode! - budget: Decimal - frequencyOfPayments: ProgramFrequencyOfPayments! - sector: ProgramSector! - scope: ProgramScope - cashPlus: Boolean! - populationGoal: Int! - administrativeAreasOfImplementation: String! - dataCollectingType: DataCollectingTypeNode - isVisible: Boolean! - householdCount: Int! - individualCount: Int! - programmeCode: String - partnerAccess: ProgramPartnerAccess! - partners: [PartnerNode] - pduFields: [PeriodicFieldNode] - households(offset: Int, before: String, after: String, first: Int, last: Int): HouseholdNodeConnection! - householdSet(offset: Int, before: String, after: String, first: Int, last: Int): HouseholdNodeConnection! - individuals(offset: Int, before: String, after: String, first: Int, last: Int): IndividualNodeConnection! - registrationImports(offset: Int, before: String, after: String, first: Int, last: Int): RegistrationDataImportNodeConnection! - paymentplanSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentPlanNodeConnection! - cashplanSet(offset: Int, before: String, after: String, first: Int, last: Int): CashPlanNodeConnection! - paymentSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentNodeConnection! - grievanceTickets(offset: Int, before: String, after: String, first: Int, last: Int): GrievanceTicketNodeConnection! - targetpopulationSet(offset: Int, before: String, after: String, first: Int, last: Int, program: [ID], createdAt: DateTime, createdAt_Lte: DateTime, createdAt_Gte: DateTime, updatedAt: DateTime, updatedAt_Lte: DateTime, updatedAt_Gte: DateTime, status: String, households: [ID], name: String, createdByName: String, totalHouseholdsCountMin: Int, totalHouseholdsCountMax: Int, totalIndividualsCountMin: Int, totalIndividualsCountMax: Int, businessArea: String, createdAtRange: String, paymentPlanApplicable: Boolean, statusNot: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, programCycle: String, orderBy: String): TargetPopulationNodeConnection! - cycles(offset: Int, before: String, after: String, first: Int, last: Int, search: String, status: [String], startDate: Date, endDate: Date, totalDeliveredQuantityUsdFrom: Float, totalDeliveredQuantityUsdTo: Float, orderBy: String): ProgramCycleNodeConnection - reports(offset: Int, before: String, after: String, first: Int, last: Int): ReportNodeConnection! - activityLogs(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationLogEntryNodeConnection! - messages(offset: Int, before: String, after: String, first: Int, last: Int): CommunicationMessageNodeConnection! - feedbackSet(offset: Int, before: String, after: String, first: Int, last: Int): FeedbackNodeConnection! - surveys(offset: Int, before: String, after: String, first: Int, last: Int): SurveyNodeConnection! - adminUrl: String - totalEntitledQuantity: Decimal - totalDeliveredQuantity: Decimal - totalUndeliveredQuantity: Decimal - totalNumberOfHouseholds: Int - totalNumberOfHouseholdsWithTpInProgram: Int - isSocialWorkerProgram: Boolean - targetPopulationsCount: Int -} - -type ProgramNodeConnection { - pageInfo: PageInfo! - edges: [ProgramNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type ProgramNodeEdge { - node: ProgramNode - cursor: String! -} - -enum ProgramPartnerAccess { - ALL_PARTNERS_ACCESS - NONE_PARTNERS_ACCESS - SELECTED_PARTNERS_ACCESS -} - -input ProgramPartnerThroughInput { - partner: String - areas: [String] - areaAccess: String -} - -enum ProgramScope { - FOR_PARTNERS - UNICEF -} - -enum ProgramSector { - CHILD_PROTECTION - EDUCATION - HEALTH - MULTI_PURPOSE - NUTRITION - SOCIAL_POLICY - WASH -} - -enum ProgramStatus { - ACTIVE - DRAFT - FINISHED -} - -type Query { - accountabilityCommunicationMessage(id: ID!): CommunicationMessageNode - allAccountabilityCommunicationMessages(offset: Int, before: String, after: String, first: Int, last: Int, numberOfRecipients: Int, numberOfRecipients_Gte: Int, numberOfRecipients_Lte: Int, targetPopulation: ID, createdBy: ID, program: String, createdAtRange: String, title: String, body: String, samplingType: String, orderBy: String): CommunicationMessageNodeConnection - allAccountabilityCommunicationMessageRecipients(offset: Int, before: String, after: String, first: Int, last: Int, messageId: String!, recipientId: String, fullName: String, phoneNo: String, sex: String, orderBy: String): CommunicationMessageRecipientMapNodeConnection - accountabilityCommunicationMessageSampleSize(input: GetAccountabilityCommunicationMessageSampleSizeInput): GetCommunicationMessageSampleSizeNode - feedback(id: ID!): FeedbackNode - allFeedbacks(offset: Int, before: String, after: String, first: Int, last: Int, businessArea: String, issueType: String, createdAtRange: String, createdBy: String, feedbackId: String, isActiveProgram: String, program: String, orderBy: String): FeedbackNodeConnection - feedbackIssueTypeChoices: [ChoiceObject] - survey(id: ID!): SurveyNode - allSurveys(offset: Int, before: String, after: String, first: Int, last: Int, program: ID, targetPopulation: ID, businessArea: String, createdAtRange: String, search: String, createdBy: String, orderBy: String): SurveyNodeConnection - recipients(offset: Int, before: String, after: String, first: Int, last: Int, survey: String!, orderBy: String): RecipientNodeConnection - accountabilitySampleSize(input: AccountabilitySampleSizeInput): AccountabilitySampleSizeNode - surveyCategoryChoices: [ChoiceObject] - surveyAvailableFlows: [RapidProFlowNode] - adminArea(id: ID!): AreaNode - allAdminAreas(offset: Int, before: String, after: String, first: Int, last: Int, name: String, name_Istartswith: String, businessArea: String, level: Int, parentId: String): AreaNodeConnection - allAreasTree(businessArea: String!): [AreaTreeNode] - allLogEntries(offset: Int, before: String, after: String, first: Int, last: Int, objectId: UUID, user: ID, businessArea: String!, search: String, module: String, userId: String, programId: String): LogEntryNodeConnection - logEntryActionChoices: [ChoiceObject] - report(id: ID!): ReportNode - allReports(offset: Int, before: String, after: String, first: Int, last: Int, createdBy: ID, reportType: [String], status: [String], businessArea: String!, createdFrom: DateTime, createdTo: DateTime, orderBy: String): ReportNodeConnection - reportTypesChoices: [ChoiceObject] - reportStatusChoices: [ChoiceObject] - dashboardReportTypesChoices(businessAreaSlug: String!): [ChoiceObject] - dashboardYearsChoices(businessAreaSlug: String!): [String] - sanctionListIndividual(id: ID!): SanctionListIndividualNode - allSanctionListIndividuals(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID, fullName: String, fullName_Startswith: String, referenceNumber: String, orderBy: String): SanctionListIndividualNodeConnection - ticketsByType(businessAreaSlug: String!): TicketByType - ticketsByCategory(businessAreaSlug: String!): ChartDatasetNode - ticketsByStatus(businessAreaSlug: String!): ChartDatasetNode - ticketsByLocationAndCategory(businessAreaSlug: String!): ChartDetailedDatasetsNode - grievanceTicket(id: ID!): GrievanceTicketNode - allGrievanceTicket(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID, id_Startswith: UUID, category: String, area: String, area_Startswith: String, assignedTo: ID, registrationDataImport: ID, admin2: ID, createdBy: ID, businessArea: String!, search: String, documentType: String, documentNumber: String, status: [String], fsp: String, cashPlan: String, createdAtRange: String, permissions: [String], issueType: String, scoreMin: String, scoreMax: String, household: String, preferredLanguage: String, priority: String, urgency: String, grievanceType: String, grievanceStatus: String, totalDays: Int, program: String, isActiveProgram: Boolean, isCrossArea: Boolean, admin1: ID, orderBy: String): GrievanceTicketNodeConnection - crossAreaFilterAvailable: Boolean - existingGrievanceTickets(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID, businessArea: String!, category: String, issueType: String, household: ID, individual: ID, paymentRecord: [ID], permissions: [String], orderBy: String): GrievanceTicketNodeConnection - allTicketNotes(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID, ticket: UUID!): TicketNoteNodeConnection - chartGrievances(businessAreaSlug: String!, year: Int!, administrativeArea: String): ChartGrievanceTicketsNode - allAddIndividualsFieldsAttributes: [FieldAttributeNode] - allEditHouseholdFieldsAttributes: [FieldAttributeNode] - grievanceTicketStatusChoices: [ChoiceObject] - grievanceTicketCategoryChoices: [ChoiceObject] - grievanceTicketManualCategoryChoices: [ChoiceObject] - grievanceTicketSystemCategoryChoices: [ChoiceObject] - grievanceTicketIssueTypeChoices: [IssueTypesObject] - grievanceTicketPriorityChoices: [ChoiceObjectInt] - grievanceTicketUrgencyChoices: [ChoiceObjectInt] - allSteficonRules(offset: Int, before: String, after: String, first: Int, last: Int, enabled: Boolean, deprecated: Boolean, type: String!): SteficonRuleNodeConnection - payment(id: ID!): PaymentNode - allPayments(offset: Int, before: String, after: String, first: Int, last: Int, businessArea: String!, paymentPlanId: String!, programId: String, orderBy: String): PaymentNodeConnection - paymentRecord(id: ID!): PaymentRecordNode - allPaymentRecords(offset: Int, before: String, after: String, first: Int, last: Int, parent: ID, household: ID, individual: String, businessArea: String, programId: String, orderBy: String): PaymentRecordNodeConnection - allPaymentRecordsAndPayments(businessArea: String!, program: String, household: ID, orderBy: String, first: Int, last: Int, before: String, after: String): PaginatedPaymentRecordsAndPaymentsNode - financialServiceProviderXlsxTemplate(id: ID!): FinancialServiceProviderXlsxTemplateNode - allFinancialServiceProviderXlsxTemplates(offset: Int, before: String, after: String, first: Int, last: Int, name: String, createdBy: ID, orderBy: String): FinancialServiceProviderXlsxTemplateNodeConnection - financialServiceProvider(id: ID!): FinancialServiceProviderNode - allFinancialServiceProviders(offset: Int, before: String, after: String, first: Int, last: Int, createdBy: ID, name: String, visionVendorNumber: String, deliveryMechanisms: [String], distributionLimit: Float, communicationChannel: String, xlsxTemplates: [ID], orderBy: String): FinancialServiceProviderNodeConnection - paymentRecordVerification(id: ID!): PaymentVerificationNode - allPaymentVerifications(offset: Int, before: String, after: String, first: Int, last: Int, paymentVerificationPlan: ID, status: String, paymentPlanId: String, search: String, businessArea: String!, verificationChannel: String, orderBy: String): PaymentVerificationNodeConnection - paymentVerificationPlan(id: ID!): PaymentVerificationPlanNode - allPaymentVerificationPlan(offset: Int, before: String, after: String, first: Int, last: Int, programId: String): PaymentVerificationPlanNodeConnection - chartPaymentVerification(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): ChartPaymentVerification - chartPaymentVerificationForPeople(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): ChartPaymentVerification - chartVolumeByDeliveryMechanism(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): ChartDatasetNode - chartPayment(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): ChartDatasetNode - sectionTotalTransferred(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): SectionTotalNode - tableTotalCashTransferredByAdministrativeArea(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String, order: String, orderBy: String): TableTotalCashTransferred - tableTotalCashTransferredByAdministrativeAreaForPeople(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String, order: String, orderBy: String): TableTotalCashTransferredForPeople - chartTotalTransferredCashByCountry(year: Int!): ChartDetailedDatasetsNode - paymentRecordStatusChoices: [ChoiceObject] - paymentRecordEntitlementCardStatusChoices: [ChoiceObject] - paymentRecordDeliveryTypeChoices: [ChoiceObject] - cashPlanVerificationStatusChoices: [ChoiceObject] - cashPlanVerificationSamplingChoices: [ChoiceObject] - cashPlanVerificationVerificationChannelChoices: [ChoiceObject] - paymentVerificationStatusChoices: [ChoiceObject] - allRapidProFlows(businessAreaSlug: String!): [RapidProFlow] - sampleSize(input: GetCashplanVerificationSampleSizeInput): GetCashplanVerificationSampleSizeObject - allPaymentVerificationLogEntries(offset: Int, before: String, after: String, first: Int, last: Int, objectId: UUID, user: ID, businessArea: String!, search: String, module: String, userId: String, programId: String, objectType: String): PaymentVerificationLogEntryNodeConnection - paymentPlan(id: ID!): PaymentPlanNode - allPaymentPlans(offset: Int, before: String, after: String, first: Int, last: Int, businessArea: String!, search: String, status: [String], totalEntitledQuantityFrom: Float, totalEntitledQuantityTo: Float, dispersionStartDate: Date, dispersionEndDate: Date, isFollowUp: Boolean, sourcePaymentPlanId: String, program: String, programCycle: String, orderBy: String): PaymentPlanNodeConnection - paymentPlanStatusChoices: [ChoiceObject] - currencyChoices: [ChoiceObject] - allDeliveryMechanisms: [ChoiceObject] - paymentPlanBackgroundActionStatusChoices: [ChoiceObject] - availableFspsForDeliveryMechanisms(input: AvailableFspsForDeliveryMechanismsInput): [FspChoices] - allCashPlansAndPaymentPlans(businessArea: String!, program: String, search: String, serviceProvider: String, deliveryType: [String], verificationStatus: [String], startDateGte: String, endDateLte: String, orderBy: String, first: Int, last: Int, before: String, after: String, isPaymentVerificationPage: Boolean): PaginatedCashPlanAndPaymentPlanNode - businessArea(businessAreaSlug: String!): BusinessAreaNode - allBusinessAreas(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID, slug: String): BusinessAreaNodeConnection - allFieldsAttributes(flexField: Boolean, businessAreaSlug: String, programId: String): [FieldAttributeNode] - allPduFields(businessAreaSlug: String!, programId: String!): [FieldAttributeNode] - allGroupsWithFields: [GroupAttributeNode] - koboProject(uid: String!, businessAreaSlug: String!): KoboAssetObject - allKoboProjects(businessAreaSlug: String!, onlyDeployed: Boolean, before: String, after: String, first: Int, last: Int): KoboAssetObjectConnection - cashAssistUrlPrefix: String - allLanguages(code: String, before: String, after: String, first: Int, last: Int): LanguageObjectConnection - dataCollectingType(id: ID!): DataCollectingTypeNode - dataCollectionTypeChoices: [DataCollectingTypeChoiceObject] - pduSubtypeChoices: [PDUSubtypeChoiceObject] - program(id: ID!): ProgramNode - allPrograms(offset: Int, before: String, after: String, first: Int, last: Int, businessArea: String!, search: String, status: [String], sector: [String], numberOfHouseholds: String, budget: String, startDate: Date, endDate: Date, name: String, numberOfHouseholdsWithTpInProgram: String, dataCollectingType: String, compatibleDct: Boolean, orderBy: String): ProgramNodeConnection - chartProgrammesBySector(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): ChartDetailedDatasetsNode - chartTotalTransferredByMonth(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): ChartDetailedDatasetsNode - cashPlan(id: ID!): CashPlanNode - allCashPlans(offset: Int, before: String, after: String, first: Int, last: Int, program: ID, assistanceThrough: String, assistanceThrough_Startswith: String, serviceProvider_FullName: String, serviceProvider_FullName_Startswith: String, startDate: DateTime, startDate_Lte: DateTime, startDate_Gte: DateTime, endDate: DateTime, endDate_Lte: DateTime, endDate_Gte: DateTime, businessArea: String, search: String, deliveryType: [String], verificationStatus: [String], orderBy: String): CashPlanNodeConnection - programStatusChoices: [ChoiceObject] - programCycleStatusChoices: [ChoiceObject] - programFrequencyOfPaymentsChoices: [ChoiceObject] - programSectorChoices: [ChoiceObject] - programScopeChoices: [ChoiceObject] - cashPlanStatusChoices: [ChoiceObject] - dataCollectingTypeChoices: [ChoiceObject] - allActivePrograms(offset: Int, before: String, after: String, first: Int, last: Int, businessArea: String!, search: String, status: [String], sector: [String], numberOfHouseholds: String, budget: String, startDate: Date, endDate: Date, name: String, numberOfHouseholdsWithTpInProgram: String, dataCollectingType: String, compatibleDct: Boolean, orderBy: String): ProgramNodeConnection - programCycle(id: ID!): ProgramCycleNode - targetPopulation(id: ID!): TargetPopulationNode - allTargetPopulation(offset: Int, before: String, after: String, first: Int, last: Int, program: [ID], createdAt: DateTime, createdAt_Lte: DateTime, createdAt_Gte: DateTime, updatedAt: DateTime, updatedAt_Lte: DateTime, updatedAt_Gte: DateTime, status: String, households: [ID], name: String, createdByName: String, totalHouseholdsCountMin: Int, totalHouseholdsCountMax: Int, totalIndividualsCountMin: Int, totalIndividualsCountMax: Int, businessArea: String, createdAtRange: String, paymentPlanApplicable: Boolean, statusNot: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, programCycle: String, orderBy: String): TargetPopulationNodeConnection - targetPopulationHouseholds(targetPopulation: ID!, offset: Int, before: String, after: String, first: Int, last: Int, orderBy: String, businessArea: String): HouseholdNodeConnection - targetPopulationStatusChoices: [ChoiceObject] - allActiveTargetPopulations(offset: Int, before: String, after: String, first: Int, last: Int, program: [ID], createdAt: DateTime, createdAt_Lte: DateTime, createdAt_Gte: DateTime, updatedAt: DateTime, updatedAt_Lte: DateTime, updatedAt_Gte: DateTime, status: String, households: [ID], name: String, createdByName: String, totalHouseholdsCountMin: Int, totalHouseholdsCountMax: Int, totalIndividualsCountMin: Int, totalIndividualsCountMax: Int, businessArea: String, createdAtRange: String, paymentPlanApplicable: Boolean, statusNot: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, programCycle: String, orderBy: String): TargetPopulationNodeConnection - household(id: ID!): HouseholdNode - allHouseholds(offset: Int, before: String, after: String, first: Int, last: Int, businessArea: String, address: String, address_Startswith: String, headOfHousehold_FullName: String, headOfHousehold_FullName_Startswith: String, size_Range: [Int], size_Lte: Int, size_Gte: Int, adminArea: ID, admin1: ID, admin2: ID, targetPopulations: [ID], residenceStatus: String, withdrawn: Boolean, program: ID, size: String, search: String, documentType: String, documentNumber: String, headOfHousehold_PhoneNoValid: Boolean, lastRegistrationDate: String, countryOrigin: String, isActiveProgram: Boolean, orderBy: String): HouseholdNodeConnection - individual(id: ID!): IndividualNode - allIndividuals(offset: Int, before: String, after: String, first: Int, last: Int, household_Id: UUID, businessArea: String, fullName: String, fullName_Startswith: String, fullName_Endswith: String, sex: [String], household_AdminArea: ID, withdrawn: Boolean, program: ID, age: String, programs: [ID], search: String, documentType: String, documentNumber: String, lastRegistrationDate: String, admin1: [ID], admin2: [ID], status: [String], excludedId: String, flags: [String], isActiveProgram: Boolean, orderBy: String): IndividualNodeConnection - allMergedHouseholds(offset: Int, before: String, after: String, first: Int, last: Int, businessArea: String, rdiId: String, orderBy: String): HouseholdNodeConnection - allMergedIndividuals(offset: Int, before: String, after: String, first: Int, last: Int, household: ID, rdiId: String, duplicatesOnly: Boolean, businessArea: String, orderBy: String): IndividualNodeConnection - sectionHouseholdsReached(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): SectionTotalNode - sectionIndividualsReached(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): SectionTotalNode - sectionPeopleReached(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): SectionTotalNode - sectionChildReached(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): SectionTotalNode - chartIndividualsReachedByAgeAndGender(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): ChartDatasetNode - chartPeopleReachedByAgeAndGender(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): ChartDatasetNode - chartIndividualsWithDisabilityReachedByAge(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): ChartDetailedDatasetsNode - chartPeopleWithDisabilityReachedByAge(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): ChartDetailedDatasetsNode - residenceStatusChoices: [ChoiceObject] - sexChoices: [ChoiceObject] - maritalStatusChoices: [ChoiceObject] - workStatusChoices: [ChoiceObject] - relationshipChoices: [ChoiceObject] - roleChoices: [ChoiceObject] - documentTypeChoices: [ChoiceObject] - identityTypeChoices: [ChoiceObject] - countriesChoices: [ChoiceObject] - observedDisabilityChoices: [ChoiceObject] - severityOfDisabilityChoices: [ChoiceObject] - flagChoices: [ChoiceObject] - allHouseholdsFlexFieldsAttributes: [FieldAttributeNode] - allIndividualsFlexFieldsAttributes: [FieldAttributeNode] - me: UserNode - allUsers(offset: Int, before: String, after: String, first: Int, last: Int, status: [String], partner: [String], businessArea: String!, program: String, search: String, roles: [String], isTicketCreator: Boolean, isSurveyCreator: Boolean, isMessageCreator: Boolean, isFeedbackCreator: Boolean, orderBy: String): UserNodeConnection - userRolesChoices: [RoleChoiceObject] - userStatusChoices: [ChoiceObject] - userPartnerChoices: [ChoiceObject] - partnerForGrievanceChoices(householdId: ID, individualId: ID): [ChoiceObject] - hasAvailableUsersToExport(businessAreaSlug: String!): Boolean - importedHousehold(id: ID!): ImportedHouseholdNode - allImportedHouseholds(offset: Int, before: String, after: String, first: Int, last: Int, rdiId: String, businessArea: String, orderBy: String): ImportedHouseholdNodeConnection - registrationDataImportDatahub(id: ID!): RegistrationDataImportDatahubNode - importedIndividual(id: ID!): ImportedIndividualNode - allImportedIndividuals(offset: Int, before: String, after: String, first: Int, last: Int, household: ID, rdiId: String, duplicatesOnly: Boolean, businessArea: String, orderBy: String): ImportedIndividualNodeConnection - importData(id: ID!): ImportDataNode - koboImportData(id: ID!): KoboImportDataNode - deduplicationBatchStatusChoices: [ChoiceObject] - deduplicationGoldenRecordStatusChoices: [ChoiceObject] - registrationDataImport(id: ID!): RegistrationDataImportNode - allRegistrationDataImports(offset: Int, before: String, after: String, first: Int, last: Int, importedBy_Id: UUID, importDate: Date, status: String, name: String, name_Startswith: String, businessArea: String, importDateRange: String, size: String, program: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, orderBy: String): RegistrationDataImportNodeConnection - registrationDataStatusChoices: [ChoiceObject] - _debug: DjangoDebug -} - -input RandomSamplingArguments { - confidenceInterval: Float! - marginOfError: Float! - excludedAdminAreas: [String] - age: AgeInput - sex: String -} - -input RapidProArguments { - flowId: String! -} - -type RapidProFlow { - id: String - name: String - type: String - archived: Boolean - labels: [String] - expires: Int - runs: [RapidProFlowRun] - results: [RapidProFlowResult] - createdOn: DateTime - modifiedOn: DateTime -} - -type RapidProFlowNode { - id: String - name: String -} - -type RapidProFlowResult { - key: String - name: String - categories: [String] - nodeUuids: [String] -} - -type RapidProFlowRun { - active: Int - completed: Int - interrupted: Int - expired: Int -} - -type ReassignRoleMutation { - household: HouseholdNode - individual: IndividualNode -} - -type RebuildTargetPopulationMutation { - targetPopulation: TargetPopulationNode -} - -type RecipientNode implements Node { - id: ID! - size: Int - headOfHousehold: IndividualNode -} - -type RecipientNodeConnection { - pageInfo: PageInfo! - edges: [RecipientNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type RecipientNodeEdge { - node: RecipientNode - cursor: String! -} - -type ReconciliationSummaryNode { - deliveredFully: Int - deliveredPartially: Int - notDelivered: Int - unsuccessful: Int - pending: Int - forceFailed: Int - numberOfPayments: Int - reconciled: Int -} - -input ReferralTicketExtras { - household: ID - individual: ID -} - -type RefuseRegistrationDataImportMutation { - registrationDataImport: RegistrationDataImportNode -} - -enum RegistrationDataImportDataSource { - XLS - KOBO - FLEX_REGISTRATION - API - EDOPOMOGA - PROGRAM_POPULATION -} - -enum RegistrationDataImportDatahubImportDone { - LOADING - NOT_STARTED - STARTED - DONE -} - -type RegistrationDataImportDatahubNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - name: String! - importDate: DateTime! - hctId: UUID - importData: ImportDataNode - importDone: RegistrationDataImportDatahubImportDone! - businessAreaSlug: String! -} - -type RegistrationDataImportNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - version: BigInt! - name: String! - status: RegistrationDataImportStatus! - importDate: DateTime! - importedBy: UserNode - dataSource: RegistrationDataImportDataSource! - numberOfIndividuals: Int! - numberOfHouseholds: Int! - batchDuplicates: Int! - batchPossibleDuplicates: Int! - batchUnique: Int! - goldenRecordDuplicates: Int! - goldenRecordPossibleDuplicates: Int! - goldenRecordUnique: Int! - datahubId: UUID - errorMessage: String! - sentryId: String - pullPictures: Boolean! - businessArea: UserBusinessAreaNode - screenBeneficiary: Boolean! - excluded: Boolean! - program: ProgramNode - erased: Boolean! - refuseReason: String - allowDeliveryMechanismsValidationErrors: Boolean! - importData: ImportDataNode - households(offset: Int, before: String, after: String, first: Int, last: Int): HouseholdNodeConnection! - individuals(offset: Int, before: String, after: String, first: Int, last: Int): IndividualNodeConnection! - grievanceticketSet(offset: Int, before: String, after: String, first: Int, last: Int): GrievanceTicketNodeConnection! - messages(offset: Int, before: String, after: String, first: Int, last: Int): CommunicationMessageNodeConnection! - adminUrl: String - batchDuplicatesCountAndPercentage: CountAndPercentageNode - batchPossibleDuplicatesCountAndPercentage: CountAndPercentageNode - batchUniqueCountAndPercentage: CountAndPercentageNode - goldenRecordDuplicatesCountAndPercentage: CountAndPercentageNode - goldenRecordPossibleDuplicatesCountAndPercentage: CountAndPercentageNode - goldenRecordUniqueCountAndPercentage: CountAndPercentageNode - totalHouseholdsCountWithValidPhoneNo: Int -} - -type RegistrationDataImportNodeConnection { - pageInfo: PageInfo! - edges: [RegistrationDataImportNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type RegistrationDataImportNodeEdge { - node: RegistrationDataImportNode - cursor: String! -} - -enum RegistrationDataImportStatus { - LOADING - DEDUPLICATION - DEDUPLICATION_FAILED - IMPORT_SCHEDULED - IMPORTING - IMPORT_ERROR - IN_REVIEW - MERGE_SCHEDULED - MERGED - MERGING - MERGE_ERROR - REFUSED -} - -type RegistrationDeduplicationMutation { - ok: Boolean -} - -type RegistrationKoboImportMutation { - validationErrors: Arg - registrationDataImport: RegistrationDataImportNode -} - -input RegistrationKoboImportMutationInput { - importDataId: String - name: String - pullPictures: Boolean - businessAreaSlug: String - screenBeneficiary: Boolean - allowDeliveryMechanismsValidationErrors: Boolean -} - -type RegistrationProgramPopulationImportMutation { - validationErrors: Arg - registrationDataImport: RegistrationDataImportNode -} - -input RegistrationProgramPopulationImportMutationInput { - importFromProgramId: String - name: String - businessAreaSlug: String - screenBeneficiary: Boolean -} - -type RegistrationXlsxImportMutation { - validationErrors: Arg - registrationDataImport: RegistrationDataImportNode -} - -input RegistrationXlsxImportMutationInput { - importDataId: ID - name: String - businessAreaSlug: String - screenBeneficiary: Boolean - allowDeliveryMechanismsValidationErrors: Boolean -} - -type ReportNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - businessArea: UserBusinessAreaNode! - file: String - createdBy: UserNode! - status: Int! - reportType: Int! - dateFrom: Date! - dateTo: Date! - numberOfRecords: Int - program: ProgramNode - adminArea(offset: Int, before: String, after: String, first: Int, last: Int, name: String): AreaNodeConnection! - fileUrl: String - adminArea1(offset: Int, before: String, after: String, first: Int, last: Int, name: String): AreaNodeConnection - adminArea2(offset: Int, before: String, after: String, first: Int, last: Int, name: String): AreaNodeConnection -} - -type ReportNodeConnection { - pageInfo: PageInfo! - edges: [ReportNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type ReportNodeEdge { - node: ReportNode - cursor: String! -} - -type RestartCreateReport { - report: ReportNode -} - -input RestartCreateReportInput { - reportId: ID! - businessAreaSlug: String! -} - -type RevertMarkPaymentAsFailedMutation { - payment: PaymentNode -} - -type RevertMarkPaymentRecordAsFailedMutation { - paymentRecord: PaymentRecordNode -} - -type RoleChoiceObject { - name: String - value: String - subsystem: String -} - -type RoleNode { - createdAt: DateTime! - updatedAt: DateTime! - name: String! - subsystem: RoleSubsystem! - permissions: [String!] - businessAreaPartnerThrough: [PartnerRoleNode!]! - userRoles: [UserRoleNode!]! -} - -enum RoleSubsystem { - HOPE - KOBO - CA - API -} - -enum RuleCommitLanguage { - PYTHON -} - -type RuleCommitNode implements Node { - id: ID! - timestamp: DateTime! - rule: SteficonRuleNode - updatedBy: UserNode - definition: String! - isRelease: Boolean! - enabled: Boolean! - deprecated: Boolean! - language: RuleCommitLanguage! - affectedFields: [String!]! - before: JSONString! - after: JSONString! - paymentPlans(offset: Int, before: String, after: String, first: Int, last: Int): PaymentPlanNodeConnection! - targetPopulations(offset: Int, before: String, after: String, first: Int, last: Int, program: [ID], createdAt: DateTime, createdAt_Lte: DateTime, createdAt_Gte: DateTime, updatedAt: DateTime, updatedAt_Lte: DateTime, updatedAt_Gte: DateTime, status: String, households: [ID], name: String, createdByName: String, totalHouseholdsCountMin: Int, totalHouseholdsCountMax: Int, totalIndividualsCountMin: Int, totalIndividualsCountMax: Int, businessArea: String, createdAtRange: String, paymentPlanApplicable: Boolean, statusNot: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, programCycle: String, orderBy: String): TargetPopulationNodeConnection! -} - -type RuleCommitNodeConnection { - pageInfo: PageInfo! - edges: [RuleCommitNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type RuleCommitNodeEdge { - node: RuleCommitNode - cursor: String! -} - -enum RuleLanguage { - PYTHON -} - -enum RuleSecurity { - A_0 - A_2 - A_4 -} - -enum RuleType { - PAYMENT_PLAN - TARGETING -} - -enum SamplingChoices { - FULL_LIST - RANDOM -} - -type SanctionListIndividualAliasNameNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - name: String! -} - -type SanctionListIndividualAliasNameNodeConnection { - pageInfo: PageInfo! - edges: [SanctionListIndividualAliasNameNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type SanctionListIndividualAliasNameNodeEdge { - node: SanctionListIndividualAliasNameNode - cursor: String! -} - -type SanctionListIndividualCountriesNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - country: String -} - -type SanctionListIndividualCountriesNodeConnection { - pageInfo: PageInfo! - edges: [SanctionListIndividualCountriesNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type SanctionListIndividualCountriesNodeEdge { - node: SanctionListIndividualCountriesNode - cursor: String! -} - -type SanctionListIndividualDateOfBirthNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - date: Date! -} - -type SanctionListIndividualDateOfBirthNodeConnection { - pageInfo: PageInfo! - edges: [SanctionListIndividualDateOfBirthNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type SanctionListIndividualDateOfBirthNodeEdge { - node: SanctionListIndividualDateOfBirthNode - cursor: String! -} - -type SanctionListIndividualDocumentNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - documentNumber: String! - typeOfDocument: String! - dateOfIssue: String - issuingCountry: String - note: String! -} - -type SanctionListIndividualDocumentNodeConnection { - pageInfo: PageInfo! - edges: [SanctionListIndividualDocumentNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type SanctionListIndividualDocumentNodeEdge { - node: SanctionListIndividualDocumentNode - cursor: String! -} - -type SanctionListIndividualNationalitiesNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - nationality: String -} - -type SanctionListIndividualNationalitiesNodeConnection { - pageInfo: PageInfo! - edges: [SanctionListIndividualNationalitiesNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type SanctionListIndividualNationalitiesNodeEdge { - node: SanctionListIndividualNationalitiesNode - cursor: String! -} - -type SanctionListIndividualNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - dataId: Int! - versionNum: Int! - firstName: String! - secondName: String! - thirdName: String! - fourthName: String! - fullName: String! - nameOriginalScript: String! - unListType: String! - referenceNumber: String! - listedOn: DateTime! - comments: String! - designation: String! - listType: String! - street: String! - city: String! - stateProvince: String! - addressNote: String! - countryOfBirth: String - active: Boolean! - documents(offset: Int, before: String, after: String, first: Int, last: Int): SanctionListIndividualDocumentNodeConnection! - nationalities(offset: Int, before: String, after: String, first: Int, last: Int): SanctionListIndividualNationalitiesNodeConnection! - countries(offset: Int, before: String, after: String, first: Int, last: Int): SanctionListIndividualCountriesNodeConnection! - aliasNames(offset: Int, before: String, after: String, first: Int, last: Int): SanctionListIndividualAliasNameNodeConnection! - datesOfBirth(offset: Int, before: String, after: String, first: Int, last: Int): SanctionListIndividualDateOfBirthNodeConnection! -} - -type SanctionListIndividualNodeConnection { - pageInfo: PageInfo! - edges: [SanctionListIndividualNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type SanctionListIndividualNodeEdge { - node: SanctionListIndividualNode - cursor: String! -} - -type SaveKoboProjectImportDataAsync { - importData: KoboImportDataNode -} - -type SectionTotalNode { - total: Float -} - -input SensitiveGrievanceTicketExtras { - household: ID - individual: ID - paymentRecord: [ID] -} - -type ServiceProviderNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - businessArea: UserBusinessAreaNode! - caId: String! - fullName: String - shortName: String - country: String! - visionId: String - cashPlans(offset: Int, before: String, after: String, first: Int, last: Int): CashPlanNodeConnection! - paymentrecordSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentRecordNodeConnection! -} - -type ServiceProviderNodeConnection { - pageInfo: PageInfo! - edges: [ServiceProviderNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type ServiceProviderNodeEdge { - node: ServiceProviderNode - cursor: String! -} - -type SetSteficonRuleOnPaymentPlanPaymentListMutation { - paymentPlan: PaymentPlanNode -} - -input SetSteficonRuleOnTargetPopulationMutationInput { - targetId: ID! - steficonRuleId: ID - version: BigInt - clientMutationId: String -} - -type SetSteficonRuleOnTargetPopulationMutationPayload { - targetPopulation: TargetPopulationNode - clientMutationId: String -} - -type SimpleApproveMutation { - grievanceTicket: GrievanceTicketNode -} - -type SplitPaymentPlanMutation { - paymentPlan: PaymentPlanNode -} - -type SteficonRuleNode implements Node { - id: ID! - allowedBusinessAreas(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID): UserBusinessAreaNodeConnection! - name: String! - definition: String! - description: String - enabled: Boolean! - deprecated: Boolean! - language: RuleLanguage! - security: RuleSecurity! - createdBy: UserNode - updatedBy: UserNode - createdAt: DateTime! - updatedAt: DateTime! - type: RuleType! - flags: JSONString! - history(offset: Int, before: String, after: String, first: Int, last: Int): RuleCommitNodeConnection! -} - -type SteficonRuleNodeConnection { - pageInfo: PageInfo! - edges: [SteficonRuleNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type SteficonRuleNodeEdge { - node: SteficonRuleNode - cursor: String! -} - -enum SurveyCategory { - RAPID_PRO - SMS - MANUAL -} - -type SurveyNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - unicefId: String - title: String! - body: String! - category: SurveyCategory! - numberOfRecipients: Int! - createdBy: UserNode - recipients(offset: Int, before: String, after: String, first: Int, last: Int): HouseholdNodeConnection! - targetPopulation: TargetPopulationNode - program: ProgramNode - businessArea: UserBusinessAreaNode! - sampleFile: String - sampleFileGeneratedAt: DateTime - samplingType: SurveySamplingType! - fullListArguments: JSONString! - randomSamplingArguments: JSONString! - sampleSize: Int! - flowId: String - successfulRapidProCalls: [JSONString!]! - adminUrl: String - sampleFilePath: String - hasValidSampleFile: Boolean - rapidProUrl: String -} - -type SurveyNodeConnection { - pageInfo: PageInfo! - edges: [SurveyNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type SurveyNodeEdge { - node: SurveyNode - cursor: String! -} - -enum SurveySamplingType { - FULL_LIST - RANDOM -} - -type TableTotalCashTransferred { - data: [_TableTotalCashTransferredDataNode] -} - -type TableTotalCashTransferredForPeople { - data: [_TableTotalCashTransferredDataForPeopleNode] -} - -enum TargetPopulationBuildStatus { - PENDING - BUILDING - FAILED - OK -} - -type TargetPopulationNode implements Node { - isRemoved: Boolean! - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - version: BigInt! - name: String! - caId: String - caHashId: String - createdBy: UserNode - changeDate: DateTime - changedBy: UserNode - finalizedAt: DateTime - finalizedBy: UserNode - businessArea: UserBusinessAreaNode - status: TargetPopulationStatus! - buildStatus: TargetPopulationBuildStatus! - builtAt: DateTime - households(offset: Int, before: String, after: String, first: Int, last: Int, orderBy: String, businessArea: String): HouseholdNodeConnection - program: ProgramNode - programCycle: ProgramCycleNode - targetingCriteria: TargetingCriteriaNode - sentToDatahub: Boolean! - steficonRule: RuleCommitNode - steficonAppliedDate: DateTime - vulnerabilityScoreMin: Float - vulnerabilityScoreMax: Float - excludedIds: String! - exclusionReason: String! - totalHouseholdsCount: Int - totalIndividualsCount: Int - childMaleCount: Int - childFemaleCount: Int - adultMaleCount: Int - adultFemaleCount: Int - paymentPlans(offset: Int, before: String, after: String, first: Int, last: Int): PaymentPlanNodeConnection! - paymentRecords(offset: Int, before: String, after: String, first: Int, last: Int): PaymentRecordNodeConnection! - selections: [HouseholdSelectionNode!]! - messages(offset: Int, before: String, after: String, first: Int, last: Int): CommunicationMessageNodeConnection! - surveys(offset: Int, before: String, after: String, first: Int, last: Int): SurveyNodeConnection! - adminUrl: String - totalFamilySize: Int - householdList(offset: Int, before: String, after: String, first: Int, last: Int, orderBy: String, businessArea: String): HouseholdNodeConnection - totalHouseholdsCountWithValidPhoneNo: Int - hasEmptyCriteria: Boolean - hasEmptyIdsCriteria: Boolean -} - -type TargetPopulationNodeConnection { - pageInfo: PageInfo! - edges: [TargetPopulationNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type TargetPopulationNodeEdge { - node: TargetPopulationNode - cursor: String! -} - -enum TargetPopulationStatus { - OPEN - LOCKED - STEFICON_WAIT - STEFICON_RUN - STEFICON_COMPLETED - STEFICON_ERROR - PROCESSING - SENDING_TO_CASH_ASSIST - READY_FOR_CASH_ASSIST - READY_FOR_PAYMENT_MODULE - ASSIGNED -} - -type TargetingCriteriaNode { - id: UUID! - createdAt: DateTime! - updatedAt: DateTime! - flagExcludeIfActiveAdjudicationTicket: Boolean! - flagExcludeIfOnSanctionList: Boolean! - householdIds: String! - individualIds: String! - targetPopulation: TargetPopulationNode - rules: [TargetingCriteriaRuleNode] -} - -input TargetingCriteriaObjectType { - rules: [TargetingCriteriaRuleObjectType] - flagExcludeIfActiveAdjudicationTicket: Boolean - flagExcludeIfOnSanctionList: Boolean - householdIds: String - individualIds: String -} - -enum TargetingCriteriaRuleFilterComparisonMethod { - EQUALS - NOT_EQUALS - CONTAINS - NOT_CONTAINS - RANGE - NOT_IN_RANGE - GREATER_THAN - LESS_THAN - IS_NULL -} - -enum TargetingCriteriaRuleFilterFlexFieldClassification { - NOT_FLEX_FIELD - FLEX_FIELD_NOT_PDU - FLEX_FIELD_PDU -} - -type TargetingCriteriaRuleFilterNode { - id: UUID! - createdAt: DateTime! - updatedAt: DateTime! - comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod! - targetingCriteriaRule: TargetingCriteriaRuleNode! - flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification! - fieldName: String! - arguments: [Arg] - fieldAttribute: FieldAttributeNode -} - -input TargetingCriteriaRuleFilterObjectType { - comparisonMethod: String! - flexFieldClassification: FlexFieldClassificationChoices! - fieldName: String! - arguments: [Arg]! - roundNumber: Int -} - -type TargetingCriteriaRuleNode { - id: UUID! - createdAt: DateTime! - updatedAt: DateTime! - targetingCriteria: TargetingCriteriaNode! - individualsFiltersBlocks: [TargetingIndividualRuleFilterBlockNode] - filters: [TargetingCriteriaRuleFilterNode] -} - -input TargetingCriteriaRuleObjectType { - filters: [TargetingCriteriaRuleFilterObjectType] - individualsFiltersBlocks: [TargetingIndividualRuleFilterBlockObjectType] -} - -enum TargetingIndividualBlockRuleFilterComparisonMethod { - EQUALS - NOT_EQUALS - CONTAINS - NOT_CONTAINS - RANGE - NOT_IN_RANGE - GREATER_THAN - LESS_THAN - IS_NULL -} - -enum TargetingIndividualBlockRuleFilterFlexFieldClassification { - NOT_FLEX_FIELD - FLEX_FIELD_NOT_PDU - FLEX_FIELD_PDU -} - -type TargetingIndividualBlockRuleFilterNode { - id: UUID! - createdAt: DateTime! - updatedAt: DateTime! - comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod! - individualsFiltersBlock: TargetingIndividualRuleFilterBlockNode! - flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification! - fieldName: String! - arguments: [Arg] - roundNumber: Int - fieldAttribute: FieldAttributeNode -} - -type TargetingIndividualRuleFilterBlockNode { - id: UUID! - createdAt: DateTime! - updatedAt: DateTime! - targetingCriteriaRule: TargetingCriteriaRuleNode! - targetOnlyHoh: Boolean! - individualBlockFilters: [TargetingIndividualBlockRuleFilterNode] -} - -input TargetingIndividualRuleFilterBlockObjectType { - individualBlockFilters: [TargetingCriteriaRuleFilterObjectType] -} - -type TicketAddIndividualDetailsNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - household: HouseholdNode - individualData: Arg - approveStatus: Boolean! -} - -type TicketAddIndividualDetailsNodeConnection { - pageInfo: PageInfo! - edges: [TicketAddIndividualDetailsNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type TicketAddIndividualDetailsNodeEdge { - node: TicketAddIndividualDetailsNode - cursor: String! -} - -type TicketByType { - userGeneratedCount: Int - systemGeneratedCount: Int - closedUserGeneratedCount: Int - closedSystemGeneratedCount: Int - userGeneratedAvgResolution: Float - systemGeneratedAvgResolution: Float -} - -type TicketComplaintDetailsNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - paymentContentType: ContentTypeObjectType - paymentObjectId: UUID - household: HouseholdNode - individual: IndividualNode - paymentRecord: PaymentRecordAndPaymentNode -} - -type TicketComplaintDetailsNodeConnection { - pageInfo: PageInfo! - edges: [TicketComplaintDetailsNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type TicketComplaintDetailsNodeEdge { - node: TicketComplaintDetailsNode - cursor: String! -} - -type TicketDeleteHouseholdDetailsNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - household: HouseholdNode - roleReassignData: JSONString! - approveStatus: Boolean! - reasonHousehold: HouseholdNode - householdData: Arg -} - -type TicketDeleteHouseholdDetailsNodeConnection { - pageInfo: PageInfo! - edges: [TicketDeleteHouseholdDetailsNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type TicketDeleteHouseholdDetailsNodeEdge { - node: TicketDeleteHouseholdDetailsNode - cursor: String! -} - -type TicketDeleteIndividualDetailsNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - individual: IndividualNode - roleReassignData: JSONString! - approveStatus: Boolean! - individualData: Arg -} - -type TicketDeleteIndividualDetailsNodeConnection { - pageInfo: PageInfo! - edges: [TicketDeleteIndividualDetailsNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type TicketDeleteIndividualDetailsNodeEdge { - node: TicketDeleteIndividualDetailsNode - cursor: String! -} - -type TicketHouseholdDataUpdateDetailsNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - household: HouseholdNode - householdData: Arg -} - -type TicketHouseholdDataUpdateDetailsNodeConnection { - pageInfo: PageInfo! - edges: [TicketHouseholdDataUpdateDetailsNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type TicketHouseholdDataUpdateDetailsNodeEdge { - node: TicketHouseholdDataUpdateDetailsNode - cursor: String! -} - -type TicketIndividualDataUpdateDetailsNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - individual: IndividualNode - individualData: Arg - roleReassignData: JSONString! -} - -type TicketIndividualDataUpdateDetailsNodeConnection { - pageInfo: PageInfo! - edges: [TicketIndividualDataUpdateDetailsNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type TicketIndividualDataUpdateDetailsNodeEdge { - node: TicketIndividualDataUpdateDetailsNode - cursor: String! -} - -type TicketNeedsAdjudicationDetailsExtraDataNode { - goldenRecords: [DeduplicationResultNode] - possibleDuplicate: [DeduplicationResultNode] -} - -type TicketNeedsAdjudicationDetailsNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - isMultipleDuplicatesVersion: Boolean! - goldenRecordsIndividual: IndividualNode! - possibleDuplicates: [IndividualNode] - selectedDistinct: [IndividualNode] - selectedIndividuals(offset: Int, before: String, after: String, first: Int, last: Int): IndividualNodeConnection! - roleReassignData: JSONString! - extraData: TicketNeedsAdjudicationDetailsExtraDataNode - scoreMin: Float! - scoreMax: Float! - isCrossArea: Boolean! - selectedIndividual: IndividualNode - possibleDuplicate: IndividualNode - hasDuplicatedDocument: Boolean - selectedDuplicates: [IndividualNode] -} - -type TicketNeedsAdjudicationDetailsNodeConnection { - pageInfo: PageInfo! - edges: [TicketNeedsAdjudicationDetailsNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type TicketNeedsAdjudicationDetailsNodeEdge { - node: TicketNeedsAdjudicationDetailsNode - cursor: String! -} - -type TicketNegativeFeedbackDetailsNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - household: HouseholdNode - individual: IndividualNode -} - -type TicketNegativeFeedbackDetailsNodeConnection { - pageInfo: PageInfo! - edges: [TicketNegativeFeedbackDetailsNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type TicketNegativeFeedbackDetailsNodeEdge { - node: TicketNegativeFeedbackDetailsNode - cursor: String! -} - -type TicketNoteNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - description: String! - createdBy: UserNode -} - -type TicketNoteNodeConnection { - pageInfo: PageInfo! - edges: [TicketNoteNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type TicketNoteNodeEdge { - node: TicketNoteNode - cursor: String! -} - -input TicketPaymentVerificationDetailsExtras { - newReceivedAmount: Float - newStatus: String -} - -enum TicketPaymentVerificationDetailsNewStatus { - NOT_RECEIVED - PENDING - RECEIVED - RECEIVED_WITH_ISSUES -} - -type TicketPaymentVerificationDetailsNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - paymentVerifications(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationNodeConnection! - paymentVerificationStatus: TicketPaymentVerificationDetailsPaymentVerificationStatus! - paymentVerification: PaymentVerificationNode - newStatus: TicketPaymentVerificationDetailsNewStatus - oldReceivedAmount: Float - newReceivedAmount: Float - approveStatus: Boolean! - hasMultiplePaymentVerifications: Boolean -} - -type TicketPaymentVerificationDetailsNodeConnection { - pageInfo: PageInfo! - edges: [TicketPaymentVerificationDetailsNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type TicketPaymentVerificationDetailsNodeEdge { - node: TicketPaymentVerificationDetailsNode - cursor: String! -} - -enum TicketPaymentVerificationDetailsPaymentVerificationStatus { - NOT_RECEIVED - PENDING - RECEIVED - RECEIVED_WITH_ISSUES -} - -type TicketPositiveFeedbackDetailsNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - household: HouseholdNode - individual: IndividualNode -} - -type TicketPositiveFeedbackDetailsNodeConnection { - pageInfo: PageInfo! - edges: [TicketPositiveFeedbackDetailsNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type TicketPositiveFeedbackDetailsNodeEdge { - node: TicketPositiveFeedbackDetailsNode - cursor: String! -} - -type TicketReferralDetailsNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - household: HouseholdNode - individual: IndividualNode -} - -type TicketReferralDetailsNodeConnection { - pageInfo: PageInfo! - edges: [TicketReferralDetailsNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type TicketReferralDetailsNodeEdge { - node: TicketReferralDetailsNode - cursor: String! -} - -type TicketSensitiveDetailsNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - paymentContentType: ContentTypeObjectType - paymentObjectId: UUID - household: HouseholdNode - individual: IndividualNode - paymentRecord: PaymentRecordAndPaymentNode -} - -type TicketSensitiveDetailsNodeConnection { - pageInfo: PageInfo! - edges: [TicketSensitiveDetailsNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type TicketSensitiveDetailsNodeEdge { - node: TicketSensitiveDetailsNode - cursor: String! -} - -type TicketSystemFlaggingDetailsNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - goldenRecordsIndividual: IndividualNode! - sanctionListIndividual: SanctionListIndividualNode! - approveStatus: Boolean! - roleReassignData: JSONString! -} - -type TicketSystemFlaggingDetailsNodeConnection { - pageInfo: PageInfo! - edges: [TicketSystemFlaggingDetailsNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type TicketSystemFlaggingDetailsNodeEdge { - node: TicketSystemFlaggingDetailsNode - cursor: String! -} - -scalar UUID - -type UnlockTargetPopulationMutation { - targetPopulation: TargetPopulationNode -} - -input UpdateAddIndividualIssueTypeExtras { - individualData: AddIndividualDataObjectType! -} - -input UpdateFeedbackInput { - feedbackId: ID! - issueType: String - householdLookup: ID - individualLookup: ID - description: String - comments: String - admin2: ID - area: String - language: String - consent: Boolean - program: ID -} - -type UpdateFeedbackMutation { - feedback: FeedbackNode -} - -input UpdateGrievanceTicketExtrasInput { - householdDataUpdateIssueTypeExtras: UpdateHouseholdDataUpdateIssueTypeExtras - individualDataUpdateIssueTypeExtras: UpdateIndividualDataUpdateIssueTypeExtras - addIndividualIssueTypeExtras: UpdateAddIndividualIssueTypeExtras - category: CategoryExtrasInput - ticketPaymentVerificationDetailsExtras: TicketPaymentVerificationDetailsExtras -} - -input UpdateGrievanceTicketInput { - ticketId: ID! - description: String - assignedTo: ID - admin: ID - area: String - language: String - linkedTickets: [ID] - household: ID - individual: ID - paymentRecord: ID - extras: UpdateGrievanceTicketExtrasInput - priority: Int - urgency: Int - partner: Int - program: ID - comments: String - documentation: [GrievanceDocumentInput] - documentationToUpdate: [GrievanceDocumentUpdateInput] - documentationToDelete: [ID] -} - -type UpdateGrievanceTicketMutation { - grievanceTicket: GrievanceTicketNode -} - -input UpdateHouseholdDataUpdateIssueTypeExtras { - householdData: HouseholdUpdateDataObjectType! -} - -input UpdateIndividualDataUpdateIssueTypeExtras { - individualData: IndividualUpdateDataObjectType! -} - -input UpdatePaymentPlanInput { - paymentPlanId: ID! - targetingId: ID - dispersionStartDate: Date - dispersionEndDate: Date - currency: String -} - -type UpdatePaymentPlanMutation { - paymentPlan: PaymentPlanNode -} - -type UpdatePaymentVerificationReceivedAndReceivedAmount { - paymentVerification: PaymentVerificationNode -} - -type UpdatePaymentVerificationStatusAndReceivedAmount { - paymentVerification: PaymentVerificationNode -} - -type UpdateProgram { - validationErrors: Arg - program: ProgramNode -} - -input UpdateProgramInput { - id: String! - name: String - status: String - startDate: Date - endDate: Date - description: String - budget: Decimal - frequencyOfPayments: String - sector: String - cashPlus: Boolean - populationGoal: Int - administrativeAreasOfImplementation: String - dataCollectingTypeCode: String - partners: [ProgramPartnerThroughInput] - partnerAccess: String - programmeCode: String - pduFields: [PDUFieldInput] -} - -input UpdateTargetPopulationInput { - id: ID! - name: String - targetingCriteria: TargetingCriteriaObjectType - programId: ID - programCycleId: ID - vulnerabilityScoreMin: Decimal - vulnerabilityScoreMax: Decimal - excludedIds: String - exclusionReason: String -} - -type UpdateTargetPopulationMutation { - validationErrors: Arg - targetPopulation: TargetPopulationNode -} - -scalar Upload - -type UploadImportDataXLSXFileAsync { - importData: ImportDataNode - errors: [XlsxRowErrorNode] -} - -type UserBusinessAreaNode implements Node { - id: ID! - createdAt: DateTime! - updatedAt: DateTime! - code: String! - name: String! - longName: String! - regionCode: String! - regionName: String! - koboUsername: String - koboToken: String - koboUrl: String - rapidProHost: String - rapidProPaymentVerificationToken: String - rapidProMessagesToken: String - rapidProSurveyToken: String - slug: String! - customFields: JSONString! - hasDataSharingAgreement: Boolean! - parent: UserBusinessAreaNode - isSplit: Boolean! - postponeDeduplication: Boolean! - deduplicationDuplicateScore: Float! - deduplicationPossibleDuplicateScore: Float! - deduplicationBatchDuplicatesPercentage: Int! - deduplicationBatchDuplicatesAllowed: Int! - deduplicationGoldenRecordDuplicatesPercentage: Int! - deduplicationGoldenRecordDuplicatesAllowed: Int! - screenBeneficiary: Boolean! - deduplicationIgnoreWithdraw: Boolean! - isPaymentPlanApplicable: Boolean! - isAccountabilityApplicable: Boolean - active: Boolean! - enableEmailNotification: Boolean! - partners: [PartnerNode!]! - businessAreaPartnerThrough: [PartnerRoleNode!]! - children(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID): UserBusinessAreaNodeConnection! - dataCollectingTypes(offset: Int, before: String, after: String, first: Int, last: Int): DataCollectingTypeNodeConnection! - partnerSet: [PartnerNode!]! - userRoles: [UserRoleNode!]! - householdSet(offset: Int, before: String, after: String, first: Int, last: Int): HouseholdNodeConnection! - individualSet(offset: Int, before: String, after: String, first: Int, last: Int): IndividualNodeConnection! - registrationdataimportSet(offset: Int, before: String, after: String, first: Int, last: Int): RegistrationDataImportNodeConnection! - ruleSet(offset: Int, before: String, after: String, first: Int, last: Int): SteficonRuleNodeConnection! - paymentplanSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentPlanNodeConnection! - financialserviceproviderSet(offset: Int, before: String, after: String, first: Int, last: Int): FinancialServiceProviderNodeConnection! - cashplanSet(offset: Int, before: String, after: String, first: Int, last: Int): CashPlanNodeConnection! - paymentrecordSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentRecordNodeConnection! - paymentSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentNodeConnection! - serviceproviderSet(offset: Int, before: String, after: String, first: Int, last: Int): ServiceProviderNodeConnection! - tickets(offset: Int, before: String, after: String, first: Int, last: Int): GrievanceTicketNodeConnection! - targetpopulationSet(offset: Int, before: String, after: String, first: Int, last: Int, program: [ID], createdAt: DateTime, createdAt_Lte: DateTime, createdAt_Gte: DateTime, updatedAt: DateTime, updatedAt_Lte: DateTime, updatedAt_Gte: DateTime, status: String, households: [ID], name: String, createdByName: String, totalHouseholdsCountMin: Int, totalHouseholdsCountMax: Int, totalIndividualsCountMin: Int, totalIndividualsCountMax: Int, businessArea: String, createdAtRange: String, paymentPlanApplicable: Boolean, statusNot: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, programCycle: String, orderBy: String): TargetPopulationNodeConnection! - programSet(offset: Int, before: String, after: String, first: Int, last: Int, name: String): ProgramNodeConnection! - reports(offset: Int, before: String, after: String, first: Int, last: Int): ReportNodeConnection! - logentrySet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationLogEntryNodeConnection! - messageSet(offset: Int, before: String, after: String, first: Int, last: Int): CommunicationMessageNodeConnection! - feedbackSet(offset: Int, before: String, after: String, first: Int, last: Int): FeedbackNodeConnection! - surveySet(offset: Int, before: String, after: String, first: Int, last: Int): SurveyNodeConnection! - permissions: [String] -} - -type UserBusinessAreaNodeConnection { - pageInfo: PageInfo! - edges: [UserBusinessAreaNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type UserBusinessAreaNodeEdge { - node: UserBusinessAreaNode - cursor: String! -} - -type UserNode implements Node { - lastLogin: DateTime - isSuperuser: Boolean! - username: String! - firstName: String! - lastName: String! - isStaff: Boolean! - isActive: Boolean! - dateJoined: DateTime! - id: ID! - status: UserStatus! - partner: PartnerNode - email: String! - availableForExport: Boolean! - customFields: JSONString! - jobTitle: String! - adUuid: String - lastModifyDate: DateTime - lastDoapSync: DateTime - doapHash: String! - userRoles: [UserRoleNode!]! - documentSet(offset: Int, before: String, after: String, first: Int, last: Int): DocumentNodeConnection! - registrationDataImports(offset: Int, before: String, after: String, first: Int, last: Int): RegistrationDataImportNodeConnection! - createdPaymentPlans(offset: Int, before: String, after: String, first: Int, last: Int): PaymentPlanNodeConnection! - createdFinancialServiceProviderXlsxTemplates(offset: Int, before: String, after: String, first: Int, last: Int): FinancialServiceProviderXlsxTemplateNodeConnection! - createdFinancialServiceProviders(offset: Int, before: String, after: String, first: Int, last: Int): FinancialServiceProviderNodeConnection! - createdDeliveryMechanisms(offset: Int, before: String, after: String, first: Int, last: Int): DeliveryMechanismPerPaymentPlanNodeConnection! - sentDeliveryMechanisms(offset: Int, before: String, after: String, first: Int, last: Int): DeliveryMechanismPerPaymentPlanNodeConnection! - approvalSet: [ApprovalNode!]! - createdTickets(offset: Int, before: String, after: String, first: Int, last: Int): GrievanceTicketNodeConnection! - assignedTickets(offset: Int, before: String, after: String, first: Int, last: Int): GrievanceTicketNodeConnection! - ticketNotes(offset: Int, before: String, after: String, first: Int, last: Int): TicketNoteNodeConnection! - targetPopulations(offset: Int, before: String, after: String, first: Int, last: Int, program: [ID], createdAt: DateTime, createdAt_Lte: DateTime, createdAt_Gte: DateTime, updatedAt: DateTime, updatedAt_Lte: DateTime, updatedAt_Gte: DateTime, status: String, households: [ID], name: String, createdByName: String, totalHouseholdsCountMin: Int, totalHouseholdsCountMax: Int, totalIndividualsCountMin: Int, totalIndividualsCountMax: Int, businessArea: String, createdAtRange: String, paymentPlanApplicable: Boolean, statusNot: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, programCycle: String, orderBy: String): TargetPopulationNodeConnection! - changedTargetPopulations(offset: Int, before: String, after: String, first: Int, last: Int, program: [ID], createdAt: DateTime, createdAt_Lte: DateTime, createdAt_Gte: DateTime, updatedAt: DateTime, updatedAt_Lte: DateTime, updatedAt_Gte: DateTime, status: String, households: [ID], name: String, createdByName: String, totalHouseholdsCountMin: Int, totalHouseholdsCountMax: Int, totalIndividualsCountMin: Int, totalIndividualsCountMax: Int, businessArea: String, createdAtRange: String, paymentPlanApplicable: Boolean, statusNot: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, programCycle: String, orderBy: String): TargetPopulationNodeConnection! - finalizedTargetPopulations(offset: Int, before: String, after: String, first: Int, last: Int, program: [ID], createdAt: DateTime, createdAt_Lte: DateTime, createdAt_Gte: DateTime, updatedAt: DateTime, updatedAt_Lte: DateTime, updatedAt_Gte: DateTime, status: String, households: [ID], name: String, createdByName: String, totalHouseholdsCountMin: Int, totalHouseholdsCountMax: Int, totalIndividualsCountMin: Int, totalIndividualsCountMax: Int, businessArea: String, createdAtRange: String, paymentPlanApplicable: Boolean, statusNot: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, programCycle: String, orderBy: String): TargetPopulationNodeConnection! - reports(offset: Int, before: String, after: String, first: Int, last: Int): ReportNodeConnection! - logs(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationLogEntryNodeConnection! - messages(offset: Int, before: String, after: String, first: Int, last: Int): CommunicationMessageNodeConnection! - feedbacks(offset: Int, before: String, after: String, first: Int, last: Int): FeedbackNodeConnection! - feedbackMessages(offset: Int, before: String, after: String, first: Int, last: Int): FeedbackMessageNodeConnection! - surveys(offset: Int, before: String, after: String, first: Int, last: Int): SurveyNodeConnection! - businessAreas(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID): UserBusinessAreaNodeConnection - partnerRoles: [PartnerRoleNode] -} - -type UserNodeConnection { - pageInfo: PageInfo! - edges: [UserNodeEdge]! - totalCount: Int - edgeCount: Int -} - -type UserNodeEdge { - node: UserNode - cursor: String! -} - -type UserRoleNode { - createdAt: DateTime! - updatedAt: DateTime! - businessArea: UserBusinessAreaNode! - role: RoleNode! - expiryDate: Date -} - -enum UserStatus { - ACTIVE - INACTIVE - INVITED -} - -type VolumeByDeliveryMechanismNode implements Node { - id: ID! - deliveryMechanism: DeliveryMechanismPerPaymentPlanNode - volume: Float - volumeUsd: Float -} - -type XlsxErrorNode { - sheet: String - coordinates: String - message: String -} - -type XlsxRowErrorNode { - rowNumber: Int - header: String - message: String -} - -type _DatasetsNode { - data: [Float] -} - -type _DetailedDatasetsNode { - label: String - data: [Float] -} - -type _TableTotalCashTransferredDataForPeopleNode { - id: String - admin2: String - totalCashTransferred: Float - totalPeople: Int -} - -type _TableTotalCashTransferredDataNode { - id: String - admin2: String - totalCashTransferred: Float - totalHouseholds: Int -} From cd35f33b3dc2300643bb69c875c0e3873266352c Mon Sep 17 00:00:00 2001 From: Szymon Wyderka Date: Fri, 23 Aug 2024 14:54:21 +0200 Subject: [PATCH 095/118] Added tests test_program_details_edit_cycle_with_wrong_date and test_program_details_add_new_cycle_with_wrong_date --- .../program_details/test_program_details.py | 56 +++++++++++-------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/backend/selenium_tests/program_details/test_program_details.py b/backend/selenium_tests/program_details/test_program_details.py index 9bf1d23d9d..25adaf9463 100644 --- a/backend/selenium_tests/program_details/test_program_details.py +++ b/backend/selenium_tests/program_details/test_program_details.py @@ -51,8 +51,14 @@ def program_with_different_cycles() -> Program: program = get_program_with_dct_type_and_name( "ThreeCyclesProgramme", "cycl", status=Program.ACTIVE, program_cycle_status=ProgramCycle.DRAFT ) - ProgramCycleFactory(program=program, status=ProgramCycle.ACTIVE) - ProgramCycleFactory(program=program, status=ProgramCycle.FINISHED) + ProgramCycleFactory(program=program, + status=ProgramCycle.ACTIVE, + start_date=datetime.now() + relativedelta(days=11), + end_date=datetime.now() + relativedelta(days=17)) + ProgramCycleFactory(program=program, + status=ProgramCycle.FINISHED, + start_date=datetime.now() + relativedelta(days=18), + end_date=datetime.now() + relativedelta(days=20)) program.save() yield program @@ -497,24 +503,24 @@ def test_program_details_delete_programme_cycle( ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("ThreeCyclesProgramme") for _ in range(50): - if 3 == len(pageProgrammeDetails.getProgramCycleId()): + if 3 == len(pageProgrammeDetails.getProgramCycleTitle()): break sleep(0.1) else: - assert 3 == len(pageProgrammeDetails.getProgramCycleId()) - program_cycle_1 = pageProgrammeDetails.getProgramCycleId()[0].text - program_cycle_3 = pageProgrammeDetails.getProgramCycleId()[2].text + assert 3 == len(pageProgrammeDetails.getProgramCycleTitle()) + program_cycle_1 = pageProgrammeDetails.getProgramCycleTitle()[0].text + program_cycle_3 = pageProgrammeDetails.getProgramCycleTitle()[2].text pageProgrammeDetails.getDeleteProgrammeCycle()[1].click() pageProgrammeDetails.getButtonDelete().click() for _ in range(50): - if 3 == len(pageProgrammeDetails.getProgramCycleId()): + if 3 == len(pageProgrammeDetails.getProgramCycleTitle()): break sleep(0.1) else: - assert 2 == len(pageProgrammeDetails.getProgramCycleId()) + assert 2 == len(pageProgrammeDetails.getProgramCycleTitle()) - assert program_cycle_1 in pageProgrammeDetails.getProgramCycleId()[0].text - assert program_cycle_3 in pageProgrammeDetails.getProgramCycleId()[1].text + assert program_cycle_1 in pageProgrammeDetails.getProgramCycleTitle()[0].text + assert program_cycle_3 in pageProgrammeDetails.getProgramCycleTitle()[1].text def test_program_details_buttons_vs_programme_cycle_status( self, program_with_different_cycles: Program, pageProgrammeDetails: ProgrammeDetails @@ -580,6 +586,7 @@ def test_program_details_add_new_cycle_with_wrong_date( pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") assert "ACTIVE" in pageProgrammeDetails.getProgramStatus().text pageProgrammeDetails.getButtonAddNewProgrammeCycle().click() + pageProgrammeDetails.getInputTitle().send_keys(Keys.CONTROL + "a") pageProgrammeDetails.getInputTitle().send_keys("New cycle with wrong date") pageProgrammeDetails.getStartDateCycle().click() pageProgrammeDetails.getStartDateCycle().send_keys( @@ -634,11 +641,12 @@ def test_program_details_add_new_cycle_with_wrong_date( assert "New cycle with wrong date" in pageProgrammeDetails.getProgramCycleTitle()[1].text def test_program_details_edit_cycle_with_wrong_date( - self, standard_active_program_cycle_draft: Program, pageProgrammeDetails: ProgrammeDetails + self, program_with_different_cycles: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: - pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") + pageProgrammeDetails.selectGlobalProgramFilter("ThreeCyclesProgramme") assert "ACTIVE" in pageProgrammeDetails.getProgramStatus().text - pageProgrammeDetails.getButtonEditProgramCycle()[0].click() + pageProgrammeDetails.getButtonEditProgramCycle()[1].click() + pageProgrammeDetails.getInputTitle().send_keys(Keys.CONTROL + "a") pageProgrammeDetails.getInputTitle().send_keys("New cycle with wrong date") pageProgrammeDetails.getStartDateCycle().click() pageProgrammeDetails.getStartDateCycle().send_keys( @@ -667,28 +675,30 @@ def test_program_details_edit_cycle_with_wrong_date( pageProgrammeDetails.getEndDateCycle().click() pageProgrammeDetails.getEndDateCycle().send_keys(Keys.CONTROL + "a") - pageProgrammeDetails.getEndDateCycle().send_keys((datetime.now() + relativedelta(days=1)).strftime("%Y-%m-%d")) + pageProgrammeDetails.getEndDateCycle().send_keys((datetime.now() + relativedelta(days=12)).strftime("%Y-%m-%d")) pageProgrammeDetails.getButtonSave().click() - for _ in range(50): - if "Programme Cycles' timeframes must not overlap with the provided start date." in pageProgrammeDetails.getStartDateCycleDiv().text: - break - sleep(0.1) - assert "Programme Cycles' timeframes must not overlap with the provided start date." in pageProgrammeDetails.getStartDateCycleDiv().text + # ToDo: Lack of information about wrong date + # for _ in range(50): + # if "Programme Cycles' timeframes must not overlap with the provided start date." in pageProgrammeDetails.getStartDateCycleDiv().text: + # break + # sleep(0.1) + # assert "Programme Cycles' timeframes must not overlap with the provided start date." in pageProgrammeDetails.getStartDateCycleDiv().text + pageProgrammeDetails.getStartDateCycle().click() pageProgrammeDetails.getStartDateCycle().send_keys( - (datetime.now() + relativedelta(days=1)).strftime("%Y-%m-%d") + (datetime.now() + relativedelta(days=12)).strftime("%Y-%m-%d") ) pageProgrammeDetails.getButtonSave().click() pageProgrammeDetails.getButtonAddNewProgrammeCycle() pageProgrammeDetails.getProgramCycleRow() - assert "Draft" in pageProgrammeDetails.getProgramCycleStatus()[1].text - assert (datetime.now() + relativedelta(days=1)).strftime( + assert "Active" in pageProgrammeDetails.getProgramCycleStatus()[1].text + assert (datetime.now() + relativedelta(days=12)).strftime( "%-d %b %Y" ) in pageProgrammeDetails.getProgramCycleStartDate()[1].text - assert (datetime.now() + relativedelta(days=1)).strftime( + assert (datetime.now() + relativedelta(days=12)).strftime( "%-d %b %Y" ) in pageProgrammeDetails.getProgramCycleEndDate()[1].text assert "New cycle with wrong date" in pageProgrammeDetails.getProgramCycleTitle()[1].text From 502961cb663d2cad0270c367d57fb85d0b8a6052 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Fri, 23 Aug 2024 15:39:28 +0200 Subject: [PATCH 096/118] resolve conflicts --- .../apps/core/migrations/0083_migration.py | 14 ------ .../apps/core/migrations/0085_migration.py | 18 ++++++++ backend/hct_mis_api/apps/program/schema.py | 4 -- .../targeting/migrations/0045_migration.py | 39 ---------------- .../targeting/migrations/0046_migration.py | 44 ++++++++++++++++--- .../targeting/migrations/0047_migration.py | 17 ------- .../targeting/migrations/0048_migration.py | 23 ---------- .../test_create_target_population_mutation.py | 14 +----- .../apps/targeting/tests/test_target_query.py | 9 +--- .../page_object/targeting/targeting_create.py | 7 +-- 10 files changed, 62 insertions(+), 127 deletions(-) create mode 100644 backend/hct_mis_api/apps/core/migrations/0085_migration.py delete mode 100644 backend/hct_mis_api/apps/targeting/migrations/0047_migration.py delete mode 100644 backend/hct_mis_api/apps/targeting/migrations/0048_migration.py diff --git a/backend/hct_mis_api/apps/core/migrations/0083_migration.py b/backend/hct_mis_api/apps/core/migrations/0083_migration.py index a3732c2658..a4aaf7bcfd 100644 --- a/backend/hct_mis_api/apps/core/migrations/0083_migration.py +++ b/backend/hct_mis_api/apps/core/migrations/0083_migration.py @@ -1,12 +1,6 @@ -<<<<<<< HEAD -# Generated by Django 3.2.25 on 2024-08-19 15:45 - -from django.db import migrations, models -======= # Generated by Django 3.2.25 on 2024-07-17 10:18 from django.db import migrations ->>>>>>> origin class Migration(migrations.Migration): @@ -16,13 +10,6 @@ class Migration(migrations.Migration): ] operations = [ -<<<<<<< HEAD - migrations.AlterField( - model_name='periodicfielddata', - name='subtype', - field=models.CharField(choices=[('DATE', 'Date'), ('DECIMAL', 'Number'), ('STRING', 'Text'), ('BOOL', 'Boolean (true/false)')], max_length=16), - ), -======= migrations.RunSQL( sql=""" CREATE OR REPLACE FUNCTION program_cycle_business_area_seq() RETURNS trigger @@ -54,5 +41,4 @@ class Migration(migrations.Migration): SELECT id, program_cycle_business_area_for_old_ba(id::text) AS result FROM core_businessarea; """, ) ->>>>>>> origin ] diff --git a/backend/hct_mis_api/apps/core/migrations/0085_migration.py b/backend/hct_mis_api/apps/core/migrations/0085_migration.py new file mode 100644 index 0000000000..e13e9a19c0 --- /dev/null +++ b/backend/hct_mis_api/apps/core/migrations/0085_migration.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.25 on 2024-08-19 15:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0084_migration'), + ] + + operations = [ + migrations.AlterField( + model_name='periodicfielddata', + name='subtype', + field=models.CharField(choices=[('DATE', 'Date'), ('DECIMAL', 'Number'), ('STRING', 'Text'), ('BOOL', 'Boolean (true/false)')], max_length=16), + ), + ] \ No newline at end of file diff --git a/backend/hct_mis_api/apps/program/schema.py b/backend/hct_mis_api/apps/program/schema.py index cc4b7ce730..6a0d98ff46 100644 --- a/backend/hct_mis_api/apps/program/schema.py +++ b/backend/hct_mis_api/apps/program/schema.py @@ -113,12 +113,8 @@ class ProgramNode(BaseNodePermissionMixin, AdminUrlNodeMixin, DjangoObjectType): data_collecting_type = graphene.Field(DataCollectingTypeNode, source="data_collecting_type") partners = graphene.List(PartnerNode) is_social_worker_program = graphene.Boolean() - pdu_fields = graphene.List(PeriodicFieldNode) -<<<<<<< HEAD target_populations_count = graphene.Int() -======= cycles = DjangoFilterConnectionField(ProgramCycleNode, filterset_class=ProgramCycleFilter) ->>>>>>> origin class Meta: model = Program diff --git a/backend/hct_mis_api/apps/targeting/migrations/0045_migration.py b/backend/hct_mis_api/apps/targeting/migrations/0045_migration.py index cbffbea8f9..803abfc656 100644 --- a/backend/hct_mis_api/apps/targeting/migrations/0045_migration.py +++ b/backend/hct_mis_api/apps/targeting/migrations/0045_migration.py @@ -1,59 +1,20 @@ -<<<<<<< HEAD -# Generated by Django 3.2.25 on 2024-08-05 23:07 - -from django.db import migrations, models - - -def convert_is_flex_field_to_classification(apps, schema_editor): - TargetingCriteriaRuleFilter = apps.get_model('targeting', 'TargetingCriteriaRuleFilter') - TargetingCriteriaRuleFilter.objects.filter(is_flex_field=True).update(flex_field_classification='FLEX_FIELD_NOT_PDU') - TargetingCriteriaRuleFilter.objects.filter(is_flex_field=False).update(flex_field_classification='NOT_FLEX_FIELD') - - TargetingIndividualBlockRuleFilter = apps.get_model('targeting', 'TargetingIndividualBlockRuleFilter') - TargetingIndividualBlockRuleFilter.objects.filter(is_flex_field=True).update(flex_field_classification='FLEX_FIELD_NOT_PDU') - TargetingIndividualBlockRuleFilter.objects.filter(is_flex_field=False).update(flex_field_classification='NOT_FLEX_FIELD') -======= # Generated by Django 3.2.25 on 2024-07-18 18:34 from django.db import migrations, models import django.db.models.deletion ->>>>>>> origin class Migration(migrations.Migration): dependencies = [ -<<<<<<< HEAD -======= ('program', '0049_migration'), ->>>>>>> origin ('targeting', '0044_migration'), ] operations = [ migrations.AddField( -<<<<<<< HEAD - model_name='targetingcriteriarulefilter', - name='flex_field_classification', - field=models.CharField(choices=[('NOT_FLEX_FIELD', 'Not Flex Field'), ('FLEX_FIELD_NOT_PDU', 'Flex Field Not PDU'), ('FLEX_FIELD_PDU', 'Flex Field PDU')], default='NOT_FLEX_FIELD', max_length=20), - ), - migrations.AddField( - model_name='targetingindividualblockrulefilter', - name='flex_field_classification', - field=models.CharField(choices=[('NOT_FLEX_FIELD', 'Not Flex Field'), ('FLEX_FIELD_NOT_PDU', 'Flex Field Not PDU'), ('FLEX_FIELD_PDU', 'Flex Field PDU')], default='NOT_FLEX_FIELD', max_length=20), - ), - migrations.RunPython(convert_is_flex_field_to_classification), - migrations.RemoveField( - model_name='targetingcriteriarulefilter', - name='is_flex_field', - ), - migrations.RemoveField( - model_name='targetingindividualblockrulefilter', - name='is_flex_field', -======= model_name='targetpopulation', name='program_cycle', field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='target_populations', to='program.programcycle'), ->>>>>>> origin ), ] diff --git a/backend/hct_mis_api/apps/targeting/migrations/0046_migration.py b/backend/hct_mis_api/apps/targeting/migrations/0046_migration.py index cc9146a66c..f87649645d 100644 --- a/backend/hct_mis_api/apps/targeting/migrations/0046_migration.py +++ b/backend/hct_mis_api/apps/targeting/migrations/0046_migration.py @@ -1,8 +1,18 @@ -# Generated by Django 3.2.25 on 2024-08-12 22:36 +# Generated by Django 3.2.25 on 2024-08-05 23:07 from django.db import migrations, models +def convert_is_flex_field_to_classification(apps, schema_editor): + TargetingCriteriaRuleFilter = apps.get_model('targeting', 'TargetingCriteriaRuleFilter') + TargetingCriteriaRuleFilter.objects.filter(is_flex_field=True).update(flex_field_classification='FLEX_FIELD_BASIC') + TargetingCriteriaRuleFilter.objects.filter(is_flex_field=False).update(flex_field_classification='NOT_FLEX_FIELD') + + TargetingIndividualBlockRuleFilter = apps.get_model('targeting', 'TargetingIndividualBlockRuleFilter') + TargetingIndividualBlockRuleFilter.objects.filter(is_flex_field=True).update(flex_field_classification='FLEX_FIELD_BASIC') + TargetingIndividualBlockRuleFilter.objects.filter(is_flex_field=False).update(flex_field_classification='NOT_FLEX_FIELD') + + class Migration(migrations.Migration): dependencies = [ @@ -12,8 +22,22 @@ class Migration(migrations.Migration): operations = [ migrations.AddField( model_name='targetingcriteriarulefilter', - name='round_number', - field=models.PositiveIntegerField(blank=True, null=True), + name='flex_field_classification', + field=models.CharField(choices=[('NOT_FLEX_FIELD', 'Not Flex Field'), ('FLEX_FIELD_BASIC', 'Flex Field Basic'), ('FLEX_FIELD_PDU', 'Flex Field PDU')], default='NOT_FLEX_FIELD', max_length=20), + ), + migrations.AddField( + model_name='targetingindividualblockrulefilter', + name='flex_field_classification', + field=models.CharField(choices=[('NOT_FLEX_FIELD', 'Not Flex Field'), ('FLEX_FIELD_BASIC', 'Flex Field Basic'), ('FLEX_FIELD_PDU', 'Flex Field PDU')], default='NOT_FLEX_FIELD', max_length=20), + ), + migrations.RunPython(convert_is_flex_field_to_classification), + migrations.RemoveField( + model_name='targetingcriteriarulefilter', + name='is_flex_field', + ), + migrations.RemoveField( + model_name='targetingindividualblockrulefilter', + name='is_flex_field', ), migrations.AddField( model_name='targetingindividualblockrulefilter', @@ -23,11 +47,21 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='targetingcriteriarulefilter', name='comparison_method', - field=models.CharField(choices=[('EQUALS', 'Equals'), ('NOT_EQUALS', 'Not Equals'), ('CONTAINS', 'Contains'), ('NOT_CONTAINS', 'Does not contain'), ('RANGE', 'In between <>'), ('NOT_IN_RANGE', 'Not in between <>'), ('GREATER_THAN', 'Greater than'), ('LESS_THAN', 'Less than'), ('IS_NULL', 'Is null')], max_length=20), + field=models.CharField( + choices=[('EQUALS', 'Equals'), ('NOT_EQUALS', 'Not Equals'), ('CONTAINS', 'Contains'), + ('NOT_CONTAINS', 'Does not contain'), ('RANGE', 'In between <>'), + ('NOT_IN_RANGE', 'Not in between <>'), ('GREATER_THAN', 'Greater than'), + ('LESS_THAN', 'Less than'), ('IS_NULL', 'Is null')], max_length=20), ), migrations.AlterField( model_name='targetingindividualblockrulefilter', name='comparison_method', - field=models.CharField(choices=[('EQUALS', 'Equals'), ('NOT_EQUALS', 'Not Equals'), ('CONTAINS', 'Contains'), ('NOT_CONTAINS', 'Does not contain'), ('RANGE', 'In between <>'), ('NOT_IN_RANGE', 'Not in between <>'), ('GREATER_THAN', 'Greater than'), ('LESS_THAN', 'Less than'), ('IS_NULL', 'Is null')], max_length=20), + field=models.CharField( + choices=[('EQUALS', 'Equals'), ('NOT_EQUALS', 'Not Equals'), ('CONTAINS', 'Contains'), + ('NOT_CONTAINS', 'Does not contain'), ('RANGE', 'In between <>'), + ('NOT_IN_RANGE', 'Not in between <>'), ('GREATER_THAN', 'Greater than'), + ('LESS_THAN', 'Less than'), ('IS_NULL', 'Is null')], max_length=20), ), ] + + diff --git a/backend/hct_mis_api/apps/targeting/migrations/0047_migration.py b/backend/hct_mis_api/apps/targeting/migrations/0047_migration.py deleted file mode 100644 index 9d88eaec6a..0000000000 --- a/backend/hct_mis_api/apps/targeting/migrations/0047_migration.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 3.2.25 on 2024-08-13 23:24 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('targeting', '0046_migration'), - ] - - operations = [ - migrations.RemoveField( - model_name='targetingcriteriarulefilter', - name='round_number', - ), - ] diff --git a/backend/hct_mis_api/apps/targeting/migrations/0048_migration.py b/backend/hct_mis_api/apps/targeting/migrations/0048_migration.py deleted file mode 100644 index 840c8e7565..0000000000 --- a/backend/hct_mis_api/apps/targeting/migrations/0048_migration.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.2.25 on 2024-08-23 11:38 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('targeting', '0047_migration'), - ] - - operations = [ - migrations.AlterField( - model_name='targetingcriteriarulefilter', - name='flex_field_classification', - field=models.CharField(choices=[('NOT_FLEX_FIELD', 'Not Flex Field'), ('FLEX_FIELD_BASIC', 'Flex Field Basic'), ('FLEX_FIELD_PDU', 'Flex Field PDU')], default='NOT_FLEX_FIELD', max_length=20), - ), - migrations.AlterField( - model_name='targetingindividualblockrulefilter', - name='flex_field_classification', - field=models.CharField(choices=[('NOT_FLEX_FIELD', 'Not Flex Field'), ('FLEX_FIELD_BASIC', 'Flex Field Basic'), ('FLEX_FIELD_PDU', 'Flex Field PDU')], default='NOT_FLEX_FIELD', max_length=20), - ), - ] diff --git a/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py b/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py index 570b2a5231..8dcc23b9a5 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py @@ -80,7 +80,6 @@ def setUpTestData(cls) -> None: create_household( {"size": 4, "residence_status": "HOST", "program": cls.program}, ) -<<<<<<< HEAD FlexibleAttribute.objects.create( name="flex_field_1", type=FlexibleAttribute.STRING, @@ -96,7 +95,6 @@ def setUpTestData(cls) -> None: label="PDU Field 1", pdu_data=pdu_data, ) -======= cls.variables = { "createTargetPopulationInput": { "name": "Example name 5", @@ -120,7 +118,6 @@ def setUpTestData(cls) -> None: }, } } ->>>>>>> origin @parameterized.expand( [ @@ -204,7 +201,6 @@ def test_targeting_in_draft_program(self) -> None: self.program.status = Program.DRAFT self.program.save() -<<<<<<< HEAD variables = { "createTargetPopulationInput": { "name": "Example name 5", @@ -228,8 +224,6 @@ def test_targeting_in_draft_program(self) -> None: } } -======= ->>>>>>> origin response_error = self.graphql_request( request_string=TestCreateTargetPopulationMutation.MUTATION_QUERY, context={"user": self.user}, @@ -245,7 +239,6 @@ def test_targeting_in_draft_program(self) -> None: def test_targeting_unique_constraints(self) -> None: self.create_user_role_with_permissions(self.user, [Permissions.TARGETING_CREATE], self.program.business_area) -<<<<<<< HEAD variables = { "createTargetPopulationInput": { "name": "Example name 5", @@ -269,8 +262,6 @@ def test_targeting_unique_constraints(self) -> None: } } -======= ->>>>>>> origin self.assertEqual(TargetPopulation.objects.count(), 0) # First, response is ok and tp is created @@ -352,7 +343,6 @@ def test_create_mutation_target_by_id(self) -> None: variables=variables, ) -<<<<<<< HEAD def test_create_mutation_with_flex_field(self) -> None: self.create_user_role_with_permissions(self.user, [Permissions.TARGETING_CREATE], self.program.business_area) @@ -424,7 +414,8 @@ def test_create_mutation_with_pdu_flex_field(self) -> None: request_string=TestCreateTargetPopulationMutation.MUTATION_QUERY, context={"user": self.user}, variables=variables, -======= + ) + def test_create_targeting_if_program_cycle_finished(self) -> None: self.create_user_role_with_permissions(self.user, [Permissions.TARGETING_CREATE], self.program.business_area) self.program_cycle.status = Program.FINISHED @@ -440,5 +431,4 @@ def test_create_targeting_if_program_cycle_finished(self) -> None: self.assertIn( "Not possible to assign Finished Program Cycle to Targeting", response_error["errors"][0]["message"], ->>>>>>> origin ) diff --git a/backend/hct_mis_api/apps/targeting/tests/test_target_query.py b/backend/hct_mis_api/apps/targeting/tests/test_target_query.py index db739442b3..ed272d276b 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_target_query.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_target_query.py @@ -12,12 +12,8 @@ ) from hct_mis_api.apps.core.models import BusinessArea, PeriodicFieldData from hct_mis_api.apps.household.fixtures import create_household -<<<<<<< HEAD from hct_mis_api.apps.periodic_data_update.utils import populate_pdu_with_null_values -from hct_mis_api.apps.program.fixtures import ProgramFactory -======= from hct_mis_api.apps.program.fixtures import ProgramCycleFactory, ProgramFactory ->>>>>>> origin from hct_mis_api.apps.program.models import Program from hct_mis_api.apps.targeting.choices import FlexFieldClassification from hct_mis_api.apps.targeting.models import ( @@ -312,7 +308,6 @@ def test_simple_target_query_next(self, _: Any, permissions: List[Permissions]) }, ) -<<<<<<< HEAD @parameterized.expand( [ ( @@ -336,7 +331,8 @@ def test_simple_target_query_pdu(self, _: Any, permissions: List[Permissions]) - "TargetPopulationNode", ) }, -======= + ) + def test_all_targets_query_filter_by_cycle(self) -> None: self.create_user_role_with_permissions( self.user, [Permissions.TARGETING_VIEW_LIST], self.business_area, self.program @@ -345,5 +341,4 @@ def test_all_targets_query_filter_by_cycle(self) -> None: request_string=TestTargetPopulationQuery.ALL_TARGET_POPULATION_QUERY, context={"user": self.user, "headers": {"Program": self.id_to_base64(self.program.id, "ProgramNode")}}, variables={"programCycle": self.id_to_base64(self.cycle_2.id, "ProgramCycleNode")}, ->>>>>>> origin ) diff --git a/backend/selenium_tests/page_object/targeting/targeting_create.py b/backend/selenium_tests/page_object/targeting/targeting_create.py index 976c95b223..d870218b44 100644 --- a/backend/selenium_tests/page_object/targeting/targeting_create.py +++ b/backend/selenium_tests/page_object/targeting/targeting_create.py @@ -52,7 +52,6 @@ class TargetingCreate(BaseComponents): autocompleteTargetCriteriaValues = 'div[data-cy="autocomplete-target-criteria-values"]' selectMany = 'div[data-cy="select-many"]' buttonEdit = 'button[data-cy="button-edit"]' -<<<<<<< HEAD datePickerFilter = 'div[data-cy="date-picker-filter"]' boolField = 'div[data-cy="bool-field"]' textField = 'div[data-cy="string-textfield"]' @@ -82,10 +81,8 @@ class TargetingCreate(BaseComponents): 'div[data-cy="select-individualsFiltersBlocks[{}].individualBlockFilters[{}].value"]' ) totalNumberOfHouseholdsCount = 'div[data-cy="total-number-of-households-count"]' -======= selectProgramCycleAutocomplete = 'div[data-cy="filters-program-cycle-autocomplete"]' programmeCycleInput = 'div[data-cy="Programme Cycle-input"]' ->>>>>>> origin # Texts textTargetingCriteria = "Targeting Criteria" @@ -229,7 +226,6 @@ def getSelectMany(self) -> WebElement: def getButtonEdit(self) -> WebElement: return self.wait_for(self.buttonEdit) -<<<<<<< HEAD def getTextField(self) -> WebElement: return self.wait_for(self.textField) @@ -316,10 +312,9 @@ def getSelectIndividualsFiltersBlocksValue( def getTotalNumberOfHouseholdsCount(self) -> WebElement: return self.wait_for(self.totalNumberOfHouseholdsCount) -======= + def getFiltersProgramCycleAutocomplete(self) -> WebElement: return self.wait_for(self.selectProgramCycleAutocomplete) def getProgrammeCycleInput(self) -> WebElement: return self.wait_for(self.programmeCycleInput) ->>>>>>> origin From ef2921046655b3422463d2a0e6db3c1d48a78402 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Fri, 23 Aug 2024 16:14:22 +0200 Subject: [PATCH 097/118] more conflicts --- backend/hct_mis_api/apps/program/schema.py | 1 + .../test_create_target_population_mutation.py | 46 ------------------- 2 files changed, 1 insertion(+), 46 deletions(-) diff --git a/backend/hct_mis_api/apps/program/schema.py b/backend/hct_mis_api/apps/program/schema.py index 6a0d98ff46..f9af2c10a3 100644 --- a/backend/hct_mis_api/apps/program/schema.py +++ b/backend/hct_mis_api/apps/program/schema.py @@ -113,6 +113,7 @@ class ProgramNode(BaseNodePermissionMixin, AdminUrlNodeMixin, DjangoObjectType): data_collecting_type = graphene.Field(DataCollectingTypeNode, source="data_collecting_type") partners = graphene.List(PartnerNode) is_social_worker_program = graphene.Boolean() + pdu_fields = graphene.List(PeriodicFieldNode) target_populations_count = graphene.Int() cycles = DjangoFilterConnectionField(ProgramCycleNode, filterset_class=ProgramCycleFilter) diff --git a/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py b/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py index 8dcc23b9a5..9df125bfc3 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py @@ -201,29 +201,6 @@ def test_targeting_in_draft_program(self) -> None: self.program.status = Program.DRAFT self.program.save() - variables = { - "createTargetPopulationInput": { - "name": "Example name 5", - "businessAreaSlug": "afghanistan", - "programId": self.id_to_base64(self.program.id, "ProgramNode"), - "excludedIds": "", - "targetingCriteria": { - "rules": [ - { - "filters": [ - { - "comparisonMethod": "EQUALS", - "fieldName": "size", - "arguments": [3], - "flexFieldClassification": "NOT_FLEX_FIELD", - } - ] - } - ] - }, - } - } - response_error = self.graphql_request( request_string=TestCreateTargetPopulationMutation.MUTATION_QUERY, context={"user": self.user}, @@ -239,29 +216,6 @@ def test_targeting_in_draft_program(self) -> None: def test_targeting_unique_constraints(self) -> None: self.create_user_role_with_permissions(self.user, [Permissions.TARGETING_CREATE], self.program.business_area) - variables = { - "createTargetPopulationInput": { - "name": "Example name 5", - "businessAreaSlug": "afghanistan", - "programId": self.id_to_base64(self.program.id, "ProgramNode"), - "excludedIds": "", - "targetingCriteria": { - "rules": [ - { - "filters": [ - { - "comparisonMethod": "EQUALS", - "fieldName": "size", - "arguments": [3], - "flexFieldClassification": "NOT_FLEX_FIELD", - } - ] - } - ] - }, - } - } - self.assertEqual(TargetPopulation.objects.count(), 0) # First, response is ok and tp is created From e103373b73475ca1ff374668cffe9ef37d427207 Mon Sep 17 00:00:00 2001 From: Maciej Szewczyk Date: Fri, 23 Aug 2024 16:20:02 +0200 Subject: [PATCH 098/118] generate schema --- frontend/data/schema.graphql | 5504 ++++++++++++++++++++++++ frontend/src/__generated__/graphql.tsx | 48 +- 2 files changed, 5514 insertions(+), 38 deletions(-) diff --git a/frontend/data/schema.graphql b/frontend/data/schema.graphql index e69de29bb2..224047abe4 100644 --- a/frontend/data/schema.graphql +++ b/frontend/data/schema.graphql @@ -0,0 +1,5504 @@ +schema { + query: Query + mutation: Mutations +} + +input AccountabilityCommunicationMessageAgeInput { + min: Int + max: Int +} + +input AccountabilityFullListArguments { + excludedAdminAreas: [String] +} + +input AccountabilityRandomSamplingArguments { + excludedAdminAreas: [String] + confidenceInterval: Float! + marginOfError: Float! + age: AccountabilityCommunicationMessageAgeInput + sex: String +} + +input AccountabilitySampleSizeInput { + targetPopulation: ID + program: ID + samplingType: String! + fullListArguments: AccountabilityFullListArguments + randomSamplingArguments: AccountabilityRandomSamplingArguments +} + +type AccountabilitySampleSizeNode { + numberOfRecipients: Int + sampleSize: Int +} + +enum Action { + LOCK + LOCK_FSP + UNLOCK + UNLOCK_FSP + SEND_FOR_APPROVAL + APPROVE + AUTHORIZE + REVIEW + REJECT + FINISH + SEND_TO_PAYMENT_GATEWAY +} + +input ActionPaymentPlanInput { + paymentPlanId: ID! + action: Action! + comment: String +} + +type ActionPaymentPlanMutation { + paymentPlan: PaymentPlanNode +} + +type ActivatePaymentVerificationPlan { + validationErrors: Arg + paymentPlan: GenericPaymentPlanNode +} + +input AddIndividualDataObjectType { + fullName: String! + givenName: String + middleName: String + familyName: String + sex: String! + birthDate: Date! + estimatedBirthDate: Boolean! + maritalStatus: String + phoneNo: String + phoneNoAlternative: String + email: String + relationship: String! + disability: String + workStatus: String + enrolledInNutritionProgramme: Boolean + administrationOfRutf: Boolean + pregnant: Boolean + observedDisability: [String] + seeingDisability: String + hearingDisability: String + physicalDisability: String + memoryDisability: String + selfcareDisability: String + commsDisability: String + whoAnswersPhone: String + whoAnswersAltPhone: String + role: String! + documents: [IndividualDocumentObjectType] + identities: [IndividualIdentityObjectType] + paymentChannels: [BankTransferObjectType] + businessArea: String + preferredLanguage: String + flexFields: Arg + paymentDeliveryPhoneNo: String + blockchainName: String + walletAddress: String + walletName: String +} + +input AddIndividualIssueTypeExtras { + household: ID! + individualData: AddIndividualDataObjectType! +} + +type AgeFilterObject { + min: Int + max: Int +} + +input AgeInput { + min: Int + max: Int +} + +type ApprovalNode { + createdAt: DateTime! + comment: String + createdBy: UserNode + info: String +} + +type ApprovalProcessNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + sentForApprovalBy: UserNode + sentForApprovalDate: DateTime + sentForAuthorizationBy: UserNode + sentForAuthorizationDate: DateTime + sentForFinanceReleaseBy: UserNode + sentForFinanceReleaseDate: DateTime + paymentPlan: PaymentPlanNode! + approvalNumberRequired: Int! + authorizationNumberRequired: Int! + financeReleaseNumberRequired: Int! + rejectedOn: String + actions: FilteredActionsListNode +} + +type ApprovalProcessNodeConnection { + pageInfo: PageInfo! + edges: [ApprovalProcessNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type ApprovalProcessNodeEdge { + node: ApprovalProcessNode + cursor: String! +} + +type AreaNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + originalId: UUID + name: String! + parent: AreaNode + pCode: String + areaType: AreaTypeNode! + validFrom: DateTime + validUntil: DateTime + extras: JSONString! + lft: Int! + rght: Int! + treeId: Int! + level: Int! + areaSet(offset: Int, before: String, after: String, first: Int, last: Int, name: String): AreaNodeConnection! + householdSet(offset: Int, before: String, after: String, first: Int, last: Int): HouseholdNodeConnection! + grievanceticketSet(offset: Int, before: String, after: String, first: Int, last: Int): GrievanceTicketNodeConnection! + programs(offset: Int, before: String, after: String, first: Int, last: Int, name: String): ProgramNodeConnection! + reports(offset: Int, before: String, after: String, first: Int, last: Int): ReportNodeConnection! + feedbackSet(offset: Int, before: String, after: String, first: Int, last: Int): FeedbackNodeConnection! +} + +type AreaNodeConnection { + pageInfo: PageInfo! + edges: [AreaNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type AreaNodeEdge { + node: AreaNode + cursor: String! +} + +type AreaTreeNode { + id: ID + name: String + pCode: String + areas: [AreaTreeNode] + level: Int +} + +type AreaTypeNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + originalId: UUID + name: String! + areaLevel: Int! + parent: AreaTypeNode + validFrom: DateTime + validUntil: DateTime + extras: JSONString! + lft: Int! + rght: Int! + treeId: Int! + level: Int! + areatypeSet(offset: Int, before: String, after: String, first: Int, last: Int): AreaTypeNodeConnection! + areaSet(offset: Int, before: String, after: String, first: Int, last: Int, name: String): AreaNodeConnection! +} + +type AreaTypeNodeConnection { + pageInfo: PageInfo! + edges: [AreaTypeNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type AreaTypeNodeEdge { + node: AreaTypeNode + cursor: String! +} + +scalar Arg + +input AssignFspToDeliveryMechanismInput { + paymentPlanId: ID! + mappings: [FSPToDeliveryMechanismMappingInput]! +} + +type AssignFspToDeliveryMechanismMutation { + paymentPlan: PaymentPlanNode +} + +input AvailableFspsForDeliveryMechanismsInput { + paymentPlanId: ID! +} + +type BankAccountInfoNode implements Node { + id: ID! + rdiMergeStatus: BankAccountInfoRdiMergeStatus! + isOriginal: Boolean! + createdAt: DateTime! + updatedAt: DateTime! + isRemoved: Boolean! + removedDate: DateTime + lastSyncAt: DateTime + individual: IndividualNode! + bankName: String! + bankAccountNumber: String! + bankBranchName: String! + accountHolderName: String! + isMigrationHandled: Boolean! + copiedFrom: BankAccountInfoNode + copiedTo(offset: Int, before: String, after: String, first: Int, last: Int): BankAccountInfoNodeConnection! + type: String +} + +type BankAccountInfoNodeConnection { + pageInfo: PageInfo! + edges: [BankAccountInfoNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type BankAccountInfoNodeEdge { + node: BankAccountInfoNode + cursor: String! +} + +enum BankAccountInfoRdiMergeStatus { + PENDING + MERGED +} + +input BankTransferObjectType { + type: String! + bankName: String! + bankAccountNumber: String! + bankBranchName: String + accountHolderName: String! +} + +scalar BigInt + +type BulkGrievanceAddNoteMutation { + grievanceTickets: [GrievanceTicketNode] +} + +type BulkUpdateGrievanceTicketsAssigneesMutation { + grievanceTickets: [GrievanceTicketNode] +} + +type BulkUpdateGrievanceTicketsPriorityMutation { + grievanceTickets: [GrievanceTicketNode] +} + +type BulkUpdateGrievanceTicketsUrgencyMutation { + grievanceTickets: [GrievanceTicketNode] +} + +type BusinessAreaNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + code: String! + name: String! + longName: String! + regionCode: String! + regionName: String! + koboUsername: String + koboToken: String + koboUrl: String + rapidProHost: String + rapidProPaymentVerificationToken: String + rapidProMessagesToken: String + rapidProSurveyToken: String + slug: String! + customFields: JSONString! + hasDataSharingAgreement: Boolean! + parent: UserBusinessAreaNode + isSplit: Boolean! + postponeDeduplication: Boolean! + deduplicationDuplicateScore: Float! + deduplicationPossibleDuplicateScore: Float! + deduplicationBatchDuplicatesPercentage: Int! + deduplicationBatchDuplicatesAllowed: Int! + deduplicationGoldenRecordDuplicatesPercentage: Int! + deduplicationGoldenRecordDuplicatesAllowed: Int! + screenBeneficiary: Boolean! + deduplicationIgnoreWithdraw: Boolean! + isPaymentPlanApplicable: Boolean! + isAccountabilityApplicable: Boolean + active: Boolean! + enableEmailNotification: Boolean! + partners: [PartnerNode!]! + businessAreaPartnerThrough: [PartnerRoleNode!]! + children(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID): UserBusinessAreaNodeConnection! + dataCollectingTypes(offset: Int, before: String, after: String, first: Int, last: Int): DataCollectingTypeNodeConnection! + partnerSet: [PartnerNode!]! + userRoles: [UserRoleNode!]! + householdSet(offset: Int, before: String, after: String, first: Int, last: Int): HouseholdNodeConnection! + individualSet(offset: Int, before: String, after: String, first: Int, last: Int): IndividualNodeConnection! + registrationdataimportSet(offset: Int, before: String, after: String, first: Int, last: Int): RegistrationDataImportNodeConnection! + ruleSet(offset: Int, before: String, after: String, first: Int, last: Int): SteficonRuleNodeConnection! + paymentplanSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentPlanNodeConnection! + financialserviceproviderSet(offset: Int, before: String, after: String, first: Int, last: Int): FinancialServiceProviderNodeConnection! + cashplanSet(offset: Int, before: String, after: String, first: Int, last: Int): CashPlanNodeConnection! + paymentrecordSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentRecordNodeConnection! + paymentSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentNodeConnection! + serviceproviderSet(offset: Int, before: String, after: String, first: Int, last: Int): ServiceProviderNodeConnection! + tickets(offset: Int, before: String, after: String, first: Int, last: Int): GrievanceTicketNodeConnection! + targetpopulationSet(offset: Int, before: String, after: String, first: Int, last: Int, program: [ID], createdAt: DateTime, createdAt_Lte: DateTime, createdAt_Gte: DateTime, updatedAt: DateTime, updatedAt_Lte: DateTime, updatedAt_Gte: DateTime, status: String, households: [ID], name: String, createdByName: String, totalHouseholdsCountMin: Int, totalHouseholdsCountMax: Int, totalIndividualsCountMin: Int, totalIndividualsCountMax: Int, businessArea: String, createdAtRange: String, paymentPlanApplicable: Boolean, statusNot: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, programCycle: String, orderBy: String): TargetPopulationNodeConnection! + programSet(offset: Int, before: String, after: String, first: Int, last: Int, name: String): ProgramNodeConnection! + reports(offset: Int, before: String, after: String, first: Int, last: Int): ReportNodeConnection! + logentrySet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationLogEntryNodeConnection! + messageSet(offset: Int, before: String, after: String, first: Int, last: Int): CommunicationMessageNodeConnection! + feedbackSet(offset: Int, before: String, after: String, first: Int, last: Int): FeedbackNodeConnection! + surveySet(offset: Int, before: String, after: String, first: Int, last: Int): SurveyNodeConnection! +} + +type BusinessAreaNodeConnection { + pageInfo: PageInfo! + edges: [BusinessAreaNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type BusinessAreaNodeEdge { + node: BusinessAreaNode + cursor: String! +} + +type CashPlanAndPaymentPlanEdges { + cursor: String + node: CashPlanAndPaymentPlanNode +} + +type CashPlanAndPaymentPlanNode { + adminUrl: String + objType: String + id: String + unicefId: String + verificationStatus: String + status: String + currency: String + totalDeliveredQuantity: Float + startDate: String + endDate: String + programName: String + updatedAt: String + verificationPlans: [PaymentVerificationPlanNode] + totalNumberOfHouseholds: Int + totalEntitledQuantity: Float + totalUndeliveredQuantity: Float + assistanceMeasurement: String + dispersionDate: String + serviceProviderFullName: String +} + +type CashPlanNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + version: BigInt! + businessArea: UserBusinessAreaNode! + statusDate: DateTime! + startDate: DateTime + endDate: DateTime + program: ProgramNode! + exchangeRate: Float + totalEntitledQuantity: Float + totalEntitledQuantityUsd: Float + totalEntitledQuantityRevised: Float + totalEntitledQuantityRevisedUsd: Float + totalDeliveredQuantity: Float + totalDeliveredQuantityUsd: Float + totalUndeliveredQuantity: Float + totalUndeliveredQuantityUsd: Float + name: String! + caId: String + caHashId: UUID + status: CashPlanStatus! + distributionLevel: String! + dispersionDate: DateTime! + coverageDuration: Int! + coverageUnit: String! + comments: String + deliveryType: String + assistanceMeasurement: String! + assistanceThrough: String! + serviceProvider: ServiceProviderNode + visionId: String + fundsCommitment: String + downPayment: String + validationAlertsCount: Int! + totalPersonsCovered: Int! + totalPersonsCoveredRevised: Int! + paymentItems(offset: Int, before: String, after: String, first: Int, last: Int): PaymentRecordNodeConnection! + bankReconciliationSuccess: Int + bankReconciliationError: Int + totalNumberOfHouseholds: Int + currency: String + canCreatePaymentVerificationPlan: Boolean + availablePaymentRecordsCount: Int + verificationPlans(offset: Int, before: String, after: String, first: Int, last: Int, programId: String): PaymentVerificationPlanNodeConnection + paymentVerificationSummary: PaymentVerificationSummaryNode + unicefId: String +} + +type CashPlanNodeConnection { + pageInfo: PageInfo! + edges: [CashPlanNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type CashPlanNodeEdge { + node: CashPlanNode + cursor: String! +} + +enum CashPlanStatus { + DISTRIBUTION_COMPLETED + DISTRIBUTION_COMPLETED_WITH_ERRORS + TRANSACTION_COMPLETED + TRANSACTION_COMPLETED_WITH_ERRORS +} + +input CategoryExtrasInput { + sensitiveGrievanceTicketExtras: SensitiveGrievanceTicketExtras + grievanceComplaintTicketExtras: GrievanceComplaintTicketExtras + positiveFeedbackTicketExtras: PositiveFeedbackTicketExtras + negativeFeedbackTicketExtras: NegativeFeedbackTicketExtras + referralTicketExtras: ReferralTicketExtras +} + +type ChartDatasetNode { + labels: [String] + datasets: [_DatasetsNode] +} + +type ChartDetailedDatasetsNode { + labels: [String] + datasets: [_DetailedDatasetsNode] +} + +type ChartGrievanceTicketsNode { + labels: [String] + datasets: [_DatasetsNode] + totalNumberOfGrievances: Int + totalNumberOfFeedback: Int + totalNumberOfOpenSensitive: Int +} + +type ChartPaymentVerification { + labels: [String] + datasets: [_DetailedDatasetsNode] + households: Int + averageSampleSize: Float +} + +type CheckAgainstSanctionListMutation { + ok: Boolean + errors: [XlsxRowErrorNode] +} + +type ChoiceObject { + name: String + value: String +} + +type ChoiceObjectInt { + name: String + value: Int +} + +input ChooseDeliveryMechanismsForPaymentPlanInput { + paymentPlanId: ID! + deliveryMechanisms: [String]! +} + +type ChooseDeliveryMechanismsForPaymentPlanMutation { + paymentPlan: PaymentPlanNode +} + +type CommunicationMessageNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + unicefId: String + title: String! + body: String! + createdBy: UserNode + numberOfRecipients: Int! + businessArea: UserBusinessAreaNode! + households(offset: Int, before: String, after: String, first: Int, last: Int): HouseholdNodeConnection! + targetPopulation: TargetPopulationNode + registrationDataImport: RegistrationDataImportNode + samplingType: MessageSamplingType! + fullListArguments: JSONString + randomSamplingArguments: JSONString + sampleSize: Int! + program: ProgramNode + isOriginal: Boolean! + isMigrationHandled: Boolean! + migratedAt: DateTime + copiedFrom: CommunicationMessageNode + copiedTo(offset: Int, before: String, after: String, first: Int, last: Int): CommunicationMessageNodeConnection! + adminUrl: String +} + +type CommunicationMessageNodeConnection { + pageInfo: PageInfo! + edges: [CommunicationMessageNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type CommunicationMessageNodeEdge { + node: CommunicationMessageNode + cursor: String! +} + +type CommunicationMessageRecipientMapNode implements Node { + id: ID! + size: Int + headOfHousehold: IndividualNode +} + +type CommunicationMessageRecipientMapNodeConnection { + pageInfo: PageInfo! + edges: [CommunicationMessageRecipientMapNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type CommunicationMessageRecipientMapNodeEdge { + node: CommunicationMessageRecipientMapNode + cursor: String! +} + +type ContentTypeObjectType { + id: ID! + appLabel: String! + model: String! + paymentverificationplanSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationPlanNodeConnection! + paymentverificationSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationNodeConnection! + paymentverificationsummarySet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationSummaryNodeConnection! + ticketcomplaintdetailsSet(offset: Int, before: String, after: String, first: Int, last: Int): TicketComplaintDetailsNodeConnection! + ticketsensitivedetailsSet(offset: Int, before: String, after: String, first: Int, last: Int): TicketSensitiveDetailsNodeConnection! + logEntries(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationLogEntryNodeConnection! + name: String +} + +type CopyProgram { + validationErrors: Arg + program: ProgramNode +} + +input CopyProgramInput { + id: String! + name: String + startDate: Date + endDate: Date + description: String + budget: Decimal + frequencyOfPayments: String + sector: String + cashPlus: Boolean + populationGoal: Int + administrativeAreasOfImplementation: String + businessAreaSlug: String + dataCollectingTypeCode: String + partners: [ProgramPartnerThroughInput] + partnerAccess: String + programmeCode: String + pduFields: [PDUFieldInput] +} + +input CopyTargetPopulationInput { + id: ID + name: String + programCycleId: ID! +} + +input CopyTargetPopulationMutationInput { + targetPopulationData: CopyTargetPopulationInput + clientMutationId: String +} + +type CopyTargetPopulationMutationPayload { + targetPopulation: TargetPopulationNode + validationErrors: Arg + clientMutationId: String +} + +type CoreFieldChoiceObject { + labels: [LabelNode] + labelEn: String + value: String + admin: String + listName: String +} + +type CountAndPercentageNode { + count: Int + percentage: Float +} + +input CreateAccountabilityCommunicationMessageInput { + households: [ID] + targetPopulation: ID + registrationDataImport: ID + samplingType: SamplingChoices! + fullListArguments: AccountabilityFullListArguments + randomSamplingArguments: AccountabilityRandomSamplingArguments + title: String! + body: String! +} + +type CreateCommunicationMessageMutation { + message: CommunicationMessageNode +} + +type CreateDashboardReport { + success: Boolean +} + +input CreateDashboardReportInput { + reportTypes: [String]! + businessAreaSlug: String! + year: Int! + adminArea: ID + program: ID +} + +input CreateFeedbackInput { + issueType: String! + householdLookup: ID + individualLookup: ID + description: String! + comments: String + admin2: ID + area: String + language: String + consent: Boolean + program: ID +} + +input CreateFeedbackMessageInput { + description: String! + feedback: ID! +} + +type CreateFeedbackMessageMutation { + feedbackMessage: FeedbackMessageNode +} + +type CreateFeedbackMutation { + feedback: FeedbackNode +} + +type CreateFollowUpPaymentPlanMutation { + paymentPlan: PaymentPlanNode +} + +input CreateGrievanceTicketExtrasInput { + category: CategoryExtrasInput + issueType: IssueTypeExtrasInput +} + +input CreateGrievanceTicketInput { + description: String! + assignedTo: ID + category: Int! + issueType: Int + admin: ID + area: String + language: String! + consent: Boolean! + businessArea: ID! + linkedTickets: [ID] + extras: CreateGrievanceTicketExtrasInput + priority: Int + urgency: Int + partner: Int + program: ID + comments: String + linkedFeedbackId: ID + documentation: [GrievanceDocumentInput] +} + +type CreateGrievanceTicketMutation { + grievanceTickets: [GrievanceTicketNode] +} + +input CreatePaymentPlanInput { + businessAreaSlug: String! + targetingId: ID! + dispersionStartDate: Date! + dispersionEndDate: Date! + currency: String! +} + +type CreatePaymentPlanMutation { + paymentPlan: PaymentPlanNode +} + +input CreatePaymentVerificationInput { + sampling: String! + verificationChannel: String! + businessAreaSlug: String! + fullListArguments: FullListArguments + randomSamplingArguments: RandomSamplingArguments + rapidProArguments: RapidProArguments + cashOrPaymentPlanId: ID! +} + +type CreateProgram { + validationErrors: Arg + program: ProgramNode +} + +input CreateProgramInput { + name: String + startDate: Date + endDate: Date + description: String + budget: Decimal + frequencyOfPayments: String + sector: String + cashPlus: Boolean + populationGoal: Int + administrativeAreasOfImplementation: String + businessAreaSlug: String + dataCollectingTypeCode: String + partners: [ProgramPartnerThroughInput] + partnerAccess: String + programmeCode: String + pduFields: [PDUFieldInput] +} + +type CreateReport { + report: ReportNode +} + +input CreateReportInput { + reportType: Int! + businessAreaSlug: String! + dateFrom: Date! + dateTo: Date! + program: ID + adminArea1: ID + adminArea2: [ID] +} + +input CreateSurveyInput { + title: String! + body: String + category: String! + targetPopulation: ID + program: ID + samplingType: String! + fullListArguments: AccountabilityFullListArguments + randomSamplingArguments: AccountabilityRandomSamplingArguments + flow: String! +} + +type CreateSurveyMutation { + survey: SurveyNode +} + +input CreateTargetPopulationInput { + name: String! + targetingCriteria: TargetingCriteriaObjectType! + businessAreaSlug: String! + programId: ID! + programCycleId: ID! + excludedIds: String! + exclusionReason: String +} + +type CreateTargetPopulationMutation { + validationErrors: Arg + targetPopulation: TargetPopulationNode +} + +input CreateTicketNoteInput { + description: String! + ticket: ID! +} + +type CreateTicketNoteMutation { + grievanceTicketNote: TicketNoteNode +} + +type CreateVerificationPlanMutation { + paymentPlan: GenericPaymentPlanNode +} + +type DataCollectingTypeChoiceObject { + name: String + value: String + description: String +} + +type DataCollectingTypeNode implements Node { + id: ID! + created: DateTime! + modified: DateTime! + label: String! + code: String! + type: DataCollectingTypeType + description: String! + active: Boolean! + deprecated: Boolean! + individualFiltersAvailable: Boolean! + householdFiltersAvailable: Boolean! + recalculateComposition: Boolean! + weight: Int! + datacollectingtypeSet(offset: Int, before: String, after: String, first: Int, last: Int): DataCollectingTypeNodeConnection! + programs(offset: Int, before: String, after: String, first: Int, last: Int, name: String): ProgramNodeConnection! +} + +type DataCollectingTypeNodeConnection { + pageInfo: PageInfo! + edges: [DataCollectingTypeNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type DataCollectingTypeNodeEdge { + node: DataCollectingTypeNode + cursor: String! +} + +enum DataCollectingTypeType { + STANDARD + SOCIAL +} + +scalar Date + +scalar DateTime + +scalar Decimal + +type DeduplicationResultNode { + hitId: ID + fullName: String + score: Float + proximityToScore: Float + location: String + age: Int + duplicate: Boolean + distinct: Boolean +} + +type DeleteHouseholdApproveMutation { + grievanceTicket: GrievanceTicketNode +} + +type DeletePaymentPlanMutation { + paymentPlan: PaymentPlanNode +} + +type DeletePaymentVerificationPlan { + paymentPlan: GenericPaymentPlanNode +} + +type DeleteProgram { + ok: Boolean +} + +type DeleteRegistrationDataImport { + ok: Boolean +} + +input DeleteTargetPopulationMutationInput { + targetId: ID! + clientMutationId: String +} + +type DeleteTargetPopulationMutationPayload { + ok: Boolean + clientMutationId: String +} + +type DeliveredQuantityNode { + totalDeliveredQuantity: Decimal + currency: String +} + +input DeliveryMechanismDataObjectType { + label: String! + approveStatus: Boolean! + dataFields: [DeliveryMechanismDataPayloadFieldObjectType]! +} + +input DeliveryMechanismDataPayloadFieldObjectType { + name: String! + value: String! + previousValue: String +} + +type DeliveryMechanismNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + paymentGatewayId: String + code: String + name: String + optionalFields: [String!]! + requiredFields: [String!]! + uniqueFields: [String!]! + isActive: Boolean! + transferType: DeliveryMechanismTransferType! + financialserviceproviderSet(offset: Int, before: String, after: String, first: Int, last: Int): FinancialServiceProviderNodeConnection! + deliverymechanismperpaymentplanSet(offset: Int, before: String, after: String, first: Int, last: Int): DeliveryMechanismPerPaymentPlanNodeConnection! + paymentrecordSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentRecordNodeConnection! + paymentSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentNodeConnection! +} + +type DeliveryMechanismNodeConnection { + pageInfo: PageInfo! + edges: [DeliveryMechanismNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type DeliveryMechanismNodeEdge { + node: DeliveryMechanismNode + cursor: String! +} + +enum DeliveryMechanismPerPaymentPlanDeliveryMechanismChoice { + CARDLESS_CASH_WITHDRAWAL + CASH + CASH_BY_FSP + CHEQUE + DEPOSIT_TO_CARD + MOBILE_MONEY + PRE_PAID_CARD + REFERRAL + TRANSFER + TRANSFER_TO_ACCOUNT + VOUCHER + ATM_CARD + CASH_OVER_THE_COUNTER + TRANSFER_TO_DIGITAL_WALLET +} + +type DeliveryMechanismPerPaymentPlanNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + paymentPlan: PaymentPlanNode! + financialServiceProvider: FinancialServiceProviderNode + createdBy: UserNode! + sentDate: DateTime! + sentBy: UserNode + status: String! + deliveryMechanismChoice: DeliveryMechanismPerPaymentPlanDeliveryMechanismChoice + deliveryMechanism: DeliveryMechanismNode + deliveryMechanismOrder: Int! + sentToPaymentGateway: Boolean! + chosenConfiguration: String + name: String + code: String + order: Int + fsp: FinancialServiceProviderNode +} + +type DeliveryMechanismPerPaymentPlanNodeConnection { + pageInfo: PageInfo! + edges: [DeliveryMechanismPerPaymentPlanNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type DeliveryMechanismPerPaymentPlanNodeEdge { + node: DeliveryMechanismPerPaymentPlanNode + cursor: String! +} + +enum DeliveryMechanismTransferType { + CASH + VOUCHER + DIGITAL +} + +type DiscardPaymentVerificationPlan { + paymentPlan: GenericPaymentPlanNode +} + +type DjangoDebug { + sql: [DjangoDebugSQL] +} + +type DjangoDebugSQL { + vendor: String! + alias: String! + sql: String + duration: Float! + rawSql: String! + params: String! + startTime: Float! + stopTime: Float! + isSlow: Boolean! + isSelect: Boolean! + transId: String + transStatus: String + isoLevel: String + encoding: String +} + +type DocumentNode implements Node { + id: ID! + rdiMergeStatus: DocumentRdiMergeStatus! + isRemoved: Boolean! + isOriginal: Boolean! + createdAt: DateTime! + updatedAt: DateTime! + lastSyncAt: DateTime + documentNumber: String! + photo: String + individual: IndividualNode! + type: DocumentTypeNode! + country: String + status: DocumentStatus! + cleared: Boolean! + clearedDate: DateTime! + clearedBy: UserNode + issuanceDate: DateTime + expiryDate: DateTime + program: ProgramNode + isMigrationHandled: Boolean! + copiedFrom: DocumentNode + copiedTo(offset: Int, before: String, after: String, first: Int, last: Int): DocumentNodeConnection! + countryIso3: String +} + +type DocumentNodeConnection { + pageInfo: PageInfo! + edges: [DocumentNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type DocumentNodeEdge { + node: DocumentNode + cursor: String! +} + +enum DocumentRdiMergeStatus { + PENDING + MERGED +} + +enum DocumentStatus { + PENDING + VALID + NEED_INVESTIGATION + INVALID +} + +type DocumentTypeNode { + id: UUID! + createdAt: DateTime! + updatedAt: DateTime! + label: String! + key: String! + isIdentityDocument: Boolean! + uniqueForIndividual: Boolean! + validForDeduplication: Boolean! + documents(offset: Int, before: String, after: String, first: Int, last: Int): DocumentNodeConnection! +} + +input EditBankTransferObjectType { + id: ID! + type: String! + bankName: String! + bankAccountNumber: String! + bankBranchName: String + accountHolderName: String! +} + +input EditDeliveryMechanismDataObjectType { + id: ID! + label: String! + approveStatus: Boolean! + dataFields: [DeliveryMechanismDataPayloadFieldObjectType]! +} + +input EditIndividualDocumentObjectType { + id: ID! + country: String! + key: String! + number: String! + photo: Arg + photoraw: Arg +} + +input EditIndividualIdentityObjectType { + id: ID! + country: String! + partner: String! + number: String! +} + +input EditPaymentVerificationInput { + sampling: String! + verificationChannel: String! + businessAreaSlug: String! + fullListArguments: FullListArguments + randomSamplingArguments: RandomSamplingArguments + rapidProArguments: RapidProArguments + paymentVerificationPlanId: ID! +} + +type EditPaymentVerificationMutation { + paymentPlan: GenericPaymentPlanNode +} + +type EraseRegistrationDataImportMutation { + registrationDataImport: RegistrationDataImportNode +} + +type ExcludeHouseholdsMutation { + paymentPlan: PaymentPlanNode +} + +type ExportPDFPaymentPlanSummaryMutation { + paymentPlan: PaymentPlanNode +} + +type ExportSurveySampleMutationMutation { + survey: SurveyNode +} + +type ExportXLSXPaymentPlanPaymentListMutation { + paymentPlan: PaymentPlanNode +} + +type ExportXLSXPaymentPlanPaymentListPerFSPMutation { + paymentPlan: PaymentPlanNode +} + +type ExportXlsxPaymentVerificationPlanFile { + paymentPlan: GenericPaymentPlanNode +} + +input FSPToDeliveryMechanismMappingInput { + fspId: ID! + deliveryMechanism: String! + chosenConfiguration: String + order: Int! +} + +enum FeedbackIssueType { + POSITIVE_FEEDBACK + NEGATIVE_FEEDBACK +} + +type FeedbackMessageNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + description: String! + createdBy: UserNode +} + +type FeedbackMessageNodeConnection { + pageInfo: PageInfo! + edges: [FeedbackMessageNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type FeedbackMessageNodeEdge { + node: FeedbackMessageNode + cursor: String! +} + +type FeedbackNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + unicefId: String + businessArea: UserBusinessAreaNode! + issueType: FeedbackIssueType! + householdLookup: HouseholdNode + individualLookup: IndividualNode + description: String! + comments: String + admin2: AreaNode + area: String! + language: String! + consent: Boolean! + program: ProgramNode + createdBy: UserNode + linkedGrievance: GrievanceTicketNode + isOriginal: Boolean! + isMigrationHandled: Boolean! + migratedAt: DateTime + copiedFrom: FeedbackNode + copiedTo(offset: Int, before: String, after: String, first: Int, last: Int): FeedbackNodeConnection! + feedbackMessages(offset: Int, before: String, after: String, first: Int, last: Int): FeedbackMessageNodeConnection! + adminUrl: String +} + +type FeedbackNodeConnection { + pageInfo: PageInfo! + edges: [FeedbackNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type FeedbackNodeEdge { + node: FeedbackNode + cursor: String! +} + +type FieldAttributeNode { + id: String + type: String + name: String + labels: [LabelNode] + labelEn: String + hint: String + required: Boolean + choices: [CoreFieldChoiceObject] + associatedWith: String + isFlexField: Boolean + pduData: PeriodicFieldDataNode +} + +type FilteredActionsListNode { + approval: [ApprovalNode] + authorization: [ApprovalNode] + financeRelease: [ApprovalNode] + reject: [ApprovalNode] +} + +type FinalizeTargetPopulationMutation { + targetPopulation: TargetPopulationNode +} + +enum FinancialServiceProviderCommunicationChannel { + API + SFTP + XLSX +} + +type FinancialServiceProviderNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + allowedBusinessAreas(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID): UserBusinessAreaNodeConnection! + createdBy: UserNode + name: String! + visionVendorNumber: String! + deliveryMechanismsChoices: [String!] + deliveryMechanisms(offset: Int, before: String, after: String, first: Int, last: Int): DeliveryMechanismNodeConnection! + distributionLimit: Float + communicationChannel: FinancialServiceProviderCommunicationChannel! + dataTransferConfiguration: JSONString + xlsxTemplates(offset: Int, before: String, after: String, first: Int, last: Int): FinancialServiceProviderXlsxTemplateNodeConnection! + paymentGatewayId: String + deliveryMechanismsPerPaymentPlan(offset: Int, before: String, after: String, first: Int, last: Int): DeliveryMechanismPerPaymentPlanNodeConnection! + paymentSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentNodeConnection! + fullName: String + isPaymentGateway: Boolean +} + +type FinancialServiceProviderNodeConnection { + pageInfo: PageInfo! + edges: [FinancialServiceProviderNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type FinancialServiceProviderNodeEdge { + node: FinancialServiceProviderNode + cursor: String! +} + +type FinancialServiceProviderXlsxTemplateNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + createdBy: UserNode + name: String! + columns: [String] + coreFields: [String!]! + flexFields: [String!]! + financialServiceProviders(offset: Int, before: String, after: String, first: Int, last: Int): FinancialServiceProviderNodeConnection! +} + +type FinancialServiceProviderXlsxTemplateNodeConnection { + pageInfo: PageInfo! + edges: [FinancialServiceProviderXlsxTemplateNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type FinancialServiceProviderXlsxTemplateNodeEdge { + node: FinancialServiceProviderXlsxTemplateNode + cursor: String! +} + +type FinishPaymentVerificationPlan { + paymentPlan: GenericPaymentPlanNode +} + +enum FlexFieldClassificationChoices { + NOT_FLEX_FIELD + FLEX_FIELD_BASIC + FLEX_FIELD_PDU +} + +scalar FlexFieldsScalar + +type FspChoice { + id: String + name: String + configurations: [FspConfiguration] +} + +type FspChoices { + deliveryMechanism: String + fsps: [FspChoice] +} + +type FspConfiguration { + id: String + key: String + label: String +} + +input FullListArguments { + excludedAdminAreas: [String] +} + +type GenericPaymentNode { + id: String + objType: String + unicefId: String + currency: String + deliveredQuantity: Float + deliveredQuantityUsd: Float + household: HouseholdNode +} + +type GenericPaymentPlanNode { + id: String + objType: String + paymentVerificationSummary: PaymentVerificationSummaryNode + availablePaymentRecordsCount: Int + verificationPlans(offset: Int, before: String, after: String, first: Int, last: Int, programId: String): PaymentVerificationPlanNodeConnection + statusDate: DateTime + status: String + bankReconciliationSuccess: Int + bankReconciliationError: Int + deliveryType: String + totalNumberOfHouseholds: Int + currency: String + totalDeliveredQuantity: Float + totalEntitledQuantity: Float + totalUndeliveredQuantity: Float + canCreatePaymentVerificationPlan: Boolean +} + +scalar GeoJSON + +input GetAccountabilityCommunicationMessageSampleSizeInput { + households: [ID] + targetPopulation: ID + registrationDataImport: ID + samplingType: SamplingChoices! + fullListArguments: AccountabilityFullListArguments + randomSamplingArguments: AccountabilityRandomSamplingArguments +} + +input GetCashplanVerificationSampleSizeInput { + cashOrPaymentPlanId: ID + paymentVerificationPlanId: ID + sampling: String! + verificationChannel: String! + businessAreaSlug: String! + fullListArguments: FullListArguments + randomSamplingArguments: RandomSamplingArguments + rapidProArguments: RapidProArguments +} + +type GetCashplanVerificationSampleSizeObject { + paymentRecordCount: Int + sampleSize: Int +} + +type GetCommunicationMessageSampleSizeNode { + numberOfRecipients: Int + sampleSize: Int +} + +input GrievanceComplaintTicketExtras { + household: ID + individual: ID + paymentRecord: [ID] +} + +input GrievanceDocumentInput { + name: String! + file: Upload! +} + +type GrievanceDocumentNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + name: String + createdBy: UserNode + grievanceTicket: GrievanceTicketNode + fileSize: Int + contentType: String! + filePath: String + fileName: String +} + +type GrievanceDocumentNodeConnection { + pageInfo: PageInfo! + edges: [GrievanceDocumentNodeEdge]! +} + +type GrievanceDocumentNodeEdge { + node: GrievanceDocumentNode + cursor: String! +} + +input GrievanceDocumentUpdateInput { + id: ID! + name: String + file: Upload +} + +type GrievanceStatusChangeMutation { + grievanceTicket: GrievanceTicketNode +} + +type GrievanceTicketNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + version: BigInt! + unicefId: String + userModified: DateTime + lastNotificationSent: DateTime + createdBy: UserNode + assignedTo: UserNode + status: Int! + category: Int! + issueType: Int + description: String! + admin2: AreaNode + area: String! + language: String! + consent: Boolean! + businessArea: UserBusinessAreaNode! + linkedTickets: [GrievanceTicketNode] + registrationDataImport: RegistrationDataImportNode + extras: JSONString! + ignored: Boolean! + householdUnicefId: String + priority: Int + urgency: Int + partner: PartnerType + programs: [ProgramNode] + comments: String + isOriginal: Boolean! + isMigrationHandled: Boolean! + migratedAt: DateTime + copiedFrom: GrievanceTicketNode + copiedTo(offset: Int, before: String, after: String, first: Int, last: Int): GrievanceTicketNodeConnection! + ticketNotes(offset: Int, before: String, after: String, first: Int, last: Int): TicketNoteNodeConnection! + complaintTicketDetails: TicketComplaintDetailsNode + sensitiveTicketDetails: TicketSensitiveDetailsNode + householdDataUpdateTicketDetails: TicketHouseholdDataUpdateDetailsNode + individualDataUpdateTicketDetails: TicketIndividualDataUpdateDetailsNode + addIndividualTicketDetails: TicketAddIndividualDetailsNode + deleteIndividualTicketDetails: TicketDeleteIndividualDetailsNode + deleteHouseholdTicketDetails: TicketDeleteHouseholdDetailsNode + systemFlaggingTicketDetails: TicketSystemFlaggingDetailsNode + needsAdjudicationTicketDetails: TicketNeedsAdjudicationDetailsNode + paymentVerificationTicketDetails: TicketPaymentVerificationDetailsNode + positiveFeedbackTicketDetails: TicketPositiveFeedbackDetailsNode + negativeFeedbackTicketDetails: TicketNegativeFeedbackDetailsNode + referralTicketDetails: TicketReferralDetailsNode + supportDocuments(offset: Int, before: String, after: String, first: Int, last: Int): GrievanceDocumentNodeConnection! + feedback: FeedbackNode + adminUrl: String + household: HouseholdNode + individual: IndividualNode + paymentRecord: PaymentRecordAndPaymentNode + admin: String + existingTickets: [GrievanceTicketNode] + relatedTickets: [GrievanceTicketNode] + totalDays: String + documentation: [GrievanceDocumentNode] +} + +type GrievanceTicketNodeConnection { + pageInfo: PageInfo! + edges: [GrievanceTicketNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type GrievanceTicketNodeEdge { + node: GrievanceTicketNode + cursor: String! +} + +type GroupAttributeNode { + id: UUID! + name: String! + label: JSONString! + flexAttributes(flexField: Boolean): [FieldAttributeNode] + labelEn: String +} + +enum HouseholdCollectIndividualData { + A_ + A_2 + A_1 + A_3 + A_0 +} + +enum HouseholdCollectType { + STANDARD + SINGLE +} + +enum HouseholdConsentSharing { + A_ + GOVERNMENT_PARTNER + HUMANITARIAN_PARTNER + PRIVATE_PARTNER + UNICEF +} + +enum HouseholdCurrency { + A_ + AED + AFN + ALL + AMD + ANG + AOA + ARS + AUD + AWG + AZN + BAM + BBD + BDT + BGN + BHD + BIF + BMD + BND + BOB + BOV + BRL + BSD + BTN + BWP + BYN + BZD + CAD + CDF + CHF + CLP + CNY + COP + CRC + CUC + CUP + CVE + CZK + DJF + DKK + DOP + DZD + EGP + ERN + ETB + EUR + FJD + FKP + GBP + GEL + GHS + GIP + GMD + GNF + GTQ + GYD + HKD + HNL + HRK + HTG + HUF + IDR + ILS + INR + IQD + IRR + ISK + JMD + JOD + JPY + KES + KGS + KHR + KMF + KPW + KRW + KWD + KYD + KZT + LAK + LBP + LKR + LRD + LSL + LYD + MAD + MDL + MGA + MKD + MMK + MNT + MOP + MRU + MUR + MVR + MWK + MXN + MYR + MZN + NAD + NGN + NIO + NOK + NPR + NZD + OMR + PAB + PEN + PGK + PHP + PKR + PLN + PYG + QAR + RON + RSD + RUB + RWF + SAR + SBD + SCR + SDG + SEK + SGD + SHP + SLL + SOS + SRD + SSP + STN + SVC + SYP + SZL + THB + TJS + TMT + TND + TOP + TRY + TTD + TWD + TZS + UAH + UGX + USD + UYU + UYW + UZS + VES + VND + VUV + WST + XAF + XAG + XAU + XCD + XOF + XPF + YER + ZAR + ZMW + ZWL + USDC +} + +type HouseholdDataChangeApproveMutation { + grievanceTicket: GrievanceTicketNode +} + +input HouseholdDataUpdateIssueTypeExtras { + household: ID! + householdData: HouseholdUpdateDataObjectType! +} + +input HouseholdDeleteIssueTypeExtras { + household: ID! +} + +type HouseholdNode implements Node { + id: ID! + size: Int + headOfHousehold: IndividualNode + rdiMergeStatus: HouseholdRdiMergeStatus! + isOriginal: Boolean! + createdAt: DateTime! + updatedAt: DateTime! + isRemoved: Boolean! + removedDate: DateTime + lastSyncAt: DateTime + version: BigInt! + unicefId: String + withdrawn: Boolean! + withdrawnDate: DateTime + consentSign: String! + consent: Boolean + consentSharing: [String] + residenceStatus: String + countryOrigin: String + country: String + address: String! + zipCode: String + adminArea: AreaNode + admin1: AreaNode + admin2: AreaNode + admin3: AreaNode + admin4: AreaNode + geopoint: GeoJSON + femaleAgeGroup05Count: Int + femaleAgeGroup611Count: Int + femaleAgeGroup1217Count: Int + femaleAgeGroup1859Count: Int + femaleAgeGroup60Count: Int + pregnantCount: Int + maleAgeGroup05Count: Int + maleAgeGroup611Count: Int + maleAgeGroup1217Count: Int + maleAgeGroup1859Count: Int + maleAgeGroup60Count: Int + femaleAgeGroup05DisabledCount: Int + femaleAgeGroup611DisabledCount: Int + femaleAgeGroup1217DisabledCount: Int + femaleAgeGroup1859DisabledCount: Int + femaleAgeGroup60DisabledCount: Int + maleAgeGroup05DisabledCount: Int + maleAgeGroup611DisabledCount: Int + maleAgeGroup1217DisabledCount: Int + maleAgeGroup1859DisabledCount: Int + maleAgeGroup60DisabledCount: Int + childrenCount: Int + maleChildrenCount: Int + femaleChildrenCount: Int + childrenDisabledCount: Int + maleChildrenDisabledCount: Int + femaleChildrenDisabledCount: Int + registrationDataImport: RegistrationDataImportNode + returnee: Boolean + flexFields: FlexFieldsScalar + firstRegistrationDate: DateTime! + lastRegistrationDate: DateTime! + fchildHoh: Boolean + childHoh: Boolean + businessArea: UserBusinessAreaNode! + start: DateTime + deviceid: String! + nameEnumerator: String! + orgEnumerator: HouseholdOrgEnumerator! + orgNameEnumerator: String! + village: String! + registrationMethod: HouseholdRegistrationMethod! + collectIndividualData: HouseholdCollectIndividualData! + currency: String + unhcrId: String! + userFields: JSONString! + detailId: String + registrationId: String + programRegistrationId: String + totalCashReceivedUsd: Decimal + totalCashReceived: Decimal + familyId: String + program: ProgramNode + copiedFrom: HouseholdNode + originUnicefId: String + isMigrationHandled: Boolean! + migratedAt: DateTime + isRecalculatedGroupAges: Boolean! + collectType: HouseholdCollectType! + koboSubmissionUuid: UUID + koboSubmissionTime: DateTime + enumeratorRecId: Int + misUnicefId: String + flexRegistrationsRecordId: Int + representatives(offset: Int, before: String, after: String, first: Int, last: Int): IndividualNodeConnection! + programs(offset: Int, before: String, after: String, first: Int, last: Int, name: String): ProgramNodeConnection! + copiedTo(offset: Int, before: String, after: String, first: Int, last: Int): HouseholdNodeConnection! + individualsAndRoles: [IndividualRoleInHouseholdNode!]! + individuals(offset: Int, before: String, after: String, first: Int, last: Int, household_Id: UUID, businessArea: String, fullName: String, fullName_Startswith: String, fullName_Endswith: String, sex: [String], household_AdminArea: ID, withdrawn: Boolean, program: ID, age: String, programs: [ID], search: String, documentType: String, documentNumber: String, lastRegistrationDate: String, admin1: [ID], admin2: [ID], status: [String], excludedId: String, flags: [String], isActiveProgram: Boolean, orderBy: String): IndividualNodeConnection + paymentrecordSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentRecordNodeConnection! + paymentSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentNodeConnection! + complaintTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketComplaintDetailsNodeConnection! + sensitiveTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketSensitiveDetailsNodeConnection! + householdDataUpdateTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketHouseholdDataUpdateDetailsNodeConnection! + addIndividualTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketAddIndividualDetailsNodeConnection! + deleteHouseholdTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketDeleteHouseholdDetailsNodeConnection! + positiveFeedbackTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketPositiveFeedbackDetailsNodeConnection! + negativeFeedbackTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketNegativeFeedbackDetailsNodeConnection! + referralTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketReferralDetailsNodeConnection! + targetPopulations(offset: Int, before: String, after: String, first: Int, last: Int, program: [ID], createdAt: DateTime, createdAt_Lte: DateTime, createdAt_Gte: DateTime, updatedAt: DateTime, updatedAt_Lte: DateTime, updatedAt_Gte: DateTime, status: String, households: [ID], name: String, createdByName: String, totalHouseholdsCountMin: Int, totalHouseholdsCountMax: Int, totalIndividualsCountMin: Int, totalIndividualsCountMax: Int, businessArea: String, createdAtRange: String, paymentPlanApplicable: Boolean, statusNot: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, programCycle: String, orderBy: String): TargetPopulationNodeConnection! + selections: [HouseholdSelectionNode!]! + messages(offset: Int, before: String, after: String, first: Int, last: Int): CommunicationMessageNodeConnection! + feedbacks(offset: Int, before: String, after: String, first: Int, last: Int): FeedbackNodeConnection! + surveys(offset: Int, before: String, after: String, first: Int, last: Int): SurveyNodeConnection! + adminUrl: String + selection: HouseholdSelectionNode + sanctionListPossibleMatch: Boolean + sanctionListConfirmedMatch: Boolean + hasDuplicates: Boolean + adminAreaTitle: String + status: String + deliveredQuantities: [DeliveredQuantityNode] + activeIndividualsCount: Int + importId: String +} + +type HouseholdNodeConnection { + pageInfo: PageInfo! + edges: [HouseholdNodeEdge]! + totalCount: Int + individualsCount: Int + edgeCount: Int +} + +type HouseholdNodeEdge { + node: HouseholdNode + cursor: String! +} + +enum HouseholdOrgEnumerator { + A_ + PARTNER + UNICEF +} + +enum HouseholdRdiMergeStatus { + PENDING + MERGED +} + +enum HouseholdRegistrationMethod { + A_ + COMMUNITY + HH_REGISTRATION +} + +enum HouseholdResidenceStatus { + A_ + IDP + REFUGEE + OTHERS_OF_CONCERN + HOST + NON_HOST + RETURNEE +} + +type HouseholdSelectionNode { + id: UUID! + createdAt: DateTime! + updatedAt: DateTime! + household: HouseholdNode! + targetPopulation: TargetPopulationNode! + vulnerabilityScore: Float + isOriginal: Boolean! + isMigrationHandled: Boolean! +} + +input HouseholdUpdateDataObjectType { + adminAreaTitle: String + status: String + consent: Boolean + consentSharing: [String] + residenceStatus: String + countryOrigin: String + country: String + size: Int + address: String + femaleAgeGroup05Count: Int + femaleAgeGroup611Count: Int + femaleAgeGroup1217Count: Int + femaleAgeGroup1859Count: Int + femaleAgeGroup60Count: Int + pregnantCount: Int + maleAgeGroup05Count: Int + maleAgeGroup611Count: Int + maleAgeGroup1217Count: Int + maleAgeGroup1859Count: Int + maleAgeGroup60Count: Int + femaleAgeGroup05DisabledCount: Int + femaleAgeGroup611DisabledCount: Int + femaleAgeGroup1217DisabledCount: Int + femaleAgeGroup1859DisabledCount: Int + femaleAgeGroup60DisabledCount: Int + maleAgeGroup05DisabledCount: Int + maleAgeGroup611DisabledCount: Int + maleAgeGroup1217DisabledCount: Int + maleAgeGroup1859DisabledCount: Int + maleAgeGroup60DisabledCount: Int + returnee: Boolean + fchildHoh: Boolean + childHoh: Boolean + start: DateTime + end: DateTime + nameEnumerator: String + orgEnumerator: String + orgNameEnumerator: String + village: String + registrationMethod: String + collectIndividualData: String + currency: String + unhcrId: String + flexFields: Arg +} + +enum ImportDataDataType { + XLSX + JSON + FLEX +} + +type ImportDataNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + status: ImportDataStatus! + businessAreaSlug: String! + file: String + dataType: ImportDataDataType! + numberOfHouseholds: Int + numberOfIndividuals: Int + error: String! + validationErrors: String! + deliveryMechanismsValidationErrors: String! + createdById: UUID + registrationDataImportHope: RegistrationDataImportNode + koboimportdata: KoboImportDataNode + registrationDataImport: RegistrationDataImportDatahubNode + xlsxValidationErrors: [XlsxRowErrorNode] +} + +enum ImportDataStatus { + PENDING + RUNNING + FINISHED + ERROR + VALIDATION_ERROR + DELIVERY_MECHANISMS_VALIDATION_ERROR +} + +type ImportXLSXPaymentPlanPaymentListMutation { + paymentPlan: PaymentPlanNode + errors: [XlsxErrorNode] +} + +type ImportXLSXPaymentPlanPaymentListPerFSPMutation { + paymentPlan: PaymentPlanNode + errors: [XlsxErrorNode] +} + +type ImportXlsxPaymentVerificationPlanFile { + paymentPlan: GenericPaymentPlanNode + errors: [XlsxErrorNode] +} + +type ImportedDocumentNode implements Node { + id: ID! + rdiMergeStatus: DocumentRdiMergeStatus! + isRemoved: Boolean! + isOriginal: Boolean! + createdAt: DateTime! + updatedAt: DateTime! + lastSyncAt: DateTime + documentNumber: String! + photo: String + individual: IndividualNode! + type: DocumentTypeNode! + country: String + status: DocumentStatus! + cleared: Boolean! + clearedDate: DateTime! + clearedBy: UserNode + issuanceDate: DateTime + expiryDate: DateTime + program: ProgramNode + isMigrationHandled: Boolean! + copiedFrom: DocumentNode +} + +type ImportedDocumentNodeConnection { + pageInfo: PageInfo! + edges: [ImportedDocumentNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type ImportedDocumentNodeEdge { + node: ImportedDocumentNode + cursor: String! +} + +type ImportedHouseholdNode implements Node { + id: ID! + size: Int + headOfHousehold: IndividualNode + rdiMergeStatus: HouseholdRdiMergeStatus! + isOriginal: Boolean! + createdAt: DateTime! + updatedAt: DateTime! + isRemoved: Boolean! + removedDate: DateTime + lastSyncAt: DateTime + version: BigInt! + unicefId: String + withdrawn: Boolean! + withdrawnDate: DateTime + consentSign: String! + consent: Boolean + consentSharing: HouseholdConsentSharing! + residenceStatus: HouseholdResidenceStatus + countryOrigin: String + country: String + address: String! + zipCode: String + adminArea: AreaNode + admin1: AreaNode + admin2: AreaNode + admin3: AreaNode + admin4: AreaNode + geopoint: GeoJSON + femaleAgeGroup05Count: Int + femaleAgeGroup611Count: Int + femaleAgeGroup1217Count: Int + femaleAgeGroup1859Count: Int + femaleAgeGroup60Count: Int + pregnantCount: Int + maleAgeGroup05Count: Int + maleAgeGroup611Count: Int + maleAgeGroup1217Count: Int + maleAgeGroup1859Count: Int + maleAgeGroup60Count: Int + femaleAgeGroup05DisabledCount: Int + femaleAgeGroup611DisabledCount: Int + femaleAgeGroup1217DisabledCount: Int + femaleAgeGroup1859DisabledCount: Int + femaleAgeGroup60DisabledCount: Int + maleAgeGroup05DisabledCount: Int + maleAgeGroup611DisabledCount: Int + maleAgeGroup1217DisabledCount: Int + maleAgeGroup1859DisabledCount: Int + maleAgeGroup60DisabledCount: Int + childrenCount: Int + maleChildrenCount: Int + femaleChildrenCount: Int + childrenDisabledCount: Int + maleChildrenDisabledCount: Int + femaleChildrenDisabledCount: Int + registrationDataImport: RegistrationDataImportNode + returnee: Boolean + flexFields: Arg + firstRegistrationDate: DateTime! + lastRegistrationDate: DateTime! + fchildHoh: Boolean + childHoh: Boolean + businessArea: UserBusinessAreaNode! + start: DateTime + deviceid: String! + nameEnumerator: String! + orgEnumerator: HouseholdOrgEnumerator! + orgNameEnumerator: String! + village: String! + registrationMethod: HouseholdRegistrationMethod! + collectIndividualData: HouseholdCollectIndividualData! + currency: HouseholdCurrency! + unhcrId: String! + userFields: JSONString! + detailId: String + registrationId: String + programRegistrationId: String + totalCashReceivedUsd: Float + totalCashReceived: Float + familyId: String + program: ProgramNode + copiedFrom: HouseholdNode + originUnicefId: String + isMigrationHandled: Boolean! + migratedAt: DateTime + isRecalculatedGroupAges: Boolean! + collectType: HouseholdCollectType! + koboSubmissionUuid: UUID + koboSubmissionTime: DateTime + enumeratorRecId: Int + misUnicefId: String + flexRegistrationsRecordId: Int + individuals(offset: Int, before: String, after: String, first: Int, last: Int): ImportedIndividualNodeConnection + hasDuplicates: Boolean + importId: String +} + +type ImportedHouseholdNodeConnection { + pageInfo: PageInfo! + edges: [ImportedHouseholdNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type ImportedHouseholdNodeEdge { + node: ImportedHouseholdNode + cursor: String! +} + +type ImportedIndividualIdentityNode implements Node { + id: ID! + created: DateTime! + modified: DateTime! + rdiMergeStatus: IndividualIdentityRdiMergeStatus! + isRemoved: Boolean! + isOriginal: Boolean! + individual: IndividualNode! + number: String! + partner: String + country: String + isMigrationHandled: Boolean! + copiedFrom: IndividualIdentityNode + copiedTo(offset: Int, before: String, after: String, first: Int, last: Int): IndividualIdentityNodeConnection! +} + +type ImportedIndividualIdentityNodeConnection { + pageInfo: PageInfo! + edges: [ImportedIndividualIdentityNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type ImportedIndividualIdentityNodeEdge { + node: ImportedIndividualIdentityNode + cursor: String! +} + +type ImportedIndividualNode implements Node { + id: ID! + rdiMergeStatus: IndividualRdiMergeStatus! + isOriginal: Boolean! + createdAt: DateTime! + updatedAt: DateTime! + isRemoved: Boolean! + removedDate: DateTime + lastSyncAt: DateTime + version: BigInt! + unicefId: String + duplicate: Boolean! + duplicateDate: DateTime + withdrawn: Boolean! + withdrawnDate: DateTime + individualId: String! + photo: String! + fullName: String! + givenName: String! + middleName: String! + familyName: String! + sex: IndividualSex! + birthDate: Date! + estimatedBirthDate: Boolean + maritalStatus: IndividualMaritalStatus! + phoneNo: String! + phoneNoValid: Boolean + phoneNoAlternative: String! + phoneNoAlternativeValid: Boolean + email: String + paymentDeliveryPhoneNo: String + relationship: String + household: HouseholdNode + registrationDataImport: RegistrationDataImportNode + workStatus: String! + firstRegistrationDate: Date! + lastRegistrationDate: Date! + flexFields: FlexFieldsScalar + userFields: JSONString! + enrolledInNutritionProgramme: Boolean + administrationOfRutf: Boolean + deduplicationGoldenRecordStatus: IndividualDeduplicationGoldenRecordStatus! + deduplicationBatchStatus: IndividualDeduplicationBatchStatus! + deduplicationGoldenRecordResults: [DeduplicationResultNode] + deduplicationBatchResults: [DeduplicationResultNode] + importedIndividualId: UUID + sanctionListPossibleMatch: Boolean! + sanctionListConfirmedMatch: Boolean! + pregnant: Boolean + disability: IndividualDisability! + observedDisability: [String] + disabilityCertificatePicture: String + seeingDisability: String! + hearingDisability: String! + physicalDisability: String! + memoryDisability: String! + selfcareDisability: String! + commsDisability: String! + whoAnswersPhone: String! + whoAnswersAltPhone: String! + businessArea: UserBusinessAreaNode! + fchildHoh: Boolean! + childHoh: Boolean! + detailId: String + registrationId: String + programRegistrationId: String + preferredLanguage: String + relationshipConfirmed: Boolean! + ageAtRegistration: Int + walletName: String! + blockchainName: String! + walletAddress: String! + program: ProgramNode + copiedFrom: IndividualNode + originUnicefId: String + isMigrationHandled: Boolean! + migratedAt: DateTime + misUnicefId: String + role: String + age: Int + importId: String + documents(offset: Int, before: String, after: String, first: Int, last: Int): ImportedDocumentNodeConnection + identities(offset: Int, before: String, after: String, first: Int, last: Int): ImportedIndividualIdentityNodeConnection +} + +type ImportedIndividualNodeConnection { + pageInfo: PageInfo! + edges: [ImportedIndividualNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type ImportedIndividualNodeEdge { + node: ImportedIndividualNode + cursor: String! +} + +type IndividualDataChangeApproveMutation { + grievanceTicket: GrievanceTicketNode +} + +input IndividualDataUpdateIssueTypeExtras { + individual: ID! + individualData: IndividualUpdateDataObjectType! +} + +enum IndividualDeduplicationBatchStatus { + DUPLICATE_IN_BATCH + NOT_PROCESSED + SIMILAR_IN_BATCH + UNIQUE_IN_BATCH +} + +enum IndividualDeduplicationGoldenRecordStatus { + DUPLICATE + NEEDS_ADJUDICATION + NOT_PROCESSED + POSTPONE + UNIQUE +} + +input IndividualDeleteIssueTypeExtras { + individual: ID! +} + +enum IndividualDisability { + DISABLED + NOT_DISABLED +} + +input IndividualDocumentObjectType { + country: String! + key: String! + number: String! + photo: Arg + photoraw: Arg +} + +type IndividualIdentityNode implements Node { + id: ID! + created: DateTime! + modified: DateTime! + rdiMergeStatus: IndividualIdentityRdiMergeStatus! + isRemoved: Boolean! + isOriginal: Boolean! + individual: IndividualNode! + number: String! + partner: String + country: String + isMigrationHandled: Boolean! + copiedFrom: IndividualIdentityNode + copiedTo(offset: Int, before: String, after: String, first: Int, last: Int): IndividualIdentityNodeConnection! + countryIso3: String +} + +type IndividualIdentityNodeConnection { + pageInfo: PageInfo! + edges: [IndividualIdentityNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type IndividualIdentityNodeEdge { + node: IndividualIdentityNode + cursor: String! +} + +input IndividualIdentityObjectType { + country: String! + partner: String! + number: String! +} + +enum IndividualIdentityRdiMergeStatus { + PENDING + MERGED +} + +enum IndividualMaritalStatus { + A_ + DIVORCED + MARRIED + SEPARATED + SINGLE + WIDOWED +} + +type IndividualNode implements Node { + id: ID! + rdiMergeStatus: IndividualRdiMergeStatus! + isOriginal: Boolean! + createdAt: DateTime! + updatedAt: DateTime! + isRemoved: Boolean! + removedDate: DateTime + lastSyncAt: DateTime + version: BigInt! + unicefId: String + duplicate: Boolean! + duplicateDate: DateTime + withdrawn: Boolean! + withdrawnDate: DateTime + individualId: String! + photo: String + fullName: String! + givenName: String! + middleName: String! + familyName: String! + sex: IndividualSex! + birthDate: Date! + estimatedBirthDate: Boolean + maritalStatus: IndividualMaritalStatus! + phoneNo: String! + phoneNoValid: Boolean + phoneNoAlternative: String! + phoneNoAlternativeValid: Boolean + email: String! + paymentDeliveryPhoneNo: String + relationship: IndividualRelationship + household: HouseholdNode + registrationDataImport: RegistrationDataImportNode + workStatus: String! + firstRegistrationDate: Date! + lastRegistrationDate: Date! + flexFields: FlexFieldsScalar + userFields: JSONString! + enrolledInNutritionProgramme: Boolean + administrationOfRutf: Boolean + deduplicationGoldenRecordStatus: IndividualDeduplicationGoldenRecordStatus! + deduplicationBatchStatus: IndividualDeduplicationBatchStatus! + deduplicationGoldenRecordResults: [DeduplicationResultNode] + deduplicationBatchResults: [DeduplicationResultNode] + importedIndividualId: UUID + sanctionListPossibleMatch: Boolean! + sanctionListConfirmedMatch: Boolean! + pregnant: Boolean + disability: IndividualDisability! + observedDisability: [String] + disabilityCertificatePicture: String + seeingDisability: String! + hearingDisability: String! + physicalDisability: String! + memoryDisability: String! + selfcareDisability: String! + commsDisability: String! + whoAnswersPhone: String! + whoAnswersAltPhone: String! + businessArea: UserBusinessAreaNode! + fchildHoh: Boolean! + childHoh: Boolean! + detailId: String + registrationId: String + programRegistrationId: String + preferredLanguage: String + relationshipConfirmed: Boolean! + ageAtRegistration: Int + walletName: String! + blockchainName: String! + walletAddress: String! + program: ProgramNode + copiedFrom: IndividualNode + originUnicefId: String + isMigrationHandled: Boolean! + migratedAt: DateTime + misUnicefId: String + representedHouseholds(offset: Int, before: String, after: String, first: Int, last: Int): HouseholdNodeConnection! + headingHousehold: HouseholdNode + documents(offset: Int, before: String, after: String, first: Int, last: Int): DocumentNodeConnection! + identities(offset: Int, before: String, after: String, first: Int, last: Int): IndividualIdentityNodeConnection! + householdsAndRoles: [IndividualRoleInHouseholdNode!]! + copiedTo(offset: Int, before: String, after: String, first: Int, last: Int): IndividualNodeConnection! + bankAccountInfo: BankAccountInfoNode + paymentrecordSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentRecordNodeConnection! + collectorPayments(offset: Int, before: String, after: String, first: Int, last: Int): PaymentNodeConnection! + paymentSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentNodeConnection! + complaintTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketComplaintDetailsNodeConnection! + sensitiveTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketSensitiveDetailsNodeConnection! + individualDataUpdateTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketIndividualDataUpdateDetailsNodeConnection! + deleteIndividualTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketDeleteIndividualDetailsNodeConnection! + ticketsystemflaggingdetailsSet(offset: Int, before: String, after: String, first: Int, last: Int): TicketSystemFlaggingDetailsNodeConnection! + ticketGoldenRecords(offset: Int, before: String, after: String, first: Int, last: Int): TicketNeedsAdjudicationDetailsNodeConnection! + ticketDuplicates(offset: Int, before: String, after: String, first: Int, last: Int): TicketNeedsAdjudicationDetailsNodeConnection! + selectedDistinct(offset: Int, before: String, after: String, first: Int, last: Int): TicketNeedsAdjudicationDetailsNodeConnection! + ticketSelected(offset: Int, before: String, after: String, first: Int, last: Int): TicketNeedsAdjudicationDetailsNodeConnection! + positiveFeedbackTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketPositiveFeedbackDetailsNodeConnection! + negativeFeedbackTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketNegativeFeedbackDetailsNodeConnection! + referralTicketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketReferralDetailsNodeConnection! + feedbacks(offset: Int, before: String, after: String, first: Int, last: Int): FeedbackNodeConnection! + adminUrl: String + status: String + role: String + age: Int + sanctionListLastCheck: DateTime + paymentChannels: [BankAccountInfoNode] +} + +type IndividualNodeConnection { + pageInfo: PageInfo! + edges: [IndividualNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type IndividualNodeEdge { + node: IndividualNode + cursor: String! +} + +enum IndividualRdiMergeStatus { + PENDING + MERGED +} + +enum IndividualRelationship { + UNKNOWN + AUNT_UNCLE + BROTHER_SISTER + COUSIN + DAUGHTERINLAW_SONINLAW + GRANDDAUGHER_GRANDSON + GRANDMOTHER_GRANDFATHER + HEAD + MOTHER_FATHER + MOTHERINLAW_FATHERINLAW + NEPHEW_NIECE + NON_BENEFICIARY + OTHER + SISTERINLAW_BROTHERINLAW + SON_DAUGHTER + WIFE_HUSBAND + FOSTER_CHILD + FREE_UNION +} + +type IndividualRoleInHouseholdNode { + id: UUID! + rdiMergeStatus: IndividualRoleInHouseholdRdiMergeStatus! + isRemoved: Boolean! + isOriginal: Boolean! + createdAt: DateTime! + updatedAt: DateTime! + lastSyncAt: DateTime + individual: IndividualNode! + household: HouseholdNode! + role: IndividualRoleInHouseholdRole + isMigrationHandled: Boolean! + migratedAt: DateTime + copiedFrom: IndividualRoleInHouseholdNode + copiedTo: [IndividualRoleInHouseholdNode!]! +} + +enum IndividualRoleInHouseholdRdiMergeStatus { + PENDING + MERGED +} + +enum IndividualRoleInHouseholdRole { + NO_ROLE + ALTERNATE + PRIMARY +} + +enum IndividualSex { + MALE + FEMALE +} + +input IndividualUpdateDataObjectType { + status: String + fullName: String + givenName: String + middleName: String + familyName: String + sex: String + birthDate: Date + estimatedBirthDate: Boolean + maritalStatus: String + phoneNo: String + phoneNoAlternative: String + email: String + relationship: String + disability: String + workStatus: String + enrolledInNutritionProgramme: Boolean + administrationOfRutf: Boolean + pregnant: Boolean + observedDisability: [String] + seeingDisability: String + hearingDisability: String + physicalDisability: String + memoryDisability: String + selfcareDisability: String + commsDisability: String + whoAnswersPhone: String + whoAnswersAltPhone: String + role: String + documents: [IndividualDocumentObjectType] + documentsToRemove: [ID] + documentsToEdit: [EditIndividualDocumentObjectType] + identities: [IndividualIdentityObjectType] + identitiesToRemove: [ID] + identitiesToEdit: [EditIndividualIdentityObjectType] + paymentChannels: [BankTransferObjectType] + paymentChannelsToEdit: [EditBankTransferObjectType] + paymentChannelsToRemove: [ID] + preferredLanguage: String + flexFields: Arg + paymentDeliveryPhoneNo: String + blockchainName: String + walletAddress: String + walletName: String + deliveryMechanismData: [DeliveryMechanismDataObjectType] + deliveryMechanismDataToEdit: [EditDeliveryMechanismDataObjectType] + deliveryMechanismDataToRemove: [ID] +} + +type InvalidPaymentVerificationPlan { + paymentPlan: GenericPaymentPlanNode +} + +input IssueTypeExtrasInput { + householdDataUpdateIssueTypeExtras: HouseholdDataUpdateIssueTypeExtras + individualDataUpdateIssueTypeExtras: IndividualDataUpdateIssueTypeExtras + individualDeleteIssueTypeExtras: IndividualDeleteIssueTypeExtras + householdDeleteIssueTypeExtras: HouseholdDeleteIssueTypeExtras + addIndividualIssueTypeExtras: AddIndividualIssueTypeExtras +} + +type IssueTypesObject { + category: String + label: String + subCategories: [ChoiceObject] +} + +scalar JSONString + +type KoboAssetObject { + id: String + name: String + sector: String + country: String + assetType: String + dateModified: DateTime + deploymentActive: Boolean + hasDeployment: Boolean + xlsLink: String +} + +type KoboAssetObjectConnection { + pageInfo: PageInfo! + edges: [KoboAssetObjectEdge]! + totalCount: Int +} + +type KoboAssetObjectEdge { + node: KoboAssetObject + cursor: String! +} + +type KoboErrorNode { + header: String + message: String +} + +type KoboImportDataNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + status: ImportDataStatus! + businessAreaSlug: String! + file: String + dataType: ImportDataDataType! + numberOfHouseholds: Int + numberOfIndividuals: Int + error: String! + validationErrors: String! + deliveryMechanismsValidationErrors: String! + createdById: UUID + importdataPtr: ImportDataNode! + koboAssetId: String! + onlyActiveSubmissions: Boolean! + pullPictures: Boolean! + koboValidationErrors: [KoboErrorNode] +} + +type LabelNode { + language: String + label: String +} + +type LanguageObject { + english: String + code: String +} + +type LanguageObjectConnection { + pageInfo: PageInfo! + edges: [LanguageObjectEdge]! + totalCount: Int +} + +type LanguageObjectEdge { + node: LanguageObject + cursor: String! +} + +type LockTargetPopulationMutation { + targetPopulation: TargetPopulationNode +} + +enum LogEntryAction { + CREATE + UPDATE + DELETE + SOFT_DELETE +} + +type LogEntryNode implements Node { + id: ID! + contentType: ContentTypeObjectType + objectId: UUID + action: LogEntryAction! + objectRepr: String! + changes: Arg + user: UserNode + businessArea: UserBusinessAreaNode + programs(offset: Int, before: String, after: String, first: Int, last: Int, name: String): ProgramNodeConnection! + timestamp: DateTime + isUserGenerated: Boolean +} + +type LogEntryNodeConnection { + pageInfo: PageInfo! + edges: [LogEntryNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type LogEntryNodeEdge { + node: LogEntryNode + cursor: String! +} + +type MarkPaymentAsFailedMutation { + payment: PaymentNode +} + +type MarkPaymentRecordAsFailedMutation { + paymentRecord: PaymentRecordNode +} + +type MergeRegistrationDataImportMutation { + registrationDataImport: RegistrationDataImportNode +} + +enum MessageSamplingType { + FULL_LIST + RANDOM +} + +type Mutations { + createAccountabilityCommunicationMessage(input: CreateAccountabilityCommunicationMessageInput!): CreateCommunicationMessageMutation + createFeedback(input: CreateFeedbackInput!): CreateFeedbackMutation + updateFeedback(input: UpdateFeedbackInput!): UpdateFeedbackMutation + createFeedbackMessage(input: CreateFeedbackMessageInput!): CreateFeedbackMessageMutation + createSurvey(input: CreateSurveyInput!): CreateSurveyMutation + exportSurveySample(surveyId: ID!): ExportSurveySampleMutationMutation + createReport(reportData: CreateReportInput!): CreateReport + restartCreateReport(reportData: RestartCreateReportInput!): RestartCreateReport + createDashboardReport(reportData: CreateDashboardReportInput!): CreateDashboardReport + createGrievanceTicket(input: CreateGrievanceTicketInput!): CreateGrievanceTicketMutation + updateGrievanceTicket(input: UpdateGrievanceTicketInput!, version: BigInt): UpdateGrievanceTicketMutation + grievanceStatusChange(grievanceTicketId: ID, status: Int, version: BigInt): GrievanceStatusChangeMutation + createTicketNote(noteInput: CreateTicketNoteInput!, version: BigInt): CreateTicketNoteMutation + approveIndividualDataChange(approvedDeliveryMechanismDataToCreate: [Int], approvedDeliveryMechanismDataToEdit: [Int], approvedDeliveryMechanismDataToRemove: [Int], approvedDocumentsToCreate: [Int], approvedDocumentsToEdit: [Int], approvedDocumentsToRemove: [Int], approvedIdentitiesToCreate: [Int], approvedIdentitiesToEdit: [Int], approvedIdentitiesToRemove: [Int], approvedPaymentChannelsToCreate: [Int], approvedPaymentChannelsToEdit: [Int], approvedPaymentChannelsToRemove: [Int], flexFieldsApproveData: JSONString, grievanceTicketId: ID!, individualApproveData: JSONString, version: BigInt): IndividualDataChangeApproveMutation + approveHouseholdDataChange(flexFieldsApproveData: JSONString, grievanceTicketId: ID!, householdApproveData: JSONString, version: BigInt): HouseholdDataChangeApproveMutation + approveAddIndividual(approveStatus: Boolean!, grievanceTicketId: ID!, version: BigInt): SimpleApproveMutation + approveDeleteIndividual(approveStatus: Boolean!, grievanceTicketId: ID!, version: BigInt): SimpleApproveMutation + approveDeleteHousehold(approveStatus: Boolean!, grievanceTicketId: ID!, reasonHhId: String, version: BigInt): DeleteHouseholdApproveMutation + approveSystemFlagging(approveStatus: Boolean!, grievanceTicketId: ID!, version: BigInt): SimpleApproveMutation + approveNeedsAdjudication(clearIndividualIds: [ID], distinctIndividualIds: [ID], duplicateIndividualIds: [ID], grievanceTicketId: ID!, selectedIndividualId: ID, version: BigInt): NeedsAdjudicationApproveMutation + approvePaymentDetails(approveStatus: Boolean!, grievanceTicketId: ID!, version: BigInt): PaymentDetailsApproveMutation + reassignRole(grievanceTicketId: ID!, householdId: ID!, householdVersion: BigInt, individualId: ID!, individualVersion: BigInt, newIndividualId: ID, role: String!, version: BigInt): ReassignRoleMutation + bulkUpdateGrievanceAssignee(assignedTo: String!, businessAreaSlug: String!, grievanceTicketIds: [ID]!): BulkUpdateGrievanceTicketsAssigneesMutation + bulkUpdateGrievancePriority(businessAreaSlug: String!, grievanceTicketIds: [ID]!, priority: Int!): BulkUpdateGrievanceTicketsPriorityMutation + bulkUpdateGrievanceUrgency(businessAreaSlug: String!, grievanceTicketIds: [ID]!, urgency: Int!): BulkUpdateGrievanceTicketsUrgencyMutation + bulkGrievanceAddNote(businessAreaSlug: String!, grievanceTicketIds: [ID]!, note: String!): BulkGrievanceAddNoteMutation + createPaymentVerificationPlan(input: CreatePaymentVerificationInput!, version: BigInt): CreateVerificationPlanMutation + editPaymentVerificationPlan(input: EditPaymentVerificationInput!, version: BigInt): EditPaymentVerificationMutation + exportXlsxPaymentVerificationPlanFile(paymentVerificationPlanId: ID!): ExportXlsxPaymentVerificationPlanFile + importXlsxPaymentVerificationPlanFile(file: Upload!, paymentVerificationPlanId: ID!): ImportXlsxPaymentVerificationPlanFile + activatePaymentVerificationPlan(paymentVerificationPlanId: ID!, version: BigInt): ActivatePaymentVerificationPlan + finishPaymentVerificationPlan(paymentVerificationPlanId: ID!, version: BigInt): FinishPaymentVerificationPlan + discardPaymentVerificationPlan(paymentVerificationPlanId: ID!, version: BigInt): DiscardPaymentVerificationPlan + invalidPaymentVerificationPlan(paymentVerificationPlanId: ID!, version: BigInt): InvalidPaymentVerificationPlan + deletePaymentVerificationPlan(paymentVerificationPlanId: ID!, version: BigInt): DeletePaymentVerificationPlan + updatePaymentVerificationStatusAndReceivedAmount(paymentVerificationId: ID!, receivedAmount: Decimal!, status: PaymentVerificationStatusForUpdate, version: BigInt): UpdatePaymentVerificationStatusAndReceivedAmount + markPaymentRecordAsFailed(paymentRecordId: ID!): MarkPaymentRecordAsFailedMutation + revertMarkPaymentRecordAsFailed(deliveredQuantity: Decimal!, deliveryDate: Date!, paymentRecordId: ID!): RevertMarkPaymentRecordAsFailedMutation + markPaymentAsFailed(paymentId: ID!): MarkPaymentAsFailedMutation + revertMarkPaymentAsFailed(deliveredQuantity: Decimal!, deliveryDate: Date!, paymentId: ID!): RevertMarkPaymentAsFailedMutation + updatePaymentVerificationReceivedAndReceivedAmount(paymentVerificationId: ID!, received: Boolean!, receivedAmount: Decimal!, version: BigInt): UpdatePaymentVerificationReceivedAndReceivedAmount + actionPaymentPlanMutation(input: ActionPaymentPlanInput!): ActionPaymentPlanMutation + createPaymentPlan(input: CreatePaymentPlanInput!): CreatePaymentPlanMutation + createFollowUpPaymentPlan(dispersionEndDate: Date!, dispersionStartDate: Date!, paymentPlanId: ID!): CreateFollowUpPaymentPlanMutation + updatePaymentPlan(input: UpdatePaymentPlanInput!): UpdatePaymentPlanMutation + deletePaymentPlan(paymentPlanId: ID!): DeletePaymentPlanMutation + chooseDeliveryMechanismsForPaymentPlan(input: ChooseDeliveryMechanismsForPaymentPlanInput!): ChooseDeliveryMechanismsForPaymentPlanMutation + assignFspToDeliveryMechanism(input: AssignFspToDeliveryMechanismInput!): AssignFspToDeliveryMechanismMutation + splitPaymentPlan(paymentPlanId: ID!, paymentsNo: Int, splitType: String!): SplitPaymentPlanMutation + exportXlsxPaymentPlanPaymentList(paymentPlanId: ID!): ExportXLSXPaymentPlanPaymentListMutation + exportXlsxPaymentPlanPaymentListPerFsp(paymentPlanId: ID!): ExportXLSXPaymentPlanPaymentListPerFSPMutation + importXlsxPaymentPlanPaymentList(file: Upload!, paymentPlanId: ID!): ImportXLSXPaymentPlanPaymentListMutation + importXlsxPaymentPlanPaymentListPerFsp(file: Upload!, paymentPlanId: ID!): ImportXLSXPaymentPlanPaymentListPerFSPMutation + setSteficonRuleOnPaymentPlanPaymentList(paymentPlanId: ID!, steficonRuleId: ID!): SetSteficonRuleOnPaymentPlanPaymentListMutation + excludeHouseholds(excludedHouseholdsIds: [String]!, exclusionReason: String, paymentPlanId: ID!): ExcludeHouseholdsMutation + exportPdfPaymentPlanSummary(paymentPlanId: ID!): ExportPDFPaymentPlanSummaryMutation + createTargetPopulation(input: CreateTargetPopulationInput!): CreateTargetPopulationMutation + updateTargetPopulation(input: UpdateTargetPopulationInput!, version: BigInt): UpdateTargetPopulationMutation + copyTargetPopulation(input: CopyTargetPopulationMutationInput!): CopyTargetPopulationMutationPayload + deleteTargetPopulation(input: DeleteTargetPopulationMutationInput!): DeleteTargetPopulationMutationPayload + lockTargetPopulation(id: ID!, version: BigInt): LockTargetPopulationMutation + unlockTargetPopulation(id: ID!, version: BigInt): UnlockTargetPopulationMutation + finalizeTargetPopulation(id: ID!, version: BigInt): FinalizeTargetPopulationMutation + setSteficonRuleOnTargetPopulation(input: SetSteficonRuleOnTargetPopulationMutationInput!): SetSteficonRuleOnTargetPopulationMutationPayload + targetPopulationRebuild(id: ID!): RebuildTargetPopulationMutation + createProgram(programData: CreateProgramInput!): CreateProgram + updateProgram(programData: UpdateProgramInput, version: BigInt): UpdateProgram + deleteProgram(programId: String!): DeleteProgram + copyProgram(programData: CopyProgramInput!): CopyProgram + uploadImportDataXlsxFileAsync(businessAreaSlug: String!, file: Upload!): UploadImportDataXLSXFileAsync + deleteRegistrationDataImport(registrationDataImportId: String!): DeleteRegistrationDataImport + registrationXlsxImport(registrationDataImportData: RegistrationXlsxImportMutationInput!): RegistrationXlsxImportMutation + registrationProgramPopulationImport(registrationDataImportData: RegistrationProgramPopulationImportMutationInput!): RegistrationProgramPopulationImportMutation + registrationKoboImport(registrationDataImportData: RegistrationKoboImportMutationInput!): RegistrationKoboImportMutation + saveKoboImportDataAsync(businessAreaSlug: String!, onlyActiveSubmissions: Boolean!, pullPictures: Boolean!, uid: Upload!): SaveKoboProjectImportDataAsync + mergeRegistrationDataImport(id: ID!, version: BigInt): MergeRegistrationDataImportMutation + refuseRegistrationDataImport(id: ID!, refuseReason: String, version: BigInt): RefuseRegistrationDataImportMutation + rerunDedupe(registrationDataImportId: ID!, version: BigInt): RegistrationDeduplicationMutation + eraseRegistrationDataImport(id: ID!, version: BigInt): EraseRegistrationDataImportMutation + checkAgainstSanctionList(file: Upload!): CheckAgainstSanctionListMutation +} + +type NeedsAdjudicationApproveMutation { + grievanceTicket: GrievanceTicketNode +} + +input NegativeFeedbackTicketExtras { + household: ID + individual: ID +} + +interface Node { + id: ID! +} + +input PDUFieldInput { + id: String + label: String + pduData: PeriodicFieldDataInput +} + +type PDUSubtypeChoiceObject { + value: String + displayName: String +} + +type PageInfo { + hasNextPage: Boolean! + hasPreviousPage: Boolean! + startCursor: String + endCursor: String +} + +type PageInfoNode { + startCursor: String + endCursor: String + hasNextPage: Boolean + hasPreviousPage: Boolean +} + +type PaginatedCashPlanAndPaymentPlanNode { + pageInfo: PageInfoNode + edges: [CashPlanAndPaymentPlanEdges] + totalCount: Int +} + +type PaginatedPaymentRecordsAndPaymentsNode { + pageInfo: PageInfoNode + edges: [PaymentRecordsAndPaymentsEdges] + totalCount: Int +} + +type PartnerNode { + id: ID! + allowedBusinessAreas(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID): UserBusinessAreaNodeConnection! + name: String + parent: PartnerNode + isUn: Boolean! + permissions: JSONString! + lft: Int! + rght: Int! + treeId: Int! + level: Int! + businessAreaPartnerThrough: [PartnerRoleNode!]! + businessAreas(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID): UserBusinessAreaNodeConnection! + partnerSet: [PartnerNode!]! + userSet(offset: Int, before: String, after: String, first: Int, last: Int): UserNodeConnection! + individualIdentities(offset: Int, before: String, after: String, first: Int, last: Int): IndividualIdentityNodeConnection! + grievanceticketSet(offset: Int, before: String, after: String, first: Int, last: Int): GrievanceTicketNodeConnection! + programs(offset: Int, before: String, after: String, first: Int, last: Int, name: String): ProgramNodeConnection! + areas: [AreaNode] + areaAccess: String +} + +type PartnerRoleNode { + createdAt: DateTime! + updatedAt: DateTime! + businessArea: UserBusinessAreaNode! + partner: PartnerNode! + roles: [RoleNode!]! +} + +type PartnerType { + id: ID! + allowedBusinessAreas(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID): UserBusinessAreaNodeConnection! + name: String! + parent: PartnerNode + isUn: Boolean! + permissions: JSONString! + lft: Int! + rght: Int! + treeId: Int! + level: Int! + businessAreaPartnerThrough: [PartnerRoleNode!]! + businessAreas(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID): UserBusinessAreaNodeConnection! + partnerSet: [PartnerNode!]! + userSet(offset: Int, before: String, after: String, first: Int, last: Int): UserNodeConnection! + individualIdentities(offset: Int, before: String, after: String, first: Int, last: Int): IndividualIdentityNodeConnection! + grievanceticketSet(offset: Int, before: String, after: String, first: Int, last: Int): GrievanceTicketNodeConnection! + programs(offset: Int, before: String, after: String, first: Int, last: Int, name: String): ProgramNodeConnection! +} + +type PaymentConflictDataNode { + paymentPlanId: String + paymentPlanUnicefId: String + paymentPlanStartDate: String + paymentPlanEndDate: String + paymentPlanStatus: String + paymentId: String + paymentUnicefId: String +} + +enum PaymentDeliveryTypeChoice { + CARDLESS_CASH_WITHDRAWAL + CASH + CASH_BY_FSP + CHEQUE + DEPOSIT_TO_CARD + MOBILE_MONEY + PRE_PAID_CARD + REFERRAL + TRANSFER + TRANSFER_TO_ACCOUNT + VOUCHER + ATM_CARD + CASH_OVER_THE_COUNTER + TRANSFER_TO_DIGITAL_WALLET +} + +type PaymentDetailsApproveMutation { + grievanceTicket: GrievanceTicketNode +} + +type PaymentHouseholdSnapshotNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + snapshotData: JSONString! + householdId: UUID! + payment: PaymentNode! +} + +type PaymentNode implements Node { + isRemoved: Boolean! + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + unicefId: String + signatureHash: String! + businessArea: UserBusinessAreaNode! + status: PaymentStatus! + statusDate: DateTime! + household: HouseholdNode! + headOfHousehold: IndividualNode + deliveryTypeChoice: PaymentDeliveryTypeChoice + deliveryType: DeliveryMechanismNode + currency: String! + entitlementQuantity: Float + entitlementQuantityUsd: Float + deliveredQuantity: Float + deliveredQuantityUsd: Float + deliveryDate: DateTime + transactionReferenceId: String + transactionStatusBlockchainLink: String + parent: PaymentPlanNode! + conflicted: Boolean! + excluded: Boolean! + entitlementDate: DateTime + financialServiceProvider: FinancialServiceProviderNode + collector: IndividualNode! + sourcePayment: PaymentNode + isFollowUp: Boolean! + reasonForUnsuccessfulPayment: String + program: ProgramNode + orderNumber: Int + tokenNumber: Int + additionalCollectorName: String + additionalDocumentType: String + additionalDocumentNumber: String + fspAuthCode: String + followUps(offset: Int, before: String, after: String, first: Int, last: Int): PaymentNodeConnection! + householdSnapshot: PaymentHouseholdSnapshotNode + adminUrl: String + paymentPlanHardConflicted: Boolean + paymentPlanHardConflictedData: [PaymentConflictDataNode] + paymentPlanSoftConflicted: Boolean + paymentPlanSoftConflictedData: [PaymentConflictDataNode] + fullName: String + targetPopulation: TargetPopulationNode + verification: PaymentVerificationNode + distributionModality: String + serviceProvider: FinancialServiceProviderNode + debitCardNumber: String + debitCardIssuer: String + totalPersonsCovered: Int + snapshotCollectorFullName: String + snapshotCollectorDeliveryPhoneNo: String + snapshotCollectorBankName: String + snapshotCollectorBankAccountNumber: String + snapshotCollectorDebitCardNumber: String +} + +type PaymentNodeConnection { + pageInfo: PageInfo! + edges: [PaymentNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type PaymentNodeEdge { + node: PaymentNode + cursor: String! +} + +enum PaymentPlanBackgroundActionStatus { + RULE_ENGINE_RUN + RULE_ENGINE_ERROR + XLSX_EXPORTING + XLSX_EXPORT_ERROR + XLSX_IMPORT_ERROR + XLSX_IMPORTING_ENTITLEMENTS + XLSX_IMPORTING_RECONCILIATION + EXCLUDE_BENEFICIARIES + EXCLUDE_BENEFICIARIES_ERROR + SEND_TO_PAYMENT_GATEWAY + SEND_TO_PAYMENT_GATEWAY_ERROR +} + +enum PaymentPlanCurrency { + A_ + AED + AFN + ALL + AMD + ANG + AOA + ARS + AUD + AWG + AZN + BAM + BBD + BDT + BGN + BHD + BIF + BMD + BND + BOB + BOV + BRL + BSD + BTN + BWP + BYN + BZD + CAD + CDF + CHF + CLP + CNY + COP + CRC + CUC + CUP + CVE + CZK + DJF + DKK + DOP + DZD + EGP + ERN + ETB + EUR + FJD + FKP + GBP + GEL + GHS + GIP + GMD + GNF + GTQ + GYD + HKD + HNL + HRK + HTG + HUF + IDR + ILS + INR + IQD + IRR + ISK + JMD + JOD + JPY + KES + KGS + KHR + KMF + KPW + KRW + KWD + KYD + KZT + LAK + LBP + LKR + LRD + LSL + LYD + MAD + MDL + MGA + MKD + MMK + MNT + MOP + MRU + MUR + MVR + MWK + MXN + MYR + MZN + NAD + NGN + NIO + NOK + NPR + NZD + OMR + PAB + PEN + PGK + PHP + PKR + PLN + PYG + QAR + RON + RSD + RUB + RWF + SAR + SBD + SCR + SDG + SEK + SGD + SHP + SLL + SOS + SRD + SSP + STN + SVC + SYP + SZL + THB + TJS + TMT + TND + TOP + TRY + TTD + TWD + TZS + UAH + UGX + USD + UYU + UYW + UZS + VES + VND + VUV + WST + XAF + XAG + XAU + XCD + XOF + XPF + YER + ZAR + ZMW + ZWL + USDC +} + +type PaymentPlanNode implements Node { + isRemoved: Boolean! + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + version: BigInt! + unicefId: String + businessArea: UserBusinessAreaNode! + statusDate: DateTime! + startDate: Date + endDate: Date + program: ProgramNode! + exchangeRate: Float + totalEntitledQuantity: Float + totalEntitledQuantityUsd: Float + totalEntitledQuantityRevised: Float + totalEntitledQuantityRevisedUsd: Float + totalDeliveredQuantity: Float + totalDeliveredQuantityUsd: Float + totalUndeliveredQuantity: Float + totalUndeliveredQuantityUsd: Float + programCycle: ProgramCycleNode + createdBy: UserNode! + status: PaymentPlanStatus! + backgroundActionStatus: PaymentPlanBackgroundActionStatus + targetPopulation: TargetPopulationNode! + currency: PaymentPlanCurrency! + dispersionStartDate: Date + dispersionEndDate: Date + femaleChildrenCount: Int! + maleChildrenCount: Int! + femaleAdultsCount: Int! + maleAdultsCount: Int! + totalHouseholdsCount: Int! + totalIndividualsCount: Int! + importedFileDate: DateTime + steficonRule: RuleCommitNode + steficonAppliedDate: DateTime + sourcePaymentPlan: PaymentPlanNode + isFollowUp: Boolean! + exclusionReason: String! + excludeHouseholdError: String! + name: String + followUps(offset: Int, before: String, after: String, first: Int, last: Int): PaymentPlanNodeConnection! + deliveryMechanisms: [DeliveryMechanismPerPaymentPlanNode] + paymentItems(offset: Int, before: String, after: String, first: Int, last: Int): PaymentNodeConnection! + approvalProcess(offset: Int, before: String, after: String, first: Int, last: Int): ApprovalProcessNodeConnection! + adminUrl: String + currencyName: String + hasPaymentListExportFile: Boolean + hasFspDeliveryMechanismXlsxTemplate: Boolean + importedFileName: String + paymentsConflictsCount: Int + volumeByDeliveryMechanism: [VolumeByDeliveryMechanismNode] + splitChoices: [ChoiceObject] + verificationPlans(offset: Int, before: String, after: String, first: Int, last: Int, programId: String): PaymentVerificationPlanNodeConnection + paymentVerificationSummary: PaymentVerificationSummaryNode + bankReconciliationSuccess: Int + bankReconciliationError: Int + canCreatePaymentVerificationPlan: Boolean + availablePaymentRecordsCount: Int + reconciliationSummary: ReconciliationSummaryNode + excludedHouseholds: [HouseholdNode] + canCreateFollowUp: Boolean + totalWithdrawnHouseholdsCount: Int + unsuccessfulPaymentsCount: Int + canSendToPaymentGateway: Boolean + canSplit: Boolean +} + +type PaymentPlanNodeConnection { + pageInfo: PageInfo! + edges: [PaymentPlanNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type PaymentPlanNodeEdge { + node: PaymentPlanNode + cursor: String! +} + +enum PaymentPlanStatus { + PREPARING + OPEN + LOCKED + LOCKED_FSP + IN_APPROVAL + IN_AUTHORIZATION + IN_REVIEW + ACCEPTED + FINISHED +} + +type PaymentRecordAndPaymentNode { + objType: String + id: String + caId: String + status: String + fullName: String + parent: CashPlanAndPaymentPlanNode + entitlementQuantity: Float + deliveredQuantity: Float + deliveredQuantityUsd: Float + currency: String + deliveryDate: String + verification: PaymentVerificationNode +} + +enum PaymentRecordDeliveryTypeChoice { + CARDLESS_CASH_WITHDRAWAL + CASH + CASH_BY_FSP + CHEQUE + DEPOSIT_TO_CARD + MOBILE_MONEY + PRE_PAID_CARD + REFERRAL + TRANSFER + TRANSFER_TO_ACCOUNT + VOUCHER + ATM_CARD + CASH_OVER_THE_COUNTER + TRANSFER_TO_DIGITAL_WALLET +} + +enum PaymentRecordEntitlementCardStatus { + ACTIVE + INACTIVE +} + +type PaymentRecordNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + version: BigInt! + businessArea: UserBusinessAreaNode! + status: PaymentRecordStatus! + statusDate: DateTime! + household: HouseholdNode! + headOfHousehold: IndividualNode + deliveryTypeChoice: PaymentRecordDeliveryTypeChoice + deliveryType: DeliveryMechanismNode + currency: String! + entitlementQuantity: Float + entitlementQuantityUsd: Float + deliveredQuantity: Float + deliveredQuantityUsd: Float + deliveryDate: DateTime + transactionReferenceId: String + transactionStatusBlockchainLink: String + caId: String + caHashId: UUID + parent: CashPlanNode + fullName: String! + totalPersonsCovered: Int! + distributionModality: String! + targetPopulation: TargetPopulationNode! + targetPopulationCashAssistId: String! + entitlementCardNumber: String + entitlementCardStatus: PaymentRecordEntitlementCardStatus + entitlementCardIssueDate: Date + visionId: String + registrationCaId: String + serviceProvider: ServiceProviderNode! + adminUrl: String + verification: PaymentVerificationNode + unicefId: String +} + +type PaymentRecordNodeConnection { + pageInfo: PageInfo! + edges: [PaymentRecordNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type PaymentRecordNodeEdge { + node: PaymentRecordNode + cursor: String! +} + +enum PaymentRecordStatus { + DISTRIBUTION_SUCCESSFUL + NOT_DISTRIBUTED + TRANSACTION_SUCCESSFUL + TRANSACTION_ERRONEOUS + FORCE_FAILED + PARTIALLY_DISTRIBUTED + PENDING + SENT_TO_PAYMENT_GATEWAY + SENT_TO_FSP + MANUALLY_CANCELLED +} + +type PaymentRecordsAndPaymentsEdges { + cursor: String + node: PaymentRecordAndPaymentNode +} + +enum PaymentStatus { + DISTRIBUTION_SUCCESSFUL + NOT_DISTRIBUTED + TRANSACTION_SUCCESSFUL + TRANSACTION_ERRONEOUS + FORCE_FAILED + PARTIALLY_DISTRIBUTED + PENDING + SENT_TO_PAYMENT_GATEWAY + SENT_TO_FSP + MANUALLY_CANCELLED +} + +type PaymentVerificationLogEntryNode implements Node { + id: ID! + contentType: ContentTypeObjectType + objectId: UUID + action: LogEntryAction! + objectRepr: String! + changes: Arg + user: UserNode + businessArea: UserBusinessAreaNode + programs(offset: Int, before: String, after: String, first: Int, last: Int, name: String): ProgramNodeConnection! + timestamp: DateTime + isUserGenerated: Boolean + contentObject: PaymentVerificationPlanNode +} + +type PaymentVerificationLogEntryNodeConnection { + pageInfo: PageInfo! + edges: [PaymentVerificationLogEntryNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type PaymentVerificationLogEntryNodeEdge { + node: PaymentVerificationLogEntryNode + cursor: String! +} + +type PaymentVerificationNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + version: BigInt! + paymentVerificationPlan: PaymentVerificationPlanNode! + paymentContentType: ContentTypeObjectType! + paymentObjectId: UUID! + status: PaymentVerificationStatus! + statusDate: DateTime + receivedAmount: Float + sentToRapidPro: Boolean! + ticketDetails(offset: Int, before: String, after: String, first: Int, last: Int): TicketPaymentVerificationDetailsNodeConnection! + ticketDetail(offset: Int, before: String, after: String, first: Int, last: Int): TicketPaymentVerificationDetailsNodeConnection! + adminUrl: String + isManuallyEditable: Boolean + payment: GenericPaymentNode +} + +type PaymentVerificationNodeConnection { + pageInfo: PageInfo! + edges: [PaymentVerificationNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type PaymentVerificationNodeEdge { + node: PaymentVerificationNode + cursor: String! +} + +type PaymentVerificationPlanNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + version: BigInt! + unicefId: String + status: PaymentVerificationPlanStatus! + paymentPlanContentType: ContentTypeObjectType! + paymentPlanObjectId: UUID! + sampling: PaymentVerificationPlanSampling! + verificationChannel: PaymentVerificationPlanVerificationChannel! + sampleSize: Int + respondedCount: Int + receivedCount: Int + notReceivedCount: Int + receivedWithProblemsCount: Int + confidenceInterval: Float + marginOfError: Float + rapidProFlowId: String! + rapidProFlowStartUuids: [String!]! + ageFilter: AgeFilterObject + excludedAdminAreasFilter: [String] + sexFilter: String + activationDate: DateTime + completionDate: DateTime + xlsxFileExporting: Boolean! + xlsxFileImported: Boolean! + error: String + paymentRecordVerifications(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationNodeConnection! + adminUrl: String + xlsxFileWasDownloaded: Boolean + hasXlsxFile: Boolean + paymentPlan: PaymentPlanNode +} + +type PaymentVerificationPlanNodeConnection { + pageInfo: PageInfo! + edges: [PaymentVerificationPlanNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type PaymentVerificationPlanNodeEdge { + node: PaymentVerificationPlanNode + cursor: String! +} + +enum PaymentVerificationPlanSampling { + FULL_LIST + RANDOM +} + +enum PaymentVerificationPlanStatus { + ACTIVE + FINISHED + PENDING + INVALID + RAPID_PRO_ERROR +} + +enum PaymentVerificationPlanVerificationChannel { + MANUAL + RAPIDPRO + XLSX +} + +enum PaymentVerificationStatus { + NOT_RECEIVED + PENDING + RECEIVED + RECEIVED_WITH_ISSUES +} + +enum PaymentVerificationStatusForUpdate { + NOT_RECEIVED + PENDING + RECEIVED + RECEIVED_WITH_ISSUES +} + +type PaymentVerificationSummaryNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + status: PaymentVerificationSummaryStatus! + activationDate: DateTime + completionDate: DateTime + paymentPlanContentType: ContentTypeObjectType! + paymentPlanObjectId: UUID! +} + +type PaymentVerificationSummaryNodeConnection { + pageInfo: PageInfo! + edges: [PaymentVerificationSummaryNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type PaymentVerificationSummaryNodeEdge { + node: PaymentVerificationSummaryNode + cursor: String! +} + +enum PaymentVerificationSummaryStatus { + ACTIVE + FINISHED + PENDING +} + +input PeriodicFieldDataInput { + subtype: String + numberOfRounds: Int + roundsNames: [String] +} + +type PeriodicFieldDataNode { + id: ID! + subtype: PeriodicFieldDataSubtype! + numberOfRounds: Int! + roundsNames: [String!]! +} + +enum PeriodicFieldDataSubtype { + DATE + DECIMAL + STRING + BOOL +} + +type PeriodicFieldNode implements Node { + name: String! + pduData: PeriodicFieldDataNode + label: JSONString! + id: ID! +} + +input PositiveFeedbackTicketExtras { + household: ID + individual: ID +} + +type ProgramCycleNode implements Node { + isRemoved: Boolean! + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + version: BigInt! + title: String + status: ProgramCycleStatus! + startDate: Date! + endDate: Date + program: ProgramNode! + createdBy: UserNode + paymentPlans(offset: Int, before: String, after: String, first: Int, last: Int): PaymentPlanNodeConnection! + targetPopulations(offset: Int, before: String, after: String, first: Int, last: Int, program: [ID], createdAt: DateTime, createdAt_Lte: DateTime, createdAt_Gte: DateTime, updatedAt: DateTime, updatedAt_Lte: DateTime, updatedAt_Gte: DateTime, status: String, households: [ID], name: String, createdByName: String, totalHouseholdsCountMin: Int, totalHouseholdsCountMax: Int, totalIndividualsCountMin: Int, totalIndividualsCountMax: Int, businessArea: String, createdAtRange: String, paymentPlanApplicable: Boolean, statusNot: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, programCycle: String, orderBy: String): TargetPopulationNodeConnection! + totalDeliveredQuantityUsd: Float + totalEntitledQuantityUsd: Float + totalUndeliveredQuantityUsd: Float +} + +type ProgramCycleNodeConnection { + pageInfo: PageInfo! + edges: [ProgramCycleNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type ProgramCycleNodeEdge { + node: ProgramCycleNode + cursor: String! +} + +enum ProgramCycleStatus { + DRAFT + ACTIVE + FINISHED +} + +enum ProgramFrequencyOfPayments { + ONE_OFF + REGULAR +} + +type ProgramNode implements Node { + isRemoved: Boolean! + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + lastSyncAt: DateTime + version: BigInt! + name: String! + status: ProgramStatus! + startDate: Date! + endDate: Date! + description: String! + caId: String + caHashId: String + adminAreas(offset: Int, before: String, after: String, first: Int, last: Int, name: String): AreaNodeConnection! + businessArea: UserBusinessAreaNode! + budget: Decimal + frequencyOfPayments: ProgramFrequencyOfPayments! + sector: ProgramSector! + scope: ProgramScope + cashPlus: Boolean! + populationGoal: Int! + administrativeAreasOfImplementation: String! + dataCollectingType: DataCollectingTypeNode + isVisible: Boolean! + householdCount: Int! + individualCount: Int! + programmeCode: String + partnerAccess: ProgramPartnerAccess! + partners: [PartnerNode] + pduFields: [PeriodicFieldNode] + households(offset: Int, before: String, after: String, first: Int, last: Int): HouseholdNodeConnection! + householdSet(offset: Int, before: String, after: String, first: Int, last: Int): HouseholdNodeConnection! + individuals(offset: Int, before: String, after: String, first: Int, last: Int): IndividualNodeConnection! + registrationImports(offset: Int, before: String, after: String, first: Int, last: Int): RegistrationDataImportNodeConnection! + paymentplanSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentPlanNodeConnection! + cashplanSet(offset: Int, before: String, after: String, first: Int, last: Int): CashPlanNodeConnection! + paymentSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentNodeConnection! + grievanceTickets(offset: Int, before: String, after: String, first: Int, last: Int): GrievanceTicketNodeConnection! + targetpopulationSet(offset: Int, before: String, after: String, first: Int, last: Int, program: [ID], createdAt: DateTime, createdAt_Lte: DateTime, createdAt_Gte: DateTime, updatedAt: DateTime, updatedAt_Lte: DateTime, updatedAt_Gte: DateTime, status: String, households: [ID], name: String, createdByName: String, totalHouseholdsCountMin: Int, totalHouseholdsCountMax: Int, totalIndividualsCountMin: Int, totalIndividualsCountMax: Int, businessArea: String, createdAtRange: String, paymentPlanApplicable: Boolean, statusNot: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, programCycle: String, orderBy: String): TargetPopulationNodeConnection! + cycles(offset: Int, before: String, after: String, first: Int, last: Int, search: String, status: [String], startDate: Date, endDate: Date, totalDeliveredQuantityUsdFrom: Float, totalDeliveredQuantityUsdTo: Float, orderBy: String): ProgramCycleNodeConnection + reports(offset: Int, before: String, after: String, first: Int, last: Int): ReportNodeConnection! + activityLogs(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationLogEntryNodeConnection! + messages(offset: Int, before: String, after: String, first: Int, last: Int): CommunicationMessageNodeConnection! + feedbackSet(offset: Int, before: String, after: String, first: Int, last: Int): FeedbackNodeConnection! + surveys(offset: Int, before: String, after: String, first: Int, last: Int): SurveyNodeConnection! + adminUrl: String + totalEntitledQuantity: Decimal + totalDeliveredQuantity: Decimal + totalUndeliveredQuantity: Decimal + totalNumberOfHouseholds: Int + totalNumberOfHouseholdsWithTpInProgram: Int + isSocialWorkerProgram: Boolean + targetPopulationsCount: Int +} + +type ProgramNodeConnection { + pageInfo: PageInfo! + edges: [ProgramNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type ProgramNodeEdge { + node: ProgramNode + cursor: String! +} + +enum ProgramPartnerAccess { + ALL_PARTNERS_ACCESS + NONE_PARTNERS_ACCESS + SELECTED_PARTNERS_ACCESS +} + +input ProgramPartnerThroughInput { + partner: String + areas: [String] + areaAccess: String +} + +enum ProgramScope { + FOR_PARTNERS + UNICEF +} + +enum ProgramSector { + CHILD_PROTECTION + EDUCATION + HEALTH + MULTI_PURPOSE + NUTRITION + SOCIAL_POLICY + WASH +} + +enum ProgramStatus { + ACTIVE + DRAFT + FINISHED +} + +type Query { + accountabilityCommunicationMessage(id: ID!): CommunicationMessageNode + allAccountabilityCommunicationMessages(offset: Int, before: String, after: String, first: Int, last: Int, numberOfRecipients: Int, numberOfRecipients_Gte: Int, numberOfRecipients_Lte: Int, targetPopulation: ID, createdBy: ID, program: String, createdAtRange: String, title: String, body: String, samplingType: String, orderBy: String): CommunicationMessageNodeConnection + allAccountabilityCommunicationMessageRecipients(offset: Int, before: String, after: String, first: Int, last: Int, messageId: String!, recipientId: String, fullName: String, phoneNo: String, sex: String, orderBy: String): CommunicationMessageRecipientMapNodeConnection + accountabilityCommunicationMessageSampleSize(input: GetAccountabilityCommunicationMessageSampleSizeInput): GetCommunicationMessageSampleSizeNode + feedback(id: ID!): FeedbackNode + allFeedbacks(offset: Int, before: String, after: String, first: Int, last: Int, businessArea: String, issueType: String, createdAtRange: String, createdBy: String, feedbackId: String, isActiveProgram: String, program: String, orderBy: String): FeedbackNodeConnection + feedbackIssueTypeChoices: [ChoiceObject] + survey(id: ID!): SurveyNode + allSurveys(offset: Int, before: String, after: String, first: Int, last: Int, program: ID, targetPopulation: ID, businessArea: String, createdAtRange: String, search: String, createdBy: String, orderBy: String): SurveyNodeConnection + recipients(offset: Int, before: String, after: String, first: Int, last: Int, survey: String!, orderBy: String): RecipientNodeConnection + accountabilitySampleSize(input: AccountabilitySampleSizeInput): AccountabilitySampleSizeNode + surveyCategoryChoices: [ChoiceObject] + surveyAvailableFlows: [RapidProFlowNode] + adminArea(id: ID!): AreaNode + allAdminAreas(offset: Int, before: String, after: String, first: Int, last: Int, name: String, name_Istartswith: String, businessArea: String, level: Int, parentId: String): AreaNodeConnection + allAreasTree(businessArea: String!): [AreaTreeNode] + allLogEntries(offset: Int, before: String, after: String, first: Int, last: Int, objectId: UUID, user: ID, businessArea: String!, search: String, module: String, userId: String, programId: String): LogEntryNodeConnection + logEntryActionChoices: [ChoiceObject] + report(id: ID!): ReportNode + allReports(offset: Int, before: String, after: String, first: Int, last: Int, createdBy: ID, reportType: [String], status: [String], businessArea: String!, createdFrom: DateTime, createdTo: DateTime, orderBy: String): ReportNodeConnection + reportTypesChoices: [ChoiceObject] + reportStatusChoices: [ChoiceObject] + dashboardReportTypesChoices(businessAreaSlug: String!): [ChoiceObject] + dashboardYearsChoices(businessAreaSlug: String!): [String] + sanctionListIndividual(id: ID!): SanctionListIndividualNode + allSanctionListIndividuals(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID, fullName: String, fullName_Startswith: String, referenceNumber: String, orderBy: String): SanctionListIndividualNodeConnection + ticketsByType(businessAreaSlug: String!): TicketByType + ticketsByCategory(businessAreaSlug: String!): ChartDatasetNode + ticketsByStatus(businessAreaSlug: String!): ChartDatasetNode + ticketsByLocationAndCategory(businessAreaSlug: String!): ChartDetailedDatasetsNode + grievanceTicket(id: ID!): GrievanceTicketNode + allGrievanceTicket(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID, id_Startswith: UUID, category: String, area: String, area_Startswith: String, assignedTo: ID, registrationDataImport: ID, admin2: ID, createdBy: ID, businessArea: String!, search: String, documentType: String, documentNumber: String, status: [String], fsp: String, cashPlan: String, createdAtRange: String, permissions: [String], issueType: String, scoreMin: String, scoreMax: String, household: String, preferredLanguage: String, priority: String, urgency: String, grievanceType: String, grievanceStatus: String, totalDays: Int, program: String, isActiveProgram: Boolean, isCrossArea: Boolean, admin1: ID, orderBy: String): GrievanceTicketNodeConnection + crossAreaFilterAvailable: Boolean + existingGrievanceTickets(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID, businessArea: String!, category: String, issueType: String, household: ID, individual: ID, paymentRecord: [ID], permissions: [String], orderBy: String): GrievanceTicketNodeConnection + allTicketNotes(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID, ticket: UUID!): TicketNoteNodeConnection + chartGrievances(businessAreaSlug: String!, year: Int!, administrativeArea: String): ChartGrievanceTicketsNode + allAddIndividualsFieldsAttributes: [FieldAttributeNode] + allEditHouseholdFieldsAttributes: [FieldAttributeNode] + grievanceTicketStatusChoices: [ChoiceObject] + grievanceTicketCategoryChoices: [ChoiceObject] + grievanceTicketManualCategoryChoices: [ChoiceObject] + grievanceTicketSystemCategoryChoices: [ChoiceObject] + grievanceTicketIssueTypeChoices: [IssueTypesObject] + grievanceTicketPriorityChoices: [ChoiceObjectInt] + grievanceTicketUrgencyChoices: [ChoiceObjectInt] + allSteficonRules(offset: Int, before: String, after: String, first: Int, last: Int, enabled: Boolean, deprecated: Boolean, type: String!): SteficonRuleNodeConnection + payment(id: ID!): PaymentNode + allPayments(offset: Int, before: String, after: String, first: Int, last: Int, businessArea: String!, paymentPlanId: String!, programId: String, orderBy: String): PaymentNodeConnection + paymentRecord(id: ID!): PaymentRecordNode + allPaymentRecords(offset: Int, before: String, after: String, first: Int, last: Int, parent: ID, household: ID, individual: String, businessArea: String, programId: String, orderBy: String): PaymentRecordNodeConnection + allPaymentRecordsAndPayments(businessArea: String!, program: String, household: ID, orderBy: String, first: Int, last: Int, before: String, after: String): PaginatedPaymentRecordsAndPaymentsNode + financialServiceProviderXlsxTemplate(id: ID!): FinancialServiceProviderXlsxTemplateNode + allFinancialServiceProviderXlsxTemplates(offset: Int, before: String, after: String, first: Int, last: Int, name: String, createdBy: ID, orderBy: String): FinancialServiceProviderXlsxTemplateNodeConnection + financialServiceProvider(id: ID!): FinancialServiceProviderNode + allFinancialServiceProviders(offset: Int, before: String, after: String, first: Int, last: Int, createdBy: ID, name: String, visionVendorNumber: String, deliveryMechanisms: [String], distributionLimit: Float, communicationChannel: String, xlsxTemplates: [ID], orderBy: String): FinancialServiceProviderNodeConnection + paymentRecordVerification(id: ID!): PaymentVerificationNode + allPaymentVerifications(offset: Int, before: String, after: String, first: Int, last: Int, paymentVerificationPlan: ID, status: String, paymentPlanId: String, search: String, businessArea: String!, verificationChannel: String, orderBy: String): PaymentVerificationNodeConnection + paymentVerificationPlan(id: ID!): PaymentVerificationPlanNode + allPaymentVerificationPlan(offset: Int, before: String, after: String, first: Int, last: Int, programId: String): PaymentVerificationPlanNodeConnection + chartPaymentVerification(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): ChartPaymentVerification + chartPaymentVerificationForPeople(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): ChartPaymentVerification + chartVolumeByDeliveryMechanism(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): ChartDatasetNode + chartPayment(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): ChartDatasetNode + sectionTotalTransferred(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): SectionTotalNode + tableTotalCashTransferredByAdministrativeArea(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String, order: String, orderBy: String): TableTotalCashTransferred + tableTotalCashTransferredByAdministrativeAreaForPeople(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String, order: String, orderBy: String): TableTotalCashTransferredForPeople + chartTotalTransferredCashByCountry(year: Int!): ChartDetailedDatasetsNode + paymentRecordStatusChoices: [ChoiceObject] + paymentRecordEntitlementCardStatusChoices: [ChoiceObject] + paymentRecordDeliveryTypeChoices: [ChoiceObject] + cashPlanVerificationStatusChoices: [ChoiceObject] + cashPlanVerificationSamplingChoices: [ChoiceObject] + cashPlanVerificationVerificationChannelChoices: [ChoiceObject] + paymentVerificationStatusChoices: [ChoiceObject] + allRapidProFlows(businessAreaSlug: String!): [RapidProFlow] + sampleSize(input: GetCashplanVerificationSampleSizeInput): GetCashplanVerificationSampleSizeObject + allPaymentVerificationLogEntries(offset: Int, before: String, after: String, first: Int, last: Int, objectId: UUID, user: ID, businessArea: String!, search: String, module: String, userId: String, programId: String, objectType: String): PaymentVerificationLogEntryNodeConnection + paymentPlan(id: ID!): PaymentPlanNode + allPaymentPlans(offset: Int, before: String, after: String, first: Int, last: Int, businessArea: String!, search: String, status: [String], totalEntitledQuantityFrom: Float, totalEntitledQuantityTo: Float, dispersionStartDate: Date, dispersionEndDate: Date, isFollowUp: Boolean, sourcePaymentPlanId: String, program: String, programCycle: String, orderBy: String): PaymentPlanNodeConnection + paymentPlanStatusChoices: [ChoiceObject] + currencyChoices: [ChoiceObject] + allDeliveryMechanisms: [ChoiceObject] + paymentPlanBackgroundActionStatusChoices: [ChoiceObject] + availableFspsForDeliveryMechanisms(input: AvailableFspsForDeliveryMechanismsInput): [FspChoices] + allCashPlansAndPaymentPlans(businessArea: String!, program: String, search: String, serviceProvider: String, deliveryType: [String], verificationStatus: [String], startDateGte: String, endDateLte: String, orderBy: String, first: Int, last: Int, before: String, after: String, isPaymentVerificationPage: Boolean): PaginatedCashPlanAndPaymentPlanNode + businessArea(businessAreaSlug: String!): BusinessAreaNode + allBusinessAreas(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID, slug: String): BusinessAreaNodeConnection + allFieldsAttributes(flexField: Boolean, businessAreaSlug: String, programId: String): [FieldAttributeNode] + allPduFields(businessAreaSlug: String!, programId: String!): [FieldAttributeNode] + allGroupsWithFields: [GroupAttributeNode] + koboProject(uid: String!, businessAreaSlug: String!): KoboAssetObject + allKoboProjects(businessAreaSlug: String!, onlyDeployed: Boolean, before: String, after: String, first: Int, last: Int): KoboAssetObjectConnection + cashAssistUrlPrefix: String + allLanguages(code: String, before: String, after: String, first: Int, last: Int): LanguageObjectConnection + dataCollectingType(id: ID!): DataCollectingTypeNode + dataCollectionTypeChoices: [DataCollectingTypeChoiceObject] + pduSubtypeChoices: [PDUSubtypeChoiceObject] + program(id: ID!): ProgramNode + allPrograms(offset: Int, before: String, after: String, first: Int, last: Int, businessArea: String!, search: String, status: [String], sector: [String], numberOfHouseholds: String, budget: String, startDate: Date, endDate: Date, name: String, numberOfHouseholdsWithTpInProgram: String, dataCollectingType: String, compatibleDct: Boolean, orderBy: String): ProgramNodeConnection + chartProgrammesBySector(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): ChartDetailedDatasetsNode + chartTotalTransferredByMonth(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): ChartDetailedDatasetsNode + cashPlan(id: ID!): CashPlanNode + allCashPlans(offset: Int, before: String, after: String, first: Int, last: Int, program: ID, assistanceThrough: String, assistanceThrough_Startswith: String, serviceProvider_FullName: String, serviceProvider_FullName_Startswith: String, startDate: DateTime, startDate_Lte: DateTime, startDate_Gte: DateTime, endDate: DateTime, endDate_Lte: DateTime, endDate_Gte: DateTime, businessArea: String, search: String, deliveryType: [String], verificationStatus: [String], orderBy: String): CashPlanNodeConnection + programStatusChoices: [ChoiceObject] + programCycleStatusChoices: [ChoiceObject] + programFrequencyOfPaymentsChoices: [ChoiceObject] + programSectorChoices: [ChoiceObject] + programScopeChoices: [ChoiceObject] + cashPlanStatusChoices: [ChoiceObject] + dataCollectingTypeChoices: [ChoiceObject] + allActivePrograms(offset: Int, before: String, after: String, first: Int, last: Int, businessArea: String!, search: String, status: [String], sector: [String], numberOfHouseholds: String, budget: String, startDate: Date, endDate: Date, name: String, numberOfHouseholdsWithTpInProgram: String, dataCollectingType: String, compatibleDct: Boolean, orderBy: String): ProgramNodeConnection + programCycle(id: ID!): ProgramCycleNode + targetPopulation(id: ID!): TargetPopulationNode + allTargetPopulation(offset: Int, before: String, after: String, first: Int, last: Int, program: [ID], createdAt: DateTime, createdAt_Lte: DateTime, createdAt_Gte: DateTime, updatedAt: DateTime, updatedAt_Lte: DateTime, updatedAt_Gte: DateTime, status: String, households: [ID], name: String, createdByName: String, totalHouseholdsCountMin: Int, totalHouseholdsCountMax: Int, totalIndividualsCountMin: Int, totalIndividualsCountMax: Int, businessArea: String, createdAtRange: String, paymentPlanApplicable: Boolean, statusNot: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, programCycle: String, orderBy: String): TargetPopulationNodeConnection + targetPopulationHouseholds(targetPopulation: ID!, offset: Int, before: String, after: String, first: Int, last: Int, orderBy: String, businessArea: String): HouseholdNodeConnection + targetPopulationStatusChoices: [ChoiceObject] + allActiveTargetPopulations(offset: Int, before: String, after: String, first: Int, last: Int, program: [ID], createdAt: DateTime, createdAt_Lte: DateTime, createdAt_Gte: DateTime, updatedAt: DateTime, updatedAt_Lte: DateTime, updatedAt_Gte: DateTime, status: String, households: [ID], name: String, createdByName: String, totalHouseholdsCountMin: Int, totalHouseholdsCountMax: Int, totalIndividualsCountMin: Int, totalIndividualsCountMax: Int, businessArea: String, createdAtRange: String, paymentPlanApplicable: Boolean, statusNot: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, programCycle: String, orderBy: String): TargetPopulationNodeConnection + household(id: ID!): HouseholdNode + allHouseholds(offset: Int, before: String, after: String, first: Int, last: Int, businessArea: String, address: String, address_Startswith: String, headOfHousehold_FullName: String, headOfHousehold_FullName_Startswith: String, size_Range: [Int], size_Lte: Int, size_Gte: Int, adminArea: ID, admin1: ID, admin2: ID, targetPopulations: [ID], residenceStatus: String, withdrawn: Boolean, program: ID, size: String, search: String, documentType: String, documentNumber: String, headOfHousehold_PhoneNoValid: Boolean, lastRegistrationDate: String, countryOrigin: String, isActiveProgram: Boolean, orderBy: String): HouseholdNodeConnection + individual(id: ID!): IndividualNode + allIndividuals(offset: Int, before: String, after: String, first: Int, last: Int, household_Id: UUID, businessArea: String, fullName: String, fullName_Startswith: String, fullName_Endswith: String, sex: [String], household_AdminArea: ID, withdrawn: Boolean, program: ID, age: String, programs: [ID], search: String, documentType: String, documentNumber: String, lastRegistrationDate: String, admin1: [ID], admin2: [ID], status: [String], excludedId: String, flags: [String], isActiveProgram: Boolean, orderBy: String): IndividualNodeConnection + allMergedHouseholds(offset: Int, before: String, after: String, first: Int, last: Int, businessArea: String, rdiId: String, orderBy: String): HouseholdNodeConnection + allMergedIndividuals(offset: Int, before: String, after: String, first: Int, last: Int, household: ID, rdiId: String, duplicatesOnly: Boolean, businessArea: String, orderBy: String): IndividualNodeConnection + sectionHouseholdsReached(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): SectionTotalNode + sectionIndividualsReached(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): SectionTotalNode + sectionPeopleReached(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): SectionTotalNode + sectionChildReached(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): SectionTotalNode + chartIndividualsReachedByAgeAndGender(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): ChartDatasetNode + chartPeopleReachedByAgeAndGender(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): ChartDatasetNode + chartIndividualsWithDisabilityReachedByAge(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): ChartDetailedDatasetsNode + chartPeopleWithDisabilityReachedByAge(businessAreaSlug: String!, year: Int!, program: String, administrativeArea: String): ChartDetailedDatasetsNode + residenceStatusChoices: [ChoiceObject] + sexChoices: [ChoiceObject] + maritalStatusChoices: [ChoiceObject] + workStatusChoices: [ChoiceObject] + relationshipChoices: [ChoiceObject] + roleChoices: [ChoiceObject] + documentTypeChoices: [ChoiceObject] + identityTypeChoices: [ChoiceObject] + countriesChoices: [ChoiceObject] + observedDisabilityChoices: [ChoiceObject] + severityOfDisabilityChoices: [ChoiceObject] + flagChoices: [ChoiceObject] + allHouseholdsFlexFieldsAttributes: [FieldAttributeNode] + allIndividualsFlexFieldsAttributes: [FieldAttributeNode] + me: UserNode + allUsers(offset: Int, before: String, after: String, first: Int, last: Int, status: [String], partner: [String], businessArea: String!, program: String, search: String, roles: [String], isTicketCreator: Boolean, isSurveyCreator: Boolean, isMessageCreator: Boolean, isFeedbackCreator: Boolean, orderBy: String): UserNodeConnection + userRolesChoices: [RoleChoiceObject] + userStatusChoices: [ChoiceObject] + userPartnerChoices: [ChoiceObject] + partnerForGrievanceChoices(householdId: ID, individualId: ID): [ChoiceObject] + hasAvailableUsersToExport(businessAreaSlug: String!): Boolean + importedHousehold(id: ID!): ImportedHouseholdNode + allImportedHouseholds(offset: Int, before: String, after: String, first: Int, last: Int, rdiId: String, businessArea: String, orderBy: String): ImportedHouseholdNodeConnection + registrationDataImportDatahub(id: ID!): RegistrationDataImportDatahubNode + importedIndividual(id: ID!): ImportedIndividualNode + allImportedIndividuals(offset: Int, before: String, after: String, first: Int, last: Int, household: ID, rdiId: String, duplicatesOnly: Boolean, businessArea: String, orderBy: String): ImportedIndividualNodeConnection + importData(id: ID!): ImportDataNode + koboImportData(id: ID!): KoboImportDataNode + deduplicationBatchStatusChoices: [ChoiceObject] + deduplicationGoldenRecordStatusChoices: [ChoiceObject] + registrationDataImport(id: ID!): RegistrationDataImportNode + allRegistrationDataImports(offset: Int, before: String, after: String, first: Int, last: Int, importedBy_Id: UUID, importDate: Date, status: String, name: String, name_Startswith: String, businessArea: String, importDateRange: String, size: String, program: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, orderBy: String): RegistrationDataImportNodeConnection + registrationDataStatusChoices: [ChoiceObject] + _debug: DjangoDebug +} + +input RandomSamplingArguments { + confidenceInterval: Float! + marginOfError: Float! + excludedAdminAreas: [String] + age: AgeInput + sex: String +} + +input RapidProArguments { + flowId: String! +} + +type RapidProFlow { + id: String + name: String + type: String + archived: Boolean + labels: [String] + expires: Int + runs: [RapidProFlowRun] + results: [RapidProFlowResult] + createdOn: DateTime + modifiedOn: DateTime +} + +type RapidProFlowNode { + id: String + name: String +} + +type RapidProFlowResult { + key: String + name: String + categories: [String] + nodeUuids: [String] +} + +type RapidProFlowRun { + active: Int + completed: Int + interrupted: Int + expired: Int +} + +type ReassignRoleMutation { + household: HouseholdNode + individual: IndividualNode +} + +type RebuildTargetPopulationMutation { + targetPopulation: TargetPopulationNode +} + +type RecipientNode implements Node { + id: ID! + size: Int + headOfHousehold: IndividualNode +} + +type RecipientNodeConnection { + pageInfo: PageInfo! + edges: [RecipientNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type RecipientNodeEdge { + node: RecipientNode + cursor: String! +} + +type ReconciliationSummaryNode { + deliveredFully: Int + deliveredPartially: Int + notDelivered: Int + unsuccessful: Int + pending: Int + forceFailed: Int + numberOfPayments: Int + reconciled: Int +} + +input ReferralTicketExtras { + household: ID + individual: ID +} + +type RefuseRegistrationDataImportMutation { + registrationDataImport: RegistrationDataImportNode +} + +enum RegistrationDataImportDataSource { + XLS + KOBO + FLEX_REGISTRATION + API + EDOPOMOGA + PROGRAM_POPULATION +} + +enum RegistrationDataImportDatahubImportDone { + LOADING + NOT_STARTED + STARTED + DONE +} + +type RegistrationDataImportDatahubNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + name: String! + importDate: DateTime! + hctId: UUID + importData: ImportDataNode + importDone: RegistrationDataImportDatahubImportDone! + businessAreaSlug: String! +} + +type RegistrationDataImportNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + version: BigInt! + name: String! + status: RegistrationDataImportStatus! + importDate: DateTime! + importedBy: UserNode + dataSource: RegistrationDataImportDataSource! + numberOfIndividuals: Int! + numberOfHouseholds: Int! + batchDuplicates: Int! + batchPossibleDuplicates: Int! + batchUnique: Int! + goldenRecordDuplicates: Int! + goldenRecordPossibleDuplicates: Int! + goldenRecordUnique: Int! + datahubId: UUID + errorMessage: String! + sentryId: String + pullPictures: Boolean! + businessArea: UserBusinessAreaNode + screenBeneficiary: Boolean! + excluded: Boolean! + program: ProgramNode + erased: Boolean! + refuseReason: String + allowDeliveryMechanismsValidationErrors: Boolean! + importData: ImportDataNode + households(offset: Int, before: String, after: String, first: Int, last: Int): HouseholdNodeConnection! + individuals(offset: Int, before: String, after: String, first: Int, last: Int): IndividualNodeConnection! + grievanceticketSet(offset: Int, before: String, after: String, first: Int, last: Int): GrievanceTicketNodeConnection! + messages(offset: Int, before: String, after: String, first: Int, last: Int): CommunicationMessageNodeConnection! + adminUrl: String + batchDuplicatesCountAndPercentage: CountAndPercentageNode + batchPossibleDuplicatesCountAndPercentage: CountAndPercentageNode + batchUniqueCountAndPercentage: CountAndPercentageNode + goldenRecordDuplicatesCountAndPercentage: CountAndPercentageNode + goldenRecordPossibleDuplicatesCountAndPercentage: CountAndPercentageNode + goldenRecordUniqueCountAndPercentage: CountAndPercentageNode + totalHouseholdsCountWithValidPhoneNo: Int +} + +type RegistrationDataImportNodeConnection { + pageInfo: PageInfo! + edges: [RegistrationDataImportNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type RegistrationDataImportNodeEdge { + node: RegistrationDataImportNode + cursor: String! +} + +enum RegistrationDataImportStatus { + LOADING + DEDUPLICATION + DEDUPLICATION_FAILED + IMPORT_SCHEDULED + IMPORTING + IMPORT_ERROR + IN_REVIEW + MERGE_SCHEDULED + MERGED + MERGING + MERGE_ERROR + REFUSED +} + +type RegistrationDeduplicationMutation { + ok: Boolean +} + +type RegistrationKoboImportMutation { + validationErrors: Arg + registrationDataImport: RegistrationDataImportNode +} + +input RegistrationKoboImportMutationInput { + importDataId: String + name: String + pullPictures: Boolean + businessAreaSlug: String + screenBeneficiary: Boolean + allowDeliveryMechanismsValidationErrors: Boolean +} + +type RegistrationProgramPopulationImportMutation { + validationErrors: Arg + registrationDataImport: RegistrationDataImportNode +} + +input RegistrationProgramPopulationImportMutationInput { + importFromProgramId: String + name: String + businessAreaSlug: String + screenBeneficiary: Boolean +} + +type RegistrationXlsxImportMutation { + validationErrors: Arg + registrationDataImport: RegistrationDataImportNode +} + +input RegistrationXlsxImportMutationInput { + importDataId: ID + name: String + businessAreaSlug: String + screenBeneficiary: Boolean + allowDeliveryMechanismsValidationErrors: Boolean +} + +type ReportNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + businessArea: UserBusinessAreaNode! + file: String + createdBy: UserNode! + status: Int! + reportType: Int! + dateFrom: Date! + dateTo: Date! + numberOfRecords: Int + program: ProgramNode + adminArea(offset: Int, before: String, after: String, first: Int, last: Int, name: String): AreaNodeConnection! + fileUrl: String + adminArea1(offset: Int, before: String, after: String, first: Int, last: Int, name: String): AreaNodeConnection + adminArea2(offset: Int, before: String, after: String, first: Int, last: Int, name: String): AreaNodeConnection +} + +type ReportNodeConnection { + pageInfo: PageInfo! + edges: [ReportNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type ReportNodeEdge { + node: ReportNode + cursor: String! +} + +type RestartCreateReport { + report: ReportNode +} + +input RestartCreateReportInput { + reportId: ID! + businessAreaSlug: String! +} + +type RevertMarkPaymentAsFailedMutation { + payment: PaymentNode +} + +type RevertMarkPaymentRecordAsFailedMutation { + paymentRecord: PaymentRecordNode +} + +type RoleChoiceObject { + name: String + value: String + subsystem: String +} + +type RoleNode { + createdAt: DateTime! + updatedAt: DateTime! + name: String! + subsystem: RoleSubsystem! + permissions: [String!] + businessAreaPartnerThrough: [PartnerRoleNode!]! + userRoles: [UserRoleNode!]! +} + +enum RoleSubsystem { + HOPE + KOBO + CA + API +} + +enum RuleCommitLanguage { + PYTHON +} + +type RuleCommitNode implements Node { + id: ID! + timestamp: DateTime! + rule: SteficonRuleNode + updatedBy: UserNode + definition: String! + isRelease: Boolean! + enabled: Boolean! + deprecated: Boolean! + language: RuleCommitLanguage! + affectedFields: [String!]! + before: JSONString! + after: JSONString! + paymentPlans(offset: Int, before: String, after: String, first: Int, last: Int): PaymentPlanNodeConnection! + targetPopulations(offset: Int, before: String, after: String, first: Int, last: Int, program: [ID], createdAt: DateTime, createdAt_Lte: DateTime, createdAt_Gte: DateTime, updatedAt: DateTime, updatedAt_Lte: DateTime, updatedAt_Gte: DateTime, status: String, households: [ID], name: String, createdByName: String, totalHouseholdsCountMin: Int, totalHouseholdsCountMax: Int, totalIndividualsCountMin: Int, totalIndividualsCountMax: Int, businessArea: String, createdAtRange: String, paymentPlanApplicable: Boolean, statusNot: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, programCycle: String, orderBy: String): TargetPopulationNodeConnection! +} + +type RuleCommitNodeConnection { + pageInfo: PageInfo! + edges: [RuleCommitNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type RuleCommitNodeEdge { + node: RuleCommitNode + cursor: String! +} + +enum RuleLanguage { + PYTHON +} + +enum RuleSecurity { + A_0 + A_2 + A_4 +} + +enum RuleType { + PAYMENT_PLAN + TARGETING +} + +enum SamplingChoices { + FULL_LIST + RANDOM +} + +type SanctionListIndividualAliasNameNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + name: String! +} + +type SanctionListIndividualAliasNameNodeConnection { + pageInfo: PageInfo! + edges: [SanctionListIndividualAliasNameNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type SanctionListIndividualAliasNameNodeEdge { + node: SanctionListIndividualAliasNameNode + cursor: String! +} + +type SanctionListIndividualCountriesNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + country: String +} + +type SanctionListIndividualCountriesNodeConnection { + pageInfo: PageInfo! + edges: [SanctionListIndividualCountriesNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type SanctionListIndividualCountriesNodeEdge { + node: SanctionListIndividualCountriesNode + cursor: String! +} + +type SanctionListIndividualDateOfBirthNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + date: Date! +} + +type SanctionListIndividualDateOfBirthNodeConnection { + pageInfo: PageInfo! + edges: [SanctionListIndividualDateOfBirthNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type SanctionListIndividualDateOfBirthNodeEdge { + node: SanctionListIndividualDateOfBirthNode + cursor: String! +} + +type SanctionListIndividualDocumentNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + documentNumber: String! + typeOfDocument: String! + dateOfIssue: String + issuingCountry: String + note: String! +} + +type SanctionListIndividualDocumentNodeConnection { + pageInfo: PageInfo! + edges: [SanctionListIndividualDocumentNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type SanctionListIndividualDocumentNodeEdge { + node: SanctionListIndividualDocumentNode + cursor: String! +} + +type SanctionListIndividualNationalitiesNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + nationality: String +} + +type SanctionListIndividualNationalitiesNodeConnection { + pageInfo: PageInfo! + edges: [SanctionListIndividualNationalitiesNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type SanctionListIndividualNationalitiesNodeEdge { + node: SanctionListIndividualNationalitiesNode + cursor: String! +} + +type SanctionListIndividualNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + dataId: Int! + versionNum: Int! + firstName: String! + secondName: String! + thirdName: String! + fourthName: String! + fullName: String! + nameOriginalScript: String! + unListType: String! + referenceNumber: String! + listedOn: DateTime! + comments: String! + designation: String! + listType: String! + street: String! + city: String! + stateProvince: String! + addressNote: String! + countryOfBirth: String + active: Boolean! + documents(offset: Int, before: String, after: String, first: Int, last: Int): SanctionListIndividualDocumentNodeConnection! + nationalities(offset: Int, before: String, after: String, first: Int, last: Int): SanctionListIndividualNationalitiesNodeConnection! + countries(offset: Int, before: String, after: String, first: Int, last: Int): SanctionListIndividualCountriesNodeConnection! + aliasNames(offset: Int, before: String, after: String, first: Int, last: Int): SanctionListIndividualAliasNameNodeConnection! + datesOfBirth(offset: Int, before: String, after: String, first: Int, last: Int): SanctionListIndividualDateOfBirthNodeConnection! +} + +type SanctionListIndividualNodeConnection { + pageInfo: PageInfo! + edges: [SanctionListIndividualNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type SanctionListIndividualNodeEdge { + node: SanctionListIndividualNode + cursor: String! +} + +type SaveKoboProjectImportDataAsync { + importData: KoboImportDataNode +} + +type SectionTotalNode { + total: Float +} + +input SensitiveGrievanceTicketExtras { + household: ID + individual: ID + paymentRecord: [ID] +} + +type ServiceProviderNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + businessArea: UserBusinessAreaNode! + caId: String! + fullName: String + shortName: String + country: String! + visionId: String + cashPlans(offset: Int, before: String, after: String, first: Int, last: Int): CashPlanNodeConnection! + paymentrecordSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentRecordNodeConnection! +} + +type ServiceProviderNodeConnection { + pageInfo: PageInfo! + edges: [ServiceProviderNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type ServiceProviderNodeEdge { + node: ServiceProviderNode + cursor: String! +} + +type SetSteficonRuleOnPaymentPlanPaymentListMutation { + paymentPlan: PaymentPlanNode +} + +input SetSteficonRuleOnTargetPopulationMutationInput { + targetId: ID! + steficonRuleId: ID + version: BigInt + clientMutationId: String +} + +type SetSteficonRuleOnTargetPopulationMutationPayload { + targetPopulation: TargetPopulationNode + clientMutationId: String +} + +type SimpleApproveMutation { + grievanceTicket: GrievanceTicketNode +} + +type SplitPaymentPlanMutation { + paymentPlan: PaymentPlanNode +} + +type SteficonRuleNode implements Node { + id: ID! + allowedBusinessAreas(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID): UserBusinessAreaNodeConnection! + name: String! + definition: String! + description: String + enabled: Boolean! + deprecated: Boolean! + language: RuleLanguage! + security: RuleSecurity! + createdBy: UserNode + updatedBy: UserNode + createdAt: DateTime! + updatedAt: DateTime! + type: RuleType! + flags: JSONString! + history(offset: Int, before: String, after: String, first: Int, last: Int): RuleCommitNodeConnection! +} + +type SteficonRuleNodeConnection { + pageInfo: PageInfo! + edges: [SteficonRuleNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type SteficonRuleNodeEdge { + node: SteficonRuleNode + cursor: String! +} + +enum SurveyCategory { + RAPID_PRO + SMS + MANUAL +} + +type SurveyNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + unicefId: String + title: String! + body: String! + category: SurveyCategory! + numberOfRecipients: Int! + createdBy: UserNode + recipients(offset: Int, before: String, after: String, first: Int, last: Int): HouseholdNodeConnection! + targetPopulation: TargetPopulationNode + program: ProgramNode + businessArea: UserBusinessAreaNode! + sampleFile: String + sampleFileGeneratedAt: DateTime + samplingType: SurveySamplingType! + fullListArguments: JSONString! + randomSamplingArguments: JSONString! + sampleSize: Int! + flowId: String + successfulRapidProCalls: [JSONString!]! + adminUrl: String + sampleFilePath: String + hasValidSampleFile: Boolean + rapidProUrl: String +} + +type SurveyNodeConnection { + pageInfo: PageInfo! + edges: [SurveyNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type SurveyNodeEdge { + node: SurveyNode + cursor: String! +} + +enum SurveySamplingType { + FULL_LIST + RANDOM +} + +type TableTotalCashTransferred { + data: [_TableTotalCashTransferredDataNode] +} + +type TableTotalCashTransferredForPeople { + data: [_TableTotalCashTransferredDataForPeopleNode] +} + +enum TargetPopulationBuildStatus { + PENDING + BUILDING + FAILED + OK +} + +type TargetPopulationNode implements Node { + isRemoved: Boolean! + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + version: BigInt! + name: String! + caId: String + caHashId: String + createdBy: UserNode + changeDate: DateTime + changedBy: UserNode + finalizedAt: DateTime + finalizedBy: UserNode + businessArea: UserBusinessAreaNode + status: TargetPopulationStatus! + buildStatus: TargetPopulationBuildStatus! + builtAt: DateTime + households(offset: Int, before: String, after: String, first: Int, last: Int, orderBy: String, businessArea: String): HouseholdNodeConnection + program: ProgramNode + programCycle: ProgramCycleNode + targetingCriteria: TargetingCriteriaNode + sentToDatahub: Boolean! + steficonRule: RuleCommitNode + steficonAppliedDate: DateTime + vulnerabilityScoreMin: Float + vulnerabilityScoreMax: Float + excludedIds: String! + exclusionReason: String! + totalHouseholdsCount: Int + totalIndividualsCount: Int + childMaleCount: Int + childFemaleCount: Int + adultMaleCount: Int + adultFemaleCount: Int + paymentPlans(offset: Int, before: String, after: String, first: Int, last: Int): PaymentPlanNodeConnection! + paymentRecords(offset: Int, before: String, after: String, first: Int, last: Int): PaymentRecordNodeConnection! + selections: [HouseholdSelectionNode!]! + messages(offset: Int, before: String, after: String, first: Int, last: Int): CommunicationMessageNodeConnection! + surveys(offset: Int, before: String, after: String, first: Int, last: Int): SurveyNodeConnection! + adminUrl: String + totalFamilySize: Int + householdList(offset: Int, before: String, after: String, first: Int, last: Int, orderBy: String, businessArea: String): HouseholdNodeConnection + totalHouseholdsCountWithValidPhoneNo: Int + hasEmptyCriteria: Boolean + hasEmptyIdsCriteria: Boolean +} + +type TargetPopulationNodeConnection { + pageInfo: PageInfo! + edges: [TargetPopulationNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type TargetPopulationNodeEdge { + node: TargetPopulationNode + cursor: String! +} + +enum TargetPopulationStatus { + OPEN + LOCKED + STEFICON_WAIT + STEFICON_RUN + STEFICON_COMPLETED + STEFICON_ERROR + PROCESSING + SENDING_TO_CASH_ASSIST + READY_FOR_CASH_ASSIST + READY_FOR_PAYMENT_MODULE + ASSIGNED +} + +type TargetingCriteriaNode { + id: UUID! + createdAt: DateTime! + updatedAt: DateTime! + flagExcludeIfActiveAdjudicationTicket: Boolean! + flagExcludeIfOnSanctionList: Boolean! + householdIds: String! + individualIds: String! + targetPopulation: TargetPopulationNode + rules: [TargetingCriteriaRuleNode] +} + +input TargetingCriteriaObjectType { + rules: [TargetingCriteriaRuleObjectType] + flagExcludeIfActiveAdjudicationTicket: Boolean + flagExcludeIfOnSanctionList: Boolean + householdIds: String + individualIds: String +} + +enum TargetingCriteriaRuleFilterComparisonMethod { + EQUALS + NOT_EQUALS + CONTAINS + NOT_CONTAINS + RANGE + NOT_IN_RANGE + GREATER_THAN + LESS_THAN + IS_NULL +} + +enum TargetingCriteriaRuleFilterFlexFieldClassification { + NOT_FLEX_FIELD + FLEX_FIELD_BASIC + FLEX_FIELD_PDU +} + +type TargetingCriteriaRuleFilterNode { + id: UUID! + createdAt: DateTime! + updatedAt: DateTime! + comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod! + targetingCriteriaRule: TargetingCriteriaRuleNode! + flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification! + fieldName: String! + arguments: [Arg] + fieldAttribute: FieldAttributeNode +} + +input TargetingCriteriaRuleFilterObjectType { + comparisonMethod: String! + flexFieldClassification: FlexFieldClassificationChoices! + fieldName: String! + arguments: [Arg]! + roundNumber: Int +} + +type TargetingCriteriaRuleNode { + id: UUID! + createdAt: DateTime! + updatedAt: DateTime! + targetingCriteria: TargetingCriteriaNode! + individualsFiltersBlocks: [TargetingIndividualRuleFilterBlockNode] + filters: [TargetingCriteriaRuleFilterNode] +} + +input TargetingCriteriaRuleObjectType { + filters: [TargetingCriteriaRuleFilterObjectType] + individualsFiltersBlocks: [TargetingIndividualRuleFilterBlockObjectType] +} + +enum TargetingIndividualBlockRuleFilterComparisonMethod { + EQUALS + NOT_EQUALS + CONTAINS + NOT_CONTAINS + RANGE + NOT_IN_RANGE + GREATER_THAN + LESS_THAN + IS_NULL +} + +enum TargetingIndividualBlockRuleFilterFlexFieldClassification { + NOT_FLEX_FIELD + FLEX_FIELD_BASIC + FLEX_FIELD_PDU +} + +type TargetingIndividualBlockRuleFilterNode { + id: UUID! + createdAt: DateTime! + updatedAt: DateTime! + comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod! + individualsFiltersBlock: TargetingIndividualRuleFilterBlockNode! + flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification! + fieldName: String! + arguments: [Arg] + roundNumber: Int + fieldAttribute: FieldAttributeNode +} + +type TargetingIndividualRuleFilterBlockNode { + id: UUID! + createdAt: DateTime! + updatedAt: DateTime! + targetingCriteriaRule: TargetingCriteriaRuleNode! + targetOnlyHoh: Boolean! + individualBlockFilters: [TargetingIndividualBlockRuleFilterNode] +} + +input TargetingIndividualRuleFilterBlockObjectType { + individualBlockFilters: [TargetingCriteriaRuleFilterObjectType] +} + +type TicketAddIndividualDetailsNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + household: HouseholdNode + individualData: Arg + approveStatus: Boolean! +} + +type TicketAddIndividualDetailsNodeConnection { + pageInfo: PageInfo! + edges: [TicketAddIndividualDetailsNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type TicketAddIndividualDetailsNodeEdge { + node: TicketAddIndividualDetailsNode + cursor: String! +} + +type TicketByType { + userGeneratedCount: Int + systemGeneratedCount: Int + closedUserGeneratedCount: Int + closedSystemGeneratedCount: Int + userGeneratedAvgResolution: Float + systemGeneratedAvgResolution: Float +} + +type TicketComplaintDetailsNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + paymentContentType: ContentTypeObjectType + paymentObjectId: UUID + household: HouseholdNode + individual: IndividualNode + paymentRecord: PaymentRecordAndPaymentNode +} + +type TicketComplaintDetailsNodeConnection { + pageInfo: PageInfo! + edges: [TicketComplaintDetailsNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type TicketComplaintDetailsNodeEdge { + node: TicketComplaintDetailsNode + cursor: String! +} + +type TicketDeleteHouseholdDetailsNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + household: HouseholdNode + roleReassignData: JSONString! + approveStatus: Boolean! + reasonHousehold: HouseholdNode + householdData: Arg +} + +type TicketDeleteHouseholdDetailsNodeConnection { + pageInfo: PageInfo! + edges: [TicketDeleteHouseholdDetailsNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type TicketDeleteHouseholdDetailsNodeEdge { + node: TicketDeleteHouseholdDetailsNode + cursor: String! +} + +type TicketDeleteIndividualDetailsNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + individual: IndividualNode + roleReassignData: JSONString! + approveStatus: Boolean! + individualData: Arg +} + +type TicketDeleteIndividualDetailsNodeConnection { + pageInfo: PageInfo! + edges: [TicketDeleteIndividualDetailsNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type TicketDeleteIndividualDetailsNodeEdge { + node: TicketDeleteIndividualDetailsNode + cursor: String! +} + +type TicketHouseholdDataUpdateDetailsNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + household: HouseholdNode + householdData: Arg +} + +type TicketHouseholdDataUpdateDetailsNodeConnection { + pageInfo: PageInfo! + edges: [TicketHouseholdDataUpdateDetailsNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type TicketHouseholdDataUpdateDetailsNodeEdge { + node: TicketHouseholdDataUpdateDetailsNode + cursor: String! +} + +type TicketIndividualDataUpdateDetailsNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + individual: IndividualNode + individualData: Arg + roleReassignData: JSONString! +} + +type TicketIndividualDataUpdateDetailsNodeConnection { + pageInfo: PageInfo! + edges: [TicketIndividualDataUpdateDetailsNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type TicketIndividualDataUpdateDetailsNodeEdge { + node: TicketIndividualDataUpdateDetailsNode + cursor: String! +} + +type TicketNeedsAdjudicationDetailsExtraDataNode { + goldenRecords: [DeduplicationResultNode] + possibleDuplicate: [DeduplicationResultNode] +} + +type TicketNeedsAdjudicationDetailsNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + isMultipleDuplicatesVersion: Boolean! + goldenRecordsIndividual: IndividualNode! + possibleDuplicates: [IndividualNode] + selectedDistinct: [IndividualNode] + selectedIndividuals(offset: Int, before: String, after: String, first: Int, last: Int): IndividualNodeConnection! + roleReassignData: JSONString! + extraData: TicketNeedsAdjudicationDetailsExtraDataNode + scoreMin: Float! + scoreMax: Float! + isCrossArea: Boolean! + selectedIndividual: IndividualNode + possibleDuplicate: IndividualNode + hasDuplicatedDocument: Boolean + selectedDuplicates: [IndividualNode] +} + +type TicketNeedsAdjudicationDetailsNodeConnection { + pageInfo: PageInfo! + edges: [TicketNeedsAdjudicationDetailsNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type TicketNeedsAdjudicationDetailsNodeEdge { + node: TicketNeedsAdjudicationDetailsNode + cursor: String! +} + +type TicketNegativeFeedbackDetailsNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + household: HouseholdNode + individual: IndividualNode +} + +type TicketNegativeFeedbackDetailsNodeConnection { + pageInfo: PageInfo! + edges: [TicketNegativeFeedbackDetailsNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type TicketNegativeFeedbackDetailsNodeEdge { + node: TicketNegativeFeedbackDetailsNode + cursor: String! +} + +type TicketNoteNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + description: String! + createdBy: UserNode +} + +type TicketNoteNodeConnection { + pageInfo: PageInfo! + edges: [TicketNoteNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type TicketNoteNodeEdge { + node: TicketNoteNode + cursor: String! +} + +input TicketPaymentVerificationDetailsExtras { + newReceivedAmount: Float + newStatus: String +} + +enum TicketPaymentVerificationDetailsNewStatus { + NOT_RECEIVED + PENDING + RECEIVED + RECEIVED_WITH_ISSUES +} + +type TicketPaymentVerificationDetailsNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + paymentVerifications(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationNodeConnection! + paymentVerificationStatus: TicketPaymentVerificationDetailsPaymentVerificationStatus! + paymentVerification: PaymentVerificationNode + newStatus: TicketPaymentVerificationDetailsNewStatus + oldReceivedAmount: Float + newReceivedAmount: Float + approveStatus: Boolean! + hasMultiplePaymentVerifications: Boolean +} + +type TicketPaymentVerificationDetailsNodeConnection { + pageInfo: PageInfo! + edges: [TicketPaymentVerificationDetailsNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type TicketPaymentVerificationDetailsNodeEdge { + node: TicketPaymentVerificationDetailsNode + cursor: String! +} + +enum TicketPaymentVerificationDetailsPaymentVerificationStatus { + NOT_RECEIVED + PENDING + RECEIVED + RECEIVED_WITH_ISSUES +} + +type TicketPositiveFeedbackDetailsNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + household: HouseholdNode + individual: IndividualNode +} + +type TicketPositiveFeedbackDetailsNodeConnection { + pageInfo: PageInfo! + edges: [TicketPositiveFeedbackDetailsNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type TicketPositiveFeedbackDetailsNodeEdge { + node: TicketPositiveFeedbackDetailsNode + cursor: String! +} + +type TicketReferralDetailsNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + household: HouseholdNode + individual: IndividualNode +} + +type TicketReferralDetailsNodeConnection { + pageInfo: PageInfo! + edges: [TicketReferralDetailsNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type TicketReferralDetailsNodeEdge { + node: TicketReferralDetailsNode + cursor: String! +} + +type TicketSensitiveDetailsNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + paymentContentType: ContentTypeObjectType + paymentObjectId: UUID + household: HouseholdNode + individual: IndividualNode + paymentRecord: PaymentRecordAndPaymentNode +} + +type TicketSensitiveDetailsNodeConnection { + pageInfo: PageInfo! + edges: [TicketSensitiveDetailsNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type TicketSensitiveDetailsNodeEdge { + node: TicketSensitiveDetailsNode + cursor: String! +} + +type TicketSystemFlaggingDetailsNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + goldenRecordsIndividual: IndividualNode! + sanctionListIndividual: SanctionListIndividualNode! + approveStatus: Boolean! + roleReassignData: JSONString! +} + +type TicketSystemFlaggingDetailsNodeConnection { + pageInfo: PageInfo! + edges: [TicketSystemFlaggingDetailsNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type TicketSystemFlaggingDetailsNodeEdge { + node: TicketSystemFlaggingDetailsNode + cursor: String! +} + +scalar UUID + +type UnlockTargetPopulationMutation { + targetPopulation: TargetPopulationNode +} + +input UpdateAddIndividualIssueTypeExtras { + individualData: AddIndividualDataObjectType! +} + +input UpdateFeedbackInput { + feedbackId: ID! + issueType: String + householdLookup: ID + individualLookup: ID + description: String + comments: String + admin2: ID + area: String + language: String + consent: Boolean + program: ID +} + +type UpdateFeedbackMutation { + feedback: FeedbackNode +} + +input UpdateGrievanceTicketExtrasInput { + householdDataUpdateIssueTypeExtras: UpdateHouseholdDataUpdateIssueTypeExtras + individualDataUpdateIssueTypeExtras: UpdateIndividualDataUpdateIssueTypeExtras + addIndividualIssueTypeExtras: UpdateAddIndividualIssueTypeExtras + category: CategoryExtrasInput + ticketPaymentVerificationDetailsExtras: TicketPaymentVerificationDetailsExtras +} + +input UpdateGrievanceTicketInput { + ticketId: ID! + description: String + assignedTo: ID + admin: ID + area: String + language: String + linkedTickets: [ID] + household: ID + individual: ID + paymentRecord: ID + extras: UpdateGrievanceTicketExtrasInput + priority: Int + urgency: Int + partner: Int + program: ID + comments: String + documentation: [GrievanceDocumentInput] + documentationToUpdate: [GrievanceDocumentUpdateInput] + documentationToDelete: [ID] +} + +type UpdateGrievanceTicketMutation { + grievanceTicket: GrievanceTicketNode +} + +input UpdateHouseholdDataUpdateIssueTypeExtras { + householdData: HouseholdUpdateDataObjectType! +} + +input UpdateIndividualDataUpdateIssueTypeExtras { + individualData: IndividualUpdateDataObjectType! +} + +input UpdatePaymentPlanInput { + paymentPlanId: ID! + targetingId: ID + dispersionStartDate: Date + dispersionEndDate: Date + currency: String +} + +type UpdatePaymentPlanMutation { + paymentPlan: PaymentPlanNode +} + +type UpdatePaymentVerificationReceivedAndReceivedAmount { + paymentVerification: PaymentVerificationNode +} + +type UpdatePaymentVerificationStatusAndReceivedAmount { + paymentVerification: PaymentVerificationNode +} + +type UpdateProgram { + validationErrors: Arg + program: ProgramNode +} + +input UpdateProgramInput { + id: String! + name: String + status: String + startDate: Date + endDate: Date + description: String + budget: Decimal + frequencyOfPayments: String + sector: String + cashPlus: Boolean + populationGoal: Int + administrativeAreasOfImplementation: String + dataCollectingTypeCode: String + partners: [ProgramPartnerThroughInput] + partnerAccess: String + programmeCode: String + pduFields: [PDUFieldInput] +} + +input UpdateTargetPopulationInput { + id: ID! + name: String + targetingCriteria: TargetingCriteriaObjectType + programId: ID + programCycleId: ID + vulnerabilityScoreMin: Decimal + vulnerabilityScoreMax: Decimal + excludedIds: String + exclusionReason: String +} + +type UpdateTargetPopulationMutation { + validationErrors: Arg + targetPopulation: TargetPopulationNode +} + +scalar Upload + +type UploadImportDataXLSXFileAsync { + importData: ImportDataNode + errors: [XlsxRowErrorNode] +} + +type UserBusinessAreaNode implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + code: String! + name: String! + longName: String! + regionCode: String! + regionName: String! + koboUsername: String + koboToken: String + koboUrl: String + rapidProHost: String + rapidProPaymentVerificationToken: String + rapidProMessagesToken: String + rapidProSurveyToken: String + slug: String! + customFields: JSONString! + hasDataSharingAgreement: Boolean! + parent: UserBusinessAreaNode + isSplit: Boolean! + postponeDeduplication: Boolean! + deduplicationDuplicateScore: Float! + deduplicationPossibleDuplicateScore: Float! + deduplicationBatchDuplicatesPercentage: Int! + deduplicationBatchDuplicatesAllowed: Int! + deduplicationGoldenRecordDuplicatesPercentage: Int! + deduplicationGoldenRecordDuplicatesAllowed: Int! + screenBeneficiary: Boolean! + deduplicationIgnoreWithdraw: Boolean! + isPaymentPlanApplicable: Boolean! + isAccountabilityApplicable: Boolean + active: Boolean! + enableEmailNotification: Boolean! + partners: [PartnerNode!]! + businessAreaPartnerThrough: [PartnerRoleNode!]! + children(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID): UserBusinessAreaNodeConnection! + dataCollectingTypes(offset: Int, before: String, after: String, first: Int, last: Int): DataCollectingTypeNodeConnection! + partnerSet: [PartnerNode!]! + userRoles: [UserRoleNode!]! + householdSet(offset: Int, before: String, after: String, first: Int, last: Int): HouseholdNodeConnection! + individualSet(offset: Int, before: String, after: String, first: Int, last: Int): IndividualNodeConnection! + registrationdataimportSet(offset: Int, before: String, after: String, first: Int, last: Int): RegistrationDataImportNodeConnection! + ruleSet(offset: Int, before: String, after: String, first: Int, last: Int): SteficonRuleNodeConnection! + paymentplanSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentPlanNodeConnection! + financialserviceproviderSet(offset: Int, before: String, after: String, first: Int, last: Int): FinancialServiceProviderNodeConnection! + cashplanSet(offset: Int, before: String, after: String, first: Int, last: Int): CashPlanNodeConnection! + paymentrecordSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentRecordNodeConnection! + paymentSet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentNodeConnection! + serviceproviderSet(offset: Int, before: String, after: String, first: Int, last: Int): ServiceProviderNodeConnection! + tickets(offset: Int, before: String, after: String, first: Int, last: Int): GrievanceTicketNodeConnection! + targetpopulationSet(offset: Int, before: String, after: String, first: Int, last: Int, program: [ID], createdAt: DateTime, createdAt_Lte: DateTime, createdAt_Gte: DateTime, updatedAt: DateTime, updatedAt_Lte: DateTime, updatedAt_Gte: DateTime, status: String, households: [ID], name: String, createdByName: String, totalHouseholdsCountMin: Int, totalHouseholdsCountMax: Int, totalIndividualsCountMin: Int, totalIndividualsCountMax: Int, businessArea: String, createdAtRange: String, paymentPlanApplicable: Boolean, statusNot: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, programCycle: String, orderBy: String): TargetPopulationNodeConnection! + programSet(offset: Int, before: String, after: String, first: Int, last: Int, name: String): ProgramNodeConnection! + reports(offset: Int, before: String, after: String, first: Int, last: Int): ReportNodeConnection! + logentrySet(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationLogEntryNodeConnection! + messageSet(offset: Int, before: String, after: String, first: Int, last: Int): CommunicationMessageNodeConnection! + feedbackSet(offset: Int, before: String, after: String, first: Int, last: Int): FeedbackNodeConnection! + surveySet(offset: Int, before: String, after: String, first: Int, last: Int): SurveyNodeConnection! + permissions: [String] +} + +type UserBusinessAreaNodeConnection { + pageInfo: PageInfo! + edges: [UserBusinessAreaNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type UserBusinessAreaNodeEdge { + node: UserBusinessAreaNode + cursor: String! +} + +type UserNode implements Node { + lastLogin: DateTime + isSuperuser: Boolean! + username: String! + firstName: String! + lastName: String! + isStaff: Boolean! + isActive: Boolean! + dateJoined: DateTime! + id: ID! + status: UserStatus! + partner: PartnerNode + email: String! + availableForExport: Boolean! + customFields: JSONString! + jobTitle: String! + adUuid: String + lastModifyDate: DateTime + lastDoapSync: DateTime + doapHash: String! + userRoles: [UserRoleNode!]! + documentSet(offset: Int, before: String, after: String, first: Int, last: Int): DocumentNodeConnection! + registrationDataImports(offset: Int, before: String, after: String, first: Int, last: Int): RegistrationDataImportNodeConnection! + createdPaymentPlans(offset: Int, before: String, after: String, first: Int, last: Int): PaymentPlanNodeConnection! + createdFinancialServiceProviderXlsxTemplates(offset: Int, before: String, after: String, first: Int, last: Int): FinancialServiceProviderXlsxTemplateNodeConnection! + createdFinancialServiceProviders(offset: Int, before: String, after: String, first: Int, last: Int): FinancialServiceProviderNodeConnection! + createdDeliveryMechanisms(offset: Int, before: String, after: String, first: Int, last: Int): DeliveryMechanismPerPaymentPlanNodeConnection! + sentDeliveryMechanisms(offset: Int, before: String, after: String, first: Int, last: Int): DeliveryMechanismPerPaymentPlanNodeConnection! + approvalSet: [ApprovalNode!]! + createdTickets(offset: Int, before: String, after: String, first: Int, last: Int): GrievanceTicketNodeConnection! + assignedTickets(offset: Int, before: String, after: String, first: Int, last: Int): GrievanceTicketNodeConnection! + ticketNotes(offset: Int, before: String, after: String, first: Int, last: Int): TicketNoteNodeConnection! + targetPopulations(offset: Int, before: String, after: String, first: Int, last: Int, program: [ID], createdAt: DateTime, createdAt_Lte: DateTime, createdAt_Gte: DateTime, updatedAt: DateTime, updatedAt_Lte: DateTime, updatedAt_Gte: DateTime, status: String, households: [ID], name: String, createdByName: String, totalHouseholdsCountMin: Int, totalHouseholdsCountMax: Int, totalIndividualsCountMin: Int, totalIndividualsCountMax: Int, businessArea: String, createdAtRange: String, paymentPlanApplicable: Boolean, statusNot: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, programCycle: String, orderBy: String): TargetPopulationNodeConnection! + changedTargetPopulations(offset: Int, before: String, after: String, first: Int, last: Int, program: [ID], createdAt: DateTime, createdAt_Lte: DateTime, createdAt_Gte: DateTime, updatedAt: DateTime, updatedAt_Lte: DateTime, updatedAt_Gte: DateTime, status: String, households: [ID], name: String, createdByName: String, totalHouseholdsCountMin: Int, totalHouseholdsCountMax: Int, totalIndividualsCountMin: Int, totalIndividualsCountMax: Int, businessArea: String, createdAtRange: String, paymentPlanApplicable: Boolean, statusNot: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, programCycle: String, orderBy: String): TargetPopulationNodeConnection! + finalizedTargetPopulations(offset: Int, before: String, after: String, first: Int, last: Int, program: [ID], createdAt: DateTime, createdAt_Lte: DateTime, createdAt_Gte: DateTime, updatedAt: DateTime, updatedAt_Lte: DateTime, updatedAt_Gte: DateTime, status: String, households: [ID], name: String, createdByName: String, totalHouseholdsCountMin: Int, totalHouseholdsCountMax: Int, totalIndividualsCountMin: Int, totalIndividualsCountMax: Int, businessArea: String, createdAtRange: String, paymentPlanApplicable: Boolean, statusNot: String, totalHouseholdsCountWithValidPhoneNoMax: Int, totalHouseholdsCountWithValidPhoneNoMin: Int, programCycle: String, orderBy: String): TargetPopulationNodeConnection! + reports(offset: Int, before: String, after: String, first: Int, last: Int): ReportNodeConnection! + logs(offset: Int, before: String, after: String, first: Int, last: Int): PaymentVerificationLogEntryNodeConnection! + messages(offset: Int, before: String, after: String, first: Int, last: Int): CommunicationMessageNodeConnection! + feedbacks(offset: Int, before: String, after: String, first: Int, last: Int): FeedbackNodeConnection! + feedbackMessages(offset: Int, before: String, after: String, first: Int, last: Int): FeedbackMessageNodeConnection! + surveys(offset: Int, before: String, after: String, first: Int, last: Int): SurveyNodeConnection! + businessAreas(offset: Int, before: String, after: String, first: Int, last: Int, id: UUID): UserBusinessAreaNodeConnection + partnerRoles: [PartnerRoleNode] +} + +type UserNodeConnection { + pageInfo: PageInfo! + edges: [UserNodeEdge]! + totalCount: Int + edgeCount: Int +} + +type UserNodeEdge { + node: UserNode + cursor: String! +} + +type UserRoleNode { + createdAt: DateTime! + updatedAt: DateTime! + businessArea: UserBusinessAreaNode! + role: RoleNode! + expiryDate: Date +} + +enum UserStatus { + ACTIVE + INACTIVE + INVITED +} + +type VolumeByDeliveryMechanismNode implements Node { + id: ID! + deliveryMechanism: DeliveryMechanismPerPaymentPlanNode + volume: Float + volumeUsd: Float +} + +type XlsxErrorNode { + sheet: String + coordinates: String + message: String +} + +type XlsxRowErrorNode { + rowNumber: Int + header: String + message: String +} + +type _DatasetsNode { + data: [Float] +} + +type _DetailedDatasetsNode { + label: String + data: [Float] +} + +type _TableTotalCashTransferredDataForPeopleNode { + id: String + admin2: String + totalCashTransferred: Float + totalPeople: Int +} + +type _TableTotalCashTransferredDataNode { + id: String + admin2: String + totalCashTransferred: Float + totalHouseholds: Int +} diff --git a/frontend/src/__generated__/graphql.tsx b/frontend/src/__generated__/graphql.tsx index 9b84076417..e4426548b0 100644 --- a/frontend/src/__generated__/graphql.tsx +++ b/frontend/src/__generated__/graphql.tsx @@ -2017,7 +2017,7 @@ export type FinishPaymentVerificationPlan = { }; export enum FlexFieldClassificationChoices { - FlexFieldNotPdu = 'FLEX_FIELD_NOT_PDU', + FlexFieldBasic = 'FLEX_FIELD_BASIC', FlexFieldPdu = 'FLEX_FIELD_PDU', NotFlexField = 'NOT_FLEX_FIELD' } @@ -8308,7 +8308,7 @@ export enum TargetingCriteriaRuleFilterComparisonMethod { } export enum TargetingCriteriaRuleFilterFlexFieldClassification { - FlexFieldNotPdu = 'FLEX_FIELD_NOT_PDU', + FlexFieldBasic = 'FLEX_FIELD_BASIC', FlexFieldPdu = 'FLEX_FIELD_PDU', NotFlexField = 'NOT_FLEX_FIELD' } @@ -8362,7 +8362,7 @@ export enum TargetingIndividualBlockRuleFilterComparisonMethod { } export enum TargetingIndividualBlockRuleFilterFlexFieldClassification { - FlexFieldNotPdu = 'FLEX_FIELD_NOT_PDU', + FlexFieldBasic = 'FLEX_FIELD_BASIC', FlexFieldPdu = 'FLEX_FIELD_PDU', NotFlexField = 'NOT_FLEX_FIELD' } @@ -9634,11 +9634,7 @@ export type ImportedIndividualDetailedFragment = { __typename?: 'ImportedIndivid export type TargetPopulationMinimalFragment = { __typename: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, createdAt: any, updatedAt: any, totalHouseholdsCount?: number | null, totalHouseholdsCountWithValidPhoneNo?: number | null, totalIndividualsCount?: number | null, program?: { __typename: 'ProgramNode', id: string, name: string } | null, createdBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null }; -<<<<<<< HEAD -export type TargetPopulationDetailedFragment = { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, roundNumber?: number | null, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null } | null }; -======= -export type TargetPopulationDetailedFragment = { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, programCycle?: { __typename: 'ProgramCycleNode', id: string, title?: string | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, isFlexField: boolean, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, isFlexField: boolean, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null } | null }; ->>>>>>> origin +export type TargetPopulationDetailedFragment = { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, programCycle?: { __typename: 'ProgramCycleNode', id: string, title?: string | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, roundNumber?: number | null, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null } | null }; export type CreateFeedbackTicketMutationVariables = Exact<{ input: CreateFeedbackInput; @@ -10230,55 +10226,35 @@ export type FinalizeTpMutationVariables = Exact<{ }>; -<<<<<<< HEAD -export type FinalizeTpMutation = { __typename?: 'Mutations', finalizeTargetPopulation?: { __typename?: 'FinalizeTargetPopulationMutation', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, roundNumber?: number | null, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null } | null } | null } | null }; -======= -export type FinalizeTpMutation = { __typename?: 'Mutations', finalizeTargetPopulation?: { __typename?: 'FinalizeTargetPopulationMutation', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, programCycle?: { __typename: 'ProgramCycleNode', id: string, title?: string | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, isFlexField: boolean, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, isFlexField: boolean, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null } | null } | null } | null }; ->>>>>>> origin +export type FinalizeTpMutation = { __typename?: 'Mutations', finalizeTargetPopulation?: { __typename?: 'FinalizeTargetPopulationMutation', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, programCycle?: { __typename: 'ProgramCycleNode', id: string, title?: string | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, roundNumber?: number | null, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null } | null } | null } | null }; export type LockTpMutationVariables = Exact<{ id: Scalars['ID']['input']; }>; -<<<<<<< HEAD -export type LockTpMutation = { __typename?: 'Mutations', lockTargetPopulation?: { __typename?: 'LockTargetPopulationMutation', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, roundNumber?: number | null, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null } | null } | null } | null }; -======= -export type LockTpMutation = { __typename?: 'Mutations', lockTargetPopulation?: { __typename?: 'LockTargetPopulationMutation', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, programCycle?: { __typename: 'ProgramCycleNode', id: string, title?: string | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, isFlexField: boolean, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, isFlexField: boolean, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null } | null } | null } | null }; ->>>>>>> origin +export type LockTpMutation = { __typename?: 'Mutations', lockTargetPopulation?: { __typename?: 'LockTargetPopulationMutation', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, programCycle?: { __typename: 'ProgramCycleNode', id: string, title?: string | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, roundNumber?: number | null, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null } | null } | null } | null }; export type RebuildTpMutationVariables = Exact<{ id: Scalars['ID']['input']; }>; -<<<<<<< HEAD -export type RebuildTpMutation = { __typename?: 'Mutations', targetPopulationRebuild?: { __typename?: 'RebuildTargetPopulationMutation', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, roundNumber?: number | null, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null } | null } | null } | null }; -======= -export type RebuildTpMutation = { __typename?: 'Mutations', targetPopulationRebuild?: { __typename?: 'RebuildTargetPopulationMutation', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, programCycle?: { __typename: 'ProgramCycleNode', id: string, title?: string | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, isFlexField: boolean, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, isFlexField: boolean, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null } | null } | null } | null }; ->>>>>>> origin +export type RebuildTpMutation = { __typename?: 'Mutations', targetPopulationRebuild?: { __typename?: 'RebuildTargetPopulationMutation', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, programCycle?: { __typename: 'ProgramCycleNode', id: string, title?: string | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, roundNumber?: number | null, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null } | null } | null } | null }; export type SetSteficonRuleOnTargetPopulationMutationVariables = Exact<{ input: SetSteficonRuleOnTargetPopulationMutationInput; }>; -<<<<<<< HEAD -export type SetSteficonRuleOnTargetPopulationMutation = { __typename?: 'Mutations', setSteficonRuleOnTargetPopulation?: { __typename?: 'SetSteficonRuleOnTargetPopulationMutationPayload', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, roundNumber?: number | null, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null } | null } | null } | null }; -======= -export type SetSteficonRuleOnTargetPopulationMutation = { __typename?: 'Mutations', setSteficonRuleOnTargetPopulation?: { __typename?: 'SetSteficonRuleOnTargetPopulationMutationPayload', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, programCycle?: { __typename: 'ProgramCycleNode', id: string, title?: string | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, isFlexField: boolean, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, isFlexField: boolean, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null } | null } | null } | null }; ->>>>>>> origin +export type SetSteficonRuleOnTargetPopulationMutation = { __typename?: 'Mutations', setSteficonRuleOnTargetPopulation?: { __typename?: 'SetSteficonRuleOnTargetPopulationMutationPayload', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, programCycle?: { __typename: 'ProgramCycleNode', id: string, title?: string | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, roundNumber?: number | null, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null } | null } | null } | null }; export type UnlockTpMutationVariables = Exact<{ id: Scalars['ID']['input']; }>; -<<<<<<< HEAD -export type UnlockTpMutation = { __typename?: 'Mutations', unlockTargetPopulation?: { __typename?: 'UnlockTargetPopulationMutation', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, roundNumber?: number | null, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null } | null } | null } | null }; -======= -export type UnlockTpMutation = { __typename?: 'Mutations', unlockTargetPopulation?: { __typename?: 'UnlockTargetPopulationMutation', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, programCycle?: { __typename: 'ProgramCycleNode', id: string, title?: string | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, isFlexField: boolean, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, isFlexField: boolean, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null } | null } | null } | null }; ->>>>>>> origin +export type UnlockTpMutation = { __typename?: 'Mutations', unlockTargetPopulation?: { __typename?: 'UnlockTargetPopulationMutation', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, programCycle?: { __typename: 'ProgramCycleNode', id: string, title?: string | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, roundNumber?: number | null, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null } | null } | null } | null }; export type UpdateTpMutationVariables = Exact<{ input: UpdateTargetPopulationInput; @@ -11475,11 +11451,7 @@ export type TargetPopulationQueryVariables = Exact<{ }>; -<<<<<<< HEAD -export type TargetPopulationQuery = { __typename?: 'Query', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, roundNumber?: number | null, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null } | null } | null }; -======= -export type TargetPopulationQuery = { __typename?: 'Query', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, programCycle?: { __typename: 'ProgramCycleNode', id: string, title?: string | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, isFlexField: boolean, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, isFlexField: boolean, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null } | null } | null> | null } | null> | null } | null } | null }; ->>>>>>> origin +export type TargetPopulationQuery = { __typename?: 'Query', targetPopulation?: { __typename?: 'TargetPopulationNode', id: string, name: string, status: TargetPopulationStatus, adminUrl?: string | null, buildStatus: TargetPopulationBuildStatus, totalHouseholdsCount?: number | null, totalIndividualsCount?: number | null, childMaleCount?: number | null, childFemaleCount?: number | null, adultMaleCount?: number | null, adultFemaleCount?: number | null, caHashId?: string | null, excludedIds: string, exclusionReason: string, vulnerabilityScoreMin?: number | null, vulnerabilityScoreMax?: number | null, changeDate?: any | null, finalizedAt?: any | null, hasEmptyCriteria?: boolean | null, hasEmptyIdsCriteria?: boolean | null, steficonRule?: { __typename: 'RuleCommitNode', id: string, rule?: { __typename: 'SteficonRuleNode', id: string, name: string } | null } | null, finalizedBy?: { __typename: 'UserNode', id: string, firstName: string, lastName: string } | null, program?: { __typename: 'ProgramNode', id: string, name: string, status: ProgramStatus, startDate: any, endDate: any, isSocialWorkerProgram?: boolean | null } | null, programCycle?: { __typename: 'ProgramCycleNode', id: string, title?: string | null } | null, createdBy?: { __typename: 'UserNode', id: string, email: string, firstName: string, lastName: string } | null, targetingCriteria?: { __typename: 'TargetingCriteriaNode', id: any, flagExcludeIfActiveAdjudicationTicket: boolean, flagExcludeIfOnSanctionList: boolean, householdIds: string, individualIds: string, rules?: Array<{ __typename: 'TargetingCriteriaRuleNode', id: any, individualsFiltersBlocks?: Array<{ __typename: 'TargetingIndividualRuleFilterBlockNode', individualBlockFilters?: Array<{ __typename: 'TargetingIndividualBlockRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingIndividualBlockRuleFilterFlexFieldClassification, roundNumber?: number | null, arguments?: Array | null, comparisonMethod: TargetingIndividualBlockRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null, filters?: Array<{ __typename: 'TargetingCriteriaRuleFilterNode', id: any, fieldName: string, flexFieldClassification: TargetingCriteriaRuleFilterFlexFieldClassification, arguments?: Array | null, comparisonMethod: TargetingCriteriaRuleFilterComparisonMethod, fieldAttribute?: { __typename: 'FieldAttributeNode', id?: string | null, name?: string | null, labelEn?: string | null, type?: string | null, choices?: Array<{ __typename?: 'CoreFieldChoiceObject', value?: string | null, labelEn?: string | null } | null> | null, pduData?: { __typename?: 'PeriodicFieldDataNode', id: string, subtype: PeriodicFieldDataSubtype, numberOfRounds: number, roundsNames: Array } | null } | null } | null> | null } | null> | null } | null } | null }; export type TargetPopulationHouseholdsQueryVariables = Exact<{ targetPopulation: Scalars['ID']['input']; From e9847a3d207dc107bb474418c4b392f3cdc608e6 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Fri, 23 Aug 2024 17:37:53 +0200 Subject: [PATCH 099/118] add test cov --- .../apps/program/tests/snapshots/snap_test_program_query.py | 3 ++- backend/hct_mis_api/apps/program/tests/test_program_query.py | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_program_query.py b/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_program_query.py index 706df485bd..bd936c207b 100644 --- a/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_program_query.py +++ b/backend/hct_mis_api/apps/program/tests/snapshots/snap_test_program_query.py @@ -61,7 +61,8 @@ } } ], - 'status': 'ACTIVE' + 'status': 'ACTIVE', + 'targetPopulationsCount': 1 } } } diff --git a/backend/hct_mis_api/apps/program/tests/test_program_query.py b/backend/hct_mis_api/apps/program/tests/test_program_query.py index 5f653ed3aa..f933be0562 100644 --- a/backend/hct_mis_api/apps/program/tests/test_program_query.py +++ b/backend/hct_mis_api/apps/program/tests/test_program_query.py @@ -13,12 +13,14 @@ from hct_mis_api.apps.core.models import PeriodicFieldData from hct_mis_api.apps.program.fixtures import ProgramFactory from hct_mis_api.apps.program.models import Program +from hct_mis_api.apps.targeting.fixtures import TargetPopulationFactory PROGRAM_QUERY = """ query Program($id: ID!) { program(id: $id) { name status + targetPopulationsCount pduFields { label name @@ -95,6 +97,7 @@ def setUpTestData(cls) -> None: label="PDU Field Other", pdu_data=pdu_data_other, ) + TargetPopulationFactory(program=cls.program) @parameterized.expand( [ @@ -103,6 +106,7 @@ def setUpTestData(cls) -> None: ] ) def test_single_program_query(self, _: Any, permissions: List[Permissions]) -> None: + print(self.program.targetpopulation_set.all()) self.create_user_role_with_permissions(self.user, permissions, self.business_area) self.snapshot_graphql_request( request_string=PROGRAM_QUERY, From b400888e719e90c0a2c6f59b00a5e15ef81afa8d Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Fri, 23 Aug 2024 17:47:56 +0200 Subject: [PATCH 100/118] and more conflicts --- .../snap_test_create_target_population_mutation.py | 6 ++++++ .../tests/test_create_target_population_mutation.py | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_create_target_population_mutation.py b/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_create_target_population_mutation.py index 71ed539791..700e1db720 100644 --- a/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_create_target_population_mutation.py +++ b/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_create_target_population_mutation.py @@ -316,6 +316,9 @@ 'hasEmptyCriteria': False, 'hasEmptyIdsCriteria': True, 'name': 'Example name 5', + 'programCycle': { + 'status': 'ACTIVE' + }, 'status': 'OPEN', 'targetingCriteria': { 'householdIds': '', @@ -356,6 +359,9 @@ 'hasEmptyCriteria': False, 'hasEmptyIdsCriteria': True, 'name': 'Example name 5', + 'programCycle': { + 'status': 'ACTIVE' + }, 'status': 'OPEN', 'targetingCriteria': { 'householdIds': '', diff --git a/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py b/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py index 9df125bfc3..d23ec90de4 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_create_target_population_mutation.py @@ -110,7 +110,7 @@ def setUpTestData(cls) -> None: "comparisonMethod": "EQUALS", "fieldName": "size", "arguments": [3], - "isFlexField": False, + "flexFieldClassification": "NOT_FLEX_FIELD", } ] } @@ -306,6 +306,7 @@ def test_create_mutation_with_flex_field(self) -> None: "businessAreaSlug": "afghanistan", "programId": self.id_to_base64(self.program.id, "ProgramNode"), "excludedIds": "", + "programCycleId": self.id_to_base64(self.program_cycle.id, "ProgramCycleNode"), "targetingCriteria": { "rules": [ { @@ -342,6 +343,7 @@ def test_create_mutation_with_pdu_flex_field(self) -> None: "businessAreaSlug": "afghanistan", "programId": self.id_to_base64(self.program.id, "ProgramNode"), "excludedIds": "", + "programCycleId": self.id_to_base64(self.program_cycle.id, "ProgramCycleNode"), "targetingCriteria": { "rules": [ { From 0cb06aecdb5cb1e9f0098fc8f8bd7e4a24488af9 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Fri, 23 Aug 2024 20:18:03 +0200 Subject: [PATCH 101/118] incr test cov, small adjustments --- backend/hct_mis_api/apps/targeting/graphql_types.py | 8 ++------ .../tests/snapshots/snap_test_target_query.py | 4 ++++ .../apps/targeting/tests/test_target_query.py | 13 ++++++++++++- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/backend/hct_mis_api/apps/targeting/graphql_types.py b/backend/hct_mis_api/apps/targeting/graphql_types.py index c531c97c83..01641ee9f2 100644 --- a/backend/hct_mis_api/apps/targeting/graphql_types.py +++ b/backend/hct_mis_api/apps/targeting/graphql_types.py @@ -74,16 +74,12 @@ def resolve_field_attribute(parent, info: Any) -> Optional[Dict]: field_attribute = get_field_by_name( parent.field_name, parent.targeting_criteria_rule.targeting_criteria.target_population ) - parent.targeting_criteria_rule return filter_choices( field_attribute, parent.arguments # type: ignore # can't convert graphene list to list ) - program = None - if parent.flex_field_classification == FlexFieldClassification.FLEX_FIELD_PDU: - encoded_program_id = info.context.headers.get("Program") - program = Program.objects.get(id=decode_id_string(encoded_program_id)) - return FlexibleAttribute.objects.get(name=parent.field_name, program=program) + else: # FlexFieldClassification.FLEX_FIELD_BASIC + return FlexibleAttribute.objects.get(name=parent.field_name) class Meta: model = target_models.TargetingCriteriaRuleFilter diff --git a/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_target_query.py b/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_target_query.py index 0944e57152..4cee6054ce 100644 --- a/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_target_query.py +++ b/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_target_query.py @@ -313,6 +313,10 @@ 'some' ], 'comparisonMethod': 'EQUALS', + 'fieldAttribute': { + 'labelEn': 'PDU Field STRING', + 'type': 'PDU' + }, 'fieldName': 'pdu_field_string', 'flexFieldClassification': 'FLEX_FIELD_PDU', 'roundNumber': 1 diff --git a/backend/hct_mis_api/apps/targeting/tests/test_target_query.py b/backend/hct_mis_api/apps/targeting/tests/test_target_query.py index ed272d276b..27f71a684e 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_target_query.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_target_query.py @@ -90,6 +90,11 @@ class TestTargetPopulationQuery(APITestCase): arguments flexFieldClassification roundNumber + fieldAttribute + { + labelEn + type + } } } } @@ -324,7 +329,13 @@ def test_simple_target_query_pdu(self, _: Any, permissions: List[Permissions]) - self.create_user_role_with_permissions(self.user, permissions, self.business_area, self.program) self.snapshot_graphql_request( request_string=TestTargetPopulationQuery.TARGET_POPULATION_QUERY, - context={"user": self.user}, + context={ + "user": self.user, + "headers": { + "Business-Area": self.business_area.slug, + "Program": self.id_to_base64(self.program.id, "ProgramNode"), + }, + }, variables={ "id": self.id_to_base64( self.target_population_with_pdu_filter.id, From 374d3309f698f7832a04e848f5edde09de772280 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Fri, 23 Aug 2024 20:19:48 +0200 Subject: [PATCH 102/118] nocover for except obj does not exist --- backend/hct_mis_api/apps/targeting/validators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/hct_mis_api/apps/targeting/validators.py b/backend/hct_mis_api/apps/targeting/validators.py index 0aa1b67b53..5aebe5cf8a 100644 --- a/backend/hct_mis_api/apps/targeting/validators.py +++ b/backend/hct_mis_api/apps/targeting/validators.py @@ -98,7 +98,7 @@ def validate(rule_filter: Any, program: Program) -> None: else: try: attribute = FlexibleAttribute.objects.get(name=rule_filter.field_name, program=program) - except FlexibleAttribute.DoesNotExist: + except FlexibleAttribute.DoesNotExist: # pragma: no cover logger.exception( f"Can't find PDU flex field attribute associated with {rule_filter.field_name} field name in program {program.name}", ) From cd171a134163953ce8a1f6fffd7279db3e374fef Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Fri, 23 Aug 2024 21:33:25 +0200 Subject: [PATCH 103/118] upd test to fix random fail --- .../tests/snapshots/snap_test_target_query.py | 16 ++++++++-------- .../apps/targeting/tests/test_target_query.py | 3 ++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_target_query.py b/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_target_query.py index 4cee6054ce..d36b1f33f0 100644 --- a/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_target_query.py +++ b/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_target_query.py @@ -55,25 +55,25 @@ { 'node': { 'createdBy': { - 'firstName': 'Second', + 'firstName': 'Test', 'lastName': 'User' }, - 'name': 'target_population_with_pdu_filter', - 'status': 'LOCKED', + 'name': 'target_population_size_2', + 'status': 'OPEN', 'totalHouseholdsCount': 1, - 'totalIndividualsCount': 3 + 'totalIndividualsCount': 2 } }, { 'node': { 'createdBy': { - 'firstName': 'Test', + 'firstName': 'Third', 'lastName': 'User' }, - 'name': 'target_population_size_2', - 'status': 'OPEN', + 'name': 'target_population_with_pdu_filter', + 'status': 'LOCKED', 'totalHouseholdsCount': 1, - 'totalIndividualsCount': 2 + 'totalIndividualsCount': 3 } } ] diff --git a/backend/hct_mis_api/apps/targeting/tests/test_target_query.py b/backend/hct_mis_api/apps/targeting/tests/test_target_query.py index 27f71a684e..dbc596522b 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_target_query.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_target_query.py @@ -128,6 +128,7 @@ def setUpTestData(cls) -> None: cls.user = UserFactory(partner=cls.partner, first_name="Test", last_name="User") user_first = UserFactory(partner=cls.partner, first_name="First", last_name="User") user_second = UserFactory(partner=cls.partner, first_name="Second", last_name="User") + user_third = UserFactory(partner=cls.partner, first_name="Third", last_name="User") targeting_criteria = cls.get_targeting_criteria_for_rule( {"field_name": "size", "arguments": [2], "comparison_method": "EQUALS"} ) @@ -207,7 +208,7 @@ def setUpTestData(cls) -> None: rule_filter.save() cls.target_population_with_pdu_filter = TargetPopulation( name="target_population_with_pdu_filter", - created_by=user_second, + created_by=user_third, targeting_criteria=targeting_criteria, status=TargetPopulation.STATUS_LOCKED, business_area=cls.business_area, From 26a6b9cbf8b9d2f2f131515743d562752143f76b Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Sat, 24 Aug 2024 01:53:13 +0200 Subject: [PATCH 104/118] more tests --- .../tests/snapshots/snap_test_target_query.py | 88 +++++++++++++++++++ .../apps/targeting/tests/test_target_query.py | 66 ++++++++++++++ 2 files changed, 154 insertions(+) diff --git a/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_target_query.py b/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_target_query.py index d36b1f33f0..300dc641e2 100644 --- a/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_target_query.py +++ b/backend/hct_mis_api/apps/targeting/tests/snapshots/snap_test_target_query.py @@ -75,6 +75,18 @@ 'totalHouseholdsCount': 1, 'totalIndividualsCount': 3 } + }, + { + 'node': { + 'createdBy': { + 'firstName': 'Third', + 'lastName': 'User' + }, + 'name': 'target_population_with_individual_filter', + 'status': 'LOCKED', + 'totalHouseholdsCount': 1, + 'totalIndividualsCount': 3 + } } ] } @@ -116,6 +128,14 @@ 'totalHouseholdsCount': 1, 'totalIndividualsCount': 3 } + }, + { + 'node': { + 'name': 'target_population_with_individual_filter', + 'status': 'LOCKED', + 'totalHouseholdsCount': 1, + 'totalIndividualsCount': 3 + } } ] } @@ -177,6 +197,14 @@ 'totalHouseholdsCount': 1, 'totalIndividualsCount': 3 } + }, + { + 'node': { + 'name': 'target_population_with_individual_filter', + 'status': 'LOCKED', + 'totalHouseholdsCount': 1, + 'totalIndividualsCount': 3 + } } ] } @@ -238,6 +266,66 @@ ] } +snapshots['TestTargetPopulationQuery::test_simple_target_query_individual_filter_0_with_permission 1'] = { + 'data': { + 'targetPopulation': { + 'hasEmptyCriteria': False, + 'hasEmptyIdsCriteria': True, + 'name': 'target_population_with_individual_filter', + 'status': 'LOCKED', + 'targetingCriteria': { + 'rules': [ + { + 'filters': [ + ], + 'individualsFiltersBlocks': [ + { + 'individualBlockFilters': [ + { + 'arguments': [ + 'disabled' + ], + 'comparisonMethod': 'EQUALS', + 'fieldAttribute': { + 'labelEn': 'Individual is disabled?', + 'type': 'SELECT_ONE' + }, + 'fieldName': 'disability', + 'flexFieldClassification': 'NOT_FLEX_FIELD', + 'roundNumber': None + } + ] + } + ] + } + ] + }, + 'totalHouseholdsCount': 1, + 'totalIndividualsCount': 3 + } + } +} + +snapshots['TestTargetPopulationQuery::test_simple_target_query_individual_filter_1_without_permission 1'] = { + 'data': { + 'targetPopulation': None + }, + 'errors': [ + { + 'locations': [ + { + 'column': 11, + 'line': 3 + } + ], + 'message': 'Permission Denied', + 'path': [ + 'targetPopulation' + ] + } + ] +} + snapshots['TestTargetPopulationQuery::test_simple_target_query_next_0_with_permission 1'] = { 'data': { 'targetPopulation': { diff --git a/backend/hct_mis_api/apps/targeting/tests/test_target_query.py b/backend/hct_mis_api/apps/targeting/tests/test_target_query.py index dbc596522b..baf2870bb7 100644 --- a/backend/hct_mis_api/apps/targeting/tests/test_target_query.py +++ b/backend/hct_mis_api/apps/targeting/tests/test_target_query.py @@ -12,6 +12,7 @@ ) from hct_mis_api.apps.core.models import BusinessArea, PeriodicFieldData from hct_mis_api.apps.household.fixtures import create_household +from hct_mis_api.apps.household.models import DISABLED from hct_mis_api.apps.periodic_data_update.utils import populate_pdu_with_null_values from hct_mis_api.apps.program.fixtures import ProgramCycleFactory, ProgramFactory from hct_mis_api.apps.program.models import Program @@ -218,6 +219,40 @@ def setUpTestData(cls) -> None: cls.target_population_with_pdu_filter = full_rebuild(cls.target_population_with_pdu_filter) cls.target_population_with_pdu_filter.save() + (household, individuals) = create_household( + {"size": 3, "residence_status": "HOST", "business_area": cls.business_area, "program": cls.program}, + ) + individual = individuals[0] + individual.disability = DISABLED + individual.save() + targeting_criteria = TargetingCriteria() + targeting_criteria.save() + rule = TargetingCriteriaRule(targeting_criteria=targeting_criteria) + rule.save() + individuals_filters_block = TargetingIndividualRuleFilterBlock( + targeting_criteria_rule=rule, target_only_hoh=False + ) + individuals_filters_block.save() + rule_filter = TargetingIndividualBlockRuleFilter( + individuals_filters_block=individuals_filters_block, + comparison_method="EQUALS", + field_name="disability", + arguments=["disabled"], + flex_field_classification=FlexFieldClassification.NOT_FLEX_FIELD, + ) + rule_filter.save() + cls.target_population_with_individual_filter = TargetPopulation( + name="target_population_with_individual_filter", + created_by=user_third, + targeting_criteria=targeting_criteria, + status=TargetPopulation.STATUS_LOCKED, + business_area=cls.business_area, + program=cls.program, + ) + cls.target_population_with_individual_filter.save() + cls.target_population_with_individual_filter = full_rebuild(cls.target_population_with_individual_filter) + cls.target_population_with_individual_filter.save() + @staticmethod def get_targeting_criteria_for_rule(rule_filter: Dict) -> TargetingCriteria: targeting_criteria = TargetingCriteria() @@ -345,6 +380,37 @@ def test_simple_target_query_pdu(self, _: Any, permissions: List[Permissions]) - }, ) + @parameterized.expand( + [ + ( + "with_permission", + [Permissions.TARGETING_VIEW_DETAILS], + ), + ( + "without_permission", + [], + ), + ] + ) + def test_simple_target_query_individual_filter(self, _: Any, permissions: List[Permissions]) -> None: + self.create_user_role_with_permissions(self.user, permissions, self.business_area, self.program) + self.snapshot_graphql_request( + request_string=TestTargetPopulationQuery.TARGET_POPULATION_QUERY, + context={ + "user": self.user, + "headers": { + "Business-Area": self.business_area.slug, + "Program": self.id_to_base64(self.program.id, "ProgramNode"), + }, + }, + variables={ + "id": self.id_to_base64( + self.target_population_with_individual_filter.id, + "TargetPopulationNode", + ) + }, + ) + def test_all_targets_query_filter_by_cycle(self) -> None: self.create_user_role_with_permissions( self.user, [Permissions.TARGETING_VIEW_LIST], self.business_area, self.program From 0462d22a471e5dde3f1500afa9dd2455981d1504 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Mon, 26 Aug 2024 10:57:04 +0200 Subject: [PATCH 105/118] fix targeting tests after cycles --- .../targeting/test_targeting.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/backend/selenium_tests/targeting/test_targeting.py b/backend/selenium_tests/targeting/test_targeting.py index 57d6af982b..b0e05ff912 100644 --- a/backend/selenium_tests/targeting/test_targeting.py +++ b/backend/selenium_tests/targeting/test_targeting.py @@ -65,7 +65,14 @@ def non_sw_program() -> Program: @pytest.fixture def program() -> Program: business_area = create_afghanistan() - return ProgramFactory(name="Test Program", status=Program.ACTIVE, business_area=business_area) + return ProgramFactory( + name="Test Program", + status=Program.ACTIVE, + business_area=business_area, + cycle__title="Cycle In Programme", + cycle__start_date=datetime.now() - relativedelta(days=5), + cycle__end_date=datetime.now() + relativedelta(months=5), + ) @pytest.fixture @@ -447,6 +454,8 @@ def test_create_targeting_with_pdu_string_criteria( pageTargeting.getButtonCreateNew().click() pageTargeting.getButtonCreateNewByFilters().click() assert "New Target Population" in pageTargetingCreate.getTitlePage().text + pageTargetingCreate.getFiltersProgramCycleAutocomplete().click() + pageTargetingCreate.select_listbox_element("Cycle In Programme") pageTargetingCreate.getAddCriteriaButton().click() pageTargetingCreate.getAddIndividualRuleButton().click() pageTargetingCreate.getTargetingCriteriaAutoComplete().click() @@ -490,6 +499,8 @@ def test_create_targeting_with_pdu_bool_criteria( pageTargeting.getButtonCreateNew().click() pageTargeting.getButtonCreateNewByFilters().click() assert "New Target Population" in pageTargetingCreate.getTitlePage().text + pageTargetingCreate.getFiltersProgramCycleAutocomplete().click() + pageTargetingCreate.select_listbox_element("Cycle In Programme") pageTargetingCreate.getAddCriteriaButton().click() pageTargetingCreate.getAddIndividualRuleButton().click() pageTargetingCreate.getTargetingCriteriaAutoComplete().click() @@ -555,6 +566,8 @@ def test_create_targeting_with_pdu_decimal_criteria( pageTargeting.getButtonCreateNew().click() pageTargeting.getButtonCreateNewByFilters().click() assert "New Target Population" in pageTargetingCreate.getTitlePage().text + pageTargetingCreate.getFiltersProgramCycleAutocomplete().click() + pageTargetingCreate.select_listbox_element("Cycle In Programme") pageTargetingCreate.getAddCriteriaButton().click() pageTargetingCreate.getAddIndividualRuleButton().click() pageTargetingCreate.getTargetingCriteriaAutoComplete().click() @@ -618,6 +631,8 @@ def test_create_targeting_with_pdu_date_criteria( pageTargeting.getButtonCreateNew().click() pageTargeting.getButtonCreateNewByFilters().click() assert "New Target Population" in pageTargetingCreate.getTitlePage().text + pageTargetingCreate.getFiltersProgramCycleAutocomplete().click() + pageTargetingCreate.select_listbox_element("Cycle In Programme") pageTargetingCreate.getAddCriteriaButton().click() pageTargetingCreate.getAddIndividualRuleButton().click() pageTargetingCreate.getTargetingCriteriaAutoComplete().click() @@ -664,6 +679,8 @@ def test_create_targeting_with_pdu_null_criteria( pageTargeting.getButtonCreateNew().click() pageTargeting.getButtonCreateNewByFilters().click() assert "New Target Population" in pageTargetingCreate.getTitlePage().text + pageTargetingCreate.getFiltersProgramCycleAutocomplete().click() + pageTargetingCreate.select_listbox_element("Cycle In Programme") pageTargetingCreate.getAddCriteriaButton().click() pageTargetingCreate.getAddIndividualRuleButton().click() pageTargetingCreate.getTargetingCriteriaAutoComplete().click() From e79542a84d2f5c0d4f96ad70b20127947a2380c0 Mon Sep 17 00:00:00 2001 From: Domenico Date: Mon, 26 Aug 2024 11:06:24 +0200 Subject: [PATCH 106/118] tweak fc admin (#4160) --- backend/hct_mis_api/apps/erp_datahub/admin.py | 1 + backend/hct_mis_api/apps/mis_datahub/admin.py | 1 + 2 files changed, 2 insertions(+) diff --git a/backend/hct_mis_api/apps/erp_datahub/admin.py b/backend/hct_mis_api/apps/erp_datahub/admin.py index 6bb0eaa3ba..4fb63f9094 100644 --- a/backend/hct_mis_api/apps/erp_datahub/admin.py +++ b/backend/hct_mis_api/apps/erp_datahub/admin.py @@ -125,6 +125,7 @@ class FundsCommitmentAdmin(HOPEModelAdminBase): ) date_hierarchy = "create_date" form = FundsCommitmentAddForm + search_fields = ("rec_serial_number", "vendor_id", "wbs_element", "funds_commitment_number") @atomic(using="cash_assist_datahub_erp") @atomic(using="default") diff --git a/backend/hct_mis_api/apps/mis_datahub/admin.py b/backend/hct_mis_api/apps/mis_datahub/admin.py index 481766153c..cabbf6d099 100644 --- a/backend/hct_mis_api/apps/mis_datahub/admin.py +++ b/backend/hct_mis_api/apps/mis_datahub/admin.py @@ -129,6 +129,7 @@ def household(self, button: button) -> Optional[str]: @admin.register(FundsCommitment) class FundsCommitmentAdmin(HUBAdminMixin): filters = (BusinessAreaFilter,) + search_fields = ("rec_serial_number", "vendor_id", "wbs_element", "funds_commitment_number") @admin.register(DownPayment) From 5c087f222f94cb892dae8b1e9aaa63d12485753e Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Mon, 26 Aug 2024 12:09:29 +0200 Subject: [PATCH 107/118] upd age --- backend/selenium_tests/programme_population/test_individuals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/selenium_tests/programme_population/test_individuals.py b/backend/selenium_tests/programme_population/test_individuals.py index 526944d5ba..8173b98171 100644 --- a/backend/selenium_tests/programme_population/test_individuals.py +++ b/backend/selenium_tests/programme_population/test_individuals.py @@ -56,7 +56,7 @@ def test_smoke_page_individuals_details( assert "-" in pageIndividualsDetails.getLabelMiddleName().text assert "Kowalska" in pageIndividualsDetails.getLabelFamilyName().text assert "Female" in pageIndividualsDetails.getLabelGender().text - assert "82" in pageIndividualsDetails.getLabelAge().text + assert "83" in pageIndividualsDetails.getLabelAge().text assert "26 Aug 1941" in pageIndividualsDetails.getLabelDateOfBirth().text assert "No" in pageIndividualsDetails.getLabelEstimatedDateOfBirth().text assert "Married" in pageIndividualsDetails.getLabelMaritalStatus().text From 6c51ed26859254cc109be18612c51b3a86f84ae9 Mon Sep 17 00:00:00 2001 From: Szymon Wyderka Date: Mon, 26 Aug 2024 14:02:30 +0200 Subject: [PATCH 108/118] black --- .../program_details/test_program_details.py | 56 ++++++++++++++++--- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/backend/selenium_tests/program_details/test_program_details.py b/backend/selenium_tests/program_details/test_program_details.py index 5a181a234e..d7dd37c11c 100644 --- a/backend/selenium_tests/program_details/test_program_details.py +++ b/backend/selenium_tests/program_details/test_program_details.py @@ -1,4 +1,5 @@ from datetime import datetime +from decimal import Decimal from time import sleep from django.conf import settings @@ -17,6 +18,7 @@ from hct_mis_api.apps.geo.models import Area from hct_mis_api.apps.household.fixtures import create_household from hct_mis_api.apps.household.models import Household +from hct_mis_api.apps.payment.fixtures import PaymentPlanFactory from hct_mis_api.apps.payment.models import PaymentPlan from hct_mis_api.apps.program.fixtures import ProgramCycleFactory, ProgramFactory from hct_mis_api.apps.program.models import Program, ProgramCycle @@ -89,6 +91,7 @@ def get_program_with_dct_type_and_name( end_date=datetime.now() + relativedelta(months=1), data_collecting_type=dct, status=status, + budget=100, cycle__status=program_cycle_status, cycle__start_date=cycle_start_date, cycle__end_date=cycle_end_date, @@ -160,6 +163,14 @@ def get_program_without_cycle_end_date( cycle__start_date=cycle_start_date, cycle__end_date=None, ) + program_cycle = ProgramCycle.objects.get(program=program) + PaymentPlanFactory( + program=program, + program_cycle=program_cycle, + total_entitled_quantity_usd=Decimal(1234.99), + total_delivered_quantity_usd=Decimal(50.01), + total_undelivered_quantity_usd=Decimal(1184.98), + ) return program @@ -526,7 +537,12 @@ def test_program_details_delete_programme_cycle( assert 2 == len(pageProgrammeDetails.getProgramCycleTitle()) assert program_cycle_1 in pageProgrammeDetails.getProgramCycleTitle()[0].text - assert program_cycle_3 in pageProgrammeDetails.getProgramCycleTitle()[1].text + for _ in range(50): + if program_cycle_3 in pageProgrammeDetails.getProgramCycleTitle()[1].text: + break + sleep(0.1) + else: + assert program_cycle_3 in pageProgrammeDetails.getProgramCycleTitle()[1].text def test_program_details_buttons_vs_programme_cycle_status( self, program_with_different_cycles: Program, pageProgrammeDetails: ProgrammeDetails @@ -693,7 +709,7 @@ def test_program_details_edit_cycle_with_wrong_date( pageProgrammeDetails.getEndDateCycle().send_keys((datetime.now() + relativedelta(days=12)).strftime("%Y-%m-%d")) pageProgrammeDetails.getButtonSave().click() - # ToDo: Lack of information about wrong date + # ToDo: Lack of information about wrong date 212579 # for _ in range(50): # if "Programme Cycles' timeframes must not overlap with the provided start date." in pageProgrammeDetails.getStartDateCycleDiv().text: # break @@ -724,17 +740,39 @@ def test_program_details_edit_cycle_with_wrong_date( ) in pageProgrammeDetails.getProgramCycleEndDate()[1].text assert "New cycle with wrong date" in pageProgrammeDetails.getProgramCycleTitle()[1].text + @pytest.mark.skip("Unskip after fix: 212581") def test_edit_program_details_with_wrong_date( - self, standard_active_program: Program, pageProgrammeDetails: ProgrammeDetails + self, + program_with_different_cycles: Program, + pageProgrammeDetails: ProgrammeDetails, + pageProgrammeManagement: ProgrammeManagement, ) -> None: - pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") - # end date program vs end date cycle - # start date program vs start date cycle + pageProgrammeDetails.selectGlobalProgramFilter("ThreeCyclesProgramme") + assert "ACTIVE" in pageProgrammeDetails.getProgramStatus().text + pageProgrammeDetails.getButtonEditProgram().click() + pageProgrammeManagement.getInputProgrammeName() + pageProgrammeManagement.getInputStartDate().click() + pageProgrammeManagement.getInputStartDate().send_keys(Keys.CONTROL + "a") + pageProgrammeManagement.getInputStartDate().send_keys(str(FormatTime(1, 1, 2022).numerically_formatted_date)) + pageProgrammeManagement.getInputEndDate().click() + pageProgrammeManagement.getInputEndDate().send_keys(Keys.CONTROL + "a") + pageProgrammeManagement.getInputEndDate().send_keys(FormatTime(1, 10, 2022).numerically_formatted_date) + pageProgrammeManagement.getButtonNext().click() + pageProgrammeManagement.getButtonAddTimeSeriesField() + pageProgrammeManagement.getButtonNext().click() + programme_creation_url = pageProgrammeDetails.driver.current_url + pageProgrammeManagement.getAccessToProgram().click() + pageProgrammeManagement.selectWhoAccessToProgram("None of the partners should have access") + pageProgrammeManagement.getButtonSave().click() + # Check Details page + with pytest.raises(Exception): + assert "details" in pageProgrammeDetails.wait_for_new_url(programme_creation_url).split("/") def test_program_details_program_cycle_total_quantities( self, standard_program_with_draft_programme_cycle: Program, pageProgrammeDetails: ProgrammeDetails ) -> None: pageProgrammeDetails.selectGlobalProgramFilter("Active Programme") - # Total Entitled Quantity - # Total Undelivered Quantity - # Total Delivered Quantity + assert "ACTIVE" in pageProgrammeDetails.getProgramStatus().text + assert "1234.99" in pageProgrammeDetails.getProgramCycleTotalEntitledQuantity()[0].text + assert "1184.98" in pageProgrammeDetails.getProgramCycleTotalUndeliveredQuantity()[0].text + assert "50.01" in pageProgrammeDetails.getProgramCycleTotalDeliveredQuantity()[0].text From d83d2d136b4c8cbcbbf49a7ed5802041c2ed5f65 Mon Sep 17 00:00:00 2001 From: Patryk Dabrowski Date: Mon, 26 Aug 2024 14:42:02 +0200 Subject: [PATCH 109/118] Make check-box "Pull Pictures" automatically checked when importing from Kobo (#4163) --- .../src/components/rdi/create/kobo/CreateImportFromKoboForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/rdi/create/kobo/CreateImportFromKoboForm.tsx b/frontend/src/components/rdi/create/kobo/CreateImportFromKoboForm.tsx index 6e4fefb217..98c9f2adaf 100644 --- a/frontend/src/components/rdi/create/kobo/CreateImportFromKoboForm.tsx +++ b/frontend/src/components/rdi/create/kobo/CreateImportFromKoboForm.tsx @@ -78,7 +78,7 @@ export function CreateImportFromKoboForm({ onlyActiveSubmissions: true, screenBeneficiary: false, allowDeliveryMechanismsValidationErrors: false, - pullPictures: false, + pullPictures: true, }, validationSchema, onSubmit, From 297bd45b8ad8bbf75c70f2334461d8ea67bc676c Mon Sep 17 00:00:00 2001 From: Szymon Wyderka Date: Mon, 26 Aug 2024 14:49:03 +0200 Subject: [PATCH 110/118] fix test_grievance_tickets_create_new_tickets_Grievance_Complaint_Payment_Related_Complaint --- .../grievance/grievance_tickets/test_grievance_tickets.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/selenium_tests/grievance/grievance_tickets/test_grievance_tickets.py b/backend/selenium_tests/grievance/grievance_tickets/test_grievance_tickets.py index 5de95d13ce..8e4c60ab62 100644 --- a/backend/selenium_tests/grievance/grievance_tickets/test_grievance_tickets.py +++ b/backend/selenium_tests/grievance/grievance_tickets/test_grievance_tickets.py @@ -785,6 +785,7 @@ def test_grievance_tickets_create_new_tickets_Grievance_Complaint_Partner_Relate pageGrievanceNewTicket.getButtonNext().click() assert "UNICEF" in pageGrievanceDetailsPage.getLabelPartner().text + @pytest.mark.skip("Unskip after fix: 212619") def test_grievance_tickets_create_new_tickets_Grievance_Complaint_Payment_Related_Complaint( self, pageGrievanceTickets: GrievanceTickets, @@ -792,6 +793,7 @@ def test_grievance_tickets_create_new_tickets_Grievance_Complaint_Payment_Relate pageGrievanceDetailsPage: GrievanceDetailsPage, hh_with_payment_record: PaymentRecord, ) -> None: + payment_id = PaymentRecord.objects.first().unicef_id pageGrievanceTickets.getNavGrievance().click() assert "Grievance Tickets" in pageGrievanceTickets.getGrievanceTitle().text pageGrievanceTickets.getButtonNewTicket().click() @@ -812,6 +814,9 @@ def test_grievance_tickets_create_new_tickets_Grievance_Complaint_Payment_Relate pageGrievanceNewTicket.getCheckboxSelectAll().click() pageGrievanceNewTicket.getButtonSubmit().click() assert hh_with_payment_record.unicef_id in pageGrievanceDetailsPage.getPaymentRecord().text + pageGrievanceNewTicket.getButtonNext().click() + # ToDo check before unskip + assert payment_id in pageGrievanceDetailsPage.getLabelTickets().text def test_grievance_tickets_look_up_linked_ticket( self, From a68a88cb7230faaeb03e18ada6736ceda4c3d55b Mon Sep 17 00:00:00 2001 From: Szymon Wyderka Date: Mon, 26 Aug 2024 15:07:38 +0200 Subject: [PATCH 111/118] ToDo test_payment_verification_create_grievance_ticket_same_value --- .../test_payment_verification.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/backend/selenium_tests/payment_verification/test_payment_verification.py b/backend/selenium_tests/payment_verification/test_payment_verification.py index 37d7467dc7..125fe326e6 100644 --- a/backend/selenium_tests/payment_verification/test_payment_verification.py +++ b/backend/selenium_tests/payment_verification/test_payment_verification.py @@ -273,3 +273,16 @@ def test_happy_path_payment_verification( pagePaymentRecord.getArrowBack().click() assert "FINISHED" in pagePaymentVerification.getCashPlanTableRow().text + + +@pytest.mark.usefixtures("login") +class TestPaymentVerification: + @pytest.mark.skip("ToDo: Old and same value - maybe parametrization with values") + def test_payment_verification_create_grievance_ticket_same_value( + self, active_program: Program, add_payment_verification: PV, pagePaymentVerification: PaymentVerification + ) -> None: + pagePaymentVerification.selectGlobalProgramFilter("Active Program") + # Upon resolving the Payment Verification grievance ticket, + # the received value changes with the new verified value. + # If the received value is 0, it should stay 0 even when a new verified value is provided in the ticket. + # Check conversation with Jakub From 0c2ec2207933a6f436d5c50b0e616d73aba27305 Mon Sep 17 00:00:00 2001 From: Pavlo Mokiichuk Date: Mon, 26 Aug 2024 16:10:56 +0200 Subject: [PATCH 112/118] Fix e2e test (#4162) * fix e2e test * Added freeze time to test test_smoke_page_individuals_details --------- Co-authored-by: Szymon Wyderka Co-authored-by: szymon-kellton <130459593+szymon-kellton@users.noreply.github.com> --- backend/selenium_tests/programme_population/test_individuals.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/selenium_tests/programme_population/test_individuals.py b/backend/selenium_tests/programme_population/test_individuals.py index 8173b98171..f19cdbcfce 100644 --- a/backend/selenium_tests/programme_population/test_individuals.py +++ b/backend/selenium_tests/programme_population/test_individuals.py @@ -2,6 +2,7 @@ from django.core.management import call_command import pytest +from freezegun import freeze_time from page_object.programme_population.individuals import Individuals from page_object.programme_population.individuals_details import IndividualsDetails @@ -40,6 +41,7 @@ def test_smoke_page_individuals( assert "Administrative Level 2" in pageIndividuals.getIndividualLocation().text assert 6 == len(pageIndividuals.getIndividualTableRow()) + @freeze_time("2024-08-26") def test_smoke_page_individuals_details( self, create_programs: None, From bea7250d2acfbe119e849f4693fad74eebbc00cf Mon Sep 17 00:00:00 2001 From: Szymon Wyderka Date: Mon, 26 Aug 2024 22:15:50 +0200 Subject: [PATCH 113/118] Fix instabilities in test_program_details_add_new_cycle_with_wrong_date --- .../program_details/test_program_details.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/backend/selenium_tests/program_details/test_program_details.py b/backend/selenium_tests/program_details/test_program_details.py index d7dd37c11c..85eefbe915 100644 --- a/backend/selenium_tests/program_details/test_program_details.py +++ b/backend/selenium_tests/program_details/test_program_details.py @@ -622,8 +622,9 @@ def test_program_details_add_new_cycle_with_wrong_date( assert "Start Date cannot be before Programme Start Date" in pageProgrammeDetails.getStartDateCycleDiv().text pageProgrammeDetails.getStartDateCycle().click() + pageProgrammeDetails.getStartDateCycle().send_keys(Keys.CONTROL + "a") pageProgrammeDetails.getStartDateCycle().send_keys( - (datetime.now() - relativedelta(days=24)).strftime("%Y-%m-%d") + (datetime.now() - relativedelta(days=1)).strftime("%Y-%m-%d") ) pageProgrammeDetails.getEndDateCycle().click() pageProgrammeDetails.getEndDateCycle().send_keys( @@ -643,14 +644,14 @@ def test_program_details_add_new_cycle_with_wrong_date( for _ in range(50): if ( - "Programme Cycles' timeframes must not overlap with the provided start date." - in pageProgrammeDetails.getStartDateCycleDiv().text + "Start date must be after the latest cycle." + in pageProgrammeDetails.getStartDateCycleDiv().text ): break sleep(0.1) assert ( - "Programme Cycles' timeframes must not overlap with the provided start date." - in pageProgrammeDetails.getStartDateCycleDiv().text + "Start date must be after the latest cycle." + in pageProgrammeDetails.getStartDateCycleDiv().text ) pageProgrammeDetails.getStartDateCycle().click() pageProgrammeDetails.getStartDateCycle().send_keys( @@ -661,6 +662,11 @@ def test_program_details_add_new_cycle_with_wrong_date( pageProgrammeDetails.getButtonAddNewProgrammeCycle() pageProgrammeDetails.getProgramCycleRow() + for _ in range(50): + if 2 == len(pageProgrammeDetails.getProgramCycleStatus()): + break + sleep(0.1) + assert "Draft" in pageProgrammeDetails.getProgramCycleStatus()[1].text assert (datetime.now() + relativedelta(days=1)).strftime( "%-d %b %Y" From be93407ebaabd745aa75b2ceaa63f7edbd20b859 Mon Sep 17 00:00:00 2001 From: Szymon Wyderka Date: Mon, 26 Aug 2024 22:20:23 +0200 Subject: [PATCH 114/118] Fix instabilities in test_program_details_add_new_cycle_with_wrong_date --- .../program_details/test_program_details.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/backend/selenium_tests/program_details/test_program_details.py b/backend/selenium_tests/program_details/test_program_details.py index 85eefbe915..62ce0d10bf 100644 --- a/backend/selenium_tests/program_details/test_program_details.py +++ b/backend/selenium_tests/program_details/test_program_details.py @@ -643,16 +643,10 @@ def test_program_details_add_new_cycle_with_wrong_date( pageProgrammeDetails.getButtonCreateProgramCycle().click() for _ in range(50): - if ( - "Start date must be after the latest cycle." - in pageProgrammeDetails.getStartDateCycleDiv().text - ): + if "Start date must be after the latest cycle." in pageProgrammeDetails.getStartDateCycleDiv().text: break sleep(0.1) - assert ( - "Start date must be after the latest cycle." - in pageProgrammeDetails.getStartDateCycleDiv().text - ) + assert "Start date must be after the latest cycle." in pageProgrammeDetails.getStartDateCycleDiv().text pageProgrammeDetails.getStartDateCycle().click() pageProgrammeDetails.getStartDateCycle().send_keys( (datetime.now() + relativedelta(days=1)).strftime("%Y-%m-%d") From 7c3de036bd58c78f1b2e5ed1d19036089bc1073a Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Tue, 27 Aug 2024 04:46:03 +0200 Subject: [PATCH 115/118] add test cov --- .../apps/household/tests/test_tasks.py | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 backend/hct_mis_api/apps/household/tests/test_tasks.py diff --git a/backend/hct_mis_api/apps/household/tests/test_tasks.py b/backend/hct_mis_api/apps/household/tests/test_tasks.py new file mode 100644 index 0000000000..b9d41e3b6f --- /dev/null +++ b/backend/hct_mis_api/apps/household/tests/test_tasks.py @@ -0,0 +1,120 @@ +from django.test import TestCase + +from hct_mis_api.apps.core.fixtures import create_afghanistan +from hct_mis_api.apps.household.celery_tasks import enroll_households_to_program_task +from hct_mis_api.apps.household.fixtures import ( + BankAccountInfoFactory, + DocumentFactory, + IndividualIdentityFactory, + IndividualRoleInHouseholdFactory, + create_household_and_individuals, +) +from hct_mis_api.apps.household.models import ROLE_PRIMARY, Household +from hct_mis_api.apps.program.fixtures import ProgramFactory +from hct_mis_api.apps.program.models import Program + + +class TestEnrollHouseholdsToProgramTask(TestCase): + @classmethod + def setUpTestData(cls) -> None: + cls.business_area = create_afghanistan() + cls.program_source = ProgramFactory( + status=Program.ACTIVE, name="Program source", business_area=cls.business_area + ) + cls.program_target = ProgramFactory( + status=Program.ACTIVE, name="Program target", business_area=cls.business_area + ) + + cls.household1, individuals1 = create_household_and_individuals( + household_data={ + "business_area": cls.business_area, + "program": cls.program_source, + }, + individuals_data=[ + { + "business_area": cls.business_area, + "program": cls.program_source, + }, + ], + ) + cls.individual = individuals1[0] + cls.individual_role_in_household1 = IndividualRoleInHouseholdFactory( + individual=cls.individual, + household=cls.household1, + role=ROLE_PRIMARY, + ) + cls.document1 = DocumentFactory(individual=cls.individual, program=cls.individual.program) + cls.individual_identity1 = IndividualIdentityFactory(individual=cls.individual) + cls.bank_account_info1 = BankAccountInfoFactory(individual=cls.individual) + cls.individual.individual_collection = None + cls.individual.save() + cls.household1.household_collection = None + cls.household1.save() + + # already existing hh in the target program + cls.household2, individuals2 = create_household_and_individuals( + household_data={ + "business_area": cls.business_area, + "program": cls.program_source, + }, + individuals_data=[ + { + "business_area": cls.business_area, + "program": cls.program_source, + }, + ], + ) + cls.household2_repr_in_target_program, individuals2_repr = create_household_and_individuals( + household_data={ + "business_area": cls.business_area, + "program": cls.program_target, + "unicef_id": cls.household2.unicef_id, + "household_collection": cls.household2.household_collection, + }, + individuals_data=[ + { + "business_area": cls.business_area, + "program": cls.program_target, + "unicef_id": cls.household2.individuals.first().unicef_id, + "individual_collection": cls.household2.individuals.first().individual_collection, + }, + ], + ) + cls.individual2_repr_in_target_program = individuals2_repr[0] + + def test_enroll_households_to_program_task(self) -> None: + self.assertEqual(self.program_target.household_set.count(), 1) + self.assertEqual(self.program_target.individuals.count(), 1) + self.assertEqual(self.program_source.household_set.count(), 2) + self.assertEqual(self.program_source.individuals.count(), 2) + + self.assertIsNone(self.household1.household_collection) + + self.assertEqual(Household.objects.filter(unicef_id=self.household1.unicef_id).count(), 1) + self.assertEqual(Household.objects.filter(unicef_id=self.household2.unicef_id).count(), 2) + + enroll_households_to_program_task( + households_ids=[self.household1.id, self.household2.id], program_for_enroll_id=str(self.program_target.id) + ) + self.household1.refresh_from_db() + self.household2.refresh_from_db() + + self.assertEqual(self.program_target.household_set.count(), 2) + self.assertEqual(self.program_target.individuals.count(), 2) + self.assertEqual(self.program_source.household_set.count(), 2) + self.assertEqual(self.program_source.individuals.count(), 2) + + self.assertIsNotNone(self.household1.household_collection) + + self.assertEqual(Household.objects.filter(unicef_id=self.household1.unicef_id).count(), 2) + self.assertEqual(Household.objects.filter(unicef_id=self.household2.unicef_id).count(), 2) + enrolled_household = Household.objects.filter( + program=self.program_target, unicef_id=self.household1.unicef_id + ).first() + self.assertEqual( + enrolled_household.individuals_and_roles.filter(role=ROLE_PRIMARY).first().individual.unicef_id, + self.individual.unicef_id, + ) + self.assertEqual(enrolled_household.individuals.first().documents.count(), 1) + self.assertEqual(enrolled_household.individuals.first().identities.count(), 1) + self.assertEqual(enrolled_household.individuals.first().bank_account_info.count(), 1) From ecaf3e0b8d42431af82a2a9b34cc8a64810937d0 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Tue, 27 Aug 2024 10:36:52 +0200 Subject: [PATCH 116/118] add migration --- .../apps/core/migrations/0086_migration.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 backend/hct_mis_api/apps/core/migrations/0086_migration.py diff --git a/backend/hct_mis_api/apps/core/migrations/0086_migration.py b/backend/hct_mis_api/apps/core/migrations/0086_migration.py new file mode 100644 index 0000000000..0143413d96 --- /dev/null +++ b/backend/hct_mis_api/apps/core/migrations/0086_migration.py @@ -0,0 +1,15 @@ +from django.db import migrations + +def migrate_subtype_boolean_to_bool(apps, schema_editor): + PeriodicFieldData = apps.get_model('core', 'PeriodicFieldData') + PeriodicFieldData.objects.filter(subtype='BOOLEAN').update(subtype='BOOL') + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0085_migration'), + ] + + operations = [ + migrations.RunPython(migrate_subtype_boolean_to_bool, reverse_code=migrations.RunPython.noop), + ] From 7c2ae875728034718bc1dcbe2ecb7681afcf2eb1 Mon Sep 17 00:00:00 2001 From: Paulina Kujawa Date: Tue, 27 Aug 2024 10:50:22 +0200 Subject: [PATCH 117/118] resolve potential instability of e2e --- backend/selenium_tests/targeting/test_targeting.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/backend/selenium_tests/targeting/test_targeting.py b/backend/selenium_tests/targeting/test_targeting.py index c20a9f983f..0f1720434b 100644 --- a/backend/selenium_tests/targeting/test_targeting.py +++ b/backend/selenium_tests/targeting/test_targeting.py @@ -606,8 +606,14 @@ def test_create_targeting_with_pdu_decimal_criteria( pageTargetingDetails.getLockButton() assert pageTargetingDetails.getCriteriaContainer().text == bool_no_expected_criteria_text - assert pageTargetingDetails.getHouseholdTableCell(1, 1).text == individual1.household.unicef_id - assert pageTargetingDetails.getHouseholdTableCell(2, 1).text == individual2.household.unicef_id + assert pageTargetingDetails.getHouseholdTableCell(1, 1).text in [ + individual1.household.unicef_id, + individual2.household.unicef_id, + ] + assert pageTargetingDetails.getHouseholdTableCell(2, 1).text in [ + individual1.household.unicef_id, + individual2.household.unicef_id, + ] assert pageTargetingCreate.getTotalNumberOfHouseholdsCount().text == "2" assert len(pageTargetingDetails.getHouseholdTableRows()) == 2 From 0b67845e5acf332bb65e60115269bf86f4e600c6 Mon Sep 17 00:00:00 2001 From: Jan Romaniak Date: Tue, 27 Aug 2024 12:42:45 +0200 Subject: [PATCH 118/118] version 2.11.0 --- backend/pyproject.toml | 2 +- frontend/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/pyproject.toml b/backend/pyproject.toml index da829ba101..e52d78d750 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -130,7 +130,7 @@ dev = [ includes = [] [project] name = "hope" -version = "2.10.1" +version = "2.11.0" description = "HCT MIS is UNICEF's humanitarian cash transfer platform." authors = [ {name = "Tivix"}, diff --git a/frontend/package.json b/frontend/package.json index 9efbe4e394..e76f5b9665 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "frontend", - "version": "2.10.1", + "version": "2.11.0", "private": true, "type": "module", "scripts": {