From acb6c853ebf8999deb4a3091d24e85d8517ff130 Mon Sep 17 00:00:00 2001 From: Pavlo Mokiichuk Date: Fri, 27 Sep 2024 12:58:33 +0200 Subject: [PATCH 01/12] init comit --- src/hct_mis_api/apps/payment/models.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/hct_mis_api/apps/payment/models.py b/src/hct_mis_api/apps/payment/models.py index cd2cbbb0c0..7ebcc7c544 100644 --- a/src/hct_mis_api/apps/payment/models.py +++ b/src/hct_mis_api/apps/payment/models.py @@ -1199,6 +1199,7 @@ def get_column_from_core_field( core_field_name: str, delivery_mechanism_data: Optional["DeliveryMechanismData"] = None, ) -> Any: + # TODO: try to get from snapshot def parse_admin_area(obj: "Area") -> str: if not obj: return "" # pragma: no cover @@ -1217,6 +1218,21 @@ def parse_admin_area(obj: "Area") -> str: if delivery_mechanism_data and core_field["associated_with"] == _DELIVERY_MECHANISM_DATA: return delivery_mechanism_data.delivery_data.get(core_field_name, None) + snapshot = payment.household_snapshot if hasattr(payment, "household_snapshot") else None + + # fields from snap_shot + # snapshot.snapshot_data["primary_collector"] or snapshot.snapshot_data["alternate_collector"] + # ["", ] + + # individual_data["bank_account_info"] = { + # "bank_name": bank_account_info.bank_name, + # "bank_account_number": bank_account_info.bank_account_number, + # "debit_card_number": bank_account_info.debit_card_number, + + # individual_data["delivery_mechanisms_data"] = { + # dmd.delivery_mechanism.code: dmd.delivery_data for dmd in individual.delivery_mechanisms_data.all() + # } + lookup = core_field["lookup"] lookup = lookup.replace("__", ".") @@ -1235,6 +1251,7 @@ def parse_admin_area(obj: "Area") -> str: @classmethod def get_column_value_from_payment(cls, payment: "Payment", column_name: str) -> Union[str, float, list]: + # TODO: try to get from snapshot if not from payment # we can get if needed payment.parent.program.is_social_worker_program alternate_collector = None alternate_collector_column_names = ( From 009bab720c6b96eb378f17d2388100f2c85944bc Mon Sep 17 00:00:00 2001 From: Pavlo Mokiichuk Date: Fri, 27 Sep 2024 14:31:26 +0200 Subject: [PATCH 02/12] add changes --- src/hct_mis_api/apps/payment/models.py | 33 ++++++++++--------- ...import_export_payment_plan_payment_list.py | 7 ++++ 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/hct_mis_api/apps/payment/models.py b/src/hct_mis_api/apps/payment/models.py index 7ebcc7c544..a34612c793 100644 --- a/src/hct_mis_api/apps/payment/models.py +++ b/src/hct_mis_api/apps/payment/models.py @@ -1199,7 +1199,6 @@ def get_column_from_core_field( core_field_name: str, delivery_mechanism_data: Optional["DeliveryMechanismData"] = None, ) -> Any: - # TODO: try to get from snapshot def parse_admin_area(obj: "Area") -> str: if not obj: return "" # pragma: no cover @@ -1218,25 +1217,28 @@ def parse_admin_area(obj: "Area") -> str: if delivery_mechanism_data and core_field["associated_with"] == _DELIVERY_MECHANISM_DATA: return delivery_mechanism_data.delivery_data.get(core_field_name, None) - snapshot = payment.household_snapshot if hasattr(payment, "household_snapshot") else None + snapshot = getattr(payment, "household_snapshot", None) # fields from snap_shot - # snapshot.snapshot_data["primary_collector"] or snapshot.snapshot_data["alternate_collector"] - # ["", ] - - # individual_data["bank_account_info"] = { - # "bank_name": bank_account_info.bank_name, - # "bank_account_number": bank_account_info.bank_account_number, - # "debit_card_number": bank_account_info.debit_card_number, + bank_account_info = ["bank_name", "bank_account_number", "debit_card_number"] + get_from_snapshot = ["full_name", "payment_delivery_phone_no"] + bank_account_info # individual_data["delivery_mechanisms_data"] = { # dmd.delivery_mechanism.code: dmd.delivery_data for dmd in individual.delivery_mechanisms_data.all() - # } + # } ??? lookup = core_field["lookup"] lookup = lookup.replace("__", ".") if core_field["associated_with"] == _INDIVIDUAL: + if lookup in get_from_snapshot and snapshot: + snapshot_data = snapshot.snapshot_data + collector_data = snapshot_data.get("primary_collector") or snapshot_data.get( + "alternate_collector") or dict() + if lookup in bank_account_info: + return collector_data.get("bank_account_info").get(lookup) + return nested_getattr(collector_data, lookup, None) + if lookup_function := core_field.get("lookup_function"): return lookup_function(collector) return nested_getattr(collector, lookup, None) @@ -1251,7 +1253,6 @@ def parse_admin_area(obj: "Area") -> str: @classmethod def get_column_value_from_payment(cls, payment: "Payment", column_name: str) -> Union[str, float, list]: - # TODO: try to get from snapshot if not from payment # we can get if needed payment.parent.program.is_social_worker_program alternate_collector = None alternate_collector_column_names = ( @@ -1276,11 +1277,11 @@ def get_column_value_from_payment(cls, payment: "Payment", column_name: str) -> "admin_level_2": (payment.household.admin2, "name"), "village": (payment.household, "village"), "collector_name": (payment.collector, "full_name"), - "alternate_collector_full_name": (alternate_collector, "full_name"), - "alternate_collector_given_name": (alternate_collector, "given_name"), - "alternate_collector_middle_name": (alternate_collector, "middle_name"), + "alternate_collector_full_name": (alternate_collector, "full_name"), # TODO: get from snapshot + "alternate_collector_given_name": (alternate_collector, "given_name"), # TODO: get from snapshot + "alternate_collector_middle_name": (alternate_collector, "middle_name"), # TODO: get from snapshot "alternate_collector_sex": (alternate_collector, "sex"), - "alternate_collector_phone_no": (alternate_collector, "phone_no"), + "alternate_collector_phone_no": (alternate_collector, "phone_no"), # TODO: get from snapshot "alternate_collector_document_numbers": (alternate_collector, "document_number"), "payment_channel": (payment.delivery_type, "name"), "fsp_name": (payment.financial_service_provider, "name"), @@ -1297,7 +1298,7 @@ def get_column_value_from_payment(cls, payment: "Payment", column_name: str) -> "additional_document_type": (payment, "additional_document_type"), "additional_document_number": (payment, "additional_document_number"), "status": (payment, "payment_status"), - "transaction_status_blockchain_link": (payment, "transaction_status_blockchain_link"), + "transaction_status_blockchain_link": (payment, "transaction_status_blockchain_link"), # TODO: get from snapshot ? } additional_columns = { "registration_token": cls.get_registration_token_doc_number, diff --git a/tests/unit/apps/payment/test_import_export_payment_plan_payment_list.py b/tests/unit/apps/payment/test_import_export_payment_plan_payment_list.py index 74d5548890..667ae51821 100644 --- a/tests/unit/apps/payment/test_import_export_payment_plan_payment_list.py +++ b/tests/unit/apps/payment/test_import_export_payment_plan_payment_list.py @@ -570,3 +570,10 @@ def test_flex_fields_admin_visibility(self) -> None: "flex_date_i_f", (name for name, _ in response.context["adminform"].form.fields["flex_fields"].choices), ) + + def test_payment_row_get_snapshot_data(self) -> None: + # TODO: will add after get some real data + # create_payment_plan_snapshot_data(self.payment_plan) + # or PaymentHouseholdSnapshot(payment=payment, snapshot_data=household_data, household_id=household.id) + # XlsxPaymentPlanExportPerFspService.get_payment_row() + pass From 5ec7c780bfdcc81299010d8c582e159c392bdc9a Mon Sep 17 00:00:00 2001 From: Pavlo Mokiichuk Date: Fri, 27 Sep 2024 14:50:06 +0200 Subject: [PATCH 03/12] black --- src/hct_mis_api/apps/payment/models.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/hct_mis_api/apps/payment/models.py b/src/hct_mis_api/apps/payment/models.py index a34612c793..1638970449 100644 --- a/src/hct_mis_api/apps/payment/models.py +++ b/src/hct_mis_api/apps/payment/models.py @@ -1233,8 +1233,9 @@ def parse_admin_area(obj: "Area") -> str: if core_field["associated_with"] == _INDIVIDUAL: if lookup in get_from_snapshot and snapshot: snapshot_data = snapshot.snapshot_data - collector_data = snapshot_data.get("primary_collector") or snapshot_data.get( - "alternate_collector") or dict() + collector_data = ( + snapshot_data.get("primary_collector") or snapshot_data.get("alternate_collector") or dict() + ) if lookup in bank_account_info: return collector_data.get("bank_account_info").get(lookup) return nested_getattr(collector_data, lookup, None) @@ -1298,7 +1299,10 @@ def get_column_value_from_payment(cls, payment: "Payment", column_name: str) -> "additional_document_type": (payment, "additional_document_type"), "additional_document_number": (payment, "additional_document_number"), "status": (payment, "payment_status"), - "transaction_status_blockchain_link": (payment, "transaction_status_blockchain_link"), # TODO: get from snapshot ? + "transaction_status_blockchain_link": ( + payment, + "transaction_status_blockchain_link", + ), # TODO: get from snapshot ? } additional_columns = { "registration_token": cls.get_registration_token_doc_number, From f1d398e8a27e56108d895dc6d6727193040d5912 Mon Sep 17 00:00:00 2001 From: Pavlo Mokiichuk Date: Mon, 30 Sep 2024 15:10:08 +0200 Subject: [PATCH 04/12] add more todos --- src/hct_mis_api/apps/payment/models.py | 2 ++ .../payment/xlsx/xlsx_payment_plan_export_per_fsp_service.py | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/hct_mis_api/apps/payment/models.py b/src/hct_mis_api/apps/payment/models.py index 1638970449..6d1d4226e2 100644 --- a/src/hct_mis_api/apps/payment/models.py +++ b/src/hct_mis_api/apps/payment/models.py @@ -1199,6 +1199,7 @@ def get_column_from_core_field( core_field_name: str, delivery_mechanism_data: Optional["DeliveryMechanismData"] = None, ) -> Any: + # TODO: get from snapshot all def parse_admin_area(obj: "Area") -> str: if not obj: return "" # pragma: no cover @@ -1254,6 +1255,7 @@ def parse_admin_area(obj: "Area") -> str: @classmethod def get_column_value_from_payment(cls, payment: "Payment", column_name: str) -> Union[str, float, list]: + # TODO: get from snapshot all # we can get if needed payment.parent.program.is_social_worker_program alternate_collector = None alternate_collector_column_names = ( diff --git a/src/hct_mis_api/apps/payment/xlsx/xlsx_payment_plan_export_per_fsp_service.py b/src/hct_mis_api/apps/payment/xlsx/xlsx_payment_plan_export_per_fsp_service.py index 349e2af3cb..22edca9641 100644 --- a/src/hct_mis_api/apps/payment/xlsx/xlsx_payment_plan_export_per_fsp_service.py +++ b/src/hct_mis_api/apps/payment/xlsx/xlsx_payment_plan_export_per_fsp_service.py @@ -148,7 +148,7 @@ def get_payment_row(self, payment: Payment, fsp_xlsx_template: "FinancialService payment_row = [ FinancialServiceProviderXlsxTemplate.get_column_value_from_payment(payment, column_name) for column_name in fsp_template_columns - ] + ] # TODO: get from snapshot delivery_mechanism_data = payment.collector.delivery_mechanisms_data.filter( delivery_mechanism=payment.delivery_type ).first() @@ -157,7 +157,7 @@ def get_payment_row(self, payment: Payment, fsp_xlsx_template: "FinancialService payment, column_name, delivery_mechanism_data ) for column_name in fsp_template_core_fields - ] + ] # TODO: get from snapshot payment_row.extend(core_fields_row) flex_field_row = [ self._get_flex_field_by_name(column_name, payment) for column_name in fsp_xlsx_template.flex_fields @@ -166,6 +166,7 @@ def get_payment_row(self, payment: Payment, fsp_xlsx_template: "FinancialService return list(map(self.right_format_for_xlsx, payment_row)) def _get_flex_field_by_name(self, name: str, payment: Payment) -> FlexibleAttribute: + # TODO: get from snapshot attribute: FlexibleAttribute = self.flexible_attributes[name] individual = payment.collector household = payment.household From df6f33cd167b75010921a0285c9cb419175a7c57 Mon Sep 17 00:00:00 2001 From: Pavlo Mokiichuk Date: Wed, 2 Oct 2024 23:37:52 +0200 Subject: [PATCH 05/12] small fixes and added shapshot_field --- .../core_fields_attributes.py | 109 ++++++++++++++++++ .../payment_channel_fields_attributes.py | 12 ++ .../payment_household_snapshot_service.py | 8 +- 3 files changed, 126 insertions(+), 3 deletions(-) diff --git a/src/hct_mis_api/apps/core/field_attributes/core_fields_attributes.py b/src/hct_mis_api/apps/core/field_attributes/core_fields_attributes.py index d17f7a084c..063527b979 100644 --- a/src/hct_mis_api/apps/core/field_attributes/core_fields_attributes.py +++ b/src/hct_mis_api/apps/core/field_attributes/core_fields_attributes.py @@ -99,6 +99,12 @@ logger = logging.getLogger(__name__) +# about "shapshot_field" +# if it's the same field that can connect with "associated_with" and "name" +# like individual.full_name or household.size <-- no needed any "shapshot_field" info +# but for "account_holder_name" it gets from "bank_account_info__account_holder_name" +# or for field "debit_card_issuer" used individual.full_name and in "shapshot_field" is "full_name" + CORE_FIELDS_ATTRIBUTES = [ { "id": "a1741e3c-0e24-4a60-8d2f-463943abaebb", @@ -139,6 +145,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "consent_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "9480fc0d-1b88-45b0-9056-6a6fe0ebe509", @@ -166,6 +173,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "country_origin_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE, Scope.XLSX_PEOPLE], + "shapshot_field": "country_origin_id__iso_code3", }, { "id": "aa79985c-b616-453c-9884-0666252c3070", @@ -180,6 +188,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "country_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE, Scope.XLSX_PEOPLE], + "shapshot_field": "country_id__iso_code3", }, { "id": "59685cec-69bf-4abe-81b4-70b8f05b89f3", @@ -220,6 +229,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "admin1_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], + "shapshot_field": "admin1_id__p_code", }, { "id": "e4eb6632-8204-44ed-b39c-fe791ded9246", @@ -234,6 +244,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "admin2_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], + "shapshot_field": "admin2_id__p_code", }, { "id": "92e425a7-db22-4026-8602-96c51471dfff", @@ -248,6 +259,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "admin3_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.XLSX_PEOPLE], + "shapshot_field": "admin3_id__p_code", }, { "id": "717620df-b804-4398-b789-e56aa7a3da5e", @@ -262,6 +274,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "admin4_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.XLSX_PEOPLE], + "shapshot_field": "admin4_id__p_code", }, { "id": "13a9d8b0-f278-47c2-9b1b-b06579b0ab35", @@ -580,6 +593,8 @@ "xlsx_field": "birth_certificate_no_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_birth_certificate_no, + # for document, it's gets from ["documents"][type][field] + "shapshot_field": "documents__birth_certificate__document_number", }, { "id": "0d8bc4b6-c2c7-4b8b-ab64-cd2e80b70717", @@ -596,6 +611,7 @@ "xlsx_field": "birth_certificate_issuer_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_birth_certificate_issuer, + "shapshot_field": "documents__", }, { "id": "12ceb917-8942-4cb6-a9d0-6a97a097258a", @@ -609,6 +625,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "birth_certificate_photo_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], + "shapshot_field": "documents__", }, { "id": "03520f48-4918-4f7a-988d-f24476e16fc4", @@ -624,6 +641,7 @@ "xlsx_field": "tax_id_no_i_c", "scope": [Scope.XLSX, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_tax_id_no, + "shapshot_field": "documents__", }, { "id": "762cb2a8-b05a-47dc-81da-c71e4b1fd68f", @@ -639,6 +657,7 @@ "xlsx_field": "tax_id_issuer_i_c", "scope": [Scope.XLSX, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_tax_id_issuer, + "shapshot_field": "documents__", }, { "id": "d0e84bc0-9ac5-4c5b-bbdc-0644f5349d53", @@ -652,6 +671,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "tax_id_photo_i_c", "scope": [Scope.XLSX, Scope.XLSX_PEOPLE], + "shapshot_field": "documents__", }, { "id": "34a9519f-9c42-4910-b097-157ec8e6e31f", @@ -667,6 +687,7 @@ "xlsx_field": "drivers_license_no_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_drivers_license_no, + "shapshot_field": "", }, { "id": "00b95153-976e-4653-b9a8-843d513df873", @@ -683,6 +704,7 @@ "xlsx_field": "drivers_license_issuer_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_drivers_license_issuer, + "shapshot_field": "", }, { "id": "7e6a41c5-0fbd-4f99-98ba-2c6a7da8dbe4", @@ -696,6 +718,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "drivers_license_photo_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "225832fc-c61b-4100-aac9-352d272d15fd", @@ -711,6 +734,7 @@ "xlsx_field": "electoral_card_no_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_electoral_card_no, + "shapshot_field": "", }, { "id": "acca5a63-538a-47d1-b343-da22a86783b0", @@ -727,6 +751,7 @@ "xlsx_field": "electoral_card_issuer_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_electoral_card_issuer, + "shapshot_field": "", }, { "id": "ffb6a487-a806-47d6-a12f-fe3c6c516976", @@ -740,6 +765,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "electoral_card_photo_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "1c7f6c85-1621-48f1-88f3-a172d69aa316", @@ -755,6 +781,7 @@ "get_query": get_unhcr_id_number_query, "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_unhcr_id_no, + "shapshot_field": "", }, { "id": "801bdd67-d27d-4afa-9d23-823e1c8d1313", @@ -771,6 +798,7 @@ "get_query": get_unhcr_id_issuer_query, "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_unhcr_id_issuer, + "shapshot_field": "", }, { "id": "2f9ca147-afde-4311-9d61-e906a8ef2334", @@ -784,6 +812,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "unhcr_id_photo_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "4e836832-2cf2-4073-80eb-21316eaf7277", @@ -799,6 +828,7 @@ "xlsx_field": "national_passport_no_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_national_passport_no, + "shapshot_field": "", }, { "id": "3da62c17-66ee-4c08-adbf-74402f31dce2", @@ -815,6 +845,7 @@ "xlsx_field": "national_passport_issuer_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_national_passport_issuer, + "shapshot_field": "", }, { "id": "234a1b5b-7900-4f67-86a9-5fcaede3d09d", @@ -828,6 +859,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "national_passport_photo_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "eff20a18-4336-4273-bbb8-ed0e9a94ebbb", @@ -843,6 +875,7 @@ "xlsx_field": "national_id_no_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_national_id_no, + "shapshot_field": "", }, { "id": "2bd255a7-1273-4a52-930f-6660573b2743", @@ -859,6 +892,7 @@ "xlsx_field": "national_id_issuer_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_national_id_issuer, + "shapshot_field": "", }, { "id": "d43304d9-91e4-4317-9356-f7066b898b16", @@ -872,6 +906,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "national_id_photo_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "201c91d2-8f89-46c9-ba5a-db7130140402", @@ -887,6 +922,7 @@ "get_query": get_scope_id_number_query, "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_scope_id_no, + "shapshot_field": "", }, { "id": "638a6383-6e87-4c4f-842c-6c5433599267", @@ -903,6 +939,7 @@ "get_query": get_scope_id_issuer_query, "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_scope_id_issuer, + "shapshot_field": "", }, { "id": "4aa3d595-131a-48df-8752-ec171eabe3be", @@ -916,6 +953,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "scope_id_photo_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "3bf6105f-87d0-479b-bf92-7f90af4d8462", @@ -929,6 +967,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "other_id_type_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "556e14af-9901-47f3-bf2c-20b4c721e8f7", @@ -944,6 +983,7 @@ "xlsx_field": "other_id_no_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_other_id_no, + "shapshot_field": "", }, { "id": "cd181a09-f4c5-4a47-8713-25bdc4a64058", @@ -960,6 +1000,7 @@ "xlsx_field": "other_id_issuer_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_other_id_issuer, + "shapshot_field": "", }, { "id": "d4279a74-377f-4f74-baf2-e1ebd001ec5c", @@ -973,6 +1014,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "other_id_photo_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "07f7005f-e70d-409b-9dee-4c3414aba40b", @@ -986,6 +1028,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "f_0_5_age_group_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], + "shapshot_field": "", }, { "id": "6b993af8-4a5d-4a08-a444-8ade115c39ad", @@ -999,6 +1042,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "f_6_11_age_group_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], + "shapshot_field": "", }, { "id": "71ce16b5-4e49-48fa-818c-0bd2eba079eb", @@ -1012,6 +1056,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "f_12_17_age_group_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], + "shapshot_field": "", }, { "id": "1af02ab4-469b-4de1-a71a-7d51cc86224b", @@ -1025,6 +1070,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "f_18_59_age_group_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], + "shapshot_field": "", }, { "id": "4009ca04-1ffd-48eb-a41f-64ddb439cc8d", @@ -1038,6 +1084,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "f_60_age_group_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], + "shapshot_field": "", }, { "id": "18fd9429-400f-4fce-b72f-035d2afca201", @@ -1051,6 +1098,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "pregnant_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], + "shapshot_field": "", }, { "id": "57233f1b-93c3-4fd4-a885-92c512c5e32a", @@ -1064,6 +1112,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "m_0_5_age_group_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], + "shapshot_field": "", }, { "id": "11e2a938-e93a-4c18-8eca-7e61355d7476", @@ -1077,6 +1126,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "m_6_11_age_group_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], + "shapshot_field": "", }, { "id": "bf28628e-0f6a-46e8-9587-3b0c17977006", @@ -1090,6 +1140,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "m_12_17_age_group_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], + "shapshot_field": "", }, { "id": "e76cd764-1466-49c3-92da-5a558069c472", @@ -1103,6 +1154,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "m_18_59_age_group_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], + "shapshot_field": "", }, { "id": "988ebe6d-8782-4bc1-95a0-eeb692980847", @@ -1116,6 +1168,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "m_60_age_group_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], + "shapshot_field": "", }, { "id": "4f59aca6-5900-40c0-a1e4-47c331a90a6f", @@ -1129,6 +1182,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "f_0_5_disability_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], + "shapshot_field": "", }, { "id": "10e33d7b-b3c4-4383-a4f0-6eba00a15e9c", @@ -1142,6 +1196,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "f_6_11_disability_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], + "shapshot_field": "", }, { "id": "623a6fd6-d863-40cc-a4d1-964f739747be", @@ -1155,6 +1210,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "f_12_17_disability_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], + "shapshot_field": "", }, { "id": "c99df741-4559-4166-a49c-05d69ba8a4fa", @@ -1168,6 +1224,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "f_18_59_disability_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], + "shapshot_field": "", }, { "id": "f21d61f2-923e-46b8-aefc-b623cb0026ca", @@ -1181,6 +1238,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "f_60_disability_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], + "shapshot_field": "", }, { "id": "d3b82576-1bba-44fa-9d5a-db04e71bb35b", @@ -1194,6 +1252,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "m_0_5_disability_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], + "shapshot_field": "", }, { "id": "78340f8f-86ab-464a-8e19-ce3d6feec5d6", @@ -1207,6 +1266,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "m_6_11_disability_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], + "shapshot_field": "", }, { "id": "519140f7-1a9e-4115-b736-2b09dbc6f036", @@ -1220,6 +1280,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "m_12_17_disability_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], + "shapshot_field": "", }, { "id": "9c6e1d87-0768-4db7-b925-41da8b5c7988", @@ -1233,6 +1294,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "m_18_59_disability_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], + "shapshot_field": "", }, { "id": "c07da769-b7c4-4e4f-aefa-1a20bb24d8b2", @@ -1246,6 +1308,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "m_60_disability_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], + "shapshot_field": "", }, { "id": "b2593385-5a81-452e-ae9a-28292e35714b", @@ -1259,6 +1322,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "pregnant_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.INDIVIDUAL_UPDATE, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "dca6748f-7831-4fa1-b5c8-e708a456656b", @@ -1272,6 +1336,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "work_status_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.INDIVIDUAL_UPDATE, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "21cd9a35-b080-4f60-97da-6ec6918a49c0", @@ -1285,6 +1350,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "observed_disability_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.INDIVIDUAL_UPDATE, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "244ec9ae-5eb8-4b80-9416-91024a3f32d7", @@ -1300,6 +1366,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "seeing_disability_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.INDIVIDUAL_UPDATE, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "bef35c02-1fe7-4f6b-a0af-8282ec31de89", @@ -1315,6 +1382,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "hearing_disability_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.INDIVIDUAL_UPDATE, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "b7346b1f-23ea-47a8-b2ec-c176c62cdb5b", @@ -1332,6 +1400,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "physical_disability_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.INDIVIDUAL_UPDATE, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "0f24c374-4428-43ef-b162-86dc1b14e39d", @@ -1349,6 +1418,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "memory_disability_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.INDIVIDUAL_UPDATE, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "59508af1-07d0-4e20-ac3d-f241bef319c1", @@ -1364,6 +1434,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "selfcare_disability_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.INDIVIDUAL_UPDATE, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "78311be2-fb3f-443c-aac6-c0e7197af20d", @@ -1379,6 +1450,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "comms_disability_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.INDIVIDUAL_UPDATE, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "9fbd2b6f-6713-445c-a7bb-e1efc398b20d", @@ -1392,6 +1464,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "fchild_hoh_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT], + "shapshot_field": "", }, { "id": "e92810b2-c6f1-480c-95a9-4f736a1f48bf", @@ -1405,6 +1478,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "child_hoh_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT], + "shapshot_field": "", }, { "id": "62692d6a-c054-418b-803a-e34393cbc1b0", @@ -1418,6 +1492,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "village_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "c640fe45-368f-4206-afae-09700a495db3", @@ -1431,6 +1506,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "deviceid", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "8f379d33-c5fd-4344-ba2b-73e136aba13a", @@ -1444,6 +1520,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "name_enumerator_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "201e9a88-fb7d-4ba4-afec-66aba748fe55", @@ -1457,6 +1534,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "org_enumerator_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "0858371e-4e5c-402e-9cda-b767eb2d337c", @@ -1470,6 +1548,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "consent_sharing_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "27bd4ef2-442d-4b49-976c-063df050b3ae", @@ -1483,6 +1562,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "org_name_enumerator_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "69dbc5f2-039f-4671-b39f-a63d96475cab", @@ -1496,6 +1576,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "disability_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.INDIVIDUAL_UPDATE, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "6536a987-a50e-453b-9517-57c1dccd1340", @@ -1509,6 +1590,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "first_registration_date_i_c", "scope": [Scope.GLOBAL, Scope.XLSX, Scope.TARGETING, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "2fe6d876-388f-45d9-b497-eb2f8af923e8", @@ -1522,6 +1604,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "first_registration_date_h_c", "scope": [Scope.GLOBAL, Scope.XLSX, Scope.TARGETING], + "shapshot_field": "", }, { "id": "c8da2910-4348-47ab-a82e-725b4cebc332", @@ -1535,6 +1618,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "number_of_children", "scope": [Scope.TARGETING], + "shapshot_field": "", }, { "id": "bc18c462-bd75-4607-b75d-b7111a658453", @@ -1549,6 +1633,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "has_phone_number", "scope": [Scope.TARGETING], + "shapshot_field": "", }, { "id": "f4032e4f-00a9-4ed9-bff4-4e47d2f7b4be", @@ -1563,6 +1648,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "has_tax_ID_number", "scope": [Scope.TARGETING], + "shapshot_field": "", }, { "id": "6b97e9a3-38bb-49a3-9637-65f05d5b8ea4", @@ -1577,6 +1663,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "has_the_bank_account_number", "scope": [Scope.TARGETING], + "shapshot_field": "", }, { "id": "0bf5fad3-8f4f-4528-85f7-57e8a84a2a43", @@ -1591,6 +1678,7 @@ "xlsx_field": "role_i_c", "get_query": get_role_query, "scope": [Scope.TARGETING, Scope.KOBO_IMPORT, Scope.INDIVIDUAL_UPDATE], + "shapshot_field": "", }, { "id": "a1662e94-5d8b-46a7-8c9a-7c72b280f497", @@ -1606,6 +1694,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "registration_data_import", "scope": [Scope.TARGETING, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "e4aa9cdf-2c9e-4e22-9928-2b63a6ea4ef0", @@ -1621,6 +1710,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "registration_data_import", "scope": [Scope.TARGETING], + "shapshot_field": "", }, { "id": "df2b2588-68af-4dea-89ab-c5a53a5764be", @@ -1634,6 +1724,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "unicef_id_h_c", "scope": [Scope.INDIVIDUAL_XLSX_UPDATE], + "shapshot_field": "", }, { "id": "bb9bcb76-c9e4-4e83-8b9a-6c8bb35cb84c", @@ -1647,6 +1738,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "unicef_id_i_c", "scope": [Scope.INDIVIDUAL_XLSX_UPDATE], + "shapshot_field": "", }, { "id": "e48a4e26-6552-4e15-855f-9ea4d5268c84", @@ -1660,6 +1752,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "admin_area_h_c", "scope": [Scope.HOUSEHOLD_UPDATE], + "shapshot_field": "", }, { "id": "9da8c56a-3c65-47d9-8149-699761842ce4", @@ -1673,6 +1766,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "start", "scope": [Scope.KOBO_IMPORT], + "shapshot_field": "", }, { "id": "06e4c4a0-28d2-4530-be24-92623a5b48b0", @@ -1686,6 +1780,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "end", "scope": [Scope.KOBO_IMPORT], + "shapshot_field": "", }, { "id": "16e68909-54f4-47cb-ab06-066d13e85134", @@ -1699,6 +1794,7 @@ "xlsx_field": "primary_collector_id", "custom_cast_value": Countries.get_country_value, "scope": [Scope.COLLECTOR, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "d7f41696-94e5-4e0c-ab1b-20ff27c25364", @@ -1712,6 +1808,7 @@ "xlsx_field": "alternate_collector_id", "custom_cast_value": Countries.get_country_value, "scope": [Scope.COLLECTOR, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "746b3d2d-19c5-4b91-ad37-d230e1d33eb5", @@ -1725,6 +1822,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "household_id", "scope": [Scope.HOUSEHOLD_ID], + "shapshot_field": "", }, { "id": "1079bfd0-fc51-41ab-aa10-667e6b2034b9", @@ -1738,6 +1836,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "household_id", "scope": [Scope.HOUSEHOLD_ID], + "shapshot_field": "", }, { "id": "519af6bf-3e8c-4899-a4f4-360ce55648e0", @@ -1751,6 +1850,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "email", "scope": [Scope.XLSX, Scope.INDIVIDUAL_UPDATE, Scope.INDIVIDUAL_XLSX_UPDATE, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "e2b8dd27-f861-4c54-9651-677636918c47", @@ -1764,6 +1864,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "preferred_language", "scope": [Scope.XLSX, Scope.INDIVIDUAL_UPDATE, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "32fcdace-6e72-48ac-9ce8-469e552cfafe", @@ -1777,6 +1878,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "age_at_registration", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "22085a8d-205d-42a9-b5b3-951b51f11915", @@ -1790,6 +1892,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "account_holder_name_i_c", "scope": [Scope.XLSX, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "e9d964b9-aa85-4a0f-b1eb-4755bdad7592", @@ -1803,6 +1906,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "bank_branch_name_i_c", "scope": [Scope.XLSX, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "bec7a6b9-476d-48a8-8822-e24ae023df42", @@ -1816,6 +1920,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "index_id", "scope": [Scope.XLSX_PEOPLE], + "shapshot_field": "", }, { "id": "15b7b623-0dee-4212-a32a-a6d6f6877b4e", @@ -1829,6 +1934,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "wallet_name_i_c", "scope": [Scope.XLSX, Scope.INDIVIDUAL_UPDATE], + "shapshot_field": "", }, { "id": "0010bc3e-0a4a-452b-a776-2cc4b760a0fd", @@ -1842,6 +1948,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "blockchain_name_i_c", "scope": [Scope.XLSX, Scope.INDIVIDUAL_UPDATE], + "shapshot_field": "", }, { "id": "15d35efa-8f36-4a40-b9ed-a7812a665e01", @@ -1855,6 +1962,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "wallet_address_i_c", "scope": [Scope.XLSX, Scope.INDIVIDUAL_UPDATE], + "shapshot_field": "", }, { "id": "8ef6fd85-032f-42cf-8f1f-3398f88316af", @@ -1868,6 +1976,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "program_registration_id_h_c", "scope": [Scope.KOBO_IMPORT], + "shapshot_field": "", }, ] + PAYMENT_CHANNEL_FIELDS_ATTRIBUTES diff --git a/src/hct_mis_api/apps/core/field_attributes/payment_channel_fields_attributes.py b/src/hct_mis_api/apps/core/field_attributes/payment_channel_fields_attributes.py index 7ed2db7927..01a99cc81f 100644 --- a/src/hct_mis_api/apps/core/field_attributes/payment_channel_fields_attributes.py +++ b/src/hct_mis_api/apps/core/field_attributes/payment_channel_fields_attributes.py @@ -8,6 +8,12 @@ get_debit_card_number, ) +# about "shapshot_field" +# if it's the same field that can connect with "associated_with" and "name" +# like individual.full_name or household.size <-- no needed any "shapshot_field" info +# but for "account_holder_name" it gets from "bank_account_info__account_holder_name" +# or for field "debit_card_issuer" used individual.full_name and in "shapshot_field" is "full_name" + PAYMENT_CHANNEL_FIELDS_ATTRIBUTES = [ { "id": "22085a8d-205d-42a9-b5b3-951b51f11915", @@ -21,6 +27,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "account_holder_name_i_c", "scope": [Scope.XLSX, Scope.KOBO_IMPORT], + "shapshot_field": "bank_account_info__account_holder_name", }, { "id": "e9d964b9-aa85-4a0f-b1eb-4755bdad7592", @@ -34,6 +41,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "bank_branch_name_i_c", "scope": [Scope.XLSX, Scope.KOBO_IMPORT], + "shapshot_field": "bank_account_info__bank_branch_name", }, { "id": "e5766962-1455-4ebc-8fad-fc89cdde792b", @@ -47,6 +55,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "bank_name_i_c", "scope": [Scope.XLSX, Scope.XLSX_PEOPLE], + "shapshot_field": "bank_account_info__bank_name", }, { "id": "3d6a45f3-d3f7-48a0-801b-7a98c0da517a", @@ -60,6 +69,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "bank_account_number_i_c", "scope": [Scope.XLSX, Scope.XLSX_PEOPLE], + "shapshot_field": "bank_account_info__bank_account_number", }, { "id": "72e79eec-0c10-42d9-9c25-86162232a389", @@ -74,6 +84,7 @@ "xlsx_field": "debit_card_issuer_i_c", "scope": [Scope.XLSX, Scope.XLSX_PEOPLE], "lookup_function": get_debit_card_issuer, + "shapshot_field": "full_name", # based on get_debit_card_issuer }, { "id": "4a2ae111-3450-41a4-8d26-5eb20f4e233c", @@ -88,6 +99,7 @@ "xlsx_field": "debit_card_number_i_c", "scope": [Scope.XLSX, Scope.XLSX_PEOPLE], "lookup_function": get_debit_card_number, + "shapshot_field": "bank_account_info__debit_card_number", }, { "id": "4a2ae111-3450-41a4-8d26-5eb20f4e233c", diff --git a/src/hct_mis_api/apps/payment/services/payment_household_snapshot_service.py b/src/hct_mis_api/apps/payment/services/payment_household_snapshot_service.py index 316de9a582..11cbce713e 100644 --- a/src/hct_mis_api/apps/payment/services/payment_household_snapshot_service.py +++ b/src/hct_mis_api/apps/payment/services/payment_household_snapshot_service.py @@ -1,6 +1,6 @@ import datetime from decimal import Decimal -from typing import Any, Callable, Dict +from typing import Any, Callable, Dict, Optional from uuid import UUID from django.contrib.gis.geos import Point @@ -10,7 +10,7 @@ from hct_mis_api.apps.geo.models import Country from hct_mis_api.apps.grievance.models import TicketNeedsAdjudicationDetails -from hct_mis_api.apps.household.models import Individual +from hct_mis_api.apps.household.models import Individual, BankAccountInfo from hct_mis_api.apps.payment.models import ( Payment, PaymentHouseholdSnapshot, @@ -138,12 +138,14 @@ def get_individual_snapshot(individual: Individual, is_hh_collector: bool = Fals } individual_data["documents"].append(document_data) - bank_account_info = individual.bank_account_info.first() + bank_account_info: Optional[BankAccountInfo] = individual.bank_account_info.first() if bank_account_info: individual_data["bank_account_info"] = { "bank_name": bank_account_info.bank_name, "bank_account_number": bank_account_info.bank_account_number, "debit_card_number": bank_account_info.debit_card_number, + "bank_branch_name": bank_account_info.bank_branch_name, + "account_holder_name": bank_account_info.account_holder_name, } if is_hh_collector: From 3e3d3c210af8314cf1df3df96db76f698148a351 Mon Sep 17 00:00:00 2001 From: Pavlo Mokiichuk Date: Thu, 3 Oct 2024 18:24:23 +0200 Subject: [PATCH 06/12] upd mapping --- .../core_fields_attributes.py | 183 ++++----------- .../payment_channel_fields_attributes.py | 24 +- src/hct_mis_api/apps/payment/models.py | 213 ++++++++++-------- .../payment_household_snapshot_service.py | 1 + 4 files changed, 184 insertions(+), 237 deletions(-) diff --git a/src/hct_mis_api/apps/core/field_attributes/core_fields_attributes.py b/src/hct_mis_api/apps/core/field_attributes/core_fields_attributes.py index 063527b979..f6929d8bd1 100644 --- a/src/hct_mis_api/apps/core/field_attributes/core_fields_attributes.py +++ b/src/hct_mis_api/apps/core/field_attributes/core_fields_attributes.py @@ -99,11 +99,11 @@ logger = logging.getLogger(__name__) -# about "shapshot_field" -# if it's the same field that can connect with "associated_with" and "name" -# like individual.full_name or household.size <-- no needed any "shapshot_field" info +# about "snapshot_field" +# if it's the same field that can connect with "associated_with" and "lookup" +# like individual.full_name or household.size <-- no needed any "snapshot_field" info # but for "account_holder_name" it gets from "bank_account_info__account_holder_name" -# or for field "debit_card_issuer" used individual.full_name and in "shapshot_field" is "full_name" +# or for field "debit_card_issuer" used individual.full_name and in "snapshot_field" it's "full_name" CORE_FIELDS_ATTRIBUTES = [ { @@ -145,7 +145,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "consent_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE, Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { "id": "9480fc0d-1b88-45b0-9056-6a6fe0ebe509", @@ -173,7 +172,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "country_origin_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE, Scope.XLSX_PEOPLE], - "shapshot_field": "country_origin_id__iso_code3", + "snapshot_field": "country_origin_id__iso_code3", }, { "id": "aa79985c-b616-453c-9884-0666252c3070", @@ -188,7 +187,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "country_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE, Scope.XLSX_PEOPLE], - "shapshot_field": "country_id__iso_code3", + "snapshot_field": "country_id__iso_code3", }, { "id": "59685cec-69bf-4abe-81b4-70b8f05b89f3", @@ -229,7 +228,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "admin1_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], - "shapshot_field": "admin1_id__p_code", + "snapshot_field": "admin1_id__p_code", }, { "id": "e4eb6632-8204-44ed-b39c-fe791ded9246", @@ -244,7 +243,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "admin2_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], - "shapshot_field": "admin2_id__p_code", + "snapshot_field": "admin2_id__p_code", }, { "id": "92e425a7-db22-4026-8602-96c51471dfff", @@ -259,7 +258,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "admin3_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.XLSX_PEOPLE], - "shapshot_field": "admin3_id__p_code", + "snapshot_field": "admin3_id__p_code", }, { "id": "717620df-b804-4398-b789-e56aa7a3da5e", @@ -274,7 +273,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "admin4_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.XLSX_PEOPLE], - "shapshot_field": "admin4_id__p_code", + "snapshot_field": "admin4_id__p_code", }, { "id": "13a9d8b0-f278-47c2-9b1b-b06579b0ab35", @@ -594,7 +593,7 @@ "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_birth_certificate_no, # for document, it's gets from ["documents"][type][field] - "shapshot_field": "documents__birth_certificate__document_number", + "snapshot_field": "documents__birth_certificate__document_number", }, { "id": "0d8bc4b6-c2c7-4b8b-ab64-cd2e80b70717", @@ -611,7 +610,7 @@ "xlsx_field": "birth_certificate_issuer_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_birth_certificate_issuer, - "shapshot_field": "documents__", + "snapshot_field": "documents__birth_certificate__country", }, { "id": "12ceb917-8942-4cb6-a9d0-6a97a097258a", @@ -625,7 +624,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "birth_certificate_photo_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], - "shapshot_field": "documents__", + "snapshot_field": "documents__birth_certificate__photo", }, { "id": "03520f48-4918-4f7a-988d-f24476e16fc4", @@ -641,7 +640,7 @@ "xlsx_field": "tax_id_no_i_c", "scope": [Scope.XLSX, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_tax_id_no, - "shapshot_field": "documents__", + "snapshot_field": "documents__tax_id__document_number", }, { "id": "762cb2a8-b05a-47dc-81da-c71e4b1fd68f", @@ -657,7 +656,7 @@ "xlsx_field": "tax_id_issuer_i_c", "scope": [Scope.XLSX, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_tax_id_issuer, - "shapshot_field": "documents__", + "snapshot_field": "documents__tax_id__country", }, { "id": "d0e84bc0-9ac5-4c5b-bbdc-0644f5349d53", @@ -671,7 +670,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "tax_id_photo_i_c", "scope": [Scope.XLSX, Scope.XLSX_PEOPLE], - "shapshot_field": "documents__", + "snapshot_field": "documents__tax_id__photo", }, { "id": "34a9519f-9c42-4910-b097-157ec8e6e31f", @@ -687,7 +686,7 @@ "xlsx_field": "drivers_license_no_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_drivers_license_no, - "shapshot_field": "", + "snapshot_field": "documents__drivers_license__document_number", }, { "id": "00b95153-976e-4653-b9a8-843d513df873", @@ -704,7 +703,7 @@ "xlsx_field": "drivers_license_issuer_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_drivers_license_issuer, - "shapshot_field": "", + "snapshot_field": "documents__drivers_license__country", }, { "id": "7e6a41c5-0fbd-4f99-98ba-2c6a7da8dbe4", @@ -718,7 +717,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "drivers_license_photo_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], - "shapshot_field": "", + "snapshot_field": "documents__drivers_license__photo", }, { "id": "225832fc-c61b-4100-aac9-352d272d15fd", @@ -734,7 +733,7 @@ "xlsx_field": "electoral_card_no_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_electoral_card_no, - "shapshot_field": "", + "snapshot_field": "documents__electoral_card__document_number", }, { "id": "acca5a63-538a-47d1-b343-da22a86783b0", @@ -751,7 +750,7 @@ "xlsx_field": "electoral_card_issuer_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_electoral_card_issuer, - "shapshot_field": "", + "snapshot_field": "documents__electoral_card__country", }, { "id": "ffb6a487-a806-47d6-a12f-fe3c6c516976", @@ -765,7 +764,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "electoral_card_photo_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], - "shapshot_field": "", + "snapshot_field": "documents__electoral_card__photo", }, { "id": "1c7f6c85-1621-48f1-88f3-a172d69aa316", @@ -781,7 +780,7 @@ "get_query": get_unhcr_id_number_query, "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_unhcr_id_no, - "shapshot_field": "", + # "snapshot_field": # TODO: this one is not added in snapshot }, { "id": "801bdd67-d27d-4afa-9d23-823e1c8d1313", @@ -798,7 +797,7 @@ "get_query": get_unhcr_id_issuer_query, "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_unhcr_id_issuer, - "shapshot_field": "", + # "snapshot_field": # TODO: this one is not added in snapshot }, { "id": "2f9ca147-afde-4311-9d61-e906a8ef2334", @@ -812,7 +811,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "unhcr_id_photo_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], - "shapshot_field": "", + # "snapshot_field": # TODO: this one is not added in snapshot }, { "id": "4e836832-2cf2-4073-80eb-21316eaf7277", @@ -828,7 +827,7 @@ "xlsx_field": "national_passport_no_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_national_passport_no, - "shapshot_field": "", + "snapshot_field": "documents__national_passport__document_number", }, { "id": "3da62c17-66ee-4c08-adbf-74402f31dce2", @@ -845,7 +844,7 @@ "xlsx_field": "national_passport_issuer_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_national_passport_issuer, - "shapshot_field": "", + "snapshot_field": "documents__national_passport__country", }, { "id": "234a1b5b-7900-4f67-86a9-5fcaede3d09d", @@ -859,7 +858,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "national_passport_photo_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], - "shapshot_field": "", + "snapshot_field": "documents__national_passport__photo", }, { "id": "eff20a18-4336-4273-bbb8-ed0e9a94ebbb", @@ -875,7 +874,7 @@ "xlsx_field": "national_id_no_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_national_id_no, - "shapshot_field": "", + "snapshot_field": "documents__national_id__document_number", }, { "id": "2bd255a7-1273-4a52-930f-6660573b2743", @@ -892,7 +891,7 @@ "xlsx_field": "national_id_issuer_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_national_id_issuer, - "shapshot_field": "", + "snapshot_field": "documents__national_id__country", }, { "id": "d43304d9-91e4-4317-9356-f7066b898b16", @@ -906,7 +905,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "national_id_photo_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], - "shapshot_field": "", + "snapshot_field": "documents__national_id__photo", }, { "id": "201c91d2-8f89-46c9-ba5a-db7130140402", @@ -922,7 +921,7 @@ "get_query": get_scope_id_number_query, "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_scope_id_no, - "shapshot_field": "", + # "snapshot_field": # TODO: this one is not added in snapshot }, { "id": "638a6383-6e87-4c4f-842c-6c5433599267", @@ -939,7 +938,7 @@ "get_query": get_scope_id_issuer_query, "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_scope_id_issuer, - "shapshot_field": "", + # "snapshot_field": # TODO: this one is not added in snapshot }, { "id": "4aa3d595-131a-48df-8752-ec171eabe3be", @@ -953,7 +952,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "scope_id_photo_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], - "shapshot_field": "", + # "snapshot_field": # TODO: this one is not added in snapshot }, { "id": "3bf6105f-87d0-479b-bf92-7f90af4d8462", @@ -967,7 +966,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "other_id_type_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], - "shapshot_field": "", + # "snapshot_field": # TODO: this one is not added in snapshot }, { "id": "556e14af-9901-47f3-bf2c-20b4c721e8f7", @@ -983,7 +982,7 @@ "xlsx_field": "other_id_no_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_other_id_no, - "shapshot_field": "", + "snapshot_field": "documents__other_id__document_number", }, { "id": "cd181a09-f4c5-4a47-8713-25bdc4a64058", @@ -1000,7 +999,7 @@ "xlsx_field": "other_id_issuer_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], "lookup_function": get_other_id_issuer, - "shapshot_field": "", + "snapshot_field": "documents__other_id__country", }, { "id": "d4279a74-377f-4f74-baf2-e1ebd001ec5c", @@ -1014,7 +1013,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "other_id_photo_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], - "shapshot_field": "", + "snapshot_field": "documents__other_id__photo", }, { "id": "07f7005f-e70d-409b-9dee-4c3414aba40b", @@ -1028,7 +1027,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "f_0_5_age_group_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], - "shapshot_field": "", }, { "id": "6b993af8-4a5d-4a08-a444-8ade115c39ad", @@ -1042,7 +1040,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "f_6_11_age_group_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], - "shapshot_field": "", }, { "id": "71ce16b5-4e49-48fa-818c-0bd2eba079eb", @@ -1056,7 +1053,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "f_12_17_age_group_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], - "shapshot_field": "", }, { "id": "1af02ab4-469b-4de1-a71a-7d51cc86224b", @@ -1070,7 +1066,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "f_18_59_age_group_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], - "shapshot_field": "", }, { "id": "4009ca04-1ffd-48eb-a41f-64ddb439cc8d", @@ -1084,7 +1079,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "f_60_age_group_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], - "shapshot_field": "", }, { "id": "18fd9429-400f-4fce-b72f-035d2afca201", @@ -1098,7 +1092,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "pregnant_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], - "shapshot_field": "", }, { "id": "57233f1b-93c3-4fd4-a885-92c512c5e32a", @@ -1112,7 +1105,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "m_0_5_age_group_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], - "shapshot_field": "", }, { "id": "11e2a938-e93a-4c18-8eca-7e61355d7476", @@ -1126,7 +1118,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "m_6_11_age_group_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], - "shapshot_field": "", }, { "id": "bf28628e-0f6a-46e8-9587-3b0c17977006", @@ -1140,7 +1131,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "m_12_17_age_group_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], - "shapshot_field": "", }, { "id": "e76cd764-1466-49c3-92da-5a558069c472", @@ -1154,7 +1144,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "m_18_59_age_group_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], - "shapshot_field": "", }, { "id": "988ebe6d-8782-4bc1-95a0-eeb692980847", @@ -1168,7 +1157,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "m_60_age_group_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], - "shapshot_field": "", }, { "id": "4f59aca6-5900-40c0-a1e4-47c331a90a6f", @@ -1182,7 +1170,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "f_0_5_disability_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], - "shapshot_field": "", }, { "id": "10e33d7b-b3c4-4383-a4f0-6eba00a15e9c", @@ -1196,7 +1183,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "f_6_11_disability_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], - "shapshot_field": "", }, { "id": "623a6fd6-d863-40cc-a4d1-964f739747be", @@ -1210,7 +1196,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "f_12_17_disability_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], - "shapshot_field": "", }, { "id": "c99df741-4559-4166-a49c-05d69ba8a4fa", @@ -1224,7 +1209,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "f_18_59_disability_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], - "shapshot_field": "", }, { "id": "f21d61f2-923e-46b8-aefc-b623cb0026ca", @@ -1238,7 +1222,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "f_60_disability_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], - "shapshot_field": "", }, { "id": "d3b82576-1bba-44fa-9d5a-db04e71bb35b", @@ -1252,7 +1235,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "m_0_5_disability_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], - "shapshot_field": "", }, { "id": "78340f8f-86ab-464a-8e19-ce3d6feec5d6", @@ -1266,7 +1248,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "m_6_11_disability_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], - "shapshot_field": "", }, { "id": "519140f7-1a9e-4115-b736-2b09dbc6f036", @@ -1280,7 +1261,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "m_12_17_disability_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], - "shapshot_field": "", }, { "id": "9c6e1d87-0768-4db7-b925-41da8b5c7988", @@ -1294,7 +1274,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "m_18_59_disability_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], - "shapshot_field": "", }, { "id": "c07da769-b7c4-4e4f-aefa-1a20bb24d8b2", @@ -1308,7 +1287,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "m_60_disability_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE], - "shapshot_field": "", }, { "id": "b2593385-5a81-452e-ae9a-28292e35714b", @@ -1322,7 +1300,6 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "pregnant_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.INDIVIDUAL_UPDATE, Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { "id": "dca6748f-7831-4fa1-b5c8-e708a456656b", @@ -1336,7 +1313,6 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "work_status_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.INDIVIDUAL_UPDATE, Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { "id": "21cd9a35-b080-4f60-97da-6ec6918a49c0", @@ -1350,7 +1326,6 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "observed_disability_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.INDIVIDUAL_UPDATE, Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { "id": "244ec9ae-5eb8-4b80-9416-91024a3f32d7", @@ -1366,7 +1341,6 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "seeing_disability_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.INDIVIDUAL_UPDATE, Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { "id": "bef35c02-1fe7-4f6b-a0af-8282ec31de89", @@ -1382,7 +1356,6 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "hearing_disability_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.INDIVIDUAL_UPDATE, Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { "id": "b7346b1f-23ea-47a8-b2ec-c176c62cdb5b", @@ -1400,7 +1373,6 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "physical_disability_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.INDIVIDUAL_UPDATE, Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { "id": "0f24c374-4428-43ef-b162-86dc1b14e39d", @@ -1418,7 +1390,6 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "memory_disability_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.INDIVIDUAL_UPDATE, Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { "id": "59508af1-07d0-4e20-ac3d-f241bef319c1", @@ -1434,7 +1405,6 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "selfcare_disability_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.INDIVIDUAL_UPDATE, Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { "id": "78311be2-fb3f-443c-aac6-c0e7197af20d", @@ -1450,7 +1420,6 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "comms_disability_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.INDIVIDUAL_UPDATE, Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { "id": "9fbd2b6f-6713-445c-a7bb-e1efc398b20d", @@ -1464,7 +1433,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "fchild_hoh_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT], - "shapshot_field": "", }, { "id": "e92810b2-c6f1-480c-95a9-4f736a1f48bf", @@ -1478,7 +1446,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "child_hoh_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT], - "shapshot_field": "", }, { "id": "62692d6a-c054-418b-803a-e34393cbc1b0", @@ -1492,7 +1459,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "village_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE, Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { "id": "c640fe45-368f-4206-afae-09700a495db3", @@ -1506,7 +1472,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "deviceid", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { "id": "8f379d33-c5fd-4344-ba2b-73e136aba13a", @@ -1520,7 +1485,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "name_enumerator_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE, Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { "id": "201e9a88-fb7d-4ba4-afec-66aba748fe55", @@ -1534,7 +1498,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "org_enumerator_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE, Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { "id": "0858371e-4e5c-402e-9cda-b767eb2d337c", @@ -1548,7 +1511,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "consent_sharing_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { "id": "27bd4ef2-442d-4b49-976c-063df050b3ae", @@ -1562,7 +1524,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "org_name_enumerator_h_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.HOUSEHOLD_UPDATE, Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { "id": "69dbc5f2-039f-4671-b39f-a63d96475cab", @@ -1576,7 +1537,6 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "disability_i_c", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.INDIVIDUAL_UPDATE, Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { "id": "6536a987-a50e-453b-9517-57c1dccd1340", @@ -1590,7 +1550,6 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "first_registration_date_i_c", "scope": [Scope.GLOBAL, Scope.XLSX, Scope.TARGETING, Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { "id": "2fe6d876-388f-45d9-b497-eb2f8af923e8", @@ -1604,7 +1563,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "first_registration_date_h_c", "scope": [Scope.GLOBAL, Scope.XLSX, Scope.TARGETING], - "shapshot_field": "", }, { "id": "c8da2910-4348-47ab-a82e-725b4cebc332", @@ -1618,7 +1576,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "number_of_children", "scope": [Scope.TARGETING], - "shapshot_field": "", }, { "id": "bc18c462-bd75-4607-b75d-b7111a658453", @@ -1633,13 +1590,13 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "has_phone_number", "scope": [Scope.TARGETING], - "shapshot_field": "", + # "snapshot_field": # TODO: this one is not added in snapshot }, { "id": "f4032e4f-00a9-4ed9-bff4-4e47d2f7b4be", "type": TYPE_BOOL, "name": "has_tax_id_number", - "lookup": "has_phone_number", + "lookup": "has_tax_id_number", "get_query": get_has_tax_id_query, "label": {"English(EN)": "Has tax ID number?"}, "hint": "", @@ -1648,13 +1605,13 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "has_tax_ID_number", "scope": [Scope.TARGETING], - "shapshot_field": "", + # "snapshot_field": # TODO: this one is not added in snapshot }, { "id": "6b97e9a3-38bb-49a3-9637-65f05d5b8ea4", "type": TYPE_BOOL, "name": "has_the_bank_account_number", - "lookup": "has_phone_number", + "lookup": "has_the_bank_account_number", "get_query": get_has_bank_account_number_query, "label": {"English(EN)": "Has the bank account number?"}, "hint": "", @@ -1663,7 +1620,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "has_the_bank_account_number", "scope": [Scope.TARGETING], - "shapshot_field": "", + # "snapshot_field": # TODO: this one is not added in snapshot }, { "id": "0bf5fad3-8f4f-4528-85f7-57e8a84a2a43", @@ -1678,7 +1635,7 @@ "xlsx_field": "role_i_c", "get_query": get_role_query, "scope": [Scope.TARGETING, Scope.KOBO_IMPORT, Scope.INDIVIDUAL_UPDATE], - "shapshot_field": "", + "snapshot_field": "roles__role__individual__id", # TODO: add method for get role from snapshot }, { "id": "a1662e94-5d8b-46a7-8c9a-7c72b280f497", @@ -1694,7 +1651,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "registration_data_import", "scope": [Scope.TARGETING, Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { "id": "e4aa9cdf-2c9e-4e22-9928-2b63a6ea4ef0", @@ -1710,7 +1666,6 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "registration_data_import", "scope": [Scope.TARGETING], - "shapshot_field": "", }, { "id": "df2b2588-68af-4dea-89ab-c5a53a5764be", @@ -1724,7 +1679,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "unicef_id_h_c", "scope": [Scope.INDIVIDUAL_XLSX_UPDATE], - "shapshot_field": "", }, { "id": "bb9bcb76-c9e4-4e83-8b9a-6c8bb35cb84c", @@ -1738,7 +1692,6 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "unicef_id_i_c", "scope": [Scope.INDIVIDUAL_XLSX_UPDATE], - "shapshot_field": "", }, { "id": "e48a4e26-6552-4e15-855f-9ea4d5268c84", @@ -1752,7 +1705,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "admin_area_h_c", "scope": [Scope.HOUSEHOLD_UPDATE], - "shapshot_field": "", + "snapshot_field": "admin_area_id__p_code", }, { "id": "9da8c56a-3c65-47d9-8149-699761842ce4", @@ -1766,7 +1719,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "start", "scope": [Scope.KOBO_IMPORT], - "shapshot_field": "", }, { "id": "06e4c4a0-28d2-4530-be24-92623a5b48b0", @@ -1780,7 +1732,7 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "end", "scope": [Scope.KOBO_IMPORT], - "shapshot_field": "", + # "snapshot_field": # TODO: this one is not added in snapshot }, { "id": "16e68909-54f4-47cb-ab06-066d13e85134", @@ -1794,7 +1746,7 @@ "xlsx_field": "primary_collector_id", "custom_cast_value": Countries.get_country_value, "scope": [Scope.COLLECTOR, Scope.XLSX_PEOPLE], - "shapshot_field": "", + "snapshot_field": "primary_collector__id", }, { "id": "d7f41696-94e5-4e0c-ab1b-20ff27c25364", @@ -1808,7 +1760,7 @@ "xlsx_field": "alternate_collector_id", "custom_cast_value": Countries.get_country_value, "scope": [Scope.COLLECTOR, Scope.XLSX_PEOPLE], - "shapshot_field": "", + "snapshot_field": "alternate_collector__id", }, { "id": "746b3d2d-19c5-4b91-ad37-d230e1d33eb5", @@ -1822,7 +1774,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "household_id", "scope": [Scope.HOUSEHOLD_ID], - "shapshot_field": "", }, { "id": "1079bfd0-fc51-41ab-aa10-667e6b2034b9", @@ -1836,7 +1787,6 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "household_id", "scope": [Scope.HOUSEHOLD_ID], - "shapshot_field": "", }, { "id": "519af6bf-3e8c-4899-a4f4-360ce55648e0", @@ -1850,7 +1800,6 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "email", "scope": [Scope.XLSX, Scope.INDIVIDUAL_UPDATE, Scope.INDIVIDUAL_XLSX_UPDATE, Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { "id": "e2b8dd27-f861-4c54-9651-677636918c47", @@ -1864,7 +1813,6 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "preferred_language", "scope": [Scope.XLSX, Scope.INDIVIDUAL_UPDATE, Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { "id": "32fcdace-6e72-48ac-9ce8-469e552cfafe", @@ -1878,37 +1826,9 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "age_at_registration", "scope": [Scope.GLOBAL, Scope.TARGETING, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], - "shapshot_field": "", - }, - { - "id": "22085a8d-205d-42a9-b5b3-951b51f11915", - "type": TYPE_STRING, - "name": "account_holder_name", - "lookup": "account_holder_name", - "label": {"English(EN)": "Account holder name"}, - "hint": "", - "required": False, - "choices": [], - "associated_with": _INDIVIDUAL, - "xlsx_field": "account_holder_name_i_c", - "scope": [Scope.XLSX, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], - "shapshot_field": "", - }, - { - "id": "e9d964b9-aa85-4a0f-b1eb-4755bdad7592", - "type": TYPE_STRING, - "name": "bank_branch_name", - "lookup": "bank_branch_name", - "label": {"English(EN)": "Bank branch name"}, - "hint": "", - "required": False, - "choices": [], - "associated_with": _INDIVIDUAL, - "xlsx_field": "bank_branch_name_i_c", - "scope": [Scope.XLSX, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { + # just using specifically for people import for mapping collector and beneficiary "id": "bec7a6b9-476d-48a8-8822-e24ae023df42", "type": TYPE_INTEGER, "name": "index_id", @@ -1920,7 +1840,6 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "index_id", "scope": [Scope.XLSX_PEOPLE], - "shapshot_field": "", }, { "id": "15b7b623-0dee-4212-a32a-a6d6f6877b4e", @@ -1934,7 +1853,6 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "wallet_name_i_c", "scope": [Scope.XLSX, Scope.INDIVIDUAL_UPDATE], - "shapshot_field": "", }, { "id": "0010bc3e-0a4a-452b-a776-2cc4b760a0fd", @@ -1948,7 +1866,6 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "blockchain_name_i_c", "scope": [Scope.XLSX, Scope.INDIVIDUAL_UPDATE], - "shapshot_field": "", }, { "id": "15d35efa-8f36-4a40-b9ed-a7812a665e01", @@ -1962,7 +1879,6 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "wallet_address_i_c", "scope": [Scope.XLSX, Scope.INDIVIDUAL_UPDATE], - "shapshot_field": "", }, { "id": "8ef6fd85-032f-42cf-8f1f-3398f88316af", @@ -1976,7 +1892,6 @@ "associated_with": _HOUSEHOLD, "xlsx_field": "program_registration_id_h_c", "scope": [Scope.KOBO_IMPORT], - "shapshot_field": "", }, ] + PAYMENT_CHANNEL_FIELDS_ATTRIBUTES diff --git a/src/hct_mis_api/apps/core/field_attributes/payment_channel_fields_attributes.py b/src/hct_mis_api/apps/core/field_attributes/payment_channel_fields_attributes.py index 01a99cc81f..85a7aebdab 100644 --- a/src/hct_mis_api/apps/core/field_attributes/payment_channel_fields_attributes.py +++ b/src/hct_mis_api/apps/core/field_attributes/payment_channel_fields_attributes.py @@ -8,11 +8,11 @@ get_debit_card_number, ) -# about "shapshot_field" -# if it's the same field that can connect with "associated_with" and "name" -# like individual.full_name or household.size <-- no needed any "shapshot_field" info +# about "snapshot_field" +# if it's the same field that can connect with "associated_with" and "lookup" +# like individual.full_name or household.size <-- no needed any "snapshot_field" info # but for "account_holder_name" it gets from "bank_account_info__account_holder_name" -# or for field "debit_card_issuer" used individual.full_name and in "shapshot_field" is "full_name" +# or for field "debit_card_issuer" used individual.full_name and in "snapshot_field" it's "full_name" PAYMENT_CHANNEL_FIELDS_ATTRIBUTES = [ { @@ -26,8 +26,8 @@ "choices": [], "associated_with": _INDIVIDUAL, "xlsx_field": "account_holder_name_i_c", - "scope": [Scope.XLSX, Scope.KOBO_IMPORT], - "shapshot_field": "bank_account_info__account_holder_name", + "scope": [Scope.XLSX, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], + "snapshot_field": "bank_account_info__account_holder_name", }, { "id": "e9d964b9-aa85-4a0f-b1eb-4755bdad7592", @@ -40,8 +40,8 @@ "choices": [], "associated_with": _INDIVIDUAL, "xlsx_field": "bank_branch_name_i_c", - "scope": [Scope.XLSX, Scope.KOBO_IMPORT], - "shapshot_field": "bank_account_info__bank_branch_name", + "scope": [Scope.XLSX, Scope.KOBO_IMPORT, Scope.XLSX_PEOPLE], + "snapshot_field": "bank_account_info__bank_branch_name", }, { "id": "e5766962-1455-4ebc-8fad-fc89cdde792b", @@ -55,7 +55,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "bank_name_i_c", "scope": [Scope.XLSX, Scope.XLSX_PEOPLE], - "shapshot_field": "bank_account_info__bank_name", + "snapshot_field": "bank_account_info__bank_name", }, { "id": "3d6a45f3-d3f7-48a0-801b-7a98c0da517a", @@ -69,7 +69,7 @@ "associated_with": _INDIVIDUAL, "xlsx_field": "bank_account_number_i_c", "scope": [Scope.XLSX, Scope.XLSX_PEOPLE], - "shapshot_field": "bank_account_info__bank_account_number", + "snapshot_field": "bank_account_info__bank_account_number", }, { "id": "72e79eec-0c10-42d9-9c25-86162232a389", @@ -84,7 +84,7 @@ "xlsx_field": "debit_card_issuer_i_c", "scope": [Scope.XLSX, Scope.XLSX_PEOPLE], "lookup_function": get_debit_card_issuer, - "shapshot_field": "full_name", # based on get_debit_card_issuer + "snapshot_field": "full_name", # based on get_debit_card_issuer }, { "id": "4a2ae111-3450-41a4-8d26-5eb20f4e233c", @@ -99,7 +99,7 @@ "xlsx_field": "debit_card_number_i_c", "scope": [Scope.XLSX, Scope.XLSX_PEOPLE], "lookup_function": get_debit_card_number, - "shapshot_field": "bank_account_info__debit_card_number", + "snapshot_field": "bank_account_info__debit_card_number", }, { "id": "4a2ae111-3450-41a4-8d26-5eb20f4e233c", diff --git a/src/hct_mis_api/apps/payment/models.py b/src/hct_mis_api/apps/payment/models.py index 6d1d4226e2..1f89bb6896 100644 --- a/src/hct_mis_api/apps/payment/models.py +++ b/src/hct_mis_api/apps/payment/models.py @@ -95,7 +95,7 @@ if TYPE_CHECKING: from hct_mis_api.apps.account.models import User from hct_mis_api.apps.core.exchange_rates.api import ExchangeRateClient - from hct_mis_api.apps.geo.models import Area + from hct_mis_api.apps.geo.models import Area, Country from hct_mis_api.apps.grievance.models import GrievanceTicket from hct_mis_api.apps.program.models import Program @@ -1192,22 +1192,73 @@ class FinancialServiceProviderXlsxTemplate(TimeStampedUUIDModel): blank=True, ) - @classmethod + @staticmethod + def get_data_from_payment_snapshot(household_data: Dict[str, Any], core_field: Dict[str, Any], delivery_mechanism_data: Optional["DeliveryMechanismData"] = None,) -> Optional[str]: + core_field_name = core_field["name"] + collector_data = ( + household_data.get("primary_collector") or household_data.get("alternate_collector") or dict() + ) + primary_collector = household_data.get("primary_collector", {}) + alternate_collector = household_data.get("alternate_collector", {}) + + if delivery_mechanism_data and core_field["associated_with"] == _DELIVERY_MECHANISM_DATA: + delivery_mech_data = collector_data.get("delivery_mechanisms_data", {}).get( + delivery_mechanism_data.delivery_mechanism.code, {}) + return delivery_mech_data.get(core_field_name, None) + + lookup = core_field["lookup"] + main_key = None # just help find specific field from snapshot + snapshot_field_path = core_field.get("snapshot_field") + if snapshot_field_path: + snapshot_field_path_split = snapshot_field_path.split("__") + main_key = snapshot_field_path.split("__")[0] if len(snapshot_field_path_split) > 0 else None + + if main_key in {"country_origin_id", "country_id"}: + country = Country.objects.filter(pk=household_data.get(main_key)).first() + return country.iso_code3 if country else None + + if main_key in {"admin1_id", "admin2_id", "admin3_id", "admin4_id", "admin_area_id"}: + area = Area.objects.filter(pk=household_data.get(main_key)).first() + return area.p_code if area else None + + if main_key == "roles": + lookup_id = primary_collector.get("id") or alternate_collector.get("id") + if not lookup_id: + return None + + for role in household_data.get("roles", []): + individual = role.get("individual", {}) + if individual.get("id") == lookup_id: + return role.get("role") + # return None if role not found + return None + + if main_key in {"primary_collector", "alternate_collector"}: + return household_data.get(main_key, {}).get("id") + + if main_key == "bank_account_info": + bank_account_info_lookup = snapshot_field_path[1] + return collector_data.get("bank_account_info", {}).get(bank_account_info_lookup) + + if main_key == "documents": + doc_type, doc_lookup = snapshot_field_path[1], snapshot_field_path[2] + documents_list = collector_data.get("documents", []) + documents_dict = {doc.get("type"): doc for doc in documents_list} + return documents_dict.get(doc_type, {}).get(doc_lookup) + + if core_field["associated_with"] == _INDIVIDUAL: + return collector_data.get(lookup, None) or collector_data.get(main_key, None) + + if core_field["associated_with"] == _HOUSEHOLD: + return household_data.get(lookup, None) + + @staticmethod def get_column_from_core_field( cls, payment: "Payment", core_field_name: str, delivery_mechanism_data: Optional["DeliveryMechanismData"] = None, ) -> Any: - # TODO: get from snapshot all - def parse_admin_area(obj: "Area") -> str: - if not obj: - return "" # pragma: no cover - return f"{obj.p_code} - {obj.name}" - - collector = payment.collector - household = payment.household - core_fields_attributes = FieldFactory(get_core_fields_attributes()).to_dict_by("name") core_field = core_fields_attributes.get(core_field_name) if not core_field: @@ -1215,76 +1266,42 @@ def parse_admin_area(obj: "Area") -> str: # which are not applicable to "People" export. return None - if delivery_mechanism_data and core_field["associated_with"] == _DELIVERY_MECHANISM_DATA: - return delivery_mechanism_data.delivery_data.get(core_field_name, None) - snapshot = getattr(payment, "household_snapshot", None) + if not snapshot: + logger.error(f"Not found snapshot for Payment {payment.unicef_id}") + return None - # fields from snap_shot - bank_account_info = ["bank_name", "bank_account_number", "debit_card_number"] - get_from_snapshot = ["full_name", "payment_delivery_phone_no"] + bank_account_info - - # individual_data["delivery_mechanisms_data"] = { - # dmd.delivery_mechanism.code: dmd.delivery_data for dmd in individual.delivery_mechanisms_data.all() - # } ??? - - lookup = core_field["lookup"] - lookup = lookup.replace("__", ".") - - if core_field["associated_with"] == _INDIVIDUAL: - if lookup in get_from_snapshot and snapshot: - snapshot_data = snapshot.snapshot_data - collector_data = ( - snapshot_data.get("primary_collector") or snapshot_data.get("alternate_collector") or dict() - ) - if lookup in bank_account_info: - return collector_data.get("bank_account_info").get(lookup) - return nested_getattr(collector_data, lookup, None) - - if lookup_function := core_field.get("lookup_function"): - return lookup_function(collector) - return nested_getattr(collector, lookup, None) - - if core_field["associated_with"] == _HOUSEHOLD: - if core_field_name in {"admin1", "admin2", "admin3", "admin4"}: - admin_area = getattr(household, core_field_name) - return parse_admin_area(admin_area) - return nested_getattr(household, lookup, None) + snapshot_data = cls.get_data_from_payment_snapshot(snapshot.snapshot_data, core_field, delivery_mechanism_data) - return None # pragma: no cover + return snapshot_data @classmethod - def get_column_value_from_payment(cls, payment: "Payment", column_name: str) -> Union[str, float, list]: - # TODO: get from snapshot all + def get_column_value_from_payment(cls, payment: "Payment", column_name: str) -> Union[str, float, list, None]: # we can get if needed payment.parent.program.is_social_worker_program - alternate_collector = None - alternate_collector_column_names = ( - "alternate_collector_full_name", - "alternate_collector_given_name", - "alternate_collector_middle_name", - "alternate_collector_sex", - "alternate_collector_phone_no", - "alternate_collector_document_numbers", + snapshot = getattr(payment, "household_snapshot", None) + if not snapshot: + logger.error(f"Not found snapshot for Payment {payment.unicef_id}") + return None + snapshot_data = snapshot.snapshot_data + primary_collector = snapshot_data.get("primary_collector", {}) + alternate_collector = snapshot_data.get("alternate_collector", {}) + collector_data = ( + primary_collector or alternate_collector or dict() ) - if column_name in alternate_collector_column_names: - if ind_role := IndividualRoleInHousehold.objects.filter( - household=payment.household, role=ROLE_ALTERNATE - ).first(): - alternate_collector = ind_role.individual map_obj_name_column = { "payment_id": (payment, "unicef_id"), - "individual_id": (payment.household.individuals.first(), "unicef_id"), # add for people export - "household_id": (payment.household, "unicef_id"), # remove for people export - "household_size": (payment.household, "size"), # remove for people export - "admin_level_2": (payment.household.admin2, "name"), - "village": (payment.household, "village"), - "collector_name": (payment.collector, "full_name"), - "alternate_collector_full_name": (alternate_collector, "full_name"), # TODO: get from snapshot - "alternate_collector_given_name": (alternate_collector, "given_name"), # TODO: get from snapshot - "alternate_collector_middle_name": (alternate_collector, "middle_name"), # TODO: get from snapshot + "individual_id": (collector_data, "unicef_id"), # add for people export + "household_id": (snapshot_data, "unicef_id"), # remove for people export + "household_size": (snapshot_data, "size"), # remove for people export + "admin_level_2": (snapshot_data, "admin2"), + "village": (snapshot_data, "village"), + "collector_name": (collector_data, "full_name"), + "alternate_collector_full_name": (alternate_collector, "full_name"), + "alternate_collector_given_name": (alternate_collector, "given_name"), + "alternate_collector_middle_name": (alternate_collector, "middle_name"), "alternate_collector_sex": (alternate_collector, "sex"), - "alternate_collector_phone_no": (alternate_collector, "phone_no"), # TODO: get from snapshot + "alternate_collector_phone_no": (alternate_collector, "phone_no"), "alternate_collector_document_numbers": (alternate_collector, "document_number"), "payment_channel": (payment.delivery_type, "name"), "fsp_name": (payment.financial_service_provider, "name"), @@ -1304,15 +1321,17 @@ def get_column_value_from_payment(cls, payment: "Payment", column_name: str) -> "transaction_status_blockchain_link": ( payment, "transaction_status_blockchain_link", - ), # TODO: get from snapshot ? + ), } additional_columns = { "registration_token": cls.get_registration_token_doc_number, "national_id": cls.get_national_id_doc_number, + "admin_level_2": cls.get_admin_level_2, + "alternate_collector_document_numbers": cls.get_alternate_collector_doc_numbers, } if column_name in additional_columns: method = additional_columns[column_name] - return method(payment) + return method(snapshot_data) if column_name not in map_obj_name_column: return "wrong_column_name" @@ -1320,31 +1339,43 @@ def get_column_value_from_payment(cls, payment: "Payment", column_name: str) -> return float(-1) if column_name == "delivery_date" and payment.delivery_date is not None: return str(payment.delivery_date) - if column_name == "alternate_collector_document_numbers" and alternate_collector: - return ( - list( - alternate_collector.documents.filter(status=Document.STATUS_VALID).values_list( - "document_number", flat=True - ) - ) - or "" - ) + obj, nested_field = map_obj_name_column[column_name] + # return if obj is dictionary from snapshot + if isinstance(obj, dict): + return obj.get(nested_field, "") + # return if obj is model return getattr(obj, nested_field, None) or "" @staticmethod - def get_registration_token_doc_number(payment: "Payment") -> str: - doc = Document.objects.filter(individual=payment.collector, type__key="registration_token").first() - return doc.document_number if doc else "" + def get_registration_token_doc_number(snapshot_data: Dict[str, Any]) -> str: + collector_data = ( + snapshot_data.get("primary_collector", {}) or snapshot_data.get("alternate_collector", {}) or dict() + ) + documents_list = collector_data.get("documents", []) + documents_dict = {doc.get("type"): doc for doc in documents_list} + return documents_dict.get("registration_token", {}).get("document_number", "") @staticmethod - def get_national_id_doc_number(payment: "Payment") -> str: - doc = Document.objects.filter( - individual=payment.collector, - type__key=IDENTIFICATION_TYPE_NATIONAL_ID.lower(), - status=Document.STATUS_VALID, - ).first() - return doc.document_number if doc else "" + def get_national_id_doc_number(snapshot_data: Dict[str, Any]) -> str: + collector_data = ( + snapshot_data.get("primary_collector", {}) or snapshot_data.get("alternate_collector", {}) or dict() + ) + documents_list = collector_data.get("documents", []) + documents_dict = {doc.get("type"): doc for doc in documents_list} + return documents_dict.get("national_id", {}).get("document_number", "") + + @staticmethod + def get_alternate_collector_doc_numbers(snapshot_data: Dict[str, Any]) -> str: + alternate_collector_data = snapshot_data.get("alternate_collector", {}) or dict() + doc_list = alternate_collector_data.get("documents", []) + doc_numbers = [doc.get("document_number", "") for doc in doc_list] + return ", ".join(doc_numbers) + + @staticmethod + def get_admin_level_2(snapshot_data: Dict[str, Any]) -> str: + area = Area.objects.filter(pk=snapshot_data.get("admin2_id")).first() + return area.name if area else "" def __str__(self) -> str: return f"{self.name} ({len(self.columns) + len(self.core_fields)})" diff --git a/src/hct_mis_api/apps/payment/services/payment_household_snapshot_service.py b/src/hct_mis_api/apps/payment/services/payment_household_snapshot_service.py index 11cbce713e..be26426990 100644 --- a/src/hct_mis_api/apps/payment/services/payment_household_snapshot_service.py +++ b/src/hct_mis_api/apps/payment/services/payment_household_snapshot_service.py @@ -135,6 +135,7 @@ def get_individual_snapshot(individual: Individual, is_hh_collector: bool = Fals "cleared": document.cleared, "cleared_by": handle_type_mapping(document.cleared_by), "cleared_date": handle_type_mapping(document.cleared_date), + "photo": document.photo.name if document.photo else None, } individual_data["documents"].append(document_data) From 68e07cb2e9aeb2edef0b80b784b6adaf03782010 Mon Sep 17 00:00:00 2001 From: Pavlo Mokiichuk Date: Thu, 3 Oct 2024 18:43:53 +0200 Subject: [PATCH 07/12] imports --- src/hct_mis_api/apps/payment/models.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/hct_mis_api/apps/payment/models.py b/src/hct_mis_api/apps/payment/models.py index 1f89bb6896..724baba5ac 100644 --- a/src/hct_mis_api/apps/payment/models.py +++ b/src/hct_mis_api/apps/payment/models.py @@ -66,15 +66,10 @@ ) from hct_mis_api.apps.core.mixins import LimitBusinessAreaModelMixin from hct_mis_api.apps.core.models import BusinessArea, FileTemp, FlexibleAttribute -from hct_mis_api.apps.core.utils import nested_getattr from hct_mis_api.apps.household.models import ( FEMALE, - IDENTIFICATION_TYPE_NATIONAL_ID, MALE, - ROLE_ALTERNATE, - Document, Individual, - IndividualRoleInHousehold, ) from hct_mis_api.apps.payment.delivery_mechanisms import DeliveryMechanismChoices from hct_mis_api.apps.payment.fields import DynamicChoiceArrayField @@ -1193,17 +1188,20 @@ class FinancialServiceProviderXlsxTemplate(TimeStampedUUIDModel): ) @staticmethod - def get_data_from_payment_snapshot(household_data: Dict[str, Any], core_field: Dict[str, Any], delivery_mechanism_data: Optional["DeliveryMechanismData"] = None,) -> Optional[str]: + def get_data_from_payment_snapshot( + household_data: Dict[str, Any], + core_field: Dict[str, Any], + delivery_mechanism_data: Optional["DeliveryMechanismData"] = None, + ) -> Optional[str]: core_field_name = core_field["name"] - collector_data = ( - household_data.get("primary_collector") or household_data.get("alternate_collector") or dict() - ) + collector_data = household_data.get("primary_collector") or household_data.get("alternate_collector") or dict() primary_collector = household_data.get("primary_collector", {}) alternate_collector = household_data.get("alternate_collector", {}) if delivery_mechanism_data and core_field["associated_with"] == _DELIVERY_MECHANISM_DATA: delivery_mech_data = collector_data.get("delivery_mechanisms_data", {}).get( - delivery_mechanism_data.delivery_mechanism.code, {}) + delivery_mechanism_data.delivery_mechanism.code, {} + ) return delivery_mech_data.get(core_field_name, None) lookup = core_field["lookup"] @@ -1285,9 +1283,7 @@ def get_column_value_from_payment(cls, payment: "Payment", column_name: str) -> snapshot_data = snapshot.snapshot_data primary_collector = snapshot_data.get("primary_collector", {}) alternate_collector = snapshot_data.get("alternate_collector", {}) - collector_data = ( - primary_collector or alternate_collector or dict() - ) + collector_data = primary_collector or alternate_collector or dict() map_obj_name_column = { "payment_id": (payment, "unicef_id"), @@ -1350,7 +1346,7 @@ def get_column_value_from_payment(cls, payment: "Payment", column_name: str) -> @staticmethod def get_registration_token_doc_number(snapshot_data: Dict[str, Any]) -> str: collector_data = ( - snapshot_data.get("primary_collector", {}) or snapshot_data.get("alternate_collector", {}) or dict() + snapshot_data.get("primary_collector", {}) or snapshot_data.get("alternate_collector", {}) or dict() ) documents_list = collector_data.get("documents", []) documents_dict = {doc.get("type"): doc for doc in documents_list} @@ -1359,7 +1355,7 @@ def get_registration_token_doc_number(snapshot_data: Dict[str, Any]) -> str: @staticmethod def get_national_id_doc_number(snapshot_data: Dict[str, Any]) -> str: collector_data = ( - snapshot_data.get("primary_collector", {}) or snapshot_data.get("alternate_collector", {}) or dict() + snapshot_data.get("primary_collector", {}) or snapshot_data.get("alternate_collector", {}) or dict() ) documents_list = collector_data.get("documents", []) documents_dict = {doc.get("type"): doc for doc in documents_list} From c208552e1c3a7a711906fc33d132bc35db87b46c Mon Sep 17 00:00:00 2001 From: Pavlo Mokiichuk Date: Fri, 4 Oct 2024 10:45:50 +0200 Subject: [PATCH 08/12] isort --- src/hct_mis_api/apps/payment/models.py | 6 +----- .../payment/services/payment_household_snapshot_service.py | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/hct_mis_api/apps/payment/models.py b/src/hct_mis_api/apps/payment/models.py index 78e7d161d1..bd03085092 100644 --- a/src/hct_mis_api/apps/payment/models.py +++ b/src/hct_mis_api/apps/payment/models.py @@ -66,11 +66,7 @@ ) from hct_mis_api.apps.core.mixins import LimitBusinessAreaModelMixin from hct_mis_api.apps.core.models import BusinessArea, FileTemp, FlexibleAttribute -from hct_mis_api.apps.household.models import ( - FEMALE, - MALE, - Individual, -) +from hct_mis_api.apps.household.models import FEMALE, MALE, Individual from hct_mis_api.apps.payment.delivery_mechanisms import DeliveryMechanismChoices from hct_mis_api.apps.payment.fields import DynamicChoiceArrayField from hct_mis_api.apps.payment.managers import PaymentManager diff --git a/src/hct_mis_api/apps/payment/services/payment_household_snapshot_service.py b/src/hct_mis_api/apps/payment/services/payment_household_snapshot_service.py index be26426990..619ef7efd0 100644 --- a/src/hct_mis_api/apps/payment/services/payment_household_snapshot_service.py +++ b/src/hct_mis_api/apps/payment/services/payment_household_snapshot_service.py @@ -10,7 +10,7 @@ from hct_mis_api.apps.geo.models import Country from hct_mis_api.apps.grievance.models import TicketNeedsAdjudicationDetails -from hct_mis_api.apps.household.models import Individual, BankAccountInfo +from hct_mis_api.apps.household.models import BankAccountInfo, Individual from hct_mis_api.apps.payment.models import ( Payment, PaymentHouseholdSnapshot, From 7cc67822ec9e719282644f591f67a7395db46e68 Mon Sep 17 00:00:00 2001 From: Pavlo Mokiichuk Date: Sun, 6 Oct 2024 23:54:11 +0200 Subject: [PATCH 09/12] upd tests --- src/hct_mis_api/apps/payment/models.py | 61 +- .../apps/payment/services/payment_gateway.py | 1 + .../payment_household_snapshot_service.py | 3 +- ...lsx_payment_plan_export_per_fsp_service.py | 24 +- tests/unit/apps/core/test_core_fields.py | 4 +- .../apps/core/test_kobo_template_upload.py | 2 +- ...test_fsp_xlsx_template_get_column_value.py | 15 +- ...import_export_payment_plan_payment_list.py | 44 +- tests/unit/apps/payment/test_models.py | 574 +++++++++++++++++- tests/unit/apps/payment/test_models1.py | 563 ----------------- .../test_payment_plan_reconciliation.py | 8 +- .../payment/test_payment_plan_services.py | 4 +- .../test_template_file_generator.py | 6 +- 13 files changed, 678 insertions(+), 631 deletions(-) delete mode 100644 tests/unit/apps/payment/test_models1.py diff --git a/src/hct_mis_api/apps/payment/models.py b/src/hct_mis_api/apps/payment/models.py index bd03085092..3f945c4745 100644 --- a/src/hct_mis_api/apps/payment/models.py +++ b/src/hct_mis_api/apps/payment/models.py @@ -66,6 +66,7 @@ ) from hct_mis_api.apps.core.mixins import LimitBusinessAreaModelMixin from hct_mis_api.apps.core.models import BusinessArea, FileTemp, FlexibleAttribute +from hct_mis_api.apps.geo.models import Area, Country from hct_mis_api.apps.household.models import FEMALE, MALE, Individual from hct_mis_api.apps.payment.delivery_mechanisms import DeliveryMechanismChoices from hct_mis_api.apps.payment.fields import DynamicChoiceArrayField @@ -86,7 +87,6 @@ if TYPE_CHECKING: from hct_mis_api.apps.account.models import User from hct_mis_api.apps.core.exchange_rates.api import ExchangeRateClient - from hct_mis_api.apps.geo.models import Area, Country from hct_mis_api.apps.grievance.models import GrievanceTicket from hct_mis_api.apps.program.models import Program @@ -1207,38 +1207,38 @@ def get_data_from_payment_snapshot( snapshot_field_path_split = snapshot_field_path.split("__") main_key = snapshot_field_path.split("__")[0] if len(snapshot_field_path_split) > 0 else None - if main_key in {"country_origin_id", "country_id"}: - country = Country.objects.filter(pk=household_data.get(main_key)).first() - return country.iso_code3 if country else None + if main_key in {"country_origin_id", "country_id"}: + country = Country.objects.filter(pk=household_data.get(main_key)).first() + return country.iso_code3 if country else None - if main_key in {"admin1_id", "admin2_id", "admin3_id", "admin4_id", "admin_area_id"}: - area = Area.objects.filter(pk=household_data.get(main_key)).first() - return area.p_code if area else None + if main_key in {"admin1_id", "admin2_id", "admin3_id", "admin4_id", "admin_area_id"}: + area = Area.objects.filter(pk=household_data.get(main_key)).first() + return area.p_code if area else None - if main_key == "roles": - lookup_id = primary_collector.get("id") or alternate_collector.get("id") - if not lookup_id: - return None + if main_key == "roles": + lookup_id = primary_collector.get("id") or alternate_collector.get("id") + if not lookup_id: + return None - for role in household_data.get("roles", []): - individual = role.get("individual", {}) - if individual.get("id") == lookup_id: - return role.get("role") - # return None if role not found - return None + for role in household_data.get("roles", []): + individual = role.get("individual", {}) + if individual.get("id") == lookup_id: + return role.get("role") + # return None if role not found + return None - if main_key in {"primary_collector", "alternate_collector"}: - return household_data.get(main_key, {}).get("id") + if main_key in {"primary_collector", "alternate_collector"}: + return household_data.get(main_key, {}).get("id") - if main_key == "bank_account_info": - bank_account_info_lookup = snapshot_field_path[1] - return collector_data.get("bank_account_info", {}).get(bank_account_info_lookup) + if main_key == "bank_account_info": + bank_account_info_lookup = snapshot_field_path_split[1] + return collector_data.get("bank_account_info", {}).get(bank_account_info_lookup) - if main_key == "documents": - doc_type, doc_lookup = snapshot_field_path[1], snapshot_field_path[2] - documents_list = collector_data.get("documents", []) - documents_dict = {doc.get("type"): doc for doc in documents_list} - return documents_dict.get(doc_type, {}).get(doc_lookup) + if main_key == "documents": + doc_type, doc_lookup = snapshot_field_path_split[1], snapshot_field_path_split[2] + documents_list = collector_data.get("documents", []) + documents_dict = {doc.get("type"): doc for doc in documents_list} + return documents_dict.get(doc_type, {}).get(doc_lookup) if core_field["associated_with"] == _INDIVIDUAL: return collector_data.get(lookup, None) or collector_data.get(main_key, None) @@ -1246,9 +1246,10 @@ def get_data_from_payment_snapshot( if core_field["associated_with"] == _HOUSEHOLD: return household_data.get(lookup, None) + return None + @staticmethod def get_column_from_core_field( - cls, payment: "Payment", core_field_name: str, delivery_mechanism_data: Optional["DeliveryMechanismData"] = None, @@ -1265,7 +1266,9 @@ def get_column_from_core_field( logger.error(f"Not found snapshot for Payment {payment.unicef_id}") return None - snapshot_data = cls.get_data_from_payment_snapshot(snapshot.snapshot_data, core_field, delivery_mechanism_data) + snapshot_data = FinancialServiceProviderXlsxTemplate.get_data_from_payment_snapshot( + snapshot.snapshot_data, core_field, delivery_mechanism_data + ) return snapshot_data diff --git a/src/hct_mis_api/apps/payment/services/payment_gateway.py b/src/hct_mis_api/apps/payment/services/payment_gateway.py index a10351a29a..2b2fd986ce 100644 --- a/src/hct_mis_api/apps/payment/services/payment_gateway.py +++ b/src/hct_mis_api/apps/payment/services/payment_gateway.py @@ -125,6 +125,7 @@ def get_payload(self, obj: Payment) -> Dict: delivery_mechanism_data = obj.collector.delivery_mechanisms_data.filter( delivery_mechanism=obj.delivery_type ).first() + # TODO: get data from snapshot base_data = { "amount": obj.entitlement_quantity, diff --git a/src/hct_mis_api/apps/payment/services/payment_household_snapshot_service.py b/src/hct_mis_api/apps/payment/services/payment_household_snapshot_service.py index 619ef7efd0..421f4c618d 100644 --- a/src/hct_mis_api/apps/payment/services/payment_household_snapshot_service.py +++ b/src/hct_mis_api/apps/payment/services/payment_household_snapshot_service.py @@ -124,6 +124,7 @@ def get_individual_snapshot(individual: Individual, is_hh_collector: bool = Fals individual_data["needs_adjudication_tickets_count"] = get_needs_adjudication_tickets_count(individual) individual_data["bank_account_info"] = {} + print("SnapShot === data docs", individual.documents.all()) for document in individual.documents.all(): document_data = { "type": document.type.key, @@ -135,7 +136,7 @@ def get_individual_snapshot(individual: Individual, is_hh_collector: bool = Fals "cleared": document.cleared, "cleared_by": handle_type_mapping(document.cleared_by), "cleared_date": handle_type_mapping(document.cleared_date), - "photo": document.photo.name if document.photo else None, + "photo": document.photo.name if document.photo else "", } individual_data["documents"].append(document_data) diff --git a/src/hct_mis_api/apps/payment/xlsx/xlsx_payment_plan_export_per_fsp_service.py b/src/hct_mis_api/apps/payment/xlsx/xlsx_payment_plan_export_per_fsp_service.py index 22edca9641..000fde18eb 100644 --- a/src/hct_mis_api/apps/payment/xlsx/xlsx_payment_plan_export_per_fsp_service.py +++ b/src/hct_mis_api/apps/payment/xlsx/xlsx_payment_plan_export_per_fsp_service.py @@ -148,7 +148,7 @@ def get_payment_row(self, payment: Payment, fsp_xlsx_template: "FinancialService payment_row = [ FinancialServiceProviderXlsxTemplate.get_column_value_from_payment(payment, column_name) for column_name in fsp_template_columns - ] # TODO: get from snapshot + ] delivery_mechanism_data = payment.collector.delivery_mechanisms_data.filter( delivery_mechanism=payment.delivery_type ).first() @@ -157,7 +157,7 @@ def get_payment_row(self, payment: Payment, fsp_xlsx_template: "FinancialService payment, column_name, delivery_mechanism_data ) for column_name in fsp_template_core_fields - ] # TODO: get from snapshot + ] payment_row.extend(core_fields_row) flex_field_row = [ self._get_flex_field_by_name(column_name, payment) for column_name in fsp_xlsx_template.flex_fields @@ -165,15 +165,23 @@ def get_payment_row(self, payment: Payment, fsp_xlsx_template: "FinancialService payment_row.extend(flex_field_row) return list(map(self.right_format_for_xlsx, payment_row)) - def _get_flex_field_by_name(self, name: str, payment: Payment) -> FlexibleAttribute: - # TODO: get from snapshot + def _get_flex_field_by_name(self, name: str, payment: Payment) -> str: attribute: FlexibleAttribute = self.flexible_attributes[name] - individual = payment.collector - household = payment.household + + snapshot = getattr(payment, "household_snapshot", None) + if not snapshot: + logger.error(f"Not found snapshot for Payment {payment.unicef_id}") + return "" + + snapshot_data = snapshot.snapshot_data + primary_collector = snapshot_data.get("primary_collector", {}) + alternate_collector = snapshot_data.get("alternate_collector", {}) + collector_data = primary_collector or alternate_collector or dict() + if attribute.associated_with == FlexibleAttribute.ASSOCIATED_WITH_INDIVIDUAL: - return individual.flex_fields.get(name, "") + return collector_data.get("flex_fields", {}).get(name, "") else: - return household.flex_fields.get(name, "") + return snapshot_data.get("flex_fields", {}).get(name, "") def save_workbook(self, zip_file: zipfile.ZipFile, wb: "Workbook", filename: str) -> None: with NamedTemporaryFile() as tmp: diff --git a/tests/unit/apps/core/test_core_fields.py b/tests/unit/apps/core/test_core_fields.py index ea9708404c..d88b4c5dd6 100644 --- a/tests/unit/apps/core/test_core_fields.py +++ b/tests/unit/apps/core/test_core_fields.py @@ -90,12 +90,12 @@ def test_xlsx_people_scope_modification(self) -> None: def test_get_all_core_fields_choices(self) -> None: choices = FieldFactory.get_all_core_fields_choices() - self.assertEqual(len(choices), 137) + self.assertEqual(len(choices), 135) self.assertEqual(choices[0], ("age", "Age (calculated)")) generate_delivery_mechanisms() choices = FieldFactory.get_all_core_fields_choices() - self.assertEqual(len(choices), 150) + self.assertEqual(len(choices), 148) self.assertEqual( choices[-1], ( diff --git a/tests/unit/apps/core/test_kobo_template_upload.py b/tests/unit/apps/core/test_kobo_template_upload.py index 7f4fc5cea0..e48ccdf9a2 100644 --- a/tests/unit/apps/core/test_kobo_template_upload.py +++ b/tests/unit/apps/core/test_kobo_template_upload.py @@ -113,9 +113,9 @@ def test_upload_invalid_template(self) -> None: "Field: tax_id_no_i_c - Field is missing", "Field: tax_id_issuer_i_c - Field is missing", "Field: national_passport_no_i_c - Field is missing", + "Field: program_registration_id_h_c - Field is missing", "Field: account_holder_name_i_c - Field is missing", "Field: bank_branch_name_i_c - Field is missing", - "Field: program_registration_id_h_c - Field is missing", ] } self.assertEqual(form.errors, expected_errors) diff --git a/tests/unit/apps/payment/test_fsp_xlsx_template_get_column_value.py b/tests/unit/apps/payment/test_fsp_xlsx_template_get_column_value.py index dea899cd13..2ad2a44c70 100644 --- a/tests/unit/apps/payment/test_fsp_xlsx_template_get_column_value.py +++ b/tests/unit/apps/payment/test_fsp_xlsx_template_get_column_value.py @@ -10,11 +10,15 @@ DocumentTypeFactory, create_household, ) +from hct_mis_api.apps.household.models import ROLE_PRIMARY, IndividualRoleInHousehold from hct_mis_api.apps.payment.fixtures import PaymentFactory, PaymentPlanFactory from hct_mis_api.apps.payment.models import ( FinancialServiceProviderXlsxTemplate, PaymentPlan, ) +from hct_mis_api.apps.payment.services.payment_household_snapshot_service import ( + create_payment_plan_snapshot_data, +) from hct_mis_api.apps.program.fixtures import ProgramFactory @@ -33,6 +37,7 @@ def test_get_column_value_registration_token_empty(self) -> None: program=self.program, status=PaymentPlan.Status.ACCEPTED, business_area=self.business_area ) payment = PaymentFactory(parent=payment_plan, household=household, collector=individual, currency="PLN") + create_payment_plan_snapshot_data(payment_plan) result = FinancialServiceProviderXlsxTemplate.get_column_value_from_payment(payment, "registration_token") # return empty string if no document @@ -55,14 +60,16 @@ def test_get_column_value_from_payment(self, _: Any, field_name: str) -> None: individual_args={"full_name": "John Wilson", "given_name": "John", "family_name": "Wilson"}, ) individual = individuals[0] - document_type = DocumentTypeFactory(key="registration_token") - document = DocumentFactory(individual=individual, type=document_type) payment_plan = PaymentPlanFactory( program=self.program, status=PaymentPlan.Status.ACCEPTED, business_area=self.business_area ) - payment = PaymentFactory(parent=payment_plan, household=household, collector=individual, currency="PLN") + primary = IndividualRoleInHousehold.objects.filter(role=ROLE_PRIMARY).first().individual + document_type = DocumentTypeFactory(key="registration_token") + document = DocumentFactory(individual=primary, type=document_type) + + create_payment_plan_snapshot_data(payment_plan) result = FinancialServiceProviderXlsxTemplate.get_column_value_from_payment(payment, field_name) @@ -70,7 +77,7 @@ def test_get_column_value_from_payment(self, _: Any, field_name: str) -> None: "payment_id": payment.unicef_id, "household_id": household.unicef_id, "household_size": 1, - "collector_name": "John Wilson", + "collector_name": primary.full_name, "currency": "PLN", "registration_token": document.document_number, "invalid_column_name": "wrong_column_name", diff --git a/tests/unit/apps/payment/test_import_export_payment_plan_payment_list.py b/tests/unit/apps/payment/test_import_export_payment_plan_payment_list.py index 667ae51821..bf01f1e919 100644 --- a/tests/unit/apps/payment/test_import_export_payment_plan_payment_list.py +++ b/tests/unit/apps/payment/test_import_export_payment_plan_payment_list.py @@ -31,8 +31,10 @@ ) from hct_mis_api.apps.household.models import ( IDENTIFICATION_TYPE_NATIONAL_ID, + ROLE_PRIMARY, Document, Household, + IndividualRoleInHousehold, ) from hct_mis_api.apps.payment.delivery_mechanisms import DeliveryMechanismChoices from hct_mis_api.apps.payment.fixtures import ( @@ -51,10 +53,14 @@ FinancialServiceProvider, FinancialServiceProviderXlsxTemplate, FspXlsxTemplatePerDeliveryMechanism, + PaymentHouseholdSnapshot, PaymentPlan, PaymentPlanSplit, ServiceProvider, ) +from hct_mis_api.apps.payment.services.payment_household_snapshot_service import ( + create_payment_plan_snapshot_data, +) from hct_mis_api.apps.payment.services.payment_plan_services import PaymentPlanService from hct_mis_api.apps.payment.utils import to_decimal from hct_mis_api.apps.payment.xlsx.xlsx_error import XlsxError @@ -128,6 +134,8 @@ def setUpTestData(cls) -> None: cls.payment_plan.status_lock() cls.payment_plan.save() + create_payment_plan_snapshot_data(cls.payment_plan) + cls.xlsx_valid_file = FileTemp.objects.create( object_id=cls.payment_plan.pk, content_type=get_content_type_for_model(cls.payment_plan), @@ -206,7 +214,10 @@ def test_export_payment_plan_payment_list(self) -> None: document_number="Test_Number_National_Id_123", individual=payment.collector, ) - + # remove old and create new snapshot with national_id document + PaymentHouseholdSnapshot.objects.all().delete() + self.assertEqual(payment.collector.documents.all().count(), 1) + create_payment_plan_snapshot_data(self.payment_plan) export_service = XlsxPaymentPlanExportService(self.payment_plan) export_service.save_xlsx_file(self.user) @@ -353,25 +364,31 @@ def test_payment_row_bank_information(self) -> None: "bank_branch_name", "bank_name", "bank_account_number", + "debit_card_number", ] export_service = XlsxPaymentPlanExportPerFspService(self.payment_plan) fsp_xlsx_template = FinancialServiceProviderXlsxTemplateFactory(core_fields=core_fields) headers = export_service.prepare_headers(fsp_xlsx_template) household, _ = create_household({"size": 1}) - individual = household.primary_collector + primary = IndividualRoleInHousehold.objects.filter(role=ROLE_PRIMARY, household=household).first().individual BankAccountInfoFactory( - individual=individual, + individual=primary, account_holder_name="Kowalski", bank_branch_name="BranchJPMorgan", bank_name="JPMorgan", bank_account_number="362277220020615398848112903", + debit_card_number="123", ) - payment = PaymentFactory(parent=self.payment_plan, household=household) + payment = PaymentFactory(parent=self.payment_plan, household=household, collector=primary) + # remove old and create new snapshot with bank account info + PaymentHouseholdSnapshot.objects.all().delete() + create_payment_plan_snapshot_data(self.payment_plan) account_holder_name_index = headers.index("account_holder_name") bank_branch_name_index = headers.index("bank_branch_name") bank_name_index = headers.index("bank_name") bank_account_number_index = headers.index("bank_account_number") + debit_card_number_index = headers.index("debit_card_number") payment_row = export_service.get_payment_row(payment, fsp_xlsx_template) @@ -379,13 +396,11 @@ def test_payment_row_bank_information(self) -> None: self.assertEqual(payment_row[bank_branch_name_index], "BranchJPMorgan") self.assertEqual(payment_row[bank_name_index], "JPMorgan") self.assertEqual(payment_row[bank_account_number_index], "362277220020615398848112903") + self.assertEqual(payment_row[debit_card_number_index], "123") def test_payment_row_flex_fields(self) -> None: core_fields = [ "account_holder_name", - "bank_branch_name", - "bank_name", - "bank_account_number", ] decimal_flexible_attribute = FlexibleAttribute( type=FlexibleAttribute.DECIMAL, @@ -418,16 +433,15 @@ def test_payment_row_flex_fields(self) -> None: household.flex_fields = { date_flexible_attribute.name: "2021-01-01", } - BankAccountInfoFactory( - individual=individual, - account_holder_name="Kowalski", - bank_branch_name="BranchJPMorgan", - bank_name="JPMorgan", - bank_account_number="362277220020615398848112903", - ) - payment = PaymentFactory(parent=self.payment_plan, household=household) + household.save() + payment = PaymentFactory(parent=self.payment_plan, household=household, collector=individual) decimal_flexible_attribute_index = headers.index(decimal_flexible_attribute.name) date_flexible_attribute_index = headers.index(date_flexible_attribute.name) + + # remove old and create new snapshot + PaymentHouseholdSnapshot.objects.all().delete() + create_payment_plan_snapshot_data(self.payment_plan) + payment_row = export_service.get_payment_row(payment, fsp_xlsx_template) self.assertEqual(payment_row[decimal_flexible_attribute_index], 123.45) self.assertEqual(payment_row[date_flexible_attribute_index], "2021-01-01") diff --git a/tests/unit/apps/payment/test_models.py b/tests/unit/apps/payment/test_models.py index 8ad017858e..6d0672f7b7 100644 --- a/tests/unit/apps/payment/test_models.py +++ b/tests/unit/apps/payment/test_models.py @@ -1,19 +1,58 @@ +import json +from datetime import datetime +from typing import Any +from unittest.mock import MagicMock, patch + +from django import forms +from django.db import models +from django.db.utils import IntegrityError +from django.test import TestCase from django.utils import timezone import pytest +from dateutil.relativedelta import relativedelta from hct_mis_api.apps.account.fixtures import BusinessAreaFactory, UserFactory +from hct_mis_api.apps.core.currencies import USDC +from hct_mis_api.apps.core.fixtures import DataCollectingTypeFactory, create_afghanistan +from hct_mis_api.apps.core.models import BusinessArea, DataCollectingType +from hct_mis_api.apps.geo.fixtures import AreaFactory, AreaTypeFactory, CountryFactory +from hct_mis_api.apps.household.fixtures import ( + DocumentFactory, + HouseholdFactory, + IndividualFactory, + create_household, +) +from hct_mis_api.apps.household.models import ROLE_PRIMARY, IndividualRoleInHousehold +from hct_mis_api.apps.payment.fields import DynamicChoiceArrayField, DynamicChoiceField from hct_mis_api.apps.payment.fixtures import ( ApprovalFactory, ApprovalProcessFactory, + DeliveryMechanismPerPaymentPlanFactory, + FinancialServiceProviderFactory, + PaymentFactory, PaymentPlanFactory, + PaymentPlanSplitFactory, + RealProgramFactory, +) +from hct_mis_api.apps.payment.models import ( + Approval, + FinancialServiceProvider, + FinancialServiceProviderXlsxTemplate, + Payment, + PaymentPlan, + PaymentPlanSplit, +) +from hct_mis_api.apps.payment.services.payment_household_snapshot_service import ( + create_payment_plan_snapshot_data, ) -from hct_mis_api.apps.payment.models import Approval, PaymentPlan +from hct_mis_api.apps.program.fixtures import ProgramFactory +from hct_mis_api.apps.program.models import ProgramCycle pytestmark = pytest.mark.django_db -class TestPaymentPlanModel: +class TestBasePaymentPlanModel: def test_get_last_approval_process_data_in_approval(self, afghanistan: BusinessAreaFactory) -> None: payment_plan = PaymentPlanFactory(business_area=afghanistan, status=PaymentPlan.Status.IN_APPROVAL) approval_user = UserFactory() @@ -87,3 +126,534 @@ def test_get_last_approval_process_data_other_status(self, afghanistan: Business modified_data = payment_plan._get_last_approval_process_data() assert modified_data.modified_date == payment_plan.updated_at assert modified_data.modified_by is None + + +class TestPaymentPlanModel(TestCase): + @classmethod + def setUpTestData(cls) -> None: + super().setUpTestData() + create_afghanistan() + cls.business_area = BusinessArea.objects.get(slug="afghanistan") + + def test_create(self) -> None: + pp = PaymentPlanFactory() + self.assertIsInstance(pp, PaymentPlan) + + def test_update_population_count_fields(self) -> None: + pp = PaymentPlanFactory() + hoh1 = IndividualFactory(household=None) + hoh2 = IndividualFactory(household=None) + hh1 = HouseholdFactory(head_of_household=hoh1) + hh2 = HouseholdFactory(head_of_household=hoh2) + PaymentFactory(parent=pp, household=hh1, head_of_household=hoh1, currency="PLN") + PaymentFactory(parent=pp, household=hh2, head_of_household=hoh2, currency="PLN") + + IndividualFactory( + household=hh1, + sex="FEMALE", + birth_date=datetime.now().date() - relativedelta(years=5), + ) + IndividualFactory( + household=hh1, + sex="MALE", + birth_date=datetime.now().date() - relativedelta(years=5), + ) + IndividualFactory( + household=hh2, + sex="FEMALE", + birth_date=datetime.now().date() - relativedelta(years=20), + ) + IndividualFactory( + household=hh2, + sex="MALE", + birth_date=datetime.now().date() - relativedelta(years=20), + ) + + pp.update_population_count_fields() + + pp.refresh_from_db() + self.assertEqual(pp.female_children_count, 1) + self.assertEqual(pp.male_children_count, 1) + self.assertEqual(pp.female_adults_count, 1) + self.assertEqual(pp.male_adults_count, 1) + self.assertEqual(pp.total_households_count, 2) + self.assertEqual(pp.total_individuals_count, 4) + + @patch( + "hct_mis_api.apps.payment.models.PaymentPlan.get_exchange_rate", + return_value=2.0, + ) + def test_update_money_fields(self, get_exchange_rate_mock: Any) -> None: + pp = PaymentPlanFactory() + PaymentFactory( + parent=pp, + entitlement_quantity=100.00, + entitlement_quantity_usd=200.00, + delivered_quantity=50.00, + delivered_quantity_usd=100.00, + currency="PLN", + ) + PaymentFactory( + parent=pp, + entitlement_quantity=100.00, + entitlement_quantity_usd=200.00, + delivered_quantity=50.00, + delivered_quantity_usd=100.00, + currency="PLN", + ) + + pp.update_money_fields() + + pp.refresh_from_db() + self.assertEqual(pp.exchange_rate, 2.0) + self.assertEqual(pp.total_entitled_quantity, 200.00) + self.assertEqual(pp.total_entitled_quantity_usd, 400.00) + self.assertEqual(pp.total_delivered_quantity, 100.00) + self.assertEqual(pp.total_delivered_quantity_usd, 200.00) + self.assertEqual(pp.total_undelivered_quantity, 100.00) + self.assertEqual(pp.total_undelivered_quantity_usd, 200.00) + + def test_not_excluded_payments(self) -> None: + pp = PaymentPlanFactory() + PaymentFactory(parent=pp, conflicted=False, currency="PLN") + PaymentFactory(parent=pp, conflicted=True, currency="PLN") + + pp.refresh_from_db() + self.assertEqual(pp.eligible_payments.count(), 1) + + def test_can_be_locked(self) -> None: + program = RealProgramFactory() + program_cycle = program.cycles.first() + + pp1 = PaymentPlanFactory(program=program, program_cycle=program_cycle) + self.assertEqual(pp1.can_be_locked, False) + + # create hard conflicted payment + pp1_conflicted = PaymentPlanFactory( + status=PaymentPlan.Status.LOCKED, + program=program, + program_cycle=program_cycle, + ) + p1 = PaymentFactory(parent=pp1, conflicted=False, currency="PLN") + PaymentFactory( + parent=pp1_conflicted, + household=p1.household, + conflicted=False, + currency="PLN", + ) + self.assertEqual(pp1.payment_items.filter(payment_plan_hard_conflicted=True).count(), 1) + self.assertEqual(pp1.can_be_locked, False) + + # create not conflicted payment + PaymentFactory(parent=pp1, conflicted=False, currency="PLN") + self.assertEqual(pp1.can_be_locked, True) + + def test_get_exchange_rate_for_usdc_currency(self) -> None: + pp = PaymentPlanFactory(currency=USDC) + self.assertEqual(pp.get_exchange_rate(), 1.0) + + def test_is_reconciled(self) -> None: + pp = PaymentPlanFactory(currency=USDC) + self.assertEqual(pp.is_reconciled, False) + + PaymentFactory(parent=pp, currency="PLN", excluded=True) + self.assertEqual(pp.is_reconciled, False) + + PaymentFactory(parent=pp, currency="PLN", conflicted=True) + self.assertEqual(pp.is_reconciled, False) + + p1 = PaymentFactory(parent=pp, currency="PLN", status=Payment.STATUS_PENDING) + self.assertEqual(pp.is_reconciled, False) + + p2 = PaymentFactory(parent=pp, currency="PLN", status=Payment.STATUS_SENT_TO_PG) + self.assertEqual(pp.is_reconciled, False) + + p1.status = Payment.STATUS_SENT_TO_FSP + p1.save() + self.assertEqual(pp.is_reconciled, False) + + p1.status = Payment.STATUS_DISTRIBUTION_SUCCESS + p1.save() + self.assertEqual(pp.is_reconciled, False) + + p2.status = Payment.STATUS_SENT_TO_FSP + p2.save() + self.assertEqual(pp.is_reconciled, False) + + p2.status = Payment.STATUS_DISTRIBUTION_PARTIAL + p2.save() + self.assertEqual(pp.is_reconciled, True) + + +class TestPaymentModel(TestCase): + @classmethod + def setUpTestData(cls) -> None: + super().setUpTestData() + create_afghanistan() + cls.business_area = BusinessArea.objects.get(slug="afghanistan") + + def test_create(self) -> None: + p1 = PaymentFactory() + self.assertIsInstance(p1, Payment) + + def test_unique_together(self) -> None: + pp = PaymentPlanFactory() + hoh1 = IndividualFactory(household=None) + hh1 = HouseholdFactory(head_of_household=hoh1) + PaymentFactory(parent=pp, household=hh1, currency="PLN") + with self.assertRaises(IntegrityError): + PaymentFactory(parent=pp, household=hh1, currency="PLN") + + def test_manager_annotations_pp_conflicts(self) -> None: + program = RealProgramFactory() + program_cycle = program.cycles.first() + + pp1 = PaymentPlanFactory(program=program, program_cycle=program_cycle) + + # create hard conflicted payment + pp2 = PaymentPlanFactory( + status=PaymentPlan.Status.LOCKED, + program=program, + program_cycle=program_cycle, + ) + # create soft conflicted payments + pp3 = PaymentPlanFactory( + status=PaymentPlan.Status.OPEN, + program=program, + program_cycle=program_cycle, + ) + pp4 = PaymentPlanFactory( + status=PaymentPlan.Status.OPEN, + program=program, + program_cycle=program_cycle, + ) + p1 = PaymentFactory(parent=pp1, conflicted=False, currency="PLN") + p2 = PaymentFactory(parent=pp2, household=p1.household, conflicted=False, currency="PLN") + p3 = PaymentFactory(parent=pp3, household=p1.household, conflicted=False, currency="PLN") + p4 = PaymentFactory(parent=pp4, household=p1.household, conflicted=False, currency="PLN") + + for obj in [pp1, pp2, pp3, pp4, p1, p2, p3, p4]: + obj.refresh_from_db() # update unicef_id from trigger + + p1_data = Payment.objects.filter(id=p1.id).values()[0] + self.assertEqual(p1_data["payment_plan_hard_conflicted"], True) + self.assertEqual(p1_data["payment_plan_soft_conflicted"], True) + + self.assertEqual(len(p1_data["payment_plan_hard_conflicted_data"]), 1) + self.assertEqual( + json.loads(p1_data["payment_plan_hard_conflicted_data"][0]), + { + "payment_id": str(p2.id), + "payment_plan_id": str(pp2.id), + "payment_plan_status": str(pp2.status), + "payment_plan_start_date": program_cycle.start_date.strftime("%Y-%m-%d"), + "payment_plan_end_date": program_cycle.end_date.strftime("%Y-%m-%d"), + "payment_plan_unicef_id": str(pp2.unicef_id), + "payment_unicef_id": str(p2.unicef_id), + }, + ) + self.assertEqual(len(p1_data["payment_plan_soft_conflicted_data"]), 2) + self.assertCountEqual( + [json.loads(conflict_data) for conflict_data in p1_data["payment_plan_soft_conflicted_data"]], + [ + { + "payment_id": str(p3.id), + "payment_plan_id": str(pp3.id), + "payment_plan_status": str(pp3.status), + "payment_plan_start_date": program_cycle.start_date.strftime("%Y-%m-%d"), + "payment_plan_end_date": program_cycle.end_date.strftime("%Y-%m-%d"), + "payment_plan_unicef_id": str(pp3.unicef_id), + "payment_unicef_id": str(p3.unicef_id), + }, + { + "payment_id": str(p4.id), + "payment_plan_id": str(pp4.id), + "payment_plan_status": str(pp4.status), + "payment_plan_start_date": program_cycle.start_date.strftime("%Y-%m-%d"), + "payment_plan_end_date": program_cycle.end_date.strftime("%Y-%m-%d"), + "payment_plan_unicef_id": str(pp4.unicef_id), + "payment_unicef_id": str(p4.unicef_id), + }, + ], + ) + + # the same conflicts when Cycle without end date + program_cycle = program.cycles.first() + ProgramCycle.objects.filter(pk=program_cycle.id).update(end_date=None) + program_cycle.refresh_from_db() + self.assertIsNone(program_cycle.end_date) + + payment_data = Payment.objects.filter(id=p1.id).values()[0] + self.assertEqual(payment_data["payment_plan_hard_conflicted"], True) + self.assertEqual(payment_data["payment_plan_soft_conflicted"], True) + + self.assertEqual(len(payment_data["payment_plan_hard_conflicted_data"]), 1) + self.assertEqual( + json.loads(payment_data["payment_plan_hard_conflicted_data"][0]), + { + "payment_id": str(p2.id), + "payment_plan_id": str(pp2.id), + "payment_plan_status": str(pp2.status), + "payment_plan_start_date": program_cycle.start_date.strftime("%Y-%m-%d"), + "payment_plan_end_date": None, + "payment_plan_unicef_id": str(pp2.unicef_id), + "payment_unicef_id": str(p2.unicef_id), + }, + ) + self.assertEqual(len(payment_data["payment_plan_soft_conflicted_data"]), 2) + self.assertCountEqual( + [json.loads(conflict_data) for conflict_data in payment_data["payment_plan_soft_conflicted_data"]], + [ + { + "payment_id": str(p3.id), + "payment_plan_id": str(pp3.id), + "payment_plan_status": str(pp3.status), + "payment_plan_start_date": program_cycle.start_date.strftime("%Y-%m-%d"), + "payment_plan_end_date": None, + "payment_plan_unicef_id": str(pp3.unicef_id), + "payment_unicef_id": str(p3.unicef_id), + }, + { + "payment_id": str(p4.id), + "payment_plan_id": str(pp4.id), + "payment_plan_status": str(pp4.status), + "payment_plan_start_date": program_cycle.start_date.strftime("%Y-%m-%d"), + "payment_plan_end_date": None, + "payment_plan_unicef_id": str(pp4.unicef_id), + "payment_unicef_id": str(p4.unicef_id), + }, + ], + ) + + def test_manager_annotations_pp_no_conflicts_for_follow_up(self) -> None: + program_cycle = RealProgramFactory().cycles.first() + pp1 = PaymentPlanFactory(program_cycle=program_cycle) + # create follow up pp + pp2 = PaymentPlanFactory( + status=PaymentPlan.Status.LOCKED, + is_follow_up=True, + source_payment_plan=pp1, + program_cycle=program_cycle, + ) + pp3 = PaymentPlanFactory( + status=PaymentPlan.Status.OPEN, + is_follow_up=True, + source_payment_plan=pp1, + program_cycle=program_cycle, + ) + p1 = PaymentFactory(parent=pp1, conflicted=False, currency="PLN") + p2 = PaymentFactory( + parent=pp2, + household=p1.household, + conflicted=False, + is_follow_up=True, + source_payment=p1, + currency="PLN", + ) + p3 = PaymentFactory( + parent=pp3, + household=p1.household, + conflicted=False, + is_follow_up=True, + source_payment=p1, + currency="PLN", + ) + + for _ in [pp1, pp2, pp3, p1, p2, p3]: + _.refresh_from_db() # update unicef_id from trigger + + p2_data = Payment.objects.filter(id=p2.id).values()[0] + self.assertEqual(p2_data["payment_plan_hard_conflicted"], False) + self.assertEqual(p2_data["payment_plan_soft_conflicted"], True) + p3_data = Payment.objects.filter(id=p3.id).values()[0] + self.assertEqual(p3_data["payment_plan_hard_conflicted"], False) + self.assertEqual(p3_data["payment_plan_soft_conflicted"], True) + self.assertEqual(p2_data["payment_plan_hard_conflicted_data"], []) + self.assertIsNotNone(p3_data["payment_plan_hard_conflicted_data"]) + + +class TestPaymentPlanSplitModel(TestCase): + @classmethod + def setUpTestData(cls) -> None: + super().setUpTestData() + create_afghanistan() + cls.business_area = BusinessArea.objects.get(slug="afghanistan") + + def test_properties(self) -> None: + pp = PaymentPlanFactory() + dm = DeliveryMechanismPerPaymentPlanFactory( + payment_plan=pp, + chosen_configuration="key1", + ) + p1 = PaymentFactory(parent=pp, currency="PLN") + p2 = PaymentFactory(parent=pp, currency="PLN") + pp_split1 = PaymentPlanSplitFactory( + payment_plan=pp, + split_type=PaymentPlanSplit.SplitType.BY_RECORDS, + chunks_no=2, + order=0, + ) + pp_split1.payments.set([p1, p2]) + self.assertEqual(pp_split1.financial_service_provider, dm.financial_service_provider) + self.assertEqual(pp_split1.chosen_configuration, dm.chosen_configuration) + self.assertEqual(pp_split1.delivery_mechanism, dm.delivery_mechanism) + + +class TestFinancialServiceProviderModel(TestCase): + @classmethod + def setUpTestData(cls) -> None: + super().setUpTestData() + create_afghanistan() + cls.business_area = BusinessArea.objects.get(slug="afghanistan") + + def test_properties(self) -> None: + fsp1 = FinancialServiceProviderFactory( + data_transfer_configuration=[ + {"key": "key1", "label": "label1", "id": 1, "random_key": "random"}, + {"key": "key2", "label": "label2", "id": 2, "random_key": "random"}, + ], + communication_channel=FinancialServiceProvider.COMMUNICATION_CHANNEL_API, + payment_gateway_id=123, + ) + fsp2 = FinancialServiceProviderFactory( + data_transfer_configuration=[ + {"key": "key1", "label": "label1", "id": 1, "random_key": "random"}, + {"key": "key2", "label": "label2", "id": 2, "random_key": "random"}, + ], + communication_channel=FinancialServiceProvider.COMMUNICATION_CHANNEL_XLSX, + ) + + self.assertEqual(fsp1.configurations, []) + self.assertEqual(fsp2.configurations, []) + + def test_fsp_template_get_column_from_core_field(self) -> None: + household, individuals = create_household( + {"size": 1, "business_area": self.business_area}, + { + "given_name": "John", + "family_name": "Doe", + "middle_name": "", + "full_name": "John Doe", + "phone_no": "+48577123654", + "phone_no_alternative": "+48111222333", + "wallet_name": "wallet_name_Ind_111", + "blockchain_name": "blockchain_name_Ind_111", + "wallet_address": "wallet_address_Ind_111", + }, + ) + country = CountryFactory() + admin_type_1 = AreaTypeFactory(country=country, area_level=1) + admin_type_2 = AreaTypeFactory(country=country, area_level=2, parent=admin_type_1) + admin_type_3 = AreaTypeFactory(country=country, area_level=3, parent=admin_type_2) + area1 = AreaFactory(parent=None, p_code="AF01", area_type=admin_type_1) + area2 = AreaFactory(parent=area1, p_code="AF0101", area_type=admin_type_2) + area3 = AreaFactory(parent=area2, p_code="AF010101", area_type=admin_type_3) + household.admin1 = area1 + household.admin2 = area2 + household.admin3 = area3 + household.save() + + payment = PaymentFactory(program=ProgramFactory(), household=household, collector=individuals[0]) + data_collecting_type = DataCollectingTypeFactory(type=DataCollectingType.Type.SOCIAL) + fsp_xlsx_template = FinancialServiceProviderXlsxTemplate + payment.parent.program.data_collecting_type = data_collecting_type + payment.parent.program.save() + primary = IndividualRoleInHousehold.objects.filter(role=ROLE_PRIMARY).first().individual + primary.phone_no = "+48577123654" + primary.phone_no_alternative = "+48111222333" + primary.wallet_name = "wallet_name_Ind_111" + primary.blockchain_name = "blockchain_name_Ind_111" + primary.wallet_address = "wallet_address_Ind_111" + primary.save() + + document = DocumentFactory( + individual=primary, + type__key="national_id", + document_number="id_doc_number_123", + ) + + create_payment_plan_snapshot_data(payment.parent) + + # check invalid filed name + result = fsp_xlsx_template.get_column_from_core_field(payment, "invalid_people_field_name") + self.assertIsNone(result) + + # People program + given_name = fsp_xlsx_template.get_column_from_core_field(payment, "given_name") + self.assertEqual(given_name, primary.given_name) + ind_unicef_id = fsp_xlsx_template.get_column_from_core_field(payment, "individual_unicef_id") + self.assertEqual(ind_unicef_id, primary.unicef_id) + + # Standard program + payment.parent.program.data_collecting_type.type = DataCollectingType.Type.STANDARD + payment.parent.program.data_collecting_type.save() + + # check fields value + size = fsp_xlsx_template.get_column_from_core_field(payment, "size") + self.assertEqual(size, 1) + admin1 = fsp_xlsx_template.get_column_from_core_field(payment, "admin1") + self.assertEqual(admin1, f"{area1.p_code}") + admin2 = fsp_xlsx_template.get_column_from_core_field(payment, "admin2") + self.assertEqual(admin2, f"{area2.p_code}") + admin3 = fsp_xlsx_template.get_column_from_core_field(payment, "admin3") + self.assertEqual(admin3, f"{area3.p_code}") + given_name = fsp_xlsx_template.get_column_from_core_field(payment, "given_name") + self.assertEqual(given_name, primary.given_name) + ind_unicef_id = fsp_xlsx_template.get_column_from_core_field(payment, "individual_unicef_id") + self.assertEqual(ind_unicef_id, primary.unicef_id) + hh_unicef_id = fsp_xlsx_template.get_column_from_core_field(payment, "household_unicef_id") + self.assertEqual(hh_unicef_id, household.unicef_id) + phone_no = fsp_xlsx_template.get_column_from_core_field(payment, "phone_no") + self.assertEqual(phone_no, primary.phone_no) + phone_no_alternative = fsp_xlsx_template.get_column_from_core_field(payment, "phone_no_alternative") + self.assertEqual(phone_no_alternative, primary.phone_no_alternative) + national_id_no = fsp_xlsx_template.get_column_from_core_field(payment, "national_id_no") + self.assertEqual(national_id_no, document.document_number) + wallet_name = fsp_xlsx_template.get_column_from_core_field(payment, "wallet_name") + self.assertEqual(wallet_name, primary.wallet_name) + blockchain_name = fsp_xlsx_template.get_column_from_core_field(payment, "blockchain_name") + self.assertEqual(blockchain_name, primary.blockchain_name) + wallet_address = fsp_xlsx_template.get_column_from_core_field(payment, "wallet_address") + self.assertEqual(wallet_address, primary.wallet_address) + + +class TestDynamicChoiceArrayField(TestCase): + def setUp(self) -> None: + self.mock_choices = [("field1", "Field 1"), ("field2", "Field 2")] + self.mock_choices_callable = MagicMock(return_value=self.mock_choices) + + def test_choices(self) -> None: + field = DynamicChoiceArrayField( + base_field=models.CharField(max_length=255), + choices_callable=self.mock_choices_callable, + ) + form_field = field.formfield() + + # Check if the choices_callable is passed to the form field + self.assertEqual(list(form_field.choices), self.mock_choices) + self.mock_choices_callable.assert_called_once() + + # Check the form field class and choices + self.assertIsInstance(form_field, DynamicChoiceField) + + +class TestFinancialServiceProviderXlsxTemplate(TestCase): + class FinancialServiceProviderXlsxTemplateForm(forms.ModelForm): + class Meta: + model = FinancialServiceProviderXlsxTemplate + fields = ["core_fields"] + + def test_model_form_integration(self) -> None: + form = self.FinancialServiceProviderXlsxTemplateForm( + data={"core_fields": ["age", "residence_status"]} + ) # real existing core fields + self.assertTrue(form.is_valid()) + template = form.save() + self.assertEqual(template.core_fields, ["age", "residence_status"]) + + form = self.FinancialServiceProviderXlsxTemplateForm(data={"core_fields": ["field1"]}) # fake core fields + self.assertFalse(form.is_valid()) + self.assertEqual( + form.errors, + {"core_fields": ["Select a valid choice. field1 is not one of the available choices."]}, + ) diff --git a/tests/unit/apps/payment/test_models1.py b/tests/unit/apps/payment/test_models1.py deleted file mode 100644 index fcd7a8918f..0000000000 --- a/tests/unit/apps/payment/test_models1.py +++ /dev/null @@ -1,563 +0,0 @@ -import json -from datetime import datetime -from typing import Any -from unittest.mock import MagicMock, patch - -from django import forms -from django.db import models -from django.db.utils import IntegrityError -from django.test import TestCase - -from dateutil.relativedelta import relativedelta - -from hct_mis_api.apps.core.currencies import USDC -from hct_mis_api.apps.core.fixtures import DataCollectingTypeFactory, create_afghanistan -from hct_mis_api.apps.core.models import BusinessArea, DataCollectingType -from hct_mis_api.apps.geo.fixtures import AreaFactory, AreaTypeFactory, CountryFactory -from hct_mis_api.apps.household.fixtures import ( - DocumentFactory, - DocumentTypeFactory, - HouseholdFactory, - IndividualFactory, - create_household, -) -from hct_mis_api.apps.payment.fields import DynamicChoiceArrayField, DynamicChoiceField -from hct_mis_api.apps.payment.fixtures import ( - DeliveryMechanismPerPaymentPlanFactory, - FinancialServiceProviderFactory, - PaymentFactory, - PaymentPlanFactory, - PaymentPlanSplitFactory, - RealProgramFactory, -) -from hct_mis_api.apps.payment.models import ( - FinancialServiceProvider, - FinancialServiceProviderXlsxTemplate, - Payment, - PaymentPlan, - PaymentPlanSplit, -) -from hct_mis_api.apps.program.fixtures import ProgramFactory -from hct_mis_api.apps.program.models import ProgramCycle - - -class TestPaymentPlanModel(TestCase): - @classmethod - def setUpTestData(cls) -> None: - super().setUpTestData() - create_afghanistan() - cls.business_area = BusinessArea.objects.get(slug="afghanistan") - - def test_create(self) -> None: - pp = PaymentPlanFactory() - self.assertIsInstance(pp, PaymentPlan) - - def test_update_population_count_fields(self) -> None: - pp = PaymentPlanFactory() - hoh1 = IndividualFactory(household=None) - hoh2 = IndividualFactory(household=None) - hh1 = HouseholdFactory(head_of_household=hoh1) - hh2 = HouseholdFactory(head_of_household=hoh2) - PaymentFactory(parent=pp, household=hh1, head_of_household=hoh1, currency="PLN") - PaymentFactory(parent=pp, household=hh2, head_of_household=hoh2, currency="PLN") - - IndividualFactory( - household=hh1, - sex="FEMALE", - birth_date=datetime.now().date() - relativedelta(years=5), - ) - IndividualFactory( - household=hh1, - sex="MALE", - birth_date=datetime.now().date() - relativedelta(years=5), - ) - IndividualFactory( - household=hh2, - sex="FEMALE", - birth_date=datetime.now().date() - relativedelta(years=20), - ) - IndividualFactory( - household=hh2, - sex="MALE", - birth_date=datetime.now().date() - relativedelta(years=20), - ) - - pp.update_population_count_fields() - - pp.refresh_from_db() - self.assertEqual(pp.female_children_count, 1) - self.assertEqual(pp.male_children_count, 1) - self.assertEqual(pp.female_adults_count, 1) - self.assertEqual(pp.male_adults_count, 1) - self.assertEqual(pp.total_households_count, 2) - self.assertEqual(pp.total_individuals_count, 4) - - @patch( - "hct_mis_api.apps.payment.models.PaymentPlan.get_exchange_rate", - return_value=2.0, - ) - def test_update_money_fields(self, get_exchange_rate_mock: Any) -> None: - pp = PaymentPlanFactory() - PaymentFactory( - parent=pp, - entitlement_quantity=100.00, - entitlement_quantity_usd=200.00, - delivered_quantity=50.00, - delivered_quantity_usd=100.00, - currency="PLN", - ) - PaymentFactory( - parent=pp, - entitlement_quantity=100.00, - entitlement_quantity_usd=200.00, - delivered_quantity=50.00, - delivered_quantity_usd=100.00, - currency="PLN", - ) - - pp.update_money_fields() - - pp.refresh_from_db() - self.assertEqual(pp.exchange_rate, 2.0) - self.assertEqual(pp.total_entitled_quantity, 200.00) - self.assertEqual(pp.total_entitled_quantity_usd, 400.00) - self.assertEqual(pp.total_delivered_quantity, 100.00) - self.assertEqual(pp.total_delivered_quantity_usd, 200.00) - self.assertEqual(pp.total_undelivered_quantity, 100.00) - self.assertEqual(pp.total_undelivered_quantity_usd, 200.00) - - def test_not_excluded_payments(self) -> None: - pp = PaymentPlanFactory() - PaymentFactory(parent=pp, conflicted=False, currency="PLN") - PaymentFactory(parent=pp, conflicted=True, currency="PLN") - - pp.refresh_from_db() - self.assertEqual(pp.eligible_payments.count(), 1) - - def test_can_be_locked(self) -> None: - program = RealProgramFactory() - program_cycle = program.cycles.first() - - pp1 = PaymentPlanFactory(program=program, program_cycle=program_cycle) - self.assertEqual(pp1.can_be_locked, False) - - # create hard conflicted payment - pp1_conflicted = PaymentPlanFactory( - status=PaymentPlan.Status.LOCKED, - program=program, - program_cycle=program_cycle, - ) - p1 = PaymentFactory(parent=pp1, conflicted=False, currency="PLN") - PaymentFactory( - parent=pp1_conflicted, - household=p1.household, - conflicted=False, - currency="PLN", - ) - self.assertEqual(pp1.payment_items.filter(payment_plan_hard_conflicted=True).count(), 1) - self.assertEqual(pp1.can_be_locked, False) - - # create not conflicted payment - PaymentFactory(parent=pp1, conflicted=False, currency="PLN") - self.assertEqual(pp1.can_be_locked, True) - - def test_get_exchange_rate_for_usdc_currency(self) -> None: - pp = PaymentPlanFactory(currency=USDC) - self.assertEqual(pp.get_exchange_rate(), 1.0) - - def test_is_reconciled(self) -> None: - pp = PaymentPlanFactory(currency=USDC) - self.assertEqual(pp.is_reconciled, False) - - PaymentFactory(parent=pp, currency="PLN", excluded=True) - self.assertEqual(pp.is_reconciled, False) - - PaymentFactory(parent=pp, currency="PLN", conflicted=True) - self.assertEqual(pp.is_reconciled, False) - - p1 = PaymentFactory(parent=pp, currency="PLN", status=Payment.STATUS_PENDING) - self.assertEqual(pp.is_reconciled, False) - - p2 = PaymentFactory(parent=pp, currency="PLN", status=Payment.STATUS_SENT_TO_PG) - self.assertEqual(pp.is_reconciled, False) - - p1.status = Payment.STATUS_SENT_TO_FSP - p1.save() - self.assertEqual(pp.is_reconciled, False) - - p1.status = Payment.STATUS_DISTRIBUTION_SUCCESS - p1.save() - self.assertEqual(pp.is_reconciled, False) - - p2.status = Payment.STATUS_SENT_TO_FSP - p2.save() - self.assertEqual(pp.is_reconciled, False) - - p2.status = Payment.STATUS_DISTRIBUTION_PARTIAL - p2.save() - self.assertEqual(pp.is_reconciled, True) - - -class TestPaymentModel(TestCase): - @classmethod - def setUpTestData(cls) -> None: - super().setUpTestData() - create_afghanistan() - cls.business_area = BusinessArea.objects.get(slug="afghanistan") - - def test_create(self) -> None: - p1 = PaymentFactory() - self.assertIsInstance(p1, Payment) - - def test_unique_together(self) -> None: - pp = PaymentPlanFactory() - hoh1 = IndividualFactory(household=None) - hh1 = HouseholdFactory(head_of_household=hoh1) - PaymentFactory(parent=pp, household=hh1, currency="PLN") - with self.assertRaises(IntegrityError): - PaymentFactory(parent=pp, household=hh1, currency="PLN") - - def test_manager_annotations_pp_conflicts(self) -> None: - program = RealProgramFactory() - program_cycle = program.cycles.first() - - pp1 = PaymentPlanFactory(program=program, program_cycle=program_cycle) - - # create hard conflicted payment - pp2 = PaymentPlanFactory( - status=PaymentPlan.Status.LOCKED, - program=program, - program_cycle=program_cycle, - ) - # create soft conflicted payments - pp3 = PaymentPlanFactory( - status=PaymentPlan.Status.OPEN, - program=program, - program_cycle=program_cycle, - ) - pp4 = PaymentPlanFactory( - status=PaymentPlan.Status.OPEN, - program=program, - program_cycle=program_cycle, - ) - p1 = PaymentFactory(parent=pp1, conflicted=False, currency="PLN") - p2 = PaymentFactory(parent=pp2, household=p1.household, conflicted=False, currency="PLN") - p3 = PaymentFactory(parent=pp3, household=p1.household, conflicted=False, currency="PLN") - p4 = PaymentFactory(parent=pp4, household=p1.household, conflicted=False, currency="PLN") - - for obj in [pp1, pp2, pp3, pp4, p1, p2, p3, p4]: - obj.refresh_from_db() # update unicef_id from trigger - - p1_data = Payment.objects.filter(id=p1.id).values()[0] - self.assertEqual(p1_data["payment_plan_hard_conflicted"], True) - self.assertEqual(p1_data["payment_plan_soft_conflicted"], True) - - self.assertEqual(len(p1_data["payment_plan_hard_conflicted_data"]), 1) - self.assertEqual( - json.loads(p1_data["payment_plan_hard_conflicted_data"][0]), - { - "payment_id": str(p2.id), - "payment_plan_id": str(pp2.id), - "payment_plan_status": str(pp2.status), - "payment_plan_start_date": program_cycle.start_date.strftime("%Y-%m-%d"), - "payment_plan_end_date": program_cycle.end_date.strftime("%Y-%m-%d"), - "payment_plan_unicef_id": str(pp2.unicef_id), - "payment_unicef_id": str(p2.unicef_id), - }, - ) - self.assertEqual(len(p1_data["payment_plan_soft_conflicted_data"]), 2) - self.assertCountEqual( - [json.loads(conflict_data) for conflict_data in p1_data["payment_plan_soft_conflicted_data"]], - [ - { - "payment_id": str(p3.id), - "payment_plan_id": str(pp3.id), - "payment_plan_status": str(pp3.status), - "payment_plan_start_date": program_cycle.start_date.strftime("%Y-%m-%d"), - "payment_plan_end_date": program_cycle.end_date.strftime("%Y-%m-%d"), - "payment_plan_unicef_id": str(pp3.unicef_id), - "payment_unicef_id": str(p3.unicef_id), - }, - { - "payment_id": str(p4.id), - "payment_plan_id": str(pp4.id), - "payment_plan_status": str(pp4.status), - "payment_plan_start_date": program_cycle.start_date.strftime("%Y-%m-%d"), - "payment_plan_end_date": program_cycle.end_date.strftime("%Y-%m-%d"), - "payment_plan_unicef_id": str(pp4.unicef_id), - "payment_unicef_id": str(p4.unicef_id), - }, - ], - ) - - # the same conflicts when Cycle without end date - program_cycle = program.cycles.first() - ProgramCycle.objects.filter(pk=program_cycle.id).update(end_date=None) - program_cycle.refresh_from_db() - self.assertIsNone(program_cycle.end_date) - - payment_data = Payment.objects.filter(id=p1.id).values()[0] - self.assertEqual(payment_data["payment_plan_hard_conflicted"], True) - self.assertEqual(payment_data["payment_plan_soft_conflicted"], True) - - self.assertEqual(len(payment_data["payment_plan_hard_conflicted_data"]), 1) - self.assertEqual( - json.loads(payment_data["payment_plan_hard_conflicted_data"][0]), - { - "payment_id": str(p2.id), - "payment_plan_id": str(pp2.id), - "payment_plan_status": str(pp2.status), - "payment_plan_start_date": program_cycle.start_date.strftime("%Y-%m-%d"), - "payment_plan_end_date": None, - "payment_plan_unicef_id": str(pp2.unicef_id), - "payment_unicef_id": str(p2.unicef_id), - }, - ) - self.assertEqual(len(payment_data["payment_plan_soft_conflicted_data"]), 2) - self.assertCountEqual( - [json.loads(conflict_data) for conflict_data in payment_data["payment_plan_soft_conflicted_data"]], - [ - { - "payment_id": str(p3.id), - "payment_plan_id": str(pp3.id), - "payment_plan_status": str(pp3.status), - "payment_plan_start_date": program_cycle.start_date.strftime("%Y-%m-%d"), - "payment_plan_end_date": None, - "payment_plan_unicef_id": str(pp3.unicef_id), - "payment_unicef_id": str(p3.unicef_id), - }, - { - "payment_id": str(p4.id), - "payment_plan_id": str(pp4.id), - "payment_plan_status": str(pp4.status), - "payment_plan_start_date": program_cycle.start_date.strftime("%Y-%m-%d"), - "payment_plan_end_date": None, - "payment_plan_unicef_id": str(pp4.unicef_id), - "payment_unicef_id": str(p4.unicef_id), - }, - ], - ) - - def test_manager_annotations_pp_no_conflicts_for_follow_up(self) -> None: - program_cycle = RealProgramFactory().cycles.first() - pp1 = PaymentPlanFactory(program_cycle=program_cycle) - # create follow up pp - pp2 = PaymentPlanFactory( - status=PaymentPlan.Status.LOCKED, - is_follow_up=True, - source_payment_plan=pp1, - program_cycle=program_cycle, - ) - pp3 = PaymentPlanFactory( - status=PaymentPlan.Status.OPEN, - is_follow_up=True, - source_payment_plan=pp1, - program_cycle=program_cycle, - ) - p1 = PaymentFactory(parent=pp1, conflicted=False, currency="PLN") - p2 = PaymentFactory( - parent=pp2, - household=p1.household, - conflicted=False, - is_follow_up=True, - source_payment=p1, - currency="PLN", - ) - p3 = PaymentFactory( - parent=pp3, - household=p1.household, - conflicted=False, - is_follow_up=True, - source_payment=p1, - currency="PLN", - ) - - for _ in [pp1, pp2, pp3, p1, p2, p3]: - _.refresh_from_db() # update unicef_id from trigger - - p2_data = Payment.objects.filter(id=p2.id).values()[0] - self.assertEqual(p2_data["payment_plan_hard_conflicted"], False) - self.assertEqual(p2_data["payment_plan_soft_conflicted"], True) - p3_data = Payment.objects.filter(id=p3.id).values()[0] - self.assertEqual(p3_data["payment_plan_hard_conflicted"], False) - self.assertEqual(p3_data["payment_plan_soft_conflicted"], True) - self.assertEqual(p2_data["payment_plan_hard_conflicted_data"], []) - self.assertIsNotNone(p3_data["payment_plan_hard_conflicted_data"]) - - -class TestPaymentPlanSplitModel(TestCase): - @classmethod - def setUpTestData(cls) -> None: - super().setUpTestData() - create_afghanistan() - cls.business_area = BusinessArea.objects.get(slug="afghanistan") - - def test_properties(self) -> None: - pp = PaymentPlanFactory() - dm = DeliveryMechanismPerPaymentPlanFactory( - payment_plan=pp, - chosen_configuration="key1", - ) - p1 = PaymentFactory(parent=pp, currency="PLN") - p2 = PaymentFactory(parent=pp, currency="PLN") - pp_split1 = PaymentPlanSplitFactory( - payment_plan=pp, - split_type=PaymentPlanSplit.SplitType.BY_RECORDS, - chunks_no=2, - order=0, - ) - pp_split1.payments.set([p1, p2]) - self.assertEqual(pp_split1.financial_service_provider, dm.financial_service_provider) - self.assertEqual(pp_split1.chosen_configuration, dm.chosen_configuration) - self.assertEqual(pp_split1.delivery_mechanism, dm.delivery_mechanism) - - -class TestFinancialServiceProviderModel(TestCase): - @classmethod - def setUpTestData(cls) -> None: - super().setUpTestData() - create_afghanistan() - cls.business_area = BusinessArea.objects.get(slug="afghanistan") - - def test_properties(self) -> None: - fsp1 = FinancialServiceProviderFactory( - data_transfer_configuration=[ - {"key": "key1", "label": "label1", "id": 1, "random_key": "random"}, - {"key": "key2", "label": "label2", "id": 2, "random_key": "random"}, - ], - communication_channel=FinancialServiceProvider.COMMUNICATION_CHANNEL_API, - payment_gateway_id=123, - ) - fsp2 = FinancialServiceProviderFactory( - data_transfer_configuration=[ - {"key": "key1", "label": "label1", "id": 1, "random_key": "random"}, - {"key": "key2", "label": "label2", "id": 2, "random_key": "random"}, - ], - communication_channel=FinancialServiceProvider.COMMUNICATION_CHANNEL_XLSX, - ) - - self.assertEqual(fsp1.configurations, []) - self.assertEqual(fsp2.configurations, []) - - def test_fsp_template_get_column_from_core_field(self) -> None: - household, individuals = create_household( - {"size": 1, "business_area": self.business_area}, - { - "given_name": "John", - "family_name": "Doe", - "middle_name": "", - "full_name": "John Doe", - "phone_no": "+48577123654", - "phone_no_alternative": "+48111222333", - "wallet_name": "wallet_name_Ind_111", - "blockchain_name": "blockchain_name_Ind_111", - "wallet_address": "wallet_address_Ind_111", - }, - ) - document_type = DocumentTypeFactory(key="national_id") - document = DocumentFactory( - individual=individuals[0], - type=document_type, - document_number="id_doc_number_123", - ) - country = CountryFactory() - admin_type_1 = AreaTypeFactory(country=country, area_level=1) - admin_type_2 = AreaTypeFactory(country=country, area_level=2, parent=admin_type_1) - admin_type_3 = AreaTypeFactory(country=country, area_level=3, parent=admin_type_2) - area1 = AreaFactory(parent=None, p_code="AF01", area_type=admin_type_1) - area2 = AreaFactory(parent=area1, p_code="AF0101", area_type=admin_type_2) - area3 = AreaFactory(parent=area2, p_code="AF010101", area_type=admin_type_3) - household.admin1 = area1 - household.admin2 = area2 - household.admin3 = area3 - household.save() - - payment = PaymentFactory(program=ProgramFactory(), household=household, collector=individuals[0]) - data_collecting_type = DataCollectingTypeFactory(type=DataCollectingType.Type.SOCIAL) - fsp_xlsx_template = FinancialServiceProviderXlsxTemplate - payment.parent.program.data_collecting_type = data_collecting_type - payment.parent.program.save() - - # check invalid filed name - result = fsp_xlsx_template.get_column_from_core_field(payment, "invalid_people_field_name") - self.assertIsNone(result) - - # People program - given_name = fsp_xlsx_template.get_column_from_core_field(payment, "given_name") - self.assertEqual(given_name, individuals[0].given_name) - ind_unicef_id = fsp_xlsx_template.get_column_from_core_field(payment, "individual_unicef_id") - self.assertEqual(ind_unicef_id, individuals[0].unicef_id) - - # Standard program - payment.parent.program.data_collecting_type.type = DataCollectingType.Type.STANDARD - payment.parent.program.data_collecting_type.save() - - # check fields value - size = fsp_xlsx_template.get_column_from_core_field(payment, "size") - self.assertEqual(size, 1) - admin1 = fsp_xlsx_template.get_column_from_core_field(payment, "admin1") - self.assertEqual(admin1, f"{area1.p_code} - {area1.name}") - admin2 = fsp_xlsx_template.get_column_from_core_field(payment, "admin2") - self.assertEqual(admin2, f"{area2.p_code} - {area2.name}") - admin3 = fsp_xlsx_template.get_column_from_core_field(payment, "admin3") - self.assertEqual(admin3, f"{area3.p_code} - {area3.name}") - given_name = fsp_xlsx_template.get_column_from_core_field(payment, "given_name") - self.assertEqual(given_name, individuals[0].given_name) - ind_unicef_id = fsp_xlsx_template.get_column_from_core_field(payment, "individual_unicef_id") - self.assertEqual(ind_unicef_id, individuals[0].unicef_id) - hh_unicef_id = fsp_xlsx_template.get_column_from_core_field(payment, "household_unicef_id") - self.assertEqual(hh_unicef_id, household.unicef_id) - phone_no = fsp_xlsx_template.get_column_from_core_field(payment, "phone_no") - self.assertEqual(phone_no, individuals[0].phone_no) - phone_no_alternative = fsp_xlsx_template.get_column_from_core_field(payment, "phone_no_alternative") - self.assertEqual(phone_no_alternative, individuals[0].phone_no_alternative) - national_id_no = fsp_xlsx_template.get_column_from_core_field(payment, "national_id_no") - self.assertEqual(national_id_no, document.document_number) - wallet_name = fsp_xlsx_template.get_column_from_core_field(payment, "wallet_name") - self.assertEqual(wallet_name, individuals[0].wallet_name) - blockchain_name = fsp_xlsx_template.get_column_from_core_field(payment, "blockchain_name") - self.assertEqual(blockchain_name, individuals[0].blockchain_name) - wallet_address = fsp_xlsx_template.get_column_from_core_field(payment, "wallet_address") - self.assertEqual(wallet_address, individuals[0].wallet_address) - - -class TestDynamicChoiceArrayField(TestCase): - def setUp(self) -> None: - self.mock_choices = [("field1", "Field 1"), ("field2", "Field 2")] - self.mock_choices_callable = MagicMock(return_value=self.mock_choices) - - def test_choices(self) -> None: - field = DynamicChoiceArrayField( - base_field=models.CharField(max_length=255), - choices_callable=self.mock_choices_callable, - ) - form_field = field.formfield() - - # Check if the choices_callable is passed to the form field - self.assertEqual(list(form_field.choices), self.mock_choices) - self.mock_choices_callable.assert_called_once() - - # Check the form field class and choices - self.assertIsInstance(form_field, DynamicChoiceField) - - -class TestFinancialServiceProviderXlsxTemplate(TestCase): - class FinancialServiceProviderXlsxTemplateForm(forms.ModelForm): - class Meta: - model = FinancialServiceProviderXlsxTemplate - fields = ["core_fields"] - - def test_model_form_integration(self) -> None: - form = self.FinancialServiceProviderXlsxTemplateForm( - data={"core_fields": ["age", "residence_status"]} - ) # real existing core fields - self.assertTrue(form.is_valid()) - template = form.save() - self.assertEqual(template.core_fields, ["age", "residence_status"]) - - form = self.FinancialServiceProviderXlsxTemplateForm(data={"core_fields": ["field1"]}) # fake core fields - self.assertFalse(form.is_valid()) - self.assertEqual( - form.errors, - {"core_fields": ["Select a valid choice. field1 is not one of the available choices."]}, - ) diff --git a/tests/unit/apps/payment/test_payment_plan_reconciliation.py b/tests/unit/apps/payment/test_payment_plan_reconciliation.py index d2f169e2f6..0857112950 100644 --- a/tests/unit/apps/payment/test_payment_plan_reconciliation.py +++ b/tests/unit/apps/payment/test_payment_plan_reconciliation.py @@ -59,6 +59,9 @@ PaymentVerification, PaymentVerificationPlan, ) +from hct_mis_api.apps.payment.services.payment_household_snapshot_service import ( + create_payment_plan_snapshot_data, +) from hct_mis_api.apps.payment.xlsx.xlsx_payment_plan_per_fsp_import_service import ( XlsxPaymentPlanImportPerFspService, ) @@ -463,8 +466,9 @@ def test_receiving_reconciliations_from_fsp(self, mock_get_exchange_rate: Any) - ) encoded_santander_fsp_id = encode_id_base64(santander_fsp.id, "FinancialServiceProvider") + payment_plan = PaymentPlan.objects.get(id=payment_plan_id) payment = PaymentFactory( - parent=PaymentPlan.objects.get(id=payment_plan_id), + parent=payment_plan, business_area=self.business_area, household=self.household_1, collector=self.individual_1, @@ -477,6 +481,7 @@ def test_receiving_reconciliations_from_fsp(self, mock_get_exchange_rate: Any) - currency="PLN", ) self.assertEqual(payment.entitlement_quantity, 1000) + create_payment_plan_snapshot_data(payment_plan) lock_payment_plan_response = self.graphql_request( request_string=PAYMENT_PLAN_ACTION_MUTATION, @@ -494,7 +499,6 @@ def test_receiving_reconciliations_from_fsp(self, mock_get_exchange_rate: Any) - rule = RuleFactory(name="Rule") RuleCommitFactory(definition="result.value=Decimal('500')", rule=rule) - payment_plan = PaymentPlan.objects.get(id=payment_plan_id) self.assertEqual(payment_plan.background_action_status, None) with patch("hct_mis_api.apps.payment.mutations.payment_plan_apply_engine_rule") as mock: diff --git a/tests/unit/apps/payment/test_payment_plan_services.py b/tests/unit/apps/payment/test_payment_plan_services.py index e3dbbc32b6..5e5a0aa32a 100644 --- a/tests/unit/apps/payment/test_payment_plan_services.py +++ b/tests/unit/apps/payment/test_payment_plan_services.py @@ -195,7 +195,7 @@ def test_create(self, get_exchange_rate_mock: Any) -> None: self.assertEqual(pp.total_households_count, 0) self.assertEqual(pp.total_individuals_count, 0) self.assertEqual(pp.payment_items.count(), 0) - with self.assertNumQueries(62): + with self.assertNumQueries(66): prepare_payment_plan_task.delay(pp.id) pp.refresh_from_db() self.assertEqual(pp.status, PaymentPlan.Status.OPEN) @@ -398,7 +398,7 @@ def test_create_follow_up_pp(self, get_exchange_rate_mock: Any) -> None: self.assertEqual(pp.follow_ups.count(), 2) - with self.assertNumQueries(45): + with self.assertNumQueries(47): prepare_follow_up_payment_plan_task(follow_up_pp_2.id) self.assertEqual(follow_up_pp_2.payment_items.count(), 1) diff --git a/tests/unit/apps/registration_data/test_template_file_generator.py b/tests/unit/apps/registration_data/test_template_file_generator.py index 8134acd68f..65d07fd0f8 100644 --- a/tests/unit/apps/registration_data/test_template_file_generator.py +++ b/tests/unit/apps/registration_data/test_template_file_generator.py @@ -92,8 +92,10 @@ def test_add_template_columns(self) -> None: self.assertEqual("pp_village_i_c", people_rows[0][70]) self.assertEqual("Village - STRING", people_rows[1][70]) - self.assertEqual("pp_index_id", people_rows[0][86]) - self.assertEqual("Index ID - INTEGER - required", people_rows[1][86]) + self.assertEqual("pp_bank_branch_name_i_c", people_rows[0][86]) + + self.assertEqual("pp_index_id", people_rows[0][84]) + self.assertEqual("Index ID - INTEGER - required", people_rows[1][84]) self.assertIn("pp_wallet_name__transfer_to_digital_wallet_i_c", people_rows[0]) self.assertIn( From 95db32802364662491438234f4aaaaa7d79879b8 Mon Sep 17 00:00:00 2001 From: Pavlo Mokiichuk Date: Mon, 7 Oct 2024 23:23:37 +0200 Subject: [PATCH 10/12] upd tests, onetime script --- .../apps/account/migrations/0079_migration.py | 19 ++++++++++ .../apps/payment/services/payment_gateway.py | 31 +++++++++------- .../payment_household_snapshot_service.py | 21 +++++++---- .../create_payment_snapshot.py | 37 +++++++++++++++++++ .../payment/test_payment_gateway_service.py | 35 ++++++++++++++---- 5 files changed, 114 insertions(+), 29 deletions(-) create mode 100644 src/hct_mis_api/apps/account/migrations/0079_migration.py create mode 100644 src/hct_mis_api/one_time_scripts/create_payment_snapshot.py diff --git a/src/hct_mis_api/apps/account/migrations/0079_migration.py b/src/hct_mis_api/apps/account/migrations/0079_migration.py new file mode 100644 index 0000000000..f89b1b0021 --- /dev/null +++ b/src/hct_mis_api/apps/account/migrations/0079_migration.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.25 on 2024-10-05 18:39 + +from django.db import migrations, models +import hct_mis_api.apps.account.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0078_migration'), + ] + + operations = [ + migrations.AlterField( + model_name='role', + name='permissions', + 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'), ('POPULATION_VIEW_INDIVIDUAL_DELIVERY_MECHANISMS_SECTION', 'POPULATION VIEW INDIVIDUAL DELIVERY MECHANISMS SECTION'), ('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_DOWNLOAD_SUPPORTING_DOCUMENT', 'PM DOWNLOAD SUPPORTING DOCUMENT'), ('PM_UPLOAD_SUPPORTING_DOCUMENT', 'PM UPLOAD SUPPORTING DOCUMENT'), ('PM_DELETE_SUPPORTING_DOCUMENT', 'PM DELETE SUPPORTING DOCUMENT'), ('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_VIEW_BIOMETRIC_RESULTS', 'GRIEVANCES VIEW BIOMETRIC RESULTS'), ('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), + ), + ] diff --git a/src/hct_mis_api/apps/payment/services/payment_gateway.py b/src/hct_mis_api/apps/payment/services/payment_gateway.py index 2b2fd986ce..f865ae2ebc 100644 --- a/src/hct_mis_api/apps/payment/services/payment_gateway.py +++ b/src/hct_mis_api/apps/payment/services/payment_gateway.py @@ -122,23 +122,26 @@ def get_extra_data(self, obj: Payment) -> Dict: return {} def get_payload(self, obj: Payment) -> Dict: - delivery_mechanism_data = obj.collector.delivery_mechanisms_data.filter( - delivery_mechanism=obj.delivery_type - ).first() - # TODO: get data from snapshot + snapshot = getattr(obj, "household_snapshot", None) + if not snapshot: + logger.error(f"Not found snapshot for Payment {obj.unicef_id}") + + snapshot_data = snapshot.snapshot_data + collector_data = snapshot_data.get("primary_collector") or snapshot_data.get("alternate_collector") or dict() + delivery_mech_data = collector_data.get("delivery_mechanisms_data", {}).get(obj.delivery_type.code, {}) base_data = { "amount": obj.entitlement_quantity, "destination_currency": obj.currency, - "phone_no": str(obj.collector.phone_no), - "last_name": obj.collector.family_name, - "first_name": obj.collector.given_name, - "full_name": obj.full_name, + "phone_no": collector_data.get("phone_no", ""), + "last_name": collector_data.get("family_name", ""), + "first_name": collector_data.get("given_name", ""), + "full_name": collector_data.get("full_name", ""), } - if ( - obj.delivery_type.code == "mobile_money" and not delivery_mechanism_data - ): # this workaround need to be dropped - base_data["service_provider_code"] = obj.collector.flex_fields.get("service_provider_code_i_f", "") + if obj.delivery_type.code == "mobile_money" and not delivery_mech_data: # this workaround need to be dropped + base_data["service_provider_code"] = collector_data.get("flex_fields", {}).get( + "service_provider_code_i_f", "" + ) payload = PaymentPayloadSerializer(data=base_data) if not payload.is_valid(): @@ -146,8 +149,8 @@ def get_payload(self, obj: Payment) -> Dict: payload_data = payload.data - if delivery_mechanism_data: - payload_data.update(delivery_mechanism_data.delivery_data) + if delivery_mech_data: + payload_data.update(delivery_mech_data) return payload_data diff --git a/src/hct_mis_api/apps/payment/services/payment_household_snapshot_service.py b/src/hct_mis_api/apps/payment/services/payment_household_snapshot_service.py index 421f4c618d..a49f17cb6e 100644 --- a/src/hct_mis_api/apps/payment/services/payment_household_snapshot_service.py +++ b/src/hct_mis_api/apps/payment/services/payment_household_snapshot_service.py @@ -10,7 +10,13 @@ from hct_mis_api.apps.geo.models import Country from hct_mis_api.apps.grievance.models import TicketNeedsAdjudicationDetails -from hct_mis_api.apps.household.models import BankAccountInfo, Individual +from hct_mis_api.apps.household.models import ( + ROLE_ALTERNATE, + ROLE_PRIMARY, + BankAccountInfo, + Individual, + IndividualRoleInHousehold, +) from hct_mis_api.apps.payment.models import ( Payment, PaymentHouseholdSnapshot, @@ -92,9 +98,7 @@ def create_payment_snapshot_data(payment: Payment) -> PaymentHouseholdSnapshot: if str(household.primary_collector.id) in individuals_dict: household_data["primary_collector"] = individuals_dict[str(household.primary_collector.id)] else: - household_data["primary_collector"] = get_individual_snapshot( - household.primary_collector, is_hh_collector=True - ) + household_data["primary_collector"] = get_individual_snapshot(household.primary_collector) household_data["needs_adjudication_tickets_count"] += household_data["primary_collector"][ "needs_adjudication_tickets_count" ] @@ -103,7 +107,7 @@ def create_payment_snapshot_data(payment: Payment) -> PaymentHouseholdSnapshot: household_data["alternate_collector"] = individuals_dict[str(household.alternate_collector.id)] else: household_data["alternate_collector"] = get_individual_snapshot( - household.alternate_collector, is_hh_collector=True + household.alternate_collector, ) household_data["needs_adjudication_tickets_count"] += household_data["alternate_collector"][ "needs_adjudication_tickets_count" @@ -113,7 +117,7 @@ def create_payment_snapshot_data(payment: Payment) -> PaymentHouseholdSnapshot: return PaymentHouseholdSnapshot(payment=payment, snapshot_data=household_data, household_id=household.id) -def get_individual_snapshot(individual: Individual, is_hh_collector: bool = False) -> dict: +def get_individual_snapshot(individual: Individual) -> dict: all_individual_data_dict = individual.__dict__ keys = [key for key in all_individual_data_dict.keys() if key not in excluded_individual_fields] individual_data = {} @@ -124,7 +128,6 @@ def get_individual_snapshot(individual: Individual, is_hh_collector: bool = Fals individual_data["needs_adjudication_tickets_count"] = get_needs_adjudication_tickets_count(individual) individual_data["bank_account_info"] = {} - print("SnapShot === data docs", individual.documents.all()) for document in individual.documents.all(): document_data = { "type": document.type.key, @@ -150,6 +153,10 @@ def get_individual_snapshot(individual: Individual, is_hh_collector: bool = Fals "account_holder_name": bank_account_info.account_holder_name, } + is_hh_collector = IndividualRoleInHousehold.objects.filter( + role__in=[ROLE_PRIMARY, ROLE_ALTERNATE], household=individual.household, individual=individual + ).exists() + if is_hh_collector: individual_data["delivery_mechanisms_data"] = { dmd.delivery_mechanism.code: dmd.delivery_data for dmd in individual.delivery_mechanisms_data.all() diff --git a/src/hct_mis_api/one_time_scripts/create_payment_snapshot.py b/src/hct_mis_api/one_time_scripts/create_payment_snapshot.py new file mode 100644 index 0000000000..130aa98f35 --- /dev/null +++ b/src/hct_mis_api/one_time_scripts/create_payment_snapshot.py @@ -0,0 +1,37 @@ +import logging + +from django.utils import timezone + +from hct_mis_api.apps.core.models import BusinessArea +from hct_mis_api.apps.payment.models import Payment, PaymentPlan +from hct_mis_api.apps.payment.services.payment_household_snapshot_service import ( + bulk_create_payment_snapshot_data, +) +from hct_mis_api.apps.program.models import Program, ProgramCycle + +logger = logging.getLogger(__name__) + + +def create_payment_snapshot() -> None: + start_time = timezone.now() + print("*** Starting Payment Snapshot creations ***\n", "*" * 60) + print(f"Found Payments without snapshot: {Payment.objects.filter(household_snapshot__isnull=True).count()}") + + for ba in BusinessArea.objects.all().only("id", "name"): + program_qs = Program.objects.filter(business_area_id=ba.id).only("id", "name") + if program_qs: + print(f"Processing {program_qs.count()} programs for {ba.name}.") + for program in program_qs: + for payment_plan in PaymentPlan.all_objects.filter(program=program): + payments_ids = list( + payment_plan.eligible_payments.filter(household_snapshot__isnull=True) + .values_list("id", flat=True) + .order_by("id") + ) + bulk_create_payment_snapshot_data(payments_ids) + + print(f"Total Cycles: {ProgramCycle.objects.all().count()}") + print(f"Completed in {timezone.now() - start_time}\n", "*" * 60) + print( + f"== After creations Payments without snapshot: {Payment.objects.filter(household_snapshot__isnull=True).count()} ==" + ) diff --git a/tests/unit/apps/payment/test_payment_gateway_service.py b/tests/unit/apps/payment/test_payment_gateway_service.py index e22a13aad3..971b12d271 100644 --- a/tests/unit/apps/payment/test_payment_gateway_service.py +++ b/tests/unit/apps/payment/test_payment_gateway_service.py @@ -33,6 +33,7 @@ DeliveryMechanism, FinancialServiceProvider, Payment, + PaymentHouseholdSnapshot, PaymentPlan, PaymentPlanSplit, ) @@ -46,6 +47,9 @@ PaymentInstructionStatus, PaymentRecordData, ) +from hct_mis_api.apps.payment.services.payment_household_snapshot_service import ( + create_payment_plan_snapshot_data, +) @pytest.fixture(autouse=True) @@ -93,9 +97,10 @@ def setUpTestData(cls) -> None: "service_provider_code_i_f": "123456789", }, ) - hoh = IndividualFactory(household=None) - hh = HouseholdFactory(head_of_household=hoh) - IndividualRoleInHouseholdFactory(household=hh, individual=hoh, role=ROLE_PRIMARY) + hh = HouseholdFactory(head_of_household=collector) + collector.household = hh + collector.save() + IndividualRoleInHouseholdFactory(household=hh, individual=collector, role=ROLE_PRIMARY) IndividualFactory.create_batch(2, household=hh) cls.payments.append( PaymentFactory( @@ -110,6 +115,8 @@ def setUpTestData(cls) -> None: ) ) + create_payment_plan_snapshot_data(cls.pp) + @mock.patch("hct_mis_api.apps.payment.models.PaymentPlan.get_exchange_rate", return_value=2.0) @mock.patch( "hct_mis_api.apps.payment.services.payment_gateway.PaymentGatewayAPI.get_records_for_payment_instruction" @@ -625,8 +632,10 @@ def test_api_add_records_to_payment_instruction_wallet_integration(self, post_mo "errors": None, }, 200 + primary_collector = self.payments[0].collector + DeliveryMechanismDataFactory( - individual=self.payments[0].collector, + individual=primary_collector, delivery_mechanism=self.dm_mobile_money, data={ "service_provider_code__mobile_money": "ABC", @@ -636,6 +645,16 @@ def test_api_add_records_to_payment_instruction_wallet_integration(self, post_mo ) self.payments[0].delivery_type = self.dm_mobile_money self.payments[0].save() + + # remove old and create new snapshot + PaymentHouseholdSnapshot.objects.all().delete() + self.assertEqual(PaymentHouseholdSnapshot.objects.count(), 0) + self.assertEqual(Payment.objects.count(), 2) + + create_payment_plan_snapshot_data(self.payments[0].parent) + self.assertEqual(PaymentHouseholdSnapshot.objects.count(), 2) + self.payments[0].refresh_from_db() + PaymentGatewayAPI().add_records_to_payment_instruction([self.payments[0]], "123") post_mock.assert_called_once_with( "payment_instructions/123/add_records/", @@ -645,10 +664,10 @@ def test_api_add_records_to_payment_instruction_wallet_integration(self, post_mo "record_code": self.payments[0].unicef_id, "payload": { "amount": str(self.payments[0].entitlement_quantity), - "phone_no": str(self.payments[0].collector.phone_no), - "last_name": self.payments[0].collector.family_name, - "first_name": self.payments[0].collector.given_name, - "full_name": self.payments[0].collector.full_name, + "phone_no": str(primary_collector.phone_no), + "last_name": primary_collector.family_name, + "first_name": primary_collector.given_name, + "full_name": primary_collector.full_name, "destination_currency": self.payments[0].currency, "service_provider_code__mobile_money": "ABC", "delivery_phone_number__mobile_money": "123456789", From bdef3dfe3549b92face1523c9ac39717e285d8c6 Mon Sep 17 00:00:00 2001 From: Pavlo Mokiichuk Date: Tue, 8 Oct 2024 10:43:55 +0200 Subject: [PATCH 11/12] fix tests --- tests/unit/apps/payment/test_build_snapshot.py | 8 +++----- tests/unit/apps/payment/test_models.py | 11 +++++++++++ tests/unit/apps/payment/test_payment_plan_services.py | 4 ++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/tests/unit/apps/payment/test_build_snapshot.py b/tests/unit/apps/payment/test_build_snapshot.py index f61dde2336..70fb24e049 100644 --- a/tests/unit/apps/payment/test_build_snapshot.py +++ b/tests/unit/apps/payment/test_build_snapshot.py @@ -38,8 +38,6 @@ def setUpTestData(cls) -> None: program_cycle=program_cycle, dispersion_start_date=datetime(2020, 8, 10), dispersion_end_date=datetime(2020, 12, 10), - # start_date=timezone.datetime(2020, 9, 10, tzinfo=utc), - # end_date=timezone.datetime(2020, 11, 10, tzinfo=utc), is_follow_up=False, ) cls.pp.unicef_id = "PP-01" @@ -55,6 +53,8 @@ def setUpTestData(cls) -> None: role=ROLE_PRIMARY, rdi_merge_status=MergeStatusModel.MERGED, ) + cls.hoh1.household = cls.hh1 + cls.hoh1.save() DeliveryMechanismDataFactory( individual=cls.hoh1, delivery_mechanism=cls.dm_atm_card, @@ -99,7 +99,7 @@ def test_build_snapshot(self) -> None: self.assertEqual(len(self.p2.household_snapshot.snapshot_data["individuals"]), self.hh2.individuals.count()) self.assertIsNotNone(self.p1.household_snapshot.snapshot_data["primary_collector"]) self.assertEqual( - self.p1.household_snapshot.snapshot_data["primary_collector"]["delivery_mechanisms_data"], + self.p1.household_snapshot.snapshot_data["primary_collector"].get("delivery_mechanisms_data", {}), { "atm_card": { "card_expiry_date__atm_card": "2022-01-01", @@ -118,8 +118,6 @@ def test_batching(self) -> None: program_cycle=program_cycle, dispersion_start_date=datetime(2020, 8, 10), dispersion_end_date=datetime(2020, 12, 10), - # start_date=timezone.datetime(2020, 9, 10, tzinfo=utc), - # end_date=timezone.datetime(2020, 11, 10, tzinfo=utc), is_follow_up=False, ) pp.unicef_id = "PP-02" diff --git a/tests/unit/apps/payment/test_models.py b/tests/unit/apps/payment/test_models.py index 6d0672f7b7..455da3559d 100644 --- a/tests/unit/apps/payment/test_models.py +++ b/tests/unit/apps/payment/test_models.py @@ -572,7 +572,12 @@ def test_fsp_template_get_column_from_core_field(self) -> None: document_number="id_doc_number_123", ) + # get None if no snapshot + none_resp = fsp_xlsx_template.get_column_from_core_field(payment, "given_name") + self.assertIsNone(none_resp) + create_payment_plan_snapshot_data(payment.parent) + payment.refresh_from_db() # check invalid filed name result = fsp_xlsx_template.get_column_from_core_field(payment, "invalid_people_field_name") @@ -616,6 +621,12 @@ def test_fsp_template_get_column_from_core_field(self) -> None: wallet_address = fsp_xlsx_template.get_column_from_core_field(payment, "wallet_address") self.assertEqual(wallet_address, primary.wallet_address) + role = fsp_xlsx_template.get_column_from_core_field(payment, "role") + self.assertEqual(role, "PRIMARY") + + primary_collector_id = fsp_xlsx_template.get_column_from_core_field(payment, "primary_collector_id") + self.assertEqual(primary_collector_id, str(primary.pk)) + class TestDynamicChoiceArrayField(TestCase): def setUp(self) -> None: diff --git a/tests/unit/apps/payment/test_payment_plan_services.py b/tests/unit/apps/payment/test_payment_plan_services.py index 5e5a0aa32a..88c8fe2e10 100644 --- a/tests/unit/apps/payment/test_payment_plan_services.py +++ b/tests/unit/apps/payment/test_payment_plan_services.py @@ -195,7 +195,7 @@ def test_create(self, get_exchange_rate_mock: Any) -> None: self.assertEqual(pp.total_households_count, 0) self.assertEqual(pp.total_individuals_count, 0) self.assertEqual(pp.payment_items.count(), 0) - with self.assertNumQueries(66): + with self.assertNumQueries(68): prepare_payment_plan_task.delay(pp.id) pp.refresh_from_db() self.assertEqual(pp.status, PaymentPlan.Status.OPEN) @@ -398,7 +398,7 @@ def test_create_follow_up_pp(self, get_exchange_rate_mock: Any) -> None: self.assertEqual(pp.follow_ups.count(), 2) - with self.assertNumQueries(47): + with self.assertNumQueries(48): prepare_follow_up_payment_plan_task(follow_up_pp_2.id) self.assertEqual(follow_up_pp_2.payment_items.count(), 1) From 45556d9e42a77e8444d6cf3e679aed93cca4f31d Mon Sep 17 00:00:00 2001 From: Pavlo Mokiichuk Date: Tue, 8 Oct 2024 13:52:56 +0200 Subject: [PATCH 12/12] add more tests --- ...import_export_payment_plan_payment_list.py | 19 +++-- tests/unit/apps/payment/test_models.py | 25 ++++++ .../test_create_payment_snapshot.py | 79 +++++++++++++++++++ 3 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 tests/unit/one_time_scripts/test_create_payment_snapshot.py diff --git a/tests/unit/apps/payment/test_import_export_payment_plan_payment_list.py b/tests/unit/apps/payment/test_import_export_payment_plan_payment_list.py index bf01f1e919..a4d739ae8d 100644 --- a/tests/unit/apps/payment/test_import_export_payment_plan_payment_list.py +++ b/tests/unit/apps/payment/test_import_export_payment_plan_payment_list.py @@ -585,9 +585,16 @@ def test_flex_fields_admin_visibility(self) -> None: (name for name, _ in response.context["adminform"].form.fields["flex_fields"].choices), ) - def test_payment_row_get_snapshot_data(self) -> None: - # TODO: will add after get some real data - # create_payment_plan_snapshot_data(self.payment_plan) - # or PaymentHouseholdSnapshot(payment=payment, snapshot_data=household_data, household_id=household.id) - # XlsxPaymentPlanExportPerFspService.get_payment_row() - pass + def test_payment_row_get_flex_field_if_no_snapshot_data(self) -> None: + flex_field = FlexibleAttribute( + type=FlexibleAttribute.DECIMAL, + name="flex_decimal_i_f", + associated_with=FlexibleAttribute.ASSOCIATED_WITH_INDIVIDUAL, + ) + flex_field.save() + fsp_xlsx_template = FinancialServiceProviderXlsxTemplateFactory(flex_fields=[flex_field.name]) + export_service = XlsxPaymentPlanExportPerFspService(self.payment_plan) + payment = PaymentFactory(parent=self.payment_plan) + empty_payment_row = export_service.get_payment_row(payment, fsp_xlsx_template) + for value in empty_payment_row: + self.assertEqual(value, "") diff --git a/tests/unit/apps/payment/test_models.py b/tests/unit/apps/payment/test_models.py index 455da3559d..d96f9e5466 100644 --- a/tests/unit/apps/payment/test_models.py +++ b/tests/unit/apps/payment/test_models.py @@ -28,15 +28,18 @@ from hct_mis_api.apps.payment.fixtures import ( ApprovalFactory, ApprovalProcessFactory, + DeliveryMechanismDataFactory, DeliveryMechanismPerPaymentPlanFactory, FinancialServiceProviderFactory, PaymentFactory, PaymentPlanFactory, PaymentPlanSplitFactory, RealProgramFactory, + generate_delivery_mechanisms, ) from hct_mis_api.apps.payment.models import ( Approval, + DeliveryMechanism, FinancialServiceProvider, FinancialServiceProviderXlsxTemplate, Payment, @@ -551,6 +554,7 @@ def test_fsp_template_get_column_from_core_field(self) -> None: household.admin1 = area1 household.admin2 = area2 household.admin3 = area3 + household.country_origin = country household.save() payment = PaymentFactory(program=ProgramFactory(), household=household, collector=individuals[0]) @@ -559,6 +563,8 @@ def test_fsp_template_get_column_from_core_field(self) -> None: payment.parent.program.data_collecting_type = data_collecting_type payment.parent.program.save() primary = IndividualRoleInHousehold.objects.filter(role=ROLE_PRIMARY).first().individual + # update primary collector + primary.household = household primary.phone_no = "+48577123654" primary.phone_no_alternative = "+48111222333" primary.wallet_name = "wallet_name_Ind_111" @@ -571,6 +577,17 @@ def test_fsp_template_get_column_from_core_field(self) -> None: type__key="national_id", document_number="id_doc_number_123", ) + generate_delivery_mechanisms() + dm_atm_card = DeliveryMechanism.objects.get(code="atm_card") + dmd = DeliveryMechanismDataFactory( + individual=primary, + delivery_mechanism=dm_atm_card, + data={ + "card_number__atm_card": "333111222", + "card_expiry_date__atm_card": "2025-11-11", + "name_of_cardholder__atm_card": "Just Random Test Name", + }, + ) # get None if no snapshot none_resp = fsp_xlsx_template.get_column_from_core_field(payment, "given_name") @@ -627,6 +644,14 @@ def test_fsp_template_get_column_from_core_field(self) -> None: primary_collector_id = fsp_xlsx_template.get_column_from_core_field(payment, "primary_collector_id") self.assertEqual(primary_collector_id, str(primary.pk)) + # get delivery_mechanisms_data field + dmd_resp = fsp_xlsx_template.get_column_from_core_field(payment, "name_of_cardholder__atm_card", dmd) + self.assertEqual(dmd_resp, "Just Random Test Name") + + # country_origin + country_origin = fsp_xlsx_template.get_column_from_core_field(payment, "country_origin") + self.assertEqual(household.country_origin.iso_code3, country_origin) + class TestDynamicChoiceArrayField(TestCase): def setUp(self) -> None: diff --git a/tests/unit/one_time_scripts/test_create_payment_snapshot.py b/tests/unit/one_time_scripts/test_create_payment_snapshot.py new file mode 100644 index 0000000000..9a4c0d92e7 --- /dev/null +++ b/tests/unit/one_time_scripts/test_create_payment_snapshot.py @@ -0,0 +1,79 @@ +from datetime import datetime + +from django.test import TestCase + +from freezegun import freeze_time + +from hct_mis_api.apps.core.fixtures import create_afghanistan +from hct_mis_api.apps.household.fixtures import HouseholdFactory, IndividualFactory +from hct_mis_api.apps.household.models import ROLE_PRIMARY, IndividualRoleInHousehold +from hct_mis_api.apps.payment.fixtures import ( + DeliveryMechanismDataFactory, + PaymentFactory, + PaymentPlanFactory, + RealProgramFactory, + generate_delivery_mechanisms, +) +from hct_mis_api.apps.payment.models import DeliveryMechanism, PaymentHouseholdSnapshot +from hct_mis_api.apps.utils.models import MergeStatusModel +from hct_mis_api.one_time_scripts.create_payment_snapshot import create_payment_snapshot + + +class TestMigratePaymentSnapShot(TestCase): + @classmethod + def setUpTestData(cls) -> None: + super().setUpTestData() + create_afghanistan() + generate_delivery_mechanisms() + cls.dm_atm_card = DeliveryMechanism.objects.get(code="atm_card") + + with freeze_time("2020-10-10"): + program = RealProgramFactory() + program_cycle = program.cycles.first() + cls.pp = PaymentPlanFactory( + program=program, + program_cycle=program_cycle, + dispersion_start_date=datetime(2020, 8, 10), + dispersion_end_date=datetime(2020, 12, 10), + is_follow_up=False, + ) + cls.hoh1 = IndividualFactory(household=None) + cls.hoh2 = IndividualFactory(household=None) + cls.hh1 = HouseholdFactory(head_of_household=cls.hoh1) + cls.hh2 = HouseholdFactory(head_of_household=cls.hoh2) + cls.primary_role = IndividualRoleInHousehold.objects.create( + household=cls.hh1, + individual=cls.hoh1, + role=ROLE_PRIMARY, + rdi_merge_status=MergeStatusModel.MERGED, + ) + cls.hoh1.household = cls.hh1 + cls.hoh1.save() + DeliveryMechanismDataFactory( + individual=cls.hoh1, + delivery_mechanism=cls.dm_atm_card, + data={ + "card_number__atm_card": "123", + "card_expiry_date__atm_card": "2022-01-01", + "name_of_cardholder__atm_card": "Marek", + }, + ) + cls.p1 = PaymentFactory( + parent=cls.pp, + conflicted=False, + household=cls.hh1, + head_of_household=cls.hoh1, + ) + cls.p2 = PaymentFactory( + parent=cls.pp, + conflicted=False, + household=cls.hh2, + head_of_household=cls.hoh2, + ) + + def test_create_payment_snapshot(self) -> None: + self.assertEqual(PaymentHouseholdSnapshot.objects.all().count(), 0) + + create_payment_snapshot() + + self.assertEqual(PaymentHouseholdSnapshot.objects.all().count(), 2)