diff --git a/src/hct_mis_api/apps/grievance/filters.py b/src/hct_mis_api/apps/grievance/filters.py index 0ab89f6892..49aa633b8d 100644 --- a/src/hct_mis_api/apps/grievance/filters.py +++ b/src/hct_mis_api/apps/grievance/filters.py @@ -192,7 +192,7 @@ def search_filter(self, qs: QuerySet, name: str, value: str) -> QuerySet: Individual.objects.filter(relationship=HEAD) .filter( Q(full_name__icontains=search) - | Q(registration_id__icontains=search) + | Q(detail_id__icontains=search) | Q(program_registration_id__icontains=search) | Q(phone_no__icontains=search) | Q(phone_no_alternative__icontains=search) diff --git a/src/hct_mis_api/apps/household/documents.py b/src/hct_mis_api/apps/household/documents.py index a590fa3919..7f99bad2c2 100644 --- a/src/hct_mis_api/apps/household/documents.py +++ b/src/hct_mis_api/apps/household/documents.py @@ -67,7 +67,7 @@ class IndividualDocument(Document): } ) program_id = fields.KeywordField(attr="program.id") - registration_id = fields.TextField() + detail_id = fields.TextField() program_registration_id = fields.TextField() bank_account_info = fields.ObjectField(properties={"bank_account_number": fields.TextField()}) registration_data_import_id = fields.KeywordField(attr="registration_data_import.id") @@ -191,7 +191,7 @@ class HouseholdDocument(Document): admin2 = fields.TextField(index_prefixes={"min_chars": 1, "max_chars": 10}) business_area = fields.KeywordField(similarity="boolean") program_id = fields.KeywordField(attr="program.id") - registration_id = fields.TextField() + detail_id = fields.TextField() program_registration_id = fields.TextField() def prepare_admin1(self, household: Household) -> Optional[str]: diff --git a/src/hct_mis_api/apps/household/filters.py b/src/hct_mis_api/apps/household/filters.py index c65fda3424..a4eae24bf0 100644 --- a/src/hct_mis_api/apps/household/filters.py +++ b/src/hct_mis_api/apps/household/filters.py @@ -188,7 +188,7 @@ def _get_elasticsearch_query_for_households(self, search: str) -> Dict: "head_of_household.bank_account_info.bank_account_number": {"query": search} } }, - {"match_phrase_prefix": {"registration_id": {"query": search}}}, + {"match_phrase_prefix": {"detail_id": {"query": search}}}, {"match_phrase_prefix": {"program_registration_id": {"query": search}}}, ], } @@ -222,12 +222,12 @@ def _search_db(self, qs: QuerySet[Household], value: str) -> QuerySet[Household] Q(head_of_household__phone_no__icontains=search) | Q(head_of_household__phone_no_alternative__icontains=search) ) - if search_type == "registration_id": + if search_type == "detail_id": try: int(search) except ValueError: raise SearchException("The search value for a given search type should be a number") - return qs.filter(registration_id__istartswith=search) + return qs.filter(detail_id__istartswith=search) if search_type == "kobo_asset_id": inner_query = Q() split_values_list = search.split(" ") @@ -373,7 +373,7 @@ def _get_elasticsearch_query_for_individuals(self, search: str) -> Dict: {"match_phrase_prefix": {"full_name": {"query": search}}}, {"match_phrase_prefix": {"phone_no_text": {"query": search}}}, {"match_phrase_prefix": {"phone_no_alternative_text": {"query": search}}}, - {"match_phrase_prefix": {"registration_id": {"query": search}}}, + {"match_phrase_prefix": {"detail_id": {"query": search}}}, {"match_phrase_prefix": {"program_registration_id": {"query": search}}}, {"match_phrase_prefix": {"bank_account_info.bank_account_number": {"query": search}}}, ], @@ -401,12 +401,12 @@ def _search_db(self, qs: QuerySet[Individual], value: str) -> QuerySet[Individua return qs.filter(full_name__icontains=search) if search_type == "phone_no": return qs.filter(Q(phone_no__icontains=search) | Q(phone_no_alternative__icontains=search)) - if search_type == "registration_id": + if search_type == "detail_id": try: int(search) except ValueError: raise SearchException("The search value for a given search type should be a number") - return qs.filter(registration_id__icontains=search) + return qs.filter(detail_id__icontains=search) if search_type == "bank_account_number": return qs.filter(bank_account_info__bank_account_number__icontains=search) if DocumentType.objects.filter(key=search_type).exists(): diff --git a/src/hct_mis_api/apps/household/migrations/0014_migration.py b/src/hct_mis_api/apps/household/migrations/0014_migration.py new file mode 100644 index 0000000000..4dba207ba5 --- /dev/null +++ b/src/hct_mis_api/apps/household/migrations/0014_migration.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.25 on 2025-02-20 13:34 + +from django.db import migrations, models + +def migrate_registration_id_to_detail_id(apps, schema_editor): + PendingHousehold = apps.get_model("household", "PendingHousehold") + PendingIndividual = apps.get_model("household", "PendingIndividual") + Household = apps.get_model("household", "Household") + Individual = apps.get_model("household", "Individual") + + PendingIndividual.objects.filter(registration_id__isnull=False, detail_id__isnull=True).update( + detail_id=models.F("registration_id")) + PendingHousehold.objects.filter(registration_id__isnull=False, detail_id__isnull=True).update( + detail_id=models.F("registration_id")) + Individual.objects.filter(registration_id__isnull=False, detail_id__isnull=True).update( + detail_id=models.F("registration_id")) + Household.objects.filter(registration_id__isnull=False, detail_id__isnull=True).update( + detail_id=models.F("registration_id")) + + +class Migration(migrations.Migration): + + dependencies = [ + ('household', '0013_migration'), + ] + + operations = [ + migrations.RunPython(migrate_registration_id_to_detail_id, reverse_code=migrations.RunPython.noop), + ] diff --git a/src/hct_mis_api/apps/household/migrations/0015_migration.py b/src/hct_mis_api/apps/household/migrations/0015_migration.py new file mode 100644 index 0000000000..70ce7eac82 --- /dev/null +++ b/src/hct_mis_api/apps/household/migrations/0015_migration.py @@ -0,0 +1,31 @@ +# Generated by Django 3.2.25 on 2025-02-20 13:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('household', '0014_migration'), + ] + + operations = [ + migrations.RemoveField( + model_name='household', + name='registration_id', + ), + migrations.RemoveField( + model_name='individual', + name='registration_id', + ), + migrations.AlterField( + model_name='household', + name='detail_id', + field=models.CharField(blank=True, help_text='Kobo asset ID, Xlsx row ID, Aurora registration ID', max_length=150, null=True), + ), + migrations.AlterField( + model_name='individual', + name='detail_id', + field=models.CharField(blank=True, help_text='Kobo asset ID, Xlsx row ID, Aurora registration ID', max_length=150, null=True), + ), + ] diff --git a/src/hct_mis_api/apps/household/models.py b/src/hct_mis_api/apps/household/models.py index ffbd7caf30..d98684006a 100644 --- a/src/hct_mis_api/apps/household/models.py +++ b/src/hct_mis_api/apps/household/models.py @@ -401,7 +401,6 @@ class CollectType(models.TextChoices): "currency", "unhcr_id", "detail_id", - "registration_id", "program_registration_id", ] ) @@ -496,14 +495,7 @@ class CollectType(models.TextChoices): currency = models.CharField(max_length=250, choices=CURRENCY_CHOICES, default=BLANK) unhcr_id = models.CharField(max_length=250, blank=True, default=BLANK, db_index=True) detail_id = models.CharField( - max_length=150, blank=True, null=True, help_text="Kobo asset ID, Xlsx row ID, Aurora source ID" - ) - registration_id = CICharField( - max_length=100, - blank=True, - null=True, - db_index=True, - verbose_name=_("Aurora Registration Id"), + max_length=150, blank=True, null=True, help_text="Kobo asset ID, Xlsx row ID, Aurora registration ID" ) program_registration_id = CICharField( max_length=100, @@ -896,7 +888,6 @@ class Individual( "who_answers_phone", "who_answers_alt_phone", "detail_id", - "registration_id", "program_registration_id", "payment_delivery_phone_no", ] @@ -1013,13 +1004,7 @@ class Individual( fchild_hoh = models.BooleanField(default=False) child_hoh = models.BooleanField(default=False) detail_id = models.CharField( - max_length=150, blank=True, null=True, help_text="Kobo asset ID, Xlsx row ID, Aurora source ID" - ) - registration_id = CICharField( - max_length=100, - blank=True, - null=True, - verbose_name=_("Aurora Registration Id"), + max_length=150, blank=True, null=True, help_text="Kobo asset ID, Xlsx row ID, Aurora registration ID" ) program_registration_id = CICharField( max_length=100, blank=True, null=True, verbose_name=_("Beneficiary Program Registration Id") diff --git a/src/hct_mis_api/apps/household/schema.py b/src/hct_mis_api/apps/household/schema.py index 731cb6a0e9..60111943f3 100644 --- a/src/hct_mis_api/apps/household/schema.py +++ b/src/hct_mis_api/apps/household/schema.py @@ -278,7 +278,7 @@ def resolve_identities(parent: Individual, info: Any) -> QuerySet[IndividualIden @staticmethod def resolve_import_id(parent: Individual, info: Any) -> str: - return f"{parent.unicef_id} (Detail ID {parent.detail_id})" if parent.unicef_id else parent.unicef_id + return f"{parent.unicef_id} (Detail ID {parent.detail_id})" if parent.detail_id else parent.unicef_id @staticmethod def resolve_preferred_language(parent: Individual, info: Any) -> Optional[str]: diff --git a/src/hct_mis_api/apps/household/views.py b/src/hct_mis_api/apps/household/views.py index 4e9aa5c48f..a600061b76 100644 --- a/src/hct_mis_api/apps/household/views.py +++ b/src/hct_mis_api/apps/household/views.py @@ -40,15 +40,15 @@ def get_individual(tax_id: str, business_area_code: Optional[str]) -> PendingInd raise Exception("Document with given tax_id not found") -def get_household(registration_id: str, business_area_code: Optional[str]) -> Union[PendingHousehold, Household]: - kobo_asset_value = _prepare_kobo_asset_id_value(registration_id) +def get_household(detail_id: str, business_area_code: Optional[str]) -> Union[PendingHousehold, Household]: + kobo_asset_value = _prepare_kobo_asset_id_value(detail_id) households = ( Household.objects.all() if not business_area_code else Household.objects.filter(business_area__code=business_area_code) ).filter(detail_id__endswith=kobo_asset_value) if households.count() > 1: - raise Exception(f"Multiple households ({households.count()}) with given registration_id found") + raise Exception(f"Multiple households ({households.count()}) with given detail_id found") if households.count() == 1: return households.first() # type: ignore @@ -65,29 +65,27 @@ def get_household(registration_id: str, business_area_code: Optional[str]) -> Un imported_households = pending_households_by_business_area.filter(detail_id__endswith=kobo_asset_value) if imported_households.count() > 1: - raise Exception( - f"Multiple imported households ({imported_households.count()}) with given registration_id found" - ) + raise Exception(f"Multiple imported households ({imported_households.count()}) with given detail_id found") if imported_households.count() == 1: return imported_households.first() - raise Exception("Household with given registration_id not found") + raise Exception("Household with given detail_id not found") def get_household_or_individual( - tax_id: Optional[str], registration_id: Optional[str], business_area_code: Optional[str] + tax_id: Optional[str], detail_id: Optional[str], business_area_code: Optional[str] ) -> Dict: - if tax_id and registration_id: - raise Exception("tax_id and registration_id are mutually exclusive") + if tax_id and detail_id: + raise Exception("tax_id and detail_id are mutually exclusive") if tax_id: individual = get_individual(tax_id, business_area_code) return serialize_by_individual(individual, tax_id) - if registration_id: - household = get_household(registration_id, business_area_code) + if detail_id: + household = get_household(detail_id, business_area_code) return serialize_by_household(household) - raise Exception("tax_id or registration_id is required") + raise Exception("tax_id or detail_id is required") class HouseholdStatusView(APIView): @@ -98,11 +96,11 @@ def get(self, request: Request) -> Response: query_params = request.query_params tax_id = query_params.get("tax_id", None) - registration_id = query_params.get("registration_id", None) + detail_id = query_params.get("detail_id", None) business_area_code: Optional[str] = query_params.get("business_area_code") try: - data = get_household_or_individual(tax_id, registration_id, business_area_code) + data = get_household_or_individual(tax_id, detail_id, business_area_code) except Exception as e: # pragma: no cover logger.exception(e) return Response({"status": "not found", "error_message": "Household not Found"}, status=404) diff --git a/src/hct_mis_api/apps/registration_datahub/tasks/rdi_merge.py b/src/hct_mis_api/apps/registration_datahub/tasks/rdi_merge.py index 02f39e06c5..7063aabd4a 100644 --- a/src/hct_mis_api/apps/registration_datahub/tasks/rdi_merge.py +++ b/src/hct_mis_api/apps/registration_datahub/tasks/rdi_merge.py @@ -152,14 +152,6 @@ class RdiMergeTask: "wallet_address", ) - # TODO DATAHUB DELETE Fix for flex registration - # if record := imported_household.flex_registrations_record: - # household_data["registration_id"] = str(record.registration) - - # TODO DATAHUB DELETE Fix for flex registration - # if enumerator_rec_id := imported_household.enumerator_rec_id: - # household_data["enumerator_rec_id"] = enumerator_rec_id - def _create_grievance_ticket_for_delivery_mechanisms_errors( self, delivery_mechanism_data: DeliveryMechanismData, obj_hct: RegistrationDataImport, description: str ) -> Tuple[GrievanceTicket, TicketIndividualDataUpdateDetails]: diff --git a/src/hct_mis_api/apps/registration_datahub/tasks/rdi_xlsx_create.py b/src/hct_mis_api/apps/registration_datahub/tasks/rdi_xlsx_create.py index dfe9f75a67..1eb06057b1 100644 --- a/src/hct_mis_api/apps/registration_datahub/tasks/rdi_xlsx_create.py +++ b/src/hct_mis_api/apps/registration_datahub/tasks/rdi_xlsx_create.py @@ -705,8 +705,8 @@ def execute_individuals_additional_steps(self, individuals: list[PendingIndividu individual.phone_no_valid = is_valid_phone_number(str(individual.phone_no)) if individual.phone_no_alternative: individual.phone_no_alternative_valid = is_valid_phone_number(str(individual.phone_no_alternative)) - if individual.household: - individual.registration_id = individual.household.registration_id + if individual.household and not individual.detail_id: # pragma: no cover + individual.detail_id = individual.household.detail_id @transaction.atomic def execute( diff --git a/src/hct_mis_api/contrib/aurora/services/generic_registration_service.py b/src/hct_mis_api/contrib/aurora/services/generic_registration_service.py index 8992ca58f8..66134ea0b0 100644 --- a/src/hct_mis_api/contrib/aurora/services/generic_registration_service.py +++ b/src/hct_mis_api/contrib/aurora/services/generic_registration_service.py @@ -347,7 +347,6 @@ def create_household_for_rdi_household(self, record: Any, registration_data_impo individual=sec_collector, household=household, role=ROLE_ALTERNATE ) - household.registration_id = record.source_id # TODO to be removed household.detail_id = record.source_id household.save() record.mark_as_imported() diff --git a/tests/unit/apps/household/snapshots/snap_test_household_program_registration_id.py b/tests/unit/apps/household/snapshots/snap_test_household_program_registration_id.py index 345dd9b49d..1f169f9885 100644 --- a/tests/unit/apps/household/snapshots/snap_test_household_program_registration_id.py +++ b/tests/unit/apps/household/snapshots/snap_test_household_program_registration_id.py @@ -7,7 +7,7 @@ snapshots = Snapshot() -snapshots['TestHouseholdRegistrationId::test_household_program_registration_id_0_ABCD_123123 1'] = { +snapshots['TestHouseholdRegistrationId::test_household_program_detail_id_0_ABCD_123123 1'] = { 'data': { 'household': { 'programRegistrationId': 'ABCD-123123' @@ -15,7 +15,7 @@ } } -snapshots['TestHouseholdRegistrationId::test_household_program_registration_id_1 1'] = { +snapshots['TestHouseholdRegistrationId::test_household_program_detail_id_1 1'] = { 'data': { 'household': { 'programRegistrationId': None @@ -23,7 +23,7 @@ } } -snapshots['TestHouseholdRegistrationId::test_household_program_registration_id_2_ 1'] = { +snapshots['TestHouseholdRegistrationId::test_household_program_detail_id_2_ 1'] = { 'data': { 'household': { 'programRegistrationId': None diff --git a/tests/unit/apps/household/test_household_query.py b/tests/unit/apps/household/test_household_query.py index cf505723a2..a9ff3ee8a9 100644 --- a/tests/unit/apps/household/test_household_query.py +++ b/tests/unit/apps/household/test_household_query.py @@ -217,7 +217,7 @@ def setUpTestData(cls) -> None: cls.households.append(household) household = cls.households[0] - household.registration_id = 123 + household.detail_id = 123 household.save() household.refresh_from_db() household.head_of_household.phone_no = "+18663567905" @@ -337,6 +337,8 @@ def test_household_query_single_import_id(self, field_name: str, field_value: st } } """ + household.detail_id = None + household.enumerator_rec_id = None if field_name is not None: setattr(household, field_name, field_value) diff --git a/tests/unit/apps/household/test_household_status_endpoint.py b/tests/unit/apps/household/test_household_status_endpoint.py index 8e9a3a0fc7..ab521fb1a3 100644 --- a/tests/unit/apps/household/test_household_status_endpoint.py +++ b/tests/unit/apps/household/test_household_status_endpoint.py @@ -10,6 +10,8 @@ from hct_mis_api.apps.core.utils import IDENTIFICATION_TYPE_TO_KEY_MAPPING from hct_mis_api.apps.household.fixtures import ( DocumentTypeFactory, + HouseholdFactory, + IndividualFactory, PendingDocumentFactory, PendingHouseholdFactory, PendingIndividualFactory, @@ -65,7 +67,7 @@ def test_filtering_business_area_code_with_tax_id(self) -> None: response_nok = self.api_client.get(f"/api/hh-status?tax_id={tax_id}&business_area_code=non-existent") self.assertEqual(response_nok.status_code, 404) - def test_filtering_business_area_code_with_registration_id(self) -> None: + def test_filtering_business_area_code_with_detail_id(self) -> None: rdi = RegistrationDataImportFactory(business_area=self.business_area) pending_household = PendingHouseholdFactory(registration_data_import=rdi) pending_individual = PendingIndividualFactory(household=pending_household, relationship=HEAD) @@ -78,16 +80,14 @@ def test_filtering_business_area_code_with_registration_id(self) -> None: household=pending_household, ) - registration_id = pending_household.detail_id + detail_id = pending_household.detail_id response_ok = self.api_client.get( - f"/api/hh-status?registration_id={registration_id}&business_area_code={self.business_area.code}" + f"/api/hh-status?detail_id={detail_id}&business_area_code={self.business_area.code}" ) self.assertEqual(response_ok.status_code, 200) - response_nok = self.api_client.get( - f"/api/hh-status?registration_id={registration_id}&business_area_code=non-existent" - ) + response_nok = self.api_client.get(f"/api/hh-status?detail_id={detail_id}&business_area_code=non-existent") self.assertEqual(response_nok.status_code, 404) def test_getting_non_existent_individual(self) -> None: @@ -204,7 +204,7 @@ def test_getting_individual_with_status_paid(self) -> None: self.assertEqual(datetime.datetime.fromisoformat(info["date"].replace("Z", "")).date(), payment.delivery_date) def test_getting_non_existent_household(self) -> None: - response = self.api_client.get("/api/hh-status?registration_id=non-existent") + response = self.api_client.get("/api/hh-status?detail_id=non-existent") self.assertEqual(response.status_code, 404) self.assertEqual(response.json()["status"], "not found") @@ -220,12 +220,47 @@ def test_getting_household_with_status_imported(self) -> None: household=pending_household, ) - registration_id = pending_household.detail_id + detail_id = pending_household.detail_id - response = self.api_client.get(f"/api/hh-status?registration_id={registration_id}") + response = self.api_client.get(f"/api/hh-status?detail_id={detail_id}") self.assertEqual(response.status_code, 200) data = response.json() info = data["info"] self.assertEqual(info["status"], "imported") self.assertEqual(info["date"], _time(pending_household.updated_at)) self.assertTrue("individual" not in info) + + def test_getting_household_with_status_paid(self) -> None: + detail_id = "HOPE-2022530111222" + household = HouseholdFactory(detail_id=detail_id) + individual = IndividualFactory(household=household, relationship=HEAD) + individual.head_of_household = individual + individual.save() + payment = PaymentFactory(household=household, delivered_quantity=1000, collector=individual) + response = self.api_client.get(f"/api/hh-status?detail_id={detail_id}") + self.assertEqual(response.status_code, 200) + data = response.json() + info = data["info"] + self.assertEqual(info["status"], "paid") + self.assertEqual(info["date"], _time(payment.delivery_date)) + self.assertTrue("individual" not in info) + + def test_query_params_validation(self) -> None: + response = self.api_client.get("/api/hh-status?detail_id=xxx&tax_id=yyy") + self.assertEqual(response.status_code, 404) + + response = self.api_client.get("/api/hh-status") + self.assertEqual(response.status_code, 404) + + def test_households_count_gt_1(self) -> None: + detail_id = "123" + + PendingHouseholdFactory(detail_id=detail_id) + PendingHouseholdFactory(detail_id=detail_id) + response = self.api_client.get(f"/api/hh-status?detail_id={detail_id}") + self.assertEqual(response.status_code, 404) + + HouseholdFactory(detail_id=detail_id) + HouseholdFactory(detail_id=detail_id) + response = self.api_client.get(f"/api/hh-status?detail_id={detail_id}") + self.assertEqual(response.status_code, 404) diff --git a/tests/unit/apps/household/test_individual_query.py b/tests/unit/apps/household/test_individual_query.py index b4682c6e23..1ca304024e 100644 --- a/tests/unit/apps/household/test_individual_query.py +++ b/tests/unit/apps/household/test_individual_query.py @@ -138,7 +138,7 @@ def setUpTestData(cls) -> None: "phone_no": "(953)682-4596", "birth_date": "1943-07-30", "id": "ffb2576b-126f-42de-b0f5-ef889b7bc1fe", - "registration_id": 1, + "detail_id": 1, }, { "full_name": "Robin Ford", diff --git a/tests/unit/apps/registration_datahub/test_program_population_to_pending_objects.py b/tests/unit/apps/registration_datahub/test_program_population_to_pending_objects.py index 9828713d2d..507bbca09c 100644 --- a/tests/unit/apps/registration_datahub/test_program_population_to_pending_objects.py +++ b/tests/unit/apps/registration_datahub/test_program_population_to_pending_objects.py @@ -157,7 +157,7 @@ def setUpTestData(cls) -> None: "admin2": AreaFactory(), "admin3": AreaFactory(), "admin4": AreaFactory(), - "registration_id": "1234567890", + "detail_id": "1234567890", "flex_fields": {"enumerator_id": "123", "some": "thing"}, "country": country, "country_origin": country_origin, diff --git a/tests/unit/apps/registration_datahub/test_registration_program_population_import_task.py b/tests/unit/apps/registration_datahub/test_registration_program_population_import_task.py index 624520b69a..23494f7e49 100644 --- a/tests/unit/apps/registration_datahub/test_registration_program_population_import_task.py +++ b/tests/unit/apps/registration_datahub/test_registration_program_population_import_task.py @@ -63,7 +63,7 @@ def setUpTestData(cls) -> None: "admin2": AreaFactory(), "admin3": AreaFactory(), "admin4": AreaFactory(), - "registration_id": "1234567890", + "detail_id": "1234567890", "flex_fields": {"enumerator_id": "123", "some": "thing"}, }, individuals_data=[